Linux内核监控

本文将简单罗列Linux监控的实现方式,安全及审计软件的前提就是能在关键点上接管系统的执行流程,而关键点的接管必须依赖Linux内核的监控能力:

1 LSM HOOK

Linux内核中为发实现其安全机制,已经预留了LSM HOOK埋点,以4.4.131的内核为例,系统共提供了多达198个监控点回调,4.4.131是当前麒麟操作操作系统的内核版本,而较新的5.4.2内核已经支持多达216监控回调:

类别 钩子函数个数 功能
ptrace 2 Ptrace操作的权限检查
能力机制 3 Capabilities相关操作的权限检查
磁盘配额 2 配盘配额管理操作的权限检查
超级块 13 mount/umount/文件系统/超级块相关操作的权限检查
dentry 2 dentry相关操作的权限检查
文件路径 11 文件名/路径相关操作的权限检查
inode 27 inode相关操作的权限检查
file 13 file相关操作的权限检查
程序加载 5 运行程序时的权限检查
进程 27 进程/进程相关操作的权限检查
IPC 21 IPC消息/共享内存/信号量等操作的权限检查
proc文件系统 2 proc文件系统相关操作的权限检查
系统设置 2 日志,时间等相关操作的权限检查
内存管理 1 内存管理相关操作的权限检查
安全属性 7 对安全相关数据结构的操作的权限检查
网络 37 网络相关操作的权限检查
XFRM 11 XFRM架构想要关操作的权限检查
密钥 4 密钥管理相关操作的权限检查
审计 4 内核审计相关操作的权限检查
Android Binder 4 Android Binder架构有关操作的权限检查
总计 198

其中我们最关心的是进程、程序加载、内存及文件相关的监控回调,从数量及功能上已足够强大,但具体能否满足我们的需求,还要在实现过程中进行分析和验证。比如以进程创建为例,Linux内核的进程创建是通过sys_clone(sys_fork/sys_vfork)及sys_execve实现的,但LSM的监控点为了避免多入口的问题,放在了task_alloc这个点上:调用栈如下所示:

backtrace
#0  security_task_alloc (task=0xffff923cad2f8000, clone_flags=4001536) at security/security.c:1472
#1  0xffffffffa0a88ff4 in copy_process (clone_flags=4001536, stack_start=<optimized out>, stack_size=<optimized out>, child_tidptr=<optimized out>, pid=0x0, trace=<optimized out>, tls=<optimized out>, node=-1) at kernel/fork.c:1746
#2  0xffffffffa0a8a59f in copy_process (node=<optimized out>, tls=<optimized out>, trace=<optimized out>, pid=<optimized out>, child_tidptr=<optimized out>, stack_size=<optimized out>, stack_start=<optimized out>, clone_flags=<optimized out>) at kernel/fork.c:1574
#3  _do_fork (clone_flags=4001536, stack_start=<optimized out>, stack_size=<optimized out>, parent_tidptr=<optimized out>, child_tidptr=<optimized out>, tls=<optimized out>) at kernel/fork.c:2056
#4  0xffffffffa0a8a969 in SYSC_clone (tls=<optimized out>, child_tidptr=<optimized out>, parent_tidptr=<optimized out>, newsp=<optimized out>, clone_flags=<optimized out>) at kernel/fork.c:2166
#5  SyS_clone (clone_flags=<optimized out>, newsp=<optimized out>, parent_tidptr=<optimized out>, child_tidptr=<optimized out>, tls=<optimized out>) at kernel/fork.c:2160
#6  0xffffffffa0a03ae3 in do_syscall_64 (regs=0xffff923cad2f8000) at arch/x86/entry/common.c:287
#7  0xffffffffa1400081 in entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:237
#8  0x00007ffc00600600 in ?? ()
#9  0x000056066c0ceec0 in ?? ()
#10 0x0000000000000000 in ?? ()

在进程创建这个点上我们需要取到哪些信息:主进程、子进程、调用栈及相应的符号信息等等,但security_task_alloc这个点不能满足我们的需求,但再利用程序加载(BPRM)的相关回调我们即可将所有的信息串联起来,有了这个信息我们就能够做进详细的检测与判定,以上就是我们解决问题的主要思路。

2 Syscall_Table HOOK

这种方式就类似于 SSDT HOOK,但Linux内核并没有类似于Windows内核的PatchGuard (KPP)的防护机制,目前天擎国产化就是使用的此技术,但在PKS系统上,可信计算以及澜起安全内存对syscall table以及内核镜像都是有安全度量以及内存防改保护。另外为了解决Meltdown,Linux内核引入的KPTI以及ARM64系统上引入的Syscall Table Wrapper (4.19之后)都给Syscall Table HOOK带来挑战

需要注意的事,Linux内核中会存存多个syscall table,比如X64为了支持32位的x86程序会保留32位移程序的syscall table,另外Linux还有一项特殊的syscall tabke专为X64_32程序使用,X64_32类别程序内部直接使用的是64位的syscall id

3 代码Inline HOOK

代码Inline hook做为针对Syscall Table hook的延伸,但同样亦面临可信度量及内存保护的挑战

4 ftrace & kprobe机制

Linux内核为了提升在线解决问题的效率,实现了动态插桩机制,可以在内核”任意函数“点进行插桩,用以输出参数及函数变量,甚至改变函数执行流程。当然这种技术主要是用于排错应急,如果用于生产环境还需要更多的技术验证。这种技术的明显限制是针对同一个函数只能进行一个插桩,并且插桩行为是能够被可信计算发现的,亦可被阻止。

5 中断向量HOOK

X86体系下应用层通过syscall或sysenter切换至内核模式,ARM体系下通过SVC或SWI可以切换至内核,切换模式的指令最终是通过自陷阱(Trap)触使CPU进行模式切换,并自动执行事先指定位置处的代码。我们可以通过接管中断向量的方式实现Syscall的HOOK,但是此方式会有以下难点:

  1. 入口片要处理相当多的初始化工作,只能用汇编级指令实现
  2. 针对开启KPTI的内核,要保证入口代码在Shadow内核空间

6 GhostHOOK

依赖于硬件PMU的HOOK实现方式,Intel及AMD在各自的CPU上均实现了类似的机制,ARM架构上的CP15协处理器亦实现了类似的功能,但相对Intel的PMU在功能上要弱一些,但均可以达成类似的HOOK效果。

当然通过高精度时间中断亦可达成同样效果

7 内核hot patch

ARM64平台可利用 aarch64_insn_patch_text 进行函数层的hook。

Windows系统通用回调

Windows系统通用回调 本文将针对Windows操作系统所提供系统回调操作做逐一分析,以了解每个回调所能达成什么样的功能及效果。比如部分回调只是用于通知,我们只能通过此回调来了解某一类型事件的发生,只能做审计,拦截动作需要通过其它途径实现;也有回调可以实现即时拦截,通过回调即可阻止恶意程序的攻击企图。 Windows系统在内核及应用层均有提供回调机制,下面分为两部分作介绍:

1 内核回调

内核回调机制 OS版本 功能描述 备注
PsSetCreateProcessNotifyRoutine WIN2K 只是进程创建及退出通知
PsSetCreateProcessNotifyRoutineEx VISTA SP1/SRV2008 可通过返回值阻止进程创建
PsSetCreateProcessNotifyRoutineEx2 WIN10 1703 可接收到WSL/PICO进程创建及退出通知;可阻止进程创建
PsSetCreateThreadNotifyRoutine (PsRemoveCreateThreadNotifyRoutine) WIN2K 线程创建及退出通知 创建时的context为创建者,可用以判断是不是远程线程
PsSetCreateThreadNotifyRoutineEx (PsRemoveCreateThreadNotifyRoutine) WIN10 线程创建及退出通知 创建时的context为新线程环境
PsSetLoadImageNotifyRoutine (PsRemoveLoadImageNotifyRoutine) WIN2K 模块加载通知:支持驱动及应用层DLL加载通知 无模块卸载通知
PsSetLoadImageNotifyRoutineEx (PsRemoveLoadImageNotifyRoutine) WIN10 1709 模块加载通知:支持驱动及应用层DLL加载通知 无模块卸载通知
IoRegisterBootDriverCallback (IoUnRegisterBootDriverCallback) WIN8 Boot-Start驱动加载通知,可拦截,但拦截的后果即BSOD 加载顺序问题;ELAM驱动最早加载,需要特殊签名
SeRegisterImageVerificationCallback (SeUnregisterImageVerificationCallback) WIN8.1 ? 驱动加载回调,可以验证驱动签名信息 函数未公开,目前Windows Defender会使用
ObRegisterCallbacks (ObUnRegisterCallbacks) VISTA SP1 SRV2008 针对进程及线程对象句柄的创建提供前置及后置回调,可修改句柄权限,如针对进程对象无VM映射权限。子进程继承句柄不会通知 WIN7系统可监控文件句柄的创建,需要修改未公开的系统结构(ObjectType->SupportsObjectCallbacks),WIN8后PG会监控。WIN10系统增加了桌面对象的监控支持
CmRegisterCallback (CmUnRegisterCallback) XP 注册表操作回调,可修改、转向或拦截原访问请求 XP/SRV2003/VISTA行为有差异
CmRegisterCallbackEx (CmUnRegisterCallback) VISTA 注册表操作回调,可修改、转向或拦截原访问请求
ExRegisterCallback
-- \Callback\SetSystemTime WIN2K 系统时钟修改回调
-- \Callback\PowerState WIN2K 电源状态改变回调(AC/DC,电源策略,待机或关机) 早于Power Manager的IRP_SET_POWER请求
-- \Callback\ProcessorAdd VISTA 动态增加新CPU事件通知
KeRegisterBoundCallback (KeDeregisterBoundCallback) WIN10 用以捕获应用层边界检测异常,可用于应用层HOOK
Standard Event Objects for VM : 所有 主要用于监控内存状态
-- \KernelObjects\HighMemoryCondition
-- \KernelObjects\LowMemoryCondition
-- \KernelObjects\HighPagedPoolCondition
-- \KernelObjects\LowPagedPoolCondition
-- \KernelObjects\HighNonPagedPoolCondition
-- \KernelObjects\LowNonPagedPoolCondition
-- \KernelObjects\LowCommitCondition
-- \KernelObjects\HighCommitCondition
-- \KernelObjects\MaximumCommitCondition
KeRegisterBugCheckCallback (KeDeregisterBugCheckCallback) WIN2K 系统出错准备蓝屏时的回调通知
KeRegisterBugCheckReasonCallback (KeDeregisterBugCheckReasonCallback) XP SP1 SRV2003 蓝屏后DUMP生成通知,可写入额外1-PAGE信息
FsRtlRegisterFileSystemFilterCallbacks XP 文件系统或过滤驱动回调,一般由I/o Manager,VM及CM发起
IoRegisterFsRegistrationChange (IoUnregisterFsRegistrationChange) 所有 文件系统注册事件通知回调 WIN2K: 已注册文件系统驱动不再通知;XP: 均会通知
IoRegisterFsRegistrationChangeEx (IoUnregisterFsRegistrationChange) WIN2K SP4 文件系统注册事件通知回调,忽略RAW设备 同IoRegisterFsRegistrationChange
IoRegisterFsRegistrationChangeMountAware (IoUnregisterFsRegistrationChange) WIN7 文件系统注册事件通知回调,忽略RAW设备 会额外处理与挂载/Mount的竞争问题
TmEnableCallbacks VISTA 文件事务操作回调
IoRegisterContainerNotification (IoUnregisterContainerNotification) WIN7 会话/Session改变通知,如新用户登录或登出,功能类似于用户层的WTSRegisterSessionNotification
KeRegisterProcessorChangeCallback (KeDeregisterProcessorChangeCallback) WIN2K 动态CPU添加事件回调 有Pre和Post处理
KeRegisterNmiCallback (KeDeregisterNmiCallback) SRV2003 发生不可屏蔽中断时的回调通知
IoRegisterShutdownNotification (IoUnregisterShutdownNotification) WIN2K IRP_MJ_SHUTDOWN请求回调
IoRegisterLastChanceShutdownNotification (IoUnregisterShutdownNotification) WIN2K IRP_MJ_SHUTDOWN请求回调 调用时机晚,在文件系统处理完毕之后
PoRegisterPowerSettingCallback (PoUnregisterPowerSettingCallback) VISTA 电源管理事件响应回调
IoRegisterPlugPlayNotification (IoUnregisterPlugPlayNotification[Ex]) WIN2K PNP事件回调(设备热插拔)
IoWMISetNotificationCallback XP WMI事件回调
DbgSetDebugPrintCallback 未公开 VISTA 捕获内核打印事件(DbgPrint)
IoRegisterPriorityCallback (IoUnregisterPriorityCallback) 未公开 WIN7 SP1 ? I/O 请求优先级调整
PoRegisterCoalescingCallback (PoUnregisterCoalescingCallback) 未公开 WIN8.1 CM和文件系统相关:在系统空闲的时候实施cache的聚合刷新
PsSetLegoNotifyRoutine 未公开 XP 类似TLS,可以实现线程终结时的回调通知

2 应用层回调

应用层回调 OS 功能 备注
LdrRegisterDllNotification (LdrUnregisterDllNotification) VISTA 可接收进程内DLL模块加载及卸载事件,不可拦截
ProcessInstrumentationCallback WIN7 可在程序每次Ring 0至3切换后收到通知,在系统服务调用结束时刻 需要进程注入后主动调用NtSetInformationProcess,需要SeDebugPrivilege等
RegisterServiceCtrlHandler(Ex) XP 在应用层服务中接收系统事件:设备热插拔、用户登录、电源管理等
RegisterDeviceNotification 所有 GUI程序获取硬件改变通知
RegNotifyChangeKeyValue WIN2K 监控注册表指定键的子键及值的改变
ReadDirectoryChange 所有 监控指定目录中的文件及子目录的变化
AddSecureMemoryCacheCallback (RemoveSecureMemoryCacheCallback) VISTA SP1 可监控Secure类型的共享内存的释放
LdrSetDllManifestProber 未公开 XP 可收到DLL模块加载事件,可用于拦截
LdrInitShimEngineDynamic 未公开 WIN7 可接收Shim Engine通知,可接管DLL加载等 调用时机有要求,需要R3 HOOK; 不同Windows版本如WIN7及8.1差别较大
RtlSetThreadPoolStartFunc 未公开 WIN2K 设置线程初始化及退出函数,kernel32内部使用

3 通用回调的限制

Windows系统虽然提供了回调接口,但因为性能等各种因素的原因还会限定回调的数量:

  • CmRegisterCallback限制为最多100个,无高度号设置,后注册的回调可能收不到请求
  • PsSetLoadImageNotifyRoutine限制为最多8个,64位系统共64个
  • PsSetCreateProcessNotifyRoutine限制为最多8个
  • PsSetCreateThreadNotifyRoutine限制为最多8个 不少驱动安装为了防止注册失败的情况选择了尽早启动并完成相关回调的注册,也有一些工具如PcHunter会通过DKOM方式进行动态扩展。 另外不同的OS版本的支持限制,在上表中也有说明,如Vista之后的Windows系统才开始支持注册表事务操作,这部分在使用中也要注意,Ob Callback也有类似的问题。

4 参考链接

  1. Magnesium Object Manager Sandbox, A More Effective Sandbox Method for Windows 7
  2. MSDN: Filtering Registry Calls
  3. Filter Manager Support for Minifilter Drivers
  4. MSDN: REG_NOTIFY_CLASS enumeration
  5. CodeMachine: Kernel Callback Functions
  6. OSR: Kernel Mode Extensions Driver
  7. WDK Samples: Registry Logging
  8. https://blog.csdn.net/whatday/article/details/52623878
  9. https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/handling-notifications

Windows进程注入

0 前言

安全软件为了达成进程安全及行为审计的目标经常会采用进程注入的方式,即将自己的DLL注入至用户进程中,以对恶意的注入模块进行对抗。就进程注入方法来说有多种方式,木马及攻击方可使用的手段更多,毕竟攻击者对稳定性及善后部分不用做过多考虑。 本文将对注入方式做一个简单的汇总对比,下面分别从应用层及内核层的不同实现方案进行拆解:

1 应用层注入手段

本章节将主要介绍完全应用层的注入实现。

1.1 远程线程及APC注入、SetThreadContext

这两种方式的实现机制是不同的,但思路是一样的,都需要上传负载(Payload)至要注入的进程空间,一般的操作过程如下:

通过OpenProcess获取进程句柄(HANDLE),然后在目标进程空间申请内存(VirtualAlloc或VirtualAllocEx]),然后调用WriteProcessMemory将shellcode负载写入目标进程空间,最后调用CreateRemoteThread、NtCreateThreadEx、RtlCreateUserThread等创建用户线程,或者添加APC(QueueUserApc)至用户线程以完成shellcode的执行目的。

SetThreadContxt机制是将原线程挂起(SuspendThread),通过修改线程Context中的eip/rip指针至上传的shellcode地址。

Shellcode的设计一般只是简单的LdrLoadDll的调用,复杂的有如DoublePulsar木马所采用的,直接将Payload DLL进行展开并手工加载。

用户层注入的问题是权限受限,另外很容易被检测到,现在的杀软普遍都会特殊关照上述的这些特征函数。

1.2 Win32消息钩子

通过Win32 API SetWindowsHookEx注册系统级消息钩子,以截获同一桌面上所有线程的消息通信,从而实现了DLL模块的注入。当然消息钩子的局限也很明显:

  1. 被注入进程必须接受用户输入,使用了消息队列,如带GUI界面程序;服务进程一般无需用户输入,所以此种注入方法对服务进程无效 64位系统中,64位进程只能设置针对64位程序的消息HOOK,32位进程只针对32位程序,不可交叉混用。和SetWindowsHookEx类似的2. SetWinEventHook 虽然同样可以取到所有进程的消息,但并不能导致DLL注入。
  2. Windows Automation API:Windows Automation API提供给程序访问其它程序窗口及组件(UI elements)的能力,一般用于自动化测试。利用Windows Automation API实现注入的过程,和消息钩子的方式没有本质区别,也有着同样的限制。

    1.3 系统提供机制(注册表选项)

    1) App Init DLLs

    所有加载User32.dll的程序均会自动加载此键值下的DLL,主要针对Win7及之前的Windows版本,从Win8之后此机制不被推荐使用,在UEFI Secure Boot模式下此项被默认关闭。

所在注册表位置:

  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows
  • HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

使用此方式的木马及病毒:GinwuiCherry PickerT9000

2) App Cert DLLs

所有调用下面Win32 API的程序均会自动加载上述注册表中所列的DLL文件:

  • CreateProcess
  • CreateProcessAsUser
  • CreateProcessWithLoginW
  • CreateProcessWithTokenW
  • WinExec

所在注册表位置:

  • HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager

使用此方式的木马及病毒:HoneybeeFIN8 PUNCHBUGGY

3) Image File Execution Options

这是Windows系统提供的一个调试辅助机制,用以在特定进程启动或退出时启动指定程序(如调试器等)。用户通过更改IFEO键值达成启动不同进程的目的,从而导致原进程加载请求失败。

所在注册表位置:

  • HKLM\SOFTWARE{\Wow6432Node}\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\

4) Shim Database (SDB)攻击

Windows系统通过Shim数据库(SDB文件,位于%windir%\AppPatch\sysmain.sdb)以提升应用程序的向后兼容(backward compatibility)。SDB数据库中包含针对上千个程序的上百种配置,只要有管理员权限就可操纵此数据库,就可以修改任意程序的各种属性,如以下多种属性:

  • InjectDll
  • LoadLibraryRedirectFlag
  • ForceAdminAccess
  • RelaunchElevated
  • WrpMitigation
  • DisableNX
  • ModifyShellLinkPath
  • VirtualRegistry
  • DisableAdvancedRPCClientHardening
  • CorrectFilePaths
  • DisableSeh
  • DisableWindowsDefender
  • ShellExecuteXP

涉及注册表项:

  • HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Custom
  • HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\InstalledSDB

使用此方式的木马及病毒:BlackEnergy, GooKit, Roaming Tiger,QianSet.exe,VzQqgi.dll木马

