日期:2012-6-19 12:28:30
环境:[win7旗舰版 SP1 ; Intel i3 x86, 支持64位 ;vs2010 ; wrk-v1.2 ; Virtual PC 2007 windows 2003 server sp1 standard edition镜像 ;WinDbg 6.11.0001.404(x86) ]
转载请注明出处
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
主要就是恢复调用时的信息,继续执行。
作者:陈曦
日期:2012-6-19 12:28:30
环境:[win7旗舰版 SP1 ; Intel i3 x86, 支持64位 ;vs2010 ; wrk-v1.2 ; Virtual PC 2007 windows 2003 server sp1 standard edition镜像 ;WinDbg 6.11.0001.404(x86) ]
转载请注明出处