首页
论坛
课程
招聘
[原创]归纳PE结构基础知识,顺便手撕个PE
2021-11-11 07:41 20301

[原创]归纳PE结构基础知识,顺便手撕个PE

2021-11-11 07:41
20301

一、PE历史对PE学习的影响

  • PE是由微软设计的一种程序文件规范格式,微软设计PE文件格式的目的主要有二:a.为了兼容16位DOS系统;b.为了兼容其它操作系统。且不论初心是否达成,只是如此的雄心壮志,就注定导致PE的学习是需要掉他个大把头发的,因为当中设计了诸多与DOS和其它操作系统相关的内容,一眼望去茫茫一片着实让人心慌。尤其在学习PE之初,面对PE内容体系繁杂的结构和内容,难免心生忐忑(比如楼主,至今看到PE这两个字依然打哆嗦),不过我可可爱爱的王老师说了:用不到的,不用管它!对此楼主表示深以为然。

  • 在正式开始手撕PE之前,还是要先捋捋相关的理论概念。

二、可执行文件及其结构格式的概念

  • 可执行文件就是无需借助其它应用程序,由操作系统直接负责加载运行的文件,如.exe后缀文件。反之需要借助其它软件才能被加载使用的诸如.txt等后缀文件,即为非可执行文件。此处的后缀名说明仅作为辅助理解,实际上,在操作系统中,判断可执行文件的主要依据并非文件名后缀标识,而是文件结构格式。

    • Windows平台可执行文件的文件结构称为"PE文件结构(Portable Executable)";
    • Linux平台可执行文件的文件结构称为"ELF文件结构(Executable and Linkable Format)"。
  • 本文将以Windows平台的"PE文件格式"作为研究对象,相关资料来源于科锐学习笔记,如有错漏不妥之处,还望各位大佬多多指教。

三、PE整体结构

(一)PE结构详图

图3-1    PE结构详图

(二)PE结构重点

1.DOS部分

(1)DOS头结构体

