Last active
March 31, 2025 12:47
-
-
Save TheFreeman193/46cf02661208efa81adf4ad3da92632b to your computer and use it in GitHub Desktop.
Detect offset of kernel release string
This file contains hidden or 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.1 ===================== | |
" >&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/magisk ] || [ -f /data/adb/magisk/magisk32 ]; then | |
ROOTMODE="Magisk" | |
BBPATH="/data/adb/magisk/busybox" | |
MBPATH="/data/adb/magisk/magiskboot" | |
elif [ -f /data/adb/ksu/bin/ksud ]; then | |
ROOTMODE="KernelSU" | |
BBPATH="/data/adb/ksu/bin/busybox" | |
MBPATH="/data/adb/ksu/bin/magiskboot" | |
elif [ -f /data/adb/ap/bin/apd ]; then | |
ROOTMODE="APatch" | |
BBPATH="/data/adb/ap/bin/busybox" | |
MBPATH="/data/adb/magiskboot" | |
else | |
echo "ERROR: Magisk, KernelSU, or APatch is needed for this script. Make sure Magisk/KSU/AP is installed and you are running a root shell." >&2 | |
exit 2 | |
fi | |
if [ ! -f "$BBPATH" ] || [ ! -f "$MBPATH" ]; then | |
echo "ERROR: Critical $ROOTMODE components not found. Make sure $ROOTMODE is installed correctly and includes busybox and magiskboot." >&2 | |
[ "$ROOTMODE" = "APatch" ] && echo "$ROOTMODE users should get a copy of magiskboot from the Magisk APK, place it at /data/adb/magiskboot and chmod +x." | |
exit 2 | |
fi | |
for cmd in dd xxd strings stat sed grep; do | |
alias $cmd="$BBPATH $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 | |
$MBPATH 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 | |
$MBPATH 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.
Can you add support for kernelSU
Can you add support for kernelSU
@Edhic1 This turned out to be simpler than I expected as KSU ships with magiskboot
. The updated v2.1 script can search and patch KSU-modified kernels. It should also work with APatch-modified kernels so long as magiskboot
is provided in /data/adb
.
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.