Created
March 31, 2025 17:40
-
-
Save ulidtko/e987303e5406134c63e389a2810febe3 to your computer and use it in GitHub Desktop.
License file cryptor for AntennaHouse Formatter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
License file (de)cryptor for AntennaHouse Formatter https://www.antennahouse.com/formatter-v7 | |
""" | |
import argparse | |
import itertools | |
import struct | |
import sys | |
from binascii import hexlify, unhexlify | |
from dataclasses import dataclass | |
from itertools import islice | |
from typing import Tuple | |
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | |
def rc4_bitstream(S): | |
i, j = 0, 0 | |
while True: | |
i = (i + 1) % 256 | |
j = (j + S[i]) % 256 | |
S[i], S[j] = S[j], S[i] | |
yield S[(S[i] + S[j]) % 256] | |
def rc4_init(key): | |
key, keylen = bytes(key), len(key) | |
S = [i for i in range(256)] | |
j = 0 | |
for i in range(256): | |
j = (j + S[i] + (key[i % keylen])) % 256 | |
S[i], S[j] = S[j], S[i] | |
return rc4_bitstream(S) | |
#-- test | |
assert bytes(islice(rc4_init(b'Key'), 10)) == unhexlify('EB9F7781B734CA72A719') | |
assert bytes(islice(rc4_init(b'Wiki'), 6)) == unhexlify('6044DB6D41B7') | |
assert bytes(islice(rc4_init(b'Secret'), 8)) == unhexlify('04D46B053CA87B59') | |
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | |
def chunks(size, source): | |
for i in range(0, len(source), size): | |
yield source[i:i+size] | |
KEYS = list(chunks(16, unhexlify(''' | |
0423 d6a3 e29d 44ac b429 9b7c 4106 ad6e | |
aae5 f3fc 80a3 4b9b 85dc 92f6 0787 2cf0 | |
2374 3f71 83d6 46a5 bff3 6dd8 e91d 2f08 | |
8d7b e806 d996 4ab1 a259 60a8 7fea 6100 | |
'''.strip().replace(' ', '').replace('\n', '')))) | |
def AH_doEncrypt(key, buf): | |
cypher = rc4_init(key) | |
#-- the original discards bitstream prefix by encrypting on-stack trash | |
_ = [next(cypher) for _ in range(256)] | |
return bytes(a ^ b for a, b in zip(buf, cypher)) | |
""" | |
AHNewLicense::License: | |
[0] -- AHNewLicenseImpl* | |
AHNewLicense::AHNewLicenseImpl: | |
[0] -- keyNum | |
[4] -- upcasted word expiry year from decbuf[0x104] | |
[8] -- upcasted byte expiry month from decbuf[0x106] | |
[C] -- upcasted byte expiry day from decbuf[0x107] | |
[18] -- ptr to dynalloc'd char[24] LicenseData eg AFC700L6EDAH000127013161 | |
[20] -- ptr to dynalloc'd char[] -- serial, non-null only for keyNum 4 | |
[28] -- ptr to dynalloc'd byte[] optionFlags hexdecoded | |
[30] -- nOptionFlags | |
[38] -- ptr to dynalloc'd char[] Company | |
[40] -- ptr to dynalloc'd char[] Section | |
[48] -- ptr to dynalloc'd char[] Username | |
[50] -- ptr to dynalloc'd char[] Addinfo | |
[58] -- LicenseFlags, 2|4|16|32|64 | |
2 - isTrialLicense() | |
4 - isEvalLicense() | |
8 - isForceEvalLicense() | |
8 == unlimited date | |
12 == isEvaluation() | |
16 - LicenseType Q or R | |
32 - LicenseType Z | |
64 - LicenseType X | |
== 256 - bypass in checkLicense() | |
[60] -- dynalloc'd ptr to char[] LicFile | |
[68] -- ? | |
[6c] -- checkLicense() retcode save in loadLicense() | |
size=0x70 | |
LicenseType enum (10th char in fullserial): | |
Q -- this.LicenseFlags |= 10 | |
R -- this.LicenseFlags |= 10 | |
T -- if keyNum <= 3: this.LicenseFlags |= 2 | |
V -- this.LicenseFlags |= 4 | |
X -- this.LicenseFlags |= 40 | |
Z -- if keyNum <= 3: this.LicenseFlags |= 20 | |
EncryptedBuffer: | |
[0x100:101] -- BE word of key index (1-based) | |
[0x102:103] -- BE word of total len of 5 varstrs @0x120 onwards | |
[0x104:105] -- BE word expiry year | |
[0x106] -- expiry month | |
[0x107] -- expiry day | |
[0x108:11f] -- 24 ascii bytes, license serial | |
[0x120] -- ascii hexpairs of optionFlags, null-terminated | |
[...] -- Company, Section, Username, Addinfo | |
ProductCode: AFA AFX AFC AFL AFY AFD | |
OptionFlag: | |
isNoWaterMark = 0 | |
isNoUrlMark = 1 | |
isMathMLEnabled = 5 | |
isCGMEnabled = 10 | |
isPSCreatorEnabled = 6 | |
isSVGCreatorEnabled = 4 | |
isPANTONEEnabled = 7 | |
isXPSCreatorEnabled = 12 | |
isPDFCreatorEnabled = 3 | |
isBarcodeOptionEnabled = 17 | |
isOOXMLCreatorDocxEnabled = 19 | |
isPNGOutputEnabled = 18 | |
forDeveloper = 31 | |
""" | |
@dataclass | |
class LicenseData: | |
fullserial: str # 24 ascii chars | |
company: str | |
section: str | |
username: str | |
addinfo: str | |
options: str # bytearray as hexpairs | |
keynum: int | |
expiry: Tuple[int, int, int] | |
def parse_decrypted(buf): | |
fffmt = '>256xHHHBB24s' | |
header = struct.calcsize(fffmt) | |
fields = struct.unpack(fffmt, buf[:header]) | |
keyNum, varstrend, expY, expM, expD, serial = fields | |
varfields = buf[header:].split(b'\0', 5) | |
if len(varfields) < 5: | |
return None | |
options, company, section, username, addinfo = varfields[:5] | |
if keyNum - 1 > len(KEYS): | |
return None | |
if varstrend != (header | |
+ 5 + sum(map(len, [options, company, section, username, addinfo])) | |
): | |
return None | |
data = LicenseData( | |
fullserial=serial.decode('ascii'), | |
options=options.decode('ascii'), | |
company=company.decode('ascii'), | |
section=section.decode('ascii'), | |
username=username.decode('ascii'), | |
addinfo=addinfo.decode('ascii'), | |
keynum=keyNum, | |
expiry=(expY, expM, expD), | |
) | |
print(data) | |
return data | |
def main(): | |
argP = argparse.ArgumentParser() | |
argP.add_argument('ahf_lic_file') | |
opts = argP.parse_args() | |
cryptext = open(opts.ahf_lic_file, 'rb').read() | |
assert len(cryptext) == 2048 | |
for key in KEYS: | |
plain = AH_doEncrypt(key, cryptext) | |
r = parse_decrypted(plain) | |
if r: | |
print("[!] Decrypted with key", hexlify(key).decode('ascii')) | |
open(opts.ahf_lic_file, 'wb').write(plain) | |
break | |
else: | |
#-- RC4 encrypt === RC4 decrypt | |
r = parse_decrypted(cryptext) | |
if r: | |
key = KEYS[r.keynum - 1] | |
cryptext = AH_doEncrypt(key, cryptext) | |
open(opts.ahf_lic_file, 'wb').write(cryptext) | |
print("[!] Encrypted with key", hexlify(key).decode('ascii')) | |
if __name__ == "__main__": | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment