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("\ne_magic: 0x%04X", pDosHeader->e_magic);

  3. printf("\ne_cblp: 0x%04X", pDosHeader->e_cblp);

  4. printf("\ne_cp: 0x%04X", pDosHeader->e_cp);

  5. printf("\ne_crlc: 0x%04X", pDosHeader->e_crlc);

  6. printf("\ne_cparhdr: 0x%04X", pDosHeader->e_cparhdr);

  7. printf("\ne_minalloc: 0x%04X", pDosHeader->e_minalloc);

  8. printf("\ne_maxalloc: 0x%04X", pDosHeader->e_maxalloc);

  9. printf("\ne_ss: 0x%04X", pDosHeader->e_ss);

  10. printf("\ne_sp: 0x%04X", pDosHeader->e_sp);

  11. printf("\ne_csum: 0x%04X", pDosHeader->e_csum);

  12. printf("\ne_ip: 0x%04X", pDosHeader->e_ip);

  13. printf("\ne_cs: 0x%04X", pDosHeader->e_cs);

  14. printf("\ne_lfarlc: 0x%04X", pDosHeader->e_lfarlc);

  15. printf("\ne_ovno: 0x%04X\n", 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("\ne_oemid: 0x%04X”, pDosHeader->e_oemid);

  20. printf("\ne_oeminfo: 0x%04X\n”, pDosHeader->e_oeminfo);

  21. for (int i = 0; i <= 9; i++) {

  22. if (!(i % 4) && i) printf("\n");

  23. printf(“e_res[%d]: 0x%04X “, i, pDosHeader->e_res2[i]);

  24. }

  25. printf("\ne_lfanew: 0x%08X\n”, pDosHeader->e_lfanew);

    1. printf("\n==================================PE NT HEADER===================================”);//大小为F8
  26. PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pbFile + pDosHeader->e_lfanew);

  27. 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

  1. printf("\n==================================PE NT HEADER===================================");//大小为F8

  2. PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pbFile + pDosHeader->e_lfanew);

  3. printf("\nSignature: 0x%08X", pNtHeader->Signature);

文件头的结构如下:

  1. //NT头的file header

  2. printf("\n==================================PE FILE HEADER===================================\n");

  3. printf(“Machine: 0x%04X\n”, pNtHeader->FileHeader.Machine);

  4. printf(“NumberOfSections: 0x%04X\n”, pNtHeader->FileHeader.NumberOfSections); //文件中存在的节区数量

  5. printf(“TimeDateStamp: 0x%08X\n”, pNtHeader->FileHeader.TimeDateStamp);

  6. printf(“PointerToSymbolTable: 0x%08X\n”, pNtHeader->FileHeader.PointerToSymbolTable);

  7. printf(“NumberOfSymbols: 0x%08X\n”, pNtHeader->FileHeader.NumberOfSymbols);

  8. printf(“SizeOfOptionalHeader: 0x%04X\n”, pNtHeader->FileHeader.SizeOfOptionalHeader); //指出optional header 的大小

  9. printf(“Characteristics: 0x%04X\n”, pNtHeader->FileHeader.Characteristics); //标识文件的属性

可选头结构如下:

  1. //NT头的optional header

  2. printf("\n===================================PE OPTIONAL HEADER====================================\n");

    1. printf(“Machine:%04X\n”, pNtHeader->OptionalHeader.Magic);
  3. printf(“MajorLinkerVersion:%02X\n”, pNtHeader->OptionalHeader.MajorLinkerVersion);

  4. printf(“MinorLinkerVersion:%02X\n”, pNtHeader->OptionalHeader.MinorLinkerVersion);

  5. printf(“SizeOfCode:%08X\n”, pNtHeader->OptionalHeader.SizeOfCode);

  6. printf(“SizeOfInitializedData:%08X\n”, pNtHeader->OptionalHeader.SizeOfInitializedData);

  7. printf(“SizeOfUninitializedData:%08X\n”, pNtHeader->OptionalHeader.SizeOfUninitializedData);

  8. printf(“AddressOfEntryPoint:%08X\n”, pNtHeader->OptionalHeader.AddressOfEntryPoint); //代码起始位置

  9. printf(“BaseOfCode:%08X\n”, pNtHeader->OptionalHeader.BaseOfCode);

  10. printf(“BaseOfData:%08X\n”, pNtHeader->OptionalHeader.BaseOfData);

  11. printf(“ImageBase:%08X\n”, pNtHeader->OptionalHeader.ImageBase);

  12. printf(“SectionAlignment:%08X\n”, pNtHeader->OptionalHeader.SectionAlignment);

  13. printf(“FileAlignment:%08X\n”, pNtHeader->OptionalHeader.FileAlignment);

  14. printf(“MajorOperatingSystemVersion:%04X\n”, pNtHeader->OptionalHeader.MajorOperatingSystemVersion);

  15. printf(“MinorOperatingSystemVersion:%04X\n”, pNtHeader->OptionalHeader.MinorOperatingSystemVersion);

  16. printf(“MajorImageVersion:%04X\n”, pNtHeader->OptionalHeader.MajorImageVersion);

  17. printf(“MinorImageVersion:%04X\n”, pNtHeader->OptionalHeader.MinorImageVersion);

  18. printf(“MajorSubsystemVersion:%04X\n”, pNtHeader->OptionalHeader.MajorSubsystemVersion);

  19. printf(“MinorSubsystemVersion:%04X\n”, pNtHeader->OptionalHeader.MinorSubsystemVersion);

  20. printf(“Win32VersionValue:%08X\n”, pNtHeader->OptionalHeader.Win32VersionValue);

  21. printf(“SizeOfImage:%08X\n”, pNtHeader->OptionalHeader.SizeOfImage);

  22. printf(“SizeOfHeaders:%08X\n”, pNtHeader->OptionalHeader.SizeOfHeaders); //整个PE头大小

  23. printf(“CheckSum:%08X\n”, pNtHeader->OptionalHeader.CheckSum);

  24. printf(“Subsystem:%04X\n”, pNtHeader->OptionalHeader.Subsystem);

  25. printf(“DllCharacteristics:%04X\n”, pNtHeader->OptionalHeader.DllCharacteristics);

  26. printf(“SizeOfStackReserve:%08X\n”, pNtHeader->OptionalHeader.SizeOfStackReserve);

  27. printf(“SizeOfStackCommit:%08X\n”, pNtHeader->OptionalHeader.SizeOfStackCommit);

  28. printf(“SizeOfHeapReserve:%08X\n”, pNtHeader->OptionalHeader.SizeOfHeapReserve);

  29. printf(“SizeOfHeapCommit:%08X\n”, pNtHeader->OptionalHeader.SizeOfHeapCommit);

  30. printf(“LoaderFlags:%08X\n”, pNtHeader->OptionalHeader.LoaderFlags);

  31. printf(“NumberOfRvaAndSizes:%08X\n”, pNtHeader->OptionalHeader.NumberOfRvaAndSizes); //用来指定最后数组的大小

  32. char DataDirectoryName[][50] = { “EXPORT Directory”,“IMPORT Directory”,“RESOURCE Directory”,“EXCEPTION Directory”,“SECURITY Directory”,“BASERELOC Directory”,

  33. “DEBUG Directory”,“COPYRIGHT Directory”,“GLOBALPTR Directory”,“TLS Directory”,“LOAD_CONFIG Directory”,“BOUND_IMPORT Directory”,“IAT Directory”,“DELAY_IMPORT Directory”,

  34. “COM_DESCRIPTOR Directory”,“Reserved Directory” };

  35. for (int i = 0; i < pNtHeader->OptionalHeader.NumberOfRvaAndSizes; i++) {

  36. printf("%s : 0x%08X 0x%08X\n", DataDirectoryName[i], pNtHeader->OptionalHeader.DataDirectory[i].VirtualAddress,pNtHeader->OptionalHeader.DataDirectory[i].Size);

  37. }

节区表:

  1. printf("\n===================================PE SECTION HEADER====================================\n\n");

  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————–\n\n",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("\nVirtualSize : 0x%04X", pSectionHeader->Misc.VirtualSize); //内存中节区所占大小

  14. printf("\nVirtualAddress : 0x%08X", pSectionHeader->VirtualAddress); //内存中节区起始地址

  15. printf("\nSizeOfRawData : 0x%08X", pSectionHeader->SizeOfRawData); //磁盘文件节区所占大小

  16. printf("\nPointerToRawData : 0x%08X", pSectionHeader->PointerToRawData); //磁盘文件中节区起始位置

  17. printf("\nPointerToRelocations : 0x%08X", pSectionHeader->PointerToRelocations);

  18. printf("\nPointerToLinenumbers : 0x%08X", pSectionHeader->PointerToLinenumbers);

  19. printf("\nNumberOfRelocations : 0x%04X", pSectionHeader->NumberOfRelocations);

  20. printf("\nNumberOfLinenumbers : 0x%04X", pSectionHeader->NumberOfLinenumbers);

  21. printf("\nCharacteristics : 0x%08X", pSectionHeader->Characteristics);

  22. pSectionHeader++;

  23. printf("\n\n");

  24. }

输入表:

  1. printf("\n===================================PE IMPORT====================================\n");

  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 ——————-\n",dwName);

  14. printf(“TimeDateStamp: 0x%08X\n”, pImport->TimeDateStamp);

  15. printf(“ForwarderChain: 0x%08X\n”, pImport->ForwarderChain);

  16. printf(“pImport->FirstThunk: 0x%X\n”, pImport->FirstThunk);

  17. DWORD * ImportByName = (DWORD *)dwINT;

  18. DWORD * pFirstThunk = (DWORD *)dwFirstThunk;

  19. int i = 0;

  20. printf("\nAddress\t\tHint\tName\n");

  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\t”, pFirstThunk[i]);

  24. printf(“0x%04X\t”,pImpoetByName->Hint);

  25. printf("%s\n", pImpoetByName->Name);

  26. i++;

  27. }

  28. dwINT++;

  29. pImport++;

  30. }

输出表:

  1. printf("\n===================================PE EXPORT====================================\n");

  2. PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pbFile + RVA_to_RAW(pNtHeader, pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress));

  3. printf(“Characteristics : 0x%X\n”, pExport->Characteristics);

  4. printf(“TimeDateStamp : 0x%X\n”, pExport->TimeDateStamp);

  5. printf(“MajorVersion : 0x%X\n”, pExport->MajorVersion);

  6. printf(“MinorVersion : 0x%X\n”, pExport->MinorVersion);

  7. printf(“Base : 0x%X\n”, pExport->Base);

  8. printf(“NumberOfNames: %d\n”, pExport->NumberOfNames);

  9. printf(“NumberOfFunctions: %d\n”, 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));

    1. if (pExport->NumberOfFunctions == 0) {
  15. printf("\n\t———- No Export Tabel! ———-\n");

  16. if (NULL != pbFile)

  17. {

  18. UnmapViewOfFile(pbFile);

  19. }

    1. if (NULL != hMapping)
  20. {

  21. CloseHandle(hMapping);

  22. }

    1. if (INVALID_HANDLE_VALUE != hFile)
  23. {

  24. CloseHandle(hFile);

  25. }

    1. return 0;
  26. }

    1. for (int i = 0; i < pExport->NumberOfNames; i++) {
  27. DWORD dwName = (DWORD)pbFile + RVA_to_RAW(pNtHeader, AddressOfNames[i]);

  28. DWORD VA = pNtHeader->OptionalHeader.ImageBase + AddressOfFunctions[i];

  29. printf(“Ordinals: %d\tName: %-30s\tRVA: 0x%08X\tVA: 0x%08X\n”, pwOrdinals[i], dwName, AddressOfFunctions[i], VA);

  30. }

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

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

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

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

发表了74篇文章 · 总计107.96k字
本博客已稳定运行
使用 Hugo 构建
主题 StackJimmy 设计