Writeup – ESPR, pdfmaker (33C3 Part 1 of 2)

During the last 2 days, I participated in the 33c3 CTF. This event was a 48-hour Jeopardy style CTF, held as part of the 33rd Chaos Communications Congress, by the CTF team Eat Sleep Pwn Repeat. I will now document my solutions to some of the challenges I solved during this event for future reference.

Due to the length of the solutions, I will split this post into two parts. This post will deal with pdfmaker (pdflatex escape) and ESPR (blind format string).


This challenge was presented as an archive containing a Python file, which you can download here, which was executing on a remote server. On initial inspection, this allowed us the ability to write several types of file, read files (with certain extensions), and compile files with “pdflatex”. Our goal was to read a flag file.

On encountering this challenge, I recalled a similar challenge during the Internetwache CTF earlier this year, but realized I had forgotten to keep my notes.

This challenge is traditionally (and most easily) solved by use of the \write18 latex primitive, which writes to system(). For example:

\immediate\write18{pwd > scriptoutput.tex}

In this challenge, \write18 had been limited: that is, you could only execute a limited number of commands (which were built in to pdflatex). Fortunately, it was possible to use this trick to escape this constraint, and execute any command via “mpost”. Therefore, this challenge could be completed by creating the following files, causing the mpost executable to execute our arbitrary code, and effect a pdflatex escape / command exec:


\immediate\write18{mpost -ini "-tex=bash -c ($COMMAND)>5.tex" "x.mp"}


etex beginfig (1) label(btex blah etex, origin);
endfig; \end{document} bye

There’s two bits of trickery involved here:

  • Firstly, we need to pipe our output to a .tex file, so we can use the “show tex $file” command to read our output
  • Secondly, we need to fiddle with our command to bypass the silent (!) filter in pdfmaker_public.py.

A little tweaking later, and we have our flag:



This challenge was presented as an image, and a remote server location:


The shirt seems to depict a very simple (but completely blind – no binary!) format string vulnerability. We can quickly confirm this with manual netcat:


The general approach to exploiting format strings is to overwrite an existing function pointer (a destructor a GOT entry, etc) with something pointing to existing code, via use of the %n format specifier and a carefully controlled format string. In this situation, where we have access to a fully controlled call to printf, the easiest approach is to simply replace the printf call with a call to system().

To do this, we will first need to:

  • identify the location of the printf call in the GOT
  • identify the address of system()
  • find a way to overwrite the printf pointer in a single call to printf.

Firstly, we can use this trick, covered in a previous post, to leak parts of the binary. Given that it is a 64-bit binary, we can start leaking at 0x00400000. Firstly, we confirm that there is an ELF file living there:


Here, we can clearly see the ELF magic bytes (7f ELF). A little bit of leaking later, and we can identify the code printed on the tshirt, slightly commented for clarity:


Here, we can quite clearly see the code on the t-shirt, as well as their corresponding GOT entries. We can double check by leaking a pointer at 0x601018:


Our next step is to leak the address of system(). Fortunately, the pwntools “DynELF” module has already done the heavy lifting for us – by providing it a memory read primitive, it will rifle through memory looking for system().

Note that this process is slow, and involves a large number of memory reads (to the tune of >5 minutes, thanks to the endless sleep(1) calls). To speed up the process, given that system and printf are both libc functions (and thus, a static distance away from each other), we leak the address of both printf and system in a single attempt: this means that we only need to leak the address of printf (2-3 reads) to find where system is:


From here (forgive the shitty hexdumped address of printf), we can determine that system is 0x10e80 away from printf

Our next challenge is to overwrite the pointer to printf, with a pointer to the leaked system, in a single call of printf.

The traditional exploitation method of [offset][offset][offset][%123c][%5$n][%123c][%6$n][%123c][%7$n] won’t work, as on a 64-bit system, the null bytes in 64-bit pointers will cause printf to terminate early (and thus, the later %n specifiers to “not work”). Instead, we can front-load our format specifiers, as follows, solving this problem:


The only remaining challenge is to generate the format string “attack” itself (more specifically, the correct %c padding between each %hhn write), such that we end up writing the address of system() to the GOT. We can use the pwntools FmtStr module to generate this payload, *except* for the first byte / first chunk of padding, which we must generate manually (as pwntools assumes the offsets go at the front of the format string).

Putting this together, our final exploit looks like this:

  • Leak enough of the binary to find where printf is in the GOT
  • Leak the address of system, so we can calculate system’s address from printf’s address
  • Use printf to leak the address of printf in the GOT at 0x601018
  • Use a single printf call to overwrite printf with system()
  • Send “ls”, recv bacon

Moments later, the flag:


You can find the full python script I used for this challenge here. This is intentionally messy, and contains a lot more functionality than needed, so I can re-use the components for future format string vulnerabilities. To trigger the exploit only, use “./espr.py exploit”.

I will continue the 33C3 writeup in the next post, where I will cover the smartfridge challenge.

About Norman

Sometimes, I write code. Occasionally, it even works.
This entry was posted in Bards, Computers, Jesting and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.