diff --git a/CHANGELOG.md b/CHANGELOG.md index facb6ee75..dc9d9a87c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Added a multi-threaded of ht2crack2search (@iceman1001) - Fixed ISO14443a bounds-checking because @doegex found cards not following ISO14443a when fuzzed (@iceman1001) - Added `mfkey32nested`: recovering partial nested authentication with known nT (@doegox) - Added support for dumping FM11RF08S data at once (@doegox) diff --git a/tools/hitag2crack/crack2/Makefile b/tools/hitag2crack/crack2/Makefile index e8dbc4bac..5764b3a9a 100644 --- a/tools/hitag2crack/crack2/Makefile +++ b/tools/hitag2crack/crack2/Makefile @@ -5,7 +5,7 @@ MYCFLAGS = -D_GNU_SOURCE MYDEFS = MYLDLIBS = -lpthread -BINS = ht2crack2buildtable ht2crack2search ht2crack2gentest +BINS = ht2crack2buildtable ht2crack2search ht2crack2gentest ht2crack2search_multi INSTALLTOOLS = $(BINS) include ../../../Makefile.host @@ -20,3 +20,4 @@ endif ht2crack2buildtable : $(OBJDIR)/ht2crack2buildtable.o $(MYOBJS) ht2crack2search : $(OBJDIR)/ht2crack2search.o $(MYOBJS) ht2crack2gentest : $(OBJDIR)/ht2crack2gentest.o $(MYOBJS) +ht2crack2search_multi : $(OBJDIR)/ht2crack2search_multi.o $(MYOBJS) diff --git a/tools/hitag2crack/crack2/ht2crack2search_multi.c b/tools/hitag2crack/crack2/ht2crack2search_multi.c new file mode 100644 index 000000000..2839bd333 --- /dev/null +++ b/tools/hitag2crack/crack2/ht2crack2search_multi.c @@ -0,0 +1,502 @@ +/* + * ht2crack2search.c + * this searches the sorted tables for the given RNG data, retrieves the matching + * PRNG state, checks it is correct, and then rolls back the PRNG to recover the key + */ + +#include "ht2crackutils.h" +#include +#include +#include + +// a global mutex to prevent interlaced printing from different threads +pthread_mutex_t print_lock; + +static int global_found = 0; +static int thread_count = 2; +static int g_bitoffset = 0; +static uint8_t g_rngmatch[6]; +static uint8_t g_rngstate[6]; + +typedef struct { + int len; + uint8_t *data; +} rngdata_t; + +typedef struct thread_args { + int thread; + int idx; + rngdata_t r; +} targs; + +#define AEND "\x1b[0m" +#define _RED_(s) "\x1b[31m" s AEND +#define _GREEN_(s) "\x1b[32m" s AEND +#define _YELLOW_(s) "\x1b[33m" s AEND +#define _CYAN_(s) "\x1b[36m" s AEND + +#define INPUTFILE "sorted/%02x/%02x.bin" +#define DATASIZE 10 + +static void print_hex(const uint8_t *data, const size_t len) { + if (data == NULL || len == 0) return; + + for (size_t i = 0; i < len; i++) { + printf("%02X", data[i]); + } + + printf("\n"); +} + +static int datacmp(const void *p1, const void *p2) { + unsigned char *d1 = (unsigned char *)p1; + unsigned char *d2 = (unsigned char *)p2; + + return memcmp(d1, d2, DATASIZE - 6); +} + +static int loadrngdata(rngdata_t *r, char *file) { + int fd; + int i, j; + int nibble; + struct stat filestat; + unsigned char *data; + + if (!r || !file) { + printf("loadrngdata: invalid params\n"); + return 0; + } + + fd = open(file, O_RDONLY); + + if (fd <= 0) { + printf("cannot open file %s\n", file); + exit(1); + } + + if (fstat(fd, &filestat)) { + printf("cannot stat file %s\n", file); + exit(1); + } + + if (filestat.st_size < 6) { + printf("file %s is too small\n", file); + exit(1); + } + + data = mmap((caddr_t)0, filestat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) { + printf("cannot mmap file %s\n", file); + exit(1); + } + + r->len = filestat.st_size / 2; +// printf("r->len = %d\n", r->len); + + r->data = (unsigned char *)calloc(1, r->len); + if (!(r->data)) { + printf("cannot calloc\n"); + exit(1); + } + + j = 0; + nibble = 0; + for (i = 0; (i < filestat.st_size) && (j < r->len); i++) { + if ((data[i] != 0x0a) && (data[i] != 0x0d) && (data[i] != 0x20)) { + if (!nibble) { + r->data[j] = hex2bin(data[i]) << 4; + nibble = 1; + } else { + r->data[j] |= hex2bin(data[i]); + nibble = 0; + j++; + } + } + } + + r->len = j; + + munmap(data, filestat.st_size); + close(fd); + + return 1; +} + +static int makecand(unsigned char *c, rngdata_t *r, int bitoffset) { + int bytenum; + int bitnum; + int i; + + if (!c || !r || (bitoffset > ((r->len * 8) - 48))) { + printf("makecand: invalid params\n"); + return 0; + } + + bytenum = bitoffset / 8; + bitnum = bitoffset % 8; + + for (i = 0; i < 6; i++) { + if (!bitnum) { + c[i] = r->data[bytenum + i]; + } else { + c[i] = (r->data[bytenum + i] << bitnum) | (r->data[bytenum + i + 1] >> (8 - bitnum)); + } + } + + return 1; +} + +// test the candidate against the next or previous rng data +static int testcand(const unsigned char *f, unsigned char *rt, int fwd) { + Hitag_State hstate; + int i; + uint32_t ks1; + uint32_t ks2; + unsigned char buf[6]; + + // build the prng state at the candidate + hstate.shiftreg = 0; + for (i = 0; i < 6; i++) { + hstate.shiftreg = (hstate.shiftreg << 8) | f[i + 4]; + } + buildlfsr(&hstate); + + if (fwd) { + // roll forwards 48 bits + hitag2_nstep(&hstate, 48); + } else { + // roll backwards 48 bits + rollback(&hstate, 48); + buildlfsr(&hstate); + } + + // get 48 bits of RNG from the rolled to state + ks1 = hitag2_nstep(&hstate, 24); + ks2 = hitag2_nstep(&hstate, 24); + + writebuf(buf, ks1, 3); + writebuf(buf + 3, ks2, 3); + + // compare them + if (!memcmp(buf, rt, 6)) { + return 1; + } else { + return 0; + } +} + +static int searchcand(unsigned char *c, unsigned char *rt, int fwd, unsigned char *m, unsigned char *s) { + + if (!c || !rt || !m || !s) { + printf("searchcand: invalid params\n"); + return 0; + } + + char file[64]; + unsigned char *data; + unsigned char item[10]; + unsigned char *found = NULL; + + snprintf(file, sizeof(file), INPUTFILE, c[0], c[1]); + + int fd = open(file, O_RDONLY); + if (fd <= 0) { + printf("cannot open table file %s\n", file); + exit(1); + } + + struct stat filestat; + if (fstat(fd, &filestat)) { + printf("cannot stat file %s\n", file); + exit(1); + } + + data = mmap((caddr_t)0, filestat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) { + printf("cannot mmap file %s\n", file); + exit(1); + } + + memcpy(item, c + 2, 4); + + found = (unsigned char *)bsearch(item, data, filestat.st_size / DATASIZE, DATASIZE, datacmp); + + if (found) { + + // our candidate is in the table + // go backwards and see if there are other matches + while (((found - data) >= DATASIZE) && (!memcmp(found - DATASIZE, item, 4))) { + found = found - DATASIZE; + } + + // now test all matches + while (((found - data) <= (filestat.st_size - DATASIZE)) && (!memcmp(found, item, 4))) { + if (testcand(found, rt, fwd)) { + memcpy(m, c, 2); + memcpy(m + 2, found, 4); + memcpy(s, found + 4, 6); + + munmap(data, filestat.st_size); + close(fd); + return 1; + } + + found = found + DATASIZE; + } + } + + munmap(data, filestat.st_size); + close(fd); + + return 0; + +} + +static void *brute_thread(void *arguments) { + + struct thread_args *args = (struct thread_args *) arguments; + + rngdata_t r; + r.len = args->r.len; + r.data = calloc(1, args->r.len); + memcpy(r.data, args->r.data, args->r.len); + + int bitlen = (r.len * 8); + + for (int i = args->idx; i <= bitlen - 48; i += thread_count) { + + // print progress + if ((i % 100) == 0) { + pthread_mutex_lock(&print_lock); + printf("searching on bit %d\n", i); + pthread_mutex_unlock(&print_lock); + } + + if (__atomic_load_n(&global_found, __ATOMIC_ACQUIRE) == 1) { + break; + } + + uint8_t l_cand[6] = {0}; + if (makecand(l_cand, &r, i) == 0) { + pthread_mutex_lock(&print_lock); + printf("cannot makecand, %d\n", i); + pthread_mutex_unlock(&print_lock); + break; + } +// printf("cand: %02x %02x %02x %02x %02x %02x : ", cand[0], cand[1], cand[2], cand[3], cand[4], cand[5]); +// printbin(cand); + + int fwd = 0; + /* make following or preceding RNG test data to confirm match */ + uint8_t l_rngtest[6] = {0}; + if (i < (bitlen - 96)) { + + if (makecand(l_rngtest, &r, i + 48) == 0) { + pthread_mutex_lock(&print_lock); + printf("cannot makecand rngtest %d + 48\n", i); + pthread_mutex_unlock(&print_lock); + break; + } + fwd = 1; + + } else { + + if (makecand(l_rngtest, &r, i - 48) == 0) { + pthread_mutex_lock(&print_lock); + printf("cannot makecand rngtest %d - 48\n", i); + pthread_mutex_unlock(&print_lock); + break; + } + fwd = 0; + } + + uint8_t l_match[6] ; + uint8_t l_state[6] ; + if (searchcand(l_cand, l_rngtest, fwd, l_match, l_state)) { + __sync_fetch_and_add(&global_found, 1); + __sync_fetch_and_add(&g_bitoffset, i); + memcpy(g_rngmatch, l_match, sizeof(l_match)); + memcpy(g_rngstate, l_state, sizeof(l_state)); + break; + } + } + + free(r.data); + free(args); + return NULL; +} + +static void rollbackrng(Hitag_State *hstate, const unsigned char *s, int offset) { + int i; + + if (!s) { + printf("rollbackrng: invalid params\n"); + return; + } + + // build prng at recovered offset + hstate->shiftreg = 0; + for (i = 0; i < 6; i++) { + hstate->shiftreg = (hstate->shiftreg << 8) | s[i]; + } + + printf("recovered prng state at offset %d:\n", offset); + printstate(hstate); + + // rollback to state after auth + rollback(hstate, offset); + + // rollback through auth (aR, p3) + rollback(hstate, 64); + + printf("prng state after initialisation:\n"); + printstate(hstate); + + +} + +static uint64_t recoverkey(Hitag_State *hstate, char *uidstr, char *nRstr) { + uint64_t key; + uint64_t keyupper; + uint32_t uid; + uint32_t uidtmp; + uint32_t nRenc; + uint32_t nR; + uint32_t nRxork; + uint32_t b = 0; + int i; + + // key lower 16 bits are lower 16 bits of prng state + key = hstate->shiftreg & 0xffff; + nRxork = (hstate->shiftreg >> 16) & 0xffffffff; + uid = rev32(hexreversetoulong(uidstr)); + nRenc = rev32(hexreversetoulong(nRstr)); + + uidtmp = uid; + // rollback and extract bits b + for (i = 0; i < 32; i++) { + hstate->shiftreg = ((hstate->shiftreg) << 1) | ((uidtmp >> 31) & 0x1); + uidtmp = uidtmp << 1; + b = (b << 1) | fnf(hstate->shiftreg); + } + + printf("end state:\n"); + printstate(hstate); + printf("b:\t\t"); + printbin2(b, 32); + printf("\n"); + printf("nRenc:\t\t"); + printbin2(nRenc, 32); + printf("\n"); + + nR = nRenc ^ b; + + printf("nR:\t\t"); + printbin2(nR, 32); + printf("\n"); + + keyupper = nRxork ^ nR; + key = key | (keyupper << 16); + printf("key:\t\t"); + printbin2(key, 48); + printf("\n"); + + return key; +} + + +int main(int argc, char *argv[]) { + + if (argc < 4) { + printf("%s rngdatafile UID nR\n", argv[0]); + exit(1); + } + + rngdata_t rng; + if (!loadrngdata(&rng, argv[1])) { + printf("loadrngdata failed\n"); + exit(1); + } + + char *uidstr; + if (!strncmp(argv[2], "0x", 2)) { + uidstr = argv[2] + 2; + } else { + uidstr = argv[2]; + } + + char *nRstr; + if (!strncmp(argv[3], "0x", 2)) { + nRstr = argv[3] + 2; + } else { + nRstr = argv[3]; + } + +#if !defined(_WIN32) || !defined(__WIN32__) + thread_count = sysconf(_SC_NPROCESSORS_CONF); + if (thread_count < 2) + thread_count = 2; +#endif /* _WIN32 */ + + printf("\nBruteforce using " _YELLOW_("%d") " threads\n", thread_count); + + pthread_t threads[thread_count]; + void *res; + + // create a mutex to avoid interlacing print commands from our different threads + pthread_mutex_init(&print_lock, NULL); + + // findmatch(&rng, rngmatch, rngstate, &bitoffset) + + // threads + for (int i = 0; i < thread_count; ++i) { + targs *a = calloc(1, rng.len + sizeof(targs)); + a->r.data = calloc(1, rng.len); + + a->thread = i; + a->idx = i; + a->r.len = rng.len; + memcpy(a->r.data, rng.data, rng.len); + + pthread_create(&threads[i], NULL, brute_thread, (void *)a); + } + + // wait for threads to terminate: + for (int i = 0; i < thread_count; ++i) { + pthread_join(threads[i], &res); + free(res); + } + + if (global_found == false) { + printf("\n" _RED_("!!!") " failed to find a key\n\n"); + } else { + printf("Found match:\n"); + printf("rngmatch.... "); print_hex(g_rngmatch, sizeof(g_rngmatch)); + printf("rngstate.... "); print_hex(g_rngstate, sizeof(g_rngstate)); + printf("bitoffset... %d\n", g_bitoffset); + + Hitag_State hstate; + rollbackrng(&hstate, g_rngstate, g_bitoffset); + + uint64_t keyrev = recoverkey(&hstate, uidstr, nRstr); + uint64_t key = rev64(keyrev); + + printf("keyrev:\t\t"); + printbin2(key, 48); + printf("\n"); + + printf("KEY:\t\t"); + for (int i = 0; i < 6; i++) { + printf("%02X", (int)(key & 0xff)); + key = key >> 8; + } + printf("\n"); + } + // clean up mutex + pthread_mutex_destroy(&print_lock); + + free(rng.data); + return 0; +} + +