typedef struct _IMAE_DOS_HEADER//  偏移, 意义
{ 
    WORD e_magic;              //  0x00, 'MZ'(0x5A4D)标识 	
		......中间成员为兼容16位操作系统,可修改可忽略......
    LONG e_lfanew;             //  0x3C, PE头的起始地址,默认0xB0处
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;  

1.WORD e_magic(0x00处)
● 对应PE文件的开头,是PE文件DOS头的标识符"MZ"→0x5A4D
  ○ 对应Winnt.h头文件中宏定义: #define IMAGE_DOS_SIGNATURE 0x4D5A // MZ
2.LONG e_lfanew(0x3C处)
● 对应PE文件0x3C处指向NT头在文件中的偏移(默认0xB0),即32位及以上系统文件头在文件中真正的偏移

图3-2    IMAGE_DOS_HEADER成员图析

2.NT头

(1)DWORD Signature:"PE\0\0"→0x00004550

对应Winnt.h头文件中宏定义:,#define IMAGE_NT_SIGNATURE 0x50450000 // PE00

(2)文件头结构体 IMAGE_FILE_HEADER FileHeader

  • MAGE_FILE_HEADER:描述磁盘上PE文件的相关信息。

  • *定位文件头地址:DOS头中的e_lfanew+4

// 文件头结构体: _IMAGE_FILE_HEADER
typedef struct _IMAGE_FILE_HEADER{
	 WORD Machine; 								// +0x00, 指定程序的运行平台,勿改
	 WORD NumberOfSections; 			// +0x02, PE中的节/块(section)数量,勿改
	 DWORD TimeDateStamp; 				// +0x04, 时间戳:链接器填写的文件生成时间
	 DWORD PointerToSymbolTable;  // +0x08, 指向符号表的地址(主要用于调试)
	 DWORD NumberOfSymbols; 			// +0x0C, 符号表中符号个数(同上)
	 WORD SizeOfOptionalHeader; 	// +0x10, IMAGE_OPTIONAL_HEADER32选项头结构大小,勿改
	 WORD Characteristics; 				// +0x12, 文件属性,勿改
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

//* 字段1:Machine 表CPU的类型, 定义在windows.h中,常用类型
			32位:#define IMAGE_FILE_MACHINE_I386, 0x014c // Intel 386, x86
			64位:#define IMAGE_FILE_MACHINE_AMD64, 0x8664 // AMD64(KB), x64
//* 字段2:NumberOfSections 表PE中的节(section)数量:
			节表紧跟在IMAGE_NT_HEADERS后面,此字段决定了节表中元素的个数,即节的个数
      遍历节表经验:根据此处的个数拿对应的节表数据
//* 字段6:SizeOfOptionalHeader 表IMAGE_OPTIONAL_HEADER32 结构大小
			定位节表位置=选项头地址+选项头大小
//* 字段7: Characteristics 表文件属性,EXE默认0100,DLL默认210Eh,或运算组合设置。
#define IMAGE_FILE_RELOCS_STRIPPED     0x0001 // 文件中不存在重定位信息
#define IMAGE_FILE_EXECUTABLE_IMAGE    0x0002 // 文件可执行
#define IMAGE_FILE_LINE_NUMS_STRIPPED   0x0004 // 文件中不存在行信息
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED   0x0008 // 文件中不存在符号信息
#define IMAGE_FILE_AGGRESIVE_WS_TRIM    0x0010 // 调整工作集
#define IMAGE_FILE_LARGE_ADDRESS_AWARE   0x0020 // 程序能处理大于2G的地址
#define IMAGE_FILE_BYTES_REVERSED_LO    0x0080 // 小尾方式
#define IMAGE_FILE_32BIT_MACHINE      0x0100 // 只在32位平台上运行
#define IMAGE_FILE_DEBUG_STRIPPED     0x0200 // 不包含调试信息
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // 不能从可移动盘运行
#define IMAGE_FILE_NET_RUN_FROM_SWAP    0x0800 // 不能从网络运行 
#define IMAGE_FILE_SYSTEM          0x1000 // 系统文件(如驱动程序),不能直接运行
#define IMAGE_FILE_DLL            0x2000 // 是一个dll文件
#define IMAGE_FILE_UP_SYSTEM_ONLY      0x4000 // 文件不能在多处理器计算机上运行
#define IMAGE_FILE_BYTES_REVERSED_HI     0x8000 // 大尾方式

图3-3    IMAGE_FILE_HEADER成员图析

(3)选项头结构体IMAGE_OPTIONAL_HEADER(区分32位和64位)

  • IMAGE_FILE_HEADER::以供操作系统加载PE文件使用,必选。

  • *定位选项头地址:DOS头中的e_lfanew+4+0x14(文件头大小)。

// 32位选项头结构体:_IMAGE_OPTIONAL_HEADER
typedef struct _IMAGE_OPTIONAL_HEADER
{
	WORD   Magic;							//* PE标志字:32位(0x10B),64位(0x20B)
	BYTE   MajorLinkerVersion;				//	主链接器版本号
	BYTE   MinorLinkerVersion;				//	副链接器版本号
	DWORD  SizeOfCode;						//	代码所占空间大小(代码节大小)
	DWORD  SizeOfInitializedData;			//	已初始化数据所占空间大小
	DWORD  SizeOfUninitializedData;			//	未初始化数据所占空间大小
	DWORD  AddressOfEntryPoint;				//*	程序执行入口RVA,(w)(Win)mainCRTStartup:即0D首次断下来的自进程地址
	DWORD  BaseOfCode;						//	代码段基址
	DWORD  BaseOfData;						//	数据段基址
	DWORD  ImageBase;						//*	内存加载基址,exe默认0x400000,dll默认0x10000000
	DWORD  SectionAlignment;				//*	节区数据在内存中的对齐值,一定是4的倍数,一般是0x1000(4096=4K)
	DWORD  FileAlignment;					//*	节区数据在文件中的对齐值,一般是0x200(磁盘扇区大小512)
	WORD   MajorOperatingSystemVersion;		//	要求操作系统最低版本号的主版本号
	WORD   MinorOperatingSystemVersion;		//	要求操作系统最低版本号的副版本号
	WORD   MajorImageVersion;				//	可运行于操作系统的主版本号
	WORD   MinorImageVersion;				//	可运行于操作系统的次版本号
	WORD   MajorSubsystemVersion;			//	主子系统版本号:不可修改
	WORD   MinorSubsystemVersion;			//	副子系统版本号
	DWORD  Win32VersionValue;				//	版本号:不被病毒利用的话一般为0,XP中不可修改
	DWORD  SizeOfImage;						//*	PE文件在进程内存中的总大小,与SectionAlignment对齐
	DWORD  SizeOfHeaders;					//*	PE文件头部在文件中的按照文件对齐后的总大小(所有头 + 节表)
	DWORD  CheckSum;						//	对文件做校验,判断文件是否被修改:3环无用,MapFileAndCheckSum获取
	WORD   Subsystem;						//	子系统,与连接选项/system相关:1=驱动程序,2=图形界面,3=控制台/Dll
	WORD   DllCharacteristics;				//	文件特性
	DWORD  SizeOfStackReserve;				//	初始化时保留的栈大小
	DWORD  SizeOfStackCommit;				//	初始化时实际提交的栈大小
	DWORD  SizeOfHeapReserve;				//	初始化时保留的堆大小
	DWORD  SizeOfHeapCommit;				//	初始化时实际提交的堆大小
	DWORD  LoaderFlags;						//	已废弃,与调试有关,默认为 0
	DWORD  NumberOfRvaAndSizes;				//	下边数据目录的项数,此字段自Windows NT发布以来,一直是16
	IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];// 数据目录表
} IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32;

//* 字段6:AddressOfEntryPoint 表 程序入口RVA,即OEP:
      EOP:程序入口点,壳相关概念
      OEP:原本的程序入口点(实际为偏移,+模块基址=实际入口点)
      EP: 被加工后的入口点
//* 字段9:ImageBase 表 模块加载基地址,exe默认0x400000,dll默认0x10000000
      建议装载地址:exe映射加载到内存中的首地址= PE 0处,即实例句柄hInstance
      一般而言,exe文件可遵从装载地址建议,但dll文件无法满足
//* 尾字段:DataDirectory 表 数据目录表,用来定义多种不通用处的数据块。
      存储了PE中各个表的位置,详情参考IMAGE_DIRECTORY_ENTRY...系列宏

图3-4    IMAGE_OPTIONAL_HEADER成员图析

3.节表

(1)节表总概

  • 节表:描述PE文件与内存之间的映射关系,由一系列的IMAGE_SECTION_HEADER结构排列而成,每个结构用来描述一个节(每个节占用0x28B),说明PE文件的指定内容拷贝至内存的哪个位置、拷贝大小及内存属性的设置。结构的排列顺序和它们描述的节在文件中的排列顺序是一致的。全部有效结构的最后以一个空的IMAGE_SECTION_HEADER结构作为结束,所以节表中总的IMAGE_SECTION_HEADER结构数量等于节的数量加一。节表总是被存放在紧接在PE文件头的地方。

  • 节表大小 = FileHeader.NumberOfSections(节数量)* IMAGE_SECTION_HEADER 结构体。

  • // IMAGE_SECTION_HEADER 节表结构体,大小40B
    typedef struct _IMAGE_SECTION_HEADER {
      BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];	// 节表名称:描述性字段
      // 下方4个字段:从文件S1处开始,拷贝S2大小的数据,到内存S3处,有效数据占用内存S4大小
      union {
        DWORD PhysicalAddress;
        DWORD VirtualSize;			// S4:内存大小
      } Misc;
      DWORD VirtualAddress;			// S3:内存地址:基于模块基址
      DWORD SizeOfRawData;			// S2:文件大小
      DWORD PointerToRawData;		// S1:文件偏移
      DWORD PointerToRelocations;	// 无用
      DWORD PointerToLinenumbers;	// 无用
      WORD  NumberOfRelocations;	// 无用
      WORD  NumberOfLinenumbers;	// 无用
      DWORD Characteristics;		// 节属性,取值IMAGE_SCN_...系列宏
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

图3-5    节属性常用位含义

  • 节表置于选项头之后,节表首地址 计算方法:

    • (1)选项头的地址 + 选项头的大小;

    • (2) e_lfanew+4+0x14(文件头大小)+0xE0(32位选项头大小)。

图3-6   节表定位图析

(2)节表重点成员--数据目录 IMAGE_DATA_DIRECTORY

  • 数据目录用来描述PE中各个表的位置及大小信息,重点表:导出表、导入表、重定位表、资源表。

// 数据目录 _IMAGE_DATA_DIRECTORY结构体
typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;     /**指向某个数据的相对虚拟地址   RAV  偏移0x00**/
  DWORD Size;               /**某个数据块的大小                 偏移0x04**/
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
  • 在这个数据目录结构体中只有两个成员 VirtualAddress Size :

    • 参数1VirtualAddress指定了数据块的相对虚拟地址(RVA),因为当exe在处理导入表的时候,已经映射进进程内存了,取值RVA更方便。

    • Size则指定了该数据块的大小,有时并不是该类型数据的总大小,可能只是该类型数据一个数据项的大小。这两个成员(主要是VirtualAddress)成为了定位各种表的关键,所以一定要知道每个数组元素所指向的数据块类型,以下表格就是它的对应关系:

    图3-7    IMAGE_DATA_DIRECTORY数组元素项图析

  • 定位数据目录的技巧:数据目录向上找10h,+4即为DataDirectory的地址,编译器生成的一般都是10h。

图3-8    数据目录定位图析

(3)数据目录重点元素--DataDirectory[1] IMP(导入表)

  • IMP(导入表):导入表用来描述模块调用的API列表,位于PE数据目录中的第二项即DataDirectory[1],其中记录了导入表的地址和大小,VirtualAddress指向IMAGE_IMPORT_DESCRIPTOR 结构体数组,这个结构体数组中的每个元素对应着一个dll文件,以全0作为最后一个元素结尾。程序产生调用会生成CALL指令,两大问题及解决思路如下:

    • 1.地址存放问题:出于运行环境等因素考虑,导入函数的地址不能为固定地址。所以在exe中保存导入函数的相关信息,系统和链接器对其进行约定:链接器在生成exe的时候,为所有调用API的地方填写一个间接地址,当程序运行起来后,相应地址则会被写入真正API的地址,此区域即为IAT表(导入地址表);

    • 2.exe如何存储导入dll及其函数信息:dll与函数是一对多的关系,原则上应该设计为多方填写1方信息的数据关系,但考虑到数据较多的情况,遍历不便。反过来设计为1方存储多方信息的数据结构,虽然会造成插入删除的不方便,但是考虑到exe加载dll的实际场景,无插入删除需求,所以应该设计为后者结构更贴合遍历查询需求。

// IMAGE_IMPORT_DESCRIPTOR 导入表结构,以全0(20个0)结尾
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;     //  作用1:导入表结束标志
        DWORD   OriginalFirstThunk;  //* 作用2:*RVA指向一个结构体数组(INT表)
    }; 
    DWORD   TimeDateStamp;           //  时间戳
    DWORD   ForwarderChain;          //  -1 if no forwarders
    DWORD   Name;                    //* RVA指向以0结尾的dll名字
    DWORD   FirstThunk;              //* RVA指向一个结构体数组(IAT表,DataDirectory[12]项)
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
  • IAT(导入地址表):IMP中的FirstThunk指向IAT表。

  • INT(导入名称表):IMP中的OriginalFirstThunk指向INT表,也是DataDirectory[12]项。

  • PE加载前,IAT和INT都指向_IMAGE_IMPORT_BY_NAME结构体
