Remember serial number for Windows (or 'product key'). 5 groups of 5 characters.
When you sell a software and want to give a user a set enabled options/features, you use some sort of license key, license code, serial number, and so on.
Clearly, a user will try to modify characters or flip some bits with the hope that other features/options will be enabled. Or expiration date will be extended. A hacker lives in every soul.
This is not as crazy as it sounds. Let's have a case: a license code has 16 bytes, it's encrypted by AES. A secret key is not known to attacker/user. Decrypted code contains serial number (32 bits), expiration date (UNIX timestamp) and options enabled (32 bits).
#!/usr/bin/env python3 import zlib, struct, hexdump, os, time # pip install pycryptodome from Crypto.Cipher import AES from datetime import datetime cnt=0 current_time=int(time.time()) def check_lic_code(code): # key is unknown to attacker: key=b"\x12\x34\x56\x78\x12\x34\x56\x78\x12\x34\x56\x78\x12\x34\x56\x78" # decrypt it IV=b"\x00"*16 cipher=AES.new(key, AES.MODE_CBC, IV) plaintext=cipher.decrypt(code) body=plaintext[0:12] exp_date=struct.unpack("<I", plaintext[4:8])[0] # is expiration date is in past? if exp_date<current_time: return print ("lic.code OK") print ("calls", cnt) print ("ciphertext:") hexdump.hexdump(code) print ("plaintext:") hexdump.hexdump(plaintext) SN=struct.unpack("<I", plaintext[0:4])[0] features=struct.unpack("<I", plaintext[8:12])[0] print ("SN", hex(SN)) print ("features", hex(features)) print ("exp_date", datetime.fromtimestamp(exp_date)) exit(0) while True: check_lic_code(os.urandom(16)) cnt=cnt+1
Only expiration date is checked, against current time. To break this 'protection', we generate random codes, without even knowing AES key. Without even knowing that AES is used. After just couple of attempts, we found the correct code:
lic.code OK calls 1 ciphertext: 00000000: B1 8A 84 A6 52 3F 38 E5 AD 48 41 54 9B 41 5B 37 ....R?8..HAT.A[7 plaintext: 00000000: 1B 7A 71 B7 66 F5 D8 9D 26 C7 9C 9F 55 41 16 F9 .zq.f...&...UA.. SN 0xb7717a1b features 0x9f9cc726 exp_date 2053-12-02 00:38:30
Such a 'protection' can be cracked even if manually entering random license codes.
OK, you will say, some check sum is required. Let's try CRC32. Let's put checksum inside of encrypted block, like, CRC32(SN || exp_date || features).
#!/usr/bin/env python3 import zlib, struct, hexdump, os, time # pip install pycryptodome from Crypto.Cipher import AES from datetime import datetime cnt=0 current_time=int(time.time()) def check_lic_code(code): # key is unknown to attacker: key=b"\x12\x34\x56\x78\x12\x34\x56\x78\x12\x34\x56\x78\x12\x34\x56\x78" # decrypt it IV=b"\x00"*16 cipher=AES.new(key, AES.MODE_CBC, IV) plaintext=cipher.decrypt(code) body=plaintext[0:12] exp_date=struct.unpack("<I", plaintext[4:8])[0] # is expiration date is in past? if exp_date<current_time: return crc32_must_be=struct.unpack("<I", plaintext[12:])[0] crc32_we_got=zlib.crc32(body) if (crc32_must_be)==(crc32_we_got): print ("lic.code OK") print ("crc32 (both)", hex(crc32_must_be)) print ("calls", cnt) print ("ciphertext:") hexdump.hexdump(code) print ("plaintext:") hexdump.hexdump(plaintext) SN=struct.unpack("<I", plaintext[0:4])[0] features=struct.unpack("<I", plaintext[8:12])[0] print ("SN", hex(SN)) print ("features", hex(features)) print ("exp_date", datetime.fromtimestamp(exp_date)) exit(0) while True: check_lic_code(os.urandom(16)) cnt=cnt+1
I continue sending random codes. And guess what? I finally found the one. Which is, after decryption, has a correct CRC32 checksum:
lic.code OK crc32 (both) 0xaa44dd9b calls 929558176 ciphertext: 00000000: 67 08 14 E6 3E D2 42 E9 FF 6F 08 59 82 B1 41 65 g...>.B..o.Y..Ae plaintext: 00000000: ED 9E 46 D3 8B 51 8B FC 0A 2F 19 91 9B DD 44 AA ..F..Q.../....D. SN 0xd3469eed features 0x91192f0a exp_date 2104-04-07 07:20:11 ['/home/i/dotfiles/bin/my_time.py', 'python3', './1.py'] seconds: 9828 or: 2h43m48s
Not fast, but doable: I spent ~3 hours and ~10**9 calls to that function. So if a software product has a similar license code check, and this check is resided in some Windows DLL, and you can call that function many times, you could finally find a correct code. Even more -- after several tries, you'll find several codes with different features enabled and different expiration date.
And this is slow Python. Pure C/C++ code will be faster, of course. This is even doable via internet connection. This is like fuzzing (fuzz testing).
OK, you'll say, here you will need to use some serious cryptography -- hash function or so. And maybe add a magic cookie in header, like 0xCAFEBABE. But this has its downside: in 1990s, license codes were pronounced via phone and typed manually. During that times, license codes had to be short. But today, one kilobyte license file is not a problem, of course.
Moral of the story -- using even strong encryption like AES with secret key may not be enough. A popular CRC32 may not be enough. Usually, MAC is a solution: (keyed) hash function like SHA2. HMAC is a popular choice today.
BTW, first versions of SSH (mid-1990s) used CRC32 for checksum. But it was a temporal ad hoc solution and quickly removed.
Yes, I know about these lousy Disqus ads. Please use adblocker. I would consider to subscribe to 'pro' version of Disqus if the signal/noise ratio in comments would be good enough.