图例:tp-link802.1x认证客户端,需要填入用户名、密码、网卡。

-
选择网卡
(1)添加网卡信息结构体
struct NetcardInfo {
char name[NET_CARD_NAME_MAX_LENGTH]; // 用于pcap_open_live()函数,格式例: \Device\NPF_{D89BD73E-7192-407B-9871-E315194968F8}
char description[NET_CARD_DESCRIPTION_MAX_LENGTH]; // 界面描述,同tp-link表述形式
u_char mac[MAC_SIZE]; // 网卡mac地址,用于组二层包头
bool is_physical; // 是否物理网卡
bool is_wireless; // 是否无线网卡
};
(2)遍历所有网卡信息
通过函数GetAdaptersInfo遍历所有网卡,去除非物理与无线网卡,界面显示可选择的有线网卡(tp-link未去除,会显示所有网卡,包括无线和虚拟网卡)

具体获取:
sprintf_s(netcard.name, sizeof(netcard.name) - 1, "\\Device\\NPF_%s", adapter->AdapterName);
sprintf_s(netcard.description, sizeof(netcard.description) - 1, adapter->Description);
for (UINT i = 0; i < adapter->AddressLength && i < 6; ++i) {
netcard.mac[i] = adapter->Address[i];
}
netcard.is_physical = (strstr(adapter->Description, "PCI") > 0);
netcard.is_wireless = (adapter->Type == 71);
-
基础函数调用
(1)pcap_open_live获取网卡handle用于通信
pcap_t* pcap_open_live(char* device, int snaplen, int promisc, int to_ms, char* ebuf);
device: 网卡名,格式 \\Device\\NPF_XXXX,如果传入NULL或"any",对所有接口进行捕获
snaplen: 设置每个数据包的捕捉长度,上限MAXIMUM_SNAPLEN
promisc: 是否打开混杂模式
to_ms: 设置获取数据包时的超时时间(ms)(时间过长导致捕获阻塞函数pcap_next_ex延迟退出?)
char error[PCAP_ERRBUF_SIZE];
handle = pcap_open_live("\\Device\\NPF_XXXX", 65536, 1, 20, error);
(2)pcap_compile && pcap_setfilter设置捕获过滤
char filter[128];
std::string src_dst = "dst"; // "src"
sprintf_s(filter, "(ether proto 0x888e) and (ether %s host %02x:%02x:%02x:%02x:%02x:%02x)",
src_dst.c_str(), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
struct bpf_program fcode;
pcap_compile(adapter_hander_, &fcode, filter, 1, 0xff);
pcap_setfilter(adapter_hander_, &fcode);
(3)pcap_next_ex获取报文
void ProcessRecvData(Process* processer) {
if (!processer) {
return;
}
pcap_t* adapter =processer->GetAdapterHandle();
if (!adapter) {
return;
}
struct pcap_pkthdr *header;
const unsigned char *captured;
while (true) {
if (connection_stop) {
return;
}
int ret = pcap_next_ex(adapter, &header, &captured);
if (1 == ret) {
processer->HandleRecvData(captured);
}
else {
Sleep(20);
}
}
}
//recv_thread = new std::thread(ProcessRecvData, this);
(4)pcap_sendpacket发送数据包
int pcap_sendpacket(pcap_t p, u_char buf, int size);
buf是发送数据包的内容缓冲区首地址,目的mac+源mac+协议类型(802.1x是0x88 8e)+802.1x authentication ...
目的mac:广播mac地址{ 0xff,0xff,0xff,0xff,0xff,0xff }或多播mac地址{ 0x01,0x80,0xc2,0x00,0x00,0x03 },首次收到回复后可保存下ac的mac地址并通过pcap_setfilter设置捕获过滤
(5)pcap_close
-
数据包处理
(1)交互流程
client->AP: EAPOL START
AP->client: Request Identity
client->AP: Response Identity
AP->client: Request MD5-challenge EAP || ...
client->AP: Response Legacy Nak // 举例仅支持PEAP
AP->client: Request Protected PEAP
client->AP: Client Hello
...
AP->client: Server Hello
...
client->AP: TLS Data
AP->client: TLS Data
...
AP->client: Repeat // 可能出现
client->AP: Response
AP->client: Success
(2)加密数据处理
struct PeapPara {
Tls* tls;
u_char *in_buf;
u_char *out_buf;
int written;
int read;
int phase;
u_char tk[PEAP_TLV_TK_LEN];
u_char ipmk[PEAP_TLV_IPMK_LEN];
u_char nonce[PEAP_TLV_NONCE_LEN];
ChapMs chap;
};
加密数据分为TLS与CHAP-MS两层,TLS使用BIO_read && BIO_write读写,CHAP-MS需要单独类处理,建议参考PPP协议的开源代码
(3)特殊情况处理
交互流程中,最后可能存在一步Repeat
调试过程中,经过解密的TLS数据,根据首位EAP type的值,分为IDentity,Tls,MsChapV2处理,然而最后一步Response Identity 后一直返回failure。查找资料才知道这边是AP要求重复Request的值,详细参考Packet15
4、参考链接
EAP-PEAP with Mschapv2: Decrypted and Decoded
microsoft's PEAP version 0 (Implementation in Windows XP SP1)
5、拓展

以上是WIFI连接的抓包
流程与有线连接基本相同,不过每个包的802.1x WIFI头较有线的源mac目的mac复杂一些
也许去除二层头后处理逻辑可以复用?
留待后续学习后解答...
6、更新
具体实现可参考我的github源码 wwp1122/net_connector (github.com)