WindowsAPI查缺补漏-进程

创建进程

ShellExecute

打开i可执行文件、文档文件、网址等。

1
2
3
4
5
6
7
8
HINSTANCE ShellExecute(
_In_opt_ HWND hwnd, //父窗口句柄
_In_opt_ LPCTSTR lpOperation, //指定的操作
_In_ LPCTSTR lpFile, //要操作的文件或文件夹
_In_opt_ LPCTSTR lpParameters, //当lpFile为可执行文件时 为命令行参数
_In_opt_ LPCTSTR lpDirectory, //要操作的文件的默认工作目录
_In_ INT nShowCmd //显示标志
)

lpOperation可以是:

枚举值 含义
open 由关联的默认程序打开lpFile文件/文件夹
explore 资源管理器打开lpFIle文件夹
edit 用编辑器(记事本)打开lpFile指定的文档,不是文档则调用失败
print 打印lpFile指定的文件,不是文档文件则失败
find 从lpDirectory目录开始搜索

执行成功返回大于32的HINSTANCE类型值,失败返回:

枚举值 含义
0 操作系统内存或资源不足
ERROR_FILE_NOT_FOUND 找不到指定文件
ERROR_PATH_NOT_FOUND 找不到指定路径
ERROR_BAD_FORMAT .exe文件无效
SE_ERR_ACCESSDENIED 操作系统拒绝对指定文件的访问
SE_ERR_ASSOCINCOMPLETE 文件名关联不完整或无效
SE_ERR_DLLNOTFOUND 找不到指定动态链接库
SE_ERR_FNF 找不到指定文件
SE_ERR_NOASSOC 没有关联的应用程序,或文件不可打印
SE_ERR_OOM 没有足够的内存来完成操作
SE_ERR_PNF 找不到指定路径
SE_ERR_SHARE 发生共享冲突

WinExec

运行指定程序,已废弃用CreateProcess

1
2
3
4
UINT WINAPI WinExec(
_In_ LPCSTR lpCmdLine, //要执行的应用程序的命令行
_In_ UINT uCmdShow //显示标志
) //成功返回值大于31

CreateProcess

为指定名称的可执行文件创建进程以及进程的主线程。

1
2
3
4
5
6
7
8
9
10
11
12
BOOL WINAPI CreateProcess(
_In_opt_ LPCTSTR lpApplicationName, //要执行的可执行文件名称
_Inout_opt_ LPTSTR lpCommandLine, //命令行参数
_In_opt_ LPSECURITY_ATTRIBTUES lpProcessAttributes, //新进程的安全属性结构
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //新进程的主线程安全属性结构
_In_ BOOL bInheritHandles, //调用进程句柄是否可被新进程继承
_In_ DWORD dwCreationFlags, //创建标志和进程优先级 可0
_In_opt_ LPVOID lpEnvironment, //指向新进程环境快的指针
_In_opt_ LPCTSTR lpCurrentDirectory, //指向新进程当前目录
_In_ LPSTARTUPINFO lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);

dwCreationFlags中创建标志可以是:

枚举值 含义
DEBUG_PROCESS 父进程调试子进程及子进程生成的所有进程,子进程发生特定事件时通知父进程
DEBUG_ONLY_THIS_PROCESS 同DEBUG_PROCESS,但孙进程不通知父进程
CREATE_SUSPENDED 新进程的主线程处于挂起状态,用ResumeThread恢复运行
EXTENDED_STARTUPINFO_PRESENT 使用扩展启动信息创建进程
CREATE_UNICODE_ENVIRONMENT lpEnvironment参数使用Unicode字符
CREATE_DEFAULT_ERROR_MODE 新进程不继承调用进程错误模式,使用默认错误模式
DETACHED_PROCESS CUI进程时不使用父进程控制台窗口,可用AllocConsole创建新的控制台。
CREATE_NEW_CONSOLE CUI进程时新建一个控制台窗口
CREATE_NO_WINDOW CUI进程时不创建控制台窗口

dwCreationFlags中进程优先级可以是:

枚举值 含义
REALTIME_PRIORITY_CLASS 实时
HIGH_PRIORITY_CLASS
ABOVE_NORMAL_PRIORITY_CLASS 高于标准
NORMAL_PRIORITY_CLASS 标准
BELOW_NORMAL_PRIORITY_CLASS 低于标准
IDLE_PRIORITY_CLASS

lpStartupInfo中STARTUPINFO或STARTUPINFOEX结构为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef struct _STARTUPINFO {
DWORD cb; //该结构大小
LPWSTR lpReserved; //NULL
LPWSTR lpDesktop; //桌面名称 通常NULL
LPWSTR lpTitle; //控制台窗口标题
DWORD dwX; //窗口左上角X坐标 单位像素
DWORD dwY; //窗口左上角Y坐标 单位像素
DWORD dwXSize; //窗口宽度 单位像素
DWORD dwYSize; //窗口高度 单位像素
DWORD dwXCountChars; //屏幕缓冲区宽度 单位字符列
DWORD dwYCountChars; //屏幕缓冲区高度 单位字符行
DWORD dwFillAttribute; //文本/背景颜色
DWORD dwFlags; //位掩码 该结构哪个字段有效
WORD wShowWindow; //显示标志
WORD cbReserved2; //0
LPBYTE lpReserved2; //NULL
HANDLE hStdInput; //标准输入句柄
HANDLE hStdOutput; //标准输出句柄
HANDLE hStdError; //标准错误句柄
} STARTUPINFO, * LPSTARTUPINFO;
typedef struct _STARTUPINFOEX { //使用时在lpCreationFlags中指定EXTENDED_STARTUPINFO_PRESENT
STARTUPINFO StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; //属性列表
} STARTUPINFOEX, * LPSTARTUPINFOEX;

dwFlags常用标志有:

枚举值 有效字段
STARTF_USESIZE dwXSize dwYSize
STARTF_USESHOWWINDOW wShowWindow
STARTF_USEPOSITION dwX dwY
STARTF_USECOUNTCHARS dwXCountChars dwYCountChars
STARTF_USEFILLATTRIBUTE dwFillAttribute
STARTF_USESTDHANDLES hStdInput hStdOutput hStdError

对于lpProcessInformation参数的PROCESS_INFORMATION结构记录新进程的信息:

1
2
3
4
5
6
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; //新进程句柄
HANDLE hThread; //主线程句柄
DWORD dwProcessId; //进程ID
DWORD dwThreadId; //主线程ID
} PROCESS_INFORMATION, * PPROCESS_INFORMATION, * LPPROCESS_INFORMATION;

因为系统打开进程内核对象和线程内核对象时,句柄的计数会加1,所以获取后要及时关闭句柄。

例如运行记事本并打开D:\Text.txt文件:

1
2
3
4
5
6
7
8
TCHAR szCommandLine[MAX_PATH] = TEXT("Notepad D:\\Test.txt");
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
GetStartupInfo(&si);
if (CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
};

GetStartupInfo

获取当前进程STARTINFO结构:

1
2
3
VOID WINAPI GetStartupInfo(
_Out_ LPSTARTUPINFO lpStartupInfo
)

GetModuleHandle

获取调用进程中指定模块的模块句柄(基地址):

1
2
3
HMODULE WINAPI GetModuleHandle(
_In_opt_ LPCTSTR lpModuleName //调用进程地址空间中加载的一个模块的名称
) //没找到返回NULL

当lpModuleName设置为NULL时获取调用进程的可执行文件模块加载基地址,即使在DLL中也返回可执行文件模块基地址。

GetModuleFileName

获取当前进程已加载模块完整路径,已废弃用GetModuleFileNameEx

1
2
3
4
5
DWORD WINAPI GetModuleFileName(
_In_opt_ HMODULE hModule,
_Out_ LPTSTR lpFilename,
_In_ DWORD nSize
)

GetModuleFileNameEx

后去当前/另一个进程中已加载模块完整路径:

1
2
3
4
5
6
DWORD WINAPI GetModuleFileNameEx(
_In_ HANDLE hProcess, //包含hModule的进程句柄
_In_opt_ HMODULE hModule, //模块句柄
_Out_ LPTSTR lpFilename, //返回文件完整路径
_In_ DWORD nSize //lpFileName缓冲区大小 单位字符
)

需要对hProcess的PROCESS_QUERY_INFORMATION和PROCESS_VM_READ访问权限。

函数成功则返回复制到缓冲区中字符个数,不含终止空字符,失败返回0。

WaitForInputIdle

创建子进程后,CreateProcess马上返回,但子进程加载和初始化需要时间。用WaitForInputIdle挂起调用线程直到指定进程初始化完毕或函数等待超时返回。

1
2
3
4
DWORD WINAPI WaitForInputIdle(
_In_ HANDLE hProcess, //等待该进程输入空闲
_In_ DWORD dwMilliseconds //等待时间 单位毫秒 INFINITE永久等待
)

返回值有:0等待成功、WAIT_TIMEOUT超时时间已过、WAIT_FAILED发生错误。

GetMappedFileName

检查指定内存地址是否位于指定进程地址空间中,并返回进程所对应可执行文件名:

1
2
3
4
5
6
DWORD GetMappedFileName(
_In_ HANDLE hProcess, //进程句柄 需要PROCESS_QUERY_INFORMATION和PROCESS_VM_READ访问权限
_In_ LPVOID lpv, //内存地址
_Out_ LPTSTR lpFileName, //返回可执行文件名称的缓冲区
_In DWORD nSize //缓冲区大小 单位字节
) //成功返回复制到缓冲区字符串长 失败0

返回的文件名称路径为本机系统路径格式如\Device\HarddiskVolume2\,已废弃用GetModuleFileNameEx/QueryFullProcessImageName

1
2
3
4
GetModuleFileNameEx(pi.hProcess, NULL, szImageFile, _countof(szImageFile));

DWORD dwLen = _countof(szImageFile);
QueryFullProcessImageName(pi.hProcess, 0, szImageFile, &dwLen);

多进程间共享内核对象

GetCurrentProcess/GetCurrentThread

俩大抽象。

获取当前进程/调用线程句柄,但获取的都是伪句柄。当前进程伪句柄为0xFFFFFFFF,调用线程伪句柄为0xFFFFFFFE。伪句柄不能由子进程继承,不使用时不用关闭。

1
2
HANDLE WINAPI GetCurrentProcess(VOID);
HANDLE WINAPI GetCurrentThread(VOID);

GetCurrentProcessId/GetCurrentThreadId

获取当前进程/调用线程的ID:

1
2
DWORD WINAPI GetCurrentProcessId(VOID);
DWORD WINAPI GetCurrentThreadId(VOID);

GetProcessId/GetThreadId

获取指定进程/线程的ID:

1
2
3
4
5
6
DWORD WINAPI GetProcessId(
_In_ HANDLE Process
);
DWORD WINAPI GetThreadId(
_In_ HANDLE Thread
);

