初识PE

1.PE文件格式

1.1 PE文件格式纵览

PE文件格式的特点是 磁盘中的数据结构布局和内存中的数据结构布局是一致的.

WINDOWS加载器 遍历PE文件并决定文件的哪一步分贝映射,这种映射方式是将文件较高的偏移位置映射到较高的内存地址中.

当PE被WINDOWS加载器装入内存后,内存中的版本称为模块(Module) .映射文件其实地址称为模块句柄(hModule), 可以通过模块句柄访问内存中的其他数据结构, 这个出事内存地址也称为基地址.

内存中的模块代表着进程从这个可执行文件中所需要的 代码,数据和资源. 描述 PE文件的主要地方在 winnt.h 头文件

 

1.1.1 区块

PE文件使用的平面地址空间,所有代码和数据被合并在一起. 文件的内容被分割为不同 Section (区块,又称区段,节等),区块中包含代码或数据.

每个区块都有自己在内存中的属性,包括是否可读写等, 也有不同点额名字,

区块叫 .rdatam 表明它是只读区块,使用区块名只是人们为了方便, 将一个区块命名为 ABC 与 .text是一样有效的

编译器会自动产生一系列标准区块,读者也可以自己创建和命名区块, 在VC++中 #pragma data_seg("MY_DATA") 告诉编译器插入一个区块

这样VC++处理的数据都将放到MY_DATA区块中,而不是默认的.data.

区块有2种对齐值,一种用于磁盘文件内,一种用于内存中. 每个区块从对齐值的倍数的偏移位置开始,一个典型的对齐值是 0x200

一旦被映射到内存中,区块总是至少从一个页边界处开始,也就是当一个PE文件被映射到内存中时,每个区块的第一个字节对应某个内存页

在X86系列CPU中,页是按4KB来排列的,

比如 .text 区块在磁盘文件中的偏移位置是 0x400,在内存中将是 KERNEL32的装入地址之上的 0x1000字节处.

 

1.1.2 相对虚拟地址

在可执行文件中,全局变量等等都需要用到内存地址,经管PE文件有一个基地址,但是他可以载入到进程空间的任何地方,所以出现了相对虚拟地址 Relative Virtual Address /  RVA 的毫末阿门.RVA只是内存中一个简单的相对于PE文件装入地址的偏移位置.

例如 一个EXE文件从地址 0x400000处装入,并且它的代码区块开始与0x401000,代码区块的RVA就是 0x401000 - 0x400000 = 0x1000

GetModuleHandle函数返回可执行文件家在到进程的地址空间所用的句柄/装入地址

 

1.1.3 数据目录表

PE中许多数据结构需要快速定位,比如 输入表,输出表,资源和基址重定位表.这个区域称为数据目录表

 

1.1.4 输入函数

使用其他DLL的代码或数据时,称为输入.当PE文件装入时,windows加载器的工作之一就是定位所有被输入的函数和数据.

许多程序都需要链接 KERNEL32.DLL 而它又从 NTDLL.dll 输入函数,  同样的,链接了 GDI32.dll 他又依赖于 USER32

在PE文件中,有一组数据结构,分别对应每个被输入的DLL,每个这样的结构都给出了被输入的DLL名称并指向一组函数指针

这组函数指针称为 输入地址表 (IAT) .

 

 

1.2 PE文件结构

1.2.1 MS-DOS 头部

每个PE文件都是一个由DOS程序开始的, PE文件第一个字节起始与一个传统的MS-DOS头部,称为 IMAGE_DOS_HEADER

其中有2个字段比较重要, 分别是 e_magic 和 e_lfanew.   e_magic  指出PE头文件的偏移位置,e_lfanew字段(一个字大小)需要被设置为值 0x5A4D  ,这个值在ASCII中表示  "MZ" , 是 MS-DOS 最初创始人之一 Mark Zblikowski名字缩写


1.2.2  IMAGE_NT_HEADERS 头部

  IMAGE_NT_HEADERS 结构是 PE文件主要定位信息所在. 他的偏移位置由 IMAGE_DOS_HEADER 结构中的 e_lfanew 字段给出


IMAGE_NT_HEADERS{

DWORD Signature;

IMAGE_FILE_HEADER FileHeader;

IMAGE_OPIONAL_HEADER32 OptionaHeader;

}IMAGE_NT_HEADERS32


一个有效的PE文件 Signature 字段被设置为 0x00004550 , ASCII对应 "PE00" , 第二个字段是一个 IMAGE_FILE_HEADER结构,包含了PE文件的基本信息,最重要的是其中一个指出了IMAGE_OPIONAL_HEADER 的大小


IMAGE_FILE_HEADER字段
大小字段描述
WORDMachine可执行文件的目标CPU.一般值是IMAGE_FILE_MACHINE_I386  0X014C
WORDNumberOfSections区块数量,紧跟在 IMAGE_NT_HEADERS后面
DWORDTimeDateStamp表明文件何时创建的,
DWORDPointerToSymbolTableCOFF符号表的文件偏移位置
DWORDNumberOfSymbols如果有COFF符号表,它代表富豪数目
WORDSizeOfOptionalHeader紧跟在IMAGE_FILE_HEADER后面的数据大小,在PE文件中,这个结构为IMAGE_OPIONAL_HEADER
WORDCharacteristics表明未见属性的一套旗标


IMAGE_OPIONAL_HEADER结构
大小成员描述
WORDMagic是一个标记字,用来确定是什么头部.常见的有值 0x10b 代表32位, 0x20b代表64
BYTEMajorLinker Version主连接器版本号
BYTEMajorLinker Version次连接器版本号
DWORDSizeOfCode所有带IMAGE_SCN)CNT)SODE属性区块的总大小
DWORDSizeOfInitializedDate所有初始化数据区快的总大小
DWORDSizeOfUninitializedDate未出世区块的大小,通常为0
DWORDAddressOfEntryPoint程序执行入口点 RVA
DWORDBaseOfCode代码区块起始RVA,代码基址
DWORDBaseOfDate数据基址,
DWORDImageBase映像基址,文件在内存中的首选装入地址
DWORDSectionAlignement区段对齐,装入地址必须是本字段执行数值的整数倍
DWORDFileAlignment文件对齐大小
WORDMajorOperatingSystemVersion主要作业系统
WORDMajorOperatingSystemVersion次要系统
WORDMajorImage Version主要映像版本
WORDMajorImage Version次要映像版本
DWORDMajorSubsystem Version主要子系统版本
WORDMinorSubSystem Version次要子系统版本
DWORDWin32Version Value不用字段,设为0
DWORDSizeOfImage映像大小
DWORDSizeOfHeaders文件头大小
DWORDCheckSum效验和
WORDSubsystem子系统
WORDDllCharacteristicsDLL特征旗帜
DWORDSizeOfStacjReserve堆栈保存器大小
DWORDSizeOfStackCommit提交的堆栈大小
DWORDSizeOfheapReserve堆保存器大小
DWORDSizeOfHeapcommit堆提交大小
DWORDLoaderFlags调试有关,默认0
DWORDNumberOfRvaAndSizes数据目录表项数,一直是16
IMAGE_ DATA_DIRECTORYdatadirectory[16]  数组的第一项总是输出表的地址及长度,第二个项是输入表地址和长度
                                                                                                                                   

MAGE_ DATA_DIRECTORY {

DWORD VirtualAddress    //数据的RVA

DWORD Size     //数据的大小

}

 

1.2.3 区块表

紧跟着上一个结构的后的是 区块表 ,它是由 IMAGE_SECTION_HEADER 结构数组,每个IMAGE_SECTION_HEADER 包含了它所关联的信息,如位置,长度,属性.

PE中区块表大小是要对齐的,VC6中默认4KB,

 

IMAGE_SECTION_HEADER
大小成员描述
BYTEName[8]块名,这是一个8为ASCII码,多数以.开头
DWORDMisc.VirtualSize区块虚拟大小
DWORDVirtualAddress虚拟偏移
DWORDSizeOfRawData文件大小
DWORDPointerToRawData文件偏移
DWORDPointerToRelocations无意义
WORDNumberOfRelocations指向重定位的数目
WORDNumberOfLinenumbers指向行号数目
DWORDCjracteristics特征码标志
IMAGE_SCN_CNT_CODE  
IMAGE_SCN_MEM_EXECUTE  
IMAGE_SCN_CNT_INITLALIZED_DATA  
IMAGE_SCN_CNT_UNINITILALIZED_DATA  
IMAGE_SCN_MEM_NOT_PAGED  
IMAGE_SCN_MEM_SHARED  
IMAGE_SCN_MEM_READ  
IMAGE_SCN_MEM_WRITE  
IMAGE_SCN_MEM_INFO  
IMAGE_SCN_MEM_REMOVE  
IMAGE_SCN_LNK_COMDAT  
IMAGE_SCN_ALIGN_XBYTES  
   

1.2.5 输出表

当创建一个DLL时,实际上创建了一组能被EXE或其他DLL调用的函数,当其他EXE使用时,他称为输出了的. 将术语 符号  来代表被输出的函数和变量,每个输出的符号都有一个能对它进行查询的序数好吗.几乎总有一个ASCII名字与他相关

1.2.7 输入表

与函数或变量的输出相对的是输入,DATADIRECTORY 数组的第二个成员指向输入表,简称IID



第二章:Pe分析工具编写

总结,首先DOS头结构  由 PIMAGE_DOS_HEADER 给出 ;

1.

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number                                                                   幻数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                                                      初始值Ss值
    WORD   e_sp;                        // Initial SP value                                                                       初始的Sp值
    WORD   e_csum;                      // Checksum                                                                          效验和
    WORD   e_ip;                        // Initial IP value                                                                          初始Ip值
    WORD   e_cs;                        // Initial (relative) CS value                                                      初始Cs值
    WORD   e_lfarlc;                    // File address of relocation table                                         重定位表的地址
    WORD   e_ovno;                      // Overlay number                                                                  覆盖数
    WORD   e_res[4];                    // Reserved words                                                                保留words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)                                      OEM 验证码
    WORD   e_oeminfo;                   // OEM information; e_oemid specific                           OEM 信息
    WORD   e_res2[10];                  // Reserved words                                                             保留words 
    LONG   e_lfanew;                    // File address of new exe header                                    新的exe文件头地址
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;



其次是 PIMAGE_NT_HEADERS ,由上面的PIMAGE_DOS_HEADER  + PIMAGE_DOS_HEADER .e_lfanew  ,基址+偏移给出

//***********

PIMAGE_DOS_HEADER Doshand;     //DOS头部结构
PIMAGE_NT_HEADERS pNt;          // PIMAGE_NT_HEADERS  结构由DOS头部结构强制转换来,过程如下

pNt = (PIMAGE_NT_HEADERS)((DWORD)Doshand + Doshand->e_lfanew);

//***************


这样 pnt 就是 

IMAGE_NT_HEADERS{

DWORD Signature;           //Pe 标志

IMAGE_FILE_HEADER FileHeader;         //COFF文件头

IMAGE_OPIONAL_HEADER32OptionaHeader;  //可选头

}IMAGE_NT_HEADERS32


类型了

这个类型中  IMAGE_FILE_HEADER  就是  COFF文件头 ,   IMAGE_OPIONAL_HEADER32  就是 可选头

可选头的最后一个数组成员 就是 接下来的 数据和目录

这个数组成员中 第一项总是输出表的地址及长度,第二个项是输入表地址和长度,数组大小是16,每个成员占8字节

定义如下:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;


上面的完了紧接着就是 区块表 IMAGE_SECTION_HEADER


typedef struct _IMAGE_SECTION_HEADER {
BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD   PhysicalAddress;
DWORD   VirtualSize;
} Misc;
DWORD   VirtualAddress;
DWORD   SizeOfRawData;
DWORD   PointerToRawData;
DWORD   PointerToRelocations;
DWORD   PointerToLinenumbers;
WORD    NumberOfRelocations;
WORD    NumberOfLinenumbers;
DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;


在可选头最后一个数组中,第一个是输入表,结构如下

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;                              //未使用,总是0
    DWORD   TimeDateStamp;                             //文件生成时间
    WORD    MajorVersion;                                      //住版本号,一般为0
    WORD    MinorVersion;                                    //此版本号,一般为0
    DWORD   Name;                                               //模块的真实名称
    DWORD   Base;                                                     //基数,加上序数就是函数地址数组的索引指
    DWORD   NumberOfFunctions;                         //元素个数
    DWORD   NumberOfNames;                              //元素名个数
    DWORD   AddressOfFunctions;     // RVA from base of image   指向函数地址数组
    DWORD   AddressOfNames;         // RVA from base of image     函数名字的指针地址
    DWORD   AddressOfNameOrdinals;  // RVA from base of image  指向输出序列号数组
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;


数据目录表中,第二个成员指向输入表, 其结构如下

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;           
        DWORD   OriginalFirstThunk;                   //包含指向输入名称表的RVA,是 IMAGE_THUNK_DATA 结构的数组,数组中每
    } DUMMYUNIONNAME;                                   //个IMAGE_THUNK_DATA 结构定义了一个输入函数的信息,数组最后以一个内容为0的IMAGE_THUNK_DATA 结束

    DWORD   TimeDateStamp;          可执行文件不与被输入的DLL进行捆绑时为0, 以新样式捆绑时为-1           
    DWORD   ForwarderChain;               这是第一个被转向的API索引,如果没有设-1
    DWORD   Name;                                指向被输入的DLL的ASCII字符串的RVA
    DWORD   FirstThunk;                      包含指向输入表地址(IAT)的RVA,IAT是一个IMAGE_THUNK_DATA 结构
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;





3.win32 Debug API

首先介绍下几种异常机制
INT3 异常最为常用,通过修改机器码为0xcc 来制造异常,当程序执行到0xcc代码就会触发INT3异常,将该0xcc代码恢复则跳过异常

内存断点, 故名思义就是用于内存监控的断点,他对内存数据的访问与写入进行监控,通过修改内存属性来制造异常

硬件断点,是由CPU实现的,由调试寄存器来完成,硬件断点监控的断点长度有限,


3.1   win32 调试 API 原理
3.3.1   简要说明

(1)
BOOL ContinueDebugEvent ( DWORD dwProcessId , DWORD dwThreadId, DWORD dwContinueStatus  )
此函数允许调试期恢复先前由于调试事件而挂起的线程
参数依次为: 被调试进程的进程标识符 |  欲恢复线程的线程标识符  |  该值制定了该线程以何种方式继续,包含2个定义值
                        DBG_CONTINUE 和 DBG_EXCEPTION_NOT_HANDLED   |  成功返回非0,失败返回0


(2)
BOOL DebugActiveProcess(DWORD dwProcessId)
此函数允许将调试期捆绑到一个正在运行的进程上
参数依次为: 欲捆绑的进程的标识符, 成功返回非0,失败返回0


(3)
BOOL DebugActiveProcessStop(DWORD dwProcessId)
此函数允许将调试期从一个正在运行的进程上装卸
参数依次为: 欲装卸的进程的标识符, 成功返回非0,失败返回0

(4)
VOID DebugBreak(VOID)
在当前进程中产生一个断点异常,如果当前进程不是调试状态,那么异常将被系统接管,跟Int3 断点一样的
无参数

(5)
VOID DebugBreakProcess(HANDLE hProcess)
在制定进程中产生一个断点异常
参数依次为:  进程句柄,  无返回至

(6)
VOID FatalExit(int eXitCode)
此函数将使调试进程强制退出,将控制权转移至调试期.与ExitProcess不同,在退出前会跳用一个INT3断点
参数依次为:  int 退出码,  无返回至

(7)
BOOLFlushInstructionCache(HANDLE hProcess, LPCVOID lpBassAddress,  SIZE_T dwSize)
刷新指令高速缓存
参数依次为:  进程句柄 |  欲刷新区域的基指针 |  欲刷新区域的长度  |  成功返回非0 , 失败返回0 

(8)
BOOL  GetThreadContext(HANDLE hTread, LPCONTEXT lpContext)
获取指定线程的执行环境
参数依次为:  线程句柄 | 指向CONTEXT 结构的指针  | 功返回非0 , 失败返回0 

(9)
BOOL  GetThreadSelectorEntry(HANDLE hTread, DWORD dwSeletor , LPLDT_ENTRY lpSelectorEntry)
返回指定选择器和线程的描述符表的入口地址
参数依次为:  包含指定选择器的线程的句柄 | 选择器数目  |  LPLDT_ENTRY  指向用来接收描述符表结构的指针  |  功返回非0 , 失败返回0 


(10)
BOOL  IsDebuggerPresent(VOID)
判断调用进程是否处于被调试环境
参数依次为:  如果处于被调试状态,则返回非0 ,否则返回0

(11)
VOID OutputDebugString(LPCYSTR lpPutputString)
将一个字符串传递给调试期显示
参数依次为:  指向"00"结尾的字符串指针 

(12)
BOOL  ReadProcessMemory(HANDLE hTread, LPVOID lpBassAddress, LPVOID lpBuffer, SIZE_T nSize , SIZE_T * lpNumberOfBytesRead)
读取指定进程的某区域内的数据
参数依次为:  进程句柄 |   欲读取区域的基指针  |  保存读取数据的缓冲指针  |  欲读取的字节数  | 功返回非0 , 失败返回0 


(13)
BOOL  SetThreadContext(HANDLE hThread , LPCONTEXT lpContext)
指定线程的执行环境
参数依次为:  欲执行环境的线程句柄  |  指向 CONTEXT结构的指针

(14)
BOOL  WaitForDebugEvent(LPDEBUG_ENENT lpDebugEvent , DWORD dwMilliseconds)
用来等待被调试进程发生的调试事件
参数依次为:  指向接收调试事件信息的 DEBUG_ENENT结构指针, |    哟个例等待调试事件发射管的毫秒数,如果这段时间内没有发生调试事件,函数将返回调用者,如果该阐述指定为 INFINITE ,函数将一直等待调试事件发生

(11)
BOOL   WriteProcessMemory(HANDLE hProcess,  LPCVOID lpBassAddress,  LPVOID lpBuffer , SIZE_T nSize, SIZE_T *lpNameberOfBytesRead)
在指定进程的某区域写入数据
参数依次为:  进程句柄  |  欲写入区域的基地址   |  保存欲写入数据的缓冲指针,  写入的字节数







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值