Fujitsu iRMC S4 PoC
This file contains 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 python3 | |
# https://watchmysys.com/blog/2023/01/fujitsu-irmc-s4-license/ | |
import argparse | |
import base64 | |
import binascii | |
import zlib | |
import hmac | |
import hashlib | |
import struct | |
from Crypto.Cipher import AES | |
# obtain values from /usr/local/lib/libfts_license.so.1.12.1 | |
# Note that in python, you enter binary hex values as b"\x0d\x0e..." | |
HMAC_KEY = b"" | |
HMAC_MSG = b"" * 4 | |
AES_IV = b"" | |
def init_lookup_table(): | |
out = [] | |
for i in range(0x100): | |
lookup_table_entry = i << 0x18 | |
for k in range(8): | |
if lookup_table_entry >> 31: | |
inlr = lookup_table_entry << 1 | |
if lookup_table_entry >> 31: | |
lookup_table_entry = inlr ^ 0x4c11db7 | |
else: | |
lookup_table_entry = lookup_table_entry << 1 | |
lookup_table_entry &= 0xffffffff | |
out.append(lookup_table_entry) | |
return out | |
def spd_crc32(param_1, x): | |
tbl = init_lookup_table() | |
for pbVar1 in param_1: | |
x = (tbl[(pbVar1 ^ x >> 0x18) & 0xff] ^ x << 8) & 0xffffffff | |
return x | |
def simple_generator(ext_features, type, serial): | |
if ext_features.lower() == "kvm": | |
features = b"\x01\x00\x00\x00" | |
elif ext_features.lower() == "kvm_media": | |
features = b"\x03\x00\x00\x00" | |
elif ext_features.lower() == "kvm_media_elcm": | |
# this license type doesn't validate on my TX chassis | |
# but apparently works on RX chassis | |
if type.lower() == "tx": | |
print("License may not be valid for TX chassis with eLCM enabled") | |
features = b"\x0f\x00\x00\x00" | |
return license_generator(serial, features, type.lower()) | |
def license_generator(serial, features, type): | |
# chassis type TX | |
struct_data = 0xffffff00 | |
if type == "rx": | |
# chassis type RX | |
struct_data = 0xffffff05 | |
DATA = b'iRMC' \ | |
+ features \ | |
+ struct.pack('>I', struct_data) + \ | |
struct.pack('<I', spd_crc32(serial.encode("ASCII"), 1)) | |
print(binascii.hexlify(DATA).decode('utf-8')) | |
aes_key = hmac.new(HMAC_KEY, HMAC_MSG, hashlib.sha1).digest() | |
# this is AES-128, so only take the first 16 bytes of the aes_key | |
cipher = AES.new(aes_key[:16], AES.MODE_CBC, iv=AES_IV) | |
encrypted_license = cipher.encrypt(DATA) | |
encoded_license = base64.b32encode(encrypted_license) | |
license = encoded_license.decode("UTF-8").replace("=","") | |
return '-'.join(license[i:i+4] for i in range(0, len(license), 4)) | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-t", "--type", type=str, default="tx", help="License type, valid values are 'TX' or 'RX'") | |
parser.add_argument("-f", "--features", type=str, default="kvm_media", help="License type, valid values are 'KVM', 'KVM_Media', or 'KVM_Media_eLCM'") | |
parser.add_argument("serial") | |
args = parser.parse_args() | |
if len(HMAC_KEY) != 16 or len(HMAC_MSG) != 16*4 or len(AES_IV) != 16: | |
print("You are missing the HMAC and AES values") | |
else: | |
print(simple_generator(args.features, args.type, args.serial)) |
If you want to investigate the exported license (using “iRMC S4” -> “Save Configuration” -> “Include License Information”), here is a validation script:
#!/usr/bin/env python3
import argparse
import base64
import binascii
import zlib
import hmac
import hashlib
import struct
from sys import exit
from Crypto.Cipher import AES
# obtain values from /usr/local/lib/libfts_license.so.1.12.1
# Note that in python, you enter binary hex values as b"\x0d\x0e..."
HMAC_KEY = b""
HMAC_MSG = b"" * 4
AES_IV = b""
aes_key = hmac.new(HMAC_KEY, HMAC_MSG, hashlib.sha1).digest()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("license")
args = parser.parse_args()
if len(args.license) != 32:
print("'%s' is not a valid license" % args.license)
exit(1)
try:
encrypted_license = base64.b32decode(args.license)
except binascii.Error:
encrypted_license_fujitsu = args.license.replace("-","")
padding = 32-len(encrypted_license_fujitsu)
padded_license = encrypted_license_fujitsu + padding*"="
encrypted_license = base64.b32decode(padded_license)
cipher = AES.new(aes_key[:16], mode=AES.MODE_CBC, iv=AES_IV)
decrypted_license = cipher.decrypt(encrypted_license)
if decrypted_license[:4].decode("UTF-8") == "iRMC":
if ((decrypted_license[4] & 0x04) >> 2 == 1):
print("eLCM: enabled")
if ((decrypted_license[4] & 0x02) >> 1 == 1):
print("Media: enabled")
if ((decrypted_license[4] & 0x01) == 1):
print("KVM: enabled")
print("(Probably) valid license: %s" % binascii.hexlify(decrypted_license))
else:
print("License is missing 'iRMC' magic")
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For people who are having trouble unpacking the firmware:
Then you can run
binwalk -e
on the firmware update to extract the relevant file.Using the iRMC 9.69F release.