在之前分享的第三篇文章 【FireBeetle 2 ESP32-S3开发板体验】基于ESP32S3+SPIFFS+AsyncWebServer+SQLite3的硬件地址归属品牌(厂商)查询工具 中,已经实现了通过硬件设备mac地址,查询其所归属的品牌(厂商)信息的功能,那么这篇文章则基于此,再进一步。
上篇文章,实现的功能如下:
这里的硬件设备地址,需要自己去查看,然后输入进来。
那么,有办法自动获取硬件设备地址吗?
有,那就是WiFi嗅探。
一、知识了解
什么是WiFi嗅探?
通过如下页面可以了解:wifi嗅探原理-掘金 (juejin.cn)
Wi-Fi 嗅探是一种通过拦截无线网络流量数据包来获取网络信息的技术。在 Wi-Fi 嗅探中,使用无线网络适配器或专门的网络嗅探工具来监控网络流量,抓取网络数据包,然后分析这些数据包中的信息。
Wi-Fi 嗅探的原理是通过监听无线网络适配器收到的数据包,获取数据包的源和目标 MAC 地址,以及数据包的内容信息。Wi-Fi 嗅探可以监听和分析包括 SSID、密码、MAC 地址、IP 地址、数据传输方式等在内的各种网络信息。
当您连接到一个 Wi-Fi 网络时,您的设备会向 Wi-Fi 接入点发送数据包,以获取网络的授权和访问权限。这些数据包通常包括无线网络的 SSID、密码和 MAC 地址等信息。Wi-Fi 嗅探器可以捕获这些数据包,并通过解密和分析这些数据包来获取网络信息。
需要注意的是,Wi-Fi 嗅探需要在合法和合适的情况下进行。非法的 Wi-Fi 嗅探行为可能会侵犯他人的隐私和安全,因此请务必遵守相关的法律法规和道德规范。
虽然WiFi嗅探不是很光彩,但是现实中,确实有很多场景使用,很多互联网DMP平台都根据硬件设备地址投放广告。
举几个简单的例子:
- 在商场中,可以通过WiFi嗅探,来了解客流量。因为现在出门都会带个手机,一般WiFi功能也没有主动关闭。
- 对于连锁门店,用户可能会去多个店询问服务价格,通过WiFi嗅探,可以感知用户是否去过其他门店,从而防止差异性服务价格的出现
但是新的版本的手机,未连接到WiFi时,可能会设置随机发送mac地址广播,但是,一旦连接到WiFi,其硬件设备地址则是固定的;可以通过提供通用WiFi服务的方式,来诱使其连接,从而获得其硬件设备地址。
好了,不多说了,说多了,你知道的太多了!!!
二、功能设计
这篇分享呢,不会分享太过于机密的内容,也不会教你怎么破解数据包获取WiFi密码蹭隔壁家的网,不会教你怎么干坏事!!!
其实际的内容,就是获取FireBeetle 2 ESP32-S3开发板周边的2.4GHz WiFi信号(包括路由、手机等一切WiFi设备)的硬件设备地址,以及识别出其对应的品牌(厂商)信息,仅此而已。
要实现上面的功能,需要以下几点:
- 使用 FireBeetle 2 ESP32-S3开发板 获取周边WiFi数据报信息
- 从数据报信息中,获取硬件设备地址
- 使用该设备地址,到上一篇文章中使用的IEEE数据库中查找信息
从 DFRobot官方的FireBeetle 2 ESP32-S3开发板WiKi页面,可以得知:
上面写了,支持混杂模式。支持混杂模式,就可以嗅探传输线路上的所有通信。
三、实际代码
上面关于原理方面,简单讲了讲,大家感兴趣的话,可以自己查找资料详细了解,下面直接贴代码:
#include <SPI.h>
#include <FS.h>
#include "SPIFFS.h"
#include <sqlite3.h>
#include "freertos/FreeRTOS.h"
#include "esp_wifi.h"
#include "esp_wifi_types.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "driver/gpio.h"
#include "Dictionary.h"
#define LED_GPIO_PIN 21
#define WIFI_CHANNEL_SWITCH_INTERVAL (500)
#define WIFI_CHANNEL_MAX (13)
uint8_t level = 0, channel = 1;
static wifi_country_t wifi_country = {.cc = "CN", .schan = 1, .nchan = 13};
typedef struct {
unsigned frame_ctrl: 16;
unsigned duration_id: 16;
uint8_t addr1[6];
uint8_t addr2[6];
uint8_t addr3[6];
unsigned sequence_ctrl: 16;
uint8_t addr4[6];
} wifi_ieee80211_mac_hdr_t;
typedef struct {
wifi_ieee80211_mac_hdr_t hdr;
uint8_t payload[0];
} wifi_ieee80211_packet_t;
static esp_err_t event_handler(void *ctx, system_event_t *event);
static void wifi_sniffer_init(void);
static void wifi_sniffer_set_channel(uint8_t channel);
static const char *wifi_sniffer_packet_type2str(wifi_promiscuous_pkt_type_t type);
static void wifi_sniffer_packet_handler(void *buff, wifi_promiscuous_pkt_type_t type);
#if 1
sqlite3 *db;
int rc;
sqlite3_stmt *res;
const char *tail;
Dictionary &d = *(new Dictionary(1000));
int db_open(const char *filename, sqlite3 **db) {
int rc = sqlite3_open(filename, db);
if (rc) {
Serial.printf("Can't open database: %s\\n", sqlite3_errmsg(*db));
return rc;
} else {
Serial.printf("Opened database successfully\\n");
}
return rc;
}
#endif
esp_err_t event_handler(void *ctx, system_event_t *event)
{
return ESP_OK;
}
void wifi_sniffer_init(void)
{
tcpip_adapter_init();
ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_country(&wifi_country) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) );
ESP_ERROR_CHECK( esp_wifi_start() );
esp_wifi_set_promiscuous(true);
esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler);
}
void wifi_sniffer_set_channel(uint8_t channel)
{
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
}
const char * wifi_sniffer_packet_type2str(wifi_promiscuous_pkt_type_t type)
{
switch (type) {
case WIFI_PKT_MGMT: return "MGMT";
case WIFI_PKT_DATA: return "DATA";
default:
case WIFI_PKT_MISC: return "MISC";
}
}
void wifi_sniffer_packet_handler(void* buff, wifi_promiscuous_pkt_type_t type)
{
if (type != WIFI_PKT_MGMT)
return;
const wifi_promiscuous_pkt_t *ppkt = (wifi_promiscuous_pkt_t *)buff;
const wifi_ieee80211_packet_t *ipkt = (wifi_ieee80211_packet_t *)ppkt->payload;
const wifi_ieee80211_mac_hdr_t *hdr = &ipkt->hdr;
char buffer[7] = {0};
sprintf(buffer, "%02X%02X%02X", hdr->addr2[0], hdr->addr2[1], hdr->addr2[2]);
String sAssignment = buffer;
String sT = "";
int t = millis()/1000;
bool o = false;
if (d(sAssignment)) {
sT = d[sAssignment];
if(t-sT.toInt()<30) {
return;
}
o = true;
}
sT = String(t);
d(sAssignment, sT);
char addr_buffer[18] = {0};
sprintf(addr_buffer, "%02x:%02x:%02x:%02x:%02x:%02x",
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5]
);
String sAddr = addr_buffer;
String sAddrSec = sAddr.substring(0, 11);
sAddrSec += ":**:**";
String sOrganizationName = "";
String sql = "Select OrganizationName from oui where Assignment = '";
sql += sAssignment;
sql += "' LIMIT 1";
rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &res, &tail);
while (sqlite3_step(res) == SQLITE_ROW) {
sOrganizationName += (const char *) sqlite3_column_text(res, 0);
}
sqlite3_finalize(res);
#if 0
printf("PACKET TYPE=%s, CHAN=%02d, RSSI=%02d"
" ADDR1=%02x:%02x:%02x:%02x:%02x:%02x,"
" ADDR2=%02x:%02x:%02x:%02x:%02x:%02x,"
" ADDR3=%02x:%02x:%02x:%02x:%02x:%02x\\n",
wifi_sniffer_packet_type2str(type),
ppkt->rx_ctrl.channel,
ppkt->rx_ctrl.rssi,
hdr->addr1[0], hdr->addr1[1], hdr->addr1[2],
hdr->addr1[3], hdr->addr1[4], hdr->addr1[5],
hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
hdr->addr2[3], hdr->addr2[4], hdr->addr2[5],
hdr->addr3[0], hdr->addr3[1], hdr->addr3[2],
hdr->addr3[3], hdr->addr3[4], hdr->addr3[5]
);
#endif
printf("[%s]RSSI=%02d, ADDR=%s, Assignment=%s, OrganizationName=%s\\n",
o ? "O" : "N",
ppkt->rx_ctrl.rssi,
sAddrSec.c_str(),
buffer,
sOrganizationName.c_str()
);
}
void setup() {
Serial.begin(115200);
delay(10);
#if 1
if (!SPIFFS.begin(true)) {
Serial.println("Failed to mount file system");
return;
}
File root = SPIFFS.open("/");
if (!root) {
Serial.println("- failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println(" - not a directory");
return;
}
sqlite3_initialize();
Serial.println("open oui.db");
if (db_open("/spiffs/oui.db", &db))
return;
Serial.println("SELECT * FROM oui ORDER BY RANDOM() LIMIT 10");
rc = sqlite3_prepare_v2(db, "SELECT * FROM oui ORDER BY RANDOM() LIMIT 10", -1, &res, &tail);
if (rc != SQLITE_OK) {
sqlite3_close(db);
return;
}
Serial.println("fetch oui data:");
while (sqlite3_step(res) == SQLITE_ROW) {
Serial.print((const char *) sqlite3_column_text(res, 0));
Serial.print("\\t");
Serial.println((const char *) sqlite3_column_text(res, 1));
}
Serial.println("fetch end");
sqlite3_finalize(res);
delay(5000);
#endif
wifi_sniffer_init();
pinMode(LED_GPIO_PIN, OUTPUT);
}
void loop() {
delay(1000);
if (digitalRead(LED_GPIO_PIN) == LOW)
digitalWrite(LED_GPIO_PIN, HIGH);
else
digitalWrite(LED_GPIO_PIN, LOW);
vTaskDelay(WIFI_CHANNEL_SWITCH_INTERVAL / portTICK_PERIOD_MS);
wifi_sniffer_set_channel(channel);
channel = (channel % WIFI_CHANNEL_MAX) + 1;
}
要使用混杂模式嗅探WiFi数据包,主要使用了如下的调用:
esp_wifi_set_mode(WIFI_MODE_NULL);
esp_wifi_set_promiscuous(true);
esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler);
第一个调用设置ESP32S3的工作模式既不是STA模式,也不是AP模式。
第二个调用设置开启混杂模式。
第三个调用设置收到数据包后的回调。
在wifi_sniffer_packet_handler()中,处理逻辑如下:
- 会从数据包中,获取当前数据包的设备地址信息
- 然后使用该设备地址信息,提取前六位有效的mac地址
- 检查该设备是否在30秒内探测过,如果探测过,则跳过
- 再到数据库查询器对应的归属品牌(厂商)
- 然后输出相关的信息
- loop循环中,会定期切换WiFi信道,以便逐个信道进行探测
四、实际效果
在上述输出结果中,[N]表示启动后新探测到的设备,[O]表示已经探测到的设备间隔30秒再次探测到。
五、总结
好了,这篇只是一点小小的分享,如果确实感兴趣,可以深入了解,网上的资料很多的!