/ IMAGE_THUNK_DATA结构体汇总只有一个联合体,
// 一般用四字节的AddressOfData来获取IMAGE_IMPORT_BY_NAME的地址。
// 四字节解析,看最高位[31]:
//      1表序号导入,低word为导入函数的序号值;
//      0表RVA,指向_IMAGE_IMPORT_BY_NAME;
typedef struct _IMAGE_THUNK_DATA32 {
    union {
        PBYTE  ForwarderString;  	// PBYTE 指向一个转向者字符串的RVA;
        PDWORD Function;      	 	// PDWORD 被输入的函数的内存地址;
        DWORD Ordinal;       		// *被输入的API的序数值
        PIMAGE_IMPORT_BY_NAME  AddressOfData; //*RVA 指向IMAGE_IMPORT_BY_NAME
    } u1;
} IMAGE_THUNK_DATA32;


// IMAGE_IMPORT_BY_NAME有两个成员:1.序号;2.函数名。
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;   // 可能为0,编译器决定,如果不为0,是函数在导出表中的索引
    BYTE    Name[1];// 函数名称以0结尾,由于不知道到底多长,所以干脆只给出第一个字符,找到0结束
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
  • PE加载后,IAT有变:加载后的IAT每一项存储的是所对应的导入函数地址。

