一、实验目的
- 了解链接的基本概念和链接过程所要完成的任务。
- 理解ELF目标代码和目标代码文件的基本概念和基本构成
- 了解ELF可重定位目标文件和可执行目标文件的差别。
- 理解符号表中包含的全局符号、外部符号和本地符号的定义。
- 理解符号解析的目的和功能以及进行符号解析的过程。
二、实验仪器设备/实验环境
Linux Ubuntu 18.04 64-bit
笔记本电脑
VMware虚拟机
三、实验内容
每个实验阶段(共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
$ 23215150438
实验结果:将修改后正确完成相应功能的各阶段模块(phase1.o, phase2.o, …)提交供评分。
四、实验步骤
1. 实验数据
学生实验数据包: linklab学号.tar
数据包中包含下面文件:
main.o:主程序的二进制可重定位目标模块(实验中无需修改)
phase1.o, phase2.o, phase3.o, phase4.o, phase5.o:各阶段实验所针对的二进制可重定位目标模块,需在相应实验阶段中予以修改。
2. 实验工具
readelf:读取ELF格式的各.o二进制模块文件中的各类信息,如节(节名、偏移量及其中数据等)、符号表、字符串表、重定位记录等
objdump:反汇编代码节中指令并提供上述部分类似功能
hexedit:编辑二进制文件内容
五、实验阶段
1、phase1
1、执行 readelf -a phase1.o,查找有关的输出函数的内容
puts 函数的参数是 g_data ,其重定向类型是绝对地址( R_X86_64_32 ),其加数为 0x11 。我们只需要将 g_data 的内容修改为自己的学号字符串就可以了
再往下看:查看 g_data 符号所在节GLOBAL可以得出是全局变量,所以我们就去看它的.data
得出要看.data,查看数据节在全文中的偏移量,所以 .data 节的偏移量为 0x60
然后我们再加上前面0x11;
得出g_data 在全文中的具体位置:0x60+0x11=0x71;
再将学号转换为对应的ascii码值:
如:我的学号是23215150438 对应的是32 33 32 31 35 31 35 30 34 33 38
得出数据后:
用hexedit phase1.o命令来修改phase1.o,并对phase1.o数据节中相应字节进行修改
光标停在要修改的数字,输入要修改的数字即可;修改完成后使用快捷键ctrl + w实现保存操作,随后通过快捷键ctrl + x实现退出操作
链接并查看输出结果
执行 gcc -o linkbomb main.o phase1.o -no-pie
执行 ./linkbomb
如果正确则会输出正确的学号
2、phase2
输入命令:readelf -a phase2.o,查看函数相关的输出内容
put 函数的参数是 g_myCharArray ,其加数为 0x11
接着往下看:
COM 表示 g_myCharArray 是一个未初始化的弱符号数组,其大小为 256 ,所以我们需要创建一个已初始化强符号的 g_myCharArray 来覆盖弱符号。
然后我们创建
phase2_patch.c文件并写入0x11个字节的“0”以及学号ASCII码。
0x32,0x33,0x32,0x31,0x35,0x31,0x35,0x30,0x34,0x33,0x38
保存运行下面三条命令:
gcc -c phase2_patch.c
gcc -o linkbomb2 main.o phase2.o phase2_patch.o -no-pie
./linkbomb2
编译出来后发现每个字符都被偏移了。
然后为了方便计算输入的结果,我们编写一个程序来手动计算偏移量得出的结果
hack.c
然后运行:gcc hack.c -o hack
gcc -o linkbomb2 main.o phase2.o phase2_patch.o -no-pie
./linkbomb2 > out
./hack < out
输入完后就会出现如下:
接着我们修改phase2_patch.c文件的内容
接着运行我们的命令即可编译通过。
gcc -c phase2_patch.c
gcc -o linkbomb2 main.o phase2.o phase2_patch.o -no-pie
./linkbomb2
3、phase3
首先先链接 linkbomb3 执行:gcc -o linkbomb3 main.o phase3.o -no-pie
使用 objdump -d linkbomb3 查看:
从中看出,就是先调用myFunc2函数获取学号赋值给%rax,然后mov %rax,%rdi设置参数在调用myFunc1
所以我们注入的命令顺序为:
1、call myFunc2
2、Mov %rax,%rdi
3、Call myFunc1
所以我们找到 myFunc2 中的内存位置修改为学号。
使用 readelf -a phase3.o 查看 .text 节的偏移量:为0x40
然后再使用 objdump -d phase3.o 查看 do_phase 填充代码地址
所以 填充的起始地址是 0x40 + 0x31 = 0x71。
其中 call 指令是相对寻址,myFunc 函数的地址为
第一条指令call指令对应的机器码是e8 xx xx xx xx ,占 5 个字节,结束地址为 0x31 + 0x5 = 0x36,所以距离 myFunc2 函数地址的相对距离为 0x1b - 0x36 = e5 ff ff ff,
所以第一条 call 指令为 e8 e5 ff ff ff。
第二条指令mov %rax,%rdi,mov %rax,%rdi的机器码是为 48 89 c7
第三条指令为call指令对应的机器码是e8 xx xx xx xx ,占 5 个字节,加上第二条指令的 3 个字节,所以结束地址为 0x36 + 0x3 + 0x5 = 0x3e ,所以距离 myFunc1 函数地址的相对距离为 0x0 - 0x3e = c2 ff ff ff ,所以第三条指令为 e8 c2 ff ff ff 。
修改 phase3.o 的二进制,将构造的代码注入
然后执行 hexedit phase3.o 从 0x71 开始写入我们构造的代码
接着来查看是否修改
执行 objdump -d phase3.o查看 do_phase3 函数:发现修改成功。
查看全局符号,查找 myFunc2 获取的数据的内存地址
执行 readelf -a phase3.o 查看重定位符号:data+11
查看 .data 节的起始地址:
所以我们需要修改为学号的位置为 0x1a0 + 0x11= 0x1b1 。
执行 hexedit phase3.o 从0x1b1修改成我们的学号:32 33 32 31 35 31 35 30 34 33 38
链接并运行
执行 gcc -o linkbomb3 main.o phase3.o -no-pie
./linkbomb3
最后可以看到我们成功输出学号了。
4、phase4
输入:readelf -a phase4.o,以此来查看我们 phase4.o 的 elf 文件,然后发现了异常块:
发现我们的.text的重定向节符号的偏移量都为0,这明显就不正常,所以我们就要从这里下手。
接着我们再查看 phase4.o 的汇编代码,查看留空位置。
执行 objdump -d phase4.o
按文本位置,我们可以得出偏移量为: 0x6; 0x11; 0x19
查看重定向 .text 节初始地址,修改偏移量。
所以 .rela.text 的初始地址为 0x250。修改偏移量:
执行 hexedit phase4.o 从 0x250修改偏移量;
查看修改结果,看到修改成为了我们想要的偏移量了,
接着修改我们的学号
按文本顺序 .data + 0x0 的位置即参数位置。查看 .data 节的起始地址:
执行 hexedit phase4.o 从0x60修改成我们的学号:32 33 32 31 35 31 35 30 34 33 38
最后链接并运行程序
执行gcc -o linkbomb4 main.o phase4.o -no-pie
./linkbomb4
然后我们发现学号输出不全,缺少了2。
然后我们就去查看符号表:发现发现 temp 的偏移量应该为 0x14
修改 phase4.o 将 .data + 0x14 位置(也就是0x74的位置)的值修改为 0x00 。
最后重新链接并运行,即可输出成功我们的学号了。
5、phase5
链接并调试 linkbomb5
执行 gcc -o linkbomb5 main.o phase5.o -no-pie
查看汇编代码:objdump -d phase5.o
进行gdb调试我们的linkbomb5:
执行:gdb ./linkbomb5
设置断点:b do_phase
运行:r
打开调试窗口layout asm,layout regs
然后设置断点进入到我们的myFunc;输入命令:b myFunc
查看发现g_guard等于1
然后我们输出一下:x/s 0x601030,下面会走到一个假的数组,这并不是我们想要的
接着再输出:x/s 0x601040,然后就出现了我们想要的字符
接着查看两个数据的重定向信息执行 readelf -a phase5.o
即我们将两个偏移量交换。从上图可知重定向的初始地址为 0x340
执行 hexedit phase5.o 修改 phase5.o 文件
重新链接并运行
执行 gcc -o linkbomb5 main.o phase5.o -no-pie
readelf -a phase5.o查找数据地址,修改内容为学号:32 33 32 31 35 31 35 30 34 33 38
修改过后我们需要的字符串为 .data + 0x10 的位置,查看 .data 节初始地址:
所以数据存放位置为 0x90 + 0x10 = 0xa0。
修改 phase5.o 文件
链接并运行程序
执行 gcc -o linkbomb5 main.o phase5.o -no-pie
成功输出学号。
六、实验总结
对于课程的内容有了进一步的加深,但是对于一些指令还是不够熟悉,今后会更加认真的进行进一步的系统学习。