[Translated by Jouko Voutilainen (KYBS2001 course assignment at JYU.FI)]
Vasemmallani on Omer Gull, ja hän puhuu meille aiheesta "SELECT code_execution FROM * USING SQLite".
Omer, Omer Gull, sinun esityksesi, sinun lavasi, pidä hauskaa!
Kiitos. Kiitos.
[Yleisö taputtaa]
Hei kaikille.
Tervetuloa esitykseeni "SELECT code_execution FROM * USING SQLite",
jossa saavutamme koodin suorittamisen (code execution) haavoittuvaisia SQLite-tietokantoja käyttäen.
Nimeni on Omer Gull. Olen haavoittuvuuksien tutkija Tel Avivista.
Olen työskennellyt Check Point Researchissa viimeiset kolme vuotta
ja olen äskettäin siirtynyt uuteen startupiin nimeltä Hunters.AI.
Agendamme tänään:
Aloitamme pienellä motivaatiolla ja taustatarinan kertomisella tästä tutkimuksesta.
Sen jälkeen meillä on lyhyt SQLite-johdanto ja tutkimme haitallisten tietokantojen hyökkäyspintaa.
Käsittelemme myös aiempaa työtä SQLite-hyödyntämisessä ja mietimme,
miten käyttää muistin korruptiohäiriöitä pelkästään SQL:n avulla.
Sitten esittelemme oman innovatiivisen tekniikkamme nimeltä "Query Oriented Programming" tai QOP,
ja demonstroimme sitä muutamassa esimerkissä.
Käärimme asiat yhteen tulevaisuuden mahdollisuuksien ja johtopäätösten kanssa.
Motivaatio tälle tutkimukselle on melko ilmeinen.
SQLite on yksi eniten käytetyistä ohjelmistoista.
Olipa kyse PHP 5:stä, PHP 7:stä, Androidista, iOS:stä,
Mac OS:stä, se on nyt sisäänrakennettu Windows 10:een.
Sitä on Firefoxissa ja Chromessa.
Tämä luettelo voisi jatkua loputtomiin.
Kuitenkin SQLite-tietokannan kyselyä pidetään
turvallisena. Toivottavasti tämän puheen lopussa tajuat,
miksi tämä ei välttämättä ole totta.
Kaikki alkoi salasanavarkaista,
mikä on melko outoa, ja niitä on paljon, paljon vapaana, mutta tarina on yleensä sama.
Ensinnäkin tietokone saa tartunnan.
Sitten haittaohjelma kerää tallennettuja käyttäjätunnuksia,
sillä niitä on tallennettuna eri asiakasohjelmistoihin.
Jotkut näistä asiakasohjelmista tallentavat salaisuutesi SQLite-tietokantoihin.
Haittaohjelma lähettää SQLite-tietokantoja C2-palvelimelle, jossa salaisuudet poimitaan
ja tallennetaan yhteiseen tietokantaan
muun saaliin kanssa.
Eräänä päivänä kollegani Omri ja minä tutkimme erittäin tunnetun salasanavarkaan vuodattamia lähdetiedostoja.
Ajattelimme: "Nämä kaverit vain keräävät tietokantojamme ja parsivat ne omaan back-endiinsä.
Voimmeko todella hyödyntää luotettamattoman tietokannan latausta ja kyselyä?"
Ja jos voimme, sillä voisi olla paljon suuremmat vaikutukset,
koska SQLiteä käytetään lukemattomissa skenaarioissa. Ja niin alkoi tähänastisen elämäni pisin CTF-haaste.
SQLite. Toisin kuin useimmat SQL-tietokannat, SQLite:llä ei ole asiakas-palvelinarkkitehtuuria.
Sen sijaan se lukee ja kirjoittaa tiedostoja suoraan tiedostojärjestelmään.
Joten sinulla on yksi täydellinen tietokanta, jossa on useita taulukoita, indeksejä, laukaisimia ja näkymiä,
ja kaikki sisältyvät yhteen tiedostoon. Tutkitaan siis hyökkäyspintaa, jonka potentiaalisesti haitallinen SQLite-tietokanta antaa.
Tämä on palanen koodia erittäin tunnetusta salasanavarkaasta,
ja meillä on kaksi tärkeintä kiinnostuksen kohdetta: Ensinnäkin meillä on sqlite3_open,
jossa potentiaalisesti haitallisen tietokanta ladataan,
ja jotain parsimista tapahtuu.
Ja ilmeisesti meillä on itse kysely. SELECT-lauseke.
Huomaa kuitenkin, että meillä ei ole hallintaa siitä lausekkeesta.
Se on kovakoodattu kohteeseemme.
Se yrittää poimia salaisuudet tietokannastamme.
Mutta me hallitsemme sisältöä, joten meillä voi olla vaikutusta siihen, mitä siellä tapahtuu.
Ensimmäinen asia on sqlite3_open,
joka on vain joukko asetus- ja kokoonpanokoodia.
Seuraavaksi siirrymme suoraviivaiseen otsikkotiedoston parsimiseen,
otsikkotiedosto itse ei ole kovin pitkä, vain 100 tavua.
Kolmanneksi, se on jo fuzzattu kuoliaaksi AFL:llä.
Joten se ei ehkä ole kovin lupaava polku jatkaa.
Mutta Sqlite3_query saattaa olla hieman mielenkiintoisempi,
koska SQLite:n tekijän mukaan "SELECT-lauseke on SQL-kielen monimutkaisin käsky".
Voit tietää, että taustalla SQLite on virtuaalikone.
Joten jokainen kysely on ensin käännettävä joksikin tavukoodin muotoon.
Tätä kutsutaan myös valmisteluvaiheeksi.
Joten sqlite3_prepare kulkee ja laajentaa tuon kyselyn.
Esimerkiksi joka kerta, kun valitset asteriskin,
se kirjoittaa tämän asteriskin kaikkien sarakkeiden nimiksi.
Joten sqlite3LocateTable() varmistaa,
että kaikki kyselyssä käytetyt taulut ja sarakkeet todella olemassa ja sijaitsevat muistissa.
Mistä se löytää ne?
Jokaisella SQLite-tietokannalla on taulu nimeltään sqlite_master,
joka määrittelee tietokannan skeeman.
Ja tämä on sen rakenne.
Joten jokaiselle tietokannan objektille sinulla on merkintä,
jossa on sen tyyppi, taulu tai näkymä, sen nimi ja aivan alareunassa jotain, jota kutsutaan SQL:ksi.
Ja SQL on itse asiassa DDL, joka kuvaa objektia.
DDL tarkoittaa datamäärittelykieltä, ja sitä voidaan verrata C-kielen otsikkotiedostoihin.
Ne määrittelevät tietokannan objektien rakenteen, nimet ja tyypit.
Lisäksi ne näkyvät selkokielisinä tiedostossa.
Annan esimerkin. Avasin SQLite-tulkin, loin taulun ja lisäsin siihen arvoja.
Sitten lopetin tulkin ja nyt avaamme tiedoston heksadesimaalimuodossa.
Voit nähdä keltaisella korostettuna näkyvän DDL-lauseke, joka on osa pääskeemaa.
Alhaalla voit myös nähdä arvot.
Palataan kyselyn valmisteluun.
Meillä on sqlite3LocateTable, joka yrittää löytää taulun rakenteen, jota haluamme kysellä.
Se lukee skeeman sqlite_masterista, jonka juuri kuvailimme.
Ja jos se tekee sen ensimmäistä kertaa,
sillä on joitakin takaisinkutsufunktioita jokaiselle näistä DDL-lausekkeista.
Takaisinkutsufunktio validoi DDL:n ja rakentaa sen jälkeen sisäiset rakenteet kyseisestä objektista.
Sitten ajattelimme DDL:n paikkaamista.
Entä jos vain korvaamme SQL-kyselyn DDL:ssä?
Tämä johtaa kuitenkin pieneen ongelmaan.
Tämä on aiemmin mainitsemani takaisinkutsufunktio, ja kuten näet,
DDL tarkistetaan ensin aloittavan "create ":lla. Vasta sen jälkeen valmistelu jatkuu.
Tämä on ehdottomasti rajoitus, eikö niin?
Meidän DDL:n on aloitettava "create ":lla.
Mutta tämä jättää jonkin verran tilaa joustavuudelle,
sillä SQLite-dokumentaation perusteella monia asioita voidaan luoda.
Voimme luoda indeksejä, taulukoita, laukaisimia, näkymiä ja jotain,
mitä emme vielä täysin ymmärrä, nimeltä virtuaaliset taulukot.
Niinpä ajattelimme "CREATE VIEW":ta, koska näkymä on vain esipakattu SELECT-lauseke,
ja näkymiä kysytään hyvin samalla tavalla kuin taulukoita.
Joten, taulukosta sarakkeen valitseminen on semanttisesti sama asia kuin sarakkeen valitseminen näkymästä.
Sitten, kun ajattelimme kyselyn kaappaamisen käsitettä.
Aiomme paikata sqlite_master DDL:n näkymillä taulukoiden sijaan.
Nyt paikatut näkymät voivat olla mitä tahansa SELECT-alilauseketta, jonka haluamme.
Ja nyt tällä alilausekkeella voin yhtäkkiä käyttää SQLite-tulkintaa.
Ja tämä on valtava askel eteenpäin! Muutimme juuri hallitsemattoman kyselyn joksikin,
jota voimme jonkin verran hallita.
Joten anna minun näyttää sinulle esimerkki kyselyn kaappaamisesta.
Sanotaan, että joissakin alkuperäisissä tietokannoissa oli yksi taulu,
ja tämä on DDL, joka määrittelee sen. Joten se on nimeltään "dummy" ja sillä on kaksi saraketta.
Joten ilmeisesti mikä tahansa kohdesovellus yrittäisi kysyä sitä seuraavasti:
Se vain yrittäisi valita nämä sarakkeet taulukosta, eikö niin.
Mutta seuraava näkymä voi todella kaapata tämän kyselyn.
Luon näkymän, se on juuri samanniminen ja sillä on sama määrä sarakkeita,
ja jokainen sarake on nimetty samalla tavalla, ja voit nähdä,
että nyt jokainen sarake voi sisältää minkä tahansa alilausekkeen,
jonka haluan, korostettuna sinisellä alareunassa.
Joten anna minun näyttää sinulle käytännön esimerkki siitä.
Tässä olen luonut näkymän nimeltä "dummy" col A ja col B:llä,
ja ensimmäinen sarake käyttää sqlite_version()-funktiota, ja se on sisäänrakennettu funktio,
joka palauttaa SQLite-version, ilmeisesti.
Toinen sarake käyttää SQLite:n omaa toteutusta printf:stä.
Juuri näin, niillä on kaikki nämä todella yllättävät ominaisuudet ja kyvyt.
Joten katsotaan sitä nyt oletetusti - kohdesivulta.
Joten kuka tahansa, joka yrittää valita näistä sarakkeista,
suorittaa yhtäkkiä meidän funktioitamme.
Vasemmalla voit nähdä SQLite-version, ja oikealla voit nähdä printf:n,
joka suoritettiin kohdesivulla. Joten taas tämä on valtava edistysaskel.
Me saavutimme juuri jonkin verran kontrollia sen kyselyn yli, eikö niin?
Ja kysymys kuuluu, mitä voimme tehdä tällä kontrollilla.
Onko SQLite:llä mitään järjestelmäkomentoja?
Voimmeko ehkä lukea ja kirjoittaa jotain muita tiedostoja tiedostojärjestelmässä?
Joten tämä oli hyvä kohta pysähtyä ja tarkastella jo tehtyä työtä alalla,
koska ilmeisesti emme ole ensimmäiset,
jotka ovat huomanneet SQLite:n valtavan potentiaalin hyväksikäytössä.
Järkevä paikka aloittaa on SQLite-injektio,
koska tämä on jonkinlainen vastaava tilanne, eikö niin?
Jollakulla haitallisella on jonkinlainen kontrolli SQL-kyselyssä.
Joten, on olemassa muutama tunnettu temppu SQL-injektiossa SQLite:llä.
Ensimmäisellä on tekemistä toisen tietokannan liittämisen kanssa
ja sen jälkeen taulun luomisen ja joitain merkkijonoja sen sisään laittamisen.
Ja koska, kuten mainitsin aiemmin, jokainen tietokanta on vain tiedosto,
tämä on jonkinlainen mielivaltainen tiedosto, tiedostojärjestelmässä.
Kuitenkin meillä on tämä rajoitus, muistattehan, että emme voi liittää,
koska DDL:n on aloitettava "CREATE"-sanalla.
Toinen hieno temppu on käyttää load extension -funktiota väärin.
Ja tässä voit nähdä, kuinka voit mahdollisesti ladata etä-DLL:n.
Tässä tapauksessa meterpreter.dll,
mutta ilmeisesti tämä erittäin vaarallinen funktio on oletuksena pois käytöstä.
Joten se taas ei onnistu. Entä muistin korruptio SQLite:ssä,
koska SQLite on todella monimutkainen ja se on kaikki kirjoitettu C:llä.
Hänen hämmästyttävässä blogikirjoituksessaan "Virheiden löytäminen SQLite:ssä helposti",
Michal Zalewski, AFL:n tekijä, kuvasi,
kuinka hän löysi 22 bugia alle 30 minuutissa fuzzingilla
Ja itse asiassa, sen jälkeen,
kun kyseessä oli versio 3.8.10 vuonna 2015,
SQLite alkoi käyttää AFL:ää osana huomattavaa testisarjaa.
Nämä muistin korruptiovirheet osoittautuivat kuitenkin erittäin vaikeiksi hyödyntää ilman sopivaa ympäristöä.
Turvallisuustutkimusyhteisö löysi kuitenkin pian täydellisen kohteen,
ja se oli nimeltään WebSQL.
Joten WebSQL on periaatteessa API tietojen tallentamiseen tietokantoihin,
ja sitä kysytään JavaScriptistä, ja sillä on SQLite-tausta.
Lisäksi se on saatavana Chromessa ja Safarissa.
Tässä voit nähdä hyvin yksinkertaisen esimerkin siitä, miten voit käyttää WebSQL:ää JavaScriptistä.
Mutta toisin sanoen, mitä täällä luulen, on se,
että meillä on joitakin luottamattomia syötteitä SQLite:lle,
ja se on saavutettavissa mistä tahansa Internetin verkkosivustosta
kahdessa maailman suosituimmassa selaimessa.
Ja yhtäkkiä nämä virheet,
nämä muistin korruptiot, voidaan nyt hyödyntää JavaScript-hyväksikäytön tiedosta ja mukavuudesta.
JavaScript-tulkin hyväksikäyttö, jossa olemme tulleet varsin hyviksi vuosien varrella.
Joten WebSQL:stä on julkaistu useita vaikuttavia tutkimuksia,
jotka vaihtelevat todella helppoista kohteista,
kuten CVE-2015-7036,
joka oli luottamattoman osoittimen purku fts3_tokenizer()-funktiossa,
monimutkaisempiin hyökkäyksiin,
kuten Blackhat 2017:ssä esitelty Chaitin-tiimin löytämä tyyppisekoittumisongelma FTS-optimoinnissa,
sekä hiljattain Magellan-bugit, jotka Tencent löysi,
jotka löysivät kokonaisluvun ylivuodon FTS-segmentinlukijassa.
Ja jos kiinnität nyt edes hieman huomiota,
niin näet mielenkiintoisen kaavan nousevan esiin.
Kaikki nämä haavoittuvat funktiot alkavat FTS:llä.
Joten mikä on FTS?
En ole koskaan kuullut siitä.
Ja kun etsin sitä Googlesta,
se vain hämmensi minua vielä enemmän.
Jonkin ajan kuluttua tajusin,
että FTS tarkoittaa "täysi tekstihaku" ja se on jotain nimeltään virtuaalinen taulukkomoduuli,
joka mahdollistaa todella hienovaraisen tekstin haun dokumenttien joukosta.
Tai kuten SQLite-tekijät kuvasivat sitä,
se on "kuin Google SQLite-tietokannoillesi".
Joten virtuaalitaulukot mahdollistavat joitain todella hienoja toimintoja SQLite:ssa,
olipa kyseessä tämä vapaa tekstin haku tai virtuaalitaulukkomoduuli nimeltään RTREE,
joka tekee todella fiksua maantieteellistä indeksointia,
tai virtuaalitaulukko nimeltään CSV,
joka antaa sinun käsitellä tietokantaasi CSV-tiedostona.
Ja näitä virtuaalitaulukoita kysytään itse asiassa ihan kuin normaaleja taulukoita.
Kuitenkin taustalla tapahtuu joitain pimeitä taikoja.
Ja jokaisen kyselyn jälkeen, kutsutaan jonkinlaista takaisinkutsufunktiota,
joka toimii niin kutsuttua shadow-taulukkoa vastaan.
Varjotaulukot olisi parasta selittää esimerkin avulla.
Joten sanotaan, että luon virtuaalitaulukon käyttäen sitä FTS-virtuaalitaulukkomoduulia
ja lisään siihen merkkijonon.
Ilmeisesti tehokkaan haun sallimiseksi minulla täytyy olla joitain metatietoja, okei?
Minun täytyy olla joitain offsetteja tai indeksejä tai merkkejä tai jotain sellaista.
Ja ilmeisesti kaikki ne ovat tekstiä, eikö niin?
Joten yksi virtuaalitaulukko on itse asiassa raakatekstiä,
ja metatiedot tallennetaan kolmen varjoshadow-taulukon joukkoon.
Joten raakateksti menisi vt_contentiin ja metatiedot menisivät vt_segmentsiin ja vt_segdiriin.
Ajan myötä nämä varjotaulukot ovat liittymäpintoja,
jotka siirtävät tietoa toistensa välillä.
Koska metatieto tallentaa kaikki nämä osoittimet,
sinun on siirrettävä ne toistensa välillä.
Ja nämä liittymäpinnat osoittautuivat todella,
todella luottavaisiksi luonteeltaan.
Ja se tekee niistä todella otollisen maaperän bugin metsästykseen.
Näytän teille nyt bugin, jonka löysin RTREE-virtuaalitaulukkomoduulista.
RTREE-virtuaalitaulumoduuli on nyt saatavilla MacOS- ja iOS-laitteille,
ja se on todella hieno, koska se on nyt myös osa Windows 10:ää.
Ja kuten mainitsin, se tekee todella fiksua maantieteellistä indeksointia.
DDL:n tulisi olla seuraava. Minkä tahansa RTREE-virtuaalitaulun tulisi alkaa "id":llä,
jonka täytyy olla kokonaisluku. Sen jälkeen sinulla on joitakin X- ja Y-koordinaatteja.
Joten jokainen RTREE-liitäntä odottaisi "id":n olevan kokonaisluku.
Mutta jos luon virtuaalitaulun ja lisään "id":hen jotain,
joka ei ole kokonaisluku, ja käytän yhtä näistä RTREE-liitännäisistä,
rtreenode, näet alhaalla, että saamme tämän kaatumisen,
tämän kekomuistin ulkopuolisen lukemisen.
Ja tämä esimerkki on kaatuminen Windows 10:ssä.
Joten se on aika hyvä. Olemme havainneet,
että virtuaalitaulussa on bugeja.
Ja nyt, käyttämällä kyselyn kaappaustekniikkaa,
voimme yhtäkkiä laukaista nämä bugit kohteellamme,
joka on salasanavaras C2, ja se kaatuu (segfault).
Ja tämä on hienoa, mutta varsinaisen ohjauksen saaminen kohteestamme
vaatii jonkinlaista skriptausta, eikö niin?
Haluamme ohittaa ASLR:n ja tehdä kaikenlaisia hulluja asioita.
Meillä ei kuitenkaan ole JavaScriptiä,
meillä ei ole JavaScript-taulukoita ja muuttujia ja logiikkalauseita,
kuten if ja and silmukoita ja sellaisia. Kuitenkin muistamme jostain kuulleemme,
että SQL on Turing-täydellinen.
Joten päätimme testata sitä hyökkäysnäkökulmasta
ja aloimme luoda omaa primitiivista toivelistaa hyökkäystä varten.
Joten jos se voisi luoda täyden hyökkäyksen,
jossa hyödynnetään muistin korruptiobugeja pelkästään SQL:llä,
mitä kyvykkyyksiä haluamme?
Joten ilmeisesti ohittaaksemme ASLR:n ja nämä asiat,
tarvitsemme vuodon muistista.
Meidän täytyy saada tietovuoto.
Jos olet tehnyt pwningia menneisyydessäsi,
sinun täytyy olla tuttu todella yleisten tehtävien suhteen,
kuten 64-bittisten osoittimen purkamisesta ja joitakin osoitinlaskutoimituksia, eikö niin?
Koska meillä oli tietovuoto, muunnamme - luemme tämän osoittimen,
ja se on little-endian, joten meidän täytyy kääntää se,
ja nyt haluamme laskea, missä on libsqlite-tiedoston perusta,
jotta voimme löytää ehkä lisää funktioita.
Joten tarvitsemme joitakin osoitinlaskutoimituksia.
Luonnollisesti, kun olemme lukeneet osoittimet ja manipuloineet niitä,
haluamme pakata ne uudelleen ja kirjoittaa ne jonnekin.
Yhden osoittimen kirjoittaminen ei koskaan riitä.
Haluamme luoda väärennettyjä objekteja muistissa,
monimutkaisempia kuin yksi osoitin. Lopuksi, haluaisimme heap sprayn,
koska se voi olla todella hyödyllinen.
Joten, kysymys kuuluu,
voiko kaikki tämä hyödyntäminen tehdä pelkästään SQL:llä?
Vastaus on "kyllä, se on mahdollista".
Ja esittelen teille ylpeänä Kysely-orientoitunut ohjelmointi tai QOP.
Demonstroidakseni QOP:ia hyväksikäytämme haavoittuvuutta CVE-2015-7036.
Ja saatatte kysyä itseltänne:
Miten neljä vuotta vanha bugi on edelleen korjaamatta?
Ja tämä on hieno asia argumenttiimme.
Tämä CVE koettiin vaaralliseksi vain luottamattomien Web-SQL:n suhteen.
Se siis kierrettiin oikein.
Se on blacklistattu ja SQL on käännetty tietyllä lipulla.
Joten selkeästi selaimet eivät enää ole kääntäneet tällä lipulla.
Mutta anna minun näyttää sinulle,
minkä kanssa nämä liput on käännetty.
Joten meillä on PHP 5 ja PHP 7,
jotka ovat vastuussa suurimmasta osasta Internetiä,
sekä iOS ja MacOS ja todennäköisesti niin monia muita kohteita,
joita emme vain ehtineet käydä läpi.
Joten selitetään hieman tätä haavoittuvuutta.
Olen maininnut, että se on FTS-tokenizerissa.
Joten tokenizer on vain joukko sääntöjä dokumenttien
tai kyselyjen termeistä erottamiseksi.
Ja oletustokenizeri, jota kutsutaan "yksinkertaiseksi",
vain jakaa merkkijonot tyhjien merkkien perusteella.
Kuitenkin, jos haluat, voit rekisteröidä oman mukautetun tokenizerin.
Voit vain siirtää C-funktion ja oikeastaan rekisteröit
tämän mukautetun tokenizerin fts3_tokenizer() -funktiolla SQL-kyselyssä.
Tässä on jonkin verran asiaa, mutta sanon sen hitaasti.
Siirrät riviosoittimen C-funktioon SQL-kyselyssä.
Tämä on täysin mielipuolista.
Rehellisesti sanottuna, vaikka olen tutkinut tätä jonkin aikaa,
en vieläkään ymmärrä, miten käyttää tätä ominaisuutta hyväksi muualla kuin hyökkäyksessäni.
[Yleisö taputtaa]
fts3_tokenizer() on ylikuormitettu funktio,
ja jos kutsut sen yhdellä argumentilla,
joka on tokenizerin nimi, saat takaisin kyseisen tokenizerin osoitteen.
Teemme sen hieman ihmiselle luettavammaksi käyttämällä heksadesimaalimuunnosta.
Voit nyt nähdä, että saimme tietovuodon libsqlite3:een.
Koska se on little-endian, meidän on käännettävä se ympäri,
mutta tämä on jo melko hienoa.
Jos kutsut fts3_tokenizer():ia kahdella argumentilla,
joista ensimmäinen on tokenizerin nimi ja toinen on raakaosoitin, ja tämä on täysin mielipuolista..
voit kirjoittaa kyseisen tokenizerin osoitteen uudelleen.
Joten aina kun joku yrittää käyttää virtuaalitaulua
ja se instantisoi oletusarvoisen tokenizerin, se kaatuu.
Tämä on melko hämmästyttävää.
Lyhyt kertaus: olemme vahvistaneet,
että SQLite on ihana yhden maalin kohde monille, eikö niin?
Se on kaikkialla.
Ja se on monimutkainen C:llä kirjoitettu kone.
Nyt, kyselyn kaappaamisen avulla voimme alkaa laukaista näitä bugeja
ja tavoitella täydellistä hyökkäystä käyttämällä SQL-kyselyjä.
Hyökkäysstrategiamme on seuraava: vuodatamme joitakin osoittimia
ja sitten laskemme joitakin funktio-osoitteita.
Luomme sitten väärennetyn tokenizer-objektin,
jolla on jokin osoitin system()-funktioon.
Ylikirjoitamme oletusarvoisen tokenizerin ja laukaisemme pahantahtoisen tokenizerimme.
Sitten tapahtuu jotain, ja lopussa meidän pitäisi pystyä hyötymään jotenkin, eikö niin?
Aloittaen muistivuodosta ja tietovuodosta libsqliteen.
Tiedät jo, miten tehdään, eikö niin?
Näimme fts3_tokenizer():in, mutta meillä on vielä pieni ongelma little-endian osoittimen kanssa.
Joten meidän on käännettävä se ympäri.
Varmaan voimme käyttää SUBSTR-funktiota
ja lukea tämän osoittimen kaksi merkkiä kerrallaan käänteisessä järjestyksessä
ja yhdistää kaiken koko osoittimen läpi.
Joten saamme SELECT-kyselyn, joka näyttää seuraavalta,
mutta nyt meillä on osoitin.
Tämä on hienoa.
Entä tietovuoto kekomuistiin?
Haluamme tietää, missä keko sijaitsee.
Aloitetaanpa tekemään temppua,
joka on melko samankaltainen kuin RTREE-bugi, jonka olemme löytäneet.
Joten jälleen kerran, aiomme sekoittaa joitakin shadow-taulukkojen käyttöliittymiä.
Luomme siis virtuaalitaulukon ja lisäämme siihen joitakin arvoja.
Nyt aiomme hämmentää match-käyttöliittymää.
Match-käyttöliittymä tekee monia asioita,
mutta taustalla se vain palvelee osoittimen muistissa, jossa tekstin sijainti sijaitsee.
Se on tätä metadataa, hienoja asioita, joita virtuaalitaululla on.
Joten aiomme hämmentää sitä ja sen sijaan,
että siirtäisimme sen toiselle virtuaalitaulun käyttöliittymälle,
siirrämme sen yksinkertaisesti heksadekooderille,
joten puramme tämän raakanosoittimen.
Ja voit nähdä, jälleen kerran little-endianin,
mutta nyt meillä on linkki kekoon.
Voimme yliviivata sen listalta.
Ja ennen kuin siirrymme eteenpäin tämän osoittimen purkamiseen,
meillä on hyvin perustavanlaatuinen ongelma.
Kuinka edes tallennamme näitä asioita?
Koska toisin kuin selaimen WebSQL:ssä, meillä ei ole JavaScript-muuttujia tai taulukoita,
joita voimme käyttää ja hyväksikäyttää myöhemmin,
mutta meidän on luotava joitakin monimutkaisia logiikoita, eikö niin?
Meidän on laskettava funktio-osoite ja luotava asioita muistissa, mutta kuinka voimme tehdä sen?
Ilmeisesti SQLite:llä, kun haluat tallentaa joitakin arvoja,
sinun on oltava insert-lausekkeita, mutta voimme vain luoda tauluja, näkymiä, indeksejä ja laukaisimia.
Sitten ajattelimme ketjuttaa tämän näkymän yhteen käyttääksemme niitä jonkinlaisena pseudomuuttujana.
Joten jälleen kerran, anna minun näyttää sinulle esimerkki.
Tässä luon näkymän, jota kutsutaan "little-endian leak", okei?
Ja taas hyväksikäytän fts3_tokenizer() -funktiota.
Nyt luon toisen näkymän sen päälle, ja tätä kutsutaan "leakiksi"
ja kääntyy käyttämällä SUBSTR-temppua,
jonka tiedät jo aikaisemmasta.
Mutta huomaa, kuinka viittasin ensimmäiseen näkymään, viittasin "little-endian leak":iin.
Joten teen sen koko osoittimen läpi, ja lopulta minulla on pseudomuuttuja nimeltä "leak".
Kun valitsen sen, saan odotetun tuloksen.
Joten nyt voimme todella edetä.
Nyt voimme aloittaa monimutkaisempien asioiden rakentamisen tämän logiikan perusteella.
Ja nyt voimme siirtyä purkamaan osoittimia.
Haluamme esimerkiksi laskea kuvan perustan tai löytää keon alun.
Joten ensinnäkin haluamme muuntaa osoittimemme kokonaisluvuiksi.
Joten jälleen kerran aloitamme lukemalla nämä osoittimet yhden merkin kerrallaan
ja käänteisessä järjestyksessä käyttäen SUBSTRia.
Ja saadaksemme heksadesimaalimerkin arvon, käytämme INSTR-funktiota,
joka on kuin strchar, ja käyttämällä seuraavaa merkkijonoa saamme heksadesimaalimerkin arvon.
Koska se on yksipohjainen, on oikealla miinus yksi.
Sitten minun täytyy tehdä hieman siirtämistä, kuten jotain pimeää magiaa,
ja sitten yhdistän kaiken yhteen osoittimessa.
Joten tulos on tämä hirviömäinen kysely.
Mutta kun kaikki tämä on tehty, saan kokonaisluvun, joka on purkamaton versio alkuperäisestä vuodosta.
Joten onnistuin purkamaan tämän osoittimen,
ja meillä on nyt käsillä kokonaislukuja, joten voimme yliviivata sen listalta myös.
Tiedämme nyt, miten muuntaa osoittimet kokonaisluvuiksi.
Nyt osoitinlaskentaa, koska haluamme saada joitain toimintojen osoitteita muistissa,
ja itse asiassa kokonaislukuilla tämä on erittäin yksinkertaista.
Kaikki mitä meidän tarvitsee tehdä on käyttää joitakin alikyselyjä.
Vasemmalla voit nähdä, että viittaan nyt pseudomuuttujiin,
joita meillä on nyt, vuotanut tieto,
ja oikealla voin joko vähentää hölmön vakion kuten tein täällä,
tai voin todella käyttää toista pseudomuuttujaa tehdäkseni sen hieman dynaamisemmaksi ja luotettavammaksi.
Lopulta, mihin päädyin, on libsqlite-perusta kokonaislukumuodossa.
Joten, olemme lukeneet joitakin osoittimia ja manipuloineet niitä.
Nyt on hyvä aika kirjoittaa ne takaisin. Ja tietysti olemme tottuneet siihen,
että "char" on täysin päinvastainen kuin "hex".
Ja voit nähdä, että se toimii melko hyvin useimmilla arvoilla,
mutta suuremmat kokonaisluvut todella käännettiin niiden kahden tavun koodipisteisiin.
Joten tämä oli suuri este meille.
Joten, kun olimme viettäneet dokumentaation parissa jonkin aikaa,
yhtäkkiä meille valkeni kummallinen oivallus.
Tajusimme, että hyökkäyksemme on itse asiassa tietokanta.
Ja jos haluan minkä tahansa muunnoksen tapahtuvan,
voin yksinkertaisesti luoda etukäteen tämän avain-arvo-kartan ja yksinkertaisesti
kysyä sitä käännettäväksi millä tahansa arvolla toiseksi arvoksi alikyselyillä.
Tämä on se Python-funktio, jonka olen käyttänyt,
ja voit nähdä, että siinä on hyvin yksinkertainen for-silmukka,
joka menee 0:sta FF:ään ja vain lisää arvoja tauluun,
jota kutsutaan "hex_map", sen avain-kartan arvoilla.
Ja nyt muunnoksemme käyttävät alikyselyjä. Joten anna minun jälleen näyttää esimerkki.
Voit nähdä, että valitsen "val" heksakartasta,
tästä avain-arvo-kartasta, jossa "int" on yhtä suuri kuin,
ja sitten teen hieman enemmän kaikenlaista, kuten siirtämistä ja modulo pimeää magiaa,
mutta lopulta päädyin pakattuun versioon libsqlite-tietokannasta.
Nyt meillä on pakattu little-endian osoitin, joten sen voi ruksata listalta pois.
Kuten mainitsin, yksittäisen osoittimen kirjoittaminen on ehdottomasti hyödyllistä,
mutta se ei riitä.
Haluamme väärentää kokonaisia objekteja, eikö niin?
Kaikki coolit tyypit tekevät niin, ja se on melko voimakas primitiivi.
Ja jos muistat, meidän on tehtävä se,
koska fts3_tokenizer() edellyttää meidän määrittävän tokenizer moduulin.
No, mikä on tokenizer-moduuli? Miltä se näyttää? Joten tämä on sen rakenteen alku,
ja siinä on "iVersion", joka on kokonaisluku alussa,
meillä ei ole oikeastaan mitään tekemistä sen kanssa,
mutta sen jälkeen on kolme funktio-osoitinta.
Meillä on "xCreate", joka on jakelijan rakentaja, ja "xDestroy", joka on purkaja.
Meidän on tehtävä molemmat kelvollisiksi, jotta sovellus ei kaadu hyökätessämme.
Kolmas funktio-osoitin on todella mielenkiintoinen, koska tämä on se,
joka todella jakaa merkkijonon osiksi.
Joten meillä on funktio-osoitin, johon ohjataan hallittavissa oleva merkkijono.
Tämä olisi täydellinen paikka laittaa system-vimpaimemme, eikö niin.
Tähän mennessä olen käyttänyt melkoisesti SQL-osaamistani, eikö niin?
Mutta minulla on vielä yksi temppu hihassani ja se on JOIN-kyselyt, eikö niin?
Koska olen oppinut siitä menneessä ajassa.
Joten nyt luomme väärennetyn tokenizer-näkymän
ja yhdistämme joukon "A"-kirjaimia ja sitten käyttäen JOIN-kyselyä
ja yhdistämme sen osoittimeen simple_create ja osoittimeen simple_destroy
ja sitten joukon "B":itä. Nyt tarkistetaan se alhaisen tason debuggerissa
ja voit itse asiassa nähdä, että jossain muistipaikassa meillä on joukko "A":ita,
jonka perässä on osoitin simple_createen,
jonka perässä on osoitin simple_destroyiin ja sitten joukko "B":iä.
Joten olemme melkein valmiit. Mutta tarvitsemme vielä yhden primitiivin tätä hyödyntääksemme.
Ja tämä johtuu siitä, että meillä on jo haitallinen tokenizerimme,
ja tiedämme, missä keko sijaitsee, mutta emme ole aivan varmoja, missä tokenizerimme sijaitsee.
Tämä on hieno aika heap sprayinglle, eikö olekin.
Ja ihanteellisesti tämä olisi jonkin toistuva muoto meidän fakeobj -primitiivistämme.
Olemme siis ajatelleet toistoa, mutta valitettavasti SQLite ei ole toteuttanut sitä kuten mySQL.
Joten kuten kuka tahansa muukin,
menimme Stack Overflowiin ja löysimme tämän todella elegantin ratkaisun.
Käytämme siis zeroblob-funktiota, joka yksinkertaisesti palauttaa nollista koostuvan läjän.
Sitten korvaamme jokaisen nollan väärennetyllä tokenisaattorillamme.
Ja teemme sen kymmenentuhatta kertaa, kuten näette yllä.
Uudelleen, varmistaaksemme sen debuggerilla,
näet paljon "A":ita ja se on vähän vaikea nähdä,
koska nämä ovat todella huonoja värejä, mutta meillä on myös täydellinen johdonmukaisuus,
koska nämä rakenteet toistuvat joka 20. heksadesimaalitavussa.
Joten loimme melko hyvät heap-spraying -kyvyt,
ja olemme valmiit exploitti -toiveluettelomme kanssa,
joten voimme palata alkuperäiseen kohteeseemme.
Tämä on koodisegmentti erittäin tunnetusta salasanavarkaasta,
ja lopussa voit nähdä, että se yrittää valita
ja poimia salaisuuksia valitsemalla sarakkeen nimeltä "BodyRich" taulusta nimeltä "Notes".
Joten valmistelemme hänelle pienen yllätyksen, okei?
Luomme näkymän, jota kutsutaan nimellä "Notes".
Ja siinä on kolme alikyselyä sarakkeessa nimeltä "BodyRich".
Jokainen näistä alikyselyistä on itse asiassa QOP-ketju.
Jos muistat hyökkäyssuunnitelmani,
aloitamme heap-spray sitten korvaamme oletustokenisaattorin
ja laukaisemme lopuksi haitallisen tokenisaattorin.
Ja sinä saatat kysyä itseltäsi, mikä on heap_spray?
Ilmeisesti heap_spray on QOP-ketju, joka hyödyntää heap-spraying -kykyjämme, eikö niin.
Me ruiskutamme kymmenentuhatta esimerkkiä väärennetystä tokenizeristamme,
joka on JOIN-kysely joukosta "A":ita ja joitain osoittimia, kuten p64_simple_create.
Ja bileet jatkuvat, koska p64_simple_create on itse asiassa peräisin u64_simple_createsta, oikein,
käyttäen osoittimenpakkauskykyjämme.
Ja tämä on kuin maatuska-nukke,
koska sinulla on u64_simple_create, joka on peräisin libsqlite_basesta plus joistakin vakioista.
Ja tämä menee takaisin purkamattomaan vuotoversioon,
miinus joitain vakioita.
Joten jälleen kerran käytämme osoitinlaskentakykyjämme.
Voimme jatkaa u64_leak:illa, joka on peräisin lähes alkuperäisestä vuodosta.
Ja me päätämme näyttämällä,
kuinka vuoto on itse asiassa peräisin alkuperäisestä haavoittuvuudesta käyttäen fts3_tokenizeria.
Ja tämä oli vain yksi kolmesta QOP-ketjusta, joita käytimme tässä hyökkäyksessä.
Ja tällä hetkellä joka kerta, kun kuvaan tätä hyökkäystä, tämä on miltä minä varmasti näytän.
Ja ollakseni rehellinen, tämä on juurikin mitä tunnen.
Mutta onneksi teidän ei tarvitse näyttää tai tuntea samalta kuin minä,
koska loimme QOP.py:n, ja se on saatavilla Checkpoint Researchin GitHubissa.
Ja yhtäkkiä nämä hullun pitkät ketjut voidaan luoda neljällä helpolla Python-rivillä.
Ja tuntuu pwntoolsilta, jos olet perehtynyt siihen,
joten voit mennä eteenpäin ja leikkiä sillä etkä näytä hullulta lavalla.
Siirrymme ensimmäiseen demoomme.
Ownaamme salasanan varastavaa taustapalvelinta, joka toimii uusimmalla PHP 7:llä.
Tietenkin tämä on malli, jonka loimme vuotaneista lähteistä,
ja näet kaikki tartunnan saaneet uhrit
Coolia
Nyt yritämme mennä web-shelliin, p.php:hen.
Tietenkään sitä ei vielä ole olemassa, saamme 404.
Siirrymme hyökkääjän tietokoneelle.
Näemme täällä kaksi skriptiä.
Ensin käytämme QOP.py:ä, joka luo ilkeämielisen tietokannan.
Katsotaanpa, tietokanta luotiin.
Nyt emuloimme saastuttamisen.
Aloitamme lähettämällä ilkeämielisen tietokantamme C2-palvelimelle
kuin olisimme tartunnan saaneet salasanavarkaan kautta.
Koska tämä prosessi vie hieman aikaa, voimme tarkastella kaikkia hienoja DDL-lauseita, oikein?
Näet siis, että aloitimme jonkin bin-vuodon ja heap-vuodon kanssa ja sitten purimme ne.
Ja aivan lopussa voit nähdä, että ohjelmamme luo yksinkertaisen webshellin p.php:lle.
Ja tämä on sama sivu, jota juuri yritimme tavoittaa.
Joten toivottavasti, joo - hienoa, se on tehty.
Nyt palaamme salasanavarkaan back-endiin.
Menemme p.php:hen ja saamme vastauksen 200.
Nyt suoritamme jotain koodia siinä. whoami. www-data.
Ja ilmeisesti meidän täytyy mennä /etc/passwordiin.
Jippii.
[Yleisö taputtaa]
Joten mitä juuri tapahtui, on se, että osoitimme,
että annetun kyselyn avulla ilkeämielisessä tietokannassa,
voimme suorittaa koodia kyselyprosessissa.
Nyt, kun otetaan huomioon, että SQLite on niin suosittu,
tämä todella avaa oven laajalle valikoimalle hyökkäyksiä.
Tutkitaan toista täysin erilaista käyttötapaa. Joka on täysin erilainen
Seuraava kohde on iOS pysyvyys.
iOS käyttää SQLiteä laajalti ja pysyvyyden saavuttaminen on todella vaikeaa,
koska kaikkien suoritettavien tiedostojen on oltava allekirjoitettuja.
Silti SQLite-tietokantoja ei ole allekirjoitettu, eikö niin, ne ovat vain dataa.
Niitä ei tarvitse allekirjoittaa.
Ja iOS ja MacOS ovat molemmat käännetty käyttäen ENABLE_FTS3_TOKENIZER:ia.
Se on se vaarallinen käännöshetken lippu.
Suunnitelmani on saada koodin suoritus uudelleenkäynnistyksen
jälkeen korvaamalla mielivaltainen SQLite-tietokanta.
Tämän saavuttamiseksi aion kohdistaa hyökkäyksen "AddressBook.sqlite" -nimiseen yhteystietokantaan.
Alkuperäisessä tietokannassa on kaksi taulua,
joilla ei ole merkittävää merkitystä, vaan ne ovat esimerkkejä.
Aion luoda haitallisen yhteystietokannan, ja aloitan kahdella haitallisella DDL-lausekkeella,
jotka ovat teille jo tuttuja.
Ensin korvaamme oletustokenizerin "simple" joukolla "A":ita.
Sitten toinen DDL-lauseke toteuttaa tämän haitallisen laukaisimen,
joten se kaataa ohjelman, koska joka kerta, kun luodaan virtuaalitaulumoduuli,
joku yrittää mennä tokenizerin rakentajan luokse.
Joten se kaatuu.
Seuraavaksi käyn läpi jokaisen alkuperäisen taulun
ja kirjoitan ne uudelleen käyttäen kyselykaappauksen tekniikkaa.
Sen sijaan että käytämme odotettuja sarakkeita,
ohjaamme suorituksen ylikirjoituslauseeseen ja kaatumislauseeseen.
Teimme sen yhdelle taululle, teemme sen myös muille.
Nyt käynnistämme uudelleen ja voilà,
saamme seuraavan CVE:n ja turvallinen käynnistys ohitettiin.
Ja jos kiinnität huomiota tarkasti, tämä on todella hienoa,
koska näemme että kaatuminen tapahtui osoitteessa 0x414141...49.
Tämä on juuri mitä odotimme tapahtuvan, koska tässä pitäisi olla konstruktorimme,
kahdeksan tavua version ensimmäisen kokonaisluvun jälkeen.
Mutta todellisuudessa tässä on enemmän. Saamme pienen bonuksen tässä.
Koska yhteystietokanta on itse asiassa käytössä monien eri prosessien kautta.
Joten Yhteystiedot, FaceTime, Springboard, WhatsApp, Telegram, XPCProxy ja monet muut.
Ja monilla näistä prosesseista on enemmän oikeuksia kuin toisilla.
Ja olemme osoittaneet, että voimme nyt suorittaa koodia prosessissa,
joka kysyy ilkeämielistä tietokantaamme.
Joten saimme myös oikeuksen kohottamisen tässä prosessissa.
Tämä on todella hienoa. Ja yhteystietokannassa ei ole mitään erityistä.
Itse asiassa mitä tahansa jaettua tietokantaa voidaan käyttää.
Tarvitsemme vain jotain, joka on kirjoitettavissa heikolla käyttäjällä
ja jota vahvempi käyttäjä sitten kysyy. Ja ilmeisesti kaikki nämä tekniikat
ja virheet ilmoitettiin Applelle, ja ne kaikki on korjattu.
Ja tässä ovat CVE:t, jos haluat lukea siitä myöhemmin.
Nyt, ynnätäkseni asioita, jos haluat oppia jotain tästä esityksestä,
en halua sen olevan hullua SQL-voimistelua tai joukko CVE-numeroita.
Haluan sen olevan seuraavaa.
Tietokannan kysely ei välttämättä ole turvallista, oli kyse sitten käynnistyksen yli,
käyttäjien välillä tai prosessien välillä,
tietokannan kysely ei välttämättä ole turvallista kyselyn kaappauksen vuoksi.
Ja nyt, kun on kyse kyselyjen kaappauksesta ja kyselykeskeisestä ohjelmoinnista,
nämä muistin korruptiot, joita voimme laukaista, voidaan todella hyödyntää luotettavasti pelkällä SQL:llä.
Emme tarvitse enää JavaScriptiä. Emme tarvitse WebSQL:ää.
Ja uskomme todella, että tämä on vain jäävuoren huippu.
Tähän mennessä SQLite on ollut erittäin suosittu,
mutta sitä on arvioitu vain WebSQL:n kapealta alueelta,
ja vaikka selaimen pwnaus on jännittävää, SQLite:lla on paljon enemmän potentiaalia.
Meillä on siis joitain ajatuksia mahdollisesta tulevasta työstä tämän kanssa.
Ilmeisesti todella hienoa olisi laajentaa primitiivejämme joitain vahvempia primitiivejä kohti, oikein.
Haluamme saada asioita, kuten absoluuttinen luku- ja kirjoituskyky.
Ja minun hatara POC-hyökkäykseni oli melko typerä,
koska siinä oli paljon vakioita, kuten olette nähneet,
mutta itse asiassa jos käytät SQLite:n sisäistä toimintoa,
jos hyökkäyksen aikana käytät funktiota kuten sqlite3_version(),
voit kysyä tulkilta, mikä versio sinulla on, millä käännösvalinnoilla se on käännetty,
voit dynaamisesti rakentaa näitä QOP-ketjuja
ja kohdistaa ne tiettyyn kohdeympäristöön, jota hyökkäät.
Itse asiassa hyödyntämällä sitä tosiasiaa, että hyökkäyksemme on tietokanta,
voimme luoda tämän todella coolin hyökkäyspolyglotin, ja ajattelemme,
että näitä tekniikoita voidaan käyttää oikeuksien kohottamiseen monissa tilanteissa,
koska kehittäjät eivät koskaan ajatelleet,
että nyt voimme ottaa minkä tahansa tietokannan, jota heikko käyttäjä voi kirjoittaa,
ja jota vahva käyttäjä voi kysyä, ja yhtäkkiä voimme yrittää kohottaa oikeuksiamme.
Toisaalta olisi todella mielenkiintoista huomata, että monet niistä primitiiveistä,
joita olen teille näyttänyt, eivät ole yksinomaan SQLite:n omaisuutta, eikö niin?
Osoitinpakkaus ja -purku sekä heap spraying
ja kaikki nämä asiat eivät ole yksinomaan SQLite:n omaisuutta.
Olisi todella mielenkiintoista ottaa nämä primitiivit ja nähdä,
voimmeko mennä eteenpäin ja hyödyntää muita muistin korruption bugia eri tietokanta-moottoreissa.
Kiitos paljon.
[Yleisö taputtaa]
Omer Gull, kiitos paljon.
Siinä oli paljon aikaa kysymyksille.
Meillä on kolme mikrofonia täällä salissa:
Numero yksi, Numero kaksi ja Numero kolme.
Jos sinulla on kysymyksiä, ole hyvä ja jonota.
Onko meillä joitakin kysymyksiä verkosta?
Ei ole. Ookei.
Aloittakaamme sitten mikrofonista numero kaksi.
Joo. Kysymys koskee sitä "CREATE-jotakin" kaappaamista.
Mainitsit, että alussa tutkimus oletti,
että CREATE oli ensimmäinen tarkistusjärjestys
ja sitten välilyönti CREATE-sanalla ja sen jälkeen se voisi luoda kaikki muut asiat.
Nyt kysymykseni on: Jos se muuttui raporttisi jälkeen,
koska tämä vaikuttaa tavalta altistaa suurempi hyökkäyspinta kuin useimmat muut bugit.
Joten haluaisin tietää, jos he muuttivat sitä.
Ja mitä oli lopulta se lieventäminen, jos sitä oli?
Joo. Niin, meidän escalate-henkilöt ovat enemmän huolissaan tiettyjen virheiden
kuin hyökkäystekniikoiden suhteen.
Tämä on todella surullista, koska tiedämme kaikki,
että virheet voidaan korjata, mutta hyökkäystekniikat jäävät käyttöön.
Joten ei, he eivät muuttaneet tätä tarkistusta.
Itse asiassa tämä "create "-validoinnin lisäys tehtiin vasta äskettäin.
Sitä ennen pystyit käyttämään mitä tahansa DDL-lauseketta haluat.
Joten tilanne ei ole todella hyvä siellä.
Hyvä heille ja onnea tulevaisuudelle.
Ja siinä oli kysymys.
Okei. Sitten siirrymme mikrofoni ykköseen, kiitos.
Oletko ehkä vahingossa hyökännyt palvelimelle, jota käytetään salasanan varastamiseen?
Ei, tietenkään en tekisi niin.
En hyökkäisi kenenkään kimppuun.
Tämä on vain meidän laboratoriossamme POC:na. Okei.
Kiitos.
Salasanasi ovat turvassa varkailta.
[Naurua] . Noniin
Kukaan ei enää jonota.
Onko meillä kysymyksiä verkosta? Ei mitään siellä.
No, tästä lähdetään.
Kiitos.
Omer Gull, kiitos paljon.
[Yleisö taputtaa]
[Translated by Jouko Voutilainen (KYBS2001 course assignment at JYU.FI)]