PE头分析详解和VC++代码实现。
2019-12-02 02:59:18 Author: bbs.pediy.com(查看原文) 阅读量:179 收藏

软件安全这课快到期末了,我把之前写的山寨一个PE解析器代码整理复习一下,奈何菜鸟我不太会MFC编程,所以下面的程序主要是在命令行完成。

代码最后全部打包压缩上传。

PE结构百度图一堆一堆

  • 首先打开文件函数,设置完参数可以让它选择DLL或者EXE文件:
//取得文件路径部分
	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,代码顺序比较乱还在学习软件保护。

[公告]安全测试和项目外包请将项目需求发到看雪企服平台:https://qifu.kanxue.com


文章来源: https://bbs.pediy.com/thread-255851.htm
如有侵权请联系:admin#unsafe.sh