Skip to content

Instantly share code, notes, and snippets.

@MrARM
Created September 2, 2025 23:39
Show Gist options
  • Select an option

  • Save MrARM/e41da1c69a35cf81870a564613f52cc3 to your computer and use it in GitHub Desktop.

Select an option

Save MrARM/e41da1c69a35cf81870a564613f52cc3 to your computer and use it in GitHub Desktop.
Flipper Zero bin to dfu packer
#!/usr/bin/env python3
"""
DFU Binary Repacker - Firmware Construction Tool
Build valid DFU containers from raw firmware for Flipper Zero
"""
import struct
import sys
import argparse
import time
import zlib
from typing import List, Tuple
# DFU file structure constants
DFU_SUFFIX_LENGTH = 16
DFU_PREFIX_LENGTH = 11
DFU_TARGET_PREFIX_LENGTH = 274
# ANSI color codes for terminal styling
class Colors:
PURPLE = "\033[95m"
CYAN = "\033[96m"
DARKCYAN = "\033[36m"
BLUE = "\033[94m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
END = "\033[0m"
def print_banner():
"""Prints the script's banner."""
banner = f"""{Colors.CYAN}
Be nice. Don't gatekeep.
"""
print(banner)
def loading_animation(message, duration=0.5):
"""Display a cool loading animation."""
chars = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
end_time = time.time() + duration
i = 0
while time.time() < end_time:
print(
f"\r{Colors.GREEN}[{chars[i % len(chars)]}] {message}{Colors.END}",
end="",
flush=True,
)
time.sleep(0.1)
i += 1
print(f"\r{Colors.GREEN}[✓] {message}{Colors.END}")
class DFUPacker:
def __init__(self, vid: int, pid: int, version: int):
self.vid = vid
self.pid = pid
self.version = version
self.bcdDFU = 0x011A # DFU version 1.1a
def _build_elements(self, binaries: List[Tuple[int, bytes]]) -> bytes:
"""Build the binary data blocks for all elements."""
elements_blob = bytearray()
for addr, data in binaries:
# Element Prefix: Address (4 bytes), Size (4 bytes)
elements_blob.extend(struct.pack("<II", addr, len(data)))
# Element Data
elements_blob.extend(data)
return bytes(elements_blob)
def _build_target(self, elements_blob: bytes, num_elements: int) -> bytes:
"""Build a DfuSe target descriptor."""
target_blob = bytearray()
# Target Prefix
target_blob.extend(b"Target") # szSignature (6 bytes)
target_blob.extend(b"\x00") # bAlternateSetting (1 byte)
target_blob.extend(struct.pack("<I", 1)) # bTargetNamed (4 bytes, bool)
# Target Name (255 bytes)
# --- CHANGE: Updated target name for Flipper Zero ---
target_name = b"Flipper Zero F7"
target_blob.extend(target_name)
target_blob.extend(b"\x00" * (255 - len(target_name)))
# Target Size and Elements Count
target_size = len(elements_blob)
target_blob.extend(struct.pack("<II", target_size, num_elements))
# Append the actual element data
target_blob.extend(elements_blob)
return bytes(target_blob)
def _build_dfuse_prefix(self, image_data: bytes, num_targets: int) -> bytes:
"""Build the main DfuSe prefix header."""
prefix = bytearray()
prefix.extend(b"DfuSe") # szSignature (5 bytes)
prefix.extend(b"\x01") # bVersion (1 byte)
# Total image size (prefix + all targets) and number of targets
image_size = len(image_data) + DFU_PREFIX_LENGTH
prefix.extend(struct.pack("<IB", image_size, num_targets))
return bytes(prefix)
def _build_suffix(self) -> bytes:
"""Build the DFU suffix (without CRC)."""
suffix = bytearray()
suffix.extend(struct.pack("<H", self.version)) # bcdDevice
suffix.extend(struct.pack("<H", self.pid)) # idProduct
suffix.extend(struct.pack("<H", self.vid)) # idVendor
suffix.extend(struct.pack("<H", self.bcdDFU)) # bcdDFU
suffix.extend(b"UFD") # ucDfuSignature
suffix.extend(struct.pack("B", DFU_SUFFIX_LENGTH)) # bLength
return bytes(suffix)
def pack(self, binaries: List[Tuple[int, bytes]]) -> bytes:
"""
Construct the full DFU file from binary data.
For Flipper, we usually have one target with one element.
"""
print(f"\n{Colors.YELLOW}[*] BUILDING DFU IMAGE...{Colors.END}")
# 1. Build the elements block
loading_animation("Constructing firmware elements", 1.0)
elements_blob = self._build_elements(binaries)
# 2. Build the target block (containing elements)
loading_animation("Constructing DFU target descriptor", 1.0)
# Assuming one target for all elements, which is standard for Flipper
target_blob = self._build_target(elements_blob, len(binaries))
# 3. Build the DfuSe prefix
loading_animation("Generating DfuSe file header", 1.0)
dfuse_prefix = self._build_dfuse_prefix(target_blob, 1)
# 4. Build the DFU suffix (without CRC)
dfu_suffix_no_crc = self._build_suffix()
# 5. Assemble the file for CRC calculation
data_for_crc = dfuse_prefix + target_blob + dfu_suffix_no_crc
# 6. Calculate and append CRC32
loading_animation("Calculating CRC32 checksum", 1.0)
# --- FIX: Apply final XOR to match DfuSe specification ---
crc = zlib.crc32(data_for_crc) ^ 0xFFFFFFFF
final_dfu = data_for_crc + struct.pack("<I", crc)
return final_dfu
def parse_binary_arg(value: str) -> Tuple[int, str]:
"""Parse 'address:filepath' argument."""
try:
addr_str, path = value.split(":", 1)
return int(addr_str, 16), path
except ValueError:
raise argparse.ArgumentTypeError(
f"Invalid format: '{value}'. Expected '0xAddress:filepath'"
)
def main():
print_banner()
parser = argparse.ArgumentParser(
description="Repack raw binary files into a Flipper Zero DFU file."
)
parser.add_argument(
"binaries",
nargs="+",
type=parse_binary_arg,
help="Binary file(s) to pack in '0xADDRESS:FILEPATH' format. Example: '0x08000000:firmware.bin'",
)
parser.add_argument(
"-o", "--output", required=True, help="Output DFU file path"
)
parser.add_argument(
"--vid", type=lambda x: int(x, 16), default=0x0483, help="USB Vendor ID (hex, default: 0483)"
)
parser.add_argument(
"--pid", type=lambda x: int(x, 16), default=0xDF11, help="USB Product ID (hex, default: DF11)"
)
parser.add_argument(
"--ver", type=lambda x: int(x, 16), default=0xFFFF, help="Device Version/bcdDevice (hex, default: 0200)"
)
args = parser.parse_args()
print(f"{Colors.YELLOW}[*] INITIALIZING DFU REPACKER...{Colors.END}")
loading_animation("Loading DFU packer modules", 1.0)
try:
# Read all binary files into memory
binary_data = []
for addr, path in args.binaries:
print(f"{Colors.YELLOW}[*] READING SOURCE FILE: {Colors.BOLD}{path}{Colors.END}")
with open(path, "rb") as f:
data = f.read()
binary_data.append((addr, data))
print(f"{Colors.GREEN}[+] Loaded {len(data):,} bytes from address 0x{addr:08X}{Colors.END}")
# Create and run the packer
packer = DFUPacker(vid=args.vid, pid=args.pid, version=args.ver)
dfu_file_data = packer.pack(binary_data)
# Write the final DFU file
print(f"\n{Colors.YELLOW}[*] WRITING FINAL DFU FILE...{Colors.END}")
with open(args.output, "wb") as f:
f.write(dfu_file_data)
loading_animation("Finalizing DFU container", 1.0)
print(f"{Colors.GREEN}[+] DFU FILE CREATED SUCCESSFULLY!{Colors.END}")
print(f"{Colors.CYAN} → Filename: {Colors.BOLD}{args.output}{Colors.END}")
print(f"{Colors.CYAN} → Size: {Colors.BOLD}{len(dfu_file_data):,} bytes{Colors.END}")
print(f"{Colors.CYAN} → Targets: {Colors.BOLD}1{Colors.END}")
print(f"{Colors.CYAN} → Binaries Packed: {Colors.BOLD}{len(args.binaries)}{Colors.END}")
print(f"\n{Colors.PURPLE}[✓] FIRMWARE REPACKING COMPLETE - READY TO FLASH!{Colors.END}")
except IOError as e:
print(
f"{Colors.RED}[!] ERROR: Failed to read file - {e}{Colors.END}",
file=sys.stderr,
)
return 1
except Exception as e:
print(f"{Colors.RED}[!] CRITICAL ERROR: {e}{Colors.END}", file=sys.stderr)
print(
f"{Colors.RED}[!] DFU packing failed. Check file paths and addresses.{Colors.END}"
)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment