[ UDP Packet Spoofing ] 動作確認はすべてLinuxで行っています。 UDPはコネクションを確立しない、ただパケットを送信するだけなのだ。よっ て送信元アドレスを変更しても問題無くパケットは届くのではないだろうか。 つまりは送信元アドレスを偽装してもデータの送信をする上では問題無いはず だ。 まず RFC 768 を読む。 0 7 8 15 16 23 24 31 +--------+--------+--------+--------+ | Source | Destination | | Port | Port | +--------+--------+--------+--------+ | | | | Length | Checksum | +--------+--------+--------+--------+ | | | data | +-----------------------------------+ UDP Header Format Source Port 送信元のポート番号 Destination Port 送信先のポート番号 Length UDPヘッダを含めたデータの長さ Checksum UDPデータが壊れていないかどうかを判断するチェックサムフィールド。 送信Port(SourcePort)、宛先Port(DestinationPort)、UDPデータグラムの長さ (Length)、チェックサム(Checksum)、データ(data)ということなので、仮に 30000Port を使用してターゲットサーバの 135Port に"TEST"という文字列デー タを送信するならば SourcePort 0x75, 0x30 (30000) DestinationPort 0x00, 0x87 (135) Length 0x00, 0x0c (12) Checksum 0x00, 0x00 (0) data 0x54, 0x45, 0x53, 0x54 (TEST) つまり unsigned char data[]= { 0x75, 0x30, 0x00, 0x87, 0x00, 0x0c, 0x00, 0x00, 0x54, 0x45, 0x53, 0x54 } というデータを送信すればいいことになる。 まず、ちゃんとデータが送られているか確認するためにサーバを書く。といっ てもこれは今回のキモの部分ではないので以前書いたやつをカスタマイズして 使う。仕様は標準出力にパケットの送信元とデータを表示する、といった簡単 なもの。 udp_serv.c ------------------------------------------------------------------------------ #include #include #define PORT 5000 int main(void) { int sock; struct sockaddr_in sa; struct sockaddr_in ca; int len, ret; char buf[1024]; if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ perror("socket"); exit(1); } memset((char *)&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = INADDR_ANY; sa.sin_port = htons(PORT); if(bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0){ perror("bind"); exit(1); } len = sizeof(ca); while(1){ if((ret = recvfrom(sock, buf, 1024, 0, (struct sockaddr *)&ca, &len)) < 0){ perror("recvform"); exit(1); } if(ret > 0) printf("IP=%s\nDATA=%s\n",inet_ntoa(ca.sin_addr), buf); } return 0; } ------------------------------------------------------------------------------ udp]$ gcc udp_serv.c -o udp_serv udp]$ ./udp_serv Cで書いたが特に意味は無い。簡潔にPerlでかいてもよい。 udp_serv.pl ------------------------------------------------------------------------------ #!/usr/bin/perl use Socket; $port = 5000; socket(SOCK, PF_INET, SOCK_DGRAM, getprotobyname('udp')) or die"Error:socket.\n"; bind(SOCK, sockaddr_in($port, INADDR_ANY)) or die "Error:bind.\n"; while(1){ $sockaddr = recv(SOCK, $buff, 64, 0); ($rport, $addr) = sockaddr_in($sockaddr); $ip = inet_ntoa($addr); print "-----------------------------\n" print "from: $ip \n"; print "data: $buff \n"; print "-----------------------------\n\n" } ------------------------------------------------------------------------------ udp]$ chmod 755 udp_serv.pl udp]$ ./udp_serv.pl さて準備は整った。 送信元を偽装するためにIPヘッダをこちら側で制御したいので rawsocket を 使用する。rawsocket を使用する場合は root でなくてはならないが、とりあ えずプログラムを書く。 まずはIPヘッダとUDPヘッダを書かないとダメだ。 unsigned char buf[]= { /* IP */ 0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0, 0, 0, 0, 0, 0, /* 送信元IP */ 0, 0, 0, 0, /* 送信先IP */ /* UDP */ 0, 0, 0, 0, /* それぞれのPort番号 */ 0x00, 0x0c, 0x00, 0x00, /* DATA */ 'T', 'E', 'S', 'T' }; 送信元IP、送信先IP、UDPのPort番号はあとで引数から渡すようにする。 Version=4[0100](4bits),IHL=5[0101](4bits)よって最初の1Byte(8bits)は 01000101 となる。これを16進数に変換すると 0x45 となる。次の1Byte(Type of Service)は無視してその次の2Bytesは Total Length である。IPヘッダを 含めたデータの長さであるが、これは数えてみればわかる。IP 20Bytes, UDP 8Bytes, DATA 4Bytes よって 32Bytes である。これを16進数に直して 0x20 と なる。Total Length は2Bytes使用するので 0x00 0x20 となる。これについて はUDPヘッダにも同じようなものがある。それが25Bytes目(UDPヘッダでの 5Bytes目)です。UDPヘッダを含めたデータの長さ(IPヘッダは含めない)をいれ ることになる。DATAが "TEST" なのでこの場合UDPヘッダを含めて12Bytesとなる。 よって 0x00 0x0c となる。 9Bytes目の Time to Live はTTLと呼ばれてるらしい。ルーティングデバイス を通過する度にこの値がひとつ減らされ 0 になると破棄される。ということ なので、最大値の 0xFF をいれとけばとりあえず問題無いだろう 。 さて必要最小限のこと(分かる範囲のこと)しかやっていないが果たして無事送 信できるだろうか。とりあえずはこれを元にプログラムを書いてみる。 引数には 送信元IP 送信元PORT 送信先IP 送信先PORT の4つを持たせることにする。 udp_spoof.c ------------------------------------------------------------------------------ #include #include #include #include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { int sock; struct sockaddr_in serv; struct hostent *he; unsigned char buf[] = { /* ip */ 0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* udp */ 0, 0, 0, 0, 0x00, 0x0c, 0x00, 0x00, /* data */ 'T', 'E', 'S', 'T' }; if(argc != 5){ fprintf(stderr, "#./program yourIP yourPORT targetIP targetPORT\n"); exit(1); } /* your ip */ if((he = gethostbyname(argv[1]))==NULL){ fprintf(stderr, "gethostbyname yourIP"); exit(1); } bcopy(*(he->h_addr_list), (buf+12), 4); /* target ip */ if((he = gethostbyname(argv[3]))==NULL){ fprintf(stderr, "gethostbyname targetIP"); exit(1); } bcopy(*(he->h_addr_list), (buf+16), 4); /* your port and target port */ *(unsigned short *)(buf+20)=htons((unsigned short)atoi(argv[2])); *(unsigned short *)(buf+22)=htons((unsigned short)atoi(argv[4])); serv.sin_family = AF_INET; bcopy(he->h_addr, &(serv.sin_addr), sizeof(struct in_addr)); if((sock=socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0){ perror("socket"); exit(1); } if((sendto(sock, &buf, sizeof(buf), 0, (struct sockaddr *)&serv, sizeof(struct sockaddr))) < 0){ perror("sendto"); exit(1); } fprintf(stderr, "ok!\n"); return 0; } ------------------------------------------------------------------------------ udp]$ gcc udp_spoof.c -o udp_spoof udp]$ あらかじめudp_servを起動しておく。 udp]$ ./udp_spoof 123.123.123.*** 30000 123.123.123.*** 5000 socket: Operation not permitted udp]$ su Password: root..udp]$ ./udp_spoof 123.123.123.*** 30000 123.123.123.*** 5000 ok! root..udp]$ さてちゃんと送れたかなと、サーバの方をみてみる。 udp]$ ./udp_serv どうやら送られてきてないようだ。ではさらにIPヘッダに必要なものがあるの だろうと推測しIPヘッダを調べてみる。 そしてIPヘッダに protocol というものがあると分かった。よく意味がわから ないが、DATA部のフォーマットを規定する。と書いてあるじゃないか。Linux の場合、 /etc/protocols に記述されてるらしい。 udp]$ vi /etc/protocols ................................. ................................. ................................. chaos 16 CHAOS # Chaos udp 17 UDP # user datagram protocol mux 18 MUX # Multiplexing protocol おお!発見!! udp は 17 と書かれてある。 17 を16進数にすると 0x11 なのでIPヘッダの protocol に 0x11 を追加してみる。 unsigned char buf[]= { /* ip */ 0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x11, 0, 0, /* 追加! */ 0, 0, 0, 0, 0, 0, 0, 0, /* udp */ 0, 0, 0, 0, 0x00, 0x0c, 0x00, 0x00, /* data */ 'T', 'E', 'S', 'T' }; ではもう一度、試してみる。 root..udp]$ gcc udp_spoof.c -o udp_spoof root..udp]$ ./udp_spoof 123.123.123.*** 30000 123.123.123.*** 5000 ok! root..udp]$ サーバ側をみてみると udp]$ ./udp_serv IP =123.123.123.*** DATA=TEST うん。ちゃんと受信されたようだ。 引数に任意のアドレスをいれればいくらでも偽装ができるようだ。 これで UDP Packet Spoofing が可能であることが分かった。 実際に使用したプログラムを載せる。(IPヘッダのProtocolを0x11に変えただけだが) udp_spoof.c ------------------------------------------------------------------------------ #include #include #include #include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { int sock; struct sockaddr_in serv; struct hostent *he; unsigned char buf[] = { 0x45, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x0c, 0x00, 0x00, 'T', 'E', 'S', 'T' }; if(argc != 5){ fprintf(stderr, "#./program yourIP yourPORT targetIP targetPORT\n"); exit(1); } if((he = gethostbyname(argv[1]))==NULL){ fprintf(stderr, "gethostbyname yourIP"); exit(1); } bcopy(*(he->h_addr_list), (buf+12), 4); if((he = gethostbyname(argv[3]))==NULL){ fprintf(stderr, "gethostbyname targetIP"); exit(1); } bcopy(*(he->h_addr_list), (buf+16), 4); *(unsigned short *)(buf+20)=htons((unsigned short)atoi(argv[2])); *(unsigned short *)(buf+22)=htons((unsigned short)atoi(argv[4])); serv.sin_family = AF_INET; bcopy(he->h_addr, &(serv.sin_addr), sizeof(struct in_addr)); if((sock=socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0){ perror("socket"); exit(1); } if((sendto(sock, &buf, sizeof(buf), 0, (struct sockaddr *)&serv, sizeof(struct sockaddr))) < 0){ perror("sendto"); exit(1); } fprintf(stderr, "ok!\n"); return 0; } ------------------------------------------------------------------------------ さて、IPヘッダやUDPヘッダを配列で扱うのもどうかと思うので、 まったく同じプログラムを構造体を使って書こうと思う。 netinet/ip.h ( /usr/include/netinet/ip.h ) netinet/udp.h ( /usr/include/netinet/udp.h ) をみてみるとそれぞれ便利そうな構造体が定義されてるようだ。 ip.h (抜粋) ------------------------------------------------------------------------------ struct iphdr { #if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int ihl:4; unsigned int version:4; #elif __BYTE_ORDER == __BIG_ENDIAN unsigned int version:4; unsigned int ihl:4; #else # error "Please fix " #endif u_int8_t tos; u_int16_t tot_len; u_int16_t id; u_int16_t frag_off; u_int8_t ttl; u_int8_t protocol; u_int16_t check; u_int32_t saddr; u_int32_t daddr; /*The options start here. */ }; ------------------------------------------------------------------------------ udp.h (抜粋) ------------------------------------------------------------------------------ struct udphdr { u_int16_t source; u_int16_t dest; u_int16_t len; u_int16_t check; }; ------------------------------------------------------------------------------ ではこれらを使用したプログラムを書いてみる。 udp_spoof2.c ------------------------------------------------------------------------------ #include #include #include #include #include #include #include #include struct _sendData{ struct iphdr ip; struct udphdr udp; unsigned char data[8]; } sendData; int main(int argc, char *argv[]) { int sock; struct sockaddr_in serv; struct hostent *he; if(argc != 5){ fprintf(stderr, "#./%s yourIP yourPORT targetIP targetPORT\n", argv[0]); exit(1); } sendData.ip.version = 4; sendData.ip.ihl = 5; sendData.ip.ttl = 255; sendData.ip.protocol = 17; sendData.udp.len = htons(12); sendData.udp.source = htons((unsigned short)atoi(argv[2])); sendData.udp.dest = htons((unsigned short)atoi(argv[4])); strcpy( sendData.data, "TEST" ); if((sendData.ip.saddr = inet_addr(argv[1])) < 0){ if((he = gethostbyname(argv[1]))==NULL){ perror("get your host"); exit(1); } bcopy(he->h_addr, (char *)&sendData.ip.saddr, he->h_length); } if((sendData.ip.daddr = inet_addr(argv[3])) < 0){ if((he = gethostbyname(argv[3]))==NULL){ perror("get target host"); exit(1); } bcopy(he->h_addr, (char *)&sendData.ip.daddr, he->h_length); } if((he = gethostbyname(argv[3]))==NULL){ perror("get target host 2"); exit(1); } serv.sin_family = AF_INET; bcopy(he->h_addr, &(serv.sin_addr), sizeof(struct in_addr)); if((sock=socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0){ perror("socket"); exit(1); } if((sendto(sock, &sendData, sizeof(sendData), 0, (struct sockaddr *)&serv, sizeof(struct sockaddr))) < 0){ perror("sendto"); exit(1); } fprintf(stderr, "ok!\n"); return 0; } ------------------------------------------------------------------------------ udp]$ gcc -Wall udp_spoof2.c -o udp_spoof2 udp]$ su Password: root..udp]$ ./udp_spoof2 123.123.123.*** 30000 123.123.123.*** 5000 ok! root..udp]$ IP =123.123.123.*** DATA=TEST サーバにちゃんと届いてるようだ。 UDP Packet Spoofing は簡単だった。重要なことは rawsocket でヘッダをそ のまま変更して送ってしまえば偽装できてしまうということだ。これは UDP がコネクションを確立せずに ただパケットを送信するだけのシンプルなもの だからだといえるだろう。信頼性を保証しないからこそのテクニックだといえ る。 End. written by kenji aiko 2003/08/20 2003/09/01 修正 2003/11/18 修正 Copyright (C) 2003 kenji aiko All Rights Reserved