This commit is contained in:
Andrew 2023-04-09 20:55:13 +03:00
parent 082f2b1ff4
commit 8459d61f47
8 changed files with 1086 additions and 493 deletions

View File

@ -6,5 +6,10 @@ set(CMAKE_CXX_STANDARD 14)
add_executable( add_executable(
Keygen Keygen
main.cpp main.cpp
xp.cpp
server.cpp
header.h
utilities.cpp
key.cpp
) )
target_link_libraries(Keygen ${CMAKE_CURRENT_SOURCE_DIR}/lib/libcrypto.lib -static) target_link_libraries(Keygen ${CMAKE_CURRENT_SOURCE_DIR}/lib/libcrypto.lib -static)

127
README.md
View File

@ -1,7 +1,126 @@
# XPKeygen # 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: The **Raw Product Key (RPK)** is supplied in a form of 9 digits `XXX-YYYYYY`.
* Some of the keys aren't valid, but it's generally a less common occurrence. About 2 in 3 of the keys should work.
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.<sup>[2]</sup>
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 - **F<sub>2m</sub>** and **F<sub>p</sub>**.
They differ only slightly. Both curves are defined over the finite field, F<sub>p</sub> uses a prime parameter that's larger than 3,
F<sub>2m</sub> assumes $p = 2m$. Microsoft used the latter in their algorithm.
An elliptic curve over the finite field F<sub>p</sub> 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 F<sub>17</sub> 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.~~<br>
**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.

126
header.h Normal file
View File

@ -0,0 +1,126 @@
//
// Created by Andrew on 09/04/2023.
//
#ifndef KEYGEN_HEADER_H
#define KEYGEN_HEADER_H
#include <cstdio>
#include <cstring>
#include <cassert>
#include <windows.h>
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#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

105
key.cpp Normal file
View File

@ -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);
}

617
main.cpp
View File

@ -3,397 +3,103 @@
Rewritten by Endermanch Rewritten by Endermanch
*/ */
#include <cstdio> #include "header.h"
#include <cstring>
#include <cassert>
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/sha.h>
#include <windows.h>
#define FIELD_BITS 384
#define FIELD_BYTES 48
HANDLE hConsole; HANDLE hConsole;
unsigned char charset[] = "BCDFGHJKMPQRTVWXY2346789"; byte charset[] = "BCDFGHJKMPQRTVWXY2346789";
/* Colored output */ const char pXP[] = "92ddcf14cb9e71f4489a2e9ba350ae29454d98cb93bdbcc07d62b502ea12238ee904a8b20d017197aae0c103b32713a9";
VOID cprintf(CONST CHAR *Format, INT nColor, ...) { const long aXP = 1;
va_list vList; const long bXP = 0;
va_start(vList, nColor); // Base point G (Generator)
const char genXXP[] = "46E3775ECE21B0898D39BEA57050D422A0AF989E497962BAEE2CB17E0A28D5360D5476B8DC966443E37A14F1AEF37742";
const char genYXP[] = "7C8E741D2C34F4478E325469CD491603D807222C9C4AC09DDB2B31B3CE3F7CC191B3580079932BC6BEF70BE27604F65E";
SetConsoleTextAttribute(hConsole, nColor); // Inverse of the public key
vprintf(Format, vList); const char pubXXP[] = "5D8DBE75198015EC41C45AAB6143542EB098F6A5CC9CE4178A1B8A1E7ABBB5BC64DF64FAF6177DC1B0988AB00BA94BF8";
SetConsoleTextAttribute(hConsole, 0x0F); 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 * 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: * 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 * 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, ... ]. * 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: * 3. The decoded result is divided into sections:
* - 12 bits -> OS Family * - 12 bits -> OS Family
* - 31 bits -> Hash * - 31 bits -> Hash
@ -430,92 +136,11 @@ void generateKey(unsigned char *pKey, EC_GROUP *eCurve, EC_POINT *generator, BIG
* CCCCCCC | least significant six digits of Raw Product Key * CCCCCCC | least significant six digits of Raw Product Key
* | plus check digit (see below) * | plus check digit (see below)
* DD | index of the public key used to verify the * 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) * EEE | random value (used for phone activation, different installation IDs are generated)
*/ */
/*
int main() {
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. * 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 * 0x6F 0xFA 0x95 0x45 0xFC 0x75 0xB5 0x52 0xBB 0xEF 0xB1 0x17 0xDA 0xCD 0x00
@ -523,7 +148,7 @@ int main() {
* Of these 15 bytes the least significant four bytes contain the Raw * Of these 15 bytes the least significant four bytes contain the Raw
* Product Key in little endian byte order. The least significant bit is * Product Key in little endian byte order. The least significant bit is
* removed by shifting this 32-bit value (0x4595FA6F - remember the * removed by shifting this 32-bit value (0x4595FA6F - remember the
* little endian byte order) to the left by one bit position, resulting * little endiannessConvert byte order) to the left by one bit position, resulting
* in a Raw Product Key of 0x22CAFD37, or * in a Raw Product Key of 0x22CAFD37, or
* *
* 583728439 * 583728439
@ -531,16 +156,22 @@ int main() {
* in decimal notation. * in decimal notation.
*/ */
int main() {
char pKey[PK_LENGTH + NULL_TERMINATOR]{};
ul32 pRaw[1]{}, nAmount = 1;
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTitleA("Windows XP VLK Keygen"); SetConsoleTitleA("Windows XP VLK Keygen");
system("cls"); system("cls");
cprintf("Windows XP VLK Keygen\n\n", 0x08); cprintf("Windows XP VLK Keygen\n\n", 0x08);
cprintf("Principle of Operation:\n", 0x0C); 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("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("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("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"); printf("EEE is a random number used to generate a different Installation ID each time.\n\n");
@ -561,26 +192,58 @@ int main() {
cprintf("Input Raw Product Key BBB-CCCCCC WITHOUT DASHES in range [100-000000; 999-999999]: ", 0x0E); cprintf("Input Raw Product Key BBB-CCCCCC WITHOUT DASHES in range [100-000000; 999-999999]: ", 0x0E);
scanf_s("%lu", pRaw); 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"); 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; 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); 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); printProductKey(pKey);
printf("\n\n"); printf("\n\n");
// Verify the key // Verify the key.
verifyKey(eCurve, genPoint, pub, (char *) pKey); verifyXPKey(eCurve, genPoint, pubPoint, pKey);
} }
// Cleanup
BN_CTX_free(context);
system("pause"); system("pause");
return 0; return 0;

216
server.cpp Normal file
View File

@ -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);
}

99
utilities.cpp Normal file
View File

@ -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;
}

260
xp.cpp Normal file
View File

@ -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);
}