1. 什么是进程 根据海哥的描述,进程是一个空间的概念。进程就是把PE结构里面的所有信息映射到了内存空间中。
如果把进程比喻成人的躯体,那么线程就是这个躯体的灵魂。
鲁迅有一句话说的好: “有的人死了,但却活着。有些人活着,但是却死了。”
进程就相当于这个活着但是却死了的人。
2. Windows加载程序流程 进程的加载知识程序加载的第一步。程序加载的流程分为以下几步
把PE结构的所有信息加载到内存空间
在内核里面创建EPROCESS
对象(这个对象最主要的作用就是存放Handle表)
将系统Dll加载到内存空间中 (like ntdll.dll)
在内核中创建ETHREAD
对象(现在只是创建了对象,还没有启动)
系统启动Thread
映射 exe 要使用的dll到内存空间中
线程开始执行
3. 进程在内存中的空间状态
4. 进程和线程的关系
进程不是凭空出现的,每一个进程都是由父进程创建。操作系统启动时创建的第一个进程是explorer.exe
。所以我们打开的程序都是explorer.exe
的子进程
每创建一个进程肯定就会自动创建一个线程,没有线程的进程是行尸走肉
5. Win32 API: CreateProcess() 直接查MSDN文档,给出以下说明:
1 2 3 4 5 6 7 8 9 10 11 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: 路径
lpCommandLine: 命令行参数
lpProcessAttributes: SecurityAttribute结构体,可以在这个结构体里面设置这个创建的进程是否能被继承
lpThreadAttributes: SecurityAttribute结构体,可以在这个结构体里面设置这个创建的线程是否能被继承
bInheritHandles: 是否继承父类的句柄表
dwCreationFlags: 有很多参数,可以自行查MSDN。最重要的是CREATE_SUSPENDED
,这个参数可以让进程创建中线程创建时suspend。当我们调用ResumeThread时系统再贴入dll,然后启动线程
lpEnvironment: 创建进程的环境,一般给NULL, 环境和父类一样。
lpCurrentDirectory: 当前工作目录,NULL默认工作目录就是你打开exe时explorer的目录。
lpStartupInfo: 必须传入, StartupInfo结构体,定义时要把第一个成员添上结构体size
lpProcessInformation: 必须输入,OUT类型参数,返回创建的这个进程和线程信息, 定义时要把第一个成员添上结构体size
以下给出CreateProcess的example:
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 void main ( VOID ) { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof (si) ); si.cb = sizeof (si); ZeroMemory( &pi, sizeof (pi) ); if ( !CreateProcess( NULL , "MyChildProcess" , NULL , NULL , FALSE, 0 , NULL , NULL , &si, &pi ) ) { ErrorExit( "CreateProcess failed." ); } WaitForSingleObject( pi.hProcess, INFINITE ); CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); }
我们可以看到结束了CreateProcess之后会close 进程和线程的Handle。
这里的closeHandle其实是在减去内核中此进程/线程内核对象EPROCESS
/ETHREAD
中的Handle计数器。当计数器为0时内核会自动清除这个对象和对象占用的内存。但是进程和线程是个例外,线程需要terminate。
进程也是歌例外。只要存在线程,就算是进程的EPROCESS
内核对象计数为0,依然不会被系统清除,只有进程里面的所有线程都terminate之后,进程才会跟着被清除。
我们在Create Process的时候,会传入一个输出型参数PROCESS_INFORMATION
。这个结构体如下:
1 2 3 4 5 6 typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION;
那么这里我们就要区分什么是进程Handle,什么是进程id
id属于的范围为全局的,保存在内核中一张全局ID表中。 句柄是局部的,只存在于EPROCESS
/ETHREAD
中。
7. 进程其它API
GetProcessImageFileName: 使用Pid获取FileName
OpenProcess: 使用Pid开启进程
CreateToolhelp32Snapshot: 获取当前系统中线程/进程/Module/Heap等的快照,可以用来扫描进程
EnumProcess:列出所有Process Id
以下是使用CreateToolhelp32Snapshot 和 Enum 扫描进程的代码
首先是使用CreateToolhelp32Snapshot 扫描进程:
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 bool ScanSnapShot () { HANDLE hProcessSnap = NULL ; PROCESSENTRY32 processEntry = { 0 }; processEntry.dwSize = sizeof (PROCESSENTRY32); hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0 ); if (hProcessSnap == INVALID_HANDLE_VALUE) { printf ("Create SnapShot Error:%d\n" , GetLastError()); return EXIT_FAILURE; } if (Process32First(hProcessSnap, &processEntry)) { do { TCHAR processName[256 ] = { 0 }; HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processEntry.th32ProcessID); DWORD rLength = GetProcessImageFileName(processHandle, processName, 256 ); if (rLength != 0 ) { printf ("Process id: %d\n" , processEntry.th32ProcessID); _tprintf(TEXT("Process Name: %s\n" ), processName); } else { } CloseHandle(processHandle); } while (Process32Next(hProcessSnap, &processEntry)); } CloseHandle(hProcessSnap); return EXIT_SUCCESS; }
下面的使用EnumProcess扫描进程:
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 bool ScanProcess (DWORD* processIdList, DWORD sizeOfList, DWORD* numOfProcess) { ZeroMemory(processIdList, sizeOfList * 4 ); if (!EnumProcesses(processIdList, sizeOfList, numOfProcess)) { printf ("Enum Process Error: %d\n" , GetLastError()); return EXIT_FAILURE; } return EXIT_SUCCESS; } int main () { DWORD* processList = new DWORD[4096 ]; DWORD numberOfBytes; ScanProcess(processList, 4096 , &numberOfBytes); printf ("Number of process: %d\n" , numberOfBytes / sizeof (DWORD)); for (int i = 0 ; i < (numberOfBytes / sizeof (DWORD)); i++) { TCHAR name[256 ]; HANDLE hProcess = OpenProcess(processList[i], FALSE, processList[i]); if (GetProcessImageFileName(hProcess, name, 256 ) != 0 ) { printf ("Process Id: %d\n" , processList[i]); _tprintf(TEXT("Process Name: %s\n" ), name); } CloseHandle(hProcess); } delete [] processList; getchar(); return EXIT_SUCCESS; }
打印出来结果如下: