Complete rewrite of XP Algorithm (w/ comments). Now fully optimized and readable

This commit is contained in:
Andrew 2023-06-04 22:01:09 +03:00
parent 21e31fd1b6
commit 9812529903
4 changed files with 188 additions and 180 deletions

View File

@ -28,20 +28,26 @@
#include <openssl/bn.h> #include <openssl/bn.h>
#include <openssl/ec.h> #include <openssl/ec.h>
#include <openssl/sha.h> #include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/rand.h> #include <openssl/rand.h>
// Algorithm macros // Algorithm macros
#define PK_LENGTH 25 #define PK_LENGTH 25
#define NULL_TERMINATOR 1 #define NULL_TERMINATOR 1
#define FIELD_BITS 384 #define FIELD_BITS 384
#define FIELD_BYTES 48 #define FIELD_BYTES 48
#define FIELD_BITS_2003 512 #define FIELD_BITS_2003 512
#define FIELD_BYTES_2003 64 #define FIELD_BYTES_2003 64
#define NEXTSNBITS(field, n, offset) (((QWORD)field >> offset) & ((1ULL << (n)) - 1)) #define SHA_MSG_LENGTH_XP (4 + 2 * FIELD_BYTES)
#define NEXTSNBITS(field, n, offset) (((QWORD)field >> offset) & ((1ULL << (n)) - 1))
#define FIRSTNBITS(field, n) NEXTSNBITS(field, n, 0) #define FIRSTNBITS(field, n) NEXTSNBITS(field, n, 0)
#define BYDWORD(n) (n[0] | n[1] << 8 | n[2] << 16 | n[3] << 24)
#define BITMASK(n) ((1ULL << n) - 1)
// Confirmation ID generator constants // Confirmation ID generator constants
#define SUCCESS 0 #define SUCCESS 0
#define ERR_TOO_SHORT 1 #define ERR_TOO_SHORT 1
@ -62,7 +68,9 @@ typedef uint64_t QWORD;
extern char pCharset[]; extern char pCharset[];
// util.cpp // util.cpp
int BN_bn2lebin(const BIGNUM *a, unsigned char *to, int tolen); // Hello OpenSSL developers, please tell me, where is this function at?
void endian(BYTE *data, int length); void endian(BYTE *data, int length);
EC_GROUP *initializeEllipticCurve( EC_GROUP *initializeEllipticCurve(
std::string pSel, std::string pSel,
std::string aSel, std::string aSel,
@ -71,8 +79,8 @@ EC_GROUP *initializeEllipticCurve(
std::string generatorYSel, std::string generatorYSel,
std::string publicKeyXSel, std::string publicKeyXSel,
std::string publicKeyYSel, std::string publicKeyYSel,
EC_POINT *&genPoint, EC_POINT *&genPoint,
EC_POINT *&pubPoint EC_POINT *&pubPoint
); );
// key.cpp // key.cpp
@ -98,18 +106,18 @@ void showHelp(char *argv[]);
// xp.cpp // xp.cpp
bool verifyXPKey( bool verifyXPKey(
EC_GROUP *eCurve, EC_GROUP *eCurve,
EC_POINT *generator, EC_POINT *basePoint,
EC_POINT *publicKey, EC_POINT *publicKey,
char (&cdKey)[25] char (&cdKey)[25]
); );
void generateXPKey( void generateXPKey(
EC_GROUP *eCurve, EC_GROUP *eCurve,
EC_POINT *generator, EC_POINT *basePoint,
BIGNUM *order, BIGNUM *genOrder,
BIGNUM *privateKey, BIGNUM *privateKey,
DWORD pRaw, DWORD pSerial,
char (&cdKey)[25] char (&cdKey)[25]
); );
// server.cpp // server.cpp

View File

@ -1,3 +1,7 @@
//
// Created by Andrew on 01/06/2023.
//
#include "header.h" #include "header.h"
char pCharset[] = "BCDFGHJKMPQRTVWXY2346789"; char pCharset[] = "BCDFGHJKMPQRTVWXY2346789";

View File

@ -84,4 +84,19 @@ EC_GROUP *initializeEllipticCurve(
BN_CTX_free(context); BN_CTX_free(context);
return eCurve; return eCurve;
}
int BN_bn2lebin(const BIGNUM *a, unsigned char *to, int tolen) {
if (a == nullptr || to == nullptr)
return 0;
int len = BN_bn2bin(a, to);
if (len > tolen)
return -1;
// Choke point inside BN_bn2lebinpad: OpenSSL uses len instead of tolen.
endian(to, tolen);
return len;
} }

