What changed, and why it matters
This commit adds two helper scripts for developers to back up and restore BitBox02 flash memory areas using a Segger J-Link debugger. The scripts require physical hardware access and a debugging probe, and they are not part of the firmware that runs on the device. They do not change any device code or introduce a remote attack path.
No security action required. Treat this as a normal developer-tooling addition. If the project documents these scripts, it should remind users that backups contain sensitive key material and should be stored securely.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces scripts/bitbox02-flash-data-backup and scripts/bitbox02-flash-data-restore, plus a .gitignore entry. They use JLinkExe to read/write shared-data, appdata, bootdata, and the MCU serial number on an ATSAMD51J20 via SWD. The backup script sets umask 077, warns that the backup contains secrets, and records SHA256 checksums and metadata. The restore script verifies file sizes, optionally checks SHA256SUMS, compares the MCU serial number, and allows override with -f. No firmware source code is modified.
Changed components
scripts/bitbox02-flash-data-backupscripts/bitbox02-flash-data-restore.gitignoreInspect captured patch +383 / −0
diff --git a/.gitignore b/.gitignore
index 929c6fb..9f36c3e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
/build/
/build-*/
+/bitbox02-flash-data-backup-*/
/releases/temp/
.vagrant/
# LSP caches and local configs
diff --git a/scripts/bitbox02-flash-data-backup b/scripts/bitbox02-flash-data-backup
new file mode 100755
index 0000000..41d28d4
--- /dev/null
+++ b/scripts/bitbox02-flash-data-backup
@@ -0,0 +1,184 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0
+
+set -euo pipefail
+
+readonly DEVICE="ATSAMD51J20"
+readonly INTERFACE="SWD"
+readonly SPEED="4000"
+readonly SHARED_DATA_SIZE_DEC="8192"
+readonly SHARED_DATA_SIZE_HEX="0x2000"
+readonly APPDATA_SIZE_DEC="65536"
+readonly APPDATA_SIZE_HEX="0x10000"
+readonly BOOTDATA_SIZE_DEC="8192"
+readonly BOOTDATA_SIZE_HEX="0x2000"
+readonly SHARED_DATA_ADDR="0x0000e000"
+readonly APPDATA_ADDR="0x000e8000"
+readonly BOOTDATA_ADDR="0x000f8000"
+readonly MCU_SERIAL_WORD0_ADDR="0x008061fc"
+readonly MCU_SERIAL_WORD1_ADDR="0x00806010"
+readonly MCU_SERIAL_WORD2_ADDR="0x00806014"
+readonly MCU_SERIAL_WORD3_ADDR="0x00806018"
+
+usage() {
+ cat <<EOF
+Usage: $0 [backup-dir]
+
+Backs up the BitBox02 flash data areas through J-Link.
+If no backup directory is given, one is created in the current directory.
+The default directory name contains the connected MCU serial number.
+
+The backup contains secret key material and user data. Keep it private.
+EOF
+}
+
+file_size() {
+ if stat -c '%s' "$1" >/dev/null 2>&1; then
+ stat -c '%s' "$1"
+ else
+ stat -f '%z' "$1"
+ fi
+}
+
+sha256_files() {
+ if command -v sha256sum >/dev/null 2>&1; then
+ sha256sum "$@"
+ else
+ shasum -a 256 "$@"
+ fi
+}
+
+if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
+ usage
+ exit 0
+fi
+
+if [[ $# -gt 1 ]]; then
+ usage >&2
+ exit 1
+fi
+
+if ! command -v JLinkExe >/dev/null 2>&1; then
+ echo "error: JLinkExe not found in PATH" >&2
+ exit 1
+fi
+
+if ! command -v sha256sum >/dev/null 2>&1 && ! command -v shasum >/dev/null 2>&1; then
+ echo "error: neither sha256sum nor shasum found in PATH" >&2
+ exit 1
+fi
+
+umask 077
+
+requested_backup_dir="${1:-}"
+created_temp_backup_dir=0
+backup_dir_finalized=0
+
+if [[ -n "$requested_backup_dir" ]]; then
+ backup_dir="$requested_backup_dir"
+ mkdir -p "$backup_dir"
+
+ if [[ -n "$(find "$backup_dir" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then
+ echo "error: backup directory is not empty: $backup_dir" >&2
+ exit 1
+ fi
+else
+ backup_dir="$(mktemp -d "$PWD/.bitbox02-flash-data-backup.XXXXXX")"
+ created_temp_backup_dir=1
+fi
+
+backup_dir="$(cd "$backup_dir" && pwd)"
+cmd_file="$(mktemp "$backup_dir/.jlink-backup.XXXXXX")"
+
+cleanup() {
+ rm -f "$cmd_file" "$backup_dir"/.mcu-serial-word*.bin
+ if [[ "$created_temp_backup_dir" == "1" && "$backup_dir_finalized" != "1" ]]; then
+ rm -rf "$backup_dir"
+ fi
+}
+trap cleanup EXIT
+
+cat >"$cmd_file" <<EOF
+h
+savebin shared-data.bin, $SHARED_DATA_ADDR, $SHARED_DATA_SIZE_HEX
+savebin appdata.bin, $APPDATA_ADDR, $APPDATA_SIZE_HEX
+savebin bootdata.bin, $BOOTDATA_ADDR, $BOOTDATA_SIZE_HEX
+savebin .mcu-serial-word0.bin, $MCU_SERIAL_WORD0_ADDR, 0x4
+savebin .mcu-serial-word1.bin, $MCU_SERIAL_WORD1_ADDR, 0x4
+savebin .mcu-serial-word2.bin, $MCU_SERIAL_WORD2_ADDR, 0x4
+savebin .mcu-serial-word3.bin, $MCU_SERIAL_WORD3_ADDR, 0x4
+q
+EOF
+
+(
+ cd "$backup_dir"
+ JLinkExe \
+ -NoGui 1 \
+ -if "$INTERFACE" \
+ -device "$DEVICE" \
+ -speed "$SPEED" \
+ -autoconnect 1 \
+ -CommanderScript "$cmd_file"
+)
+
+size="$(file_size "$backup_dir/shared-data.bin")"
+if [[ "$size" != "$SHARED_DATA_SIZE_DEC" ]]; then
+ echo "error: unexpected size for shared-data.bin: $size bytes" >&2
+ exit 1
+fi
+
+size="$(file_size "$backup_dir/appdata.bin")"
+if [[ "$size" != "$APPDATA_SIZE_DEC" ]]; then
+ echo "error: unexpected size for appdata.bin: $size bytes" >&2
+ exit 1
+fi
+
+size="$(file_size "$backup_dir/bootdata.bin")"
+if [[ "$size" != "$BOOTDATA_SIZE_DEC" ]]; then
+ echo "error: unexpected size for bootdata.bin: $size bytes" >&2
+ exit 1
+fi
+
+for i in 0 1 2 3; do
+ file="$backup_dir/.mcu-serial-word${i}.bin"
+ size="$(file_size "$file")"
+ if [[ "$size" != "4" ]]; then
+ echo "error: unexpected size for .mcu-serial-word${i}.bin: $size bytes" >&2
+ exit 1
+ fi
+done
+cat \
+ "$backup_dir/.mcu-serial-word0.bin" \
+ "$backup_dir/.mcu-serial-word1.bin" \
+ "$backup_dir/.mcu-serial-word2.bin" \
+ "$backup_dir/.mcu-serial-word3.bin" \
+ >"$backup_dir/mcu-serial-number.bin"
+
+(
+ cd "$backup_dir"
+ mcu_serial_number_hex="$(od -An -tx1 -v mcu-serial-number.bin | tr -d ' \n')"
+ sha256_files shared-data.bin appdata.bin bootdata.bin mcu-serial-number.bin >SHA256SUMS
+ cat >metadata.txt <<EOF
+format=bitbox02-flash-data-v1
+created_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ)
+mcu_serial_number_le_hex=$mcu_serial_number_hex
+EOF
+)
+
+mcu_serial_number_hex="$(od -An -tx1 -v "$backup_dir/mcu-serial-number.bin" | tr -d ' \n')"
+
+rm -f "$cmd_file" "$backup_dir"/.mcu-serial-word*.bin
+
+if [[ -z "$requested_backup_dir" ]]; then
+ final_backup_dir="$PWD/bitbox02-flash-data-backup-mcu-sn-${mcu_serial_number_hex}-$(date -u +%Y%m%dT%H%M%SZ)"
+ if [[ -e "$final_backup_dir" ]]; then
+ echo "error: backup directory already exists: $final_backup_dir" >&2
+ exit 1
+ fi
+ mv "$backup_dir" "$final_backup_dir"
+ backup_dir="$final_backup_dir"
+ backup_dir_finalized=1
+fi
+
+echo "Backed up BitBox02 flash data areas to: $backup_dir"
+echo "Treat shared-data.bin, appdata.bin, and bootdata.bin as private data."
diff --git a/scripts/bitbox02-flash-data-restore b/scripts/bitbox02-flash-data-restore
new file mode 100755
index 0000000..330aa3e
--- /dev/null
+++ b/scripts/bitbox02-flash-data-restore
@@ -0,0 +1,198 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0
+
+set -euo pipefail
+
+readonly DEVICE="ATSAMD51J20"
+readonly INTERFACE="SWD"
+readonly SPEED="4000"
+readonly SHARED_DATA_SIZE_DEC="8192"
+readonly APPDATA_SIZE_DEC="65536"
+readonly BOOTDATA_SIZE_DEC="8192"
+readonly SHARED_DATA_ADDR="0x0000e000"
+readonly APPDATA_ADDR="0x000e8000"
+readonly BOOTDATA_ADDR="0x000f8000"
+readonly MCU_SERIAL_WORD0_ADDR="0x008061fc"
+readonly MCU_SERIAL_WORD1_ADDR="0x00806010"
+readonly MCU_SERIAL_WORD2_ADDR="0x00806014"
+readonly MCU_SERIAL_WORD3_ADDR="0x00806018"
+
+usage() {
+ cat <<EOF
+Usage: $0 [-f] <backup-dir>
+
+Restores a backup created by scripts/bitbox02-flash-data-backup through J-Link.
+This overwrites the BitBox02 shared-data, appdata, and bootdata flash areas.
+
+By default, the connected MCU serial number must match the backup. Use -f/--force to override.
+EOF
+}
+
+file_size() {
+ if stat -c '%s' "$1" >/dev/null 2>&1; then
+ stat -c '%s' "$1"
+ else
+ stat -f '%z' "$1"
+ fi
+}
+
+sha256_check() {
+ if command -v sha256sum >/dev/null 2>&1; then
+ sha256sum --check --status SHA256SUMS
+ else
+ shasum -a 256 -c SHA256SUMS >/dev/null
+ fi
+}
+
+force=0
+cmd_file=""
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -h | --help)
+ usage
+ exit 0
+ ;;
+ -f | --force)
+ force=1
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage >&2
+ exit 1
+ ;;
+ *)
+ break
+ ;;
+ esac
+done
+
+if [[ $# -ne 1 ]]; then
+ usage >&2
+ exit 1
+fi
+
+if ! command -v JLinkExe >/dev/null 2>&1; then
+ echo "error: JLinkExe not found in PATH" >&2
+ exit 1
+fi
+
+if ! command -v sha256sum >/dev/null 2>&1 && ! command -v shasum >/dev/null 2>&1; then
+ echo "error: neither sha256sum nor shasum found in PATH" >&2
+ exit 1
+fi
+
+backup_dir="$1"
+if [[ ! -d "$backup_dir" ]]; then
+ echo "error: backup directory does not exist: $backup_dir" >&2
+ exit 1
+fi
+
+backup_dir="$(cd "$backup_dir" && pwd)"
+
+check_backup_file() {
+ local file="$1"
+ local expected_size="$2"
+ if [[ ! -f "$backup_dir/$file" ]]; then
+ echo "error: missing $file in $backup_dir" >&2
+ exit 1
+ fi
+ size="$(file_size "$backup_dir/$file")"
+ if [[ "$size" != "$expected_size" ]]; then
+ echo "error: unexpected size for $file: $size bytes" >&2
+ exit 1
+ fi
+}
+
+check_backup_file shared-data.bin "$SHARED_DATA_SIZE_DEC"
+check_backup_file appdata.bin "$APPDATA_SIZE_DEC"
+check_backup_file bootdata.bin "$BOOTDATA_SIZE_DEC"
+
+check_mcu_serial_number() {
+ local current_serial="$backup_dir/.current-mcu-serial-number.bin"
+ local read_id_cmd_file
+ read_id_cmd_file="$(mktemp "$backup_dir/.jlink-read-id.XXXXXX")"
+
+ cat >"$read_id_cmd_file" <<EOF
+h
+savebin .current-mcu-serial-word0.bin, $MCU_SERIAL_WORD0_ADDR, 0x4
+savebin .current-mcu-serial-word1.bin, $MCU_SERIAL_WORD1_ADDR, 0x4
+savebin .current-mcu-serial-word2.bin, $MCU_SERIAL_WORD2_ADDR, 0x4
+savebin .current-mcu-serial-word3.bin, $MCU_SERIAL_WORD3_ADDR, 0x4
+q
+EOF
+
+ (
+ cd "$backup_dir"
+ JLinkExe \
+ -NoGui 1 \
+ -if "$INTERFACE" \
+ -device "$DEVICE" \
+ -speed "$SPEED" \
+ -autoconnect 1 \
+ -CommanderScript "$read_id_cmd_file"
+ cat \
+ .current-mcu-serial-word0.bin \
+ .current-mcu-serial-word1.bin \
+ .current-mcu-serial-word2.bin \
+ .current-mcu-serial-word3.bin \
+ >"$current_serial"
+ )
+
+ rm -f \
+ "$read_id_cmd_file" \
+ "$backup_dir"/.current-mcu-serial-word*.bin
+
+ if [[ ! -f "$backup_dir/mcu-serial-number.bin" ]]; then
+ echo "error: backup has no MCU serial number file" >&2
+ return 1
+ fi
+
+ if ! cmp --silent "$backup_dir/mcu-serial-number.bin" "$current_serial"; then
+ if [[ "$force" == "1" ]]; then
+ echo "warning: MCU serial number does not match backup; restoring because -f was used" >&2
+ return 0
+ fi
+ echo "error: MCU serial number does not match backup; use -f to restore anyway" >&2
+ return 1
+ fi
+}
+
+trap 'rm -f "$backup_dir"/.current-mcu-*.bin "$backup_dir"/.jlink-read-id.* "$cmd_file"' EXIT
+
+if [[ -f "$backup_dir/SHA256SUMS" ]]; then
+ (
+ cd "$backup_dir"
+ sha256_check
+ )
+fi
+
+check_mcu_serial_number
+
+cmd_file="$(mktemp "$backup_dir/.jlink-restore.XXXXXX")"
+
+cat >"$cmd_file" <<EOF
+h
+exec SetFlashDLNoRMWThreshold = 0xFFFFFFFF
+loadfile shared-data.bin $SHARED_DATA_ADDR noreset
+loadfile appdata.bin $APPDATA_ADDR noreset
+loadfile bootdata.bin $BOOTDATA_ADDR noreset
+r
+q
+EOF
+
+(
+ cd "$backup_dir"
+ JLinkExe \
+ -NoGui 1 \
+ -if "$INTERFACE" \
+ -device "$DEVICE" \
+ -speed "$SPEED" \
+ -autoconnect 1 \
+ -CommanderScript "$cmd_file"
+)
+
+echo "Restored BitBox02 flash data areas from: $backup_dir"
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.