Supermicro BMCs export their settings to an encrypted save_config.bin from the web UI’s Maintenance → Save/Restore Configuration page. On a current (November 2025) X11 firmware, the key that encrypts it is obfuscated in the firmware.

TargetDetail
BoardSupermicro X11, ASPEED AST2400 BMC
FirmwareBMC_X11AST2400-3101MS v1.78 (20251117)
Binaries/bin/key_obfuscation_SW (ARM), libipmi.so

The backup format

Two binaries from the firmware image carry the scheme: libipmi.so and /bin/key_obfuscation_SW.

The save/restore feature runs through libipmi.so: UtilSaveConfig packs a preserve_config/ tree into a save_config.tar.gz and encrypts it; UtilRestoreConfig decrypts and restores. It’s a standard OpenSSL BIO chain, and the format decompiles to:

AES-128-CBC( uint32_le crc32(plaintext)  ||  plaintext ),  PKCS#7 padding

A 4-byte CRC32 of the plaintext is prepended, the whole thing is AES-128-CBC encrypted, and the IV is hardcoded. The only interesting question is where the AES key comes from.

Where the key comes from

libipmi.so does not store the key directly. It holds a fixed 119-byte token blob and at runtime runs popen("/bin/key_obfuscation_SW -a <tokens>"); the helper de-obfuscates the tokens and returns a 16-byte AES-128 key over a one-shot shared-memory segment. The blob is constant, so the key is too:

AES-128 key : a22874547aba7a32cf8ac49c1896722f
IV          : 2e7b4c71d0452a6b89b89af96ab3c6ca   (libipmi.so @ 0x10f1d4)

These are re-derivable by anyone with the public firmware. End to end:

libipmi.so
  token blob @ 0x10f614  (119 fixed bytes)
        │  format as "0x%02X " tokens
        ▼
  popen("/bin/key_obfuscation_SW -a <tokens>")
        │
        ▼
  key_obfuscation_SW  (-a transform)
    · checksum on token[0]        bad → "error"
    · 10×255 table                keeps real bytes, drops decoys
    · 12-way transform            (+ XOR on 2 of 12 cases)
        │  write cleartext
        ▼
  SysV shm 0x5B9946  ──read once──▶ shmctl(IPC_RMID)
        │
        ▼
  AES-128 key  a22874547aba7a32cf8ac49c1896722f   (+ hardcoded IV)
        │
        ▼
  AES-128-CBC( crc32(plain) ‖ plain )  ⇄  save_config.bin

key_obfuscation_SW is a 20 KB ARM binary that does nothing but this transform.

Decrypt and encrypt with openssl

With the key and IV, both directions are plain openssl. The file is AES-128-CBC(crc32 ‖ plaintext) with PKCS#7 padding, where the 4-byte CRC32 is little-endian over the plaintext tarball.

Decrypt a save_config.bin: AES, then drop the leading CRC32.

$ openssl enc -d -aes-128-cbc \
    -K a22874547aba7a32cf8ac49c1896722f \
    -iv 2e7b4c71d0452a6b89b89af96ab3c6ca \
    -in save_config.bin | tail -c +5 > out.tar.gz

Encrypt a modified tarball back into a backup: prepend the CRC32, then AES.

$ python3 -c 'import zlib,sys; d=open("out.tar.gz","rb").read(); \
    sys.stdout.buffer.write(zlib.crc32(d).to_bytes(4,"little")+d)' \
  | openssl enc -aes-128-cbc \
      -K a22874547aba7a32cf8ac49c1896722f \
      -iv 2e7b4c71d0452a6b89b89af96ab3c6ca \
      -out save_config.bin

Impact

The key is public, so anyone with a copy of a save_config.bin can read it. These files turn up in support tickets and on backup shares, and inside is the IPMI user table with its password hashes, the BMC’s TLS private key, the DDNS keys, and the network config.

Encryption works the same in both directions, so you can edit a backup, re-encrypt it, and the BMC will take it on restore.

Applying more obfuscation to the key won’t help. The fix is to stop shipping a key in the firmware at all: ask for a passphrase when the backup is exported and derive the key from that, like pfSense and MikroTik do. Per-device keys would also keep the key off a public download, but they break restoring onto a replacement board, so a passphrase is the better default.

Prior art

Public tooling covers the older Supermicro config format (3DES-CBC, zero IV; c0d3z3r0’s gist and Binarly’s BRLY-2023-030) and, separately, the AES keys in firmware images (smcbmc). This build instead uses AES-128-CBC with a non-zero hardcoded IV and the key_obfuscation_SW helper; I found no public tool for this variant.

← Back to Blog