1.4 PE程序IAT表静态修改

早期的Win32病毒常采用的就是静态感染方式,会将自身的代码写入被被感染程序,然后修改PE入口函数至病毒的代码段;同样的方式,静态修改PE文件的导入表以增加新的DLL注入亦不是难事。

针对有签名的PE程序,类似的修改会导致签名校验失败。

1.5 DLL替换

创建一个假个但导出一模一样的DLL文件用以替换原系统的,替换办法一般有两种办法:

  1. 系统原DLL文件改名,并替换系统DLL
  2. 更改DLL搜索路径(SetDLLDirectory)

这种方式要处理的问题:

  1. 系统模块签名问题无法处理:PPL进程将无法运行
  2. 不同版本的系统DLL处理堆积

2 内核层注入手段

内核R0层注入要比应用层R3的手段少了很多,实现难度会大一些,但内核实现更加隐蔽,难以被屏蔽。通过创建用户层线程或插入APC以实现注入的方式被普遍使用,原理上同R3的远程线程及APC注入的思路基本一致,均需要向目标进程空间上载参数及shellcode代码,只是R0及R3各自使用的支持函数不同。

2.1 IAT表注入

在进程创建过程中,内核驱动通过PsSetCreateProcessNotifyRoutine或PsSetCreateProcessNotifyRoutineEx得到通知,此时用户进程的创建过程是被阻塞的,在处理通知的回调中,内核驱动可以修改进程的内存镜像中的导入表,将要注入的驱动加入其中。

以wermgr.exe进程(PID: 0x6bc)的创建为例,此进程由services.exe(PID: 0x2bc)创建: Windows注入-IAT表 后续会收到模块加载的通知回调,加载模块的顺序依次为wermgr.exe自身、ntdll.dll、kernel32.dll、KernelBase.dll、msvcrt.dll等,回调时的调用栈为: Windows IAT注入 导入表修改动作是在进程镜像本身的模块加载回调中执行的,即程序本身模块加载的时机。

IAT表注入的主要问题:

  1. .NET程序的兼容性问题
  2. 托管代码与非托管DLL代码的混合
  3. 64位.NET程序没有导入表项
  4. 受保护进程的注入问题,必须要通过NTDLL HOOK来解决,因为创建Section对象时内核会验证DLL签名等,在IAT已注入的情况下签名又无法签证通过时会导致程序加载失败

2.2 Shellcode注入

创建用户线程及插入APC的注入手段均需要上载shellcode代码至目标进程,shellcode可以只是简单的LdrLoadDll调用用以加载HOOK引擎及工作模块,复杂一些的话可以在内核层将DLL手工加载至目标进程空间,正如木马DoublePulsar所实现的加载器【D23】"Generic Relective DLL Loader"或者内核层Turla Driver Loader(TDL)驱动加载器【D18】。

常用shellcode/payload构造(BlackBone的实现):

// shellcode for X64
UCHAR code[] =
{
0x48, 0x83, 0xEC, 0x28,             // sub rsp, 0x28
0x48, 0x31, 0xC9,                   // xor rcx, rcx
0x48, 0x31, 0xD2,                   // xor rdx, rdx
0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov r8, ModuleFileName   offset+12
0x49, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0, // mov r9, ModuleHandle     offset+28
0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov rax, LdrLoadDll      offset+32
0xFF, 0xD0,                         // call rax
0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdx, COMPLETE_OFFSET offset+44
0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [rdx], CALL_COMPLETE
0x48, 0x83, 0xC4, 0x28,             // add rsp, 0x28
0xC3                                // ret
};

// shellcode for X86
UCHAR code[] =
{
0x68, 0, 0, 0, 0,                   // push ModuleHandle        offset+01
0x68, 0, 0, 0, 0,                   // push ModuleFileName      offset+06
0x6A, 0,                            // push Flags
0x6A, 0,                            // push PathToFile
0xE8, 0, 0, 0, 0,                   // call LdrLoadDll          offset+15
0xBA, 0, 0, 0, 0,                   // mov edx, COMPLETE_OFFSET offset+20
0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [edx], CALL_COMPLETE
0xC2, 0x04, 0x00                    // ret 4
};

1) 创建用户线程

创建用户线程一般通过NtCreateThreadEx(Visata及以后OS)或NtCreateThread(XP),这两个函数在内核中均未导出且是未公开的,其地址获取可以通过SSDT或者ntoskrnl镜像解析完成。

NTKERNELAPI NTSTATUS NTAPI
NtCreateThread(
__out PHANDLE ThreadHandle,
__in ACCESS_MASK DesiredAccess,
__in_opt POBJECT_ATTRIBUTES ObjectAttributes,
__in HANDLE ProcessHandle,
__out PCLIENT_ID ClientId,
__in PCONTEXT ThreadContext,
__in PINITIAL_TEB InitialTeb,
__in BOOLEAN CreateSuspended
);

NTKERNELAPI NTSTATUS NTAPI
NtCreateThreadEx(
OUT PHANDLE hThread,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID lpStartAddress,
IN PVOID lpParameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID lpBytesBuffer
);

2) APC注入

在内核中构造一个APC结构并添加至用户线程的APC队列,等条件满足时系统会执行APC中设定的callback,从而达成进程DLL注入的目的。

相关函数原型如下:

NTKERNELAPI VOID NTAPI
KeInitializeApc (
__out PRKAPC    Apc,
__in PRKTHREAD  Thread,
__in KAPC_ENVIRONMENT   Environment,
__in PKKERNEL_ROUTINE   KernelRoutine,
__in_opt PKRUNDOWN_ROUTINE  RundownRoutine,
__in_opt PKNORMAL_ROUTINE   NormalRoutine,
__in_opt KPROCESSOR_MODE    ProcessorMode,
__in_opt PVOID  NormalContext
);

NTKERNELAPI BOOLEAN NTAPI
KeInsertQueueApc    (
__inout PRKAPC  Apc,
__in_opt PVOID  SystemArgument1,
__in_opt PVOID  SystemArgument2,
__in KPRIORITY  Increment
);

360安全卫士的驱动360fsflt.sys、木马DoublePulsar【D22】等所采用的方式就是Kernel APC注入,这种方式被普遍使用。但APC注入要注意以下问题:

  1. 时机问题:要求所注入进程已经创建了线程并且已经执行(主线程都是紧随进程创建的)
  2. 依赖线程状态:APC的执行依赖于所注入线程的状态:一般是从内核态切换回用户态时执行
  3. 必须与注入线程做同步,或者在所注入线程环境中插入APC,不然可能会注入至错误进程,另外与第三方驱动同时注入APC存在竞争及一致性问题,当然竞争窗口极小; 每个线程均有两个APC队列,当线程在内核态下调用KeAttachProcess时会做切换,切换过程中没有任何保护

3) HOOK NTDLL方式

内核驱动可以通过内核的回调机制截获用户进程创建及PE加载器加载并初始化DLL链的每个环节,可以监控到指定模块加载时同步进行inline hook,并将关注点的执行流程导向至已上载的shellcode负载。

回调本身是串行在程序的加载流程中的,即回调不返回,进程的创建及DLL的加载过程是被阻塞的,省却了同步与不一致性的问题的处理。

之所以要选择ntdll.dll的原因是,ntdll.dll是所有的Win32程序必须加载的,并且其执行过程比程序本身的入口执行要早;另外选择ntdll.dll而不是Kernel32.dll等模块的原因是,Kernel32.dll等模块并不是必须的,比如Native程序如csrss,exe、autochk.exe等,还有一些程序将Kernel32.dll等模块设置为Delay-Loading-DLL,并不会在程序启动之初就立即加载并执行。

为了尽早地获取控制权,常用的HOOK点一般放在ntdll!LdrInitializeThunk、ntdll!NtOpenDirectoryObject或者ntdll!LdrLoadDLL等关键函数点。Haiheiwang木马【D16】就是通过修改ntdll!NtTestAlert的流程将自己的工作模块加入被感染进程的,当然ntdll!NtTestAlert也存在时机较晚的问题,并不能满足咱们的需求。

这种方式在安全软件中也广泛使用,特别是对针对Win8系统年引入的受保护进程的注入问题,由内核注入Shellcode并通过HOOK ntdll!NtCreateSection来加载非系统模块是最实用且有效方式,目前360安全卫士的注入亦是采用此方式。受保护PPL进程只对用户层的Section对象创建有签名验证,我们通过将内核层创建Section对象映射至用户空间的方式达成向受保护PPL进程注入的目的。

其它的替代方案会导致受保护进行安全性的妥协等,比如短暂取消受保护进行的保护状态以达成注入目的,此种方式有可能会触发KPP/PG检测失效,另外也很容易被第三方恶意利用,比如MalwareFox AntiMalware的一个被曝光的漏洞【D24: MalwareFox AntiMalware 2.74.0.150 LPE】。

Shellcode内存映射可以在HOOK引擎模块加载之后进行销毁,存在窗口时间非常短,被其它安全软件查到或被其它恶意程序所利用的可能比较小。

3 参考资料

3.1 浏览器自身防护资讯

B1: About Google Chrome's incompatible applications warning

B2: Firefox will block DLL Injections

B3: Protecting Microsoft Edge against binary injection

3.2 进程DLL注入资料

D1: DLL Injection with SetThreadContext

D2: R3 DLL Injection: Inject All The Things

D3: COUNTERCEPT: Analyzing the DOUBLEPULSAR Kernel DLL Injection Technique

D4: COUNTERCEPT: Dynamic Shellcode Execution

D5: Ten Process Injection Techniques

D6: DLL Injector via Windows Automation API

D7: Microsoft: AppInit DLLs and Secure Boot

D8: BlackHat: Malicious Application Compatibility Shims

D9: To SDB, Or Not To SDB: FIN7 Leveraging Shim Databases for Persistence

D10: FreeBuf: 走近微软安全技术Shim

D11: ABICC: Windows API/ABI Changes Analysis

D12: MITRE ATT&CK: Application Shimming

D13: FireEye: The Real Shim Shady

D14: iSIGHTPARTNERS: 固守: 微软Fix It补丁机制原理及攻击利用

D15: 腾讯:木马牟利再出新招-恶意利用Windows shim技术锁主页

D16: 腾讯:Haiheiwang木马分析

D17: Windows Vista APC Internals

D18: TDL: Turla Driver Loader

D19: Scylla and API Set Map

D20: Microsoft Docs: Windows API Sets

D21: Windows_7_Kernel_Changes: api-ms-win-core DLLs

D22: DOUBLEPULSAR Kernel DLL Injection Technique

D23: DOUBLEPULSAR: Generic Reflective DLL Loader

D24: MalwareFox AntiMalware 2.74.0.150 - LPE

D25:DLL Injection via SetDLLDirectory

D26: CodeProject: Create your Proxy DLLs automatically

D27: Proxy WS2_32.DLL to create your own firewall

D28: CodeProject: API hooking revealed

3.3 DLL注入商业方案

D30: Shellter Pro: DLL Injection Kit

D31: madCodeHook: DLL Injection Kit

3.4 开源HOOK引擎

H1: Microsoft Detours

H2: Detours NT

H3: minhook

H4: mhook

H5: EasyHook

H6: Deviare Hook Engine

3.5 Instrumentation Callback

I1: 利用KPROCESS结构的InstrumentationCallback域实现Hook

I2: Hooking-via-InstrumentationCallback

I3: Alex Ionescu: Hooking Nirvana

3.6 PPL (Protected Process Light)

P1: Injecting Code into Windows Protected Processes using COM - Part 1

P2: Injecting Code into Windows Protected Processes using COM - Part 2

P3: The Evolution of Protected Processes Part 1: Pass-the-Hash Mitigations in Windows 8.1

P4: The Evolution of Protected Processes Part 2: Exploit/Jailbreak Mitigations, Unkillable Processes and Protected Services

P5: Protected Processes Part 3 : Windows PKI Internals (Signing Levels, Scenarios, Root Keys, EKUs & Runtime Signers)

P6: Protected Processes Light killer

P7: Why Protected Processes Are A Bad Idea

P8: Micorosft: Protecting Anti-Malware Services

P9: Microsoft: Early launch antimalware

P10: Microsoft Virus Initiative

P11: Early Launch Anti-Malware Driver

3.7 WSL (Windows Subsystem for Linux)

W1: The Linux kernel hidden inside Windows 10

W2: Fun with the Windows Subsystem for Linux (WSL/LXSS)

W3: Hunting for Windows Subsystem for Linux

W4: Exploring Windows Subsystem for Linux

W5: Windows 10 and the Anti-malware Ecosystem

W6: Pico Process Overview

W7: WSL System Calls

--- END ---

奔跑在理想之国 — 2018梅里100赛记

“他是两个世界之间的流浪者,注定要永远流浪。”

James Hilton在其所著的《消失的地平线》一书中用来形容主人翁Conway的这句话,同样用来形容每一个跑者也是恰如其分!很多人生活在繁杂拥闹的大城市,所向往的却是野外大自然的宁静悠然。我们在两个世界间不停地穿梭与奔跑,大自然就是我们心中的“香格里拉”。

香格里拉、虎跳、梅里、雨崩、神瀑,2010年7月我曾经来过,不知8年之后的今天,又会变成什么模样?那个与世隔绝的村子是否还保有着仙境般的梦幻?卡瓦格博是否依然还是那个羞涩的小姑娘,始终不肯摘下紧裹的云纱?

万千往事揉着纷杂思绪潮水般涌向心头,而我却无从下笔,就像拿起了一根烟放在嘴里却尴尬地发现没有了火,只能呆呆地望着窗外......

起心

1月11日UTMB抽签结果新鲜出炉,可惜没有带来好消息,几个朋友都没有中签。“不跑个168都不好意思”的小群中4个报了UTMB168的人中,只有过客一人中签,另外天路报了TDS也顺利收获了一张入场券。虽然遗憾,但不中自有不中的好处,就在结果出来后仅几个小时,我和老大便已报名梅里100。

决心一旦下定,旋即就进入了一种亢奋:去赴它个一场与雨崩的8年之约!去跑它个一场高海拔的100公里!连从未体验过高海拔的老大也同样踌躇满志。

只要有了目标,对我们而言,就自会有行动!我们是一群最不缺乏行动力的人。

动念

考虑到老大没有高海拔的经验,我们计划早于比赛几天到达以适应海拔,先去徒步虎跳峡,然后去雨崩村。后来赛事时间变更,结果我们前面可安排的时间更充裕了,多出了一天半的时间,我开始盘算这一天半的时间完全可以去爬个哈巴,毕竟来一趟不容易,即使爬不成雪山,在大本营做个高海拔适应也好。

在香格里拉等老大到来的空,我给哈巴向导武桑打了电话,问询了哈巴山上的情况及近期的天气,得知山上已连着下了三天雨,武桑相信后面会有一个好的天气窗口,另外所需的技术装备如冰镐、冰爪等均有出租。听到这些,我更是按奈不住地兴奋,要玩就不妨玩大的,上次雪山攀登还是2011年的雀儿山。从接触户外之初就对雪山有着一莫名的向往,一连串地爬了几座之后却又对当前的商业登山失望之至,而今又能够亲近雪山了,虽然哈巴只是一座初级山峰,依然莫名兴奋。

等老大到达汇合后,和他说了我的想法,我们一拍即合。眼前,除了兴奋与鸡血。没有别的!

买好了去桥头镇的车票,还有20分钟发车,老大说有点饿,我们遂到车站边上的小饭馆问最快的吃的是什么,老板娘得知我们只有20分钟时她很会意地答曰:米粉。一阵风卷残云,仅过了7分钟,与满脸笑容的老板娘道别,我们俩扬长而去。

28道拐前没有了赌你爬不动的马夫,徒步的小径很多已被宽阔的水泥路面所替代,走到了路上才知道准备的不足,我下载的轨迹虽是2017年的,但已然过时,其中一段腰绕的线路因为修隧道的缘故被破坏了。我和老大在铁丝网上爬了好一阵子,被工地上的人叫了下来,告知路在下面。经过几经探寻之后,终于对上了轨迹走上了正途,但1个小时的时间已飘然而逝。

8年后再走虎跳,已经记不得当时走过的路,反而觉得脚下的路和尼泊尔EBC很像很像。记忆就像一张网,咋一想起便牵动了所有的枝枝叶叶,但一旦仔细回味起来,却只剩下了破碎而又坚硬的梗。

天开始下雨,我们穿好冲锋衣准备好了一场雨战,但高原的天气就是喜欢捉弄你,一会雨又一会晴,湿闷得不行,再加上已好久没有重装徒步,爬起28道拐来相当绝望,远处的玉龙雪山一直藏在云层里,峡谷中蜿蜒着的混浊泥流发出轰鸣的水声却响彻山谷,像是对你的嘲笑。

在茶马客栈补了瓶水之后,我们继续出发,很快到达中途客栈(Half Way G.H.),此时已近8点,天色开始变暗。经过6个小时的行走,我们觉得相当疲惫,毕竟背着近14公斤的包,而且还是几经压缩精简后的。

客栈里已经来了不少客人,我们到达时他们正聊着天。第一眼看去,Half Way还是以前的Half Way,安静的小园子,可以眺望峡谷的天台,但房间内早已升级换代,以前的公共浴室已被各个房间内的沐浴设施取代,不知“天下第一厕”还安否?已然没了去参观它的兴致。

不群

也不知道为什么,反正莫名地就将不群岳兄和中途的老板联系在一起了,为了想明白这个问题,着实费了不少脑细胞但还是没有想明白个缘由。

途经茶马时就得知整个虎跳峡因输电线杆被撞已全部停电,到达中途客栈后问老板洗澡的事,还以为是以前的公共沐浴间,结果老板高逼格说了句:“洗澡是必备的最基本的要求”,结果晚上发电机还没发动就爆了,亏得热水器中存有不少热水,足够我们俩冲个澡;老板还说过早上7点准点有早饭,第二天我们起来后不见人影,我不得不找遍了客栈去逐个敲门,

结完账,我和老大快速撤离,50分钟后在中峡客栈Tina's吃了顿超丰盛的早餐。

行定

和武桑约好11点在山白脸汇合,我想着两个半小时的时间足够我们在中虎跳游玩了。谁知老大竟然这么爱拍照,每过一个点都要正拍10张再反拍10张,然后回过身来再来10张,搞得我好不紧张,毕竟后面的哈巴才是重点。

好在过了一线天之后,走出峡谷,风景不在,我们遂加快了脚步。

外面,武桑和车已经等候多时!到山白脸走了一圈,权算故地重游。

车子发动,沿着金沙江峡谷急驰。

哈巴,我们来了!

在武桑家稍做停留,我们即从彝族村出发去哈巴大本营。路上我叮嘱老大要慢点,慢慢上升慢慢适应海拔,可最后我们还是快过了马,用了3个小时到达海拔约4100的大本营后依然还是一脸的兴奋劲。马儿虽慢,但马儿更会休息,马儿更会保持速度,马儿能够跑得更远,但我们比它更有激情!特别是对于老大来说,高海拔绝对是个正儿八经的新鲜事。

在大本营转了几圈,在营房内烤了几个小时的火,拍照、聊天、烤火、喝水、撒尿,看着山上厚厚的云层被风卷来卷去,山体在云层的包裹中幻生幻灭,木头在火中噼啪作响,火星似流星般向上划出道道弧线,然后湮灭在房间窒息的空气中。我,静静地望着它们,百无聊奈。

是夜,我如期的头痛着入睡,做着各种努力让自己好好睡觉的梦!

是夜,老大如我所叮嘱的一样,躺着一动不动,只是他一丁点儿都没睡着。他说也不头痛也不胸闷,可就是睡不着!连续着三天只睡了两个多小时,超级铁人一个!

凌晨3点半起来吃了碗咸面片粥,觉得超级香,现在想起来还直流口水,看来有必要向武桑索要配方。3:52出发,我们俩是第一队,后面有大部队正在吃饭。

天上滴着小雨,好天气窗口并没有因为前面连续三天的雨而特别眷顾我们,相反而是继续让坏天气来考验我们向上的决心。走过了一小段灌木丛路,我们踏上了光亮的岩石,岩缝中还夹有雪渍,但哈巴的砾岩一点不滑,走起来并不费力。但随着海拔的上升,部分路段覆盖了一层薄薄的冰,这部分走起来要格外小心。

过了雪线后绑上了冰爪,虽然天色渐亮,但我们逐渐走进了浓雾中,天是白的,地上白的,分不清界限,透过雪镜中依稀能看到两侧的山脊的模糊轮廓,山脊两侧就是悬崖,而我们就在悬崖间窄窄的通道上艰难爬行,凭着感觉向上、再向上,只有感觉到确实向上才能明确路线是对的。

前面一段路老大一直很活跃,冲在前面,但到了这一段,开始有点跟不上了,毕竟连续几夜没能休息好再加上连续几天的高强度运动,着实让人吃不消。

从C1营地出发的队伍和我们汇合在一起,一个女生和我们不停地穿插行走交替领先,令我惊讶的是,她和老大一样,也是第一次攀登雪山。

浓雾中两侧山脊渐渐收窄,上山的坡度依然延伸在前面的路上,每慢走50步就要停下来喘口大气,尽我最大的贪婪吸进面前的浓雾,但呼出的热气却更妨碍了前面的视野。

在第N个50步,或者在喘过第N个粗气之后,终于看到了顶峰标志,像是一个刻着字的简陋木板,注意到右侧的山顶更高,我向右上侧挪去,走到一半的时候,向导安牛上来了,安牛叫停了我,说右侧危险,到这里就可以了,这里就是哈巴顶峰。

似乎还不过瘾,似乎是因为天不作美,顶峰上的风景也就4-5米的视野内的一片白茫茫......

时间正好8点半,从大本营出发到现在共4小时38分。后面的女生也上来了,帮她和向导拍照后,我和老大及安牛便开始下撤。

浓浓的大雾中,上来的脚印已经看不出了,天好像还下着雪,小心翼翼地循着山势向下探寻了好长一段,终于遇见了向上攀登的大队伍,一下子热闹了起来,相互间打着招呼,我向他们广播着此地到顶峰的大体所需时间,终于能够放开步子开走。

下至雪线,撤了冰爪,撒欢向下跑,一溜跑到大本营,从凌晨出发到回到大本营共6小时20分。哈巴之行现在可以说是圆满。

发心

和武桑沟通了一下,他说先休息会吃了饭就下山,然后去香格里拉。

老大吵着要好好睡一觉,我却兴奋地没有一点疲倦,在营房里和从广东来哈巴考察做科研的土豪(耗)烤火、聊天。

他们一行几人是考察高海拔上老鼠的种类及分布的,篝火边的木板上全是老鼠的标本,他们准备要将标本烤干,回去还要做肉质分析。问了他不少问题,也聊了不少以前徒步的经历,以至临走时已有几分不舍。

海拔4100的大本营,虽然条件比较艰苦,但这里的人来自全国各地,每个人背后都有着一串串精彩的故事,每个人都有着一颗开放的心。虽然相见短暂,但相聊甚欢,几言几语间已成知己,更多的话不必发出声即能颜传意会,如同《消失的地平线中》所述的康维或亨舍尔与佩劳尔特相逢相知和相互欣赏。

然而时间很快,道别时刻终于还是到来,虽然心里不舍,手上却是轻轻一挥,故作轻松 ...

老大,他 终究 还是 没能 睡着!

随性

重担已卸,警觉亦歇,下山过程全无负担,心意飞悦。我们一行4人(我、老大、武桑、安牛)加上马儿轻松自在,路上比较泥汀,我们不急于赶路,边走边聊,用了2个多小时到达武桑家。

见到了武桑的妈妈,一位慈祥硬朗的彝族老妈妈。我们的哈巴之行就定格在了她满脸的笑容里。

梅里100准备会上古神播放的路线视频让我印象深刻,以至于跑完之后我又看了几遍,但其中几个CP点,纵使我每一个都曾经走过,但怎么也想不起来它的模样了,就好像我压根没看到过似的,难道是因为高海拔缺氧所致!?

30号中午到达飞来寺,入住了老大早早预定好的梅里往事。路过白马雪山时还飘着雪,飞来寺这边则是下着小雨,看到明天将是一场艰苦卓绝的泥战了。

准备会后是丰盛的自助餐,吃得非常非常非常饱,必须对得起赛事组织方的盛情和用心。

下午时分,雨已停,但梅里雪山方向依然密布着云层,白马雪山方向风云幻变,一会亮了半边的天空,一会又升起了浓浓的白雾。

所有人都在祈祷 明天天会变好。

时钟转到了早上8点,发令枪响,人流开始旋转,山在动,地已醒,鞋子上的泥点在飞,雨点也在盘旋着下降,汗一滴滴钻出皮肤而后又钻入土地,等待着变成雨点的轮回。

过了永宗桥后开始上升, 高海拔上升路段已没有了冲下山的那股势气。路上追上了王同伟,30号刚刚认识的北京跑友,同住在梅里往事;晚上在跑神爆的路上又曾碰到他,赛后得知他是30公里的第一名。

过西当村,不时传来加油的吆喝声,我则回应“扎西德勒”,然后就是更多的“扎西德勒”的回声。经过八一茶馆,古神在门口忙碌着,我则只是加满热水即冲向下一站。

去冰湖的路太过漫长,关键回来也要走同样多的路,高海拔的漫长上坡真是让人崩溃,感觉从中午一直到傍晚。至冰川一段和天路同行,冰川上又偶遇古神,顿觉世界真小。

正好在天就要变黑之前,我走到了神瀑脚下,水量比上次来看她的时候要少很多,但现在的她更轻盈活泼,躲在似透非透的薄雾里婀娜着缓缓流下,我凝视许久,天又开始下雨,瀑布迸溅的水滴混着雨点打在我身上,一身清凉。

飞奔而下,走出峡谷的时候碰到了老大,看他状态还不错,他说在上雨崩休息了挺长时间。

从下雨崩出来,志愿者伸出手掌,我们击掌相别,黑夜中我向着白塔跑去,然后就是一路的狂奔,左侧咆哮着的雨崩河无怨无悔地投向澜沧江的怀抱,而我也顺着她无心无肺地奔跑,一路上只有雨崩河的陪伴,好不惬意。直至神秘客栈前才看到志愿者,后又追上了两名选手。

也许是海拔下降之故,晚上的气温相当高,我只得在尼龙村的取水渠里用水浇在头上及身上,顿觉一身的舒服,此时1052号的李灏赶上来,我的号码是1025。从此之后30多公里我们一路偕行,直到终点飞来寺。

从尼龙出发到拥用的机耕路真得要走死人,无语!漫长的路总有尽头,但到了尽头之后又发现黑暗中还有新的尽头在等待着你。

