mirror of
https://github.com/Neo-Desktop/WindowsXPKg
synced 2026-11-03 07:13:19 +02:00
* Add Wither's gists as references * remove broken ones --------- Co-authored-by: WitherOrNot <damemem@gmail.com>
18 KiB
18 KiB
In [ ]:
# Windows XP Professional Retail (Bink ID 2C)
key_data = {
"p": 24412280675538104642884792561502783185577987209710041026341163083973933860854736635268965257725055809364646140091249,
"a": 1,
"b": 0,
"B": [
21673361717619259910600499419800485528178801849923454062050055236231939594233283543796077751210469045350919066368895,
5232476492611604888729825305639232005017822876108144652169892952989580351454246958886421453535493897842819359154864
],
"K": [
21551722775458524408480112576069559265917312687549112053580919391285918530584174752292844347621326558272739603979057,
13463977158522661542654520438933687107907187215503371589980428235633526671841388652148099285621876350916055100879930
],
"order": 55681564377333977,
"private_key": 30951839223306173
}In [ ]:
import hashlib
# p = order of field Fp
# Fp = Galois field of order p
# E = Elliptic curve y^2 = x^3 + ax + b over Fp
# B = generator on E
# K = inverse of public key
# order = order of E
p = key_data["p"]
Fp = GF(p)
E = EllipticCurve(Fp, [0, 0, 0, key_data["a"], key_data["b"]])
B = E.point(key_data["B"])
K = E.point(key_data["K"])
order = key_data["order"]
private_key = -key_data["private_key"] % order
# PID of product key
pid = 756_696969
# Key alphabet
KCHARS = "BCDFGHJKMPQRTVWXY2346789"
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 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
pid <<= 1
while True:
k = getrandbits(384)
r = k * B
x, y = r.xy()
md = hashlib.sha1(int_to_bytes(pid, 4) + int_to_bytes(x, 48) + int_to_bytes(y, 48)).digest()
h = int.from_bytes(md[:4], byteorder="little") >> 4
h &= 0xfffffff
s = int(abs((private_key * h + k) % order))
raw_pkey = s << 59 | h << 31 | pid
print(hex(pid)[2:], hex(h)[2:], hex(s)[2:], hex(raw_pkey)[2:])
if raw_pkey >> 96 < 0x40000:
break
print(encode_pkey(raw_pkey))In [ ]:
def decode_pkey(k):
k = k.replace("-", "")
out = 0
for c in k:
out *= 24
out += KCHARS.index(c)
return out
pkey = input("Product Key (dashes optional): ")
raw_pkey = decode_pkey(pkey)
kpid = (raw_pkey & 0x7fffffff) >> 1
verify = (kpid // 1000000) == ((pid >> 1) // 1000000)
print(kpid, pid >> 1)
if verify:
h = (raw_pkey >> 31) & 0xfffffff
s = (raw_pkey >> 59) & 0x7ffffffffffffff
r = h * K + s * B
x, y = r.xy()
md = hashlib.sha1(int_to_bytes(kpid << 1, 4) + int_to_bytes(x, 48) + int_to_bytes(y, 48)).digest()
hp = int.from_bytes(md[:4], byteorder="little") >> 4
hp &= 0xfffffff
print(h, hp)
if h == hp:
print("Valid key")
else:
print("Invalid key")In [ ]:
import hashlib
# order of field Fp
p = 0x16A6B036D7F2A79
# Galois field of order p
Fp = GF(p)
# Polynomial field Fp[x] over Fp
Fpx.<x> = Fp[]
# Hyperellptic curve function
F = x^5+0x1400606322B3B04*x^4+0x1400606322B3B04*x^3+0x44197B83892AD0*x^2+0x21840136C85381*x
# Hyperelliptic curve E: y^2 = F(x) over Fp
E = HyperellipticCurve(F)
# The jacobian over E
J = E.jacobian()
# This constant inverts multiplication by 0x1001 in verification
# My best guess for how it was calculated: INV = 0x10001^-1 (mod |J|)
# |J| is hard to compute, how can we calculate for other curves?
INV = 0x40DA7C36D44C04E21B9D10F127C1
# Key to decrypt installation IDs
IID_KEY = b'\x6A\xC8\x5E\xD4'
# 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("".join(parts))
# 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:]
sha1_result = hashlib.sha1(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:]
sha1_result = hashlib.sha1(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
# unpack&decrypt installationId
installationId = validate_cksum(input("Installation ID (dashes optional): "))
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 = iid[:8]
productid = int.from_bytes(iid[8:17], byteorder='little')
productkeyhash = iid[17:]
pid1 = productid & ((1 << 17) - 1)
pid2 = (productid >> 17) & ((1 << 10) - 1)
pid3 = (productid >> 27) & ((1 << 25) - 1)
version = (productid >> 52) & 7
pid4 = productid >> 55
assert version == (4 if len(iid) == 17 else 5)
key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')
data = [0x00] * 14
print("\nConfirmation IDs:")
for i in range(0x81):
data[7] = i
# 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) * 43)
u = x^2 + u1 * x + u0
# Generate original divisor
v = find_v(u)
if not 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]) / 43).lift() + p
# Win
conf_id = x1 * (p + 1) + x2
conf_id = add_cksum(conf_id)
print(conf_id)In [ ]:
import hashlib
# 226512-274743-842923-777124-961370-722240-570042-517722-757426
installationId = 114535500880440159787527912804896629001083118
installationIdSize = 19 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key))
# all three of following are valid generated
# 013705-060122-603141-961392-086136-909901-494476
confirmationId = 15771960290497900806797040541467113
# 022032-220754-159721-909624-985141-504586-914001
#confirmationId = 02203220751597290962985145045891400
# 137616-847280-708585-827476-874935-313366-790880
#confirmationId = 13761847287085882747874933133679088
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:]
sha1_result = hashlib.sha1(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
# unpack&decrypt installationId
iid = int(installationId).to_bytes(installationIdSize, byteorder='little')
iid = decrypt(iid, b'\x6A\xC8\x5E\xD4')
hwid = iid[:8]
productid = int.from_bytes(iid[8:17], byteorder='little')
productkeyhash = iid[17:]
pid1 = productid & ((1 << 17) - 1)
pid2 = (productid >> 17) & ((1 << 10) - 1)
pid3 = (productid >> 27) & ((1 << 25) - 1)
version = (productid >> 52) & 7
pid4 = productid >> 55
assert version == (4 if len(iid) == 17 else 5)
key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')
# productkeyhash is not used for validation, it exists just to allow the activation server to reject keygenned pids
# now the math
p = 0x16A6B036D7F2A79
Fp = GF(p)
Fpx.<x> = Fp[]
E = HyperellipticCurve(x^5+0x1400606322B3B04*x^4+0x1400606322B3B04*x^3+0x44197B83892AD0*x^2+0x21840136C85381*x)
J = E.jacobian()
# deserialize divisor
x1 = confirmationId // (p + 1)
x2 = confirmationId % (p + 1)
if x1 <= p:
# two or less points over GF(p)
point1 = E.lift_x(Fp(-x1)) if x1 != p else None
point2 = E.lift_x(Fp(-x2)) if x2 != p else None
if point1 is not None and point2 is not None:
# there are 4 variants of how lift_x() could select both y-s
# we don't distinguish D and -D, but this still leaves 2 variants
# the chosen one is encoded by order of x1 <=> x2
lastbit1 = point1[1].lift() & 1
lastbit2 = point2[1].lift() & 1
if x2 < x1:
if lastbit1 == lastbit2:
point2 = E(point2[0], -point2[1])
else:
if lastbit1 != lastbit2:
point2 = E(point2[0], -point2[1])
point1 = J(point1) if point1 is not None else J(0)
point2 = J(point2) if point2 is not None else J(0)
divisor = point1 + point2
else:
# a pair of conjugate points over GF(p^2)
f = (x+x2)*(x+x2)-43*x1*x1 # 43 is the minimal quadratic non-residue in Fp
Fp2 = GF(p^2)
point1 = E.lift_x(f.roots(Fp2)[0][0])
point2 = E(Fp2)(point1[0].conjugate(), point1[1].conjugate())
divisor = J(Fp2)(point1) + J(Fp2)(point2)
divisor = J(Fpx(divisor[0]), Fpx(divisor[1])) #return from Fp2 to Fp
d2 = divisor * 0x10001
assert d2[0].degree() == 2
x1 = d2[0][1]/2
x2 = sqrt((x1*x1-d2[0][0])/43)
encrypted = x1.lift() + (x2.lift() - 1) * p
encrypted = int(encrypted).to_bytes(14,byteorder='little')
# end of the math
decrypted = decrypt(encrypted, key)
print(decrypted.hex())
# 0000000000000001000000000000 for the first confirmationId
# 0000000000000002000000000000 for the second confirmationId
# 0000000000000006000000000000 for the last confirmationId
assert decrypted[8:] == b'\0' * 6
assert decrypted[7] <= 0x80
# all zeroes in decrypted[0:7] are okay for the checker
# more precisely: if decrypted[6] == 0, first 6 bytes can be anything
# otherwise, decrypted[0] = length, and decrypted[1:1+length] must match first length bytes of sha1(product key)