Imported from zombiegirlcz/kali_core_emulator (
AGENTS.md). Install upstream withnpx skills add zombiegirlcz/kali_core_emulator. Copyright stays with the author.
AGENTS.md — NetHunter AI Operator (komprimováno 2026-09-10)
Provozní kontrakt pro agenty. Neopakovat zde, co už je jinde: funkce + celé
nhCLI →README.md; MITM →assets/nethunter_docs.md; bezpečnost →docs/SECURITY_AUDIT.md; velké změny →docs/plans/. S uživatelem komunikuj česky.
0. Nepřekročitelná pravidla
- ZÁKAZ LOKÁLNÍHO BUILDU — nikdy
./gradlew …lokálně. Build jen přes Modal (zsh mbuild …). - Nikdy neměnit ownership/perms systémových složek na zařízení (rekurzivní
chmod/chownna/system,/data, …) → bootloop incident 2026-08-23. Deploy jen do appfilesDir,/data/adbnebo přes Magisk modul. - Zdroj pravdy je GitHub (
zombiegirlcz/kali_core_emulator, branchdev). Před buildem vždygit commit && git push;mbuild syncklonuje/pulluje z GitHubu, ne z telefonu. - Neměnit:
--link2symlinkv proot (bez něj je apt/dpkg rozbitý), pořadí načítáníjniLibs,useLegacyPackaging = true. - Nebezpečné operace (
rm -rf /,mkfs,reboot, …) jsou blokované dvakrát (su_daemonblocklist +LocalApiServer+ ashell) — neobcházet.
1. Build & ověření (VŽDY Modal)
# 1) commit + push na GitHub (jinak se změny neprojeví)
# 2) sync = git clone/pull zdroje na Modal straně (nikdy upload z telefonu)
zsh mbuild sync
zsh mbuild all # sync + SMART inkrementální build (proot/native/gradle) → APK
zsh mbuild build # jen Gradle build + stažení APK
zsh mbuild native # jen NDK/C compile + pull_full_assets()
zsh mbuild smart # inkrementální build bez sync
zsh mbuild clean # smaže src + gradle-cache na Volume
- APK se stahuje na
~/Download/kali_core.apk(na Volumekali-build-data,builds/app-debug.apk). pull_full_assets()stahuje celýapp/src/main/assets/rekurzivně z Volume → přepíše lokální verze.- Pravidlo: sync se dělá vždy zvlášť, build nikdy nevolá sync.
- Logcat bez ADB (z hostujícího shellu/guestu):
nethunter-log [-n N] [-g VZOR](nasazujeProotManagerdo guest/usr/local/bin/; HTTPGET /app/logs?limit=N). - Diagnostická binárka
nethunter-logje Python skript, ne Kotlin — nehledat v dexu.
2. Identita a podepisování
| Atribut | Hodnota |
|---|---|
| Balíček | com.linux_core (ne cz.hackai.nethunter_ai_operator) |
| Zdroj | app/src/main/java/com/linux_core/ |
| Verze | versionCode = 20, versionName = "4.5-MULTI-ROOTFS" (app/build.gradle.kts) |
| minSdk / targetSdk | 28 / 28 (ne 33/36 — copilot-instructions.md je zastaralé) |
| Java/Kotlin | JVM 17 (sourceCompatibility = JavaVersion.VERSION_17), Kotlin official styl |
| BuildConfig | ENABLE_MITM=false, ENABLE_ATTESTATION=true (default v app/build.gradle.kts) |
| Podpis | app/release.jks — debug i release stejný keystore → adb install -r bez odinstalace |
| Alias/heslo | releaseKey / password123 (env: KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD) |
Před každou distribuovanou verzí navyš versionCode.
3. Architektura
Jednomodulová app (:app). Spouští Kali/ParrotOS
v nerootovaném PRoot kontejneru (Termux terminál) s AdGuard C++ VPN, TLS MITM a X11 serverem.
GUI není součást core — desktop renderuje externí NetHunter X11 Launcher (kali_GUI app).
Runtime porty (host loopback)
| Port | Služba |
|---|---|
| 1337 | LocalApiServer — REST most (baterka, toast, wifi, GPS, schránka, VPN, USB, /shell, /distro/*) |
| 13338 | AI agent démon (nethunter_agent.py, ReAct LLM, nástroj analyze_network) |
| 13339 | VPN bypass proxy (http(s)_proxy pro guest → obchází AdGuard) |
| 6000/tcp | X11 server Xvfb :0 v guestu (DISPLAY=:0, viewer 127.0.0.1:6000) |
Klíčové třídy
| Třída | Role |
|---|---|
MainActivity |
Compose UI: rootfs download/extrakce, terminál, VPN centrum, auto-start toggle |
ProotManager |
Arch detekce, deploy PRoot/loader + boot launcheru a helper skriptů do guestu |
RootfsManager |
OkHttp download + commons-compress tar.xz extrakce, Flow<Int> průběh |
TerminalActivity / TerminalService |
Termux TerminalView, session lifecycle, headless session (view=null) |
BackgroundBoot / BootReceiver |
Auto-start po bootu / MY_PACKAGE_REPLACED → headless session s cronem |
FloatingTerminalService |
Overlay plovoucí terminál (nh float) |
ShareReceiverActivity |
„Open with"/share → filesDir/share → guest ~/share |
VpnCaptureService / VpnNatEngine |
VPN lifecycle, TCP/UDP NAT, QUIC gating, packet forwarding |
TlsMitmEngine / TlsClientHelloParser |
TLS MITM (TlsMitmSession), SNI extrakce, dešifrovaný provoz |
AIBrain / AIBrainWorker / VerdictEngine |
ONNX klasifikace toků (vpn_brain_v7.onnx, LightGBM) |
VpnLogManager / VpnFirewallManager / VpnProxyManager / VpnPeerManager |
logy, IP blocklist, custom IP:Port proxy, Mesh VPN |
ShellDaemonClient / shell_daemon |
Privilegované příkazy bez rootu (uid 2000, ashell adb) — nahradilo odstraněný ShizukuManager (2026-09-19) |
UsbHostManager / usb_bridge.c / usbfd_jni.c |
Raw USB pro mtkclient/EDL (/usb/stream binární frame protokol) |
core/ je ~66 souborů, ui/ 8, security/ 10 — nevyjmenovávat všechny, viz README |
PRoot binární strategie
- Jen STATICKÉ buildy z kořene
assets/:proot-static-{aarch64,arm,i686,x86_64}+loader-static-*. Dynamické (proot-*,loader-*,libtalloc-*.so) byly z repa odstraněny 2026-09-08 (ProotManager.deployArchBinaries). tallocje doproot-static-*slinkovan staticky (libtalloc.a, viztools/modal_build.py); žádnýlibtalloc.so.2se za běhu nenasazuje.- Launcher je univerzální
assets/usr/bin/boot(re-entry módboot -- <args…>,-0 --kill-on-exit,--link2symlink); nasazuje se do$PREFIX/bin/boot. - Guest bindy zahrnují
$FILES_DIR/share → /root/share,$FILES_DIR/ipc → /run/host_ipc.
jniLibs struktura
app/src/main/jniLibs/arm64-v8a/ = AdGuard knihovny. Na pořadí načítání záleží:
liba → libio_utils → libcommon_native_jni → libadguard-core → libadguard-dns.
Ostatní ABI (x86, x86_64, armeabi-v7a) obsahují jen PRoot.
4. Native moduly → assets (PRAVIDLO)
Nativní C moduly (app/src/main/cpp/*.c) se kompilují na Modalu a musí být vždy v APK:
- Přidej kompilační krok do
build_native()(resp._build_native_bin/_build_native_lib/_build_usrtools) vtools/modal_build.py— NDK cross-compile →assets/(např.su_daemon,su_wrapper,usb_bridge) nebojniLibs/arm64-v8a/(*.so). - Spusť
zsh mbuild native(neboall) +pull_full_assets()→ artefakty do lokálního repa. - Commitni a pushni binárky (
assets/su_daemon,su_wrapper,usb_bridge,usr/bin/*,usr/lib/*)..gitignoremá pro ně explicitní!výjimky a sync klonuje z GitHubu — necommitnutá binárka v APK nebude. (Přesně tak se 2026-08-23 ztratily bionic usrtools; obnova:modal run tools/rebuild_usrtools_recovery.py::rebuild.) - Nikdy nenechávej artefakt jen na Volume — další
rsync --deleteho smaže.
5. Závislosti a konvence
- Version catalog
gradle/libs.versions.tomluž obsahuje icommons-compress,xz, Termux (terminal-view/terminal-emulator/termux-sharedv0.118.0),guava33.6.0,bouncycastle,androidx.biometric— už není potřeba deklarovat přímo vapp/build.gradle.kts. Přímo jsou jenandroidx.viewpager2,recyclerviewaonnxruntime-android:1.17.1. - Guava exclude:
com.google.guava:listenablefuturevyloučit ze všech Termux závislostí. packaging.jniLibs.useLegacyPackaging = true(nutné pro Termux.so),org.gradle.parallel=false.- Compose BOM z catalogu spravuje Compose verze; ONNX modely
vpn_brain*.onnxv assets. - Bionic usrtools (
sed/rsync/nano/rg) — ne glibc: NDK r28, interpreter/system/bin/linker64, bezusr/libzávislostí; deploy je version-gated (USR_TOOLS_VERSION, aktuálnělayout-20260815-1vProotManager.kt) → staré rozbité binárky se smažou a nasadí znovu.
6. Repozitář
- Git LFS povinné (
.apkpřes LFS, viz.gitattributes). - Submodul
nethunter-store-data→https://gitlab.com/zombiegirlcz/nethunter-store-data.git. - Klon:
git clone --recurse-submodules+git lfs pull.
7. Bezpečnost
Původní audit: 25 nálezů (9 CRITICAL / 8 HIGH / 5 MEDIUM / 3 LOW) — všechny hlavní opravené:
- build.gradle.kts: hesla keystoru z env/gradle properties,
isJniDebuggableodstraněno. - AndroidManifest.xml:
allowBackup=false,usesCleartextTraffic=false,networkSecurityConfig; citlivé komponenty (TerminalActivity, notification/accessibility service,DistroDocumentsProvider)exported="false". - LocalApiServer.kt: Bearer token (UUID v
api_securitySharedPreferences), localhost detekce pro citlivé endpointy, blocklist destruktivních příkazů, max délka commandu 1024 znaků. - RootfsManager.kt: HTTPS + host whitelist (
kali.org,parrot.sh,raw.githubusercontent.com), TLS 1.2+, OkHttp timeouty. VpnFirewallManager.kt: IPv4/IPv6 validace před blokací. - res/xml/network_security_config.xml: cert piny (platnost do 2027-12-31).
- Hotovo: cert pinning,
OffensiveEnginenotification confirm (Allow/Deny, 30 s), odstraněný hex dump z CSV/JSON exportu, autentizace agenta na 13338.
Cert pin SHA-256 (k 2026-06-27, potřebují obnovu po expiraci)
Kali → GTS: leaf kali.org vxHMRAr73HgUyGzWLG8C4xtO/qsK9nkPG59jH3i/mqc=,
leaf kali.download xAu7m0o10HbvBkBpvcS7+PYtxxX1rdUN8FHEI2Kg0Fo=,
GTS WE1 kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa/A4=,
GTS Root R4 mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=.
Parrot/GitHub → Let's Encrypt: leaf deb.parrot.sh lkI2NEknt/oq8INt5aiW7TriA18Z1mMNvvT6tjZjghs=,
leaf raw.githubusercontent.com PaZDXCM44SEEkf5qy7PN/gi0Z1u+nhGbRcKHSZQxhmA=,
LE YR2 nWN7PSep5XDQdge5zK24CnCRXHr3KvzhKEGxsdqCX9E=,
ISRG Root YR fk6IOKit1ild5647BH06ujSIq5XbCgqlbYl6ANhhi88=.
Certificate & Attestation modul (app/src/main/java/com/linux_core/security/)
CertificateManager(fasáda, init zMainActivity.onCreate, zálohaLocalApiServer.start),RootCaInstaller(MITM CA zassets/certs/mitm-ca.crt; produkce nikdy neinstaluje CA systémově),SslContextFactory(assets/certs/internal.p12, fallback heslo zKEYSTORE_PASSWORD).AttestationKeyManager(aliasattest_ecStrongBox→TEE,attest_secretAES-GCM-256, 30 s biometric okno),AttestationVerifier(PKIX chain + nonce + signature),BiometricGate,KeystoreManager,MitmCertSigner(BouncyCastle).- Debug
network_security_config_mitm.xmlje samostatný (jen dokumentace); produkční config beze změn. - Testy:
app/src/test/java/com/linux_core/security/.
8. TLS MITM Inspection
Kompletní proxy pro dešifrování HTTPS v VPN tunelu. Zapnuto/vypnuto přes enable_mitm
(SharedPreferences vpn_settings, default BuildConfig.ENABLE_MITM=false).
- Tok:
VpnNatEngine.handleTcpPacketdetekuje Client Hello →TlsMitmEngine.onClientDatavytvoříTlsMitmSession→ SNI parse → server-side handshake →RootCaInstaller.signLeafForServer()→ client-side handshake s forged certem →proxyLoopdešifruje/přeposílá; snippety (max 200) vdecryptedSnippets. - Soubory:
TlsClientHelloParser.kt,TlsMitmEngine.kt,RootCaInstaller.kt,MitmCertSigner.kt,VpnNatEngine.kt(detekce),VpnSecurityTab.kt(UI),VpnSettingsTab.kt(přepínač),LocalApiServer.kt(endpointy). - CLI:
vpn-cli mitm on|off|status|ca,vpn-cli logs [json](token z/data/data/com.linux_core/shared_prefs/api_security.xml). - API (1337, Bearer):
POST /vpn/mitm(on|off),GET /vpn/mitm,GET /vpn/mitm/ca,GET /vpn/mitm/logs[?format=json]. - Známé bugy — nevracet zpět: double-flip v
writeToServer(volající flipují sami); passthrough vždy s fresh socketem (jinak otrávený kanál);writeFully()místo non-blocking dropu přiw=0;sendTcpAckna MITM cestě; fail-fast server handshake (~50 iterací);protect()fail →null; QUIC blokovat jen když MITM aktivně dešifruje (shouldBlockQuic());extractSni/isTlsClientHelločtou sessionIdLen ze stejného offsetu (43);RootCaInstallerdebug fallback heslo"nethunter-dev"(P12 v assetech); forged cert musí mít vlastní RSA keypair (ne CA private key). - Capture-only režim:
startCaptureOnly()/captureLoop()(dešifruje lokálně, timeout 10 s),VpnSettings.isMitmCaptureOnly(). - Postup při selhání:
vpn-cli mitm status→nethunter-log -g "TlsMitm"(hledejSNI=null,falling back to passthrough) →nethunter-log -g "RootCaInstaller"→ ověřassets/certs/mitm-ca.crta nainstalovanou CA v trust store → nouzověvpn-cli mitm off.
9. ashell — host shell konfigurace (ashell.conf, v4.5)
/shell API (ashell -c) nezná hardcoded allowlist, ale blocklist + env z <filesDir>/ashell.conf.
DESTRUCTIVE_PATTERNS zůstávají druhá vrstva; SHELL_ALLOWLIST chrání jen /proot/exec.
block <cmd> # zakáže <cmd> přes /shell (basename match)
export FOO=bar # sh řádky aplikované před KAŽDÝM příkazem
unset LD_LIBRARY_PATH
${FILES_DIR}se expanduje na filesDir (config přenositelný); config se vytvoří při prvním použití.- Endpointy (Bearer; citlivé i remote):
GET/POST /ashell/config,GET/POST /ashell/blocklist. - CLI:
ashell -e(editor; bez$EDITORjen hláška),ashell --list,--add <cmd>,--remove <cmd>. - Interaktivní host shell čte config z
.ashell_envpřesENV=(unset přebije spawnLD_LIBRARY_PATH). - Test parseru:
app/src/test/java/com/linux_core/core/AshellConfigParserTest.kt.
9b. ashell adb — shell_daemon pod uid 2000 (non-root)
Persistentní démon, který appce dává shell UID (2000) — stejná práva jako adb shell
(pm, settings, cmd, dumpsys, logcat), bez roota a bez reálného adb.
- Binárka:
app/src/main/jniLibs/arm64-v8a/libshelldaemon.so(ELF smain, ne JNI lib). Android ji extrahuje doapplicationInfo.nativeLibraryDir(useLegacyPackaging=true). Nespouští ji appka (app UID 10323 nedokáže spawnout uid 2000) — spouští ji guest:ashell adb start→adb shell "nohup <nativeLibraryDir>/arm64/libshelldaemon.so … &". - Protokol: TCP
127.0.0.1:13341, binární (magicSHLL, modeEXEC/ATTACH/INSTALL, length-prefixed bloby) — vizapp/src/main/cpp/shell_daemon.c. TCP místo UNIX socketu proto, že uid 2000 nesmí zapisovat dofilesDirappky. - Token:
filesDir/shell_daemon.token(appka zapisuje), uid 2000 ho dostane přesGET /shelldaemon/infona127.0.0.1:1337.nativeLibraryDirje read-only (system:system). ashell adb <cmd>=daemon_exec()v ashellu →POST /shelldaemon/exec. Sdílená funkce pro<cmd>,shell <cmd>i-c <cmd>; parsuje{stdout,stderr,exit_code}.ashell adb shell(bez args) otevřeTerminalActivity→startAdbShellSession()→libshelldaemon.so --attach→ PTY shell pod uid 2000.
UI nikdy neběží pod uid 2000 — WindowManager přiděluje okno jen procesu s app identitou.
Rozdělení: emulace + render v app procesu (Termux terminal-emulator/terminal-view, uid 10323),
spouštění příkazů + PTY v daemonu (uid 2000). Attach klient (--attach) běží jako dítě appky
(uid 10323) a jen tuneluje bajty; shell za ním je uid 2000. Perzistence screen state by vyžadovala
emulátor v daemonu (tmux model) — dnes handle_attach po zavření socketu shell zabíjí.
Co nevrátit zpět (pitfalls):
ashell adb shell <cmd>nesmí otevírat okno — s argumenty musí jít přesdaemon_exec. Vnějšícase "$SUBCMD"matchneshell)dřív než passthrough*).LocalApiServer.handleAshellnesmí posílatashellMode=trueproadb-shell— patří jenashell-host. JinakTerminalActivityspustístartAshellSession()(uid 10323) místostartAdbShellSession()(uid 2000). Platí i procmd activity start-activityv ashellu (tam--ez ashellMode trueuashell-adbnepatří).- V
TerminalActivity(onNewIntentisetupAndStartSession) musí býtrootfsDirName == "ashell-adb"vyhodnoceno předashellMode(přidána i explicitní podmínka!= "ashell-adb"). - OPRAVA (2026-09-27, ověřeno na zařízení — tvrzení níže bylo NESPRÁVNÉ): démon přežívá
vypnutí wireless debugging bez roota. Stejný
pidodpovídal na/shelldaemon/statusi poadb_wifi_enabled=0. Důvod:main()vshell_daemon.cse démonizuje (fork()+setsid()v dítěti,stdin→/dev/null, viz sekce „Daemonizace" ubind()/listen()) — přesně stejný trik jako Shizuku (nohup/setsidvstart.sh).setsid()odpojí proces odadb shellu ovládajícího terminálu/session → je imunní vůči SIGHUP při zavření adbd spojení. Cgroup teorie níže (kdyby platila) bysetsid()nevyřešil (cgroup ≠ session), ale empiricky proces PŘEŽÍVÁ, takže buď cgroup vůbec neumírá při pouhém vypnutí wireless debugging (jen SIGHUP by bezsetsid()zabil), nebo je teorie z 2026-09-25 mylná. Původní text (nechán jako historie/pro referenci, NEŘÍDIT se jím):proces spuštěný přesadb shellžije v adbd session cgroup (/sys/fs/cgroup/uid_0/pid_<adbd>); vypnutí wireless debugging ukončí adbd → cgroup se zabije → daemon umře. Trvalé přežití = spustit/přesunout daemona do root cgroup (su 2000 -c …nebo zápis do/sys/fs/cgroup/cgroup.procsjako root) +oom_score_adj=-1000. Bez roota nelze. - Diagnostika:
ashell -c 'curl -s 127.0.0.1:1337/shelldaemon/info'(nezávislé na adb), PID file/data/local/tmp/shelldaemon.pid.
10. Diagnostika a CLI
nethunter-log [-n N] [-g VZOR]— barevný logcat (V šedá, D modrá, I zelená, W žlutá, E/F červená;error/failčerveně,success/establishedzeleně). API:GET /app/logs?limit=N.vpn-cli status|start|stop|logs [json]|mitm …|bypass …→127.0.0.1:1337.- Unified
nh <kategorie> <akce>(symlinkynethunter-*/vpn-*ještě fungují):system,network,vpn,agent,device,fix permission,distro,desktop,float, … — celý seznam vREADME.md. nh distro list|ps|kill|remove|backup|restorevyžaduje u destruktivních--force.
11. Historie oprav — co nevrátit zpět (pitfalls)
VPN / logy: UDP/443 drop jen při MITM ON; první DNS UDP write v BLOCKING režimu (API>33 vrací 0);
SYN_RECEIVED timeout 15 s; elapsedTime z reálného času (žádný random); ProcessResolver čte celý
/proc/net/tcp, ne /proc/self; bytesSent/Received ze TcpSession; getConnectionOwnerUid z user
app nefunguje → -1/„Unknown App".
Proxy: SOCKS5 rotace (pool + režimy) odstraněna → jediný custom IP:Port
(VpnProxyManager.setCustomProxy), volitelný, fallback na direct.
su_wrapper shadow musí být idempotentní při KAŽDÉM startu: ProotManager (deploySuWrapper)
přejmenovává /usr/bin/{su,sudo} → .orig, aby sudo/su v guestu šly přes su_wrapper
(/usr/local/bin/sudo). Guard nesmí být !origBackup.exists() (jen "poprvé") — apt umí
sudo/su kdykoliv přeinstalovat jako závislost něčeho jiného a tiše obnovit /usr/bin/sudo,
čímž náš wrapper odstíní (PATH ho najde dřív). Výsledek: sudo <cmd> tiše běží jen pod fake-root
PRootem (žádná skutečná eskalace, žádná chybová hláška), a spadne až na hostitelsky vlastněných
cestách (/data/app, …) s Permission denied. Fix: if (origBin.exists()) bez druhé podmínky,
přepsat .orig pokaždé znovu. Zdrojová diagnóza vyžadovala strncpy/which sudo/PATH pořadí
kontrolu — /bin (merged-usr symlink na /usr/bin) před /usr/local/bin v PATH má stejný efekt,
i kdyby byl shadow OK.
PRoot/perf: seccomp je aktivní (proot -V → seccomp_filter = yes) — ne „vypnutý"; --link2symlink
nutné (apt/dpkg zálohy přes link()); -0 kvůli fake root UX; tracer cost ~100 µs/syscall je intrinsický
(ne degradace live vs. fresh); LD_LIBRARY_PATH v hostShellEnv() způsoboval SIGBUS — nikdy
nepřidávat; /usr/sbin/find musí být symlink na find (ne rg).
PRoot verze — tmux/multiplexery vyžadují ≥ v5.1.107.91: staré v5.1.107.90 desynchronizuje
syscall tracer state machine na aarch64 zařízeních se starým 4.x kernelem (arm64 before v5.3, tj.
většina Android telefonů) při emulaci syscallů, které PRoot cancelluje/fejkuje (upstream fix
61681c64, „syscall: don't wait for a sysenter stop the kernel skips"). PROOT_TAG je teď
v5.1.107.93 — nevracet zpět na .90.
PRoot USERLAND mode (fake_id0) nelze použít — ničí PTY/tmux: USERLAND mode (#define USERLAND
v fake_id0/config.h) zabraňuje SELinux audit bouři (comm="proot" setattr proc:dir), ale způsobuje
jinou regresi: ioctl(fd, TCGETS) selže pro jakýkoli fd otevřený přes open() uvnitř proot —
tj. isatty() = 0 pro /dev/tty, /dev/pts/N, vše otevřené nově. Zděděný fd 0 funguje,
nově otevřené PTY fdy ne → tmux new → "open terminal failed: not a terminal"; tmux < /dev/tty
→ "can't use /dev/tty". setsid ani < /dev/tty redirect nepomůže.
boot kopíruje referenční příkazy proot-distro (docs/proot-cmd-mod.md, 2026-09-24):
proot se spouští přes env -i $(guest_env) "$PROOT" …: guest nedědí prostředí appky, jen
explicitní seznam proměnných (navíc proti referenci LANG/LC_CTYPE, NETHUNTER_SESSION_ID,
NH_DISTRO pro guest nh). Flagy --kill-on-exit --link2symlink -L --change-id=0:0 (+ --sysvipc,
--kernel-release mimo M). -b /dev/urandom:/dev/random vždy (mimo M). Storage = jeden zdroj
/storage/self/primary bindnutý do /mnt/sdcard, /sdcard, /storage/emulated/0,
/storage/self/primary, bez bindu celého /storage (gate NH_MOUNT_STORAGE = přepínač
„Shared Storage" v MainActivity). Navíc proti referenci jen ipc/share a uživatelské
NH_EXTRA_MOUNTS/--bind.
CLI flagy boot (kdekoli před --, za -- se nesahá): -d/-i/-m (mód, přebije env),
-b src:dest / --bind src:dest / --bind=src:dest, --shared-tmp (-b $FILES_DIR/tmp:/tmp
ve všech módech; appka ho přidá z Root Bridge přepínače shared_tmp, pro sudo přes root_env).
Výpočet módu je ve funkci apply_mode(), kterou main() volá až po parsování argumentů.
tmux „not a terminal" z terminálu appky: mód M funguje, plný D ne. Tmux server dostane
pts fd přes SCM_RIGHTS a isatty() na něm selže. Příčina v D zatím není potvrzená. Samotné
odebrání samostatných storage bindů problém nevyřešilo. Proot binárka ani env (env -i ve stejné
session) to nejsou. Diagnostické helpery v guestu: scmtest, ptsdiag, prootcmd, tmuxdiag.
SELinux fix (aktuální) — --wrap=chmod linker-level wrapper: proot binary má 4 volání
chmod@plt z různých .c souborů. Per-file #define nestačí. Správné řešení: selinux_android_fix.c
s __wrap_chmod + -Wl,--wrap=chmod v LDFLAGS → linker přesměruje VŠECHNA chmod volání
v proot binary přes wrapper, který přeskočí /proc a /sys. NON-USERLAND mode zachován →
tmux a PTY fungují.
Viz _SELINUX_FIX_C + selinux_fix_o v tools/modal_build.py::_build_proot_one_arch().
Detekce binárky: NON-USERLAND proot má .l2s. string (USERLAND měl .proot.l2s.).
Druhý zdroj audit bouře — execute, ne setattr (2026-09-27, fatální reboot zařízení):
--wrap=chmod řeší jen setattr proc:dir. Nezávisle na tom každý exec řetěz v PRootu
(proot → ld-linux-aarch64.so.1 → loader → binárka) generuje vlastní
avc: granted { execute } na untrusted_app_27:app_data_file:file — to je kernelová
LSM hook na execve(), nejde to obejít v proot/loader kódu. Pod PRoot workloadem to
zahltí audit_backlog_limit=64/audit_rate_limit=5 (dmesg: audit_lost=21095,
rate limit exceeded). audit_log_start() je synchronní kernelová cesta sdílená
VŠEMI procesy v systému → přeplněná fronta dokázala zaseknout i system_server
(pozorován MIUI FW_SCOUT_HANG na hwbinder volání hned po pádu), ne jen appku.
OPRAVA (2026-09-27, ověřeno na zařízení — recidiva freezu, modul nikdy nebyl nainstalovaný):
dontaudit untrusted_app_27 app_data_file:file { execute execute_no_trans }
(magisk-modules/audit_flood_fix/sepolicy.rule) je proti tomuhle bezúčinný —
dontaudit potlačuje logování ZAMÍTNUTÝCH přístupů, ale execute je tu ALLOW;
jeho logování řídí samostatný auditallow untrusted_app_27 app_data_file:file { execute execute_no_trans } v MIUI vendor policy (magiskpolicy --print-rules
ho ukázal souběžně s dontaudit — nesouvisí s žádným naším modulem, dontaudit
ho nepřebije). Skutečný fix: auditctl -r 1000 (Android auditctl na tomto
zařízení podporuje jen -r rate, ne -b backlog) — zvedne audit_rate_limit
z defaultní 5 msg/s, což je pro PRoot execve-řetězec workload hluboko
nedostatečné. Ověřeno: po auditctl -r 1000 žádný další audit_lost i pod
zátěží (40× exec v proot), zatímco dontaudit fix (aplikovaný živě přes
magiskpolicy --live) na běžící storm nic nezměnil. audit_flood_fix/service.sh
teď dělá obojí (dontaudit pro starý setattr mechanismus + auditctl -r 1000
jako hlavní fix), čeká na sys.boot_completed. META-INF zkopírováno ze
sesterského anti_phantom modulu. Modul samotný ale nikdy nebyl nainstalovaný
do /data/adb/modules/ (jen v repu) — proto se freeze zopakoval; při
podobném incidentu nejdřív zkontrolovat ls /data/adb/modules/audit_flood_fix.
Boot módy D/I/M + fake sys: NH_ISOLATED a NH_MINIMAL jsou nezávislé flagy
(D=0/0, I=1/0, M=1/1) — I není minimal, i když starší docs/proot-cmd-mod.md
tvrdil opak. Fake /proc + /sys overlay (sysdata, sys_empty:/sys/fs/selinux,
--kernel-release) je gate-ovaný na NH_MINIMAL=0 a NH_FAKE_SYS=1; reálný -b /sys
je v základním řádku build_binds() ve všech módech. NH_FAKE_SYS=0
(RootBridge → „Fake /proc & /sys", pref bind_fake_sys) dá guestu skutečný
kernel//proc//sys, ale zachová --sysvipc + /dev fixes (Frida). Pref bind_data
přidá nativní bind /data — obsah je čitelný jen v sudo seanci (su_daemon re-entry),
nikdy se nedělá hostitelský mount --bind (leak mountů do globálního namespace).
Root Bridge extra bindy jdou na nativní cesty (2026-09-25): -b /system, -b /vendor,
-b /data/local/tmp, -b /config/usb_gadget, -b /data/misc/bluetooth,
-b /data/user/0/com.linux_core, -b /data/user/0/com.kali.aiassistant, -b /data (ne pro
docker/Termux image). Žádné /mnt/*. /dev/bus/usb a /sys/class/bluetooth samostatný bind
nemají (/dev a /sys jsou bindnuté vždy). boot dedupe_binds() vynechá bind se stejným
zdrojem i cílem, který už přidal mód (např. /system v D). Guest env: PROOT_TMP_DIR se v guest
wrapperu unsetuje (zbyl by vedle TMPDIR), EXTERNAL_STORAGE=/storage/emulated/0 jen při
NH_MOUNT_STORAGE=1.
Preset rootfs z zombiegirlcz/ROOTFS-for-proot (2026-09-25): appka skripty nespouští,
jen z nich regexem čte TARBALL_URL['<arch>'] (jen přesná arch, žádný fallback), SHA256 a heredocy
bootstrap.sh, root/entrypoint.sh, .nh/manifest (RemoteRootfsCatalog.kt, zápis v
RootfsManager.pullRemoteDistroScript). boot_docker čte /.nh/manifest (NH_SHELL,
NH_ENTRYPOINT, NH_BOOTSTRAP jednou se značkou /.nh/bootstrap.done, NH_PATH, NH_WORKDIR,
NH_ENV, NH_BIND); cesty ověřuje rootfs_resolve (symlinky uvnitř rootfs, ne na hostu). Login
shell vždy -l (busybox/dash --login neznají). ProotManager do docker image nenasazuje debianí
bootstrap/entrypoint/zshrc/profil (jen s NH_INTEGRATION=full). Extrakce docker image: hardlink =
kopie zdroje (linkName je od kořene archivu), procházení stromu jen přes nio bez symlinků;
resolv.conf symlink se nahradí souborem. Mazání rootfs jen RootfsManager.deleteRootfsTree()
(nenásleduje symlinky — File.deleteRecursively() přes absolutní symlink leze na host).
Spec + validátor + prompt denního agenta: ROOTFS-for-proot/AGENTS.md, tools/validate.py.
Výkon PRoot = affinity, ne flagy (2026-09-25): každý trasovaný syscall ~320 µs (i
fstat/getcwd; netrasovaný getpid 0,5 µs) = latence ptrace výměny mezi jádry. Flagy
(-L, --sysvipc, --change-id, počet bindů) jsou v šumu, PROOT_NO_SECCOMP=1 3× horší.
Pin proot + guestu na jedno velké jádro = 3–5× rychlejší → NH_CPU_PIN (boot
cpu_pin_apply, stav $FILES_DIR/nh/cpu/pin.<pid>, hlídač obnovuje pin po změně cpusetu;
free.<pid> = PIDy z nh cpu all a z CPU_ALL whitelistu — hlídač ho čte z
/proc/<pid>/environ guest procesů, cesty v něm relativně k rootfs z -r v cmdline proot), pref boot_modes/cpu_pin_<kali|parrot|docker>, ikona
CpuPinToggle na kartě distra, nh cpu. Hlídač se spouští dvojitým forkem — dítě procesu,
který pak exec-ne proot, by proot sklízel jako neznámý tracee.
Hlídač čte /proc/<pid>/{cmdline,environ} jen přes proc_lines() (dd count=1 + timeout),
nikdy tr … < /proc/…: u zaniklého PID se toybox tr zacyklí na chybě read() (~50 % jádra)
a hlídač visí navždy (2026-09-27, osiřelé tr/head pod boot).
nh cpu core <N|auto> [distro] (2026-09-27): persistentní manuální override jádra,
odděleně od nh cpu pin [N] (ten je jen živý/session-only, nepřežije boot). Motivace: nh cpu bench
naměřil v jedné session „little 2× rychlejší", v jiné „big 1.18× rychlejší" — rozdíl byl kontaminace
běžící AI-agent session (Claude Code sám běží ve stejném guest cpuset jako proot, nh cpu trace
ho ukázal jako 47–51 % šumu na pinovaném jádru, nh cpu isolate ho nezachytí — noise_patterns
v cpu_isolate() je natvrdo daný seznam known daemonů, ne obecná detekce). Uživatel chtěl způsob,
jak automatiku (cpu_pick_core()/cpu_fastest(), heuristika „nejnižší cpuinfo_max_freq") natvrdo
přebít a mít jistotu, že se jádro nebude nikdy přepínat pod ním. Implementace: BootModePersistence.kt
(loadCpuPinCore/saveCpuPinCore, null = automatika, SharedPreferences -1 sentinel), ProotManager.kt
(NH_CPU_PIN_CORE env — mechanismus v boot's cpu_pin_apply() uz existoval,
core=${NH_CPU_PIN_CORE:-$(cpu_pick_core)}, jen nebyl nikde vystavený uživateli), LocalApiServer.kt
(/distro/cpupin GET/POST rozšířeno o "core": int|null, POST teď akceptuje enabled NEBO core
samostatně — nemusí se posílat obojí), assets/nh (cpu_core() + dispatch core) + cpu_status()
zobrazuje manuální/automatický stav). Neplést s nh cpu pin [N] — ten mění jen běžící session,
nh cpu core mění perzistentní volbu pro příští booty daného distra.
Magisk modul nh_cpuctl (volitelný, root): statická binárka cpuctl
(app/src/main/cpp/cpuctl.c, výstup magisk-modules/nh_cpuctl/system/bin/cpuctl).
service.sh po bootu spustí cpuctl daemon — netlink proc connector (EXEC/FORK eventy)
okamžitě zachytí nové procesy a aplikuje session masku / CPU_ALL, s fallbackem na /proc
scan. Heartbeat $FILES_DIR/nh/cpu/cpuctld (<PID> <unix_ts>, každých 5 s); boot
cpu_pin_apply ho čte — čerstvý heartbeat (<15 s) → mksh hlídač se nespouští.
cpuctl boost on|off [N] nastaví scaling_min_freq=max pro policy jádra N (přežije jen
do rebootu). cpuctl pin <hexmask> <pid> one-shot. cpuctl status vše.
nh cpu boost on|off|status → sudo /system/bin/cpuctl boost ….
Řízení CPU cizích aplikací (cpuctl apps/app-pin, 2026-09-27): cpuctl apps [N] čte
per-balíček CPU% (2 vzorky 1 s od sebe z /proc/<pid>/stat utime+stime), aktuální masku a
uid; jen aplikace (uid >= 10000, konstanta APP_UID_MIN) — systémové procesy se nikdy
nečtou ani nepinují. cpuctl app-pin <balíček> <hexmask|off> uloží pravidlo do
$CPU_DIR/app.<balíček> a hned pinuje všechny běžící PIDy balíčku (pin_pkg); daemon pravidla
načítá v HB smyčce (scan_apprules), aplikuje na nové procesy (handle_new_app v handle_new)
a obnovuje po změně cpusetu (repin_apps). Balíček = první token /proc/<pid>/cmdline bez
:subprocess. Vystaveno: nh cpu apps [N] / nh cpu app <balíček> <jádra|off> (guest, přes
sudo), API GET /cpu/apps + POST /cpu/apps/pin ({package, cores:"0-3"|null}, Bearer +
localhost gate — v sensitiveEndpoints), a UI CpuAppsActivity (spouští se z CPU řádku v
services panelu, volá cpuctl přes su -c jako runCpuBoost). Neplést nh cpu app (cizí
appka) s nh cpu core/pin (proot session).
cpuctl binárka musí jít spolu se zdrojem (2026-09-27): apps/app-pin byly v cpuctl.c
commitnuté bez přebuildu a modul na telefonu zůstal v1.0, která je nezná →
nh cpu apps/app i tools/usage-cpu.sh modul „neviděly" (přebuild až f8ab7e7). Od v1.1 má
cpuctl version (CPUCTL_VERSION = module.prop version); nh (cpu_ctl_new) a test podle
něj (fallback: app-pin v nápovědě) hlásí starou binárku. Po změně cpuctl.c: zsh mbuild native → commit binárky → navýšit module.prop →
python3 magisk-modules/magiskb.py nh_cpuctl → flash + reboot.
Non-root měření (cpu_apps_raw v assets/nh, nh cpu apps/appmon): tři úrovně —
app uid nevidí cizí /proc (hidepid), uid 2000 (ashell adb, dumpsys cpuinfo) měří ale
nepinuje, root (nh_cpuctl) obojí. cpu_apps_raw vrací TSV a kódem zdroj (0 root / 2 non-root
dumpsys / 1 nic); cpu_apps tiskne tabulku, cpu_appmon živé bary (čistý sh+awk, bez python).
Přidělení jader cizí appce je fyzicky root-only (sched_setaffinity na cizí uid = CAP_SYS_NICE);
non-root pinování nelze — neslibovat ho v UI ani CLI.
sudo dědí nastavení přes soubor: su_daemon execv-ne boot -- cmd pod rootem, ale
dítě dědí jen prostředí daemonu (žádné NH_*) → sudo session by měla prázdné
/data a fake uname -r. Proto ProotManager zapisuje $FILES_DIR/nh/root_env
(NH_ISOLATED/MINIMAL/FAKE_SYS/MOUNT_STORAGE/EXTRA_MOUNTS) a boot ho nasourceuje,
jen když NH_ENV_FROM_APP != 1 (appka předává hodnoty přes env, ty mají přednost).
X server / desktop: bývalý :linux-x11 modul s assetem usr/lib/linux-x11 (libXlorie.so z Termux-X11) byl odstraněn (2026-09-11) — byla to JNI knihovna bez main, nespustitelná samostatně. Desktop jede na Xvfb (apt install xvfb, nh desktop start): display :0 → TCP 6000, -ac -listen tcp; MIT-SHM funguje i přes loopback TCP (ověřeno). Renderuje externí app kali_GUI (com.linux_core.xlauncher, X11Client + XTEST). ProotManager.removeLegacyLinuxX11() uklidí staré ~20 MB artefakty z files/usr/{bin,lib}.
git pod prootem (link2symlink): PRoot -L mění git hardlinky (pack/idx/rev i loose objekty)
na symlinky do $ROOTFS/.l2s. Když se .l2s vyčistí (nová session / přepnutí módu), symlinky
osiří → invalid object … Not a directory a rozbitý repo (postihuje VŠECHNA repa v rootfs).
Pojistka: nh fix git [path] materializuje symlinky na reálné soubory (přes host bind App Data /data/user/0/com.linux_core, fallback /mnt/app,
kde jsou symlinky vidět; vyžaduje bind_aiapp). Pouštět po git clone/gc/repack pod prootem.
ShellDaemonClient.kt ↔ shell_daemon.c byl endianness-broken od začátku (2026-09-27):
Kotlinovo DataOutputStream.writeInt/writeLong a DataInputStream.readInt jsou podle Java
kontraktu VŽDY big-endian, ale shell_daemon.c čte/píše uint32_t/uint64_t čistě nativně
(read_all(fd, &x, sizeof(x)), žádné ntohl/htonl) — na aarch64 little-endian. Důsledek: KAŽDÉ
volání exec()/install()/stopDaemon()'s SH_MODE_STOP z appky posílalo magii SHLL
byte-prohozenou (0x4c4c4853 místo 0x53484c4c); démon to zalogoval jako spatny magic a zavřel
socket bez odpovědi (appka ještě psala zbytek požadavku → "Broken pipe"/"Connection reset").
Interaktivní --attach cesta (ashell adb shell bez args → libshelldaemon.so --attach, C-to-C)
tím postižena NEBYLA (nativní klient, stejná endianness na obou stranách) — proto bug přežil
nepovšimnutý, non-interaktivní ashell adb <cmd>//shelldaemon/exec vždy padal na fallback.
Fix: writeIntLE/writeLongLE/readIntLE helpery v ShellDaemonClient.kt (manuální bajt-po-bajtu
LE write/read) namísto writeInt/writeLong/readInt. Nevracet zpět na DataOutputStream.writeInt
pro cokoliv, co jde na shell_daemon.c socket — ten protokol je a zůstává nativní (LE), ne network
byte order. Diagnostika: restartovat démon s > logfile 2>&1 místo > /dev/null 2>&1 (ashell adb start
default přesměrovává stderr do /dev/null, takže spatny magic/crash hlášky jsou jinak ztracené).
su_daemon / Root Bridge: fork-per-connection (parent hned accept(), žádné blokování nových sudo),
POLLHUP → SIGKILL command childa, config v g_* globálech, ignorovat SIGPIPE, pkill -x (ne -f),
fail-closed bez launcheru (_exit(126)), re-entry do PRoot místo host chroot (ochrana proti
host-globálním příkazům), ownership fix nftw+lchown s vynecháním bind dirů
(dev proc sys run sdcard mnt system vendor product apex storage data), @FIX režim + nh fix permission.
Terminál: paste přes emulator.paste() (bracketed paste, ESC/C1 sanitizace), ne session.write();
spawn s LANG=C.UTF-8/LC_CTYPE=C.UTF-8 (glibc ≥ 2.35); MIUI multi-input = debounce
updateSuggestions() (žádné synchronní Button() v IME commitText).
Bionic usrtools: všechny 4 nástroje Bionic (glibc v app kontextu padá na seccomp rseq → SIGSYS;
v guestu to maskuje PRootův seccomp filtr). Deploy musí mít exec bit + version gate.
Auto-start: RECEIVE_BOOT_COMPLETED + .core.BootReceiver (BOOT_COMPLETED, MY_PACKAGE_REPLACED;
ne LOCKED_BOOT_COMPLETED — filesDir je credential-encrypted), TerminalService START_STICKY
restart s dedupem (jedna cron session) a backoffem; toggle boot_autostart.
ashell je od 2026-09-27 nativní binárka, ne /bin/sh skript (app/src/main/cpp/ashell.c,
build v _build_native_bin v tools/modal_build.py, -static stejným aarch64-linux-android24-clang
toolchainem jako su_wrapper/ashell_pty — bionic binárka běžně běží i v glibc guestu, PRoot je
ptrace-based a ABI trasovaného procesu nerozlišuje). Motivace: starý skript dělal curl+python3 na
KAŽDÉ volání (api_call()/daemon_exec()) a psal si dočasný .py klient pro ashell_pty protokol —
každý ten fork+exec generoval vlastní avc: granted { execute } (viz „Druhý zdroj audit bouře" výše);
-c dělal 1 extra exec (python3 pro PTY bridge), ashell adb <cmd> dělal 2 (curl+python3). Nativní
klient mluví HTTP (127.0.0.1:1337) i binární ashell_pty protokol (127.0.0.1:13340, framing
0x01 STDIN/0x02 STDOUT/0x03 WINCH/0x04 EXIT/0x05 HELLO/0x06 STDIN_EOF) přímo raw sockety — 0 extra
execů na hot paths. CLI grammar zachována 1:1 (-c, adb start/stop/status/shell/<cmd>/install/ uninstall/push/pull/devices/help, --add/--remove/--list/-e, bare = host shell) + vedoucí boolean vlajky
-v/--verbose (debug marker, nebo env ASHELL_DEBUG=1) a -t/--tty (vynuť serverový PTY). Oddělené
i slepené getopt clustery (-vtc, -tc, -vc, -ve); terminální -c/-e berou argument → ve slepeném
clusteru MUSÍ být poslední, opačné pořadí (-cv) NE. -v → ashell -c napíše na stderr, kterou cestou šel:
via PTY (…13340…) vs. via HTTP fallback (/shell) (marker na stderr, stdout čistý).
ashell -c default = PIPE režim (ssh model, interactive=0 v HELLO), ne PTY — levnější (žádná
kernelová tty line-discipline, čistý výstup, žádný per-loop traced ioctl pod prootem). PTY se vyžádá jen
-t (ashell -tc htop) — interaktivní TUI bez -t spadnou na „not a terminal". Nevracet zpět na
interactive = isatty(stdin) (over-selektovalo PTY pro každý příkaz z terminálu). Relay smyčka v
run_via_ashell_pty je event-driven: blokující select bez timeoutu + SIGWINCH handler (bez
SA_RESTART → EINTR přepošle velikost okna) — žádný 200ms polling ani per-loop TIOCGWINSZ, takže když
příkaz tiše běží, klient (pod prootem) nedělá žádné trasované syscally. Nízko-frekventní
větve (adb start/stop, -e editor, otevření PTY okna) klidně používají system()/execvp — nejsou
hot path. JSON parsing je ručně napsaný minimální extraktor (jen pro known ploché tvary odpovědí
tohoto projektu, ne obecný parser) — nerozšiřovat na obecné vnořené struktury bez rozmyslu.
Testováno lokální kompilací v guestu (glibc, jen pro validaci — oficiální artefakt musí přes Modal).
Launcher: flag -E pro proot neexistuje — LD_PRELOAD/PROOT_LOADER se v guestu řeší přes
/bin/sh -c 'unset LD_PRELOAD PROOT_LOADER; exec "$@"' před prvním exec.
12. Známé technické dluhy
- MITM je historicky nestabilní (padá do passthrough na
SSLExceptionv unwrapu) — vždy ověř na zařízení. - Widget zakomentován v manifestu („pro later").
- DNS tab prakticky prázdný (moderní Android jede DoH/TCP, ne UDP/53).
app/src/main/assets/certs/mitm-ca.p12chybí (je jen.crt) — build projde díky debug fallbacku.- Cert piny expirují 2027-12-31 → pak obnovit SHA-256 v
network_security_config.xml.
