Windows创建进程的用户态和内核态交互

Q: 在windows下,调用CreateProcess这个API来创建进程,它内部究竟做了什么?

A: 对于操作系统,一般肯定是分层的。内核将处理最终的创建进程操作,但是它的上层可能有一些模块,进行一些参数合法性判断或者为了可移植考虑的判断。windows同样不例外。看看windows下面内核上面的模块:


不妨先写一个CreateProcess的程序,通过逆向工程得到内部调用的东西。


Q: 如下代码:

[cpp]  view plain copy
  1. #include <windows.h>  
  2. #include <stdio.h>  
  3.   
  4. int main()  
  5. {  
  6.     PROCESS_INFORMATION processInfo;  
  7.     STARTUPINFOA    startupInfo;  
  8.     ZeroMemory(&processInfo, sizeof(processInfo));  
  9.     ZeroMemory(&startupInfo, sizeof(startupInfo));  
  10.     startupInfo.cb = sizeof(startupInfo);  
  11.   
  12.     BOOL ret = CreateProcessA(NULL, "c:\\windows\\system32\\cmd.exe", NULL, NULL, false,   
  13.                 0, NULL, NULL, &startupInfo, &processInfo);  
  14.     if(ret)  
  15.     printf("create process ok...\n");  
  16.     else  
  17.     {  
  18.     printf("create process failed...\n");  
  19.     printf("error is %d", GetLastError());  
  20.     }  
  21.   
  22.     return 0;  
  23. }  

编译成CreateProcessDemo.exe, 运行:

可以看出,它正确地创建了进程。


A:  下面我们将找出哪个模块包含CreateProcessA函数。进入VS的命令行工具,

使用如下命令dumpbin.exe  /all  CreateProcessDemo.exe  >  d:\dumpbin_createprocessdemo.txt得到所有dump的信息,找到如下信息:

[plain]  view plain copy
  1. KERNEL32.dll  
  2.                41819C Import Address Table  
  3.                41803C Import Name Table  
  4.                     0 time date stamp  
  5.                     0 Index of first forwarder reference  
  6.   
  7.                   A4 CreateProcessA  
  8.                  1C0 GetCurrentProcess  
  9.                  4C0 TerminateProcess  
  10.                  162 FreeLibrary  
  11.                  4F1 VirtualQuery  
  12.                  214 GetModuleFileNameW  
  13.                  24A GetProcessHeap  
  14.                  2CB HeapAlloc  
  15.                  2CF HeapFree  
  16.                  279 GetSystemTimeAsFileTime  
  17.                  1C1 GetCurrentProcessId  
  18.                  1C5 GetCurrentThreadId  
  19.                  293 GetTickCount  
  20.                  3A7 QueryPerformanceCounter  
  21.                   CA DecodePointer  
  22.                  4A5 SetUnhandledExceptionFilter  

可以看出,CreateProcessA是在KERNEL32.DLL中被引用的。


Q: 现在我们可以在kernel32.dll中查看CreateProcessA的调用关系了?

A: 是的。使用ida,打开系统目录下面的kernel32.dll, 并查找CreateProcessA函数的位置:


可以在靠近最后的时候发现调用CreateProcessInternalA例程。


Q: 继续查找CreateProcessInternalA例程的内部实现,它最终会调用CreateProcessInternalW来实现。继续查找CreateProcessInternalW的实现,发现它最终会调用NtCreateUserProcess例程(在xp或者server 2003下,会调用NtCreateProcessEx).此例程不在kernel32.dll中,它在哪里?

A: 正如上面的图示描述,它以nt开头,它在ntdll.dll中。同样适用ida打开ntdll.dll, 找到NtCreateUserProcess的实现:



Q: ZwCreateUserProcess是什么,好像和NtCreateUserProcess是一样的?

A: 仅仅在ntdll.dll中来说,依照上面的截图,它们是一致的;可是在内核中,它们不完全一致。Nt开头的例程会进行访问权限和参数合法性判断,而Zw不会,Zw它可以由内核模式代码直接使用;同时,调用Zw开头的例程会将先前的模式改变为内核模式,而使用Nt开头的例程不能。

这里,可以看下xp或2003 server下的nt内核对应的代码:

