-
当向一个PE文件添加代码的时候,有三种方法:
- 添加到节区末尾的空白区域(代码大小有限制)
- 增加最后一个节区的大小(不限制代码大小)
- 在最后添加新节区(不会受到代码节剩余空间大小的限制,但是要修改PE头的更多字段)
参考自:《逆向工程核心原理》
以第二个方法为例,步骤如下(工具为HxD):
(1) 先查看待修改程序的Section Alignment以及File Alignment。在PE文件中增加的空间要是FileAlignment的大小的倍数。先在对应区域Edit-Insert bytes:Bytecount=200添加200h个字节。由于PE装载器按照Section Alignment进行对齐Virtual Size,Section Alignment一般是1000,所以如果原本的Virtual Size和扩展的字节数的和不超过1000,就不需要单独增大Virtual Size的值。
(2) 编辑PE头被扩展节区的对应字段:Size of Raw Data
与Characteristics
。这两个字段分别是修改为节区被扩展之后的大小,以及为该节区添加代码属性、可写、可执行属性。(IMAGE_SCN_CNT_CODE
,IMAGE_SCN_MEM_EXECUTE
,IMAGE_SCN_MEM_WRITE
)
(3) 现在以添加TLS函数为例:先设置TLS表,对应可选头的数据目录中索引为9的内容。设置的两个字段在32位下是两个DWORD
的值,其中一个代表IMAGE_TLS_DIRECTORY
结构体数组的地址,第二个值代表结构体的大小。==地址即被扩展节区中新加字节的起始地址。==一般是被扩展节区的RVA+节区原始大小(对齐File Alignment后的大小)。
(4) 设置IMAGE_TLS_DIRECTORY
结构体:将TLS回调函数注册到其中。下面是IMAGE_TLS_DIRECTORY
结构体的定义:typedef struct __IMAGE_TLS_DIRECTORY64{ ULONGLONG StartAddressOfRawData; ULONGLONG EndAddressOfRawData; ULONGLONG AddressOfIndex; ULONGLONG AddressOfCallBacks; DWROD SizeOfZeroFill; DWORD Characteristics; } IMAGE_TLS_DIRECTORY64; typedef IMAGE_TLS_DIRECTORY64* PIMAGE_TLS_DIRECTORY64; typedef struct _IMAGE_TLS_DIRECTORY32 { DWORDStartAddressOfRawData; DWORDEndAddressOfRawData; DWORDAddressOfIndex; DWORDAddressOfCallBacks; DWROD SizeOfZeroFill; DWORD Characteristics; } IMAGE_TLS_DIRECTORY64; typedef IMAGE_TLS_DIRECTORY32* PIMAGE_TLS_DIRECTORY32; #ifdef _WIN64 typedef IMAGE_TLS_DIRECTORY64 IMAGE_TLS_DIRECTORY; typedef PIMAGE_TLS_DIRECTORY64 PIMAGE_TLS_DIRECTORY; #else typedef IMAGE_TLS_DIRECTORY32 IMAGE_TLS_DIRECTORY; typedef PIMAGE_TLS_DIRECTORY32 PIMAGE_TLS_DIRECTORY; #endif
存在32为版本和64位版本。第三个字段就是回调函数数组的地址,其在文件中的地址可以通过RVA到raw address的转换查看。即RVA - RVA_BASE = RawAddress - RAW_BASE,将回调函数的地址放入该数组,即可成功注册回调函数。
(5) 先向TLS回调函数写入C2 0C00 - RETURN 0C
命令,即不执行任何操作直接返回。TLS函数的返回指令不是RETN,而是RETN 0C指令,目的是函数有三个参数(大小为0C),需要修正栈,修正大小为0C。
(6) 现在可以为TLS回调函数添加反调试功能,原理是检查PEB.BeginDebugged成员,若处于调试状态,就弹出消息框然后终止程序。一些细节如下:
先比较Reanson参数值是否是1(
DLL_PROCESS_ATTACH
),不是则直接退出。
检查PEB的成员的值,结构体在FS:[30]
中,BeginDebugged
成员的偏移为2,与0比较。
给MessageBoxA传参数,中间两个参数是两个字符串所在的内存地址。
使用Ollydbg进行编写TLS函数时,命令形式为CALL user32.MesageBoxA
、CALL Kernal32.ExitProcess
即可,Ollydbg会自动求得API的地址。前提是要调用的API在IAT中,否则则需要更多步骤。