PE文件格式:导入表&IAT——手工重组
最近学了导入表和IAT之后,有了动手实践一番的想法。于是乎,又是一个大半个晚上的折腾。。。。。不过终于成功了。
因为第一次尝试,途中错误不断所以截图可能有误,但描述应该无误(除非有的地方错了,不影响运行)。
下面是一个锁定任务栏程序(来自《PE权威指南》中的例子),和一个简单的Helloworld程序
.386
.model flat,stdcall
option casemap :none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
sz1 db "Shell_TrayWnd",0
hTray dd ?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
invoke FindWindow,addr sz1,0
mov hTray,eax
invoke ShowWindow,hTray,SW_HIDE
invoke EnableWindow,hTray,FALSE
invoke MessageBox,NULL,addr sz1,NULL,0
invoke ShowWindow,hTray,SW_SHOW
invoke EnableWindow,hTray,TRUE
invoke ExitProcess,0
end start
helloWorld程序
.386 .model flat,stdcall option casemap:none include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib .data szText db 'HelloWorld',0 .code start: invoke MessageBox,NULL,offset szText,NULL,MB_OK invoke ExitProcess,NULL end start
节的名称 未对齐前真实长度 内存中的偏移(对齐后的) 文件中对齐后的长度 文件中的偏移 节的属性
———————————————————————————————
.text 00000024 00001000 00000200 00000400 60000020
.rdata 00000092 00002000 00000200 00000600 40000040
.data 0000000b 00003000 00000200 00000800 c0000040
我们需要加入下面的代码:
Invoke FindWindowA,addr szTrayWnd, 0
Mov hTray,eax
Invoke ShowWindow,hTray,SW_HIDE ; //SW_HIDE==0
Invoke EndbleWindow,hTray,FALSE;
……………………….
Invoke ShowWindow,hTray,SW_SHOW; //SW_SHOW==5
Invoke EndbleWindow,hTray,TRUE;
每一个Invoke都可以分解三部分:
1、 Push 参数
2、 Call XXXXXXXX
3、 Jmp WORD ptr DS:[XXXXXXXX] //指向IAT相应数据 的VA
完整的代码见下面的第三部分.data 段的修改
先修改.Data段
.data段文件偏移是800H,那在十六进制编辑器中打开helloworld.exe看到800H处。修改前如图:
我们需要添加以下几个数据:
szTrayWnd db “Shell_TrayWnd”,0
hTray dd ?
修改后如下图红色部分,最后四个字节的00是为了标示hTray。
修改后.data段的size=0000000bH变大了12H,所以需要将PE文件头中的IMAGE_SECTION_HEADER(.data).VirtualSize (文件偏移为200H处)修改为1DH。
修改完成后helloworld仍然可以运行。
接下来修改.rdata段:
这是最难的一部分,这也是我写这篇文章的主要目的,手工重组IAT。.rdata段文件偏移是600H,看到600H处。修改前如图:
.rdata段中总共有两种数据,一种是IAT,另外一种是导入表数据结构。IAT的描述信息在IMAGE_NT_HEADERS.DataDirectory[12].VirtualAddress(相对于IMAGE_NT_HEADERS的偏移是D8H)。另一个导入表数据结构在IMAGE_NT_HEADERS.DataDirectory[1].VirtualAddress (相对于IMAGE_NT_HEADERS的偏移是80H)。正如成员名,VirtualAddress都是RVA。
导入表数据结构存放着IMAGE_IMPORT_DESCRIPTOR结构,每个结构占14H个字节空间,最后以一个空的IMAGE_IMPORT_DESCRIPTOR结构作为结尾。
typedefstruct _IMAGE_IMPORT_DESCRIPTOR {
union {
ULONG Characteristics;
ULONG OriginalFirstThunk; // RVA
};
ULONG TimeDateStamp;
ULONG ForwarderChain; //-1 if no forwarders
ULONG Name; //RVA导入动态链接库(DLL)名
ULONG FirstThunk; //RVA
}IMAGE_IMPORT_DESCRIPTOR;
IMAGE_IMPORT_DESCRIPTOR结构中的OriginalFirstThunk和FirstThunk(指向IAT的RVA)分别当其最高位为零时,指向(RVA)一个不同的存储着相同数据的IMAGE_THUNK_DATA结构,每个结构占4个字节,最后以一个空的结构作为结尾。最高位为1时,导入符号为名称
typedefstruct _IMAGE_THUNK_DATA32 {
union {
ULONG ForwarderString; // PUCHAR
ULONG Function; // PULONG
ULONG Ordinal;
ULONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
}IMAGE_THUNK_DATA32;
AddressOfData指向(RVA)一个(大小未定的)IMAGE_IMPORT_BY_NAME结构
typedefstruct _IMAGE_IMPORT_BY_NAME {
USHORT Hint; //导入函数在DLL中的编号
UCHAR Name[1]; //长度不定,以‘\0’结束的函数名
}IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
在文件中找到IMAGE_NT_HEADERS.DataDirectory[1](文件偏移为130H)和IMAGE_NT_HEADERS.DataDirectory[12](文件偏移为188H):
得到IAT RVA=2000H 导入数据 RVA=2010H。然后转换成文件偏移地址就是600H和610H
如下图:
IAT放在前面,所以先计算IAT所需空间,IAT中存放着IMAGE_THUNK_DATA结构,大小为4字节。一个导入函数需要一个该结构,共有5个函数,user32.dll中有四个,加上结尾的空结构,需要4*4+4=14H,kernel32.dll中有一个函数,需要4*1+4=8H,总共是14h+8H=1CH。.rdata文件中偏移位600H,所以将600H~61BH预留给IAT。
从61CH开始编辑导入数据(就是DataDirectory[1]所指数据)。即IMAGE_IMPORT_DESCRIPTOR结构。总共有两个动态链接库(user32.dll和kernel32.dll)加上作为结尾标示的空结构,共3*14H=3CH字节空间。
即从61CH~657H之间。然后后面紧接着是originalFirstThunk所指向(RVA)的INT。大小与IAT相同。即658H~673H。再然后从674H开始就是存放着dll名和函数编号+函数名。那先对dll名和函数编号+函数名进行修改。如图:
在函数名之前有两个字节的函数编号!
然后就可以得到下表
名称 文件偏移 RVA
user32.dll 6AC 000020AC
00B2EnableWindow 69D 0000209D
00d0FindWindowA 68F 0000208F
01B1MessageBoxA 681 00002081
0248ShowWindow 674 00002074
kernel32.dll 6C5 000020C5
0080ExitProcess 6B7 000020B7
接下来编辑61CH~657H,即导入表数据区域。先是user32.dll
OriginalFirstThunk文件偏移为 660H RVA=00002060H
TimeDataStamp 忽略,全零
ForWardChain 全零
Name RVA指向“user32.dll”,为000020AC
FirstThunk IAT 文件偏移为608H,RVA为00002008H
然后是kernel32.dll
OriginalFirstThunk指向(INT)658H的RVA即00002058H
TimeDataStamp 忽略,全零
ForWardChain 全零
Name RVA指向“kernel32.dll”,为000020C5H
FirstThunk IAT 文件偏移为600H,RVA为00002000H
接着是全为零的14H个字节。修改后如图:
然后修改IAT600H~61BH,IAT存放着IMAGE_THUNK_DATA。另外注意一点,IAT中总是按照函数编号或者函数名进行升序排序。
先处理kernel32.dll指向的IAT部分,只有Exitprocess一个函数。所以填入其RVA在加上一个空的IMAGE_THUNK_DATA。User32.dll紧随其后,按函数名升序排列。
因为INT(文件偏移658H)在文件中和IAT一样,所以直接拷贝相同长度数据即可。如图。
然后所有的调用API的jmp dword ptr ds:[VA]都是取得IAT的值。
那么ExitProcess 的文件偏移是600H,则VA是00402000H,一样的道理,剩下的API则是:
EnableWindow 614H 00402014H
FindWindowA 610h 00402010H
MessageBoxA 60CH 0040200CH
ShowWindow 608H 00402008H
这些数据会在.data段修改时用到
修改完上面的东西后还没有完成.rdata段的修改。还要修改
IMAGE_SECTION_HEADER(.data).VirtualSize(文件偏移1D8H)=D2H
DataDirectory[1].VitualAddress=0000201C (RVA文件偏移为61CH)
DataDirectory[1].isize= 3CH 不变
DataDirectory[12].VitualAddress=00002000 (RVA文件偏移为600H,不变)
DataDirectory[12].isize= 1CH
最后就是修改.text段
.text段文件偏移是400H。
需要加入的代码如下:(OD反汇编得到的)
为了省事从OD反汇编来的,所以代码中的地址全部要修改。
00401000>/$ 6A 00 push 0x0 ;/Title = NULL
//当确定push 1个字节的内容时用6A
00401002 |. 6800304000 push LockTray.00403000 ; |Class = “Shell_TrayWnd”
//68 0B304000
00401007 |. E856000000 call<jmp.&user32.FindWindowA>; \FindWindowA
//E8
0040100C |. A30E304000 mov dword ptrds:[0x40300E],eax
// A3 19304000
00401011 |. 6A00 push 0x0 ; /ShowState = SW_HIDE
00401013 |. FF35 0E304000 push dword ptr ds:[0x40300E] ; |hWnd
//FF3519304000
00401019 |. E850000000 call<jmp.&user32.ShowWindow> ; \ShowWindow
//E8
0040101E |. 6A00 push 0x0 ; /Enable = FALSE
00401020 |. FF35 0E304000 push dword ptr ds:[0x40300E] ; |hWnd = NULL
//FF35 19304000
00401026 |. E831000000 call<jmp.&user32.EnableWindow> ; \EnableWindow
//E8
; MessgaeBox 部分
;
0040103B |. 6A05 push 0x5 ; /ShowState = SW_SHOW
0040103D |. FF35 0E304000 push dword ptr ds:[0x40300E] ; |hWnd = NULL
//FF35 19304000
00401043 |. E826000000 call<jmp.&user32.ShowWindow> ;\ShowWindow
//E8
00401048 |. 6A01 push 0x1 ; /Enable = TRUE
0040104A |. FF35 0E304000 push dword ptr ds:[0x40300E] ; |hWnd = NULL
//FF35 19304000
00401050 |. E807000000 call <jmp.&user32.EnableWindow>; \EnableWindow //E8
;ExitProcess部分
;所有jmp部分 这里用到.rdata得到的jmp的VA
0040105C FF25 14204000 jmp dword ptr ds:[<&user32.EnableWindow>]
// FF2514204000
00401062 FF25 10204000 jmp dword ptr ds:[<&user32.FindWindowA>]
// FF25 10204000
00401068 FF25 0C204000 jmp dword ptr ds:[<&user32.MessageBoxA>]
// FF25 0C204000
0040106E FF25 08204000 jmp dword ptr ds:[<&user32.ShowWindow>]
// FF25 08204000
00401074 FF25 00204000 jmp dword ptr ds:[<&kernel32.ExitProcess>]
//不变
完整16进制代码如下:
6A 00 680B 30 40 00 E8 57 00 00 00 A3 19 30 40
00 6A 00FF 35 19 30 40 00 E8 51 00 00 00 6A 00
FF 35 1930 40 00 E8 32 00 00 00 6A 00 6A 00 68
00 30 4000 6A 00 E8 2E 00 00 00 6A 05 FF 35 19
30 40 00E8 27 00 00 00 6A 01 FF 35 19 30 40 00
E8 08 0000 00 6A 01 E8 19 00 00 00 CC FF 25 14
20 40 00FF 25 10 20 40 00 FF 25 0C 20 40 00 FF
25 08 2040 00 FF 25 00 20 40 00
完整指令如下:
地址 十六进制数据 指令 注释
00401000 6A 00 push 0 ; WindowName = NULL
00401002 68 0B304000 push offset HelloWorld.0040300B
; ClassName = “Shell_TrayWnd”
00401007 E857000000 call<jmp.&user32.FindWindowA>
; USER32.FindWindowA
0040100C A3 19304000 mov dword ptr ds:[HelloWorld.403019], eax
00401011 6A 00 push 0
00401013 FF35 19304000 push dword ptr ds:[HelloWorld.403019]
00401019 E8 51000000 call <jmp.&user32.ShowWindow>
; 跳转至 user32.ShowWindow
0040101E 6A 00 push 0 ; Enable = FALSE
00401020 FF35 19304000 push dword ptr ds:[HelloWorld.403019]
; hWnd= NULL
00401026 E8 32000000 call <jmp.&user32.EnableWindow>
; USER32.EnableWindow
0040102B 6A 00 push 0
0040102D 6A 00 push 0
0040102F 68 00304000 push offset HelloWorld.00403000
; ASCII “HelloWorld”
00401034 6A 00 push 0
00401036 E8 2E000000 call <jmp.&user32.MessageBoxA>
; 跳转至user32.MessageBoxA
0040103B 6A 05 push 5
0040103D FF35 19304000 push dword ptr ds:[HelloWorld.403019]
00401043 E8 27000000 call <jmp.&user32.ShowWindow>
; 跳转至user32.ShowWindow
00401048 6A 01 push 1 ; Enable = TRUE
0040104A FF35 19304000 push dword ptr ds:[HelloWorld.403019]
; hWnd = NULL
00401050 E8 08000000 call <jmp.&user32.EnableWindow>
; USER32.EnableWindow
00401055 6A 01 push 1 ; ExitCode = 1
00401057 E8 19000000 call <jmp.&kernel32.ExitProcess>
; KERNEL32.ExitProcess
0040105C CC int3
0040105D FF25 14204000 jmp dword ptr ds:[<&user32.EnableWindow>]
00401063 FF25 10204000 jmp dword ptrds:[<&user32.FindWindowA>]
00401069 FF25 0C204000 jmp dword ptr ds:[<&user32.MessageBoxA>]
0040106F FF25 08204000 jmp dword ptr ds:[<&user32.ShowWindow>]
00401075 FF25 00204000 jmp dword ptr ds:[<&kernel32.ExitProcess>]
修改之后截图如下:
然后修改IMAGE_SECTION_HEADER(.text).VirtualSize(文件偏移1B0H)=7BH
再然后,就修改成功了,可以双击运行了。
发表评论