OpenProcess

通过进程ID获取进程句柄:

1
2
3
4
5
HANDLE WINAPI OpenProcess(
_In_ DWORD dwDesiredAccess, //对进程访问权限
_In_ BOOL bInheritHandle, //句柄是否可被调用进程的子进程继承
_In_ DWORD dwProcessId //进程ID
) //成功返回指定ID进程句柄 失败返回NULL

成功打开一个进程句柄大致进程对象引用计数加1,不需要时用CloseHandle关闭。

dwDesiredAccess指定访问权限,常用的有:

枚举值 允许的操作
PROCESS_QUERY_INFORMATION 获取进程相关信息,如访问令牌、退出码、进程优先级
PROCESS_SET_INFORMATION 设置进程相关信息,如进程优先级
PROCESS_SUSPEND_RESUME 挂起或恢复进程
PROCESS_TERMINATE TerminateProcess终止进程
PROCESS_DUP_HANDLE DuplicateHandle复制句柄
PROCESS_VM_OPERATION 修改进程地址空间,如写入内存、修改页面保护属性
PROCESS_VM_READ 对进程地址空间读取
PROCESS_VM_WRITE 对进程地址空间写入
PROCESS_CREATE_PROCESS 创建进程
PROCESS_CREATE_THREAD 创建线程
SYNCHRONIZE 调用等待函数等待进程终止
PROCESS_ALL_ACCESS 所有可能的访问权限

当打开系统空闲进程或csrss等系统关键进程,函数调用也失败。

GetProcessIdOfThread

根据线程句柄获取所在进程的ID:

1
2
3
DWORD WINAPI GetProcessIdOfThread(
_In_ HANDLE hThread
)

DuplicateHandle

复制内核对象句柄:

1
2
3
4
5
6
7
8
9
BOOL WINAPI DuplicateHandle(
_In_ HANDLE hSourceProcessHandle, //源进程句柄
_In_ HANDLE hSourceHandle, //源进程中源内核对象句柄
_In_ HANDLE hTargetProcessHandle, //目标进程句柄
_Out_ LPHANDLE lpTargetHandle, //目标内核对象句柄指针
_In_ DWORD dwDesiredAccess, //新句柄访问权限 可0
_In_ BOOL bInheritHandle, //新句柄是否可被目标进程的子进程继承 通常TRUE
_In_ DWORD dwOptions //操作选项 可0
)

dwOptions有组合:

枚举值 含义
DUPLICATE_CLOSE_SOURCE 复制后关闭源句柄,内核对象引用计数不变
DUPLICATE_SAME_ACCESS 复制访问权限,忽略dwDesiredAccess参数

可以用于32位和64位进程之间复制句柄。

进程终止

ExitProcess

终止调用进程及其所有线程:

1
2
3
VOID WINAPI ExitProcess(
_In_ UINT uExitCode //进程和所有线程的退出码
)

该函数不返回,因为进程已终止。

TerminateProcess

无条件终止调用进程或其他进程及其所有线程:

1
2
3
4
BOOL WINAPI TerminateProcess(
_In_ HANDLE hProcess, //进程句柄
_In_ UINT uExitCode //进程和所有线程的退出码
)

其中hProcess需要对该进程的PROCESS_TERMINATE访问权限。

GetExitCodeProcess

获取一个进程的退出码:

1
2
3
4
BOOL WINAPI GetExitCodeProcess(
_In_ HANDLE hProcess, //进程句柄
_Out_ LPDWORD lpExitCode //返回进程退出码
)

其中hProcess需要对该进程的PROCESS_QUERY_INFORMATION访问权限。

该函数立即返回,当进程尚未终止时退出码为STILL_ACTIVE。

进程间通信

WM_COPYDATA法

注意SendMessage将指定消息发送到若干个窗口,直到窗口过程处理完消息后函数才返回。PostMessage将消息投递到一个线程的消息队列后立即返回,由线程分发。注意PostMessageSendNotifyMessageSendMessageCallback的wParam和lParam参数不能传递指针,这些函数立即返回后指针指向的内存会被释放,GetLastError返回ERROR_MESSAGE_SYNC_ONLY。

SendMessageSendNotifyMessageSendMessageCallbackPostMessagePostThreadMessage发送WM_COPYDATA消息,wParam为目标进程窗口句柄,lParam为指向COPYDATASTRUCT结构的指针,目标进程窗口过程处理完WM_COPYDATA后应返回TRUE。

1
2
3
4
5
typedef struct tagCOPYDATASTRUCT {
ULONG_PTR dwData; //传递给目标进程的数据
DWORD cbData; //lpData指向数据的大小 单位字节
_Field_size_bytes_(cbData) PVOID lpData; //传递给目标进程的数据指针 可NULL
} COPYDATASTRUCT, * PCOPYDATASTRUCT;

对于lpData,目标进程不可以修改共享内存数据v,应提前把数据v复制到自己所属进程的缓冲区中。

FindWindow

查找具有指定窗口类名和窗口标题的窗口:

1
2
3
4
HWND WINAPI FindWindow(
_In_opt_ LPCTSTR lpClassName, //窗口类名
_In_opt_ LPCTSTR lpWindowName //窗口标题
) //成功返回句柄 失败返回NULL

参数不区分大小写。

lpClassName可以是用RegisterClassRegisterClassEx注册的窗口类名称,也可以是任何预定义的控件类名称,如对话框窗口类名为#32770。

StringCchPrintf

微软建议使用的格式化字符串,之前学了这里不讲:

1
2
3
4
5
6
HRESULT StringCchPrintf(
_Out_ LPTSTR pszDest, //目标缓冲区 以0结尾
_In_ SIZE_T cchDest, //pszDest大小 单位字符 最大STRSAFE_MAX_CCH
_In_ LPCTSTR pszFormat, //格式字符串
_In_ ...
)

例子

发送方:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <windows.h>
#include <tchar.h>
#include "resource.h"
#include "DataStructure.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
COPYDATASTRUCT cds = { 0 };
PersonStruct ps = { 0 };
ScoreStruct ss = { 0 };
TCHAR szBuf[32] = { 0 };
HWND hwndTarget;
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_PERSON: {
// 查找具有指定类名和窗口名的窗口的窗口句柄
hwndTarget = FindWindow(TEXT("#32770"), TEXT("接收数据"));
if (hwndTarget) {
// 获取姓名、年龄、存款
GetDlgItemText(hwndDlg, IDC_EDIT_NAME, ps.m_szName, _countof(ps.m_szName));
ps.m_nAge = GetDlgItemInt(hwndDlg, IDC_EDIT_AGE, NULL, FALSE);
GetDlgItemText(hwndDlg, IDC_EDIT_MONEY, szBuf, _countof(szBuf));
ps.m_dMoney = _ttof(szBuf);
// 发送WM_COPYDATA消息
cds.dwData = PERSONDATA;
cds.cbData = sizeof(PersonStruct);
cds.lpData = &ps;
SendMessage(hwndTarget, WM_COPYDATA, (WPARAM)hwndTarget, (LPARAM)&cds);
};
break;
};
case IDC_BTN_SCORE: {
// 查找具有指定类名和窗口名的窗口的窗口句柄
hwndTarget = FindWindow(TEXT("#32770"), TEXT("接收数据"));
if (hwndTarget) {
// 获取语文、数学、英语成绩
GetDlgItemText(hwndDlg, IDC_EDIT_CHINESE, szBuf, _countof(szBuf));
ss.m_dChinese = _ttof(szBuf);
GetDlgItemText(hwndDlg, IDC_EDIT_MATH, szBuf, _countof(szBuf));
ss.m_dMath = _ttof(szBuf);
GetDlgItemText(hwndDlg, IDC_EDIT_ENGLISH, szBuf, _countof(szBuf));
ss.m_dEnglish = _ttof(szBuf);
// 发送WM_COPYDATA消息
cds.dwData = SCOREDATA;
cds.cbData = sizeof(ScoreStruct);
cds.lpData = &ss;
SendMessage(hwndTarget, WM_COPYDATA, (WPARAM)hwndTarget, (LPARAM)&cds);
};
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

接收方:

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
#include <windows.h>
#include <strsafe.h>
#include "resource.h"
#include "../CopyDataDemo/DataStructure.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
PCOPYDATASTRUCT pCDS;
PPersonStruct pPS;
PScoreStruct pSS;
TCHAR szBuf[128] = { 0 };
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_COPYDATA: {
pCDS = (PCOPYDATASTRUCT)lParam;
if (pCDS->dwData == PERSONDATA) {
pPS = (PPersonStruct)(pCDS->lpData);
StringCchPrintf(szBuf, _countof(szBuf), TEXT("个人信息:\n姓名:%s\n年龄:%d\n存款:%.2lf"), pPS->m_szName, pPS->m_nAge, pPS->m_dMoney);
MessageBox(hwndDlg, szBuf, TEXT("个人信息"), MB_OK);
}
else if (pCDS->dwData == SCOREDATA) {
pSS = (PScoreStruct)pCDS->lpData;
StringCchPrintf(szBuf, _countof(szBuf), TEXT("考试成绩:\n语文:%6.2lf\n数学:%6.2lf\n英语:%6.2lf"), pSS->m_dChinese, pSS->m_dMath, pSS->m_dEnglish);
MessageBox(hwndDlg, szBuf, TEXT("考试成绩"), MB_OK);
};
return TRUE;
};
};
return FALSE;
};

数据结构定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#pragma once
// 常量定义
#define PERSONDATA 1
#define SCOREDATA 2
// 数据结构定义
typedef struct _PersonStruct{
TCHAR m_szName[32]; // 姓名
int m_nAge; // 年龄
double m_dMoney; // 存款
}PersonStruct, *PPersonStruct;
typedef struct _ScoreStruct{
double m_dChinese; // 语文
double m_dMath; // 数学
double m_dEnglish; // 英语
}ScoreStruct, *PScoreStruct;

匿名管道法

CreatePipe

创建一个匿名管道:

1
2
3
4
5
6
BOOL WINAPI CreatePipe(
_Out_ PHANDLE hReadPipe, //读取句柄
_Out_ PHANDLE hWritePipe, //写入句柄
_In_opt_ LPSECURITY_ATTRIBUTES lpPipeAttributes, //安全属性结构
_In_ DWORD nSize //管道缓冲区大小 单位字节 0表示默认大小
)

管道读写用WriteFileReadFile,句柄hReadPipe和hWritePipe需要用CloseHandle关闭。因为用于父进程与子进程之间传输数据,创建匿名管道时lpPipeAttributes要设置为可继承,如:

1
2
3
4
HANDLE hReadPipe, hWritePipe;
SECURITY_ATTRIBUTES sa = { sizeof(sa) };
sa.bInheritHandle = TRUE;
CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);

例子

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include "resource.h"
// 常量定义
#define BUF_SIZE 1024
// 全局变量
HWND g_hwndDlg;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
HANDLE hThread = NULL;
switch (uMsg) {
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
// 初始化编辑控件
SetDlgItemText(hwndDlg, IDC_EDIT_URL, TEXT("www.baidu.com"));
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_PING: {
// 创建线程
if ((hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL)) != NULL)
CloseHandle(hThread);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
// 创建匿名管道
HANDLE hReadPipe, hWritePipe;
SECURITY_ATTRIBUTES sa = { sizeof(sa) };
sa.bInheritHandle = TRUE;
CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);
// 创建子进程,把子进程Ping的输出重定向到匿名管道的写入句柄
STARTUPINFO si = { sizeof(si) };
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.hStdOutput = si.hStdError = hWritePipe;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi;
// 命令行参数拼接为:Ping www.baidu.com的形式
TCHAR szCommandLine[MAX_PATH] = TEXT("Ping ");
TCHAR szURL[256] = { 0 };
GetDlgItemText(g_hwndDlg, IDC_EDIT_URL, szURL, _countof(szURL));
StringCchCat(szCommandLine, _countof(szCommandLine), szURL);
// 创建Ping子进程
if (CreateProcess(NULL, szCommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
CHAR szBuf[BUF_SIZE + 1] = { 0 };
CHAR szOutput[BUF_SIZE * 8] = { 0 };
DWORD dwNumOfBytesRead;
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
while (TRUE) {
// 读取匿名管道的读取句柄
ZeroMemory(szBuf, sizeof(szBuf));
ReadFile(hReadPipe, szBuf, BUF_SIZE, &dwNumOfBytesRead, NULL);
if (dwNumOfBytesRead == 0)
break;
// Ping控制台的输出是ANSI编码,因此使用StringCchCatA和SetDlgItemTextA
// 把读取到的数据追加到szOutput缓冲区
StringCchCatA(szOutput, _countof(szOutput), szBuf);
// 显示到编辑控件中
SetDlgItemTextA(g_hwndDlg, IDC_EDIT_CONTENT, szOutput);
};
};
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
return 0;
};

命名管道法

略。

邮件槽法

邮件槽是一种进程间单向通信机制,创建并拥有邮件槽的进程称为邮件槽服务器,邮件槽服务器从邮件槽中读取数据,其他进程可打开邮件槽并写入数据,这些进程称为邮件槽客户端。

CreateMailslot

创建一个指定名称的邮件槽内核对象:

1
2
3
4
5
6
HANDLE WINAPI CreateMailslot(
_In_ LPCTSTR lpName, //邮件槽名称 格式"\\.\mailslot\邮件槽名"
_In_ DWORD nMaxMessageSize, //可写入邮件槽的单条消息最大大小 单位字节 0任意大小
_In_ DWORD lReadTimeout, //等待写入邮件槽的时间 单位毫秒 MAILSLOT_WAIT_FOREVER一直等待可读取的消息
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes //安全属性结构
) //成功返回邮件槽句柄 失败返回INVALID_HANDLE_VALUE

指定名称邮件槽已存在时GetLastError返回ERROR_ALREADY_EXISTS。

CreateFile打开邮件槽,用WriteFile向邮件槽写入数据,用ReadFile读取数据。读取数据前应用GetMailslotInfo确定邮件槽中是否由消息。不需要读写数据时用CloseHandle关闭邮件槽句柄。

GetMailslotInfo

确定邮件槽中是否有消息:

1
2
3
4
5
6
7
BOOL WINAPI GetMailslotInfo(
_In_ HANDLE hMailslot, //邮件槽句柄
_Out_opt_ LPDWORD lpMaxMessageSize, //单条消息最大大小 单位字节
_Out_opt_ LPDWORD lpNextSize, //下一条待读取消息大小 单位字节 MAILSLOT_NO_MESSAGE没有待读取消息
_Out_opt_ LPDWORD lpMessageCount, //待读取消息总数量
_Out_opt_ LPDWORD lpReadTimeout //读取操作可等待时间 单位毫秒
)

例子

