C语言的PELode编写记录

记录一下C语言写的简单的PE文件分析器。

边学边做的,有些地方有疏漏了还请指点。

我按照《逆向工程核心原理》这本书给出的大体结构对各部分内容进行输出,下面记录一下我遇到的难题和一些值得注意的点。

 

一、

分析一个pe文件要做的第一件事就是把这个文件加载到内存中,也就是文件映射。

这里我用的代码是:

 

  1. char FilePath[] = "E:\test\notepad.exe";  
  2. HANDLE hFile = CreateFileA(FilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);  
  3. HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);  
  4. 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头的成员:

  1. printf("==================================PE DOS HEADER===================================");  
  2. printf(" e_magic: 0x%04X", pDosHeader->e_magic);  
  3. printf(" e_cblp: 0x%04X", pDosHeader->e_cblp);  
  4. printf(" e_cp: 0x%04X", pDosHeader->e_cp);  
  5. printf(" e_crlc: 0x%04X", pDosHeader->e_crlc);  
  6. printf(" e_cparhdr: 0x%04X", pDosHeader->e_cparhdr);  
  7. printf(" e_minalloc: 0x%04X", pDosHeader->e_minalloc);  
  8. printf(" e_maxalloc: 0x%04X", pDosHeader->e_maxalloc);  
  9. printf(" e_ss: 0x%04X", pDosHeader->e_ss);  
  10. printf(" e_sp: 0x%04X", pDosHeader->e_sp);  
  11. printf(" e_csum: 0x%04X", pDosHeader->e_csum);  
  12. printf(" e_ip: 0x%04X", pDosHeader->e_ip);  
  13. printf(" e_cs: 0x%04X", pDosHeader->e_cs);  
  14. printf(" e_lfarlc: 0x%04X", pDosHeader->e_lfarlc);  
  15. printf(" e_ovno: 0x%04X ", pDosHeader->e_ovno);  
  16. for (int i = 0; i <= 3; i++) {  
  17.    printf("e_res[%d]: 0x%04X   ",i, pDosHeader->e_res[i]);  
  18. }  
  19. printf(" e_oemid: 0x%04X", pDosHeader->e_oemid);  
  20. printf(" e_oeminfo: 0x%04X ", pDosHeader->e_oeminfo);  
  21. for (int i = 0; i <= 9; i++) {  
  22.     if (!(i % 4) && i) printf(" ");  
  23.     printf("e_res[%d]: 0x%04X   ", i, pDosHeader->e_res2[i]);  
  24. }  
  25. printf(" e_lfanew: 0x%08X ", pDosHeader->e_lfanew);  
  26.     
  27. printf(" ==================================PE NT HEADER===================================");//大小为F8  
  28. PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pbFile + pDosHeader->e_lfanew);  
  29. printf(" Signature: 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

  1. printf(" ==================================PE NT HEADER===================================");//大小为F8  
  2. PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pbFile + pDosHeader->e_lfanew);  
  3. printf(" Signature: 0x%08X", pNtHeader->Signature);  

 

 

文件头的结构如下:

 

  1. //NT头的file header  
  2. printf(" ==================================PE FILE HEADER=================================== ");  
  3. printf("Machine: 0x%04X ", pNtHeader->FileHeader.Machine);  
  4. printf("NumberOfSections: 0x%04X ", pNtHeader->FileHeader.NumberOfSections);           //文件中存在的节区数量  
  5. printf("TimeDateStamp: 0x%08X ", pNtHeader->FileHeader.TimeDateStamp);  
  6. printf("PointerToSymbolTable: 0x%08X ", pNtHeader->FileHeader.PointerToSymbolTable);  
  7. printf("NumberOfSymbols: 0x%08X ", pNtHeader->FileHeader.NumberOfSymbols);  
  8. printf("SizeOfOptionalHeader: 0x%04X ", pNtHeader->FileHeader.SizeOfOptionalHeader);   //指出optional header 的大小  
  9. printf("Characteristics: 0x%04X ", pNtHeader->FileHeader.Characteristics);             //标识文件的属性  

 

 

可选头结构如下:

 

 

  1. //NT头的optional header  
  2. printf(" ===================================PE OPTIONAL HEADER==================================== ");  
  3.     
  4. printf("Machine:%04X ", pNtHeader->OptionalHeader.Magic);  
  5. printf("MajorLinkerVersion:%02X ", pNtHeader->OptionalHeader.MajorLinkerVersion);  
  6. printf("MinorLinkerVersion:%02X ", pNtHeader->OptionalHeader.MinorLinkerVersion);  
  7. printf("SizeOfCode:%08X ", pNtHeader->OptionalHeader.SizeOfCode);  
  8. printf("SizeOfInitializedData:%08X ", pNtHeader->OptionalHeader.SizeOfInitializedData);  
  9. printf("SizeOfUninitializedData:%08X ", pNtHeader->OptionalHeader.SizeOfUninitializedData);  
  10. printf("AddressOfEntryPoint:%08X ", pNtHeader->OptionalHeader.AddressOfEntryPoint);            //代码起始位置  
  11. printf("BaseOfCode:%08X ", pNtHeader->OptionalHeader.BaseOfCode);  
  12. printf("BaseOfData:%08X ", pNtHeader->OptionalHeader.BaseOfData);  
  13. printf("ImageBase:%08X ", pNtHeader->OptionalHeader.ImageBase);  
  14. printf("SectionAlignment:%08X ", pNtHeader->OptionalHeader.SectionAlignment);  
  15. printf("FileAlignment:%08X ", pNtHeader->OptionalHeader.FileAlignment);  
  16. printf("MajorOperatingSystemVersion:%04X ", pNtHeader->OptionalHeader.MajorOperatingSystemVersion);  
  17. printf("MinorOperatingSystemVersion:%04X ", pNtHeader->OptionalHeader.MinorOperatingSystemVersion);  
  18. printf("MajorImageVersion:%04X ", pNtHeader->OptionalHeader.MajorImageVersion);  
  19. printf("MinorImageVersion:%04X ", pNtHeader->OptionalHeader.MinorImageVersion);  
  20. printf("MajorSubsystemVersion:%04X ", pNtHeader->OptionalHeader.MajorSubsystemVersion);  
  21. printf("MinorSubsystemVersion:%04X ", pNtHeader->OptionalHeader.MinorSubsystemVersion);  
  22. printf("Win32VersionValue:%08X ", pNtHeader->OptionalHeader.Win32VersionValue);  
  23. printf("SizeOfImage:%08X ", pNtHeader->OptionalHeader.SizeOfImage);  
  24. printf("SizeOfHeaders:%08X ", pNtHeader->OptionalHeader.SizeOfHeaders);                        //整个PE头大小  
  25. printf("CheckSum:%08X ", pNtHeader->OptionalHeader.CheckSum);  
  26. printf("Subsystem:%04X ", pNtHeader->OptionalHeader.Subsystem);  
  27. printf("DllCharacteristics:%04X ", pNtHeader->OptionalHeader.DllCharacteristics);  
  28. printf("SizeOfStackReserve:%08X ", pNtHeader->OptionalHeader.SizeOfStackReserve);  
  29. printf("SizeOfStackCommit:%08X ", pNtHeader->OptionalHeader.SizeOfStackCommit);  
  30. printf("SizeOfHeapReserve:%08X ", pNtHeader->OptionalHeader.SizeOfHeapReserve);  
  31. printf("SizeOfHeapCommit:%08X ", pNtHeader->OptionalHeader.SizeOfHeapCommit);  
  32. printf("LoaderFlags:%08X ", pNtHeader->OptionalHeader.LoaderFlags);  
  33. printf("NumberOfRvaAndSizes:%08X ", pNtHeader->OptionalHeader.NumberOfRvaAndSizes);            //用来指定最后数组的大小  
  34. char DataDirectoryName[][50] = { "EXPORT Directory","IMPORT Directory","RESOURCE Directory","EXCEPTION Directory","SECURITY Directory","BASERELOC Directory",  
  35.     "DEBUG Directory","COPYRIGHT Directory","GLOBALPTR Directory","TLS Directory","LOAD_CONFIG Directory","BOUND_IMPORT Directory","IAT Directory","DELAY_IMPORT Directory",  
  36.     "COM_DESCRIPTOR Directory","Reserved Directory" };  
  37. for (int i = 0; i < pNtHeader->OptionalHeader.NumberOfRvaAndSizes; i++) {  
  38.     printf("%s : 0x%08X    0x%08X ", DataDirectoryName[i], pNtHeader->OptionalHeader.DataDirectory[i].VirtualAddress,pNtHeader->OptionalHeader.DataDirectory[i].Size);  
  39. }  

 

 

节区表:

 

 

  1. printf(" ===================================PE SECTION HEADER==================================== ");  
  2. //PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)IMAGE_FIRST_SECTION(pNtHeader);  
  3. PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeader + sizeof(IMAGE_NT_HEADERS));  
  4. for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++) {  
  5.     printf("--------------stction %d-------------- ",i+1);  
  6.     for (int j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++) {  
  7.         printf("%c", pSectionHeader->Name[j]);  
  8.     }//name的名称可能和实际作用没什么联系  
  9.     printf("      0x");  
  10.     for (int j = 0; j < IMAGE_SIZEOF_SHORT_NAME; j++) {  
  11.         printf("%X", pSectionHeader->Name[j]);  
  12.     }  
  13.     printf(" VirtualSize : 0x%04X", pSectionHeader->Misc.VirtualSize);                 //内存中节区所占大小  
  14.     printf(" VirtualAddress : 0x%08X", pSectionHeader->VirtualAddress);                //内存中节区起始地址  
  15.     printf(" SizeOfRawData : 0x%08X", pSectionHeader->SizeOfRawData);                  //磁盘文件节区所占大小  
  16.     printf(" PointerToRawData : 0x%08X", pSectionHeader->PointerToRawData);            //磁盘文件中节区起始位置  
  17.     printf(" PointerToRelocations : 0x%08X", pSectionHeader->PointerToRelocations);  
  18.     printf(" PointerToLinenumbers : 0x%08X", pSectionHeader->PointerToLinenumbers);  
  19.     printf(" NumberOfRelocations : 0x%04X", pSectionHeader->NumberOfRelocations);  
  20.     printf(" NumberOfLinenumbers : 0x%04X", pSectionHeader->NumberOfLinenumbers);  
  21.     printf(" Characteristics : 0x%08X", pSectionHeader->Characteristics);  
  22.     pSectionHeader++;  
  23.     printf(" ");  
  24. }  

 

 

 

输入表:

 

  1. printf(" ===================================PE IMPORT==================================== ");  
  2. DWORD pImportOffset = RVA_to_RAW(pNtHeader,pNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress);  
  3. PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pbFile + pImportOffset);  
  4. while (1) {  
  5.     if (pImport->FirstThunk == 0 && pImport->ForwarderChain == 0 && pImport->Name == 0 && pImport->OriginalFirstThunk == 0 && pImport->TimeDateStamp == 0) {  
  6.         break;  
  7.     }  
  8.     DWORD dwINT =  (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->OriginalFirstThunk);  
  9.     DWORD dwTimeDateStamp = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->TimeDateStamp);  
  10.     DWORD dwForwarderChain = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->ForwarderChain);  
  11.     DWORD dwName = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->Name);  
  12.     DWORD dwFirstThunk = (DWORD)pbFile + RVA_to_RAW(pNtHeader, pImport->FirstThunk);  
  13.     printf("------------------- %s ------------------- ",dwName);  
  14.     printf("TimeDateStamp: 0x%08X ", pImport->TimeDateStamp);  
  15.     printf("ForwarderChain: 0x%08X ", pImport->ForwarderChain);   
  16.     printf("pImport->FirstThunk: 0x%X ", pImport->FirstThunk);  
  17.     DWORD* ImportByName = (DWORD*)dwINT;  
  18.     DWORD* pFirstThunk = (DWORD*)dwFirstThunk;  
  19.     int i = 0;  
  20.     printf(" Address Hint Name ");  
  21.     while ((ImportByName[i])) {  
  22.         PIMAGE_IMPORT_BY_NAME pImpoetByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pbFile + RVA_to_RAW(pNtHeader, ImportByName[i]));  
  23.         printf("0x%04X ", pFirstThunk[i]);  
  24.         printf("0x%04X ",pImpoetByName->Hint);  
  25.         printf("%s ", pImpoetByName->Name);  
  26.         i++;  
  27.     }  
  28.         dwINT++;  
  29.     pImport++;  
  30. }  

 

 

输出表:

 

  1. printf(" ===================================PE EXPORT==================================== ");  
  2. PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress));  
  3. printf("Characteristics : 0x%X ", pExport->Characteristics);  
  4. printf("TimeDateStamp : 0x%X ", pExport->TimeDateStamp);  
  5. printf("MajorVersion : 0x%X ", pExport->MajorVersion);  
  6. printf("MinorVersion : 0x%X ", pExport->MinorVersion);  
  7. printf("Base : 0x%X ", pExport->Base);  
  8. printf("NumberOfNames: %d ", pExport->NumberOfNames);  
  9. printf("NumberOfFunctions: %d ", pExport->NumberOfFunctions);  
  10. DWORD* AddressOfFunctions = (DWORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfFunctions));  
  11. DWORD* AddressOfNameOrdinals = (DWORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfNameOrdinals));  
  12. DWORD* AddressOfNames = (DWORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfNames));  
  13. DWORD* Name = (DWORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->Name));  
  14. WORD* pwOrdinals = (WORD*)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pExport->AddressOfNameOrdinals));  
  15.     
  16. if (pExport->NumberOfFunctions == 0) {  
  17.     printf(" ---------- No Export Tabel! ---------- ");  
  18.     if (NULL != pbFile)  
  19.     {  
  20.         UnmapViewOfFile(pbFile);  
  21.     }  
  22.     
  23.     if (NULL != hMapping)  
  24.     {  
  25.         CloseHandle(hMapping);  
  26.     }  
  27.     
  28.     if (INVALID_HANDLE_VALUE != hFile)  
  29.     {  
  30.         CloseHandle(hFile);  
  31.     }  
  32.     
  33.     return 0;  
  34. }  
  35.     
  36. for (int i = 0; i < pExport->NumberOfNames; i++) {  
  37.     DWORD dwName = (DWORD)pbFile + RVA_to_RAW(pNtHeader, AddressOfNames[i]);  
  38.     DWORD VA = pNtHeader->OptionalHeader.ImageBase + AddressOfFunctions[i];  
  39.     printf("Ordinals: %d Name: %-30s RVA: 0x%08X VA: 0x%08X ", pwOrdinals[i], dwName, AddressOfFunctions[i], VA);  
  40. }  

 

在输入输出表这里遇到了新的问题:在知晓如何把RVA转化成RAW之后,要再次以新的RAW和起始地址找新的结构,这个时候利用了指针指向把数据输出,我一直输出的是指针存放的地址,所以非常难受。

 

整个代码已经上传至github和gitee:

Github:https://github.com/DorinXL/Easy_PEInfo

Gitee: https://gitee.com/dorinxl/Easy_PEInfo

原文地址:https://www.cnblogs.com/DorinXL/p/13368041.html