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/ec.h>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
// Algorithm macros
#define PK_LENGTH 25
#define NULL_TERMINATOR 1
#define PK_LENGTH 25
#define NULL_TERMINATOR 1
#define FIELD_BITS 384
#define FIELD_BYTES 48
#define FIELD_BITS_2003 512
#define FIELD_BYTES_2003 64
#define FIELD_BITS 384
#define FIELD_BYTES 48
#define FIELD_BITS_2003 512
#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 BYDWORD(n) (n[0] | n[1] << 8 | n[2] << 16 | n[3] << 24)
#define BITMASK(n) ((1ULL << n) - 1)
// Confirmation ID generator constants
#define SUCCESS 0
#define ERR_TOO_SHORT 1
@ -62,7 +68,9 @@ typedef uint64_t QWORD;
extern char pCharset[];
// 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);
EC_GROUP *initializeEllipticCurve(
std::string pSel,
std::string aSel,
@ -71,8 +79,8 @@ EC_GROUP *initializeEllipticCurve(
std::string generatorYSel,
std::string publicKeyXSel,
std::string publicKeyYSel,
EC_POINT *&genPoint,
EC_POINT *&pubPoint
EC_POINT *&genPoint,
EC_POINT *&pubPoint
);
// key.cpp
@ -98,18 +106,18 @@ void showHelp(char *argv[]);
// xp.cpp
bool verifyXPKey(
EC_GROUP *eCurve,
EC_POINT *generator,
EC_POINT *basePoint,
EC_POINT *publicKey,
char (&cdKey)[25]
char (&cdKey)[25]
);
void generateXPKey(
EC_GROUP *eCurve,
EC_POINT *generator,
BIGNUM *order,
BIGNUM *privateKey,
DWORD pRaw,
char (&cdKey)[25]
EC_POINT *basePoint,
BIGNUM *genOrder,
BIGNUM *privateKey,
DWORD pSerial,
char (&cdKey)[25]
);
// server.cpp

View File

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

View File

@ -84,4 +84,19 @@ EC_GROUP *initializeEllipticCurve(
BN_CTX_free(context);
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
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
*/
//
// Created by Andrew on 01/06/2023.
//
#include "header.h"
/* Unpacks the Windows XP Product Key. */
/* Unpacks the Windows XP-like Product Key. */
void unpackXP(
QWORD (&pRaw)[2],
DWORD &pSerial,
@ -37,7 +24,7 @@ void unpackXP(
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(
QWORD (&pRaw)[2],
DWORD pSerial,
@ -53,213 +40,207 @@ void packXP(
pRaw[1] = NEXTSNBITS(pSignature, 51, 5);
}
/* Verify Product Key */
bool verifyXPKey(EC_GROUP *eCurve, EC_POINT *generator, EC_POINT *publicKey, char (&cdKey)[25]) {
BN_CTX *context = BN_CTX_new();
/* Verifies the Windows XP-like Product Key. */
bool verifyXPKey(
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.
QWORD bKey[2]{};
DWORD pID, checkHash;
unbase24((BYTE *)pRaw, cdKey);
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.
unpackXP(bKey, pID, checkHash, sig);
BIGNUM *e = BN_lebin2bn((BYTE *)&pHash, sizeof(pHash), nullptr),
*s = BN_lebin2bn((BYTE *)&pSignature, sizeof(pSignature), nullptr),
*x = BN_new(),
*y = BN_new();
// e = Hash
// s = Signature
BIGNUM *e, *s;
// Create 2 points on the elliptic curve.
EC_POINT *t = EC_POINT_new(eCurve);
EC_POINT *p = EC_POINT_new(eCurve);
// Put hash word into BigNum e.
e = BN_new();
BN_set_word(e, checkHash);
// t = sG
EC_POINT_mul(eCurve, t, nullptr, basePoint, s, numContext);
// Reverse signature and create a new BigNum s.
endian((BYTE *)&sig, sizeof(sig));
s = BN_bin2bn((BYTE *)&sig, sizeof(sig), nullptr);
// p = eK
EC_POINT_mul(eCurve, p, nullptr, publicKey, e, numContext);
// Create x and y.
BIGNUM *x = BN_new();
BIGNUM *y = BN_new();
// p += t
EC_POINT_add(eCurve, p, t, p, numContext);
// Create 2 new points on the existing elliptic curve.
EC_POINT *u = EC_POINT_new(eCurve);
EC_POINT *v = EC_POINT_new(eCurve);
// x = p.x; y = p.y;
EC_POINT_get_affine_coordinates(eCurve, p, x, y, numContext);
// EC_POINT_mul calculates r = generator * n + q * m.
// v = s * generator + e * (-publicKey)
BYTE msgDigest[SHA_DIGEST_LENGTH]{},
msgBuffer[SHA_MSG_LENGTH_XP]{},
xBin[FIELD_BYTES]{},
yBin[FIELD_BYTES]{};
// u = generator * s
EC_POINT_mul(eCurve, u, nullptr, generator, s, context);
DWORD compHash;
// v = publicKey * e
EC_POINT_mul(eCurve, v, nullptr, publicKey, e, context);
// Convert resulting point coordinates to bytes.
BN_bn2lebin(x, xBin, FIELD_BYTES);
BN_bn2lebin(y, yBin, FIELD_BYTES);
// v += u
EC_POINT_add(eCurve, v, u, v, context);
// Assemble the SHA message.
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.
// x = v.x; y = v.y;
EC_POINT_get_affine_coordinates(eCurve, v, x, y, context);
// Retrieve the message digest.
SHA1(msgBuffer, SHA_MSG_LENGTH_XP, msgDigest);
BYTE buf[FIELD_BYTES], md[SHA_DIGEST_LENGTH], t[4];
DWORD newHash;
SHA_CTX hContext;
// 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;
// Translate the byte digest into a 32-bit integer - this is our computed hash.
// Truncate the hash to 28 bits.
compHash = BYDWORD(msgDigest) >> 4;
compHash &= BITMASK(28);
BN_free(e);
BN_free(s);
BN_free(x);
BN_free(y);
BN_CTX_free(context);
BN_CTX_free(numContext);
EC_POINT_free(u);
EC_POINT_free(v);
EC_POINT_free(t);
EC_POINT_free(p);
// If we managed to generate a key with the same hash, the key is correct.
return newHash == checkHash;
// If the computed hash checks out, the key is valid.
return compHash == pHash;
}
/* Generate a valid Product Key. */
void generateXPKey(EC_GROUP *eCurve, EC_POINT *generator, BIGNUM *order, BIGNUM *privateKey, DWORD pRaw, char (&pKey)[25]) {
EC_POINT *r = EC_POINT_new(eCurve);
BN_CTX *ctx = BN_CTX_new();
void generateXPKey(
EC_GROUP *eCurve,
EC_POINT *basePoint,
BIGNUM *genOrder,
BIGNUM *privateKey,
DWORD pSerial,
char (&pKey)[25]
) {
BN_CTX *numContext = BN_CTX_new();
BIGNUM *c = BN_new();
BIGNUM *s = BN_new();
BIGNUM *x = BN_new();
BIGNUM *y = BN_new();
QWORD bKey[2]{};
QWORD pRaw[2]{};
do {
DWORD hash = 0;
QWORD sig = 0;
EC_POINT *r = EC_POINT_new(eCurve);
memset(bKey, 0, 2 * sizeof(QWORD));
QWORD pSignature = 0;
DWORD pHash;
// 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);
// r = generator * c;
EC_POINT_mul(eCurve, r, nullptr, generator, c, ctx);
// Pick a random derivative of the base point on the elliptic curve.
// R = cG;
EC_POINT_mul(eCurve, r, nullptr, basePoint, c, numContext);
// x = r.x; y = r.y;
EC_POINT_get_affine_coordinates(eCurve, r, x, y, ctx);
// Acquire its coordinates.
// x = R.x; y = R.y;
EC_POINT_get_affine_coordinates(eCurve, r, x, y, numContext);
SHA_CTX hContext;
BYTE md[SHA_DIGEST_LENGTH]{}, buf[FIELD_BYTES]{}, t[4]{};
BYTE msgDigest[SHA_DIGEST_LENGTH]{},
msgBuffer[SHA_MSG_LENGTH_XP]{},
xBin[FIELD_BYTES]{},
yBin[FIELD_BYTES]{};
// h = (First-32(SHA1(pRaw, r.x, r.y)) >> 4
SHA1_Init(&hContext);
// Convert coordinates to bytes.
BN_bn2lebin(x, xBin, FIELD_BYTES);
BN_bn2lebin(y, yBin, FIELD_BYTES);
// Chop Raw Product Key into 4 bytes.
t[0] = (pRaw & 0xff);
t[1] = (pRaw & 0xff00) >> 8;
t[2] = (pRaw & 0xff0000) >> 16;
t[3] = (pRaw & 0xff000000) >> 24;
// Assemble the SHA message.
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);
// Hash chunk of data.
SHA1_Update(&hContext, t, sizeof(t));
// Retrieve the message digest.
SHA1(msgBuffer, SHA_MSG_LENGTH_XP, msgDigest);
// Empty buffer, place r.x in little-endian
memset(buf, 0, FIELD_BYTES);
BN_bn2bin(x, buf);
endian(buf, FIELD_BYTES);
// Translate the byte digest into a 32-bit integer - this is our computed pHash.
// Truncate the pHash to 28 bits.
pHash = BYDWORD(msgDigest) >> 4;
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.
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;
// s = ek;
BN_copy(s, privateKey);
BN_mul_word(s, pHash);
// s *= hash;
BN_mul_word(s, hash);
// s += c (mod n)
BN_mod_add(s, s, c, genOrder, numContext);
// BN_mod_add() adds a to b % m and places the non-negative result in r.
// s = |s + c % order|;
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));
// Translate resulting scalar into a 64-bit integer (the byte order is little-endian).
BN_bn2lebinpad(s, (BYTE *)&pSignature, BN_num_bytes(s));
// 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 << " PID: " << std::hex << std::setw(8) << std::setfill('0') << pRaw << std::endl
<< "Hash: " << std::hex << std::setw(8) << std::setfill('0') << hash << std::endl
<< " Sig: " << std::hex << std::setw(8) << std::setfill('0') << sig << std::endl
<< std::endl;
std::cout << " Serial: 0x" << std::hex << std::setw(8) << std::setfill('0') << pSerial << std::endl
<< " Hash: 0x" << std::hex << std::setw(8) << std::setfill('0') << pHash << std::endl
<< " Signature: 0x" << std::hex << std::setw(8) << std::setfill('0') << pSignature << 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
// the CD-key longer than 25 characters.
// pRaw[1] can't be longer than 50 bits, else the signature part
// will make the CD-key longer than 25 characters.
// Convert the key to Base24.
base24(pKey, (BYTE *)bKey);
// Convert bytecode to Base24 CD-key.
base24(pKey, (BYTE *)pRaw);
BN_free(c);
BN_free(s);
BN_free(x);
BN_free(y);
BN_CTX_free(ctx);
EC_POINT_free(r);
BN_CTX_free(numContext);
}