View File

@ -1,23 +1,10 @@
/* //
Windows XP CD Key Verification/Generator v0.03 // Created by Andrew on 01/06/2023.
by z22 //
Compile with OpenSSL libs, modify to suit your needs.
http://gnuwin32.sourceforge.net/packages/openssl.htm
History:
0.03 Stack corruptionerror on exit fixed (now pkey is large enough)
More Comments added
0.02 Changed name the *.cpp;
Fixed minor bugs & Make it compilable on VC++
0.01 First version compilable MingW
*/
#include "header.h" #include "header.h"
/* Unpacks the Windows XP Product Key. */ /* Unpacks the Windows XP-like Product Key. */
void unpackXP( void unpackXP(
QWORD (&pRaw)[2], QWORD (&pRaw)[2],
DWORD &pSerial, DWORD &pSerial,
@ -37,7 +24,7 @@ void unpackXP(
pSignature = FIRSTNBITS(pRaw[1], 51) << 5 | NEXTSNBITS(pRaw[0], 5, 59); pSignature = FIRSTNBITS(pRaw[1], 51) << 5 | NEXTSNBITS(pRaw[0], 5, 59);
} }
/* Packs the Windows XP Product Key. */ /* Packs the Windows XP-like Product Key. */
void packXP( void packXP(
QWORD (&pRaw)[2], QWORD (&pRaw)[2],
DWORD pSerial, DWORD pSerial,
@ -53,213 +40,207 @@ void packXP(
pRaw[1] = NEXTSNBITS(pSignature, 51, 5); pRaw[1] = NEXTSNBITS(pSignature, 51, 5);
} }
/* Verify Product Key */ /* Verifies the Windows XP-like Product Key. */
bool verifyXPKey(EC_GROUP *eCurve, EC_POINT *generator, EC_POINT *publicKey, char (&cdKey)[25]) { bool verifyXPKey(
BN_CTX *context = BN_CTX_new(); EC_GROUP *eCurve,
EC_POINT *basePoint,
EC_POINT *publicKey,
char (&cdKey)[25]
) {
BN_CTX *numContext = BN_CTX_new();
QWORD pRaw[2]{},
pSignature = 0;
DWORD pSerial = 0,
pHash = 0;
// Convert Base24 CD-key to bytecode. // Convert Base24 CD-key to bytecode.
QWORD bKey[2]{}; unbase24((BYTE *)pRaw, cdKey);
DWORD pID, checkHash;
QWORD sig = 0; // Extract RPK, hash and signature from bytecode.
unpackXP(pRaw, pSerial, pHash, pSignature);
unbase24((BYTE *)bKey, cdKey); /*
*
* Scalars:
* e = Hash
* s = Schnorr Signature
*
* Points:
* G(x, y) = Generator (Base Point)
* K(x, y) = Public Key
*
* Equation:
* P = sG + eK
*
*/
// Extract data, hash and signature from the bytecode. BIGNUM *e = BN_lebin2bn((BYTE *)&pHash, sizeof(pHash), nullptr),
unpackXP(bKey, pID, checkHash, sig); *s = BN_lebin2bn((BYTE *)&pSignature, sizeof(pSignature), nullptr),
*x = BN_new(),
*y = BN_new();
// e = Hash // Create 2 points on the elliptic curve.
// s = Signature EC_POINT *t = EC_POINT_new(eCurve);
BIGNUM *e, *s; EC_POINT *p = EC_POINT_new(eCurve);
// Put hash word into BigNum e. // t = sG
e = BN_new(); EC_POINT_mul(eCurve, t, nullptr, basePoint, s, numContext);
BN_set_word(e, checkHash);
// Reverse signature and create a new BigNum s. // p = eK
endian((BYTE *)&sig, sizeof(sig)); EC_POINT_mul(eCurve, p, nullptr, publicKey, e, numContext);
s = BN_bin2bn((BYTE *)&sig, sizeof(sig), nullptr);
// Create x and y. // p += t
BIGNUM *x = BN_new(); EC_POINT_add(eCurve, p, t, p, numContext);
BIGNUM *y = BN_new();
// Create 2 new points on the existing elliptic curve. // x = p.x; y = p.y;
EC_POINT *u = EC_POINT_new(eCurve); EC_POINT_get_affine_coordinates(eCurve, p, x, y, numContext);
EC_POINT *v = EC_POINT_new(eCurve);
// EC_POINT_mul calculates r = generator * n + q * m. BYTE msgDigest[SHA_DIGEST_LENGTH]{},
// v = s * generator + e * (-publicKey) msgBuffer[SHA_MSG_LENGTH_XP]{},
xBin[FIELD_BYTES]{},
yBin[FIELD_BYTES]{};
// u = generator * s DWORD compHash;
EC_POINT_mul(eCurve, u, nullptr, generator, s, context);
// v = publicKey * e // Convert resulting point coordinates to bytes.
EC_POINT_mul(eCurve, v, nullptr, publicKey, e, context); BN_bn2lebin(x, xBin, FIELD_BYTES);
BN_bn2lebin(y, yBin, FIELD_BYTES);
// v += u // Assemble the SHA message.
EC_POINT_add(eCurve, v, u, v, context); memcpy((void *)&msgBuffer[0], (void *)&pSerial, 4);
memcpy((void *)&msgBuffer[4], (void *)xBin, FIELD_BYTES);
memcpy((void *)&msgBuffer[4 + FIELD_BYTES], (void *)yBin, FIELD_BYTES);
// EC_POINT_get_affine_coordinates() sets x and y, either of which may be nullptr, to the corresponding coordinates of p. // Retrieve the message digest.
// x = v.x; y = v.y; SHA1(msgBuffer, SHA_MSG_LENGTH_XP, msgDigest);
EC_POINT_get_affine_coordinates(eCurve, v, x, y, context);
BYTE buf[FIELD_BYTES], md[SHA_DIGEST_LENGTH], t[4]; // Translate the byte digest into a 32-bit integer - this is our computed hash.
DWORD newHash; // Truncate the hash to 28 bits.
compHash = BYDWORD(msgDigest) >> 4;
SHA_CTX hContext; compHash &= BITMASK(28);
// h = First32(SHA-1(pID || v.x || v.y)) >> 4
SHA1_Init(&hContext);
// Chop Product ID into 4 bytes.
t[0] = (pID & 0xff); // First 8 bits
t[1] = (pID & 0xff00) >> 8; // Second 8 bits
t[2] = (pID & 0xff0000) >> 16; // Third 8 bits
t[3] = (pID & 0xff000000) >> 24; // Fourth 8 bits
// Hash chunk of data.
SHA1_Update(&hContext, t, sizeof(t));
// Empty buffer, place v.x in little-endian.
memset(buf, 0, FIELD_BYTES);
BN_bn2bin(x, buf);
endian(buf, FIELD_BYTES);
// Hash chunk of data.
SHA1_Update(&hContext, buf, FIELD_BYTES);
// Empty buffer, place v.y in little-endian.
memset(buf, 0, FIELD_BYTES);
BN_bn2bin(y, buf);
endian(buf, FIELD_BYTES);
// Hash chunk of data.
SHA1_Update(&hContext, buf, FIELD_BYTES);
// Store the final message from hContext in md.
SHA1_Final(md, &hContext);
// h = First32(SHA-1(pID || v.x || v.y)) >> 4
newHash = (md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24)) >> 4;
newHash &= 0xfffffff;
BN_free(e); BN_free(e);
BN_free(s); BN_free(s);
BN_free(x); BN_free(x);
BN_free(y); BN_free(y);
BN_CTX_free(context); BN_CTX_free(numContext);
EC_POINT_free(u); EC_POINT_free(t);
EC_POINT_free(v); EC_POINT_free(p);
// If we managed to generate a key with the same hash, the key is correct. // If the computed hash checks out, the key is valid.
return newHash == checkHash; return compHash == pHash;
} }
/* Generate a valid Product Key. */ /* Generate a valid Product Key. */
void generateXPKey(EC_GROUP *eCurve, EC_POINT *generator, BIGNUM *order, BIGNUM *privateKey, DWORD pRaw, char (&pKey)[25]) { void generateXPKey(
EC_POINT *r = EC_POINT_new(eCurve); EC_GROUP *eCurve,
BN_CTX *ctx = BN_CTX_new(); EC_POINT *basePoint,
BIGNUM *genOrder,
BIGNUM *privateKey,
DWORD pSerial,
char (&pKey)[25]
) {
BN_CTX *numContext = BN_CTX_new();
BIGNUM *c = BN_new(); BIGNUM *c = BN_new();
BIGNUM *s = BN_new(); BIGNUM *s = BN_new();
BIGNUM *x = BN_new(); BIGNUM *x = BN_new();
BIGNUM *y = BN_new(); BIGNUM *y = BN_new();
QWORD bKey[2]{}; QWORD pRaw[2]{};
do { do {
DWORD hash = 0; EC_POINT *r = EC_POINT_new(eCurve);
QWORD sig = 0;
memset(bKey, 0, 2 * sizeof(QWORD)); QWORD pSignature = 0;
DWORD pHash;
// Generate a random number c consisting of 384 bits without any constraints. // Generate a random number c consisting of 384 bits without any constraints.
BN_rand(c, FIELD_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); BN_rand(c, FIELD_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY);
// r = generator * c; // Pick a random derivative of the base point on the elliptic curve.
EC_POINT_mul(eCurve, r, nullptr, generator, c, ctx); // R = cG;
EC_POINT_mul(eCurve, r, nullptr, basePoint, c, numContext);
// x = r.x; y = r.y; // Acquire its coordinates.
EC_POINT_get_affine_coordinates(eCurve, r, x, y, ctx); // x = R.x; y = R.y;
EC_POINT_get_affine_coordinates(eCurve, r, x, y, numContext);
SHA_CTX hContext; BYTE msgDigest[SHA_DIGEST_LENGTH]{},
BYTE md[SHA_DIGEST_LENGTH]{}, buf[FIELD_BYTES]{}, t[4]{}; msgBuffer[SHA_MSG_LENGTH_XP]{},
xBin[FIELD_BYTES]{},
yBin[FIELD_BYTES]{};
// h = (First-32(SHA1(pRaw, r.x, r.y)) >> 4 // Convert coordinates to bytes.
SHA1_Init(&hContext); BN_bn2lebin(x, xBin, FIELD_BYTES);
BN_bn2lebin(y, yBin, FIELD_BYTES);
// Chop Raw Product Key into 4 bytes. // Assemble the SHA message.
t[0] = (pRaw & 0xff); memcpy((void *)&msgBuffer[0], (void *)&pSerial, 4);
t[1] = (pRaw & 0xff00) >> 8; memcpy((void *)&msgBuffer[4], (void *)xBin, FIELD_BYTES);
t[2] = (pRaw & 0xff0000) >> 16; memcpy((void *)&msgBuffer[4 + FIELD_BYTES], (void *)yBin, FIELD_BYTES);
t[3] = (pRaw & 0xff000000) >> 24;
// Hash chunk of data. // Retrieve the message digest.
SHA1_Update(&hContext, t, sizeof(t)); SHA1(msgBuffer, SHA_MSG_LENGTH_XP, msgDigest);
// Empty buffer, place r.x in little-endian // Translate the byte digest into a 32-bit integer - this is our computed pHash.
memset(buf, 0, FIELD_BYTES); // Truncate the pHash to 28 bits.
BN_bn2bin(x, buf); pHash = BYDWORD(msgDigest) >> 4;
endian(buf, FIELD_BYTES); pHash &= BITMASK(28);
// Hash chunk of data. /*
SHA1_Update(&hContext, buf, FIELD_BYTES); *
* Scalars:
* c = Random multiplier
* e = Hash
* s = Signature
* n = Order of G
* k = Private Key
* K = Public Key
*
* Points:
* G(x, y) = Generator (Base Point)
*
* We need to find the signature s that satisfies the equation with a given hash:
* P = sG + eK
* s = ek + c (mod n) <- computation optimization
*/
// Empty buffer, place r.y in little-endian. // s = ek;
memset(buf, 0, FIELD_BYTES);
BN_bn2bin(y, buf);
endian(buf, FIELD_BYTES);
// Hash chunk of data.
SHA1_Update(&hContext, buf, FIELD_BYTES);
// Store the final message from hContext in md.
SHA1_Final(md, &hContext);
// h = (First-32(SHA1(pRaw, r.x, r.y)) >> 4
hash = (md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24)) >> 4;
hash &= 0xfffffff;
/* s = privateKey * hash + c; */
// s = privateKey;
BN_copy(s, privateKey); BN_copy(s, privateKey);
BN_mul_word(s, pHash);
// s *= hash; // s += c (mod n)
BN_mul_word(s, hash); BN_mod_add(s, s, c, genOrder, numContext);
// BN_mod_add() adds a to b % m and places the non-negative result in r. // Translate resulting scalar into a 64-bit integer (the byte order is little-endian).
// s = |s + c % order|; BN_bn2lebinpad(s, (BYTE *)&pSignature, BN_num_bytes(s));
BN_mod_add(s, s, c, order, ctx);
// Convert s from BigNum back to bytecode and reverse the endianness.
BN_bn2bin(s, (BYTE *)&sig);
endian((BYTE *)&sig, BN_num_bytes(s));
// Pack product key. // Pack product key.
packXP(bKey, pRaw, hash, sig); packXP(pRaw, pSerial, pHash, pSignature);
//printf("PID: %.8X\nHash: %.8X\nSig: %.8X %.8X\n", pRaw[0], hash, sig[1], sig[0]); std::cout << " Serial: 0x" << std::hex << std::setw(8) << std::setfill('0') << pSerial << std::endl
std::cout << " PID: " << std::hex << std::setw(8) << std::setfill('0') << pRaw << std::endl << " Hash: 0x" << std::hex << std::setw(8) << std::setfill('0') << pHash << std::endl
<< "Hash: " << std::hex << std::setw(8) << std::setfill('0') << hash << std::endl << " Signature: 0x" << std::hex << std::setw(8) << std::setfill('0') << pSignature << std::endl
<< " Sig: " << std::hex << std::setw(8) << std::setfill('0') << sig << std::endl << std::endl;
<< std::endl;
} while (bKey[1] >= (1ULL << 50)); EC_POINT_free(r);
} while (pRaw[1] > BITMASK(50));
// ↑ ↑ ↑ // ↑ ↑ ↑
// bKey[1] can't be longer than 50 bits, else the signature part will make // pRaw[1] can't be longer than 50 bits, else the signature part
// the CD-key longer than 25 characters. // will make the CD-key longer than 25 characters.
// Convert the key to Base24. // Convert bytecode to Base24 CD-key.
base24(pKey, (BYTE *)bKey); base24(pKey, (BYTE *)pRaw);
BN_free(c); BN_free(c);
BN_free(s); BN_free(s);
BN_free(x); BN_free(x);
BN_free(y); BN_free(y);
BN_CTX_free(ctx); BN_CTX_free(numContext);
EC_POINT_free(r);
} }