单片机/MCUwilliam hill官网
直播中

HonestQiao

8年用户 549经验值
擅长:嵌入式技术
私信 关注

【FireBeetle 2 ESP32-S3开发板体验】基于FireBeetle 2 ESP32-S3开发板的WiFi嗅探器

在之前分享的第三篇文章 【FireBeetle 2 ESP32-S3开发板体验】基于ESP32S3+SPIFFS+AsyncWebServer+SQLite3的硬件地址归属品牌(厂商)查询工具 中,已经实现了通过硬件设备mac地址,查询其所归属的品牌(厂商)信息的功能,那么这篇文章则基于此,再进一步。

上篇文章,实现的功能如下:
image.png

这里的硬件设备地址,需要自己去查看,然后输入进来。

那么,有办法自动获取硬件设备地址吗?

有,那就是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平台都根据硬件设备地址投放广告。

举几个简单的例子:

  1. 在商场中,可以通过WiFi嗅探,来了解客流量。因为现在出门都会带个手机,一般WiFi功能也没有主动关闭。
  2. 对于连锁门店,用户可能会去多个店询问服务价格,通过WiFi嗅探,可以感知用户是否去过其他门店,从而防止差异性服务价格的出现

但是新的版本的手机,未连接到WiFi时,可能会设置随机发送mac地址广播,但是,一旦连接到WiFi,其硬件设备地址则是固定的;可以通过提供通用WiFi服务的方式,来诱使其连接,从而获得其硬件设备地址。

好了,不多说了,说多了,你知道的太多了!!!

二、功能设计

这篇分享呢,不会分享太过于机密的内容,也不会教你怎么破解数据包获取WiFi密码蹭隔壁家的网,不会教你怎么干坏事!!!

其实际的内容,就是获取FireBeetle 2 ESP32-S3开发板周边的2.4GHz WiFi信号(包括路由、手机等一切WiFi设备)的硬件设备地址,以及识别出其对应的品牌(厂商)信息,仅此而已。

要实现上面的功能,需要以下几点:

  1. 使用 FireBeetle 2 ESP32-S3开发板 获取周边WiFi数据报信息
  2. 从数据报信息中,获取硬件设备地址
  3. 使用该设备地址,到上一篇文章中使用的IEEE数据库中查找信息

从 DFRobot官方的FireBeetle 2 ESP32-S3开发板WiKi页面,可以得知:
image.png

上面写了,支持混杂模式。支持混杂模式,就可以嗅探传输线路上的所有通信。

三、实际代码

上面关于原理方面,简单讲了讲,大家感兴趣的话,可以自己查找资料详细了解,下面直接贴代码:

#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}; //Most recent esp32 library struct

typedef struct {
  unsigned frame_ctrl: 16;
  unsigned duration_id: 16;
  uint8_t addr1[6]; /* receiver address */
  uint8_t addr2[6]; /* sender address */
  uint8_t addr3[6]; /* filtering address */
  unsigned sequence_ctrl: 16;
  uint8_t addr4[6]; /* optional */
} wifi_ieee80211_mac_hdr_t;

typedef struct {
  wifi_ieee80211_mac_hdr_t hdr;
  uint8_t payload[0]; /* network data ended with 4 bytes csum (CRC32) */
} 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) ); /* set country for channel range [1, 13] */
  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,
         /* ADDR1 */
         hdr->addr1[0], hdr->addr1[1], hdr->addr1[2],
         hdr->addr1[3], hdr->addr1[4], hdr->addr1[5],
         /* ADDR2 */
         hdr->addr2[0], hdr->addr2[1], hdr->addr2[2],
         hdr->addr2[3], hdr->addr2[4], hdr->addr2[5],
         /* ADDR3 */
         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,
         /* ADDR2 */
         sAddrSec.c_str(),
         buffer,
         sOrganizationName.c_str()
        );
}

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin 5 as an output.
  Serial.begin(115200);
  delay(10);

#if 1
  if (!SPIFFS.begin(true)) {
    Serial.println("Failed to mount file system");
    return;
  }
  // list SPIFFS contents
  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);
  // sqlite3_close(db);

  delay(5000);
#endif

  wifi_sniffer_init();
  pinMode(LED_GPIO_PIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  //Serial.print("inside loop");
  delay(1000); // wait for a second

  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信道,以便逐个信道进行探测

四、实际效果
iShot_2023-08-03_23.13.20.png

在上述输出结果中,[N]表示启动后新探测到的设备,[O]表示已经探测到的设备间隔30秒再次探测到。

五、总结

好了,这篇只是一点小小的分享,如果确实感兴趣,可以深入了解,网上的资料很多的!

回帖(1)

sipower

2023-8-4 09:11:47
哈哈哈,被我猜中了
1 举报

更多回帖

发帖
×
20
完善资料,
赚取积分