-
-
[原创]pe文件结构解读-1
-
发表于: 2023-5-6 18:02 7239
-
前言:
pe文件逆向分析、免杀、病毒分析,脱壳加壳都是有着非常重要的技能,掌握学习pe还是比较重要滴
1.PE文件简介
PE(PortableExecutable),即可移植的执行体。所有的Windows(包括Win9x、WinNT、Win CE)下的可执行文件(包括EXE、DLL、SYS、OCX、COM)均使用PE文件结构,这些使用PE文件结构的可执行文件也成为PE文件。
作为一个普通的程序员也许没有必要掌握 PE文件结构,因为普通的程序员大多都是开发服务性、决策性、的软件。但是对于学习黑客知识或者学习安全编程的程序员来说,那么掌握PE文件结构的知识就非常重要了。通过pe文件可以大致猜出此文件的功能
现在对PE结构有一个整体上的认识,要知道PE结构包含的结构体有DOS头、PE头(PE标识、文件头、可选头)、节表、节表数据等,如图所示:
DOS头部其实是一个DOS头部,也可以叫MZ头,该部分用于在DOS下加载可执行程序,是用IMAGE_DOS_HEADER来定义的
DOS存根是一段简单的程序,主要用于输出"This program cannot be run in DOS mode"类似的字符串。
PE头部是保存Windows系统加载可执行文件的重要信息。
节表:是PE文件对后续节表数据的描述。
节表的数据:每个节实际上是一个容器,这里面可以包含程序的代码、数据等等,每个节可以有独立的内存权限,比如代码节默认有读/执行权限,节的名字和数量可以自己定义。
1.1 DOS头部详解:
IMAGE_DOS_HEADER:
DOS部分主要是为了兼容以前的DOS系统,DOS部分可以分为DOS MZ文件头(IMAGE_DOS_HEADER)和DOS块(DOS Stub)组成,PE文件的第一个字节位于一个传统的MS-DOS头部,称作IMAGE_DOS_HEADER,占用64个字节 ,其结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | typedef struct _IMAGE_DOS_HEADER { / / DOS .EXE header WORD e_magic; / / 一个WORD类型,值是一个常数 0x4D5A ,用文本编辑器查看该值位‘MZ’,可执行文件必 WORD e_cblp; / / Bytes on last page of file WORD e_cp; / / Pages in file WORD e_crlc; / / Relocations WORD e_cparhdr; / / Size of header in paragraphs WORD e_minalloc; / / Minimum extra paragraphs needed WORD e_maxalloc; / / Maximum extra paragraphs needed WORD e_ss; / / Initial (relative) SS value WORD e_sp; / / Initial SP value WORD e_csum; / / Checksum WORD e_ip; / / Initial IP value WORD e_cs; / / Initial (relative) CS value WORD e_lfarlc; / / File address of relocation table WORD e_ovno; / / Overlay number WORD e_res[ 4 ]; / / Reserved words WORD e_oemid; / / OEM identifier ( for e_oeminfo) WORD e_oeminfo; / / OEM information; e_oemid specific WORD e_res2[ 10 ]; / / Reserved words LONG e_lfanew; / / 指向PE文件头的地址 } IMAGE_DOS_HEADER, * PIMAGE_DOS_HEADER; |
DOS部分我们主要的是e_magic成员和e_lfanew成员,前者是标识PE指纹的一部分,后者则是寻找PE文件头的部分,除了这两个成员,其他成员全部用0填充都不会影响程序正常运行,所以我们不需要过多的对其他部分深究,DOS部分在16进制编辑器中看就是下图的部分:
DOS存根:我们可以看到e_lfanew指向PE文件头,我们可以通过它来寻找PE文件头,而DOS块的部分自然就是PE文件头和DOS MZ文件头中间的部分,这部分是由链接器所写入的,可以随意进行修改,并不影响程序的运行:
1.2 PE头部详解:
IMAGE_NT_HEADERS:
PE文件头由PE文件头标志,标准PE头,扩展PE头三部分组成。PE头部是真正用来装载Win32程序的头部,PE文件头标志是50 45 00 00,也就是PE,我们从结构体的角度看一下PE文件头的详细信息:
64位和32位结构体不同是IMAGE_OPTIONAL_HEADER的字节不同,体现在拓展PE头,64位结构体占240字节、32位结构体占224字节。(读取标准pe头的Magic字段为0x010b为32位程序,为0x020b是64位程序)
1 2 3 4 5 | typedef struct _IMAGE_NT_HEADERS64 { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER64 OptionalHeader; } IMAGE_NT_HEADERS64, * PIMAGE_NT_HEADERS64; |
1 2 3 4 5 | typedef struct _IMAGE_NT_HEADERS { DWORD Signature; / / PE文件头标志 = > 4 字节 IMAGE_FILE_HEADER FileHeader; / / 文件头标准PE头 = > 20 字节 IMAGE_OPTIONAL_HEADER32 OptionalHeader; / / 扩展PE头 = > 32 位下 224 字节( 0xE0 ) 64 位下 240 字节( 0xF0 ) } IMAGE_NT_HEADERS32, * PIMAGE_NT_HEADERS32; |
PE标识Signature:
PE标识占4个字节,winhex打开如下所示4550,ANSI码为PE
标准PE头IMAGE_FILE_HEADER:
标准PE头结构如下,有20个字节,主要存储运行(平台、节的数量、时间、拓展头大小、文件类型)我们可以从PE文件头标志后20个字节找到它,如下图:
1 2 3 4 5 6 7 8 9 | typedef struct _IMAGE_FILE_HEADER { WORD Machine; / / 可以运行的目标CPU类型 WORD NumberOfSections; / / 节的数量 DWORD TimeDateStamp; / / 表示文件何时被创建的编译器填写的时间戳 DWORD PointerToSymbolTable; / / 调试相关 DWORD NumberOfSymbols; / / 调试相关 WORD SizeOfOptionalHeader; / / 标识扩展PE头(IMAGE_OPTIONAL_HEADER)大小 WORD Characteristics; / / 文件的属性 } IMAGE_FILE_HEADER, * PIMAGE_FILE_HEADER; |
(1)Machine:可以运行的目标CPU类型
(2)NumberOfSection:表示节表的数目,节表紧跟在IMAGE_NT_HEADERS后面
(3)TimeDateStamp:表示文件的创建时间。
(4)PointerToSymbolTable:调试相关
(5)NumberOfSymbols: 调试相关
(6)SizeOfOptionalHeaders:标识pe拓展头的大小
(7)Characteristics: 文件的属性,普通的exe字段一般为010fh,dll字段为2102h
扩展PE头IMAGE_OPTIONAL_HEADER:
IMAGE_OPTIONAL_HEADER被称为"可选头"。虽然被称为可选头,但是必须存在的一个头,之所以称作为"可选头"认为是在该头的数据目录数组中,有的数据目录项(后面会讲到)是可有可无的,这部分内容是可选的。可选头紧挨着文件头。其中较为重要的是文件对齐、内存对齐、数据目录表。
32位结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | typedef struct _IMAGE_OPTIONAL_HEADER { / / / / Standard fields. / / WORD Magic; / / 指定文件状态的类型 PE32: 10B PE64: 20B BYTE MajorLinkerVersion; / / 主链接版本号 BYTE MinorLinkerVersion; / / 次链接版本号 DWORD SizeOfCode; / / 所有含有代码的区块的大小 编译器填入 没用(可改) DWORD SizeOfInitializedData; / / 所有初始化数据区块的大小 编译器填入 没用(可改) DWORD SizeOfUninitializedData; / / 所有含未初始化数据区块的大小 编译器填入 没用(可改) DWORD AddressOfEntryPoint; / / 程序入口RVA DWORD BaseOfCode; / / 代码区块起始RVA DWORD BaseOfData; / / 数据区块起始RVA / / / / 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; / / 内存中整个PE文件的映射的尺寸,可比实际值大,必须是SectionAlignment的整数倍 DWORD SizeOfHeaders; / / 所有的头加上节表文件对齐之后的值 DWORD CheckSum; / / 映像校验和,一些系统.dll文件有要求,判断是否被修改 WORD Subsystem; WORD DllCharacteristics; / / 文件特性,不是针对DLL文件的, 16 进制转换 2 进制可以根据属性对应的表格得到相应的属性 DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; / / 数据目录表,结构体数组 } IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32; |
64位结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | typedef struct _IMAGE_OPTIONAL_HEADER64 { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; ULONGLONG 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; ULONGLONG SizeOfStackReserve; ULONGLONG SizeOfStackCommit; ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER64, * PIMAGE_OPTIONAL_HEADER64; |
这里介绍32位的结构,因为32位结构覆盖完了64位所有属性,只是属性的占用字节数不同(加粗的是32位有的,64位没用的):
(1)Magic:这是一个标记字,32位为010bh,64位为020bh
(2)MajorLinkerVersion: 主链接版本号
(3)MinorLinkerVersion: 次链接版本号
(4)SizeOfCode: 代码节的大小,如果有多个代码节的话,就是它们的总和。该处是指所有包含可执行属性的节的大小。
(5)SizeOflnitializedData: 已初始化数据块的大小。
(6)SizeOfUninitializedData:未初始化数据块的大小。
(7)AddressOfEntryPoint:程序执行的入口地址,该地址是一个相对虚拟地址,该地址简称EP。如果加壳后,找到了该地址,就被称作了OEP。该地址指向的不是main(),也不是WinMain()的地址该地址指向了运行库代码的地址。对于DLL这个值的意义不大,因为DLL甚至可以没有DIIMain()函数,没有DIIMain()是无法捕获DLL的4个消息的
(8)BaseOfCode:代码段的起始相对虚拟地址。
(9)BaseOfData:数据段的起始相对虚拟地址。
(10)ImageBase:文件被装入内存后的首选建议装载地址。对于EXE文件来说,通常情况下该地址就是装载地址:对于DLL 来说,可能就不是其装入内存后的地址了。程序加载到内存的起始地址
(11)SectionAlignment:节被装入内存后的对齐值。节被映射到内存中需要对齐的单位。通常情况下0x1000,也就是4KB大小。Windows操作系统的内存分页一般为4KB
(12)FileAlignment:节在文件中的对齐值。节在磁盘上是对齐单位。
(13)MajorOperatingSystemVersion:要求最低操作系统的主版本号。
(14)MinorOperatingSystemVersion:要求最低操作系统的次版本号。
(15)MajorImageVersion:可执行文件的主版本号。
(16)MinorImageVersion:可执行文件的次版本号
(I7)MajorSussystemVersion:要求最低子系统的主版本号。
(18)MinorSubsystemVersion:要求最低子系统的次版本号。
(19)Win32VersionValue:该成员变量是被保留的。
(20)SizeOflmage:可执行文件装入内存后的总大小。该大小按内存对齐方式对齐
(21)SizeOfHeaders:PE头的大小,这个PE 头泛指DOS头、PE头、节表的总和大小。
(22)CheckSum:校验和对于EXE文件通常为0对SYS文件则必须有一个校验和
(23)Subsystem:可执行文件的子系统类型(只对exe重要)。
(24)DIICharacteristics:指定DLL文件的属性。该值大部分时候为0。
(25)SizefStackReserve:为线程保留的栈大小
(26)SizeOfStackCommit:为线程已经提交的找大小
(27)SizeOfHeapReserve:为线程保留的堆大小
(28)SizeOfHeapCommit:为线程已经提交的堆大小
(29)LoaderFlags:被废弃的成员值。MSDN上的原话为“This member is obsolete”。但是该值在某些情况下还是会被用到的,比如针对旧版OD时,修改该值会起到反调试的作用
(30)NumberOfRvaAndSizes:数据目录项的个数。
(31)DataDirectory:数据目录表,由NumberOfRvaAndSize个IMAGE_DATA_DIRECTORY结构体组成。该数组中包含了输入表、输出表、资源等数据的RVA。IMAGE_DATA_DIRECTORY 的定义如下
1 2 3 4 | typedef struct _IMAGE_DATA_DIRECTORY ( DWORD VirtualAddress; DWORD Size; ) IMAGE_DATA_DIRECTORY, * PIMAGE_DATA_DIRECTORY; |
该结构体的第一个变量为该目录的相对虚拟地址的起始值,第二个是该目录的长度。(这里先做大概了解,后续文章会对数据目录表进行讲解)
1.3 节表详解:
在pe文件头与原始数据之间存在一个节区表(Section Table)。节区表中包含每个块在映像中的信息,分别指向不同的区块实体。
节表结构:
每个IMAGE_SECTION_HEADER代表一个节表,其中都存放着可执行文件被映射到内存中所在位置的信息。节表的位置在IMAGE_OPTIONAL_HEADER的后面,其中节的个数由IMAGE_FILE_HEADER中的NumberOfSections给出。节表的结构如下,40个字节: DWORD 4个字节 WORD占2个字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; / / ASCII字符串 可自定义 只截取 8 个字节 union { / / 该节在没有对齐之前的真实尺寸,该值可以不准确 DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; / / 加载到内存中的部分的第一个字节的地址,相对于映像基地址RVA DWORD SizeOfRawData; / / 节在文件中对齐的大小 DWORD PointerToRawData; / / 节区在文件中的偏移 DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; / / 节的属性 } IMAGE_SECTION_HEADER, * PIMAGE_SECTION_HEADER; |
(1)Name: 该成员变量保存节的名称,节的名称用ASCII编码来保存。节名的长度IMAGE_SIZEOF_SHORT_NAME,这是一个宏,该宏的定义如下:
1 | #define IMAGE_SIZEOF_SHORT_NAME 8 |
节名的长度为8个字节,多余的字节会被自动截断。通常情况下节名以“.”为开始,当然这是编译器的习惯。我们看一下图的前8个字节的内容为“2E74657874000000”其对应ASCII字符为“text
(2)VirtualSize: 指出实际被使用的区块大小,是在对齐处理前的区块的大小
(3)VirtualAddress: 该节区载入到内存后的相对虚拟地址。这个地址是按内存进行对齐的,是sectionAlignment的整数倍
(4)SizeOfRawData:该节区在磁盘上所占用的空间大小,该值通常是对齐后的值该字段包括经过FileAlignment调整后块的长度。例如FileAlignment的大小为200h,如果VirtualSize中的块长度为19Ah个字节,这一块保存的长度为200h个字节,用0进行填充。
(5)PointerToRawData:该节区在磁盘文件上的偏移值。
(6)PointerToRelocations:在exe文件中没用意义,在obj文件中,表示本块重定位信息的偏移量。
(7)PointerToLinenumbers:行号表在文件中的偏移量。这是文件的调试信息。
(8)NumberOfRelocations:在exe中无意义。
(9)NumberOfLinenumbers:该块在行号表中的行号数目
(10)Characteristics:节区属性。该属性的值如图所示。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | / / 区段解析遍历 printf( "Selection Header Info:\n" ); PIMAGE_SECTION_HEADER ReadSectionHeader = IMAGE_FIRST_SECTION(ReadNTHeaders); PIMAGE_FILE_HEADER pFileHeader = &ReadNTHeaders - >FileHeader; for ( int i = 0 ; i < pFileHeader - >NumberOfSections; i + + ) { printf( "Name(区段名称):%s\n" , ReadSectionHeader[i].Name); printf( "VirtualSize(数据实际节区大小VirtualSize):%08X\n" , ReadSectionHeader[i].Misc.VirtualSize); printf( "VOffset(起始的相对虚拟地址VirtualAddress):%08X\n" , ReadSectionHeader[i].VirtualAddress); printf( "VSize(节区在磁盘上的大小SizeOfRawData):%08X\n" , ReadSectionHeader[i].SizeOfRawData); printf( "ROffset(节区在磁盘上的偏移PointerToRawData):%08X\n" , ReadSectionHeader[i].PointerToRawData); printf( "标记(区段的属性Characteristics):%08X\n\n" , ReadSectionHeader[i].Characteristics); } |
地址转换:
为什么要做地址转换,因为内存页和磁盘页对齐的大小不同,存储的数据代码在内存和磁盘的位置也不同,例如:如果你要修改pe文件,那么就需要进行地址转换。
RVA和FOA的转换:
一些pe文件为了减少体积,磁盘对齐值不是一个内存页1000h,而是200h。当这类文件被映射到内存中后,同一数据相对于文件头的偏移量在内存中和磁盘中是不同的,就出现了文件偏移和虚拟地址的偏移问题。
RVA : 相对虚拟地址(Relative Virtual Address),PE 文件中的各种数据结构中涉及地址的字段大部分都是以 RVA 表示的。
VA 是当PE 文件被装载到内存中后,某个数据位置相对于文件头的偏移量。实际的内存地址
FOA (文件偏移地址): PE文件储存在磁盘上时, 某个数据的位置相对于文件头的偏移量,称为文件偏移地址(File Offset)或物理地址(RAW Offset)文件偏移地址从PE文件的第一个字节开始计 数,起始值为0。用十六进制工具 (例如WinHex、C32等)打开文件 所显示的地址就是文件偏移地址。
1 | RVA = 内存地址(VA) - ImageBase |
RVA 转换为 FOA:
某数据的文件偏移 = 该数据所在节的起始文件偏移 + (某数据的RVA –该数据所在节的起始RVA)
1 2 3 4 5 6 | PointerToRawData: 该节区在磁盘文件上的偏移值 VitualAddress: 该节区载入到内存后的相对虚拟地址 Misc.VirtualSize: 表示节的大小,是在对齐处理前的区块的大小 SizeOfHeaders: PE头的大小,这个PE 头泛指DOS头、PE头、节表的总和大小 SectionAlignment:加载到内存中的节的对齐方式,以字节为单位。此值必须大于或等于 FileAlignment成员。默认值是系统的页面大小。 FileAlignment:图像文件中各个部分的原始数据的对齐方式,以字节为单位。该值应该是 2 的幂,介于 512 和 64K (含)之间。默认值 512 。如果SectionAlignment成员小于系统页面大小,则该成员必须与 SectionAlignment相同。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 | 第一种情况:文件对齐跟内存对齐一样的情况,那么这样就可以直接去找 RVA的地址了 这个地址也就是FOA 第二种情况:文件对齐和内存对齐不一样的情况 1. 如果RVA属于文件头部(DOS头 + PE头 + 节表),头部大小是文件对齐大小的整数倍! 那么不需要进行计算了,因为DOS头和PE头和节表在文件中和在内存中展开都是一样的,直接从开始位置寻 找到RVA个字节即可,就是找RVA,也就是FOA(文件偏移地址)pOptionalHeader - >SizeOfHeaders 2. 判断 RVA 位于哪个节 RVA > = 节.VirtualAddress RVA < = 节.VirtualAddress + 当前内存对齐后的大小(节.Misc.VirtualSize) 差值 = RVA - 节.VirtualAddress 3.FOA = 节.PointerToRawData + 差值 |
RVA转换FOA例子:
创建一个a64.exe变量:
1 2 3 4 5 6 7 8 9 10 | #include <iostream> #include <stdlib.h> int Test = 0x12345678 ; int main( int argc, char * argv[]) { printf( "全局变量地址 = %X \r\n" , &Test); printf( "全局变量值 = %X \r\n" , Test); getchar(); } |
输出的值是VA(内存地址)
VA 4001C000
Imagebase 40000000
RVA = VA -ImageBase = 1C000
1.判断RVA是否在PE文件头中
001FF <1C000 不在PE文件头中
2.判断属于哪个节:
.data.VirtualAddress >=1C000 且 1C000<1C200(.data.VirtualAddress+.data.SizeOfRawData) 所以属于这个data节区中
3.FOA = 节.PointerToRawData + 差值(RVA - 节.VirtualAddress)
=B200+1C000-1C000=B200
下图就找到了对应的值:
在winhex中修改为78 56 44 12运行a64.exe成功
代码:
VA->Offset
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | void CPEmfcDlg::OnBnClickedButtoncalc() { / / 获取输入的VA DWORD dwRva; CString strRVA; GetDlgItem(IDC_EDIT_VAshow) - >GetWindowText(strRVA); / / 字符串转换 16 进制 _stscanf_s(strRVA.GetBuffer(), _T( "%x" ), &dwRva); CString str ; DWORD offset = VA2Offset(dwRva, m_pNtHdr, m_pSecHdr); str . Format ( "%08X" , offset); SetDlgItemText(IDC_EDIT_offsetshow, str ); / / SetDlgItemText(IDC_EDIT_quduan, (LPCTSTR)m_pSecHdr[nInNum].Name); } DWORD VA2Offset(DWORD dwVa, PIMAGE_NT_HEADERS m_pNtHdr, PIMAGE_SECTION_HEADER m_pSecHdr) { / / imagebase文件基地址 DWORD dwImageBase = m_pNtHdr - >OptionalHeader.ImageBase; / / 文件对齐和内存对齐相等且不在文件头中 if (m_pNtHdr - >OptionalHeader.FileAlignment = = m_pNtHdr - >OptionalHeader.SectionAlignment || dwVa < = m_pNtHdr - >OptionalHeader.SizeOfHeaders) { return dwVa; } else { for ( int nInNum = 0 ; nInNum < m_pNtHdr - >FileHeader.NumberOfSections; nInNum + + ) { if (dwVa > = dwImageBase + m_pSecHdr[nInNum].VirtualAddress && dwVa < = dwImageBase + m_pSecHdr[nInNum].VirtualAddress + m_pSecHdr[nInNum].Misc.VirtualSize) { return m_pSecHdr[nInNum].PointerToRawData + dwVa - m_pSecHdr[nInNum].VirtualAddress - m_pNtHdr - >OptionalHeader.ImageBase; } } } } |
FOA转换RVA:
文件地址为:FOA=15E00 ,根据区块表查看到了此在.rsrc区块数据中 。
15E00-9C00=C200 (这个为偏移量)
此时的区块相对虚拟地址为:D000,
RVA: C200+D000=19200
VA= imagebase +RVA =140000000+19200=140019200
最后:
感谢互联网知识分享~
可以关注微信公众号酷酷信安
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!