Masimaro's blog - PE结构
type: channel
link: https://www.masimaro.top/category/PE/
description: 介绍Windows PE文件格式
items: PE解析器的编写（四）——数据目录表的解析
items: PE解析器的编写（三）——区块表的解析
items: PE文件解析器的编写（二）——PE文件头的解析
items: PE解析器的编写（一）——总体说明
items: PE文件详解（九）
items: PE文件详解（八）
items: PE文件详解（七）
items: PE文件详解（六）
items: PE文件详解（五）
items: PE文件详解（四）

PE解析器的编写（四）——数据目录表的解析
type: item
date: 2017-05-08T17:00:00+08:00
description: 在PE结构中最重要的就是区块表和数据目录表，上节已经说明了如何解析区块表，下面就是数据目录表，在数据目录表中一般只关心导入表，导出表和资源这几个部分，但是资源实在是太复杂了，而且在一般的病毒木马中也不会存在资源，所以在这个工具中只是简单的解析了一下导出表和导出表。这节主要说明导入表，下节来说导出表。RVA到fRva的转化RVA转化为fRva主要是通过某个数据在内存中的相对偏移地址找到其在文件中的相对偏移地址，在对某个程序进行逆向时，如果找到关键的那个变量或者那句指令，我根据变量或者代码指令在内存中的RVA找到它在文件中的偏移，就可以找到它的位置，修改它可能就可以破解某个程序。废话不多说，直接上代码：DWORD CPeFileInfo::RVA2fOffset(DWORD dwRVA, DWORD dwImageBase) { InitSectionTable(); vector<IMAGE_SECTION_HEADER>::iterator it; for (it = m_SectionTable.begin(); it != m_SectionTable.end(); it++) { if (dwRVA >= (DWORD)(it->VirtualAddress) && dwRVA <= (DWORD)((DWORD)(it->VirtualAddress) + it->Misc.VirtualSize) ) { break; } } if (it == m_SectionTable.end()) { return -1; } return (DWORD)(dwRVA - (DWORD)(it->VirtualAddress) + (DWORD)(it->PointerToRawData) + dwImageBase); }系统在将PE文件加载到内存中时，PE中的区块是按页的方式对齐的，也就是说同一节中的内容在一页内存中的排列方式与在文件中的排列方式相同，所以这里利用这一关系就可以根据RVA推算出它在文件中的偏移，即：fRva - Roffset = Rva - Voffset ,其中fRva是某个成员在文件中的偏移，Roffset是区块在文件中的偏移，Voffset是该区块在内存中的偏移。上述代码就是利用这个原理来计算的，代码中存在一个循环，VirtualAddress 和 VirtualSize分别代表这个区块在内存中的起始地址和这个区块所占内存的大小，当这个RVA大于起始地址，小于起始地址 + 区块大小也就说明这个RVA是处在这个区块中，这样我们就找到RVA所在区块，用RVA - 区块起始地址就得到它在区块中的偏移，这个偏移加上区块在文件中的首地址，得到的就是RVA对应的在文件中的偏移，只要知道文件的起始地址就可以知道它在文件中的详细位置了。获取数据目录表的信息数据目录表的信息主要存储在PE头结构中的OptionHeader中，回顾一下它的定义：typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCom…

