整体过程如下:
需要说明两点:
1.在XP中,新进程主线程的启动,会先执行一个用户态的APC,会执行ntdll!LdrInitializeThunk进行程序执行前的一些列初始化操作。其中很重要任务就是加载从Kernel32.dll开始的系统DLL。注意的是,这个APC的插入,根据WRK中的代码看来是在PspUserThreadStartup中进行的:
但实际调试XP SP3发现,该函数并未有这个动作。并且在进入这个函数的时候APC已经插入好了。于是追踪到在XP SP3中,该APC的插入时机是在nt!PspCreateThread中进行的。
2.虽然XP和Win7都会在主线程Ring3入口(XP是BaseProcessStart,Win7是RtlUserThreadStart)前执行LdrInitializeThunk进行初始化,但二者细节处还是不同。
对于XP,如上所述,是通过nt!PspCreateThread在创建线程完成后插入APC,等到新进程主线程得到调度,在nt!KiThreadStartup完成后,跳到nt!_KiSystemExit处返回到用户模式时,该APC得到交付从而执行到。
而对于Win7,创建进程路径发生了大变化,不再有nt!PspCreateThread,而是nt! PspAllocateThread,这里面也没有插入APC的动作。即使追踪到nt!KiThreadStartup即将跳到nt!_KiSystemExit的位置,KTHREAD中的APC队列仍然没有LdrInitializeThunk这一项。按理说,这个时候返回就会到线程启动入口ntdll!RtlUserThreadStart才对。那LdrInitializeThunk怎么会执行呢?调试发现,在即将跳到nt!_KiSystemExit的时候,线程的KTRAP_FRAME中的EIP已经是LdrInitializeThunk而不是RtlUserThreadStart了。进一步跟踪发现,原来是在nt!PspCreateThread返回的前夕,通过nt!PspInitializeThunkContext—> nt!PspSetContextThreadInternal-->nt!PspGetSetContextSpecialApc-->nt!KeContextToKframes进行对KTRAP_FRAME进行修改了。通过函数名字也可以看出,这个动作是在模拟交付用户模式APC的动作。
因此,总结一下就是,对于XP,是通过正常的插入APC的形式,在线程内核启动函数完成后返回到用户模式时,触发了APC的交付。而对于Win7,则是在内核启动函数完成时,模拟出一个APC交付的过程,提前修改了KTRAP_FRAME,等到返回到用户模式时直接就从LdrInitializeThunk处开始执行。
LdrInitializeThunk执行完成后需要进入内核(通过NtContinue),重新进入到线程的用户态执行入口。由于在XP中是通过APC进入的,所以这个工作就不用LdrInitializeThunk来做,APC的执行器ntdll!KiUserApcDispatcher会负责这件事。但在Win7上并非通过APC执行的,所以调用NtContinue的工作需要LdrInitializeThunk自己来完成了。
Win7:
最后说一下,经常见到在LoadImageNotify回调中做(用户态)APC注入。由图可见exe&ntdll的回调通知和后面加载的dll回调通知执行的路径不一样。最开始这两个模块是在DbgkCreateThread中执行回调的,后面的是通过MmMapViewOfSection-->MiMapViewOfImageSection执行的回调。而MmMapViewOfSection在回调执行之前会通过LOCK_ADDRESS_SPACE进行进程地址空间的锁定。这个时候再进行(用户态)APC注入,在使用ZwAllocateVirtualMemory分配shellcode的内存空间时会卡死在这里。所以(用户态)APC注入的时机是在ntdll的回调中,这之后就会面临卡死的问题。