目录
一、实验内容
1. 实验数据
2. 实验工具
3. 实验提示
实验阶段1
实验阶段2
实验阶段3
实验阶段4
实验阶段5
二、实验过程与结果
Phase1:
Phase2:
Phase3:
Phase4:
Phase5:
每个实验阶段(共5个)考察ELF文件组成与程序链接过程的不同方面知识
阶段1:全局变量-数据节
阶段2:强符号与弱符号-数据节
阶段3:代码节修改
阶段4:代码与重定位位置
阶段5:代码与重定位类型
在实验中的每一阶段n(n=1,2,3,4,5…),按照阶段的目标要求修改相应可重定位二进制目标模块phase[n].o后,使用如下命令生成可执行程序linkbomb:
$ gcc -o linkbomb main.o phase[n].o [其他附加模块——见具体阶段说明]
正确性验证:如下运行可执行程序linkbomb,应输出符合各阶段期望的字符串:
$ ./linkbomb
$ 19210320303 [仅供示例,具体目标字符为每位学生学号]
学生实验数据包: linklab学号.tar
数据包中包含下面文件:
main.o:主程序的二进制可重定位目标模块(实验中无需修改)
phase1.o, phase2.o, phase3.o, phase4.o, phase5.o:各阶段实验所针对的二进制可重定位目标模块,需在相应实验阶段中予以修改。
readelf:读取ELF格式的各.o二进制模块文件中的各类信息,如节(节名、偏移量及其中数据等)、符号表、字符串表、重定位记录等
objdump:反汇编代码节中指令并提供上述部分类似功能
hexedit:编辑二进制文件内容
检查反汇编代码,获得printf(根据情况有可能被编译时转换为puts)输出函数的参数的(数据节中)地址 。
使用hexedit工具(或自己编写实现二进制ELF文件编辑程序),对phase1.o数据节中相应字节进行修改。
Phase2.o模块的符号表中,包含了类型为COM的符号。此类符号的特点是:未被赋初值。所以,其在ELF的数据节中并不真实存在。所以需要另寻解决办法,创造出真实存在的数据并对其进行二进制编辑,以达到输出自己学号的目的。
解题需要运用的主要知识为强弱符号的解析规则。另外,层序中包含了一个数值转换过程,学生需要根据反汇编代码确定其修改规则,并根据修改规则进行“反制”。
检查反汇编代码,定位模块中的各组成函数并推断其功能作用。 根据反汇编程序的执行逻辑,修改函数中的机器指令(用自己指令替换函数体中的nop指令)以实现期望的输出。
为了实现输出功能,自行编写获得的二进制程序(可以通过编写汇编代码然后使用gcc -c命令的方式实现)可以“借用”其他函数中的“有用代码或数据”,比如输出函数和数据引用等具体部分。
本阶段学生所拿到的.o文件中的“重定位位置”信息已经被抹除,学生需要根据实际情况确认冲重定位的发生位置,并根据重定位类型对位置信息进行恢复。若程序未能够正确修改重定位位置,则典型问题表现为段错误segmentation fault。此外,还需要学生根据程序所用到的数据情况进行数据部分的二进制修改。
如果实验中对缺失重定位信息的恢复不完整或不正确的话,链接生成linkbomb程序时可能不报错。
打开Ubuntu执行语句“readelf -a phase1.o”来通过使用readelf查看phase1.o,查找有关输出函数的内容,如下图1所示。其中我们完成学号字符串的输出,找到对应参数g_data,其重定向类型是绝对地址(R_X86_64_32),且+了0x18,同时也是一个OBJECT-全局变量。因此我们需要找到对应的.data节在全文中的偏移量,从而将g_data的内容修改为学号字符串,完成phase1。
图1:偏移量为0x18
查看到数据节在全文中的偏移量为0x60。
图2:地址为0x60
结合g_data的具体位置及数据节的偏移量,从而确定在0x18+0x60=0x78处插入我们需要插入的数据-学号字符串。我的学号是20215120305,对应的字符串为“32 30 32 31 35 31 32 30 33 30 35”,同时加上“00”来完成ASCII码使用hexedit插入。Ctrl+X保存并退出完成插入,并执行结果,如下图3、4所示,完成phase1。
图3:在0x78开始修改为学号ASCII并加上00
图4:编译运行,得到学号
同phase1一样使用readelf查看phase2.o并查找有关输出函数的内容。从中可以看到put函数的参数是g_myCharArray,其+0x18,重定向类型也为绝对地址。
图5:readelf
查看符号表,COM表示g_myCharArray是一个未初始化的弱符号数组,大小Size为256,所以我们需要创建一个已初始化强符号的g_myCharArray来覆盖弱符号。
图6-1:符号表
创建phase2_patch.c文件并写入0x18个字节的“0”以及学号ASCII码。
图6-2:初始化强符号文件
使用“gcc -c phase2_patch.c”编译生成phase2_patch.o文件并链接:“gcc -o linkbomb2 main.o phase2.o phase2_patch.o -no-pie”,最后运行程序,如下图7所示。
图7:编译运行
结合图7运行结果发现,每个字符都发生了偏移。为了得到偏移量来反偏移得到字符串,实现学号的插入,编写一个hack.c文件来计算,如下图8所示。最终通过编辑好学号key,来计算be=%d输出计算结果,即正确的反偏移答案。
图8:编写计算文件
编译计算文件并链接上linkbomb2,将结果导出为out并使用out运行计算文件,得到的be值即为学号对应的反偏移量,修改patch文件中的字符串。
图9:得到计算结果
得到结果文件后再次编译链接运行linkbomb2,成功输出学号,完成phase2。
图10:完成phase2
不同于phase1和2,phase3部分基于实验提示:“检查反汇编代码,定位模块中的各组成函数并推断其功能作用。 根据反汇编程序的执行逻辑,修改函数中的机器指令(用自己指令替换函数体中的nop指令)以实现期望的输出。而为了实现输出功能,自行编写获得的二进制程序(可以通过编写汇编代码然后使用gcc -c命令的方式实现)可以“借用”其他函数中的“有用代码或数据”,比如输出函数和数据引用等具体部分。”因此需要考虑查看汇编代码并寻找puts语句。在终端输入“gcc -c linkbomb3 main.o phase3.o -no-pie”再进行反汇编objdump来查看汇编代码,部分内容如下图11所示。结合代码发现,如果想要成功打印学号,应该是打印在后,所以包含puts语句的myFunc1方法在后面,并且接收一个参数,这个参数应该是学号;而myFunc2则是获取一个地址的值给到%rax寄存器。
图11:部分汇编代码
基于以上分析:先调用myFunc2函数来获取学号并赋值给%rax寄存器,然后“mov %rax,%rdi”来设置参数再调用myFunc1。则需要注入的命令即为:
call myFunc2
mov %rax,%rdi
call myFunc1
call指令对应的机器码是为“E8 cd”,其中“cd”是一个16位的相对偏移地址,表示将控制转移到目标子程序的地址。当执行call指令时,会将当前指令的下一条指令的地址(即call指令的下一条指令)压入栈中,并跳转到目标子程序的地址。因此我们需要在函数do_phase3的0x400554处和0x40055c处形成两句call指令机器码:myFunc2的地址为0x40053e,从而计算出相对偏移量即是地址0x400559到0x40053e,值为-1b即补码ff ff ff e5、myFunc1的地址是0x400523,从而计算出相对偏移量即是地址0x400561到0x400523,值为-3e即补码ff ff ff c2。都以小端法记入代码;“mov %rax,%rdi”的机器码为48 89 c7,最后形成机器码如下:
0x400554:e8 e6 ff ff ff (call myFunc2)
0x400559:48 89 c7 (mov %rax,%rdi)
0x40055c:e8 c2 ff ff ff (call myFunc1)
得到了相应的机器插入码,接下来需要寻找插入的位置。使用objdump -d phase3.o查看汇编代码,如下图所示,发现是在.text节处偏移量为0x31处,因此也使用readelf -a phase3.o命令查看ELF数据,查询.text节的偏移量,得到是0x40。因此需要注入代码的地址即为0x40+0x31=0x71。
图12:查看phase3汇编
图13:查看.text节偏移量
确定了注入地址,使用hexedit phase3.o来进行插入,可以看到在0x71前确实是48 89 e5,对应上了do_phase3的插入位置,可见图12地址2e处机器码。如下图所示实现插入。
图14:hexedit插入
至此完成代码插入,现在需要完成学号赋值。回看myFunc2函数汇编,发现学号赋值处函数获取数据的内存地址,即为.data节。因此我们需要找到节头以及加数。图13中显示.data节节头为0x1a0,重新查看ELF发现.data加数是为0x18,从而确定学号应当位于phase3.o的0x1a0+0x18=0x1b8处,并且以“00”结尾进行插入。如下图所示。
图15:.data节加数
结合以上分析使用hexedit phase3.o修改0x1b8后的值为学号的ASCII码,并以“00”结尾,如下图所示。
图16:hexedit插入
编译运行,成功输出学号,完成phase3。
图17:完成phase3
结合实验提示,已知本阶段所拿到的.o文件中的“重定位位置”信息已经被抹除,需要根据实际情况确认重定位的发生位置,并根据重定位类型对位置信息进行恢复。而若程序未能够正确修改重定位位置,则典型问题表现为段错误segmentation fault。此外,还需要根据程序所用到的数据情况进行数据部分的二进制修改。因此,我们首先查看phase4.o的elf文件,发现了异常块,.text节的重定向符号偏移量都为0,很明显不会出现多个符号覆盖同一地址的操作,可以判断这三个符号的偏移量被抹除了。
图18:查看elf
执行objdump -d phase4.o来查看phase4的汇编代码,查看留空位置,发现留空位置分别是0x4+0x2=0x6,0xf+0x2=0x11,0x18+0x1=0x19,如下图所示。
图19:查看phase4汇编
基于上述三个偏移量解析,查找.rela.text的初始地址并进行偏移量修改,由图18“’.rela.text’ at offset 0x250”得到初始地址为0x250,因此使用hexedit来进行修改操作。
图20:修改偏移量
再次查看偏移量,发现成功修改。
图21:成功修改
至此我们需要修改puts函数的参数为学号,按文本顺序的位置即为参数位置。查看.data节的起始地址,得到是0x60:
图22:查看.data起始地址
最终修改phase4.o文件,使用hexedit在0x60处将学号ASCII码填入并以“00”结尾。
图23:编译运行phase4
由结果发现学号输出不全,内部发生了偏移,回看elf可以看出temp是一个位于.data节中偏移量为0x14处,而回看图21中.data+10并不是真的temp的值,而.symtab里的value才是它的偏移量,因此重新调用hexedit -a phase4.o来修改0x60+0x14=0x74处的值为“00”。
图24:发现temp值
图25:修改0x74处
修改完毕,重新编译运行phase4,成功输出学号。
图26:完成phase4
结合实验提示需要我们对实验中对缺失重定位信息的恢复不完整或不正确的地方进行分析。首先编译查看汇编代码,可以发现myFunc是关键函数,运行并调试发现g_guard不等于0,查看一下发现等于1。而0x400542与0x40054e处的语句分别查看值发现一个为fake假值,一个是真值0,因此考虑对换来为后续学号赋值处找到正确位置。
图27:gdb调试myFunc
为了实现偏移量的交换,我们查看两个数据的重定向信息,来获取重定向的初始地址为0x340,从而寻址并修改phase5.o文件,将两个偏移量进行互换。
图28:elf重定位信息
使用hexedit来修改phase5.o文件。
图29:修改前后变化
修改完毕,现在需要更改数据为学号,完成最终目的。由图28中.symtab处Num7可以看出g_myCharArray是一个位于.dataa节中偏移量为0x10处的数据,而.data地址在0x90处,因此需要我们在0x90+0x10=0xa0处填入学号,并以“00”结尾。
图30:查看.data地址
图31:hexedit插入学号
编译运行,成功输出学号,完成phase5。
图32:完成phase5