实验目的与要求
1. 理解链接的作用与工作步骤
2、掌握ELF结构、符号解析与重定位的工作过程
3、熟练使用binutils和hexedit等工具完成ELF分析与修改
实验仪器设备/实验环境
1.Linux操作系统 — 64位Ubuntu 18.04
2. gdb调试器和objdump反汇编指令、readelf和hexedit工具
3. 笔记本电脑
实验内容及步骤
题目分五关,每关都围绕main.o和phaseX.o操作,输出自己的学号。这些都是一些可重定位目标文件,满足ELF文件格式。
ELF头
段头部表:将连续的文件映射到运行时的内存段
. init : 定义了_init函数,程序初始化代码会调用它
. text : 已编译程序的机器代码
. rodata : 只读数据,比如printf语句中的格式串和开关语句的跳转表
. data : 已初始化的全局和静态C变量
. bss : 未初始化的全局和静态C变量
. symtab :一个符号表,它存放在程序中定义和引用的函数和全局变量的信息
. debug : 一个调试符号表,其条目时程序中定义的全局变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。
. line : 原始C源程序的行号和.text节中机器指令之间的映射
. strtab : 一个字符串表,其内容包括 .symtab 和 .debug节中的符号表,以及节头部中的节名字。
每个实验阶段(共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 [仅供示例,具体目标字符为每位学生学号]
实验注意事项
1.建议在linux下进行linklab文本编辑。
2.建议使用gdb或IDA之类的调试软件辅助进行。
3.实验前仔细阅读实验要求。
实验过程与结果
Phase1:
运行看看有什么,发现是乱码
使用readelf -a phase1.o命令查看phase1.o的ELF数据
经过分析得知需要修改.data处的信息就可以输出自己的学号
先输出.data里面的东西看看是不是和一开始运行的东西一样
可以看到g_data的全局变量在[3]处,即.data处
用objdump -rd phase1.0看到.data用到的偏移量是0x7
即在.data的0x60+7=0x67处
先安装hexdit命令,因为乌班图没有
Hexdit常用的命令例举
用hexedit phase1.o命令来修改phase1.o,找到0x67处
从上图位置开始修改,输入自己的学号信息的ASCII码,最后加00
保存后退出用readelf -x .data phase1.o查看自己是否修改完成
然后重新链接运行就可以得到输出学号
Phase2:
运行输出看看有什么,还是乱码
readelf -a phase2.o命令查看phase2.o的ELF数据
有个COM未被赋初始值,是个弱符号,根据文档提示打个补丁phase2_patch.o。
里面的内容应该是cahr类型的大小为256的g_myCharArray变量的初始化
用objdump -rd phase2.0看到g_myCharArray用到的偏移量是0x7
所以创建phase2_patch.c文件,创建一个g_myCharArray,偏移量是0x7,所以前7个填0,例如下图
然后在后面添加两个35测试一下输出结果,发现输出了*+,查看ASCII表发现了规律
然后添加到phase2_patch.c文件中,最后的字符结束位是-18,可参照上图规律自懂
重新链接运行后即可输出学号
Phase3:
先导出phase3.o编译后的linkbomb3的汇编文件查看
根据编程思想要打印学号,那肯定打印在后,所以包含puts语句的myFunc1方法在后面,接收一个参数,这个参数应该是学号,myFunc2获取一个地址的值给
到%rax寄存器,那就是先调用myFunc2函数获取学号赋值给%rax,然后mov %rax,%rdi设置参数在调用myFunc1。需要注入的命令是
call myFunc2
mov %rax,%rdi
call myFunc1
call指令对应的机器码是e8 xx xx xx xx。
myFunc2的地址是0x40053e
根据call指令的特性,计算地址0x400559到0x40053e的偏移量是-1b即补码ff ff ff e5。小端法即
0x400554:e8 e5 ff ff ff
0x400559:这里是mov %rax,%rdi
mov %rax,%rdi的机器码是48 89 c7
所以即
0x400554:e8 e5 ff ff ff (call myFunc2)
0x400559:48 89 c7 (movq %rax,%rdi)
0x40055c:e8 xx xx xx xx (call myFunc1)
0x400561:(下一条指令的地址)
myFunc1的地址是0x400523
0x400561到0x400523的偏移量是-3e即补码ff ff ff c2
即
0x400554:e8 e6 ff ff ff (call myFunc2)
0x400559:48 89 c7 (movq %rax,%rdi)
0x40055c:e8 c2 ff ff ff (call myFunc1)
使用readelf -a phase3.o命令查看ELF数据,得到已编译程序的机器代码在
objdump -d phase3.o查看汇编代码
所以要注入的代码地址就是0x40+0x31=0x71
hexedit phase3.0修改
写入机器码
重新编译后查看汇编,确实写入了
现在输入学号到.data中,和之前步骤一样
用objdump -rd phase3.o查看到.data的偏移量还是0x7
用readelf -a phase3.o查看得到.data的地址是0x1A0
即0x1a0+0x7=0x1a7,hexedit phase3.o修改0x1a7后的值为学号,以”00”结尾
修改
重新编译链接运行得到输出学号
Phase4:
先还是先编译并运行一下,发现报错了,然后对比汇编,看到重定位被修改了开头的四个数值
readelf -a phase4.o,查看phase4.o文件ELF数据
查看重定位表,需要修改三个地方
要修改这里的偏移量,从这里可以看出,一个是变量g_myCharArray一个是变量temp还有一个puts函数且g_myCharArray是位于.data节中偏移量为0(即value值)处,temp是位于.data节中偏移量为0x14(即value值)处
用objdump -d phase4.o查看汇编代码
观察可得,偏移量是要使变量和函数到这里即0x6、0x11、0x19
0x18是call指令,所以它对应的是call puts函数。要把这里的值设置为19
Hexedit phase4.0.rela.text段的开始为250
经过我的分析①是偏移量,②是信息,③是符号名称+加数的加数
这个是“第一组”的,以此类推可以找到”puts组”的位置,”FC FF FF FF FF FF FF”是-4的补码也验证了我们的计算
所以puts-4就是修改前面的内容就可以修改他的偏移量
重新查看汇编文件对比发现改对了,会发现变成了call puts
现在还有两个偏移量要修改
这两个是数据,看下面是这两个变量的值
用上面同样方法查看和计算插入学号的地址,得到下图插入学号到.data中
现在就要改对应的偏移量,就是上面说的汇编中0x6和0x11处
0x11应该放学号,因为要赋值给%rdi传递给puts函数打印,0x6应该放temp
“.data+10”组偏移量,修改为6
“.data+0”组偏移量,修改为11
查看修改后的
重新编译链接就可以输出学号了
Phase5:
先编译链接运行后发现输出的是hahaha
然后查看汇编可知myFunc将函数是关键,主要做了一个判断,如果%rax为0则将0x60101040存入%edi并调用blankFunc函数;如果%rax为1则将0x60101030存入%edi并输出。
用gdb查看0x601030中保存的就是一开始输出的hahaha
本来想修改这来存储我们的学号拿来输出的,但是发现这数组的大小只有0x8,所以用看汇编,发现他还有用到一个数组的地址是0x601040,然后用gdb看来一下是g_myCharArray这个数组,查看他的elf发现是一个20字节的数组,可以用来存储我们的学号
从汇编中知道把%rax置为0就不会跳转,就得改变0x601038处的g_guard
可以看到.data是从0x90开始的
下图可以看到在g_myCharArray前面还有两个八字节的数组,所以往后退就可以知道输入学号的位置在哪里了
然后需要修改三个地方
- 将call指令调用的函数改成put函数(方法同上)
- 修改%rax的值为0不跳转
- 修改添加学号
保存后重新编译链接运行得到输出为学号