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.
| Target | Detail |
|---|---|
| Board | Supermicro X11, ASPEED AST2400 BMC |
| Firmware | BMC_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.