Wednesday, May 20, 2026

Lesk a bieda zodpovedného oznamovania zraniteľností

Tento príbeh nadväzuje na naše nahlasovanie bezpečnostných zraniteľností z roku 2019, kde sa nám podarilo nájsť kritickú zraniteľnosť v slovenskom eID klientovi. Tentokrát sme sa znovu rozhodli pozrieť na bezpečnostnú hygienu štátneho SW súvisiaceho s vytváraním elektronického podpisu.

Mnohí z Vás vedia, že nájdenie zraniteľnosti v softvéri je iba začiatkom celého procesu. Rovnako dôležité ako odhalenie chyby je to, čo nasleduje po tom: nahlásenie zraniteľnosti, vyhodnotenie zraniteľnosti, určenie závažnosti, prijatie nápravných opatrení, prípadne overenie odstránenia zraniteľnosti. Tento článok bude o našich praktických skúsenostiach s nahlasovaním zraniteľností v Čechách a na Slovensku.

Odporúčanie pre slovenských občanov: Aplikáciu D.Launcher 2 aktualizujte minimálne na verziu 2.0.7.

Pre českých občanov: V aplikácii eObčanka-Identifikace na macOS prejdite na aktuálnu verziu 3.6.0, alebo ju, ak môžete, jednoducho odinštalujte. eObčanka-Identifikace na macOS umožňuje útočníkovi spustiť kód na vašom počítači po kliknutí na podstrčený odkaz a potvrdení otvorenia aplikácie.

Prečo sme sa rozhodli hľadať zraniteľnosti v štátnom SW?

Tieto ciele sú zaujímavé najmä z pohľadu toho, že štát nepriamo núti svojich občanov a zároveň zamestnancov štátu k ich používaniu, čo v preklade znamená relatívne veľkú penetráciu používateľov na lokálnych trhoch. Zo skúseností vieme,  že lokálne inštalovaný softvér - teda aplikácie, ktoré si používateľ inštaluje na svoj počítač - nepodliehajú penetračným testom a auditom takmer vôbec, nakoľko sú spoločnosti zamerané najmä na testovanie webov a infraštruktúr. Je teda veľká šanca, že sa nám podarí niečo zaujímavé nájsť. Tento blog zároveň vznikol ako satira na nasledujúce vyjadrenia: 


Uvedený článok odkazuje na zraniteľnosti v aplikácii na overovanie veku, ktoré samé osebe neboli až tak závažné resp. vyžadovali fyzický prístup k telefónu. Napriek tomu vznikla na Slovensku aj v Česku horlivá diskusia, pričom sa niektoré osoby snažili tieto zraniteľnosti a články zneužiť na "politické" účely, v podobe rôznych "pro" alebo "anti"-štátnych vyhláseniach. 

Ako náročné je nájsť zraniteľnosti?

Každý, kto aspoň trochu vie, ako funguje výskum zraniteľností vie, že vyjadrenia ako "hackli do X minút" sú len úsmevná neznalosť žurnalistov. Každopádne, chceli sme zistiť či aj my sa dokážeme dostať na titulky článkov a taktiež, či je to možné v stanovenom časovom limite. Dnes máme k dispozícii silné LLM, a tak sme mu dali jasnú úlohu: 

  **Prompt:**

  > allright bro, hackers claim that they can hack state software in 2 minutes, now you have 2 minutes to find a remote code execution in eObcanka – Identifikace, hurry

  up you have only 2 minutes


  **Claude Code session:**

  

  ● I'll do a quick security review of the eObčanka app contents on your machine.

    Let me explore the application structure.


    Searched for 1 pattern, listed 1 directory

  

  CRITICAL FINDING: Remote Code Execution via Command Injection

                     in Custom URL Scheme Handler


    Found it in the AppleScript launcher at:

    /Applications/Identifikace_eOP.app/Contents/Resources/Scripts/main.scpt

  


  **Prompt:**

  > you did it in less than minute, congrats bro, kthx


  **Claude Code:**


  ● Cheers! Yeah, the AppleScript launcher being a plaintext .scpt file made it

    trivially auditable — and the bug was right there on line 3. No decompilation,

    no reverse engineering needed.

V tomto článku ukazujeme, aké zraniteľnosti sme našli a ako v praxi (ne)fungovalo ich koordinované nahlasovanie. Je to blog z kategórie „Vyskúšali sme za vás“ – a exploitácia nebude sofistikovaná, skôr naopak. Ak Vás zaujíma technický dodatok, nájdete ho v samostatnom blogu, ktorý vyjde neskôr. Tento článok nie je primárne o tom, že v štátnom softvéri vznikajú chyby. Tie vznikajú všade. Je o tom, čo sa stane potom, a ako proces zlyháva.

Ako nahlasovanie zraniteľnosti na papieri funguje?


V zmysle európskej smernice NIS 2, článok 12, určí každý členský štát jednu zo svojich jednotiek CSIRT za koordinátora na účely koordinovaného zverejňovania zraniteľností. Pre Slovensko je to v zmysle zákona č. 69/2018 Z. z. o kybernetickej bezpečnosti “Úrad (NBÚ) prostredníctvom národnej jednotky CSIRT” v Čechách je to podla zákona č. 264/2025 Sb. o kybernetické bezpečnosti NÚKIB.

Koordinované zverejňovanie zraniteľností

Národní úřad pro kybernetickou a informační bezpečnost (NÚKIB) na svojich stránkach píše: „Koordinované zveřejňování zranitelností – proces, který umožňuje bezpečně nahlásit objevenou zranitelnost, dát prostor pro její opravu a teprve poté ji zveřejnit." NÚKIB definoval v decembri 2025 svoju politiku nahlasovania, o ktorej sa dozviete aj z reklamných príspevkov na Facebooku či LinkedIne: 


Slovenský Národný bezpečnostný úrad (NBÚ) taktiež zadefinoval politiku na oznamovanie zraniteľností. Krásne slogany, vznešené PR posty, ale poďme teda zistiť, čo sa ukrýva za lesklým závojom. NBÚ a NÚKIB majú vystupovať v roli koordinátora, a teda: „Nahlásit koordinátorovi nalezenou zranitelnost může jakákoli osoba, a to i anonymně. Koordinátor poskytne oznamovateli pomoc a identifikuje a kontaktuje subjekty dotčené nalezenou zranitelností. Dále je koordinátor zapojen do jednání o lhůtách pro zveřejnění a řešení zranitelností, které mají dopad na více subjektů." Dobre si túto vetu zapamätajte, ešte sa k nej vrátime. 

CVE identifikátor, CVSS skóre

Súčasťou riadneho zverejnenia je pridelenie CVE identifikátora (Common Vulnerabilities and Exposures) - unikátneho identifikátora zraniteľnosti. Pre občana je to dôležité preto, lebo o hrozbe sa môže dozvedieť aj nezávisle od dodávateľa: cez médiá, antivírus, novinky operačného systému alebo bezpečnostné kanály, ktoré čerpajú z oznamov o nových zraniteľnostiach. Bez CVE je občan odkázaný výhradne na to, či sa dodávateľ rozhodne komunikovať alebo nie.

Samotné CVE ešte nehovorí, aká je zraniteľnosť vážna; závažnosť sa typicky dopĺňa cez CVSS skóre (Common Vulnerability Scoring System, od 0.0 do 10.0, kde vyššie číslo znamená vážnejšiu zraniteľnosť) a ďalšie údaje napríklad v NVD (National Vulnerability Database). CVE prideľuje buď CVE Assignment Team, alebo autorizované organizácie označované ako CNA (CVE Numbering Authorities), typicky výrobcovia softvéru, koordinátori alebo bezpečnostné organizácie.

Nahlasovanie a lehoty

Na samotné nahlasovanie zraniteľností existuje mnoho názorov, pričom neexistuje žiadny oficiálny štandard na lehoty, nakoľko sa jedná o komplexný problém. Google nastavil politiku 90+30 dní (https://projectzero.google/vulnerability-disclosure-policy.html), pričom je možné požiadať o 2-týždňové predĺženie. Konkrétne to je 90 dní na opravu a 30 dní na to, aby mali používatelia šancu si aktualizáciu nainštalovať. Po 30 dňoch sú detaily o zraniteľnosti zverejnené. My používame presne rovnakú politiku nahlasovania, na ktorú pri nahlásení aj upozorníme. Chcel by som upozorniť, že máme mnoho skúseností s nahlasovaním komerčným firmám ako napríklad veľmi čerstvé nahlásenia pre ESET, JetBrains atď., a tak máme predstavu o tom, ako by mal takýto proces vyzerať, a vždy sme ochotní kooperovať. Dodnes držíme embargo na zraniteľnosť v produkte od Esetu a taktiež na zraniteľnosť v TeamCity, ktorá je ešte viac závažná ako popisuje blog (avšak nechceme na ňu upozorňovať viac ako je to potrebné, nakoľko podobná zraniteľnosť bola v minulosti využitá na šírenie ransomware). 

Upozornenia na zraniteľnosti v slovenskom SW 

Keďže sme sa už slovenskému eID klientovi venovali v minulosti, na rad prišli podporné a súvisiace programy, ktoré sú však integrálnou časťou eID ekosystému. Konkrétne sme sa venovali aplikáciám D.Launcher2. Vyhlásenie k požadovaným aktualizáciám nájdete na:


Český prípad


V ďalšom prípade sme sa venovali aj podpornému SW k českej eObčanke na macOS. Tu, bohužiaľ, vyhlásenie o závažnej zraniteľnosti nenájdete a samotná eObčanka vás dokonca nevyzve na jej aktualizáciu. Z tohto jedného riadku by ste teda ako občan mali zistiť, že váš počítač je v ohrození: https://info.identita.gov.cz/eop/ZmenySWMacOS.aspx„Úprava pro zvýšení bezpečnosti aplikace". Verzia bola vydaná 13.mája. 2026, pričom ani dodnes neprišlo žiadne aktívne upozornenie ani zo strany NÚKIB, ani Ministerstva vnitra ČR, či samotného dodávateľa aplikácie. Občania sa môžu dozvedieť o aktualizácii len tak, že si spustia aplikáciu, ktorá zahlási: 


Z aplikácie nie je jasné, že sa jedná o bezpečnostnú záplatu a dokonca ani nie je možná priama aktualizácia. Občania si musia ísť manuálne stiahnuť novú verziu z oficiálnej stránky. 
Toto správanie navonok pôsobí ako takzvaný „silent patch“: bezpečnostná oprava bola vydaná, ale používateľ nedostal jasnú informáciu o závažnosti, dotknutých verziách ani potrebe okamžitej aktualizácie. Problematické je najmä to, že občania o danej hrozbe nevedia, zatiaľ čo útočník dokáže opravu zreverzovať a vyvinúť exploit. V tomto konkrétnom prípade má exploit doslova jeden riadok. Práve z tohto dôvodu sme sa rozhodli publikovať tento blog oveľa skôr, ako sme pôvodne plánovali.

Niekto môže namietať, že nejde o klasický silent patch, keďže zmenový list spomína „úpravu pro zvýšení bezpečnosti". Tento argument však neobstojí. Riadne zverejnenie zraniteľnosti podľa štandardov ISO/IEC 29147 a bežnej praxe odporúča:

  • závažnosť / CVSS skóre
  • CVE identifikátor
  • zoznam dotknutých verzií
  • popis workaroundu pre používateľov, ktorí nemôžu hneď aktualizovať
  • primerane viditeľnú komunikáciu smerom k dotknutým používateľom.

Veta „Úprava pro zvýšení bezpečnosti aplikace" nespĺňa ani jedno z toho. Útočník má všetky informácie potrebné na exploit (opravená verzia je verejná). Obeť, žiaľ, nemá takmer žiadne. To je  presne asymetrický stav, ktorý silent patch vytvára - bez ohľadu na to, či bola v zmenovom liste „nejaká zmienka", alebo nie. Pekný blog o škodlivosti „silent patch" od Rapid7 nájdete: https://www.rapid7.com/blog/post/2022/06/06/the-hidden-harm-of-silent-patches/


Zraniteľnosti v slovenskom D.Launcher 2


D.Launcher 2 prepája webový portál s lokálnymi komponentmi na elektronické podpisovanie. Nie je to priamo eID klient, ale patrí do rovnakého štátneho ekosystému. D.Launcher je vyvíjaný a podporovaný spoločnosťou DITEC, a.s. Pred tým ako sme identifikovali zraniteľnosť sme už predpokladali, že spoločnosť bude reagovať na nahlásenie, keďže aj na viacerých fórach sa objavili pozitívne komentáre ako poskytovali podporu a opravy aj pre neštandardné konfigurácie. 

Zraniteľnosti umožnovali útočnikom nalákať používateľov na špeciálne pripravený odkaz, napríklad v phishingovom e-maile tváriacom sa ako notifikácia zo slovensko.sk, a potvrdiť otvorenie aplikácie. V jednom scenári mohol útočník získať NTLM hash používateľa; v druhom prípade spracovanie XML mohlo v najhoršom prípade viesť až k spusteniu cudzieho kódu.

24. apríla 2026 sme nahlásili zraniteľnosti e-mailom na podporu: servis.kep@ditec.sk. Zároveň bol informovaný NBÚ v ten istý deň, kde som ich vyzval na riešenie tejto situácie ako incidentu, keďže sme sa domnievali, že okrem krádeže formy prihlasovacích údajov (NTLM hash), bolo možné pomocou známej zraniteľnosti v zastaraných použitých knižniciach aj spustenie cudzieho kódu. O 5 dní sme kontaktovali NBÚ s aktualizáciami od dodávateľa, ktorý sa k situácií postavil veľmi korektne. 

O 6 dní (30. apríla.2026) sme z NBÚ dostal automatizovanú odpoveď: 

"Dobrý deň,
potvrdzujeme prijatie Vašej e-mailovej správy a ďakujeme Vám za poskytnutie dodatočných informácií..."

7.mája.2026 teda keď už bola oprava vydaná, dodávateľ vo veľmi podrobnom a rozsiahlom maile informoval NBÚ o krokoch, ktoré podnikol. Následne dňa 11.mája.2026 teda o ďalšie 4 dni sme sa dočkali odpovede: 

"Dobrý deň,
potvrdzujeme prijatie Vašej e-mailovej správy a ďakujeme Vám za poskytnutie informácií.
Ďakujeme za spoluprácu.".

NBÚ nevydal žiadne upozornenie, nebol koordinátorom dátumov ani zverejnenia. Naproti tomu sa dodávateľ DITEC k nahláseniu postavil príkladne. Komunikoval transparentne, vydal opravu v rozumnom čase (6.mája.2026) a samostatne poslal NBÚ detailný report o krokoch, ktoré podnikol. V skutočnosti to teda bol DITEC, kto suploval koordinačnú úlohu NBÚ. Komunikoval priamo s nami ako oznamovateľmi aj s NASES-om (prevádzkovateľom portálu slovensko.sk, kde sa zverejnilo upozornenie), dokumentoval celý postup a v záveroch zaviedol úpravy aj vo svojich vývojových procesoch.


Časová os: 
Dátum Aktér Udalosť
24. 4. 2026 (D+0) Binary House Nahlásenie zraniteľností v D.Launcher 2 dodávateľovi Ditec + informovanie NBÚ
29. 4. 2026 (D+5) Binary House Odoslanie aktualizácie NBÚ s informáciami od dodávateľa, ktorý sa k riešeniu postavil korektne
30. 4. 2026 (D+6) NBÚ Automatizovaná odpoveď s potvrdením prijatia dodatočných informácií
6. 5. 2026 (D+12) Ditec Vydanie opravy v D.Launcher 2 v2.0.7
7. 5. 2026 (D+13) Ditec Dodávateľ posiela NBÚ detailný report o vykonaných krokoch a riešení zraniteľností
11. 5. 2026 (D+17) NBÚ Stručná odpoveď: potvrdenie prijatia informácií a poďakovanie za spoluprácu
20.5.2026 NBÚ Bez samostatného verejného upozornenia NBÚ a bez aktívnej koordinácie dátumu zverejnenia (CVE bude priradené)
stav k publikácii Ditec / NASES Oprava vydaná, verejné upozornenie dostupné na slovensko.sk


Zraniteľnosť v eObčanka - Identifikace


Ako je spomenuté v úvode: eObčanka-Identifikace na macOS umožňuje útočníkovi spustiť kód na vašom počítači po kliknutí na podstrčený odkaz a potvrdení otvorenia aplikácie. 
21.apríla.2026 sme zaslali email na cvd@nukib.gov.cz s predmetom: "Kriticka zranitelnost v eObcanka - Identifickace - macOS" v znení: 
"Zranitelnost dosud nebyla veřejně zveřejněna. Zasílám tento nález za účelem koordinovaného řešení s výrobcem a provozovatelem. Pokud je zajem, vim vam zaslat kompletni proof of concept, je to jenom par radku."
Zároveň sme kontaktovali dodávateľa, pričom sme nedokázali zohnať lepší kontakt ako na ich recepciu. 
Od NÚKIB sme nedostali žiadnu odpoveď do 2 dní, a tak nebolo zrejmé, či je email vôbec aktívny. Kontaktovali sme teda CSIRT.CZ (Abuse team), ktorý odpovedal: "Bohužel tato záležitost nespadá do naší působnosti, a proto Vás prosíme, abyste incident nahlásili přímo společnosti, která daný projekt vytvořila.". Medzitým sme kontaktovali Digitální informační agenturu (DIA) cez ich kontaktný email o tom, že sa daná situácia nerieši. 

29.apríla.2026 sme dostali prvý email od NÚKIB, teda o 8 dní:

"Dobrý den,
děkujeme za zprávu a omlouvám se za prodlení.
Na základě této informace jsme začali řešení v koordinaci s provozovatelem. O dalším postupu nebo v případě nutnosti doplnění dalších informací, Vás budeme informovat.". 

Pričom spomeňte si, ako má koordinátor vystupovať v procese, má byť akýmsi prostredníkom medzi nahlasovateľom a spoločnosťou, ktorá zasiahnutý SW dodáva. V tento deň sme NÚKIBu vysvetlili, že podľa dodávateľa sa nejedná o zraniteľnosť. 

Odpoveď NÚKIBu: 
"Dobrý den,

děkuji za informaci.
Budeme apelovat na odstranění Vámi popsané zranitelnosti. 

Přeji hezký večer.
"

Medzičasom sa nám konečne ozval dodávateľ a 28.apríla.2026 zaslal odpoveď:

"Dobrý den,
děkujeme za nahlášení podezření. Zasíláme vyjádření od vývojáře:
Nejedná se o zranitelnost, ale vlastnost. URI handler je nastaven pod silnými právy instalátoru na straně klienta. Tato akce není řízená ze strany serveru. Pokud má uživatel silné právo podvrhnout URI handleru infikovanou aplikaci, má již grantovaná práva, která by útokem získal, nepotřebuje je získávat tímto způsobem.
Toto řešení je zvoleno pro zachování multi-platformnosti aplikace."


Nie je jasné, čo tým dodávateľ myslel, avšak zjavne nepochopil podstatu nášho nahlásenia. Keďže teda nejde o zraniteľnosť, slušne sme sa opýtali: 

"Zdravim,
Dakujem za odoved. Suhlasite teda ze to mozem zverejnit na nasom blogu, kedze sa teda podla vas nejedna o zranitelnost. Viete mi to prosim potvrdit?

Znovu nastalo hrobové ticho z oboch strán. 

Následne sa 5.mája 2026 ozval znovu dodávateľ: 

"Dobrý den,
Kontaktuji Vás v návaznosti na Vaše oznámení o nalezené bezpečnostní zranitelnosti v aplikaci eObčanka-Identifikace.
Zjistili jsme, že proces pro zpracování podobných oznámení nemáme optimálně nastaven a vyhodnocení Vašeho oznámení vývojovým týmem nebylo provedeno korektně.
Zranitelnost již řešíme, jako rychlý fix bude zavedeno ošetření parametrů skriptu, cílové řešení pak může být i jiné.
Mrzí mě, že na naší straně k takovéto situaci došlo.
Po dohodě s CISO jsme upravili interní proces tak, aby v rámci řešení těchto hlášení byl zapojen také tým specificky se zabývající bezpečností.
Chyba ve zpracování tohoto oznámení nám tak pomohla vylepšit interní procesy, za což Vám děkuji a věřím, že se nebude opakovat.
"

Odpovedali sme aj s kópiou na NÚKIB, kde sme poukázali na naše pravidlá na zverejňovanie zranitelností. Keďže nikto nestanovil žiadne dátumy, upozornili sme, že beží 90 dňová lehota na vydanie záplaty. Z technického hľadiska pozostáva oprava z dvoch riadkov kódu.

"Dobrý den,
děkuji za zprávu.
Dle informací od dodavatele se na řešení zranitelnosti aktivně pracuje.
Současně si dovolím upozornit, že v České republice plní vládní CERT roli koordinátora procesu CVD, v jehož rámci mimo jiné stanovuje lhůty pro nápravu a zveřejnění nahlášené zranitelnosti. V případě nedodržení stanovených lhůt se na Vás nevztahuje ochrana nahlašovatele.
Přeji hezký víkend."

Zároveň nás NÚKIB uistil, že:

"Dle aktuálně dostupných informací byla oprava již implementována a úspěšně prošla závěrečným testováním na straně dodavatele. Další kroky, tj. finální notarizace, formální vyjádření a distribuce nové verze aplikace, se ze strany dodavatele očekávají v průběhu nadcházejícího týdne.

Jakmile budu mít k dispozici další informace, ozvu se Vám.
"

Keďže nám teda úplne neboli jasné dátumy, milo sme dňa 8.mája 2026 odpovedali: 

"Dobry den,
Dakujem za informacie. Dovolim si pripomenut ze lehoty mali byt navrhnute aj z vasej strany aj zo strany dodavatela a nemal by som problem ich respektovat. Myslite si ze sa pocita do lehoty aj to ze ako NUKIB ste prvy krat zareagovali po 8 dnoch? A ideme pocitat aj tu dobu pocas, ktorej to ignoroval dodavatel na zaklade jeho zleho interneho vyhodnotenia? Skutocna oprava minimalne teda rychly fix je doslova jeden riadok kodu (avsak chapem ze aj testovania a release proces nejaky cas trva). 
Navrhnite teda prosim datumy a podme to spravit koordinovane spolu s registrovanim CVE.
"

Dodnes nemáme žiadnu odpoveď. 

Dňa 13.mája.2026 nám zatelefonovala pani z DIA, kde nám oznámila niečo v štýle, že "dodávateľ sa konečne probudil" a poprosila o kontrolu opravy. To sme následne urobili a obratom telefonicky potvrdili efektivitu záplaty. Mimochodom, "exploit" na cudzieho kódu tvorí doslova jeden riadok kódu. Obligatórne spustenie kalkulačky:


 
Ak je teda spustenie cudzieho kódu naozaj "feature", odporúčame občanom spúšťať túto aplikáciu len v oddelenom virtuálnom prostredí. Prípadne, radšej ju nemať nainštalovanú.


Časová os: 

Dátum Aktér Udalosť
21. 4. 2026 (D+0) Binary House Nahlásenie NÚKIB (cvd@nukib.gov.cz) + dodávateľovi
~23. 4. 2026 (D+2) Binary House Po 2 dňoch bez odpovede následný kontakt CSIRT.CZ — odpoveď „nespadá do našej pôsobnosti"
28. 4. 2026 (D+7) Dodávateľ „Není to zranitelnost, je to vlastnost"
29. 4. 2026 (D+8) NÚKIB Prvá odpoveď (po 8 dňoch): „omlouváme se za prodlení"
5. 5. 2026 (D+14) Dodávateľ Priznáva chybu vo vyhodnotení, oprava sa rieši
8. 5. 2026 (D+17) Binary House Follow-up a upozornenie na lehoty
8. 5. 2026 (D+17) NÚKIB Upozornenie na možnú stratu ochrany nahlasovateľa bez jasne komunikovaného termínu
8. 5. 2026 (D+17) Binary House Ďalší follow-up na NÚKIB s otázkami o lehotách
13. 5. 2026 (D+22) DIA + dodávateľ Telefonát z DIA + vydanie opravy
19.5.2026 NÚKIB Žiadosť o neskoré uverejnenie tohto blogu, zamietnuté
dodnes NÚKIB Bez CVE, bez verejného upozornenia, bez koordinácie


Záver našej recenzie


Vyskúšali sme, ako funguje proces zodpovedného nahlasovania zraniteľností. 
Pokiaľ ako občania platíme za štátne služby a takýto SW, máme právo očakávať lepšie riešenia a procesy. Komerčná sféra ukazuje, že to ide, odpovede do jedného dňa, komplexné záplaty do pár dní, transparentné oznámenia s CVE identifikátormi.
  
Štátne inštitúcie a dodávatelia by to mali dohnať. Svetlou výnimkou bol prístup spoločnosti DITEC. Avšak PR posty od úradov o intergalaktických spoluprácach žiadnu zraniteľnosť neopravia.

Dva prípady, dvaja národní koordinátori, jeden vzor: PR posty existujú, procesy na papieri existujú tiež, ale keď príde na koordináciu konkrétneho prípadu, koordinátor sa stratí alebo začne hroziť výskumníkovi.

Pre dodávateľov štátneho softvéru: Ďakujeme tým, ktorí sa k nahláseniam postavili profesionálne. Ostatným pripomíname, že vyhlásenie "není to zranitelnost, je to vlastnost" sa v roku 2026 prijíma asi tak ako "není to bug, je to feature". Zatiaľ nám zostáva len čakať na sľúbené lepšie procesy a zajtrajšky. 

Pred publikovaním sme dotknuté strany požiadali o stanovisko a opravu prípadných faktických nepresností s časovým limitom do 24 hodín. Do limtu sme dostali stanivisko od spoločnosti DITEC a ich pripomienky sme zapracovali. Následne nás kontaktoval NÚKIB s tým, že potvrdzuje pôvodne navrhované termíny a žiada o neskoršie zverejnenie, konkrétne k 20. júlu 2026. Keďže však podľa nášho názoru používatelia stále nedostali jasnú informáciu o závažnosti zraniteľnosti a odporúčanom postupe, zverejňujeme tento blog v pôvodne avizovanom termíne.

Ďakujeme aj Alfimu a Lacimu za spätnú väzbu.

Tuesday, March 17, 2026

Prípadová štúdia: Ako sme s Claude Code hľadali zraniteľnosti v aplikácii Autogram (a prečo AI stále potrebuje kontext)

Umelá inteligencia je dnes bežnou súčasťou vývoja, a tak sme sa rozhodli otestovať schopnosti nástroja Claude Code pri bezpečnostnom audite (security review). Našou cieľovou aplikáciou bol Autogram vo verzii 2.7.1 – desktopová aplikácia (JavaFX) s integrovaným lokálnym HTTP serverom, ktorá slúži na elektronické podpisovanie.

Výsledok? Ukážka toho, ako AI dokáže rýchlo analyzovať kód, ale aj toho, prečo by sme sa bez znalosti reálneho threat modelu utopili vo falošných poplachoch.

Prvý dojem: Hrozivý report 🚨

Po zadaní príkazu na preskúmanie repozitára, Claude Code model Opus4.6 analyzoval desiatky súborov (70k+ tokenov) za necelé 3 minúty. Pôvodný report vyzeral hrozivo. Claude identifikovalo 17 zraniteľností:

  • 4 Critical (napr. Wildcard CORS na všetkých endpointoch, chýbajúca autentifikácia, hardcodované TLS heslo)

  • 4 High (napr. riziko DoS pre neobmedzenú veľkosť requestu, ...)

  • 4 Medium a 5 Low (XSLT injection, resource leaky, logovanie citlivých dát, ...)

Ak by sme tento report zobrali bez rozmyslu, aplikácia by pôsobila ako deravé rešeto.

Kontext je kráľ: Ako sme škrtali falošné poplachy ✂️

Tu prišiel na rad ľudský faktor. Začali sme s Claude diskutovať a vysvetľovať mu architektúru aplikácie. Autogram nie je bežný webový server vystavený do internetu; avšak beží ako aplikácia na počítači (localhoste) používateľa, ktorý volajú webové portály tretích strán.

Postupne sme "hrozby" eliminovali:

  1. "Kritický" CORS a chýbajúca autentifikácia: Ide o zámerný dizajn. Autogram musia vedieť zavolať rôzne portály. Skutočnou bezpečnostnou hranicou nie je HTTP vrstva, ale samotný používateľ, ktorý musí v GUI aplikácie vizuálne skontrolovať dokument a zadať PIN. Výsledok: Vyškrtnuté.

  2. Hardcodované TLS heslo: Certifikát je self-signed a slúži len na lokálne šifrovanie. Ak má útočník prístup k súborom na disku, už dávno ovládol počítač. Výsledok: Vyškrtnuté.

  3. Teoretické DoS útoky a prístupy k internej JDK: Neobmedzená veľkosť pre HTTP body znie zle, ale zneužiť to cez webový prehliadač znemožnia tvrdé pamäťové limity samotného browsera – gigabajty dát ním jednoducho nepretlačíte. A ak by útočník chcel poslať takýto obrovský payload inou cestou, znamená to, že už musí mať prístup k danému stroju a možnosť spúšťať na ňom vlastný kód. Zvyšné Medium a Low nálezy sme preklasifikovali na bežný technický dlh. Výsledok: Vyškrtnuté.

Jediný skutočne závažný nález: Ihla v kope sena 🪡

Po zhodnotení reálneho prostredia desktopovej aplikácie uznal chybu aj samotný Claude Code. Z pôvodných 17 nálezov zostal presne jeden skutočne relevantný, s Vysokým rizikom:

  • Neúplná mitigácia XXE (XML External Entity): Pri spracovaní XML (súbor XMLUtils.java) bola vypnutá len jedna funkcia (external-general-entities). Chýbalo však zakázanie parameter entít a externých DTD. Pôvodný report to označil za kritickú chybu, no závažnosť sme nakoniec upravili na Vysokú. Dôvodom je, že na úspešný útok musí obeť najprv navštíviť útočníkovu webovú stránku a útočník zároveň musí poznať presný názov a cestu k súboru, ktorý chce exfiltrovať. Napriek týmto prekážkam išlo o reálnu zraniteľnosť – úspešnú exploitáciu a exfiltráciu lokálneho súboru z počítača obete sme prakticky potvrdili.

Zodpovedné nahlásenie a CVE-2026-3511 🛡️

Keď sme túto zraniteľnosť s pomocou AI potvrdili, postupovali sme štandardnou cestou zodpovedného zverejňovania (responsible disclosure). Nález sme oficiálne nahlásili prostredníctvom Národného bezpečnostného úradu (NBÚ).

Tu musíme vyzdvihnúť prístup vývojárov zo Slovensko.Digital, ktorí zareagovali absolútne ukážkovo. Zraniteľnosť bola z ich strany vyriešená promptne a extrémne rýchlo vydali aj novú verziu 2.7.2. 

Pre tento konkrétny prípad bolo zároveň rezervované označenie CVE-2026-3511.

Záver

Použitie Claude Code na rýchle skenovanie bola vynikajúca skúsenosť. Nástroj dokázal okamžite nájsť zložité patterny a potenciálne zraniteľnosti (ako napríklad spomínané XXE), ktoré by sa pri manuálnom code review hľadali ťažšie, nakoľko existovala čiastočná ochrana.

Je však dôležité zdôrazniť, že tento náš experiment sa v žiadnom prípade nedá pokladať za komplexný bezpečnostný audit kódu (comprehensive security code audit) alebo plnohodnotný penetračný test. Išlo o cielenú ukážku možností AI asistenta.

Zároveň sa ukázalo, že AI zatiaľ nedokáže plne pochopiť aplikačný kontext bez pomoci človeka. Bez inžiniera, ktorý vie, že „tento server beží len lokálne a autorizuje sa klikom v UI a následným zadaním PINu“, by bol výstupom len dlhý zoznam falošných poplachov (false positives). LLM sú dnes už nepostrádateľný nástroj, no kapitánom lode prezatiaľ musí ostať človek. Výsledkom tejto rýchlej spolupráce je nakoniec bezpečnejší softvér pre všetkých.

Mimochodom, tento krátky experiment len potvrdzuje neformálnu metodológiu pre hľadanie zraniteľností pomocou LLM, ktorú spísal researcher Devansh na svojom blogu. Určite to stojí za prečítanie!

Tuesday, May 30, 2023

Penetračný tester / Penetračná testerka

Miesto práce

Bratislava, Slovensko (Pozícia umožňuje aj prácu z domu)

Mzdové podmienky (brutto)

Od 2 000 EUR/mesiac plus motivačné bonusy

Finálna suma základnej zložky mzdy môže byť navýšená v závislosti od odborných skúseností vybraného kandidáta.

Druh pracovného pomeru

Plný úväzok, živnosť, alebo dohodou

Termín nástupu

Ihneď

 

Hľadáme nadšenca do nášho tímu, ktorý chce tráviť čas hackingom - hľadaním zraniteľností vo webových aplikáciách. Je jedno či ste junior alebo senior.

Ak sa pripojíte k nášmu tímu, budete mať príležitosť pracovať na rôznych projektoch pre rôznych klientov. Vaše úlohy budú zahŕňať skúmanie zabezpečenia a hľadanie zraniteľností, ktoré môžu ohroziť systémy a aplikácie klientov. Taktiež budete mať možnosť vylepšiť si svoje zručnosti v penetračnom testovaní, pretože sa budete učiť od skúsených členov tímu.

Snažíme sa rozvíjať kultúru Work-to-Live (práca pre život), a nie Live-to-Work (život pre prácu). Ľúbime to, čo robíme, ale máme radi aj osobný život. Aj keď sa stáva, že členovia nášho tímu pracujú do polnoci hľadaním zraniteľností, na súťažiach CTF alebo na osobných projektoch. Keďže máme radi aj osobný život, ponúkame Vám 4-dňový pracovný týždeň, ale o tom viac na úvodnom pohovore.

Od penetračného testovania webu sa môžete posunúť aj k hackovaniu mobilných aplikácii, infraštruktúry, cloudových a kontajnerových riešení až k reverznému inžinierstvu a exploitácii.

Nezáleží nám na vašom vzdelaní ani certifikátoch. Ak ste naozaj dobrý a máte vášeň pre počítačovú bezpečnosť a chuť sa neustále vzdelávať, ste ten správny kandidát.

 

Náplň práce:

·        Vykonávať penetračné testy webových aplikácií

·        Identifikovať a zneužiť zraniteľnosti

·        Vytvoriť záverečnú správu (report) z penetračného testu v slovenskom alebo anglickom jazyku

·        Komunikovať so zákazníkmi

·        Spolupracovať s vývojovým tímom na implementácii bezpečnostných opatrení

·        Výskum

 

Osobnostné predpoklady a zručnosti:

·        Vášeň pre počítačovú bezpečnosť

·        Chuť skúmať ako veci fungujú

·        Disciplinovanosť

·        Pochopenie etiky v podnikaní v počítačovej bezpečnosti

·        Schopnosť manuálne identifikovať a exploitovat bežné webové zraniteľnosti

·        Znalosť webových protokolov a webových architektúr / frameworkov

·        Vedieť čítať zdrojové kódy (Java, JavaScript, TypeScript, C#, PHP, ..)

·        Sledovanie nových trendov v oblasti bezpečnosti webových aplikácií

·        Byť samostatný, ale nebojácny sa spýtať kolegu ak niečomu nerozumie

 

Čo ponúkame:

·        Platená pozícia na plný úväzok, živnosť, alebo dohodou

·        Motivácia založená na kvalite a efektivite

·        Školenie a certifikácie v oblasti ofenzívnej bezpečnosti

·        Konferencie (zahraničné aj domáce)

 

Možnosť pracovať:

·        V kancelárii

·        Z domu (vzdialene, ale záleží aj od projektu)

·        Kombinácia

 

Ak máte záujem, pošlite nám svoj životopis na hr@binary.house a povedzte nám o svojich zručnostiach.

 

O spoločnosti:

Binary House je slovenská spoločnosť so sídlom v Bratislave, ktorá poskytuje služby v oblasti ofenzívnej IT bezpečnosti. Pomáha svojim klientom identifikovať a opraviť ich zraniteľné miesta v aplikáciách, sieťach a systémoch. Medzi poskytované služby patrí penetračné testovanie, bezpečnostné audity, reverzné inžinierstvo, vývoj PoC / Exploitov a útoky pomocou sociálneho inžinierstva.

Friday, April 28, 2023

ZeroPointSecurity Certified Red Team Operator (CRTO) Course - Few notes

Preface 

During the end-of-year madness with projects, I was searching for distractions for my troubled mind and saw several praising posts on Twitter about a red teaming course. Having a homie who also took part in the course and wrote a very comprehensive, although dated, blog post (https://v3ded.github.io/misc/certified-red-team-operator-crto-review) convinced me to look into it. Despite my reluctance to work within a Windows environment, I decided to take a step out of my comfort zone, so I jumped on the hype train and bought the course. The course strongly revolves around Active Directory misconfigurations leveraging Cobalt Strike. As my friends from the AV industry would say: "duh, Cobalt Strike again, boring." Their words are a true testament that probably one of the most widespread malware contains packed and obfuscated cracked versions of Cobalt Strike. Being able to emulate the tactics, techniques, and procedures (TTPs) of real-world APT groups—that's what red teaming is really about, isn't it? 

Labs and course material 

All of the materials are hosted on Canvas in the form of short Markdown articles. From the very beginning of the course, the author guides you through topics about the specifics of red teaming, especially in comparison with penetration testing. These mini-articles are clear and concise, which is good on the one hand, but on the other hand, some of the modules are so sparse that someone with no experience in red teaming will have a hard time materializing the concepts. There are also sections where the author does not care about explaining underlying concepts, which are left to the astute reader. For this reason, I would not recommend this course for beginners in IT security, as many topics are very light in terms of theory, and the section just shows that you carry out this specific attack in this way and that's it. If you blindly follow the commands for carrying out an attack, you will have a tough time in the exam. I've seen many students struggling with basic concepts and techniques, which Cobalt Strike allows you to perform in a single click. It's a double-edged sword: anyone can execute the attacks, but if the concept is not understood well, you just don't know when or how to use it properly. Many students didn't realize what the pivoting or session passing are really good for. As I mentioned, the modules are very straightforward and give you step-by-step instructions to execute an attack. I must confess that it is a very different approach to learning, as OffSec encourages in their courses. There are very few explicitly stated challenges that could push your critical thinking. On the flip side, you should pay close attention to every sentence, as many of the modules contain little nudges that hide a treasure that can be leveraged in the exam. Throughout the course, you will go through each stage of the attack lifecycle—from initial compromise to full domain takeover, data hunting, and exfiltration. You will also learn how common "OPSEC failures" can lead to detection by defenders and how to carry out those attacks in a more stealthy way. This is a huge feature, as the course allows you to check all the metadata and events that were triggered by your attack in the Splunk instance. When you have exhausted all of your ideas for replicating the attack, there is a very active and helpful Discord channel for the course available to all who participate in the course, where you can ask questions. 

Exam 

The exam is a 48 hour, hands-on CTF, carried as an assumed breach, where you attack several forests.   You are required to get at least 6 out of 8 flags to pass. The exam is, however, available for 4 days or 48 lab hours (whatever expires first), and it's possible to pause it if you want to take a break. The difficulty of the exam was fair and everything you need to successfully pass is in the course, even without annoying proctoring (unlike OffSec). The course teaches you techniques and attacks within latently protected environment (e.g. no antivirus and firewall). However, it is no secret that exam lab is protected by Defender with AMSI and a firewall, which will probably give you headaches. Therefore, after you scramble through the lab once, it's recommended to turn the Defender on and rinse and repeat the modules one more time. In my experience, I was very impatient with executing the commands and beacons were very often killed by behavioral analysis. During the exam I had several connection issues and the whole exam lab was so slow. The only support is the author of the course, so if you experience an issue and he is currently offline, you are left in the dark alone. 

Costs 

When it comes to value for money, there are only few courses that could beat this one to the punch. All in all, for £399.00, you will get lifetime access to the course materials, which seems to be fairly updated every now and then, 40 hours in the labs (can be extended for another 40 hours for 20£), that includes all the important server instances that you find in enterprise environments and hands-on experience with the premiere redteaming framework Cobalt Strike. 

Conclusion 

It should be noted that the course is a rated as beginner-intemediate course and thus, the Defender isn't fully up to date, even though the servers are Windows Servers 2022, but I witnessed multiple times during engagements that the many attacks carry over as they are of misconfigurations nature, rather than straight vulnerabilities that are being patched. After all, I would recommend this course after taking OSCP as a logical step towards the beauty of the red teaming world.

Monday, October 8, 2018

Sneak peek under the hood of Electra's kexecute


Few months ago, a jailbreak called Electra (1) has been publicly released. The jailbreak targets iOS 11.2 - 11.3.1 with astonishing kernel exploits (2-3) provided by Ian Beer of Google's Project Zero.

The team behind Electra combines and packs multiple publicly known techniques in order to achieve goals to weaken the security mechanisms and run unsigned code on the device.

This short blog post does not provide any novel techniques but it rather describes internals of Electra's kexecute function that serves a limited way to call kernel functions from the userland.

Actually, we wanted to compare it with kexecute provided by Qilin (4) (post-exploitation framework) but at the time of writing this post, Qilin exports the kexecute, however the .o library does not contain any implementation (shasum: fabc6a8d99b1c2cdd35277533b003f630fd2a2bf).

During the post-exploitation phase, we find ourselves in the position, when calling a kernel function can save us from the complicated situations. Simulating function calls with raw arbitrary r/w capability can be really daunting task, so let the kernel work for us!

This technique has been published in the "Tales from iOS 6 Exploitation and iOS 7 Security Changes" by Stefan Esser (5).

First of all, we need a convenient way to pass our user-supplied arguments to the kernel, that's where IOKit comes to the rescue.

kern_return_t
IOConnectTrap6(io_connect_t connect,
        uint32_t  index,
        uintptr_t p1,
        uintptr_t p2,
        uintptr_t p3,
        uintptr_t p4,
        uintptr_t p5,
        uintptr_t p6 )
{
    return iokit_user_client_trap(connect, index, p1, p2, p3, p4, p5, p6);
}

IOConnectTrap6(...) allows a user to send up to 6 arguments to the kernelland. The function immediately calls iokit_user_client_trap(...). 

The iokit_user_client_trap(...) function returns a value from the function called upon the IOExternalTrap instance.

The IOExternalTrap is descibed by the following structure:

struct IOExternalTrap {
 IOService * object;
 IOTrap func;
};

Subsequently, the iokit_user_client_trap(...) function calls the trap function with the user-supplied arguments.

kern_return_t iokit_user_client_trap(struct iokit_user_client_trap_args *args)
{
    kern_return_t result = kIOReturnBadArgument;
    IOUserClient *userClient;

    if ((userClient = OSDynamicCast(IOUserClient,
            iokit_lookup_connect_ref_current_task((OSObject *)(args->userClientRef))))) {
        IOExternalTrap *trap;
        IOService *target = NULL;

        trap = userClient->getTargetAndTrapForIndex(&target, args->index);

        if (trap && target) { // [A]
            IOTrap func;

            func = trap->func;

            if (func) {
                result = (target->*func)(args->p1, args->p2, args->p3, args->p4, args->p5, args->p6);
            }
        }

 iokit_remove_connect_reference(userClient);
    }

    return result;
}

Can we control the target and trap returned by getTargetAndTrapForIndex(...)?

From the following code snippet is obvious, that if we could subvert the getExternalTrapForIndex(...) of the IOUserClient instance, we are immediately in control of the *targetP and returned trap, hence we control the called function pointer (target->*func).

IOExternalTrap * IOUserClient::
getTargetAndTrapForIndex(IOService ** targetP, UInt32 index)
{
      IOExternalTrap *trap = getExternalTrapForIndex(index);

      if (trap) {
              *targetP = trap->object;
      }

      return trap;
}

The generic idea behind calling arbitrary functions, is to modify the vtable of the IOUserClient instance so that invoking the iokit_user_client_trap(...) ends up calling a function pointer controlled by us. Regarding the fact that the IOUserClient instances are backed by heap memory and behave as C++ objects stored in the memory, thus the very first address stored in this representation of instances in the memory is an address of the vtable. The vtable consists of all virtual functions that can be called on the particular instance of the objects. The other thing, that we need to take into account is the calling convention of methods, which requires us to put the "this" pointer into very first argument. The pointer points to the instance of the object itself. Note that this cannot be NULL because of the statement at [A].

The addresses of the IOUserClient instances are stored in the port member called ip_kobject, which is confirmed when we take a closer look at the function iokit_lookup_connect_ref(...) from the first code snippet:

EXTERN io_object_t
iokit_lookup_connect_ref(io_object_t connectRef, ipc_space_t space)
{
 io_object_t obj = NULL;

 if (connectRef && MACH_PORT_VALID((mach_port_name_t)connectRef)) {
  ipc_port_t port;
  kern_return_t kr;

  kr = ipc_object_translate(space, (mach_port_name_t)connectRef, MACH_PORT_RIGHT_SEND, (ipc_object_t *)&port);

  if (kr == KERN_SUCCESS) {
            assert(IP_VALID(port));
            
            if (ip_active(port) && (ip_kotype(port) == IKOT_IOKIT_CONNECT)) {
                obj = (io_object_t) port->ip_kobject;
                iokit_add_reference(obj);
            }
            
            ip_unlock(port);
  }
 }

 return obj;
}

Let's look how the team behind Electra deals with the implementation. Please note, that the code is shrank, so that the only important lines are shown.

mach_port_t prepare_user_client(void) {
...
  io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot"));

  err = IOServiceOpen(service, mach_task_self(), 0, &user_client);
...
  return user_client;
}

The function prepare_user_client() spawns an instance of IOSurfaceRoot and returns its address. 

Closer look at the following function reveals the actual implementation of the desired functionality.

void init_kexecute(void) {
    user_client = prepare_user_client();

    // From v0rtex - get the IOSurfaceRootUserClient port, and then the address of the actual client, and vtable
    IOSurfaceRootUserClient_port = find_port(user_client); // UserClients are just mach_ports, so we find its address

    IOSurfaceRootUserClient_addr = rk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject); // The UserClient itself (the C++ object) is at the kobject field

    uint64_t IOSurfaceRootUserClient_vtab = rk64(IOSurfaceRootUserClient_addr); // vtables in C++ are at *object

    // The aim is to create a fake client, with a fake vtable, and overwrite the existing client with the fake one
    // Once we do that, we can use IOConnectTrap6 to call functions in the kernel as the kernel

    // Create the vtable in the kernel memory, then copy the existing vtable into there
    fake_vtable = kalloc(fake_kalloc_size);

    for (int i = 0; i < 0x200; i++) {
        wk64(fake_vtable+i*8, rk64(IOSurfaceRootUserClient_vtab+i*8));
    }

    // Create the fake user client
    fake_client = kalloc(fake_kalloc_size);

    for (int i = 0; i < 0x200; i++) {
        wk64(fake_client+i*8, rk64(IOSurfaceRootUserClient_addr+i*8));
    }

    // Write our fake vtable into the fake user client
    wk64(fake_client, fake_vtable);

    // Replace the user client with ours
    wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, fake_client);

    // Replace IOUserClient::getExternalTrapForIndex with our ROP gadget (add x0, x0, #0x40; ret;)
    wk64(fake_vtable+8*0xB7, find_add_x0_x0_0x40_ret());

}

The find_port(user_client) looks up the specific address of the port in our task. The actual address of the IOSurfaceRootUserClient instance is grabbed from the kobject member. From now on, there are multiple venues that can get authors to the objective. One of the venues includes faking an object with a fake vtable, which is subsequently written in the the kobject member.

The team could rewrite the vtable record of getExternalTrapForIndex(index) of a spawned fake object with a function that would return a controlled value (e.g. getRegistryEntryID(), ...). The value is controlled because of the arbitrary r/w capability. 

Another way, is to replace the getExternalTrapForIndex(index) function with a gadget that would advance a controlled address to the place, where they can safely create an IOExternalTrap object.

The authors went with the latter. Firstly, a fake vtable is initialized in the heap. Afterward, up to 0x200 records from the original vtable of the IOSurfaceRootUserClient are copied into the fake vtable. A fake object of the fake_kalloc_size size is initialised. The 0x200 properties (including vtable address, refcount, etc.) from the original object are copied into the fake one. The address of fake vtable is written to the fake object. The object is now ready to be written into the kobject member.

The vtable record of the method IOUserClient::getExternalTrapForIndex is rewritten with an address of the "add x0, x0, #0x40; ret;" gadget.

Let's recapitulate the situation, what happens when we fire up iokit_user_client_trap(...). The kernel will looks up our fake object instance and calls the gadget. Do you remember what's the first parameter (stored in x0) of the call? Yes, that's the address of our fake object with the controlled data. The gadget will advance the value of x0 #0x40 upfront, so that it skips the vtable, refcount, etc because we do not want to screw that up. The advanced address of x0 is actually treated as the address of IOExternalTrap object. The goal is to fake IOExternalTrap object at that address.

The member called target would point the address of the object upon which the function is going to be called. However, in our case this represents the first argument sent to the function.

The "func" member of this object would be an address of the function that the kernel calls.

The following function called kexecute(...) creates a fake IOExternalTrap object, where the x0 represents the "this" pointer and the addr represents function that the kernel would call. From now on, the IOConnectTrap6(...) provides us a limited way to call almost arbitrary functions. The limitation comes from the restriction of the first arguments to be not NULL, the maximum number of parameters is 7 and the return value is truncated to 32bits. Besides that, the function fix up the members that were tampered with. 

uint64_t kexecute(uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6) {

    uint64_t offx20 = rk64(fake_client+0x40);
    uint64_t offx28 = rk64(fake_client+0x48);
    wk64(fake_client+0x40, x0);
    wk64(fake_client+0x48, addr);
    uint64_t returnval = IOConnectTrap6(user_client, 0, (uint64_t)(x1), (uint64_t)(x2), (uint64_t)(x3), (uint64_t)(x4), (uint64_t)(x5), (uint64_t)(x6));
    wk64(fake_client+0x40, offx20);
    wk64(fake_client+0x48, offx28);

    pthread_mutex_unlock(&kexecute_lock);

    return returnval;
}

The term_kexecute writes back the original address of IOSurfaceRootUserClient and cleans the fake object with the fake vtable out of the memory.

void term_kexecute(void) {
    wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, IOSurfaceRootUserClient_addr);
    kfree(fake_vtable, fake_kalloc_size);
    kfree(fake_client, fake_kalloc_size);
}

If you would like to know how this technique can be boosted utilizing JOP chains to introspect the kernel (at least on older devices), please check the wonderful blog post https://bazad.github.io/2017/09/live-kernel-introspection-ios/. 

We would like to thank Brandon Azad for reviewing this blog.

References: 



Krátky pohľad na implementáciu kexecute v jailbreaku Electra


Pred niekoľkými mesiacmi bol zverejnený jailbreak nazvaný Electra (1). Jailbreak je vyvinutý pre iOS verzie 11.2 - 11.3.1 s využitím kernel exploitov (2-3), ktoré zverejnil Ian Beer z Project Zero spoločnosti Google.

Tím za jailbreakom Electra kombinuje a elegantne využíva viacero verejne známych techník, aby dosiahol oslabenie bezpečnostných mechanizmov a spustenie nepodpísaného kódu na zariadení.

Tento krátky príspevok neobsahuje žiadne nové techniky, ale skôr podrobne opisuje implementáciu kexecute, ktorá slúži na obmedzené, no za to efektívne volania funkcií jadra z tzv. userspace.

Pôvodne sme chceli porovnať Electra kexecute s kexecute poskytovanou Qilinom (4) (známy post-exploitačný framework) avšak v čase písania tohto príspevku, Qilin exportuje funkciu kexecute, avšak knižnica neobsahuje jej implementáciu (shasum: fabc6a8d99b1c2cdd35277533b003f630fd2a2bf).

Počas post-exploitácií sa ocitáme v pozícii, keď priame volanie funkcií jadra nás môže zachrániť pred komplikovanými situáciami. Simulácia volaní kernel funkcií za pomoci schopnosti čítať a zapisovať ľubovoľné hodnoty na ľubovoľné adresy môže byť naozaj komplikovanou úlohou. Cieľom je nechať jadro pracovať pre nás!

Táto technika bola uverejnená v prednáške "Tales from iOS 6 Exploitation and iOS 7 Security Changes" od Stefana Essera (5).

V prvom rade potrebujeme pohodlný spôsob, ako preniesť argumenty poslané používateľom do jadra. Pozrime sa teda, čo nám poskytne IOKit framework.

kern_return_t
IOConnectTrap6(io_connect_t connect,
        uint32_t  index,
        uintptr_t p1,
        uintptr_t p2,
        uintptr_t p3,
        uintptr_t p4,
        uintptr_t p5,
        uintptr_t p6 )
{
    return iokit_user_client_trap(connect, index, p1, p2, p3, p4, p5, p6);
}

IOConnectTrap6(...) umožňuje používateľom zaslať až 6 argumentov do kernelland. Funkcia automaticky vyvolá iokit_user_client_trap(...).

Funkcia iokit_user_client_trap(...) vracia návratovú hodnotu z funkcie zavolanej nad inštanciou IOExternalTrap, ktorá je popísaná nasledujúcou štruktúrou:

struct IOExternalTrap {
 IOService * object;
 IOTrap func;
};

Následne iokit_user_client_trap(...) funkcia vyvolá trap funkciu s argumentmi, ktoré sú zaslané používateľom.

kern_return_t iokit_user_client_trap(struct iokit_user_client_trap_args *args)
{
    kern_return_t result = kIOReturnBadArgument;
    IOUserClient *userClient;

    if ((userClient = OSDynamicCast(IOUserClient,
            iokit_lookup_connect_ref_current_task((OSObject *)(args->userClientRef))))) {
        IOExternalTrap *trap;
        IOService *target = NULL;

        trap = userClient->getTargetAndTrapForIndex(&target, args->index);

        if (trap && target) { // [A]
            IOTrap func;

            func = trap->func;

            if (func) {
                result = (target->*func)(args->p1, args->p2, args->p3, args->p4, args->p5, args->p6);
            }
        }

 iokit_remove_connect_reference(userClient);
    }

    return result;
}

Môžme kontrolovať target a trap, ktoré sú vrátené z funkcie getTargetAndTrapForIndex(...)?

Z nasledujúceho urývku zdrojového kódu je jasné, že ak sa nám podarí nahradiť funkcia getExternalTrapForIndex(...) inštancie IOUserClient, inou, nami kontrolovanou funkciou, máme pod kontrolou *targetP a navrátený trap. Z nami ovládaného trapu vieme kontrolovať ukazovateľ na funkciu (target->*func), ktorú kernel zavolá.

IOExternalTrap * IOUserClient::
getTargetAndTrapForIndex(IOService ** targetP, UInt32 index)
{
      IOExternalTrap *trap = getExternalTrapForIndex(index);

      if (trap) {
              *targetP = trap->object;
      }

      return trap;
}

Generická metóda volania ľubovoľných funkcií je modifikovať vtable IOUserClient inštancie tak, že vyvolanie iokit_user_client_trap(...) nakoniec zavolá nami kontrolovaný ukazovateľ na funkciu. Vzhľadom na to, že inštancie IOUserClient sú alokované na halde a správajú sa v pamäti ako C++ objekty, prvou adresou C++ inštancií uložených v pamäti je adresa takzvanej vtable. Vtable obsahuje záznamy adries všetkých virtuálnych funkcií, ktoré môžu byť nad inštanciou zavolané. Okrem toho konvencia volaní metód vyžaduje aby prvý argument zaslaný do volanej funkcie bol ukazovateľ "this". Tento ukazovateľ odkazuje na samého seba. Podľa if podmienky [A] vidíme, že tento argument nemôže byť NULL.

Adresy IOUserClient inštancií sú uložené v portoch respektíve v ich ip_kobject, čo je možné vidieť ak sa bližšie pozrieme na implementáciu iokit_lookup_connect_ref(...) z predchádzajúceho úryvku:

EXTERN io_object_t
iokit_lookup_connect_ref(io_object_t connectRef, ipc_space_t space)
{
 io_object_t obj = NULL;

 if (connectRef && MACH_PORT_VALID((mach_port_name_t)connectRef)) {
  ipc_port_t port;
  kern_return_t kr;

  kr = ipc_object_translate(space, (mach_port_name_t)connectRef, MACH_PORT_RIGHT_SEND, (ipc_object_t *)&port);

  if (kr == KERN_SUCCESS) {
            assert(IP_VALID(port));
            
            if (ip_active(port) && (ip_kotype(port) == IKOT_IOKIT_CONNECT)) {
                obj = (io_object_t) port->ip_kobject;
                iokit_add_reference(obj);
            }
            
            ip_unlock(port);
  }
 }

 return obj;
}

Pozrime sa na konkrétnu implementáciu v podaní Electra tímu. Upozorňujeme, že kód je len úryvok a sú znázornené iba dôležité riadky.

mach_port_t prepare_user_client(void) {
...
  io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot"));

  err = IOServiceOpen(service, mach_task_self(), 0, &user_client);
...
  return user_client;
}

Funkcia prepare_user_client() vytvorí IOSurfaceRoot inštanciu a vráti jej adresu. 
Bližší pohľad na nasledujúcu funkciu odhaľuje skutočnú implementáciu požadovanej funkcionality.

