Skip to content

Instantly share code, notes, and snippets.

@TheFreeman193
Last active June 8, 2024 23:11
Show Gist options
  • Save TheFreeman193/46cf02661208efa81adf4ad3da92632b to your computer and use it in GitHub Desktop.
Save TheFreeman193/46cf02661208efa81adf4ad3da92632b to your computer and use it in GitHub Desktop.
Detect offset of kernel release string
#!/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
@TAbdiukov
Copy link

Hi

Where can I find kr_patch.sh?

@TheFreeman193
Copy link
Author

Hi

Where can I find kr_patch.sh?

kr_patch.sh is just what I'm calling badabing2003's patch script.

@danielaixer
Copy link

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