Last active
August 2, 2024 14:55
-
-
Save TheFreeman193/46cf02661208efa81adf4ad3da92632b to your computer and use it in GitHub Desktop.
Detect offset of kernel release string
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/system/bin/sh | |
# Copyright (C) MIT License 2024 Nicholas Bissell (TheFreeman193) | |
NL=" | |
" | |
SYSNMLN=65 | |
showHeader() { | |
echo " | |
========= Kernel release offset finder ========= | |
Buy me a coffee: https://ko-fi.com/nickbissell | |
===================== v2.0 ===================== | |
" >&2 | |
} | |
showUsage() { | |
showHeader 2>&1 | |
echo "Usage: | |
$0 --auto [--hex] [--quiet] [--patch new_value [--inplace]] | |
$0 [--kernel] kernel_file [--hex] [--quiet] [--patch new_value [--inplace]] | |
$0 --image boot_img [--hex] [--quiet] [--patch new_value [--inplace]] | |
$0 --boot boot_part [--hex] [--quiet] [--patch new_value [--inplace]] | |
--auto -a Detect boot partition and extract automatically | |
--kernel -k Get offset from an uncompressed kernel file (e.g. kernel) | |
--image -i Get offset from a boot image file (e.g. boot.img) | |
--boot -b Get offset from a boot partition (e.g. /dev/block/...) | |
--hex -x Return offset in hexadecimal format | |
--quiet -q Quiet operation - don't print operations to stderr | |
--patch -p Patch the partition/image/kernel with new_value | |
--inplace -n For --kernel/--image: Patch the original file instead of copying | |
kernel_file An uncompressed Linux kernel extracted with magiskboot | |
boot_img A boot image file containing the Linux kernel | |
boot_part A path to the boot partition of the device | |
new_value A new value for the non-numeric part of the release string | |
" | |
exit 0 | |
} | |
[ $# -eq 0 ] || [ -z "$1" ] && showUsage | |
if [ ! -f /data/adb/magisk/busybox ] || [ ! -f /data/adb/magisk/magiskboot ]; then | |
echo "ERROR: Critical Magisk components not found. Make sure magisk is installed and this is a root shell." >&2 | |
exit 2 | |
fi | |
for cmd in dd xxd strings stat sed grep; do | |
alias $cmd="/data/adb/magisk/busybox $cmd" | |
done | |
readUtsFieldHex() { | |
[ -z "$SYSNMLN" ] && SYSNMLN=65 | |
dd if="$1" skip="$2" count="$SYSNMLN" iflag="skip_bytes,count_bytes" status=none | xxd -p -c "$SYSNMLN" | |
} | |
toUtsFieldHex() { | |
[ -z "$SYSNMLN" ] && SYSNMLN=65 | |
hexSYSNMLN="$((SYSNMLN * 2))" | |
hexPad="$(dd if=/dev/zero iflag=count_bytes count="$SYSNMLN" status=none | xxd -p -c "$SYSNMLN")" | |
echo "$(printf %s "$1" | xxd -p -c "$SYSNMLN")$hexPad" | cut -b "-$hexSYSNMLN" | |
} | |
findStringsInBin() { | |
strings -t d "$1" | grep -F "$2" | sed -r 's/^[[:space:]]*([0-9]+)[[:space:]]+.+$/\1/g' | grep -E "^[0-9]+$" | |
} | |
findUtsFieldOffset() { | |
file="$1" | |
value="$2" | |
[ -z "$SYSNMLN" ] && SYSNMLN=65 | |
[ $quietMode -eq 0 ] && echo "${NL}Looking for instances of '$value' in '$file'..." >&2 | |
curSys="$(uname -s | cut -b "-$SYSNMLN")" | |
curMachine="$(uname -m | cut -b "-$SYSNMLN")" | |
hexValue="$(toUtsFieldHex "$value")" | |
hexCurSys="$(toUtsFieldHex "$curSys")" | |
hexCurMachine="$(toUtsFieldHex "$curMachine")" | |
potentialOffsets="$(findStringsInBin "$file" "$value")" | |
validOffsets="" | |
[ $asHex -eq 0 ] && offsetFormat="%s%s... " || offsetFormat="%s0x%x... " | |
for offset in $potentialOffsets; do | |
[ $quietMode -eq 0 ] && printf "$offsetFormat" " Checking match at offset " "$offset" >&2 | |
if [ "$(readUtsFieldHex "$file" "$offset")" == "$hexValue" ]; then | |
found="" | |
sysOffset="$((offset - SYSNMLN * 5))" | |
endOffset="$((offset + SYSNMLN * 5))" | |
while [ "$sysOffset" -lt "$endOffset" ]; do | |
curHex="$(readUtsFieldHex "$file" "$sysOffset")" | |
if [ "$curHex" == "$hexCurSys" ]; then | |
found="sysname" | |
break 1 | |
elif [ "$curHex" == "$hexCurMachine" ]; then | |
found="machine" | |
break 1 | |
fi | |
sysOffset="$((sysOffset + SYSNMLN))" | |
done | |
if [ -n "$found" ]; then | |
validOffsets+="$offset$NL" | |
[ $quietMode -eq 0 ] && echo "Found UTS $found field!" >&2 | |
else | |
[ $quietMode -eq 0 ] && echo "Not a complete UTS structure" >&2 | |
fi | |
else | |
[ $quietMode -eq 0 ] && echo "Partial match/not UTS" >&2 | |
fi | |
done | |
if [ -n "$validOffsets" ]; then | |
echo "$validOffsets" | sed '/^$/d' | |
else | |
echo "${NL}No offsets with UTS format found for '$curRelease'" >&2 | |
return 1 | |
fi | |
} | |
findBootPath() { | |
[ $quietMode -eq 0 ] && echo "${NL}Detecting boot partition path..." >&2 | |
bootPath="/dev/block/bootdevice/by-name" | |
if [ ! -d "$bootPath/" ]; then | |
echo "ERROR: Boot partition directory not found." >&2 | |
return 1 | |
fi | |
if [ -e "$bootPath/boot" ]; then | |
echo "$bootPath/boot" | |
elif [ -e "$bootPath/boot_a" ] && [ -e "$bootPath/boot_b" ]; then | |
slot_suffix="$(getprop ro.boot.slot_suffix)" | |
if [ -n "$slot_suffix" ]; then | |
echo "$bootPath/boot$slot_suffix" | |
return 0 | |
fi | |
current_slot="$(getprop current-slot)" | |
if [ -n "$current_slot" ]; then | |
echo "$bootPath/boot$current_slot" | |
return 0 | |
fi | |
command -v bootctl && slot_number="$(bootctl get-current-slot)" | |
[ -n "$slot_number" ] && slot_suffix="$(bootctl get-suffix "$slot_number")" | |
if [ -n "$slot_suffix" ]; then | |
echo "$bootPath/boot$slot_suffix" | |
return 0 | |
fi | |
slot_suffix="$(cat /proc/cmdline | sed 's/ /\n/g' | grep -Ei 'slot_suffix|current-slot' | sed -r 's/.+=//')" | |
if [ -n "$slot_suffix" ]; then | |
echo "$bootPath/boot$slot_suffix" | |
return 0 | |
fi | |
echo "ERROR: Cannot get active slot." >&2 | |
return 1 | |
else | |
echo "ERROR: Unexpected boot partition configuration." >&2 | |
return 1 | |
fi | |
} | |
getOutFile() { | |
fileOut="$1.new" | |
ctr=1 | |
while [ -f "$fileOut" ]; do | |
ctr=$((ctr + 1)) | |
fileOut="$1.new$ctr" | |
done | |
echo "$fileOut" | |
} | |
patchKernel() { | |
if [ $# -ne 3 ]; then | |
echo "ERROR patchKernel: Invalid number of parameters!" >&2 | |
return 21 | |
fi | |
if [ ! -f "$1" ]; then | |
echo "ERROR patchKernel: Kernel file doesn't exist!" >&2 | |
return 22 | |
fi | |
if [ -z "$2" ]; then | |
echo "ERROR patchKernel: Replacement string is empty!" >&2 | |
return 23 | |
fi | |
if [ -z "$3" ]; then | |
echo "ERROR patchKernel: No replacement offsets provided!" >&2 | |
return 24 | |
fi | |
kSize="$(stat -c "%s" $1)" | |
maxOffset=$((kSize - SYSNMLN)) | |
maxLen=$((SYSNMLN - 1)) | |
[ $quietMode -eq 0 ] && echo "${NL}Patching kernel file '$1'..." >&2 | |
for offset in $3; do | |
if [ "$offset" -lt 1 ] || [ "$offset" -gt $maxOffset ]; then | |
echo "WARNING patchKernel: Offset $offset not between 0 and $maxOffset. Ignoring." >&2 | |
continue | |
fi | |
if [ $quietMode -eq 0 ]; then | |
[ $asHex -eq 1 ] && prettyOffset="$(printf "0x%x" "$offset")" || prettyOffset="$offset" | |
printf %s " Offset $prettyOffset: " >&2 | |
fi | |
curVal="$(dd if="$1" iflag="count_bytes,skip_bytes" skip="$offset" count="$SYSNMLN" status=none)" | |
versionPart="$(echo "$curVal" | sed -r 's/^([0-9.]+).*/\1/g')" | |
replRaw="$versionPart$2" | |
replSafe="${replRaw:0:$maxLen}" | |
dd if="/dev/zero" conv=notrunc iflag=count_bytes count="$SYSNMLN" oflag=seek_bytes seek="$offset" of="$1" status=none 2>/dev/null | |
printf %s "$replSafe" | dd conv=notrunc oflag="seek_bytes" seek="$offset" of="$1" status=none 2>/dev/null | |
readBack="$(dd if="$1" iflag="count_bytes,skip_bytes" skip="$offset" count="$SYSNMLN" status=none)" | |
if [ "$readBack" == "$replSafe" ]; then | |
[ $quietMode -eq 0 ] && echo "'$curVal' -> '$replSafe'" >&2 | |
else | |
echo "ERROR patchKernel: Read back '$readBack' doesn't match target value '$replSafe'!" >&2 | |
return 25 | |
fi | |
[ "${#replRaw}" -gt $maxLen ] && echo "WARNING patchKernel: Replacement string is longer than field length ($maxLen) and has been truncated." >&2 | |
done | |
return 0 | |
} | |
newStage() { | |
lastDir="$PWD" | |
stageDir="/data/local/tmp/kr_offset_$RANDOM" | |
mkdir "$stageDir" | |
if [ ! -d "$stageDir" ]; then | |
echo "ERROR: Couldn't create temporary directory '$stageDir'" >&2 | |
return 1 | |
fi | |
cd "$stageDir" | |
} | |
cleanupQuit() { | |
if [ -n "$stageDir" ] && [ -d "$stageDir" ]; then | |
[ -n "$lastDir" ] && [ -d "$lastDir" ] && cd "$lastDir" | |
rm -rf "$stageDir" | |
fi | |
[ -n "$1" ] && exit $1 || exit 0 | |
} | |
kernelFile="" | |
imageFile="" | |
bootPath="" | |
newValue="" | |
autoExtract=0 | |
asHex=0 | |
quietMode=0 | |
inPlace=0 | |
while [ $# -gt 0 ]; do | |
param="$1" | |
hasShifted=0 | |
case "$param" in | |
-h|-\?|--help) | |
showUsage | |
;; | |
--kernel|-k|-[aqxn]*k) | |
kernelFile="$(readlink -f "$2")" | |
shift; shift; hasShifted=1 | |
;; | |
--image|-i|-[aqxn]*i) | |
imageFile="$(readlink -f "$2")" | |
shift; shift; hasShifted=1 | |
;; | |
--boot|-b|-[aqxn]*b) | |
bootPath="$2" | |
shift; shift; hasShifted=1 | |
;; | |
--patch|-p|-[aqxn]*p) | |
newValue="$2" | |
shift; shift; hasShifted=1 | |
;; | |
--auto|-a) | |
autoExtract=1 | |
shift; continue | |
;; | |
--quiet|-q) | |
quietMode=1 | |
shift; continue | |
;; | |
--hex|-x) | |
asHex=1 | |
shift; continue | |
;; | |
--inplace|-n) | |
inPlace=1 | |
shift; continue | |
;; | |
*) | |
if echo "$1" | grep -Eqv '^-'; then | |
kernelFile="$(readlink -f "$1")" | |
shift; continue | |
fi | |
;; | |
esac | |
echo "$param" | grep -Eq '^-[qxn]*a[qxn]*[kibp]$' && autoExtract=1 | |
echo "$param" | grep -Eq '^-[qan]*x[qan]*[kibp]$' && asHex=1 | |
echo "$param" | grep -Eq '^-[axn]*q[axn]*[kibp]$' && quietMode=1 | |
echo "$param" | grep -Eq '^-[qax]*n[qax]*[kibp]$' && inPlace=1 | |
[ $hasShifted -eq 1 ] && continue | |
echo "ERROR: Couldn't parse parameter '$1'." >&2 | |
exit 1 | |
done | |
[ $quietMode -eq 0 ] && showHeader | |
primArgs=0 | |
mode=0 | |
[ -n "$kernelFile" ] && mode=1 && primArgs=$((primArgs + 1)) | |
[ -n "$imageFile" ] && mode=2 && primArgs=$((primArgs + 1)) | |
[ -n "$bootPath" ] && mode=3 && primArgs=$((primArgs + 1)) | |
[ $autoExtract -eq 1 ] && mode=4 && primArgs=$((primArgs + 1)) | |
if [ $primArgs -gt 1 ]; then | |
echo "ERROR: Too many parameters ($primArgs) - only 1 of --kernel, --image, --boot, --auto allowed." >&2 | |
exit 3 | |
fi | |
if [ $mode -eq 0 ]; then | |
echo "ERROR: You must either pass a kernel file, boot image, partition path, or use --auto." >&2 | |
exit 4 | |
fi | |
if [ $mode -ge 4 ]; then | |
bootPath="$(findBootPath)" || cleanupQuit 5 | |
fi | |
if [ $mode -ge 2 ]; then | |
newStage | |
[ $? -ne 0 ] && cleanupQuit 6 | |
fi | |
if [ $mode -ge 3 ]; then | |
if [ ! -e "$bootPath" ]; then | |
echo "ERROR: Boot partition path '$bootPath' not found." >&2 | |
cleanupQuit 7 | |
fi | |
imageFile="boot.img" | |
[ $quietMode -eq 0 ] && echo "${NL}Extracting boot image '$bootPath'..." >&2 | |
dd if="$bootPath" of="$imageFile" status=none | |
if [ ! -f "$imageFile" ]; then | |
echo "ERROR: Failed to extract boot image." >&2 | |
cleanupQuit 8 | |
fi | |
fi | |
if [ $mode -ge 2 ]; then | |
kernelFile="kernel" | |
[ $quietMode -eq 0 ] && echo "${NL}Unpacking boot image '$imageFile'..." >&2 | |
/data/adb/magisk/magiskboot unpack "$imageFile" 2>/dev/null | |
if [ $? -ne 0 ] || [ ! -f "$kernelFile" ]; then | |
echo "ERROR: Failed to unpack boot image." >&2 | |
cleanupQuit 9 | |
fi | |
fi | |
if [ $mode -eq 1 ] && [ ! -f "$kernelFile" ]; then | |
echo "ERROR: Failed to unpack boot image." >&2 | |
cleanupQuit 10 | |
fi | |
curRelease="$(uname -r | cut -b "-$SYSNMLN")" | |
releaseOffsets="$(findUtsFieldOffset "$kernelFile" "$curRelease")" | |
[ $? -ne 0 ] || [ -z "$releaseOffsets" ] && cleanupQuit 0 | |
if [ -z "$newValue" ]; then | |
[ $quietMode -eq 0 ] && echo "" >&2 | |
if [ $asHex -eq 1 ]; then | |
for offset in $releaseOffsets; do | |
printf "0x%x\n" "$offset" | |
done | |
else | |
echo "$releaseOffsets" | |
fi | |
[ $quietMode -eq 0 ] && echo "" >&2 | |
cleanupQuit 0 | |
fi | |
if [ $mode -eq 1 ]; then | |
if [ $inPlace -eq 1 ]; then | |
kernelOut="$kernelFile" | |
else | |
kernelOut="$(getOutFile "$kernelFile")" | |
dd if="$kernelFile" of="$kernelOut" status=none | |
fi | |
patchKernel "$kernelOut" "$newValue" "$releaseOffsets" | |
res=$?; [ $res -ne 0 ] && cleanupQuit $res | |
echo "$kernelOut" | |
cleanupQuit 0 | |
fi | |
patchKernel "$kernelFile" "$newValue" "$releaseOffsets" | |
res=$?; [ $res -ne 0 ] && cleanupQuit $res | |
imageOut="$(getOutFile "$imageFile")" | |
[ $quietMode -eq 0 ] && echo "${NL}Packing new boot image '$imageOut'..." >&2 | |
/data/adb/magisk/magiskboot repack "$imageFile" "$imageOut" 2>/dev/null | |
if [ $? -ne 0 ] || [ ! -f "$imageOut" ]; then | |
cleanupQuit 11 | |
fi | |
if [ $mode -eq 2 ]; then | |
if [ $inPlace -eq 1 ]; then | |
[ $quietMode -eq 0 ] && echo "${NL}Overwriting existing image '$imageFile'..." >&2 | |
mv -f "$imageOut" "$imageFile" | |
echo "$imageFile" | |
else | |
echo "$imageOut" | |
fi | |
cleanupQuit 0 | |
fi | |
[ $quietMode -eq 0 ] && echo "${NL}Flashing new boot image to '$bootPath'..." >&2 | |
srcHash="$(sha1sum -b "$imageOut")" | |
dd if="$imageOut" of="$bootPath" status=none | |
[ $? -ne 0 ] && cleanupQuit 12 | |
destHash="$(sha1sum -b "$bootPath")" | |
if [ "$destHash" != "$srcHash" ]; then | |
echo "ERROR: SHA-1 hash of flashed image '$bootPath' doesn't match source '$imageOut'!" >&2 | |
cleanupQuit 13 | |
fi | |
[ $quietMode -eq 0 ] && echo "${NL}Finished. Patched '$bootPath'.$NL" >&2 | |
echo "$bootPath" | |
[ $quietMode -eq 0 ] && echo "" >&2 | |
cleanupQuit 0 |
For anyone reading this:
DO NOT RUN run it with bash kr_offset.sh
(yes, I'm dumb and it made me try to debug the script)
If ./kr_offset.sh
does not work, use sh kr_offset.sh
sh, NOT bash
Also: for kernels in the format of 4.14.190-lineageos-g27cc0d58ea9e
, remember that you're replacing everything after the kernel number, so in this case we're replacing -lineageos-g27cc0d58ea9e
with our new string.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
kr_patch.sh
is just what I'm calling badabing2003's patch script.