V zadnjih dveh tednih smo videli vrsto člankov, ki govorijo o tem, kar je bilo opisano kot "razbijanje glavnega gesla" v priljubljenem odprtokodnem upravitelju gesel KeePass.
Napaka je veljala za dovolj pomembno, da je dobila uradni identifikator vlade ZDA (znana je kot CVE-2023-32784, če ga želite uloviti), in glede na to, da je glavno geslo za vaš upravitelj gesel skoraj ključ do vašega celotnega digitalnega gradu, lahko razumete, zakaj je zgodba izzvala veliko navdušenja.
Dobra novica je, da bi moral napadalec, ki bi želel izkoristiti to napako, skoraj zagotovo že okužiti vaš računalnik z zlonamerno programsko opremo in bi tako vseeno lahko vohunil za vašimi pritiski tipk in zagnanimi programi.
Z drugimi besedami, napako lahko štejemo za preprosto obvladljivo tveganje, dokler ustvarjalec KeePass ne izda posodobitve, ki naj bi se pojavila kmalu (očitno v začetku junija 2023).
Ker razkrivalec hrošča poskrbi za izpostaviti:
Če uporabljate celotno šifriranje diska z močnim geslom in je vaš sistem [brez zlonamerne programske opreme], bi moralo biti vse v redu. Samo s to ugotovitvijo nihče ne more ukrasti vaših gesel na daljavo prek interneta.
Pojasnjena tveganja
Če povzamemo, se hrošč zmanjša na težavo pri zagotavljanju, da so vse sledi zaupnih podatkov izbrisane iz pomnilnika, ko z njimi končate.
Tu ne bomo upoštevali težav, kako se sploh izogniti skrivnim podatkom v pomnilniku, četudi za kratek čas.
V tem članku želimo samo spomniti programerje povsod, da je koda, ki jo je odobril varnostno ozaveščen pregledovalec, s komentarjem, kot je "videti, da pravilno počisti za seboj" ...
... v resnici morda sploh ne bodo v celoti počiščeni in morebitno uhajanje podatkov morda ni očitno iz neposredne študije same kode.
Preprosto povedano, ranljivost CVE-2023-32784 pomeni, da je mogoče glavno geslo KeePass obnoviti iz sistemskih podatkov tudi po tem, ko je program KeyPass zaprl, ker je dovolj informacij o vašem geslu (čeprav ne dejansko samo neobdelano geslo, na katerega se bomo osredotočili vklopljeno čez trenutek) lahko ostanejo v datotekah za zamenjavo sistema ali v datotekah za spanje, kjer se lahko dodeljeni sistemski pomnilnik shrani za pozneje.
V računalniku z operacijskim sistemom Windows, kjer se BitLocker ne uporablja za šifriranje trdega diska, ko je sistem izklopljen, bi to dalo goljufu, ki je ukradel vaš prenosnik, bojno možnost, da se zažene s pogona USB ali CD-ja in celo obnovi vaše glavno geslo čeprav program KeyPass sam poskrbi, da ga nikoli trajno ne shrani na disk.
Dolgotrajno uhajanje gesla v pomnilnik tudi pomeni, da bi bilo geslo teoretično mogoče obnoviti iz izpisa pomnilnika programa KeyPass, tudi če je bil ta izpis prevzet dolgo po tem, ko ste vnesli geslo, in dolgo po tem, ko je KeePass sam ni imel več potrebe po obdržanju.
Jasno je, da bi morali domnevati, da bi zlonamerna programska oprema, ki je že v vašem sistemu, lahko obnovila skoraj vsako vneseno geslo z različnimi tehnikami vohljanja v realnem času, če so bila aktivna v času, ko ste tipkali. Lahko pa razumno pričakujete, da bo vaš čas, izpostavljen nevarnosti, omejen na kratko obdobje tipkanja, ne bo podaljšan na več minut, ur ali dni po tem ali morda dlje, tudi po tem, ko izklopite računalnik.
Kaj ostane za sabo?
Zato smo mislili, da bomo na visoki ravni preučili, kako lahko tajni podatki ostanejo v pomnilniku na načine, ki niso neposredno razvidni iz kode.
Ne skrbite, če niste programer – ohranili bomo preprostost in sproti razlagali.
Začeli bomo z ogledom uporabe in čiščenja pomnilnika v preprostem programu C, ki simulira vnos in začasno shranjevanje gesla tako, da naredi naslednje:
- Dodelitev namenskega dela pomnilnika posebej za shranjevanje gesla.
- Vstavljanje znanega besedilnega niza tako da ga po potrebi zlahka najdemo v spominu.
- Dodajanje 16 psevdo-naključnih 8-bitnih znakov ASCII iz območja AP.
- Tiskanje medpomnilnik simuliranega gesla.
- Sproščanje pomnilnika v upanju, da bodo izbrisali medpomnilnik gesel.
- Izhod Program.
Zelo poenostavljeno, koda C bi lahko izgledala nekako takole, brez preverjanja napak, z uporabo psevdonaključnih števil slabe kakovosti iz funkcije izvajalnega okolja C rand()
in ignoriranje kakršnih koli preverjanj prekoračitve medpomnilnika (tega nikoli ne počnite v pravi kodi!):
// 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);
Pravzaprav koda, ki smo jo končno uporabili v naših preizkusih, vključuje nekaj dodatnih bitov in delov, prikazanih spodaj, tako da smo lahko izpustili celotno vsebino našega začasnega medpomnilnika za gesla, ko smo ga uporabljali, da bi poiskali neželeno ali ostanke vsebine.
Upoštevajte, da namerno izpustimo medpomnilnik po klicu free()
, ki je tehnično napaka brez uporabe, vendar to počnemo tukaj kot prikrit način, da vidimo, ali po vrnitvi našega medpomnilnika ostane kaj kritičnega, kar bi lahko povzročilo nevarno luknjo uhajanja podatkov v resničnem življenju.
Vstavili smo tudi dva Waiting for [Enter]
pozove v kodo, da si damo priložnost ustvariti izpise pomnilnika na ključnih točkah v programu, kar nam daje neobdelane podatke za kasnejše iskanje, da vidimo, kaj je ostalo za seboj med izvajanjem programa.
Za izdelavo izpisov pomnilnika bomo uporabili Microsoft Orodje Sysinternals procdump
s -ma
možnost (izpusti ves pomnilnik), ki se izogne potrebi po pisanju lastne kode za uporabo sistema Windows DbgHelp
sistem in je precej zapleten MiniDumpXxxx()
funkcije.
Za prevajanje kode C smo uporabili lastno majhno in preprosto gradnjo brezplačne in odprtokodne različice Fabricea Bellarda Majhen prevajalnik C, na voljo za 64-bitni Windows v izvorni in dvojiški obliki neposredno z naše strani GitHub.
Besedilo, ki ga je mogoče kopirati in prilepiti, vse izvorne kode, prikazane na sliki v članku, se prikaže na dnu strani.
To se je zgodilo, ko smo prevedli in zagnali testni program:
C:UsersduckKEYPASS> petcc64 -stdinc -stdlib unl1.c Tiny C Compiler - Copyright (C) 2001-2023 Fabrice Bellard Odstranil Paul Ducklin za uporabo kot učno orodje Različica petcc64-0.9.27 [0006] - Generira 64-bitno Samo PE -> 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 -------- ----------------------- velikost datoteke virt razdelek 1000 200 438 .text 2000 800 2ac .data 3000 c00 24 .pdata -------- ---------------------- <- unl1.exe (3584 bajtov) C:UsersduckKEYPASS> unl1.exe Izpis "novega" medpomnilnika na začetku 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 6D 64 2E 65 78 65 00 44 steblo 32cmd. exe.D 00F513B0: 72 69 76 65 72 44 61 74 61 3D 43 3A 5C 57 69 6E riverData=C:Win 00F513C0: 64 6F 77 73 5C 53 79 73 74 65 6D 33 32 5C 44 72 dowsSystem32Dr 00F513D0: 69 76 65 72 73 5C 44 72 69 76 65 72 44 61 74 61 iversDriverData 00F513E0: 00 45 46 43 5F 34 33 37 32 3D 31 00 46 50 53 5F .EFC_4372=1.FPS_ 00F 513F0: 42 52 4F 57 53 45 52 5F 41 50 50 5F 50 52 4F 46 BROWSER_APP_PROF 00F51400: 49 4C 45 5F 53 54 52 49 4E 47 3D 49 6E 74 65 72 ILE_STRING=Inter 00F51410: 6E 65 74 20 45 78 70 6C 7A 56 F4 3C AC 4B 00 00 net ExplzV.< .K.. Celoten niz je bil: unlikelytextJHKNEJJCPOMDJHAN 00F51390: 75 6E 6C 69 6B 65 6C 79 74 65 78 74 4A 48 4B 4E unlikelytextJHKN 00F513A0: 45 4A 4A 43 50 4F 4 D 44 4A 48 41 4E 00 65 00 44 EJJCPOMDJHAN.eD 00F513B0 : 72 69 76 65 72 44 61 74 61 3D 43 3A 5C 57 69 6E riverData=C:Win 00F513C0: 64 6F 77 73 5C 53 79 73 74 65 6D 33 32 5C 44 72 dowsSystem 32Dr 00F513D0: 69 76 65 72 73 5C 44 72 69 76 65 72 44 61 74 61 iversDriverData 00F513E0: 00 45 46 43 5F 34 33 37 32 3D 31 00 46 50 53 5F .EFC_4372=1.FPS_ 00F513F0: 42 52 4F 57 53 45 52 5F 41 50 50 5F 50 52 4F 46 BROWSER_APP_PROF 00F51400: 49 4C 45 5F 53 54 52 49 4E 47 3D 49 6E 74 65 72 ILE_STRING=Inter 00F51410: 6E 65 74 20 45 78 70 6C 7A 56 F 4 3C AC 4B 00 00 net ExplzV.<.K.. Čakanje na [ENTER] za sprostitev medpomnilnika ... Izpis medpomnilnika po free() 00F51390: A0 67 F5 00 00 00 00 00 50 01 F5 00 00 00 00 00 .g......P...... 00F513A0: 45 4A 4A 43 50 4F 4D 44 4A 48 41 4E 00 65 00 44 EJJCPOMDJHAN.eD 00F513B0: 72 69 76 65 72 44 61 74 61 3D 43 3A 5C 57 69 6E riverData=C:Win 00F513C0: 64 6F 77 73 5C 53 79 73 74 65 6D 33 32 5C 44 72 dowsSystem32Dr 00F513D0: 69 76 65 72 73 5C 44 72 69 76 65 72 44 61 74 61 iversDriverData 00F513E0 : 00 45 46 43 5F 34 33 37 32 3D 31 00 46 50 53 5F .EFC_4372=1.FPS_ 00F513F0: 42 52 4F 57 53 45 52 5F 41 50 50 5F 50 52 4F 46 BROWSER_APP_PROF 00F51400: 49 4C 45 5F 53 54 52 49 4 E 47 3D 49 6E 74 65 72 ILE_STRING=Inter 00F51410: 6E 65 74 20 45 78 70 6C 4D 00 00 4D AC 4B 00 00 net ExplM..MK. Čakanje na [ENTER] za izhod iz main() ... C:UsersduckKEYPASS>
V tem zagonu se nismo trudili zagrabiti izpisov pomnilnika procesa, ker smo lahko iz izhoda takoj videli, da ta koda pušča podatke.
Takoj po klicu funkcije knjižnice izvajalnega okolja Windows C malloc()
, lahko vidimo, da vmesni pomnilnik, ki ga dobimo nazaj, vključuje tisto, kar je videti kot podatki spremenljivke okolja, ki so ostali od zagonske kode programa, pri čemer je prvih 16 bajtov očitno spremenjenih, da izgledajo kot nekakšna glava za dodelitev preostalega pomnilnika.
(Upoštevajte, kako je teh 16 bajtov videti kot dva 8-bajtna pomnilniška naslova, 0xF55790
in 0xF50150
, ki sta tik za in tik pred našim lastnim pomnilniškim medpomnilnikom.)
Ko naj bi bilo geslo v pomnilniku, lahko jasno vidimo celoten niz v medpomnilniku, kot bi pričakovali.
Toda po klicu free()
, upoštevajte, kako je bilo prvih 16 bajtov našega vmesnega pomnilnika znova zapisanih z nečim, kar je videti kot bližnji pomnilniški naslovi, verjetno zato, da lahko dodeljevalec pomnilnika sledi blokom v pomnilniku, ki jih lahko ponovno uporabi ...
… ampak ostalo besedilo našega »izbrisanega« gesla (zadnjih 12 naključnih znakov EJJCPOMDJHAN
) je zaostal.
Ne le, da moramo upravljati lastne dodelitve in de-dodeljevanja pomnilnika v C, zagotoviti moramo tudi, da izberemo prave sistemske funkcije za medpomnilnike podatkov, če jih želimo natančno nadzorovati.
Na primer, če namesto tega preklopimo na to kodo, dobimo malo več nadzora nad tem, kaj je v pomnilniku:
S prehodom iz malloc()
in free()
za uporabo funkcij dodeljevanja Windows na nižji ravni VirtualAlloc()
in VirtualFree()
neposredno, dobimo boljši nadzor.
Vendar pa plačamo ceno hitrosti, saj vsak klic na VirtualAlloc()
opravi več dela kot klic malloc()
, ki deluje tako, da nenehno deli in deli blok vnaprej dodeljenega pomnilnika nizke ravni.
Uporaba VirtualAlloc()
večkrat za majhne bloke porabi tudi več pomnilnika na splošno, ker je vsak blok razdeljen na VirtualAlloc()
običajno porabi večkratnik 4KB pomnilnika (ali 2MB, če uporabljate t.i velike pomnilniške strani), tako da je naš 128-bajtni vmesni pomnilnik zgoraj zaokrožen na 4096 bajtov, kar zapravi 3968 bajtov na koncu pomnilniškega bloka 4 KB.
Toda, kot lahko vidite, je pomnilnik, ki ga dobimo nazaj, samodejno izpraznjen (nastavljen na nič), tako da ne moremo videti, kaj je bilo tam prej, in tokrat se program zruši, ko poskušamo izvesti našo uporabo po brezplačnem trik, ker Windows zazna, da poskušamo pokukati v pomnilnik, ki ga nimamo več:
C:UsersduckKEYPASS> unl2 Izpis 'novega' medpomnilnika na začetku 0000000000EA0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .. .............. 0000000000EA0030: 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 00 00 00 ................ 0000000000EA0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 ................ Celoten niz je bil: unlikelytextIBIPJPPHEOPOIDLL 0000EA75: 6 6E 69C 6 65B 6 79C 74 65 78 74 49 42 49 50 0000000000 unlikelytextIBIP 0010EA4: 50A 50 48 45 4 50F 4 49f 44 4 4c 00c 00 00 00 0000000000 jppheopoidll .... 0020EA00: 00 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 0000000000 0040 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0050 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0060 00 00 ............... 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0070 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0080 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0000 XNUMX XNUMX ............. ... Čakanje na [ENTER] za sprostitev medpomnilnika ... Izpis medpomnilnika po free() XNUMXEAXNUMX: [Program je tukaj prekinjen, ker je Windows ujel našo uporabo po brezplačnem]
Ker bo treba pomnilnik, ki smo ga sprostili, ponovno dodeliti VirtualAlloc()
Preden ga lahko ponovno uporabimo, lahko predpostavimo, da bo izničen, preden bo recikliran.
Če pa bi se želeli prepričati, da je izpraznjen, bi lahko poklicali posebno funkcijo Windows RtlSecureZeroMemory()
tik preden ga sprostimo, da zagotovimo, da bo Windows najprej zapisal ničle v naš medpomnilnik.
Povezana funkcija RtlZeroMemory()
, če ste se spraševali, naredi podobno, vendar brez zagotovila, da dejansko deluje, ker ga lahko prevajalniki odstranijo kot teoretično odvečnega, če opazijo, da se vmesni pomnilnik pozneje ne uporablja več.
Kot lahko vidite, moramo zelo paziti na uporabo pravih funkcij sistema Windows, če želimo čim bolj skrajšati čas, ko bodo skrivnosti, shranjene v pomnilniku, morda ležale za pozneje.
V tem članku ne bomo preučevali, kako preprečite, da bi se skrivnosti pomotoma shranile v vašo izmenjalno datoteko, tako da jih zaklenete v fizični RAM. (Namig: VirtualLock()
sama po sebi pravzaprav ni dovolj.) Če bi radi izvedeli več o nizki ravni varnosti pomnilnika Windows, nam to sporočite v komentarjih in to bomo preučili v prihodnjem članku.
Uporaba samodejnega upravljanja pomnilnika
En dober način, da se izognemo temu, da bi morali sami dodeljevati, upravljati in sprostiti pomnilnik, je uporaba programskega jezika, ki skrbi za malloc()
in free()
ali VirtualAlloc()
in VirtualFree()
, samodejno.
Skriptni jezik, kot je npr Perl, Python, Lua, JavaScript in drugi se znebijo najpogostejših napak pri varnosti pomnilnika, ki pestijo kodo C in C++, tako da namesto vas v ozadju spremljajo uporabo pomnilnika.
Kot smo že omenili, naša zgoraj slabo napisana vzorčna koda C zdaj deluje dobro, vendar le zato, ker je še vedno izjemno preprost program s podatkovnimi strukturami fiksne velikosti, kjer lahko s pregledom preverimo, da ne bomo prepisali našega 128- medpomnilnik bajtov in da obstaja samo ena izvršilna pot, ki se začne z malloc()
in se konča z ustreznim free()
.
Toda če bi ga posodobili tako, da bi omogočilo ustvarjanje gesel s spremenljivo dolžino, ali dodali dodatne funkcije v proces ustvarjanja, potem bi mi (ali tisti, ki bo naslednji vzdrževal kodo) lahko zlahka končali s prelivi medpomnilnika, napakami, ki se nanašajo na uporabo, potem ko so proste, ali pomnilniku, ki se nikoli ne sprosti in zato pusti skrivne podatke naokoli še dolgo potem, ko niso več potrebni.
V jeziku, kot je Lua, lahko pustimo izvajalnemu okolju Lua, ki počne tisto, kar je v žargonu znano kot samodejno zbiranje smeti, se ukvarjajo s pridobivanjem pomnilnika iz sistema in vračanjem, ko zazna, da smo ga prenehali uporabljati.
Program C, ki smo ga navedli zgoraj, postane zelo preprostejši, če namesto nas poskrbimo za dodeljevanje in de-alokacijo pomnilnika:
Dodelimo pomnilnik za shranjevanje niza s
preprosto z dodelitvijo niza 'unlikelytext'
z njo.
Kasneje lahko Lui izrecno namignemo, da nas ne zanima več s
tako da mu dodelite vrednost nil
(vse nils
so v bistvu isti predmet Lua), ali prenehajte uporabljati s
in počakajte, da Lua zazna, da ni več potreben.
Kakor koli že, pomnilnik, ki ga uporablja s
bodo sčasoma samodejno obnovljene.
In da preprečite prelivanje medpomnilnika ali napačno upravljanje velikosti pri dodajanju besedilnim nizom (operator Lua ..
, izrečeno concat, v bistvu sešteje dva niza skupaj, npr +
v Pythonu), vsakič, ko podaljšamo ali skrajšamo niz, Lua čarobno dodeli prostor za popolnoma nov niz, namesto da bi spremenil ali zamenjal prvotnega na njegovi obstoječi pomnilniški lokaciji.
Ta pristop je počasnejši in vodi do konic porabe pomnilnika, ki so višji, kot bi jih dobili v C zaradi vmesnih nizov, dodeljenih med manipulacijo besedila, vendar je veliko varnejši v zvezi s prelivanjem medpomnilnika.
Toda tovrstno samodejno upravljanje nizov (v žargonu znano kot nespremenljivost, ker strune nikoli ne dobijo mutiran, ali spremenjeni na mestu, ko so že bili ustvarjeni), prinaša nove glavobole kibernetske varnosti.
Zgornji program Lua smo zagnali v sistemu Windows do druge pavze, tik preden je program zaprl:
C:UsersduckKEYPASS> lua s1.lua Celoten niz je: unlikelytextHLKONBOJILAGLNLN Čakanje na [ENTER] pred sprostitvijo niza ... Čakanje na [ENTER] pred izhodom ...
Tokrat smo naredili izpis pomnilnika procesa, kot je ta:
C:UsersduckKEYPASS> procdump -ma lua lua-s1.dmp ProcDump v11.0 - pripomoček za izpis izpisa procesa Sysinternals Copyright (C) 2009-2022 Mark Russinovich in Andrew Richards Sysinternals - www.sysinternals.com [00:00:00] Izmet 1 sproženo: C:UsersduckKEYPASSlua-s1.dmp [00:00:00] Pisanje izpisa 1: Ocenjena velikost datoteke izpisa je 10 MB. [00:00:00] Izvoz 1 končan: 10 MB zapisanih v 0.1 sekunde [00:00:01] Doseženo število izpisov.
Nato smo zagnali ta preprost skript, ki prebere datoteko izpisa nazaj in povsod v pomnilniku najde tisti znani niz unlikelytext
se prikaže in ga natisne skupaj z lokacijo v datoteki izpisa in znaki ASCII, ki sledijo takoj:
Tudi če ste že uporabljali skriptne jezike ali delali v katerem koli programskem ekosistemu, ki vključuje t.i. upravljani nizi, kjer sistem sledi dodelitvam in sprostitvam pomnilnika namesto vas in jih obravnava, kot se mu zdi primerno ...
... boste morda presenečeni, ko boste videli rezultat, ki ga ustvari to skeniranje pomnilnika:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp 006D8AFC: unlikelytextALJBNGOAPLLBDEB 006D8B3C: unlikelytextALJBNGOA 006D8B7C: unlikelytextALJBNGO 006D8BFC: unlikelytextALJBNGOAPLLBDEBJ 006D8CBC: unlike lytextALJBN 006D8D7C: malo verjetno besediloALJBNGOAP 006D903C: malo verjetno besediloALJBNGOAPL 006D90BC: malo verjetno besediloALJBNGOAPLL 006D90FC: malo verjetno besediloALJBNG 006D913C: malo verjetno besediloALJBNGOAPLLB 006D91BC: malo verjetno besediloALJB 006D91FC: malo verjetno besediloALJBNGOAPLLBD 006D923C : maloverjetnobesediloALJBNGOAPLLBDE 006DB70C: maloverjetnobesediloALJ 006DBB8C: maloverjetnobesediloAL 006DBD0C: maloverjetnobesediloA
Glej in glej, takrat smo zagrabili izpis spomina, čeprav smo končali z nizom s
(in rekel Lui, da tega ne potrebujemo več, tako da je rekel s = nil
), vsi nizi, ki jih je koda ustvarila med potjo, so bili še vedno prisotni v RAM-u, še niso bili obnovljeni ali izbrisani.
Če zgornji izhod razvrstimo po samih nizih, namesto da sledimo vrstnemu redu, v katerem so se pojavili v RAM-u, si boste lahko predstavljali, kaj se je zgodilo med zanko, kjer smo en znak naenkrat povezovali v naš niz gesla:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp | razvrsti /+10 006DBD0C: unlikelytextA 006DBB8C: unlikelytextAL 006DB70C: unlikelytextALJ 006D91BC: unlikelytextALJB 006D8CBC: unlikelytextALJBN 006D90FC: unlikelytextALJBNG 006D8B7C: unlikelytextALJBNGO 006D 8B3C: unlikelytextALJBNGOA 006D8D7C: unlikelytextALJBNGOAP 006D903C: unlikelytextALJBNGOAPL 006D90BC: unlikelytextALJBNGOAPLL 006D913C: unlikelytextALJBNGOAPLLB 006D91FC: unlikelytextALJBNGOAPLLBD 006 D923C: malo verjetno besedilo ALJBNGOAPLLBDE 006D8AFC: malo verjetno besedilo ALJBNGOAPLLBDEB 006D8BFC : malo verjetnobesediloALJBNGOAPLLBDEBJ
Vsi ti začasni, vmesni nizi so še vedno tam, torej tudi če bi uspešno izbrisali končno vrednost s
, bi še vedno razkrili vse razen zadnjega lika.
Pravzaprav v tem primeru, tudi ko smo namenoma prisilili naš program, da odstrani vse nepotrebne podatke s klicem posebne funkcije Lua collectgarbage()
(večina skriptnih jezikov ima nekaj podobnega), je večina podatkov v teh nadležnih začasnih nizih tako ali tako obtičala v RAM-u, ker smo prevedli Luo za samodejno upravljanje pomnilnika z dobrim starim malloc()
in free()
.
Z drugimi besedami, tudi potem, ko je Lua sama pridobila svoje začasne pomnilniške bloke, da bi jih znova uporabila, nismo mogli nadzorovati, kako ali kdaj bodo ti pomnilniški bloki ponovno uporabljeni, in s tem, kako dolgo bodo ležali v procesu z levo nad podatki, ki čakajo, da jih prevohajo, odvržejo ali kako drugače razkrijejo.
Vnesite .NET
Kaj pa KeePass, kjer se je začel ta članek?
KeePass je napisan v C# in uporablja izvajalno okolje .NET, tako da se izogne težavam slabega upravljanja s pomnilnikom, ki jih programi C prinašajo s seboj ...
… vendar C# upravlja lastne besedilne nize, podobno kot Lua, kar postavlja vprašanje:
Tudi če bi se programer izognil shranjevanju celotnega glavnega gesla na enem mestu, potem ko je končal z njim, bi lahko napadalci z dostopom do izpisa pomnilnika kljub temu našli dovolj preostalih začasnih podatkov, da bi lahko ugibali ali vseeno obnovili glavno geslo, tudi če ti so napadalci dobili dostop do vašega računalnika nekaj minut, ur ali dni po tem, ko ste vnesli geslo?
Preprosto povedano, ali obstajajo zaznavni, srhljivi ostanki vašega glavnega gesla, ki preživijo v RAM-u, tudi potem ko bi pričakovali, da so bili izbrisani?
Moteče kot uporabnik Githuba Vdohney odkril, je odgovor (vsaj za različice KeePass, starejše od 2.54) "Da."
Da bo jasno, mislimo, da vašega dejanskega glavnega gesla ni mogoče obnoviti kot en besedilni niz iz pomnilnika pomnilnika KeePass, ker je avtor ustvaril posebno funkcijo za vnos glavnega gesla, ki se zelo potrudi, da prepreči shranjevanje celotnega geslo, kjer bi ga zlahka opazili in izvohali.
O tem smo se prepričali tako, da smo glavno geslo nastavili na SIXTEENPASSCHARS
, vtipkajte in nato takoj, kmalu in dolgo zatem naredite izpise pomnilnika.
Odlagališča smo preiskali s preprostim skriptom Lua, ki je povsod iskal to besedilo gesla, tako v 8-bitnem formatu ASCII kot v 16-bitnem formatu UTF-16 (Windows widechar), kot je ta:
Rezultati so bili spodbudni:
C:UsersduckKEYPASS> lua searchknown.lua kp2-post.dmp Branje v datoteki izpisa ... KONČANO. Iskanje SIXTEENPASSCHARS kot 8-bitni ASCII ... ni bilo mogoče najti. Iskanje SIXTEENPASSCHARS kot UTF-16 ... ni bilo mogoče najti.
Toda Vdohney, odkritelj CVE-2023-32784, je opazil, da vam KeePass med vnašanjem glavnega gesla daje vizualno povratno informacijo s sestavo in prikazom nadomestnega niza, sestavljenega iz znakov Unicode »blob«, do in vključno z dolžino vašega geslo:
V besedilnih nizih s širokim znakom v sistemu Windows (ki so sestavljeni iz dveh bajtov na znak, ne samo enega bajta kot v ASCII), je znak »blob« kodiran v RAM-u kot šestnajstiški bajt 0xCF
čemur sledi 0x25
(kar je po naključju znak za odstotek v ASCII).
Torej, tudi če KeePass zelo pazi na neobdelane znake, ki jih vnesete, ko vnesete samo geslo, lahko na koncu ostanejo nizi znakov »blob«, ki jih je zlahka zaznati v pomnilniku, ko se ponavljajo, kot je npr. CF25CF25
or CF25CF25CF25
...
… in če je tako, bi najdaljši niz znakov, ki ste jih našli, verjetno izdal dolžino vašega gesla, kar bi bila skromna oblika uhajanja informacij o geslu, če nič drugega.
Uporabili smo naslednji skript Lua za iskanje znakov preostalih nizov nadomestnih znakov za geslo:
Rezultat je bil presenetljiv (izbrisali smo zaporedne vrstice z enakim številom madežev ali z manj madeži kot v prejšnji vrstici, da smo prihranili prostor):
C:UsersduckKEYPASS> lua findblobs.lua kp2-post.dmp 000EFF3C: * [. . .] 00BE621B: ** 00BE64C7: *** [. . .] 00BE6E8F: **** [. . .] 00BE795F: ***** [. . .] 00BE84F7: ****** [. . .] 00BE8F37: ******* [ se nadaljuje podobno za 8 blobov, 9 blobov itd. ] [ do dveh zadnjih vrstic po natanko 16 blobov v vsaki ] 00C0503B: ************* *** 00C05077: **************** 00C09337: * 00C09738: * [vsa preostala ujemanja so dolga en blok] 0123B058: *
Na tesno skupaj, vendar vedno večjih pomnilniških naslovih smo našli sistematičen seznam 3 blobov, nato 4 blobov in tako naprej do 16 blobov (dolžina našega gesla), čemur je sledilo veliko naključno razpršenih primerkov nizov z enim blobom. .
Torej se zdi, da ti nadomestni nizi "blob" res uhajajo v pomnilnik in ostajajo zadaj, da uhaja dolžina gesla, dolgo potem, ko je programska oprema KeePass končala z vašim glavnim geslom.
Naslednji korak
Odločili smo se, da bomo kopali naprej, tako kot je naredil Vdohney.
Spremenili smo našo kodo za ujemanje vzorcev, da bi zaznali verige blob znakov, ki jim sledi kateri koli posamezen znak ASCII v 16-bitni obliki (znaki ASCII so predstavljeni v UTF-16 kot njihova običajna 8-bitna koda ASCII, ki ji sledi ničelni bajt).
Tokrat smo, da bi prihranili prostor, zavrnili izpis za vsako ujemanje, ki se natančno ujema s prejšnjim:
Presenečenje, presenečenje:
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
Poglejte, kaj imamo od območja upravljanega pomnilnika nizov .NET!
Tesno združen nabor začasnih "nizov blob", ki razkrijejo zaporedne znake v našem geslu, začenši z drugim znakom.
Tem puščajočim nizom sledijo široko razširjena ujemanja enega znaka, za katera predvidevamo, da so nastala po naključju. (Datoteka izpisa KeePass je velika približno 250 MB, tako da je dovolj prostora za znake »blob«, ki se prikažejo kot po sreči.)
Tudi če upoštevamo ta dodatna štiri ujemanja, namesto da jih zavržemo kot verjetna neujemanja, lahko ugibamo, da je glavno geslo eno od:
?IXTEENPASSCHARS ?NXTEENPASSCHARS ?WXTEENPASSCHARS ?SXTEENPASSCHARS
Očitno je, da ta preprosta tehnika ne najde prvega znaka v geslu, ker je prvi "niz blob" sestavljen šele po vnosu tega prvega znaka
Upoštevajte, da je ta seznam lep in kratek, ker smo izločili ujemanja, ki se niso končala z znaki ASCII.
Če ste iskali znake v drugem obsegu, kot so kitajski ali korejski znaki, boste morda na koncu dobili več nenamernih zadetkov, ker obstaja veliko več možnih znakov za ujemanje na ...
... vendar sumimo, da se boste vseeno precej približali svojemu glavnemu geslu in zdi se, da so »nizi blob«, ki se nanašajo na geslo, združeni v RAM-u, verjetno zato, ker jih je približno ob istem času dodelil isti del izvajalnega okolja .NET.
In tam je, resda dolga in diskurzivna, fascinantna zgodba o CVE-2023-32784.
Kaj storiti?
- Če ste uporabnik KeePass, brez panike. Čeprav je to hrošč in je tehnično ranljivost, ki jo je mogoče izkoristiti, bi morali oddaljeni napadalci, ki bi s tem hroščem želeli razbiti vaše geslo, najprej namestiti zlonamerno programsko opremo v vaš računalnik. To bi jim omogočilo številne druge načine za neposredno krajo vaših gesel, tudi če ta hrošč ne bi obstajal, na primer z beleženjem vaših pritiskov tipk med tipkanjem. Na tej točki lahko preprosto pazite na prihajajočo posodobitev in jo zgrabite, ko bo pripravljena.
- Če ne uporabljate šifriranja celotnega diska, ga omogočite. Če želite pridobiti preostala gesla iz izmenjevalne datoteke ali datoteke mirovanja (datoteke diska operacijskega sistema, ki se uporabljajo za začasno shranjevanje vsebine pomnilnika med veliko obremenitvijo ali ko vaš računalnik »spi«), bi napadalci potrebovali neposreden dostop do vašega trdega diska. Če imate aktiviran BitLocker ali njegov ekvivalent za druge operacijske sisteme, ne bodo mogli dostopati do izmenjalne datoteke, datoteke za mirovanje ali drugih osebnih podatkov, kot so dokumenti, preglednice, shranjena e-pošta itd.
- Če ste programer, bodite obveščeni o težavah pri upravljanju pomnilnika. Ne domnevajte, da samo zato, ker vsak
free()
se ujema z ustreznimmalloc()
da so vaši podatki varni in dobro upravljani. Včasih boste morda morali sprejeti dodatne previdnostne ukrepe, da preprečite, da bi skrivni podatki ležali naokoli, in te previdnostne ukrepe zelo razlikujejo od operacijskega sistema do operacijskega sistema. - Če ste preizkuševalec kakovosti ali pregledovalec kode, vedno razmišljajte »za kulisami«. Tudi če je koda za upravljanje pomnilnika videti urejena in dobro uravnotežena, se zavedajte, kaj se dogaja v zakulisju (ker prvotni programer tega morda ni vedel) in se pripravite na nekaj dela v slogu pentestinga, kot je spremljanje izvajalnega časa in pomnilnika damping, da preverite, ali se varna koda res obnaša, kot bi se morala.
ŠIFRA IZ ČLANKA: UNL1.C
#vključi #vključi #vključi void hexdump(unsigned char* buff, int len) { // Medpomnilnik za tiskanje v 16-bajtnih delih za (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Prikaži 16 bajtov kot šestnajstiške vrednosti za (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Ponovi teh 16 bajtov kot znake za (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) { // Pridobi pomnilnik za shranjevanje gesla in pokaže, kaj // je v medpomnilniku, ko je uradno "novo"... char* buff = malloc(128); printf("Izpis 'novega' medpomnilnika ob zagonu"); hexdump(buff,128); // Uporabi psevdonaključni naslov medpomnilnika kot naključno seme srand((unsigned)buff); // Geslo začnite s fiksnim besedilom, ki ga je mogoče iskati strcpy(buff,"unlikelytext"); // Pripni 16 psevdonaključnih črk, eno za drugo za (int i = 1; i <= 16; i++) { // Izberite črko od A (65+0) do P (65+15) char ch = 65 + (rand() & 15); // Nato spremenite buff niz na mestu strncat(buff,&ch,1); } // Celotno geslo je zdaj v pomnilniku, zato ga // natisni kot niz in prikaži celoten medpomnilnik... printf("Celoten niz je bil: %sn",buff); hexdump(buff,128); // Premor za izpis RAM-a procesa zdaj (poskusite: 'procdump -ma') puts("Čakanje [ENTER] za sprostitev medpomnilnika ..."); getchar(); // Formalno osvobodi() pomnilnik in ponovno prikaži medpomnilnik // da vidiš, če je kaj ostalo zadaj... free(buff); printf("Izpis medpomnilnika po free()n"); hexdump(buff,128); // Premor za ponovni izpis RAM-a za pregled razlik puts("Čakanje [ENTER] za izhod iz main()..."); getchar(); vrni 0; }
ŠIFRA IZ ČLANKA: UNL2.C
#vključi #vključi #vključi #vključi void hexdump(unsigned char* buff, int len) { // Medpomnilnik za tiskanje v 16-bajtnih delih za (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Prikaži 16 bajtov kot šestnajstiške vrednosti za (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Ponovi teh 16 bajtov kot znake za (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) { // Pridobi pomnilnik za shranjevanje gesla in pokaže, kaj // je v medpomnilniku, ko je uradno "novo"... char* buff = VirtualAlloc(0,128,MEM_COMMIT,PAGE_READWRITE); printf("Izpis 'novega' medpomnilnika ob zagonu"); hexdump(buff,128); // Uporabi psevdonaključni naslov medpomnilnika kot naključno seme srand((unsigned)buff); // Geslo začnite s fiksnim besedilom, ki ga je mogoče iskati strcpy(buff,"unlikelytext"); // Pripni 16 psevdonaključnih črk, eno za drugo za (int i = 1; i <= 16; i++) { // Izberite črko od A (65+0) do P (65+15) char ch = 65 + (rand() & 15); // Nato spremenite buff niz na mestu strncat(buff,&ch,1); } // Celotno geslo je zdaj v pomnilniku, zato ga // natisni kot niz in prikaži celoten medpomnilnik... printf("Celoten niz je bil: %sn",buff); hexdump(buff,128); // Premor za izpis RAM-a procesa zdaj (poskusite: 'procdump -ma') puts("Čakanje [ENTER] za sprostitev medpomnilnika ..."); getchar(); // Formalno osvobodi() pomnilnik in ponovno prikaži medpomnilnik // da vidiš, če je kaj ostalo ... VirtualFree(buff,0,MEM_RELEASE); printf("Izpis medpomnilnika po free()n"); hexdump(buff,128); // Premor za ponovni izpis RAM-a za pregled razlik puts("Čakanje [ENTER] za izhod iz main()..."); getchar(); vrni 0; }
ŠIFRA IZ ČLANKA: S1.LUA
-- Začnite s fiksnim besedilom, ki ga je mogoče iskati s = 'unlikelytext' -- Dodajte 16 naključnih znakov od 'A' do 'P' za i = 1,16 do s = s .. string.char(65+math.random( 0,15)) end print('Celoten niz je:',s,'n') -- Začasna zaustavitev za izpis RAM-a procesa print('Čakanje na [ENTER] pred sprostitvijo niza...') io.read() - - Obrišite niz in označite spremenljivko kot neuporabljeno s = nil -- Ponovno izpustite RAM, da poiščete razlike print('Čakanje na [ENTER] pred izhodom ...') io.read()
KODA IZ ČLANKA: FINDIT.LUA
-- branje v datoteki izpisa lokalnega f = io.open(arg[1],'rb'):read('*a') -- poiščite besedilo označevalca, ki mu sledi en -- ali več naključnih znakov ASCII lokalno b,e ,m = 0,0,nil while true do -- poiščite naslednje ujemanje in si zapomnite odmik b,e,m = f:find('(unlikelytext[AZ]+)',e+1) -- izhod, ko ni več ujema se, če ne b, nato prelomi konec -- sporoči položaj in najden niz print(string.format('%08X: %s',b,m)) konec
KODA IZ ČLANKA: SEARCHKNOWN.LUA
io.write('Branje v datoteki izpisa ... ') local f = io.open(arg[1],'rb'):read('*a') io.write('DONE.n') io. write('Iskanje SIXTEENPASSCHARS kot 8-bitni ASCII ... ') local p08 = f:find('SIXTEENPASSCHARS') io.write(p08 in 'FOUND' ali 'not found','.n') io.write ('Iskanje SIXTEENPASSCHARS kot UTF-16 ... ') local p16 = f:find('Sx00Ix00Xx00Tx00Ex00Ex00Nx00Px00'.. 'Ax00Sx00Sx00Cx00Hx00Ax00Rx00Sx00') io.write(p16 in 'FOUND' ali 'not found ','.n')
KODA IZ ČLANKA: FINDBLOBS.LUA
-- branje v datoteki izpisa, podani v ukazni vrstici local f = io.open(arg[1],'rb'):read('*a') -- Poiščite enega ali več gesel, ki jim sledi kateri koli ne-blob -- Upoštevajte, da se blob-znaki (●) kodirajo v široke znake Windows -- kot kode UTF-16 z litte-endianom, ki izhajajo kot CF 25 v hex. local b,e,m = 0,0,nil while true do -- Želimo enega ali več blobov, ki jim sledi kateri koli ne-blob. -- Kodo poenostavimo tako, da poiščemo eksplicitni CF25 -- ki mu sledi poljuben niz, ki vsebuje samo CF ali 25 -- tako da bomo našli CF25CFCF ali CF2525CF kot tudi CF25CF25. -- Kasneje bomo filtrirali "lažne pozitivne rezultate", če bodo. -- Namesto x25 moramo napisati '%%', ker je znak x25 -- (znak za odstotek) poseben iskalni znak v Lui! b,e,m = f:find('(xCF%%[xCF%%]*)',e+1) -- izhod, ko ni več ujemanj, če ne b potem prekini konec -- CMD.EXE ne more tiskati blobs, zato jih pretvorimo v zvezde. print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) konec
KODA IZ ČLANKA: SEARCHKP.LUA
-- branje v datoteki izpisa, podani v ukazni vrstici local f = io.open(arg[1],'rb'):read('*a') local b,e,m,p = 0,0,nil,nil medtem ko je true do -- Zdaj želimo enega ali več blobov (CF25), ki mu sledi koda -- za A..Z, ki mu sledi 0 bajtov za pretvorbo ACSCII v UTF-16 b,e,m = f:find(' (xCF%%[xCF%%]*[AZ])x00',e+1) -- izhod, ko ni več ujemanj, če ni b, nato prekinitev konca -- CMD.EXE ne more natisniti blobov, zato jih pretvorimo v zvezde. -- Da prihranimo prostor, zavrnemo zaporedna ujemanja, če m ~= p, potem print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) p = m konec konec
- Distribucija vsebine in PR s pomočjo SEO. Okrepite se še danes.
- PlatoAiStream. Podatkovna inteligenca Web3. Razširjeno znanje. Dostopite tukaj.
- Kovanje prihodnosti z Adryenn Ashley. Dostopite tukaj.
- Kupujte in prodajajte delnice podjetij pred IPO s PREIPO®. Dostopite tukaj.
- vir: https://nakedsecurity.sophos.com/2023/05/31/serious-security-that-keepass-master-password-crack-and-what-we-can-learn-from-it/
- :ima
- : je
- :ne
- :kje
- ][str
- $GOR
- 1
- 10
- 12
- 15%
- 20
- 200
- 2023
- 24
- 250
- 27
- 31
- 3d
- 49
- 50
- 67
- 70
- 72
- 77
- 8
- 9
- a
- Sposobna
- O meni
- nad
- absolutna
- AC
- dostop
- Račun
- pridobiti
- pridobitev
- aktivna
- dejanska
- dejansko
- dodano
- Dodatne
- Naslov
- naslovi
- Dodaja
- po
- potem
- spet
- vsi
- dodeljenih
- dodeli
- dodelitev
- dodelitve
- omogočajo
- sam
- skupaj
- že
- Prav tako
- spremenila
- Čeprav
- vedno
- an
- in
- Andrew
- odgovor
- kaj
- karkoli
- karkoli kritičnega
- zdi
- pojavil
- pristop
- odobren
- SE
- okoli
- članek
- članki
- AS
- At
- Avtor
- avto
- Samodejno
- samodejno
- Na voljo
- izogniti
- izognili
- zaveda
- stran
- nazaj
- ozadje
- ozadja, slike
- BE
- ker
- postane
- bilo
- pred
- Začetek
- zadaj
- v zakulisju
- spodaj
- Boljše
- Bit
- Block
- Bloki
- meja
- tako
- Bottom
- blagovne znamke
- Brand New
- Break
- Na kratko
- prinašajo
- varovalni
- prelivanje medpomnilnika
- Bug
- hrošči
- izgradnjo
- vendar
- by
- C + +
- klic
- kliče
- CAN
- Lahko dobiš
- ki
- primeru
- ujete
- CD
- center
- Zagotovo
- verige
- priložnost
- spremenilo
- značaja
- znaki
- preverjanje
- Pregledi
- kitajski
- Izberite
- jasno
- jasno
- Zapri
- Koda
- barva
- COM
- prihaja
- prihajajo
- komentar
- komentarji
- Skupno
- dokončanje
- kompleksna
- računalnik
- Razmislite
- velika
- šteje
- Sestavljeno
- gradnjo
- vsebina
- Vsebina
- stalno
- se nadaljuje
- nadzor
- pretvorbo
- avtorske pravice
- Ustrezno
- bi
- pokrov
- tresk
- ustvarjajo
- ustvaril
- kreator
- kritično
- Cybersecurity
- NEVARNOST
- Nevarno
- datum
- uhajanje podatkov
- Dnevi
- ponudba
- odločil
- namenjen
- opisano
- DID
- razlike
- drugačen
- Težavnost
- DIG
- digitalni
- neposredna
- Neposreden dostop
- neposredno
- zaslon
- prikazovanje
- je
- do
- Dokumenti
- ne
- Ne
- tem
- opravljeno
- dont
- navzdol
- pogon
- 2
- smetišče
- med
- e
- vsak
- prej
- enostavno
- ekosistem
- bodisi
- ostalo
- e-pošta
- omogočanje
- spodbujanje
- šifriranje
- konec
- konča
- dovolj
- zagotovitev
- zagotoviti
- Vnesite
- V
- Celotna
- Vpis
- okolje
- Enakovredna
- Napaka
- v bistvu
- ocenjeni
- itd
- Eter (ETH)
- Tudi
- sčasoma
- vedno večja
- Tudi vsak
- vse
- točno
- Primer
- Razen
- Vznemirjenje
- izvedba
- obstajajo
- obstoječih
- Izhod
- Izhod
- pričakovati
- Pojasnite
- Izkoristite
- izpostavljena
- razširiti
- dodatna
- ekstrakt
- Dejstvo
- false
- zanimivo
- Lastnosti
- povratne informacije
- manj
- boju proti
- file
- datoteke
- filter
- končna
- končno
- Najdi
- iskanje
- najdbe
- konec
- prva
- Všita
- Osredotočite
- sledili
- po
- za
- obrazec
- Formalno
- format
- prihajajoči
- je pokazala,
- štiri
- brezplačno
- iz
- polno
- v celoti
- funkcija
- funkcije
- nadalje
- Prihodnost
- ustvarja
- generacija
- dobili
- pridobivanje
- GitHub
- Daj
- dana
- daje
- Giving
- Go
- goes
- dogaja
- dobro
- vlada
- zgrabi
- veliko
- Garancija
- imel
- Ročaji
- se je zgodilo
- Zgodi se
- se zgodi
- Trdi
- Imajo
- ob
- glavoboli
- težka
- višina
- tukaj
- HEX
- na visoki ravni
- več
- Hits
- držite
- Luknja
- upam,
- URE
- hover
- Kako
- Kako
- HTTPS
- Lov
- i
- identifikator
- if
- takoj
- Pomembno
- in
- vključuje
- Vključno
- Podatki
- obvestila
- Namesto
- zainteresirani
- Vmesna
- Internet
- v
- Vprašanja
- IT
- ITS
- sam
- žargon
- junij
- samo
- samo en
- Imejte
- Ključne
- Vedite
- znano
- Korejski
- jezik
- jeziki
- laptop
- Zadnja
- pozneje
- vodi
- Interesenti
- uhajanje
- puščanje
- UČITE
- učenje
- vsaj
- odhodu
- levo
- dolžina
- pismo
- Knjižnica
- življenje
- kot
- Verjeten
- Limited
- vrstica
- linije
- Seznam
- Navedeno
- ll
- obremenitev
- lokalna
- kraj aktivnosti
- sečnja
- Long
- dolgoročna
- več
- Poglej
- izgleda kot
- Pogledal
- si
- POGLEDI
- Sklop
- sreča
- vzdržuje
- Znamka
- zlonamerna programska oprema
- upravljanje
- upravlja
- upravljanje
- upravitelj
- upravlja
- Manipulacija
- več
- Marža
- znamka
- marker
- mojster
- Stave
- ujemanje
- max širine
- Maj ..
- pomeni
- Spomin
- omenjeno
- Microsoft
- morda
- min
- skromen
- spremembe
- spremenite
- Trenutek
- spremljanje
- več
- Najbolj
- veliko
- več
- Čisto
- Nimate
- potrebna
- net
- nikoli
- Kljub temu
- Novo
- novice
- Naslednja
- lepo
- št
- normalno
- nič
- Opaziti..
- zdaj
- Številka
- številke
- predmet
- Očitna
- of
- off
- Uradni
- Uradno
- odmik
- Staro
- on
- enkrat
- ONE
- samo
- open source
- deluje
- operacijski sistem
- Operacijski sistemi
- operater
- Možnost
- or
- Da
- izvirno
- Ostalo
- drugi
- drugače
- naši
- sami
- ven
- izhod
- več
- Splošni
- lastne
- Stran
- Panic
- del
- Geslo
- vodja geslo
- gesla
- pot
- Vzorec
- paul
- pavza
- Plačajte
- odstotkov
- mogoče
- Obdobje
- trajno
- Osebni
- osebni podatki
- fizično
- slika
- kosov
- Kraj
- placeholder
- Kuga
- platon
- Platonova podatkovna inteligenca
- PlatoData
- Veliko
- Točka
- točke
- Popular
- Stališče
- mogoče
- Prispevkov
- potencial
- Ravno
- predstaviti
- precej
- preprečiti
- prejšnja
- Cena
- Tiskanje
- printi
- verjetno
- Težave
- Postopek
- Program
- Programmer
- Programerji
- Programiranje
- programi
- izrazit
- dal
- Python
- Vprašanja in odgovori
- vprašanje
- povečuje
- RAM
- naključno
- območje
- precej
- Surovi
- surovi podatki
- RE
- dosegel
- Preberi
- reading
- pripravljen
- pravo
- resnično življenje
- v realnem času
- res
- prepoznati
- Obnovi
- opomore
- povezane
- Preostalih
- ne pozabite
- daljinsko
- odstrani
- ponovite
- ponovi
- PONOVNO
- poročilo
- zastopan
- spoštovanje
- oziroma
- REST
- Rezultati
- vrnitev
- vrnitev
- razkrivajo
- znebi
- Pravica
- Tveganje
- tveganja
- soba
- Run
- tek
- spremljanje časa delovanja
- s
- varna
- varnejši
- Enako
- zadovoljni
- Shrani
- rek
- skeniranje
- razpršene
- prizori
- Iskalnik
- iskanje
- drugi
- sekund
- skrivnost
- Oddelek
- zavarovanje
- varnost
- glej
- seme
- videnje
- zdi se
- videl
- vidi
- Serija
- resno
- nastavite
- nastavitev
- Kratke Hlače
- Kmalu
- shouldnt
- Prikaži
- pokazale
- podpisati
- Znaki
- Podoben
- podobno
- Enostavno
- poenostavljeno
- poenostavitev
- preprosto
- sam
- Velikosti
- spanje
- majhna
- Sneaky
- vohljal
- So
- Software
- trdna
- nekaj
- Nekaj
- Kmalu
- vir
- Izvorna koda
- Vesolje
- posebna
- posebej
- določeno
- hitrost
- Stars
- Začetek
- začel
- Začetek
- začne
- zagon
- Še vedno
- ukradel
- stop
- ustavil
- trgovina
- shranjeni
- Zgodba
- String
- močna
- študija
- Uspešno
- taka
- dovolj
- naj
- presenečenje
- presenečen
- presenetljivo
- preživetje
- SVG
- swap
- sistem
- sistemi
- Bodite
- sprejeti
- meni
- ob
- pogovor
- tehnično
- tehnike
- začasna
- Test
- testi
- kot
- da
- O
- Vir
- njihove
- Njih
- sami
- POTEM
- Teorija
- Tukaj.
- zato
- jih
- stvar
- mislim
- ta
- tisti,
- čeprav?
- mislil
- čas
- Naslov
- do
- skupaj
- vzel
- orodje
- vrh
- sledenje
- Sledenje
- Prehod
- pregleden
- Res
- poskusite
- Obrnjen
- dva
- tip
- tipično
- razumeli
- unicode
- dokler
- neuporabljeno
- nezaželen
- Nadgradnja
- posodobljeno
- URL
- us
- nas vlada
- Uporaba
- usb
- uporaba
- uporaba brez uporabe
- Rabljeni
- uporabnik
- uporablja
- uporabo
- pripomoček
- vrednost
- Vrednote
- raznolikost
- preverjanje
- različica
- zelo
- preko
- ranljivost
- W
- Počakaj
- Čakam
- želeli
- hotel
- je
- Watch
- način..
- načini
- we
- Weeks
- Dobro
- so bili
- Kaj
- kdaj
- ali
- ki
- medtem
- WHO
- kdorkoli
- celoti
- zakaj
- bo
- zmago
- okna
- obrišite
- z
- brez
- spraševati
- besede
- delo
- delal
- deluje
- deluje
- skrbi
- bi
- bi dal
- pisati
- pisanje
- pisni
- še
- jo
- Vaša rutina za
- sami
- zefirnet
- nič