提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一.PE文件学习
- PE文件是目前Windows平台上的主流的可执行文件格式
- winnt.h是PE文件定义的最终决定者
1. PE基本概念
-
PE文件种类:
可执行系列:EXE、SCR
驱动程序系列:SYS、VXD
库系列:DLL、OCX、CPL、DRV
对象文件系列:OBJ(本身不能以任何形式执行) -
基本结构:PE头部分(DOS头+节区头)+PE体(其下的节区)
-
PE文件有一个共同特点:前两个字节为4D 5A(MZ)
-
文件中使用偏移,内存中使用VA来表示位置。文件加载到内存时,节区的大小、位置等就会发生变化。文件的内容一般可分为代码、数据、资源节,分别保存。
-
各节区头定义了各节区在文件或内存中的大小、位置、属性等。 PE头与各节区的尾部存在一个区域,成为NULL填充
1.1 基地址
- 当PE文件通过windows加载器载入内存后,内存中的版本称为模块,映射文件的起始地址为模块句柄,可以通过模块句柄访问内存中的其他数据结构,也成为基地址
1.2 虚拟地址与相对虚拟地址
- PE文件被系统加载器映射到内存中,每个程序的虚拟空间的内存地址称为虚拟地址
- RVA是内存中一个简单的,相对于PE文件载入地址(基地址)的偏移位置,是偏移量
- 虚拟地址(VA)=基地址(ImageBase)+相对虚拟地址(RVA)
- 文件偏移地址:PE文件储存在磁盘中,某个数据的位置相对于文件头的偏移量称为文件偏移地址或物理地址。
- PE头内部信息大多以RVA形式存在
2. MS–DOS头部
- 每个PE文件都是以一个DOS程序开始的,将DOS MZ头(64字节)与DOS stub(可选项,大小不固定)合称为DOS 文件头
- e_magic字段:一个字大小,值被设置为5A4Dh,这个值有一个#define,是IMAGE_DOS_SIGNATURE,在ASC码中是MZ
- e_lfanew字段:真正的PE文件头的相对偏移(RVA),占用4字节,在3Ch字节处
3. PE文件头(NT头)
- 即指PE相关结构NT映像头,IMAGE_NT_HEADER
- pe装载器从DOS头的结构体中的e_lfanew字段中找到PE HEADER的起始偏移量,加上基址,得到PE文件头的指针
3.1 signature字段
- 该字段被设置为0x00004550,ASC码字符是PE00,
#define IMAGE_NT_SIGNATURE 0X00004550
- PE00是PE文件头的开始,同时也是e_lfanew字段的指向
3.2 IMAGE_FILE_HEADER结构
- 即指映像文件头,包含PE文件的基本信息,指出了IMAGE_OPTIONAL_HEADER的大小
1.Machine:可执行文件的目标CPU类型,不同机器不同
- NumberOfSections:区块的数目,块表紧跟在IMAGE_NT_HEADERS后面
3.SizeOfOptionalHeader::指出IMAGE_OPTIONAL_HEADER结构体的长度
4.文件属性:标识文件的属性 0002H exe 2000H dll
3.3 IMAGE_OPTIONAL_HEADER结构
- 该可选映像头中定义了更多的数据,PE头结构体中最大的
- magic 10B (32) 20B(64)
- AddressOfEntryPoint : EP的RVA值,指出程序最先执行的代码起始地址
- ImageBase : 指出文件的优先装入地址
- SectionAlignment:指定节区在内存中的最小单位
- FileAlignment: 指定了节区在磁盘文件中的最小单位
- 磁盘文件或内存的节区大小必定为上述两值的整数倍
- DataDirectory:结构体数组,每项都有被定义的值,[0]—EXPORT Directory [1]—IMPORT Directory [2]—RESOURCE Directory [9]—TLS Directory
3.4节区头
- PE文件格式把具有相似属性的数据统一保存在一个被称为“节区”的地方,然后需要把各节区的属性记录在节区头中。
- 节区头是由IMAGE_SECTION_HEADER结构体组成的数组,每个结构体对应一个节区
- 关于name字段,name中可以填任何值,且可以填充NUll值
3.5 RVA to RAW
- PE文件加载到内存时,每个节区都要准确完成内存地址与文件偏移间的映射
- RAW(文件偏移)计算公式:
4.PE头核心部分----IAT—导入地址表
IAT保存的内容与windows操作系统的核心进程,内存,DLL结构有关
4.1 DLL–动态链接库
-
加载DLL的方式:
显式链接:程序使用DLL时加载,使用完毕后释放内存 隐式链接:程序开始时即一同加载DLL,程序终止时再释放占用的内存
4.2 IMAGE_IMPORT_DESCRIPTOR
-
执行一个普通程序时往往需要导入多个库,导入多少库就存在多少个IMAGE_IMPORT_DESCRIPTOR结构体,这些结构体形成了数组,且结构体数组最后以NULL结构体结束
-
INT与IAT是长整型数组,以NULL结束,INT与IAT的大小相同
-
INT中各元素的值为IMAGE_IMPORT_BY_NAME结构体指针
5.EAT—导出地址表
- EAT是一种核心机制,使不同的应用程序可以调用库文件中提供的函数,只有通过EAT才能准确求从相应库中导出函数的起始地址
二. IDA学习
1. 交叉引用
1.1 代码交叉引用
-
代码交叉引用可帮助 IDA 生成控制流图形和函数调用图形
-
后面的地址(这里为_main+2A)是交叉引用的源头地址
-
地址后面总是有一个上行或下行箭头,表示引用位置的相对方向。下行箭头表示_main+2A 的地址比 sub_401000 要高,因此,你需要向下滚动才能到达该地址。
-
代码交叉引用用于表示一条指令将控制权转交给另一条指令
-
***普通流(ordinary flow)***是一种最简单的流,它表示由一条指令到另一条指令的顺序流。这是所有非分支指令(如 ADD)的默认执行流
-
call 指令,它分配到一个调用流(call flow),表示控制权被转交给目标函数。多数情况下,call 指令也分配到一个普通流,因为大多数函数会返回到 call之后的位置。如果 IDA 认为某个函数并不返回(在分析阶段确定),那么,在调用该函数时,它就不会为该函数分配普通流。调用流通过在目标函数(流的目的地址)处显示交叉引用来表示。
-
除非调用地址有相应的名称,否则,交叉引用中的地址会以调用函数中的偏移量表示
-
每个无条件分支指令和条件分支指令将分配到一个跳转流(jump flow)。条件分支还分配到普通流,以在不进入分支时对流进行控制。无条件分支并没有相关的普通流,因为它总会进入分支。
-
虚线表示相邻的两条指令之间并不存在普通流
-
跳转交叉引用使用后缀 j,函数调用导致的交叉引用使用后缀 p