LinkLab实验(阶段1-5)
官网:http://csapp.cs.cmu.edu/3e/labs.html
实验内容
每个实验阶段(共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:各阶段实验所针对的二进制可重定位目标模块,需在相应实验阶段中予以修改。
解压命令:tar xvf linklab学号.tar - 实验工具
readelf:读取ELF格式的各.o二进制模块文件中的各类信息,如节(节名、偏移量及其中数据等)、符号表、字符串表、重定位记录等
objdump:反汇编代码节中指令并提供上述部分类似功能
hexedit:编辑二进制文件内容
实验过程
目标:修改目标文件,使其输出学号。
阶段 1
-
使用 readelf 查看 phase1.o,查找有关输出函数的内容
执行
readelf -a phase1.o
我们可以看出 puts 函数的参数是 g_data ,其重定向类型是绝对地址( R_X86_64_32 ),其加数为 0x50 。我们只需要将 g_data 的内容修改为学号字符串,即可完成阶段一。 -
查看 g_data 符号所在节
OBJECT 说明 g_data 是一个全局变量,所以它应该在数据节 .data 中。
-
查看数据节在全文中的偏移量
所以 .data 节在全文的偏移量为 0x60
-
计算 g_data 在全文中的具体位置
根据计算公式
可得 g_data 所在位置为 0x60+0x50=0xb0
-
使用 hexedit 修改 g_data 为学号并保存
-
链接并查看输出结果
执行
gcc -o linkbomb1 main.o phase1.o -no-pie
执行
./linkbomb1
成功输出学号。
阶段 2
-
使用 readelf 查看 phase2.o,查找有关输出函数的内容
从中可以看出 put 函数的参数是 g_myCharArray ,其加数为 0x50 ,重定向类型为绝对地址。
-
查看符号表
COM 表示 g_myCharArray 是一个未初始化的弱符号数组,其大小为 256 ,所以我们需要创建一个已初始化强符号的 g_myCharArray 来覆盖弱符号。
-
创建 phase2_match.c 文件
加数为 0x50 字节需要填充。
-
编译 phase2_match.c 并链接,运行程序
编译:运行
gcc -c phase2_match.c
生成 phase2_match.o 目标文件。链接:运行
gcc -o linkbomb2 main.o phase2.o phase2_match.o -no-pie
运行
./linkbomb2
发现每个字符都被偏移了。为了方便我们编写一个程序来计算应该输入的结果,可手动计算偏移量得出结果。
-
编写 crack.c 文件计算反偏移的字符串
-
修改 phase2_match.c 中的学号为基准值
-
编译 crack.c ,链接 linkbomb2,将 linkbomb2 的结果导出为 out,使用 out 运行 crack。
编译 crack.c :运行
gcc crack.c -o crack
编译 phase2_match.c :运行
gcc -c phase2_match.c
链接 linkbomb2 :运行
gcc -o linkbomb2 main.o phase2.o phase2_match.o -no-pie
运行 linkbomb2 并将结果导出为 out :
./linkbomb2 > out
运行 crack 程序 :
./crack < out
-
将结果覆盖 phase2_match.c 中的字符串
-
编译链接运行 linkbomb2
编译 phase2_match.c :运行
gcc -c phase2_match.c
链接 linkbomb2 :运行
gcc -o linkbomb2 main.o phase2.o phase2_match.o -no-pie
运行 linkbomb2 :运行
./linkbomb2
成功输出学号。
阶段 3
-
链接 linkbomb3 了解程序运行流程
执行:
gcc -o linkbomb3 main.o phase3.o -no-pie
使用 gdb 进行调试
发现程序流程并没有执行 puts 函数,而是执行了一个叫 do_phase3 的函数,所以我们需要查找可利用的代码,来完善 do_phase3 函数。
-
查看 linkbomb3 中可利用的代码
使用
objdump -d linkbomb3
查看:
从中看出,myFunc2 函数的作用是从内存中取出某值放到 %rax 中,myFunc1 函数的作用是以 %rdi 为参数执行 puts 函数。所以我们的目标是在 do_phase3 中 call myFunc2 输出我们的学号。而我们可以修改的值在 myFunc1 ,所以我们的流程为:在 do_phase3 中按顺序写下
call myFunc2 mov %rax,%rdi call myFunc1
然后找到 myFunc2 中的内存位置修改为学号。
-
查找源代码文本位置
使用readelf -a phase3.o
查看 .text 节的偏移量
使用objdump -d phase3.o
查看 do_phase 填充代码地址
所以 90 填充的起始地址是 0x40 + 0x31 = 0x71。 -
构造写入代码
我们要构造的代码为call myFunc2 mov %rax,%rdi call myFunc1
其中 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。
第二条指令为 48 89 c7
第三条指令为 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 节+ 0x50 的位置获取的。查看 .data 节的起始地址:
所以我们需要修改为学号的位置为 0x1a0 + 0x50 = 0x1f0 。 -
修改 phase3.o
-
链接并运行
执行gcc -o linkbomb3 main.o phase3.o -no-pie
成功输出学号。
阶段 4
-
查看 phase4.o 的 elf 文件,查看异常块
发现 .text 的重定向节符号的偏移量都为 0 ,很明显不会出现多个符号覆盖同一地址的操作。所以这三个符号的偏移量缺失了。 -
查看 phase4.o 的汇编代码,查看留空位置。
执行objdump -d phase4.o
按文本位置,从上到下偏移量为 0x4 + 0x2 = 0x6 ,0xf + 0x2 = 0x11 ,0x18 + 0x1 = 0x19 。 -
查看重定向 .text 节初始地址,修改偏移量。
所以 .rela.text 的初始地址为 0x250。修改偏移量:
-
查看修改
成功修改。 -
修改 puts 函数的参数为学号
按文本顺序 .data + 0x0 的位置即参数位置。查看 .data 节的起始地址:
修改 phase4.o 文件 -
链接并运行程序
执行
gcc -o linkbomb4 main.o phase4.o -no-pie
发现输出不全。我们使用 gdb 进行调试
发现 temp 的值为 0x4d 。并非 .data + 0x10 位置上的值。我们查看符号表:
发现 temp 的偏移量应该为 0x14 。 -
修改 phase4.o 将 .data + 0x14 位置的值修改为 0x00 。
-
重新链接并运行
执行
gcc -o linkbomb4 main.o phase4.o -no-pie
成功输出学号。
阶段 5
-
链接并调试 linkbomb5
执行gcc -o linkbomb5 main.o phase5.o -no-pie
查看值:
发现这里是一个假的字符串,我们查看上面 0x404030 地址的值:
发现这才是我们需要字符串。 -
查看两个数据的重定向信息
我们将两个偏移量交换。从上图可知重定向的初始地址为 0x340 。 -
修改 phase5.o 文件
-
重新链接并运行
执行
gcc -o linkbomb5 main.o phase5.o -no-pie
-
查找数据地址,修改内容为学号
修改过后我们需要的字符串为 .data + 0x10 的位置,查看 .data 节初始地址:
所以数据存放位置为 0x90 + 0x10 = 0xa0。 -
修改 phase5.o 文件
-
链接并运行程序
执行
gcc -o linkbomb5 main.o phase5.o -no-pie
成功输出学号。