/*- * Copyright (c) 2006, Andrea Bittau * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD: src/tools/tools/net80211/stumbler/stumbler.c,v 1.2 2009/07/24 15:31:22 sam Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //int hopfreq = 3*1000; // ms int hopfreq = 500; // ms int sig_reset = 1*1000; // ms int ioctl_s = -1; int bpf_s = -1; struct chan_info { int locked; int chan; struct ieee80211req ireq; struct timeval last_hop; } chaninfo; #define CRYPT_NONE 0 #define CRYPT_WEP 1 #define CRYPT_WPA1 2 #define CRYPT_WPA 3 #define CRYPT_WPA1_TKIP 4 #define CRYPT_WPA1_TKIP_PSK 5 #define CRYPT_WPA1_CCMP 6 #define CRYPT_WPA1_CCMP_PSK 7 #define CRYPT_80211i 8 #define CRYPT_80211i_TKIP 9 #define CRYPT_80211i_TKIP_PSK 10 struct node_info { unsigned char mac[6]; int signal; int noise; int max; unsigned char ssid[256]; int chan; int wep; int pos; int ap; struct timeval seen; struct node_info* prev; struct node_info* next; } *nodes = NULL; void clean_crap() { struct node_info* next; if (ioctl_s != -1) close(ioctl_s); if (bpf_s != -1) close(bpf_s); while (nodes) { next = nodes->next; free(nodes); nodes = next; } } char* mac2str(unsigned char* mac) { static char ret[6*3]; sprintf(ret, "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return ret; } char* wep2str(int w) { char* wep = NULL; static char res[14]; switch (w) { case CRYPT_NONE: wep = ""; break; case CRYPT_WEP: wep = "WEP"; break; case CRYPT_WPA1: wep = "WPA1"; break; case CRYPT_WPA: wep = "WPA?"; break; case CRYPT_WPA1_TKIP: wep = "WPA1-TKIP"; break; case CRYPT_WPA1_TKIP_PSK: wep = "WPA1-TKIP-PSK"; break; case CRYPT_WPA1_CCMP: wep = "WPA1-CCMP"; break; case CRYPT_WPA1_CCMP_PSK: wep = "WPA1-CCMP-PSK"; break; case CRYPT_80211i: wep = "i"; break; case CRYPT_80211i_TKIP: wep = "11i-TKIP"; break; case CRYPT_80211i_TKIP_PSK: wep = "11i-TKIP-PSK"; break; default: wep = "FIXME!"; break; } memset(res, ' ', sizeof(res)); assert(strlen(wep) < sizeof(res)); memcpy(res, wep, strlen(wep)); res[sizeof(res)-1] = 0; return res; } char* ssid2str(struct node_info* node) { static char res[24]; memset(res, ' ', sizeof(res)); res[0] = '['; strcpy(&res[sizeof(res)-2], "]"); if (node->ap) { int left = sizeof(res) - 3; if (strlen(node->ssid) < left) left = strlen(node->ssid); memcpy(&res[1], node->ssid, left); } else { memcpy(&res[1], "", 8); } return res; } void save_state() { FILE* f; struct node_info* node = nodes; f = fopen("stumbler.log", "w"); if (!f) { perror("fopen()"); exit(1); } while (node) { struct tm* t; char tim[16]; t = localtime( (time_t*) &node->seen.tv_sec); if (!t) { perror("localtime()"); exit(1); } tim[0] = 0; strftime(tim, sizeof(tim), "%H:%M:%S", t); fprintf(f, "%s %s %s %2d %s 0x%.2x\n", tim, mac2str(node->mac), wep2str(node->wep), node->chan, ssid2str(node), node->max); node = node->next; } fclose(f); } void cleanup(int x) { endwin(); clean_crap(); exit(0); } void die(int p, char* msg) { endwin(); if (p) perror(msg); else printf("%s\n", msg); clean_crap(); exit(1); } void display_chan() { int x, y; char tmp[3]; x = COLS - 2; y = LINES - 1; snprintf(tmp, sizeof(tmp), "%.2d", chaninfo.chan); mvaddstr(y, x, tmp); refresh(); } void set_chan(int c) { chaninfo.ireq.i_val = c; if (ioctl(ioctl_s, SIOCS80211, &chaninfo.ireq) == -1) die(1, "ioctl(SIOCS80211) [chan]"); chaninfo.chan = c; if (gettimeofday(&chaninfo.last_hop, NULL) == -1) die(1, "gettimeofday()"); display_chan(); } void setup_if(char *dev) { struct ifreq ifr; unsigned int flags; // set chan memset(&chaninfo.ireq, 0, sizeof(chaninfo.ireq)); strcpy(chaninfo.ireq.i_name, dev); chaninfo.ireq.i_type = IEEE80211_IOC_CHANNEL; set_chan(1); // set iface up and promisc memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, dev); if (ioctl(ioctl_s, SIOCGIFFLAGS, &ifr) == -1) die(1, "ioctl(SIOCGIFFLAGS)"); flags = (ifr.ifr_flags & 0xffff) | (ifr.ifr_flagshigh << 16); flags |= IFF_UP | IFF_PPROMISC; memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, dev); ifr.ifr_flags = flags & 0xffff; ifr.ifr_flagshigh = flags >> 16; if (ioctl(ioctl_s, SIOCSIFFLAGS, &ifr) == -1) die(1, "ioctl(SIOCSIFFLAGS)"); } void open_bpf(char *dev, int dlt) { int i; char buf[64]; int fd = -1; struct ifreq ifr; for(i = 0;i < 16; i++) { sprintf(buf, "/dev/bpf%d", i); fd = open(buf, O_RDWR); if(fd < 0) { if(errno != EBUSY) die(1,"can't open /dev/bpf"); continue; } else break; } if(fd < 0) die(1, "can't open /dev/bpf"); strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)-1); ifr.ifr_name[sizeof(ifr.ifr_name)-1] = 0; if(ioctl(fd, BIOCSETIF, &ifr) < 0) die(1, "ioctl(BIOCSETIF)"); if (ioctl(fd, BIOCSDLT, &dlt) < 0) die(1, "ioctl(BIOCSDLT)"); i = 1; if(ioctl(fd, BIOCIMMEDIATE, &i) < 0) die(1, "ioctl(BIOCIMMEDIATE)"); bpf_s = fd; } void user_input() { static char chan[3]; static int pos = 0; int c; c = getch(); switch (c) { case 'w': save_state(); break; case 'q': cleanup(0); break; case 'c': chaninfo.locked = !chaninfo.locked; break; case ERR: die(0, "getch()"); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': chan[pos++] = c; if (pos == 2) { int ch = atoi(chan); if (ch <= 11 && ch >= 1) { set_chan(atoi(chan)); chaninfo.locked = 1; } pos = 0; } break; default: pos = 0; break; } } void display_node(struct node_info* node) { int x = 0; int y = 0; int i; char chan[3]; char* ssid = NULL; int sig, max, left, noise; char* wep = NULL; y = node->pos; if (y == -1) // offscreen return; assert(y < LINES); // MAC mvaddstr(y, x, mac2str(node->mac)); x += 6*3; // WEP wep = wep2str(node->wep); assert(wep); mvaddstr(y, x, wep); x += strlen(wep); x++; // CHAN sprintf(chan, "%.2d", node->chan); mvaddstr(y, x, chan); x += 3; // ssid ssid = ssid2str(node); assert(ssid); mvaddstr(y, x, ssid); x += strlen(ssid); x++; left = COLS - x - 1; sig = (int) ( ((double)node->signal)*left/100.0 ); noise=(int) ( ((double)node->noise)*left/100.0 ); max = (int) ( ((double)node->max)*left/100.0 ); // SIGNAL BAR for (i = 0; i < noise; i++) mvaddch(y, x++, 'N'); for (; i < sig; i++) mvaddch(y,x++, 'X'); for (; i < max; i++) mvaddch(y,x++, ' '); mvaddch(y,x++, '|'); for (; x < COLS-1; x++) mvaddch(y, x, ' '); assert (x <= COLS); } void update_node(struct node_info* data) { struct node_info* node; int sort = 0; assert(data->signal <= 100); node = nodes; // first time [virgin] if (!node) { node = (struct node_info*) malloc(sizeof(struct node_info)); if (!node) die(1, "malloc()"); memset(node, 0, sizeof(*node)); memcpy(node->mac, data->mac, 6); nodes = node; } while (node) { // found it if (memcmp(node->mac, data->mac, 6) == 0) break; // end of chain if (!node->next) { node->next = (struct node_info*) malloc(sizeof(struct node_info)); if (!node->next) die(1, "malloc()"); memset(node->next, 0, sizeof(*node->next)); memcpy(node->next->mac, data->mac, 6); node->next->prev = node; node->next->pos = node->pos+1; node = node->next; if (node->pos == LINES) sort = 1; break; } node = node->next; } assert(node); // too many nodes for screen if (sort) { struct node_info* ni = nodes; while (ni) { if (ni->pos != -1) ni->pos--; display_node(ni); ni = ni->next; } } node->signal = data->signal; if (data->signal > node->max) node->max = data->signal; if (gettimeofday(&node->seen, NULL) == -1) die(1, "gettimeofday()"); if (data->ssid[0] != 0) strcpy(node->ssid, data->ssid); if (data->chan != -1) node->chan = data->chan; if (data->wep != -1) { // XXX LAME --- won't detect if AP changes WEP mode in // beacons... if (node->wep != CRYPT_WEP && node->wep != CRYPT_NONE && data->wep == CRYPT_WEP) { } else node->wep = data->wep; } if (data->ap != -1) node->ap = data->ap; display_node(node); refresh(); } void get_beacon_info(unsigned char* data, int rd, struct node_info* node) { int blen = 8 + 2 + 2; strcpy(node->ssid, ""); node->chan = 0; node->wep = CRYPT_NONE; assert(rd >= blen); if (IEEE80211_BEACON_CAPABILITY(data) & IEEE80211_CAPINFO_PRIVACY) node->wep = CRYPT_WEP; data += blen; rd -= blen; while (rd > 2) { int eid, elen; eid = *data; data++; elen = *data; data++; rd -= 2; // short! if (rd < elen) { return; } // ssid if (eid == 0) { if (elen == 1 && data[0] == 0) { // hidden } else { memcpy(node->ssid, data, elen); node->ssid[elen] = 0; } } // chan else if(eid == 3) { // weird chan! if( elen != 1) goto next; node->chan = *data; } // WPA else if (eid == 221 && node->wep == CRYPT_WEP) { struct ieee80211_ie_wpa* wpa; wpa = (struct ieee80211_ie_wpa*) data; if (elen < 6) goto next; if (!memcmp(wpa->wpa_oui, "\x00\x50\xf2", 3)) { // node->wep = CRYPT_WPA; } else goto next; if (wpa->wpa_type == WPA_OUI_TYPE && le16toh(wpa->wpa_version) == WPA_VERSION) { int cipher, auth; unsigned char* ptr; node->wep = CRYPT_WPA1; if (elen < 12) goto next; cipher = ((unsigned char*) wpa->wpa_mcipher)[3]; ptr = (unsigned char*)wpa + 12 + 4 * le16toh(wpa->wpa_uciphercnt); if (elen < (ptr - data + 6)) goto next; if ( *((unsigned short*) ptr) == 0) goto next; ptr += 2 + 3; auth = *ptr; if (cipher == WPA_CSE_TKIP) { node->wep = CRYPT_WPA1_TKIP; if (auth == WPA_ASE_8021X_PSK) node->wep = CRYPT_WPA1_TKIP_PSK; } if (cipher == WPA_CSE_CCMP) { node->wep = CRYPT_WPA1_CCMP; if (auth == WPA_ASE_8021X_PSK) node->wep = CRYPT_WPA1_CCMP_PSK; } } } else if (eid == 48 && node->wep == CRYPT_WEP) { unsigned char* ptr; // XXX no bounds checking ptr = data; if (ptr[0] == 1 && ptr[1] == 0) { unsigned short* count; int cipher = 0; ptr += 2; node->wep = CRYPT_80211i; if (!memcmp(ptr, "\x00\x0f\xac\x02", 4)) { node->wep = CRYPT_80211i_TKIP; cipher = 1; } ptr += 4; count = (unsigned short*) ptr; ptr +=2 + *count*4; count = (unsigned short*) ptr; if (*count) { ptr += 2; if (!memcmp(ptr,"\x00\x0f\xac\x02", 4)) { if (cipher) node->wep = CRYPT_80211i_TKIP_PSK; } } } } next: data += elen; rd -= elen; } } int get_packet_info(struct ieee80211_frame* wh, unsigned char* body, int bodylen, struct node_info* node) { int type, stype; node->chan = chaninfo.chan; node->wep = -1; node->ssid[0] = 0; node->ap = -1; type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; if (type == IEEE80211_FC0_TYPE_CTL) return 0; #if 0 if (wh->i_addr2[0] != 0) { mvprintw(30,30,"%s %x",mac2str(wh->i_addr2), wh->i_fc[0]); } #endif stype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; if (type == IEEE80211_FC0_TYPE_MGT && stype == IEEE80211_FC0_SUBTYPE_BEACON) { get_beacon_info(body, bodylen, node); node->ap = 1; } else if (type == IEEE80211_FC0_TYPE_DATA && stype == IEEE80211_FC0_SUBTYPE_DATA) { if (wh->i_fc[1] & IEEE80211_FC1_WEP) { unsigned char* iv; node->wep = CRYPT_WEP; iv = body; iv += 3; // extended IV? if (*iv & (1 << 1)) { #if 0 node->wep = CRYPT_WPA; mvprintw(20,20, "shei"); exit(1); #endif } } if (wh->i_fc[1] & IEEE80211_FC1_DIR_FROMDS) node->ap = 1; else node->ap = 0; } memcpy(node->mac, wh->i_addr2, 6); return 1; } void radiotap(unsigned char* data, int rd) { struct ieee80211_radiotap_header* rth; struct ieee80211_frame* wh; char* body; struct node_info node; int8_t signal_dbm, noise_dbm; uint8_t signal_db, noise_db; int dbm = 0; int signal = 0; int i; rd -= 4; // 802.11 CRC // radiotap rth = (struct ieee80211_radiotap_header*) data; // 802.11 wh = (struct ieee80211_frame*) ((char*)rth + rth->it_len); rd -= rth->it_len; assert (rd >= 0); // body body = (char*) wh + sizeof(*wh); rd -= sizeof(*wh); if (!get_packet_info(wh, body, rd, &node)) return; // signal and noise body = (char*) rth + sizeof(*rth); signal_dbm = noise_dbm = signal_db = noise_db = 0; for (i = IEEE80211_RADIOTAP_TSFT; i <= IEEE80211_RADIOTAP_EXT; i++) { if (!(rth->it_present & (1 << i))) continue; switch (i) { case IEEE80211_RADIOTAP_TSFT: body += sizeof(uint64_t); break; case IEEE80211_RADIOTAP_FLAGS: case IEEE80211_RADIOTAP_RATE: body += sizeof(uint8_t); break; case IEEE80211_RADIOTAP_CHANNEL: body += sizeof(uint16_t)*2; break; case IEEE80211_RADIOTAP_FHSS: body += sizeof(uint16_t); break; case IEEE80211_RADIOTAP_DBM_ANTSIGNAL: signal_dbm = *body; body++; dbm = 1; break; case IEEE80211_RADIOTAP_DBM_ANTNOISE: noise_dbm = *body; body++; break; case IEEE80211_RADIOTAP_DB_ANTSIGNAL: signal_db = *((unsigned char*)body); body++; break; case IEEE80211_RADIOTAP_DB_ANTNOISE: noise_db = *((unsigned char*)body); body++; break; case IEEE80211_RADIOTAP_EXT: abort(); break; } } if (dbm) { signal = signal_dbm - noise_dbm; } else { signal = signal_db - noise_db; } if (signal < 0) signal = 0; node.signal = signal; #if 0 if (node.signal > 100 || node.signal < 0) { mvprintw(25,25, "sig=%d", node.signal); } #else assert (node.signal <= 100 && node.signal >= 0); #endif update_node(&node); } void bpf_input() { static unsigned char buf[4096]; int rd; struct bpf_hdr* bpfh; unsigned char* data; rd = read(bpf_s, buf, sizeof(buf)); if (rd == -1) die(1,"read()"); bpfh = (struct bpf_hdr*) buf; rd -= bpfh->bh_hdrlen; if (rd != bpfh->bh_caplen) { assert( rd > bpfh->bh_caplen); rd = bpfh->bh_caplen; } data = (unsigned char*) bpfh + bpfh->bh_hdrlen; radiotap(data, rd); } unsigned long elapsed_ms(struct timeval* now, struct timeval* prev) { unsigned long elapsed = 0; if (now->tv_sec > prev->tv_sec) elapsed = 1000*1000 - prev->tv_usec + now->tv_usec; else { assert(now->tv_sec == prev->tv_sec); elapsed = now->tv_usec - prev->tv_usec; } elapsed /= 1000; //ms elapsed += (now->tv_sec - prev->tv_sec)*1000; return elapsed; } void chanhop(struct timeval* tv) { unsigned long elapsed = 0; if (gettimeofday(tv, NULL) == -1) die(1, "gettimeofday()"); elapsed = elapsed_ms(tv, &chaninfo.last_hop); // need to chan hop if (elapsed >= hopfreq) { int c; c = chaninfo.chan + 1; if (c > 11) c = 1; set_chan(c); elapsed = hopfreq; } // how much can we sleep? else { elapsed = hopfreq - elapsed; } // ok calculate sleeping time... tv->tv_sec = elapsed/1000; tv->tv_usec = (elapsed - tv->tv_sec*1000)*1000; } void check_seen(struct timeval* tv) { unsigned long elapsed = 0; struct timeval now; int need_refresh = 0; unsigned long min_wait = 0; unsigned long will_wait; will_wait = tv->tv_sec*1000+tv->tv_usec/1000; min_wait = will_wait; struct node_info* node = nodes; if (gettimeofday(&now, NULL) == -1) die(1, "gettimeofday()"); while(node) { if (node->signal) { elapsed = elapsed_ms(&now, &node->seen); // node is dead... if (elapsed >= sig_reset) { node->signal = 0; display_node(node); need_refresh = 1; } // need to check soon possibly... else { unsigned long left; left = sig_reset - elapsed; if (left < min_wait) left = min_wait; } } node = node->next; } if (need_refresh) refresh(); // need to sleep for less... if (min_wait < will_wait) { tv->tv_sec = min_wait/1000; tv->tv_usec = (min_wait - tv->tv_sec*1000)*1000; } } void own(char* ifname) { int rd; fd_set fds; struct timeval tv; int dlt = DLT_IEEE802_11_RADIO; hopfreq = 1000; setup_if(ifname); open_bpf(ifname, dlt); while(1) { // XXX innefficient all of this... if (!chaninfo.locked) chanhop(&tv); else { tv.tv_sec = 1; tv.tv_usec = 0; } // especially this... check_seen(&tv); FD_ZERO(&fds); FD_SET(0, &fds); FD_SET(bpf_s, &fds); rd = select(bpf_s+1, &fds,NULL , NULL, &tv); if (rd == -1) die(1, "select()"); if (FD_ISSET(0, &fds)) user_input(); if (FD_ISSET(bpf_s, &fds)) bpf_input(); } } void init_globals() { ioctl_s = socket(PF_INET, SOCK_DGRAM, 0); if (ioctl_s == -1) { perror("socket()"); exit(1); } chaninfo.locked = 0; chaninfo.chan = 0; } int main(int argc, char *argv[]) { if (argc < 2) { printf("Usage: %s \n", argv[0]); exit(1); } init_globals(); initscr(); cbreak(); noecho(); nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE); curs_set(0); clear(); refresh(); signal(SIGINT, cleanup); signal(SIGTERM, cleanup); own(argv[1]); cleanup(0); exit(0); }