Write-Flag-Where
Writeup for WRITE-FLAG-WHERE (Pwn) - Google CTF (2023) ๐
Description
In order to solve it will take skills of your own An excellent primitive you get for free Choose an address and I will write what I see But the author is cursed or perhaps it's just out of spite For the flag that you seek is the thing you will write ASLR isn't the challenge so I'll tell you what I'll give you my mappings so that you'll have a shot.
Recon
First use pwninit to patch the binary with local libc (the supplied libc.so.6 wasn't enough, I had to copy ld-linux-x86-64.so.2 from a local backup of GLIBC_2.34, which is pretty standard for CTFs these days).
ldd chal
linux-vdso.so.1 (0x00007fff98fb4000)
libc.so.6 => ./libc.so.6 (0x00007f1de5200000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1de557b000)file shows the binary isn't stripped, which will make [[#Static Analysis]] easier.
file chal
chal: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter ./ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=325b22ba12d76ae327d8eb123e929cece1743e1e, not strippedCheck binary protections with checksec.
checksec --file chal
[*] '/home/crystal/Desktop/chall/chal'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'So, we don't need to worry about canaries, but we can't execute shellcode on the stack. Furthermore, PIE is enabled, so addresses won't be fixed.
Running the binary prints flag.txt not found.
After creating flag.txt, the binary exits immediately, with no output.
We could use ltrace to get a better understanding of what's happening.
OK, so the program..
reads 4096 bytes from
/proc/self/mapsinto file descriptor (fd) 3reads the flag into
fd3tries to duplicate
fd1 to 1337opens
/dev/nullasfd3tries to duplicate
fd3 to 0 (stdin)tries to duplicate
fd3 to (stdout)tries to duplicate
fd3 to 2 (stderr)3 x
dprintfcalls (print format string), but use an invalidfdof -1
dprintf is similar to printf, but it allows you to specify a file descriptor as the output stream. It writes the formatted output to the specified file descriptor instead of the standard output.
I guess it's time to take a look at the code! Let's open it in ghidra ๐
Static Analysis
I pasted the main() function into chatGPT and asked it to rename variables and make it more readable, here's what it gave me.
We quickly realise the program should print some output. When connecting to the remote server, it does so as intended. A teammate later informed me of a fix for this ulimit -n 1338 will increase the maximum number of open file descriptors for the current shell session to 1338 (remember, the program sets the output to fd 1337).
Anyway, the while loop at the bottom is interesting. It takes a user-supplied address and length, then reads the flag to that address.
What address might we choose to write the flag to? How about the string that is printed at the beginning of each loop?
It's in the .data section of the binary, at an offset of 0x21e0.
The program has PIE enabled, so each time it's run, the binary will have a new base address.
We need to find the base, then add the offset. Luckily, running the program against the remote server will print out the memory mappings, e.g.
Therefore, we'll write a script to parse this response and calculate the correct address. We'll send that address when requested and the next time the loop executes, it will print the newly written data (๐ฉ).
Solve Script
We run the script and get the flag.
Flag:CTF{Y0ur_j0urn3y_is_0n1y_ju5t_b39innin9}
Last updated