CSAPP Attack Lab 笔记
Phase 0x1
一个练手的,要求通过缓冲区栈溢出攻击来调用 touch1
gdb ctarget
然后 set args -q
防止与不存在的服务器通信(run -q
也可以)
b Gets
run -q
看一下栈信息:
1 | ────────────────────────[ STACK ]──────────────────────── |
rsp 是当前栈顶指针,由于是在 Gets 的开头查看的,所以栈顶存的就是 Gets 的返回地址
思路应该是覆盖栈顶指针 0x5561dc70
处的 正确返回地址为
touch1
的地址0x4017c0
如何覆盖?看一下读入字符串的逻辑:
_IO_getc@plt
很明显是库函数 getchar()
那附近是一个循环,如果是 -1 (0x7ffffffff
) 就跳出,
如果不是 0xA (经查表这个就是 ASCII 的行分隔符)就读入下一个 char
那就看一下读入单个字符的逻辑
$rbx = $rdi, 推测 $rdi 作为 Gets()
的第一个参数就是存到的字符串地址
也就是说 (%rdi) 应该就是我们输入的字符串存储地址的第一位
当然这里因为要调用 _IO_getc
并且 rdi
是调用者保存寄存器
所以先用 rbx 存一下 rdi
看了下 Gets 的 $rdi 存的是 0x5561dc78
发现其实缓冲区不在 Gets() 的栈帧里(78 > 70, 栈内存向低地址增长)…
重新观察 getbuf
的汇编代码,发现
sub $0x28, %rsp
,分配了 40 字节的缓冲区
由于缓冲区栈溢出攻击的原理大概是这样:
1 | // 注意这个栈是从高地址向低地址增加的 |
所以只能向相对缓冲区更高的地址溢出,于是换个思路,在 getbuf 处 ret 到
touch1()
所以就不用考虑 Gets 的细节了,一共 40 字节的 buffer, 直接填充 40 个
00
,
然后填充 c0 17 40
即可(gdb 中在 getbuf 起始处看 %rsp
也能发现就是 0x5561dca0 = 0x5561dc78 + 0x28
)
1 | 00 00 00 00 00 00 00 00 |
一个地址对应的通常是一字节,而一个十六进制的每个位数是 4 个二进制位 (1111=15=f)
两个十六进制位就是一字节。
由于只有网络协议相关会用大端法,所以这里是小端法,地址小的位置存的是低位
至于 hex2raw
(Hexadecimal to Raw), 其实就是
echo -n -e "\xc0\x17\x40"
Phase 0x2
要求将 cookie.txt
作为参数传递给 touch2()
并调用
至此调用函数我们已经非常熟悉了,直接填充对应函数的地址到溢出后的位置即可。
但是我们还需要传递参数,所以就不能再填充 00
了。
这个 ctarget
编译时是关闭栈保护的,也就是说栈内存上的代码可以直接被执行。
编写 phase2_inject.asm
:
1 | mov $0x59b997fa, %rdi |
其中,0x59b997fa
是 cookie,0x4017ec
是
touch2()
的地址。
当然这个是 AT&T 的语法,nasm
默认似乎是 Intel,所以我用
as
1 | as phase2_inject.asm |
于是得到了我们的 inraw.txt
1 | 48 c7 c7 fa 97 b9 59 90 |
这里 90 是 nop
, 用于填充的,不过似乎不需要?
返回地址也直接覆盖为 0x5561dc78
,
缓冲区开始的地方,也就是我们注入代码开始的地方
还是要注意小端法。
(用时最短的一个,第一个做完第二个就轻车熟路了)
Phase 0x3
查看 attack.pdf
的要求,发现主要是把 cookie
十六进制对应的字符串 ( \(without\ a\ leading\
\) 0x
), 然后用 hexmatch
比较是否相等。
但是这个 hexmatch
写得太折磨了。
1 | int hexmatch(unsigned val, char * sval) { |
这个 110 大小的缓冲区 cbuf
会把我们 inject code 和
cookie string 所在的缓冲区弄得一团糟,而且 random
的存在导致我们不能直接填充这个地址。
解决方案有点取巧。在 gdb 中打断点,分别在 touch3()
起始和hexmatch()
后检查缓冲区 x/100bx 0x556178
,找到前后没变并且看起来没什么用的区域填充 cookie。
1 | 48 c7 c7 b3 dc 61 55 90 |
这里 0x5561dcb3
及之后是 cookie。 (注意结尾
\0
)
1 | .. .. .. 35 39 62 39 39 |
完美通过。
1 | amiriox@makinohara ‹ master ●● › : ~/csapplab/attacklab |
Phase 0x4
attack.pdf
中介绍了关于 ROP
的相关知识.
因此推测思路是,通过缓冲区溢出攻击在栈顶放 cookie,
通过 popq %rdi
恢复到 %rdi 寄存器以传参,
然后 call 0x4017ec
(touch2()
).
但是 gadgets farm 中没有出现 call 的 gadget, 所以只能用 ret
转移到 touch2()
,
这样就要保证 popq
弹栈恢复 %rdi 之后的栈顶是
0x4017ec
.
popq %rdi
的机器码是5f
ret
的机器码是c3
- 需要用
nop
即90
对齐
有两个问题需要考虑: 1. 前几个 phase
是上周做的了,这几天一直在复习高数,所以栈的布局还得再研究一下 2.
怎么执行到第一个 gadget ? 第一个 gadget ret
后,会通过 ret
弹出栈顶的地址并跳转到那个地址实现链式调用.
solve: 1. 返回地址在栈上的位置是 0x5561dca0
=
0x5561dc78
+ 0x28
2. 第一个 gadget 大概直接在
0x5561dca0
写地址就行,栈保护是没事的因为 gadget
不在栈内存,但是地址随机化不好说。
紧接着发现 gadget farm 里没有 5f
, 感觉很不解,
这么精妙的做法, 这 lab 不这么解说明教授水平不行啊。
attacklab.pdf
说只需要两条指令就能做 phase 4, 但是调用
touch2 的 ret 肯定需要一条, 剩下的除了 popq
还能是什么?
谔谔, 卡了半天决定去看 Arthals
在北大计算机系统导论(ICS)课程中所做的详尽笔记
这个 lab 是北京大学改过的(甚至还买了 csapp 的版权, 9
爷吃的就是好啊)。
然后发现他们也没给 5f
, 那我估计就是先 pop
再
mov
, ugly.
重新审阅 rtarget.asm
中 start_farm
到
mid_farm
之间的机器码, 发现有 58
即
popq %rax
则我们需要
1 | popq %rax; ret # 58 90 c3 at 0x4019cc |
填充 inraw.txt
, 当然传递给 hex2raw
的是不能有注释的.
1 | 00 00 00 00 00 00 00 00 |
栈中的情况和具体执行流程:
1 | +---------------------------+ 高地址 |
其实这里直接过了我不太理解的, 为什么不需要保持 rsp 16
位对齐啊?
这里没有发生段错误而是直接栈对齐了是因为恰好两次 ret, 每次 +8,
然后调用 touch2 时满足了 rsp 16 位对齐。
说是普通的运算不需要一直保持 rsp % 16 == 0
,
只需要在函数调用时满足就可以了, 具体还有待后续学习.
1 | amiriox@makinohara ‹ master ●● › : ~/csapplab/attacklab |