Viimeisten kahden viikon aikana olemme nähneet sarjan artikkeleita, joissa puhutaan "pääsalasanan murtamisesta" suositussa avoimen lähdekoodin salasanojen hallinnassa KeePassissa.
Virhettä pidettiin tarpeeksi tärkeänä saadakseen virallisen Yhdysvaltain hallituksen tunnisteen (se tunnetaan nimellä CVE-2023-32784, jos haluat metsästää sen) ja koska salasananhallinnan pääsalasana on pitkälti avain koko digitaaliseen linnaasi, voit ymmärtää, miksi tarina herätti paljon jännitystä.
Hyvä uutinen on, että hyökkääjän, joka halusi hyödyntää tätä vikaa, on lähes varmasti jo täytynyt tartuttaa tietokoneesi haittaohjelmilla, ja siksi hän pystyisi joka tapauksessa vakoilemaan näppäinpainalluksiasi ja käynnissä olevia ohjelmia.
Toisin sanoen bugia voidaan pitää helposti hallittavana riskinä, kunnes KeePassin luoja julkaisee päivityksen, jonka pitäisi ilmestyä pian (ilmeisesti kesäkuun 2023 alussa).
Kuten vian paljastaja huolehtii huomauttaa:
Jos käytät täydellistä levysalausta vahvalla salasanalla ja järjestelmässäsi ei ole [haittaohjelmia], sinun pitäisi olla kunnossa. Kukaan ei voi varastaa salasanojasi etäyhteyden kautta internetin kautta pelkästään tämän löydön avulla.
Riskit selitetty
Karkeasti tiivistettynä vika johtuu vaikeudesta varmistaa, että kaikki luottamuksellisten tietojen jäljet poistetaan muistista, kun olet lopettanut ne.
Jätämme tässä huomioimatta ongelmat, kuinka välttää salaisia tietoja muistissa ollenkaan, edes lyhyesti.
Tässä artikkelissa haluamme vain muistuttaa ohjelmoijia kaikkialla, että turvallisuustietoisen arvioijan hyväksymä koodi kommentilla, kuten "näyttää siivoavan oikein itsestään"…
…ei ehkä itse asiassa puhdistu kokonaan, ja mahdollinen tietovuoto ei välttämättä ole ilmeinen itse koodin suorasta tutkimuksesta.
Yksinkertaisesti sanottuna CVE-2023-32784-haavoittuvuus tarkoittaa, että KeePass-pääsalasana saattaa olla palautettavissa järjestelmätiedoista myös KeyPass-ohjelman sulkemisen jälkeen, koska salasanastasi on riittävästi tietoa (tosin ei itse raakasalasanaa, johon keskitymme hetkessä) saattaa jäädä jälkeen järjestelmän vaihto- tai lepotilatiedostoista, joissa varattu järjestelmämuisti voi päätyä tallentumaan myöhempää käyttöä varten.
Windows-tietokoneessa, jossa BitLockeria ei käytetä kiintolevyn salaamiseen, kun järjestelmä on sammutettu, tämä antaisi kannettavan tietokoneesi varastaneelle huijarille mahdollisuuden käynnistää tietokoneesi USB- tai CD-asemasta ja palauttaa pääsalasanasi. vaikka KeyPass-ohjelma itse huolehtii siitä, ettei se koskaan tallenna sitä pysyvästi levylle.
Pitkäaikainen salasanavuoto muistissa tarkoittaa myös sitä, että salasana voitaisiin teoriassa palauttaa KeyPass-ohjelman muistivedosta, vaikka vedos olisi nappattu kauan salasanan kirjoittamisen jälkeen ja kauan KeePass-ohjelman jälkeen. itsellä ei ollut enää tarvetta pitää sitä mukana.
On selvää, että sinun pitäisi olettaa, että järjestelmässäsi jo olevat haittaohjelmat voivat palauttaa melkein minkä tahansa kirjoitetun salasanan useiden reaaliaikaisten nuuskimistekniikoiden avulla, kunhan ne olivat aktiivisia kirjoittaessasi. Mutta voit kohtuudella olettaa, että vaaralle altistumisaikasi rajoittuu lyhyeen kirjoittamisjaksoon, ei useisiin minuutteihin, tunteihin tai päiviin sen jälkeen tai kenties pidempään, mukaan lukien tietokoneen sammuttamisen jälkeen.
Mitä jää jäljelle?
Ajattelimme siksi tarkastelevamme korkeatasoista, kuinka salaiset tiedot voivat jäädä muistiin tavoilla, jotka eivät ole suoraan ilmeisiä koodista.
Älä huolehdi, jos et ole ohjelmoija – pidämme asian yksinkertaisena ja selitämme edetessämme.
Aloitamme tarkastelemalla muistin käyttöä ja puhdistamista yksinkertaisessa C-ohjelmassa, joka simuloi salasanan syöttämistä ja väliaikaista tallentamista seuraavasti:
- Varaa oma muistipala erityisesti salasanan tallentamiseen.
- Lisätään tunnettu tekstimerkkijono jotta voimme tarvittaessa löytää sen helposti muistista.
- Lisätään 16 näennäissatunnaista 8-bittistä ASCII-merkkiä alueelta AP.
- Tulostaminen ulos simuloitu salasanapuskuri.
- Muistin vapauttaminen salasanapuskurin tyhjentämisen toivossa.
- poistuminen ohjelma.
Hyvin yksinkertaistettuna C-koodi saattaa näyttää suunnilleen tältä ilman virheentarkistusta, käyttämällä huonolaatuisia näennäissatunnaisia lukuja C-ajonaikaisesta funktiosta rand()
, ja jättää huomiotta kaikki puskurin ylivuototarkistukset (älä koskaan tee tätä todellisessa koodissa!):
// Ask for memory char* buff = malloc(128); // Copy in fixed string we can recognise in RAM strcpy(buff,"unlikelytext"); // Append 16 pseudo-random ASCII characters for (int i = 1; i <= 16; i++) { // Choose a letter from A (65+0) to P (65+15) char ch = 65 + (rand() & 15); // Modify the buff string directly in memory strncat(buff,&ch,1); } // Print it out, so we're done with buff printf("Full string was: %sn",buff); // Return the unwanted buffer and hope that expunges it free(buff);
Itse asiassa koodi, jota lopulta käytimme testeissämme, sisältää joitain lisäbittejä ja osia, jotka on esitetty alla, jotta voimme tyhjentää väliaikaisen salasanapuskurimme koko sisällön sitä käyttäessämme etsiäksemme ei-toivottua tai ylijäämää.
Huomaa, että tyhjennämme puskurin tarkoituksella soiton jälkeen free()
, joka on teknisesti use-after-free -virhe, mutta teemme sen täällä lujasti nähdäksemme, jääkö mitään kriittistä jälkeen puskurimme takaisin antamisen jälkeen, mikä voi johtaa vaaralliseen tietovuotoaukkoon tosielämässä.
Lisäsimme myös kaksi Waiting for [Enter]
kehottaa koodiin antaaksemme itsellemme mahdollisuuden luoda muistivedoksia ohjelman avainkohdissa, jolloin voimme etsiä raakadataa myöhemmin nähdäksemme, mitä jäi jäljelle ohjelman suorittamisen aikana.
Muistivedosten tekemiseen käytämme Microsoftia Sysinternals työkalu procdump
jossa -ma
vaihtoehto (tyhjennä kaikki muisti), jolloin sinun ei tarvitse kirjoittaa omaa koodiamme Windowsin käyttöä varten DbgHelp
järjestelmä ja sen melko monimutkainen MiniDumpXxxx()
tehtävät.
C-koodin kääntämiseen käytimme omaa pientä ja yksinkertaista Fabrice Bellardin ilmaisen avoimen lähdekoodin versiota. Pieni C-kääntäjä, saatavana 64-bittiselle Windowsille lähde ja binäärimuoto suoraan GitHub-sivultamme.
Kaiken artikkelissa olevan lähdekoodin kopioitava ja liitettävä teksti näkyy sivun alareunassa.
Näin tapahtui, kun käänsimme ja suoritimme testiohjelman:
C:UsersduckKEYPASS> petcc64 -stdinc -stdlib unl1.c Pieni C-kääntäjä - Tekijänoikeus (C) 2001-2023 Fabrice Bellard, jonka Paul Ducklin riisutti käytettäväksi oppimistyökaluna Versio petcc64-0.9.27 [0006] - Luo 64 Vain PE:t -> unl1.c -> c:/users/duck/tcc/petccinc/stdio.h [. . . .] -> c:/users/duck/tcc/petcclib/libpetcc1_64.a -> C:/Windows/system32/msvcrt.dll -> C:/Windows/system32/kernel32.dll -------- ----------------------- virt-tiedoston koko-osio 1000 200 438 .teksti 2000 800 2ac .data 3000 c00 24 .pdata --------- ----------------------- <- unl1.exe (3584 tavua) C:UsersduckKEYPASS> unl1.exe "uuden" puskurin tyhjennys alussa 00F51390: 90 57 F5 00 00 00 00 00 50 01 F5 00 00 00 00 00 .W......P....... 00F513A0: 73 74 65 6D 33 32 5C 63 tem 6 64E 2 65 78 65 00 44 . exe.D 32F00B513: 0 72 69 76 65 72 44 61 74 61D 3 43A 3C 5 57 69E jokiData=C:Win 6F00C513: 0 64F 6 77 73C 5 53D 79 73 74 dowsSystem65Dr 6F33D32: 5 44 72 32 00 513C 0 69 76 65 72 73 5 44 72 69 ivers DriverData 76F65E72: 44 61 74 61 00F 513 0 00 45 46D 43 5 34 33 37 32 3 31 00 46 F50F53: 5 4372 1F 00 513 0 42 52F 4 57 53 45F 52 5 41F 50 BROWSER_APP_PROF 50F5: 50 52C 4 46F 00 51400 49 4 45E 5 53D 54 52E 49 4 47 ILE_STRING=Inter 3:49E 6 74C 65A 72 F00 51410C AC 6B 65 74 net ExplzV.< .K.. Täysi merkkijono oli: epätodennäköinentekstiJHKNEJJCPOMDJHAN 20F45: 78 70E 6C 7 56B 4 3C 4 00 00 00 51390 75A 6 6B 69E epätodennäköinentekstiJHKN 6F65F6 79 74A 65 78 74E 4 48 4 4 EJJCPOMDJHAN.eD 00F513B0 : 45 4 4 43 50 4 4 44 4 48D 41 4A 00C 65 00 44E riverData=C:Win 00F513C0: 72 69F 76 65 72C 44 61 74 61C 3 43 3 5C 57 69 6 00C 513 0 64 6D 77 73 5s 53 79 73 74 65Dr 6F33D32: 5 44 72 32 00 513C 0 69 76 65 72 73 5 44 72 69 iversDriverData 76F65E72: 44 61 74 61 00F 513 0 00 45 46D 43 5 34 33 37 32F .EFC 3 31 00 46F . 50 53F 5 4372 1 00 513F 0 42 52 4F 57 53 45F 52 BROWSER_APP_PROF 5F41: 50 50C 5 50F 52 4 46 00 51400E 49 4D 45 5E 53 54 52 ILE_STRING=Inter 49F4: 47E 3A 49F6: 74E 65A 72F00 51410 6C AC 65B 74 20 net ExplzV.<.K.. Odotetaan, että [ENTER] vapauttaa puskurin... Poistetaan puskuri vapaan() 45F78 jälkeen: A70 6 F7 56 4 3 4 00 00 00 F51390 0 67 5 00 00 .g......P...... 00F00A00: 50 01A 5A 00 00 00F 00D 00 00A 513 0 45E 4 4 43 50 EJJCPOMDJHAN.eD 4F4B44: 4 48 41 4 00 65 00 44 00E riverData=C:Win 513F0C72: 69 76F 65 72 44C 61 74 61 3 43 3D 5 57 69C 6 00 dowsSystem513Dr 0F64D6: 77 73 5 53 79 73C 74 65 6 33 32 5 44 72 32 00 513 0 69 76: 65 72 73 5 44F 72 69 76 65 72D 44 61 74 61 00 513F .EFC_0=00.FPS_ 45F46F43: 5 34 33F 37 32 3 31 00F 46 50 53 5F 4372 1 00F 513 BROWSER_APP_PROF 0F42C 52C 4 57C E 53 45D 52 5E 41 50 50 ILE_STRING=Inter 5F50: 52E 4 46 00 51400 49 4 45C 5D 53 54 52D AC 49B 4 47 net ExplM..MK. Odotetaan [ENTER] poistumista main()... C:UsersduckKEYPASS>
Tässä ajossa emme vaivautuneet nappaamaan prosessimuistivedoksia, koska näimme heti lähdöstä, että tämä koodi vuotaa dataa.
Heti Windows C -ajonaikaisen kirjastotoiminnon kutsumisen jälkeen malloc()
, voimme nähdä, että puskuri, jonka saamme takaisin, sisältää ympäristömuuttujadataa, joka on jäänyt jäljelle ohjelman käynnistyskoodista, ja ensimmäiset 16 tavua on ilmeisesti muutettu näyttämään jonkinlaiselta jäljellä olevan muistin varausotsakkeelta.
(Huomaa, kuinka nuo 16 tavua näyttävät kahdelta 8-tavuisesta muistiosoitteesta, 0xF55790
ja 0xF50150
, jotka ovat juuri oman muistipuskurimme jälkeen ja juuri ennen vastaavasti.)
Kun salasanan oletetaan olevan muistissa, näemme koko merkkijonon selvästi puskurissa, kuten odotamme.
Mutta soiton jälkeen free()
Huomaa, kuinka puskurimme ensimmäiset 16 tavua on kirjoitettu uudelleen läheisillä muistiosoitteilla, oletettavasti, jotta muistin varaaja voi seurata muistissa olevia lohkoja, joita se voi käyttää uudelleen...
… mutta loput "poistetun" salasanamme tekstistä (viimeiset 12 satunnaista merkkiä EJJCPOMDJHAN
) on jäänyt jälkeen.
Meidän ei vain tarvitse hallita omia muistivarauksiamme ja varausten purkamista C:ssä, vaan meidän on myös varmistettava, että valitsemme tietopuskureille oikeat järjestelmätoiminnot, jos haluamme hallita niitä tarkasti.
Esimerkiksi vaihtamalla tähän koodiin saamme hieman enemmän hallintaa siihen, mitä muistissa on:
Vaihtamalla malloc()
ja free()
käyttääksesi alemman tason Windowsin allokointitoimintoja VirtualAlloc()
ja VirtualFree()
suoraan, saamme paremman hallinnan.
Maksamme kuitenkin hinnan nopeudesta, koska jokainen puhelu VirtualAlloc()
tekee enemmän työtä kuin soittaa malloc()
, joka toimii jatkuvasti jakamalla ja jakamalla lohkon ennalta varattua matalan tason muistia.
Käyttäminen VirtualAlloc()
toistuvasti pienille lohkoille kuluttaa myös enemmän muistia kaiken kaikkiaan, koska jokainen lohko kuluu VirtualAlloc()
kuluttaa tyypillisesti 4 kilotavua (tai 2 Mt, jos käytät ns suuria muistisivuja), joten yllä oleva 128-tavuinen puskurimme pyöristetään 4096 tavuun, mikä hukkaa 3968 tavua 4 kilotavun muistilohkon lopussa.
Mutta kuten näet, takaisin saamamme muisti tyhjenee automaattisesti (asetetaan nollaan), joten emme näe, mitä siellä oli aiemmin, ja tällä kertaa ohjelma kaatuu, kun yritämme käyttää jälkikäteen vapaata. temppu, koska Windows havaitsee, että yritämme kurkistaa muistiin, jota emme enää omista:
C:UsersduckKEYPASS> unl2 "uuden" puskurin tyhjennys alussa 0000000000EA0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0010 00 00 00 EA00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0020 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0030 ............. 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ Täysi merkkijono oli: epätodennäköinentekstiIBIPJPPHEOPOIDLL 00EA00: 00 0000000000E 0080C 00 00B 00 00C 00 00 00 00 00 00 00 00 00 epätodennäköinenteksti IBIPJPPHEOPOIDLL 00IP00 00 0000000000 0000 75F 6 6F 69 6 65C 6C 79 74 65 78 JPPHEOPOIDLL .... 74EA49 42 49 50 0000000000 0010 4 50 50 48 45 4 50 4 49 44 4 ................ 4EA00 : 00 00 00 0000000000 0020 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 00 00 00 0000000000 0030 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 00 00 00 00 0000000000 0040 00 00 00 00 00 00 00 00 00 00 ............... . 00EA00: 00 00 00 00 0000000000 0050 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 00 00 00 00 0000000000 0060 00 00 00 00 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 0000000000 0070 00 00 00 00 00 00 .............. ... Odotetaan, että [ENTER] vapauttaa puskurin... Poistetaan puskuri free() 00EA00 jälkeen: [Ohjelma lopetettiin tästä, koska Windows sai kiinni meidän after-free-käytöstä]
Koska vapauttamamme muisti on varattava uudelleen VirtualAlloc()
Ennen kuin sitä voidaan käyttää uudelleen, voimme olettaa, että se nollataan ennen kuin se kierrätetään.
Kuitenkin, jos halusimme varmistaa, että se on tyhjennetty, voisimme kutsua erityistä Windows-toimintoa RtlSecureZeroMemory()
juuri ennen sen vapauttamista varmistaaksemme, että Windows kirjoittaa ensin nollia puskuriimme.
Asiaan liittyvä toiminto RtlZeroMemory()
, jos mietit, tekee samanlaisen asian, mutta ilman takuuta todellisesta toimivuudesta, koska kääntäjät saavat poistaa sen teoreettisena ylimääräisenä, jos huomaavat, ettei puskuria käytetä uudelleen jälkeenpäin.
Kuten näet, meidän on huolehdittava oikeiden Windows-toimintojen käyttämisestä, jos haluamme minimoida ajan, jonka muistiin tallennetut salaisuudet saattavat jäädä myöhempää käyttöä varten.
Tässä artikkelissa emme aio tarkastella, kuinka voit estää salaisuuksien tallentumisen vahingossa swap-tiedostoosi lukitsemalla ne fyysiseen RAM-muistiin. (Vihje: VirtualLock()
ei itse asiassa riitä yksinään.) Jos haluat tietää lisää matalan tason Windowsin muistin suojauksesta, kerro siitä meille kommenteissa, niin tarkastelemme sitä seuraavassa artikkelissa.
Automaattisen muistinhallinnan käyttö
Yksi siisti tapa välttää muistin varaaminen, hallinta ja purkaminen itse on käyttää ohjelmointikieltä, joka huolehtii malloc()
ja free()
tai VirtualAlloc()
ja VirtualFree()
, automaattisesti.
Skriptikieli, esim Perl, Python, lua, JavaScript ja muut pääsevät eroon yleisimmistä C- ja C++-koodia vaivaavista muistin turvavirheistä seuraamalla muistin käyttöä puolestasi taustalla.
Kuten aiemmin mainitsimme, yllä oleva huonosti kirjoitettu C-näytekoodimme toimii nyt hyvin, mutta vain siksi, että se on edelleen erittäin yksinkertainen ohjelma, jossa on kiinteäkokoiset tietorakenteet ja jossa voimme varmistaa tarkastelemalla, että emme ylikirjoita 128-koodiamme. tavupuskuriin ja että on vain yksi suorituspolku, joka alkaa malloc()
ja päättyy vastaavaan free()
.
Mutta jos päivitimme sen sallimaan muuttuvapituisten salasanojen luominen tai lisäisimme luontiprosessiin lisäominaisuuksia, me (tai kuka tahansa, joka ylläpitää koodia seuraavaksi) voisimme helposti päätyä puskurin ylivuotoon, use-af-free -virheisiin tai muistiin, jotka ei koskaan vapaudu ja jättää siksi salaiset tiedot roikkumaan pitkään sen jälkeen, kun niitä ei enää tarvita.
Luan kaltaisella kielellä voimme antaa Lua-ajonaikaisen ympäristön, joka tekee sen, mitä ammattikielessä tunnetaan nimellä automaattinen jätteenkeräys, käsittelee muistin hankkimista järjestelmästä ja sen palauttamista, kun se havaitsee, että olemme lopettaneet sen käytön.
Yllä luettelemamme C-ohjelma muuttuu paljon yksinkertaisemmiksi, kun muistin varaus ja varauksen purkaminen hoidetaan puolestamme:
Varaamme muistia merkkijonon säilyttämiseen s
yksinkertaisesti määrittämällä merkkijono 'unlikelytext'
siihen.
Voimme myöhemmin joko vihjailla Lualle, ettemme ole enää kiinnostuneita s
antamalla sille arvon nil
(kaikki nils
ovat olennaisesti sama Lua-objekti) tai lopeta käyttö s
ja odota, että Lua havaitsee, ettei sitä enää tarvita.
Joka tapauksessa käyttämä muisti s
palautetaan lopulta automaattisesti.
Ja puskurin ylivuotojen tai koon väärinkäytön estämiseksi lisättäessä tekstimerkkijonoihin (Lua-operaattori ..
, lausutaan concat, olennaisesti lisää kaksi merkkijonoa yhteen, kuten +
Pythonissa), joka kerta kun pidennämme tai lyhennämme merkkijonoa, Lua varaa maagisesti tilaa aivan uudelle merkkijonolle sen sijaan, että muuttaisi tai korvaisi alkuperäistä sen olemassa olevassa muistipaikassa.
Tämä lähestymistapa on hitaampi ja johtaa C:ssä korkeampiin muistinkäyttöhuippuihin tekstinkäsittelyn aikana varattujen välimerkkijonojen vuoksi, mutta se on paljon turvallisempaa puskurin ylivuotojen suhteen.
Mutta tällainen automaattinen merkkijonojen hallinta (tunnetaan ammattikielessä nimellä muuttumattomuudesta, koska merkkijonot eivät koskaan saa mutatoitunut, tai kun niitä on muokattu paikoilleen, kun ne on luotu), tuo omaa kyberturvallisuuspäänsärkyä.
Suoritimme yllä olevan Lua-ohjelman Windowsissa toiseen taukoon asti juuri ennen ohjelman sulkemista:
C:UsersduckKEYPASS> lua s1.lua Täysi merkkijono on: unlikelytextHLKONBOJILAGLNLN Odotetaan [ENTER] ennen merkkijonon vapauttamista... Odotetaan [ENTER] ennen poistumista...
Tällä kertaa otimme prosessimuistivedoksen, kuten:
C:UsersduckKEYPASS> procdump -ma lua lua-s1.dmp ProcDump v11.0 - Sysinternalsin prosessivedostyökalu Copyright (C) 2009-2022 Mark Russinovich ja Andrew Richards Sysinternals - www.sysinternals.com [00:00:00] Dump aloitettu: C:UsersduckKEYPASSlua-s1.dmp [1:00:00] Dump 00 kirjoitus: Arvioitu vedostiedoston koko on 1 MB. [10:00:00] Veto 00 valmis: 1 Mt kirjoitettu 10 sekunnissa [0.1:00:00] Vetomäärä saavutettu.
Sitten suoritimme tämän yksinkertaisen skriptin, joka lukee vedostiedoston takaisin sisään, löytää kaikkialta muistista, että tunnettu merkkijono unlikelytext
ilmestyi, ja tulostaa sen yhdessä sen sijainnin dump-tiedostossa ja välittömästi seuranneiden ASCII-merkkien kanssa:
Vaikka olisit aiemmin käyttänyt kirjoituskieliä tai työskennellyt missä tahansa ohjelmointiekosysteemissä, jossa on ns hallitut merkkijonot, jossa järjestelmä pitää kirjaa muistin varauksista ja varauksista puolestasi ja käsittelee niitä parhaaksi katsomallaan tavalla...
… saatat yllättyä nähdessäsi tämän muistiskannauksen tuottaman tulosteen:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp 006D8AFC: epätodennäköinentekstiALJBNGOAPLLBDEB 006D8B3C: epätodennäköinentekstiALJBNGOA 006D8B7C: epätodennäköinentekstiALJBNGO 006D8B006C: epätodennäköinentekstiALJBNGO 8D006AFC: epätodennäköinentekstiALJBNGO 8D7AFC unlikely006 lytextALJBN 903D006D90C: epätodennäköinentekstiALJBNGOAP 006D90C: epätodennäköinentekstiALJBNGOAPL 006D913BC: epätodennäköinentekstiALJBNGOAPLL 006D91FC: epätodennäköinentekstiALJBNG 006D91BBALCALJBNGOAP 006D923BBALC006text70APDALC:006text8 006D0FC: epätodennäköinentekstiALJBNGOAPLLBD XNUMXDXNUMXC : epätodennäköinentekstiALJBNGOAPLLBDE XNUMXDBXNUMXC: epätodennäköinentekstiALJ XNUMXDBBXNUMXC: epätodennäköinentekstiAL XNUMXDBDXNUMXC: epätodennäköinentekstiA
Katso ja katso, silloin kun tartuimme muistiin, vaikka olimme lopettaneet merkkijonon s
(ja kertoi Lualle, että emme tarvitse sitä enää sanomalla s = nil
), kaikki koodin matkan varrella luomat merkkijonot olivat edelleen RAM-muistissa, eikä niitä ole vielä palautettu tai poistettu.
Todellakin, jos lajittelemme yllä olevat tulosteet itse merkkijonojen mukaan sen sijaan, että noudattaisimme järjestystä, jossa ne esiintyivät RAM-muistissa, voit kuvitella, mitä tapahtui silmukan aikana, jossa ketjutimme merkin kerrallaan salasanamerkkijonoamme:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp | lajittele /+10 006DBD0C: epätodennäköinentekstiA 006DBB8C: epätodennäköinentekstiAL 006DB70C: epätodennäköinentekstiALJ 006D91BC: epätodennäköinentekstiALJB 006D8CBC: epätodennäköinentekstiALJBN 006D90FC: epätodennäköinentekstiALJBN 006D8FC: epätodennäköinentekstiALJDBB7BJ006textALJDBB8 3B006C: epätodennäköinentekstiALJBNGOA 8D7D006C: epätodennäköinentekstiALJBNGOAP 903D006C: epätodennäköinentekstiALJBNGOAPL 90D006BC: epätodennäköinentekstiALJBNGOAPLL 913D006C: epätodennäköinentekstiALJBNGOAPLL91teksti006BDAL923AP006 D8C: epätodennäköinentekstiALJBNGOAPLLBDE 006D8AFC: epätodennäköinentekstiALJBNGOAPLLBDEB XNUMXDXNUMXBFC : epätodennäköinentekstiALJBNGOAPLLBDEBJ
Kaikki nuo väliaikaiset, välimerkkijonot ovat edelleen olemassa, joten vaikka olisimme onnistuneesti pyyhkiä pois lopullisen arvon s
, vuotaisimme silti kaiken paitsi sen viimeisen hahmon.
Itse asiassa tässä tapauksessa, vaikka tarkoituksella pakotimme ohjelmamme hävittämään kaikki tarpeettomat tiedot kutsumalla erityistä Lua-toimintoa collectgarbage()
(useimmissa skriptikielissä on jotain samanlaista), suurin osa noiden ärsyttävien väliaikaisten merkkijonojen tiedoista jäi joka tapauksessa kiinni RAM-muistiin, koska olimme kääntäneet Luan suorittamaan sen automaattisen muistinhallinnan vanhoilla hyvillä malloc()
ja free()
.
Toisin sanoen, vaikka Lua itse otti takaisin väliaikaiset muistilohkonsa käyttääkseen niitä uudelleen, emme voineet hallita, kuinka tai milloin noita muistilohkoja käytetään uudelleen ja kuinka kauan ne pysyisivät prosessin sisällä vasen- yli datan, joka odottaa haistamista, tyhjentämistä tai muuten vuotamista.
Kirjoita .NET
Mutta entä KeePass, josta tämä artikkeli alkoi?
KeePass on kirjoitettu C#-kielellä ja käyttää .NET-ajoaikaa, joten se välttää C-ohjelmien mukanaan tuomat muistin huonon hallinnan ongelmat…
…mutta C# hallitsee omia tekstijonojaan, aivan kuten Lua tekee, mikä herättää kysymyksen:
Vaikka ohjelmoija välttisi tallentamasta koko pääsalasanan yhteen paikkaan sen lopettamisen jälkeen, voisivat hyökkääjät, joilla on pääsy muistivedokseen, löytää riittävästi jäljellä olevaa väliaikaista tietoa pääsalasanan arvaamiseksi tai palauttamiseksi, vaikka hyökkääjät pääsivät tietokoneellesi minuuttien, tuntien tai päivien kuluttua salasanan kirjoittamisesta?
Yksinkertaisesti sanottuna: säilyvätkö pääsalasanoissasi havaittavissa olevia haamujäänteitä RAM-muistissa, vaikka niiden olettaisitkin poistuneen?
Ärsyttävästi Githubin käyttäjänä Vdohney löysi, vastaus (ainakin KeePass-versioille, jotka ovat vanhempia kuin 2.54) on "Kyllä".
Selvyyden vuoksi emme usko, että todellista pääsalasanasi voidaan palauttaa yhtenä tekstimerkkijonona KeePass-muistivedosta, koska kirjoittaja loi pääsalasanan syöttämiseen erityisen toiminnon, joka estää koko salasanan tallentamisen. salasana, josta se voidaan helposti havaita ja haistella.
Tyydyimme tähän asettamalla pääsalasanamme arvoon SIXTEENPASSCHARS
, kirjoittamalla sen ja tekemällä muistivedoksia välittömästi, pian ja kauan sen jälkeen.
Etsimme kaatopaikkoja yksinkertaisella Lua-komentosarjalla, joka etsi kaikkialta kyseistä salasanatekstiä, sekä 8-bittisessä ASCII-muodossa että 16-bittisessä UTF-16-muodossa (Windows widechar), kuten tämä:
Tulokset olivat rohkaisevia:
C:UsersduckKEYPASS> lua searchknown.lua kp2-post.dmp Luetaan vedostiedostoa... VALMIS. Haetaan SIXTEENPASSCHARSia 8-bittisenä ASCII-muodossa... ei löydy. Haetaan sanaa SIXTEENPASSCHARS UTF-16-muodossa... ei löytynyt.
Mutta Vdohney, CVE-2023-32784:n löytäjä, huomasi, että kun kirjoitat pääsalasanasi, KeePass antaa sinulle visuaalista palautetta rakentamalla ja näyttämällä paikkamerkkijonon, joka koostuu Unicode-blob-merkeistä aina sinun salasanasi pituuteen asti. Salasana:
Windowsin widechar-tekstijonoissa (jotka koostuvat kahdesta tavusta per merkki, ei vain yhtä tavua kukin kuten ASCII:ssa) "blob"-merkki on koodattu RAM-muistiin heksatavuna. 0xCF
jälkeen 0x25
(joka vain sattuu olemaan prosenttimerkki ASCII:ssa).
Joten vaikka KeePass pitääkin erittäin varovaisena syöttämiäsi raakamerkkejä, kun kirjoitat itse salasanan, saatat päätyä jäljelle jääneisiin "blob"-merkkijonoihin, jotka voidaan helposti havaita muistissa toistuvina ajoina, kuten CF25CF25
or CF25CF25CF25
...
…ja jos näin on, pisin löytämäsi blob-merkkien sarja todennäköisesti paljastaisi salasanasi pituuden, mikä olisi vaatimaton muoto salasanatietojen vuotamisesta, ellei muuta.
Käytimme seuraavaa Lua-skriptiä etsimään merkkejä jäljellä olevista salasanan paikkamerkkijonoista:
Tulos oli yllättävä (olemme poistaneet peräkkäisiä rivejä, joissa on sama määrä blobeja tai vähemmän blobeja kuin edellisellä rivillä tilan säästämiseksi):
C:UsersduckKEYPASS> lua findblobs.lua kp2-post.dmp 000EFF3C: * [. . .] 00BE621B: ** 00BE64C7: *** [. . .] 00BE6E8F: **** [. . .] 00BE795F: ***** [. . .] 00BE84F7: ****** [. . .] 00BE8F37: ******* [ jatkuu samalla tavalla 8 blobille, 9 blobille jne. ] [ kunnes kaksi viimeistä riviä, joissa kussakin on tasan 16 blobia ] 00C0503B: ************* *** 00C05077: **************** 00C09337: * 00C09738: * [ kaikki jäljellä olevat osumat ovat yhden blobin pituisia] 0123B058: *
Läheisistä, mutta jatkuvasti lisääntyvistä muistiosoitteista löysimme systemaattisen luettelon 3 blobista, sitten 4 blobista ja niin edelleen 16 blobista (salasanamme pituus), joita seurasi monia satunnaisesti hajallaan olevia yksittäisiä blob-merkkijonoja. .
Joten nuo paikkamerkkien "blob"-merkkijonot näyttävät todellakin vuotavan muistiin ja jäävän taakse vuotaakseen salasanan pituuden kauan sen jälkeen, kun KeePass-ohjelmisto on saanut pääsalasanasi valmiiksi.
Seuraava askel
Päätimme kaivaa pidemmälle, aivan kuten Vdohney teki.
Muutimme mallinsovituskoodimme havaitsemaan blob-merkkien ketjut, joita seuraa yksittäinen ASCII-merkki 16-bittisessä muodossa (ASCII-merkit esitetään UTF-16:ssa tavallisena 8-bittisenä ASCII-koodina, jota seuraa nolla tavu).
Tällä kertaa olemme tilan säästämiseksi estäneet kaikkien edellistä täsmälleen vastaavien tulosten:
Yllätys, yllätys:
C:UsersduckKEYPASS> lua searchkp.lua kp2-post.dmp 00BE581B: *I 00BE621B: **X 00BE6BD3: ***T 00BE769B: ****E 00BE822B: *****E 00BE8C6B: ******N 00BE974B: *******P 00BEA25B: ********A 00BEAD33: *********S 00BEB81B: **********S 00BEC383: ***********C 00BECEEB: ************H 00BEDA5B: *************A 00BEE623: **************R 00BEF1A3: ***************S 03E97CF2: *N 0AA6F0AF: *W 0D8AF7C8: *X 0F27BAF8: *S
Katso, mitä saamme irti .NET:n hallitusta merkkijonomuistialueesta!
Tiivis joukko väliaikaisia "blob-merkkijonoja", jotka paljastavat salasanamme peräkkäiset merkit alkaen toisesta merkistä.
Näitä vuotavia merkkijonoja seuraavat laajalle levinneitä yksimerkkiosuuksia, joiden oletamme syntyneen sattumalta. (KeePass-vedostiedosto on kooltaan noin 250 Mt, joten siellä on runsaasti tilaa "blob"-merkeille ilmaantua kuin onnesta.)
Vaikka otamme nämä neljä ylimääräistä vastaavuutta huomioon sen sijaan, että hylkäämme ne todennäköisinä yhteensopimattomina, voimme olettaa, että pääsalasana on jokin seuraavista:
?IXTEENPASSCHARS ?NXTEENPASSCHARS ?WXTEENPASSCHARS ?SXTEENPASSCHARS
Ilmeisesti tämä yksinkertainen tekniikka ei löydä ensimmäistä merkkiä salasanasta, koska ensimmäinen "blob-merkkijono" muodostetaan vasta sen jälkeen, kun ensimmäinen merkki on kirjoitettu.
Huomaa, että tämä luettelo on mukava ja lyhyt, koska suodatimme pois osumat, jotka eivät pääty ASCII-merkkeihin.
Jos etsit eri merkkejä, kuten kiinalaisia tai korealaisia merkkejä, saatat saada enemmän vahingossa osumia, koska merkkejä on paljon enemmän...
...mutta epäilemme, että pääset joka tapauksessa melko lähelle pääsalasanaasi, ja salasanaan liittyvät "blob-merkkijonot" näyttävät olevan ryhmitelty RAM-muistiin, luultavasti siksi, että sama osa on allokoinut ne suunnilleen samaan aikaan. .NET-ajoaika.
Ja siellä, tosin pitkässä ja diskursiivisessa pähkinänkuoressa, on kiehtova tarina CVE-2023-32784.
Mitä tehdä?
- Jos olet KeePass-käyttäjä, älä panikoi. Vaikka tämä on bugi ja teknisesti hyödynnettävissä oleva haavoittuvuus, etähyökkääjien, jotka haluavat murtaa salasanasi tällä bugilla, on ensin istutettava haittaohjelma tietokoneellesi. Tämä antaisi heille monia muita tapoja varastaa salasanasi suoraan, vaikka tätä vikaa ei olisikaan, esimerkiksi kirjaamalla näppäinpainallukset kirjoittaessasi. Tässä vaiheessa voit vain varoa tulevaa päivitystä ja tarttua siihen, kun se on valmis.
- Jos et käytä koko levyn salausta, harkitse sen ottamista käyttöön. Jäljelle jääneiden salasanojen purkamiseksi sivutustiedostosta tai lepotilatiedostosta (käyttöjärjestelmän levytiedostot, joita käytetään muistin sisällön väliaikaiseen tallentamiseen raskaan kuormituksen aikana tai kun tietokoneesi on "nukkumassa"), hyökkääjien on käytettävä suoraa kiintolevyäsi. Jos sinulla on BitLocker tai vastaava muille käyttöjärjestelmille aktivoitu, he eivät voi käyttää sivutustiedostoasi, lepotilatiedostoasi tai muita henkilökohtaisia tietoja, kuten asiakirjoja, laskentataulukoita, tallennettuja sähköposteja ja niin edelleen.
- Jos olet ohjelmoija, pidä itsesi ajan tasalla muistinhallintaongelmista. Älä oleta sitä vain siksi, että jokainen
free()
vastaa vastaavaamalloc()
että tietosi ovat turvassa ja hyvin hallittuja. Joskus saatat joutua ryhtymään ylimääräisiin varotoimiin, jotta salaiset tiedot eivät jää piiloon, ja nämä varotoimenpiteet käyttöjärjestelmästä toiseen. - Jos olet laadunvarmistuksen testaaja tai koodin tarkistaja, ajattele aina "kulissien takana". Vaikka muistinhallintakoodi näyttää siistiltä ja tasapainoiselta, ole tietoinen siitä, mitä kulissien takana tapahtuu (koska alkuperäinen ohjelmoija ei ehkä tiennyt niin) ja valmistaudu tekemään testaustyylisiä töitä, kuten ajonajan valvontaa ja muistia. polkumyynti varmistaaksesi, että suojattu koodi todella käyttäytyy niin kuin sen pitääkin.
KOODI ARTIKKESTA: UNL1.C
#sisältää #sisältää #sisältää void hexdump(signed char* buff, int len) { // Tulosta puskuri 16-tavuisina paloina for (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Näytä 16 tavua heksadesimaaliarvoina kohteelle (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Toista nämä 16 tavua merkeinä kohteelle (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("n"); } printf("n"); } int main(void) { // Hanki muisti salasanan tallentamista varten ja näytä, mitä // puskurissa on, kun se on virallisesti "uusi"... char* buff = malloc(128); printf("Uuden puskurin tyhjentäminen käynnistyksen yhteydessä"); hexdump(buff,128); // Käytä pseudosatunnaispuskuriosoitetta satunnaisena siemenenä srand((unsigned)buff); // Aloita salasana kiinteällä, haettavissa olevalla tekstillä strcpy(buff"unlikelytext"); // Liitä 16 näennäissatunnaista kirjainta, yksi kerrallaan (int i = 1; i <= 16; i++) { // Valitse kirjain väliltä A (65+0) - P (65+15) char ch = 65 + (rand() & 15); // Muokkaa sitten buff-merkkijonoa strncat(buff,&ch,1); } // Täysi salasana on nyt muistissa, joten tulosta // se merkkijonona ja näytä koko puskuri... printf("Täysi merkkijono oli: %sn",buff); hexdump(buff,128); // Keskeytä prosessin RAM-muistin tyhjentäminen nyt (kokeile: 'procdump -ma') puts("Odotetaan, että [ENTER] vapauttaa puskurin..."); getchar(); // Vapauta muodollisesti() muisti ja näytä puskuri // uudelleen nähdäksesi onko jotain jäänyt... free(buff); printf("Tyhjennyspuskuri free()n:n jälkeen"); hexdump(buff,128); // Keskeytä RAM-muistin tyhjentäminen uudelleen tarkastaaksesi erot puts("Odotetaan [ENTER] poistumista main(..."); getchar(); paluu 0; }
KOODI ARTIKKESTA: UNL2.C
#sisältää #sisältää #sisältää #sisältää void hexdump(signed char* buff, int len) { // Tulosta puskuri 16-tavuisina paloina for (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Näytä 16 tavua heksadesimaaliarvoina kohteelle (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Toista nämä 16 tavua merkeinä kohteelle (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("n"); } printf("n"); } int main(void) { // Hanki muistia salasanan tallentamista varten ja näytä, mitä // puskurissa on, kun se on virallisesti "uusi"... char* buff = VirtualAlloc(0,128,MEM_COMMIT,PAGE_READWRITE); printf("Uuden puskurin tyhjentäminen käynnistyksen yhteydessä"); hexdump(buff,128); // Käytä pseudosatunnaispuskuriosoitetta satunnaisena siemenenä srand((unsigned)buff); // Aloita salasana kiinteällä, haettavissa olevalla tekstillä strcpy(buff"unlikelytext"); // Liitä 16 näennäissatunnaista kirjainta, yksi kerrallaan (int i = 1; i <= 16; i++) { // Valitse kirjain väliltä A (65+0) - P (65+15) char ch = 65 + (rand() & 15); // Muokkaa sitten buff-merkkijonoa strncat(buff,&ch,1); } // Täysi salasana on nyt muistissa, joten tulosta // se merkkijonona ja näytä koko puskuri... printf("Täysi merkkijono oli: %sn",buff); hexdump(buff,128); // Keskeytä prosessin RAM-muistin tyhjentäminen nyt (kokeile: 'procdump -ma') puts("Odotetaan, että [ENTER] vapauttaa puskurin..."); getchar(); // Vapauta muodollisesti() muisti ja näytä puskuri // uudelleen nähdäksesi, onko jotain jäänyt... VirtualFree(buff,0,MEM_RELEASE); printf("Tyhjennyspuskuri free()n:n jälkeen"); hexdump(buff,128); // Keskeytä RAM-muistin tyhjentäminen uudelleen tarkastaaksesi erot puts("Odotetaan [ENTER] poistumista main(..."); getchar(); paluu 0; }
KOODI ARTIKKESTA: S1.LUA
-- Aloita jollain kiinteällä, haettavalla tekstillä s = 'epätodennäköinen teksti' -- Liitä 16 satunnaista merkkiä A:sta P:hen, kun i = 1,16 do s = s .. string.char(65+math.random( 0,15)) end print('Täysi merkkijono on:',s,'n') -- Keskeytä prosessin tyhjennys RAM print('Odotetaan [ENTER] ennen merkkijonon vapauttamista...') io.read() - - Pyyhi merkkijono ja merkitse muuttuja unused s = nil -- Tyhjennä RAM uudelleen etsiäksesi eroja print('Odotetaan [ENTER] ennen poistumista...') io.read()
KOODI ARTIKKESTA: FINDIT.LUA
-- lue vedostiedostosta paikallinen f = io.open(arg[1],'rb'):read('*a') -- etsi merkkitekstiä, jota seuraa yksi -- tai useampi satunnainen ASCII-merkki paikallinen b,e ,m = 0,0,nil kun taas true do -- etsi seuraava vastaavuus ja muista offset b,e,m = f:find('(epätodennäköinenteksti[AZ]+)',e+1) -- poistu kun ei enää vastaa jos ei b sitten break end -- raportin sijainti ja merkkijono löytyi print(string.format('%08X: %s',b,m)) end
KOODI ARTIKKESTA: SEARCHKNOWN.LUA
io.write('Luetaan vedostiedostoa... ') local f = io.open(arg[1],'rb'):read('*a') io.write('DONE.n') io. write('Etsitään SIXTEENPASSCHARSia 8-bittisenä ASCII-muodossa... ') local p08 = f:find('SIXTEENPASSCHARS') io.write(p08 ja 'FOUND' tai 'not found','.n') io.write ('Etsitään SIXTEENPASSCHARSia UTF-16-muodossa... ') local p16 = f:find('Sx00Ix00Xx00Tx00Ex00Ex00Nx00Px00'.. 'Ax00Sx00Sx00Cx00Hx00Ax00Rx'00) ja 00 . ei löydy','.n')
KOODI ARTIKKESTA: FINDBLOBS.LUA
-- lue vedostiedostossa, joka on määritetty komentorivillä local f = io.open(arg[1],'rb'):read('*a') -- Etsi yksi tai useampi salasanablob, jonka jälkeen on jokin muu kuin blob -- Huomaa, että blob-merkit (●) koodaavat Windowsin laajamerkkeihin -- litte-endian UTF-16-koodeiksi, jotka tulevat ulos CF 25:nä heksadesimaalimuodossa. paikallinen b,e,m = 0,0,nil, kun taas true do -- Haluamme yhden tai useamman blobin, jota seuraa mikä tahansa ei-blob. -- Yksinkertaistamme koodia etsimällä eksplisiittistä CF25:tä -- jota seuraa mikä tahansa merkkijono, jossa on vain CF tai 25, -- joten löydämme CF25CFCF tai CF2525CF sekä CF25CF25. -- Suodatamme "väärät positiiviset" myöhemmin pois, jos niitä on. -- Meidän on kirjoitettava '%%' x25:n sijaan, koska x25 ---merkki (prosenttimerkki) on erityinen hakumerkki Luassa! b,e,m = f:find('(xCF%%[xCF%%]*)',e+1) -- poistu, kun ei ole enää osumia, jos ei b, lopeta loppu -- CMD.EXE ei voi tulostaa möykkyjä, joten muunnamme ne tähdiksi. print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) end
KOODI ARTIKKESTA: SEARCHKP.LUA
-- lue vedostiedostossa, joka on määritetty komentorivillä local f = io.open(arg[1],'rb'):read('*a') local b,e,m,p = 0,0,nil,nil kun taas true do -- Nyt haluamme yhden tai useamman blobin (CF25) ja sen jälkeen koodin -- for A..Z ja sen jälkeen 0 tavun ACSCII:n muuntamiseksi UTF-16:ksi b,e,m = f:find(' (xCF%%[xCF%%]*[AZ])x00',e+1) -- poistu, kun ei ole enää osumia, jos ei b, sitten katkaise loppu -- CMD.EXE ei voi tulostaa blobeja, joten muunnamme ne muotoon tähdet. -- Tilan säästämiseksi estämme peräkkäiset osumat, jos m ~= p sitten print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) p = m loppu loppu
- SEO-pohjainen sisällön ja PR-jakelu. Vahvista jo tänään.
- PlatoAiStream. Web3 Data Intelligence. Tietoa laajennettu. Pääsy tästä.
- Tulevaisuuden lyöminen Adryenn Ashley. Pääsy tästä.
- Osta ja myy osakkeita PRE-IPO-yhtiöissä PREIPO®:lla. Pääsy tästä.
- Lähde: https://nakedsecurity.sophos.com/2023/05/31/serious-security-that-keepass-master-password-crack-and-what-we-can-learn-from-it/
- :on
- :On
- :ei
- :missä
- ][s
- $ YLÖS
- 1
- 10
- 12
- 15%
- 20
- 200
- 2023
- 24
- 250
- 27
- 31
- 3d
- 49
- 50
- 67
- 70
- 72
- 77
- 8
- 9
- a
- pystyy
- Meistä
- edellä
- absoluuttinen
- AC
- pääsy
- Tili
- hankkia
- hankkiminen
- aktiivinen
- todellinen
- todella
- lisä-
- lisä-
- osoite
- osoitteet
- Lisää
- Jälkeen
- jälkeenpäin
- uudelleen
- Kaikki
- kohdennetaan
- allokoi
- jako
- määrärahat
- sallia
- yksin
- pitkin
- jo
- Myös
- muuttunut
- Vaikka
- aina
- an
- ja
- Andrew
- vastaus
- Kaikki
- mitään
- mitään kriittistä
- näyttää
- ilmestyi
- lähestymistapa
- hyväksytty
- OVAT
- noin
- artikkeli
- artikkelit
- AS
- At
- kirjoittaja
- auto
- automaattisesti
- automaattisesti
- saatavissa
- välttää
- välttää
- tietoinen
- pois
- takaisin
- tausta
- background-image
- BE
- koska
- tulee
- ollut
- ennen
- Alku
- takana
- kulissien takana
- alle
- Paremmin
- Bitti
- Tukkia
- Blocks
- reunus
- sekä
- pohja
- merkki
- Brand New
- Tauko
- lyhyesti
- tuoda
- puskuri
- puskurin ylivuoto
- Vika
- Bugs
- rakentaa
- mutta
- by
- C + +
- soittaa
- soittamalla
- CAN
- Voi saada
- joka
- tapaus
- kiinni
- CD
- keskus
- varmasti
- kahleet
- mahdollisuus
- muuttunut
- merkki
- merkkejä
- tarkkailun
- Tarkastukset
- kiinalainen
- Valita
- selkeä
- selvästi
- lähellä
- koodi
- väri
- KOM
- tulee
- tuleva
- kommentti
- kommentit
- Yhteinen
- täydellinen
- monimutkainen
- tietokone
- Harkita
- huomattava
- harkittu
- Koostuu
- rakentamalla
- pitoisuus
- sisältö
- jatkuvasti
- jatkuu
- ohjaus
- muuntaa
- tekijänoikeus
- vastaava
- voisi
- kattaa
- crack
- luoda
- luotu
- luoja
- kriittinen
- tietoverkkojen
- VAARA
- Vaarallinen
- tiedot
- tietojen vuotaminen
- päivää
- sopimus
- päätti
- omistautunut
- on kuvattu
- DID
- erot
- eri
- vaikeus
- DIG
- digitaalinen
- ohjata
- Suora pääsy
- suoraan
- näyttö
- näyttämällä
- on
- do
- asiakirjat
- ei
- ei
- tekee
- tehty
- Dont
- alas
- ajaa
- kaksi
- dumpata
- aikana
- e
- kukin
- Aikaisemmin
- helposti
- ekosysteemi
- myöskään
- muu
- sähköpostit
- mahdollistaa
- rohkaiseva
- salaus
- loppu
- päättyy
- tarpeeksi
- varmistaa
- varmistamalla
- enter
- kirjoittamalla
- Koko
- merkintä
- ympäristö
- Vastaava
- virhe
- olennaisesti
- arvioidaan
- jne.
- Eetteri (ETH)
- Jopa
- lopulta
- jatkuvasti lisääntyvä
- Joka
- kaikki
- täsmälleen
- esimerkki
- Paitsi
- jännitys
- teloitus
- olla
- olemassa
- poistuminen
- poistuminen
- odottaa
- Selittää
- Käyttää hyväkseen
- avoin
- laajentaa
- lisää
- uute
- tosiasia
- väärä
- lumoava
- Ominaisuudet
- palaute
- vähemmän
- taistelee
- filee
- Asiakirjat
- suodattaa
- lopullinen
- Vihdoin
- Löytää
- löytäminen
- löydöt
- loppu
- Etunimi
- kiinteä
- Keskittää
- seurannut
- jälkeen
- varten
- muoto
- muodollisesti
- muoto
- tuleva
- löytyi
- neljä
- Ilmainen
- alkaen
- koko
- täysin
- toiminto
- tehtävät
- edelleen
- tulevaisuutta
- synnyttää
- sukupolvi
- saada
- saada
- GitHub
- Antaa
- tietty
- antaa
- Antaminen
- Go
- Goes
- menee
- hyvä
- Hallitus
- napata
- suuri
- taata
- HAD
- Vetimet
- tapahtui
- Happening
- tapahtuu
- Kova
- Olla
- ottaa
- päänsärkyä
- raskas
- korkeus
- tätä
- HEX
- korkean tason
- korkeampi
- Osumien
- pitää
- Reikä
- toivoa
- TUNTIA
- liihottaa
- Miten
- Miten
- HTTPS
- metsästys
- i
- tunniste
- if
- heti
- tärkeä
- in
- sisältää
- Mukaan lukien
- tiedot
- tietoa
- sen sijaan
- kiinnostunut
- väli-
- Internet
- tulee
- kysymykset
- IT
- SEN
- itse
- ammattikieli
- kesäkuu
- vain
- vain yksi
- Pitää
- avain
- Tietää
- tunnettu
- Korean
- Kieli
- kielet
- kannettava tietokone
- Sukunimi
- myöhemmin
- johtaa
- Liidit
- vuotaa
- Vuodot
- OPPIA
- oppiminen
- vähiten
- jättäen
- vasemmalle
- Pituus
- kirjain
- Kirjasto
- elämä
- pitää
- Todennäköisesti
- rajallinen
- linja
- linjat
- Lista
- lueteltu
- ll
- kuormitus
- paikallinen
- sijainti
- hakkuu
- Pitkät
- pitkän aikavälin
- kauemmin
- katso
- näyttää joltakin
- Katsoin
- näköinen
- ulkonäkö
- Erä
- onni
- ylläpitää
- tehdä
- haittaohjelmat
- hoitaa
- onnistui
- johto
- johtaja
- hallinnoi
- Manipulointi
- monet
- Marginaali
- Merkitse
- merkki
- mestari
- ottelu
- matching
- max-width
- Saattaa..
- välineet
- Muisti
- mainitsi
- Microsoft
- ehkä
- pöytäkirja
- vaatimaton
- muokattu
- muokata
- hetki
- seuranta
- lisää
- eniten
- paljon
- moninkertainen
- Siisti
- Tarve
- tarvitaan
- netto
- ei ikinä
- silti
- Uusi
- uutiset
- seuraava
- mukava
- Nro
- normaali
- ei mitään
- Ilmoitus..
- nyt
- numero
- numerot
- objekti
- Ilmeinen
- of
- pois
- virallinen
- Virallisesti
- offset
- Vanha
- on
- kerran
- ONE
- vain
- avoimen lähdekoodin
- toiminta
- käyttöjärjestelmän
- käyttöjärjestelmät
- operaattori
- Vaihtoehto
- or
- tilata
- alkuperäinen
- Muut
- Muuta
- muuten
- meidän
- itse
- ulos
- ulostulo
- yli
- yleinen
- oma
- sivulla
- Paniikki
- osa
- Salasana
- Password Manager
- salasanat
- polku
- Kuvio
- Paavali
- tauko
- Maksaa
- prosentti
- ehkä
- aika
- vakinaisesti
- henkilöstö
- henkilökohtaiset tiedot
- fyysinen
- kuva
- kappaletta
- Paikka
- placeholder
- Vitsaus
- Platon
- Platonin tietotieto
- PlatonData
- paljon
- Kohta
- pistettä
- Suosittu
- sijainti
- mahdollinen
- Viestejä
- mahdollinen
- tarkasti
- esittää
- aika
- estää
- edellinen
- hinta
- Painaa
- tulosteet
- todennäköisesti
- ongelmia
- prosessi
- Ohjelma
- Ohjelmoija
- Ohjelmoijat
- Ohjelmointi
- Ohjelmat
- korostunut
- laittaa
- Python
- Kysymyksiä ja vastauksia
- kysymys
- herättää
- RAM
- satunnainen
- alue
- pikemminkin
- raaka
- raakadata
- RE
- saavutettu
- Lue
- Lukeminen
- valmis
- todellinen
- oikea elämä
- reaaliaikainen
- ihan oikeesti
- tunnistaa
- toipua
- toipumassa
- liittyvä
- jäljellä oleva
- muistaa
- kaukosäädin
- poistaa
- toistaa
- toistuva
- TOISTUVASTI
- raportti
- edustettuina
- kunnioittaminen
- vastaavasti
- REST
- tulokset
- palata
- palaavat
- paljastaa
- Eroon
- oikein
- Riski
- riskit
- Huone
- ajaa
- juoksu
- ajonaikainen seuranta
- s
- turvallista
- turvallisempaa
- sama
- vakuuttunut
- Säästä
- sanonta
- skannata
- hajallaan
- kohtaukset
- Haku
- haku
- Toinen
- sekuntia
- salaisuus
- Osa
- turvallinen
- turvallisuus
- nähdä
- siemenet
- koska
- näyttää
- nähneet
- näkee
- Sarjat
- vakava
- setti
- asetus
- Lyhyt
- Pian
- shouldnt
- näyttää
- esitetty
- merkki
- Signs
- samankaltainen
- samalla lailla
- Yksinkertainen
- yksinkertaistettu
- yksinkertaistaa
- yksinkertaisesti
- single
- Koko
- nukkua
- pieni
- Luihu
- vakoilla
- So
- Tuotteemme
- vankka
- jonkin verran
- jotain
- Pian
- lähde
- lähdekoodi
- Tila
- erityinen
- erityisesti
- määritelty
- nopeus
- Tähteä
- Alkaa
- alkoi
- Aloita
- alkaa
- käynnistyksen
- Yhä
- varasti
- stop
- pysähtynyt
- verkkokaupasta
- tallennettu
- Tarina
- jono
- vahva
- tutkimus
- Onnistuneesti
- niin
- riittävä
- tarkoitus
- yllätys
- yllättynyt
- yllättävä
- hengissä
- SVG
- vaihtaa
- järjestelmä
- järjestelmät
- ottaa
- otettava
- vie
- ottaen
- puhuminen
- teknisesti
- tekniikat
- tilapäinen
- testi
- testit
- kuin
- että
- -
- Lähde
- heidän
- Niitä
- itse
- sitten
- teoria
- Siellä.
- siksi
- ne
- asia
- ajatella
- tätä
- ne
- vaikka?
- ajatus
- aika
- Otsikko
- että
- yhdessä
- otti
- työkalu
- ylin
- raita
- Seuranta
- siirtyminen
- läpinäkyvä
- totta
- yrittää
- Sorvatut
- kaksi
- tyyppi
- tyypillisesti
- ymmärtää
- Unicode
- asti
- käyttämätön
- toivottuja
- Päivitykset
- päivitetty
- URL
- us
- meidän hallitus
- Käyttö
- usb
- käyttää
- käyttö jälkikäteen
- käytetty
- käyttäjä
- käyttötarkoituksiin
- käyttämällä
- hyödyllisyys
- arvo
- arvot
- lajike
- todentaa
- versio
- hyvin
- kautta
- alttius
- W
- odottaa
- odotus
- haluta
- halusi
- oli
- Katso
- Tapa..
- tavalla
- we
- viikkoa
- HYVIN
- olivat
- Mitä
- kun
- onko
- joka
- vaikka
- KUKA
- kuka tahansa
- koko
- miksi
- tulee
- voittaa
- ikkunat
- pyyhkiä
- with
- ilman
- Mietitkö
- sanoja
- Referenssit
- työskenteli
- työskentely
- toimii
- huoli
- olisi
- antaisi
- kirjoittaa
- kirjoittaminen
- kirjallinen
- vielä
- te
- Sinun
- itse
- zephyrnet
- nolla-