这是一个里程碑,是我学习WIN32 API和PE结构后我的第一个项目,虽然已经满大街了,但还是想记录一下这个项目的一些心得
本项目使用32位编译,所以在64位下解析是错误的,需要注意
图形化框架
我使用了Windows消息控件来实现图形化界面,使用了Win32 API中的各种消息机制,包括注册窗口类、创建窗口、显示窗口、处理消息、发送消息等
在这一部分的时候踩了不少坑,比如通用控件和自定义控件消息处理方式的区别,前者使用WM_NOTIFY消息,后者使用WM_COMMAND消息
但在写这篇文章的时候,我已经完成了我的界面,所以在这里不再记录我怎么实现的
核心内容
接下来写的就是我正在实现的功能了,也是程序的核心模块
进程遍历
这块内容我早已写过无数遍,虽然之前是在没系统学过WIN32 API的时候,但现在再写一遍却有不同的体会,现在我知道了每个函数的功能,并且我能够自己查阅MSDN文档来解决问题
在这个功能函数里,主要的到进程名称、PID、镜像基址、镜像大小、进程模块
- 进程名称:PROCESSENTRY32->szExeFile
- PID:PROCESSENTRY32->th32ProcessID
- 镜像基址:MODULEENTRY32->modBaseAddr
- 镜像大小:MODULEENTRY32->modBaseSize
可见后两个都需要遍历进程模块,但我们需要的东西一般在第一个模块里,所以可以偷个懒看第一个模块就行了
VOID EnumProcess(HWND hListProcess)
{
LV_ITEM lvItem;
// 初始化
memset(&lvItem, 0, sizeof(LV_ITEM));
lvItem.mask = LVIF_TEXT;
// 进程遍历
// 获取系统快照
HANDLE hProcessSnap;
HANDLE hProcess;
DWORD dwPriorityClass;
PROCESSENTRY32 pe32;
MODULEENTRY32 me32;
pe32.dwSize = sizeof(PROCESSENTRY32);
me32.dwSize = sizeof(MODULEENTRY32);
HANDLE hModuleSnap;
char* pProcessName = nullptr;
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
MessageBox(0, "获取系统快照失败", "Error", 0);
return;
}
// 通过Process32First得到hProcessSnap的进程信息,通过PROCESSENTRY32结构体存储
if (!Process32First(hProcessSnap, &pe32))
{
CloseHandle(hProcessSnap);
}
do
{
dwPriorityClass = 0;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
if (hProcess == NULL) {
printError(TEXT("OpenProcess"));
}
else
{
dwPriorityClass = GetPriorityClass(hProcess);
if (!dwPriorityClass) {
printError(TEXT("GetPriorityClass"));
}
CloseHandle(hProcess);
}
hModuleSnap = INVALID_HANDLE_VALUE;
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pe32.th32ProcessID);
if (hModuleSnap == INVALID_HANDLE_VALUE)
{
//MessageBox(0, "获取进程模块失败", "Error", 0);
DWORD err = GetLastError();
char Error[256];
memset(Error, 0, 256);
sprintf(Error, "CreateToolhelp32Snapshot(MODULE) failed for PID %u, err=%u\n", (unsigned)pe32.th32ProcessID, err);
OutputDebugString(Error);
continue;
}
if (!Module32First(hModuleSnap, &me32))
{
DWORD err = GetLastError();
char Error[256];
memset(Error, 0, 256);
sprintf(Error, "CreateToolhelp32Snapshot(MODULE) failed for PID %u, err=%u\n", (unsigned)pe32.th32ProcessID, err);
OutputDebugString(Error);
CloseHandle(hModuleSnap); // clean the snapshot object
continue;
}
// 进程名称
pProcessName = pe32.szExeFile;
lvItem.pszText = pProcessName;
lvItem.iItem = 0;
lvItem.iSubItem = 0;
ListView_InsertItem(hListProcess, &lvItem);
// 进程PID
char buf[256];
memset(buf, 0, 256);
sprintf(buf, "%d", pe32.th32ProcessID);
lvItem.pszText = buf;
lvItem.iItem = 0;
lvItem.iSubItem = 1;
ListView_SetItem(hListProcess, &lvItem);
// 镜像基址
sprintf(buf, "0x%08x", me32.modBaseAddr);
lvItem.pszText = buf;
lvItem.iItem = 0;
lvItem.iSubItem = 2;
ListView_SetItem(hListProcess, &lvItem);
// 镜像大小
sprintf(buf, "0x%08x", me32.modBaseSize);
lvItem.pszText = buf;
lvItem.iItem = 0;
lvItem.iSubItem = 3;
ListView_SetItem(hListProcess, &lvItem);
} while (Process32Next(hProcessSnap, &pe32));
}
这里需要注意编译的时候最好编译成和你当前使用的系统相同的位数,否则遍历不完全,给出我32位编译下和64位编译下结果的对比:

可以看到64位下的结果远远大于32位下的结果
模块遍历
这一块就要涉及消息处理了,因为要遍历的每个模块是针对单个进程的每个模块
那么现在的问题就是如何把我选中的行的PID传递给遍历模块的窗口?
首先需要知道进程窗口的id为IDC_LIST_PROCESS,当我点击这个窗口内的某个值时,应该触发一个点击事件,且这个事件的条件是我点击了,并且点击的是IDC_LIST_PROCESS的内容
又已知DialogBox为通用控件,需要用到WM_NOTIFY来处理消息,所以我们需要在WM_NOTIFY中判断消息来源是否是IDC_LIST_PROCESS
这里又有问题:我们的点击消息存储在哪里?
查阅MSDN,搜索WM_NOTIFY
有两个参数,wParam和lParam,wParam表示控件的id,lParam表示NMHDR结构体
再查阅NMHDR
typedef struct tagNMHDR
{
HWND hwndFrom;
UINT_PTR idFrom;
UINT code; // NM_ code
} NMHDR;
注释中就写了code字段表示消息类型,我们需要判断code是否为NM_CLICK
case WM_NOTIFY:
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (wParam == IDC_LIST_PROCESS && pNMHDR->code == NM_CLICK)
{
EnumModules(GetDlgItem(hwndDlg, IDC_LIST_PROCESS));
}
break;
}
VOID EnumModules(HWND hwndDlg)
{
DWORD dwRowId;
TCHAR szPid[0x20];
LV_ITEM lvItem;
char* pModuleName = nullptr;
HWND hListProcess = GetDlgItem(hAppHwnd, IDC_LIST_MODULE);
// 初始化
memset(&lvItem, 0, sizeof(LV_ITEM));
memset(szPid, 0, 0x20);
// 获取选择行dwRowId
dwRowId = ListView_GetNextItem(hwndDlg, -1, LVNI_SELECTED);
if (dwRowId == -1) {
//MessageBox(NULL, TEXT("请选择进程"), TEXT("出错啦"), MB_OK);
return;
}
lvItem.iSubItem = 1; // 指定获取的列
lvItem.pszText = szPid; // 存放结果的缓冲区
lvItem.cchTextMax = 0x20; // 缓冲区大小
SendMessage(hwndDlg, LVM_GETITEMTEXT, dwRowId, (UINT64)&lvItem); // 将本行本列信息存储在lvItem
HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
MODULEENTRY32 me32;
me32.dwSize = sizeof(MODULEENTRY32);
// Take a snapshot of all modules in the specified process.
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, atoi(szPid));
if (hModuleSnap == INVALID_HANDLE_VALUE)
{
printError(TEXT("CreateToolhelp32Snapshot (of modules)"));
return;
}
if (!Module32First(hModuleSnap, &me32))
{
printError(TEXT("Module32First")); // show cause of failure
CloseHandle(hModuleSnap); // clean the snapshot object
return;
}
int n = 0;
do
{
// 模块名称
pModuleName = me32.szModule;
lvItem.pszText = pModuleName;
lvItem.iItem = n;
lvItem.iSubItem = 0;
ListView_InsertItem(hListProcess, &lvItem);
// 模块地址
char buf[256];
memset(buf, 0, 256);
sprintf(buf, "%s", me32.szExePath);
lvItem.pszText = buf;
lvItem.iItem = n;
lvItem.iSubItem = 1;
ListView_SetItem(hListProcess, &lvItem);
n++;
} while (Module32Next(hModuleSnap, &me32));
CloseHandle(hModuleSnap);
return;
}
总体和遍历进程差不多,只是多了一些消息处理
至此,这个项目看起来应该是这样

PE结构解析
先画界面
我把这个dialog资源为IDD_DIALOG_PE,当我打开文件时,会弹出这个新的窗口
其中编辑框内需要写入PE信息,如何往编辑框写入如下
HWND hEdit = GetDlgItem(hwndDlg, IDC_EDIT_ADDRESSOFENTRYPOINT);
char strTempAddr[12] = { 0 };
if (hEdit == NULL) {
printError("GetDlgItem");
}
else {
sprintf(strTempAddr, "0x%08X", pFileStruct->pNTHeader->OptionalHeader.AddressOfEntryPoint);
SetWindowText(hEdit, strTempAddr);
}
GetDlgItem获取到编辑框的句柄,然后用SetWindowText将地址写入编辑框内
- 打开文件
直接调用
GetOpenFileName获取文件绝对路径,再通过ReadFile读取文件内容,然后调用AnalysisPE分析PE结构
TCHAR szPeFileExt[100] = "*.exe;*.dll;*.scr;*.drv;*.sys";
TCHAR szFileName[256];
memset(szFileName, 0, 256);
memset(&stOpenFile, 0, sizeof(OPENFILENAME));
stOpenFile.lStructSize = sizeof(OPENFILENAME);
stOpenFile.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
stOpenFile.hwndOwner = hwndDlg;
stOpenFile.lpstrFilter = szPeFileExt;
stOpenFile.lpstrFile = szFileName; // 文件绝对路径
stOpenFile.nMaxFile = MAX_PATH;
GetOpenFileName(&stOpenFile);
ReadFile(stOpenFile.lpstrFile, &pFileBuffer);
AnalysisPE(pFileBuffer, &pFileStruct);
- 分析PE结构
分析PE结构,主要是获取PE头、NT头、节表、导出表等信息,并将这些信息存储在
PEstruct结构体内
struct PEstruct {
PIMAGE_DOS_HEADER pDosHeader = nullptr;
PIMAGE_NT_HEADERS pNTHeader = nullptr;
PIMAGE_SECTION_HEADER pSectionHeader = nullptr;
PIMAGE_EXPORT_DIRECTORY pExportTable = nullptr;
};
void AnalysisPE(IN PDWORD pFileBuffer, OUT PEstruct** pPEstruct)
{
if (pFileBuffer == nullptr) {
OutputDebugString("文件为空");
return;
}
PEstruct* pTempStruct = nullptr;
pTempStruct = new PEstruct();
if (!pTempStruct) {
OutputDebugString("申请内存失败");
return;
}
pTempStruct->pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(pFileBuffer);
pTempStruct->pNTHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<uint8_t*>(pFileBuffer) + pTempStruct->pDosHeader->e_lfanew);
pTempStruct->pSectionHeader = reinterpret_cast<PIMAGE_SECTION_HEADER>(reinterpret_cast<uint8_t*>(&pTempStruct->pNTHeader->OptionalHeader) + pTempStruct->pNTHeader->FileHeader.SizeOfOptionalHeader);
*pPEstruct = pTempStruct;
}
- 显示PE信息
最后的效果如图所示,显示了PE头、NT头、节表、导出表等信息
