Android root tool for Samsung Galaxy S25 Ultra (SM-S938B) that chains DirtyFrag CVE-2026-43284 and CVE-2026-43499 to gain root automatically at boot via KernelSU.
diabl0w/DFRoot 的 fork,专门适配 三星 Galaxy S25 Ultra(SM-S938B / pa3q),界面与运行输出全部中文。
两条链路,一个界面:
方案 漏洞 特点 快通道 DirtyFrag(CVE-2026-43284) 上游 DFRoot 那条,几秒到几十秒 手动 CVE-2026-43499 概率型,三轮阶梯,最长十几分钟 默认「自动」:先跑快通道,它不成再自动接手动方案。
一句话:开机自动获取 root。每次开机先试几秒的快通道;不成的话, 自动接手动那条链路,按「快 → 稳 → 耐心」重试三轮。
拿到 root 之后还会自动跑两条 cmd connectivity(去掉三星「软件包安装程序」的广告),
命令、输出、退出码一样打在界面日志里 —— 见第五节「安装器广告设置」。
v1.8 起这两条命令会被写成 KernelSU 的开机脚本,以后每次开机由 KernelSU 自己以 root
执行,不用打开应用、也不用授权弹窗。
Cannot run program "su": error=2, No such file or directory:
v1.8 为了不再额外重启一次,去掉了给 ksud 的 --soft-reboot;而 KernelSU 的 su 恰恰是
内核模块在 post-fs-data 阶段才挂到 /system/bin/su 上的,ksud 的 late-load 只跑
late-load / post-mount / service / boot-completed 这几个阶段(KernelSU 源码
userspace/ksud/src/late_load.rs)—— 不重启框架,本轮开机这个挂载点就永远不出现,
应用里 su -c … 必定 error=2。这两个问题是同一个根因;KsudChannel)—— APK 里多带一份 ksud
(libksud.so,放在 jniLibs/arm64-v8a/,装进 nativeLibraryDir,App 可以直接 execve),
用 libksud.so debug su 开一个 root shell,命令写进它的 stdin。提权走内核的
ioctl(KSU_IOCTL_GRANT_ROOT),不依赖 /system/bin/su、不用授权弹窗、也不用重启系统框架;* root 通道:helper=…,ksud=可用,su=…,卡在哪一眼可见;su 之前把 /data/adb/ksu/bin、/debug_ramdisk、/data/adb/magisk、/data/adb/ap/bin
塞进 PATH,SU_PATHS 也扩到 8 条(有的 KernelSU 变体只在这些目录里放 su)。--soft-reboot。以前这个参数会让 ksud 装完
之后重启一次系统框架 —— 用户看到的就是「开机之后又自己重启了一次」;更糟的是这次重启
会把应用进程连同它正在跑的「安装器广告设置」一起打断;su 去跑
(那时候 KernelSU 还没就绪,实机上每次开机都失败,必须手动打开 KernelSU 再打开应用
才行)。现在把同样的两条命令写进 /data/adb/service.d/dfroot-ads.sh,KernelSU 每次开机
都会以 root 执行它 —— 不经过应用、不经过 su、不需要任何授权弹窗;cmd connectivity,
命令、输出、退出码全部打在界面日志里(执行成功一定有输出);上游 DFRoot 自带的那条,全部代码在 app/src/main/jni/(exp.c + 两段 shellcode +
dirtyfrag-lkm/ 里的内核模块),编译成 libexp.so 由 App 直接调用:
splice() 改掉只读文件的 page cache;/vendor/lib64/libstagefrighthw.so 再 finit_module 加载,
把 SELinux 设成 permissive;libc.so / libc++.so,借 modprobe 的域启动自带的 ksud,
late-load KernelSU。快(几秒),代价是它会在 /dev/df 留下"本轮已布防"的痕迹,
而且它的 ksud 安装路径和手动那条不一样(见第七节)。
三个二进制都是预编译好的(逐字节未改):
| 文件 | 位置 | 作用 |
|---|---|---|
libcve43499root.so | jniLibs/arm64-v8a/ | helper,可执行 ELF,App 直接 execve,不需要 Shizuku |
cve-2026-43499-app.so | assets/payloads/ | payload,由 helper dlopen 后执行漏洞 |
ksud-s25u-kdp | assets/payloads/ | KernelSU 本体(ksud + 内嵌 kernelsu.ko) |
1. helper --run-payload <payload> <helper> <log> 拿 root(概率型)
2. helper -c "cp ksud …" 把 ksud 落到 /data/local/tmp
3. helper --late-load bind mount /system/bin/logcat,
再 exec "logcat late-load …" 装 KernelSU
成功判定:日志里同时出现 exploit completed 与 done=1 root=1。
界面上的「手动」方案跑的就是这条(「自动」方案在快通道不成时,也会接着跑它)。
手动点按钮(界面上)
└─ 就在当前进程里跑:方案「自动」= 快通道 → 手动;方案「手动」= 直接手动
开机自动(BootReceiver,两次广播各来一遍)
│
├─ 本轮已经拿到 root(已装凭据,或别的 root 还在)?
│ ├─ 安装器广告设置还没做 → 补做一次,结束
│ └─ 已经做过 → 跳过
├─ 前台服务正在跑 / 上一轮还没结束? → 跳过
│
├─ 第 1 步 · 快通道 DirtyFrag(几秒到几十秒,直接在广播里跑)
│ ├─ 成功 → 第 3 步
│ ├─ 失败但没留下痕迹(/dev/df 不存在)→ 继续第 2 步
│ └─ 失败但已经布防(/dev/df 存在)→ 停止,提示重启手机
│
├─ 第 2 步 · 手动方案 CVE-2026-43499(三轮阶梯,交给前台服务)
│ ├─ 成功 → 第 3 步
│ └─ 三轮都没成 → 本次不再折腾,等下次开机
│
└─ 第 3 步 · 安装器广告设置(两条 cmd connectivity,见第五节)
├─ 成功 → 顺手写成 KernelSU 的开机脚本(/data/adb/service.d/),以后每次开机自动执行
└─ 没成功(还没解锁 / KernelSU 还没就绪)→ 前台服务每 30 秒重试一次,最多 5 分钟
「开机(未解锁)」那次广播只跑快通道:手动链路要把 ksud 落到
/data/local/tmp,那是凭据加密存储,解锁前写不进去,跑了也是白跑。 所以手动链路等「开机完成」(用户解锁后)那次广播再跑。
v1.5 的开机自动是「收到广播 → 起前台服务,由服务去跑链路」,实机上不生效:
Android 12+ 限制后台应用启动前台服务,LOCKED_BOOT_COMPLETED 那一刻
startForegroundService() 会被系统直接拒掉,广播就静悄悄结束了 —— 开关是开的,
但什么都没发生。
v1.6 改回上游 DFRoot 的路子(它在实机上是验证过的):直接在广播里把快通道跑掉, 并且做得更稳一点:
goAsync() 保住进程:广播没结束,系统不会把这个进程当缓存进程回收;BootLog(设备加密存储),界面上那行「上次开机自动:…」
直接显示最后一次结果 —— 显示「还没有记录」就说明开机广播压根没到。为什么布防后不接着跑手动方案:两条链路都在内核里动手 —— 快通道会改 page cache、
加载内核模块把 SELinux 变成 permissive;这时候再叠一条概率型的漏洞,
是在一个已经"脏"的内核上再加一层不确定性。所以只要 /dev/df 出现,
就停下来让用户重启,而不是硬接。
| 轮次 | 尝试次数 | 首次探测上限 | 单次上限 | 卡死判定 | 本轮上限 |
|---|---|---|---|---|---|
| 第 1 轮 · 快 | 6 | 20 s | 60 s | 40 s 没新日志 | 3 分钟 |
| 第 2 轮 · 稳 | 12 | 35 s | 100 s | 70 s 没新日志 | 8 分钟 |
| 第 3 轮 · 耐心 | 24 | 60 s | 180 s | 110 s 没新日志 | 15 分钟 |
slide-kaslr-ok … slide=…),省掉重复探测 —— 这个思路来自
polygraphene/DFReroot;app/src/main/java/df/root/RmgChain.java 的 LADDER 表里,想调直接改。| 机型 | 固件 | 内核 | 状态 |
|---|---|---|---|
| SM-S938B(Galaxy S25 Ultra,pa3q) | BP4A.251205.006.S938BXXS9CZE1 | 6.6.98-android15-8-pe17667d-abogkiS938BXXS9CZE1-4k | 两条链路都已适配 |
两条链路都按内核版本匹配(快通道靠 android15-6.6 的内核模块,
手动靠 6.6.98 的载荷)。换固件 / 换内核要换对应的内核模块与载荷。
App 启动时会把机型、固件、内核、KMI 和快通道需要的 6 条路径全部打出来。
需要:JDK 17+、Android SDK(platform 36 / build-tools 36.0.0)。
不需要 NDK:libexp.so 是编译好的(随仓库提供),手动方案的三个二进制也是预编译的,
app/build.gradle.kts 里没有 externalNativeBuild。
export JAVA_HOME=/path/to/jdk-21
export PATH="$JAVA_HOME/bin:$PATH"
export ANDROID_HOME=/path/to/android-sdk
./create-keystore.sh # 生成 app/keystore.jks(已被 .gitignore 排除)
echo "sdk.dir=$ANDROID_HOME" > local.properties
./gradlew :app:assembleRelease
# → app/build/outputs/apk/release/dfroot.apk
或者直接 ./build.sh,它会构建并复制成根目录下的 dfroot.apk。
想自己重编快通道的原生部分(
app/src/main/jni/):装 NDK 27.0.12077973, 在app/build.gradle.kts里加上ndkVersion与externalNativeBuild即可。 内核模块(dirtyfrag-lkm/)的重编需要 GKI DDK,见该目录下的build.sh。
装好 KernelSU Manager(me.weishu.kernelsu,v3.3.0):
https://github.com/tiann/KernelSU/releases/tag/v3.3.0
adb install -r dfroot.apk,或直接点安装);cmd connectivity,见下),
并把它们写成 KernelSU 的开机脚本 —— 以后每次开机由 KernelSU 自己以 root 执行,
不用打开应用,也不会再弹授权;方案只有两个:自动(先快通道,不成再手动)和手动(只跑 CVE-2026-43499)。 界面上的「上次开机自动:…」会显示上一次开机自动跑到哪一步。
第一次使用建议在能充电、别乱动手机的时候做:快通道几秒就有结果, 但手动那条最长可能跑十几分钟,中途别熄屏、别退出应用。 开机自动那一轮先跑快通道(几秒),要跑手动链路时才有前台服务通知。
三星「软件包安装程序」的安装界面会拉广告。这两条命令本来是 adb 里手动敲的, 现在由 App 在每次拿到 root 之后自动执行:
cmd connectivity set-chain3-enabled true
cmd connectivity set-package-networking-enabled false com.samsung.android.packageinstaller