c++异常机制
c++中try catch
模块用于处理异常,程序会去执行try中的代码,如果try中没有throw一个对象,那么会跳过后续的catch,继续执行。如果throw了一个对象,程序会尝试往下去寻找对应的catch去接收这个对象,然后执行该catch中的代码
关于catch的机制,如果try中尝试进行函数嵌套使用,如果在子函数中进行了throw,那么他会中断当前运行去寻找相应的catch。
- 寻找catch首先会在当前函数下文去寻找,如果没找到就去母函数中的下文去寻找
- 如果程序没有处理该对象的对应的catch,程序直接退出
详细过程
栈恢复
正如上文中提到,寻找catch会去母函数中寻找,当前代码不在继续执行。这时程序会进行栈的恢复。
由于rbp寄存器的特性会去保存函数调用栈地址,因此对栈的恢复本质就是对rbp的恢复。
栈的恢复过程简单来说如下
rbp<--address1<--address2<--data
=>>rbp<--address2<--data
回收栈空间
程序会调用cleanup去回收栈空间
这里是编译器加上的代码,在c++中并没有直接的显示对clean的调用。
这里还包含主动调用析构函数去回收定义在该函数中的对象
寻找catch块
上图的cleanup()中的_Unwind_Resume
会去栈空间上去寻找catch块。漏洞点的利用也是出现在这里
若有两个catch(class A)
如果程序throw一个A,那么默认情况下会去向下去寻找对应的catch,然后恢复栈。
如果控制了ret,到另外一个catch中,那么执行完throw后会去执行另一个catch中的代码,而不是本身要去执行的代码。
eg 伪代码
1 | try{ |
在执行完vuln后理论上回去执行#2catch中的代码,如果劫持了vuln中的ret就会去执行#1中的catch代码。
这里的ret的地址一般为#1catch对应try结束位置+1
如图,如果想要去执行0x401299中的catch,那么ret的地址要设置成 0x401292+1。(这里尝试+1+2+3均可,但大多数都是+1)
注意,跳转的catch所接收的数据类型必须要一样
实例
1 | // exception.cpp |
开启canary,关闭PIE
ida与原码对比
throw
会先分配一个exception,然后再把exception对应到要throw的类,最后再抛出
而在原码中显示为throw "Buffer overflow"
同样的,throw 1也是对应着三行
try_catch
catch部分在ida中的伪代码是不显示的,因为他默认程序不会触发throw,而在汇编部分则会清楚的显示
其次关于try,如main中的try,在ida的main中会显示一遍,在input中还会再显示一遍,实则两个try对应着一个
代码顺序
如上文,原码的顺序是try catch 其他代码。而编译器会把catch的部分放在函数最后
catch在汇编中不是catch,而是
1 | .text:0000000000401461 cmp rdx, 1 |
去寻找对应的代码
编译器在catch中也加入try和cleanup来回收资源
具体执行
首先在input函数中有一个明显的栈溢出漏洞,利用该漏洞覆盖ret的地址为backdoor的try末尾地址+1即可
注意由于要恢复栈,因此rbp的地址要设置成一个前后可写的位置,否则就会段错误
exp如下
1 | from pwn import* |
reference
[原创]分享一次 C++ PWN 出题经历——深入研究异常处理机制-Pwn-看雪-安全社区|安全招聘|kanxue.com