羊城杯2024
pstack
1 |
|
TravelGraph
1 |
|
logger
异常抛出(throw)后会去捕获catch来执行异常处理,
- 但是当当前层没有的时候就会一直逆到外层去捕获catch,直到走完整个调用链,然后程序abort。
- 如果捕获到catch就会执行catch块中的代码
整个过程被叫做栈展开( stack unwind),分为两部分:
- 异常后,对调用链一直捕获catch
- 如果没有找到catch,程序abort,如果找到会记下当前位置再重新抛回到异常处,然后清理调用链上的所有局部变量,直到catch处
Itanium C++ ABI
Itanium ABI 定义了一系列函数及相应的数据结构来建立整个异常处理的流程及框架
1 |
|
_Unwind_RaiseException都会调用personality_routine(__gxx_personality_v0) ,两个合并完成的工作:
- _Unwind_RaiseException:会在内部把当前函数栈调用重建,然后传给personality_routine
- personality_routine:1、检查是否有catch;2、清理调用的栈上局部变量
当我们在程序里执行了抛出异常后,编译器为我们做了如下的事情:
- 调用 __cxa_allocate_exception 函数,分配一个异常对象。
- 调用 __cxa_throw 函数,这个函数会将异常对象做一些初始化。
- __cxa_throw() 调用 Itanium ABI 里的 _Unwind_RaiseException() 从而开始 unwind。
- _Unwind_RaiseException() 对调用链上的函数进行 unwind 时,调用 personality routine。
- 该异常如能被处理(有相应的 catch),则 personality routine 会依次对调用链上的函数进行清理。
- _Unwind_RaiseException() 将控制权转到相应的catch代码。
- unwind 完成,用户代码继续执行。
分析catch捕获后的一个执行代码
首先了解一下DWARF
这是以一种统一的风格来适应多种语言符号化的源码级调试需求的调试信息格式
1 |
|
DW_CFA_def_cfa_expression很长
DW_CFA_def_cfa_expression就是DWARF调试信息格式中的一部分,特别是描述栈帧的恢复和计算方式。它包含了几个操作,表示了一种用于调试的栈帧指针(CFA,Canonical Frame Address)定义,使用的是一组DWARF操作码。
1 |
|
C++异常处理攻击手法
异常流程
C++一个标准的抛出异常函数通过**_cxa_allocateexception来完成初始化,然后通过**__cxa_throw
来完成抛出
最关键的函数,_Unwind_RaiseException
栈展开和catch捕获的一个实现
如果捕获到会直接返回到catch块中,捕获完毕交给_Unwind_Resume,否则弹到stderr然后abort
进入catch块中,通过_Unwind_Resume恢复程序的正常执行流程,在调用栈中转移控制权,确保程序能从异常发生的地方继续执行
执行完成,通过__cxa_begin_catch
初始化与捕获异常相关的上下文执行
__cxa_end_catch
用于处理异常捕获结束后的清理工作,执行完成直接交给原有程序继续执行,发现本来的stack_fail检查没有了
如果这个时候存在栈溢出,通过栈溢出+异常触发就能绕过canary的检查,并且程序正常执行
利用
如果通过溢出覆盖了rbp,也能正常执行吗,是可以进行的,栈展开的时候是通过在内部把当前函数栈调用重建,然后通过回抛方式一层一层往上捕获的过程,不以当前rbp做操作
通过_Unwind_RaiseException
依然能够捕获到catch块,并且rbp被覆盖
再次通过_Unwind_Resume
恢复后rbp变为覆盖的地址,并且程序正常执行
如果后面程序执行函数返回leave;ret;我们能直接完成栈迁移控制rip
logger
盖rbp的时候,返回地址0x401a37
,是try块下面的一个地址
指向的这个地址又是一个jmp,jmp后面的可以不用管,注意这里IDA识别catch块
当触发异常的时候会通过_Unwind_Resume
一层一层抛上去
当在Warn
函数触发异常的时候,throw先去捕获Warn
的catch
然后会调转指向_Unwind_Resume
现在还是再Warn函数中,还没有抛到上一层,通过_Unwind_Resume
回抛到上一级main
函数
main
函数中的catch块,然后再次通过_Unwind_Resume
去恢复到原来mian函数的执行上下文
当覆盖返回地址的时候,如果覆盖地址为其他的try捕获,按照的栈展开回抛机制,程序会被回抛到,try对应的catch块中
把返回地址改为0x401BC2+1(执行特性,下一条执行地址不能当前地址一样)
(存在system对一个全局变量的执行,改全局变量开可以使用Trace
函数中存在的循环溢出来进行修改为/bin/sh
)
exp
1 |
|
hard+sandbox
1 |
|