Q: 在windows下,调用CreateProcess这个API来创建进程,它内部究竟做了什么?
A: 对于操作系统,一般肯定是分层的。内核将处理最终的创建进程操作,但是它的上层可能有一些模块,进行一些参数合法性判断或者为了可移植考虑的判断。windows同样不例外。看看windows下面内核上面的模块:
不妨先写一个CreateProcess的程序,通过逆向工程得到内部调用的东西。
Q: 如下代码:
- #include <windows.h>
- #include <stdio.h>
- int main()
- {
- PROCESS_INFORMATION processInfo;
- STARTUPINFOA startupInfo;
- ZeroMemory(&processInfo, sizeof(processInfo));
- ZeroMemory(&startupInfo, sizeof(startupInfo));
- startupInfo.cb = sizeof(startupInfo);
- BOOL ret = CreateProcessA(NULL, "c:\\windows\\system32\\cmd.exe", NULL, NULL, false,
- 0, NULL, NULL, &startupInfo, &processInfo);
- if(ret)
- printf("create process ok...\n");
- else
- {
- printf("create process failed...\n");
- printf("error is %d", GetLastError());
- }
- return 0;
- }
编译成CreateProcessDemo.exe, 运行:
可以看出,它正确地创建了进程。
A: 下面我们将找出哪个模块包含CreateProcessA函数。进入VS的命令行工具,
使用如下命令dumpbin.exe /all CreateProcessDemo.exe > d:\dumpbin_createprocessdemo.txt得到所有dump的信息,找到如下信息:
- KERNEL32.dll
- 41819C Import Address Table
- 41803C Import Name Table
- 0 time date stamp
- 0 Index of first forwarder reference
- A4 CreateProcessA
- 1C0 GetCurrentProcess
- 4C0 TerminateProcess
- 162 FreeLibrary
- 4F1 VirtualQuery
- 214 GetModuleFileNameW
- 24A GetProcessHeap
- 2CB HeapAlloc
- 2CF HeapFree
- 279 GetSystemTimeAsFileTime
- 1C1 GetCurrentProcessId
- 1C5 GetCurrentThreadId
- 293 GetTickCount
- 3A7 QueryPerformanceCounter
- CA DecodePointer
- 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内核对应的代码:
- //
- //NtCreateProcess函数是调用它 的。
- //此函数调用PspCreateProcess函数。
- //
- NTSTATUS // typedef ULONG NTSTATUS;
- NtCreateProcessEx(
- __out PHANDLE ProcessHandle,
- __in ACCESS_MASK DesiredAccess,
- __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
- __in HANDLE ParentProcess,
- __in ULONG Flags, //创建标志
- __in_opt HANDLE SectionHandle,
- __in_opt HANDLE DebugPort,
- __in_opt HANDLE ExceptionPort,
- __in ULONG JobMemberLevel
- )
- /*++
- Routine Description: //例程描述
- This routine creates a process object. //创建一个进程对象
- Arguments: //参数
- ProcessHandle - Returns the handle for the new process. //返回新进程的句柄指针
- DesiredAccess - Supplies the desired access modes to the new process. //提供新进程的访问权限
- ObjectAttributes - Supplies the object attributes of the new process. //提供新进程的对象属性
- .
- .
- .
- --*/
- {
- NTSTATUS Status;
- //
- // 在调试模式下才有用;否则,被定义为空语句
- //
- PAGED_CODE();
- //如果线程之前执行的模式不是内核模式
- if (KeGetPreviousMode() != KernelMode) {
- //
- // Probe all arguments //检查所有的参数
- //
- try {
- ProbeForWriteHandle (ProcessHandle); //ProbeForWriteHandle宏在ex.h文件中定义
- } except (EXCEPTION_EXECUTE_HANDLER) {
- return GetExceptionCode ();
- }
- }
- if (ARGUMENT_PRESENT (ParentProcess)) { //如果参数--父进程句柄存在
- Status = PspCreateProcess (ProcessHandle, //调用PspCreateProcess函数
- DesiredAccess,
- ObjectAttributes,
- ParentProcess,
- Flags,
- SectionHandle,
- DebugPort,
- ExceptionPort,
- JobMemberLevel);
- } else { //否则,返回参数不合法的错误
- Status = STATUS_INVALID_PARAMETER;
- }
- return Status;
- }
Q: 刚刚所说的用户模式是如何进入内核模式的?
A: 上面的ntdll.dll中执行依然是用户模式,NtCreateUserProcess通过向eax传入中断号, edx传入7FFE0300H为参数地址来进行系统调用,进入内核模式。它的内部实现为:
- NTSTATUS
- NtCreateProcess( //创建进程
- __out PHANDLE ProcessHandle, //进程句柄的指针
- __in ACCESS_MASK DesiredAccess,
- __in_opt POBJECT_ATTRIBUTES ObjectAttributes,
- __in HANDLE ParentProcess, //父进程句柄
- __in BOOLEAN InheritObjectTable, //是否继承句柄表
- __in_opt HANDLE SectionHandle,
- __in_opt HANDLE DebugPort, //调试端口
- __in_opt HANDLE ExceptionPort //异常端口
- )
- {
- ULONG Flags = 0;
- if ((ULONG_PTR)SectionHandle & 1) {
- Flags |= PROCESS_CREATE_FLAGS_BREAKAWAY;
- }
- if ((ULONG_PTR) DebugPort & 1) {
- Flags |= PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT;
- }
- if (InheritObjectTable) { //是否继承句柄表
- Flags |= PROCESS_CREATE_FLAGS_INHERIT_HANDLES;
- }
- //调用NtCreateProcessEx函数
- return NtCreateProcessEx (ProcessHandle,
- DesiredAccess,
- ObjectAttributes OPTIONAL, //OPTIONAL只是表示可选参数的意思,并不会对编译造成影响
- ParentProcess,
- Flags, //上面计算得到的Flags
- SectionHandle,
- DebugPort,
- ExceptionPort,
- 0);
- }
因为未找到win7的WRK源代码信息,上面为nt内核的实现。注意,如上程序执行是在win7系统下的执行。
Q: 为了更清楚地得到内部调用细节,是否可以使用虚拟机来测试一下?
A: 可以,使用虚拟机进行内核模式调试,来验证上面的整个过程;当然,因为使用的虚拟机系统是2003 server sp1, 它的内核和win7内核是不同的,所以会有不一致的地方。
Q: 当虚拟机和调试器启动后(此具体过程略,关于wrk的配置可以在网上搜索),然后做什么?
A: 先在win7下面编写一个可以在虚拟机系统2003 server sp1下可以跑的程序.可以是任意的了。
Q: 如下代码:
- #include <stdio.h>
- int main()
- {
- printf("hello\n");
- return 0;
- }
编译成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中包含了返回时的处理:
- kss61:
- ;
- ; Upon return, (eax)= status code. This code may also be entered from a failed
- ; KiCallbackReturn call.
- ;
- mov esp, ebp ; deallocate stack space for arguments
- ;
- ; Restore old trap frame address from the current trap frame.
- ;
- kss70: mov ecx, PCR[PcPrcbData+PbCurrentThread] ; get current thread address
- mov edx, [ebp].TsEdx ; restore previous trap frame address
- mov [ecx].ThTrapFrame, edx ;
- ;
- ; System service's private version of KiExceptionExit
- ; (Also used by KiDebugService)
- ;
- ; Check for pending APC interrupts, if found, dispatch to them
- ; (saving eax in frame first).
- ;
- public _KiServiceExit
- _KiServiceExit:
- cli ; disable interrupts
- DISPATCH_USER_APC ebp, ReturnCurrentEax
- ;
- ; Exit from SystemService
- ;
- EXIT_ALL NoRestoreSegs, NoRestoreVolatile
主要就是恢复调用时的信息,继续执行。