【嵌入式開發】自定義AT指令實現sniffer網路嗅探功能
基礎
該功能是在NON-OS SDK下實現的。
Non-OS SDK 是不不基於作業系統的 SDK,提供 IOT_Demo 和 AT 的編譯。Non-OS SDK 主要使⽤用定時器和回撥函式的方式實現各個功能事件的巢狀,達到特定條件下觸發特定功能函式的目的。Non-OS SDK 使用 espconn 介面實現網路操作,使用者需要按照 espconn 介面的使用規則進行軟體開發。
下面給出官方文件中的SDK使用流程圖:
這裡就不過多闡述如何編譯並燒寫韌體的問題,稍微提兩點:
其一就是如果大家要編譯example裡的檔案
記得將對應的資料夾從example中拖到主目錄下,比如我寫好的韌體叫作at
其二就是關於AT韌體的燒寫格式(注意文件中用到的是Mbit,32Mbit = 4 MB):
自定義AT指令
自定義 AT 指令命名時,使用英文字元,請勿使⽤用其他特殊字元或數字。 AT 基於 ESP8266_NONOS_SDK 編譯,ESP8266_NONOS_SDK/example/at
中提供了開發者自定 AT指令的示例。樂鑫原本提供的 AT 指令以庫檔案 libat.a
的形式提供,將包含在編譯⽣生成的 AT BIN 韌體中。
首先是參照樣例程式註冊我們的AT指令
extern void at_exeCmdCiupdate(uint8_t id );
at_funcationType at_custom_cmd[] = {
{"+TEST", 5, at_testCmdTest, at_queryCmdTest, at_setupCmdTest, at_exeCmdTest},
{"+MACSNIFFER", 11, at_testMacSniffer, NULL, NULL, at_exeMacSniffer}
};
第一個引數是我們命令的名字,如上圖,執行韌體後傳送AT+MACSNIFFER
樣式的命令即可執行我們的程式。
第二個引數 11
是命令字串的長度。
而一條指令可以有4種類型,下面給出官方文件截圖:
由於我們只需要測試指令和執行指令,所以只在引數3和6裡填上對應的函式即可,其餘填入NULL
。
然後需要在user_init(void)
裡註冊我們的AT指令
//註冊使用者自定義的 AT 指令
at_cmd_array_regist(&at_custom_cmd[0], sizeof(at_custom_cmd)/sizeof(at_custom_cmd[0]));
接著是實現我們的函式,這裡只給出執行函式的實現
void ICACHE_FLASH_ATTR
at_exeMacSniffer(uint8_t id)
{
uint8 buffer[32] = {0};
os_sprintf(buffer, "\r\n");
at_port_print(buffer);
at_getMac();
//at_response_ok();
}
為什麼要有 ICACHE_FLASH_ATTR, 文件如是說:
新增了ICACHE_FLASH_ATTR
的程式碼通常比使用IRAM_ATTR
標記的程式碼執行得慢。然而, 像大多數嵌入式平臺一樣,ESP8266 的 iRAM 空間有限,因此建議一般程式碼新增 ICACHE_FLASH_ATTR
,僅對執行行效率要求高的程式碼新增 ICACHE_FLASH_ATTR
巨集
這裡我沒讓我的函式立刻response ok, 因為是使用回撥的機制,我不想在我沒給出結果的時候就返回OK,同時我也不想在我執行我的指令的時候,還能輸入別的AT指令,所以我還需要接著新增一句程式碼:at_enter_special_state()
,這句程式碼保證了在我的指令執行期間,其他指令輸入時,命令列返回busy
。at_getMac()
定義如下:
void ICACHE_FLASH_ATTR
at_getMac()
{
at_enter_special_state();
// Promiscuous works only with station mode
wifi_set_opmode(STATION_MODE);
#ifdef DEBUG
uart_init(115200, 115200);
UART_SetPrintPort(0);
#endif
// Scan all aps.
struct scan_config scanCof;
os_bzero(&scanCof, sizeof(struct scan_config));
scanCof.show_hidden = 1;
wifi_station_scan(&scanCof, wifi_scan_done_cb);
}
其中由於嗅探只能執行在STATION_MODE
模式下,所以要用wifi_set_opmode(STATION_MODE)
進行設定。
然後我們需要對嗅探的頻道進行設定,由於我們其實並不需要嗅探那些WIFI不支援的頻道(我們只需要對範圍內WIFI所支援的頻道進行嗅探),我們需要獲取WIFI的頻道資訊,相應的API定義如下:
依然要用到回撥的機制,所以我們繼續完成下一個回撥函式,該函式主要是對頻道進行篩選,然後設定更換頻道的定時器,保證掃描總時長為10秒,每掃描到一個mac執行promisc_cb()
回撥。
void ICACHE_FLASH_ATTR
wifi_scan_done_cb(void *arg, STATUS status)
{
queue_i = 0;
os_memset(temp_mac, 0, 30 * sizeof(uint8_t));
if (status != OK) {
os_printf("Err: wifi_scan_done_cb status error.\r\n");
at_leave_special_state();
at_response_error();
return;
}
struct bss_info *bss = (struct bss_info *)arg;
bss = STAILQ_NEXT(bss, next); //ignore first
int bss_count = 0;
while (bss)
{
if (bss->channel != 0)
{
channel_check[bss->channel] = 1;
}
bss = STAILQ_NEXT(bss, next);
}
int i;
#ifdef DEBUG
os_printf("Channel check: ");
#endif
for (i = 1; i < 14; i++) {
#ifdef DEBUG
os_printf("%d ", channel_check[i]);
#endif
if (channel_check[i]) bss_count++;
}
#ifdef DEBUG
os_printf("\r\n");
#endif
//os_printf("Total bss count: %d\r\n", bss_count);
wifi_set_channel(1);
wifi_promiscuous_enable(0);
wifi_set_promiscuous_rx_cb(promisc_cb);
wifi_promiscuous_enable(1);
channel_i = 1;
uint32_t timeInterval = 10000 / bss_count;
#ifdef DEBUG
os_printf("bss_count: %d timeInterval: %d\r\n", bss_count, timeInterval);
#endif
os_timer_disarm(&channelHop_timer);
os_timer_setfn(&channelHop_timer, (os_timer_func_t *) channelHop, NULL);
os_timer_arm(&channelHop_timer, timeInterval, 1);
}
接著完成每檢測到一個mac的回撥,這裡首先需要判斷包的長度,然後通過判斷報文裡的內容判斷包是否為重傳,重傳的不要,包的型別,以及由於我要篩選掉路由器的mac和由ARP協助產生的 ff:ff:ff:ff:ff:ff mac地址,同時使獲得的MAC不重複,我做了多重過濾。
static void ICACHE_FLASH_ATTR
promisc_cb(uint8_t *buf, uint16_t len)
{
if (len == 12){
struct RxControl *sniffer = (struct RxControl*) buf;
} else if (len == 128) {
struct sniffer_buf2 *sniffer = (struct sniffer_buf2*) buf;
} else {
FrameCtrlPtr frame_ctrl_ptr = NULL;
frame_ctrl_ptr =
(FrameCtrlPtr)(buf + sizeof(struct RxControl));
if (frame_ctrl_ptr->type != FRAME_TYPE_DATA)
{
return;
}
// Ignore retry package.
if (frame_ctrl_ptr->retry == 1) return;
struct sniffer_buf *sniffer = (struct sniffer_buf*) buf;
int i=0;
// Check MACs
// AP->Station, get station
if (frame_ctrl_ptr->fromDS == 1)
{
// ignore ff:ff:ff:ff:ff:ff
if(0==os_memcmp(noused_mac, &sniffer->buf[4], 6)){
return;
}
if (NULL == macListHead)
{
macListHead = (macListNote *)os_malloc(sizeof(macListNote));
for (i = 0; i < 6; i++)
{
macListHead->mac[i] = sniffer->buf[i+4];
}
macListHead->rssiSum = (int)(sniffer->rx_ctrl.rssi);
macListHead->channel = sniffer->rx_ctrl.channel;
macListHead->repeatCount = 1;
macListHead->next = NULL;
}
else
{
macListNote *p = macListHead;
while(p != NULL) {
if (0==os_memcmp(p->mac, &sniffer->buf[4], 6))
{
p->rssiSum = p->rssiSum + (int)(sniffer->rx_ctrl.rssi);
p->repeatCount++;
return;
}
if (p->next == NULL)
{
p->next = (macListNote *)os_malloc(sizeof(macListNote));
p = p->next;
for (i = 0; i < 6; i++)
{
p->mac[i] = sniffer->buf[i+4];
}
p->rssiSum = (int)(sniffer->rx_ctrl.rssi);
p->channel = sniffer->rx_ctrl.channel;
p->repeatCount = 1;
p->next = NULL;
}
p = p->next;
}
}
return;
}
// Station->AP, get station
if (frame_ctrl_ptr->toDS == 1)
{
// ignore ff:ff:ff:ff:ff:ff
if(0==os_memcmp(noused_mac, &sniffer->buf[10], 6)){
return;
}
if (NULL == macListHead)
{
macListHead = (macListNote *)os_malloc(sizeof(macListNote));
for (i = 0; i < 6; i++)
{
macListHead->mac[i] = sniffer->buf[i+10];
}
macListHead->rssiSum = (int)(sniffer->rx_ctrl.rssi);
macListHead->channel = sniffer->rx_ctrl.channel;
macListHead->repeatCount = 1;
macListHead->next = NULL;
}
else
{
macListNote *p = macListHead;
while(p != NULL) {
if (0==os_memcmp(p->mac, &sniffer->buf[10], 6))
{
p->rssiSum = p->rssiSum + (int)(sniffer->rx_ctrl.rssi);
p->repeatCount++;
return;
}
if (p->next == NULL)
{
p->next = (macListNote *)os_malloc(sizeof(macListNote));
p = p->next;
for (i = 0; i < 6; i++)
{
p->mac[i] = sniffer->buf[i+10];
}
p->rssiSum = (int)(sniffer->rx_ctrl.rssi);
p->channel = sniffer->rx_ctrl.channel;
p->repeatCount = 1;
p->next = NULL;
}
p = p->next;
}
}
return;
}
}
}
由於不能使用STL庫進行重複性判斷,所以自己實現了個單向連結串列來判斷重複,同時將重複獲得的MAC的RSSI進行了求平均的操作,連結串列結構如下:
struct macListNote {
uint8_t mac[6];
int rssiSum;
unsigned channel:4;
uint8_t repeatCount;
struct macListNote *next;
};
typedef struct macListNote macListNote;
當然,關於收到包的結構體,參考ESP8266 技術參考裡的 Sniffer應用設計說明 ,我們需要定義如下結構體:
struct RxControl {
signed rssi:8; // signal intensity of packet
unsigned rate:4;
unsigned is_group:1;
unsigned:1;
unsigned sig_mode:2; // 0:is 11n packet; 1:is not 11n packet;
unsigned legacy_length:12; // if not 11n packet, shows length of packet.
unsigned damatch0:1;
unsigned damatch1:1;
unsigned bssidmatch0:1;
unsigned bssidmatch1:1;
unsigned MCS:7; // if is 11n packet, shows the modulation
// and code used (range from 0 to 76)
unsigned CWB:1; // if is 11n packet, shows if is HT40 packet or not
unsigned HT_length:16; // if is 11n packet, shows length of packet.
unsigned Smoothing:1;
unsigned Not_Sounding:1;
unsigned:1;
unsigned Aggregation:1;
unsigned STBC:2;
unsigned FEC_CODING:1; // if is 11n packet, shows if is LDPC packet or not.
unsigned SGI:1;
unsigned rxend_state:8;
unsigned ampdu_cnt:8;
unsigned channel:4; // which channel this packet in.
unsigned:12;
};
struct FrameCtrl {
uint8 protocol:2;
uint8 type:2;
uint8 subtype:4;
uint8 toDS:1;
uint8 fromDS:1;
uint8 moreFlag:1;
uint8 retry:1;
uint8 pwrMgmt:1;
uint8 moreData:1;
uint8 protectedframe:1;
uint8 order:1;
};
struct LenSeq {
uint16_t length;
uint16_t seq;
uint8_t address3[6];
};
struct sniffer_buf {
struct RxControl rx_ctrl;
uint8_t buf[36];
uint16_t cnt;
struct LenSeq lenseq[1];
};
struct sniffer_buf2{
struct RxControl rx_ctrl;
uint8_t buf[112];
uint16_t cnt;
uint16_t len;
};
//record wifi AP's info
struct ApInfo
{
char ssid[33];
uint8_t bssid[6];//MAC address
uint8_t channel;
struct ApInfo *next;
};