Skip to content

Commit

Permalink
Merge pull request #2542 from Antiklesys/master
Browse files Browse the repository at this point in the history
Added hf iclass unhash
  • Loading branch information
iceman1001 authored Sep 29, 2024
2 parents 7be8ee0 + d5469d5 commit ce1f176
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `hf iclass unhash` command to reverse an iclass diversified key to hash0 pre-images (@antiklesys)
- Added crypto1 support to `hf 14a raw` (@doegox)
- Changed `hw version` command to print LUA and Python versions (@jmichelp)
- Updated LUA to v5.4.7 which adds utf-8 support (@jmichelp)
Expand Down
41 changes: 38 additions & 3 deletions client/src/cmdhficlass.c
Original file line number Diff line number Diff line change
Expand Up @@ -4123,7 +4123,7 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) {
DropField();
return PM3_ESOFT;
}
HFiClassCalcDivKey(csn, iClass_Key_Table[1], new_div_key, false);
diversifyKey(csn, iClass_Key_Table[1], new_div_key);
memcpy(no_first_auth,new_div_key,PICOPASS_BLOCK_SIZE);

CLIParserFree(ctx);
Expand All @@ -4142,6 +4142,40 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) {

}

static int CmdHFiClassUnhash(const char *Cmd) {

CLIParserContext *ctx;
CLIParserInit(&ctx, "hf iclass unhash",
"Reverses the hash0 function used generate iclass diversified keys after DES encryption, returning the DES crypted CSN.",
"hf iclass unhash --divkey B4F12AADC5301A2D"
);

void *argtable[] = {
arg_param_begin,
arg_str1(NULL, "divkey", "<hex>", "The card's Diversified Key value"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);

int dk_len = 0;
uint8_t div_key[PICOPASS_BLOCK_SIZE] = {0};
CLIGetHexWithReturn(ctx, 1, div_key, &dk_len);

CLIParserFree(ctx);

if (dk_len && dk_len != PICOPASS_BLOCK_SIZE) {
PrintAndLogEx(ERR, "Diversified Key is incorrect length");
return PM3_EINVARG;
}

PrintAndLogEx(INFO, _YELLOW_("Div Key: ")"%s", sprint_hex(div_key, sizeof(div_key)));

invert_hash0(div_key);

PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}

static int CmdHFiClassLookUp(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf iclass lookup",
Expand Down Expand Up @@ -5070,8 +5104,9 @@ static command_t CommandTable[] = {
{"chk", CmdHFiClassCheckKeys, IfPm3Iclass, "Check keys"},
{"loclass", CmdHFiClass_loclass, AlwaysAvailable, "Use loclass to perform bruteforce reader attack"},
{"lookup", CmdHFiClassLookUp, AlwaysAvailable, "Uses authentication trace to check for key in dictionary file"},
{"legrec", CmdHFiClassLegacyRecover, IfPm3Iclass, "Attempts to recover the standard key of a legacy card"},
{"legbrute", CmdHFiClassLegRecLookUp, AlwaysAvailable, "Bruteforces 40 bits of a partial raw key"},
{"legrec", CmdHFiClassLegacyRecover, IfPm3Iclass, "Recovers 24 bits of the diversified key of a legacy card provided a valid nr-mac combination"},
{"legbrute", CmdHFiClassLegRecLookUp, AlwaysAvailable, "Bruteforces 40 bits of a partial diversified key, provided 24 bits of the key and two valid nr-macs"},
{"unhash", CmdHFiClassUnhash, AlwaysAvailable, "Reverses a diversified key to retrieve hash0 pre-images after DES encryption"},
{"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"},
{"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"},
{"eload", CmdHFiClassELoad, IfPm3Iclass, "Upload file into emulator memory"},
Expand Down
264 changes: 249 additions & 15 deletions client/src/loclass/ikeys.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,7 @@ static uint64_t ck(int i, int j, uint64_t z) {
if (getSixBitByte(z, i) == getSixBitByte(z, j)) {
//ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] )
uint64_t newz = 0;
int c;
for (c = 0; c < 4; c++) {
for (int c = 0; c < 4; c++) {
uint8_t val = getSixBitByte(z, c);
if (c == i)
pushbackSixBitByte(&newz, j, c);
Expand Down Expand Up @@ -201,24 +200,53 @@ static uint64_t check(uint64_t z) {
return ck1 | ck2 >> 24;
}

static void permute(BitstreamIn_t *p_in, uint64_t z, int l, int r, BitstreamOut_t *out) {
if (bitsLeft(p_in) == 0)
return;
// Reverse ck (scramble-1)
static uint64_t reverse_ck(int i, int j, uint64_t z) {
if (i == 1 && j == -1) {
return z;
} else if (j == -1) {
return reverse_ck(i - 1, i - 2, z);
}

bool pn = tailBit(p_in);
if (pn) {
// pn = 1
uint8_t zl = getSixBitByte(z, l);
push6bits(out, zl + 1);
permute(p_in, z, l + 1, r, out);
uint64_t newz = 0;
if (getSixBitByte(z, i) == j) { // Reverse the swap logic based on condition in scramble^{-1}
// Perform reverse swap
for (int c = 0; c < 4; c++) {
uint8_t val = getSixBitByte(z, c);
if (c == i) {
pushbackSixBitByte(&newz, getSixBitByte(z, j), c);
} else {
pushbackSixBitByte(&newz, val, c);
}
}
return reverse_ck(i, j - 1, newz);
} else {
// otherwise
uint8_t zr = getSixBitByte(z, r);
push6bits(out, zr);
permute(p_in, z, l, r + 1, out);
// Continue recursion
return reverse_ck(i, j - 1, z);
}
}

static uint64_t reverse_check(uint64_t z) {

//retrieve ck1 and ck2 from the hash

// Step 1: Extract ck2 shifted part from result
// Assuming ck2 shifted part is from bits 0-23 of the result
uint64_t shifted_ck2_part = z & 0x0000000000FFFFFF; // Mask the lower 24 bits

// Step 2: Reconstruct ck2
uint64_t ck2 = shifted_ck2_part << 24; // Shift back to get original ck2 value
// Step 3: Recover ck1
uint64_t ck1 = z & ~(ck2 >> 24); // Clear the bits where ck2 affected the result
// Now ck1 and ck2 have their original values before (after ck function took place)

ck1 = reverse_ck(3,2,ck1);
ck2 = reverse_ck(3,2,ck2);

return ck1 | ck2 >> 24; //This is now zP

}

static void printState(const char *desc, uint64_t c) {
if (g_debugMode == 0)
return;
Expand All @@ -237,6 +265,43 @@ static void printState(const char *desc, uint64_t c) {
PrintAndLogEx(DEBUG, "%s", s);
}

static void permute(BitstreamIn_t *p_in, uint64_t z, int l, int r, BitstreamOut_t *out) {
if (bitsLeft(p_in) == 0)
return;
bool pn = tailBit(p_in);
if (pn) {
// pn = 1
uint8_t zl = getSixBitByte(z, l);
push6bits(out, zl + 1);
permute(p_in, z, l + 1, r, out);
} else {
// otherwise
uint8_t zr = getSixBitByte(z, r);
push6bits(out, zr);
permute(p_in, z, l, r + 1, out);
}
}

static void reverse_permute(BitstreamIn_t *p_in, uint64_t z, int l, BitstreamOut_t *out1, BitstreamOut_t *out2, bool fix) {
if (bitsLeft(p_in) == 0)
return;
bool pn = tailBit(p_in);
if (pn) { //if p == 1 for that six bit position, then sum it
// pn = 1
uint8_t zl = getSixBitByte(z, l);
if(fix){
push6bits(out1, zl - 1);
}else{
push6bits(out1, zl );
}
} else {
// otherwise
uint8_t zr = getSixBitByte(z, l);
push6bits(out2, zr);
}
reverse_permute(p_in, z, l + 1, out1, out2, fix);
}

/**
* @brief
*Definition 11. Let the function hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as
Expand Down Expand Up @@ -339,6 +404,175 @@ void hash0(uint64_t c, uint8_t k[8]) {
}
}
}

static int find_p_in_pi(uint8_t p) {
for (int i = 0; i < 35; i++) {
if (pi[i] == p) {
return i; // Value found
}
}
return -1; // Value not found
}

//Reverse hash0
void invert_hash0(uint8_t k[8]) {

uint8_t y = 0;
uint64_t zTilde = 0;
uint8_t p = 0;

for (int i = 0; i < 8; i++) {
y |= ((k[i] & 0x80) >> (7 - i)); // Recover the bit of y from the leftmost bit of k[i]
pushbackSixBitByte(&zTilde, (k[i] & 0x7E) >> 1, i); // Recover the six bits of zTilde from the middle of k[i]
if(g_debugMode > 0)printState("z~", zTilde);
p |= ((k[i] & 0x01) << i);
}
if(g_debugMode > 0)PrintAndLogEx(INFO, " y : %02x", y); //value of y (recovered 1 byte of the pre-image)
//check if p is part of the array pi, if not invert it
if(g_debugMode > 0)PrintAndLogEx(INFO, " p : %02x", p); //value of p (at some point in the original hash0)
int remainder = find_p_in_pi(p);
if (remainder < 0){
p = ~p;
remainder = find_p_in_pi(p);
}

if(g_debugMode > 0)PrintAndLogEx(INFO, " p or ~p : %02x", p); //value of p (at some point in the original hash0)

//find possible values of x that can return the same remainder
uint8_t x_count = 0;
uint8_t x_array[8];
for (int x = 0x00; x <= 0xFF; x++) {
if(x % 35 == remainder){
x_array[x_count] = x;
x_count++;
}
}

uint8_t pre_image_base[8] = {0};
pre_image_base[1] = y;
//calculate pre-images based on the potential values of x (should we use pre-flip p and post flip p just in case?)
uint64_t zTil_img[8] = {0}; //8 is the max size it'll have as per max number of X pre-images
for(int img = 0; img < x_count; img++){ //for each potential value of x calculate a pre-image
zTil_img[img] = zTilde;
pre_image_base[0] = x_array[img];
uint8_t pc = p; //redefine and reassociate it here or it'll keep changing through the loops
if (x_array[img] & 1){ //Check if potential x7 is 1, if it is then invert p
pc = ~p;
}
//calculate zTilde for the x preimage
for (int i = 0; i < 8; i++) {
uint8_t p_i = (pc >> i) & 0x1; //this is correct!
uint8_t zTilde_i = getSixBitByte(zTilde, i) << 1;
if (k[i] & 0x80) { //this checks the value of the first bit of the byte (value of y_i)
if(p_i){
zTilde_i--;
}
zTilde_i = ~zTilde_i; //flip the 6 bit string
} else {
zTilde_i |= p_i & 0x1;
}

pushbackSixBitByte(&zTil_img[img], zTilde_i >> 1, i);
}

if(g_debugMode > 0){
PrintAndLogEx(INFO, _YELLOW_("Testing Pre-Image Base: %s"), sprint_hex(pre_image_base, sizeof(pre_image_base)));
PrintAndLogEx(DEBUG, " | x| y|z0|z1|z2|z3|z4|z5|z6|z7|");
printState("0|0|z~", zTil_img[img]); //we retrieve the values of z~
PrintAndLogEx(INFO, " p or ~p : %02x", pc); //value of p (at some point in the original hash0)
}
//reverse permute
BitstreamIn_t p_in = { &pc, 8, 0 };
uint8_t outbuffer_1[] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t outbuffer_2[] = {0, 0, 0, 0, 0, 0, 0, 0};
BitstreamOut_t out_1 = {outbuffer_1, 0, 0};
BitstreamOut_t out_2 = {outbuffer_2, 0, 0};
reverse_permute(&p_in, zTil_img[img], 0, &out_1, &out_2, false); //sort the bits

//Shift z-values down onto the lower segment
uint64_t zCaret_1 = x_bytes_to_num(outbuffer_1, sizeof(outbuffer_1));
zCaret_1 >>= 16;
uint64_t zCaret_2 = x_bytes_to_num(outbuffer_2, sizeof(outbuffer_2));
zCaret_2 >>= 40;
uint64_t zCaret = zCaret_1 | zCaret_2;
if (g_debugMode > 0) printState("0|0|z^", zCaret);

//fix the bits values
uint8_t p_fix = 0x0F; //fix bits mask as the bits will be in 11110000 order
BitstreamIn_t p_in_f = { &p_fix, 8, 0 };
uint8_t outbuffer_f1[] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t outbuffer_f2[] = {0, 0, 0, 0, 0, 0, 0, 0};
BitstreamOut_t out_f1 = {outbuffer_f1, 0, 0};
BitstreamOut_t out_f2 = {outbuffer_f2, 0, 0};
reverse_permute(&p_in_f, zCaret, 0, &out_f1, &out_f2, true); //fixes the bits accordingly

//Shift z-values down onto the lower segment
uint64_t zCaret_fixed1 = x_bytes_to_num(outbuffer_f1, sizeof(outbuffer_f1));
zCaret_fixed1 >>= 16;
uint64_t zCaret_fixed2 = x_bytes_to_num(outbuffer_f2, sizeof(outbuffer_f2));
zCaret_fixed2 >>= 40;

uint64_t zCaret_fixed = zCaret_fixed1 | zCaret_fixed2;
if (g_debugMode > 0) printState("0|0|z^", zCaret_fixed);

uint64_t zP = reverse_check(zCaret_fixed);
if (g_debugMode > 0) printState("0|0|z'", zP);

//reverse the modulo transformation in the hash0 function for the six-bit chunks

uint64_t c = 0;

//Depending on their positions, values 3c, 3d, 3e, 3f can lead to additional pre-images.
//When these values are present we need to generate additional pre-images if they have the same modulo as other values

for (int n=0; n < 4; n++){
uint8_t _zn = getSixBitByte(zP, n); //handles the first 4 six-bit bytes
uint8_t _zn4 = getSixBitByte(zP, n + 4); //handles the second 4 six-bit bytes

uint8_t zn = (_zn + (63 - 2 * n)) % (63 - n);
uint8_t zn4 = (_zn4 + (64 - 2 * n)) % (64 - n);

pushbackSixBitByte(&c, zn, n);
pushbackSixBitByte(&c, zn4, n + 4);
}

//restore the two most significant bytes (x and y)
c |= ((uint64_t)x_array[img] << 56);
c |= ((uint64_t)y << 48);
if (g_debugMode > 0){
PrintAndLogEx(DEBUG, " | x| y|z0|z1|z2|z3|z4|z5|z6|z7|");
printState("origin_r1", c);
}
//reverse the swapZbalues function to get the original six-bit byte order
uint64_t original_z = swapZvalues(c);

if (g_debugMode > 0){
PrintAndLogEx(DEBUG, " | x| y|z0|z1|z2|z3|z4|z5|z6|z7|");
printState("origin_r2", original_z);
PrintAndLogEx(INFO, "--------------------------");
}
//run pre-image through hash0
uint8_t img_div_key[8] = {0};
hash0(original_z, img_div_key); //commented to avoid log spam

//verify result, if it matches add it to the list as a valid pre-image
bool image_match = true;
for(int v=0; v<8; v++){
if(img_div_key[v] != k[v]){ //compare against input key k
image_match = false;
}
}
uint8_t des_pre_image[8] = {0};
x_num_to_bytes(original_z,sizeof(original_z),des_pre_image);

if(image_match){
PrintAndLogEx(INFO, _GREEN_("Valid pre-image: ")_YELLOW_("%s"), sprint_hex(des_pre_image, sizeof(des_pre_image)));
}else if (!image_match && g_debugMode > 0){
PrintAndLogEx(INFO, _RED_("Invalid pre-image: %s"), sprint_hex(des_pre_image, sizeof(des_pre_image)));
}
}
}

/**
* @brief Performs Elite-class key diversification
* @param csn
Expand Down
1 change: 1 addition & 0 deletions client/src/loclass/ikeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
* @return
*/
void hash0(uint64_t c, uint8_t k[8]);
void invert_hash0(uint8_t k[8]);
int doKeyTests(void);
/**
* @brief Performs Elite-class key diversification
Expand Down

0 comments on commit ce1f176

Please sign in to comment.