Skip to content

Instantly share code, notes, and snippets.

@rvtr
Last active August 21, 2023 08:28
Show Gist options
  • Save rvtr/3793a40dd0c7930f12b79ca1931a3296 to your computer and use it in GitHub Desktop.
Save rvtr/3793a40dd0c7930f12b79ca1931a3296 to your computer and use it in GitHub Desktop.
Extract firmware from gigaleak 3DS SystemUpdaters
# extract-updater-rofs
# Extracts firmware CIAs from ROFS containers for some gigaleak SystemUpdaters. Will accept "Contents.cnt" and decrypted "romfs.bin"
# Usage: ./extract-updater-romfs <input file> (output directory)
###################################################################################
# Set input and output paths
###################################################################################
OUTPUT_DIR="extract-updater-romfs_output"
rm -r "extract-updater-romfs_output"
mkdir "extract-updater-romfs_output"
ERROR="0"
if [ "${1}" != "" ]; then
INPUT_FILE="${1}"
if [ "${2}" != "" ]; then
OUTPUT_DIR="${2}"
rm -r "${OUTPUT_DIR}"
rm -r "extract-updater-romfs_output"
mkdir "${OUTPUT_DIR}"
fi
else
echo "ERROR: No input files specified."
echo ""
echo "./extract-updater-rofs.sh <input> (output dir)"
echo ""
echo "Note that if the output directory already exists, IT WILL BE DELETED"
ERROR=1
fi
###################################################################################
# CIA extraction
###################################################################################
if [ "$ERROR" == "0" ]; then
echo "Finding CIA headers in file..."
od -t x -A d "${INPUT_FILE}" | grep "00002020 00000000 00000a00 00000350" | sed 's/ .*//' | sed 's/^0*//' > romfs-dir.txt
# Get start address of every CIA header and store to file
echo "Found all headers!"
echo "================================================="
declare -i x=0
declare -i i=1
declare -i z=1
echo "Extracting odd CIAs..."
echo "================================================="
sed 1d romfs-dir.txt | while IFS=, read -r START_HEADER; read NEXT_HEADER
do
echo " Check CIA length"
echo "CIA $i header at ${START_HEADER}"
echo "Next header at ${NEXT_HEADER}"
echo "Finding CIA $i end from CIA $((i + 1)) header... "
y="00"
x=0
while [ "$y" = "00" ]; do
x+=1
y=$(od -j $((NEXT_HEADER - x)) -N 1 -x -A n "${INPUT_FILE}" | sed 's|[ ,]||g' | sed 's/^..//');
# Get bytes one backwards from next header
# printf '%x\n' $((NEXT_HEADER - x))
# echo $y
# echo $x
done
echo "End found!"
echo "Non-zerobyte ($y) at $((NEXT_HEADER - x))"
echo "Padding from CIA $i to $((i + 1)) is $((x - 1)) bytes."
echo " Extract CIA"
CIA_LENGTH=$(((NEXT_HEADER - START_HEADER) - x + 1))
dd skip=${START_HEADER} count=${CIA_LENGTH} if="${INPUT_FILE}" of="${OUTPUT_DIR}/$i.cia" bs=1
echo "CIA $i saved as '$i.cia'"
echo "================================================="
i+=2
done < romfs-dir.txt
i=2
echo "Extracting even CIAs..."
echo "================================================="
sed 1d romfs-dir.txt | while IFS=, read -r START_HEADER; read NEXT_HEADER
do
echo " Check CIA length"
echo "CIA $i header at ${START_HEADER}"
echo "Next header at ${NEXT_HEADER}"
echo "Finding CIA $i end from CIA $((i + 1)) header... "
y="00"
x=0
while [ "$y" = "00" ]; do
x+=1
y=$(od -j $((NEXT_HEADER - x)) -N 1 -x -A n "${INPUT_FILE}" | sed 's|[ ,]||g' | sed 's/^..//');
# Get bytes one backwards from next header
# printf '%x\n' $((NEXT_HEADER - x))
# echo $y
# echo $x
done
echo "End found!"
echo "Non-zerobyte ($y) at $((NEXT_HEADER - x))"
echo "Padding from CIA $i to $((i + 1)) is $((x - 1)) bytes."
echo " Extract CIA"
CIA_LENGTH=$(((NEXT_HEADER - START_HEADER) - x + 1))
dd skip=${START_HEADER} count=${CIA_LENGTH} if="${INPUT_FILE}" of="${OUTPUT_DIR}/$i.cia" bs=1
echo "CIA $i output as '$i.cia', ${CIA_LENGTH} bytes."
CIA_LENGTH=""
echo "================================================="
i+=2
done
START_HEADER=$( tail -n 1 romfs-dir.txt )
NEXT_HEADER=$((16#$(xxd "${INPUT_FILE}" | grep "226e 6f6e 6522" | sed 's/: .*//' | sed 's/^0*//')));
# Find "226e 6f6e 6522" as it is the last predictable data to mark the end of Contents.cnt and the last CIA.
# Upsettingly I have to use xxd because od wouldn't turn up any results for this... I liked od's formatting more :despair:
if [ $NEXT_HEADER != "" ]; then
echo " Check CIA length"
echo "Final CIA header at ${START_HEADER}"
echo "Contents.cnt end at ${NEXT_HEADER}"
echo "Finding end of final CIA from Contents.cnt end..."
y="00"
x=16
# Start x as 16 to skip changing data and go right to padding
while [ "$y" = "00" ]; do
x+=1
y=$(od -j $((NEXT_HEADER - x)) -N 1 -x -A n "${INPUT_FILE}" | sed 's|[ ,]||g' | sed 's/^..//');
# Get bytes one backwards from next header
# printf '%x\n' $((NEXT_HEADER - x))
# echo $y
# echo $x
done
echo "End found!"
echo "Non-zerobyte ($y) at $((NEXT_HEADER - x))"
echo "Padding from final CIA to Content.cnt end is $((x - 1)) bytes."
echo " Extract CIA"
CIA_LENGTH=$(((NEXT_HEADER - START_HEADER) - x + 1))
dd skip=${START_HEADER} count=${CIA_LENGTH} if="${INPUT_FILE}" of="${OUTPUT_DIR}/0.cia" bs=1
echo "Final CIA output as '0.cia', ${CIA_LENGTH} bytes."
echo "================================================="
echo "All CIAs extracted from RomFS!"
else
echo "Could not find end of last CIA! For extracting manually, start address is ${START_HEADER} (decimal)"
fi
fi
@rvtr
Copy link
Author

rvtr commented Aug 21, 2023

L34:
There's a leftover Contents.cnt in the gigaleaks at:
ctr.7z/ctr/sources/firmware/CTR-Kernel/updater1st/UpdaterCardImage/Default/Contents_11_without_shareddate.cnt

This has CIAs with a different header, so you'll need to change grep to find the following:
"00002020 00000000 00000000 00000378"

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