6月1号早上5点48我们从曲登阁出发,向终点发起冲击,天逐渐变亮,然后太阳出来了,直辣辣地照在身上,让我们毫无防备,后果当然不言而喻,左手腕被晒红,鼻子、嘴及下巴区域成了黑三角。

最后关头,冲进24小时的希望越来越大,但是不到终点,你永远不会知道还有什么样的路,我一边快步走着,一边催促着李灏。

在奔跑了近24小时之后,我们俩同时冲线!23小时28分。

情切

6月2号早上,我和老大拖着麻木的双腿走到楼下,正遇到梅里往事的老板,他问我们:你们吃过我们的早饭没?“没有”,我们回答说。接着老板很自豪地说:那你们必须要尝一尝!

于是我们坐下,老板则忙开了,陆续端上来牛奶、奶昔,烤好的面包片及黄油、草莓酱,然后煎蛋和青菜卷,好丰盛的早餐。

临走的时候,正吃饭的老板看到了我们,放下碗筷起身相送。

那一刻,真得不想走,真得想留下来。

素蚁、土豆、老大、我,我们四人将自己塞进了一辆小速腾,任由它飞驰着将我们带离这个世界:这个世界里我们曾挥洒汗水,这个世界里我们曾兴奋而又绝望,这个世界里我们狂奔着叫嚣着狂热着,这个世界里我们又曾无比安静和虔诚地凝视着、祈祷着...

不舍,情切,甚切!

锚点

凡事你曾用心,事必然也会铸造着你。做了但没有一点反响,岂不是等同于从未做过!

8年前的雨崩之行,是我开始独立走长线的锚点;

8年后的梅里之行,是我最惬意的一次高海奔跑体验。

还记得20年前的哈尔滨的一个冬夜,跑过了第一个10000米;

还记得在华政操场上的第一个60圈 ....

太多,太多,早已被抛在了脑后,因为一直有新的锚点在筑起新的高度。

结束即开始

迪庆,是一个奇迹之地,民族的融合,三江并流,山川壮丽,每个人心中的香巴拉圣境,理想之国!每一个跑者的奔跑之地!转梅里,穿三江,跑出自我!

就如在西奈山上上帝对摩西所言:"I AM WHO I AM ..." (From Exodus 3.14)

跑我所跑 !

爱跑而跑 !

无所不能跑 !

WE ARE RUNNING WHAT WE ARE RUNNING !

WE ARE RUNNING WHY (BECAUSE) WE ARE RUNNING !

WE ARE RUNNING WHERE WE ARE RUNNING !

1394 is back !

从XP时代开始就一直在用1394 Kernel Debugging,虽然Windows相继增加支持了USB2、USB3或者网络调试,但1394因其方便及更好的适用性一直是我的最爱,这也是为什么没有将我的工作笔记本升级到超级本的原因。

但从Windows 10发布以后,微软不再提供Kernel Debugging over 1394,但好在有相应的Workaround延长了1394的寿命,参见 KD 1394 Work-Around。但此办法无法解决boot debugging的问题,好在我目前都是系统级的内核调试,用不着boot debugging。

只是好景不长,随着Win10版本升级,1394又不行了。曾经为了测试新版Win10的兼容性,装了Windows 10 Pro Insider Preview 16226,结果发现Kernel Debugging over 1394彻底罢工,试过几个版本的Kd1394.dll均不行。

本以为1394就此死翘翘的时候,看到M$发布的一则消息:New WinDbg available in preview!。安装只能通过Windows Store,安装后习惯性的检查了一下文件目录,发现了新版的Kd1394.dll,随即在Win10 Pro 16226系统上做替换测试,然后 bing bing bing ! Windbg host端不断吐出字符来!

后来在Win10 Pro 15063上做了验证,发现此版Kd1394.dll并不适用,但老版的Kd1394.dll还是可以正常适配Win10 15063系统的。作为一个重度1394使用者,又可以欢呼了,但此好景能延续到什么时候呢?

Win10升级导致的休眠故障

一切都是因为手贱,看到Win10的升级引入了不少新的特性,心痒就赶了个时髦。升级过程花了不短的时间,但总体顺利,使用上也没有发现异常,也没有发现改进,比如Hyper-V照常和Vmware Workstation冲突。

下班后将电脑休眠从公司带回家,但等到再开机却发现系统重启了,日志中没有看到明显异常。之后就发现休眠时不时就会出问题,不是休眠中当机,就是休眠后无法正常开机。

用驱动大师升级了所有能升级的驱动,问但题还是没有解决,只得回退系统,但回退之后发现还是不能正常休眠。看来只能上Windbg来动动手术了:

开始调试时休眠正常:过程中曾中断系统枚举了所有的进程、IRP及ERESOURCE锁的,只发现dxgkrnl.sys驱动中有明显的锁等待,不少线程都因尝试获取一个已经Exclusively Owned ERESOURCE锁。dxgkrnl是DirectX显示驱动,与此相关的只有intel显卡,等系统恢复后卸载了intel显卡驱动,用Windows来自动安装。

重试几次后windbg端忽然显示出熟悉的输出:

*** Fatal System Error: 0x0000009f
           (0x0000000000000003,0xFFFF980487D7B060,0xFFFFD3810AFB68F0,0xFFFF980490101010)

果然有坑!分析下来发现是蓝牙驱动的问题:

2: kd> !devobj ffff980487d7b060; !irp  ffff980490101010 1
Device object (ffff980487d7b060) is for:
   USBPDO-8 \Driver\usbhub DriverObject ffff980487f6b300
   ...

2: kd> !devnode ffff98048901c010
DevNode 0xffff98048901c010 for PDO 0xffff980487d7b060
  Parent 0xffff980488186d30   Sibling 0xffff98048940f790   Child 0xffff98048929dd30   
  InstancePath is "USB\VID_0A5C&PID_21E6\7CE9D3E86CA0"
  ServiceName is "BTHUSB"
  ...

2: kd> !dh -f bthport
File Type: DLL
FILE HEADER VALUES
    8664 machine (X64)
       C number of sections
CED98754 time date stamp Wed Dec 20 22:34:12 2079
...

bthport的timestamp竟然是2079 !? 什么鬼!先不管这些,重启系统后将蓝牙禁掉后,再测试休眠,试了几次均正常。然后开启蓝牙,什么都不做再次尝试休眠,果然又中招。

问题解决起来倒也简单:将所有的蓝牙设备都删除,重新安装Thinkpad Bluetooth,之后又测试了几次休眠,再没有出问题,世界终于又大同了!

在检查系统加载驱动过程中还有个新发现,国内的公司还有各大银行简直是驱动开发专业户,每家都有几个驱动,就连迅雷,你个下载软件,竟然有两个内核驱动。不由分说,直接咔嚓了此类驱动。从此和谐社会!比新闻联播还鸡血!