
삼성 Galaxy S25 Ultra(SM-S938B)용 Android 루트 도구로, DirtyFrag CVE-2026-43284와 CVE-2026-43499를 연쇄하여 KernelSU를 통해 부팅 시 자동으로 루트 권한을 획득합니다.
diabl0w/DFRoot의 fork로, **삼성 Galaxy S25 Ultra(SM-S938B / pa3q)**에 특화하여 적응했으며, 인터페이스와 실행 출력이 전부 중국어입니다.
두 개의 경로, 하나의 인터페이스:
방식 취약점 특징 고속 채널 DirtyFrag(CVE-2026-43284) 업스트림 DFRoot의 그것, 몇 초에서 수십 초 수동 CVE-2026-43499 확률형, 3단계 사다리, 최대 십여 분 기본값 「자동」: 먼저 고속 채널을 실행하고, 실패하면 자동으로 수동 방식으로 전환.
한마디로: 부팅 시 자동으로 root 획득. 매번 부팅 시 먼저 몇 초짜리 고속 채널을 시도하고, 실패하면 자동으로 수동 경로로 전환하여 「빠름 → 안정 → 인내」 3단계로 재시도.
root를 획득한 후에는 두 개의 cmd connectivity(삼성 「소프트웨어 패키지 설치 프로그램」의 광고 제거)를 자동으로 실행하며,
명령, 출력, 종료 코드가 모두 인터페이스 로그에 기록됩니다 — 제5절 「설치 프로그램 광고 설정」 참조.
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 설치 경로가 수동 경로와 다름(제7절 참조).
세 개의 바이너리 모두 사전 컴파일됨(바이트 단위로 변경 없음):
| 파일 | 위치 | 역할 |
|---|---|---|
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참조.