merge server and confid with xpkey, add LH keys (#18)

* merge server and confid with xpkey, add LH keys

* improve error handling

* update README.md

* fix line endings

* reorder products section of keys.json

* use options as global variable

* rename genServer to isBink2002

* small refactor

* handle unknown error codepath on confirmation id
remove redundant else for BINK1998 generation

* finish conversion from std::cout -> fmt::print();

---------

Co-authored-by: Neo <321592+Neo-Desktop@users.noreply.github.com>
This commit is contained in:
WitherOrNot
2023-06-06 18:04:39 -04:00
committed by GitHub
parent 8c295cf973
commit 98c8db7e02
9 changed files with 342 additions and 222 deletions

View File

@@ -6,12 +6,17 @@
bool loadJSON(const fs::path& filename, json *output) {
if (!fs::exists(filename)) {
fmt::print("{} does not exist", filename.string());
fmt::print("ERROR: File {} does not exist\n", filename.string());
return false;
}
std::ifstream f(filename);
*output = json::parse(f);
*output = json::parse(f, nullptr, false, false);
if (output->is_discarded()) {
fmt::print("ERROR: Unable to parse keys from {}\n", filename.string());
return false;
}
return true;
}
@@ -21,7 +26,9 @@ void showHelp(char *argv[]) {
fmt::print("usage: {} \n", argv[0]);
fmt::print("\t-h --help\tshow this message\n");
fmt::print("\t-v --verbose\tenable verbose output\n");
fmt::print("\t-n --number\tnumber of keys to generate (defaults to 1)\n");
fmt::print("\t-f --file\tspecify which keys file to load (defaults to keys.json)\n");
fmt::print("\t-i --instid\tinstallation ID used to generate confirmation ID\n");
fmt::print("\t-b --binkid\tspecify which BINK identifier to load (defaults to 2E)\n");
fmt::print("\t-l --list\tshow which products/binks can be loaded\n");
fmt::print("\t-c --channelid\tspecify which Channel Identifier to use (defaults to 640)\n");
@@ -34,6 +41,10 @@ int parseCommandLine(int argc, char* argv[], Options* options) {
"2E",
640,
"keys.json",
1,
"",
false,
false,
false,
false,
false
@@ -46,12 +57,35 @@ int parseCommandLine(int argc, char* argv[], Options* options) {
options->verbose = true;
} else if (arg == "-h" || arg == "--help") {
options->help = true;
} else if (arg == "-n" || arg == "--number") {
if (i == argc - 1) {
options->error = true;
break;
}
int nKeys;
if (!sscanf(argv[i+1], "%d", &nKeys)) {
options->error = true;
} else {
options->numKeys = nKeys;
}
i++;
} else if (arg == "-b" || arg == "--bink") {
if (i == argc - 1) {
options->error = true;
break;
}
options->binkid = argv[i+1];
i++;
} else if (arg == "-l" || arg == "--list") {
options->list = true;
} else if (arg == "-c" || arg == "--channelid") {
if (i == argc - 1) {
options->error = true;
break;
}
int siteID;
if (!sscanf(argv[i+1], "%d", &siteID)) {
options->error = true;
@@ -60,8 +94,21 @@ int parseCommandLine(int argc, char* argv[], Options* options) {
}
i++;
} else if (arg == "-f" || arg == "--file") {
if (i == argc - 1) {
options->error = true;
break;
}
options->keysFilename = argv[i+1];
i++;
} else if (arg == "-i" || arg == "--instid") {
if (i == argc - 1) {
options->error = true;
break;
}
options->instid = argv[i+1];
i++;
} else {
options->error = true;
}
@@ -71,6 +118,18 @@ int parseCommandLine(int argc, char* argv[], Options* options) {
}
int validateCommandLine(Options* options, char *argv[], json *keys) {
if (options->verbose) {
fmt::print("Loading keys file {}\n", options->keysFilename);
}
if (!loadJSON(options->keysFilename, keys)) {
return 2;
}
if (options->verbose) {
fmt::print("Loaded keys from {} successfully\n",options->keysFilename);
}
if (options->help || options->error) {
if (options->error) {
fmt::print("error parsing command line options\n");
@@ -79,26 +138,11 @@ int validateCommandLine(Options* options, char *argv[], json *keys) {
return 1;
}
if (options->verbose) {
fmt::print("loading {}\n", options->keysFilename);
}
if (!loadJSON(options->keysFilename, keys)) {
return 2;
}
if (options->verbose) {
fmt::print("loaded {} successfully\n",options->keysFilename);
}
if (options->list) {
for (auto el : (*keys)["Products"].items()) {
int id;
sscanf((el.value()["BINK"][0]).get<std::string>().c_str(), "%x", &id);
if (id >= 0x50) {
continue;
}
std::cout << el.key() << ": " << el.value()["BINK"] << std::endl;
fmt::print("{}: {}\n", el.key(), el.value()["BINK"]);
}
fmt::print("\n\n");
@@ -110,13 +154,12 @@ int validateCommandLine(Options* options, char *argv[], json *keys) {
int intBinkID;
sscanf(options->binkid.c_str(), "%x", &intBinkID);
if (intBinkID >= 0x50) {
std::cout << "ERROR: BINK2002 and beyond is not supported in this application at this time" << std::endl;
return 1;
if (intBinkID >= 0x40) {
options->isBink2002 = true;
}
if (options->channelID > 999) {
std::cout << "ERROR: refusing to create a key with a siteID greater than 999" << std::endl;
fmt::print("ERROR: refusing to create a key with a siteID greater than 999\n");
return 1;
}

View File

@@ -629,12 +629,11 @@ static void Unmix(unsigned char* buffer, size_t bufSize, const unsigned char* ke
}
}
#define CHARTYPE char
static int generateConfId(const CHARTYPE* installation_id_str, CHARTYPE confirmation_id[49])
int generateConfId(const char* installation_id_str, char confirmation_id[49])
{
unsigned char installation_id[19]; // 10**45 < 256**19
size_t installation_id_len = 0;
const CHARTYPE* p = installation_id_str;
const char* p = installation_id_str;
size_t count = 0, totalCount = 0;
unsigned check = 0;
size_t i;
@@ -787,7 +786,7 @@ static int generateConfId(const CHARTYPE* installation_id_str, CHARTYPE confirma
decimal[34 - i] = c4;
}
assert(e.encoded[0] == 0 && e.encoded[1] == 0 && e.encoded[2] == 0 && e.encoded[3] == 0);
CHARTYPE* q = confirmation_id;
char* q = confirmation_id;
for (i = 0; i < 7; i++) {
if (i)
*q++ = '-';
@@ -802,39 +801,4 @@ static int generateConfId(const CHARTYPE* installation_id_str, CHARTYPE confirma
}
*q++ = 0;
return 0;
}
int main(int argc, char** argv) {
if (argc < 2) {
std::cout << "usage:" << std::endl;
std::cout << argv[0] << " <Installation ID>" << std::endl;
return 1;
}
char confirmation_id[49];
int err = generateConfId(argv[1], confirmation_id);
switch (err) {
case ERR_TOO_SHORT:
std::cout << "ERROR: Installation ID is too short" << std::endl;
return 1;
case ERR_TOO_LARGE:
std::cout << "ERROR: Installation ID is too long" << std::endl;
return 1;
case ERR_INVALID_CHARACTER:
std::cout << "ERROR: Invalid character in installation ID" << std::endl;
return 1;
case ERR_INVALID_CHECK_DIGIT:
std::cout << "ERROR: Installation ID checksum failed. Please check that it is typed correctly" << std::endl;
return 1;
case ERR_UNKNOWN_VERSION:
std::cout << "ERROR: Unknown installation ID version" << std::endl;
return 1;
case ERR_UNLUCKY:
std::cout << "ERROR: Unable to generate valid confirmation ID" << std::endl;
return 1;
case SUCCESS:
std::cout << "Confirmation ID: " << confirmation_id << std::endl;
return 0;
}
}

View File

@@ -104,11 +104,15 @@ struct Options {
std::string binkid;
int channelID;
std::string keysFilename;
bool verbose;
int numKeys;
std::string instid;
bool help;
bool list;
bool isBink2002;
bool verbose;
bool error;
};
extern Options options;
int parseCommandLine(int argc, char* argv[], Options* output);
int validateCommandLine(Options* options, char* argv[], json* output);
@@ -120,7 +124,7 @@ bool verifyXPKey(
EC_GROUP *eCurve,
EC_POINT *basePoint,
EC_POINT *publicKey,
char (&pKey)[25]
char (&pKey)[25]
);
void generateXPKey(
@@ -129,9 +133,28 @@ void generateXPKey(
BIGNUM *genOrder,
BIGNUM *privateKey,
DWORD pSerial,
char (&cdKey)[25]
char (&pKey)[25]
);
// server.cpp
bool verifyServerKey(
EC_GROUP *eCurve,
EC_POINT *basePoint,
EC_POINT *publicKey,
char (&cdKey)[25]
);
void generateServerKey(
EC_GROUP *eCurve,
EC_POINT *basePoint,
BIGNUM *genOrder,
BIGNUM *privateKey,
DWORD pChannelID,
DWORD pAuthInfo,
char (&pKey)[25]
);
// confid.cpp
int generateConfId(const char* installation_id_str, char confirmation_id[49]);
#endif //WINDOWSXPKG_HEADER_H

View File

@@ -5,18 +5,20 @@
#include "header.h"
char pCharset[] = "BCDFGHJKMPQRTVWXY2346789";
Options options;
int main(int argc, char *argv[]) {
Options options;
if (!parseCommandLine(argc, argv, &options)) {
fmt::print("error parsing command line\n");
fmt::print("error parsing command line options\n");
showHelp(argv);
return !options.error ? 0 : 1;
}
json keys;
if (validateCommandLine(&options, argv, &keys) < 0) {
return 1;
int status = validateCommandLine(&options, argv, &keys);
if (status > 0) {
return status;
}
const char* BINKID = options.binkid.c_str();
@@ -61,8 +63,74 @@ int main(int argc, char *argv[]) {
pubPoint
);
if (!options.instid.empty()) {
char confirmation_id[49];
int err = generateConfId(options.instid.c_str(), confirmation_id);
switch (err) {
case ERR_TOO_SHORT:
fmt::print("ERROR: Installation ID is too short.\n");
return 1;
case ERR_TOO_LARGE:
fmt::print("ERROR: Installation ID is too long.\n");
return 1;
case ERR_INVALID_CHARACTER:
fmt::print("ERROR: Invalid character in installation ID.\n");
return 1;
case ERR_INVALID_CHECK_DIGIT:
fmt::print("ERROR: Installation ID checksum failed. Please check that it is typed correctly.\n");
return 1;
case ERR_UNKNOWN_VERSION:
fmt::print("ERROR: Unknown installation ID version.\n");
return 1;
case ERR_UNLUCKY:
fmt::print("ERROR: Unable to generate valid confirmation ID.\n");
return 1;
case SUCCESS:
fmt::print("Confirmation ID: {}\n", confirmation_id);
return 0;
default:
fmt::print("Unknown error occurred during Confirmation ID generation: {}\n", err);
}
return 1;
}
// Calculation
char pKey[25];
int count = 0, total = options.numKeys;
// BINK2002 Generation
if (options.isBink2002) {
DWORD pChannelID = options.channelID << 1;
if (options.verbose) {
fmt::print("> Channel ID: {:03d}\n", options.channelID);
}
// generate a key
for (int i = 0; i < total; i++) {
DWORD pAuthInfo;
RAND_bytes((BYTE *)&pAuthInfo, 4);
pAuthInfo &= 0x3ff;
if (options.verbose) {
fmt::print("> AuthInfo: {}\n", pAuthInfo);
}
generateServerKey(eCurve, genPoint, genOrder, privateKey, pChannelID, pAuthInfo, pKey);
print_product_key(pKey);
fmt::print("\n\n");
// verify a key
count += verifyServerKey(eCurve, genPoint, pubPoint, pKey);
}
fmt::print("Success count: {}/{}\n", count, total);
return 0;
}
// BINK1998 Generation
DWORD nRaw = options.channelID * 1000000 ; /* <- change */
@@ -82,7 +150,6 @@ int main(int argc, char *argv[]) {
// generate a key
BN_sub(privateKey, genOrder, privateKey);
nRaw <<= 1;
int count = 0, total = 1000;
for (int i = 0; i < total; i++) {
generateXPKey(eCurve, genPoint, genOrder, privateKey, nRaw, pKey);
@@ -94,6 +161,5 @@ int main(int argc, char *argv[]) {
}
fmt::print("Success count: {}/{}\n", count, total);
return 0;
}

View File

@@ -4,11 +4,6 @@
#include "header.h"
char pCharset[] = "BCDFGHJKMPQRTVWXY2346789";
const std::string filename = "keys.json";
using json = nlohmann::json;
/* Unpacks the Windows XP-like Product Key. */
void unpackServer(
QWORD (&pRaw)[2],
@@ -67,11 +62,14 @@ bool verifyServerKey(
// Extract segments from the bytecode and reverse the signature.
unpackServer(bKey, pChannelID, pHash, pSignature, pAuthInfo);
std::cout << "Validation results:\n Serial: 0x" << std::hex << std::setw(8) << std::setfill('0') << pChannelID << std::endl
<< " Hash: 0x" << std::hex << std::setw(8) << std::setfill('0') << pHash << std::endl
<< " Signature: 0x" << std::hex << std::setw(8) << std::setfill('0') << pSignature << std::endl
<< " AuthInfo: 0x" << std::hex << std::setw(8) << std::setfill('0') << pAuthInfo << std::endl
<< std::endl;
if (options.verbose) {
fmt::print("Validation results:\n");
fmt::print(" Serial: 0x{:08x}\n", pChannelID);
fmt::print(" Hash: 0x{:08x}\n", pHash);
fmt::print(" Signature: 0x{:08x}\n", pSignature);
fmt::print(" AuthInfo: 0x{:08x}\n", pAuthInfo);
fmt::print("\n");
}
BYTE msgDigest[SHA_DIGEST_LENGTH]{},
msgBuffer[SHA_MSG_LENGTH_2003]{},
@@ -294,19 +292,29 @@ void generateServerKey(
// Pack product key.
packServer(pRaw, pChannelID, hash, pSignature, pAuthInfo);
std::cout << "Generation results:\n Serial: 0x" << std::hex << std::setw(8) << std::setfill('0') << pChannelID << std::endl
<< " Hash: 0x" << std::hex << std::setw(8) << std::setfill('0') << hash << std::endl
<< " Signature: 0x" << std::hex << std::setw(8) << std::setfill('0') << pSignature << std::endl
<< " AuthInfo: 0x" << std::hex << std::setw(8) << std::setfill('0') << pAuthInfo << std::endl
<< std::endl;
if (options.verbose) {
fmt::print("Generation results:\n");
fmt::print(" Serial: 0x{:08x}\n", pChannelID);
fmt::print(" Hash: 0x{:08x}\n", hash);
fmt::print(" Signature: 0x{:08x}\n", pSignature);
fmt::print(" AuthInfo: 0x{:08x}\n", pAuthInfo);
fmt::print("\n");
}
EC_POINT_free(r);
} while (HIBYTES(pSignature, sizeof(DWORD)) >= 0x40000000);
DWORD chkChannelID, chkHash, chkAuthInfo;
QWORD chkSignature;
unpackServer(pRaw, chkChannelID, chkHash, chkSignature, chkAuthInfo);
if (chkHash != hash || chkSignature != pSignature) {
wrong = true;
}
} while ((HIBYTES(pSignature, sizeof(DWORD)) >= 0x40000000) || wrong);
base24(pKey, (BYTE *)pRaw);
std::cout << "attempt pass " << pKey << " key is " << (wrong ? "INVALID" : "VALID") << std::endl;
BN_free(c);
BN_free(s);
BN_free(x);
@@ -314,53 +322,4 @@ void generateServerKey(
BN_free(e);
BN_CTX_free(numContext);
}
int main()
{
const char* BINKID = "5A";
// 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();
std::ifstream f(filename);
json keys = json::parse(f);
EC_POINT *genPoint, *pubPoint;
EC_GROUP *eCurve = initializeEllipticCurve(
keys["BINK"][BINKID]["p"].get<std::string>(),
keys["BINK"][BINKID]["a"].get<std::string>(),
keys["BINK"][BINKID]["b"].get<std::string>(),
keys["BINK"][BINKID]["g"]["x"].get<std::string>(),
keys["BINK"][BINKID]["g"]["y"].get<std::string>(),
keys["BINK"][BINKID]["pub"]["x"].get<std::string>(),
keys["BINK"][BINKID]["pub"]["y"].get<std::string>(),
genPoint,
pubPoint
);
BN_dec2bn(&genOrder, keys["BINK"][BINKID]["n"].get<std::string>().c_str());
BN_dec2bn(&privateKey, keys["BINK"][BINKID]["priv"].get<std::string>().c_str());
char pKey[25]{};
DWORD pChannelID = 640 << 1, pAuthInfo;
RAND_bytes((BYTE *)&pAuthInfo, 4);
pAuthInfo &= 0x3ff;
printf("AuthInfo: %d\n", pAuthInfo);
do {
generateServerKey(eCurve, genPoint, genOrder, privateKey, pChannelID, pAuthInfo, pKey);
} while (!verifyServerKey(eCurve, genPoint, pubPoint, pKey));
print_product_key(pKey);
std::cout << std::endl << std::endl;
return 0;
}
}

View File

@@ -219,10 +219,13 @@ void generateXPKey(
// Pack product key.
packXP(pRaw, pSerial, pHash, pSignature);
fmt::print(" Serial: 0x{:08x}\n", pSerial);
fmt::print(" Hash: 0x{:08x}\n", pHash);
fmt::print(" Signature: 0x{:08x}\n", pSignature);
fmt::print("\n");
if (options.verbose) {
fmt::print("Generation results:\n");
fmt::print(" Serial: 0x{:08x}\n", pSerial);
fmt::print(" Hash: 0x{:08x}\n", pHash);
fmt::print(" Signature: 0x{:08x}\n", pSignature);
fmt::print("\n");
}
EC_POINT_free(r);
} while (pRaw[1] > BITMASK(50));