ec_shakespeare
Resources
- The ROP tutorial video
- This ROP Emporium writeup
Guidance
In this challenge, we’re going to introduce and work with a particular kind of ROP gadget schema: the write-what-where (WWW) gadget!
In our associated tutorial video, we show how we might pop a value into a given register so that it might be passed as an argument to an associated function call. For a single argument, this can reductively be shown as:
payload = p64(pop_rdi_gadget_addr)
payload += p64(0x12345678) # The argument passed (in this case to RDI)
payload += p64(function_addr)
The above works so long as whatever function we’re calling at function_addr is expecting an integer value not unlike 0x12345678. But what about a function like system()? If we reference our very first exercise (01_basic_overflow_1) back in Stage A, we can use GDB to see how our call to system() is setup:
Notice how system() does not process the string cat /proc/flag directly, but instead receives a pointer to the string (in this case, at address 0x599ef4d57051).
Again, this isn’t so much a problem so long as…
- …the string cat /proc/flag already exists in the binary AND…
- …we know the address where that string exists at runtime.
So long as the above is true, we would only need to make minor modifications to our payload:
payload = p64(pop_rdi_gadget_addr)
payload += p64(0x599ef4d57051) # The pointer to “cat /proc/flag”
payload += p64(call_system)
But what if neither of these conditions were true? Here’s where the WWW ROP technique comes in handy!
Write-What-Where
In principle, the WWW method makes use of existing functions/gadgets within the target binary in order to allow us to arbitrarily write a payload into memory; in practice, this might be passing malicious strings like /bin/sh in order to attain a shell or overwriting other values not otherwise directly accessible by the buffer overflow. This – as you might imagine – is a very powerful exploit primitive.
One common WWW gadget is the mov <[register1]>, <register2> gadget. Note how the square brackets around registerl indicate that the address is getting dereferenced. This gadget writes the contents stored within register2 to the area of memory pointed at in registerl .
Assuming we have control of both registers (usually though not always through the use of other gadgets), we can perform the arbitrary write through the following steps:
- Set registerl to an address pointing to a writable area of memory
- Set register2 to be the data you want to write
- Invoke the WWW gadget
But where are we meant to write to?
Theoretically, anywhere that’s writable, including your buffer that you’ve overflowed. Practically speaking however, it’s more appropriate to say anywhere that’s writable that we know the address of. Because our variable addresses are assigned to the stack, predicting those addresses is near impossible without a kind of memory leak. So where else then?
Well, assuming our binary isn’t compiled with PIE, then our binary has several fixed virtual address spaces available for us to use. Try running readelf -S flag to see them (areas flagged with a “W” are considered writable). Typically the .data and .bss sections are safe to write in without introducing too much instability (being reserved for initialized and uninitialized global variables). You can also see the address spaces within GDB by running the info files command (though it won’t tell you if they are writable or not).
Challenge Instructions
- This binary plays with the infinite monkey theorem: that while most keystrokes produced by a monkey at a typewriter are nonsense, given infinite time they would be able to produce the complete works of William Shakespeare. In the spirit of that theme, this flag binary ingests (seemingly) nonsensical input looking for signs of Shakespeare’s work!
- Assuming you can satisfy the binary’s logic, you then have to perform a standard buffer Review the flag.c source code and figure out where the vulnerability is and how you can exploit it.
Once exploited, you’ll note that there is no system(“cat /proc/flag”) call made anywhere. In fact, the string cat /proc/flag isn’t resident on the binary at all! Fortunately, the gadgets() deadcode has a usable set of gadgets available to us. You’ll need to leverage them (and any other code laying about) in order to write your own arguments



