Add id48lib and second half of key recovery.

This commit is contained in:
Henry Gabryjelski
2024-03-03 12:16:16 -08:00
parent 5b038631ca
commit 4ebd6d4bff
16 changed files with 11107 additions and 14 deletions
+1
View File
@@ -758,6 +758,7 @@ target_link_libraries(proxmark3 PRIVATE
pm3rrg_rdv4_amiibo
pm3rrg_rdv4_reveng
pm3rrg_rdv4_hardnested
pm3rrg_rdv4_id48
${ADDITIONAL_LNK})
if (NOT SKIPPTHREAD EQUAL 1)
+17
View File
@@ -71,6 +71,12 @@ HARDNESTEDLIBINC = -I$(HARDNESTEDLIBPATH)
HARDNESTEDLIB = $(HARDNESTEDLIBPATH)/libhardnested.a
HARDNESTEDLIBLD =
## ID48
ID48LIBPATH = ./deps/id48
ID48LIBINC = -I$(ID48LIBPATH)
ID48LIB = $(ID48LIBPATH)/libid48.a
ID48LIBLD =
## Jansson
JANSSONLIBPATH = ./deps/jansson
JANSSONLIBINC = -I$(JANSSONLIBPATH)
@@ -157,6 +163,12 @@ STATICLIBS += $(HARDNESTEDLIB)
LDLIBS +=$(HARDNESTEDLIBLD)
PM3INCLUDES += $(HARDNESTEDLIBINC)
## ID48
# not distributed as system library
STATICLIBS += $(ID48LIB)
LDLIBS += $(ID48LIBLD)
PM3INCLUDES += $(ID48LIBINC)
## Linenoise
# wait to see if Readline is available
@@ -850,6 +862,7 @@ clean:
$(Q)$(MAKE) --no-print-directory -C $(AMIIBOLIBPATH) clean
$(Q)$(MAKE) --no-print-directory -C $(CLIPARSERLIBPATH) clean
$(Q)$(MAKE) --no-print-directory -C $(HARDNESTEDLIBPATH) clean
$(Q)$(MAKE) --no-print-directory -C $(ID48LIBPATH) clean
$(Q)$(MAKE) --no-print-directory -C $(JANSSONLIBPATH) clean
ifeq ($(LINENOISE_LOCAL_FOUND), 1)
$(Q)$(MAKE) --no-print-directory -C $(LINENOISELIBPATH) clean
@@ -902,6 +915,10 @@ $(HARDNESTEDLIB): .FORCE
$(info [*] MAKE $@)
$(Q)$(MAKE) --no-print-directory -C $(HARDNESTEDLIBPATH) all
$(ID48LIB): .FORCE
$(info [*] MAKE $@)
$(Q)$(MAKE) --no-print-directory -C $(ID48LIBPATH) all
$(JANSSONLIB): .FORCE
ifneq ($(JANSSON_FOUND),1)
$(info [*] MAKE $@)
+3
View File
@@ -7,6 +7,9 @@ endif()
if (NOT TARGET pm3rrg_rdv4_hardnested)
include(hardnested.cmake)
endif()
if (NOT TARGET pm3rrg_rdv4_id48)
include(id48lib.cmake)
endif()
if (NOT TARGET pm3rrg_rdv4_jansson)
include(jansson.cmake)
endif()
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2024 by Henry Gabryjelski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+13
View File
@@ -0,0 +1,13 @@
# Makefile for ID48LIB library
MYSRCPATHS =
MYINCLUDES = -I.
MYCFLAGS = -Wpedantic -Wall -Werror -O3 -Wno-unknown-pragmas -Wno-inline -Wno-unused-function
MYDEFS =
MYSRCS = \
id48_data.c \
id48_generator.c \
id48_recover.c
LIB_A = libid48.a
include ../../../Makefile.host
+50
View File
@@ -0,0 +1,50 @@
# ID48LIB
## Purpose
Enable reliable and repeatable key-writing and verification
on ID48 RFID tags. Improve key recovery when the tag is
present and writable.
### Why this is needed.
Have you ever lost data when a computer shutdown unexpectedly?
This occurs when the program believes the data was written,
but the device did not actually store the data before power
was lost.
With RFID tags, the same problem exists, except the power is
wirelessly provided, and may be only enough to read data
(writing takes more power). Thus, it's even more critical
for RFID tags to validate what was written.
If you are bothered by unreliable processes, and enjoy a measure
of certainty, and need to write a new key to an ID48 tag,
then read on.
### Problem background
The ProxMark3 RFID research tool has had basic support for
reading and writing ID48-based RFID tags (aka em4x70). However,
although the code existed to write new 96-bit keys, there was no
way to verify that the keys were actually written to the tag.
This is because the keys are not directly readable from the tag
(by design). The *only* way to verify that the key was successfully
stored on the tag is to perform an authentication against the tag,
using the new key that you had attempted to write, and verify that
the tag's response to the nonce and challenge matches.
Obviously, this requires the ability to calculate, given a known
key and nonce, the challenge to send to the tag, and the expected
response from the tag. Without this, folks simply could not know
if the key was safely stored on the tag or not.
## Capabilities
This library provides the ability to calculate the challenge and
expected response for a known key and nonce. In addition, if
provided the first half of the key, and at least one successful
authentication trio of nonce, challenge, and response, then
the library can recover all potentially valid values for the
second half of the key.
File diff suppressed because it is too large Load Diff
+206
View File
@@ -0,0 +1,206 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2024 by Henry Gabryjelski
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#if !defined(ID48_H__)
#define ID48_H__
// This file defines only the structs and API surface.
// There are no dependencies on any external code.
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#if defined(NDEBUG)
#define ASSERT(x) ((void)0)
#elif defined(ID48_NO_STDIO)
#define ASSERT(x) ((void)0)
#else // neither NDEBUG nor ID48_NO_STDIO defined
#include <stdio.h>
#include <assert.h>
#define ASSERT(x) assert((x))
#endif
#if defined(__cplusplus)
extern "C" {
#endif
/// <summary>
/// [0..11] stores K₉₅..K₀₀
/// </summary>
/// <remarks>
/// Big-endian, "native" bit order, when viewed linearly from k[ 0..11]
/// Mapping to the indices used in the research paper:
/// k[ 0] :== K₉₅..K₈₈
/// k[ 1] :== K₈₇..K₈₀
/// k[ 2] :== K₇₉..K₇₂
/// k[ 3] :== K₇₁..K₆₄
/// k[ 4] :== K₆₃..K₅₆
/// k[ 5] :== K₅₅..K₄₈
/// k[ 6] :== K₄₇..K₄₀
/// k[ 7] :== K₃₉..K₃₂
/// k[ 8] :== K₃₁..K₂₄
/// k[ 9] :== K₂₃..K₁₆
/// k[10] :== K₁₅..K₀₈
/// k[11] :== K₀₇..K₀₀
/// </remarks>
typedef struct _ID48LIB_KEY { // 96-bit
uint8_t k[12];
} ID48LIB_KEY;
/// <summary>
/// [0..6] stores N₅₅..N₀₀
/// </summary>
/// <remarks>
/// Big-endian, "native" bit order, when viewed linearly from rn[0..6]
/// Mapping to the indices used in the research paper:
/// rn[ 0] :== N₅₅..N₄₈
/// rn[ 1] :== N₄₇..N₄₀
/// rn[ 2] :== N₃₉..N₃₂
/// rn[ 3] :== N₃₁..N₂₄
/// rn[ 4] :== N₂₃..N₁₆
/// rn[ 5] :== N₁₅..N₀₈
/// rn[ 6] :== N₀₇..N₀₀
/// </remarks>
typedef struct _ID48LIB_NONCE { // 56-bit
uint8_t rn[7];
} ID48LIB_NONCE;
/// <summary>
/// [0..3] stores O₀₀..O₂₇ 0000
/// </summary>
/// <remarks>
/// Big-endian, "bitstream" bit order, when viewed linearly from frn[0..3]
/// This is the order in which the research paper typically lists the bits.
/// Mapping to the indices used in the research paper,
/// where ( O₀₀ .. O₂₇ ) :== output(s₀₇,k₃₂..k₀₅ )
/// then:
/// frn[ 0] :== O₀₀..O₀₇
/// frn[ 1] :== O₀₈..O₁₅
/// frn[ 2] :== O₁₆..O₂₃
/// frn[ 3] :== O₂₄..O₂₇ 0000
/// </remarks>
typedef struct _ID48LIB_FRN {
uint8_t frn[4];
} ID48LIB_FRN;
/// <summary>
/// [0..3] stores O₂₈..O₄₇ (12x 0)
/// </summary>
/// <remarks>
/// Native format if viewed linearly from frn[0..6].
/// Mapping to the indices used in the research paper,
/// where ( O₂₈ .. O₅₅ ) :== output( s₃₅, k₀₄..k₀₀ (15x 0) ) == grn
///
/// then:
/// rn[ 0] :== O₂₈ .. O₃₅
/// rn[ 1] :== O₃₆ .. O₄₃
/// rn[ 2] :== O₄₄..O₄₇ 0000
/// rn[ 3] :== 0000 0000
/// </remarks>
typedef struct _ID48LIB_GRN {
uint8_t grn[3];
} ID48LIB_GRN;
/// <summary>
/// When provided a key and nonce, will calculate
/// the frn and grn values and store in caller-provided
/// output parameters.
/// </summary>
/// <remarks>
/// Note: In C++, each parameter would be a reference (not pointer).
/// </remarks>
void id48lib_generator(
const ID48LIB_KEY* key_96bit,
const ID48LIB_NONCE* nonce_56bit,
ID48LIB_FRN* frn28_out,
ID48LIB_GRN* grn20_out
);
/// <summary>
/// Initializes to allow iterative recovery
/// of multiple potential keys. After calling
/// this init() function, can repeatedly call
/// the next() function until it returns false
/// to obtain all potential keys.
/// </summary>
/// <param name="input_partial_key">
/// Top 48 bits of the key, such as those discovered
/// using the proxmark3 command `lf em 4x70 brute`.
/// Only k[0..5] are used from this parameter,
/// corresponding to K₉₅..K₄₈.
/// </param>
/// <param name="input_nonce">
/// The nonce value.
/// Typically from a sniffed authentication.
/// </param>
/// <param name="input_frn">
/// The challenge sent from the reader (e.g., car)
/// to the tag (e.g., key).
/// Typically from a sniffed authentication.
/// </param>
/// <param name="input_grn">
/// The response sent from the tag (e.g., key)
/// to the car (e.g., car).
/// Typically from a sniffed authentication.
/// </param>
/// <remarks>
/// Note: In C++, each parameter would be a reference (not pointer).
/// </remarks>
void id48lib_key_recovery_init(
const ID48LIB_KEY* input_partial_key,
const ID48LIB_NONCE* input_nonce,
const ID48LIB_FRN* input_frn,
const ID48LIB_GRN* input_grn
);
/// <summary>
/// This can be repeated called (after calling init())
/// to find the next potential key for the given
/// partial key + nonce + frn + grn values.
/// I've seen combinations that have up to six
/// potential keys available, although typically
/// there are 1-3 results.
/// Each call to this function will return a single
/// value. Call repeatedly until the function returns
/// false to get all potential keys.
/// </summary>
/// <param name="potential_key_output">
/// When the function returns true, this caller-provided
/// value will be filled with the 96-bit key that, when
/// programmed to the tag, should authenticate against
/// the nonce+frn values, with tag returning the grn value.
/// </param>
/// <returns>
/// true when another potential key has been found.
/// false if no additional potential keys have been found.
/// </returns>
/// <remarks>
/// Note: In C++, each parameter would be a reference (not pointer).
/// </remarks>
bool id48lib_key_recovery_next(
ID48LIB_KEY* potential_key_output
);
#if defined(__cplusplus)
}
#endif
#endif // !defined(ID48_H__)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2024 by Henry Gabryjelski
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
//-----------------------------------------------------------------------------
// NOTE: This file describes internal details used by the ID48 library.
// Changes to this file are unannounced, and may break any code that
// is relying on it. Nothing in this file is considered part of the
// public API.
// The public API can be found in id48.h
//-----------------------------------------------------------------------------
#if !defined(ID48_INTERNALS_H__)
#define ID48_INTERNALS_H__
#include "id48.h"
typedef struct _ID48LIBX_STATE_REGISTERS {
uint64_t Raw;
} ID48LIBX_STATE_REGISTERS;
typedef struct _ID48LIBX_SUCCESSOR_RESULT {
ID48LIBX_STATE_REGISTERS state;
bool output;
} ID48LIBX_SUCCESSOR_RESULT;
// the following are used in key recovery but implemented in id48.c
ID48LIBX_SUCCESSOR_RESULT id48libx_retro003_successor(const ID48LIBX_STATE_REGISTERS* initial_state, uint8_t input_bit);
ID48LIBX_STATE_REGISTERS id48libx_retro003_init (const ID48LIB_KEY* key, const ID48LIB_NONCE* nonce);
bool id48libx_output_lookup(uint32_t output_index);
#endif // !defined(ID48_INTERNALS_H__)
+439
View File
@@ -0,0 +1,439 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2024 by Henry Gabryjelski
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h> // memset()
#include <assert.h>
#include "id48_internals.h"
#ifndef nullptr
#define nullptr ((void*)0)
#endif
#pragma region // reverse_bits()
static inline uint8_t reverse_bits_08(uint8_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint8_t mask = (uint8_t)(~((uint8_t)(0u)));
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap);
n = (uint8_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap));
}
return n;
}
static inline uint16_t reverse_bits_16(uint16_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint16_t mask = (uint16_t)(~((uint16_t)(0u)));
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap);
n = (uint16_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap));
}
return n;
}
static inline uint32_t reverse_bits_32(uint32_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint32_t mask = (uint32_t)(~((uint32_t)(0u)));
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap);
n = (uint32_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap));
}
return n;
}
static inline uint64_t reverse_bits_64(uint64_t n) {
uint8_t bitsToSwap = sizeof(n) * 8;
uint64_t mask = (uint64_t)(~((uint64_t)(0u)));
while (bitsToSwap >>= 1) {
mask ^= mask << (bitsToSwap);
n = (uint64_t)(((n & ~mask) >> bitsToSwap) | ((n & mask) << bitsToSwap));
}
return n;
}
#pragma endregion // reverse_bits()
#define MAXIMUM_STATE_HISTORY (56u)
typedef struct _EXPECTED_OUTPUT_BITS {
uint64_t Raw; // s07: 1ull << 0, s08: 1ull << 1, s09: 1ull << 2, ... s55: 1ull << 47
} EXPECTED_OUTPUT_BITS;
typedef struct _KEY_BITS_K47_TO_K00 {
uint64_t Raw;
} KEY_BITS_K47_TO_K00;
typedef struct _RECOVERY_STATE {
/// <summary>
/// What are the 48 expected output bits?
/// Stored as 0¹⁶·O₄₇..O₀₀.
/// </summary>
EXPECTED_OUTPUT_BITS expected_output_bits; // const once initialized
/// <summary>
/// The value of the low 48-bits most recently
/// returned to the caller as a potential match.
/// </summary>
KEY_BITS_K47_TO_K00 last_returned_potential_key;
/// <summary>
/// State history. Overwritten during testing
/// of input bits (next bit of possible key).
/// Storing the full history allows backtracking
/// without re-computing the state.
/// </summary>
ID48LIBX_STATE_REGISTERS states[MAXIMUM_STATE_HISTORY]; // history ... avoids re-computation when backtracking
/// <summary>
/// The 48-bit partial key to recover the remaining 48 bits of.
/// Constant after initialization.
/// </summary>
ID48LIB_KEY known_k95_to_k48;
/// <summary>
/// The 56-bit nonce corresponding to the frn/grn (output bits).
/// Constant after initialization.
/// </summary>
ID48LIB_NONCE known_nonce;
/// <summary>
/// boolean to identify first run after initialization (an edge case)
/// </summary>
bool is_fresh_initialization;
/// <summary>
/// boolean to identify that all keys have been tested.
/// If set, caller would need to call init() function again.
/// </summary>
bool more_keys_to_test;
} RECOVERY_STATE;
// Need equivalent of the following two function pointers:
typedef ID48LIBX_SUCCESSOR_RESULT (*ID48LIB_SUCCESSOR_FN)(const ID48LIBX_STATE_REGISTERS* initial_state, uint8_t input_bit);
typedef ID48LIBX_STATE_REGISTERS(*ID48LIB_INIT_FN )(const ID48LIB_KEY* key, const ID48LIB_NONCE* nonce);
static const ID48LIB_INIT_FN init_fn = id48libx_retro003_init;
static const ID48LIB_SUCCESSOR_FN successor_fn = id48libx_retro003_successor;
/// <summary>
/// Creates PM3-formatted key with K₉₅..K₄₈ from the provided partial key,
/// and with K₄₇..K₃₃ from bit-reversed `more_bits`.
/// </summary>
/// <param name="input_partial_key">Key with K₉₅..K₄₈, in PM3 compatible layout</param>
/// <param name="more_bits">0 K₃₃..K₄₇ (to support simple incrementing input)</param>
/// <returns>PM3-formatted key: K₉₅..K₃₃ 0³³</returns>
static ID48LIB_KEY create_partial_key56(const ID48LIB_KEY * input_partial_key, uint8_t k47_to_k40) {
ID48LIB_KEY result;
result.k[ 0] = input_partial_key->k[0]; // k[ 0] :== K₉₅..K₈₈
result.k[ 1] = input_partial_key->k[1]; // k[ 1] :== K₈₇..K₈₀
result.k[ 2] = input_partial_key->k[2]; // k[ 2] :== K₇₉..K₇₂
result.k[ 3] = input_partial_key->k[3]; // k[ 3] :== K₇₁..K₆₄
result.k[ 4] = input_partial_key->k[4]; // k[ 4] :== K₆₃..K₅₆
result.k[ 5] = input_partial_key->k[5]; // k[ 5] :== K₅₅..K₄₈
result.k[ 6] = k47_to_k40; // k[ 6] :== K₄₇..K₄₀
result.k[ 7] = 0; // k[ 7] :== K₃₉..K₃₂
result.k[ 8] = 0; // k[ 8] :== K₃₁..K₂₄
result.k[ 9] = 0; // k[ 9] :== K₂₃..K₁₆
result.k[10] = 0; // k[10] :== K₁₅..K₀₈
result.k[11] = 0; // k[11] :== K₀₇..K₀₀
return result;
}
/// <summary>
/// Returns 48-bit value (using 64-bits of storage): 0¹⁶ O₄₇..O₀₀.
/// This allows simple calculation of the relevant bit to review,
/// or simply shifting the value right each time a bit is used and using lsb.
/// </summary>
/// <param name="input_frn">PM3 compatible input for frn</param>
/// <param name="input_grn">PM3 compatible input for grn</param>
/// <returns></returns>
static EXPECTED_OUTPUT_BITS create_expected_output_bits(const ID48LIB_FRN* input_frn, const ID48LIB_GRN* input_grn) {
// inputs:
// frn[ 0] :== O₀₀..O₀₇
// frn[ 1] :== O₀₈..O₁₅
// frn[ 2] :== O₁₆..O₂₃
// frn[ 3] :== O₂₄..O₂₇ 0000
// grn[ 0] :== O₂₈ .. O₃₅
// grn[ 1] :== O₃₆ .. O₄₃
// grn[ 2] :== O₄₄..O₄₇ 0000
EXPECTED_OUTPUT_BITS result; result.Raw = 0u;
result.Raw <<= 4; result.Raw |= reverse_bits_08(input_grn->grn[2] & 0xF0u); // adds grn₁₉..grn₁₆ aka O₄₇..O₄₄
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_grn->grn[1] & 0xFFu); // adds grn₁₅..grn₀₈ aka O₄₃..O₃₆
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_grn->grn[0] & 0xFFu); // adds grn₀₇..grn₀₀ aka O₃₅..O₂₈
result.Raw <<= 4; result.Raw |= reverse_bits_08(input_frn->frn[3] & 0xF0u); // adds frn₂₇..frn₂₄ aka O₂₇..O₂₄
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_frn->frn[2] & 0xFFu); // adds frn₂₃..frn₁₆ aka O₂₃..O₁₆
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_frn->frn[1] & 0xFFu); // adds frn₁₅..frn₀₈ aka O₁₅..O₀₈
result.Raw <<= 8; result.Raw |= reverse_bits_08(input_frn->frn[0] & 0xFFu); // adds frn₀₇..frn₀₀ aka O₀₇..O₀₀
return result;
}
/// <summary>
/// For a current state, get the expected output bit.
/// This is used to determine which value(s) the key bit
/// may be valid, allowing early pruning of the search space.
/// </summary>
/// <param name="recovery_state">A value in the range [0,55]</param>
/// <returns>Zero or non-zero (boolean) corresponding to the expected output.</returns>
static bool get_expected_output_bit(const RECOVERY_STATE* recovery_state, uint8_t current_state_index) {
assert(recovery_state != nullptr);
assert(current_state_index >= 7);
assert(current_state_index <= 55);
uint64_t shifted = recovery_state->expected_output_bits.Raw >> (current_state_index - 7u);
return !!(shifted & 0x1u); // return the single bit result
}
static void restart_and_calculate_s00(RECOVERY_STATE* s, const KEY_BITS_K47_TO_K00* k_low) {
assert(s != nullptr);
assert(k_low != nullptr);
memset(&(s->states[0]), 0xAA, sizeof(ID48LIBX_STATE_REGISTERS) * MAXIMUM_STATE_HISTORY);
uint8_t k47_to_k40 = (uint8_t)(k_low->Raw >> 40);
const ID48LIB_KEY start_56b_key = create_partial_key56(&(s->known_k95_to_k48), k47_to_k40);
s->states[0] = init_fn(&start_56b_key, &(s->known_nonce));
}
static bool validate_output_from_additional_fifteen_zero_bits(RECOVERY_STATE* s) {
bool all_still_match = true;
for (uint8_t i = 0; all_still_match && i < 15; i++) {
const uint8_t src_idx = 40 + i;
const ID48LIBX_STATE_REGISTERS* state = &(s->states[src_idx]);
ID48LIBX_SUCCESSOR_RESULT r = successor_fn(state, 0);
bool expected_result = get_expected_output_bit(s, src_idx);
if (expected_result != (!!r.output)) {
all_still_match = false;
}
s->states[src_idx + 1] = r.state;
}
return all_still_match;
}
// intentionally declare this global state only here, as a way
// of forcing the above functions to act on a pointer. Ensuring
// the above routines don't inadvertently use the global state
// makes it easier to enable a multi-threaded version.
RECOVERY_STATE g_S = { 0 };
static void init(
const ID48LIB_KEY * input_partial_key,
const ID48LIB_NONCE * input_nonce,
const ID48LIB_FRN * input_frn,
const ID48LIB_GRN * input_grn
)
{
memset(&g_S, 0, sizeof(RECOVERY_STATE));
memset(&(g_S.states[0]), 0xAA, sizeof(ID48LIBX_STATE_REGISTERS) * MAXIMUM_STATE_HISTORY);
g_S.known_k95_to_k48.k[0] = input_partial_key->k[0];
g_S.known_k95_to_k48.k[1] = input_partial_key->k[1];
g_S.known_k95_to_k48.k[2] = input_partial_key->k[2];
g_S.known_k95_to_k48.k[3] = input_partial_key->k[3];
g_S.known_k95_to_k48.k[4] = input_partial_key->k[4];
g_S.known_k95_to_k48.k[5] = input_partial_key->k[5];
g_S.known_nonce = *input_nonce;
g_S.expected_output_bits = create_expected_output_bits(input_frn, input_grn);
g_S.more_keys_to_test = true;
g_S.is_fresh_initialization = true;
}
static bool get_next_potential_key(
ID48LIB_KEY* potential_key_output
) {
memset(potential_key_output, 0, sizeof(ID48LIB_KEY));
// Three possible states when this function enters:
// 1. Never initialized / finished enumerating keys
// --> returns false immediately
// 2. First time after initialization
// --> key starts at zero, with zero current bits
// 3. After a key was provided as a potential match
// --> If that last reported potential match was
// all one-bits, then early-exit with false
// because the whole keyspace was exhausted.
// --> Since state stores the last key reported,
// setup to continue search at the first
// bit that was zero.
// Early exit when no more keys to test
if (!g_S.more_keys_to_test) {
return false;
}
KEY_BITS_K47_TO_K00 k_low;
int8_t current_key_bit_shift;
// Setup the next key to be tested.
if (g_S.is_fresh_initialization) {
// first-time init is easy: key is zero, and zero bits set
g_S.is_fresh_initialization = false;
k_low.Raw = 0ull;
current_key_bit_shift = 47;
}
else {
// by definition, a returned potential key had all the bits defined
current_key_bit_shift = 0;
k_low = g_S.last_returned_potential_key;
// edge case: returned potential key 0xFFFFFFFFFFFFull, so no more keys to be tested!
if (k_low.Raw == 0xFFFFFFFFFFFFull) {
g_S.more_keys_to_test = false;
return false;
}
// backtrack to first zero value, flipping bits...
if (1) {
uint64_t mask = (1ull << current_key_bit_shift);
while ((mask & k_low.Raw) != 0) {
k_low.Raw ^= mask;
mask <<= 1;
++current_key_bit_shift;
}
// and flip that next bit also
k_low.Raw ^= mask;
}
}
// TODO: move above setup to re-use code in below loop ...
// especially since backtracking logic is duplicated?
// may require re-arranging the order in which things
// occur in the while loop?
// Two exit conditions from this point on:
// 1. potential key was found (and thus returned)
// 2. no more keys to be tested (returns false)
while (1) {
// Currently, at loop start, ready to test the current bit vs. expected value
assert(current_key_bit_shift < 48);
// Anytime bit shift is 40+, changes would affect s00 ...
if (current_key_bit_shift > 39) {
restart_and_calculate_s00(&g_S, &k_low);
current_key_bit_shift = 39; // k47..k40 used to get to s00
}
assert(current_key_bit_shift < 40);
// Anytime bit shift is 33+, unconditionally calculate through s07,
// because the output bits are not exposed, and thus cannot be validated.
while (current_key_bit_shift > 32) { // k39..k33 used to move from s00-->s07
uint8_t src_idx = 39 - current_key_bit_shift;
bool input_bit = !!(((uint8_t)(k_low.Raw >> current_key_bit_shift)) & 0x1u);
ID48LIBX_SUCCESSOR_RESULT r = successor_fn(&(g_S.states[src_idx]), input_bit);
g_S.states[src_idx + 1] = r.state;
--current_key_bit_shift;
}
assert(current_key_bit_shift <= 32); // K₃₂ is used with s₀₇ to generate first output bit O₀₀
assert(current_key_bit_shift >= 0); // K₀₀ is a special case ... so negative is unexpected
// Check if the current state + current key bit (as stored) gives expected result.
const uint8_t src_idx = 39 - current_key_bit_shift;
bool input_bit = !!(((uint8_t)(k_low.Raw >> current_key_bit_shift)) & 0x1u);
ID48LIBX_SUCCESSOR_RESULT r = successor_fn(&(g_S.states[src_idx]), input_bit);
// can unconditionally overwrite next state...
g_S.states[src_idx + 1] = r.state;
bool expected_result = get_expected_output_bit(&g_S, src_idx);
bool matched = expected_result == (!!r.output);
// when matched the last bit, actually check the next 15x inputs (all zero) as well
if (matched && current_key_bit_shift == 0) {
// that was the last bit to be checked in this potential key
// but, must also test 15x additional zero bit inputs before
// reporting that this may be a potential key
assert(src_idx == 39);
matched = validate_output_from_additional_fifteen_zero_bits(&g_S);
}
// Exit point ... found a potential key!
if (matched && current_key_bit_shift == 0) {
g_S.last_returned_potential_key = k_low;
potential_key_output->k[ 0] = g_S.known_k95_to_k48.k[0];
potential_key_output->k[ 1] = g_S.known_k95_to_k48.k[1];
potential_key_output->k[ 2] = g_S.known_k95_to_k48.k[2];
potential_key_output->k[ 3] = g_S.known_k95_to_k48.k[3];
potential_key_output->k[ 4] = g_S.known_k95_to_k48.k[4];
potential_key_output->k[ 5] = g_S.known_k95_to_k48.k[5];
potential_key_output->k[ 6] = (uint8_t)(k_low.Raw >> (8 * 5));
potential_key_output->k[ 7] = (uint8_t)(k_low.Raw >> (8 * 4));
potential_key_output->k[ 8] = (uint8_t)(k_low.Raw >> (8 * 3));
potential_key_output->k[ 9] = (uint8_t)(k_low.Raw >> (8 * 2));
potential_key_output->k[10] = (uint8_t)(k_low.Raw >> (8 * 1));
potential_key_output->k[11] = (uint8_t)(k_low.Raw >> (8 * 0));
return true;
}
// that bit of the key was OK, but there are more to check
else if (matched) {
--current_key_bit_shift;
}
// wrong output generated with that bit.
// Backtrack to find next one to be tested.
else {
// not required ... but makes debugging easier
memset(&g_S.states[src_idx + 1], 0xAA, sizeof(ID48LIBX_STATE_REGISTERS));
// that bit of the key results in wrong output.
// backtrack until the next zero bit, flip it to one, and
// continue testing from there...
if (1) {
// This is ***NOT*** the same as simply adding 1.
// (Consider, for example, when current_key_bit_shift == 3.)
uint64_t mask = 1ull << current_key_bit_shift;
while ((mask & k_low.Raw) != 0) {
k_low.Raw ^= mask;
mask <<= 1;
++current_key_bit_shift;
}
// found a zero bit, so flip it
// this is the next key to test
k_low.Raw ^= mask;
}
// EXIT CONDITION: k_low wraps to invalid value
if (current_key_bit_shift >= 48) {
// no more results available ... return!
g_S.more_keys_to_test = false;
return 0u;
}
}
} // end while(1) loop
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ******************************************************************************************************************** //
// *** Everything above this line in the file is declared static, *** //
// *** which avoids polluting the global namespace. *** //
// *** Everything below is technically visible, but not necessarily an exported API. *** //
// *** In C++, this separation is much more easily achieved using an anonymous namespace. C'est la vie! *** //
// ******************************************************************************************************************** //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void id48lib_key_recovery_init(
const ID48LIB_KEY * input_partial_key,
const ID48LIB_NONCE * input_nonce,
const ID48LIB_FRN * input_frn,
const ID48LIB_GRN * input_grn
)
{
init(input_partial_key, input_nonce, input_frn, input_grn);
}
bool id48lib_key_recovery_next(
ID48LIB_KEY* potential_key_output
) {
return get_next_potential_key(potential_key_output);
}
+9
View File
@@ -0,0 +1,9 @@
add_library(pm3rrg_rdv4_id48 STATIC
id48/id48_data.c
id48/id48_generator.c
id48/id48_recover.c
)
target_compile_options( pm3rrg_rdv4_id48 PRIVATE -Wpedantic -Wall -Werror -O3 -Wno-unknown-pragmas -Wno-inline -Wno-unused-function)
target_include_directories(pm3rrg_rdv4_id48 PRIVATE id48)
target_include_directories(pm3rrg_rdv4_id48 INTERFACE id48)
set_property(TARGET pm3rrg_rdv4_id48 PROPERTY POSITION_INDEPENDENT_CODE ON)
+245 -11
View File
@@ -23,6 +23,8 @@
#include "fileutils.h"
#include "commonutil.h"
#include "em4x70.h"
#include "id48.h"
#include "time.h"
#define LOCKBIT_0 BITMASK(6)
#define LOCKBIT_1 BITMASK(7)
@@ -31,6 +33,15 @@
static int CmdHelp(const char *Cmd);
static void fill_buffer_prng_bytes(void* buffer, size_t byte_count) {
if (byte_count <= 0) return;
srand((unsigned) time(NULL));
for (size_t i = 0; i < byte_count; i++) {
((uint8_t*)buffer)[i] = (uint8_t)rand();
}
}
static void print_info_result(const uint8_t *data) {
PrintAndLogEx(NORMAL, "");
@@ -52,7 +63,7 @@ static void print_info_result(const uint8_t *data) {
}
PrintAndLogEx(INFO, "------+----------+-----------------------------");
// Print Crypt Key (will never have data)
// Print Key (will never have data)
for (int i = 12; i < 24; i += 2) {
PrintAndLogEx(INFO, " %2d | -- -- | KEY write-only", INDEX_TO_BLOCK(i));
}
@@ -109,6 +120,13 @@ bool detect_4x70_block(void) {
return em4x70_info() == PM3_SUCCESS;
}
// TODO: split the below functions, so can use them as building blocks for more complex interactions
// without generating fake `const char *Cmd` strings. First targets:
// Auth
// Write
// WriteKey
// Together, they will allow writekey to verify the key was written correctly.
int CmdEM4x70Info(const char *Cmd) {
// envoke reading of a EM4x70 tag which has to be on the antenna because
@@ -388,8 +406,10 @@ int CmdEM4x70Auth(const char *Cmd) {
CLIParserInit(&ctx, "lf em 4x70 auth",
"Authenticate against an EM4x70 by sending random number (RN) and F(RN)\n"
" If F(RN) is incorrect based on the tag crypt key, the tag will not respond",
"lf em 4x70 auth --rnd 45F54ADA252AAC --frn 4866BB70 --> Test authentication, tag will respond if successful\n"
" If F(RN) is incorrect based on the tag key, the tag will not respond\n"
" If F(RN) is correct based on the tag key, the tag will give a 20-bit response\n",
"lf em 4x70 auth --rnd 45F54ADA252AAC --frn 4866BB70 --> (using pm3 test key)\n"
"lf em 4x70 auth --rnd 3FFE1FB6CC513F --frn F355F1A0 --> (using research paper key)\n"
);
void *argtable[] = {
@@ -499,20 +519,21 @@ int CmdEM4x70WritePIN(const char *Cmd) {
int CmdEM4x70WriteKey(const char *Cmd) {
// Write new crypt key to tag
// Write new key to tag
em4x70_data_t etd = {0};
CLIParserContext *ctx;
CLIParserInit(&ctx, "lf em 4x70 writekey",
"Write new 96-bit key to tag\n",
"lf em 4x70 writekey -k F32AA98CF5BE4ADFA6D3480B\n"
"lf em 4x70 writekey -k F32AA98CF5BE4ADFA6D3480B (pm3 test key)\n"
"lf em 4x70 writekey -k A090A0A02080000000000000 (research paper key)\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0(NULL, "par", "Add parity bit when sending commands"),
arg_str1("k", "key", "<hex>", "Crypt Key as 12 hex bytes"),
arg_str1("k", "key", "<hex>", "Key as 12 hex bytes"),
arg_param_end
};
@@ -526,7 +547,7 @@ int CmdEM4x70WriteKey(const char *Cmd) {
CLIParserFree(ctx);
if (key_len != 12) {
PrintAndLogEx(FAILED, "Crypt key length must be 12 bytes instead of %d", key_len);
PrintAndLogEx(FAILED, "Key length must be 12 bytes instead of %d", key_len);
return PM3_EINVARG;
}
@@ -540,23 +561,236 @@ int CmdEM4x70WriteKey(const char *Cmd) {
}
if (resp.status) {
PrintAndLogEx(INFO, "Writing new crypt key: " _GREEN_("ok"));
PrintAndLogEx(INFO, "Writing new key: " _GREEN_("ok"));
// TODO: use prng to generate a new nonce, calculate frn/grn, and authenticate with tag
return PM3_SUCCESS;
}
PrintAndLogEx(FAILED, "Writing new crypt key: " _RED_("failed"));
PrintAndLogEx(FAILED, "Writing new key: " _RED_("failed"));
return PM3_ESOFT;
}
// largest seen "in the wild" was 6
#define MAXIMUM_ID48_RECOVERED_KEY_COUNT 10
typedef struct _em4x70_recovery_data_t {
ID48LIB_KEY key;
ID48LIB_NONCE nonce;
ID48LIB_FRN frn;
ID48LIB_GRN grn;
bool verify; // if true, tag must be present
bool parity; // if true, add parity bit to commands sent to tag
uint8_t keys_found_count;
uint8_t keys_validated_count;
ID48LIB_KEY potential_keys[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
ID48LIB_NONCE alt_nonce;
ID48LIB_FRN alt_frn[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
ID48LIB_GRN alt_grn[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
bool potential_keys_validated[MAXIMUM_ID48_RECOVERED_KEY_COUNT];
} em4x70_recovery_data_t;
static int ValidateArgsForRecover(const char *Cmd, em4x70_recovery_data_t* out_results) {
memset(out_results, 0, sizeof(em4x70_recovery_data_t));
int result = PM3_SUCCESS;
CLIParserContext *ctx;
CLIParserInit(
&ctx,
"lf em 4x70 recover",
"After obtaining key bits 95..48 (such as via 'lf em 4x70 brute'), this command will recover\n"
"key bits 47..00. By default, this process does NOT require a tag to be present.\n"
"\n"
"By default, the potential keys are shown (typically 1-6) along with a corresponding\n"
"'lf em 4x70 auth' command that will authenticate, if that potential key is correct.\n"
"The user can copy/paste these commands when the tag is present to manually check\n"
"which of the potential keys is correct.\n"
// "\n"
// "If the `--verify` option is provided, the tag must be present. The rnd/frn parameters will\n"
// "be used to authenticate against the tag, and then any potential keys will be automatically\n"
// "be checked for correctness against the tag, reducing manual steps.\n"
,
"lf em 4x70 recover --key F32AA98CF5BE --rnd 45F54ADA252AAC --frn 4866BB70 --grn 9BD180 (pm3 test key)\n"
"lf em 4x70 recover --key A090A0A02080 --rnd 3FFE1FB6CC513F --frn F355F1A0 --grn 609D60 (research paper key)\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0(NULL, "par", "Add parity bit when sending commands"),
arg_str1("k", "key", "<hex>", "Key as 6 hex bytes"),
arg_str1(NULL, "rnd", "<hex>", "Random 56-bit"),
arg_str1(NULL, "frn", "<hex>", "F(RN) 28-bit as 4 hex bytes"),
arg_str1(NULL, "grn", "<hex>", "G(RN) 20-bit as 3 hex bytes"),
//arg_lit0(NULL, "verify", "automatically use tag for validation"),
arg_param_end
};
// do the command line arguments even parse?
if (CLIParserParseString(ctx, Cmd, argtable, arg_getsize(argtable), true)) {
result = PM3_ESOFT;
}
int key_len = 0; // must be 6 bytes hex data
int rnd_len = 0; // must be 7 bytes hex data
int frn_len = 0; // must be 4 bytes hex data
int grn_len = 0; // must be 3 bytes hex data
// if all OK so far, convert to internal data structure
if (PM3_SUCCESS == result) {
// magic number == index in argtable above. Fragile technique!
out_results->parity = arg_get_lit(ctx, 1);
if (CLIParamHexToBuf(arg_get_str(ctx, 2), &(out_results->key.k[0]), 12, &key_len)) {
result = PM3_ESOFT;
}
if (CLIParamHexToBuf(arg_get_str(ctx, 3), &(out_results->nonce.rn[0]), 7, &rnd_len)) {
result = PM3_ESOFT;
}
if (CLIParamHexToBuf(arg_get_str(ctx, 4), &(out_results->frn.frn[0]), 4, &frn_len)) {
result = PM3_ESOFT;
}
if (CLIParamHexToBuf(arg_get_str(ctx, 5), &(out_results->grn.grn[0]), 3, &grn_len)) {
result = PM3_ESOFT;
}
//out_results->verify = arg_get_lit(ctx, 6);
}
// if all OK so far, do additional parameter validation
if (PM3_SUCCESS == result) {
// Validate number of bytes read for hex data
if (key_len != 6) {
PrintAndLogEx(FAILED, "Key length must be 6 bytes instead of %d", key_len);
result = PM3_EINVARG;
}
if (rnd_len != 7) {
PrintAndLogEx(FAILED, "Random number length must be 7 bytes instead of %d", rnd_len);
result = PM3_EINVARG;
}
if (frn_len != 4) {
PrintAndLogEx(FAILED, "F(RN) length must be 4 bytes instead of %d", frn_len);
result = PM3_EINVARG;
}
if (grn_len != 3) {
PrintAndLogEx(FAILED, "G(RN) length must be 3 bytes instead of %d", grn_len);
result = PM3_EINVARG;
}
}
if (PM3_SUCCESS == result) {
ID48LIB_NONCE alt_n;
fill_buffer_prng_bytes(&alt_n, sizeof(ID48LIB_NONCE));
}
// single exit point
CLIParserFree(ctx);
return result;
}
int CmdEM4x70Recover(const char *Cmd) {
// From paper "Dismantling Megamos Crypto", Roel Verdult, Flavio D. Garcia and Barıs¸ Ege.
// Partial Key-Update Attack -- final 48 bits (after optimized version gets k95..k48)
em4x70_recovery_data_t recover_ctx = {0};
int result = PM3_SUCCESS;
result = ValidateArgsForRecover(Cmd, &recover_ctx);
// recover the potential keys -- no more than a few seconds
if (PM3_SUCCESS == result) {
// The library is stateful. First must initialize its internal context.
id48lib_key_recovery_init(&recover_ctx.key, &recover_ctx.nonce, &recover_ctx.frn, &recover_ctx.grn);
// repeatedly call id48lib_key_recovery_next() to get the next potential key
ID48LIB_KEY q;
while ((PM3_SUCCESS == result) && id48lib_key_recovery_next(&q)) {
if (recover_ctx.keys_found_count >= MAXIMUM_ID48_RECOVERED_KEY_COUNT) {
PrintAndLogEx(ERR, "ERROR: too many potential keys found. This is unexpected and likely a code failure.");
result = PM3_EFAILED;
} else {
recover_ctx.potential_keys[recover_ctx.keys_found_count] = q;
++recover_ctx.keys_found_count;
}
}
if (recover_ctx.keys_found_count == 0) {
PrintAndLogEx(ERR, "No potential keys recovered. This is unexpected and likely a code failure.");
result = PM3_EFAILED;
}
}
// generate alternate authentication for each potential key -- sub-second execution, no error paths
if (PM3_SUCCESS == result) {
for (uint8_t i = 0; i < recover_ctx.keys_found_count; ++i) {
// generate the alternate frn/grn for the alternate nonce
id48lib_generator(&recover_ctx.potential_keys[i], &recover_ctx.alt_nonce, &recover_ctx.alt_frn[i], &recover_ctx.alt_grn[i]);
}
}
// display alternate authentication for each potential key -- no error paths
if (PM3_SUCCESS == result) {
PrintAndLogEx(NORMAL, "Recovered %d potential keys:", recover_ctx.keys_found_count);
for (uint8_t i = 0; i < recover_ctx.keys_found_count; ++i) {
// generate an alternative authentication based on the potential key
// and the alternate nonce.
ID48LIB_KEY q = recover_ctx.potential_keys[i];
ID48LIB_FRN alt_frn = recover_ctx.alt_frn[i];
ID48LIB_GRN alt_grn = recover_ctx.alt_grn[i];
// dump the results to screen, to enable the user to manually check validity
// PrintAndLogEx() automatically adds newline, forcing this large parameter count
PrintAndLogEx(NORMAL,
"Potential Key #%d: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
" --> " _YELLOW_("lf em 4x70 auth --rnd %02X%02X%02X%02X%02X%02X%02X --frn %02X%02X%02X%02X")
" --> %02X%02X%02X\n",
i,
q.k[ 0], q.k[ 1], q.k[ 2], q.k[ 3], q.k[ 4], q.k[ 5],
q.k[ 6], q.k[ 7], q.k[ 8], q.k[ 9], q.k[10], q.k[11],
recover_ctx.alt_nonce.rn[0],
recover_ctx.alt_nonce.rn[1],
recover_ctx.alt_nonce.rn[2],
recover_ctx.alt_nonce.rn[3],
recover_ctx.alt_nonce.rn[4],
recover_ctx.alt_nonce.rn[5],
recover_ctx.alt_nonce.rn[6],
alt_frn.frn[0],
alt_frn.frn[1],
alt_frn.frn[2],
alt_frn.frn[3],
alt_grn.grn[0],
alt_grn.grn[1],
alt_grn.grn[2]
);
}
printf("\n");
}
if (PM3_SUCCESS == result && recover_ctx.verify) {
// TODO: automatic verification against a present tag.
// Updates ctx.potential_keys_validated[10] and ctx.keys_validated_count
PrintAndLogEx(WARNING, "Automatic verification against tag is not yet implemented.");
// 0. verify a tag is present
// 1. verify the parameters provided authenticate against the tag
// if not, print "Authentication failed. Verify the current tag matches parameters provided."
// print the authentication command used (allows user to easily copy/paste)
// SET ERROR
// 2. for each potential key:
// a. Attempt to authentic against the tag using alt_nonce and alt_frn[i]
// b. verify tag's response is alt_grn[i]
// c. if successful, set ctx.potential_keys_validated[i] = true and increment ctx.keys_validated_count
//
// All validation done... now just interpret the results....
//
// 3. if ctx.keys_validated_count == 0, print "No keys recovered. Check tag for good coupling (position, etc)?"
// 4. if ctx.keys_validated_count >= 2, print "Multiple keys recovered. Run command again (will use different alt nonce)?"
// 5. if ctx.keys_validated_count == 1, print "Found key: " ...
}
return result;
}
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"brute", CmdEM4x70Brute, IfPm3EM4x70, "Bruteforce EM4X70 to find partial Crypt Key"},
{"brute", CmdEM4x70Brute, IfPm3EM4x70, "Bruteforce EM4X70 to find partial key"},
{"info", CmdEM4x70Info, IfPm3EM4x70, "Tag information EM4x70"},
{"write", CmdEM4x70Write, IfPm3EM4x70, "Write EM4x70"},
{"unlock", CmdEM4x70Unlock, IfPm3EM4x70, "Unlock EM4x70 for writing"},
{"auth", CmdEM4x70Auth, IfPm3EM4x70, "Authenticate EM4x70"},
{"writepin", CmdEM4x70WritePIN, IfPm3EM4x70, "Write PIN"},
{"writekey", CmdEM4x70WriteKey, IfPm3EM4x70, "Write Crypt Key"},
{"writekey", CmdEM4x70WriteKey, IfPm3EM4x70, "Write key"},
{"recover", CmdEM4x70Recover, IfPm3EM4x70, "Recover remaining key from partial key"},
{NULL, NULL, NULL, NULL}
};
+1 -1
View File
@@ -31,7 +31,7 @@ int CmdEM4x70Unlock(const char *Cmd);
int CmdEM4x70Auth(const char *Cmd);
int CmdEM4x70WritePIN(const char *Cmd);
int CmdEM4x70WriteKey(const char *Cmd);
//int CmdEM4x70Recover(const char *Cmd);
int CmdEM4x70Recover(const char *Cmd);
// for `lf search`:
bool detect_4x70_block(void);
+12 -2
View File
@@ -13,12 +13,15 @@
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Low frequency EM4x70 structs
// Low frequency EM4x70 structs -- common to both ARM firmware and client
//-----------------------------------------------------------------------------
#ifndef EM4X70_H__
#define EM4X70_H__
#include <stdint.h>
#include <stdbool.h>
#define EM4X70_NUM_BLOCKS 16
// Common word/block addresses
@@ -26,6 +29,12 @@
#define EM4X70_PIN_WORD_UPPER 11
typedef struct {
// ISSUE: `bool` type does not have a standard-defined size.
// therefore, compatibility between architectures /
// compilers is not guaranteed.
// ISSUE: C99 has no _Static_assert() ... was added in C11
// TODO: add _Static_assert(sizeof(bool)==1);
// TODO: add _Static_assert(sizeof(em4x70_data_t)==36);
bool parity;
// Used for writing address
@@ -36,8 +45,9 @@ typedef struct {
uint32_t pin;
// Used for authentication
uint8_t rnd[7];
uint8_t frnd[4];
uint8_t grnd[3];
uint8_t rnd[7];
// Used to write new key
uint8_t crypt_key[12];