Tweaking EAP’s weak link – sucking WiFi PMKs out of RADIUS with pmkXtract
When faced with a WiFi network using WPA2/EAP-TLS, options for a direct attack are limited. You can’t feasibly attack the encryption (most likely CCMP), and if the client devices are configured properly attacking the authentication mechanism directly isn’t likely to bear fruit either.
One of the advantages of the “enterprise” variants of WPA is that the Pairwise Master Keys are specific to a client device and to a single association (WPA Personal has the same PMK shared amongst all devices). To oversimplify somewhat, with WPA Enterprise the PMK for a given device is negotiated between the device and the RADIUS server as part of the EAP process. The PMK is subsequently delivered to the access point via RADIUS. Once both the client device and the AP have the PMK the normal WPA four-way handshake can complete, the output of which is a Pairwise Transient Key which is used to encrypt the session. There’s a nice diagram of the process here.
However, there is a weak link in the chain. Hacking Exposed Wireless, 2nd edition describes a possible attack against the delivery of the PMK from the RADIUS server to the access point. The theory of operation is as follows:
- Start capturing the wireless traffic of interest.
- Start capturing the RADIUS traffic between the RADIUS server and the access point.
- Using an obtained RADIUS shared secret, decrypt the RADIUS traffic that contains the PMKs.
- Use the extracted PMKs and the captured four-way handshakes to derive each device’s PTK.
- Decrypt every single data frame you’ve captured from the wireless network 🙂
The book didn’t cite any tools capable of extracting the PMKs from the RADIUS conversation, so I thought I’d have a go at writing one myself. The reason the book didn’t cite any tools is probably that if an attacker is in a position to capture your RADIUS traffic, you probably have bigger things to worry about. Nevertheless, it seemed like a challenge worth attempting.
Of the five points above, 1 and 2 are straightforward (assuming the attacker is well placed). 4 and 5 can be easily carried out with Wireshark. Step 3 breaks down into obtaining the shared secret and decrypting the PMKs.
Obtaining the RADIUS shared secret
As noted in Hacking Exposed Wireless, a brute force attack on the shared secret is quite feasible. Alternatively, one can just poke around the RADIUS server and find the list of secrets. For example:
- FreeRADIUS stores all of this in clients.conf
- Internet Authentication Service on Windows 2K3 stores everything in system32\ias\ias.mdb which you can open in MS Access (look in the Objects table)
- Network Policy Server on Windows 2k8 stores everything in system32\ias\ias.xml in plaintext.
Secrets in hand, we can move on to…
Decrypting the PMKs
We first need to start capturing the WiFi traffic of interest. We can do this with airodump-ng:
airmon-ng start wlan0
airodump-ng –channel 1 –bssid 00:1c:df:8b:50:66 –write pmkXtract mon0
Given the correct shared secret, Wireshark is apparently capable of decrypting RADIUS traffic but I couldn’t make it work for MS-MPPE-Recv-Key. Shying away from actually writing decryption code from scratch, I invoked the age-old software engineering tradition of “code re-use”, aka pinching someone else’s stuff.
hostapd is a user-space access point daemon, and has full support for EAP-TLS. After nosing around and debugging the code, I found the procedure that does the decryption – decrypt_ms_key() in radius.c.
pmkXtract
pmkXtract is a quick and dirty hack that wraps up decrypt_ms_key(); all we need to do is feed it:
- The RADIUS shared secret
- The request authenticator from the final Access-Request message
- The MS-MPPE-Recv-Key from the following Access-Accept message
A neater, future version of pmkXtract would capture the latter two straight off the wire, but for now they are CLI parameters. The most convenient way to get these values is with tshark, supplying the appropriate capture filter:
tshark -T fields -e radius.code -e radius.id -e radius.authenticator -e radius.MS_MPPE_Recv_Key -E separator=! ip host access.point.ip.addr
This will produce output like this:
<snip>
1!10!a0:29:27:a6:60:a8:f4:cb:4c:1d:b3:fa:46:b4:63:e4!
11!10!8b:eb:bf:dd:47:b8:46:35:92:33:6d:bb:ec:23:e8:2f!
1!11!a2:9f:92:ec:93:5b:05:ef:f6:a1:82:0a:6c:dd:37:52!
11!11!85:f7:35:08:39:da:f1:0c:4a:3e:fa:af:00:e7:af:d4!
1!12!5d:5c:3c:99:86:fc:d6:2e:a1:fc:fb:85:7e:6d:69:5d!
2!12!9e:13:52:10:ab:b9:74:99:37:ab:8d:b9:ec:3f:b0:9a!80:8a:7c:
d5:62:6c:0a:d9:ad:5b:0f:e5:5b:1d:56:d5:59:38:7a:06:ab:
b1:65:8a:a6:b4:a6:9f:b2:ea:d9:32:49:d2:3a:f0:ba:d0:b1:
77:fc:e5:b9:77:fe:dc:c2:04:7d:79
What we’re looking for is a pair of code1/code2 messages with the same ID, as is the case with the last two lines above with ID 12. Code 1 denotes Access-Request, code 2 is Access-Accept. Now we can pass the message authenticator from the access-request and the MS-MPPE-Recv-Key from the access-accept to pmkXtract, along with the shared secret:
C:\>pmkXtract secret 5d:5c:<snip>:69:5d 80:8a:<snip>:7d:79
PMK is:c8cad1f342e431bdf97b3fd1c37bf2fe
d74b9c785d302430ea418a04998b9ee4
Score! We’ve got the PMK.
Decrypting the captured WiFi traffic
The first thing we need to do is look through our airodump capture for the WPA four-way handshake:
As we might expect, the contents of the capture is unintelligible:
Now we can enter the PMK that pmkXtract gave us, by going Edit->Preferences, and selecting IEEE802.11 under Protocols:
And hey presto, we have decrypted the traffic. Note the “Decrypted CCMP data” sub-tab at the bottom of the window:
As I’ve already said, if an attacker can pull this off, they already have a deep foothold in your infrastructure. Use strong RADIUS secrets, secure the list of secrets on the server (clients.conf, ias.mdb, etc) as well as you can, and if it’s possible, protect the RADIUS exchange with IPSec.
Alec Waters is responsible for all things security at Dataline Software, and can be emailed at alec.waters@dataline.co.uk
24 January, 2011 at 04:02
[…] When faced with a WiFi network using WPA2/EAP-TLS, options for a direct attack are limited. You can’t feasibly attack the encryption (most likely CCMP), and if the client devices are configured properly attacking the authentication … View original post here: Tweaking EAP's weak link – sucking WiFi PMKs out of RADIUS with … […]
23 May, 2011 at 18:39
[…] EAP authentication via remote RADIUS server ! Knowing the RADIUS server’s secret can be very useful! radius-server host 10.9.8.7 auth-port 1645 acct-port 1646 key mySecret aaa new-model aaa group […]
7 November, 2012 at 07:19
Hi,
This is a great article.
Could you please let me know how did you change the decrypt_ms_key(); procedure in order to feed it with Radius shared secret and the other ttwo “burgers”?
My programing skill is very low.
Thanks a lot for your help,
Marius
7 November, 2012 at 13:52
Hi Marius,
I didn’t change it, I just wrote some wrapper code to call it un-altered.
hth,
alec
25 January, 2013 at 18:29
How to get your pmkXtract code?
28 January, 2013 at 08:13
Hi,
I’ll see if I can dig it out – it wasn’t particularly polished though!
Alec
28 January, 2013 at 08:58
It would be nice of you!
30 January, 2013 at 15:47
I’m trying to write my own. But I can get nothing but “failed to decrypt mope key”
5 February, 2013 at 10:04
Don’t want to be annoying, but
Did you succeed?
7 February, 2013 at 10:30
Hi Els,
You can download it from here:
http://www.wirewatcher.net/files/pmkxtract.cpp
It’s a bit rough and ready, but it worked for me. You may need to tweak it a little if you’re not building on a Windows platform, but it should compile OK for you.
Good luck,
alec
14 January, 2015 at 09:13
I put together a python implementation. A bit rough, but should do the job!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
key_decode.py
hosted with ❤ by GitHub
14 January, 2015 at 09:40
Thanks Robert 🙂
8 May, 2018 at 10:20
For posterity:
from hashlib import md5
from binascii import unhexlify, hexlify
seq_xor = lambda c, b: ”.join(
chr(x) for x in map(lambda X: ord(X[0]) ^ ord(X[1]), zip(c, b))
)
#
# RFC-2548: 2.4.3. MS-MPPE-Recv-Key
#
# C: Cyphertext
# S: Secret
# R: Request Authenticator
# A: Salt field
# P: Plaintext – key-len + key + padding
#
def rad_tunnel_pwdecode(C, S, R, A):
c = [C[i:i+16] for i in range(0, len(C), 16)]
p = [‘\0’] * len(c)
b = [‘\0′] * len(c)
b[0] = md5(S + R + A).digest()
p[0] = seq_xor(c[0], b[0])
for i in range(1, len(p)):
b[i] = md5(S + c[i-1]).digest()
p[i] = seq_xor(c[i], b[i])
P = ”.join(p)
return P
Secret = “my_radius_shared_secret”
RequestAuthenticator = “\0” * 16
RequestAuthenticator = “abcdefd7f69b22662fd752c39f97e81e”
Cyphertext =’abcdef5c56144a4b63f591cbcc7d3d9be8f317f811f5009c57d6706e68238948685836ff4c19127c190218198c6f998830a7’
print “Cypher:%s” % Cyphertext
Salt = Cyphertext[:4]
Plaintext = rad_tunnel_pwdecode(
unhexlify(Cyphertext[4:]),
Secret,
unhexlify(RequestAuthenticator),
unhexlify(Salt)
)
Plaintext = hexlify(Plaintext)
print “Plain: %s”%Plaintext
26 February, 2013 at 12:04
Thanks! It worked fine for me
26 February, 2013 at 12:26
Glad you got it working 🙂
alec
2 April, 2013 at 14:13
Thank you very much. This helped me, too.
It was kinda tricky, but finally managed to get the PMK out of the traffic between the AP and the RADIUS-Server.
Hint: Freeradius prints the PMK as plaintext, if you enable logging as:
Sending Access-Accept of id 183 to 192.168.150.3 port 3072
MS-MPPE-Recv-Key = 0x980b92b88b6c2000d37643811e3f33a6f1ea2490e56db1d830931b2a114a5552