C语言的PELode编写记录
记录一下C语言写的简单的PE文件分析器。
边学边做的,有些地方有疏漏了还请指点。
我按照《逆向工程核心原理》这本书给出的大体结构对各部分内容进行输出,下面记录一下我遇到的难题和一些值得注意的点。
一、
分析一个pe文件要做的第一件事就是把这个文件加载到内存中,也就是文件映射。
这里我用的代码是:
- char FilePath[] = "E:\\test\\notepad.exe";
- HANDLE hFile = CreateFileA(FilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
- HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
- PVOID pbFile = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
把FilePath[]内容改成需要分析的pe文件的路径即可。
然后我们准备分析一个文件的DOS头、NT头、输入表输出表,其他内容暂且不分析。
一个DOS头的内容如下:
作为一个结构体,每个PE文件的DOS头长度不定,但是e_lfanew的值应该都是3cH。
二、
如何找到并输出DOS头。
在网络上搜集各种资料观察各种大牛的代码后我发现,当我把文件映射之后,可以直接定义DOS头的入口:
这也让我对文件的本质和C语言编程多了一些理解,事实上这也是我第一次接触这种很像windows编程的小程序。
直接用系统已经给出的结构体定义去声明一个DOS头,然后我们就可以以指针的形式去访问内部成员。
这里我又遇见了第三个问题,实际上这个问题应该是在映射文件前就该遇到了:
三、
刚开始的时候我并不明白这些DWORD、HANDLE都是些什么东西,查过资料后明白了,不过是和int、char这些东西一样,只是微软把他们typedef成了其他名称,本质还是一些基础的数据定义。
搞清楚这个之后我们对于DOS头甚至是之后的整体文件就能有个大概的认识了。这些支离破碎的二进制被我们观测为十六进制,再加以联系组成一块一块的结构,最后一起拼接成了一个PE文件。
访问DOS头的成员:
- printf("==================================PE DOS HEADER===================================");
- printf("\ne_magic: 0x%04X", pDosHeader->e_magic);
- printf("\ne_cblp: 0x%04X", pDosHeader->e_cblp);
- printf("\ne_cp: 0x%04X", pDosHeader->e_cp);
- printf("\ne_crlc: 0x%04X", pDosHeader->e_crlc);
- printf("\ne_cparhdr: 0x%04X", pDosHeader->e_cparhdr);
- printf("\ne_minalloc: 0x%04X", pDosHeader->e_minalloc);
- printf("\ne_maxalloc: 0x%04X", pDosHeader->e_maxalloc);
- printf("\ne_ss: 0x%04X", pDosHeader->e_ss);
- printf("\ne_sp: 0x%04X", pDosHeader->e_sp);
- printf("\ne_csum: 0x%04X", pDosHeader->e_csum);
- printf("\ne_ip: 0x%04X", pDosHeader->e_ip);
- printf("\ne_cs: 0x%04X", pDosHeader->e_cs);
- printf("\ne_lfarlc: 0x%04X", pDosHeader->e_lfarlc);
- printf("\ne_ovno: 0x%04X\n", pDosHeader->e_ovno);
- for (int i = 0; i <= 3; i++) {
- printf("e_res[%d]: 0x%04X ",i, pDosHeader->e_res[i]);
- }
- printf("\ne_oemid: 0x%04X", pDosHeader->e_oemid);
- printf("\ne_oeminfo: 0x%04X\n", pDosHeader->e_oeminfo);
- for (int i = 0; i <= 9; i++) {
- if (!(i % 4) && i) printf("\n");
- printf("e_res[%d]: 0x%04X ", i, pDosHeader->e_res2[i]);
- }
- printf("\ne_lfanew: 0x%08X\n", pDosHeader->e_lfanew);
- printf("\n==================================PE NT HEADER===================================");//大小为F8
- PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pbFile + pDosHeader->e_lfanew);
- printf("\nSignature: 0x%08X", pNtHeader->Signature);
大部分以十六进制的方法输出,小部分需要看一下表示的内容于是便输出为%s或者是%d。
注意,在文件映射的开始我们定义了一个PVOID的pbFile,这个代表文件的入口,之后我们想要定位NT头或者其他什么内容时需要用到。
e_lfanew的值就是指从文件开头到NT头的距离,中间可能夹着DOS存根,但是无伤大雅,存根是只有在DOS环境下启动程序才会运行的内容,现在一般用来告诉用户这个软件应该在windows下打开。
把注意力放在NT头上:
通过强制类型转化可以找到NT头的起始位置,下面是NT头的内容:
NT头看着没什么东西,里面其实包含了两个结构体,一个文件头一个可选头。
NT头的第一个signature是签名,作为PE文件其值为0x50450000,也就是"PE"00
- printf("\n==================================PE NT HEADER===================================");//大小为F8
- PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pbFile + pDosHeader->e_lfanew);
- printf("\nSignature: 0x%08X", pNtHeader->Signature);
文件头的结构如下:
- //NT头的file header
- printf("\n==================================PE FILE HEADER===================================\n");
- printf("Machine: 0x%04X\n", pNtHeader->FileHeader.Machine);
- printf("NumberOfSections: 0x%04X\n", pNtHeader->FileHeader.NumberOfSections); //文件中存在的节区数量
- printf("TimeDateStamp: 0x%08X\n", pNtHeader->FileHeader.TimeDateStamp);
- printf("PointerToSymbolTable: 0x%08X\n", pNtHeader->FileHeader.PointerToSymbolTable);
- printf("NumberOfSymbols: 0x%08X\n", pNtHeader->FileHeader.NumberOfSymbols);
- printf("SizeOfOptionalHeader: 0x%04X\n", pNtHeader->FileHeader.SizeOfOptionalHeader); //指出optional header 的大小
- printf("Characteristics: 0x%04X\n", pNtHeader->FileHeader.Characteristics); //标识文件的属性
可选头结构如下:
- //NT头的optional header
- printf("\n===================================PE OPTIONAL HEADER====================================\n");
- printf("Machine:%04X\n", pNtHeader->OptionalHeader.Magic);
- printf("MajorLinkerVersion:%02X\n", pNtHeader->OptionalHeader.MajorLinkerVersion);
- printf("MinorLinkerVersion:%02X\n", pNtHeader->OptionalHeader.MinorLinkerVersion);
- printf("SizeOfCode:%08X\n", pNtHeader->OptionalHeader.SizeOfCode);
- printf("SizeOfInitializedData:%08X\n", pNtHeader->OptionalHeader.SizeOfInitializedData);
- printf("SizeOfUninitializedData:%08X\n", pNtHeader->OptionalHeader.SizeOfUninitializedData);
- printf("AddressOfEntryPoint:%08X\n", pNtHeader->OptionalHeader.AddressOfEntryPoint); //代码起始位置
- printf("BaseOfCode:%08X\n", pNtHeader->OptionalHeader.BaseOfCode);
- printf("BaseOfData:%08X\n", pNtHeader->OptionalHeader.BaseOfData);
- printf("ImageBase:%08X\n", pNtHeader->OptionalHeader.ImageBase);
- printf("SectionAlignment:%08X\n", pNtHeader->OptionalHeader.SectionAlignment);
- printf("FileAlignment:%08X\n", pNtHeader->OptionalHeader.FileAlignment);
- printf("MajorOperatingSystemVersion:%04X\n", pNtHeader->OptionalHeader.MajorOperatingSystemVersion);
- printf("MinorOperatingSystemVersion:%04X\n", pNtHeader->OptionalHeader.MinorOperatingSystemVersion);
- printf("MajorImageVersion:%04X\n", pNtHeader->OptionalHeader.MajorImageVersion);
- printf("MinorImageVersion:%04X\n", pNtHeader->OptionalHeader.MinorImageVersion);
- printf("MajorSubsystemVersion:%04X\n", pNtHeader->OptionalHeader.MajorSubsystemVersion);
- printf("MinorSubsystemVersion:%04X\n", pNtHeader->OptionalHeader.MinorSubsystemVersion);
- printf("Win32VersionValue:%08X\n", pNtHeader->OptionalHeader.Win32VersionValue);
- printf("SizeOfImage:%08X\n", pNtHeader->OptionalHeader.SizeOfImage);
- printf("SizeOfHeaders:%08X\n", pNtHeader->OptionalHeader.SizeOfHeaders); //整个PE头大小
- printf("CheckSum:%08X\n", pNtHeader->OptionalHeader.CheckSum);
- printf("Subsystem:%04X\n", pNtHeader->OptionalHeader.Subsystem);
- printf("DllCharacteristics:%04X\n", pNtHeader->OptionalHeader.DllCharacteristics);
- printf("SizeOfStackReserve:%08X\n", pNtHeader->OptionalHeader.SizeOfStackReserve);
- printf("SizeOfStackCommit:%08X\n", pNtHeader->OptionalHeader.SizeOfStackCommit);
- printf("SizeOfHeapReserve:%08X\n", pNtHeader->OptionalHeader.SizeOfHeapReserve);
- printf("SizeOfHeapCommit:%08X\n", pNtHeader->OptionalHeader.SizeOfHeapCommit);
- printf("LoaderFlags:%08X\n", pNtHeader->OptionalHeader.LoaderFlags);
- printf("NumberOfRvaAndSizes:%08X\n", pNtHeader->OptionalHeader.NumberOfRvaAndSizes); //用来指定最后数组的大小
- char DataDirectoryName[][50] = { "EXPORT Directory","IMPORT Directory","RESOURCE Directory","EXCEPTION Directory","SECURITY Directory","BASERELOC Directory",
- "DEBUG Directory","COPYRIGHT Directory","GLOBALPTR Directory","TLS Directory","LOAD_CONFIG Directory","BOUND_IMPORT Directory","IAT Directory","DELAY_IMPORT Directory",
- "COM_DESCRIPTOR Directory","Reserved Directory" };
- for (int i = 0; i < pNtHeader->OptionalHeader.NumberOfRvaAndSizes; i++) {
- printf("%s : 0x%08X 0x%08X\n", DataDirectoryName[i], pNtHeader->OptionalHeader.DataDirectory[i].VirtualAddress,pNtHeader->OptionalHeader.DataDirectory[i].Size);
- }
节区表:
- printf("\n===================================PE SECTION HEADER====================================\n\n");
- //PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)IMAGE_FIRST_SECTION(pNtHeader);
- PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeader + sizeof(IMAGE_NT_HEADERS));
- for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++) {
- printf("--------------stction %d--------------\n\n",i+1);
- for (int j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++) {
- printf("%c", pSectionHeader->Name[j]);
- }//name的名称可能和实际作用没什么联系
- printf(" 0x");
- for (int j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++) {
- printf("%X", pSectionHeader->Name[j]);
- }
- printf("\nVirtualSize : 0x%04X", pSectionHeader->Misc.VirtualSize); //内存中节区所占大小
- printf("\nVirtualAddress : 0x%08X", pSectionHeader->VirtualAddress); //内存中节区起始地址
- printf("\nSizeOfRawData : 0x%08X", pSectionHeader->SizeOfRawData); //磁盘文件节区所占大小
- printf("\nPointerToRawData : 0x%08X", pSectionHeader->PointerToRawData); //磁盘文件中节区起始位置
- printf("\nPointerToRelocations : 0x%08X", pSectionHeader->PointerToRelocations);
- printf("\nPointerToLinenumbers : 0x%08X", pSectionHeader->PointerToLinenumbers);
- printf("\nNumberOfRelocations : 0x%04X", pSectionHeader->NumberOfRelocations);
- printf("\nNumberOfLinenumbers : 0x%04X", pSectionHeader->NumberOfLinenumbers);
- printf("\nCharacteristics : 0x%08X", pSectionHeader->Characteristics);
- pSectionHeader++;
- printf("\n\n");
- }
输入表:
- printf("\n===================================PE IMPORT====================================\n");
- DWORD pImportOffset = RVA_to_RAW(pNtHeader,pNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress);
- PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pbFile + pImportOffset);
- while (1) {
- if (pImport->FirstThunk == 0 && pImport->ForwarderChain == 0 && pImport->Name == 0 && pImport->OriginalFirstThunk == 0 && pImport->TimeDateStamp == 0) {
- break;
- }
- DWORD dwINT = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->OriginalFirstThunk);
- DWORD dwTimeDateStamp = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->TimeDateStamp);
- DWORD dwForwarderChain = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->ForwarderChain);
- DWORD dwName = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->Name);
- DWORD dwFirstThunk = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->FirstThunk);
- printf("------------------- %s -------------------\n",dwName);
- printf("TimeDateStamp: 0x%08X\n", pImport->TimeDateStamp);
- printf("ForwarderChain: 0x%08X\n", pImport->ForwarderChain);
- printf("pImport->FirstThunk: 0x%X\n", pImport->FirstThunk);
- DWORD* ImportByName = (DWORD*)dwINT;
- DWORD* pFirstThunk = (DWORD*)dwFirstThunk;
- int i = 0;
- printf("\nAddress\t\tHint\tName\n");
- while ((ImportByName[i])) {
- PIMAGE_IMPORT_BY_NAME pImpoetByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pbFile + RVA_to_RAW(pNtHeader, ImportByName[i]));
- printf("0x%04X\t", pFirstThunk[i]);
- printf("0x%04X\t",pImpoetByName->Hint);
- printf("%s\n", pImpoetByName->Name);
- i++;
- }
- dwINT++;
- pImport++;
- }
输出表:
- printf("\n===================================PE EXPORT====================================\n");
- PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress));
- printf("Characteristics : 0x%X\n", pExport->Characteristics);
- printf("TimeDateStamp : 0x%X\n", pExport->TimeDateStamp);
- printf("MajorVersion : 0x%X\n", pExport->MajorVersion);
- printf("MinorVersion : 0x%X\n", pExport->MinorVersion);
- printf("Base : 0x%X\n", pExport->Base);
- printf("NumberOfNames: %d\n", pExport->NumberOfNames);
- printf("NumberOfFunctions: %d\n", pExport->NumberOfFunctions);
- DWORD* AddressOfFunctions = (DWORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfFunctions));
- DWORD* AddressOfNameOrdinals = (DWORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfNameOrdinals));
- DWORD* AddressOfNames = (DWORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfNames));
- DWORD* Name = (DWORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->Name));
- WORD* pwOrdinals = (WORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfNameOrdinals));
- if (pExport->NumberOfFunctions == 0) {
- printf("\n\t---------- No Export Tabel! ----------\n");
- if (NULL != pbFile)
- {
- UnmapViewOfFile(pbFile);
- }
- if (NULL != hMapping)
- {
- CloseHandle(hMapping);
- }
- if (INVALID_HANDLE_VALUE != hFile)
- {
- CloseHandle(hFile);
- }
- return 0;
- }
- for (int i = 0; i < pExport->NumberOfNames; i++) {
- DWORD dwName = (DWORD)pbFile + RVA_to_RAW(pNtHeader, AddressOfNames[i]);
- DWORD VA = pNtHeader->OptionalHeader.ImageBase + AddressOfFunctions[i];
- printf("Ordinals: %d\tName: %-30s\tRVA: 0x%08X\tVA: 0x%08X\n", pwOrdinals[i], dwName, AddressOfFunctions[i], VA);
- }
在输入输出表这里遇到了新的问题:在知晓如何把RVA转化成RAW之后,要再次以新的RAW和起始地址找新的结构,这个时候利用了指针指向把数据输出,我一直输出的是指针存放的地址,所以非常难受。
整个代码已经上传至github和gitee:
Github:https://github.com/DorinXL/Easy_PEInfo
Gitee: https://gitee.com/dorinxl/Easy_PEInfo