专栏首页有趣的django逆向初级-PE(五)

逆向初级-PE(五)

5.1.PE文件结构

1、什么是可执行文件? 可执行文件(executable fle)指的是可以由操作系统进行加载执行的文件。 可执行文件的格式: Windows平台: PE(Portable Executable)文件结构 Linux平台: ELF(Executable and Linking Format)文件结构 哪些领域会用到PE文件格式: <1>病毒与反病毒 <2>外挂与反外挂 <3>加壳与脱壳(保护与破解)

<4>无源码修改功能、软件汉化等

2、如何识别PE文件

<1> PE文件的特征(PE指纹) 分别打开.exe .dlI .sys 等文件,观察特征前2个字节。

<2>不要仅仅通过文件的后缀名来认定PE文件

5.2.PE文件的两种状态

1、PE文件主要结构体

  • IMAGE_DOS_HEADER占64个字节。
  • DOS Sub:IMAGE_DOS_HEADER尾部的四个字节指向PE文件的开始位置。IMAGE_DOS_HEADER尾部到PE文件头开始的中间部分是DOS_Sub部分(大小不固定)
  • PE文件头标志:PE头是前面4个字节
  • PE文件表头:IMAGE_FILE_HEADER是20个字节
  • 扩展PE头:IMAGE_OPTIONAL_HEADER在32位中占224个字节(这个大小是可以修改的)
  • IMAGE_SECTION_HEADER:40个字节

2、PE文件的两种状态

5.3.DOS头属性说明

IMAGE_DOS_HEADER结构体

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    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;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

主要就看两个成员

WORD   e_magic;       //PE文件判断表示   4D5A,ascii是MZ
LONG   e_lfanew;     //存储PE头首地址
  • e_magic两个字节和e_lfanew四个字节内容不能修改
  • 开头e_magic和结尾e_lfanew中间的成员部分可以随意修改
  • e_lfanew到PE头文件中间的DOS Stub部分可以随便修改

5.4.标志PE头属性说明

1、PE头

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;   //PE标识,占4字节
    IMAGE_FILE_HEADER FileHeader;    //标志PE头
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;    //扩展PE头
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

PE标识不能破坏,操作系统在启动一个程序的时候会检测这个标识。

2、标准PE头(占20字节)

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;//可以运行在什么样的CPU上   任意:0    Intel 386以及后续:14C   x64:8664  
    WORD    NumberOfSections;//表示节的数量
    DWORD   TimeDateStamp;//编译器填写的时间戳 与文件属性里面(创建时间、修改时间)无关
    DWORD   PointerToSymbolTable;//调试相关
    DWORD   NumberOfSymbols;//调试相关
    WORD    SizeOfOptionalHeader;//可选PE头的大小(32位PE文件:0xE0  64位PE文件:0xF0)
    WORD    Characteristics;//文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Characteristics文件属性

文件属性 Characteristics值为: 01 0F 转换为二进制:0000 0001 0000 1111 说明下标0,1,2,3,8有值,根据下标是不是1,然后查看对应的文件属性

5.5.扩展PE头属性说明

1、扩展PE头结构体(总共224字节)

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    // 
    WORD    Magic;  // 分辨32位程序还是64位,如果32位则10B,64位则20B
    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; //PE文件自身的版本号
    WORD    MinorImageVersion; //PE文件自身的版本号
    WORD    MajorSubsystemVersion; //运行所需要子系统的版本号
    WORD    MinorSubsystemVersion; //运行所需要子系统的版本号
    DWORD   Win32VersionValue; //子系统版本的值,必须为0
    DWORD   SizeOfImage; //内存中整个PE文件的映射尺寸,比实际的值大,必须是SectionAlignment整数倍
    DWORD   SizeOfHeaders; //所有的头+节表按照文件对齐后的大小
    DWORD   CheckSum; //校验和,可伪造
    WORD    Subsystem; //子系统, 驱动程序(1) 图形界面(2) DLL(3)
    WORD    DllCharacteristics;	 //文件特性 不是针对DLL文件的
    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;

2、ImageBase和AddressOfEntryPoint

ImageBase; //内存镜像基址
AddressOfEntryPoint; // 程序入口,相对于ImageBase的偏移

实例

程序入口:0193BE
内存镜像:400000

程序真正入口=内存镜像+程序入口=4193BE

通过DTDebug确认

3、 DllCharacteristics文件特性

5.6.PE节表

节表结构体(占40字节)

#define IMAGE_SIZEOF_SHORT_NAME 8              
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];  //ASCII字符串 可自定义 只截取8个字节(占8字节)
    union {                     //Misc双子是该字节没有在对齐前的真实尺寸 该值可以不准确(占4字节)
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;        //在内存中的偏移地址加上ImageBase才是内存中的真正地址
    DWORD   SizeOfRawData;		   //节在文件中对齐后的尺寸	
    DWORD   PointerToRawData;      //节区在文件中的偏移
    DWORD   PointerToRelocations;  //调试相关
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;        //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SECTION_HEADER          40

DOS头64字节+PE标识4字节+PE标准头20字节+PE扩展头224字节,然后就是节表的起始位置,每个节表占40个字节

5.7.RVA与FOA的转换

1、RVA(相对虚拟地址)到FOA(文件偏移地址)的转换: <1>得到RVA的值:内存地址- ImageBase <2>判断RVA是否位于PE头中,如果是: FOA== RVA <3>判断RVA位于哪个节: RVA>=节VirtualAddress RVA <=节.VirtualAddress +当前节内存对齐后的大小 差值= RVA-节VirtualAddress; <4> FOA=节.PointerToRawData +差值;

如果文件对齐和内存对齐的值一样,则RVA=内存地址- ImageBase,F0A=RVA,就可以得出在文件中的地址

5.8.空白区添加代码

给程序添加一个MessageBox对话框,步骤

  • 在PE的空白区构造一段代码
  • 修改入口地址为新增代码的地址
  • 新增代码执行后,跳回到入口地址

1、MessageBox的反汇编硬编码

E8 表示call

6A表示push

9:        MessageBox(0,0,0,0);
00401028 8B F4                mov         esi,esp
0040102A 6A 00                push        0
0040102C 6A 00                push        0
0040102E 6A 00                push        0
00401030 6A 00                push        0
00401032 FF 15 8C 42 42 00    call        dword ptr [__imp__MessageBoxA@16 (0042428c)]
00401038 3B F4                cmp         esi,esp
0040103A E8 31 00 00 00       call        __chkesp (00401070)

2、找到要运行的程序的MessageBoxA的地址

用DTDdbug打开程序,点“E”,找到“USER32.DLL”,按“Ctrl+n”,然后找到MessageBoxA函数的地址

构造自己的代码,找一段空白区,写上自己的代码

先执行我们要写的代码(弹出信息框),执行完,然后jmp到程序入口位置

构造要写入的代码

6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00

E8表示call 
E8后面的硬编码 = 要跳转的地址 - E8指令当前的地址 - 5

要跳转的MessageBoxA的地址:77D5050B

E8后面的硬编码 = 77D5050B - (ImageBase+F98)- 5 = 7794F56E
    
程序入口:000193BE
ImageBase:00400000
程序运行入口=ImageBase+程序入口=004193BE
    
E9后面的硬编码 = 004193BE - 400F9D - 5 = 1841C

最终代码
6A 00 6A 00 6A 00 6A 00 E8 6E F5 94 77 E9 1C 84 01 00

修改程序入口

把入口改成我们自己构造的代码的起始位置F90

5.9.扩大节

1、为什么要扩大节

我们可以在任意空白区添加自己的代码,但如果添加的代码比较多,空白区不够怎么办?

2、扩大节的步骤

<1>分配一块新的空间,大小为S <2>将最后-一个节的SizeOfRawData和VirtualSize改成N N = (SizeOfRawData或者VirtualSize内存对齐后的值)+ S <3>修改SizeOflmage大小

S = 1000

VirtualSize:78B0 当前节内存中没有对齐的实际大小

SizeOfRawData:8000 当前节文件对齐后的大小

N = 8000 + 1000 = 9000

修改VirtualSize和SizeOfRawData值

扩大节,添加1000h,也就是十进制4096字节。右键-->粘贴-->粘贴零字节-->4096

修改SizeOflmage的值,先内存对齐后再加1000

SizeOflmage结果为

5.10.新增节

1、新增节的步骤: <1>判断是否有足够的空间,可以添加一个节表. <2>在节表中新增一个成员. <3>修改PE头中节的数量. <4>修改sizeOflmage的大小. <5>在原有数据的最后,新增一个节的数据(内存对齐的整数倍). <6>修正新增节表的属性.

2、节表结构

#define IMAGE_SIZEOF_SHORT_NAME 8              
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];  //ASCII字符串 可自定义 只截取8个字节(占8字节)
    union {                     //Misc双子是该字节没有在对齐前的真实尺寸 该值可以不准确(占4字节)
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;        //在内存中的偏移地址加上ImageBase才是内存中的真正地址
    DWORD   SizeOfRawData;		   //节在文件中对齐后的尺寸	
    DWORD   PointerToRawData;      //节区在文件中的偏移
    DWORD   PointerToRelocations;  //调试相关
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;        //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SECTION_HEADER          40

在节表中新增一个节,把.txet节的40个字节复制粘贴到新增加的节,然后修改新增加节的成员属性

  • 前8个字节是节的名字:随便改个名字
  • 把之前最后一个节的VirtualSize(内存中没有对齐的实际值)改为内存对齐后的值

改为8000

修改新增加节的VirtualSize和SizeOfRawData,因为新增加的节大小为1000h

新增加节的VirtualAddress = 上一个节内存对齐后的大小+上一个节.VirtualAddress

新增加节
VirtualAddress = 00008000+0002B000 = 00033000
PointerToRawData=VirtualAddress

修改sizeOflmage的大小

修改为34000

在原有数据的最后,新增一个节的数据,新增加节的大小为1000h

先删除第一个节前面的40个字节(因为前面新增加了一个节表,数据全部往后推移了40个字节)

在最后面添加1000h字节

5.11.导出表

1、如何查找导出表

扩展PE头最后一个成员是一个数组(包含16和元素),每个数组对应一个表(每个表占8字节),如导出表、导入表等。

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;     //表的起始位置RVA
    DWORD   Size; 				//表的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

2、导出表结构

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics; 		//未使用
    DWORD   TimeDateStamp;			//时间戳
    WORD    MajorVersion;			//未使用
    WORD    MinorVersion;			//未使用
    DWORD   Name;					//指向该导出表文件名字符串
    DWORD   Base;					//导出函数起始序号
    DWORD   NumberOfFunctions;		//所有导出函数的个数
    DWORD   NumberOfNames;			//以函数名字导出的函数个数
    DWORD   AddressOfFunctions;     // RVA from base of image 导出函数地址表RVA
    DWORD   AddressOfNames;         // RVA from base of image 导出函数名称表RVA
    DWORD   AddressOfNameOrdinals;  // RVA from base of image 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

3、导出表成员 40字节

导出表位置,数组DataDirectory[0]

起始位置2AD80

Name:2ADBC (RVA),然后从2ADBC的位置开始找,到以0结尾,就是导出表的名字

NumberOfFunctions:导出函数的个数 2个

NumberOfNames:以函数名字导出的函数个数 2个

AddressOfFunctions:导出函数地址表RVA

AddressOfNames:导出函数名称表RVA

AddressOfNameOrdinals:导出函数序号表RVA。序号是两个字节,序号的个数跟函数名称的个数相同

这里序号为0和1

4、参考

  • 总共四个函数
  • 所有导出函数的个数为5,因为序号中间隔了个14没有。函数个数 = 最大序号 - 最小序号 + 1
  • 以函数名导出的函数个数为3,因为有一个函数没有名字
  • 把函数地址对应的二进制复制到OD里面,可以查看到具体是什么函数

5.12.导入表_确定依赖模块

1、定位导入表

导入表位置,数组DataDirectory[1]

第一个导入表开始的位置:22A10

2、导入表结构

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA指向IMAGE_THUNK_DATA结构数组
    };
    DWORD   TimeDateStamp;                  // 时间戳
    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;							//RVA 指向dll名字,该名字以0结尾
    DWORD   FirstThunk;                     // RVA 指向IMAGE_THUNK_DATA结构数组
} IMAGE_IMPORT_DESCRIPTOR;

