题记:废话不说,直接进正题。本月主线一句话:把”三星S24 Ultra国行免解锁root”从一台机子的偶然成功,磨成一套可复现、可审计、可给新固件快速适配的流水线。活儿大部分是配合AI(某自然语言处理大模型)干的,代码说实话我都没完全吃透,但我相信总会理解的——毕竟这代码本身就是大模型写的。
0x00 RootMyS24
先说结论:载荷是按内核构建定标的,不是按机型。机型只是表面,真正决定能不能跑的是那几行偏移和那张滑动指纹。
0x00-1 定标三规则
这一个月反复验证,沉淀成三条规则:
- 同构建号、不同机型,载荷可共用。S9210/S9260/S9280国行同build,一套载荷通吃(代码段符号、数据段偏移、指纹都一样)。
- 国行与上版/外版不可混用。哪怕内核版本号一样,kmalloc_caches、nfnetlink_log这类数据段偏移不同,硬套就是全挂。港台同build可互用,国行是单独一条线。
- 内核版本不同,必须单独定标。最费功夫、最容易翻车,这个月栽过跟头。
0x00-2 把定标流程从”玄学”变成”脚本”
以前适配新固件:下载14GB全包 → 解包 → 反汇编 → 手抄偏移 → 真机试错。现在:下120MB → 跑三个脚本 → 编一次,从头到尾十几分钟。核心脚本逐段看:
0x00-2-1 取 boot:fetch_boot_prefix.sh
VER="${1:?需要4段版本串}" # 例:S9280ZCS5CZB2/S9280CHC5CZB2/S9280ZCS5CZB1/S9280ZCS5CZB2
OUT="${2:?需要输出目录}"
NEED=${3:-120000000} # 默认只要文件开头120MB就够覆盖AP里的boot
"$T/samloader" download -m SM-S9280 -r CHC -j 1 -v "$VER" -d "$OUT" & # -j1单线程,避免分块线程卡死
# 等"文件开头N字节都真正落盘"(稀疏文件里未下载区域是空洞,读出来是全零)
prefix_cov() { # 用SEEK_DATA/SEEK_HOLE数出[0,第一个空洞)的长度,即已连续下载的字节
python3 - "$f" <<'PY'
import os, sys
fd=os.open(sys.argv[1], os.O_RDONLY)
try:
d=os.lseek(fd, 0, os.SEEK_DATA) # 第一个有数据的偏移(从0开始=0)
h=os.lseek(fd, d, os.SEEK_HOLE) # 第一个空洞偏移
print(h if d==0 else 0) # 若0处是数据,前缀覆盖到h;否则0
finally: os.close(fd)
PY
}
# 覆盖够120MB后杀掉下载,把zip里第一个tar的boot.img.lz4抠出来解压
妙处:samloader多线程默认把文件分成8段乱序下载,chunk 0那条线程有时会卡死(上次DZH3卡在86MB不动)。单线程顺序写,前缀就能稳定增长,到120MB立刻掐。
0x00-2-2 还原内核:analyze_boot.sh
# 拆boot,得到kernel + ramdisk magiskboot unpack boot.img # 内核本身可能是lz4压缩的,解出来;raw的话直接用 magiskboot decompress kernel kernel_raw 2>/dev/null || cp kernel kernel_raw # 从原始Image还原带符号的ELF——后面所有偏移都从这查 PYTHONPATH=vmlinux-to-elf python3 -m vmlinux_to_elf.scripts.vmlinux_to_elf \ kernel_raw vmlinux.elf # 产出43MB ELF,符号可用llvm-nm/objdump查
0x00-2-3 符号身份迁移:gen_target_h.py
把”适配从抄作业变成查符号表”的关键。思路:以DZF2的target.h为模板,对每个偏移取”模板内核里那个地址上的符号名”,在新内核里按同名回查,自动生成新target.h:
def main():
...
for i,line in enumerate(lines):
m = re.match(r"(\s*#define\s+)([A-Z0-9_]+_OFF)(\s+)(0x[0-9a-fA-F]+)", line)
if not m: continue
old = int(m.group(4), 16)
# 1) 模板内核里,这个偏移落在哪个符号上?
sym = tpl_addr2sym.get(BASE + old) # 地址→符号名
# 2) 新内核里按同名回查,得到新地址
if sym and sym in new_sym2addr:
new = new_sym2addr[sym] - BASE
lines[i] = f"{m.group(1)}{m.group(2)}{m.group(3)}0x{new:09x}\n"
# 3) 特殊"符号+字段偏移"型:ashmem_miscs+0x10, worker_thread+0xa0 显式处理
# 顺带把 BUILD_VARIANT_LABEL / BUILD_FINGERPRINT 换成目标型号
实测:DZF2→DZH3迁移了23个偏移全对(符号99.99%相同);CZB2是另一条线,20个偏移0个对得上,只能老老实实单独定标。
0x00-2-4 生成 KASLR 滑动指纹:gen_p0_fingerprint.py
# 32行指纹,每行对应一个可能的slide(0x000000..0x1f0000步进0x10000)
for slide in range(0, 0x200000, 0x10000):
at = 0x1f0000 - slide # 该slide下,内核Image的采样基址
words = [unpack64(img, at + o) for o in (0x000,0x200,...,0xe00)] # 8个qword
assert words[0] == unpack64(img, 0) # 末行(Index[0])=Image[0]的MZ魔数,自检
# 这8个qword就是P0物理页oracle用来认slide的指纹
0x01-1 DZH3:捡了个便宜,省了整段定标
DZF2和DZH3是同一内核线:6.1.145-android14-11-3254743,只差末尾DZF2→DZH3。比对——103,514个符号里103,507个地址完全相同,就7个静态字符串(f_midi_longname、pty_line_name、trunc_msg那些)不同。所以DZF2载荷对DZH3直接有效,只差p0指纹里1个词(slide 0x1a0000),重生成一份即可。
0x01-2 CZB2:另一条线,偷不得懒
CZB2是6.1.128,构建号2755301,跟DZF2完全不是一根藤上的瓜:20个关键偏移0个相同,10万共有符号里仅0.44%地址一致。只能走完整流水线。实测在S9210ZCS5CZB2上exploit一遍过(attempt 1就triggered=1)——等于给整套流程做了次真机验收。
0x01-3 踩过最大的坑:改vermagic只对相邻build code安全
这月最痛的一课。KernelSU内嵌模块要匹配目标内核vermagic。图省事把S928U模块的vermagic字符串改成DZH3——结果一上真机,late-load后一解锁就卡死重启,毫无征兆。
- 表象:加载KernelSU后系统极不稳定,解锁(唤醒显示/GPU的work)一碰就崩。
- 根因:vermagic改串只骗过版本检查,但模块是按S928U内核(构建号33419968)编的,DZH3是3254743——跨内核,运行时的KDP/RKP钩子、cred处理、结构体假设全对不上。
- 解法:跨内核一律用DDK(ghcr.io/ylarod/ddk-min)按真机内核正式重建,UTS_RELEASE设成目标release,约两分钟编一个,再跑check_symbol + audit_module_against_target.py(209导入/0缺失/0 CRC不匹配全过)。
经验:改串这招只对同一内核线、相邻build code安全(DZF2↔DZH3这种);跨内核版本,省下的时间会加倍赔进去。
0x01-4 真机踩坑记录
- 坑A 解锁就卡死重启——加载了按错误内核构建的模块(见0x01-3)。解法:DDK按真机内核重建。已解决。
- 坑B 要么一遍过要么一直失败——fops-slide竞态要多个写线程在精确窗口抢调度;app熄屏/后台把payload压进后台cgroup(Android限CPU)→写线程饿死→triggered=0。解法:跑的时候别让进程进后台cgroup(不熄屏/保持前台),命中率明显提升。
- 坑C 源码ksud缺–ephemeral——ksud late-load: exit=2 unexpected argument ‘–ephemeral’ found。出厂ksud-selected是定制版自带该参数(加载模块后跳过ksud安装与/data/adb写入),源码重编的没有。解法:把–ephemeral加进cli.rs的LateLoad(ephemeral=true时跳过stage_daemon_from与finish_install)。已补,exit=0。
- 坑D 管理器显示版本不对——拿3.3.0的ksud测,管理器还是显示v3.2.5(32525-2)。根因:管理器显示的是内核模块版本不是ksud版本;我的模块从v3.2.5源码编,永远报32525。解法:要显示32601,得把模块也从v3.3.0源码+三星补丁重编(需rebase补丁,独立活,还没做)。
- 坑E CZB2同款坑又踩一遍——CZB2机器exploit一遍过,但late-load后同样卡死。解法:走DDK为CZB2正式重建(vermagic 6.1.128-android14-11-2755301-abS9280ZCS5CZB2),审计全过。
0x01 网站
这个月不全是root。把rms24的日志上报、统计、下载页整到了blog.nanoturtle.cn;地图瓦片换了国内合规底图(高德+可选天地图+无底图,坐标GCJ-02转换);导航、首页卡片重做(PDO直写wp_posts绕开KSES过滤)。顺手修了台本地机的fcitx5拼音——升级addons后ABI对不上(新版addons引用了旧核心没有的fcitx::RegexConstrain符号),换回官方核心5.1.22就好。真的有点难绷。
0x02 总结
一句话:
活都在工具的自动化里,真正难的还是内核侧的运行时假设。
这个月能让我如此忙碌的导火索还是因为我手贱升级OneUI8.5,唉,累了。
就这样。好久没见。
本文采用 CC BY-NC-SA 4.0 国际许可协议进行许可。
转载请注明作者及原文链接,并遵循相同方式共享原则。
链接:







