1. 什么是进程

根据海哥的描述,进程是一个空间的概念。进程就是把PE结构里面的所有信息映射到了内存空间中。

如果把进程比喻成人的躯体,那么线程就是这个躯体的灵魂。

鲁迅有一句话说的好: “有的人死了,但却活着。有些人活着,但是却死了。”

进程就相当于这个活着但是却死了的人。

2. Windows加载程序流程

进程的加载知识程序加载的第一步。程序加载的流程分为以下几步

  • 把PE结构的所有信息加载到内存空间
  • 在内核里面创建EPROCESS对象(这个对象最主要的作用就是存放Handle表)
  • 将系统Dll加载到内存空间中 (like ntdll.dll)
  • 在内核中创建ETHREAD对象(现在只是创建了对象,还没有启动)
  • 系统启动Thread
    • 映射 exe 要使用的dll到内存空间中
    • 线程开始执行

3. 进程在内存中的空间状态

0.png

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, // name of executable module
LPTSTR lpCommandLine, // command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
BOOL bInheritHandles, // handle inheritance option
DWORD dwCreationFlags, // creation flags
LPVOID lpEnvironment, // new environment block
LPCTSTR lpCurrentDirectory, // current directory name
LPSTARTUPINFO lpStartupInfo, // startup information
LPPROCESS_INFORMATION lpProcessInformation // process information);

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) );

// Start the child process.
if( !CreateProcess( NULL, // No module name (use command line).
"MyChildProcess", // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
FALSE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi ) // Pointer to PROCESS_INFORMATION structure.
)
{
ErrorExit( "CreateProcess failed." );
}

// Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );

// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}

我们可以看到结束了CreateProcess之后会close 进程和线程的Handle。

这里的closeHandle其实是在减去内核中此进程/线程内核对象EPROCESS/ETHREAD中的Handle计数器。当计数器为0时内核会自动清除这个对象和对象占用的内存。但是进程和线程是个例外,线程需要terminate。

进程也是歌例外。只要存在线程,就算是进程的EPROCESS内核对象计数为0,依然不会被系统清除,只有进程里面的所有线程都terminate之后,进程才会跟着被清除。

6. Process Information

我们在Create Process的时候,会传入一个输出型参数PROCESS_INFORMATION 。这个结构体如下:

1
2
3
4
5
6
typedef struct _PROCESS_INFORMATION { 
HANDLE hProcess; //Process Handle
HANDLE hThread; //Thread Handl
DWORD dwProcessId; // Process Id
DWORD dwThreadId; // Thread Id
} 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 }; //Used for storing the information of process

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);
//printf("Process id: %d\n", processEntry.th32ProcessID);

if (rLength != 0)
{
printf("Process id: %d\n", processEntry.th32ProcessID);
_tprintf(TEXT("Process Name: %s\n"), processName);
}
else
{
//printf("GetModuleFileNameEx error: %d\n", GetLastError());
}

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;
}

打印出来结果如下:

1.png