[cpp]  view plain copy
  1. //  
  2. //NtCreateProcess函数是调用它 的。  
  3. //此函数调用PspCreateProcess函数。  
  4. //  
  5.   
  6. NTSTATUS              // typedef  ULONG  NTSTATUS;  
  7. NtCreateProcessEx(  
  8.     __out PHANDLE ProcessHandle,  
  9.     __in ACCESS_MASK DesiredAccess,  
  10.     __in_opt POBJECT_ATTRIBUTES ObjectAttributes,  
  11.     __in HANDLE ParentProcess,  
  12.     __in ULONG Flags,        //创建标志  
  13.     __in_opt HANDLE SectionHandle,  
  14.     __in_opt HANDLE DebugPort,  
  15.     __in_opt HANDLE ExceptionPort,  
  16.     __in ULONG JobMemberLevel  
  17.     )  
  18.   
  19. /*++  
  20.  
  21. Routine Description: //例程描述 
  22.  
  23.     This routine creates a process object.    //创建一个进程对象 
  24.  
  25. Arguments:   //参数 
  26.  
  27.     ProcessHandle - Returns the handle for the new process. //返回新进程的句柄指针 
  28.  
  29.     DesiredAccess - Supplies the desired access modes to the new process. //提供新进程的访问权限 
  30.  
  31.     ObjectAttributes - Supplies the object attributes of the new process. //提供新进程的对象属性 
  32.     . 
  33.     . 
  34.     . 
  35.  
  36. --*/  
  37.   
  38. {  
  39.     NTSTATUS Status;  
  40.   
  41.     //  
  42.     //  在调试模式下才有用;否则,被定义为空语句  
  43.     //  
  44.   
  45.     PAGED_CODE();     
  46.   
  47.     //如果线程之前执行的模式不是内核模式  
  48.     if (KeGetPreviousMode() != KernelMode) {   
  49.   
  50.         //  
  51.         // Probe all arguments    //检查所有的参数  
  52.         //  
  53.   
  54.         try {  
  55.             ProbeForWriteHandle (ProcessHandle); //ProbeForWriteHandle宏在ex.h文件中定义  
  56.         } except (EXCEPTION_EXECUTE_HANDLER) {  
  57.             return GetExceptionCode ();  
  58.         }  
  59.     }  
  60.   
  61.     if (ARGUMENT_PRESENT (ParentProcess)) {  //如果参数--父进程句柄存在  
  62.         Status = PspCreateProcess (ProcessHandle, //调用PspCreateProcess函数  
  63.                                    DesiredAccess,  
  64.                                    ObjectAttributes,  
  65.                                    ParentProcess,  
  66.                                    Flags,  
  67.                                    SectionHandle,  
  68.                                    DebugPort,  
  69.                                    ExceptionPort,  
  70.                                    JobMemberLevel);  
  71.     } else {    //否则,返回参数不合法的错误  
  72.         Status = STATUS_INVALID_PARAMETER;  
  73.     }  
  74.   
  75.     return Status;  
  76. }  


Q: 刚刚所说的用户模式是如何进入内核模式的?

A: 上面的ntdll.dll中执行依然是用户模式,NtCreateUserProcess通过向eax传入中断号, edx传入7FFE0300H为参数地址来进行系统调用,进入内核模式。它的内部实现为:

[cpp]  view plain copy
  1. NTSTATUS  
  2. NtCreateProcess(  //创建进程  
  3.     __out PHANDLE ProcessHandle,   //进程句柄的指针  
  4.     __in ACCESS_MASK DesiredAccess,  
  5.     __in_opt POBJECT_ATTRIBUTES ObjectAttributes,  
  6.     __in HANDLE ParentProcess,    //父进程句柄  
  7.     __in BOOLEAN InheritObjectTable,  //是否继承句柄表  
  8.     __in_opt HANDLE SectionHandle,  
  9.     __in_opt HANDLE DebugPort,         //调试端口  
  10.     __in_opt HANDLE ExceptionPort     //异常端口  
  11.     )  
  12. {  
  13.     ULONG Flags = 0;  
  14.   
  15.     if ((ULONG_PTR)SectionHandle & 1) {  
  16.         Flags |= PROCESS_CREATE_FLAGS_BREAKAWAY;  
  17.     }  
  18.   
  19.     if ((ULONG_PTR) DebugPort & 1) {  
  20.         Flags |= PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT;  
  21.     }  
  22.   
  23.     if (InheritObjectTable) {  //是否继承句柄表  
  24.         Flags |= PROCESS_CREATE_FLAGS_INHERIT_HANDLES;  
  25.     }  
  26.   
  27.     //调用NtCreateProcessEx函数  
  28.     return NtCreateProcessEx (ProcessHandle,  
  29.                               DesiredAccess,  
  30.                               ObjectAttributes OPTIONAL, //OPTIONAL只是表示可选参数的意思,并不会对编译造成影响  
  31.                               ParentProcess,  
  32.                               Flags,   //上面计算得到的Flags  
  33.                               SectionHandle,  
  34.                               DebugPort,  
  35.                               ExceptionPort,  
  36.                               0);  
  37. }  

因为未找到win7的WRK源代码信息,上面为nt内核的实现。注意,如上程序执行是在win7系统下的执行。


Q: 为了更清楚地得到内部调用细节,是否可以使用虚拟机来测试一下?

A: 可以,使用虚拟机进行内核模式调试,来验证上面的整个过程;当然,因为使用的虚拟机系统是2003 server sp1, 它的内核和win7内核是不同的,所以会有不一致的地方。


Q: 当虚拟机和调试器启动后(此具体过程略,关于wrk的配置可以在网上搜索),然后做什么?

A: 先在win7下面编写一个可以在虚拟机系统2003 server sp1下可以跑的程序.可以是任意的了。


Q: 如下代码:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. int main()  
  4. {  
  5.     printf("hello\n");  
  6.   
  7.     return 0;  
  8. }  
为了保证在低版本下可运行,编译时将使用的库设置成使用静态库。

编译成hello.exe.放到虚拟机系统桌面上。此时让hello.exe运行吗?

A: 不要着急,我们先在NtCreateProcessEx开始处加断点。如下:


如上图红色地方。接着让虚拟机继续运行,在虚拟机的桌面双击hello.exe运行起来。


Q: 双击后,调试器遇到断点停顿下来:

如上,紫色位置为windbg跑到断点时的截图。此时,是否就可以看看调用堆栈了?

A: 是的。使用在kd提示符后输入k命令得到堆栈信息:

可以看出从kernel32中的CreateProcessW,到CreateProcessInternalW, 又到ntdll中的NtCreateProcessEx,接着调入陷入内核例程KiFastSystemCallRet进入nt模块(即为内核模块)。在内核中,它调用NtCreateProcessEx函数来完成具体的工作。


Q: ntdll中的NtCreateProcessEx与nt中的NtCreateProcessEx名称一样,会有冲突吗?

A: 它们不是简单的上层和下层的模块,中间又有了一些模块,所以名称一样不会直接造成冲突;并且ntdll调用内核例程也肯定不是把函数名称传进去的; 编译器是可以正确地找到了地址,运行时也不会混淆。


Q: 在内核中,NtCreateProcessEx和ZwCreateProcessEx有什么区别?

A: 使用x  nt!ZwCreateProcessEx先看看内核中是否存在ZwCreateProcessEx:

由上可以看出确实存在。

接着反汇编:

可以看出,它的实现很直接,调用系统调用具体处理。它和之前看到的NtCreateProcessEx显然不一样,NtCreateProcessEx先进行了一些参数和模式的判断。所以说,ZwCreateProcessEx会将运行的模式转变成内核模式,因为它通过系统调用被迫陷入内核模式。不过,NtCreateProcessEx内部的处理依然是它实际的实现。


Q: 那么,调用结束时如何返回呢?

A: 如下,

在base\ntos\ke\i386\trap.asm中包含了返回时的处理:

[cpp]  view plain copy
  1. kss61:  
  2.   
  3. ;  
  4. ; Upon return, (eax)= status code. This code may also be entered from a failed  
  5. ; KiCallbackReturn call.  
  6. ;  
  7.   
  8.         mov     esp, ebp                ; deallocate stack space for arguments  
  9.   
  10. ;  
  11. ; Restore old trap frame address from the current trap frame.  
  12. ;  
  13.   
  14. kss70:  mov     ecx, PCR[PcPrcbData+PbCurrentThread] ; get current thread address  
  15.         mov     edx, [ebp].TsEdx        ; restore previous trap frame address  
  16.         mov     [ecx].ThTrapFrame, edx  ;  
  17.   
  18. ;  
  19. ;   System service's private version of KiExceptionExit  
  20. ;   (Also used by KiDebugService)  
  21. ;  
  22. ;   Check for pending APC interrupts, if found, dispatch to them  
  23. ;   (saving eax in frame first).  
  24. ;  
  25.         public  _KiServiceExit  
  26. _KiServiceExit:  
  27.   
  28.         cli                                         ; disable interrupts  
  29.         DISPATCH_USER_APC   ebp, ReturnCurrentEax  
  30.   
  31. ;  
  32. ; Exit from SystemService  
  33. ;  
  34.   
  35.         EXIT_ALL    NoRestoreSegs, NoRestoreVolatile  

主要就是恢复调用时的信息,继续执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值