Skip to content

Instantly share code, notes, and snippets.

@halmartin
Last active April 5, 2024 05:33
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save halmartin/28e013a9034be04777073bccc918cb95 to your computer and use it in GitHub Desktop.
Save halmartin/28e013a9034be04777073bccc918cb95 to your computer and use it in GitHub Desktop.
Fujitsu iRMC S4 PoC
#!/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))
@halmartin
Copy link
Author

halmartin commented Mar 17, 2023

For people who are having trouble unpacking the firmware:

FROM ubuntu:16.04

RUN apt-get update && apt-get install -y --no-install-recommends binwalk cramfsprogs bsdmainutils

COPY FTS_D3373TX1320M3TX1330M3iRMCKronos4Firmwa_D3373B10969Fsdr0318_1272411.BIN /tmp/
WORKDIR /tmp/

CMD "/bin/bash"

Then you can run binwalk -e on the firmware update to extract the relevant file.

Using the iRMC 9.69F release.

@halmartin
Copy link
Author

halmartin commented May 4, 2023

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")

@b3nis
Copy link

b3nis commented Jul 17, 2023

I am trying to recover the values from the firmware file and I manage to get the libfts_license.so.1.12.1 most of the file is just @ and not really readable. The only thing that makes "sense" is:

^K^N^N^O^@^A^B^C        ^H^G^FH!^@^@^A^@^@^@�!^@^@^B^@^@^@�!^@^@^H^@^@^@^@^@^@^@^@^@^@^@^A^B^C^D^A^B^C^D^A^B^C^D^A^B^C^DFujitsu!^A^A�(^@^@��AES CBC 128 Decrypt Fail
^@^@^@AES CBC 128 Decrypt final Fail
^@AES CBC 128 Encrypt updated in Confidentiality Trailer Fail
^@^@^@^@Confidentiality Trailer AES CBC 128 Encryption Fail
^@^@^@^@/conf/fts/license_key^@^@^@rb^@^@wb^@^@KVM^@2015-01-01 00:00:00^@%F %H:%M:%S^@Rejecting valid volume license due to cutoff date '%s'
^@sh /etc/init.d/licenseapp.sh manual &^@^@^@Could not decode license '%s' len=%d
^@^@^@MEDIA^@^@^@eLCM^@^@^@^@^@^@^@^@X^Q^@^@�^P^@^@^@^@^@^@^A^@^@^@8^C^@^@^A^@^@^@B^C^@^@^A^@^@^@R^C^@^@^A^@^@^@e^C^@^@^N^@^@^@�^C^@^@^O^@^@^@�^C^@^@^L^@^@^@�

I guess I have missed something on the way here? I am trying to open the file with nano after extracted the firmware with binwalk -e.

@halmartin
Copy link
Author

halmartin commented Jul 17, 2023

I guess I have missed something on the way here? I am trying to open the file with nano after extracted the firmware with binwalk -e.

libfts_license in Ghidra, showing decompiled function and hexdump

@halmartin
Copy link
Author

halmartin commented Nov 19, 2023

@Alotire432 Please do not post personal information in a public comment on GitHub. My contact information is available on my profile.

Your values for HMAC_KEY, HMAC_MSG, and AES_IV are incorrect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment