软件安全这课快到期末了,我把之前写的山寨一个PE解析器代码整理复习一下,奈何菜鸟我不太会MFC编程,所以下面的程序主要是在命令行完成。
代码最后全部打包压缩上传。
PE结构百度图一堆一堆
//取得文件路径部分 TCHAR szFilePath[MAX_PATH]; OPENFILENAME ofn = { 0 }; memset(szFilePath, 0, MAX_PATH); memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = NULL; ofn.hInstance = GetModuleHandle(NULL); ofn.nMaxFile = MAX_PATH; ofn.lpstrInitialDir = L"."; ofn.lpstrFile = szFilePath; ofn.lpstrTitle = L"选择PE文件"; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrFilter = L"(*.*)\0*.exe;*.dll\0"; GetOpenFileName(&ofn);
运行之后可以选择文件并得到文件绝对路径地址如图:
然后用内存映射文件打开文件(这里不能用openfile函数),然后得到映射基地址,那打开文件之后获得的地址是文件地址:
if (szFilePath == NULL) { MessageBox(NULL, L"打开文件错误", NULL, NULL); return 0; } //创建文件句柄 HANDLE hFile = CreateFile(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) { MessageBox(NULL, L"创建PE内核失败", NULL, NULL); return 0; } //创建文件映射内核对象 HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (NULL == hMapping) { CloseHandle(hFile); hFile = NULL; return 0; } //获得映射基地址 LPVOID ImageBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); if (NULL == ImageBase) { CloseHandle(hMapping); CloseHandle(hFile); hMapping = NULL; hFile = NULL; return FALSE; }
//下面是DOS头分析///////////////////////////////////////////////// PIMAGE_DOS_HEADER pDosHeader = NULL; pDosHeader = (PIMAGE_DOS_HEADER)ImageBase; if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { MessageBox(NULL, TEXT("PE文件错误"), NULL, MB_OK); } //下面是获得NT头///////////////////////////////////////////////////// PIMAGE_NT_HEADERS pNtHeader = NULL; pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)(pDosHeader->e_lfanew) + (DWORD)ImageBase); if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { MessageBox(NULL, TEXT("PE文件错误"), NULL, MB_OK); } PIMAGE_FILE_HEADER pFileHeader = NULL; pFileHeader = &pNtHeader->FileHeader; PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL; pOptionHeader = &pNtHeader->OptionalHeader; PIMAGE_SECTION_HEADER pSectionHeader = NULL; pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
/***************** 显示需要的数据部分 *******************/ cout << "所有需要的关键信息如下:" << endl; cout << "_IMAGE_DOS_HEADER RVA地址:" << hex << pDosHeader << endl; cout << "e_magic值:" << hex << pDosHeader->e_magic << endl; cout << "e_lfarlc值:" << hex << pDosHeader->e_lfarlc << endl << endl; cout << "_IMAGE_NT_HEADERS RVA地址:" << hex << pNtHeader << endl; cout << "Signature 值:" << hex << pNtHeader->Signature << endl << endl; cout << "_IMAGE_FILE_HEADERS RVA地址:" << hex << pFileHeader << endl; cout << "Machine 值:" << hex << pFileHeader->Machine << endl; cout << "NumberOfSections 值:" << hex << pFileHeader->NumberOfSections << endl; cout << "Characteristics 值:" << hex << pFileHeader->Characteristics << endl << endl; cout << "_IMAGE_OPTION_HEADERS RVA地址:" << hex << pOptionHeader << endl; cout << "Magic 值:" << hex << pOptionHeader->Magic << endl; cout << "SizeOfCode 值:" << hex << pOptionHeader->SizeOfCode << endl; cout << "AddressOfEntryPoint 值" << hex << pOptionHeader->AddressOfEntryPoint << endl; cout << "ImageBase 值:" << hex << pOptionHeader->ImageBase << endl; cout << "SectionAlignment 值" << hex << pOptionHeader->SectionAlignment << endl; cout << "FileAlignment 值" << hex << pOptionHeader->FileAlignment << endl; cout << "SizeOfImage 值:" << hex << pOptionHeader->SizeOfImage << endl << endl; cout << "_IMAGE_SECTION_HEADERS RVA地址:" << pSectionHeader << endl; for (int i = 0; i < pFileHeader->NumberOfSections; i++) //循环打印各个区段的值 { cout << " Section Name:" << pSectionHeader->Name << endl; cout << " VirtualAddress:" << hex << pSectionHeader->VirtualAddress << endl; cout << " SizeOfRawData:" << hex << pSectionHeader->SizeOfRawData << endl; cout << " PointerToRelocations:" << hex << pSectionHeader->PointerToRelocations << endl; cout << " NumberOfLinenumbers:" << hex << pSectionHeader->NumberOfLinenumbers << endl; cout << " Characteristics:" << hex << pSectionHeader->Characteristics << endl; ++pSectionHeader; cout << endl; }
打印输入表和输出表,这里一定不要忘记这个头文件:
#include <imagehlp.h> #pragma comment ( lib, "imagehlp.lib" ) //这里是关键,我是谷歌找到的,不然编译不了imagehlp.h #include<iostream> #include<CommCtrl.h>
打印导入表,我用的是IAT表,这个比较保险,就算函数没有名字也有VA地址:
int showIAT(PIMAGE_IMPORT_DESCRIPTOR pImport, LPVOID ImageBase, PIMAGE_NT_HEADERS pNtHeader, PIMAGE_OPTIONAL_HEADER pOptionHeader) { char* szFuncName; DWORD * pdwThunkRVA = NULL; PIMAGE_IMPORT_BY_NAME pByName = NULL; pdwThunkRVA = (DWORD*)ImageRvaToVa(pNtHeader, ImageBase, pImport->FirstThunk, NULL); //取出o指向的IAT地址,并转换成DWORD指针 if (!pdwThunkRVA) { return 0; } while (*pdwThunkRVA) { if (HIWORD(*pdwThunkRVA) == 0x8000) { cout << "函数序号:" << hex << *pdwThunkRVA << endl; } else { pByName = (PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(pNtHeader, ImageBase, (DWORD)(*pdwThunkRVA), NULL); if (pByName) cout << "funName:" << (char*)pByName->Name << endl; } ++pdwThunkRVA; } return 0; } void showIIDandIED( LPVOID ImageBase, PIMAGE_NT_HEADERS pNtHeader, PIMAGE_OPTIONAL_HEADER pOptionHeader) { PIMAGE_IMPORT_DESCRIPTOR pImport = NULL; DWORD IIDRVA; IIDRVA = pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; //记住是数组的第二项开始的,保存的是RVA值 pImport = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(pNtHeader, ImageBase, IIDRVA,NULL);//调用 imagehlp中的ImageRvaToVA string name = ""; if (!pImport) { int error = GetLastError(); if (error == 0) MessageBox(NULL, TEXT("文件没有输入表"), NULL, MB_OK); else MessageBox(NULL, TEXT("无法获得Import Directory"), NULL, MB_OK); return; } while (pImport->FirstThunk) { cout << " DLLName: " << (char*)ImageRvaToVa(pNtHeader, ImageBase, pImport->Name, NULL) << endl; //四个参数 cout << " Characteristics: " << hex << pImport->Characteristics << endl; cout << " ForwarderChain: " << hex << pImport->ForwarderChain << endl; cout << " TimeDateStamp: " << hex << pImport->TimeDateStamp << endl; cout << " OriginalFirstThunk(INT): " << hex << pImport->OriginalFirstThunk << endl; cout << " FirstThunk(IAT):" << hex << pImport->FirstThunk << endl; showIAT(pImport, ImageBase, pNtHeader, pOptionHeader); cout << endl; ++pImport; }
输出表部分IED:
////////////////////////////////////////IED///////////////////////////////////////////////////// cout << "//////////////////////////////文件导出表部分/////////////////////////////////////////" << endl; PIMAGE_EXPORT_DIRECTORY pExportDir = NULL; //指向IED指针 pExportDir = (PIMAGE_EXPORT_DIRECTORY)ImageRvaToVa(pNtHeader, ImageBase, pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress,NULL); if (!pExportDir) { int error = GetLastError(); if (error == 0) MessageBox(NULL, TEXT("文件没有输出表"), NULL, MB_OK); else MessageBox(NULL, TEXT("无法获得Export Directory"), NULL, MB_OK); return; } cout << " AddressOfFunctions: " << hex << pExportDir->AddressOfFunctions << endl; cout << " AddressOfNameOrdinals: " << hex << pExportDir->AddressOfNameOrdinals << endl; cout << " AddressOfNames: " << hex << pExportDir->AddressOfNames << endl; cout << " Base: " << hex << pExportDir->Base << endl; cout << " MajorVersion: " << hex << pExportDir->MajorVersion << endl; cout << " MinorVersion: " << hex << pExportDir->MinorVersion << endl; cout << " Characteristics: " << hex << pExportDir->Characteristics << endl; cout << " Name: " << hex << pExportDir->Name << endl; cout << " NumberOfFunctions: " << hex << pExportDir->NumberOfFunctions << endl; cout << " NumberOfNames: " << hex << pExportDir->NumberOfNames << endl; cout << " TimeDateStamp: " << hex << pExportDir->TimeDateStamp << endl; //cout << " pExportDir->Name: " << (char*)ImageRvaToVa(pNTH, LocalImageBase, pExportDir->Name, NULL) << endl; char* szFuncName; UINT NumOfName = 0; PDWORD pEAT, pENT; PWORD pEOT; UINT i = 0, j = 0, k = 0; pEOT = (PWORD)ImageRvaToVa(pNtHeader, ImageBase, pExportDir->AddressOfNameOrdinals,NULL); pEAT = (PDWORD)ImageRvaToVa(pNtHeader, ImageBase, pExportDir->AddressOfFunctions,NULL); pENT = (PDWORD)ImageRvaToVa(pNtHeader, ImageBase, pExportDir->AddressOfNames,NULL); NumOfName = pExportDir->NumberOfNames; //函数名字数量 if (!pEAT) return; for (i = 0; i < pExportDir->NumberOfFunctions; i++) { if (*pEAT) { for (j = 0; j < NumOfName; j++) { if (i == pEOT[j]) { szFuncName = (char*)ImageRvaToVa(pNtHeader, ImageBase, pENT[j],NULL); cout <<"FunctioRVA:"<< pEAT[i] <<'\t'<<"FunctionName:"<< szFuncName<<endl; } } } ++pEAT; } }
之后老师要求写一个程序,可以找出加载一个程序起来之后调用的DLL VA地址,代码也放进来,(备注应该比较详细)。里面写了一些函数处理,比如之前读取文件之后选择的是绝对路径,要把那些前缀什么的去掉。然后就是Tchar 转 STRING函数,char转tchar函数(当时搞这些编码转换头都疼)。这个程序的原理和一种反调试技术的实现差不多,就是遍历进程空间,找到对应的模块名,然后关掉它。代码中我也用了。
#include <iostream> #include <iomanip> #include <string> #include <tchar.h> #include <windows.h> #include <TlHelp32.h> #include<tlhelp32.h> #include<wchar.h> #include <cstring> #include <imagehlp.h> #pragma comment ( lib, "imagehlp.lib" ) //这里是关键,我是谷歌找到的,不然编译不了imagehlp.h #include<CommCtrl.h> #pragma comment (lib, "imagehlp.lib") #pragma comment (lib, "advapi32.lib") using namespace std; string TCHAR2STRING(TCHAR* STR) //tchar转string { int iLen = WideCharToMultiByte(CP_ACP, 0, STR, -1, NULL, 0, NULL, NULL); char* chRtn = new char[iLen * sizeof(char)]; WideCharToMultiByte(CP_ACP, 0, STR, -1, chRtn, iLen, NULL, NULL); std::string str(chRtn); return str; } HMODULE GetProcessModuleHandle(DWORD pid, CONST TCHAR* moduleName) { // 根据 PID 、模块名(需要写后缀,如:".dll"),获取模块入口地址。 MODULEENTRY32 moduleEntry; HANDLE handle = NULL; handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); // 获取进程快照中包含在th32ProcessID中指定的进程的所有的模块。 if (!handle) { CloseHandle(handle); return NULL; } ZeroMemory(&moduleEntry, sizeof(MODULEENTRY32)); moduleEntry.dwSize = sizeof(MODULEENTRY32); if (!Module32First(handle, &moduleEntry)) { CloseHandle(handle); return NULL; } do { if (_tcscmp(moduleEntry.szModule, moduleName) == 0) { return moduleEntry.hModule; } } while (Module32Next(handle, &moduleEntry)); CloseHandle(handle); return 0; } string NameOfexe(string name) //自定义一个取出文件地址绝对路径剔除多余参数函数 { string exeName; int k=0; for (int i = 0; i< name.length(); i++) { if (name[i] == '\\'){ k = i; for (int j = i; j < name.length() - i; j++) { if (name[j] == '\\') break; } } } int k2 = 0; int i = k+1; for(i; i < name.length(); i++) { exeName+= name[i]; k2++; } return exeName; } TCHAR* char2TCAHR(const char* str) //char*转tchar* { int size = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); TCHAR* retStr = new TCHAR[size * sizeof(TCHAR)]; MultiByteToWideChar(CP_ACP, 0, str, -1, retStr, size); return retStr; } void showAllImageBase(TCHAR *name, LPVOID ImageBase, PIMAGE_NT_HEADERS pNtHeader, PIMAGE_OPTIONAL_HEADER pOptionHeader){ HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // 进程快照句柄 PROCESSENTRY32 process = { sizeof(PROCESSENTRY32) }; // 存放进程快照的结构体 /*下面这部分是取出导入表DLL名字,保存起来用来,和之前实验的一样。*/ PIMAGE_IMPORT_DESCRIPTOR pImport = NULL; DWORD IIDRVA; IIDRVA = pOptionHeader->DataDirectory[1].VirtualAddress; //记住是数组的第二项开始的,保存的是RVA值 pImport = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(pNtHeader, ImageBase, IIDRVA, NULL);//调用 imagehlp中的ImageRvaToVA // 遍历进程 char* a; int k = 0; while (Process32Next(hProcessSnap, &process)) { // .exe 进程 string s_szExeFile = TCHAR2STRING(process.szExeFile); // char* 转 string if (s_szExeFile == NameOfexe(TCHAR2STRING(name))) { cout << NameOfexe(TCHAR2STRING(name)) <<'\t'<<"基地址:"<< ImageBase << endl; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process.th32ProcessID); // 进程句柄 while (pImport->FirstThunk) { a= (char*)ImageRvaToVa(pNtHeader, ImageBase, pImport->Name,NULL) ; cout << a; cout <<'\t'<< "模块VA地址:" << GetProcessModuleHandle(process.th32ProcessID, char2TCAHR(a)) << endl; ++pImport; } k = 1; } } if (k == 0) //程序没有运行 { STARTUPINFO si; //一些必备参数设置 memset(&si, 0, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; PROCESS_INFORMATION pi; //必备参数设置结束 if (!CreateProcess(NULL, name, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) // { cout << "Create Fail!" << endl; } Sleep(1000); HANDLE hProcessSnap1 = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // 进程快照句柄 PROCESSENTRY32 process1 = { sizeof(PROCESSENTRY32) }; // 存放进程快照的结构体 char* a; while (Process32Next(hProcessSnap1, &process1)) { // .exe 进程 string s_szExeFile = TCHAR2STRING(process1.szExeFile); // char* 转 string if (s_szExeFile == NameOfexe(TCHAR2STRING(name))) { cout << NameOfexe(TCHAR2STRING(name)) << ImageBase << endl; HANDLE hProcess1 = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process1.th32ProcessID); // 进程句柄 while (pImport->FirstThunk) { a = (char*)ImageRvaToVa(pNtHeader, ImageBase, pImport->Name, NULL); cout << a; cout <<'\t'<< "模块VA地址:" << GetProcessModuleHandle(process1.th32ProcessID, char2TCAHR(a)) << endl; ++pImport; } k = 1; } } } }
为了验证自己的代码正不正确,我找了一个内存映射文件读取程序,然后再代码中调用它:(这有一个弊端,就是程序一定要运行起来。)
int FindPID(string name) { PROCESSENTRY32 pe32; pe32.dwSize = sizeof(pe32); HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) { cout << "CreateToolhelp32Snapshot Error!" << endl;; return false; } BOOL bResult = Process32First(hProcessSnap, &pe32); int num(0); while (bResult) { if (TCHAR2STRING(pe32.szExeFile) == NameOfexe(name)) { return pe32.th32ProcessID; } bResult = Process32Next(hProcessSnap, &pe32); } CloseHandle(hProcessSnap); return -1; } /*int string2tchar(std::string& src, TCHAR* buf) { #ifdef UNICODE _stprintf_s(buf, MAX_PATH, _T("%S"), src.c_str());//%S宽字符 #else _stprintf_s(buf, MAX_PATH, _T("%s"), src.c_str());//%s单字符 #endif return 0; }*/ void showAllImageBase2(TCHAR* name, LPVOID ImageBase, PIMAGE_NT_HEADERS pNtHeader, PIMAGE_OPTIONAL_HEADER pOptionHeader) { //std::cout << FindPID(TCHAR2STRING(name)); STARTUPINFO si; //一些必备参数设置 memset(&si, 0, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; PROCESS_INFORMATION pi; //必备参数设置结束 string vmmapName = "S:\\14-VMMap.exe"; vmmapName += " "; vmmapName += to_string(FindPID(TCHAR2STRING(name))); //cout << vmmapName << endl; TCHAR wc[MAX_PATH]; #ifdef UNICODE _stprintf_s(wc, MAX_PATH, _T("%S"), vmmapName.c_str());//%S宽字符 #else _stprintf_s(wc, MAX_PATH, _T("%s"), vmmapName.c_str());//%s单字符 #endif if (!CreateProcess(NULL,wc , NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) // { cout << "Create Fail!" << endl; } }
程序是VMmap.exe,把它放到D盘下,压缩包里面有。
运行示意图:
选择刚刚的exe,一直否下去(因为我把各个模块放在不同头文件里面,比较乱不好意思),就可以得到头文件的相关信息:
然后命令行得到相关信息:
选择调试程序就是得到导入表的DLL的VA地址:
实验2就是那个导入表和输出表信息:一般DLL都没有输出表。
代码打包上传了,三个头文件和一个cpp,代码顺序比较乱还在学习软件保护。