目标:
改动PE导入表,手工给HelloWorld增加一个功能,就是启动的时候写入一条开机启动项,C:cmd0000000000000000000000000000.exe
实现方法:
直接在注册相关自启动里面添加一个开启启动文件路径就行了。
HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun
NewValue C:cmd0000000000000000000000000000.exe
代码逻辑相关:
RegCreateA()
RegSetValueExA()
RegClose()
对应汇编代码:
0001 68xxxxxxxx Push addr @hKey
0002 68xxxxxxxx Push addr szKey
0003 68xxxxxxxx Push HKEY_LOCAL_MACHINE
0004 E8xxxxxxxx Call RegCreateA
0005 6A27 Push 39
0006 68xxxxxxxx Push addr lpszValue
0007 6A01 Push REG_SZ
0008 6A00 Push NULL
0009 68xxxxxxxx Push addr lpszValueName
000A FF35 xxxxxxxx Push @hKey
000B E8xxxxxxxx Call RegSetValueExA
000C FF35 xxxxxxxx Push @hKey
000D E8xxxxxxxx Call RegClose
.
.
.
000E FF25xxxxxxxx JMP DWORD PTR [xxxxxxxx]
000F FF25xxxxxxxx JMP DWORD PTR [xxxxxxxx]
0010 FF25xxxxxxxx JMP DWORD PTR [xxxxxxxx]
细化指令:
(1)64xxxxxxxx push addr @hKey
如果数据在.data段中,而传递的是地址,比如addr @hKey,则push的指令码为64h,后面紧跟着8位的地址,该地址只是了数据所在位置的VA.。即@hKey所在的VA。关于这个值得计算方法如下:
获得程序默认装载地址=0x00400000
获得数据段.data的起始RVA=0x03000
假设数据段的安排如下(注意路径不是***cmd000000.....exe,但是长度一样)
对应字节码为:
根据数据段各变量的位置,从上述所列字节码中找到@hKey所在的位置为0x00403069,所以第一条指令字节码是68 69 30 40 00。
(2)68xxxxxxxx push addr sz1
采用同样的方式 68 0B 30 40 00
(3)68xxxxxxxx push HKEY_LOCAL_MACHINE
因为HKEY_LOCAL_MACHINE是一个双字节常亮,所以压栈的字节码为68h,其后是该常量的值。
So 68 02 00 00 80
(4)E8xxxxxxxx call RgeCreateKeyA
由于这是一个段内调用,所以call的指令字节码为E8,其后跟随的则是距离真正跳转指令的偏移。偏移这么计算:
首先确定HelloWorld.exe的指令长度为24h。
其次,他要跳转过自身后面的所有指令,如下所示:
这些指令加起来长度为26h,两者相加即为call到jmp的偏移量4Ah;所以第四条指令的字节码是 E8 4A 00 00 00。
(5) 6A27 PUSH 27H
如果往栈里推入的双子可以控制在一个字节内,那么需要使用6A指令。后面直接跟一个字节的数值。
根据上面的分析方法,不难得出6-9条指令的字节码依次为:
68 42 30 40 00
6A 01
6A 00
68 39 30 40 00。
(6)FF35xxxxxxxx push @hKey
当要讲数据段制定位置的双字压入栈时,需要使用指令FF35,其跟着制定位置的VA。其实该指令可以解释为: push dword prt ds:[xxxxxxxx]。
所以第十条指令字节码是FF 35 69 30 40 00。
(7)E8xxxxxxxx call RegSetValueExA
同第四条指令,该跳转地址紧跟在第四条指令跳转的后面。现在计算操作数,长度由一下部分组成(call 的话 是直接call的距离,不是直接call VA):
1)该指令下的其他指令如下,总计0Bh个字节
2)原Helloworld.exe指令,总计24个字节。
3)第四条跳转指令FF25xxxxxxxx,总共6h个字节
以上长度加起来后结果是35h。所以第是一条指令字节码为E8 35 00 00 00。
(8) FF35 xxxxxxxx Push @hKey
第十二条指令字节码是 FF 35 69 30 40 00。
(9)E8xxxxxxxx Call RegClose
同第四条指令,该跳转地址紧跟在是一条指令后,其长度的计算包含以下几个部分:
其后再无添加代码(0h)
原Helloworld指令(24h)
第四条指令的跳转指令FF25xxxxxxxx(6h)
第是一条指令的跳转指令FF25xxxxxxxx(6h)
以上长度加起来后的结果是30h。则第十三条指令字节为E8 30 00 00 00。
(10)跳转指令
跳转指令为FF25,后面跟着要跳转到的VA地址。从前面对导入表的分析看,这三个函数地址是IAT的前三个,也就是最后一个非零IMAGE_IMAPORT_DESCRIPTOR结构中字段FirstThunk只想的位置。其地址依次为00402000 00402004 00402008,所以,最后三个跳转指令依次为:(这个地方看不懂的话就去看下之前总结的导入表那章)
FF 25 00 20 40 00
FF 25 04 20 40 00
FF 25 08 20 40 00
综上所述,新增加代码的字节码如下:
与原Helloworld的字节码相比,代码部分增加了4Ch大小。指令字节码构造完成后,接下来的任务是分析此次修改导致的PE头部的变化。
PE头部变化
1.IMAGE_SECTION_HEADER(.data).VirtualSize
新增加的常量都定义在源码的数据段主要包括以下内容:
(上面路径我改成c下的cmdaaaa...aa.exe了)
共98个字节,所以.data接的尺寸要增加64h。
2.IMAGE_DATA_DIRECORY[IAT].size
由于新增加了一个IMAGE_IMPORT_DESCRIPTOR,所以在IAT中要加入新增加的三个入口函数地址;同时,增加一个结尾的0,共四个双字,16个字节,所以IMAGE_DATA_DIRECTOY[IAT].isize要多加10h。
3.IMAGE_DATA_DIRECOTRY[IT].VirtualAddress
由于导入表的起始地址紧跟在IAT后,IAT多增加的字节数也是导入表起始位置偏移的数量,所以该字段要后移16个字节,即IMAGE_DATA_DIRECORY[IT].VirtualAddress需要多加10h。
4.IMAGE_SECTION_HEADER(.rdata).VirtualSize
1)增加一个动态链接库,就增加一个IMAGE_IMPORT_DESCRIPTOR结构,大小14h。
2)OriginalFiresThunk部分,3个新增注册表操作函数名指针和一个0结尾的指针共10h。
3)“编号/名称”部分包含以下定义:
xxRegCreateKeyA xxRegSetValueExA xxRegCkiseKey advapi32.dll 共61个字节(3Dh)。
4)FirstThrunk部分,3个新增注册表函数名指针和一个0结尾的指针共10h。
综上所述,IMAGE_SECTION_HEADER(.rdata).VirtualSize的值需要多加71h。
5)IMAGE_DATA_DIRECTORY[IT].isize
多了一个动态库,就多出一个IMAGE_IMPORT_DESCRIPTOR结构,该结构大小为14h。即IMAGE_DATA_DIRECTORY[IT].isize多增加14h。
6.IMAGE_SECTION_HEADER(.text).VritualSize
代码段前面已经说过,增加4Ch大小。
由于插入代码而使得PE头部发生变化如下:
上面按顺序依次是:
导入表地址 [IT].VirtualAddress
导入表大小 [IT].isize
数据目录数 [IAT].isize
代码段大小 .text
增加的一些导入表相关,就是上面4说的那些,只读数据 .rdata
数据段大小 .data
手工重组
完成了指令字节码构造,明确了PE头部需要更改的字段,接下来就是手工重组部分。由于新的代码和数据与原来的代码和数据加在一起没有超出文件对其要求得粒度,所以DOS头、PE头的大部分字段不需要修改。以下是手动修改过程:
1.数据段
首先,添加程序中用到的数据。定位到HelloWorld.exe文件的800h处,从第一个字节为0的地方复制以下数据:
为什么从800h开始,因为800h是数据段:
然后修改数据段大小:
到200h的地方找到0B改成6Dh
此时的exe还是可以正常运行的。
2.代码段
找到代码段在400h处:
原来是只有这些:
然后在代码段的开始部分加入我们新增的代码,就是最上面分析的那些代码:
之后再修改
橙色是新增功能代码段,黑色是源代码段,黑色原代码段中红色部分是修改部分,因为新产生的IA被安排在IAR的头部,所以其他的调用地址必须往后调整。在程序中,一共增加了三个函数(来自同一个动态链接库),所以按照导入表的要求,在IAT表头需要增加三个函数入口地址(12个字节)和一个全0(4字节)的地址,这样,原来的入口函数地址都要往后调整10h个字节,所以FF25 JMP的IAT都向后偏移10h。
然后把.text代码段长度修改下:
找到代码段长度位置:
到1B0h处把24h改为70h:
改之后代码段长度显示也就变了:
此时的程序无法运行,因为.rdata处还没改,导入表部分还没有改。上面直接偏移了两个原来函数JMP地址。那个地址是什么还不知道呢!所以目前无法运行。
3. .rdata段
这个地方相对较难,要细心些:
首先看下原始导入表数据字节码:
去FOA=600h地方去找:
原来的样子:
修改后的内容如下(带下划线为待定部分,一会会重新计算)
新增的内容,三个函数加一个0位,是4*4
之前有两个函数,Message,和ExitProcess分别只有一个函数,每个又有一个0位,所以是
2*4+2*4最后一共20h
共涉及到3个Dll加上一个0位,20*3+20=50h
对应IAT,3个函数(3+1)*4 一个函数(1+1)*4 所有的一共4*4+2*4+2*4=20h
以上列出了导入表的四个组成部分,只要知道了代码中调用函数的个数以及链接库的个数,INT、导入表项、IAT三个大部分的大小就是固定的了。实际上,导入表的重组只需要先将第4部分也就是函数编号、函数名和动态库这一部分根据编码设置好,剩下的其他部分的数据就可以按照数据结构自行构造了。
第三个导入表项IMAGE_IMPORT_DESCRIPTOR的OriginalFirstChunk值为00002070h,他是一个RVA,转换为文件偏移地址为0x00000670、FirstChunk的值为00002000h,也是一个RVA,对应的FOA为0x00000600。
文件中IAT和INT维护了两份值相同的双字数组。
000020C6H 来源于表4-2中函数RegCreateKeyA的VA地址。由于是RVA所以要减去基地址0x00400000h。另外两个双字用同样的方法填充。构造好的第三部分代码如下:
4.修改与数据目录表项和节表想相关字段
上面说过了:
5.运行测试:
开机启动项的注册表是加上了,但是没有之前的Messagebox弹窗了。
结果往后看,书中是给了解释的(一开始第一遍做没注意,以为做错了)。
修改之后:
这个地方别改错了,书上没有告诉具体细节,我改了好几次最后找到了,JMP顺序
402018,402010,402000,402004,402008改成402010,402018,402000,402004,402008
OK就这些,所有的都搞定了。这个写了好几天,今天又是一点多了。终于整完了,明天还要上班。酱紫。