在空闲空间中插入程序
目标:将一个程序的代码段、数据段、导入表、重定位表等信息插入到另一个程序中,保证先运行插入的程序,再正常运行插入前的程序。
两个程序都是使用汇编语言编写编译,程序体积较小,但程序较大时的原理与插入步骤一样。实验中涉及的两个软件如下:
插入程序打开时,先弹出一个对话框,再打开网址“http://www.baidu.com”,如下图所示:
目标程序打开时只弹出一个对话框,如下图所示:
我们要做的是将代码和数据插入到目标程序的空闲空间中,这样插入后。目标程序的大小不会改变。
1、 分析程序的结构大小
1) 用DIE等工具打开目标程序,得到各节的信息如下:
a) 代码节占用大小为26H,占用块大小为200H,即剩余1DAH的空间
b) 数据节占用大小为14H,占用块大小为200H,即剩余1ECH的空间
c) 导入表占用大小为92H,占用块大小为200H,即剩余16EH的空间
d) 重定位节占用大小为10H,占用块大小为200H,即剩余1F0H的空间
2) 打开插入程序得到各节信息如下:
2、 插入数据
目标程序的数据存储在虚拟地址0x3000开始的地方,转换成文件地址就是0x800H,而数据占用14H字节,因此将数据插入到文件地址0x814开始的地方。
同理插入程序的数据存储在文件地址0x800开始的地方,占用大小为3DH,因此需要把插入程序0x800 – 0x83C的数据插入到目标程序0x814开始的地址。插入数据的字节码如下:CC E1 CA BE 21 00 D5 E2 CA C7 B2 E5 C8 EB B5 C4 B3 CC D0 F2 A3 AC BD AB D2 AA B4 F2 BF AA B0 D9 B6 C8 21 00 6F 70 65 6E 00 68 74 74 70 3A 2F 2F 77 77 77 2E 62 61 69 64 75 2E 63 6F 6D
但是通过分析发现,目标程序与插入程序的提示框标题相同,即全部插入会导致数据重复,虽然不影响程序运行,但是不必要,即我们可以去掉前5个字节CC E1 CA BE 21 00(字符为“提示!”,最后的00表示字符串的结束),因此只需要把如下数据插入到目标程序文件偏移0x814开始的地方:
红色表示新插入的值,插入的字符串最后一个字符串末尾应该也是00结束,由于空闲空间本身全是00,因此不用更改。
更新之后,字符串“提示!”的文件地址为0x800,虚拟偏移为3000H,由于默认装载地址是400000,因此该字符串的虚拟地址是0x403000。
同理字符串“这是插入的程序,将要打开百度!”的虚拟地址为0x403014。字符串“open”的虚拟地址为0x403032。字符串“http://www.baidu.com”的虚拟地址为0x403037
3、 插入导入表
在数据目录表的第二个元素有指向导入表的虚拟偏移,第二项元素的文件偏移为148H,找到目标程序的文件偏移148H处,存储的虚拟偏移为2010H。偏移2010H转换为文件偏移为610H。通过分析目标程序有两个导入表,每个导入表都只调用了一个函数:
分析发现在.idata这个节中,第一个部分存储的是导入地址表,第二部分存储的才是导入表结构体,由于第一个部分不能移动(代码中会引用这里的值,如果IAT较大,则需要较大幅度改动代码)。第三个部分为导入名字表、第四个部分为名字存储字符串(最好不要移动,较多时同样改动较大)。
因此这里的一种做法是:将导入表结构体加上要插入的值后移动到最后面(预留一定空间给字符串插入),在原导入地址表最后面插入导入地址表,在原导入名称表前插入导入名称表,再修改相应字段的值。由于两者都导入了user32.dll因此这个导入表不需要插入。
1) 移动并插入导入表结构体
目标程序含有两个导入表结构体,且最后全00结构体表示结束,一个结构体占用20个字节,因此先复制文件偏移 610H – 637H之间的字节码:
54 20 00 00 00 00 00 00 00 00 00 00 6A 20 00 00 08 2000 00 4C 20 00 00 00 00 00 00 00 00 00 00 84 20 00 00 00 20 00 00
并在后面加入插入程序的导入表shell32.dll字节码,通过分析在文件偏移624H – 637H :
这里有几个字段需要改变,第一个是INT字段,由于导入名字表将被插在原导入名字表的前面,且只调用了一个函数,因此要插入的导入名称表在目标函数导入名称表地址的前8个字节处。
根据目标程序的导入表结构体,得出其INT的起始字段在文件偏移64CH,因此插入后导入名字表的起始地址应该为644H,转成虚拟偏移为:2044H。
第二个是Name RVA字段,由于字符串是插在了目标程序导入表字符串紧接着的最后面,根据目标程序导入表四个部分的排放结构,由于idata的虚拟大小为92H,因此新插入的字符串放在文件偏移692H开始的地方,由于前面存放的是函数名,后面存放的才是dll名,因此dll名存放在文件偏移6A2处,转换成虚拟地址就是20A2H。第三个是IAT字段,由于新的导入地址表插在目标程序导入地址表的最后面,且目标程序的导入地址表的文件偏移为600H,大小为16个字节,因此新的导入地址表在610H开始的地方,虚拟偏移为2010。
因此更新后,需要插入的导入表字节码为:
44 20 00 00 00 00 00 00 00 00 00 00 A2 20 00 00 10 20 00 00
由于需要预留的空间大小为1CH字节,如下图所示:
因此合并后的导入表结构插入在文件偏移6AEH处,插入的合并数据为:
54 20 00 00 00 00 00 00 00 00 00 00 6A 20 00 00 08 20 00 00 4C 20 00 00 00 00 00 00 00 00 00 00 84 20 00 00 00 20 00 00 44 20 00 00 00 00 00 00 00 00 00 00 A2 20 00 00 10 20 00 00
插入结果如下:
由于更改了导入地址表,因此需要更改原数据目录表第二项的值,虚拟偏移改为20AEH,大小改为50H。如下图所示:
2) 插入字符串
由前面的分析可知,插入的字符串字节码为:
D9 00 53 68 65 6C 6C 45 78 65 63 7574 65 41 00 73 68 65 6C 6C 33 32 2E 64 6C 6C 00
从文件偏移692H开始处插入字符串:
插入后shell32.dll的文件偏移为6A2H,虚拟偏移为20A2H
3) 插入导入地址表
按照前面的分析,我们是在原导入表的最后面插入了新的导入地址表,由于在没有导入程序时,导入地址表中存放的实质上还是一个函数名的RVA,所以这里存放的是ShellExecuteA函数名字符串的RVA,而这里的结构实际上是IMAGE_IMPORT_BY_NAME,字符串前面有两个字节表示序号,因此开始时导入地址表存的就是这个结构的地址,即文件偏移692H,转换成虚拟偏移就是2092H。所以需要插入的字节为 92 20 00 00 00 00 00 00。
插入地址紧接着原来的导入地址表,文件偏移为610H:
4) 插入导入名字表
由前面的分析可知,倒入名字表插在文件偏移644H开始的地方,开始时,导入名字表与导入地址表的内容一样,因此同样为:
92 20 00 00 00 00 00 00,插入过程如下:
4、 插入代码
从最开始的分析可知,代码所在节的文件偏移为400H,且代码的虚拟长度为26H,即插入程序的代码插入到文件地址426H开始的后面,要插入的代码如下:
00AC1000 > $ 6A 00 push 0x0 00AC1002 . 68 0030AC00 push 插入程序.00AC3000 00AC1007 . 68 0630AC00 push 插入程序.00AC3006 00AC100C . 6A 00 push 0x0 00AC100E . E8 17000000 call <jmp.&user32.MessageBoxA> 00AC1013 . 6A 01 push 0x1 00AC1015 . 6A 00 push 0x0 00AC1017 . 6A 00 push 0x0 00AC1019 . 68 2930AC00 push 插入程序.00AC3029 00AC101E . 68 2430AC00 push 插入程序.00AC3024 00AC1023 . 6A 00 push 0x0 00AC1025 . E8 06000000 call <jmp.&shell32.ShellExecuteA> 00AC102A $- FF25 0820AC00 jmp dword ptr ds:[<&user32.MessageBoxA>] ; 00AC1030 $- FF25 0020AC00 jmp dword ptr ds:[<&shell32.ShellExecute>; |
地址0030AC00是函数MessageBox第三个参数的地址,这里的地址进行了重定位,这里偏移是3000,不用改动。
地址0630AC00的偏移为3060,需要改成3014,即地址为0x00403014。
地址2930AC00的偏移为3029,需要改成3037,即地址为0x00403037
地址2430AC00的偏移为3024,需要改成3032,即地址为0x00403032
地址0820AC00的偏移为2008,是函数MessageBox对应的导入表地址,在目标程序中,它的偏移仍然是2008,因此不用改动。
地址0020AC00的偏移为2000,是函数ShellExecute对应的导入表地址,在目标程序中,它被插入到了偏移2010处,因此虚拟地址为:0x00402010
还有两个call指令对应的地址也要改动,但由于指令后的四字节是偏移地址,因此我们先插入转移指令后,再计算偏移地址
由于程序执行完后需要转到原来的入口点去执行,我们在文件偏移0XF0处查看到入口点是1000H,因此我们需要将入口点的值改成1026H:
然后在call指令后加一个跳转指令转向原入口点:0x1000。这里的跳转指令使用E9,后面跟一个四字节的偏移,由于这条跳转指令的偏移地址是1050,入口地址是1000,因此偏移=1000-(1050+5)=FFFFFFAB(补码表示)。因此加入的指令字节吗为:E9ABFFFFFF。
由于中间加入了五个字节,因此字节17000000改成1C000000,四字节06000000改成0B000000,即最后要插入的字节码为:
6A 00 68 00 30 40 00 68 14 30 40 00 6A 00 E8 1C 00 00 00 6A 01 6A 00 6A 00 68 37 30 40 00 68 32 30 40 00 6A 00 E8 0B 00 00 00 E9 AB FF FF FF FF 25 08 20 40 00 FF 25 10 20 40 00
5、 插入重定位表
从插入的代码可以看到,上述标红的地址中,除了有两个不需要重定位,其余的都需要重定位,这些需要重定位的偏移分别是:
1029、102E、1040、1045、1057、105D
目标程序重定位节点信息如下:
这里两字节的表示方法为前4位表示定位方式,在Windows中一般为3,后12位表示与当前页的偏移。
因此上述偏移转换得到:3029、302E、3040、3045、3057、305D,转换为字节码为:29 30 2E 30 40 30 45 30 5730 5D 30。存储在文件偏移A10开始后的地方,如下:
修改文件偏移A04处的值为1C(表示当前重定位表块的大小):
6、 完善字段值
数据、代码、导入表、重定位表插入完成后,修改相应的表头节点字段,如修改目标数据段虚拟大小为3BH(14H+37H)等,但是不修改也不影响程序运行,因为程序运行时不会以头节点设置的大小为准。