我在 Android 上面跑 root 這麼久,一直以來都是使用 Magisk。但最近遇到需要逆向一些有反逆向工程偵測的應用程式,正好讀到這篇《Root 隐藏基础教程》才知道原來有 KernelSU, APatch 這些 magisk 以外的 root 方案。KernelSU 的 root 比起 magisk 更難被應用程式偵測,因為 root 的功能是直接內建在核心裡面的。
在實體裝置上刷新的 kernel 這種事情我以前還真的沒做過,為了避免資料遺失,我決定先拿模擬器來跑 kernelsu 看看,畢竟如果隱藏效果夠好的話應該連模擬器特徵都可以隱藏才對。
KernelSU 的原理基本上就是去修改 Android 使用的 Linux kernel,讓 kernel 提供一個特別的功能可以讓 userspace app 獲得 root 權限(基本上就是個 rootkit?)。所以也就是說我們會需要一個特製的 kernel,這有兩種方法:
- GKI (Generic Kernel Image): 直接把 KernelSU 功能編譯進 kernel,也就是整個 kernel 要重編
- LKM (Linux Kernel Module): 把 KernelSU 編譯成一個 .ko 模組檔案,開機時插入 kernel,需注意的是
- 目前大部分 android kernel 都有啟用可以載入模組的功能,這點對我們有利
- KernelSU 官方有提供 .ko 模組可以直接下載,只要你的 kernel 是 GKI kernel(Google Treble 計劃後 kernel 有一個比較穩定統一的界面),都可以正確載入運作
- 但我研究了一下還是搞不懂 Image.gz , zImage, boot.img , ramdisk.img 到底要怎麼重包裝嵌入這個 .ko
- 而且 KernelSU 官方文件其實建議如果要用於模擬器那用 GKI 方式載入比較好
- 因此先專注於搞定 GKI
參考:Awesome Android Root(資訊有點過時但大致有用)
目前網路上能找到關於虛擬機內 kernelsu 的資訊只有 5ec1cff 的兩篇,但他的文章省略了很多細節,對於我這種第一次編譯 android kernel 的人來說實在看不出來實際的操作步驟。因此這邊記錄下我自己摸索出來的操作流程(核心概念還是來自於他的兩篇文章)。
下載 android kernel 原始碼
跟隨官方指引,使用 repo 指令下載原始碼。
repo init -u https://android.googlesource.com/kernel/manifest -b common-android15-6.6
找到正確原始碼版本
目前下載下來的是最新版本的原始碼,但如果直接編譯這個版本會遇到不相容的問題(5ec1cff 的第一篇就是這樣)。
因此我們要設法找到正確的 kernel 原始碼版本,因為我們接下來要修改原始碼然後重新編譯。幸好在第二篇他找到一個很方便的方法可以精確定位每一個虛擬機 kernel 的來源版本號 (commit hash)。這邊我就用這個方法來操作。
首先照尋常方法啟動虛擬機,然後 adb shell cat /proc/version,我的虛擬機(Android 15, kernel 6.6, host 是 macOS ARM)顯示如下:
Linux version 6.6.30-android15-8-gdd9c02ccfe27-ab11987101-4k (kleaf@build-host) (Android (11368308, +pgo, +bolt, +lto, +mlgo, based on r510928) clang version 18.0.0 (https://android.googlesource.com/toolchain/llvm-project 477610d4d0d988e69dbc3fae4fe86bff3f07f2b5), LLD 18.0.0) #1 SMP PREEMPT Tue Jun 18 20:50:32 UTC 2024
ab11987101 這串數字表示 Android CI 的一次構建,可以在以下網址找到本次構建的資訊:
https://ci.android.com/builds/submitted/11987101/kernel_virt_aarch64/latest
直接將數字換成你自己找到的數字就可以,另外注意, kernel_virt_aarch64 是 macOS ARM 上面的 AVD 會使用的 kernel CPU architecture,如果是 x86 的話對應的字串是 kernel_virt_x86_64。
找到構建之後,打開 BUILD_INFO 檔案,裡面有這麼一部分:
"repo-dict": {
"kernel/build": "a9c2595a49f03227e30069b0b7ea4fa7ab69a906",
"kernel/common": "dd9c02ccfe27152c269eec860e74f845b94647e9",
"kernel/common-modules/virtual-device": "94e52d00fd4e9921efbad3c4b5f424f90b57dfce",
"kernel/configs": "7de932a4b942d7e7677b0116a2e257e050163bb9",
"kernel/manifest": "3ae476a46dfdb746553080768f8eb3f53116a8af",
"kernel/prebuilts/build-tools": "b46264b70e3cdf70d08c9ae2df6ea3002b242ebc",
"kernel/tests": "b46b107f40a525386657729465fefac179fc5404",
"platform/build/bazel_common_rules": "212bca45fa9e280adc98081483f649814baf4b61",
"platform/external/bazel-skylib": "6b103c40d8113f001475d5e13672922ef2aa0e5a",
"platform/external/bazelbuild-apple_support": "f6003e1e3763f8aad9fb9acae79cfa5fff9ae988",
"platform/external/bazelbuild-bazel-central-registry": "7018452ffac4fc070b29d5c5c872bac3c746a06a",
"platform/external/bazelbuild-platforms": "04d937168075c80205c96f31752000e9de759adb",
"platform/external/bazelbuild-rules_cc": "5d626dcf48e0e97933b48456a86966971799c9e9",
"platform/external/bazelbuild-rules_java": "8e548c7053dffd1717d565f0409a88992f401da1",
"platform/external/bazelbuild-rules_license": "f578df4fd057ffe2023728444759535685631548",
"platform/external/bazelbuild-rules_pkg": "632ab98c81cf295750dc096142d4ca76264084bc",
"platform/external/bazelbuild-rules_python": "f71847ac898655b67634bb14e77a7408c4fb5e00",
"platform/external/libcap": "8729338c078737da57a8b63ee88016799dfb4cdb",
"platform/external/libcap-ng": "2bcc92ae19481dd2b8d3ce3abdfbbee49261abe6",
"platform/external/lz4": "124599333514066524f55f5f41fbf0d46212e598",
"platform/external/pigz": "9bc9fa17d499ddde88b77820f6d063e16c0cdd42",
"platform/external/python/absl-py": "9ae5a78fc57c3cd539398373ae39601a8b923e62",
"platform/external/toybox": "91f90361fc563025e6d152d68d2072109500d602",
"platform/external/zlib": "b34cd2e4371556e913ad9801a924a891c74d1463",
"platform/external/zopfli": "36c79f00e5229800d2aaa13fc42c301ec8ef1153",
"platform/prebuilts/asuite": "659188034e89b2456c4cea7e38c80c28589dafa5",
"platform/prebuilts/build-tools": "5c0b50c49d7c958c05cd3050e43f4772003df800",
"platform/prebuilts/clang-tools": "1634c6a556d1f2c24897bf74156c6449486e8941",
"platform/prebuilts/clang/host/linux-x86": "7061673283909f372f4938e45149d23bd10cbd40",
"platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8": "739d8a41b6096d384338a62995a04739aff8693d",
"platform/prebuilts/jdk/jdk11": "c6c90521b7c317f13d41bbd9336a8d45ee202cec",
"platform/prebuilts/rust": "442511af884f074018466f85b4daadd4b0ac0050",
"platform/system/tools/mkbootimg": "f5c284e8998dd8a085cf027bb518b6c2b84cf744",
"platform/tools/tradefederation/prebuilts": "fddb4c6cb91a580ee1f06536b8b5de15e98af638",
"toolchain/prebuilts/ndk/r26": "e535051ebc04204cec44bde38f62385d63180388"
},
這些就是本次構建使用的 commit hash。
接下來可以請 LLM 工具去編輯 android-kernel/.repo/manifests/default.xml 的內容,讓 default.xml 內指定這些 commit hash。也可以自己手動編輯,一一指定 revision=XXX,編輯完後我的 xml 長這樣:
```<?xml version="1.0" encoding="UTF-8"?><manifest> <remote name="aosp" fetch=".." review="https://android-review.googlesource.com/" /> <default revision="main-kernel-build-2024" remote="aosp" sync-j="4" /> <superproject name="kernel/superproject" remote="aosp" revision="common-android15-6.6" /> <project path="build/kernel" name="kernel/build" groups="ddk" revision="a9c2595a49f03227e30069b0b7ea4fa7ab69a906" > <linkfile src="kleaf/bazel.sh" dest="tools/bazel" /> <linkfile src="kleaf/bazel.WORKSPACE" dest="WORKSPACE" /> <linkfile src="kleaf/bzlmod/bazel.MODULE.bazel" dest="MODULE.bazel" /> <linkfile src="kleaf/bzlmod/bazel.WORKSPACE.bzlmod" dest="WORKSPACE.bzlmod" /> </project> <project path="build/bazel_common_rules" name="platform/build/bazel_common_rules" groups="ddk" revision="212bca45fa9e280adc98081483f649814baf4b61" /> <project path="common" name="kernel/common" revision="dd9c02ccfe27152c269eec860e74f845b94647e9" /> <project path="kernel/configs" name="kernel/configs" revision="7de932a4b942d7e7677b0116a2e257e050163bb9"/> <project path="kernel/tests" name="kernel/tests" revision="b46b107f40a525386657729465fefac179fc5404" > <linkfile src="tools/run_test_only.sh" dest="run_test_only.sh" /> <linkfile src="tools/launch_cvd.sh" dest="launch_cvd.sh" /> <linkfile src="tools/flash_device.sh" dest="flash_device.sh" /> </project> <project path="common-modules/trusty" name="kernel/common-modules/trusty" /> <project path="common-modules/virtio-media" name="platform/external/virtio-media" /> <project path="common-modules/virtual-device" name="kernel/common-modules/virtual-device" revision="94e52d00fd4e9921efbad3c4b5f424f90b57dfce" /> <project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" clone-depth="1" groups="ddk" revision="7061673283909f372f4938e45149d23bd10cbd40" /> <project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8" clone-depth="1" groups="ddk" revision="739d8a41b6096d384338a62995a04739aff8693d" /> <project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="5c0b50c49d7c958c05cd3050e43f4772003df800" /> <project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" clone-depth="1" revision="1634c6a556d1f2c24897bf74156c6449486e8941" /> <project path="prebuilts/kernel-build-tools" name="kernel/prebuilts/build-tools" clone-depth="1" groups="ddk" revision="b46264b70e3cdf70d08c9ae2df6ea3002b242ebc" /> <project path="prebuilts/rust" name="platform/prebuilts/rust" clone-depth="1" revision="442511af884f074018466f85b4daadd4b0ac0050" /> <project path="prebuilts/tradefed" name="platform/tools/tradefederation/prebuilts" revision="fddb4c6cb91a580ee1f06536b8b5de15e98af638" clone-depth="1" /> <project path="prebuilts/asuite" name="platform/prebuilts/asuite" revision="659188034e89b2456c4cea7e38c80c28589dafa5" clone-depth="1" /> <project path="tools/mkbootimg" name="platform/system/tools/mkbootimg" revision="f5c284e8998dd8a085cf027bb518b6c2b84cf744" /> <project path="prebuilts/jdk/jdk11" name="platform/prebuilts/jdk/jdk11" clone-depth="1" revision="c6c90521b7c317f13d41bbd9336a8d45ee202cec" /> <project path="prebuilts/ndk-r26" name="toolchain/prebuilts/ndk/r26" clone-depth="1" groups="ddk" revision="e535051ebc04204cec44bde38f62385d63180388" /> <project path="external/libcap-ng" name="platform/external/libcap-ng" revision="2bcc92ae19481dd2b8d3ce3abdfbbee49261abe6"/> <project path="external/libcap" name="platform/external/libcap" revision="8729338c078737da57a8b63ee88016799dfb4cdb"/> <!-- deps for for bzlmod --> <project path="external/bazel-skylib" name="platform/external/bazel-skylib" groups="ddk-external" revision="6b103c40d8113f001475d5e13672922ef2aa0e5a" /> <project path="external/bazelbuild-bazel-central-registry" name="platform/external/bazelbuild-bazel-central-registry" groups="ddk-external" revision="7018452ffac4fc070b29d5c5c872bac3c746a06a" /> <project path="external/bazelbuild-platforms" name="platform/external/bazelbuild-platforms" groups="ddk-external" revision="04d937168075c80205c96f31752000e9de759adb" /> <project path="external/bazelbuild-apple_support" name="platform/external/bazelbuild-apple_support" groups="ddk-external" revision="f6003e1e3763f8aad9fb9acae79cfa5fff9ae988" /> <project path="external/bazelbuild-rules_cc" name="platform/external/bazelbuild-rules_cc" groups="ddk-external" revision="5d626dcf48e0e97933b48456a86966971799c9e9" /> <project path="external/bazelbuild-rules_java" name="platform/external/bazelbuild-rules_java" groups="ddk-external" revision="8e548c7053dffd1717d565f0409a88992f401da1"/> <project path="external/bazelbuild-rules_license" name="platform/external/bazelbuild-rules_license" groups="ddk-external" revision="f578df4fd057ffe2023728444759535685631548" /> <project path="external/bazelbuild-rules_pkg" name="platform/external/bazelbuild-rules_pkg" groups="ddk-external" revision="632ab98c81cf295750dc096142d4ca76264084bc" /> <project path="external/bazelbuild-rules_python" name="platform/external/bazelbuild-rules_python" groups="ddk-external" revision="f71847ac898655b67634bb14e77a7408c4fb5e00" /> <project path="external/python/absl-py" name="platform/external/python/absl-py" groups="ddk-external" revision="9ae5a78fc57c3cd539398373ae39601a8b923e62" /> <!-- dependencies used to build toolchain from sources --> <project path="external/lz4" name="platform/external/lz4" groups="ddk" revision="124599333514066524f55f5f41fbf0d46212e598"/> <project path="external/pigz" name="platform/external/pigz" groups="ddk" revision="9bc9fa17d499ddde88b77820f6d063e16c0cdd42"/> <project path="external/toybox" name="platform/external/toybox" groups="ddk" revision="91f90361fc563025e6d152d68d2072109500d602"/> <project path="external/zlib" name="platform/external/zlib" groups="ddk" revision="b34cd2e4371556e913ad9801a924a891c74d1463"/> <project path="external/zopfli" name="platform/external/zopfli" groups="ddk" revision="36c79f00e5229800d2aaa13fc42c301ec8ef1153"/></manifest>
然後跑一次 repo sync 來切換到剛剛指定的版本。
設定虛擬機使用自訂 system image
AVD 預設同系統版本的不同 VM 之間會使用同一個系統碟映像檔 (system image),為了避免 Kernelsu 安裝影響到其他虛擬機,這邊複製一份系統映像檔出來專供此虛擬機使用。
- 找到 system image 目錄
cd ~/Library/Android/sdk/system-images/android-XX/ - 複製一份
cp -cr google_apis_playstore ksu- cp -c 在 macOS 上面會使用 APFS clone 功能(在檔案系統層做兩個檔案的 block copy on write),相當於 Linux 上面的 cp –reflink=always
- 修改 avd 設定檔裡面指定的 system image 路徑,設定檔通常在
~/.android/avd/XXXXXX.avd/hardware-qemu.ini - 有提到
google_apis_playstore的路徑都改成剛剛複製的一份ksu - AVD 的圖形界面現在應該會顯示 missing system images,不過如果用
emulator指令其實可以正常開機emulator -avd MYAVD -no-snapshot -show-kernel
若 VM 可以正常開機,恭喜,代表找到了正確的核心源碼版本,可以開始修改這份核心源碼了。
在核心源碼內加入 KernelSU 並重新編譯
參照文件在核心源碼內插入 KernelSU 即可。
實際的編譯指令也可以從 Android CI 的 BUILD_INFO 內取得(參見 5ec1cff’s blog),ARM 架構的核心編譯指令如下:
tools/bazel run --config=fast --config=stamp --lto=none //common-modules/virtual-device:virtual_device_aarch64_dist -- --dist_dir=virt
編譯產物會輸出到 virt/ 資料夾。
以 emulator 指令啟動虛擬機
將稍早編譯的 kernelsu 產物 Image.gz 複製到 system image 目錄並改名為 kernel-ranchu ,蓋掉本來的 kernel 就可以了。
需注意如果是 x86,編譯出來的核心執行檔是 bzImage。
接下來我開啟虛擬機測試,KernelSU 可以正常運作了!
進一步客製化 kernel:安裝 susfs
susfs 是在 KernelSU 對 kernel 源碼的修改上再增加一層修改。susfs 修改了主要是核心的 VFS 模組,讓核心可以刻意對 userspace 隱藏特定檔案、甚至偷偷重導向檔案內容,比如說在 userspace 呼叫 openat() syscall 打開某個路徑的檔案的時候,susfs 核心可以將 openat 重導向到另一個路徑。這些對於隱藏 root 和模改過的系統非常有幫助。
不過 susfs 系列 patch 安裝起來相當複雜(畢竟是 patch 的 patch),所幸官方 README 內的指引還算清楚準確。susfs patch 主要會對 KernelSU 源碼和核心源碼進行修改,對應的 patch 分別是 10_enable_susfs_for_ksu.patch 及 50_add_susfs_in_gki-android15-6.6.patch 。也就是說在套用 susfs patch 之前我們不只要確定核心源碼版本正確,也要確定目前安裝的 KernelSU 源碼版本正確,我在這步卡了一下。(註:我希望安裝的 susfs 版本是最新的開發版,因為 tag 的版本有些太舊)
如前文,我安裝 KernelSU 的時候直接選擇了 dev 分支來安裝,但是 susfs 的開發都是基於某個特定版本的 KernelSU 來開發,因此如果選錯 KernelSU 版本的話 susfs patch 就無法正確套用。至於要怎麼找到正確的 KernelSU 版本?要自己去翻 susfs4ksu 的 commit log,作者每次與 KernelSU 源碼同步都會在 commit log 裡面寫 KernelSU 的 commit hash。
舉例來說,我 1/17 想要安裝的 susfs4ksu repo 內最新的 commit 是 e64cd1f0,打開該 branch 的 commit log,一個一個 commit message 閱讀,找到在該 commit 之前,距離最近的一次 “Synced with official KernelSU main repo" 訊息,也就是 40cdafc0,此 commit 訊息稱與 KernelSU df6409 版本同步,因此我們要安裝的 KernelSU 就是這一個版本。
步驟 8 有個稍微麻煩的地方是對核心源碼打 patch 的時候經常會有 conflict,無法自動 patch,需要一些基本的 C 技能手動上 patch。
另外,susfs gitlab repo 內提供的 userspace 操控 CLI 工具 ksu_module_susfs 功能有些陽春,sidex15 的版本比較完整。
進一步客製化 kernel:自訂 defconfig
有些應用程式會使用 anti-debugging 技巧,讓 debugger (如 gdb, strace)無法正常 attach 上應用程式的 process。這個技巧就是開多個 process 然後從 parent process 先 attach 上 child process,這樣其他人就不能 attach 了,因為一次只能有一個 attach。
然後我查到 Linux 上面有個核心選項可以限制 attach (PTRACE_ATTACH),叫做 ptrace_scope。直接去虛擬機開這個選項看看發現不行,因為這個核心沒有內建提供這個限制功能的 SECURITY_YAMA 模組。
我就想說,既然我現在可以自己編譯核心,那我自然也可以自己去改核心編譯選項加上這個模組,但摸索了一下才找到正確指令開啟編譯選項的選單:
tools/bazel run //common:kernel_aarch64_config -- menuconfig
接下來找到 CONFIG_SECURITY_YAMA ,打開,再重新編譯。
重開機確認可以正常運行,我就把 ptrace_scope 調成 2 – admin-only attach,讓這個應用程式沒辦法用自己的 parent process attach child process。結果,應用程式直接啟動 crash。看來要繞過 anti-debugging 可沒那麼簡單啊。