PE解析器的编写（三）——区块表的解析
type: item
date: 2017-05-06T17:02:00+08:00
description: PE文件中所有节的属性都被定义在节表中，节表由一系列的IMAGE_SECTION_HEADER结构排列而成，每个结构用来描述一个节，结构的排列顺序和它们描述的节在文件中的排列顺序是一致的。 具有相同属性的数据被安排到同一个区块中。区块表的结构为IMAGE_SECTION_HEADER，在PE文件中存在一个该结构的数组，用来保存各个区块的信息，这个数组的大小在PE头的结构 IMAGE_NT_HEADERS 的成员NumberOfSections描述。区块表结构IMAGE_SECTION_HEADER结构如下：typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节表名称,如“.text” //IMAGE_SIZEOF_SHORT_NAME=8 union { DWORD PhysicalAddress; // 物理地址 DWORD VirtualSize; // 真实长度，这两个值是一个联合结构，可以使用其中的任何一个，一般是取后一个 } Misc; DWORD VirtualAddress; // 节区的 RVA 地址 DWORD SizeOfRawData; // 在文件中对齐后的尺寸 DWORD PointerToRawData; // 在文件中的偏移量 DWORD PointerToRelocations; // 在OBJ文件中使用，重定位的偏移 DWORD PointerToLinenumbers; // 行号表的偏移（供调试使用地) WORD NumberOfRelocations; // 在OBJ文件中使用，重定位项数目 WORD NumberOfLinenumbers; // 行号表中行号的数目 DWORD Characteristics; // 节属性如可读，可写，可执行等 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;在程序中我们主要列出了，区块名称、RVA、在进行内存对齐后的尺寸，在磁盘中的大小，在文件中的偏移，节属性。在界面中，定义了一个listctrl来显示这些信息。在CPeFileInfo类中定义了一个vector<IMAGE_SECTION_HEADER> m_SectionTable;专门用来存储区块表的属性信息。获取这个信息。在这个类中与区块表有关的函数主要有两个:GetSectionHeader : 用来获取指向表的指针InitSectionTable：初始化上面定义的结构下面来一一说明这两个函数PIMAGE_SECTION_HEADER CPeFileInfo::GetSectionHeader() { PIMAGE_FILE_HEADER pFileHeader = GetFileHeader(); PIMAGE_SECTION_HEADER pSectionHeader = NULL; if (NULL == pFileHeader) { return NULL; } PIMAGE_OPTIONAL_HEADER pOptionHeader = GetOptionalHeader(); pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)(pOptionHeader) + pFileHeader->SizeOfOptionalHeader); return pSectionHeader; // PIMAGE_NT_HEADERS pNtHeader = GetNtHeaders(); // return (PIMAGE_SECTION_HEADER)((DWORD)pNtHeader + sizeof(IMAGE_NT_HEADERS)); } 在PE文件中区块表的属性信息是紧密排列在PE头结构后面的，所以我们只要知道OptionHeader结构的指针，然后加上这个结构的大小就可以获取到区块表的地址，上面的代码也是这样做的，首先获取了FileHeader的指针，这个结构中的SizeOfOptionalHeader定义了OptionHeader这个结构的大小，我们利用FileHeader + SizeOfOptionalHeader这样就偏移到了区块表所在的位置。或者更简单的方式是利用PE文件头的地址 + 文件头的大小也一样可以获取到区块表的地址void CPeFileInfo::InitSectionTable() { if (!m_SectionTable.empty()) { return ; } PIMAGE_SECTION_HEADER pSectionHeader = GetSectio…

