mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-12 11:18:11 -07:00
Add id48lib and second half of key recovery.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 $@)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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__)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user