图3-9    PE加载前后IMP、IAT、INT之间的关系

4.节数据

  • 节数据:即是由不同属性数据组成的不同节。

四、手撕PE

(一)实现目标

  • 实现目标:手写实现196 Byte大小的PE文件(又名:畸形PE/变形PE),要求MessageBox弹框显示"hello world"。

  • 实现要点:充分利用空间,在保证遵循PE结构的基础上对数据结构进行重构存放。

(二)环境工具

  • 运行环境--XP系统:XP对于畸形PE的兼容性更高。

  • 编辑器--winhex:用于编写PE文件。

(三)步骤

1.创建文件

  • 使用winhex工具,新建196字节大小的PE文件并将数据初始化为全CC,以便分辨空闲区。

图4-1    myWinPE.exe文件创建流程

2.编写DOS_HEADER部分

  • 文件偏移+0x00(注1),WORD e_magic→'MZ'文件头标识:0x5A4D(注2)

  • 文件偏移+0x3C,LONG e_lfanew→指向NT头'PE'标识的地址:0x00000004

    • 此处设置NT头地址为0x4偏移的原因:1.充分利用每字节可用空间,DOS除首尾两字段外都非必要字段,可用;2.当NT头置于0x4处,0x3C的功能不仅是指向NT头标识,还将成为选项头的成员SectionAlignment(文件对齐值),在XP中,此项最小值为4。综上两点,此处写入0x4最为合适。

图4-2    DOS_HEADER部分内容

3.编写NT头的Signature部分

  • 文件偏移+0x04,DWORD Signature→'PE00'NT头标识:0x00004550

图4-3    NT头的Signature部分内容

4.编写NT头的FILE_HEADER部分

  • 文件偏移+0x08,WORD Machine→指定程序的运行平台0x104C(运行于Intel 386

  • 文件偏移+0x0A,WORD NumberOfSections→PE中的节数量:0x0001(最小限值)

  • 文件偏移+0x18,WORD SizeOfOptionalHeader选项头大小:0x0080
    • 选项头大小默认为0xE0,不过以目标为导向,我们还是要力求最小,计算可得,不包含数据目录字段的选项头结构体大小是0x60,而每一个数据目录数组元素的大小是0x8,此处我们留出4个数据目录元素的大小,即4*8=0x20,加上0x60=0x80。
  • 文件偏移+0x1A,WORD Characteristics文件属性:0x010F
  • 0x010F组合属性表示:只在32位平台上运行的不存在行信息、重定位信息、符号信息的可执行文件。

图4-4    NT头的FILE_HEADER部分内容

5.编写NT头的OPTIONAL_HEADER部分

  • 文件偏移+0x1C,WORD Magic→PE标志字即程序位数0x010B(32位系统)

  • 文件偏移+0x2C,DWORD AddressOfEntryPoint→程序执行入口RVA:0x00000000

  • 文件偏移+0x38,DWORD ImageBase→内存加载基址:0x00400000(exe默认)
  • 文件偏移+0x40,DWORD FileAlignment→文件对齐值:0x00000004(最小限值)
  • 文件偏移+0x4C,WORD  MajorSubsystemVersion→主子系统版本号0x0004(恢复默认不可修改
  • 文件偏移+0x4E,WORD  MinorSubsystemVersion→副子系统版本号0x0000(恢复默认不可修改)
  • 文件偏移+0x50,DWORD  Win32VersionValue版本号,XP中须为00x00000000
  • 文件偏移+0x54,DWORD SizeOfImage→PE文件在进程内存中的总大小0x00001000(内存预计占用1页内)
  • 文件偏移+0x58,DWORD SizeOfHeaders→PE文件头部在文件中的总大小0x000000C4(=文件实现目标大小)
  • 文件偏移+0x60,WORD  Subsystem→程序类型:0x00022=图形界面
  • 文件偏移+0x62,WORD   DllCharacteristics文件特性0x0000默认为0
  • 文件偏移+0x64,DWORD  SizeOfStackReserve初始化时保留的栈大小0x07FFFFFF
  • 文件偏移+0x68,DWORD  SizeOfStackCommit初始化时实际提交的栈大小0x07FFFFFF
  • 文件偏移+0x6C,DWORD  SizeOfHeapReserve初始化时保留的堆大小0x07FFFFFF
  • 文件偏移+0x70,DWORD  SizeOfHeapCommit初始化时实际提交的堆大小0x07FFFFFF
  • 文件偏移+0x78,DWORD  NumberOfRvaAndSizes→数据目录的元素个数0x00000004
  • 文件偏移+0x7C,DataDirectory[4]→4项元素的数据目录:全0

图4-5    NT头的OPTIONAL_HEADER部分内容

6.编写节表部分

  • 文件偏移+0xA4,DWORD VirtualSize内存时机占用大小:0x000000C4

  • 文件偏移+0xA8,DWORD VirtualAddress内存地址RVA:0x00000000

  • 文件偏移+0xAC,DWORD SizeOfRawData文件大小:0x000000C4
  • 文件偏移+0xB0,DWORD PointerToRawData文件偏移:0x00000000
  • 文件偏移+0xC4,DWORD Characteristics节属性0xE00000E0
    • 0xE00000E0 = 1110 0000 0000 0000 0000 0000 1110 0000,根据图3-5显示,此属性值表示节中包含代码、已初始化数据和未初始化数据,且映射到内存后的页面可执行可读写。
  • *0xA4-0xB3含义:从文件偏移为0x0的位置,拷贝0xC4大小的数据到内存偏移为0x0的位置,占用内存0xC4大小

图4-6   节表部分内容

7.基础PE总览(xdm先备份一下,再做后方花里胡哨的操作)

图4-7   基础PE内容总览

8.编写导入内容:库+函数

  • 根据导入内容思考设计:实现弹框需要写入两项内容:1.库名(user32);2.函数名(MessageBoxA)。根据两个字符串的长度(+00结束符)找到最合适的位置并写入:

    • 文件偏移+0x30, dll名"user32 ",7Byte

    • 文件偏移+0x0C, 函数名"MessageBoxA ",12Byte

图4-8   写入库名和函数名

9.编写导入表项

  • 导入表IMAGE_IMPORT_DESCRIPTOR 0xB0处,因为PE加载后,0xB0-0xC3处内存会被初始化为全0。

  • 文件偏移+0xBCDWORD   Name→RVA指向dll名0x00000030

  • 文件偏移+0xC0:DWORD   FirstThunk→RVA指向IAT表:0x0000009C

图4-9   填充导入表项

10.编写IAT表 IMAGE_THUNK_DATA32

  • 文件偏移+0x9C,PIMAGE_IMPORT_BY_NAME  AddressOfData指向IMAGE_IMPORT_BY_NAME:0x0000000A

    • 因为IMAGE_IMPORT_BY_NAME结构体的指向函数名的NAME成员前还有一个WORD Hint成员,所以指向的地址需要跳过一个WORD 2字节,以实现NAME指向函数名。
  • 文件偏移+0xA0,以全0作结尾项0x00000000

图4-10  编写IAT表

11.将导入表位置写入数据目录中

  • 文件偏移+0x80DataDirectory[1]导入表位置:0x000000B0

图4-11  写入导入表位置

  • OD检测:内存窗口中跟随地址0x40009C,以"长型→地址"方式查看,可见导入成功。

图4-12  OD检测导入表

12.编写汇编指令:获取MessageBoxA函数地址并传参调用

  • 文件偏移+0x60,在选项头的堆栈空间值区编写弹框文本:"hello world"

图4-13  写入MessageBox参数

  • *选线头主版本号不可用,故以副链接器版本号成员处(0x1F)开始非重要区域编写指令

  • 文件偏移+0x1F参数4 UINT uType→对话框按钮类型:6A 00 = push 0(注3)

  • 文件偏移+0x21参数3 LPCTSTR lpCaption→消息框标题:6A 00 = push 0
  • 文件偏移+0x23,参数2 LPCTSTR lpText→消息框内容,指向"hello world":68 64004000 = push 00400064
  • 文件偏移+0x25,参数1 HWND hWnd→窗口句柄:6A 00 = push 0
  • 文件偏移+0x2A短跳至空闲区以备调用函数:EB18= jmp short 00400044
  • 文件偏移+0x44从IAT表中获取API地址并调用:FF15 9C004000= CALL  0040009C
  • 文件偏移+0x4A,调用后返回:C3=ret

图4-14  写入指令

14.修改OEP

  • 文件偏移+0x44,OEP:FF15 9C004000= CALL  0040009C

图4-15  修改OEP


15.完整PE总览

图4-16  完整PE总览

16.程序测试

  • 完结撒花花

图4-17  效果展示

五、注释说明

  • [注1]:编写数据时标注的偏移值为相对于文件而言,而非对应结构体。

  • [注2]:内存中采用小尾存储方式,高位字节存放于低地址单元,低位字节存放在高地址单元中。所以在进行文字描述时是根据其数据类型进行转换书写,例如文件前两个字节即'MZ'标识处内存分别显示0x4D、0x5A,但在描述时根据其类型WORD(2字节)就书写为0x5A4D。

  • [注3]:指令描述中=前为机器码,=后为汇编指令内容,如机器码6A 00所代表的指令为push 0。

【公告】看雪团队招聘安全工程师,将兴趣和工作融合在一起!看雪20年安全圈的口碑,助你快速成长!

最后于 2021-11-23 16:45 被633编辑 ,原因: 改进文章排版
收藏
点赞8
打赏
分享
最新回复 (11)
雪    币: 26
活跃值: 活跃值 (42)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_〒_〒 活跃值 2021-11-11 19:14
2
0
shanjie YYDS
雪    币: 238
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
瑞皇 活跃值 2021-11-11 19:28
3
0
写的挺好的,刨析了较小PE的结构,加油!!!
雪    币: 136
活跃值: 活跃值 (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_krvlpodl 活跃值 2021-11-11 19:32
4
0
写的很棒,吃水不忘挖井人。
雪    币: 1636
活跃值: 活跃值 (1545)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
0xC5 活跃值 1 2021-11-12 08:50
5
0
很不错,我猜你可能是我的学弟学妹
雪    币: 476
活跃值: 活跃值 (612)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
633 活跃值 2021-11-12 10:29
6
0
wx_〒_〒 shanjie YYDS
夸夸师强哥持续在线
雪    币: 476
活跃值: 活跃值 (612)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
633 活跃值 2021-11-12 10:30
7
0
瑞皇 写的挺好的,刨析了较小PE的结构,加油!!!
雪    币: 476
活跃值: 活跃值 (612)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
633 活跃值 2021-11-12 10:32
8
0
雪    币: 476
活跃值: 活跃值 (612)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
633 活跃值 2021-11-12 10:33
9
0
0xC5 很不错,我猜你可能是我的学弟学妹[em_48]
40期学妹表示久闻学长大名,感谢肯定,会继续加油哒
雪    币: 1
能力值: (RANK:0 )
在线值:
发帖
回帖
粉丝
mb_kejxxtnj 活跃值 2021-11-21 14:39
10
0
加个Q互相交流一下PE 技术呗 852056282
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
大魔法狮子 活跃值 2021-11-25 21:48
11
0
手撸shellcode +手撸pe=最小最精简可执行文件--》变种=自己组织可执行带重定位和导入导出结构的类pe文件
雪    币: 476
活跃值: 活跃值 (612)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
633 活跃值 2021-11-30 22:18
12
0
大魔法狮子 手撸shellcode +手撸pe=最小最精简可执行文件--》变种=自己组织可执行带重定位和导入导出结构的类pe文件
今天刚接触到shellcode,等琢磨明白了就跟着大佬的思路来一个,谢谢指点
游客
登录 | 注册 方可回帖
返回