From f7427792e304763e91b6c9304ef356c6272b82de Mon Sep 17 00:00:00 2001 From: TheTank20 <57580668+thepwrtank18@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:12:49 +0000 Subject: [PATCH] Add Wither's gists as references (#137) * Add Wither's gists as references * remove broken ones --------- Co-authored-by: WitherOrNot --- extras/GenerateCIDOffice0307.sage | 302 +++++++++++++++++ extras/confid.ipynb | 433 ++++++++++++++++++++++++ extras/parse_dpcdll.py | 65 ++++ extras/parse_pubkey.py | 98 ++++++ extras/pidgenx.ipynb | 348 ++++++++++++++++++++ extras/tspkgen.py | 159 +++++++++ extras/winxp_act.ipynb | 526 ++++++++++++++++++++++++++++++ extras/wsrv_x64_lh_act.ipynb | 267 +++++++++++++++ 8 files changed, 2198 insertions(+) create mode 100644 extras/GenerateCIDOffice0307.sage create mode 100644 extras/confid.ipynb create mode 100644 extras/parse_dpcdll.py create mode 100644 extras/parse_pubkey.py create mode 100644 extras/pidgenx.ipynb create mode 100644 extras/tspkgen.py create mode 100644 extras/winxp_act.ipynb create mode 100644 extras/wsrv_x64_lh_act.ipynb diff --git a/extras/GenerateCIDOffice0307.sage b/extras/GenerateCIDOffice0307.sage new file mode 100644 index 0000000..4b1c26c --- /dev/null +++ b/extras/GenerateCIDOffice0307.sage @@ -0,0 +1,302 @@ +import hashlib + +def add_pid_cksum(pid): + sumPID = 0 + val = pid + + while val != 0: + sumPID += val % 10 + val //= 10 + + return pid * 10 + 7 - (sumPID % 7) + +def decode_iid_new_version(iid, pid): + buffer = [0] * 5 + + for i in range(len(buffer)): + buffer[i] = int.from_bytes(iid[i*4:i*4+4], byteorder='little') + # print("buffer[" + str(i) + "] = " + hex(buffer[i])[2:].zfill(8)) + + + v1 = (buffer[3] & 0xFFFFFFF8) | 2 # Not really sure but seems to work + v2 = (buffer[3] & 7) << 29 | buffer[2] >> 3 + hardwareId = (v1) << 32 | v2 + hardwareId = int(hardwareId).to_bytes(8, byteorder='little') + + + v3 = (buffer[0] & 0xFFFFFF80) >> 7 & 0xFFFFFFFF + unknown1 = v3 & 0x000007FF + v4 = v3 & 0xFFFFF800 + + v5 = buffer[1] & 0x7F + v6 = buffer[1] >> 7 + + v7 = (v5 << 25 | v4) >> 11 + productID1 = v7 & 0x000003FF + v8 = v7 & 0xFFFFFC00 + + v9 = (v6 >> 11) & 0x00001FFF + v10 = v9 & 0x00001C00 + v11 = v9 & 0x000003FF + + v12 = ((v6 << 21) & 0xFFFFFFFF | v8) >> 10 + v13 = (v11 << 22) & 0xFFFFFFFF + v14 = v13 | v12 + + productID3RandomPart = (v14 & 0x3FF00000) >> 20 + productID2NoChecksum = v14 & 0x000FFFFF + + v15 = v13 >> 30 # 0x00000003 + v16 = v10 >> 8 # 0x0000001C + v17 = (buffer[2] & 7) << 6 # 0x000001C0 + v18 = (buffer[4] & 1) << 9 # 0x00000200 + authInfo = v18 | v17 | v16 | v15 # Not that important bug: bit 5 is not present + + + productID0 = pid[0] + productID2 = add_pid_cksum(productID2NoChecksum) + productID3 = (pid[3] // 1000) * 1000 + productID3RandomPart + # Just to remember: public key index I of pid (XXXXX-XXX-XXXXXXX-IIXXX) = BINK ID // 2 + + + # Where is actually located the version number? + # version1 = buffer[0] & 7 + # print("Decoded IID Version1?: " + str(version1)) + + # version2 = (int.from_bytes(iid[8:17], byteorder='little') >> 52) & 7 + # print("Decoded IID Version2?: " + str(version2)) + + # version3 = buffer[3] & 7 + # print("Decoded IID Version3?: " + str(version3)) + + + if productID1 != pid[1] or productID2 != pid[2] or pid[3] % 1000 != productID3RandomPart: + print("Error: Product ID not matching!") + return 0, 0, 0 + + + return hardwareId, authInfo, unknown1 + +# Validate installation ID checksum +def validate_cksum(n): + print("Checksumming installation ID...") + n = n.replace("-", "") + + cksum = 0 + for i, k in enumerate(map(int, n)): + if (i + 1) % 6 == 0 or i == len(n) - 1: + print("Expected last digit", cksum % 7, "got", k) + if cksum % 7 != k: + return None + + cksum = 0 + else: + cksum += k * (i % 2 + 1) + + parts = [n[i:i+5] for i in range(0, len(n), 6)] + n_out = "".join(parts) + + if len(n_out) == 42: + n_out = n_out[:-1] + + if len(n_out) != 45 and len(n_out) != 41: + return None + + return int(n_out) + +# Insert checksum digits into confirmation ID +def add_cksum(n): + cksums = [] + n = str(n).zfill(35) + parts = [n[i:i+5] for i in range(0, len(n), 5)] + + for p in parts: + cksum = 0 + + for i, k in enumerate(map(int, p)): + cksum += k * (i % 2 + 1) + + cksums.append(str(cksum % 7)) + + n_out = "" + + for i in range(7): + n_out += parts[i] + cksums[i] + ("-" if i != 6 else "") + + return n_out + +def encrypt(decrypted, key): + size_half = len(decrypted) // 2 + size_half_dwords = size_half - (size_half % 4) + last = decrypted[size_half*2:] + decrypted = decrypted[:size_half*2] + for i in range(4): + first = decrypted[:size_half] + second = decrypted[size_half:] + # A magic byte 0x79 is now added at the beginning of the list of bytes to hash + sha1_result = hashlib.sha1(bytearray.fromhex("79") + second + key).digest() + sha1_result = (sha1_result[:size_half_dwords] + + sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)]) + decrypted = second + bytes(x^^y for x,y in zip(first, sha1_result)) + return decrypted + last + +def decrypt(encrypted, key): + size_half = len(encrypted) // 2 + size_half_dwords = size_half - (size_half % 4) + last = encrypted[size_half*2:] + encrypted = encrypted[:size_half*2] + for i in range(4): + first = encrypted[:size_half] + second = encrypted[size_half:] + # A magic byte 0x79 is now added at the beginning of the list of bytes to hash + sha1_result = hashlib.sha1(bytearray.fromhex("79") + first + key).digest() + sha1_result = (sha1_result[:size_half_dwords] + + sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)]) + encrypted = bytes(x^^y for x,y in zip(second, sha1_result)) + first + return encrypted + last + +# Find v of divisor (u, v) of curve y^2 = F(x) +def find_v(u): + f = F % u + c2 = u[1]^2 - 4 * u[0] + c1 = 2 * f[0] - f[1] * u[1] + + if c2 == 0: + if c1 == 0: + return None + + try: + v1 = sqrt(f[1]^2 / (2 * c1)) + v1.lift() + except: + return None + else: + try: + d = 2 * sqrt(f[0]^2 + f[1] * (f[1] * u[0] - f[0] * u[1])) + v1_1 = sqrt((c1 - d)/c2) + v1_2 = sqrt((c1 + d)/c2) + except: + return None + + try: + v1_1.lift() + v1 = v1_1 + except: + try: + v1_2.lift() + v1 = v1_2 + except: + return None + + v0 = (f[1] + u[1] * v1^2) / (2 * v1) + v = v0 + v1 * x + + assert (v^2 - f) % u == 0 + return v + + + +# order of field Fp +p = 0x16E48DD18451FE9 +# Coefficients of F +coeffs = [0, 0xE5F5ECD95C8FD2, 0xFF28276F11F61, 0xFB2BD9132627E6, 0xE5F5ECD95C8FD2, 1] +# This constant inverts multiplication by 0x10001 in verification +INV = 0x01fb8cf48a70dfefe0302a1f7a5341 +# Key to decrypt installation IDs +IID_KEY = b'\x5A\x30\xB9\xF3' +#""" + +# minimal quadratic non-residue of p +mqnr = least_quadratic_nonresidue(p) +# Galois field of order p +Fp = GF(p) +# Polynomial field Fp[x] over Fp +Fpx. = Fp[] + +# Hyperellptic curve function +F = sum(k*x^i for i, k in enumerate(coeffs)) +# Hyperelliptic curve E: y^2 = F(x) over Fp +E = HyperellipticCurve(F) +# The jacobian over E +J = E.jacobian() + + + +# unpack&decrypt installationId + +installationId = validate_cksum(input("Installation ID (dashes optional): ")) +productId = input("Product ID (with dashes): ").split("-") +pid = [int(x) for x in productId] + + +# Office 2003 Professional Edition FWYTB-C7PPP-4497G-FV737-2HQWG (UMSKT generated) +# installationId = 020572391118023984229275432949036355811509788 # 020570-239116-180233-984220-927546-329495-036352-581151-097880 +# pid = [73931, 746, 6952006, 57345] # 73931-746-6952006-57345 + + +# Office 2007 Enterprise Edition XGQ68-R77XM-FPYFH-B436K-46QDY (UMSKT generated) +# installationId = 032422660398632786377841998280144793681167281 # 032424-266032-986324-786370-784193-982801-144791-368115-672814 +# pid = [89388, 864, 6523093, 65443] # 89388-864-6523093-65443 + + +print(installationId) + +if not installationId: + raise Exception("Invalid Installation ID (checksum fail)") + +installationIdSize = 19 if len(str(installationId)) > 41 else 17 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key)) +iid = int(installationId).to_bytes(installationIdSize, byteorder='little') +iid = decrypt(iid, IID_KEY) +hwid, authInfo, unknown1 = decode_iid_new_version(iid, pid) + +print("\nDecoded Hardware ID: " + hex(int.from_bytes(hwid, byteorder='big'))) +print("Decoded AuthInfo: " + hex(authInfo)) +print("Decoded Unknown1: " + hex(unknown1)) + +assert hwid != 0 + +key = hwid + int((pid[0] << 41 | pid[1] << 58 | pid[2] << 17 | pid[3]) & ((1 << 64) - 1)).to_bytes(8, byteorder='little') + +data = [0x00] * 14 +# data = b'\xb9g\xdd\xe1\xb0\xef-\x1e\xbd\x0frE\xd8\xbe' +print("\nConfirmation IDs:") + +for i in range(0x81): + data[6] = i # Attempt number was byte 7 in older confirmation ID version but it is now byte 6 + # Encrypt conf ID, find u of divisor (u, v) + encrypted = encrypt(bytes(data), key) + encrypted = int.from_bytes(encrypted, byteorder="little") + + x1, x2 = Fp(encrypted % p), Fp((encrypted // p) + 1) + u1, u0 = x1 * 2, (x1 ^ 2) - ((x2 ^ 2) * mqnr) + u = x^2 + u1 * x + u0 + + # Generate original divisor + v = find_v(u) + + if not v: + print(v) + continue + + d2 = J(u, v) + divisor = d2 * INV + + # Get x1 and x2 + roots = [x for x, y in divisor[0].roots()] + + if len(roots) > 0: + y = [divisor[1](r) for r in roots] + x1 = (-roots[0]).lift() + x2 = (-roots[1]).lift() + + if (x1 > x2) or (y[0].lift() % 2 != y[1].lift() % 2): + x1 = (-roots[1]).lift() + x2 = (-roots[0]).lift() + else: + x2 = (divisor[0][1] / 2).lift() + x1 = sqrt((x2^2 - divisor[0][0]) / mqnr).lift() + p + + # Win + conf_id = x1 * (p + 1) + x2 + conf_id = add_cksum(conf_id) + print(conf_id) \ No newline at end of file diff --git a/extras/confid.ipynb b/extras/confid.ipynb new file mode 100644 index 0000000..0da2039 --- /dev/null +++ b/extras/confid.ipynb @@ -0,0 +1,433 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Constants (run this cell first!)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "# MS Plus! DME\n", + "\n", + "# order of field Fp \n", + "p = 0x16A5DABA0605983\n", + "# Coefficients of F\n", + "coeffs = [0x334f24f75caa0e, 0x1392ff62889bd7b, 0x135131863ba2db8, 0x153208e78006010, 0x163694f26056db, 1]\n", + "# This constant inverts multiplication by 0x10001 in verification\n", + "INV = 0x01c61212ece6107c4254c43a5d1181\n", + "# Key to decrypt installation IDs\n", + "IID_KEY = b'\\x6A\\xC8\\x5E\\xD4'\n", + "#\"\"\"\n", + "\n", + "#\"\"\"\n", + "# Office XP/2003\n", + "\n", + "# order of field Fp \n", + "p = 0x16E48DD18451FE9\n", + "# Coefficients of F\n", + "coeffs = [0, 0xE5F5ECD95C8FD2, 0xFF28276F11F61, 0xFB2BD9132627E6, 0xE5F5ECD95C8FD2, 1]\n", + "# This constant inverts multiplication by 0x10001 in verification\n", + "INV = 0x01fb8cf48a70dfefe0302a1f7a5341\n", + "# Key to decrypt installation IDs\n", + "IID_KEY = b'\\x5A\\x30\\xB9\\xF3'\n", + "#\"\"\"\n", + "\n", + "\"\"\"\n", + "# Whistler 2428 (could be others)\n", + "\n", + "# order of field Fp \n", + "p = 0x16BD82821354FA3\n", + "# Coefficients of F\n", + "coeffs = [0, 0xDEFD8C5651954F, 0xA23AA12556ECE5, 0x89D79AD61B786D, 0xCCA087F0A6A4A4, 1]\n", + "# This constant inverts multiplication by 0x10001 in verification\n", + "INV = 0xd9ed873ed84a45761c23fd7fafd1\n", + "# Key to decrypt installation IDs\n", + "IID_KEY = b'\\x6A\\xC8\\x5E\\xD4'\n", + "#\"\"\"\n", + "\n", + "\n", + "\"\"\"\n", + "# Windows XP/Server 2003/Longhorn Pre-Reset\n", + "\n", + "# order of field Fp \n", + "p = 0x16A6B036D7F2A79\n", + "# Coefficients of F\n", + "coeffs = [0, 0x21840136C85381, 0x44197B83892AD0, 0x1400606322B3B04, 0x1400606322B3B04, 1]\n", + "# This constant inverts multiplication by 0x10001 in verification\n", + "INV = 0x40DA7C36D44C04E21B9D10F127C1\n", + "# Key to decrypt installation IDs\n", + "IID_KEY = b'\\x6A\\xC8\\x5E\\xD4'\n", + "#\"\"\"\n", + "\n", + "# minimal quadratic non-residue of p\n", + "mqnr = least_quadratic_nonresidue(p)\n", + "# Galois field of order p\n", + "Fp = GF(p)\n", + "# Polynomial field Fp[x] over Fp\n", + "Fpx. = Fp[]\n", + "\n", + "# Hyperellptic curve function\n", + "F = sum(k*x^i for i, k in enumerate(coeffs))\n", + "# Hyperelliptic curve E: y^2 = F(x) over Fp\n", + "E = HyperellipticCurve(F)\n", + "# The jacobian over E\n", + "J = E.jacobian()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Generate Confirmation ID" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "import hashlib\n", + "\n", + "# Validate installation ID checksum\n", + "def validate_cksum(n):\n", + " print(\"Checksumming installation ID...\")\n", + " n = n.replace(\"-\", \"\")\n", + "\n", + " cksum = 0\n", + " for i, k in enumerate(map(int, n)):\n", + " if (i + 1) % 6 == 0 or i == len(n) - 1:\n", + " print(\"Expected last digit\", cksum % 7, \"got\", k)\n", + " if cksum % 7 != k:\n", + " return None\n", + " \n", + " cksum = 0\n", + " else:\n", + " cksum += k * (i % 2 + 1)\n", + " \n", + " parts = [n[i:i+5] for i in range(0, len(n), 6)]\n", + " n_out = \"\".join(parts)\n", + " \n", + " if len(n_out) == 42:\n", + " n_out = n_out[:-1]\n", + " \n", + " if len(n_out) != 45 and len(n_out) != 41:\n", + " return None\n", + " \n", + " return int(n_out)\n", + "\n", + "# Insert checksum digits into confirmation ID\n", + "def add_cksum(n):\n", + " cksums = []\n", + " n = str(n).zfill(35)\n", + " parts = [n[i:i+5] for i in range(0, len(n), 5)]\n", + " \n", + " for p in parts:\n", + " cksum = 0\n", + " \n", + " for i, k in enumerate(map(int, p)):\n", + " cksum += k * (i % 2 + 1)\n", + " \n", + " cksums.append(str(cksum % 7))\n", + " \n", + " n_out = \"\"\n", + " \n", + " for i in range(7):\n", + " n_out += parts[i] + cksums[i] + (\"-\" if i != 6 else \"\")\n", + " \n", + " return n_out\n", + "\n", + "def encrypt(decrypted, key):\n", + " size_half = len(decrypted) // 2\n", + " size_half_dwords = size_half - (size_half % 4)\n", + " last = decrypted[size_half*2:]\n", + " decrypted = decrypted[:size_half*2]\n", + " for i in range(4):\n", + " first = decrypted[:size_half]\n", + " second = decrypted[size_half:]\n", + " sha1_result = hashlib.sha1(second + key).digest()\n", + " sha1_result = (sha1_result[:size_half_dwords] +\n", + " sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])\n", + " decrypted = second + bytes(x^^y for x,y in zip(first, sha1_result))\n", + " return decrypted + last\n", + "\n", + "def decrypt(encrypted, key):\n", + " size_half = len(encrypted) // 2\n", + " size_half_dwords = size_half - (size_half % 4)\n", + " last = encrypted[size_half*2:]\n", + " encrypted = encrypted[:size_half*2]\n", + " for i in range(4):\n", + " first = encrypted[:size_half]\n", + " second = encrypted[size_half:]\n", + " sha1_result = hashlib.sha1(first + key).digest()\n", + " sha1_result = (sha1_result[:size_half_dwords] +\n", + " sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])\n", + " encrypted = bytes(x^^y for x,y in zip(second, sha1_result)) + first\n", + " return encrypted + last\n", + "\n", + "# Find v of divisor (u, v) of curve y^2 = F(x)\n", + "def find_v(u):\n", + " f = F % u\n", + " c2 = u[1]^2 - 4 * u[0]\n", + " c1 = 2 * f[0] - f[1] * u[1]\n", + " \n", + " if c2 == 0:\n", + " if c1 == 0:\n", + " return None\n", + " \n", + " try:\n", + " v1 = sqrt(f[1]^2 / (2 * c1))\n", + " v1.lift()\n", + " except:\n", + " return None\n", + " else:\n", + " try:\n", + " d = 2 * sqrt(f[0]^2 + f[1] * (f[1] * u[0] - f[0] * u[1]))\n", + " v1_1 = sqrt((c1 - d)/c2)\n", + " v1_2 = sqrt((c1 + d)/c2)\n", + " except:\n", + " return None\n", + "\n", + " try:\n", + " v1_1.lift()\n", + " v1 = v1_1\n", + " except:\n", + " try:\n", + " v1_2.lift()\n", + " v1 = v1_2\n", + " except:\n", + " return None\n", + " \n", + " v0 = (f[1] + u[1] * v1^2) / (2 * v1)\n", + " v = v0 + v1 * x\n", + " \n", + " assert (v^2 - f) % u == 0\n", + " return v\n", + "\n", + "# unpack&decrypt installationId\n", + "installationId = validate_cksum(input(\"Installation ID (dashes optional): \"))\n", + "# installationId = 11234597509478704096883784033789146715149\n", + "print(installationId)\n", + "\n", + "if not installationId:\n", + " raise Exception(\"Invalid Installation ID (checksum fail)\")\n", + "\n", + "installationIdSize = 19 if len(str(installationId)) > 41 else 17 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key))\n", + "iid = int(installationId).to_bytes(installationIdSize, byteorder='little')\n", + "iid = decrypt(iid, IID_KEY)\n", + "hwid = iid[:8]\n", + "productid = int.from_bytes(iid[8:17], byteorder='little')\n", + "productkeyhash = iid[17:]\n", + "pid1 = productid & ((1 << 17) - 1)\n", + "pid2 = (productid >> 17) & ((1 << 10) - 1)\n", + "pid3 = (productid >> 27) & ((1 << 24) - 1)\n", + "version = (productid >> 52) & 7\n", + "pid4 = productid >> 55\n", + "\n", + "if version != (4 if len(iid) == 17 else 5):\n", + " print(f\"Invalid Installation ID (unknown version {version})\")\n", + "\n", + "print(installationIdSize)\n", + "print(pid1, pid2, pid3, pid4)\n", + "\n", + "key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')\n", + "\n", + "data = [0x00] * 14\n", + "# data = b'\\xb9g\\xdd\\xe1\\xb0\\xef-\\x1e\\xbd\\x0frE\\xd8\\xbe'\n", + "print(\"\\nConfirmation IDs:\")\n", + "\n", + "for i in range(0x81):\n", + " data[4] = i\n", + " # Encrypt conf ID, find u of divisor (u, v)\n", + " encrypted = encrypt(bytes(data), key)\n", + " encrypted = int.from_bytes(encrypted, byteorder=\"little\")\n", + " x1, x2 = Fp(encrypted % p), Fp((encrypted // p) + 1)\n", + " u1, u0 = x1 * 2, (x1 ^ 2) - ((x2 ^ 2) * mqnr)\n", + " u = x^2 + u1 * x + u0\n", + "\n", + " # Generate original divisor\n", + " v = find_v(u)\n", + " \n", + " if not v:\n", + " continue\n", + " \n", + " d2 = J(u, v)\n", + " divisor = d2 * INV\n", + " \n", + " # Get x1 and x2\n", + " roots = [x for x, y in divisor[0].roots()]\n", + "\n", + " if len(roots) > 0:\n", + " y = [divisor[1](r) for r in roots]\n", + " x1 = (-roots[0]).lift()\n", + " x2 = (-roots[1]).lift()\n", + "\n", + " if (x1 > x2) or (y[0].lift() % 2 != y[1].lift() % 2):\n", + " x1 = (-roots[1]).lift()\n", + " x2 = (-roots[0]).lift()\n", + " else:\n", + " x2 = (divisor[0][1] / 2).lift()\n", + " x1 = sqrt((x2^2 - divisor[0][0]) / mqnr).lift() + p\n", + "\n", + " # Win\n", + " conf_id = x1 * (p + 1) + x2\n", + " conf_id = add_cksum(conf_id)\n", + " print(conf_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Validate Confirmation ID (originally by diamondggg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "import hashlib\n", + "\n", + "# 226512-274743-842923-777124-961370-722240-570042-517722-757426\n", + "installationId = 44706039542602728435285810860722693781\n", + "installationIdSize = 17 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key))\n", + "# all three of following are valid generated\n", + "# 013705-060122-603141-961392-086136-909901-494476\n", + "confid = input(\"Confirmation ID: \").replace(\"-\", \"\")\n", + "confirmationId = int(\"\".join([confid[i:i+5] for i in range(0, len(confid), 6)]))\n", + "# confirmationId = 13009861034010972507754924748629391\n", + "print(confirmationId)\n", + "# 022032-220754-159721-909624-985141-504586-914001\n", + "#confirmationId = 2203220751597290962985145045891400\n", + "# 137616-847280-708585-827476-874935-313366-790880\n", + "#confirmationId = 13761847287085882747874933133679088\n", + "\n", + "def decrypt(encrypted, key):\n", + " size_half = len(encrypted) // 2\n", + " size_half_dwords = size_half - (size_half % 4)\n", + " last = encrypted[size_half*2:]\n", + " encrypted = encrypted[:size_half*2]\n", + " for i in range(4):\n", + " first = encrypted[:size_half]\n", + " second = encrypted[size_half:]\n", + " sha1_result = hashlib.sha1(first + key).digest()\n", + " sha1_result = (sha1_result[:size_half_dwords] +\n", + " sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])\n", + " encrypted = bytes(x^^y for x,y in zip(second, sha1_result)) + first\n", + " return encrypted + last\n", + "\n", + "# unpack&decrypt installationId\n", + "iid = int(installationId).to_bytes(installationIdSize, byteorder='little')\n", + "iid = decrypt(iid, IID_KEY)\n", + "hwid = iid[:8]\n", + "productid = int.from_bytes(iid[8:17], byteorder='little')\n", + "# productkeyhash is not used for validation, it exists just to allow the activation server to reject keygenned pids\n", + "productkeyhash = iid[17:]\n", + "pid1 = productid & ((1 << 17) - 1)\n", + "pid2 = (productid >> 17) & ((1 << 10) - 1)\n", + "pid3 = (productid >> 27) & ((1 << 24) - 1)\n", + "version = (productid >> 52) & 7\n", + "pid4 = productid >> 55\n", + "\n", + "print(pid1, pid2, pid3, pid4)\n", + "\n", + "if version != (4 if len(iid) == 17 else 5):\n", + " print(version)\n", + "\n", + "key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')\n", + "\n", + "# deserialize divisor\n", + "x1 = confirmationId // (p + 1)\n", + "x2 = confirmationId % (p + 1)\n", + "if x1 <= p:\n", + " # two or less points over GF(p)\n", + " point1 = E.lift_x(Fp(-x1)) if x1 != p else None\n", + " point2 = E.lift_x(Fp(-x2)) if x2 != p else None\n", + " if point1 is not None and point2 is not None:\n", + " # there are 4 variants of how lift_x() could select both y-s\n", + " # we don't distinguish D and -D, but this still leaves 2 variants\n", + " # the chosen one is encoded by order of x1 <=> x2\n", + " lastbit1 = point1[1].lift() & 1\n", + " lastbit2 = point2[1].lift() & 1\n", + " if x2 < x1:\n", + " if lastbit1 == lastbit2:\n", + " point2 = E(point2[0], -point2[1])\n", + " else:\n", + " if lastbit1 != lastbit2:\n", + " point2 = E(point2[0], -point2[1])\n", + " point1 = J(point1) if point1 is not None else J(0)\n", + " point2 = J(point2) if point2 is not None else J(0)\n", + " divisor = point1 + point2\n", + "else:\n", + " # a pair of conjugate points over GF(p^2)\n", + " f = (x+x2)*(x+x2) - mqnr*x1*x1 # 43 is the minimal quadratic non-residue in Fp\n", + " Fp2 = GF(p^2)\n", + " point1 = E.lift_x(f.roots(Fp2)[0][0])\n", + " point2 = E(Fp2)(point1[0].conjugate(), point1[1].conjugate())\n", + " divisor = J(Fp2)(point1) + J(Fp2)(point2)\n", + " divisor = J(Fpx(divisor[0]), Fpx(divisor[1])) #return from Fp2 to Fp\n", + "\n", + "d2 = divisor * 0x10001\n", + "assert d2[0].degree() == 2\n", + "x1 = d2[0][1]/2\n", + "x2 = sqrt((x1*x1-d2[0][0])/mqnr)\n", + "\n", + "encrypted = x1.lift() + (x2.lift() - 1) * p\n", + "encrypted = int(encrypted).to_bytes(14,byteorder='little')\n", + "\n", + "# end of the math\n", + "decrypted = decrypt(encrypted, key)\n", + "print(decrypted.hex())\n", + "# 0000000000000001000000000000 for the first confirmationId\n", + "# 0000000000000002000000000000 for the second confirmationId\n", + "# 0000000000000006000000000000 for the last confirmationId\n", + "assert decrypted[8:] == b'\\0' * 6\n", + "assert decrypted[7] <= 0x80\n", + "# all zeroes in decrypted[0:7] are okay for the checker\n", + "# more precisely: if decrypted[6] == 0, first 6 bytes can be anything\n", + "# otherwise, decrypted[0] = length, and decrypted[1:1+length] must match first length bytes of sha1(product key)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "SageMath 9.0", + "language": "sage", + "name": "sagemath" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/extras/parse_dpcdll.py b/extras/parse_dpcdll.py new file mode 100644 index 0000000..e2fdaef --- /dev/null +++ b/extras/parse_dpcdll.py @@ -0,0 +1,65 @@ +from glob import glob +from struct import unpack +import json +import sys + +def readint(f): + return unpack(" 999 or max_pid > 999: + break + + lic_type = readint(f) + + if lic_type > 6: + break + + if lic_type == 2 and int(bink_id, 16) % 2 == 1: + lic_type_str = "OEM-COA" + else: + lic_type_str = lic_types[lic_type] + + days_to_act = readint(f) + eval_days = readint(f) + sig_len = readint(f) + f.read(sig_len) + + dpcdata.append({ + "index": ind, + "bink": bink_id, + "pid_range": [min_pid, max_pid], + "type": lic_type_str, + "days_to_activate": days_to_act, + "days_evaluation": eval_days + }) + + with open(sys.argv[2], "w") as f: + f.write(json.dumps(dpcdata, indent=4)) \ No newline at end of file diff --git a/extras/parse_pubkey.py b/extras/parse_pubkey.py new file mode 100644 index 0000000..9fade79 --- /dev/null +++ b/extras/parse_pubkey.py @@ -0,0 +1,98 @@ +from struct import pack, unpack, calcsize +from json import dumps +from os.path import basename +import sys + +def readint(f): + return unpack(" = K[]\n", + "K3. = K.extension(Kx(pubkey_data[\"field\"][\"k3_minpoly\"]))\n", + "K3y. = K3[]\n", + "K6. = K3.extension(K3y(pubkey_data[\"field\"][\"k6_minpoly\"]))\n", + "\n", + "E = EllipticCurve(K, [a, b])\n", + "E6 = EllipticCurve(K6, [a, b])\n", + "\n", + "Qi = [E6(K3(point[\"x\"]) * t^-2, K3(point[\"y\"]) * t^-3) for point in pubkey_data[\"points\"]]\n", + "\n", + "# pairing_val = e_m(P, S)\n", + "pairing_val = K6([K3(pubkey_data[\"pairing_val\"][0]), K3(pubkey_data[\"pairing_val\"][1])])\n", + "\n", + "assert is_prime(p)\n", + "assert len(h1_bases) == len(Qi)\n", + "assert h1_bases[0] == 2\n", + "assert pairing_val^order == 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pkey_chars = \"33PXH-7Y6KF-2VJC9-XBBR8-HVTHH\"\n", + "\n", + "# pkey = HASH(M)\n", + "# HASH is a currently unknown hash function\n", + "pkey = decode_pkey(pkey_chars)\n", + "\n", + "# h1_coeffs = H1(M)\n", + "# During validation, coeffs must be found by a search that i havent implemented\n", + "# h1_coeffs = [1, 0, 7, 1, 4, 15, 9, 1, 1, 0, 2, 1, 0, 19]\n", + "\n", + "# 10 bits unknown, 30 bits product ID, 1 bit unknown (upgrade?)\n", + "key_data = (342 << 31 | 918500000 << 1 | 0)\n", + "h1_coeffs = [1]\n", + "\n", + "for i in range(len(h1_bases) - 1):\n", + " h1_coeffs.append(key_data % h1_bases[i + 1])\n", + " key_data //= h1_bases[i + 1]\n", + "\n", + "print(h1_coeffs)\n", + "\n", + "# H2(M) = E.lift_x(HASH(M) % p)\n", + "T = E6(E.lift_x(pkey % p))\n", + "Q = sum(map(lambda x: x[0] * x[1], zip(h1_coeffs, Qi))) \n", + "\n", + "test_pairing = T.tate_pairing(Q, order, 6, q=p)\n", + "\n", + "print(test_pairing == pairing_val or test_pairing == 1/pairing_val)\n", + "\n", + "key_data = 0\n", + "\n", + "for i in range(len(h1_bases) - 1, 0, -1):\n", + " key_data *= h1_bases[i]\n", + " key_data += h1_coeffs[i]\n", + " print(h1_bases[i], h1_coeffs[i], key_data)\n", + "\n", + "pid = (key_data & ((1 << 31) - 1)) >> 1" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "SageMath 9.0", + "language": "sage", + "name": "sagemath" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/extras/tspkgen.py b/extras/tspkgen.py new file mode 100644 index 0000000..13f0dfd --- /dev/null +++ b/extras/tspkgen.py @@ -0,0 +1,159 @@ +from Crypto.Cipher import ARC4 +from hashlib import sha1, md5 +from random import randint +from ecutils.core import Point, EllipticCurve +from sys import argv + +KCHARS = "BCDFGHJKMPQRTVWXY2346789" + +SPK_ECKEY = { + "a": 1, + "b": 0, + "g": { + "x": 10692194187797070010417373067833672857716423048889432566885309624149667762706899929433420143814127803064297378514651, + "y": 14587399915883137990539191966406864676102477026583239850923355829082059124877792299572208431243410905713755917185109 + }, + "n": 629063109922370885449, + "p": 21782971228112002125810473336838725345308036616026120243639513697227789232461459408261967852943809534324870610618161, + "priv": 153862071918555979944, + "pub": { + "x": 3917395608307488535457389605368226854270150445881753750395461980792533894109091921400661704941484971683063487980768, + "y": 8858262671783403684463979458475735219807686373661776500155868309933327116988404547349319879900761946444470688332645 + } +} + +LKP_ECKEY = { + "a": 1, + "b": 0, + "g": { + "x": 18999816458520350299014628291870504329073391058325678653840191278128672378485029664052827205905352913351648904170809, + "y": 7233699725243644729688547165924232430035643592445942846958231777803539836627943189850381859836033366776176689124317 + }, + "n": 675048016158598417213, + "p": 28688293616765795404141427476803815352899912533728694325464374376776313457785622361119232589082131818578591461837297, + "priv": 100266970209474387075, + "pub": { + "x": 7147768390112741602848314103078506234267895391544114241891627778383312460777957307647946308927283757886117119137500, + "y": 20525272195909974311677173484301099561025532568381820845650748498800315498040161314197178524020516408371544778243934 + } +} + + +def encode_pkey(n): + out = "" + + while n > 0: + out = KCHARS[n % 24] + out + n //= 24 + + out = "-".join([out[i:i+5] for i in range(0, len(out), 5)]) + return out + +def decode_pkey(k): + k = k.replace("-", "") + out = 0 + + for c in k: + out *= 24 + out += KCHARS.index(c) + + return out + +def int_to_bytes(n, l=None): + n = int(n) + + if not l: + l = (n.bit_length() + 7) // 8 + + return n.to_bytes(l, byteorder="little") + +def make_curve(curve_def): + G = Point(x=curve_def["g"]["x"], y=curve_def["g"]["y"]) + K = Point(x=curve_def["pub"]["x"], y=curve_def["pub"]["y"]) + E = EllipticCurve(p=curve_def["p"], a=curve_def["a"], b=curve_def["b"], G=G, n=curve_def["n"], h=1) + + return E, G, K + +def get_spkid(pid): + spkid_s = pid[10:16] + pid[18:23] + return int(spkid_s.split("-")[0]) + +def validate_tskey(pid, tskey, is_spk=True): + keydata = decode_pkey(tskey).to_bytes(21, "little") + rk = md5(pid.encode("utf-16-le")).digest()[:5] + b"\x00" * 11 + c = ARC4.new(rk) + dc_kdata = c.decrypt(keydata) + keydata = dc_kdata[:7] + + sigdata = int.from_bytes(dc_kdata[7:], "little") + h = sigdata & 0x7ffffffff + s = (sigdata >> 35) & 0x1fffffffffffffffff + + params = SPK_ECKEY if is_spk else LKP_ECKEY + E, G, K = make_curve(params) + R = E.add_points(E.multiply_point(h, K), E.multiply_point(s, G)) + md = sha1(keydata + int_to_bytes(R.x, 48) + int_to_bytes(R.y, 48)).digest() + ht = ((int.from_bytes(md[4:8], "little") >> 29) << 32) | (int.from_bytes(md[:4], "little")) + + spkid = int.from_bytes(keydata, "little") & 0x1FFFFFFFFF + + return h == ht and (not is_spk or spkid == get_spkid(pid)) + +def generate_tskey(pid, keydata, is_spk=True): + params = SPK_ECKEY if is_spk else LKP_ECKEY + priv = SPK_ECKEY["priv"] if is_spk else LKP_ECKEY["priv"] + + E, G, K = make_curve(params) + s = 0 + + while True: + c = randint(1, E.n - 1) + R = E.multiply_point(c, G) + + md = sha1(keydata + int_to_bytes(R.x, 48) + int_to_bytes(R.y, 48)).digest() + h = ((int.from_bytes(md[4:8], "little") >> 29) << 32) | (int.from_bytes(md[:4], "little")) + s = ((-priv * h + c) % E.n) & 0x1fffffffffffffffff + + keyinf = int.from_bytes(keydata, "little") + pkdata = ((s << 91) | (h << 56) | keyinf).to_bytes(21, "little") + rk = md5(pid.encode("utf-16-le")).digest()[:5] + b"\x00" * 11 + c = ARC4.new(rk) + pke = c.encrypt(pkdata)[:20] + pk = int.from_bytes(pke, "little") + pkstr = encode_pkey(pk) + + if s < 0x1fffffffffffffff and validate_tskey(pid, pkstr, is_spk): + return pkstr + +def generate_spk(pid): + spkid = get_spkid(pid) + spkdata = spkid.to_bytes(7, "little") + + return generate_tskey(pid, spkdata) + +def generate_lkp(pid, count, major_ver, minor_ver, chid): + version = 1 + + if (major_ver == 5 and minor_ver > 0) or major_ver > 5: + version = (major_ver << 3) | minor_ver + + lkpinfo = (chid << 46) | (count << 32) | (2 << 18) | (144 << 10) | (version << 3) + + lkpdata = lkpinfo.to_bytes(7, "little") + + return generate_tskey(pid, lkpdata, False) + +if __name__ == "__main__": + if len(argv) == 2: + pid = argv[1] + print(f"License Server ID: {generate_spk(pid)}") + elif len(argv) == 5: + pid = argv[1] + count = int(argv[2]) + ver_major, ver_minor = map(int, argv[3].split(".")) + chid = int(argv[4]) + + print(f"License Key Pack ID: {generate_lkp(pid, count, ver_major, ver_minor, chid)}") + else: + print(f"Usage: {argv[0]} [ ]") + print(f"Example: {argv[0]} 00490-92005-99454-AT527 1234 10.3 32") \ No newline at end of file diff --git a/extras/winxp_act.ipynb b/extras/winxp_act.ipynb new file mode 100644 index 0000000..7fa8481 --- /dev/null +++ b/extras/winxp_act.ipynb @@ -0,0 +1,526 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Product Key Generator - Windows XP" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Paste JSON object for BINK data here:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Windows XP Professional Retail (Bink ID 2C)\n", + "key_data = {\n", + " \"p\": 24412280675538104642884792561502783185577987209710041026341163083973933860854736635268965257725055809364646140091249,\n", + " \"a\": 1,\n", + " \"b\": 0,\n", + " \"B\": [\n", + " 21673361717619259910600499419800485528178801849923454062050055236231939594233283543796077751210469045350919066368895,\n", + " 5232476492611604888729825305639232005017822876108144652169892952989580351454246958886421453535493897842819359154864\n", + " ],\n", + " \"K\": [\n", + " 21551722775458524408480112576069559265917312687549112053580919391285918530584174752292844347621326558272739603979057,\n", + " 13463977158522661542654520438933687107907187215503371589980428235633526671841388652148099285621876350916055100879930\n", + " ],\n", + " \"order\": 55681564377333977,\n", + " \"private_key\": 30951839223306173\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run this cell to generate key" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "import hashlib\n", + "\n", + "# p = order of field Fp\n", + "# Fp = Galois field of order p\n", + "# E = Elliptic curve y^2 = x^3 + ax + b over Fp\n", + "# B = generator on E\n", + "# K = inverse of public key\n", + "# order = order of E\n", + "\n", + "p = key_data[\"p\"]\n", + "Fp = GF(p)\n", + "E = EllipticCurve(Fp, [0, 0, 0, key_data[\"a\"], key_data[\"b\"]])\n", + "B = E.point(key_data[\"B\"])\n", + "K = E.point(key_data[\"K\"])\n", + "order = key_data[\"order\"]\n", + "private_key = -key_data[\"private_key\"] % order\n", + "\n", + "# PID of product key\n", + "pid = 756_696969\n", + "\n", + "# Key alphabet\n", + "KCHARS = \"BCDFGHJKMPQRTVWXY2346789\"\n", + "\n", + "def int_to_bytes(n, l=None):\n", + " n = int(n)\n", + " \n", + " if not l:\n", + " l = (n.bit_length() + 7) // 8\n", + " \n", + " return n.to_bytes(l, byteorder=\"little\")\n", + "\n", + "def encode_pkey(n):\n", + " out = \"\"\n", + " \n", + " while n > 0:\n", + " out = KCHARS[n % 24] + out\n", + " n //= 24\n", + " \n", + " out = \"-\".join([out[i:i+5] for i in range(0, len(out), 5)])\n", + " return out\n", + "\n", + "pid <<= 1\n", + "\n", + "while True:\n", + " k = getrandbits(384)\n", + " r = k * B\n", + " x, y = r.xy()\n", + "\n", + " md = hashlib.sha1(int_to_bytes(pid, 4) + int_to_bytes(x, 48) + int_to_bytes(y, 48)).digest()\n", + " h = int.from_bytes(md[:4], byteorder=\"little\") >> 4\n", + " h &= 0xfffffff\n", + "\n", + " s = int(abs((private_key * h + k) % order))\n", + " raw_pkey = s << 59 | h << 31 | pid\n", + " \n", + " print(hex(pid)[2:], hex(h)[2:], hex(s)[2:], hex(raw_pkey)[2:])\n", + " \n", + " if raw_pkey >> 96 < 0x40000:\n", + " break\n", + "\n", + "print(encode_pkey(raw_pkey))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Key decoder (run above cell first)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "def decode_pkey(k):\n", + " k = k.replace(\"-\", \"\")\n", + " out = 0\n", + " \n", + " for c in k:\n", + " out *= 24\n", + " out += KCHARS.index(c)\n", + " \n", + " return out\n", + "\n", + "pkey = input(\"Product Key (dashes optional): \")\n", + "raw_pkey = decode_pkey(pkey)\n", + "\n", + "kpid = (raw_pkey & 0x7fffffff) >> 1\n", + "verify = (kpid // 1000000) == ((pid >> 1) // 1000000)\n", + "print(kpid, pid >> 1)\n", + "\n", + "if verify:\n", + " h = (raw_pkey >> 31) & 0xfffffff\n", + " s = (raw_pkey >> 59) & 0x7ffffffffffffff\n", + "\n", + " r = h * K + s * B\n", + " x, y = r.xy()\n", + "\n", + " md = hashlib.sha1(int_to_bytes(kpid << 1, 4) + int_to_bytes(x, 48) + int_to_bytes(y, 48)).digest()\n", + " hp = int.from_bytes(md[:4], byteorder=\"little\") >> 4\n", + " hp &= 0xfffffff\n", + "\n", + " print(h, hp)\n", + " \n", + " if h == hp:\n", + " print(\"Valid key\")\n", + " else:\n", + " print(\"Invalid key\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Confirmation ID generator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import hashlib\n", + "\n", + "# order of field Fp \n", + "p = 0x16A6B036D7F2A79\n", + "# Galois field of order p\n", + "Fp = GF(p)\n", + "# Polynomial field Fp[x] over Fp\n", + "Fpx. = Fp[]\n", + "# Hyperellptic curve function\n", + "F = x^5+0x1400606322B3B04*x^4+0x1400606322B3B04*x^3+0x44197B83892AD0*x^2+0x21840136C85381*x\n", + "# Hyperelliptic curve E: y^2 = F(x) over Fp\n", + "E = HyperellipticCurve(F)\n", + "# The jacobian over E\n", + "J = E.jacobian()\n", + "\n", + "# This constant inverts multiplication by 0x1001 in verification\n", + "# My best guess for how it was calculated: INV = 0x10001^-1 (mod |J|)\n", + "# |J| is hard to compute, how can we calculate for other curves?\n", + "INV = 0x40DA7C36D44C04E21B9D10F127C1\n", + "\n", + "# Key to decrypt installation IDs\n", + "IID_KEY = b'\\x6A\\xC8\\x5E\\xD4'\n", + "\n", + "# Validate installation ID checksum\n", + "def validate_cksum(n):\n", + " print(\"Checksumming installation ID...\")\n", + " n = n.replace(\"-\", \"\")\n", + "\n", + " cksum = 0\n", + " for i, k in enumerate(map(int, n)):\n", + " if (i + 1) % 6 == 0 or i == len(n) - 1:\n", + " print(\"Expected last digit\", cksum % 7, \"got\", k)\n", + " if cksum % 7 != k:\n", + " return None\n", + " \n", + " cksum = 0\n", + " else:\n", + " cksum += k * (i % 2 + 1)\n", + " \n", + " parts = [n[i:i+5] for i in range(0, len(n), 6)]\n", + " n_out = \"\".join(parts)\n", + " \n", + " if len(n_out) == 42:\n", + " n_out = n_out[:-1]\n", + " \n", + " if len(n_out) != 45 and len(n_out) != 41:\n", + " return None\n", + " \n", + " return int(\"\".join(parts))\n", + "\n", + "# Insert checksum digits into confirmation ID\n", + "def add_cksum(n):\n", + " cksums = []\n", + " n = str(n).zfill(35)\n", + " parts = [n[i:i+5] for i in range(0, len(n), 5)]\n", + " \n", + " for p in parts:\n", + " cksum = 0\n", + " \n", + " for i, k in enumerate(map(int, p)):\n", + " cksum += k * (i % 2 + 1)\n", + " \n", + " cksums.append(str(cksum % 7))\n", + " \n", + " n_out = \"\"\n", + " \n", + " for i in range(7):\n", + " n_out += parts[i] + cksums[i] + (\"-\" if i != 6 else \"\")\n", + " \n", + " return n_out\n", + "\n", + "def encrypt(decrypted, key):\n", + " size_half = len(decrypted) // 2\n", + " size_half_dwords = size_half - (size_half % 4)\n", + " last = decrypted[size_half*2:]\n", + " decrypted = decrypted[:size_half*2]\n", + " for i in range(4):\n", + " first = decrypted[:size_half]\n", + " second = decrypted[size_half:]\n", + " sha1_result = hashlib.sha1(second + key).digest()\n", + " sha1_result = (sha1_result[:size_half_dwords] +\n", + " sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])\n", + " decrypted = second + bytes(x^^y for x,y in zip(first, sha1_result))\n", + " return decrypted + last\n", + "\n", + "def decrypt(encrypted, key):\n", + " size_half = len(encrypted) // 2\n", + " size_half_dwords = size_half - (size_half % 4)\n", + " last = encrypted[size_half*2:]\n", + " encrypted = encrypted[:size_half*2]\n", + " for i in range(4):\n", + " first = encrypted[:size_half]\n", + " second = encrypted[size_half:]\n", + " sha1_result = hashlib.sha1(first + key).digest()\n", + " sha1_result = (sha1_result[:size_half_dwords] +\n", + " sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])\n", + " encrypted = bytes(x^^y for x,y in zip(second, sha1_result)) + first\n", + " return encrypted + last\n", + "\n", + "# Find v of divisor (u, v) of curve y^2 = F(x)\n", + "def find_v(u):\n", + " f = F % u\n", + " c2 = u[1]^2 - 4 * u[0]\n", + " c1 = 2 * f[0] - f[1] * u[1]\n", + " \n", + " if c2 == 0:\n", + " if c1 == 0:\n", + " return None\n", + " \n", + " try:\n", + " v1 = sqrt(f[1]^2 / (2 * c1))\n", + " v1.lift()\n", + " except:\n", + " return None\n", + " else:\n", + " try:\n", + " d = 2 * sqrt(f[0]^2 + f[1] * (f[1] * u[0] - f[0] * u[1]))\n", + " v1_1 = sqrt((c1 - d)/c2)\n", + " v1_2 = sqrt((c1 + d)/c2)\n", + " except:\n", + " return None\n", + "\n", + " try:\n", + " v1_1.lift()\n", + " v1 = v1_1\n", + " except:\n", + " try:\n", + " v1_2.lift()\n", + " v1 = v1_2\n", + " except:\n", + " return None\n", + " \n", + " v0 = (f[1] + u[1] * v1^2) / (2 * v1)\n", + " v = v0 + v1 * x\n", + " \n", + " assert (v^2 - f) % u == 0\n", + " return v\n", + "\n", + "# unpack&decrypt installationId\n", + "installationId = validate_cksum(input(\"Installation ID (dashes optional): \"))\n", + "print(installationId)\n", + "\n", + "if not installationId:\n", + " raise Exception(\"Invalid Installation ID (checksum fail)\")\n", + "\n", + "installationIdSize = 19 if len(str(installationId)) > 41 else 17 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key))\n", + "iid = int(installationId).to_bytes(installationIdSize, byteorder='little')\n", + "iid = decrypt(iid, IID_KEY)\n", + "hwid = iid[:8]\n", + "productid = int.from_bytes(iid[8:17], byteorder='little')\n", + "productkeyhash = iid[17:]\n", + "pid1 = productid & ((1 << 17) - 1)\n", + "pid2 = (productid >> 17) & ((1 << 10) - 1)\n", + "pid3 = (productid >> 27) & ((1 << 25) - 1)\n", + "version = (productid >> 52) & 7\n", + "pid4 = productid >> 55\n", + "\n", + "assert version == (4 if len(iid) == 17 else 5)\n", + "\n", + "key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')\n", + "\n", + "data = [0x00] * 14\n", + "\n", + "print(\"\\nConfirmation IDs:\")\n", + "\n", + "for i in range(0x81):\n", + " data[7] = i\n", + " # Encrypt conf ID, find u of divisor (u, v)\n", + " encrypted = encrypt(bytes(data), key)\n", + " encrypted = int.from_bytes(encrypted, byteorder=\"little\")\n", + " x1, x2 = Fp(encrypted % p), Fp((encrypted // p) + 1)\n", + " u1, u0 = x1 * 2, (x1 ^ 2) - ((x2 ^ 2) * 43)\n", + " u = x^2 + u1 * x + u0\n", + "\n", + " # Generate original divisor\n", + " v = find_v(u)\n", + " \n", + " if not v:\n", + " continue\n", + " \n", + " d2 = J(u, v)\n", + " divisor = d2 * INV\n", + " \n", + " # Get x1 and x2\n", + " roots = [x for x, y in divisor[0].roots()]\n", + "\n", + " if len(roots) > 0:\n", + " y = [divisor[1](r) for r in roots]\n", + " x1 = (-roots[0]).lift()\n", + " x2 = (-roots[1]).lift()\n", + "\n", + " if (x1 > x2) or (y[0].lift() % 2 != y[1].lift() % 2):\n", + " x1 = (-roots[1]).lift()\n", + " x2 = (-roots[0]).lift()\n", + " else:\n", + " x2 = (divisor[0][1] / 2).lift()\n", + " x1 = sqrt((x2^2 - divisor[0][0]) / 43).lift() + p\n", + "\n", + " # Win\n", + " conf_id = x1 * (p + 1) + x2\n", + " conf_id = add_cksum(conf_id)\n", + " print(conf_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Confirmation ID decoder/validator (made by diamondggg)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import hashlib\n", + "\n", + "# 226512-274743-842923-777124-961370-722240-570042-517722-757426\n", + "installationId = 114535500880440159787527912804896629001083118\n", + "installationIdSize = 19 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key))\n", + "# all three of following are valid generated\n", + "# 013705-060122-603141-961392-086136-909901-494476\n", + "confirmationId = 15771960290497900806797040541467113\n", + "# 022032-220754-159721-909624-985141-504586-914001\n", + "#confirmationId = 02203220751597290962985145045891400\n", + "# 137616-847280-708585-827476-874935-313366-790880\n", + "#confirmationId = 13761847287085882747874933133679088\n", + "\n", + "def decrypt(encrypted, key):\n", + " size_half = len(encrypted) // 2\n", + " size_half_dwords = size_half - (size_half % 4)\n", + " last = encrypted[size_half*2:]\n", + " encrypted = encrypted[:size_half*2]\n", + " for i in range(4):\n", + " first = encrypted[:size_half]\n", + " second = encrypted[size_half:]\n", + " sha1_result = hashlib.sha1(first + key).digest()\n", + " sha1_result = (sha1_result[:size_half_dwords] +\n", + " sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])\n", + " encrypted = bytes(x^^y for x,y in zip(second, sha1_result)) + first\n", + " return encrypted + last\n", + "\n", + "# unpack&decrypt installationId\n", + "iid = int(installationId).to_bytes(installationIdSize, byteorder='little')\n", + "iid = decrypt(iid, b'\\x6A\\xC8\\x5E\\xD4')\n", + "hwid = iid[:8]\n", + "productid = int.from_bytes(iid[8:17], byteorder='little')\n", + "productkeyhash = iid[17:]\n", + "pid1 = productid & ((1 << 17) - 1)\n", + "pid2 = (productid >> 17) & ((1 << 10) - 1)\n", + "pid3 = (productid >> 27) & ((1 << 25) - 1)\n", + "version = (productid >> 52) & 7\n", + "pid4 = productid >> 55\n", + "\n", + "assert version == (4 if len(iid) == 17 else 5)\n", + "\n", + "key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')\n", + "# productkeyhash is not used for validation, it exists just to allow the activation server to reject keygenned pids\n", + "\n", + "# now the math\n", + "\n", + "p = 0x16A6B036D7F2A79\n", + "Fp = GF(p)\n", + "Fpx. = Fp[]\n", + "E = HyperellipticCurve(x^5+0x1400606322B3B04*x^4+0x1400606322B3B04*x^3+0x44197B83892AD0*x^2+0x21840136C85381*x)\n", + "J = E.jacobian()\n", + "\n", + "# deserialize divisor\n", + "x1 = confirmationId // (p + 1)\n", + "x2 = confirmationId % (p + 1)\n", + "if x1 <= p:\n", + " # two or less points over GF(p)\n", + " point1 = E.lift_x(Fp(-x1)) if x1 != p else None\n", + " point2 = E.lift_x(Fp(-x2)) if x2 != p else None\n", + " if point1 is not None and point2 is not None:\n", + " # there are 4 variants of how lift_x() could select both y-s\n", + " # we don't distinguish D and -D, but this still leaves 2 variants\n", + " # the chosen one is encoded by order of x1 <=> x2\n", + " lastbit1 = point1[1].lift() & 1\n", + " lastbit2 = point2[1].lift() & 1\n", + " if x2 < x1:\n", + " if lastbit1 == lastbit2:\n", + " point2 = E(point2[0], -point2[1])\n", + " else:\n", + " if lastbit1 != lastbit2:\n", + " point2 = E(point2[0], -point2[1])\n", + " point1 = J(point1) if point1 is not None else J(0)\n", + " point2 = J(point2) if point2 is not None else J(0)\n", + " divisor = point1 + point2\n", + "else:\n", + " # a pair of conjugate points over GF(p^2)\n", + " f = (x+x2)*(x+x2)-43*x1*x1 # 43 is the minimal quadratic non-residue in Fp\n", + " Fp2 = GF(p^2)\n", + " point1 = E.lift_x(f.roots(Fp2)[0][0])\n", + " point2 = E(Fp2)(point1[0].conjugate(), point1[1].conjugate())\n", + " divisor = J(Fp2)(point1) + J(Fp2)(point2)\n", + " divisor = J(Fpx(divisor[0]), Fpx(divisor[1])) #return from Fp2 to Fp\n", + "\n", + "d2 = divisor * 0x10001\n", + "assert d2[0].degree() == 2\n", + "x1 = d2[0][1]/2\n", + "x2 = sqrt((x1*x1-d2[0][0])/43)\n", + "\n", + "encrypted = x1.lift() + (x2.lift() - 1) * p\n", + "encrypted = int(encrypted).to_bytes(14,byteorder='little')\n", + "\n", + "# end of the math\n", + "decrypted = decrypt(encrypted, key)\n", + "print(decrypted.hex())\n", + "# 0000000000000001000000000000 for the first confirmationId\n", + "# 0000000000000002000000000000 for the second confirmationId\n", + "# 0000000000000006000000000000 for the last confirmationId\n", + "assert decrypted[8:] == b'\\0' * 6\n", + "assert decrypted[7] <= 0x80\n", + "# all zeroes in decrypted[0:7] are okay for the checker\n", + "# more precisely: if decrypted[6] == 0, first 6 bytes can be anything\n", + "# otherwise, decrypted[0] = length, and decrypted[1:1+length] must match first length bytes of sha1(product key)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "SageMath 9.0", + "language": "sage", + "name": "sagemath" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/extras/wsrv_x64_lh_act.ipynb b/extras/wsrv_x64_lh_act.ipynb new file mode 100644 index 0000000..932cb8b --- /dev/null +++ b/extras/wsrv_x64_lh_act.ipynb @@ -0,0 +1,267 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "# From Longhorn 4074\n", + "# Any version that accepts TCP8W-T8PQJ-WWRRH-QH76C-99FBW will work\n", + "key_data = {\n", + " \"p\": 7181106593102322766813520532476531209871483588988471009176871145241389568314039093657656718839885029493125387894856821599452867350054864568294961595970889,\n", + " \"a\": 1,\n", + " \"b\": 0,\n", + " \"B\": [\n", + " 520282615406607935808830413235837609227529008118239433194891765554084261177667142590192616462797266047427714603514505726507565809100858610756034340614180,\n", + " 4557046395510954851157569206449480560848332315791566919607580280750304632075435589109908909351625686398512699199297926705742962219032991805095344264722444\n", + " ],\n", + " \"K\": [\n", + " 1748427561645745685508888890965804844329037567281415535239953290167653001827496844268667372126127464466687812723744919132659150838866693283679107969476861,\n", + " 6808711632346399211426562555523956018872055718394662707289722207520029794097689415773036615424757895159410496488301598927496012713658489637493990459415502\n", + " ],\n", + " \"order\": 4633201844252750473,\n", + " \"private_key\": 4329540238250287790\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Product Key Generator, run above cell first" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10928323 10928323 2003509697754295848 6615195716181683752\n", + "868186915 868186915 878552257861989116 5490238276289377020\n", + "446954708 446954708 776512975037445878 776512975037445878\n", + "353785506 353785506 1772257197849916146 6383943216277304050\n", + "1598465793 1598465793 385978941068063200 4997664959495451104\n", + "1118655069 1118655069 374356218385227934 374356218385227934\n", + "52 531faff484f609e 42ad525d d2\n", + "CH89R-TPQRK-GPJMW-7KTYQ-F8PJD\n" + ] + } + ], + "source": [ + "import hashlib\n", + "\n", + "# p = order of field Fp\n", + "# Fp = Galois field of order p\n", + "# E = Elliptic curve y^2 = x^3 + ax + b over Fp\n", + "# B = generator on E\n", + "# K = inverse of public key\n", + "# order = order of E\n", + "# Ro = Ring Z/orderZ\n", + "\n", + "p = key_data[\"p\"]\n", + "Fp = GF(p)\n", + "E = EllipticCurve(Fp, [0, 0, 0, key_data[\"a\"], key_data[\"b\"]])\n", + "B = E.point(key_data[\"B\"])\n", + "K = E.point(key_data[\"K\"])\n", + "order = key_data[\"order\"]\n", + "Ro = Integers(order)\n", + "private_key = -key_data[\"private_key\"] % order\n", + "\n", + "# OS Family of product key\n", + "# x64 VLK - 652\n", + "# x64 Retail - 306\n", + "os_family = 105\n", + "\n", + "# Key alphabet\n", + "KCHARS = \"BCDFGHJKMPQRTVWXY2346789\"\n", + "\n", + "def int_to_bytes(n, l=None):\n", + " n = int(n)\n", + " \n", + " if not l:\n", + " l = (n.bit_length() + 7) // 8\n", + " \n", + " return n.to_bytes(l, byteorder=\"little\")\n", + "\n", + "def encode_pkey(n):\n", + " out = \"\"\n", + " \n", + " for i in range(25):\n", + " out = KCHARS[n % 24] + out\n", + " n //= 24\n", + " \n", + " out = \"-\".join([out[i:i+5] for i in range(0, len(out), 5)])\n", + " return out\n", + "\n", + "os_family <<= 1\n", + "\n", + "while True:\n", + " k = getrandbits(512)\n", + " prefix = getrandbits(32) & 0x3ff\n", + " \n", + " r = k * B\n", + " x, y = r.xy()\n", + "\n", + " mde = hashlib.sha1(b\"\\x79\" + int_to_bytes(os_family, 2) + int_to_bytes(x, 64) + int_to_bytes(y, 64)).digest()\n", + " e = int.from_bytes(mde[:4], byteorder=\"little\")\n", + " e &= 0x7fffffff\n", + " \n", + " mdh = hashlib.sha1(b\"\\x5d\" + int_to_bytes(os_family, 2) + int_to_bytes(e, 4) + int_to_bytes(prefix, 4)).digest()\n", + " h1 = int.from_bytes(mdh[:4], byteorder=\"little\")\n", + " h2 = int.from_bytes(mdh[4:8], byteorder=\"little\") >> 2\n", + " h2 &= 0x3fffffff\n", + " h = h2 << 32 | h1\n", + " b = Ro(-h * private_key)\n", + " \n", + " try:\n", + " s = Ro(b)\n", + " s = int((-b + sqrt(b^2 + 4 * Ro(k))) / 2)\n", + " except:\n", + " continue\n", + " \n", + " if s % 2 == 1:\n", + " s += order\n", + " \n", + " if (s * (s * B + h * K)) != (s * (s * B + int(b) * B)):\n", + " continue\n", + " \n", + " raw_pkey = prefix << 104 | s << 42 | e << 11 | os_family\n", + " \n", + " print((raw_pkey >> 11) & 0x7fffffff, e, (raw_pkey >> 42) & 0x3fffffffffffffff, s)\n", + " \n", + " # I could fix whatever bug made this necessary, but it works so I don't care\n", + " if ((raw_pkey >> 11) & 0x7fffffff) != e or ((raw_pkey >> 42) & 0x3fffffffffffffff) != s:\n", + " continue\n", + " \n", + " if (raw_pkey >> 32) & 0xffffffff < 0x40000000:\n", + " break\n", + "\n", + "print(hex(prefix)[2:], hex(s)[2:], hex(e)[2:], hex(os_family)[2:])\n", + "print(encode_pkey(raw_pkey))\n", + "pkey = encode_pkey(raw_pkey)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Product Key Verifier (must run above cell first)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Product Key (dashes optional): TCP8W-T8PQJ-WWRRH-QH76C-99FBW\n", + "TCP8W-T8PQJ-WWRRH-QH76C-99FBW\n", + "318 3e550ae1709773d8 6674d45a ce\n", + "f0ce4281d7695e3\n", + "110001100011111001010101000010101110000101110000100101110111001111011000110011001110100110101000101101000011001110\n", + "110001100011111001010101000010101110000101110000100101110111001111011000110011001110100110101000101101000011001110\n", + "1718932570 1718932570 True\n" + ] + } + ], + "source": [ + "def decode_pkey(k):\n", + " k = k.replace(\"-\", \"\")\n", + " out = 0\n", + " \n", + " for c in k:\n", + " out *= 24\n", + " out += KCHARS.index(c)\n", + " \n", + " return out\n", + "\n", + "pkey = input(\"Product Key (dashes optional): \")\n", + "print(pkey)\n", + "raw_key = decode_pkey(pkey)\n", + "\n", + "osf = raw_key & 0x7ff\n", + "e = (raw_key >> 11) & 0x7fffffff\n", + "s = (raw_key >> 42) & 0x3fffffffffffffff\n", + "pf = (raw_key >> 104) & 0x3ff\n", + "\n", + "mdh = hashlib.sha1(b\"\\x5d\" + int_to_bytes(osf, 2) + int_to_bytes(e, 4) + int_to_bytes(pf, 4)).digest()\n", + "h1 = int.from_bytes(mdh[:4], byteorder=\"little\")\n", + "h2 = int.from_bytes(mdh[4:8], byteorder=\"little\") >> 2\n", + "h2 &= 0x3fffffff\n", + "h = h2 << 32 | h1\n", + "\n", + "print(hex(pf)[2:], hex(s)[2:], hex(e)[2:], hex(osf)[2:])\n", + "print(hex(h)[2:])\n", + "print(bin(raw_key)[2:])\n", + "print(bin(pf << 104 | s << 42 | e << 11 | osf)[2:])\n", + "\n", + "v = s * (s * B + h * K)\n", + "x, y = v.xy()\n", + "\n", + "mde = hashlib.sha1(b\"\\x79\" + int_to_bytes(osf, 2) + int_to_bytes(x, 64) + int_to_bytes(y, 64)).digest()\n", + "ep = int.from_bytes(mde[:4], byteorder=\"little\")\n", + "ep &= 0x7fffffff\n", + "\n", + "print(e, ep, e == ep)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "71" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "0x8e/2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "SageMath 9.0", + "language": "sage", + "name": "sagemath" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}