{ "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 }