diff --git a/CMakeLists.txt b/CMakeLists.txt index 62f2531..cf7d19b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,5 +6,10 @@ set(CMAKE_CXX_STANDARD 14) add_executable( Keygen main.cpp + xp.cpp + server.cpp + header.h + utilities.cpp + key.cpp ) target_link_libraries(Keygen ${CMAKE_CURRENT_SOURCE_DIR}/lib/libcrypto.lib -static) \ No newline at end of file diff --git a/README.md b/README.md index 50a141b..2109643 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,126 @@ # XPKeygen -Windows XP VLK Keygen +A command line Windows XP VLK key generator. This tool allows you to generate _valid Windows XP keys_ based on a single +_raw product key_, which can be random. You can also provide the amount of keys to be generated using that raw +product key. -Known issues: -* Some of the keys aren't valid, but it's generally a less common occurrence. About 2 in 3 of the keys should work. +The **Raw Product Key (RPK)** is supplied in a form of 9 digits `XXX-YYYYYY`. -Issues/Pull Requests welcome. + +### Download +Check the **Releases** tab and download the latest version from there. + + +### Principle of operation +We need to use a random Raw Product Key as a base to generate a Product ID in a form of `AAAAA-BBB-CCCCCCS-DDEEE`. + +#### Product ID + +| Digits | Meaning | +|-------:|:-------------------------------------------------------| +| AAAAA | OS Family constant | +| BBB | Most significant 3 digits of the RPK | +| CCCCCC | Least significant 6 digits of the RPK | +| S | Check digit | +| DD | Index of the public key used to verify the Product Key | +| EEE | Random 3-digit number | + +The OS Family constant `AAAAA` is different for each series of Windows XP. For example, it is 76487 for SP3. + +The `BBB` and `CCCCCC` sections essentially directly correspond to the Raw Product Key. If the RPK is `XXXYYYYYY`, these two sections +will transform to `XXX` and `YYYYYY` respectively. + +The check digit `S` is picked so that the sum of all `C` digits with it added makes a number divisible by 7. + +The public key index `DD` lets us know which public key was used to successfully verify the authenticity of our Product Key. +For example, it's 22 for Professional keys and 23 for VLK keys. + +A random number `EEE` is used to generate a different Installation ID each time. + +#### Product Key + +The Product Key itself (not to confuse with the RPK) is of form `FFFFF-GGGGG-HHHHH-JJJJJ-KKKKK`, encoded in Base-24 with +the alphabet `BCDFGHJKMPQRTVWXY2346789` to exclude any characters that can be easily confused, like `I` and `1` or `O` and `0`. + +As per the alphabet capacity formula, the key can at most contain 114 bits of information. +$$N = log2(24^25) ~ 114$$ + +Based on that calculation, we unpack the 114-bit Product Key into 4 ordered segments: + +| Segment | Capacity | Data | +|-----------|----------|-------------------------------------------| +| Flag | 1 bit | Reserved, always set to `0x01`* | +| Serial | 30 bits | Raw Product Key (RPK) | +| Hash | 28 bits | RPK hash | +| Signature | 55 bits | Elliptic Curve signature for the RPK hash | + +For simplicity' sake, we'll combine `Flag` and `Serial` segments into a single segment called `Data`. By that logic we'll be able to extract the RPK by +shifting `Data` right and pack it back by shifting bits left. + +*It's not fully known what that bit does, but all a priori valid product keys I've checked had it set to 1. + +#### Elliptic Curves +Elliptic Curve Cryptography (ECC) is a type of public-key cryptographic system. +This class of systems relies on challenging "one-way" math problems - easy to compute one way and intractable to solve the "other" way. +Sometimes these are called "trapdoor" functions - easy to fall into, complicated to escape.[2] + +ECC relies on solving equations of the form +$$y^2 = x^3 + ax + b$$ + +In general, there are 2 special cases for the Elliptic Curve leveraged in cryptography - **F2m** and **Fp**. +They differ only slightly. Both curves are defined over the finite field, Fp uses a prime parameter that's larger than 3, +F2m assumes $p = 2m$. Microsoft used the latter in their algorithm. + +An elliptic curve over the finite field Fp consists of: +* a set of integer coordinates ${x, y}$, such that $0 <= x, y < p$; +* a set of points $y^2 = x^3 + ax + b \mod p$. + +**An elliptic curve over F17 would look like this:** + +The curve consists of the blue points in above image. In practice the "elliptic curves" +used in cryptography are "sets of points in square matrix". + +The above curve is "educational". It provides very small key length (4-5 bits). +In real world situations developers typically use curves of 256-bits or more. + + +Since it is a public-key cryptographic system, Microsoft had to share the public key with their Windows XP release to check entered product keys against. +It is stored within `pidgen.dll` in a form of a BINK resource. The first set of BINK data is there to validate retail keys, the second is for the +OEM keys respectively. + +In case you want to explore further, the source code of `pidgen.dll` and all its functions is available within this repository, in the "pidgen" folder. + +#### Generating valid keys + +To create the CD-key generation algorithm we must compute the corresponding private key using the public key supplied with `pidgen.dll`, +which means we have to reverse-solve the one-way ECC task. + +Judging by the key exposed in BINK, p is a prime number with a length of **384 bits**. +The computation difficulty using the most efficient Pollard's Rho algorithm ($O(\sqrtn)$) would be at least $O(2^168)$, but lucky for us, +Microsoft limited the value of the signature to 55 bits in order to reduce the amount of matching product keys, reducing the difficulty +to a far more manageable $O(2^28)$. + +The private key was, of course, conveniently computed before us in just 6 hours on a Celeron 800 machine. + +The rest of the job is done within the code of this keygen. + + +### Known issues +* ~~Some keys aren't valid, but it's generally a less common occurrence. About 2 in 3 of the keys should work.~~
+**Fixed in v1.2**. Prior versions generated a valid key with an exact chance of `0x40000/0x62A32`, which resulted in exactly +`0.64884`, or about 65%. My "2 in 3" estimate was inconceivably accurate. +* Tested **only** on Windows XP Professional SP3, but should work everywhere else as well. +* Server 2003 key generation not included yet. + + +### Literature +I will add more decent reads into the bibliography in a later release. + +**Understanding basics of Windows XP Activation**: +* [[1] Inside Windows Product Activation - Fully Licensed](https://www.licenturion.com/xp/fully-licensed-wpa.txt) +* [[2] Elliptic Curve Cryptography for Beginners - Matt Rickard](https://matt-rickard.com/elliptic-curve-cryptography) +* [[3] Elliptic Curve Cryptography (ECC) - Practical Cryptography for Developers](https://cryptobook.nakov.com/asymmetric-key-ciphers/elliptic-curve-cryptography-ecc) + + +**Tested on Windows XP Professional SP3**. + +Testing/Issues/Pull Requests welcome. diff --git a/header.h b/header.h new file mode 100644 index 0000000..036bd3b --- /dev/null +++ b/header.h @@ -0,0 +1,126 @@ +// +// Created by Andrew on 09/04/2023. +// + +#ifndef KEYGEN_HEADER_H +#define KEYGEN_HEADER_H + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define FIELD_BITS 384 +#define FIELD_BYTES (FIELD_BITS / 8) + +#define FIELD_BITS_2003 512 +#define FIELD_BYTES_2003 (FIELD_BITS_2003 / 8) + +#define PK_LENGTH 25 +#define NULL_TERMINATOR 1 + +typedef unsigned long ul32; + +extern HANDLE hConsole; +extern byte charset[]; + +extern const char pXP[]; +extern const long aXP; +extern const long bXP; + +// xp.cpp +void unpackXP( + ul32 *serial, + ul32 *hash, + ul32 *sig, + ul32 *raw + ); + +void packXP( + ul32 *raw, + ul32 *serial, + ul32 *hash, + ul32 *sig + ); + +void verifyXPKey( + EC_GROUP *eCurve, + EC_POINT *generator, + EC_POINT *publicKey, + char *cdKey + ); + +void generateXPKey( + byte *pKey, + EC_GROUP *eCurve, + EC_POINT *generator, + BIGNUM *order, + BIGNUM *privateKey, + ul32 *pRaw + ); + +// server.cpp +void unpackServer( + ul32 *osFamily, + ul32 *hash, + ul32 *sig, + ul32 *prefix, + ul32 *raw + ); + +void packServer( + ul32 *raw, + ul32 *osFamily, + ul32 *hash, + ul32 *sig, + ul32 *prefix + ); + +void verifyServerKey( + EC_GROUP *eCurve, + EC_POINT *generator, + EC_POINT *public_key, + char *cdKey + ); + +void generateServerKey( + byte *pKey, + EC_GROUP *eCurve, + EC_POINT *generator, + BIGNUM *order, + BIGNUM *privateKey, + ul32 *osFamily, + ul32 *prefix + ); + +// utilities.cpp +void cprintf(const char *Format, int nColor, ...); +void endiannessConvert(byte *data, int length); + +EC_GROUP *initializeEllipticCurve( + const char *pSel, + long aSel, + long bSel, + const char *generatorXSel, + const char *generatorYSel, + const char *publicKeyXSel, + const char *publicKeyYSel, + BIGNUM *genOrderSel, + BIGNUM *privateKeySel, + EC_POINT **genPoint, + EC_POINT **pubPoint + ); + +// key.cpp +void unbase24(ul32 *byteSeq, byte *cdKey); +void base24(byte *cdKey, ul32 *byteSeq); +void printProductKey(const char *pKey); +void printProductID(const ul32 *pRaw); + +#endif //KEYGEN_HEADER_H diff --git a/key.cpp b/key.cpp new file mode 100644 index 0000000..aea7c53 --- /dev/null +++ b/key.cpp @@ -0,0 +1,105 @@ +// +// Created by Andrew on 09/04/2023. +// + +#include "header.h" + +/* Convert from byte sequence to the CD-key. */ +void base24(byte *cdKey, ul32 *byteSeq) { + byte rbs[16]; + BIGNUM *z; + + // Copy byte sequence to the reversed byte sequence. + memcpy(rbs, byteSeq, sizeof(rbs)); + + // Skip trailing zeroes and reverse y. + int length; + + for (length = 15; rbs[length] == 0; length--); + endiannessConvert(rbs, ++length); + + // Convert reversed byte sequence to BigNum z. + z = BN_bin2bn(rbs, length, nullptr); + + // Divide z by 24 and convert the remainder to a CD-key char. + cdKey[25] = 0; + + for (int i = 24; i >= 0; i--) + cdKey[i] = charset[BN_div_word(z, 24)]; + + BN_free(z); +} + +/* Convert from CD-key to a byte sequence. */ +void unbase24(ul32 *byteSeq, byte *cdKey) { + BIGNUM *y = BN_new(); + BN_zero(y); + + // Empty byte sequence. + memset(byteSeq, 0, 16); + + // For each character in product key, place its ASCII-code. + for (int i = 0; i < 25; i++) { + BN_mul_word(y, 24); + BN_add_word(y, cdKey[i]); + } + + // Acquire length. + int n = BN_num_bytes(y); + + // Place the generated code into the byte sequence. + BN_bn2bin(y, (unsigned char *)byteSeq); + BN_free(y); + + // Reverse the byte sequence. + endiannessConvert((unsigned char *) byteSeq, n); +} + +/* Print Product Key. */ +void printProductKey(const char *pKey) { + assert(strlen((const char *)pKey) == 25); + + SetConsoleTextAttribute(hConsole, 0x0A); + + for (int i = 0; i < 25; i++) { + putchar(pKey[i]); + if (i != 24 && i % 5 == 4) putchar('-'); + } + + SetConsoleTextAttribute(hConsole, 0x0F); +} + +/* Print Product ID using a Product Key. */ +void printProductID(const ul32 *pRaw) { + char raw[12]; + char b[6], c[8]; + + // Cut away last bit of the product key and convert it to an ASCII-number (=raw) + sprintf(raw, "%lu", pRaw[0] >> 1); + + // Make B-part {...-640-...} -> most significant 3 digits of Raw Product Key + strncpy(b, raw, 3); + b[3] = 0; + + // Make C-part {...-123456X-...} -> least significant 6 digits of Raw Product Key + strcpy(c, raw + 3); + + // Make checksum digit-part {...56X-} + assert(strlen(c) == 6); + + int digit = 0; + + // Reverse sum algorithm to find a check digit that would add to the rest to form a sum divisible by 7. + for (int i = 0; i < 6; i++) + digit -= c[i] - '0'; + + while (digit < 0) + digit += 7; + + // Append check digit + null terminate. + c[6] = digit + '0'; + c[7] = 0; + + printf("Product ID: "); + cprintf("PPPPP-%s-%s-23XXX\n", 0x0E, b, c); +} \ No newline at end of file diff --git a/main.cpp b/main.cpp index 1a1fcb8..44e18e7 100644 --- a/main.cpp +++ b/main.cpp @@ -3,397 +3,103 @@ Rewritten by Endermanch */ -#include -#include -#include -#include -#include -#include -#include - -#define FIELD_BITS 384 -#define FIELD_BYTES 48 +#include "header.h" HANDLE hConsole; -unsigned char charset[] = "BCDFGHJKMPQRTVWXY2346789"; +byte charset[] = "BCDFGHJKMPQRTVWXY2346789"; -/* Colored output */ -VOID cprintf(CONST CHAR *Format, INT nColor, ...) { - va_list vList; +const char pXP[] = "92ddcf14cb9e71f4489a2e9ba350ae29454d98cb93bdbcc07d62b502ea12238ee904a8b20d017197aae0c103b32713a9"; +const long aXP = 1; +const long bXP = 0; - va_start(vList, nColor); +// Base point G (Generator) +const char genXXP[] = "46E3775ECE21B0898D39BEA57050D422A0AF989E497962BAEE2CB17E0A28D5360D5476B8DC966443E37A14F1AEF37742"; +const char genYXP[] = "7C8E741D2C34F4478E325469CD491603D807222C9C4AC09DDB2B31B3CE3F7CC191B3580079932BC6BEF70BE27604F65E"; - SetConsoleTextAttribute(hConsole, nColor); - vprintf(Format, vList); - SetConsoleTextAttribute(hConsole, 0x0F); +// Inverse of the public key +const char pubXXP[] = "5D8DBE75198015EC41C45AAB6143542EB098F6A5CC9CE4178A1B8A1E7ABBB5BC64DF64FAF6177DC1B0988AB00BA94BF8"; +const char pubYXP[] = "23A2909A0B4803C89F910C7191758B48746CEA4D5FF07667444ACDB9512080DBCA55E6EBF30433672B894F44ACE92BFA"; - va_end(vList); +// The order of G was computed in 18 hours using a Pentium III 450 +const char genOrderXP[] = "DB6B4C58EFBAFD"; - return; +// The private key was computed in 10 hours using a Pentium III 450 +const char privateKeyXP[] = "565B0DFF8496C8"; + +int mainServer() { + BIGNUM *a, *b, *p, *generatorX, *generatorY, *publicKeyX, *publicKeyY, *genOrder, *privateKey; + BN_CTX *context = BN_CTX_new(); + + a = BN_new(); + b = BN_new(); + p = BN_new(); + + generatorX = BN_new(); + generatorY = BN_new(); + + publicKeyX = BN_new(); + publicKeyY = BN_new(); + + genOrder = BN_new(); + + privateKey = BN_new(); + + /* Public data */ + // Data taken from pidgen.dll BINK-resources + BN_hex2bn(&p, "C9AE7AED19F6A7E100AADE98134111AD8118E59B8264734327940064BC675A0C682E19C89695FBFA3A4653E47D47FD7592258C7E3C3C61BBEA07FE5A7E842379"); + + BN_set_word(a, 1); + BN_set_word(b, 0); + + // Base point G (Generator) + BN_hex2bn(&generatorX, "85ACEC9F9F9B456A78E43C3637DC88D21F977A9EC15E5225BD5060CE5B892F24FEDEE574BF5801F06BC232EEF2161074496613698D88FAC4B397CE3B475406A7"); + BN_hex2bn(&generatorY, "66B7D1983F5D4FE43E8B4F1E28685DE0E22BBE6576A1A6B86C67533BF72FD3D082DBA281A556A16E593DB522942C8DD7120BA50C9413DF944E7258BDDF30B3C4"); + + // Inverse of the public key + BN_hex2bn(&publicKeyX, "90BF6BD980C536A8DB93B52AA9AEBA640BABF1D31BEC7AA345BB7510194A9B07379F552DA7B4A3EF81A9B87E0B85B5118E1E20A098641EE4CCF2045558C98C0E"); + BN_hex2bn(&publicKeyY, "6B87D1E658D03868362945CDD582E2CF33EE4BA06369E0EFE9E4851F6DCBEC7F15081E250D171EA0CC4CB06435BCFCFEA8F438C9766743A06CBD06E7EFB4C3AE"); + + /* Computed data */ + // Order of G <- from MSKey 4-in-1 + BN_hex2bn(&genOrder, "4CC5C56529F0237D"); + + // Computed private key + BN_hex2bn(&privateKey, "2606120F59C05118"); + + /* Elliptical Curve calculations. */ + // The group is defined via Fp = all integers [0; p - 1], where p is prime. + // The function EC_POINT_set_affine_coordinates() sets the x and y coordinates for the point p defined over the curve given in group. + EC_GROUP *eCurve = EC_GROUP_new_curve_GFp(p, a, b, context); + + // Create new point for the generator on the elliptic curve and set its coordinates to (genX; genY). + EC_POINT *genPoint = EC_POINT_new(eCurve); + EC_POINT_set_affine_coordinates(eCurve, genPoint, generatorX, generatorY, context); + + // Create new point for the public key on the elliptic curve and set its coordinates to (pubX; pubY). + EC_POINT *pubPoint = EC_POINT_new(eCurve); + EC_POINT_set_affine_coordinates(eCurve, pubPoint, publicKeyX, publicKeyY, context); + + // If generator and public key points are not on the elliptic curve, either the generator or the public key values are incorrect. + assert(EC_POINT_is_on_curve(eCurve, genPoint, context) == 1); + assert(EC_POINT_is_on_curve(eCurve, pubPoint, context) == 1); + + char pkey[25]{}; + ul32 osfamily[1], prefix[1]; + + osfamily[0] = 1280; + RAND_bytes((byte *)prefix, 4); + + prefix[0] &= 0x3ff; + generateServerKey((byte *)pkey, eCurve, genPoint, genOrder, privateKey, osfamily, prefix); + printProductKey(pkey); + printf("\n\n"); + verifyServerKey(eCurve, genPoint, pubPoint, (char *) pkey); + + BN_CTX_free(context); + + return 0; } -/* Unpacks the Product Key. */ -void extract(unsigned long *serial, unsigned long *hash, unsigned long *sig, unsigned long *raw) { - - // We're assuming that the quantity of information within the product key is at most 114 bits. - // log2(24^25) = 114. - - // Serial = Bits [0..30] -> 31 bits - serial[0] = raw[0] & 0x7fffffff; - - // Hash (e) = Bits [31..58] -> 28 bits - hash[0] = ((raw[0] >> 31) | (raw[1] << 1)) & 0xfffffff; - - // Signature (s) = Bits [59..113] -> 55 bits - sig[0] = (raw[1] >> 27) | (raw[2] << 5); - sig[1] = (raw[2] >> 27) | (raw[3] << 5); -} - -/* Repacks the Product Key. */ -void pack(unsigned long *raw, unsigned long *serial, unsigned long *hash, unsigned long *sig) { - raw[0] = serial[0] | ((hash[0] & 1) << 31); - raw[1] = (hash[0] >> 1) | ((sig[0] & 0x1f) << 27); - raw[2] = (sig[0] >> 5) | (sig[1] << 27); - raw[3] = sig[1] >> 5; -} - -/* Convert data between endianness types. */ -void endiannessConvert(unsigned char *data, int length) { - for (int i = 0; i < length / 2; i++) { - unsigned char temp = data[i]; - data[i] = data[length - i - 1]; - data[length - i - 1] = temp; - } -} - -/* Convert from byte sequence to the CD-key. */ -void base24(unsigned char *cdKey, unsigned long *byteSeq) { - unsigned char rbs[16]; - BIGNUM *z; - - // Copy byte sequence to the reversed byte sequence. - memcpy(rbs, byteSeq, sizeof(rbs)); - - // Skip trailing zeroes and reverse y. - int length; - - for (length = 15; rbs[length] == 0; length--); - endiannessConvert(rbs, ++length); - - // Convert reversed byte sequence to BigNum z. - z = BN_bin2bn(rbs, length, nullptr); - - // Divide z by 24 and convert the remainder to a CD-key char. - cdKey[25] = 0; - - for (int i = 24; i >= 0; i--) - cdKey[i] = charset[BN_div_word(z, 24)]; - - BN_free(z); -} - -/* Convert from CD-key to a byte sequence. */ -void unbase24(unsigned long *byteSeq, unsigned char *cdKey) { - BIGNUM *y = BN_new(); - BN_zero(y); - - // Empty byte sequence. - memset(byteSeq, 0, 16); - - // For each character in product key, place its ASCII-code. - for (int i = 0; i < 25; i++) { - BN_mul_word(y, 24); - BN_add_word(y, cdKey[i]); - } - - // Acquire length. - int n = BN_num_bytes(y); - - // Place the generated code into the byte sequence. - BN_bn2bin(y, (unsigned char *)byteSeq); - BN_free(y); - - // Reverse the byte sequence. - endiannessConvert((unsigned char *) byteSeq, n); -} - -/* Print Product ID using a Product Key. */ -void printProductID(unsigned long *pKey) { - char raw[12]; - char b[6], c[8]; - - // Cut away last bit of the product key and convert it to an ASCII-number (=raw) - sprintf(raw, "%lu", pKey[0] >> 1); - - // Make B-part {...-640-...} -> most significant 3 digits of Raw Product Key - strncpy(b, raw, 3); - b[3] = 0; - - // Make C-part {...-123456X-...} -> least significant 6 digits of Raw Product Key - strcpy(c, raw + 3); - - // Make checksum digit-part {...56X-} - assert(strlen(c) == 6); - - int digit = 0; - - // Reverse sum algorithm to find a check digit that would add to the rest to form a sum divisible by 7. - for (int i = 0; i < 6; i++) - digit -= c[i] - '0'; - - while (digit < 0) - digit += 7; - - // Append check digit + null terminate. - c[6] = digit + '0'; - c[7] = 0; - - printf("Product ID: "); - cprintf("PPPPP-%s-%s-23XXX\n", 0x0E, b, c); -} - -/* Print Product Key. */ -void printProductKey(unsigned char *pKey) { - assert(strlen((const char *)pKey) == 25); - - SetConsoleTextAttribute(hConsole, 0x0A); - - for (int i = 0; i < 25; i++) { - putchar(pKey[i]); - if (i != 24 && i % 5 == 4) putchar('-'); - } - - SetConsoleTextAttribute(hConsole, 0x0F); -} - -/* Verify Product Key */ -void verifyKey(EC_GROUP *ec, EC_POINT *generator, EC_POINT *publicKey, char *cdKey) { - unsigned char key[25]; - - BN_CTX *ctx = BN_CTX_new(); - - // Remove dashes from the CD-key. - for (int i = 0, k = 0; i < strlen(cdKey) && k < 25; i++) { - for (int j = 0; j < 24; j++) { - if (cdKey[i] != '-' && cdKey[i] == charset[j]) { - key[k++] = j; - break; - } - - // Make sure the CD-key passes the verification procedure. - assert(j < 24); - } - } - - // Convert Base24 CD-key to bytecode. - unsigned long bKey[4]{}; - unsigned long pID[1], hash[1], sig[2]; - - unbase24(bKey, key); - - // Output CD-key bytecode. - printf("Bytecode: %.8lX %.8lX %.8lX %.8lX\n", bKey[3], bKey[2], bKey[1], bKey[0]); - - // Extract pid_data, hash and signature from the bytecode. - extract(pID, hash, sig, bKey); - printProductID(pID); - - printf("PID: %.8lX\nHash: %.8lX\nSignature: %.8lX %.8lX\n", pID[0], hash[0], sig[1], sig[0]); - - // e = Hash - // s = Signature - BIGNUM *e, *s; - - // Put hash word into BigNum e. - e = BN_new(); - BN_set_word(e, hash[0]); - - // Reverse signature and create a new BigNum s. - endiannessConvert((unsigned char *) sig, sizeof(sig)); - s = BN_bin2bn((unsigned char *)sig, sizeof(sig), nullptr); - - // Create x and y. - BIGNUM *x = BN_new(); - BIGNUM *y = BN_new(); - - // Create 2 new points on the existing elliptic curve. - EC_POINT *u = EC_POINT_new(ec); - EC_POINT *v = EC_POINT_new(ec); - - - // EC_POINT_mul calculates r = generator * n + q * m. - // v = s * generator + e * (-publicKey) - - // u = generator * s - EC_POINT_mul(ec, u, nullptr, generator, s, ctx); - - // v = publicKey * e - EC_POINT_mul(ec, v, nullptr, publicKey, e, ctx); - - // v += u - EC_POINT_add(ec, v, u, v, ctx); - - // EC_POINT_get_affine_coordinates() sets x and y, either of which may be NULL, to the corresponding coordinates of p. - // x = v.x; y = v.y; - EC_POINT_get_affine_coordinates(ec, v, x, y, ctx); - - - unsigned char buf[FIELD_BYTES], md[SHA_DIGEST_LENGTH], t[4]; - unsigned long h; - - SHA_CTX hContext; - - /* h = (first 32 bits of SHA1(pID || v.x, v.y)) >> 4 */ - SHA1_Init(&hContext); - - // Chop Product ID into 4 bytes. - t[0] = pID[0] & 0xff; // First 8 bits - t[1] = (pID[0] & 0xff00) >> 8; // Second 8 bits - t[2] = (pID[0] & 0xff0000) >> 16; // Third 8 bits - t[3] = (pID[0] & 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, sizeof(buf)); - BN_bn2bin(x, buf); - endiannessConvert((unsigned char *) buf, sizeof(buf)); - - // Hash chunk of data. - SHA1_Update(&hContext, buf, sizeof(buf)); - - // Empty buffer, place v.y in little-endian. - memset(buf, 0, sizeof(buf)); - BN_bn2bin(y, buf); - endiannessConvert((unsigned char *) buf, sizeof(buf)); - - // Hash chunk of data. - SHA1_Update(&hContext, buf, sizeof(buf)); - - // Store the final message from hContext in md. - SHA1_Final(md, &hContext); - - // h = (first 32 bits of SHA1(pID || v.x, v.y)) >> 4 - h = (md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24)) >> 4; - h &= 0xfffffff; - - printf("Calculated hash: %.8lX\n", h); - - // If we managed to generateKey a key with the same hash, the key is correct. - if (h == hash[0]) cprintf("Key valid\n", 0x0A); - else cprintf("Key invalid\n", 0x0C); - - putchar('\n'); - - BN_free(e); - BN_free(s); - BN_free(x); - BN_free(y); - - BN_CTX_free(ctx); - - EC_POINT_free(u); - EC_POINT_free(v); -} - -/* Generate a valid Product Key. */ -void generateKey(unsigned char *pKey, EC_GROUP *eCurve, EC_POINT *generator, BIGNUM *order, BIGNUM *privateKey, unsigned long *pRaw) { - EC_POINT *r = EC_POINT_new(eCurve); - BN_CTX *ctx = BN_CTX_new(); - - BIGNUM *c = BN_new(); - BIGNUM *s = BN_new(); - BIGNUM *x = BN_new(); - BIGNUM *y = BN_new(); - - unsigned long bKey[4]; - - do { - // 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, NULL, generator, c, ctx); - - // x = r.x; y = r.y; - EC_POINT_get_affine_coordinates(eCurve, r, x, y, ctx); - - SHA_CTX hContext; - unsigned char md[SHA_DIGEST_LENGTH], buf[FIELD_BYTES], t[4]; - unsigned long hash[1]; - - // h = (First-32(SHA1(pRaw, r.x, r.y)) >> 4 - SHA1_Init(&hContext); - - // Chop Raw Product Key into 4 bytes. - t[0] = pRaw[0] & 0xff; - t[1] = (pRaw[0] & 0xff00) >> 8; - t[2] = (pRaw[0] & 0xff0000) >> 16; - t[3] = (pRaw[0] & 0xff000000) >> 24; - - // Hash chunk of data. - SHA1_Update(&hContext, t, sizeof(t)); - - // Empty buffer, place r.x in little-endian. - memset(buf, 0, sizeof(buf)); - BN_bn2bin(x, buf); - endiannessConvert((unsigned char *) buf, sizeof(buf)); - - // Hash chunk of data. - SHA1_Update(&hContext, buf, sizeof(buf)); - - // Empty buffer, place r.y in little-endian. - memset(buf, 0, sizeof(buf)); - BN_bn2bin(y, buf); - endiannessConvert((unsigned char *) buf, sizeof(buf)); - - // Hash chunk of data. - SHA1_Update(&hContext, buf, sizeof(buf)); - - // Store the final message from hContext in md. - SHA1_Final(md, &hContext); - - // h = (First-32(SHA1(pRaw, r.x, r.y)) >> 4 - hash[0] = (md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24)) >> 4; - hash[0] &= 0xfffffff; - - /* s = privateKey * hash + c; */ - // s = privateKey; - BN_copy(s, privateKey); - - // s *= hash; - BN_mul_word(s, hash[0]); - - // 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. - unsigned long sig[2]{}; - - BN_bn2bin(s, (unsigned char *)sig); - endiannessConvert((unsigned char *) sig, BN_num_bytes(s)); - - // Pack product key. - pack(bKey, pRaw, hash, sig); - - printf("PID: %.8lX\nHash: %.8lX\nSignature: %.8lX %.8lX\n\n", pRaw[0], hash[0], sig[1], sig[0]); - } while (bKey[3] >= 0x62A32); // Loop in case signature part will make the CD-key longer than 25 characters. - - // Convert the key to Base24. - base24(pKey, bKey); - - BN_free(c); - BN_free(s); - BN_free(x); - BN_free(y); - - BN_CTX_free(ctx); - EC_POINT_free(r); -} - - /* * PK: VX8CG-8KC6V-PVPMD-GKPPH-GC7W8 * @@ -404,7 +110,7 @@ void generateKey(unsigned char *pKey, EC_GROUP *eCurve, EC_POINT *generator, BIG * To convert a 25-digit key to binary data, we need to: * 1. Think of the key as of an array of bytes. Then convert the concatenated key VX8CG8KC6VPVPMDGKPPHGC7W8 * into its Base24 representation ('B' = 0, 'C' = 1, 'D' = 2, ...) -> [ 13, 15, 22, 1, 4, ... ]. - * 2. Compute the decoded array in little-endian. + * 2. Compute the decoded array in little-endiannessConvert. * 3. The decoded result is divided into sections: * - 12 bits -> OS Family * - 31 bits -> Hash @@ -430,117 +136,42 @@ void generateKey(unsigned char *pKey, EC_GROUP *eCurve, EC_POINT *generator, BIG * CCCCCCC | least significant six digits of Raw Product Key * | plus check digit (see below) * DD | index of the public key used to verify the - * | Product Key. Example: 22 for PRO version and 23 for VLK version + * | Product Key. Example: 22 for Professional keys; 23 for VLK keys * EEE | random value (used for phone activation, different installation IDs are generated) */ +/* + * Decoding the Product Key results in an example byte sequence. + * + * 0x6F 0xFA 0x95 0x45 0xFC 0x75 0xB5 0x52 0xBB 0xEF 0xB1 0x17 0xDA 0xCD 0x00 + * + * Of these 15 bytes the least significant four bytes contain the Raw + * Product Key in little endian byte order. The least significant bit is + * removed by shifting this 32-bit value (0x4595FA6F - remember the + * little endiannessConvert byte order) to the left by one bit position, resulting + * in a Raw Product Key of 0x22CAFD37, or + * + * 583728439 + * + * in decimal notation. + */ int main() { + char pKey[PK_LENGTH + NULL_TERMINATOR]{}; + ul32 pRaw[1]{}, nAmount = 1; + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - // Initialize BIGNUM and BIGNUMCTX structures. - // BIGNUM - Large numbers - // BIGNUMCTX - Context large numbers (temporary) - BIGNUM *a, *b, *p, *generatorX, *generatorY, *publicKeyX, *publicKeyY, *genOrder, *privateKey; - BN_CTX *context; - - // Microsoft Product Key identification program uses a public key stored in pidgen.dll's BINK resource, - // which is an Elliptic Curve Cryptography (ECC) public key. It can be decomposed into a following mathematical task: - - // We're presented with an elliptic curve, a multivariable function y(x; p; a; b), where - // y^2 % p = x^3 + ax + b % p. - a = BN_new(); - b = BN_new(); - p = BN_new(); - - // Public key will consist of the resulting (x; y) values. - publicKeyX = BN_new(); - publicKeyY = BN_new(); - - // G(x; y) is a generator function, its return value represents a point on the elliptic curve. - generatorX = BN_new(); - generatorY = BN_new(); - - // We cannot produce a valid key without knowing the private key k. The reason for this is that - // we need the result of the function K(x; y) = kG(x; y). - privateKey = BN_new(); - - // We can, however, validate any given key using the available public key: {p, a, b, G, K}. - // genOrder the order of the generator G, a value we have to reverse -> Schoof's Algorithm. - genOrder = BN_new(); - - // Context variable - context = BN_CTX_new(); - - - /* Public data */ - // Data taken from pidgen.dll BINK-resources - BN_hex2bn(&p, "92ddcf14cb9e71f4489a2e9ba350ae29454d98cb93bdbcc07d62b502ea12238ee904a8b20d017197aae0c103b32713a9"); - - BN_set_word(a, 1); - BN_set_word(b, 0); - - // Base point G (Generator) - BN_hex2bn(&generatorX, "46E3775ECE21B0898D39BEA57050D422A0AF989E497962BAEE2CB17E0A28D5360D5476B8DC966443E37A14F1AEF37742"); - BN_hex2bn(&generatorY, "7C8E741D2C34F4478E325469CD491603D807222C9C4AC09DDB2B31B3CE3F7CC191B3580079932BC6BEF70BE27604F65E"); - - // Inverse of the public key - BN_hex2bn(&publicKeyX, "5D8DBE75198015EC41C45AAB6143542EB098F6A5CC9CE4178A1B8A1E7ABBB5BC64DF64FAF6177DC1B0988AB00BA94BF8"); - BN_hex2bn(&publicKeyY, "23A2909A0B4803C89F910C7191758B48746CEA4D5FF07667444ACDB9512080DBCA55E6EBF30433672B894F44ACE92BFA"); - - - /* Computed data */ - // The order of G was computed in 18 hours using a Pentium III 450 - BN_hex2bn(&genOrder, "DB6B4C58EFBAFD"); - - // The private key was computed in 10 hours using a Pentium III 450 - BN_hex2bn(&privateKey, "565B0DFF8496C8"); - - - /* Elliptical Curve calculations. */ - // The group is defined via Fp = all integers [0; p - 1], where p is prime. - // The function EC_POINT_set_affine_coordinates() sets the x and y coordinates for the point p defined over the curve given in group. - EC_GROUP *eCurve = EC_GROUP_new_curve_GFp(p, a, b, context); - - // Create new point for the generator on the elliptic curve and set its coordinates to (genX; genY). - EC_POINT *genPoint = EC_POINT_new(eCurve); - EC_POINT_set_affine_coordinates(eCurve, genPoint, generatorX, generatorY, context); - - // Create new point for the public key on the elliptic curve and set its coordinates to (pubX; pubY). - EC_POINT *pub = EC_POINT_new(eCurve); - EC_POINT_set_affine_coordinates(eCurve, pub, publicKeyX, publicKeyY, context); - - - /* Generate a key. */ - unsigned char pKey[26]{}; - unsigned long pRaw[1]{}; - - /* - * Decoding the Product Key results in an example byte sequence. - * - * 0x6F 0xFA 0x95 0x45 0xFC 0x75 0xB5 0x52 0xBB 0xEF 0xB1 0x17 0xDA 0xCD 0x00 - * - * Of these 15 bytes the least significant four bytes contain the Raw - * Product Key in little endian byte order. The least significant bit is - * removed by shifting this 32-bit value (0x4595FA6F - remember the - * little endian byte order) to the left by one bit position, resulting - * in a Raw Product Key of 0x22CAFD37, or - * - * 583728439 - * - * in decimal notation. - */ - SetConsoleTitleA("Windows XP VLK Keygen"); system("cls"); cprintf("Windows XP VLK Keygen\n\n", 0x08); cprintf("Principle of Operation:\n", 0x0C); - printf("We need a valid Raw Product Key to generate the Product ID in form of AAAAA-BBB-CCCCCCS-DDEEE.\n\n"); + printf("We need a valid Raw Product Key to generate a Product ID in form of AAAAA-BBB-CCCCCCS-DDEEE.\n\n"); printf("AAAAA is the Windows XP Series constant - different for each version.\n"); printf("Raw Product Key directly represents the BBB-CCCCCC part of the Product ID.\n"); - printf("S is a \"check bit\": it's picked so that the sum of all C digits with it added makes a number divisble by 7.\n"); + printf("S is a \"check bit\": it's picked so that the sum of all C digits with it added makes a number divisible by 7.\n"); printf("DD is the index of the public key used to verify the Product Key.\n"); printf("EEE is a random number used to generate a different Installation ID each time.\n\n"); @@ -561,27 +192,59 @@ int main() { cprintf("Input Raw Product Key BBB-CCCCCC WITHOUT DASHES in range [100-000000; 999-999999]: ", 0x0E); scanf_s("%lu", pRaw); + system("cls"); + + printf("Raw Product Key: "); + cprintf("%d-%d\n", 0x0E, pRaw[0] / 1'000'000, pRaw[0] % 1'000'000); + printf("How many keys would you like to generate? "); + scanf_s("%lu", &nAmount); + printf("\n"); + // We cannot produce a valid key without knowing the private key k. The reason for this is that + // we need the result of the function K(x; y) = kG(x; y). + BIGNUM *privateKey = BN_new(); + + // We can, however, validate any given key using the available public key: {p, a, b, G, K}. + // genOrder the order of the generator G, a value we have to reverse -> Schoof's Algorithm. + BIGNUM *genOrder = BN_new(); + + /* Computed data */ + BN_hex2bn(&genOrder, genOrderXP); + BN_hex2bn(&privateKey, privateKeyXP); + + EC_POINT *genPoint, *pubPoint; + EC_GROUP *eCurve = initializeEllipticCurve( + pXP, + aXP, + bXP, + genXXP, + genYXP, + pubXXP, + pubYXP, + genOrder, + privateKey, + &genPoint, + &pubPoint + ); + + // Shift left once. pRaw[0] <<= 1; - for (int i = 0; i < 10; i++) { + for (int i = 0; i < nAmount; i++) { cprintf("Product Key %d:\n", 0x08, i + 1); - generateKey(pKey, eCurve, genPoint, genOrder, privateKey, pRaw); + // Generate the key. + generateXPKey((byte *)pKey, eCurve, genPoint, genOrder, privateKey, pRaw); printProductKey(pKey); printf("\n\n"); - // Verify the key - verifyKey(eCurve, genPoint, pub, (char *) pKey); + // Verify the key. + verifyXPKey(eCurve, genPoint, pubPoint, pKey); } - - // Cleanup - BN_CTX_free(context); - system("pause"); - return 0; -} + return 0; +} \ No newline at end of file diff --git a/server.cpp b/server.cpp new file mode 100644 index 0000000..59ed7fa --- /dev/null +++ b/server.cpp @@ -0,0 +1,216 @@ +// +// Created by Andrew on 09/04/2023. +// + +#include "header.h" + +void unpackServer(ul32 *osFamily, ul32 *hash, ul32 *sig, ul32 *prefix, ul32 *raw) { + osFamily[0] = raw[0] & 0x7ff; + hash[0] = ((raw[0] >> 11) | (raw[1] << 21)) & 0x7fffffff; + sig[0] = (raw[1] >> 10) | (raw[2] << 22); + sig[1] = ((raw[2] >> 10) | (raw[3] << 22)) & 0x3fffffff; + prefix[0] = (raw[3] >> 8) & 0x3ff; +} + +void packServer(ul32 *raw, ul32 *osFamily, ul32 *hash, ul32 *sig, ul32 *prefix) { + raw[0] = osFamily[0] | (hash[0] << 11); + raw[1] = (hash[0] >> 21) | (sig[0] << 10); + raw[2] = (sig[0] >> 22) | (sig[1] << 10); + raw[3] = (sig[1] >> 22) | (prefix[0] << 8); +} + +void verifyServerKey(EC_GROUP *eCurve, EC_POINT *generator, EC_POINT *public_key, char *cdKey) { + byte key[25]; + int i, j, k; + + BN_CTX *ctx = BN_CTX_new(); + + for (i = 0, k = 0; i < strlen(cdKey); i++) { + for (j = 0; j < 24; j++) { + if (cdKey[i] != '-' && cdKey[i] == charset[j]) { + key[k++] = j; + break; + } + assert(j < 24); + } + if (k >= 25) break; + } + + ul32 bkey[4] = {0}; + ul32 osfamily[1], hash[1], sig[2], prefix[1]; + unbase24(bkey, key); + printf("%.8x %.8x %.8x %.8x\n", bkey[3], bkey[2], bkey[1], bkey[0]); + unpackServer(osfamily, hash, sig, prefix, bkey); + + printf("OS Family: %u\nHash: %.8x\nSig: %.8x %.8x\nPrefix: %.8x\n", osfamily[0], hash[0], sig[1], sig[0], prefix[0]); + + byte buf[FIELD_BYTES_2003], md[SHA_DIGEST_LENGTH]; + ul32 h1[2]; + SHA_CTX h_ctx; + + /* h1 = SHA-1(5D || OS Family || Hash || Prefix || 00 00) */ + SHA1_Init(&h_ctx); + buf[0] = 0x5d; + buf[1] = osfamily[0] & 0xff; + buf[2] = (osfamily[0] & 0xff00) >> 8; + buf[3] = hash[0] & 0xff; + buf[4] = (hash[0] & 0xff00) >> 8; + buf[5] = (hash[0] & 0xff0000) >> 16; + buf[6] = (hash[0] & 0xff000000) >> 24; + buf[7] = prefix[0] & 0xff; + buf[8] = (prefix[0] & 0xff00) >> 8; + buf[9] = buf[10] = 0; + SHA1_Update(&h_ctx, buf, 11); + SHA1_Final(md, &h_ctx); + h1[0] = md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24); + h1[1] = (md[4] | (md[5] << 8) | (md[6] << 16) | (md[7] << 24)) >> 2; + h1[1] &= 0x3FFFFFFF; + printf("h1: %.8x %.8x\n", h1[1], h1[0]); + + BIGNUM *s, *h, *x, *y; + x = BN_new(); + y = BN_new(); + endiannessConvert((byte *) sig, 8); + endiannessConvert((byte *) h1, 8); + s = BN_bin2bn((byte *)sig, 8, NULL); + h = BN_bin2bn((byte *)h1, 8, NULL); + + EC_POINT *r = EC_POINT_new(eCurve); + EC_POINT *t = EC_POINT_new(eCurve); + /* r = sig*(sig*generator + h1*public_key) */ + EC_POINT_mul(eCurve, t, NULL, generator, s, ctx); + EC_POINT_mul(eCurve, r, NULL, public_key, h, ctx); + EC_POINT_add(eCurve, r, r, t, ctx); + EC_POINT_mul(eCurve, r, NULL, r, s, ctx); + EC_POINT_get_affine_coordinates_GFp(eCurve, r, x, y, ctx); + + ul32 h2[1]; + /* h2 = SHA-1(79 || OS Family || r.x || r.y) */ + SHA1_Init(&h_ctx); + buf[0] = 0x79; + buf[1] = osfamily[0] & 0xff; + buf[2] = (osfamily[0] & 0xff00) >> 8; + SHA1_Update(&h_ctx, buf, 3); + + memset(buf, 0, FIELD_BYTES_2003); + BN_bn2bin(x, buf); + endiannessConvert((byte *) buf, FIELD_BYTES_2003); + SHA1_Update(&h_ctx, buf, FIELD_BYTES_2003); + + memset(buf, 0, FIELD_BYTES_2003); + BN_bn2bin(y, buf); + endiannessConvert((byte *) buf, FIELD_BYTES_2003); + SHA1_Update(&h_ctx, buf, FIELD_BYTES_2003); + + SHA1_Final(md, &h_ctx); + h2[0] = (md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24)) & 0x7fffffff; + printf("Calculated hash: %.8x\n", h2[0]); + + if (h2[0] == hash[0]) printf("Key VALID\n"); + else printf("Key invalid\n"); + + BN_free(s); + BN_free(h); + BN_free(x); + BN_free(y); + EC_POINT_free(r); + EC_POINT_free(t); + BN_CTX_free(ctx); +} + +void generateServerKey(byte *pKey, EC_GROUP *eCurve, EC_POINT *generator, BIGNUM *order, BIGNUM *privateKey, ul32 *osFamily, ul32 *prefix) { + BN_CTX *ctx = BN_CTX_new(); + + BIGNUM *k = BN_new(); + BIGNUM *s = BN_new(); + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + BIGNUM *b = BN_new(); + EC_POINT *r = EC_POINT_new(eCurve); + + ul32 bkey[4]; + byte buf[FIELD_BYTES_2003], md[20]; + ul32 h1[2]; + ul32 hash[1], sig[2]; + + SHA_CTX h_ctx; + + for (;;) { + /* r = k*generator */ + BN_rand(k, FIELD_BITS_2003, -1, 0); + EC_POINT_mul(eCurve, r, NULL, generator, k, ctx); + EC_POINT_get_affine_coordinates(eCurve, r, x, y, ctx); + + /* hash = SHA-1(79 || OS Family || r.x || r.y) */ + SHA1_Init(&h_ctx); + buf[0] = 0x79; + buf[1] = osFamily[0] & 0xff; + buf[2] = (osFamily[0] & 0xff00) >> 8; + SHA1_Update(&h_ctx, buf, 3); + + memset(buf, 0, FIELD_BYTES_2003); + BN_bn2bin(x, buf); + endiannessConvert((byte *) buf, FIELD_BYTES_2003); + SHA1_Update(&h_ctx, buf, FIELD_BYTES_2003); + + memset(buf, 0, FIELD_BYTES_2003); + BN_bn2bin(y, buf); + endiannessConvert((byte *) buf, FIELD_BYTES_2003); + SHA1_Update(&h_ctx, buf, FIELD_BYTES_2003); + + SHA1_Final(md, &h_ctx); + hash[0] = (md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24)) & 0x7fffffff; + + /* h1 = SHA-1(5D || OS Family || Hash || Prefix || 00 00) */ + SHA1_Init(&h_ctx); + buf[0] = 0x5d; + buf[1] = osFamily[0] & 0xff; + buf[2] = (osFamily[0] & 0xff00) >> 8; + buf[3] = hash[0] & 0xff; + buf[4] = (hash[0] & 0xff00) >> 8; + buf[5] = (hash[0] & 0xff0000) >> 16; + buf[6] = (hash[0] & 0xff000000) >> 24; + buf[7] = prefix[0] & 0xff; + buf[8] = (prefix[0] & 0xff00) >> 8; + buf[9] = buf[10] = 0; + SHA1_Update(&h_ctx, buf, 11); + SHA1_Final(md, &h_ctx); + h1[0] = md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24); + h1[1] = (md[4] | (md[5] << 8) | (md[6] << 16) | (md[7] << 24)) >> 2; + h1[1] &= 0x3FFFFFFF; + printf("h1: %.8x %.8x\n", h1[1], h1[0]); + + /* s = ( -h1*privateKey + sqrt( (h1*privateKey)^2 + 4k ) ) / 2 */ + endiannessConvert((byte *) h1, 8); + BN_bin2bn((byte *)h1, 8, b); + BN_mod_mul(b, b, privateKey, order, ctx); + BN_copy(s, b); + BN_mod_sqr(s, s, order, ctx); + BN_lshift(k, k, 2); + BN_add(s, s, k); + BN_mod_sqrt(s, s, order, ctx); + BN_mod_sub(s, s, b, order, ctx); + if (BN_is_odd(s)) { + BN_add(s, s, order); + } + BN_rshift1(s, s); + sig[0] = sig[1] = 0; + BN_bn2bin(s, (byte *)sig); + endiannessConvert((byte *) sig, BN_num_bytes(s)); + if (sig[1] < 0x40000000) break; + } + packServer(bkey, osFamily, hash, sig, prefix); + printf("OS family: %u\nHash: %.8x\nSig: %.8x %.8x\nPrefix: %.8x\n", osFamily[0], hash[0], sig[1], sig[0], prefix[0]); + printf("%.8x %.8x %.8x %.8x\n", bkey[3], bkey[2], bkey[1], bkey[0]); + base24(pKey, bkey); + + BN_free(k); + BN_free(s); + BN_free(x); + BN_free(y); + BN_free(b); + EC_POINT_free(r); + + BN_CTX_free(ctx); + +} \ No newline at end of file diff --git a/utilities.cpp b/utilities.cpp new file mode 100644 index 0000000..c96ad74 --- /dev/null +++ b/utilities.cpp @@ -0,0 +1,99 @@ +// +// Created by Andrew on 09/04/2023. +// + +#include "header.h" + +/* Colored output */ +void cprintf(const char *Format, int nColor, ...) { + va_list vList; + + va_start(vList, nColor); + + SetConsoleTextAttribute(hConsole, nColor); + vprintf(Format, vList); + SetConsoleTextAttribute(hConsole, 0x0F); + + va_end(vList); +} + +/* Convert data between endianness types. */ +void endiannessConvert(byte *data, int length) { + for (int i = 0; i < length / 2; i++) { + byte temp = data[i]; + data[i] = data[length - i - 1]; + data[length - i - 1] = temp; + } +} + +EC_GROUP *initializeEllipticCurve( + const char *pSel, + long aSel, + long bSel, + const char *generatorXSel, + const char *generatorYSel, + const char *publicKeyXSel, + const char *publicKeyYSel, + BIGNUM *genOrderSel, + BIGNUM *privateKeySel, + EC_POINT **genPoint, + EC_POINT **pubPoint +) { + // Initialize BIGNUM and BIGNUMCTX structures. + // BIGNUM - Large numbers + // BIGNUMCTX - Context large numbers (temporary) + BIGNUM *a, *b, *p, *generatorX, *generatorY, *publicKeyX, *publicKeyY; + BN_CTX *context; + + // Microsoft Product Key identification program uses a public key stored in pidgen.dll's BINK resource, + // which is an Elliptic Curve Cryptography (ECC) public key. It can be decomposed into a following mathematical task: + + // We're presented with an elliptic curve, a multivariable function y(x; p; a; b), where + // y^2 % p = x^3 + ax + b % p. + a = BN_new(); + b = BN_new(); + p = BN_new(); + + // Public key will consist of the resulting (x; y) values. + publicKeyX = BN_new(); + publicKeyY = BN_new(); + + // G(x; y) is a generator function, its return value represents a point on the elliptic curve. + generatorX = BN_new(); + generatorY = BN_new(); + + // Context variable + context = BN_CTX_new(); + + /* Public data */ + BN_hex2bn(&p, pSel); + BN_set_word(a, aSel); + BN_set_word(b, bSel); + BN_hex2bn(&generatorX, generatorXSel); + BN_hex2bn(&generatorY, generatorYSel); + + BN_hex2bn(&publicKeyX, publicKeyXSel); + BN_hex2bn(&publicKeyY, publicKeyYSel); + + /* Elliptical Curve calculations. */ + // The group is defined via Fp = all integers [0; p - 1], where p is prime. + // The function EC_POINT_set_affine_coordinates() sets the x and y coordinates for the point p defined over the curve given in group. + EC_GROUP *eCurve = EC_GROUP_new_curve_GFp(p, a, b, context); + + // Create new point for the generator on the elliptic curve and set its coordinates to (genX; genY). + *genPoint = EC_POINT_new(eCurve); + EC_POINT_set_affine_coordinates(eCurve, *genPoint, generatorX, generatorY, context); + + // Create new point for the public key on the elliptic curve and set its coordinates to (pubX; pubY). + *pubPoint = EC_POINT_new(eCurve); + EC_POINT_set_affine_coordinates(eCurve, *pubPoint, publicKeyX, publicKeyY, context); + + // If generator and public key points are not on the elliptic curve, either the generator or the public key values are incorrect. + assert(EC_POINT_is_on_curve(eCurve, *genPoint, context) == 1); + assert(EC_POINT_is_on_curve(eCurve, *pubPoint, context) == 1); + + // Cleanup + BN_CTX_free(context); + + return eCurve; +} \ No newline at end of file diff --git a/xp.cpp b/xp.cpp new file mode 100644 index 0000000..a9b5f58 --- /dev/null +++ b/xp.cpp @@ -0,0 +1,260 @@ +// +// Created by Andrew on 09/04/2023. +// + +#include "header.h" + +/* Unpacks the Product Key. */ +void unpackXP(ul32 *serial, ul32 *hash, ul32 *sig, ul32 *raw) { + + // We're assuming that the quantity of information within the product key is at most 114 bits. + // log2(24^25) = 114. + + // Serial = Bits [0..30] -> 31 bits + serial[0] = raw[0] & 0x7fffffff; + + // Hash (e) = Bits [31..58] -> 28 bits + hash[0] = ((raw[0] >> 31) | (raw[1] << 1)) & 0xfffffff; + + // Signature (s) = Bits [59..113] -> 55 bits + sig[0] = (raw[1] >> 27) | (raw[2] << 5); + sig[1] = (raw[2] >> 27) | (raw[3] << 5); +} + +/* Repacks the Product Key. */ +void packXP(ul32 *raw, ul32 *serial, ul32 *hash, ul32 *sig) { + raw[0] = serial[0] | ((hash[0] & 1) << 31); + raw[1] = (hash[0] >> 1) | ((sig[0] & 0x1f) << 27); + raw[2] = (sig[0] >> 5) | (sig[1] << 27); + raw[3] = sig[1] >> 5; +} + +/* Verify Product Key */ +void verifyXPKey(EC_GROUP *eCurve, EC_POINT *generator, EC_POINT *publicKey, char *cdKey) { + byte pKey[PK_LENGTH]; + + BN_CTX *context = BN_CTX_new(); + + // Remove dashes from the CD-pKey. + for (int i = 0, k = 0; i < strlen(cdKey) && k < PK_LENGTH; i++) { + for (int j = 0; j < PK_LENGTH - 1; j++) { + if (cdKey[i] != '-' && cdKey[i] == charset[j]) { + pKey[k++] = j; + break; + } + } + + if (k >= PK_LENGTH) break; + } + + // Convert Base24 CD-pKey to bytecode. + ul32 bKey[4]{}; + ul32 pID[1], hash[1], sig[2]; + + unbase24(bKey, pKey); + + // Output CD-pKey bytecode. + printf("Bytecode: %.8lX %.8lX %.8lX %.8lX\n", bKey[3], bKey[2], bKey[1], bKey[0]); + + // Extract data, hash and signature from the bytecode. + unpackXP(pID, hash, sig, bKey); + printProductID(pID); + + printf("PID: %.8lX\nHash: %.8lX\nSignature: %.8lX %.8lX\n", pID[0], hash[0], sig[1], sig[0]); + + // e = Hash + // s = Signature + BIGNUM *e, *s; + + // Put hash word into BigNum e. + e = BN_new(); + BN_set_word(e, hash[0]); + + // Reverse signature and create a new BigNum s. + endiannessConvert((unsigned char *) sig, sizeof(sig)); + s = BN_bin2bn((unsigned char *)sig, sizeof(sig), nullptr); + + // Create x and y. + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + + // Create 2 new points on the existing elliptic curve. + EC_POINT *u = EC_POINT_new(eCurve); + EC_POINT *v = EC_POINT_new(eCurve); + + // EC_POINT_mul calculates r = generator * n + q * m. + // v = s * generator + e * (-publicKey) + + // u = generator * s + EC_POINT_mul(eCurve, u, nullptr, generator, s, context); + + // v = publicKey * e + EC_POINT_mul(eCurve, v, nullptr, publicKey, e, context); + + // v += u + EC_POINT_add(eCurve, v, u, v, context); + + // EC_POINT_get_affine_coordinates() sets x and y, either of which may be NULL, to the corresponding coordinates of p. + // x = v.x; y = v.y; + EC_POINT_get_affine_coordinates(eCurve, v, x, y, context); + + + byte buf[FIELD_BYTES], md[SHA_DIGEST_LENGTH], t[4]; + ul32 h; + + 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[0] & 0xff; // First 8 bits + t[1] = (pID[0] & 0xff00) >> 8; // Second 8 bits + t[2] = (pID[0] & 0xff0000) >> 16; // Third 8 bits + t[3] = (pID[0] & 0xff000000) >> 24; // Fourth 8 bits + + // Hash chunk of data. + SHA1_Update(&hContext, t, sizeof(t)); + + // Empty buffer, place v.x in little-endiannessConvert. + memset(buf, 0, sizeof(buf)); + BN_bn2bin(x, buf); + endiannessConvert((unsigned char *) buf, sizeof(buf)); + + // Hash chunk of data. + SHA1_Update(&hContext, buf, sizeof(buf)); + + // Empty buffer, place v.y in little-endiannessConvert. + memset(buf, 0, sizeof(buf)); + BN_bn2bin(y, buf); + endiannessConvert((unsigned char *) buf, sizeof(buf)); + + // Hash chunk of data. + SHA1_Update(&hContext, buf, sizeof(buf)); + + // Store the final message from hContext in md. + SHA1_Final(md, &hContext); + + // h = First32(SHA-1(pID || v.x || v.y)) >> 4 + h = (md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24)) >> 4; + h &= 0xfffffff; + + printf("Calculated hash: %.8lX\n", h); + + // If we managed to generateXPKey a pKey with the same hash, the pKey is correct. + if (h == hash[0]) cprintf("Key valid\n", 0x0A); + else cprintf("Key invalid\n", 0x0C); + + putchar('\n'); + + BN_free(e); + BN_free(s); + BN_free(x); + BN_free(y); + + BN_CTX_free(context); + + EC_POINT_free(u); + EC_POINT_free(v); +} + +/* Generate a valid Product Key. */ +void generateXPKey(byte *pKey, EC_GROUP *eCurve, EC_POINT *generator, BIGNUM *order, BIGNUM *privateKey, ul32 *pRaw) { + EC_POINT *r = EC_POINT_new(eCurve); + BN_CTX *ctx = BN_CTX_new(); + + BIGNUM *c = BN_new(); + BIGNUM *s = BN_new(); + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + + ul32 bKey[4]{}; + + do { + memset(bKey, 0, 4); + + // 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); + + // x = r.x; y = r.y; + EC_POINT_get_affine_coordinates(eCurve, r, x, y, ctx); + + SHA_CTX hContext; + byte md[SHA_DIGEST_LENGTH], buf[FIELD_BYTES], t[4]; + ul32 hash[1]; + + // h = (First-32(SHA1(pRaw, r.x, r.y)) >> 4 + SHA1_Init(&hContext); + + // Chop Raw Product Key into 4 bytes. + t[0] = pRaw[0] & 0xff; + t[1] = (pRaw[0] & 0xff00) >> 8; + t[2] = (pRaw[0] & 0xff0000) >> 16; + t[3] = (pRaw[0] & 0xff000000) >> 24; + + // Hash chunk of data. + SHA1_Update(&hContext, t, sizeof(t)); + + // Empty buffer, place r.x in little-endiannessConvert. + memset(buf, 0, sizeof(buf)); + BN_bn2bin(x, buf); + endiannessConvert((unsigned char *) buf, sizeof(buf)); + + // Hash chunk of data. + SHA1_Update(&hContext, buf, sizeof(buf)); + + // Empty buffer, place r.y in little-endiannessConvert. + memset(buf, 0, sizeof(buf)); + BN_bn2bin(y, buf); + endiannessConvert((unsigned char *) buf, sizeof(buf)); + + // Hash chunk of data. + SHA1_Update(&hContext, buf, sizeof(buf)); + + // Store the final message from hContext in md. + SHA1_Final(md, &hContext); + + // h = (First-32(SHA1(pRaw, r.x, r.y)) >> 4 + hash[0] = (md[0] | (md[1] << 8) | (md[2] << 16) | (md[3] << 24)) >> 4; + hash[0] &= 0xfffffff; + + /* s = privateKey * hash + c; */ + // s = privateKey; + BN_copy(s, privateKey); + + // s *= hash; + BN_mul_word(s, hash[0]); + + // 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. + ul32 sig[2]{}; + + BN_bn2bin(s, (byte *)sig); + endiannessConvert((byte *) sig, BN_num_bytes(s)); + + // Pack product key. + packXP(bKey, pRaw, hash, sig); + + printf("PID: %.8lX\nHash: %.8lX\nSignature: %.8lX %.8lX\n\n", pRaw[0], hash[0], sig[1], sig[0]); + } while (bKey[3] >= 0x40000); + // ↑ ↑ ↑ + // bKey[3] can't be longer than 18 bits, else the signature part will make + // the CD-key longer than 25 characters. + + // Convert the key to Base24. + base24(pKey, bKey); + + BN_free(c); + BN_free(s); + BN_free(x); + BN_free(y); + + BN_CTX_free(ctx); + EC_POINT_free(r); +} \ No newline at end of file