pwn | Exploiting DWARF | 异常处理的DWARF相关利用姿势
pwn | Exploiting DWARF | 异常处理的DWARF相关利用姿势
突然想到了,就深入研究一下这块儿。
丢一个原文PPT,是2011年国外的研究:https://cs.dartmouth.edu/~sergey/battleaxe/hackito_2011_oakley_bratus.pdf
https://cs.dartmouth.edu/~sergey/battleaxe/
边学边记录的,写的比较乱,翻译的不对的地方还希望指正。
同时的参考文章还有:
https://bbs.kanxue.com/thread-271891.htm
https://zhuanlan.zhihu.com/p/302726082 (这个是中文的,对异常处理栈&eh_frame讲的很清晰的一个文章)
总述
所有的GCC编译的二进制都支持使用DWARF进行异常处理,二进制文件中会有一个DWARF字节码解释器,且几乎可以执行所有指令。
DWARF的主要作用:
- 描述栈帧
- 在异常发生时解释执行,恢复栈帧
通过这种方式可以执行代码,并且不会被杀毒软件检测。
题外话,PPT真的很有意思(所以推荐直接看ppt)【矮人->黄金哈哈哈哈哈哈】:

ELF and DWARF
DWARF(Debugging With Attributed Records Format)
http://dwarfstd.org/
ELF调试相关的节,都使用了DWARF格式的信息。

而gcc以及x86_64的ABI使用了.eh_frame段去描述在异常发生时如何恢复栈帧。
eh_frame
从概念上来说,这个段代表了一个表格,对于每个text段中的地址而言如何去设置寄存器来保存前一个call的栈帧。

概念解释:
CFA(Canonical Frame Address): 规范栈帧地址。Address other address within the call frame can be relative to.
它的值是在执行(不是执行完)当前函数(callee)的caller的call指令时的RSP值, 例子如下:
caller:
push arg1 --> RSP = 0xFFF8
push arg2 --> RSP = 0xFFF0 (执行call指令时的RSP值在这
call callee --> RSP = 0xFFE8
callee:
push rbp --> CFA = 0xFFF0
在上面的表格中,每一行表示了当前的代码位置如何回溯到之前的栈帧。
如果是按上面的表格来描述的话,会有一个非常大的表格,并不方便,因此才有了DWARF/eh_frame来进行数据的压缩。
接下来是eh_frame的两个重要数据结构。
每个.eh_frame section 包含一个或多个CFI(Call Frame Information)记录,记录的条目数量由.eh_frame 段大小决定。每条CFI记录包含一个CIE(Common Information Entry Record)记录,每个CIE包含一个或者多个FDE(Frame Description Entry)记录:

通常情况下,CIE对应一个文件,FDE对应一个函数。
可以注意到,FDE中存在一个CIE_pointer,指向一个CIE结构。
也就是在FDE中,存在DWARF字节码。
DWARF字节码
DWARF执行的基础是一个栈虚拟机。
它的执行方式就像是一个RPN calculator(逆波兰式计算)
可以访问内存以及修改寄存器。
PPT中也提到了一些使用限制,比如说gcc(4.5.2)版本限制栈空间在64字以下。
有一下几个指令示例:

有一些关键数据结构(还是上面提到的CIE和FDE):

可以用如下指令读取elf中的.eh_frame段(test是我编译的二进制文件名):

也可以用readelf -wF查看相关的信息:

这个eh_frame意味着什么?
本质山意味着,在抛出异常的时候,可以回溯到上一个栈帧,将所有用到的寄存器给还原回去,这也是异常处理的目的(我想应该是这样子的OvO)
PPT中提到了可以使用katana工具来编辑dwarf字节码:


我们可以通过上面的工具来修改CIE和FDE的结构,以及DWARF字节码。
.gcc_except_table
另一个关键的段就是.gcc_except_table。
我在有一篇文章中看到了一个形象的描述:要知道着陆垫在哪里,要使用称为gcc_except_table的东西。 https://blog.csdn.net/wuhui_gdnt/article/details/88737310
Exception Handling Flow 异常处理流
给了一张非常详细的图:

并提出了两个问题:
- libgcc是怎么知道要如何unwind的(恢复现场)
- 一个异常处理是怎么被识别的?
【之后补一个实验,主要是还没搞太清楚,所以没办法弄呜呜呜太菜了】
如果有问题可以在下方评论或者email:mzi_mzi@163.com