Skip to content

Instantly share code, notes, and snippets.

@lexfrei
Created December 5, 2025 15:36
Show Gist options
  • Select an option

  • Save lexfrei/3e8dc9e97608502cf226371f6badb1e2 to your computer and use it in GitHub Desktop.

Select an option

Save lexfrei/3e8dc9e97608502cf226371f6badb1e2 to your computer and use it in GitHub Desktop.
Samsung 990 EVO NVMe firmware update on ARM64 (Raspberry Pi) using nvme-cli
#!/usr/bin/env python3
"""
Samsung NVMe Firmware Decryption Tool
Decrypts official Samsung firmware for use with nvme-cli on ARM64/RISC-V/etc.
Only works with official Samsung firmware - cannot be used for modification.
The AES-256 key must be extracted from the fumagician binary:
strings fumagician | grep -E '^[A-Za-z0-9+/]{43}=$'
Usage:
python3 decrypt_samsung.py <key_base64> <firmware.enc>
Example:
python3 decrypt_samsung.py "Sr8dN+...=" 1B2QKXJ7.enc
Requirements:
pip install pycryptodome
License: MIT
"""
import sys
import base64
import zipfile
import os
from pathlib import Path
try:
from Crypto.Cipher import AES
except ImportError:
print("Error: pycryptodome not installed")
print("Install with: pip install pycryptodome")
sys.exit(1)
def decrypt_aes256_ecb(data: bytes, key: bytes) -> bytes:
"""Decrypt AES-256-ECB encrypted data."""
if len(data) % 16 != 0:
raise ValueError(f"Data length must be multiple of 16 (got {len(data)})")
cipher = AES.new(key, AES.MODE_ECB)
return cipher.decrypt(data)
def verify_magic(decrypted: bytes) -> bool:
"""Check for fumagician magic signature in header."""
magic = b"_icianMAG_@*!.8&"
return decrypted[:16] == magic
def main():
if len(sys.argv) != 3:
print(__doc__)
sys.exit(1)
key_b64 = sys.argv[1]
enc_file = sys.argv[2]
# Validate inputs
if not os.path.exists(enc_file):
print(f"Error: File not found: {enc_file}")
sys.exit(1)
# Decode key
try:
key = base64.b64decode(key_b64)
except Exception as e:
print(f"Error: Invalid base64 key: {e}")
sys.exit(1)
if len(key) != 32:
print(f"Error: Key must be 32 bytes for AES-256 (got {len(key)})")
sys.exit(1)
print(f"Decrypting: {enc_file}")
# Read and decrypt outer layer
with open(enc_file, 'rb') as f:
encrypted = f.read()
print(f" Input size: {len(encrypted):,} bytes")
decrypted = decrypt_aes256_ecb(encrypted, key)
# Verify header
if not verify_magic(decrypted):
print("Warning: Magic signature not found in header")
print(" This may not be a valid Samsung firmware file")
print(" Or the key may be incorrect")
# Skip 32-byte fumagician header, rest should be ZIP
zip_data = decrypted[32:]
# Check for ZIP signature
if zip_data[:4] != b'PK\x03\x04':
print("Error: Decrypted data is not a ZIP archive")
print(" The key may be incorrect for this firmware version")
sys.exit(1)
# Save ZIP
base_name = Path(enc_file).stem
zip_path = f"{base_name}.zip"
with open(zip_path, 'wb') as f:
f.write(zip_data)
print(f" Extracted ZIP: {zip_path}")
# Extract inner .enc from ZIP
with zipfile.ZipFile(zip_path, 'r') as zf:
enc_files = [n for n in zf.namelist() if n.endswith('.enc')]
if not enc_files:
print("Error: No .enc file found in ZIP archive")
sys.exit(1)
inner_name = enc_files[0]
zf.extract(inner_name)
print(f" Extracted inner: {inner_name}")
# Decrypt inner layer
with open(inner_name, 'rb') as f:
inner_encrypted = f.read()
inner_decrypted = decrypt_aes256_ecb(inner_encrypted, key)
# Verify inner header
if not verify_magic(inner_decrypted):
print("Warning: Inner file magic signature not found")
# Save final payload (skip 32-byte header)
payload = inner_decrypted[32:]
output_name = 'firmware_payload.bin'
with open(output_name, 'wb') as f:
f.write(payload)
print(f"\nFirmware ready: {output_name}")
print(f" Size: {len(payload):,} bytes")
# Show firmware metadata if readable
# Metadata is at offset 0x3000 in the payload
if len(payload) > 0x3020:
meta_sig = payload[0x3000:0x3020]
if b'FW_IMAGE_META' in meta_sig:
version = payload[0x3020:0x3030].rstrip(b'\x00').decode('ascii', errors='ignore')
print(f" Version: {version}")
print(f"\n{'='*60}")
print("Next steps (run on target system):")
print(f"{'='*60}")
print(f" # Copy firmware to target")
print(f" scp {output_name} user@target:/tmp/")
print(f"")
print(f" # SSH to target and apply")
print(f" sudo nvme fw-download /dev/nvme0 --fw=/tmp/{output_name} --xfer=32768")
print(f" sudo nvme fw-commit /dev/nvme0 --slot=2 --action=1")
print(f" sudo reboot")
print(f"")
print(f" # Verify after reboot")
print(f" sudo nvme id-ctrl /dev/nvme0 | grep fr")
if __name__ == '__main__':
main()

Samsung 990 EVO Firmware Update on ARM64 (Raspberry Pi)

Samsung NVMe firmware updates require fumagician — an x86-64 Linux binary that cannot run on ARM64 systems. This guide documents how to apply official Samsung firmware on ARM64 using standard nvme-cli tools.

The Problem

Samsung distributes firmware as bootable ISOs containing:

  • fumagician — x86-64 ELF binary (update tool)
  • *.enc — AES-256-ECB encrypted firmware files

The encryption prevents direct use of firmware files with nvme fw-download.

Solution Overview

  1. Extract the AES key from fumagician binary
  2. Decrypt the transport layer encryption
  3. Upload firmware using standard NVMe commands

The SSD handles device-specific decryption internally — we only need to remove the transport layer.

Requirements

  • Linux system (any architecture) with Python 3
  • nvme-cli package on target system
  • pycryptodome Python library
  • Samsung firmware ISO

Step 1: Extract Firmware from ISO

# Download firmware ISO from Samsung
# Example: Samsung_SSD_990_EVO_1B2QKXJ7.iso

mkdir -p /tmp/samsung_fw && cd /tmp/samsung_fw

# Mount ISO (or extract with 7z)
sudo mount -o loop Samsung_SSD_990_EVO_*.iso /mnt

# Extract initrd (contains firmware)
gzip -dc /mnt/initrd | cpio -idmv

# Files are in root/fumagician/
ls root/fumagician/
# *.enc files and fumagician binary

Step 2: Extract Decryption Key

The AES-256 key is stored as base64 in the fumagician binary:

# Find base64-encoded 32-byte key
strings root/fumagician/fumagician | grep -E '^[A-Za-z0-9+/]{43}=$'

# Output will be a 44-character base64 string
# Decode to get 32-byte (256-bit) AES key

Keys vary between Samsung model generations. Extract from the specific fumagician version for your SSD model.

Step 3: Decrypt Firmware

#!/usr/bin/env python3
"""
Samsung NVMe Firmware Decryption Tool

Decrypts official Samsung firmware for use with nvme-cli on ARM64/RISC-V.
Only works with official Samsung firmware - cannot be used for modification.

Usage: python3 decrypt_samsung.py <key_base64> <firmware.enc>
"""

import sys
import base64
import zipfile
from Crypto.Cipher import AES

def decrypt_aes256_ecb(data: bytes, key: bytes) -> bytes:
    """Decrypt AES-256-ECB encrypted data."""
    cipher = AES.new(key, AES.MODE_ECB)
    return cipher.decrypt(data)

def main():
    if len(sys.argv) != 3:
        print(f"Usage: {sys.argv[0]} <key_base64> <firmware.enc>")
        print("  key_base64: Base64-encoded AES-256 key from fumagician")
        print("  firmware.enc: Encrypted firmware file (e.g., 1B2QKXJ7.enc)")
        sys.exit(1)

    key_b64 = sys.argv[1]
    enc_file = sys.argv[2]

    # Decode key
    key = base64.b64decode(key_b64)
    if len(key) != 32:
        print(f"Error: Key must be 32 bytes (got {len(key)})")
        sys.exit(1)

    # Read and decrypt outer layer
    with open(enc_file, 'rb') as f:
        encrypted = f.read()

    decrypted = decrypt_aes256_ecb(encrypted, key)

    # Verify header magic
    header = decrypted[:32]
    magic = decrypt_aes256_ecb(header, key) if header == encrypted[:32] else header

    # Skip 32-byte fumagician header, extract ZIP
    zip_data = decrypted[32:]
    zip_path = enc_file.replace('.enc', '.zip')

    with open(zip_path, 'wb') as f:
        f.write(zip_data)

    # Extract inner .enc from ZIP
    with zipfile.ZipFile(zip_path, 'r') as zf:
        inner_name = [n for n in zf.namelist() if n.endswith('.enc')][0]
        zf.extract(inner_name)
        print(f"Extracted: {inner_name}")

    # Decrypt inner layer
    with open(inner_name, 'rb') as f:
        inner_encrypted = f.read()

    inner_decrypted = decrypt_aes256_ecb(inner_encrypted, key)

    # Save final payload (skip 32-byte header)
    output_name = 'firmware_payload.bin'
    with open(output_name, 'wb') as f:
        f.write(inner_decrypted[32:])

    print(f"Firmware ready: {output_name} ({len(inner_decrypted) - 32} bytes)")
    print(f"\nNext steps:")
    print(f"  scp {output_name} user@target:/tmp/")
    print(f"  ssh user@target")
    print(f"  sudo nvme fw-download /dev/nvme0 --fw=/tmp/{output_name} --xfer=32768")
    print(f"  sudo nvme fw-commit /dev/nvme0 --slot=2 --action=1")
    print(f"  sudo reboot")

if __name__ == '__main__':
    main()

Step 4: Upload to SSD

# Check current firmware
sudo nvme fw-log /dev/nvme0
sudo nvme id-ctrl /dev/nvme0 | grep -E '^(mn|fr|sn)'

# Upload firmware (32KB chunks)
sudo nvme fw-download /dev/nvme0 --fw=/tmp/firmware_payload.bin --xfer=32768

# Commit to slot 2 (slot 1 is typically read-only)
sudo nvme fw-commit /dev/nvme0 --slot=2 --action=1

# Verify new firmware is staged
sudo nvme fw-log /dev/nvme0
# Should show new version in frs2

# Reboot to activate
sudo reboot

# Verify after reboot
sudo nvme id-ctrl /dev/nvme0 | grep fr

Tested Configuration

  • Hardware: Raspberry Pi 5 (ARM64)
  • OS: Debian 12 (Bookworm)
  • SSD: Samsung 990 EVO 1TB
  • Update: 0B2QKXJ7 → 1B2QKXJ7
  • Result: Success

Important Notes

  • Official firmware only: This method applies unmodified Samsung firmware
  • Backup first: Create backups before firmware updates
  • Don't interrupt: Power loss during update may brick the SSD
  • Slot 1 preserved: Factory firmware remains in read-only slot 1
  • First boot delay: Initial boot after update may take 1-2 minutes

How It Works

Samsung uses two-layer encryption:

  1. Transport layer (AES-256-ECB): Protects firmware during distribution. Key is in fumagician binary.

  2. Device layer: Protects against firmware modification. Key is burned into SSD ROM.

We decrypt layer 1, then send the payload via standard NVMe commands. The SSD internally handles layer 2 using its ROM key.

This is why the method only works with official Samsung firmware — modified firmware would fail the SSD's internal signature verification.

Disclaimer

This documentation is provided for interoperability purposes under DMCA §1201(f). It enables use of official Samsung firmware on platforms not supported by Samsung's tools. No Samsung intellectual property is distributed. Users must obtain firmware from official Samsung sources.

References

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