void init_kexecute(void) {
    user_client = prepare_user_client();

    // From v0rtex - get the IOSurfaceRootUserClient port, and then the address of the actual client, and vtable
    IOSurfaceRootUserClient_port = find_port(user_client); // UserClients are just mach_ports, so we find its address

    IOSurfaceRootUserClient_addr = rk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject); // The UserClient itself (the C++ object) is at the kobject field

    uint64_t IOSurfaceRootUserClient_vtab = rk64(IOSurfaceRootUserClient_addr); // vtables in C++ are at *object

    // The aim is to create a fake client, with a fake vtable, and overwrite the existing client with the fake one
    // Once we do that, we can use IOConnectTrap6 to call functions in the kernel as the kernel

    // Create the vtable in the kernel memory, then copy the existing vtable into there
    fake_vtable = kalloc(fake_kalloc_size);

    for (int i = 0; i < 0x200; i++) {
        wk64(fake_vtable+i*8, rk64(IOSurfaceRootUserClient_vtab+i*8));
    }

    // Create the fake user client
    fake_client = kalloc(fake_kalloc_size);

    for (int i = 0; i < 0x200; i++) {
        wk64(fake_client+i*8, rk64(IOSurfaceRootUserClient_addr+i*8));
    }

    // Write our fake vtable into the fake user client
    wk64(fake_client, fake_vtable);

    // Replace the user client with ours
    wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, fake_client);

    // Replace IOUserClient::getExternalTrapForIndex with our ROP gadget (add x0, x0, #0x40; ret;)
    wk64(fake_vtable+8*0xB7, find_add_x0_x0_0x40_ret());

}

Funkcia find_port(user_client) vyhľadá adresu portu v našom tasku. Adresa IOSurfaceRootUserClient inštancii je vyextrahovaná z kobject. Od tohto momentu, existuje viacero možností, ktoré môžu dostať autorov k cieľu.

Tím mohol prepísať vtable záznam getExternalTrapForIndex(...) falošného objektu s adresou funkcie, ktorá by vrátila nimi kontrolované hodnoty (napr. getRegistryEntryID(), ...). Hodnota je kontrolovaná vzhľadom na schopnosť prepisovať ľubovoľné hodnoty na ľubovoľnom mieste.

Ďalšou možnosťou je nahradiť getExternalTrapForIndex(index) tzv. gadgetom, ktorý posunie kontrolovanú adresu v registri na miesto, kde môžu vytvárať ľubovoľné dáta bez toho aby sa niečo pokazilo.

Táto metóda je využitá v Electre. Najprv, je v hlade vytvorená a inicializovaná falošná vtable. Následne až 0x200 záznamov z pôvodnej vtable IOSurfaceRootUserClient inštancie je skopírovaných do falošnej vtable. Falošná inštancia o veľkosti _kalloc_size je inicializovaná na halde. Až 0x200 členov originálnej inštancie (vrátane adresy vtable address, refcount, atď..)  je skopírovaných do falošnej inštancii. Adresa falošnej vtable je zapísaná do falošnej inštancii. Takto vytvorená inštancia je následne zapísaná do kobject v tasku.

Vtable záznam metódy IOUserClient::getExternalTrapForIndex je prepísaný adresou, kde sa nachádza gadget "add x0, x0, #0x40; ret;".

Pozrime sa, čo sa stane ak zavoláme iokit_user_client_trap(...). Kernel vyhľadá našu falošnú inštanciu a zavolá nad ňou spomenutý gadget. Pamätáte si čo je prvý parameter zaslaný do metódy(momentálne uložený v x0)? Samozrejme sa jedná o adresu nášho falošného objektu s kontrolovateľnými dátami. Gadget posunie adresu v x0 #0x40 dopredu, tak, že x0 preskakuje miesta, ktoré nemôžeme pokaziť ako napr. adresa vtable, refcount, etc. Táto adresa uložená v x0 je následne použitá ako adresa objektu IOExternalTrap. Cieľom je vytvoriť falošný IOExternalTrap objekt na tejto adrese.

Člen nazvaný target bude ukazovať na objekt nad ktorým bude metóda volaná. V našom prípade je tento člen použitý ako implicitne zaslaný prvý argument volanej metódy.

Člen "func" bude ukazovať na adresu, ktorú kernel zavolá.

Nasledujúca funkcia kexecute(...) vytvára takýto falošný IOExternalTrap objekt, kde x0 reprezentuje "this" ukazovateľ a addr reprezentuje adresu funkcie, ktorú kernel zavolá.

IOConnectTrap6(...) nám týmto spôsobom poskytuje možnosť volať takmer ľubovoľnú funkciu z kernelu s určitými obmedzeniami. Limitácie sú nasledujúce:  prvý argument nemôže byť NULL, maximálny počet zaslaných argumentov je 7, návratová hodnota je zoseknutá na 32 bitov.

Okrem toho, táto funkcia upravuje členy, ktoré boli zmenené na originálne.

uint64_t kexecute(uint64_t addr, uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6) {

    uint64_t offx20 = rk64(fake_client+0x40);
    uint64_t offx28 = rk64(fake_client+0x48);
    wk64(fake_client+0x40, x0);
    wk64(fake_client+0x48, addr);
    uint64_t returnval = IOConnectTrap6(user_client, 0, (uint64_t)(x1), (uint64_t)(x2), (uint64_t)(x3), (uint64_t)(x4), (uint64_t)(x5), (uint64_t)(x6));
    wk64(fake_client+0x40, offx20);
    wk64(fake_client+0x48, offx28);

    pthread_mutex_unlock(&kexecute_lock);

    return returnval;
}

Funkcia term_kexecute zapisuje naspať pôvodnú adresu inštancie IOSurfaceRootUserClient a uvoľnuje falošný objekt s falošnou vtable z pamäte.

void term_kexecute(void) {
    wk64(IOSurfaceRootUserClient_port + offsetof_ip_kobject, IOSurfaceRootUserClient_addr);
    kfree(fake_vtable, fake_kalloc_size);
    kfree(fake_client, fake_kalloc_size);
}

Ak by ste chceli vedieť ako táto technika môže byť vylepšená a použitá na skúmanie jadra za pomoci JOP chains, odporúčame prečítať si blog dostupný na https://bazad.github.io/2017/09/live-kernel-introspection-ios/.

Ďakujeme Brandovoni Azadovi za revíziu blogu a následné usmernenie.

Odkazy:

(1) https://coolstar.org/electra/
(2) https://bugs.chromium.org/p/project-zero/issues/detail?id=1564
(3) https://bugs.chromium.org/p/project-zero/issues/detail?id=1558
(4) http://newosxbook.com/QiLin/
(5) https://conference.hitb.org/hitbsecconf2013kul/materials/D2T2 - Stefan Esser - Tales from iOS 6 Exploitation and iOS7 Security Changes.pdf