BlackFeather'S Blog 我的技术小博 -- C/C++,Python,Golang

WPA/WPA2数据包解密还原


无线网络越来越普及,从此涉及到的业务也出现了多个。从安全圈子来讲,破解、劫持、嗅探等等都开始玩的不亦乐乎。本篇文章详细说明有了数据包如何还原WPA/WPA2加密的密文数据。此文章仅代表博主对WPA/WPA2加密解密的理解,用于学习流程,也为共同研究的同学提供关键字查询更多资料,如有错误请多包涵。

关键字:WAP,WPA2,解密,EAPOL,WPADecrypt。


开放wifi就不提了,直接通过嗅探可以看到所有数据。

加密算法,首先说下常用的有三种:WEP、WPA、WPA2。


WEP这里也不提了,因为爆出了漏洞,所以几乎没有人再使用此加密,更多的是使用WPA/WPA2加密算法。

至于WPA/WPA2的基础知识就不科普了,看这篇文章的朋友都应该有所了解。


接下来直奔主题,如果通过数据包还原出来WPA/WPA2加密的数据内容。WPA和WPA2的流程是一样的,只是部分算法不同,以下流程均可用,如何区别会额外说明。


说解密就要先了解加密。加密流程比较复杂,乱七八糟协议等不多说了,直接说关键流程,文字描述为:


1.输入ssid和密码,生成pmk(Pairwise Master Key)。


2.EAPOL握手包,四次握手,主要信息是里面的A-NONCE、S-NONCE、MIC。EAPOL的四次握手包的流程为:

eapol message 1:AP->Client,AP随机生成32个字节的A-NONCE发往客户端。有用的信息是A-NONCE和EncryptType(用于区分WPA还是WPA2)。

eapol message 2:Client->AP,客服端随机生成32个字节的S-NONCE,并用A-NONCE、S-NONCE和第一步生成的pmk一起生成ptk(Pairwise Transient Key),然后使用ptk加密整个数据流生成mic(Message Integrity Code,类似于tcp数据包的checksum)并填到数据中,发送到AP。有用的信息为S-NONCE、MIC和EncryptType。

eapol message 3:AP->Client,AP验证完上一个包通过后,会生成gtk(Group Temporal Key,这个是解密的关键,通俗来讲可以认为是接下来数据包通信加密用的sessionKey),并用上个握手包生成的ptk加密后,发送到客户端。此包里面的A-NONCE跟message 1里面的A-NONCE一致。有用的信息就是这个gtk(需要用ptk解密)。

eapol message 4:Client->AP,没有什么大作用了,只是跟message 2类似,用ptk加密数据包生成mic发送到AP,用于验证。


3.完成四次EAPOL握手后,就通过了验证开始正常使用。数据包就使用上述生成的gtk来加密。



WPA和WPA2的主要区别在于

1.eapol message 3里面解密还原出gtk,WPA用的是rc4,WPA2用的是aes

2.解密数据包,WPA用的是rc4,WPA2用的是aes


看文字描述有些头大,贴一张图。


难懂的几个关键名词解释:

pmk,使用ssid和密码生成,客户端和AP均生成,用于生成ptk。

ptk,在eapol握手包1和2交互完毕后,AP和客户端均生成。

mic,相当于tcp包里面的checksum,用ptk加密整个数据包生成的。

gtk,解密后续数据包的SessionKey,由AP生成并用ptk加密后发给客户端。


个人理解就是mic是登录wifi输入密码的时候,双方验证密码的checksum信息,客户端和AP用密码共同加密同一份数据(a-nonce、s-nonce、apmac、clientmac),然后出来一个结果交换看看一致不,一致就通过了密码验证。通过验证后AP生成gtk,接下来的数据包都用gtk来加密。



大致流程说了,接下来就是解密和算法了(基于openssl库)。

pmk生成算法:

void makepmk(char *ssid, char *psk, unsigned char *pmk)
{
	//calc pmk(Pairwise Master Key)
	PKCS5_PBKDF2_HMAC_SHA1(psk, strlen(psk),
		(const unsigned char *)ssid, strlen(ssid),
		4096,
		32, (unsigned char *)pmk);

	//printf("PMK from %s:%s -- %02X %02X %02X %02X ... %02X%02X%02X%02X\r\n",
	//	ssid, psk,
	//	pmk[0], pmk[1], pmk[2], pmk[3], pmk[28], pmk[29], pmk[30], pmk[31]);
}



ptk生成算法:

void makeptk(UINT8 *apmac, UINT8 *cmac,
 UINT8 *anonce, UINT8 *snonce,
 char *pmk, unsigned char *ptk)
{
UINT8 i;
UINT8 R[100];
int offset=sizeof("Pairwise key expansion");
memset(R, 0, 100);
memcpy(R, "Pairwise key expansion", offset);
/* Min(AA, SPA) || Max(AA, SPA) */
if (memcmp(cmac, apmac, MAC_ADDR_LEN) < 0)
{
memcpy(R + offset, cmac, MAC_ADDR_LEN);
memcpy(R + offset+MAC_ADDR_LEN, apmac, MAC_ADDR_LEN);
}
else
{
memcpy(R + offset, apmac, MAC_ADDR_LEN);
memcpy(R + offset+MAC_ADDR_LEN, cmac, MAC_ADDR_LEN);
}
offset+=MAC_ADDR_LEN*2;
/* Min(ANonce,SNonce) || Max(ANonce,SNonce) */
if( memcmp(snonce, anonce, 32) < 0 )
{
memcpy(R + offset, snonce, 32);
memcpy(R + offset + 32, anonce, 32);
}
else
{
memcpy(R + offset, anonce, 32);
memcpy(R + offset + 32, snonce, 32);
}
offset+=32*2;
for(i = 0; i < 4; i++)
{
R[offset] = i;
HMAC(EVP_sha1(), pmk, 32, R, 100, ptk + i * 20, 0);
}
}



解密生成gtk:

UINT8 AES_unwrap(unsigned char *kek, UINT16 key_len, char *cipher_text, UINT16 cipher_len, UINT8 *output)
{
	UINT8 a[8], b[16];
	UINT8 *r;
	char *c;
	UINT16 i, j, n;
	AES_KEY ctx;

	if (! kek || cipher_len < 16 || ! cipher_text || ! output)
	{
		/* We don't do anything with the return value */
		return 1;
	}

	/* Initialize variables */

	n = (cipher_len/8)-1;  /* the algorithm works on 64-bits at a time */
	memcpy(a, cipher_text, 8);
	r = output;
	c = cipher_text;
	memcpy(r, c+8, cipher_len - 8);

	/* Compute intermediate values */
	for (j=5; j >= 0; --j)
	{
		r = output + (n - 1) * 8;
		/* DEBUG_DUMP("r1", (r-8), 8); */
		/* DEBUG_DUMP("r2", r, 8); */
		for (i = n; i >= 1; --i)
		{
			UINT16 t = (n*j) + i;
			/* DEBUG_DUMP("a", a, 8); */
			memcpy(b, a, 8);
			b[7] ^= t;
			/* DEBUG_DUMP("a plus t", b, 8); */
			memcpy(b+8, r, 8);
			AES_set_decrypt_key(kek, 128, &ctx);
			AES_decrypt(b, b, &ctx);  /* NOTE: we are using the same src and dst buffer. It's ok. */
			/* DEBUG_DUMP("aes decrypt", b, 16) */
			memcpy(a,b,8);
			memcpy(r, b+8, 8);
			r -= 8;
		}
	}

	/* DEBUG_DUMP("a", a, 8); */
	/* DEBUG_DUMP("output", output, cipher_len - 8); */

	return 0;
}

void decryptgtk(eapol_data *pdata, unsigned char* ptk)
{
UINT16 key_bytes_len = 0;
UINT8  key_version;
unsigned char *decryption_key = ptk + 16;
key_version = pdata->kif_desp;
if (key_version == WPA_KEY_VER_NOT_CCMP)
{
/* TKIP */
key_bytes_len = pdata->it_eapollen;
}
else if (key_version == WPA_KEY_VER_AES_CCMP)
{
/* AES */
key_bytes_len = pdata->it_eapolwpakeydatalen;
}
if (key_bytes_len > TKIP_GROUP_KEYBYTES_LEN_MAX || key_bytes_len == 0)
{
return;
}
if (key_version == WPA_KEY_VER_NOT_CCMP)
{
UINT8  new_key[32];
memcpy(new_key, pdata->it_eapolkeyiv, 16);
memcpy(new_key+16, decryption_key, 16);
UINT8 dummy[256];
rc4_state_struct rc4_state;
crypt_rc4_init(&rc4_state, new_key, sizeof(new_key));
crypt_rc4(&rc4_state, dummy, 256);
crypt_rc4(&rc4_state, (unsigned char *)pdata->it_wpadata, key_bytes_len);
}
else if (key_version == WPA_KEY_VER_AES_CCMP)
{
UINT8 key_found;
UINT16 key_index;
UINT8 *decrypted_data;
decrypted_data = (UINT8 *)malloc(key_bytes_len);
AES_unwrap(decryption_key, 16, pdata->it_wpadata,  key_bytes_len, decrypted_data);
key_found = 0;
key_index = 0;
while(key_index < key_bytes_len && !key_found)
{
UINT8 rsn_id;
/* Get RSN ID */
rsn_id = decrypted_data[key_index];
if (rsn_id != 0xdd)
key_index += decrypted_data[key_index+1]+2;
else
key_found = 1;
}
if (key_found)
{
memcpy(pdata->it_wpadata, decrypted_data+key_index+8, key_bytes_len-key_index-8);
}
free(decrypted_data);
}
memset(ptk, 0, 80);
memcpy(ptk+32, pdata->it_wpadata, key_bytes_len);
}




解密数据包这里,目前完成了WPA2的算法:

#define XOR_BLOCK(b, a, len) \
{\
    int __i__;\
    for (__i__ = 0; __i__ < (int)(len); __i__++)\
        (b)[__i__] ^= (a)[__i__];\
}
#define CCMP_DECRYPT(_i, _b, _b0, _pos, _a, _len) {\
/* Decrypt, with counter */                             \
_b0[14] = (UINT8)((_i >> 8) & 0xff);                    \
_b0[15] = (UINT8)(_i & 0xff);                           \
AES_encrypt(_b0, _b, &key);        \
XOR_BLOCK(_pos, _b, _len);\
/* Authentication */\
XOR_BLOCK(_a, _pos, _len);\
AES_encrypt(_a, _a, &key);\
}
void ccmp_init_blocks(AES_KEY *ctx,
                      qos_data *pdata,
                      size_t dlen,
                      UINT8 b0[AES_BLOCK_LEN],
                      UINT8 aad[2 * AES_BLOCK_LEN],
                      UINT8 a[AES_BLOCK_LEN],
                      UINT8 b[AES_BLOCK_LEN])
{
    memset(aad, 0, 2*AES_BLOCK_LEN);
    /* CCM Initial Block:
    * Flag (Include authentication header, M=3 (8-octet MIC),
    *       L=1 (2-octet Dlen))
    * Nonce: 0x00 | A2 | PN
    * Dlen */
    b0[0] = 0x59;
    /* NB: b0[1] set below */
    memcpy(b0 + 2, pdata->header.it_sa, MAC_ADDR_LEN);
    b0[8] = pdata->it_ivp[7];
    b0[9] = pdata->it_ivp[6];
    b0[10] = pdata->it_ivp[5];
    b0[11] = pdata->it_ivp[4];
    b0[12] = pdata->it_ivp[1];
    b0[13] = pdata->it_ivp[0];
    b0[14] = (UINT8)((UINT8)(dlen >> 8) & 0xff);
    b0[15] = (UINT8)(dlen & 0xff);
    /* AAD:
    * FC with bits 4..6 and 11..13 masked to zero; 14 is always one
    * A1 | A2 | A3
    * SC with bits 4..15 (seq#) masked to zero
    * A4 (if present)
    * QC (if present)
    */
    aad[0] = 0;     /* AAD length >> 8 */
    /* NB: aad[1] set below */
    UINT8 fc[2];
    memcpy(fc, &(pdata->header.control), 2);
    aad[2] = (UINT8)(fc[0] & 0x8f);    /* XXX magic #s */
    aad[3] = (UINT8)(fc[1] & 0xc7);    /* XXX magic #s */
    /* NB: we know 3 addresses are contiguous */
    memcpy(aad + 4, pdata->header.it_da, 3 * MAC_ADDR_LEN);
    aad[22] = (UINT8)(pdata->header.it_seq[0] & 0x0f);
    aad[23] = 0; /* all bits masked */
    /*
    * Construct variable-length portion of AAD based
    * on whether this is a 4-address frame/QOS frame.
    * We always zero-pad to 32 bytes before running it
    * through the cipher.
    *
    * We also fill in the priority bits of the CCM
    * initial block as we know whether or not we have
    * a QOS frame.
    */
    aad[24] = (UINT8)(pdata->it_qosctrl[0] & 0x0f); /* just priority bits */
    aad[25] = 0;
    b0[1] = aad[24];
    aad[1] = 22 + 2;
    memset(&aad[26], 0, 4);
    /* Start with the first block and AAD */
    AES_encrypt(b0, a, ctx);
    XOR_BLOCK(a, aad, AES_BLOCK_LEN);
    AES_encrypt(a, a, ctx);
    XOR_BLOCK(a, &aad[AES_BLOCK_LEN], AES_BLOCK_LEN);
    AES_encrypt(a, a, ctx);
    b0[0] &= 0x07;
    b0[14] = b0[15] = 0;
    AES_encrypt(b0, b, ctx);
    /** //XOR( m + len - 8, b, 8 ); **/
}
UINT8 ccmpdecryptqosdata(qos_data *pdata, UINT16 qosdatalen, const unsigned char *tk/*16 bytes*/)
{
UINT8 aad[2 * AES_BLOCK_LEN];
UINT8 b0[AES_BLOCK_LEN], b[AES_BLOCK_LEN], a[AES_BLOCK_LEN];
UINT8 mic[AES_BLOCK_LEN];
UINT8 *pos;
UINT32 i;
size_t data_len;
AES_KEY key;
AES_set_encrypt_key(tk, 128, &key);
data_len = qosdatalen - sizeof(qos_data) - CCMP_TRAILER_LEN;
if (data_len < 1)
return 0;
ccmp_init_blocks(&key, pdata, data_len, b0, aad, a, b);
memcpy(mic, (UINT8 *)pdata + qosdatalen - CCMP_TRAILER_LEN, CCMP_TRAILER_LEN);
XOR_BLOCK(mic, b, CCMP_TRAILER_LEN);
i = 1;
pos = (UINT8 *)pdata->it_buf;
while (data_len >= AES_BLOCK_LEN)
{
CCMP_DECRYPT(i, b, b0, pos, a, AES_BLOCK_LEN);
pos += AES_BLOCK_LEN;
data_len -= AES_BLOCK_LEN;
i++;
}
if (data_len != 0)         /* short last block */
CCMP_DECRYPT(i, b, b0, pos, a, data_len);
/*MIC Key ?= MIC*/
if (memcmp(mic, a, CCMP_TRAILER_LEN) == 0)
{
return 1;
}
/* TODO replay check(IEEE 802.11i-2004, pg. 62)*/
/* TODO PN must be incremental (IEEE 802.11i-2004, pg. 62)*/
return 0;
}





总结:

解密的关键就是gtk,gtk是用ptk解密的,ptk使用pmk生成的,pmk使用ssid和pass生成的,所以要想解密,必须需要以下几个信息:

1.ssid和对应的密码

2.登录验证的四次eapol包中的a-nonce和s-nonce,以及加密算法区分WAP或者WPA2。


只要有了这几个信息,解密就是分分钟的事情了WPADecrypt.exe。


更多详细技术讨论,联系博主q 345 3824 62。

2015年9月29日 | 发布:blackfeather | 分类:C/C++代码 | 评论:1

留言列表:

  • choose 发布于 2020/7/15 17:12:42  回复
  • 请问一下GTK不是解密组播包的么,如果解密单播包是不是只要用PTK?还有解密之后能校验码,该怎么做
    • 博主 发布于 2020/7/22 12:17:44  回复
    • 不知道你说的组播包和单播包是什么类型的,上述的解密都指的是解密QosData数据包,用的就是gtk,也只有这一个key。当然如果是多AP组网切换时session变化等等情况不在这个帖子范围内。

发表留言: