Skip to content

Commit 8459d61

Browse files
committed
v1.2
1 parent 082f2b1 commit 8459d61

File tree

8 files changed

+1065
-472
lines changed

8 files changed

+1065
-472
lines changed

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,10 @@ set(CMAKE_CXX_STANDARD 14)
66
add_executable(
77
Keygen
88
main.cpp
9+
xp.cpp
10+
server.cpp
11+
header.h
12+
utilities.cpp
13+
key.cpp
914
)
1015
target_link_libraries(Keygen ${CMAKE_CURRENT_SOURCE_DIR}/lib/libcrypto.lib -static)

README.md

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,126 @@
11
# XPKeygen
2-
Windows XP VLK Keygen
2+
A command line Windows XP VLK key generator. This tool allows you to generate _valid Windows XP keys_ based on a single
3+
_raw product key_, which can be random. You can also provide the amount of keys to be generated using that raw
4+
product key.
35

4-
Known issues:
5-
* Some of the keys aren't valid, but it's generally a less common occurrence. About 2 in 3 of the keys should work.
6+
The **Raw Product Key (RPK)** is supplied in a form of 9 digits `XXX-YYYYYY`.
67

7-
Issues/Pull Requests welcome.
8+
9+
### Download
10+
Check the **Releases** tab and download the latest version from there.
11+
12+
13+
### Principle of operation
14+
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`.
15+
16+
#### Product ID
17+
18+
| Digits | Meaning |
19+
|-------:|:-------------------------------------------------------|
20+
| AAAAA | OS Family constant |
21+
| BBB | Most significant 3 digits of the RPK |
22+
| CCCCCC | Least significant 6 digits of the RPK |
23+
| S | Check digit |
24+
| DD | Index of the public key used to verify the Product Key |
25+
| EEE | Random 3-digit number |
26+
27+
The OS Family constant `AAAAA` is different for each series of Windows XP. For example, it is 76487 for SP3.
28+
29+
The `BBB` and `CCCCCC` sections essentially directly correspond to the Raw Product Key. If the RPK is `XXXYYYYYY`, these two sections
30+
will transform to `XXX` and `YYYYYY` respectively.
31+
32+
The check digit `S` is picked so that the sum of all `C` digits with it added makes a number divisible by 7.
33+
34+
The public key index `DD` lets us know which public key was used to successfully verify the authenticity of our Product Key.
35+
For example, it's 22 for Professional keys and 23 for VLK keys.
36+
37+
A random number `EEE` is used to generate a different Installation ID each time.
38+
39+
#### Product Key
40+
41+
The Product Key itself (not to confuse with the RPK) is of form `FFFFF-GGGGG-HHHHH-JJJJJ-KKKKK`, encoded in Base-24 with
42+
the alphabet `BCDFGHJKMPQRTVWXY2346789` to exclude any characters that can be easily confused, like `I` and `1` or `O` and `0`.
43+
44+
As per the alphabet capacity formula, the key can at most contain 114 bits of information.
45+
$$N = log2(24^25) ~ 114$$
46+
47+
Based on that calculation, we unpack the 114-bit Product Key into 4 ordered segments:
48+
49+
| Segment | Capacity | Data |
50+
|-----------|----------|-------------------------------------------|
51+
| Flag | 1 bit | Reserved, always set to `0x01`* |
52+
| Serial | 30 bits | Raw Product Key (RPK) |
53+
| Hash | 28 bits | RPK hash |
54+
| Signature | 55 bits | Elliptic Curve signature for the RPK hash |
55+
56+
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
57+
shifting `Data` right and pack it back by shifting bits left.
58+
59+
*It's not fully known what that bit does, but all a priori valid product keys I've checked had it set to 1.
60+
61+
#### Elliptic Curves
62+
Elliptic Curve Cryptography (ECC) is a type of public-key cryptographic system.
63+
This class of systems relies on challenging "one-way" math problems - easy to compute one way and intractable to solve the "other" way.
64+
Sometimes these are called "trapdoor" functions - easy to fall into, complicated to escape.<sup>[2]</sup>
65+
66+
ECC relies on solving equations of the form
67+
$$y^2 = x^3 + ax + b$$
68+
69+
In general, there are 2 special cases for the Elliptic Curve leveraged in cryptography - **F<sub>2m</sub>** and **F<sub>p</sub>**.
70+
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,
71+
F<sub>2m</sub> assumes $p = 2m$. Microsoft used the latter in their algorithm.
72+
73+
An elliptic curve over the finite field F<sub>p</sub> consists of:
74+
* a set of integer coordinates ${x, y}$, such that $0 <= x, y < p$;
75+
* a set of points $y^2 = x^3 + ax + b \mod p$.
76+
77+
**An elliptic curve over F<sub>17</sub> would look like this:**
78+
79+
The curve consists of the blue points in above image. In practice the "elliptic curves"
80+
used in cryptography are "sets of points in square matrix".
81+
82+
The above curve is "educational". It provides very small key length (4-5 bits).
83+
In real world situations developers typically use curves of 256-bits or more.
84+
85+
86+
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.
87+
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
88+
OEM keys respectively.
89+
90+
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.
91+
92+
#### Generating valid keys
93+
94+
To create the CD-key generation algorithm we must compute the corresponding private key using the public key supplied with `pidgen.dll`,
95+
which means we have to reverse-solve the one-way ECC task.
96+
97+
Judging by the key exposed in BINK, p is a prime number with a length of **384 bits**.
98+
The computation difficulty using the most efficient Pollard's Rho algorithm ($O(\sqrtn)$) would be at least $O(2^168)$, but lucky for us,
99+
Microsoft limited the value of the signature to 55 bits in order to reduce the amount of matching product keys, reducing the difficulty
100+
to a far more manageable $O(2^28)$.
101+
102+
The private key was, of course, conveniently computed before us in just 6 hours on a Celeron 800 machine.
103+
104+
The rest of the job is done within the code of this keygen.
105+
106+
107+
### Known issues
108+
* ~~Some keys aren't valid, but it's generally a less common occurrence. About 2 in 3 of the keys should work.~~<br>
109+
**Fixed in v1.2**. Prior versions generated a valid key with an exact chance of `0x40000/0x62A32`, which resulted in exactly
110+
`0.64884`, or about 65%. My "2 in 3" estimate was inconceivably accurate.
111+
* Tested **only** on Windows XP Professional SP3, but should work everywhere else as well.
112+
* Server 2003 key generation not included yet.
113+
114+
115+
### Literature
116+
I will add more decent reads into the bibliography in a later release.
117+
118+
**Understanding basics of Windows XP Activation**:
119+
* [[1] Inside Windows Product Activation - Fully Licensed](https://www.licenturion.com/xp/fully-licensed-wpa.txt)
120+
* [[2] Elliptic Curve Cryptography for Beginners - Matt Rickard](https://matt-rickard.com/elliptic-curve-cryptography)
121+
* [[3] Elliptic Curve Cryptography (ECC) - Practical Cryptography for Developers](https://cryptobook.nakov.com/asymmetric-key-ciphers/elliptic-curve-cryptography-ecc)
122+
123+
124+
**Tested on Windows XP Professional SP3**.
125+
126+
Testing/Issues/Pull Requests welcome.

header.h

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//
2+
// Created by Andrew on 09/04/2023.
3+
//
4+
5+
#ifndef KEYGEN_HEADER_H
6+
#define KEYGEN_HEADER_H
7+
8+
#include <cstdio>
9+
#include <cstring>
10+
#include <cassert>
11+
12+
#include <windows.h>
13+
14+
#include <openssl/bn.h>
15+
#include <openssl/ec.h>
16+
#include <openssl/sha.h>
17+
#include <openssl/rand.h>
18+
19+
#define FIELD_BITS 384
20+
#define FIELD_BYTES (FIELD_BITS / 8)
21+
22+
#define FIELD_BITS_2003 512
23+
#define FIELD_BYTES_2003 (FIELD_BITS_2003 / 8)
24+
25+
#define PK_LENGTH 25
26+
#define NULL_TERMINATOR 1
27+
28+
typedef unsigned long ul32;
29+
30+
extern HANDLE hConsole;
31+
extern byte charset[];
32+
33+
extern const char pXP[];
34+
extern const long aXP;
35+
extern const long bXP;
36+
37+
// xp.cpp
38+
void unpackXP(
39+
ul32 *serial,
40+
ul32 *hash,
41+
ul32 *sig,
42+
ul32 *raw
43+
);
44+
45+
void packXP(
46+
ul32 *raw,
47+
ul32 *serial,
48+
ul32 *hash,
49+
ul32 *sig
50+
);
51+
52+
void verifyXPKey(
53+
EC_GROUP *eCurve,
54+
EC_POINT *generator,
55+
EC_POINT *publicKey,
56+
char *cdKey
57+
);
58+
59+
void generateXPKey(
60+
byte *pKey,
61+
EC_GROUP *eCurve,
62+
EC_POINT *generator,
63+
BIGNUM *order,
64+
BIGNUM *privateKey,
65+
ul32 *pRaw
66+
);
67+
68+
// server.cpp
69+
void unpackServer(
70+
ul32 *osFamily,
71+
ul32 *hash,
72+
ul32 *sig,
73+
ul32 *prefix,
74+
ul32 *raw
75+
);
76+
77+
void packServer(
78+
ul32 *raw,
79+
ul32 *osFamily,
80+
ul32 *hash,
81+
ul32 *sig,
82+
ul32 *prefix
83+
);
84+
85+
void verifyServerKey(
86+
EC_GROUP *eCurve,
87+
EC_POINT *generator,
88+
EC_POINT *public_key,
89+
char *cdKey
90+
);
91+
92+
void generateServerKey(
93+
byte *pKey,
94+
EC_GROUP *eCurve,
95+
EC_POINT *generator,
96+
BIGNUM *order,
97+
BIGNUM *privateKey,
98+
ul32 *osFamily,
99+
ul32 *prefix
100+
);
101+
102+
// utilities.cpp
103+
void cprintf(const char *Format, int nColor, ...);
104+
void endiannessConvert(byte *data, int length);
105+
106+
EC_GROUP *initializeEllipticCurve(
107+
const char *pSel,
108+
long aSel,
109+
long bSel,
110+
const char *generatorXSel,
111+
const char *generatorYSel,
112+
const char *publicKeyXSel,
113+
const char *publicKeyYSel,
114+
BIGNUM *genOrderSel,
115+
BIGNUM *privateKeySel,
116+
EC_POINT **genPoint,
117+
EC_POINT **pubPoint
118+
);
119+
120+
// key.cpp
121+
void unbase24(ul32 *byteSeq, byte *cdKey);
122+
void base24(byte *cdKey, ul32 *byteSeq);
123+
void printProductKey(const char *pKey);
124+
void printProductID(const ul32 *pRaw);
125+
126+
#endif //KEYGEN_HEADER_H

key.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//
2+
// Created by Andrew on 09/04/2023.
3+
//
4+
5+
#include "header.h"
6+
7+
/* Convert from byte sequence to the CD-key. */
8+
void base24(byte *cdKey, ul32 *byteSeq) {
9+
byte rbs[16];
10+
BIGNUM *z;
11+
12+
// Copy byte sequence to the reversed byte sequence.
13+
memcpy(rbs, byteSeq, sizeof(rbs));
14+
15+
// Skip trailing zeroes and reverse y.
16+
int length;
17+
18+
for (length = 15; rbs[length] == 0; length--);
19+
endiannessConvert(rbs, ++length);
20+
21+
// Convert reversed byte sequence to BigNum z.
22+
z = BN_bin2bn(rbs, length, nullptr);
23+
24+
// Divide z by 24 and convert the remainder to a CD-key char.
25+
cdKey[25] = 0;
26+
27+
for (int i = 24; i >= 0; i--)
28+
cdKey[i] = charset[BN_div_word(z, 24)];
29+
30+
BN_free(z);
31+
}
32+
33+
/* Convert from CD-key to a byte sequence. */
34+
void unbase24(ul32 *byteSeq, byte *cdKey) {
35+
BIGNUM *y = BN_new();
36+
BN_zero(y);
37+
38+
// Empty byte sequence.
39+
memset(byteSeq, 0, 16);
40+
41+
// For each character in product key, place its ASCII-code.
42+
for (int i = 0; i < 25; i++) {
43+
BN_mul_word(y, 24);
44+
BN_add_word(y, cdKey[i]);
45+
}
46+
47+
// Acquire length.
48+
int n = BN_num_bytes(y);
49+
50+
// Place the generated code into the byte sequence.
51+
BN_bn2bin(y, (unsigned char *)byteSeq);
52+
BN_free(y);
53+
54+
// Reverse the byte sequence.
55+
endiannessConvert((unsigned char *) byteSeq, n);
56+
}
57+
58+
/* Print Product Key. */
59+
void printProductKey(const char *pKey) {
60+
assert(strlen((const char *)pKey) == 25);
61+
62+
SetConsoleTextAttribute(hConsole, 0x0A);
63+
64+
for (int i = 0; i < 25; i++) {
65+
putchar(pKey[i]);
66+
if (i != 24 && i % 5 == 4) putchar('-');
67+
}
68+
69+
SetConsoleTextAttribute(hConsole, 0x0F);
70+
}
71+
72+
/* Print Product ID using a Product Key. */
73+
void printProductID(const ul32 *pRaw) {
74+
char raw[12];
75+
char b[6], c[8];
76+
77+
// Cut away last bit of the product key and convert it to an ASCII-number (=raw)
78+
sprintf(raw, "%lu", pRaw[0] >> 1);
79+
80+
// Make B-part {...-640-...} -> most significant 3 digits of Raw Product Key
81+
strncpy(b, raw, 3);
82+
b[3] = 0;
83+
84+
// Make C-part {...-123456X-...} -> least significant 6 digits of Raw Product Key
85+
strcpy(c, raw + 3);
86+
87+
// Make checksum digit-part {...56X-}
88+
assert(strlen(c) == 6);
89+
90+
int digit = 0;
91+
92+
// Reverse sum algorithm to find a check digit that would add to the rest to form a sum divisible by 7.
93+
for (int i = 0; i < 6; i++)
94+
digit -= c[i] - '0';
95+
96+
while (digit < 0)
97+
digit += 7;
98+
99+
// Append check digit + null terminate.
100+
c[6] = digit + '0';
101+
c[7] = 0;
102+
103+
printf("Product ID: ");
104+
cprintf("PPPPP-%s-%s-23XXX\n", 0x0E, b, c);
105+
}

0 commit comments

Comments
 (0)