SYNスキャンをするプログラム
HACKING:美しき策謀を読んでいるのですが、この本のネットワークの章で取り扱われてるlibnetライブラリは古いバージョンの1.0で、現在入手できるバージョンのものとは互換性がないようです。
構造体の名前や関数名とその仕様が一新されている部分が多く、この本の通りにプログラムを実装することはできません。
なので新しいバージョンのlibnetの使い方を調べた上で、今回は本の中でも紹介されているSYNスキャンを行うプログラムを作成しようと思います。SYNスキャンはターゲット上の特定のポートにSYNパケットを送り、SYN/ACKが帰ってきたらそのポートが空いていると判断するというものです。
通常通りの3wayハンドシェイクの流れですね。
今回のプログラムでは一応最後にRSTパケットを送信していますが、私の勉強不足もあり、この処理が有効に作用するものであるか正直自身がありません。
そのような者のプログラムですので、拙いものになりますが、あしからず。
#include<stdio.h> #include<libnet.h> #include<pcap.h> void usage(char *name){ fprintf(stderr, "usage: %s <target IP> <target port> <device>\n", name); } //libnetコンテキストを破棄 void quit(libnet_t *l){ libnet_destroy(l); printf("quit\n"); exit(0); } //受信したパケットにACKフラグが立っているか確認 int check_ctrflag(const u_char *header_start){ u_int header_size; const struct libnet_tcp_hdr *tcp_header; uint8_t ctrflag; tcp_header = (const struct libnet_tcp_hdr *)header_start; if(tcp_header->th_flags & TH_ACK) return 1; else return 0; } void pcap_fatal(const char *failed_in, const char *errbuf){ printf("fatal error: %s: %s\n", failed_in, errbuf); exit(1); } //キャプチャしている間に表示するスピナー(全く必要ない処理ですが何かカッコイイのでつけました) void efect(int second){ int cnt = 0; char mark[5] = {'|', '/', '-', '\\'}; printf("capturing packet...."); for(cnt=0; cnt<second; cnt++){ //\033[?25l はカーソルを非表示にする printf("\033[?25l"); //\e[1D でカーソルの左一文字を削除し、スピナーを上書きする fprintf(stdout, "%c\e[1D", mark[cnt%4]); fflush(stdout); usleep(80000); } // \e[2K\r で一行を削除し、行頭までカーソルを移動 fprintf(stdout, "\e[2K\r"); fflush(stdout); // \033[?25h でカーソルの表示を復活 printf("\n\033[?25h"); } int main(int argc, char *argv[]){ int flag = 0; libnet_t *l; char libnet_errbuf[LIBNET_ERRBUF_SIZE]; uint32_t dstip; u_int16_t dport; libnet_ptag_t tcp = 0; libnet_ptag_t ip = 0; struct pcap_pkthdr header; const u_char *packet; char pcap_errbuf[PCAP_ERRBUF_SIZE]; char *device = argv[3]; pcap_t *pcap_handle; if(argc != 4){ usage(argv[0]); exit(0); } //syn packetを作る l = libnet_init(LIBNET_RAW4, "enp0s8", libnet_errbuf); if(l==NULL){ fprintf(stderr, "Error opening context: %s", libnet_errbuf); exit(1); } dstip = libnet_name2addr4(l, argv[1], LIBNET_DONT_RESOLVE); dport = (u_int16_t)atoi(argv[2]); tcp = libnet_build_tcp( libnet_get_prand(LIBNET_PRu16), dport, libnet_get_prand(LIBNET_PRu32), libnet_get_prand(LIBNET_PRu32), TH_SYN, libnet_get_prand(LIBNET_PRu16), 0, 0, libnet_get_prand(LIBNET_PRu16), 0, 0, l, 0); if(tcp == -1){ fprintf(stderr, "Unable to build TCP header: %s\n", libnet_geterror(l)); exit(1); } ip = libnet_autobuild_ipv4( libnet_get_prand(LIBNET_PRu16), IPPROTO_TCP, dstip, l); if(ip == -1){ fprintf(stderr, "Unable to build IP header: %s\n", libnet_geterror(l)); exit(1); } pcap_handle = pcap_open_live(device, 4096, 0, 0, pcap_errbuf); if(pcap_handle == NULL) pcap_fatal("pcap_open_live", pcap_errbuf); if(libnet_write(l) == -1){ fprintf(stderr, "Unable to send packet: %s\n", libnet_geterror(l)); exit(1); } //1個目のパケットは自分が送ったパケットなので、2個目のパケットを受信 packet = pcap_next(pcap_handle, &header); efect(30); packet = pcap_next(pcap_handle, &header); int count =0; flag = check_ctrflag(packet+34); if(flag == 1){ printf("this port is open!\n"); //RSTパケットを送信 //正直ここはいらないかもしれない tcp = libnet_build_tcp( libnet_get_prand(LIBNET_PRu16), dport, libnet_get_prand(LIBNET_PRu32), libnet_get_prand(LIBNET_PRu32), TH_RST, libnet_get_prand(LIBNET_PRu16), 0, 0, libnet_get_prand(LIBNET_PRu16), 0, 0, l, 0); if(tcp == -1){ fprintf(stderr, "Unable to build TCP header: %s\n", libnet_geterror(l)); exit(1); } ip = libnet_autobuild_ipv4( libnet_get_prand(LIBNET_PRu16), IPPROTO_TCP, dstip, l); if(ip == -1){ fprintf(stderr, "Unable to build IP header: %s\n", libnet_geterror(l)); exit(1); } if(libnet_write(l) == -1){ fprintf(stderr, "Unable to send packet: %s\n", libnet_geterror(l)); exit(1); } } else printf("this port is closed :(\n"); pcap_close(pcap_handle); libnet_destroy(l); return 0; }
libnetでパケットを送信するにはまず、libnet_init関数でlibnet_t型のlibnetコンテキストを作成しなければならないようです。
deviceにはインターフェース名を指定してください。(ifconfigでみれます)
その後、TCPヘッダ->IPヘッダの順にヘッダを作ります。
これにはlibnet_build_tcpやlibnet_build_ip関数を使用します。この関数にはそれぞれのヘッダ要素に値を設定していきますが、今回はほとんどの要素にlibnet_get_prand関数でランダムな値を設定しています。
ヘッダを作ったらlibnet_write関数で実際にパケットを送信します。エラーが発生すると-1を返すようです。
次はlibcapライブラリを使ってターゲットからの応答をキャプチャします。
libcapについては本で使われているバージョンと同じ記法で使えるようなので苦労しませんでした。なので省きます。
キャプチャしたパケットの先頭から14(etherヘッダの長さ) + 20(ipヘッダの長さ) = 34バイト分進んだ部分から最後までをTCPヘッダとして扱います。これは、libnetが用意するlibnet_tcp_hdr型に変換することで行います。
そしてlibnet_tcp_hdr構造体のth_flags要素とTH_ACKを&演算子で比較し、ACKフラグが立っているか確認します。
以上がSYNスキャンプログラムになります。このような処理はnmap -sSで簡単に行えますが、自分で作ってみると色々学べるものがあります。 今後も色々作って行こうと思います。
ここまで見ていただき、ありがとうございました。