服务端:

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
46
47
48
49
50
51
52
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szMailslotName[] = TEXT("\\\\.\\mailslot\\99F1755D-31FD-4CE5-8183-3F438316E8D7");
static HANDLE hMailslot;
DWORD dwNextSize, dwMessageCount, dwNumOfBytesRead;
switch (uMsg) {
case WM_INITDIALOG: {
// 创建邮件槽
hMailslot = CreateMailslot(szMailslotName, 0, MAILSLOT_WAIT_FOREVER, NULL);
if (hMailslot == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_ALREADY_EXISTS)
MessageBox(hwndDlg, TEXT("指定名称的邮件槽已经存在"), TEXT("错误提示"), MB_OK);
else
MessageBox(hwndDlg, TEXT("CreateMailslot函数调用失败"), TEXT("错误提示"), MB_OK);
ExitProcess(0);
};
// 创建计时器
SetTimer(hwndDlg, 1, 1000, NULL);
return TRUE;
};
case WM_TIMER: {
// 在调用ReadFile函数读取数据以前,先调用GetMailslotInfo函数来确定邮件槽中是否有消息
GetMailslotInfo(hMailslot, NULL, &dwNextSize, &dwMessageCount, NULL);
for (DWORD i = 0; i < dwMessageCount; i++) {
LPBYTE lpBuf = new BYTE[dwNextSize];
ZeroMemory(lpBuf, dwNextSize);
ReadFile(hMailslot, lpBuf, dwNextSize, &dwNumOfBytesRead, NULL);
SendMessage(GetDlgItem(hwndDlg, IDC_LIST_MSG), LB_ADDSTRING, 0, (LPARAM)(LPTSTR)lpBuf);
delete[]lpBuf;
};
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDCANCEL: {
CloseHandle(hMailslot);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

客户端:

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
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szMailslotName[] = TEXT("\\\\.\\mailslot\\99F1755D-31FD-4CE5-8183-3F438316E8D7");
HANDLE hMailslot;
TCHAR szBuf[1024] = { 0 };
int n;
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_SEND: {
// 打开邮槽
hMailslot = CreateFile(szMailslotName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hMailslot != INVALID_HANDLE_VALUE) {
// 写入数据
n = GetDlgItemText(hwndDlg, IDC_EDIT_MSG, szBuf, _countof(szBuf));
WriteFile(hMailslot, szBuf, (n + 1) * sizeof(TCHAR), NULL, NULL);
CloseHandle(hMailslot);
};
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

进程枚举

TlHelp32法

CreateToolhelp32Snapshot

捕获系统中当前正在运行的所有进程的快照,得到一个进程列表:

1
2
3
4
HANDLE WINAPI CreateToolhelp32Snapshot(
_In_ DWORD dwFlags, //标志
_In_ DWORD th32ProcessID //进程ID 0表示当前ID
)

dwFlags可以是组合:

枚举值 枚举对象
TH32CS_SNAPPROCESS 系统所有进程,忽略th32ProcessID
TH32CS_SNAPTHREAD 系统所有线程,忽略th32ProcessID
TH32CS_SNAPHEAPLIST th32ProcessID指定进程中所有堆
TH32CS_SNAPMODULE th32ProcessID指定进程中所有模块
TH32CS_SNAPMODULE32 64位进程中32位模块
TH32CS_SNAPALL 上面所有
TH32CS_INHERIT 快照句柄可继承

Process32First/Process32Next

获取快照中第一个/其他进程信息,直到Process32Next返回FALSE:

1
2
3
4
5
6
7
8
BOOL WINAPI Process32First(
_In_ HANDLE hSnapshot, //快照句柄
_Out_ LPPROCESSENTRY32 lppe //返回进程信息
);
BOOL WINAPI Process32Next(
_In_ HANDLE hSnapshot,
_Out_ LPPROCESSENTRY32 lppe
);

其中PROCESSENTRY32结构为:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct tagPROCESSENTRY32 {
DWORD dwSize; //该结构大小
DWORD cntUsage; //0
DWORD th32ProcessID; //进程ID
ULONG_PTR th32DefaultHeapID; //0
DWORD th32ModuleID; //0
DWORD cntThreads; //进程启动的线程个数
DWORD th32ParentProcessID; //进程的父进程ID
LONG pcPriClassBase; //进程创建的线程的基本优先级
DWORD dwFlags; //0
CHAR szExeFile[MAX_PATH]; //进程的可执行文件名称
} PROCESSENTRY32;

szExeFile返回的只是文件名不带路径。

QueryFullProcessImageName

获取一个进程对应可执行文件的完整路径:

1
2
3
4
5
6
BOOL WINAPI QueryFullProcessImageName(
_In_ HANDLE hProcess, //进程句柄
_In_ DWORD dwFlags, //返回路径格式
_Out_ LPTSTR lpExeName, //返回可执行文件路径的缓冲区
_Inout_ PDWORD lpdwSize //缓冲区大小 单位字符 不含终止空字符
)

dwFlags一般为0,表示普通格式,设置为PROCESS_NAME_NATIVE表示本机系统路径格式,如\Device\HarddiskVolumn3\Windows\System32\svchost.exe。

还有一种更高效的替代方法:用GetModuleFileNameEx,hModule为NULL表示获取hProcess进程的可执行文件完整路径。

OpenProcessToken

打开与进程关联的访问令牌以获得一个访问令牌句柄:

1
2
3
4
5
BOOL OpenProcessToken(
_In_ HANDLE ProcessHandle, //进程句柄
_In_ DWORD DesiredAccess, //请求的访问令牌访问类型 可TOKEN_ALL_ACCESS
_Outptr_ PHANDLE TokenHandle //返回ProcessHandle进程关联的访问令牌句柄
)

不用时用CloseHandle关闭。

LookupPrivilegeValue

获取指定特权名称在系统中的本地唯一标识符LUID:

1
2
3
4
5
BOOL LookupPrivilegeValue(
_In_opt_ LPCTSTR lpSystemName, //系统名称 本地系统NULL
_In_ LPCTSTR lpName, //特权名称 如调试权限SE_DEBUG_NAME
_Out_ PLUID lpLuid //返回lpName在lpSystemName系统中LUID
)

AdjustTokenPrivileges

启用访问令牌句柄中指定的特权名称:

1
2
3
4
5
6
7
8
BOOL AdjustTokenPrivileges(
_In_ HANDLE TokenHandle, //进程访问令牌句柄
_In_ BOOL DisableAllPrivileges, //是否禁用所有特权 一般FALSE
_In_opt_ PTOKEN_PRIVILEGES NewState,
_In_ DWORD BufferLength, //PreviousState缓冲区大小 单位字节
_Out_opt_ PTOKEN_PRIVILEGES PreviousState, //返回进程的先前特权状态
_Out_opt_ PDWORD ReturnLength //返回PreviousState参数所需缓冲区大小
)

其中TOKEN_PRIVILEGES结构:

1
2
3
4
5
6
7
8
9
typedef struct _TOKEN_PRIVILEGES {
DWORD PrivilegeCount; //Privileges数组元素个数
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
typedef struct _LUID_AND_ATTRIBUTES {
LUID Luid; //代表特权名称的LUID
DWORD Attributes; //启用SE_PRIVILEGE_ENABLE 移除SE_PRIVILEGE_REMOVED 禁用None
} LUID_AND_ATTRIBUTES, * PLUID_AND_ATTRIBUTES;
#define ANYSIZE_ARRAY 1

例子

注意Windows中不存在暂停和恢复进程的概念,暂停一个进程中所有线程时,指定TH32CS_SNAPTHREAD标志获取线程列表,并改用Thread32FirstThread32Next枚举。同理枚举模块时指定TH32CS_SNAPMODULE并使用Module32FirstModule32Next,枚举堆时指定TH32CS_SNAPHEAPLIST并使用Heap32FirstheHeap32Next

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#include <windows.h>
#include <Commctrl.h>
#include <TlHelp32.h>
#include <Psapi.h>
#include <tchar.h>
#include "resource.h"
#pragma comment(lib, "Comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 全局变量
HINSTANCE g_hInstance;
HWND g_hwndDlg; // 对话框窗口句柄
HIMAGELIST g_hImagListSmall; // 列表视图控件所用的图像列表
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 显示进程列表
BOOL GetProcessList();
// 提升本进程的权限
BOOL AdjustPrivileges(HANDLE hProcess, LPCTSTR lpPrivilegeName = SE_DEBUG_NAME);
// 暂停、恢复进程
VOID SuspendProcess(DWORD dwProcessId, BOOL bSuspend);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
g_hInstance = hInstance;
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam){
LVCOLUMN lvc = { 0 };
POINT pt = { 0 };
int nSelected, nRet;
LVITEM lvi = { 0 };
TCHAR szProcessName[MAX_PATH] = { 0 }, szProcessID[16] = { 0 }, szBuf[MAX_PATH] = { 0 };
HANDLE hProcess;
HMENU hMenu;
BOOL bRet = FALSE;
switch (uMsg){
case WM_INITDIALOG: {
g_hwndDlg = hwndDlg;
// 设置列表视图控件的扩展样式
SendMessage(GetDlgItem(hwndDlg, IDC_LIST_PROCESS), LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
// 设置列标题:进程名称、进程ID、父进程ID、可执行文件路径
lvc.mask = LVCF_SUBITEM | LVCF_WIDTH | LVCF_TEXT;
lvc.iSubItem = 0; lvc.cx = 150; lvc.pszText = TEXT("进程名称");
SendMessage(GetDlgItem(hwndDlg, IDC_LIST_PROCESS), LVM_INSERTCOLUMN, 0, (LPARAM)&lvc);
lvc.iSubItem = 1; lvc.cx = 60; lvc.pszText = TEXT("进程ID");
SendMessage(GetDlgItem(hwndDlg, IDC_LIST_PROCESS), LVM_INSERTCOLUMN, 1, (LPARAM)&lvc);
lvc.iSubItem = 2; lvc.cx = 60; lvc.pszText = TEXT("父进程ID");
SendMessage(GetDlgItem(hwndDlg, IDC_LIST_PROCESS), LVM_INSERTCOLUMN, 2, (LPARAM)&lvc);
lvc.iSubItem = 3; lvc.cx = 260; lvc.pszText = TEXT("可执行文件路径");
SendMessage(GetDlgItem(hwndDlg, IDC_LIST_PROCESS), LVM_INSERTCOLUMN, 3, (LPARAM)&lvc);
// 为列表视图控件设置图像列表
g_hImagListSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_MASK | ILC_COLOR32, 500, 0);
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM)g_hImagListSmall);
// 提升本进程的权限
AdjustPrivileges(GetCurrentProcess());
// 显示进程列表
GetProcessList();
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case ID_REFRESH: {
// 显示进程列表
GetProcessList();
break;
};
case ID_TERMINATE: {
// 结束选定进程
nSelected = SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETSELECTIONMARK, 0, 0);
// 确定要结束进程吗?
lvi.iItem = nSelected; lvi.iSubItem = 0;
lvi.mask = LVIF_TEXT;
lvi.pszText = szProcessName;
lvi.cchTextMax = _countof(szProcessName);
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETITEM, 0, (LPARAM)&lvi);
wsprintf(szBuf, TEXT("确定要结束 %s 进程吗?"), lvi.pszText);
nRet = MessageBox(hwndDlg, szBuf, TEXT("结束进程"), MB_OKCANCEL | MB_ICONINFORMATION | MB_DEFBUTTON2);
if (nRet == IDCANCEL)
return FALSE;
// 获取进程句柄
lvi.iSubItem = 1;
lvi.pszText = szProcessID;
lvi.cchTextMax = _countof(szProcessID);
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETITEM, 0, (LPARAM)&lvi);
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, _ttoi(lvi.pszText));
if (hProcess) {
// 结束进程
bRet = TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
};
if (!bRet) {
wsprintf(szBuf, TEXT("结束 %s 进程失败"), szProcessName);
MessageBox(hwndDlg, szBuf, TEXT("错误提示"), MB_OK);
}
else
// 删除列表项
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_DELETEITEM, nSelected, 0);
break;
};
case ID_OPEN: {
// 打开文件所在位置
nSelected = SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETSELECTIONMARK, 0, 0);
lvi.iItem = nSelected; lvi.iSubItem = 3;
lvi.mask = LVIF_TEXT;
lvi.pszText = szProcessName;
lvi.cchTextMax = _countof(szProcessName);
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETITEM, 0, (LPARAM)&lvi);
// 打开父目录并选定指定文件的命令:Explorer.exe /select,文件名称
wsprintf(szBuf, TEXT("/select,%s"), lvi.pszText);
ShellExecute(hwndDlg, TEXT("open"), TEXT("Explorer.exe"), szBuf, NULL, SW_SHOW);
break;
};
case ID_SUSPEND: {
// 暂停进程
nSelected = SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETSELECTIONMARK, 0, 0);
lvi.iItem = nSelected; lvi.iSubItem = 1;
lvi.mask = LVIF_TEXT;
lvi.pszText = szProcessID;
lvi.cchTextMax = _countof(szProcessID);
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETITEM, 0, (LPARAM)&lvi);
SuspendProcess(_ttoi(lvi.pszText), TRUE);
break;
};
case ID_RESUME: {
// 恢复进程
nSelected = SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETSELECTIONMARK, 0, 0);
lvi.iItem = nSelected; lvi.iSubItem = 1;
lvi.mask = LVIF_TEXT;
lvi.pszText = szProcessID;
lvi.cchTextMax = _countof(szProcessID);
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETITEM, 0, (LPARAM)&lvi);
SuspendProcess(_ttoi(lvi.pszText), FALSE);
break;
};
case IDCANCEL: {
ImageList_Destroy(g_hImagListSmall);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_NOTIFY: {
if (((LPNMHDR)lParam)->idFrom == IDC_LIST_PROCESS && ((LPNMHDR)lParam)->code == NM_RCLICK) {
if (((LPNMITEMACTIVATE)lParam)->iItem < 0)
return FALSE;
// 如果可执行文件路径一列为空,则禁用结束该进程、打开文件所在位置、暂停进程、结束进程菜单
nSelected = SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETSELECTIONMARK, 0, 0);
hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_MENU));
lvi.iItem = nSelected; lvi.iSubItem = 3;
lvi.mask = LVIF_TEXT;
lvi.pszText = szProcessName;
lvi.cchTextMax = _countof(szProcessName);
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETITEM, 0, (LPARAM)&lvi);
if (_tcsicmp(lvi.pszText, TEXT("")) == 0) {
EnableMenuItem(hMenu, ID_TERMINATE, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_OPEN, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_SUSPEND, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(hMenu, ID_RESUME, MF_BYCOMMAND | MF_DISABLED);
};
// 弹出快捷菜单
GetCursorPos(&pt);
TrackPopupMenu(GetSubMenu(hMenu, 0), TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, hwndDlg, NULL);
};
return TRUE;
};
};
return FALSE;
};
BOOL GetProcessList() {
HANDLE hSnapshot;
PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) };
BOOL bRet;
HANDLE hProcess;
TCHAR szPath[MAX_PATH] = { 0 };
TCHAR szBuf[16] = { 0 };
DWORD dwLen;
SHFILEINFO fi = { 0 };
int nImage;
LVITEM lvi = { 0 };
// 删除图像列表中的所有图像
ImageList_Remove(g_hImagListSmall, -1);
// 删除所有列表项
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_DELETEALLITEMS, 0, 0);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
MessageBox(g_hwndDlg, TEXT("CreateToolhelp32Snapshot函数调用失败"), TEXT("提示"), MB_OK);
return FALSE;
};
bRet = Process32First(hSnapshot, &pe);
while (bRet) {
nImage = -1;
ZeroMemory(szPath, sizeof(szPath));
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
if (hProcess) {
// 获取可执行文件路径
dwLen = _countof(szPath);
QueryFullProcessImageName(hProcess, 0, szPath, &dwLen);
// 获取程序图标
SHGetFileInfo(szPath, 0, &fi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_SMALLICON);
if (fi.hIcon)
nImage = ImageList_AddIcon(g_hImagListSmall, fi.hIcon);
CloseHandle(hProcess);
};
lvi.mask = LVIF_TEXT | LVIF_IMAGE;
lvi.iItem = SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_GETITEMCOUNT, 0, 0);
// 第1列,进程名称
lvi.iSubItem = 0; lvi.pszText = pe.szExeFile; lvi.iImage = nImage;
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_INSERTITEM, 0, (LPARAM)&lvi);
if (fi.hIcon)
DestroyIcon(fi.hIcon);
// 第2列,进程ID
lvi.mask = LVIF_TEXT;
lvi.iSubItem = 1; _itot_s(pe.th32ProcessID, szBuf, _countof(szBuf), 10); lvi.pszText = szBuf;
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_SETITEM, 0, (LPARAM)&lvi);
// 第3列,父进程ID
lvi.iSubItem = 2; _itot_s(pe.th32ParentProcessID, szBuf, _countof(szBuf), 10); lvi.pszText = szBuf;
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_SETITEM, 0, (LPARAM)&lvi);
// 第4列,可执行文件路径
lvi.iSubItem = 3; lvi.pszText = szPath;
SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_PROCESS), LVM_SETITEM, 0, (LPARAM)&lvi);
bRet = Process32Next(hSnapshot, &pe);
};
CloseHandle(hSnapshot);
return TRUE;
};
VOID SuspendProcess(DWORD dwProcessId, BOOL bSuspend) {
HANDLE hSnapshot = INVALID_HANDLE_VALUE;
THREADENTRY32 te = { sizeof(THREADENTRY32) };
BOOL bRet = FALSE;
HANDLE hThread = NULL;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return;
bRet = Thread32First(hSnapshot, &te);
while (bRet) {
if (te.th32OwnerProcessID == dwProcessId) {
hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
if (hThread) {
if (bSuspend)
SuspendThread(hThread);
else
ResumeThread(hThread);
// 关闭线程句柄
CloseHandle(hThread);
};
};
bRet = Thread32Next(hSnapshot, &te);
};
CloseHandle(hSnapshot);
return;
};
BOOL AdjustPrivileges(HANDLE hProcess, LPCTSTR lpPrivilegeName) {
HANDLE hToken;
TOKEN_PRIVILEGES tokenPrivileges;
if (OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken)) {
LUID luid;
if (LookupPrivilegeValue(NULL, lpPrivilegeName, &luid)) {
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
tokenPrivileges.Privileges[0].Luid = luid;
if (AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL))
return TRUE;
};
CloseHandle(hToken);
};
return FALSE;
};