PE文件解析器的编写（二）——PE文件头的解析
type: item
date: 2017-05-04T17:06:00+08:00
description: 之前在学习PE文件格式的时候，是通过自己查看各个结构，自己一步步计算各个成员在结构中的偏移，然后在计算出其在文件中的偏移，从而找到各个结构的值，但是在使用C语言编写这个工具的时候，就比这个方便的多，只要将对应的指针类型转化为各个结构类型，就可以使用指针中的箭头来直接寻址到结构中的各个成员。这次主要说明的是PE文件头的解析，也就是之前看到的第一个界面中显示的内容,这个部分涉及到CPeFileInfo这个解析类的部分代码，以及CPeFileInfoDlg这个对话框类的代码。选择目标文件首先通过点击open按钮来弹出一个对话框，让用户选择需要解析的文件。这个部分的代码如下：void CPEInfoDlg::OnBnClickedBtnOpen() { // TODO: 在此添加控件通知处理程序代码 TCHAR szFilePath[MAX_PATH] = _T(""); OPENFILENAME ofn = {0}; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = m_hWnd; ofn.hInstance = GetModuleHandle(NULL); ofn.nMaxFile = MAX_PATH; ofn.lpstrInitialDir = _T("."); ofn.lpstrFile = szFilePath; ofn.lpstrTitle = _T("Open ...[PEInfo] by liuhao"); ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; ofn.lpstrFilter = _T("*.*\0*.*\0"); GetOpenFileName(&ofn); m_PeFileInfo.strFilePath = szFilePath; GetDlgItem(IDC_FILE_PATH)->SetWindowText(szFilePath); m_PeFileInfo.UnLoadFile(); BOOL bLoadSuccess = m_PeFileInfo.LoadFile(); if (bLoadSuccess) { //成功打开 if (m_PeFileInfo.IsPeFile()) { ShowFileHeaderInfo(); ShowOptionHeaderInfo(); GetDlgItem(IDC_BTN_CALC)->EnableWindow(TRUE); GetDlgItem(IDC_BTN_DATA_DIR_INFO)->EnableWindow(TRUE); GetDlgItem(IDC_BTN_SECTION_INFO)->EnableWindow(TRUE); GetDlgItem(IDC_BTN_CHAR_INFO)->EnableWindow(TRUE); GetDlgItem(IDC_RVA)->EnableWindow(TRUE); }else { MessageBox(_T("打开的文件不是PE文件")); InitCommandCtrl(); } } }在这段代码中首先通过GetOpenFileName函数来弹出一个选择文件的对话框。这个函数需要传入一个OPENFILENAME 结构的指针变量，这个结构比较复杂，在这说下它比较重要的几个成员：lStructSize //结构的大小 hwndOwner //这个对话框所属的父窗口句柄 hInstance //所在模块的句柄 lpstrFile //用来保存用户选择文件的路径的缓冲 nMaxFile //缓冲区的大小 lpstrTitle //这个对话框的标题 Flags//对话框的标识，具体标识请查看MSDN，一般我们用这样几个就足够了一般只需要更改标题，内存缓冲区指针和它的大小，其余按照上面的代码默认就好用户选择后，将用户选择的文件的全路径显示出来，并调用CPefileInfo类中的加载函数进行加载，如果之前加载过，那么先卸载它。然后再在对话框中显示它主要的信息，并且将所有按钮设置为可用状态，加载与卸载PE文件结构在这个里面主要有这样几个函数m_PeFileInfo.UnLoadFile(); m_PeFileInfo.LoadFile(); m_PeFileInfo.IsPeFile(); ShowFileHeaderInfo(); ShowOptionHeaderInfo();接下来转到CPeFileInfo这个类中在这个类中定义了这样四个主要的成员 CString strFilePath; //对应PE文件所在路径 HANDLE hFile; //打开这个文件时的文件句柄 P…

PE解析器的编写（一）——总体说明
type: item
date: 2017-05-03T17:13:00+08:00
description: 之前自己学习了PE文件的格式，后来自己写了个PE文件的解析器，这段时间工作上刚好要用到它，老板需要能查看某个exe中加载的dll的一个工具，我在使用之前自己写的这个东西的时候，发现很多东西都忘记了，所以，我在这回顾下当时的思路，并记录下来，方便以后直接使用。也算是回顾下之前学习的内容，将学的东西学以致用工具总体分为这样几个部分：文件头的信息pe文件节表的信息pe文件数据目录表的信息简单的从RVA到Frva的计算工具主要采用MFC的框架作为界面，pe文件的解析部分完全由自己编写，主要使用了Windows中定义的一些结构体。刚开始开启界面时，所有功能按钮和显示界面都为空，当我们正确加载一个pe文件后这些按钮就都可以使用。加载后效果如图：两侧显示pe文件的基本信息，比如文件头部中的信息，文件的OEP，基地址等等，右侧提供一个根据RVA计算它在文件中偏移的功能，工具可以显示数据目录表的信息和节表的信息。显示节表的信息如下：显示数据目录表的信息如下：一般在数据目录表中关心导出导出表，所以这个工具也提供了导入导出表的解析功能。下面是查看导入表的功能：这个主要分为两个部分，上部分是它导入的dll文件，当点击某个dll的时候，会将这个dll中的函数给列举出来。导出表的界面如下：在这个界面中主要显示这个dll模块的名称，其中导出的函数等信息。以上是程序的主要功能，下面说下程序各个模块的组成：这个是工具中的主要对话框资源，从上到下依次是关于（这个是MFC自己生成的，我只是将它的版本信息作了修改）、显示数据目录表信息的对话框，它对应的是第三个图、用来显示文件具体标识信息的对话框、显示节区表信息的对话框、显示导出表对话框、显示导入表对话框、显示pe文件头信息的对话框，是程序的主界面；我们为每一个对话框都关联了一个类，然后在专门写了一个解析pe文件中各种信息的类，这样整体的类视图如下：到此，我对这个工具中的模块作了简单的说明，后面会一一讲解各个部分的实现。敬请期待！！！！O(∩_∩)O程序源码在此

PE文件详解（九）
type: item
date: 2017-03-22T21:39:00+08:00
description: 本篇文章转载自小甲鱼的一篇日志，原文地址我们知道，Windows 将程序的各种界面定义为资源，包括加速键（Accelerator）、位图（Bitmap）、光标（Cursor）、对话框（Dialog Box）、图标（Icon）、菜单（Menu）、串表（String Table）、工具栏（Toolbar）和版本信息（Version Information）等。为了吸引大家的兴趣和目光，咱先来做个学前试验，然后再憧憬一下我们将来学习的内容有啥意义！好，小甲鱼先来演示一下如何用工具来修改资源实现汉化、改图标等，接着我们进一步从原理上来解剖 PE文件如何对资源进行存放和索引。最后，在 PE系列章节讲解完毕后，小甲鱼和大家将所有学到的知识结合在一起，我们自己打造属于我们的个性 PE工具。资源结构资源是PE 文件中非常重要的部分，几乎所有的PE 文件中都包含着资源，与导入表和导出表相比，资源的组织方式要复杂很多，其实我们只要看下图就知道俺所言不虚。我们知道我们的资源有很多种类型，每种类型的资源中可能存在多个资源项，这些资源项用不同的ID 或者名称来区分。但是要将这么多种类型的不同ID 的资源有序地组织起来是一件非常痛苦的事情，因此，我们采取类似于磁盘目录结构的方式保存。从图中我们可以看到，PE 文件中的资源是按照 资源类型 -> 资源ID -> 资源代码页的3层树型目录结构来组织资源的，通过层层索引才能够进入相应的子目录找到正确的资源。资源目录结构数据目录表中的 IMAGE_DIRECTORY_ENTRY_RESOURCE 条目（第三项）包含资源的 RVA 和大小。资源目录结构中的每一个节点都是由 IMAGE_RESOURCE_DIRECTORY 结构和紧跟其后的数个IMAGE_RESOURCE_DIRECTORY_ENTRY 结构组成的。（是不是有点像我们之前提到的文件目录？文件夹每个都长得一样，一个嵌套另一个，这样子可以实现将非常复杂的数据细化切分，小泽玛利亚、苍井空、吉泽明步、松岛枫……）我们再来看这张图：认识了这层关系后，我们来看下 IMAGE_RESOURCE_DIRECTORY 这个结构，该结构长度为 16 字节，共有 6 个字段，定义如下：IMAGE_RESOURCE_DIRECTORY STRUCTCharacteristics DWORD ? ;理论上为资源的属性，不过事实上总是0TimeDateStamp DWORD ? ;资源的产生时刻MajorVersion WORD ? ;理论上为资源的版本，不过事实上总是0MinorVersion WORD ?NumberOfNamedEntries WORD ? ;以名称（字符串）命名的入口数量NumberOfIdEntries WORD ? ;以ID（整型数字）命名的入口数量IMAGE_RESOURCE_DIRECTORY ENDS其实在这里边我们唯一要注意的就是 NameberOfNamedEntries 和 NumberOfIdEntries，它们说明了本目录中目录项的数量。两者加起来就是本目录中的目录项总和。也就是后边跟着的IMAGE_RESOURCE_DIRECTORY_ENTRY 数目。资源目录入口的结构（IMAGE_RESOURCE_DIRECTORY_ENTRY）IMAGE_RESOURCE_DIRECTORY_ENTRY 紧跟在资源目录结构后，此结构长度为 8 个字节，包含 2 个字段。该结构定义如下：IMAGE_RESOURCE_DIRECTORY_ENTRY STRUCTName DWORD ? ;目录项的名称字符串指针或IDOffsetToData DWORD ? ;目录项指针IMAGE_RESOURCE_DIRECTORY_ENTRY ENDSName 字段完全是个百变精灵，改字段定义的是目录项的名称或ID。当结构用于第一层目录时，定义的是资源类型；当结构定义于第二层目录时，定义的是资源的名称；当结构用于第三层目录时，定义的是代码页编号。注意：当最高位为 0 的时候，表示字段的值作为 ID 使用；而最高位为 1 的时候，字段的低位作为指针使用（资源名称字符串是使用 UNICODE编码），但是这个指针不是直接指向字符串哦，而是指向一个 IMAGE_RESOURCE_DIR_STRING_U 结构的。该结构定义如下：IMAGE_RESOURCE_DIR_STRING_U STRUCTLength DWORD ? ; 字符串的长度NameString DWORD ? ; UNICODE字符串，由于字符串是不定长的。由Length 制定长度IMAGE_RESOURCE_DIR_STRING_U ENDSOffsetOfData 字段是一个指针，当最高位为 1 …

PE文件详解（八）
type: item
date: 2017-03-22T20:01:00+08:00
description: 本文转载自小甲鱼PE文件详解系列教程原文传送门当应用程序需要调用DLL中的函数时，会由系统将DLL中的函数映射到程序的虚拟内存中，dll中本身没有自己的栈，它是借用的应用程序的栈，这样当dll中出现类似于mov eax, [1000000]这样直接寻址的代码时，由于事先并不知道它会被映射到应用程序中的哪个位置，并且可能这个内存地址已经被使用，所以当调用dll中的函数时，系统会进行一个基址重定位的操作。系统是根据dll中的基址重定位表中的信息决定如何进行基址重定位，哪些位置的指令需要进行基址重定位。所以这次主要说明基址重定位表。这个重定位表位于数据目录表的第六项。这个表的主要结构如下：IMAGE_BASE_RELOCATION STRUC VirtualAddress DWORD ? ; 重定位数据开始的RVA 地址 SizeOfBlock DWORD ? ; 重定位块得长度 TypeOffset WORD ? ; 重定项位数组 IMAGE_BASE_RELOCATION ENDSVirtualAddress 是 Base Relocation Table 的位置它是一个 RVA 值SizeOfBlock 是 Base Relocation Table 的大小；这个结构的示意图如下：TypeOffset 是一个数组，数组每项大小为两个字节（16位），它由高 4位和低 12位组成，高 4位代表重定位类型，低 12位是重定位地址。高4位一般是3，表示这个地址是一个32位的地址，它与 VirtualAddress 相加即是指向PE 映像中需要修改的那个地址的位置，注意这里不是定位到对应代码的位置接下来进行手工的方式找到需要重定位的代码位置：打开一个dll文件，发现它的基址重定位表所在RVA = 0x00004000通过计算得到它是在.relo ，对应文件的偏移为0x800,查看这个位置的值为：VirtualAddress = 0x1000,SizeOfBlock = 0x18通过它的大小，得知需要重定位的位置主要有(0x18 - 8) / 2 = 8，最后一个以0结尾，所以实际上总共有7处需要重定位这8个位置分别为:0x028, 0x02e, 0x003e 0x04b, 0x051, 0x61, 0x6c后面的以此类推，可以发现这些需要重定位的位置，存储的都是一些立即寻址的地址

PE文件详解（七）
type: item
date: 2017-03-21T22:00:00+08:00
description: 本文转载自小甲鱼PE文件讲解系列原文传送门这次主要说明导出表，导出表一般记录着文件中函数的地址等相关信息，供其他程序调用，常见的.exe文件中一般不存在导出表，导出表更多的是存在于dll文件中。一般在dll中保存函数名称以及它的地址，当某个程序需要调用dll中的函数时，如果这个dll在内存中，则直接找到对应函数在内存中的位置，并映射到对应的虚拟地址空间中，如果在内存中没有对应的dll，则会先通过PE加载器加载到内存，然后再进行映射导出表结构导出表（Export Table）中的主要成分是一个表格，内含函数名称、输出序数等。序数是指定DLL 中某个函数的16位数字，在所指向的DLL 文件中是独一无二的。在此我们不提倡仅仅通过序数来索引函数的方法，这样会给DLL 文件的维护带来问题。例如当DLL 文件一旦升级或修改就可能导致调用改DLL 的程序无法加载到需要的函数。数据目录表的第一个成员指向导出表，是一个IMAGE_EXPORT_DIRECTORY(以后简称IED)结构，IED 结构的定义如下：IMAGE_EXPORT_DIRECTORY STRUCT Characteristics DWORD ? ; 未使用，总是定义为0 TimeDateStamp DWORD ? ; 文件生成时间 MajorVersion WORD ? ; 未使用，总是定义为0 MinorVersion WORD ? ; 未使用，总是定义为0 Name DWORD ? ; 模块的真实名称 Base DWORD ? ; 基数，加上序数就是函数地址数组的索引值 NumberOfFunctions DWORD ? ; 导出函数的总数 NumberOfNames DWORD ? ; 以名称方式导出的函数的总数 AddressOfFunctions DWORD ? ; 指向输出函数地址的RVA AddressOfNames DWORD ? ; 指向输出函数名字的RVA AddressOfNameOrdinals DWORD ? ; 指向输出函数序号的RVA IMAGE_EXPORT_DIRECTORY ENDSName：一个RVA 值，指向一个定义了模块名称的字符串。如即使Kernel32.dll 文件被改名为”Ker.dll”。仍然可以从这个字符串中的值得知其在编译时的文件名是”Kernel32.dll”。NumberOfFunctions：文件中包含的导出函数的总数。NumberOfNames：被定义函数名称的导出函数的总数，显然只有这个数量的函数既可以用函数名方式导出。也可以用序号方式导出，剩下的NumberOfFunctions 减去NumberOfNames 数量的函数只能用序号方式导出。该字段的值只会小于或者等于 NumberOfFunctions 字段的值，如果这个值是0，表示所有的函数都是以序号方式导出的。AddressOfFunctions：一个RVA 值，指向包含全部导出函数入口地址的双字数组。数组中的每一项是一个RVA 值，数组的项数等于NumberOfFunctions 字段的值。Base：导出函数序号的起始值，将AddressOfFunctions 字段指向的入口地址表的索引号加上这个起始值就是对应函数的导出 序号。假如Base 字段的值为x，那么入口地址表指定的第1个导出函数的序号就是x；第2个导出函数的序号就是x＋1。总之，一个导出函数的导出序号等 于Base 字段的值加上其在入口地址表中的位置索引值。这个只是一个导出序号导出给外部进行使用的，当我们在分析PE文件进行相关函数的定址时，不使用这个序号，表中也没有存储函数的导出序号AddressOfNames 和 AddressOfNameOrdinals：均为RVA 值。前者指向函数名字符串地址表。这个地址表是一个双字数组，数组中的每一项指向一个函数名称字符串的RVA。数组的项数等于NumberOfNames 字段的值，所有有名称的导出函数的名称字符串都定义在这个表中；后者指向另一个word 类型的数组（注意不是双字数组）。数组项目与文件名地址表中的项目一一对应，项目值代表函数入口地址表的索引，这样函 数名称与函数入口地址关联起来。（举个例子说，加入函数名称字符串地址表的第n 项指向一个字符串“MyFunction”。那么可以去查找 AddressOfNameOrdinals 指向的数组的第n 项，假如第n 项中存放的值是x，则表示AddressOfFunctions 字段描述的地址表中的第x 项函数入口地址对应的名称就是“MyFunction”他们的关系如图所示：一般在分析定位函数地址的时候采用的是通过函数名称来定位在定位时可以使用序号的方式，也可以使用函数名的方式来定位，使用序号需要提…

PE文件详解（六）
type: item
date: 2017-03-21T19:00:00+08:00
description: 这篇文章转载自小甲鱼的PE文件详解系列原文传送门之前简单提了一下节表和数据目录表，那么他们有什么区别？其实这些东西都是人为规定的，一个数据在文件中或者在内存中的位置基本是固定的，通过数据目录表进行索引和通过节表进行索引都是可以找到的，也可以这么说，同一个数据在节表和数据目录表中都有一份索引值，那么这两个表有什么区别？一般将具有相同属性的值放到同一个节区中，这也就是说同一个节区的值只是保护属性相同，但是他们的用途不一定是一样的，但是在同一数据目录表中的数据的作用是相同的，比如输入函数表中只会保存输入函数的相关信息，输出函数表中只会保存输出函数的信息，而输入输出函数在PE文件中可能都位于.text这个节中。输入函数表输入函数：一般将那些在本程序中调用，但是它的代码不在本程序中的函数称为输入函数，输入函数一般都在另外一个独立的dll中。在之前谈到PE头的时候说到，在PE头中有一个结构是数据目录表，它的结构如下：IMAGE_DATA_DIRECTORY STRUCT VirtualAddress DWORD ? ; 数据的起始RVA isize DWORD ? ; 数据块的长度 IMAGE_DATA_DIRECTORY ENDS这个结构大小为8，相对于PE文件头的偏移为0x78。在PE文件中，通过一个数组来保存多个数据目录表的信息，而输入函数表则是这个数组的第二个元素。而输入表是以一个 IMAGE_IMPORT_DESCRIPTOR(简称IID) 的数组开始。每个被 PE文件链接进来的 DLL文件都分别对应一个 IID数组结构。在这个 IID数组中，并没有指出有多少个项(就是没有明确指明有多少个链接文件)，但它最后是以一个全为NULL(0) 的 IID 作为结束的标志。IMAGE_IMPORT_DESCRIPTORIMAGE_IMPORT_DESCRIPTOR STRUCT union Characteristics DWORD ? OriginalFirstThunk DWORD ? ends TimeDateStamp DWORD ? ForwarderChain DWORD ? Name DWORD ? FirstThunk DWORD ? IMAGE_IMPORT_DESCRIPTOR ENDSOriginalFirstThunk它指向first thunk，IMAGE_THUNK_DATA，该 thunk 拥有 Hint 和 Function name 的地址。TimeDateStamp该字段可以忽略。如果那里有绑定的话它包含时间/数据戳（time/data stamp）。如果它是0，就没有绑定在被导入的DLL中发生。在最近，它被设置为0xFFFFFFFF以表示绑定发生。ForwarderChain一般情况下我们也可以忽略该字段。在老版的绑定中，它引用API的第一个forwarder chain（传递器链表）。它可被设置为0xFFFFFFFF以代表没有forwarder。Name它表示DLL 名称的相对虚地址（译注：相对一个用null作为结束符的ASCII字符串的一个RVA，该字符串是该导入DLL文件的名称。如：KERNEL32.DLL）。FirstThunk它包含由IMAGE_THUNK_DATA定义的 first thunk数组的虚地址，通过loader用函数虚地址初始化thunk。在Orignal First Thunk缺席下，它指向first thunk：Hints和The Function names的thunks。这个OriginalFirstThunk 和 FirstThunk明显是亲家，两家伙首先名字就差不多哈。那他们有什么不可告人的秘密呢？IMAGE_THUNK_DATA STRUC union u1 ForwarderString DWORD ? ; 指向一个转向者字符串的RVA Function DWORD ? ; 被输入的函数的内存地址 Ordinal DWORD ? ; 被输入的API 的序数值 AddressOfData DWORD ? ; 指向 IMAGE_IMPORT_BY_NAME ends IMAGE_THUNK_DATA ENDS我们可以看出由于是union结构，所以IMAGE_THUNK_DATA 事实上是4个字节大小。这个共用体是怎么使用的呢：当 IMAGE_THUNK_DATA 值的最高位为 1时，表示函数以序号方式输入，这时候低 31位被看作一个函数序号。当 IMAGE_THUNK_DATA 值的最高位为 0时，表示函数以字符串类型的函数名方式输入，这时双字的值是一个 RVA，指向一个 IMAGE_IMPORT_BY_NAME 结构。接下来说明IMAGE_IMPORT_BY_NA…

PE文件详解（五）
type: item
date: 2017-03-20T18:21:00+08:00
description: 在前面几节中经常提到相对虚拟地址RVA，在这篇博客中主要说明这个概念。本来是想接着转载小甲鱼的，但是我自己根据这篇文章和他的视频来学习的时候，发现在RVA与文件的相对偏移地址进行转化的时候，那块我看不懂，不知道为什么要这样转化，而且前面很多东西都反复讲了好多遍，比如对齐的问题，所以，这篇我就自己根据自己掌握的情况来写，还是在此处放上原文的连接：原文(上)传送门原文(下)传送门什么是RVA某个位置的RVA是该位置在内存中的地址相对于整个文件在内存中首地址的偏移值，举个例子，当PE文件中某个全局区中的一个数值所在内存的地址为0x0040871234，而PE文件被系统加载到内存时，它的的首地址为0x00400000(PS:评论区的老哥说的不错，这个地址只是一个参考值，加载器会优先选择以这个地址作为加载的首地址，如果该地址被占用，则另外选取，一般在加载DLL中出现占用情况，由于exe一般作为进程来加载，它占用4GB的独立地址空间，所以一般不会出现占用)，那么这个数值的RVA就是0x0040871234 - 0x00400000 = 0x871234RVA到文件偏移的转化有了RVA之后，发现所有数据在内存中进行索引都十分的方便，只要有了基地址和RVA后，通过基地址 + RVA就可以找到对应的数据，但是在PE中并没有类似值来记录某个数据到文件头的偏移，而且由于PE文件在内存中和在磁盘中的对齐值不同，造成某个数据在内存中的偏移和在磁盘中的偏移也不同，这样在磁盘中找到某些值就比较困难虽然PE文件在内存中的对齐值与在磁盘中的不同，各个区块在内存中的地址与在文件中的不同，但是各个区块里面数据的摆放基本是一样的，也就是在区块中，某个值相对于区块首地址的偏移是一样的，现在定义这样几个变量：Rva：某个数值在内存中的偏移Voffset：节点首地址在内存中相当于基地址的偏移Roffset：节点首地址在磁盘中相当于文件头的偏移fRva：某个数值在磁盘中相对于文件头的偏移根据上面的等量关系，可以得到这样的方程:fRva - Roffset = Rva - Voffset==>fRva = Rva- Voffset + Roffset根据这个公式来计算时需要进行如下的步骤：根据某个Rva落在哪个区块中：取每个节区在内存中的首地址（IMAGE_SECTION_HEADER中的VirtualAddress值） + 区块真实大小（IMAGE_SECTION_HEADER中的VirtualSize），如果Rva小于这个值得话，说明是落在这个节中通过第一步，我们可以知道Rva处于哪个区块，这样就可以得到区块的首地址在磁盘中相对于文件头的偏移就也就是Voffset的值。在文件中根据IMAGE_SECTION_HEADER中的PointerToRawData值可以得到这个区块在磁盘中相当于文件头的首地址，这样通过上述公式可以计算出Rva在文件中的偏移最终得到的公式如下：fRva = Rva - VirtualAddress + PointerToRawData举个例子，用lordPE这个工具打开一个.exe文件：我们来在磁盘中找到ImportTable在磁盘中的入口位置，即0x0001B1C4这个RVA对应在磁盘中的位置一个个对比发现.textbss:这个区块的RVA从0x00001000~0x00011000,这个值不在这个区块内.text:这个区块的RVA从0x00011000~0x00016060,这个值不在这个区块内.rdata这个区块的RVA从0x00017000~0x0001903d,这个值不在这个区块内.data这个区块的RVA从0x0001A000~0x000A158C,这个值不在这个区块内.idata这个区块的RVA从0x0001B000~0x0001BAF2，这个值在这个区块内根据上面的信息可以得到fRva = Rva - VirualAddress + PointerToRawData = 0x0001B1C4 - 0x0001B000 + 0x00007A00 = 7BC4

PE文件详解（四）
type: item
date: 2017-03-18T18:24:00+08:00
description: 本文转自小甲鱼的PE文件详解系列原文传送门到此为止，小甲鱼和大家已经学了许多关于 DOS header 和 PE header 的知识。接下来就该轮到SectionTable (区块表，也成节表)。越学越多的结构，大家可能觉得PE挺乱挺杂的哈，所以这里插播下一下必要知识的详细注释，大伙可以按需要看。PE文件中所有节的属性都被定义在节表中，节表由一系列的IMAGE_SECTION_HEADER结构排列而成，每个结构用来描述一个节，结构的排列顺序和它们描述的节在文件中的排列顺序是一致的。全部有效结构的最后以一个空的IMAGE_SECTION_HEADER结构作为结束，所以节表中总的IMAGE_SECTION_HEADER结构数量等于节的数量加一。节表总是被存放在紧接在PE文件头的地方。另外，节表中 IMAGE_SECTION_HEADER 结构的总数总是由PE文件头 IMAGE_NT_HEADERS 结构中的 FileHeader.NumberOfSections 字段来指定的。typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节表名称,如“.text” //IMAGE_SIZEOF_SHORT_NAME=8 union { DWORD PhysicalAddress; // 物理地址 DWORD VirtualSize; // 真实长度，这两个值是一个联合结构，可以使用其中的任何一个，一般是取后一个 } Misc; DWORD VirtualAddress; // 节区的 RVA 地址 DWORD SizeOfRawData; // 在文件中对齐后的尺寸 DWORD PointerToRawData; // 在文件中的偏移量 DWORD PointerToRelocations; // 在OBJ文件中使用，重定位的偏移 DWORD PointerToLinenumbers; // 行号表的偏移（供调试使用地) WORD NumberOfRelocations; // 在OBJ文件中使用，重定位项数目 WORD NumberOfLinenumbers; // 行号表中行号的数目 DWORD Characteristics; // 节属性如可读，可写，可执行等 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;Name：区块名。这是一个由8位的ASCII 码名，用来定义区块的名称。多数区块名都习惯性以一个“.”作为开头（例如：.text），这个“.” 实际上是不是必须的。值得我们注意的是，如果区块名超过 8 个字节，则没有最后的终止标志“NULL” 字节。并且前边带有一个“$” 的区块名字会从连接器那里得到特殊的待遇，前边带有“$” 的相同名字的区块在载入时候将会被合并，在合并之后的区块中，他们是按照“$” 后边的字符的字母顺序进行合并的。另外小甲鱼童鞋要跟大家啰嗦一下的是：每个区块的名称都是唯一的，不能有同名的两个区块。但事实上节的名称不代表任何含义，他的存在仅仅是为了正规统一编程的时候方便程序员查看方便而设置的一个标记而已。所以将包含代码的区块命名为“.Data” 或者说将包含数据的区块命名为“.Code” 都是合法的。因此，小甲鱼建议大家：当我们要从PE 文件中读取需要的区块时候，不能以区块的名称作为定位的标准和依据。正确的方法是按照 IMAGE_OPTIONAL_HEADER32 结构中的数据目录字段结合进行定位。Virtual Size:对应的区块的大小，这是区块的数据在没有进行对齐处理前的实际大小。Virtual Address:该区块装载到内存中的RVA 地址。这个地址是按照内存页来对齐的，因此它的数值总是 SectionAlignment 的值的整数倍。在Microsoft 工具中，第一个块的默认 RVA 总为1000h。在OBJ 中，该字段没有意义地，并被设为0。SizeOfRawData：该区块在磁盘中所占的大小。在可执行文件中，该字段是已经被FileAlignment 潜规则处理过的长度。PointerToRawData：该区块在磁盘中的偏移。这个数值是从文件头开始算起的偏移量哦。PointerToRelocations：这哥们在EXE文件中没有意义，在OBJ 文件中，表示本区块重定位信息的偏移值。（在OBJ 文件中如果不是零，它会指向一个IMAGE_RELOCATION 结构的数组）PointerToLinenumbers：行号表在文件中的偏移值，文件的调试信息，于我们没用，鸡肋。NumberOfRelocations：这哥们在EXE文件中也没有意义，在OBJ …