3、导入表个数

导入表的个数判断:,每个导入表占20个字节,判断有多少个导入表,以20个0为结尾的位置

4、查看依赖的模块名

第一个模块名字

查看

5.13.导入表_确定依赖函数

1、确定需要导入的函数

第一个成员指向的是一张表INT(导入名称表),INT表里面每个成员都是结构体IMAGE_THUNK_DATA,大小是4个字节

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        PBYTE  ForwarderString;
        PDWORD Function;
        DWORD Ordinal;
        PIMAGE_IMPORT_BY_NAME  AddressOfData;
    } u1;
} IMAGE_THUNK_DATA32;

2、INT表里面的结构体

INT表位置22A88,INT表里面有多少个成员(4个字节),就说明依赖当前导入模块多少个函数。结尾标志:四个字节都是00

INT表

3、确定需要导入的函数的名字

确定函数名字为ExitThread

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;		//可能为空,编译器决定,如果不为空,是函数在导出表中的索引
    BYTE    Name[1];	//函数名称,以0结尾
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

5.14.导入表_确定函数地址

PE文件加载前

PE文件加载后

5.15.重定位表

重定位表的位置(第六个表)

导入表位置,数组DataDirectory[5]

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;     
    DWORD   SizeOfBlock;        
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • android 逆向 初级系列(二)

    用户1127566
  • 逆向初级-硬编码(六)

    当指令中出现内存操作对象的时候,就需要在操作码后面附加一个字节来进行补充说明,这个字节被称为ModR/M,该字节的8个位被分成了三部分

    zhang_derek
  • 滴水逆向初级-C++(三)

    1、什么是封装: 将函数定义到结构体内部,就是封装。 2、什么是类: 带有函数的结构体,称为类。 3、什么是成员函数: 结构体里面的函数,称为成员函数...

    zhang_derek
  • 滴水逆向初级-win32(四)

    1、什么是Win32 API?有哪些?在哪里? 主要是存放在C:\WINDOWS\system32下面所有的dll 几个重要的DLL: <1> Kerne...

    zhang_derek
  • 滴水逆向初级-汇编(一)

    进制的定义: 八进制的定义:由八个符号组成,分别是01234567逢八进一。 十进制的定义:由十个符号组成,分别是0123456789逢十进一。 N进制的...

    zhang_derek
  • [系统安全] 二.如何学好逆向分析及吕布传游戏逆向案例

    您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列。因此,我重新开设了这个专栏...

    Eastmount
  • [系统安全] 三.IDA Pro反汇编工具初识及逆向工程解密实战

    您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列。因此,我重新开设了这个专栏...

    Eastmount
  • 滴水逆向初级-C语言(二)

    1、声明变量 变量类型变量名; 变量类型用来说明宽度是多大 int 4个字节 short 2个字节 char 1个字节

    zhang_derek
  • 逆向工厂(一):从hello world开始

    * 本文原创作者:追影人,本文属FreeBuf原创奖励计划,未经许可禁止转载 前言 从本篇起,逆向工厂带大家从程序起源讲起,领略计算机程序逆向技术,了解程序的运...

    FB客服
  • android开发书籍推荐大全

    android开发书籍推荐大全 ? 写在前面 首先,不提供电子版本的下载,请大家自行百度,如果还是找不到,可以加微信联系我。 再次很感谢写...

    用户1263308
  • APP逆向神器之Frida【Android初级篇】

    说到逆向APP,很多人首先想到的都是反编译,但是单看反编译出来的代码很难得知某个函数在被调用时所传入的参数和它返回的值,极大地增加了逆向时的复杂度,有没有什么办...

    Python编程与实战
  • 韩银和:如何设计机器人处理器?

    机器人是否需要专用的芯片支持?要想开发机器人芯片,首先必须弄清楚是否有这方面的需求。一方面,尽管今天能实用的机器人数量还比较少,但在可预期的未来,很多人相信会诞...

    马上科普尚尚
  • 网络安全自学篇(五)| IDA Pro反汇编工具初识及逆向工程解密实战

    介绍;自幼受贵州大山的熏陶,养成了诚实质朴的性格。经过寒窗苦读,考入BIT,为完成自己的教师梦,放弃IT、航天等工作,成为贵财一名大学教师,并想把自己所学所感真...

    天钧
  • 一道题入门逆向分析

    将自己学习逆向破解的知识总结一下,主要是逆向的入门知识以及自己的学习感悟,包括逆向时的一些思路和补丁,注册机,保护壳等方面的一些知识,有不到之处请师傅们斧正。

    FB客服
  • 夺取应用程序的 “制空权”:内存数据

    在病毒查杀,应用安全对抗,静态逆向应用,动态逆向应用,最重要的对象就是,应用程序的内存数据。

    小道安全
  • 逆向工程基础:从PE文件到进程地址空间

    对于确定的处理器,它能执行的指令是确定的,这就是CPU的指令集。CPU的指令集是机器码,于是人们为了容易编程,发明了使用助记符的汇编语言。后来,又出现 了高级语...

    范蠡
  • 第十一章 LVM逻辑卷管理

    上一章我们讲解了标准分区的使用过程,可以看到,标准分区的配置比较简单,但是标准分区也有很显著的缺点,如:分区创建后不可扩容、分区的空间必须连续,不允许跨越多块空...

    晓天
  • 逆向工厂(二):静态分析技术

    * 本文原创作者:追影人,本文属FreeBuf原创奖励计划,未经许可禁止转载 前言 [逆向工厂]第一章节中介绍了逆向技术的相关基础知识,其中提到逆向的两种形式:...

    FB客服
  • 你在看电脑,而黑客们在看你

    这是早期的一则推特,眼尖的用户能注意到: 似乎扎克伯格的笔记本摄像头和麦克风插口都是用胶带蒙住的 Mark Zuckerberg为什么要把摄像头给封住了? ...

    HACK学习

扫码关注云+社区

领取腾讯云代金券