Skip to content

Instantly share code, notes, and snippets.

@ulidtko
Created March 31, 2025 17:40
Show Gist options
  • Save ulidtko/e987303e5406134c63e389a2810febe3 to your computer and use it in GitHub Desktop.
Save ulidtko/e987303e5406134c63e389a2810febe3 to your computer and use it in GitHub Desktop.
License file cryptor for AntennaHouse Formatter
#!/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