Endermanch / XPKeygen

Windows XP Keygen
GNU General Public License v3.0
853 stars 48 forks source link

How is a Confirmation ID derived from the Installation ID in WPA? #2

Open CONIGUERO opened 1 year ago

CONIGUERO commented 1 year ago

Would be pretty neat to incorporate a generator for it to get rid of WPA in a "legitimate" way without resorting to patches or third-party XP repacks.

Endermanch commented 1 year ago

That's a job for a way different keygen. The algorithm for Confirmation ID generation isn't publicly known and it would require deep research into XP's source code, particularly pidgen / dpcdll. The Microsoft call center still works, so you can generate a valid Confirmation ID via normal means still for free. The only publicly known and confirmed correct information about Installation ID's is on Licenturion. That's how far I am in my research.

https://www.licenturion.com/xp/fully-licensed-wpa.txt

What I can tell you for sure is such a keygen exists and I'm currently trying to acquire it.

CONIGUERO commented 1 year ago

The algorithm for Confirmation ID generation isn't publicly known and it would require deep research into XP's source code, particularly pidgen / dpcdll.

Bad news: the "good stuff" for the WPA system was stripped out of the WinXP code "release". Good news: some kind soul decompiled the libraries and provided a nice zip for the code. Some variables have autogenerated names, but it should be a good starting point. ds.zip_decompiled_XPSP1_winlogon (1).zip

Endermanch commented 1 year ago

That doesn't help a ton, but I've figured the algorithm out. Huge props to diamondggg for providing necessary discussions and documents, here's how that works:

The Confirmation ID generation is based on a hyperelliptic curve of genus 2 with a set jacobian (generator): image

There exists an algorithm that generates valid Confirmation IDs based on Installation IDs. The Installation ID is first unpacked using the Licenturion cheat sheet, then decrypted via a certain scheme. The rest of the job is finding such set of points that would satisfy the curve equation.

It must be noted that cryptographically that scheme is not secure. The algorithm:

import hashlib

# 226512-274743-842923-777124-961370-722240-570042-517722-757426
installationId = 226512747484292777129613772224570045177275742
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 = 01370060126031496139086139099049447
# 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)

All credit belongs to diamondggg.