Ako som zo Samsungu S20 Ultra spravil Linux handheld bez oficiálnej podpory
Prakticky postavené na telefóne, ktorý na to oficiálne nemal žiadnu podporu — a takmer žiadnu online dokumentáciu.
Prečo tento projekt vznikol
Starý Samsung Galaxy S20 Ultra mi ležal doma bez väčšieho využitia. Namiesto toho, aby skončil v šuplíku, som sa rozhodol spraviť z neho niečo, čo sa dá reálne používať do ruky — plnohodnotný Linux/pentesting handheld, nie hračku na víkend.
Kali NetHunter má oficiálnu referenciu len pre Note20 Ultra (Exynos 990, kódové meno c2s). Galaxy S20 Ultra beží na inom čipe (z3s) a oficiálnu podporu nemá vôbec. Keď som hľadal, čo už existuje, narazil som na jednu staršiu komunitnú predchodkyňu pre presne tento čip — je ale z roku 2023, cieli na Android 13, flashuje sa cez TWRP a jej vlastný popis priznáva len čiastočnú funkčnosť (Bluetooth s výhradou, jeden otestovaný WiFi adaptér, USB Arsenal nefunkčný). V diskusiách som zase videl ľudí na aktuálnej LineageOS 23/Android 16 vetve pre tento telefón, ktorí funkčný NetHunter kernel hľadali a nenašli.
Takže nie prázdne pole, ale reálna medzera pre aktuálnu verziu — presne to, čo som napokon riešil.
Kernel port
Základ je presná zdrojová revízia z nainštalovaného LineageOS 23.2 buildu. Na nej som postupne opravil tri konkrétne kernel bugy, ktoré sa prejavili až v reálnej prevádzke, nie pri čítaní kódu:
- Bluetooth crash pri
hciconfig—hci_sock_create/bind/release/ioctl/getnameboli v zdroji obalené v#if 0s chybným fallbackomreturn 0, takžehci_sock_createnikdy nevolalsk_alloc. Bez tejto opravy padal kernel pri akomkoľvek pokuse o BT diagnostiku. - Nepodporovaný BT príkaz —
bredr_setup()posielalHCI_OP_SET_EVENT_FLT, ktorý môj testovací CSR8510 dongle nepodporuje. Kernel bral odpoveď "Unknown HCI Command" ako fatálnu chybu namiesto toho, aby ju ignoroval. Stačilo odstrániť redundantný príkaz. - USB sieťový gadget (NCM) — tri NULL-pointer chyby vo
f_ncm.c. Po oprave kernel-level časť funguje, ale ADB a sieťový gadget naraz aj tak nejdú —UsbService.setCurrentFunctionsv Android frameworku túto kombináciu explicitne odmieta (IllegalArgumentException). To už nie je kernel bug, ale vedomé rozhodnutie Androidu, ktoré sa opraviť nedá.
Každý build (baseline → jednotlivé opravy → finálny konsolidovaný kernel) je pred flashnutím overený SHA-256 checksumom a validáciou na reálnom telefóne — žiadny "skús a uvidíš" krok. Magisk namiesto TWRP znamená, že sa dá vždy vrátiť k pôvodnému boot.img, ktorý mám odložený bokom presne pre tento účel.
DeX nejde — a prečo som sa ho vzdal
Na stock Samsung ROM cez ten istý dock a kábel DeX fungoval bez problémov. Na LineageOS/NetHunter kerneli nie — a chvíľu mi trvalo pochopiť prečo, kým som sa nedostal k presnému mechanizmu v dmesg.
DisplayPort link sa nadviaže, video sa čiastočne rozbehne (rozlíšenie 1920×1080 sa naviaže korektne), ale presne pri HDCP 2.2 negociácii (dpcd_read_for_hdcp22) príde AUX_TRAN_START waiting timeout — opakovane, každých ~200-230 sekúnd, celý cyklus sa odpojí a začne odznova. Vypnutie HDCP na strane samotného monitora nemalo žiadny vplyv, čím som vylúčil, že problém je v monitore alebo kábli — je čisto v telefóne.
Žiadny runtime prepínač na obídenie neexistuje (skúšal som sysfs uzly aj kernel module parametre — nič). Riešenie by vyžadovalo zásah priamo do displayport driveru, pravdepodobne v komunikácii s TrustZone/TEE, keďže HDCP 2.2 kľúče bežne idú cez secure world applet, ktorý sa na custom boote možno nesprávne inicializuje. Navyše — aj po prípadnej oprave HDCP by LineageOS aj tak nemal Samsungov proprietárny DeX framework, takže skutočný DeX zážitok by aj tak nebol dostupný.
Namiesto naháňania tohto konkrétneho bugu som sa rozhodol pre iný cieľ — nie DeX, ale natívny Android desktop mód. Ten problém úplne obchádza, pretože nezávisí od HDCP negociácie rovnakým spôsobom. Viac nižšie.
Natívny Kali chroot
Nad kernelom beží plný Kali NetHunter chroot (nie odľahčená Termux-rootless náhrada), s dvoma vlastnými opravami, ktoré ho robia reálne použiteľným:
- Detekcia mountpointu v appkinych vlastných skriptoch (
bootkali_init,killkali) hlásila falošné negatíva —busybox mountpoint -qv tomto prostredí nefunguje spoľahlivo, nahradené priamym čítaním z/proc/self/mountinfo. - Android cgroup2 (
/sys/fs/cgroup) som zviazal priamo do chrootu — potrebné pre akýkoľvek kontajnerový runtime vnútri chrootu, bez toho Docker/Podman v chroote vôbec nenaštartuje.
K tomu trvalý SSH prístup cez verejný kľúč (heslové aj interaktívne prihlásenie vypnuté), a vlastný USB gadget nastavený na každom boote — ConfigFS HID klávesnica, ADB a sériová konzola naraz na jednom porte. Autostart sshd v chroote mal svoj vlastný, menej zjavný problém: /data/data/com.offsec.nethunter je šifrované úložisko, ktoré neexistuje, kým sa telefón po boote aspoň raz neodomkne — a to je ľudská akcia bez pevného časového limitu. Pôvodný skript s 5-minútovým timeoutom sa vzdával príliš skoro; teraz čaká na odomknutie bez časového obmedzenia.
Android desktop mód (nie Samsung DeX)
Toto bola posledná a najzaujímavejšia časť. LineageOS neobsahuje Samsungov proprietárny DeX framework vôbec — takže cieľ bol iný: rozbehať natívne AOSP "connected displays" okná na externom monitore cez USB-C dock, ako to má robiť samotný Android, nie ako náhradu DeXu.
Vyžadovalo to dve veci naraz: perzistentné ro.build.characteristics=tablet (nastavené trvalo cez Magisk), a rozhodujúci Desktop Experience developer-preview prepínač (persist.wm.debug.desktop_experience_devopts). Po jeho zapnutí a reštarte system_server dostal externý monitor skutočné okná, taskbar aj vlastnú plochu — nezávislú od uzamknutej plochy telefónu, aj po reštarte telefónu.
Ako launcher pre ikony na externej ploche som použil appku Smart Dock, ale samotné okná a taskbar riadi priamo AOSP shell. Dva launchery sa spočiatku prekrývali — Smart Dock vedel vykresliť duplicitný overlay panel nad tým AOSP vytvoril sám. Vyriešil som to malým watchdog skriptom, ktorý zistí duplicitný panel (parsovaním dumpsys window windows) a pošle naň syntetické swipe gesto smerom dole — presne to isté gesto, čo urobí prst pri manuálnom zatvorení, zámerne namiesto klikacieho tlačidla, lebo swipe nemá opačný (toggle) efekt, ktorý by mohol stav omylom prepnúť naspäť.
Výsledok: telefón funguje ako samostatné zariadenie s vlastnou uzamknutou plochou, a súčasne beží ako plnohodnotná druhá pracovná plocha na externom monitore, keď ho pripojím k docku — s vlastným launcherom, ikonami na ploche a myšou smerovanou na správny displej.
Čo je hotové, čo nie je
Fungujúce a overené: Wi-Fi, Bluetooth, ADB, Magisk root, normálny Android boot cez reboot, natívny Kali terminál, Nmap/Metasploit/OpenSSH v chroote, externé USB Wi-Fi adaptéry s monitor módom, desktop mód prežívajúci reštart telefónu.
Čo nefunguje alebo je vedomý kompromis:
- Interná Broadcom WiFi karta monitor mód nepodporuje vôbec — treba externý adaptér.
- Chýba driver pre USB mass storage gadget (
CONFIG_USB_F_MASS_STORAGE) — telefón nevie ponúknuť súbory ako USB disk. - Chýba UVC driver — externé USB kamery majú funkčný zvuk aj úložisko, ale žiadne video.
- USB Arsenal/HID naživo neprepínam, lebo by to odrezalo jediný ADB kanál.
- Otváranie NetHunter appky mení SELinux z Enforcing na Permissive, čo je vedomý bezpečnostný kompromis, nie prehliadnutie.
- DeX (vyššie) zostáva nefunkčný — a asi taký aj ostane.
Zdrojový kód
Celý build (skripty, chroot/module opravy, on-device automatizácia, aj poľná príručka) je verejne na GitHube: github.com/stanleysvk87/s20-nethunter-port. Boot images sú tam ako GitHub Release, keďže presahujú limit na veľkosť súboru priamo v repe.
Nepíšem to len pre seba — ak má niekto ten istý telefón a narazil na rovnakú medzeru ako ja, dúfam že mu to ušetrí čas. Toto je len samotný build — čo všetko sa z takto zrootovaného telefónu dá ešte vytiahnuť (a ako som ho napokon aj zverejnil) je na ďalší text.