首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
58 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
Java
emacs
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
308
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
8
篇与
的结果
2017-03-22
PE文件详解(九)
本篇文章转载自小甲鱼的一篇日志,原文地址我们知道,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 时,低位数据指向下一层目录块的其实地址;当最高位为 0 时,指针指向 IMAGE_RESOURCE_DATA_ENTRY 结构。注意:将 Name 和 OffsetToData 用做指针时需要注意,该指针是从资源区块开始的地方算起的偏移量(即根目录的起始位置的偏移量),不是我们习惯的 RVA 哦。最后,在上图中我们看到,在第一层的时候,IMAGE_RESOURCE_DIRECTORY_ENTRY的Name 字段作为资源类型使用。具体类型匹配见下表:资源数据入口经过三层 IAMGE_RESOURCE_DIRECTORY_ENTRY (一般是3层,偶尔更年期少一些。第一层资源类型,第二层资源名,第三层是资源的 Language),第三层目录结构中的 OffsetOfData 指向 IMAGE_RESOURCE_DATA_ENTRY 结构。该结构描述了资源数据的位置和大小,定义如下:IMAGE_RESOURCE_DATA_ENTRY STRUCTOffsetToData DWORD ? ; 资源数据的RVASize DWORD ? ; 资源数据的长度CodePage DWORD ? ; 代码页, 一般为0Reserved DWORD ? ; 保留字段IMAGE_RESOURCE_DATA_ENTRY ENDS千山万水,此处的 IMAGE_RESOURCE_DATA_ENTRY 结构就是真正的资源数据了。结构中的OffsetOfData 指向资源数据的指针,其为 RVA 值。
2017年03月22日
6 阅读
0 评论
0 点赞
2017-03-22
PE文件详解(八)
本文转载自小甲鱼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后面的以此类推,可以发现这些需要重定位的位置,存储的都是一些立即寻址的地址
2017年03月22日
3 阅读
0 评论
0 点赞
2017-03-21
PE文件详解(七)
本文转载自小甲鱼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 文件头从PE 文件头中的 IMAGE_OPTIONAL_HEADER32 结构中取出数据目录表,并从第一个数据目录中得到导出表的RVA从导出表的 Base 字段得到起始序号将需要查找的导出序号减去起始序号,得到函数在入口地址表中的索引检测索引值是否大于导出表的 NumberOfFunctions 字段的值,如果大于后者的话,说明输入的序号是无效的用这个索引值在 AddressOfFunctions 字段指向的导出函数入口地址表中取出相应的项目,这就是函数入口地址的RVA 值,当函数被装入内存的时候,这个RVA 值加上模块实际装入的基地址,就得到了函数真正的入口地址从函数名称查找入口地址如果已知函数的名称,如何得到函数的入口地址呢?与使用序号来获取入口地址相比,这个过程要相对复杂一点!Windows 装载器的工作步骤如下:最初的步骤是一样的,那就是首先得到导出表的地址从导出表的 NumberOfNames 字段得到已命名函数的总数,并以这个数字作为循环的次数来构造一个循环从 AddressOfNames 字段指向得到的函数名称地址表的第一项开始,在循环中将每一项定义的函数名与要查找的函数名相比较,如果没有任何一个函数名是符合的,表示文件中没有指定名称的函数如果某一项定义的函数名与要查找的函数名符合,那么记下这个函数名在字符串地址表中的索引值,然后在 AddressOfNamesOrdinals 指向的数组中以同样的索引值取出数组项的值,我们这里假设这个值是x最后,以 x 值作为索引值,在 AddressOfFunctions 字段指向的函数入口地址表中获取的 RVA 就是函数的入口地址一帮情况下病毒程序就是通过函数名称查找入口地址的,因为病毒程序作为一段额外的代码被附加到可执行文件中的。如果病毒代码中用到某些 API 的话,这些 API 的地址不可能在宿主文件的导出表中为病毒代码准备好。因此只能通过在内存中动态查找的方法来实现获取API 的地址。接下来就是来实际分析一个PE文件。通过之前的知识,发现这个导出表的RVA = 0x00002060,表所在节区为.rdat,节区在内存中的RVA = 0x00002000,节区在文件中的偏移 = 0x00000600。通过之前的计算公式得到导出表在文件中的偏移为0x00000660.定位到这个地方发现这个表中的内容如下:通过解析知道Name = 0x0000209c ==>0x0000069cBase = 0x00000002 NumberOfFunctions = 0x02NumberOfNames = 0x02AddressOfFunctions = 0x2088 ==>0x688AddressOfNames = 0x2090==>0x690AddressOfNameOrdinals = 0x2098 ⇒ 0x698对于保存的是RVA的变量,后面都是通过换算得到的其值在内存中的偏移对于AddressOfNames来说,它指向的是一个保存了函数名的RVA,我们在对应偏移位置得到它的值为0x20A8 ==> 0x6a8,从文件中的内容来看,这个位置保存到额正好是两个导出函数的值。两个函数名分别为:_DecCount ==>0_IncCount ==> 1后面的是它们在这个位置的编号,等会需要这个编号,中的它们在函数地址表中对应的索引接下来根据AddressOfNameOrdinals中的值,00 01,发现它们在函数地址表中的索引分别为0 1最后再AddressOfFunctions中得到它们分别为0x1046和0x1023也就是_DecCount = 0x1046 _IncCount = 0x1023我们通过反汇编工具W32Dasm,查看这个dll的反汇编代码:这个dll加载到内存中后它的基地址为0x10000000,这样得到两个函数在内存中的地址为:_DecCount =0x10001046_IncCount =0x10001023在它的反汇编中找到函数的地址发现正好是这两个值:
2017年03月21日
9 阅读
0 评论
0 点赞
2017-03-21
PE文件详解(六)
这篇文章转载自小甲鱼的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_NAME 结构:IMAGE_IMPORT_BY_NAME STRUCT Hint WORD ? Name BYTE ? IMAGE_IMPORT_BY_NAME ENDS结构中的 Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0。Name 字段定义了导入函数的名称字符串,这是一个以 0 为结尾的字符串。输入函数表的加载从上面的图上来看,OriginalFirstThunk与FirstThunk指向的是同一个数据结构,在PE文件中既可以通过OriginalFirstThunk来找到函数名,也可以通过FirstThunk来找到函数名,为什么会出现两个指针指向同一个数据结构的现象呢,其实这个与PE文件的加载有关第一个数组(由 OriginalFirstThunk 所指向)是单独的一项,而且不能被改写,我们前边称为 INT。第二个数组(由 FirstThunk 所指向)事实上是由 PE 装载器重写的。PE 装载器首先搜索 OriginalFirstThunk ,找到之后加载程序迭代搜索数组中的每个指针,找到每个 IMAGE_IMPORT_BY_NAME 结构所指向的输入函数的地址,然后加载器用函数真正入口地址来替代由 FirstThunk 数组中的一个入口,也就是说此时的FirstThunk 不在指向这个INAGE_IMPORT_BY_NAME结构,而是真实的函数的RVA。因此我们称为输入地址表(IAT)。所以,当我们的 PE 文件装载内存后准备执行时,刚刚的图就会转化为下图:实验操作我们来编译一个具体的程序,源代码如下:#include <windows.h> int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) { MessageBox( NULL, TEXT("Hello, welcome to Fishc.com!"), TEXT("Welcome!"), MB_OKCANCEL | MB_OK ); return 0; }这个程序就是弹出一个MessageBox,通过W32Dasm静态反汇编发现MessageBox函数所在地址应该在0x0042A2AC在数据目录表中根据OriginalFirstThunk 项获取函数名称用UE打开这个PE文件,发现输入函数表的RVA = 0x0002A000在节表中查询发现它是在.idata这个节中通过之前说的公式,可以得到,这个RVA在文件中的偏移地址为0x0002A000 - 0x0002A000 + 0x00028000 = 0x00028000读取在这个位置的信息发现,OriginalFirstThunk = 0x0002A15C,这个偏移,发现它仍然在这个节中,通过上述公式计算得出,他在文件中的偏移地址为:0x0002815C从这个位置得到的值来看,它的最高值为0,也就是IMAGE_THUNK_DATA保存的是函数名的字符串,字符串的RVA为0x0002A2DC,通过计算得到它在文件中的偏移为:0x000282DC.从图上可以看出这个地址所对应的值正好是函数的名称MessgeBoxA通过FirstThunk成员找到函数名称首先根据PE文件的内容,可以知道,输入函数表在PE文件的偏移为0x00028000,而根据这个结构来看,FirstThunk在x00028000 + 16 = 0x00028010的位置,在这位置,我们发现它里面的值为0x0002A2AC计算得到在磁盘中的偏移为0x000282AC,在PE文件中这个值为0x0002A2DC,它的最高位仍然为0,也就是说这个地址保存的内容为函数名称。另外我们发现这个值与之前用OriginalFirstThunk 寻址到的函数名称所在RVA一样,也就是说到此成功找到函数名称查找函数在内存的偏移地址根据上面所说的内容,只有当这个PE文件被加载到内存中,PE加载器才会将IMAGE_IMPORT_BY_NAME结构中的值替换为对应函数的地址,所以要查找函数的地址就需要先将PE文件加载到内存,然后再将内存中的数据抓取下来,最后再来分析得出这个函数的偏移地址。其实这个工作可以由lordPE工具来帮忙完成。首先是启动程序,然后打开lordPE,找到程序的进程,然后选择dump full抓取全部即可这样会生成一个dump文件,分析这个文件,就可以得出相应的内容:由于这个是内存镜像的拷贝,所以在这在内存中的RVA就是在文件中的偏移。首先得到导入表的偏移为0x0002a000,这个值里面存储的值为0x0002A15C,这个值是OriginalFirstThunk的值,通过这个值找到对应的IMAGE_THUNK_DATA地址:0x0002A2DC我们发现这个值得高地址为是0,那么它所指向的应该就是函数名称,我们寻址到这个地址,发现它正好是函数名称接下来,再来解析函数地址,在0x0002a010中找到对应的FirstThunk值,这个值为0x0002A2AC,它是指向一个IMAGE_THUNK_DATA结构,在这个地址处,发现它的值为0x77D507EA,这个值的最高位为1,所以它对应的是一个函数地址,它的低32位是一个函数编号,此时0x0002A2AC指向的不在是一个IMAGE_IMPORT_BY_NAME结构,而是函数地址的偏移,而这个程序是由VC6.0编译而成,VC6默认的加载地址为0x00400000,所以基址 + 偏移地址就是函数的正确地址,也就是0x0042A2AC,与之前用静态反汇编得到的值相同
2017年03月21日
5 阅读
0 评论
0 点赞
2017-03-18
PE文件详解(四)
本文转自小甲鱼的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 文件中,是本区块在重定位表中的重定位数目来着。NumberOfLinenumbers:该区块在行号表中的行号数目,鸡肋。Characteristics:该区块的属性。该字段是按位来指出区块的属性(如代码/数据/可读/可写等)的标志。具体内容可以参考MSDN在线文档:传送门.aspx)下面通过一个例子来详细朔门这些内容:还是以上次那个为例根据以前的内容可以知道这个文件PE头在0xf0的位置,上一次是通过各个结构体大小来找到PE头中这个OptionalHeader结构的地址,但是当时我忘记了,在FileHeader 这个结构中有一个SizeOfOptionalHeader这个域专门用来记录OptionalHeader结构的大小,它在PE头的偏移为0x14也就是在0xf0 + 0x14 = 0x104的位置查看文件得知这个值为0xe0, OptionalHeader偏移0x18 + 大小0xe0 + pe头的偏移0xf0 = 0x1e8根据这个结构中的成员很容易计算出来,这个结构占0x28个字节,这样根据上一个的起始地址 + 0x28就可以得到下一个的地址,这样可以陆陆续续找到所有的节节表中的最后一个为全0,这样这个PE文件中总共有.textbss、.text、.radta、.data、.idata、.rsrc、.reloc这样几个节。接下来读取各个部分的内容,比如说在text节中,VirtualSize = 0x00014360PointerToRawData = 0x000400 VirtualAddress = 0x00011000SizeOfRawData = 0x00014400Characteristics = 0x60000020这些节区都是按照文件中的某个值对齐,然后在紧密排列的,所以根据它在文件中的偏移 + 对齐后的值可以得到下一个节在文件中的偏移地址,根据这点在text节中 PointerToRawData + SizeOfRawData = 0x000400 + 0x00014400 = 0x00014800,而下一个的文件偏移地址正好是这个,这个根据在PE中查找到的数据,发现下一个确实是这个值
2017年03月18日
5 阅读
0 评论
0 点赞
2017-03-16
PE文件详解(三)
本文转自小甲鱼的PE文件详解系列传送门PE文件到内存的映射在执行一个PE文件的时候,windows 并不在一开始就将整个文件读入内存的,二十采用与内存映射文件类似的机制。也就是说,windows 装载器在装载的时候仅仅建立好虚拟地址和PE文件之间的映射关系。当且仅当真正执行到某个内存页中的指令或者访问某一页中的数据时,这个页面才会被从磁盘提交到物理内存,这种机制使文件装入的速度和文件大小没有太大的关系。但是要注意的是,系统装载可执行文件的方法又不完全等同于内存映射文件。当使用内存映射文件的时候,系统对“原著”相当忠实,如果将磁盘文件和内存映像比较的话,可以发现不管是数据本身还是数据之间的相对位置它丫丫的都是完全相同的。而我们知道,在装载可执行文件的时候,有些数据在装入前会被预处理,如重定位等,正因此,装入以后,数据之间的相对位置可能发生微妙的变化。Windows 装载器在装载DOS部分、PE文件头部分和节表(区块表)部分是不进行任何特殊处理的,而在装载节(区块)的时候则会自动按节(区块)的属性做不同的处理。一般情况下,它会处理以下几个方面的内容:内存页的属性;节的偏移地址;节的尺寸;不进行映射的节。内存页的属性:对于磁盘映射文件来说,所有的页都是按照磁盘映射文件函数指定的属性设置的。但是在装载可执行文件时,与节对应的内存页属性要按照节的属性来设置。所以,在同属于一个模块的内存页中,从不同节映射过来的的内存页的属性是不同的。节的偏移地址:节的起始地址在磁盘文件中是按照 IMAGE_OPTIONAL_HEADER32 结构的 FileAlignment 字段的值进行对齐的。而当被加载到内存中时是按照同一结构中的 SectionAlignment 字段的值对其的,两者的值可能不同。所以一个节被装入内存后相对于文件头的偏移和在磁盘文件中的偏移可能是不同的。注意,节事实上就是相同属性数据的组合!当节被装入到内存中的时候,相同一个节所对应的内存页都将被赋予相同的页属性。事实上,Windows 系统对内存属性的设置是以页为单位进行的,所以节在内存中的对齐单位必须至少是一个页的大小。(小甲鱼温馨提示:对于32位操作系统来说,这个值一般是4KB==1000H; 对于64位操作系统这个值一般是8KB==2000H)在磁盘中就没有这个限制,因为在磁盘中排放是以什么为主?肯定是以空间为主导,在磁盘只是存放,不是使用,所以不用设置那么详细的属性。试想想看,如果在磁盘中都是以4KB为大小对齐的话,不够就用0来填充,那么一个只占20字节的数据就要消耗4KB的空间来存放,是不是浪费?有木有??节的尺寸:对节的尺寸的处理主要分为两个方面:第一个方面,正如刚刚我们所讲的,由于磁盘映像和内存映像中节对齐存储单位的不同而导致了长度扩展不同(填充的0数量不同嘛~);第二个方面,是对于包含未初始化数据的节的处理问题。既然是未初始化,那么没有必要为其在磁盘中浪费空间资源,但在内存中不同,因为程序一运行,之前未初始化的数据便有可能要被赋值初始化,那么就必须为他们留下空间。不进行映射的节:有些节并不需要被映射到内存中,例如.reloc节,重定位数据对于文件的执行代码来说是透明的,无作用的,它只是提供Windows 装载器使用,执行代码根本不会去访问到它们,所以没有必要将他们映射到物理内存中。
2017年03月16日
5 阅读
0 评论
0 点赞
2017-03-14
PE文件详解二
本文转自小甲鱼的PE文件相关教程,原文传送门咱接着往下讲解IMAGE_OPTIONAL_HEADER32 结构定义即各个属性的作用!接着我们来谈谈 IMAGE_OPTIONAL_HEADER 结构,正如名字的意思,这是一个可选映像头,是一个可选的结构。但是呢,实际上上节课我们讲解的 IMAGE_FILE_HEADER 结构远远不足以来定义 PE 文件的属性。因此,这些属性在 IMAGE_OPTIONAL_HEADER 结构中进行定义。因此这两个结构联合起来,才是一个完整的 “PE文件结构” 。{ // // Standard fields. // +18h WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh) +1Ah BYTE MajorLinkerVersion; // 链接程序的主版本号 +1Bh BYTE MinorLinkerVersion; // 链接程序的次版本号 +1Ch DWORD SizeOfCode; // 所有含代码的节的总大小 +20h DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小 +24h DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小 +28h DWORD AddressOfEntryPoint; // 程序执行入口RVA +2Ch DWORD BaseOfCode; // 代码的区块的起始RVA +30h DWORD BaseOfData; // 数据的区块的起始RVA // // NT additional fields. 以下是属于NT结构增加的领域。 // +34h DWORD ImageBase; // 程序的首选装载地址 +38h DWORD SectionAlignment; // 内存中的区块的对齐大小 +3Ch DWORD FileAlignment; // 文件中的区块的对齐大小 +40h WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号 +42h WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号 +44h WORD MajorImageVersion; // 可运行于操作系统的主版本号 +46h WORD MinorImageVersion; // 可运行于操作系统的次版本号 +48h WORD MajorSubsystemVersion; // 要求最低子系统版本的主版本号 +4Ah WORD MinorSubsystemVersion; // 要求最低子系统版本的次版本号 +4Ch DWORD Win32VersionValue; // 莫须有字段,不被病毒利用的话一般为0 +50h DWORD SizeOfImage; // 映像装入内存后的总尺寸 +54h DWORD SizeOfHeaders; // 所有头 + 区块表的尺寸大小 +58h DWORD CheckSum; // 映像的校检和 +5Ch WORD Subsystem; // 可执行文件期望的子系统 +5Eh WORD DllCharacteristics; // DllMain()函数何时被调用,默认为 0 +60h DWORD SizeOfStackReserve; // 初始化时的栈大小 +64h DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小 +68h DWORD SizeOfHeapReserve; // 初始化时保留的堆大小 +6Ch DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小 +70h DWORD LoaderFlags; // 与调试有关,默认为 0 +74h DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16 +78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录表 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;上述代码中的偏移地址是相对于PE头的偏移地址不是针对IMAGE_OPTIONAL_HEADER32的偏移其中重要的几个字段如下:AddressOfEntryPoint字段:指出文件被执行时的入口地址,这是一个RVA地址(RVA的含义在下一节中详细介绍)。如果在一个可执行文件上附加了一段代码并想让这段代码首先被执行,那么只需要将这个入口地址指向附加的代码就可以了。ImageBase字段:指出文件的优先装入地址。也就是说当文件被执行时,如果可能的话,Windows优先将文件装入到由ImageBase字段指定的地址中。当这个地址被其他程序或者模块霸占时,系统会进行重定向,将它放置到其他地址处链接器产生可执行文件的时候对应这个地址来生成机器码,所以当文件被装入这个地址时不需要进行重定位操作,装入的速度最快。如果文件被装载到其他地址的话,将不得不进行重定位操作,这样就要慢一点。对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被其他模块占据,所以EXE总是能够按照这个地址装入。这也意味着EXE文件不再需要重定位信息。对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被其他的DLL使用,所以DLL文件中必须包含重定位信息以防万一。因此,在前面介绍的 IMAGE_FILE_HEADER 结构的 Characteristics 字段中,DLL 文件对应的 IMAGE_FILE_RELOCS_STRIPPED 位总是为0,而EXE文件的这个标志位总是为1。在链接的时候,可以通过对link.exe指定/base:address选项来自定义优先装入地址,如果不指定这个选项的话,一般EXE文件的默认优先装入地址被定为00400000h,而DLL文件的默认优先装入地址被定为10000000h。SectionAlignment 字段和 FileAlignment字段:SectionAlignment字段指定了节被装入内存后的对齐单位。也就是说,每个节被装入的地址必定是本字段指定数值的整数倍。而FileAlignment字段指定了节存储在磁盘文件中时的对齐单位。Subsystem字段:指定使用界面的子系统,这个字段决定了系统如何为程序建立初始的界面,链接时的/subsystem:**选项指定的就是这个字段的值,在前面章节的编程中我们早已知道:如果将子系统指定为Windows CUI,那么系统会自动为程序建立一个控制台窗口,而指定为Windows GUI的话,窗口必须由程序自己建立。DataDirectory字段:这个字段可以说是最重要的字段之一,它由16个相同的IMAGE_DATA_DIRECTORY结构组成。虽然PE文件中的数据是按照装入内存后的页属性归类而被放在不同的节中的,但是这些处于各个节中的数据按照用途可以被分为导出表、导入表、资源、重定位表等数据块,这16个IMAGE_DATA_DIRECTORY结构就是用来定义多种不同用途的数据块的IMAGE_DATA_DIRECTORY结构的定义很简单,它仅仅指出了某种数据块的位置和长度。IMAGE_DATA_DIRECTORY STRUCT VirtualAddress DWORD ? ; 数据的起始RVA isize DWORD ? ; 数据块的长度 IMAGE_DATA_DIRECTORY ENDS在PE文件中寻找特定的数据时就是从这些IMAGE_DATA_DIRECTORY结构开始的。比如要存取资源,那么必须从第3个IMAGE_DATA_DIRECTORY结构(索引为2)中得到资源数据块的大小和位置;同理,如果要查看PE文件导入了哪些DLL文件的哪些API函数,那就必须首先从第2个IMAGE_DATA_DIRECTORY结构得到导入表的位置和大小。最后再根据这些信息接着解析上节中的PE文件PE头所在位置的偏移为0xf8 + IMAGE_OPTIONAL_HEADER 结构在IMAGE_NT_HEADERS结构中的偏移0x18 = OptionalHeader成员的地址0x110被选中的这块就是结构IMAGE_NT_HEADERS中的内容:从图中可以找到上面所表述的各个部分偏移的地址和它对应的具体的内容:AddressOfEntryPoint所在地址为:偏移 0x28 + 0xf8 = 0x120,值为0x011285ImageBase所在地址为:偏移0x34 + 0xf8 = 0x12c,值为0x00400000SectionAlignment 所在地址为:偏移0x38 + 0xf8 = 130,值为0x00001000,也就是一页内存FileAlignment所在的地址为偏移0x3c + 0xf8 = 0x134,值为0x00000200,也就是512,是一簇的大小Subsystem所在地址为:偏移0x5c + 0xf8 = 0x154,值为0x0003,也就是控制台程序DataDirectory所在地址为偏移0x78 + 0xf8 = 170 ,也是就是从0x170开始往后每8个字节为一个元素,指定了一些数据表的地址
2017年03月14日
3 阅读
0 评论
0 点赞
2017-03-13
PE格式详解讲解1
这篇文章主要转载自小甲鱼的加密解密部分,然后补充加上我自己的少许内容,原文地址-->[传送门](http://blog.fishc.com/1551.html)下面的内容主要是围绕这个图来进行MS-DOS头部这个头部是为了兼容早期的DOS系统,PE文件的第一个字节起始于一个传统的MS-DOS头,被称为IMAGE_DOS_HEADER,这个结构体完整的定义如下:(注:最左边是文件头的偏移量。) IMAGE_DOS_HEADER STRUCT { +0h WORD e_magic // Magic DOS signature MZ(4Dh 5Ah) DOS可执行文件标记 +2h WORD e_cblp // Bytes on last page of file +4h WORD e_cp // Pages in file +6h WORD e_crlc // Relocations +8h WORD e_cparhdr // Size of header in paragraphs +0ah WORD e_minalloc // Minimun extra paragraphs needs +0ch WORD e_maxalloc // Maximun extra paragraphs needs +0eh WORD e_ss // intial(relative)SS value DOS代码的初始化堆栈SS +10h WORD e_sp // intial SP value DOS代码的初始化堆栈指针SP +12h WORD e_csum // Checksum +14h WORD e_ip // intial IP value DOS代码的初始化指令入口[指针IP] +16h WORD e_cs // intial(relative)CS value DOS代码的初始堆栈入口 +18h WORD e_lfarlc // File Address of relocation table +1ah WORD e_ovno // Overlay number +1ch WORD e_res[4] // Reserved words +24h WORD e_oemid // OEM identifier(for e_oeminfo) +26h WORD e_oeminfo // OEM information;e_oemid specific +29h WORD e_res2[10] // Reserved words +3ch DWORD e_lfanew // Offset to start of PE header 指向PE文件头 } IMAGE_DOS_HEADER ENDS这个头中只有两个需要重点关注:e_magic:DOS可执行文件的标记,一般是两个字节,标记值是固定的,只有当其值是4D5A的时候,这个文件才被识别为可执行文件,这个结构在文件头位置e_lfanew:指向PE文件头的指针,这个在偏移3c处利用UE来分析可以看到,这两个在文件中的位置如下:PE文件头PE Header 是PE相关结构NT映像头(IMAGE_NT_HEADER)的简称,里边包含着许多PE装载器用到的重要字段。装载到内存中时,PE状态器将从IMAGE_DOS_HEADER结构中的e_lfanew字段中岛PE Header的起始偏移量,加上基地址就得到PE文件的头指针PEHeader = ImageBase + DosHeader->e_lfnewIMAGE_NT_HEADERS STRUCT { +0h DWORDSignature +4h IMAGE_FILE_HEADER FileHeader +18h IMAGE_OPTIONAL_HEADER32OptionalHeader } IMAGE_NT_HEADERS ENDSSignature字段在一个有效的 PE 文件里,Signature 字段被设置为00004550h, ASCII 码字符是“PE00”。标志这 PE 文件头的开始。“PE00” 字符串是 PE 文件头的开始,DOS 头部的 e_lfanew 字段正是指向这里。一般如果需要验证一个文件是否为PE文件就要验证这个标志是否为这个值FileHeader字段这个字段是一个IMAGE_FILE_HEADER结构,他的定义如下:struct IMAGE_FILE_HEADER { WORD Machine; //运行平台 WORD NumberOfSections; //区块表的个数 DWORD TimeDataStamp;//文件创建时间,是从1970年至今的秒数 DWORD PointerToSymbolicTable;//指向符号表的指针 DWORD NumberOfSymbols;//符号表的数目 WORD SizeOfOptionalHeader;//IMAGE_NT_HEADERS结构中OptionHeader成员的大小,对于win32平台这个值通常是0x00e0 WORD Characteristics;//文件的属性值 }(1)Machine:可执行文件的目标CPU类型,记载可执行文件的目标CPU类型,各个值表示的平台如下:ValueMeaningIMAGE_FILE_MACHINE_I386 0x014cx86IMAGE_FILE_MACHINE_IA64 0x0200Intel ItaniumIMAGE_FILE_MACHINE_AMD64 0x8664x64(2)NumberOfSection: 区块的数目。(注:区块表是紧跟在 IMAGE_NT_HEADERS 后边的)(3)TimeDataStamp: 表明文件是何时被创建的。这个值是自1970年1月1日以来用格林威治时间(GMT)计算的秒数,这个值是比文件系统(FILESYSTEM)的日期时间更加精确的指示器。(4)PointerToSymbolTable: COFF 符号表的文件偏移位置,现在基本没用了。(5)NumberOfSymbols: 如果有COFF 符号表,它代表其中的符号数目,COFF符号是一个大小固定的结构,如果想找到COFF 符号表的结束位置,则需要这个变量。(6)SizeOfOptionalHeader: 紧跟着IMAGE_FILE_HEADER 后边的数据结构(IMAGE_OPTIONAL_HEADER)的大小。(对于32位PE文件,这个值通常是00E0h;对于64位PE32+文件,这个值是00F0h )。(7)Characteristics: 文件属性,有选择的通过几个值可以运算得到。( 这些标志的有效值是定义于 winnt.h 内的 IMAGE_FILE_** 的值,具体含义见下表。普通的EXE文件这个字段的值一般是 0100h,DLL文件这个字段的值一般是 210Eh。)小甲鱼温馨提示:多种属性可以通过 “或运算” 使得同时拥有! 下面分析上述程序中的IMAGE_NT_HEADERS 结构:PE文件头的标记为0x00004550 后面所有方框都是IMAGE_FILE_HEADER的内容: 运行平台的值为0x014c,查上面的表得知,它是运行在intel x86平台下 区块表的个数为0x0007 文件创建时间为0x5848fc56 指向符号表的指针为NULL,也就是没有符号表,符号表是用于调试的,在这即使没有这个东西也不影响调试 符号表的数目为0 OptionHeader成员的大小为0x00e0 文件的属性值为0x0102
2017年03月13日
6 阅读
0 评论
0 点赞