EnumProcesses法

EnumProceses

系统维护正在运行的进程的列表,用这个获取进程ID。

1
2
3
4
5
BOOL EnumProcesses(
_Out_ PDWORD lpidProcess, //接收进程ID列表
_In_ DWORD cb, //lpidProcess数组大小 单位字节
_Out_ LPDWORD lpcbNeeded //返回字节数 进程个数即为*lpcbNeeded/sizeof(DWORD)
)

NtQueryInformationProcess/ZwQueryInformationProcess

获取指定进程信息:

1
2
3
4
5
6
7
8
9
#include <Winternl.h>
#pragma comment(lib,"Ntdll.lib")
NTSTATUS WINAPI NtQueryInformationProcess(
_In_ HANDLE ProcessHandle, //进程句柄
_In_ PROCESSINFOCLASS ProcessInformationClass, //要获取的进程信息类型
_Out_ PVOID ProcessInformation, //返回所请求信息的缓冲区
_In_ ULONG ProcessInformationLength, //ProcessInformation缓冲区大小 单位字节
_Out_opt_ PULONG ReturnLength //请求信息大小
)

其中ProcessInformationClass可以是:

枚举值 含义 ProcessInformation类型 上层封装
ProcessBasicInformation 是否正在被调试的进程环境快PEB结构、进程ID、父进程ID等 PROCESS_BASIC_INFORMATION CheckRemoteDebuggerPresentGetProcessId
ProcessDebugPort 该进程调试器的端口号,非0表示在Ring3调试器控制下运行 DWORD_PTR CheckRemoteDebuggerPresentIsDebuggerPresent
ProcessWow64Information 非0表示该进程正在WOW64环境中运行,0反之 ULONG_PTR IsWow64Process
ProcessImageFileName 该进程文件名称字段 UNICODE_STRING QueryFullProcessImageNameGetProcessImageFileName
ProcessBreakOnTermination 非0表示系统关键进程,0反之,需要PROCESS_QUERY_LIMITED_INFORMATION权限 ULONG IsProcessCritical

对于PROCESS_BASIC_INFORMATION结构有:

1
2
3
4
5
6
7
8
typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
PPEB PebBaseAddress; //进程环境块PEB结构指针
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId; //进程ID
PVOID Reserved3; //父进程ID
} PROCESS_BASIC_INFORMATION;
typedef PROCESS_BASIC_INFORMATION *PPROCESS_BASIC_INFORMATION;

系统关键进程

LoadLibrary

将指定模块加载到调用进程地址空间中:

1
2
3
HMODULE LoadLibrary(
_In_ LPCTSTR lpLibFileName //模块名称 可相对路径或绝对路径
) //失败NULL

注意该函数只是一个宏,实际为LoadLibraryALoadLibraryW

LoadLibraryEx

LoadLibrary,可指定加载选项:

1
2
3
4
5
HMODULE WINAPI LoadLibraryEx(
_In_ LPCTSTR lpLibFileName,
_Reserved_ HANDLE hFile, //必须NULL
_In_ DWORD dwFlags //加载选项
);

当dwFlags为0时同LoadLibrary,常用选项有:

枚举值 含义
DONT_RESOLVE_DLL_REFERENCES lpLibFileName指定的为DLL模块时系统不会调用其DllMain,也不会加载其引用的其他模块
LOAD_LIBRARY_AS_DATAFILE 把lpLibFileName指定的模块作为数据文件映射到调用进程虚拟地址空间,映射后没有可执行属性,函数返回模块句柄,可与LOAD_LIBRARY_AS_IMAGE_RESOURCE一同使用
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE 同LOAD_LIBRARY_AS_DATAFILE,但以独占访问模式打开模块文件。
LOAD_LIBRARY_AS_IMAGE_RESOURCE 把lpLibFileName指定的模块作为数据文件映射到调用进程虚拟地址空间,并对模块中相对虚拟地址RVA修复使用户可直接使用模块中虚拟地址而不必根据映射到的地址进行转换

FreeLibrary

释放模块并减少引用次数:

1
2
3
BOOL FreeLibrary(
_In_ HMODULE hLibModule //模块句柄
)

GetProcAddress

获取其中一个函数地址:

1
2
3
4
5
typedef INT_PTR (FAR WINAPI *FARPROC)();
FARPROC GetProcAddress(
_In_ HMODULE hModule, //模块句柄
_In_ LPCSTR lpProcName //函数名称/序数 区分大小写
) //失败NULL

RtlSetProcessIsCritical

未公开API,将一个进程设置为系统关键进程,强制结束将导致系统崩溃。这玩意儿也不知道咋用,暂且定义如下:

1
2
3
4
5
NTSTATUS RtlSetProcessIsCritical(
_In_ BOOL NewValue,
_Out_opt_ PBOOL OldValue,
_In_ BOOL CheckFlag
)

例子

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
#include <windows.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
typedef NTSTATUS(__cdecl* pfnRtlSetProcessIsCritical)(_In_ BOOL NewValue, _Out_opt_ PBOOL OldValue, _In_ BOOL CheckFlag);
pfnRtlSetProcessIsCritical pRtlSetProcessIsCritical;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static HMODULE hNtdll;
switch (uMsg) {
case WM_INITDIALOG: {
hNtdll = LoadLibrary(TEXT("Ntdll.dll"));
if (hNtdll) {
pRtlSetProcessIsCritical = (pfnRtlSetProcessIsCritical)GetProcAddress(hNtdll, "RtlSetProcessIsCritical");
if (pRtlSetProcessIsCritical)
pRtlSetProcessIsCritical(TRUE, NULL, FALSE);
else
FreeLibrary(hNtdll);
};
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDCANCEL: {
if (pRtlSetProcessIsCritical)
pRtlSetProcessIsCritical(FALSE, NULL, FALSE);
if (hNtdll)
FreeLibrary(hNtdll);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

进程环境块PEB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged; //当前进程是否正被调试 0或1
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId; //会话ID
} PEB, * PPEB;

Ldr字段

1
2
3
4
5
typedef struct _PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList; //进程已加载模块的双向链表头
} PEB_LDR_DATA, * PPEB_LDR_DATA;

其中LIST_ENTRY作双向链表头和节点定义分别为:

1
2
3
4
5
6
7
8
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY* Flink; //指向第一个节点 空链表则指向链表头
struct _LIST_ENTRY* Blink; //指向最后一个节点 空链表则指向链表头
} LIST_ENTRY, * PLIST_ENTRY, * RESTRICTED_POINTER PRLIST_ENTRY;
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY* Flink; //指向下一个节点 最后一个节点指向链表头
struct _LIST_ENTRY* Blink; //指向上一个节点 第一个节点指向链表头
} LIST_ENTRY, * PLIST_ENTRY, * RESTRICTED_POINTER PRLIST_ENTRY;

LIST_ENTRY通过CONTAINING_RECORD计算LDR_DATA_TABLE_ENTRY结构地址:

1
#define CONTAINING_RECORD(address, type, field) ((type*)((PCHAR)(address)-(ULONG_PTR)(&((type*)0)->field)))

其中LDR_DATA_TABLE_ENTRY有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase; //模块基地址
PVOID Reserved3[2];
UNICODE_STRING FullDllName; //模块文件完整路径
BYTE Reserved4[8];
PVOID Reserved5[3];
union {
ULONG CheckSum; //校验和
PVOID Reserved6;
} DUMMYUNIONNAME;
ULONG TimeDateStamp; //时间戳
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;

例如枚举指定进程已加载模块信息:

1
2
3
4
5
6
7
8
9
10
PROCESS_BASIC_INFORMATION pbi = { 0 };
PLIST_ENTRY pListEntry = NULL;
PLDR_DATA_TABLE_ENTRY pDataTableEntry = NULL;
NtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL);
pListEntry = pbi.PebBaseAddress->Ldr->InMemoryOrderModuleList.Flink; //第一个节点
if (pListEntry != &(pbi.PebBaseAddress->Ldr->InMemoryOrderModuleList)) //链表为空则第一个节点指向链表头
while (pListEntry != &(pbi.PebBaseAddress->Ldr->InMemoryOrderModuleList)) { //最后一个节点的Flink指向链表头
pDataTableEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
pListEntry = pListEntry->Flink; //指向下一个节点
};

ProcessParameters字段

1
2
3
4
5
6
typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[16];
PVOID Reserved2[10];
UNICODE_STRING ImagePathName; //进程文件完整路径
UNICODE_STRING CommandLine; //传递给进程的命令行参数
} RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS;

在x64下可以修改,但没法进行伪装。

进程调试

进程空间读写

ReadProcessMemory

读指定进程地址空间地址处内存数据:

1
2
3
4
5
6
7
BOOL WINAPI ReadProcessMemory(
_In_ HANDLE hProcess, //进程句柄 需要PROCESS_VM_READ权限
_In_ LPCVOID lpBaseAddress, //hProcess进程中基地址
_Out_ LPVOID lpBuffer, //返回hProcess进程中从lpBaseAddress开始的nSize字节数据
_In_ SIZE_T nSize, //要读取的字节数
_Out_opt_ PSIZE_T lpNumberOfBytesRead //返回实际读取字节数
)

WriteProcessMemory

向指定进程地址空间地址处写入数据:

1
2
3
4
5
6
7
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess, //需要PROCESS_VM_WRITE和PROCESS_VM_OPERATION权限
_In_ LPVOID lpBaseAddress,
_In_ LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ PSIZE_T lpNumberOfBytesWritten
)

例子

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
46
47
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szCommandLine[MAX_PATH] = TEXT("Test.exe");
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
LPVOID lpBaseAddress = (LPVOID)0x009E1009;
WORD wCodeOld, wCodeNew = 0x9090;
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_LOADTEST: {
GetStartupInfo(&si);
if (CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
if (ReadProcessMemory(pi.hProcess, lpBaseAddress, &wCodeOld, sizeof(WORD), NULL)) {
// 目标进程lpBaseAddress地址处的数据内容是否为0x1774,如果是则替换
if (wCodeOld == 0x1774) {
// 改写机器码
WriteProcessMemory(pi.hProcess, lpBaseAddress, &wCodeNew, sizeof(WORD), NULL);
ResumeThread(pi.hThread);
}
else {
MessageBox(hwndDlg, TEXT("目标软件版本错误"), TEXT("错误提示"), MB_OK);
TerminateProcess(pi.hProcess, 0);
};
};
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
};
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

获取模块基地址

EnumProcessModules法

获取进程中每个模块句柄:

1
2
3
4
5
6
BOOL WINAPI EnumProcessModules(
_In_ HANDLE hProcess, //进程句柄
_Out_ PMODULE lphModule, //接收模块句柄列表的数组
_In_ DWORD cb, //lphModule数组大小 单位字节
_Out_ LPDWORD lpcbNeeded //返回所需字节数 模块个数为*lpcbNeeded/sizeof(HMODULE)
)

其中第一个模块句柄就是主程序模块的句柄。例如获取主程序模块基地址:

1
2
3
HMODULE hModule;
DWORD dwNeeded;
EnumProcessModules(hProcess, &hModule, sizeof(HMODULE), &dwNeeded);

VirtualQueryEx法

通过GetThreadContext获取目标进程主线程环境,context.Eax寄存器为程序入口点地址。再用VirutalQueryEx查询进程虚拟地址空间页面信息,其中MEMORY_BASIC_INFORMATION.AllocationBase是空间区域/可执行模块基地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TCHAR szCommandLine[MAX_PATH] = TEXT("ThreeThousandYears.exe"); // 目标程序
MEMORY_BASIC_INFORMATION mbi = { 0 }; // VirtualQueryEx参数
SIZE_T nBufSize; // VirtualQueryEx返回值
TCHAR szImageFile[MAX_PATH] = { 0 }; // 目标程序完整路径
TCHAR szBuf[MAX_PATH * 2] = { 0 }; // 缓冲区
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
CONTEXT context = { 0 };
// 创建一个挂起的进程
GetStartupInfo(&si);
CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
// 获取目标进程主线程环境
context.ContextFlags = CONTEXT_ALL;
GetThreadContext(pi.hThread, &context);
// context.Eax是程序入口点地址
nBufSize = VirtualQueryEx(pi.hProcess, (LPVOID)context.Rax, &mbi, sizeof(mbi));
if (nBufSize > 0) {
GetMappedFileName(pi.hProcess, (LPVOID)context.Rax, szImageFile, _countof(szImageFile));
wsprintf(szBuf, TEXT("%s 基地址:0x%p"), szImageFile, mbi.AllocationBase);
MessageBox(NULL, szBuf, TEXT("提示"), MB_OK);
};

GetSystemInfo法

以暂停模式启动目标进程后,可执行模块本身已经在内存中映射,但依赖的DLL模块还没有映射。用GetSystemInfo查询进程地址空间最小/大内存地址、页面大小,从最小内存地址lpMinAppAddress开始递增查找第一个MEM_IMAGE类型已提交页面即属于可执行模块空间区域。MEMORY_BASIC_INFORMATION.AllocationBase是空间区域/可执行模块基地址。

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
//变量继承VirtualQueryEx法
SYSTEM_INFO systemInfo = { 0 };
LPVOID lpMinAppAddress = NULL;
// 获取进程地址空间的最小、最大内存地址,和页面大小
GetSystemInfo(&systemInfo);
lpMinAppAddress = systemInfo.lpMinimumApplicationAddress;
// 从最小内存地址开始查找第一个具有MEM_IMAGE存储类型的页面(页面状态为已提交)
while (lpMinAppAddress < systemInfo.lpMaximumApplicationAddress) {
ZeroMemory(&mbi, sizeof(MEMORY_BASIC_INFORMATION));
nBufSize = VirtualQueryEx(pi.hProcess, lpMinAppAddress, &mbi, sizeof(mbi));
if (nBufSize == 0) {
lpMinAppAddress = (LPBYTE)lpMinAppAddress + systemInfo.dwPageSize;
continue;
};
switch (mbi.State) {
case MEM_RESERVE:
case MEM_FREE: {
lpMinAppAddress = (LPBYTE)(mbi.BaseAddress) + mbi.RegionSize;
break;
};
case MEM_COMMIT: {
if (mbi.Type == MEM_IMAGE) {
GetMappedFileName(pi.hProcess, lpMinAppAddress, szImageFile, _countof(szImageFile));
wsprintf(szBuf, TEXT("%s 基地址:0x%p"), szImageFile, mbi.AllocationBase);
MessageBox(NULL, szBuf, TEXT("提示"), MB_OK);
break;
};
lpMinAppAddress = (LPBYTE)(mbi.BaseAddress) + mbi.RegionSize;
break;
};
};
// 找到了就退出循环
if (mbi.Type == MEM_IMAGE)
break;
};
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);

调试API

DebugActiveProcess

使进程进入被调试状态:

1
2
3
BOOL WINAPI DebugActiveProcess(
_In_ DWORD dwProcessId //进程ID
)

DebugActiveProcessStop

停止堆指定进程的调试:

1
2
3
BOOL DebugActiveProcessStop(
_In_ DWORD dwProcessId //进程ID
)

WaitForDebugEvent

等待正在调试的进程发生调试事件:

1
2
3
4
BOOL WINAPI WaitForDebugEvent(
_Out_ LPDEBUG_EVENT lpDebugEvent, //返回调试事件信息
_In_ DWORD dwMilliseconds //等待调试事件发生的毫秒数 0立即返回 INFINITE一直等待
)

其中DEBUG_EVENT有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode; //调试事件类型
DWORD dwProcessId; //发生调试事件的进程ID
DWORD dwThreadId; //发生调试事件的线程ID
union {
EXCEPTION_DEBUG_INFO Exception; //EXCEPTION_DEBUG_EVENT
CREATE_THREAD_DEBUG_INFO CreateThread; //CREATE_THREAD_DEBUG_EVENT
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo; //CREATE_PROCESS_DEBUG_EVENT
EXIT_THREAD_DEBUG_INFO ExitThread; //EXIT_THREAD_DEBUG_EVENT
EXIT_PROCESS_DEBUG_INFO ExitProcess; //EXIT_PROCESS_DEBUG_EVENT
LOAD_DLL_DEBUG_INFO LoadDll; //LOAD_DLL_DEBUG_EVENT
UNLOAD_DLL_DEBUG_INFO UnloadDll; //UNLOAD_DLL_DEBUG_EVENT
OUTPUT_DEBUG_STRING_INFO DebugString; //OUTPUT_DEBUG_STRING_EVENT
RIP_INFO RipInfo; //RIP_EVENT
} u;
} DEBUG_EVENT, * LPDEBUG_EVENT;

不同调试事件类型有:

调试事件类型 含义
EXCEPTION_DEBUG_EVENT 被调试进程发生异常事件或被调试进程开始执行第一条指令
CREATE_THREAD_DEBUG_EVENT 被调试进程创建了一个新线程,主线程除外
CREATE_PROCESS_DEBUG_EVENT 被调试进程被创建但还未开始运行,或正在运行的进程被附加到调试器
EXIT_THREAD_DEBUG_EVENT 被调试进程中有线程结束
EXIT_PROCESS_DEBUG_EVENT 被调试进程退出
LOAD_DLL_DEBUG_EVENT 被调试进程加载一个DLL,或系统根据导入表加载DLL,或被调试进程用LoadLibrary加载DLL
UNLOAD_DLL_DEBUG_EVENT 一个DLL从被调试进程中卸载时
OUTPUT_DEBUG_STRING_EVENT 被调试进程用DebugOutputString
RIP_EVENT 调试发生错误

对于CREATE_THREAD_DEBUG_EVENT,u.CreateProcessInfo字段有CREATE_PROCESS_DEBUG_INFO结构:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _CREATE_PROCESS_DEBUG_INFO {
HANDLE hFile; //进程可执行映像文件句柄
HANDLE hProcess; //进程句柄
HANDLE hThread; //初始线程句柄
LPVOID lpBaseOfImage; //可执行文件加载到的基地址
DWORD dwDebugInfoFileOffset; //可执行文件中调试信息偏移量
DWORD nDebugInfoSize; //文件中调试信息大小 单位字节
LPVOID lpThreadLocalBase; //线程本地存储相关
LPTHREAD_START_ROUTINE lpStartAddress; //指向线程起始地址
LPVOID lpImageName; //可执行文件名称
WORD fUnicode; //非0则lpImageName为Unicode 0表示ANSI
} CREATE_PROCESS_DEBUG_INFO, * LPCREATE_PROCESS_DEBUG_INFO;

对于EXCEPTION_DEBUG_EVENT,u.Exception字段有EXCEPTION_DEBUG_INFO结构:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode; //异常代码
DWORD ExceptionFlags; //异常标志
struct _EXCEPTION_RECORD* ExceptionRecord;
PVOID ExceptionAddress; //发生异常的地址
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
typedef struct _EXCEPTION_DEBUG_INFO {
EXCEPTION_RECORD ExceptionRecord;
DWORD dwFirstChance; //是否第一次发生异常
} EXCEPTION_DEBUG_INFO, * LPEXCEPTION_DEBUG_INFO;

其中ExceptionCode异常代码有:

枚举值 含义
EXCEPTION_BREAKPOINT 遇到断点,如int 3断点
EXCEPTION_SINGLE_STEP 跟踪陷阱或单步中断。单步中断指当发现TF位为1时触发异常并暂停,系统自动将TF置0。每条指令都单步中断时需要手动TF置1
EXCEPTION_ACCESS_VIOLATION 线程试图读取或写入对其没有适当访问权限的虚拟地址

注:标志寄存器前16位分别为:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
CF PF AF ZF SF TF IF DF OF

ExceptionsFlags为异常标志,为0表示程序可继续执行的异常;为EXCEPTION_NONCONTINUABLE表示程序不可继续执行的异常,这时继续执行会发生EXCEPTION_NONCONTINUABLE_EXCEPTION异常。

当发生嵌套异常时,ExceptionRecord指向EXCEPTION_RECORD结构,包含另一个异常的异常信息;没发生嵌套异常则为NULL。

ExceptionInformation数组为描述异常的附加参数,该字段通常为NULL。NumberParameters为ExceptionInformation数组元素个数,最多EXCEPTION_MAXIMUM_PARAMETERS个,通常为0。目前这两个字段只有EXCEPTION_ACCESS_VIOLATION和EXCEPTION_IN_PAGE_ERROR用到了。

对于EXCEPTION_ACCESS_VIOLATION,ExceptionInformation[0]为0表示试图读取非法地址,为1表示试图写入非法地址,为8表示数据执行保护DEP检测到线程执行没可执行权限内存页中的代码。ExceptionInformation[1]为不可访问数据的内存地址。对于EXCEPTION_IN_PAGE_ERROR的ExceptionInformation前两个元素同上,ExceptionInformation[2]表示导致异常的NTSTATUS代码。

ContinueDebugEvent

恢复被调试进程运行:

1
2
3
4
5
BOOL WINAPI ContinueDebugEvent(
_In_ DWORD dwProcessId, //被恢复运行的进程ID
_In_ DWORD dwThreadId, //被恢复运行的线程ID
_In_ DWORD dwContinueStatus //继续执行选项
)

dwProcessId和dwThreadId从DEBUG_EVENT中返回即可。有常用dwContinueStatus:

枚举值 含义
DBG_CONTINUE 当指定线程发生EXCEPTION_DEBUG_EVENT则停止所有异常处理并继续执行该线程,将异常标记为已处理;发生其他调试事件则继续执行
DBG_EXCEPTION_NOT_HANDLED 当指定线程发生EXCEPTION_DEBUG_EVENT则使用被调试进程结构化异常处理程序处理,否则进程终止;发生其他调试事件则继续执行
DBG_REPLY_LATER 继续执行指定线程,再次触发相同异常

例子

常见的调试器处理调试事件:

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
46
47
DEBUG_EVENT debugEvent;
while (TRUE) {
WaitForDebugEvent(&debugEvent, INFINITE);
if (debugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) {
switch (debugEvent.u.ExceptionRecord.ExceptionCode) {
case EXCEPTION_BREAKPOINT: { //断点中断
break;
};
case EXCEPTION_SINGLE_STEP: { //单步中断
break;
};
case EXCEPTION_ACCESS_VIOLATION: { //访问违规
break;
};
default: {
break;
};
};
}
else if (debugEvent.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT) {
//一般用GetThreadContext SetThreadContext SuspendThread ResumeThread
}
else if (debugEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
//一般用GetThreadContext SetThreadContext SuspendThread ResumeThread ReadProcessMemory WriteProcessMemory
CloseHandle(debugEvent.u.CreateProcessInfo.hFile);
}
else if (debugEvent.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT) {
//...
}
else if (debugEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) {
//...
}
else if (debugEvent.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT) {
//...
CloseHandle(debugEvent.u.LoadDll.hFile);
}
else if (debugEvent.dwDebugEventCode == UNLOAD_DLL_DEBUG_EVENT) {
//...
}
else if (debugEvent.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT) {
//...
}
else if (debugEvent.dwDebugEventCode == RIP_EVENT) {
//...
};
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
};

例子

内存读写:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szCommandLine[MAX_PATH] = TEXT("Test.exe");
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
LPVOID lpBaseAddr;
WORD wCodeOld, wCodeNew = 0x9090;
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_LOADTEST: {
GetStartupInfo(&si);
if (!CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi))
break;
DEBUG_EVENT debugEvent;
while (TRUE) {
// 等待调试事件发生
WaitForDebugEventEx(&debugEvent, INFINITE);
// 处理调试事件
if (debugEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
lpBaseAddr = (LPBYTE)(debugEvent.u.CreateProcessInfo.lpBaseOfImage) + 0x1009;
if (ReadProcessMemory(pi.hProcess, lpBaseAddr, &wCodeOld, sizeof(WORD), NULL)) {
// 目标进程lpBaseAddr地址处的数据内容是否为0x1774,如果是则替换
if (wCodeOld == 0x1774)
WriteProcessMemory(pi.hProcess, lpBaseAddr, &wCodeNew, sizeof(WORD), NULL);
else {
MessageBox(hwndDlg, TEXT("目标软件版本错误"), TEXT("提示"), MB_OK);
TerminateProcess(pi.hProcess, 0);
};
};
CloseHandle(debugEvent.u.CreateProcessInfo.hFile);
}
else if (debugEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) {
MessageBox(hwndDlg, TEXT("被调试进程退出"), TEXT("提示"), MB_OK);
break;
};
// 处理完一个调试事件以后调用ContinueDebugEvent恢复线程执行并继续等待下个调试事件
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
};
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

例子

UPX脱壳:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <windows.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
TCHAR szCommandLine[MAX_PATH] = TEXT("Test_UPX.exe");
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = { 0 };
static LPVOID lpPopad, lpPatch;// popad地址(基地址 + 0x18C4E),补丁地址(基地址 + 0x1000 + 0x9)
BYTE bInt3 = 0xCC; // popad指令地址处写入int 3指令的机器码0xCC
BYTE bOld = 0x61; // 恢复popad指令的机器码
WORD wCodeNew = 0x9090;
DEBUG_EVENT debugEvent;
CONTEXT context;
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_BTN_LOADTEST: {
GetStartupInfo(&si);
if (!CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi))
break;
while (TRUE) {
// 等待调试事件发生
WaitForDebugEvent(&debugEvent, INFINITE);
// 进程被创建
if (debugEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
// lpPopad, lpPatch
lpPopad = (LPBYTE)(debugEvent.u.CreateProcessInfo.lpBaseOfImage) + 0x18C4E;
lpPatch = (LPBYTE)(debugEvent.u.CreateProcessInfo.lpBaseOfImage) + 0x1000 + 0x9;
// popad指令处下int 3断点
WriteProcessMemory(pi.hProcess, lpPopad, &bInt3, 1, NULL);
CloseHandle(debugEvent.u.CreateProcessInfo.hFile);
}
// 被调试进程中发生异常事件
else if (debugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) {
switch (debugEvent.u.Exception.ExceptionRecord.ExceptionCode) {
case EXCEPTION_BREAKPOINT: { // 断点中断
context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(pi.hThread, &context);
// int 3指令执行以后才会发生异常,这时候eip已经指向了下一条指令
if (context.Rip == (DWORD)((LPBYTE)lpPopad + 1)) {
// popad指令处的int 3断点改回原popad指令
WriteProcessMemory(pi.hProcess, lpPopad, &bOld, 1, NULL);
// 内存补丁,JE指令修改为两个NOP指令
WriteProcessMemory(pi.hProcess, lpPatch, &wCodeNew, 2, NULL);
// 重新执行popad指令
context.Rip -= 1;
SetThreadContext(pi.hThread, &context);
};
break;
};
case EXCEPTION_SINGLE_STEP: { // 单步中断
break;
};
};
}
// 被调试进程退出
else if (debugEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
break;
// 处理完一个调试事件以后调用ContinueDebugEvent恢复线程执行并继续等待下个事件
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
};
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
break;
};
case IDCANCEL: {
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
};
return FALSE;
};

线程环境

GetThreadContext/SetThreadContext

获取/设置线程环境:

1
2
3
4
5
6
7
8
BOOL WINAPI GetThreadContext(
_In_ HANDLE hThread, //线程句柄
_Inout_ LPCONTEXT lpContext
);
BOOL WINAPI SetThreadContext(
_In_ HANDLE hThread,
_In_ CONST LPCONTEXT lpContext
);

其中CONTEXT在x64下为:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
typedef struct DECLSPEC_ALIGN(16) DECLSPEC_NOINITALL _CONTEXT {
DWORD64 P1Home;
DWORD64 P2Home;
DWORD64 P3Home;
DWORD64 P4Home;
DWORD64 P5Home;
DWORD64 P6Home;
DWORD ContextFlags; //线程环境标志
DWORD MxCsr;
WORD SegCs;
WORD SegDs;
WORD SegEs;
WORD SegFs;
WORD SegGs;
WORD SegSs;
DWORD EFlags;
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7;
DWORD64 Rax;
DWORD64 Rcx;
DWORD64 Rdx;
DWORD64 Rbx;
DWORD64 Rsp;
DWORD64 Rbp;
DWORD64 Rsi;
DWORD64 Rdi;
DWORD64 R8;
DWORD64 R9;
DWORD64 R10;
DWORD64 R11;
DWORD64 R12;
DWORD64 R13;
DWORD64 R14;
DWORD64 R15;
DWORD64 Rip;
union {
XMM_SAVE_AREA32 FltSave;
struct {
M128A Header[2];
M128A Legacy[8];
M128A Xmm0;
M128A Xmm1;
M128A Xmm2;
M128A Xmm3;
M128A Xmm4;
M128A Xmm5;
M128A Xmm6;
M128A Xmm7;
M128A Xmm8;
M128A Xmm9;
M128A Xmm10;
M128A Xmm11;
M128A Xmm12;
M128A Xmm13;
M128A Xmm14;
M128A Xmm15;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
M128A VectorRegister[26];
DWORD64 VectorControl;
DWORD64 DebugControl;
DWORD64 LastBranchToRip;
DWORD64 LastBranchFromRip;
DWORD64 LastExceptionToRip;
DWORD64 LastExceptionFromRip;
} CONTEXT, * PCONTEXT;

根据线程环境标志ContextFlag可多选,分为:

枚举值 所含寄存器 含义
CONTEXT_CONTROL SegSs, Rsp, SegCs, Rip, EFlags 控制寄存器
CONTEXT_INTEGER Rax, Rcx, Rdx, Rbx, Rbp, Rsi, Rdi, R8-R15 整数寄存器
CONTEXT_SEGMENTS SegDs, SegEs, SegFs, SegGs 段寄存器
CONTEXT_FLOATING_POINT Xmm0-Xmm15 浮点寄存器
CONTEXT_DEBUG_REGISTERS Dr0-Dr3, Dr6-Dr7 调试寄存器

注意用这俩函数前应暂停线程,获取/设置相关寄存器值后再恢复线程运行,防止函数执行到一半被Windows切走。例如更改Rip等值时需要目标线程的THREAD_GET_CONTEXT和TRHEAD_SET_CONTEXT权限,如:

1
2
3
4
CONTEXT Context;
Context.Rip = 0;
SetThreadContext(hThread, &Context);
ResumeThread(hThread);

Spy++

WindowFromPoint

获取指定坐标处窗口的窗口句柄,可能不准确:

1
2
3
HWND WINAPI WindowFromPoint(
_In_ POINT Point //指定坐标
);

GetParent

获取指定窗口的父窗口句柄:

1
2
3
HWND WINAPI GetParent(
_In_ HWND hWnd //指定窗口
);

GetWindow

获取与指定窗口具有指定关系的窗口句柄:

1
2
3
4
HWND WINAPI GetWindow(
_In_ HWND hWnd, //指定窗口
_In_ UINT uCmd //指定关系
);

uCmd可以有:

枚举值 含义
GW_HWNDFIRST 同一级别中Z顺序最高的窗口
GW_HWNDLAST 同一级别中Z顺序最低的窗口
GW_HWNDNEXT 同一级别中下一个Z顺序窗口
GW_HWNDPREV 同一级别中上一个Z顺序窗口
GW_CHILD Z顺序最高的,即第一个子窗口

GetWindowThreadProcessId

根据窗口句柄获取创建该窗口的进程/线程ID:

1
2
3
4
DWORD WINAPI GetWindowThreadProcessId(
_In_ HWND hWnd, //窗口句柄
_Out_opt_ LPDWORD lpdwProcessId //返回创建该窗口的进程ID
); //成功返回创建该窗口的线程ID

例子

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include <windows.h>
#include <TlHelp32.h>
#include "resource.h"
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 全局变量
HINSTANCE g_hInstance;
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 获取目标窗口句柄
HWND SmallestWindowFromPoint(POINT pt);
// 获取父进程ID
DWORD GetParentProcessIDByID(DWORD dwProcessId);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
g_hInstance = hInstance;
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam){
static HCURSOR hCursorDrag; // 拖动时的光标句柄
static HICON hIconNormal; // 正常情况下图像静态控件所用的图标句柄
static HICON hIconDrag; // 拖动时的图像静态控件所用的图标句柄
static HWND hwndTarget; // 目标窗口句柄
static HDC hdcDesk; // 桌面设备环境句柄,用于在目标窗口周围绘制闪动矩形
RECT rect;
POINT pt;
DWORD dwProcessID, dwParentProcessID, dwCtrlID;
TCHAR szBuf[128] = { 0 };
LPTSTR lpBuf = NULL;
int nLen;
switch (uMsg){
case WM_INITDIALOG: {
// 为对话框程序左上角设置一个图标
SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON_MAIN)));
// 拖动时的光标句柄,正常情况下和拖动时的图像静态控件所用的图标句柄
hCursorDrag = LoadCursor(g_hInstance, MAKEINTRESOURCE(IDC_CURSOR_DRAG));
hIconNormal = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON_NORMAL));
hIconDrag = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON_DRAG));
// 桌面设备环境,用于在目标窗口周围绘制闪动矩形
hdcDesk = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
SelectObject(hdcDesk, CreatePen(PS_SOLID, 2, RGB(255, 0, 255)));
SetROP2(hdcDesk, R2_NOTXORPEN);
return TRUE;
};
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDC_CHK_TOPMOST: {
// 窗口置顶
if (IsDlgButtonChecked(hwndDlg, IDC_CHK_TOPMOST) == BST_CHECKED)
SetWindowPos(hwndDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
else
SetWindowPos(hwndDlg, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
break;
};
case IDC_BTN_MODIFYTITLE: {
// 修改标题
nLen = SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_WINDOWTITLE), WM_GETTEXTLENGTH, 0, 0);
lpBuf = new TCHAR[nLen + 1];
SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_WINDOWTITLE), WM_GETTEXT, (nLen + 1), (LPARAM)lpBuf);
SendMessage(hwndTarget, WM_SETTEXT, 0, (LPARAM)lpBuf);
delete[] lpBuf;
break;
};
case IDCANCEL: {
DeleteDC(hdcDesk);
EndDialog(hwndDlg, 0);
break;
};
};
return TRUE;
};
case WM_LBUTTONDOWN: {
// 开始拖动
GetWindowRect(GetDlgItem(hwndDlg, IDC_STATIC_ICON), &rect);
GetCursorPos(&pt);
if (PtInRect(&rect, pt)) {
SetCapture(hwndDlg);
SetCursor(hCursorDrag);
SendMessage(GetDlgItem(hwndDlg, IDC_STATIC_ICON), STM_SETIMAGE, IMAGE_ICON, (LPARAM)hIconDrag);
SetTimer(hwndDlg, 1, 200, NULL);
};
return TRUE;
};
case WM_LBUTTONUP: {
// 停止拖动
ReleaseCapture();
SendMessage(GetDlgItem(hwndDlg, IDC_STATIC_ICON), STM_SETIMAGE, IMAGE_ICON, (LPARAM)hIconNormal);
KillTimer(hwndDlg, 1);
return TRUE;
};
case WM_TIMER: {
GetCursorPos(&pt);
hwndTarget = SmallestWindowFromPoint(pt);
// 显示窗口句柄
wsprintf(szBuf, TEXT("0x%08X"), (UINT_PTR)hwndTarget);
SetDlgItemText(hwndDlg, IDC_EDIT_WINDOWHANDLE, szBuf);
// 显示窗口类名
GetClassName(hwndTarget, szBuf, _countof(szBuf));
SetDlgItemText(hwndDlg, IDC_EDIT_CLASSNAME, szBuf);
// 显示窗口过程
wsprintf(szBuf, TEXT("0x%08X"), (ULONG_PTR)GetClassLongPtr(hwndTarget, GCLP_WNDPROC));
SetDlgItemText(hwndDlg, IDC_EDIT_WNDPROC, szBuf);
// 如果是子窗口控件,显示ID
if (dwCtrlID = GetDlgCtrlID(hwndTarget)) {
wsprintf(szBuf, TEXT("%d"), dwCtrlID);
SetDlgItemText(hwndDlg, IDC_EDIT_CONTROLID, szBuf);
}
else
SetDlgItemText(hwndDlg, IDC_EDIT_CONTROLID, TEXT(""));
// 显示窗口标题
nLen = SendMessage(hwndTarget, WM_GETTEXTLENGTH, 0, 0);
if (nLen > 0) {
lpBuf = new TCHAR[nLen + 1];
SendMessage(hwndTarget, WM_GETTEXT, (nLen + 1), (LPARAM)lpBuf);
SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_WINDOWTITLE), WM_SETTEXT, 0, (LPARAM)lpBuf);
delete[] lpBuf;
}
else
SendMessage(GetDlgItem(hwndDlg, IDC_EDIT_WINDOWTITLE), WM_SETTEXT, 0, (LPARAM)TEXT(""));
// 显示进程ID
GetWindowThreadProcessId(hwndTarget, &dwProcessID);
wsprintf(szBuf, TEXT("%d"), dwProcessID);
SetDlgItemText(hwndDlg, IDC_EDIT_PROCESSID, szBuf);
// 显示父进程ID
if ((dwParentProcessID = GetParentProcessIDByID(dwProcessID)) >= 0) {
wsprintf(szBuf, TEXT("%d"), dwParentProcessID);
SetDlgItemText(hwndDlg, IDC_EDIT_PARENTPROCESSID, szBuf);
}
else
SetDlgItemText(hwndDlg, IDC_EDIT_PARENTPROCESSID, TEXT(""));
// 目标窗口周围矩形闪动
GetWindowRect(hwndTarget, &rect);
if (rect.left < 0) rect.left = 0;
if (rect.top < 0) rect.top = 0;
Rectangle(hdcDesk, rect.left, rect.top, rect.right, rect.bottom); // 绘制洋红色矩形
Sleep(200);
Rectangle(hdcDesk, rect.left, rect.top, rect.right, rect.bottom); // 擦除洋红色矩形
return TRUE;
};
};
return FALSE;
};
HWND SmallestWindowFromPoint(POINT pt) {
RECT rect, rcTemp;
HWND hwnd, hwndParent, hwndTemp;
hwnd = WindowFromPoint(pt);
if (hwnd != NULL) {
GetWindowRect(hwnd, &rect);
hwndParent = GetParent(hwnd);
// 如果hwnd窗口具有父窗口
if (hwndParent != NULL) {
// 查找和hwnd同一级别的下一个Z顺序窗口
hwndTemp = hwnd;
do {
hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT);
// 如果找到的和hwnd同一级别的下一个Z顺序窗口 包含指定的坐标点pt并且可见
GetWindowRect(hwndTemp, &rcTemp);
if (PtInRect(&rcTemp, pt) && IsWindowVisible(hwndTemp))
// 找到的窗口是不是比hwnd窗口更小
if (((rcTemp.right - rcTemp.left) * (rcTemp.bottom - rcTemp.top)) < ((rect.right - rect.left) * (rect.bottom - rect.top))); {
hwnd = hwndTemp;
GetWindowRect(hwnd, &rect);
}
} while (hwndTemp != NULL);
};
};
return hwnd;
};
DWORD GetParentProcessIDByID(DWORD dwProcessId) {
HANDLE hSnapshot;
PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) };
BOOL bRet;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return -1;
bRet = Process32First(hSnapshot, &pe);
while (bRet) {
if (pe.th32ProcessID == dwProcessId)
return pe.th32ParentProcessID;
bRet = Process32Next(hSnapshot, &pe);
};
CloseHandle(hSnapshot);
return -1;
};

自删除

用ping实现延迟5秒,第一个del删除该.exe文件,第二个del %0删除该批处理。

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
46
47
48
49
50
51
52
53
54
55
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include "resource.h"
// 函数声明
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL);
return 0;
};
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
CHAR szApplicationName[MAX_PATH] = { 0 }; // 本程序文件路径
TCHAR szCmdPath[MAX_PATH] = { 0 }; // cmd.exe文件路径
TCHAR szBatFilePath[MAX_PATH]; // .bat文件路径
TCHAR szBatFileName[MAX_PATH] = TEXT("删除程序.bat"); // .bat文件名称
CHAR szBatFileContent[MAX_PATH * 3] = { 0 }; // .bat文件内容
CHAR szBatFileContentFormat[MAX_PATH] = { "@ping 127.0.0.1 -n 5 >nul\r\ndel \"%s\"\r\ndel %%0" };
HANDLE hFile;
TCHAR szCommandLine[MAX_PATH] = TEXT("/c "); // CreateProcess的lpCommandLine参数
STARTUPINFO si = { sizeof(STARTUPINFO) };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi = { 0 };
switch (uMsg) {
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDCANCEL:
// 本程序文件路径
GetModuleFileNameA(NULL, szApplicationName, _countof(szApplicationName));
// cmd.exe文件路径
GetEnvironmentVariable(TEXT("ComSpec"), szCmdPath, _countof(szCmdPath));
// .bat文件路径,放到系统临时目录
GetTempPath(_countof(szBatFilePath), szBatFilePath);
if (szBatFilePath[_tcslen(szBatFilePath) - 1] != TEXT('\\'))
StringCchCat(szBatFilePath, _countof(szBatFilePath), TEXT("\\"));
StringCchCat(szBatFilePath, _countof(szBatFilePath), szBatFileName);
// 创建.bat文件,写入内容
hFile = CreateFile(szBatFilePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
wsprintfA(szBatFileContent, szBatFileContentFormat, szApplicationName);
WriteFile(hFile, szBatFileContent, strlen(szBatFileContent), NULL, NULL);
CloseHandle(hFile);
// 创建进程,执行.bat批处理文件
StringCchCat(szCommandLine, _countof(szCommandLine), szBatFilePath);
if (CreateProcess(szCmdPath, szCommandLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
};
EndDialog(hwndDlg, 0);
break;
};
return TRUE;
};
};
return FALSE;
};