win32 进程创建_句柄表
在之前的学习中,我们了解到了,程序、镜像(ImageBuffer)、进程。那么这三个有什么区别呢?
通俗来讲程序就是写好的.exe但是还没有运行,死的一个东西;镜像是将程序按照PE格式拉伸贴到4GB空间中的东西,而进程就是这个镜像跑起来后的东西。当eip”给到”这个ImageBufffer时这个就运行起来了
父进程创建子进程,父进程挂了子进程不会挂
一、进程创建的过程

步骤一:
当系统启动后,会创建一个进程:Explorer.exe 也就是桌面进程。
步骤二:
当用户双击某一个exe时,Explorer 进程使用CreateProcess函数创建被双击的exe,也就是说:我们在桌面上双击创建的进程都是Explorer进程的子进程。
我们可以通过XueTr.exe ,查看那些进程是由Explorer创建的。

二、CreateProcess函数做了什么
1 2 3 4 5 6 7 8 9 10 11 12
| BOOL CreateProcess( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
|
参数说明
现在先主要了解几个,其他的等用到再了解:
lpApplicationName : 要执行的程序的名称,字符串可以指定要执行的模块的完整路径和文件名,也可以指定部分名称。如果只有程序名称没有路径,则系统会按照以下顺序解释:
1 2 3 4
| c:\program.exe c:\program files\sub.exe c:\program files\sub dir\program.exe c:\program files\sub dir\program name.exe
|
[in, out, optional] lpCommandLine : 要执行的命令行
[in] bInheritHandles : 如果这个参数为TRUE,句柄表可继承的句柄都会被由这个进程创建的进程继承。如:A进程句柄表中的存在0x1句柄,并且这个句柄可以被继承,如果A进程通过CreateProcess创建出进程B则进程B,就能继承这个0x1
[in] lpStartupInfo : 指向STARTUPINFO或STARTUPINFOEX结构的指针,当不使用时,必须用CloseHandle关闭他们。
[out] lpProcessInformation : 指向接收有关新进程的标识信息的 PROCESS_INFORMATION 结构的指针。
1、创建内核对象

当CreateProcess后,就会创建这个句柄表,但是这个表刚刚创建的时候是空的。得做Create内核对象时计数器++,然后往表里面写东西。
句柄表:第一列就是句柄,也就是我们常用的hThread之类的,相当与是内核对象的编号;第二列是内核对象的真正地址,第三列表示能不能被继承
2、分配4GB的虚拟内存空间(Windows 32位)

创建线程的过程:
- 将exe拉伸,存储到指定位置
- 遍历exe导入表,将需要用到的dll拉伸存储到指定位置,如果位置被占用,换地方,并通过DLL的重定位表,修复全局
- DLL如果引用了其他DLL,递归第二步
- 修复exe/dll中的IAT表
- 创建线程、设置线程CONTEXT开始执行
3、创建进程的主线程

三、创建进程(示例代码)
代码一:
通过名字创建,也就是CreateProcess第一个参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| VOID TestCreateProcessByAPPName() { STARTUPINFO si = {0}; PROCESS_INFORMATION pi; si.cb = sizeof(si); TCHAR szApplicationName[] =TEXT(文件路径); BOOL res = CreateProcess( szApplicationName, NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); }
|

执行后直接打开了pe查看器
代码二:
通过命令行打开(参数二):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| VOID TestCreateProcessByCmdline() { STARTUPINFO si = {0}; PROCESS_INFORMATION pi; si.cb = sizeof(si); TCHAR szCmdline[] =TEXT("c://program files//internet explorer//iexplore.exe http://www.ifeng.com"); BOOL res = CreateProcess( NULL, szCmdline, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); }
|
代码三:
两个参数一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| VOID TestCreateProcess() { STARTUPINFO si = {0}; PROCESS_INFORMATION pi; si.cb = sizeof(si); TCHAR szCmdline[] =TEXT(" http://www.ifeng.com"); BOOL res = CreateProcess( TEXT("c://program files//internet explorer//iexplore.exe"), szCmdline, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); }
|
接下来看看最后两个结构体是什么东西:
STARTUPINFO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| typedef struct _STARTUPINFO { DWORD cb; PSTR lpReserved; PSTR lpDesktop; PSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; PBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO;
|
这个结构体是用来设定要创建的应用程序的属性,比如可以指定新创建的控制台程序的标题等待。一般情况下 只用给第一个成员赋值就可以了。
也就是最后一个参数的结构体
1 2 3 4 5 6 7 8
| typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION;
|
在CreateProcess函数参数说明中,这个参数前面有一个[out]的东西,说明这个是CreateProcess函数输出的详细。这个结构是用来存放 进程和主线程的句柄和ID的,我们可以用以下代码来查看:
1 2
| printf("dwProcessId: %x dwThreadId: %x\nhProcess: %x hThread: %x\n", pi.dwProcessId, pi.dwThreadId, pi.hProcess, pi.hThread);
|
输出:

关于句柄和ID
- 都是系统分配的一个编号,句柄是客户程序使用,ID主要是系统调度时使用
- 调用CloseHandle关闭进程或者线程句柄的时候,只是让内核计数器减少一,并不是终止进程或者线程。进程猴子线程将继续运行,直到它自己终止运行
- 在进程执行过程中,进程Id与线程id 是不可能相同的。但是不要通过进程或者线程ID来操作进程或者线程,因为当进程关闭或意外中断再打开后id就不是原来的ID了,因为系统会把这个ID给了其他进程或者线程
四、进程终止
进程终止的三种方式:
1 2 3
| VOID ExitProcess(UINT fuExitCode) BOOL TerminateProcess(HANDLE hProcess, UINT fuExitCode); ExitThread
|
获取进程的退出码:
1
| BOOL GetExitCodeProcess(HANDLE hProcess, PDWORD pdwExitCode);
|
进程终止时相关操作:
- 进程中剩余的所有线程全部停止运行
- 进程指定的所有用户对象均被释放,所有内核对象均被关闭
- 进程内核对象的状态变成收到通知的状态
- 进程内核对象的使用计数递减1
五、句柄的继承
让不同进程间拥有相同的内核对象
首先先了解一下需要用到的知识
1、命令行参数的使用
1 2 3 4 5 6 7
| char szBuffer[256] = {0}; memcpy(szBuffer,argv[1],8); DWORD dwHandle = 0; sscanf(szBuffer,"%x",&dwHandle); printf("%s\n",argv[0]); printf("%x\n",dwHandle); getchar();
|
2、 句柄的继承
我们可以通过创建一个事件内核对象来验证这个句柄的继承:
进程A中的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| VOID TestExtendHandle() { CHAR szBuffer[256] = { 0 }; memset(szBuffer, 0, 256); CHAR szHandle[8] = { 0 };
SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE;
HANDLE g_hEvent = CreateEvent(&sa, TRUE, FALSE, NULL);
sprintf_s(szHandle, 8, "%d", g_hEvent); sprintf_s(szBuffer, 256, "C:\\Users\\lys05\\Desktop\\test3.exe %s", szHandle);
STARTUPINFOA si = { 0 }; si.cb = sizeof(si);
PROCESS_INFORMATION pi;
BOOL ret = CreateProcessA( NULL, szBuffer, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi );
SetEvent(g_hEvent);
CloseHandle(g_hEvent); }
|
进程B中的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int main(int argc,char* argv[]) { CHAR szBuffer[256] = { 0 }; memcpy(szBuffer, argv[1],8); DWORD dwHandle = 0; sscanf_s(szBuffer, "%d", &dwHandle); printf_s("%s\n", argv[0]); printf_s("%d\n", dwHandle);
HANDLE g_hEvent = (HANDLE)dwHandle; printf_s(".....开始等待\n"); WaitForSingleObject(g_hEvent, INFINITE); DWORD dwCode = GetLastError(); printf_s("%d\n", dwCode); printf_s("等到消息.....\n"); getchar(); }
|
思路就是进程A创建出一个可继承的事件,然后进程A创建出进程B,并且让进程B继承进程A的句柄表,此时B的句柄表中就就有A创建的事件的句柄,A创建B时又是通过命令行传参,此时B就可以通过参数获得事件的句柄,然后存到g_hEvent当中,接下来就是等待线程A将事件设置成已通知了。然后B就可以进行往下执行了

可以看到已经输出了等待到消息,可以说明进程B继承了A的句柄表
注意: 这里参数传递有个坑,当我们要用sscanf将g_hEvent传到szBuffer中,里面如果用”%d”,则线程B中获取sprintf中的占位符也要用”%d”
形象化:


