În ultimele două săptămâni, am văzut o serie de articole care vorbesc despre ceea ce a fost descris drept „crack master password” în popularul manager de parole open-source KeePass.
Bug-ul a fost considerat suficient de important pentru a obține un identificator oficial al guvernului SUA (este cunoscut ca CVE-2023-32784, dacă vrei să-l vânezi), și având în vedere că parola principală a managerului tău de parole este aproape cheia întregului tău castel digital, poți înțelege de ce povestea a provocat multă emoție.
Vestea bună este că un atacator care a vrut să exploateze această eroare ar trebui aproape sigur să fi infectat computerul cu programe malware și, prin urmare, ar putea oricum să-ți spioneze apăsările de taste și programele care rulează.
Cu alte cuvinte, bug-ul poate fi considerat un risc ușor de gestionat până când creatorul KeePass iese cu o actualizare, care ar trebui să apară în curând (la începutul lunii iunie 2023, se pare).
După cum are grijă dezvăluitorul bug-ului A arăta:
Dacă utilizați criptarea completă a discului cu o parolă puternică și sistemul dvs. este [fără malware], ar trebui să vă descurcați. Nimeni nu vă poate fura parolele de la distanță prin internet doar cu această constatare.
Riscurile explicate
Pe scurt, eroarea se rezumă la dificultatea de a vă asigura că toate urmele de date confidențiale sunt curățate din memorie odată ce ați terminat cu ele.
Vom ignora aici problemele legate de cum să evitați deloc să aveți date secrete în memorie, chiar și pe scurt.
În acest articol, vrem doar să le reamintim programatorilor de pretutindeni acel cod aprobat de un examinator conștient de securitate cu un comentariu de genul „pare să se curețe corect după sine”...
…de fapt, s-ar putea să nu se curețe complet deloc, iar potențiala scurgere de date ar putea să nu fie evidentă dintr-un studiu direct al codului în sine.
Mai simplu spus, vulnerabilitatea CVE-2023-32784 înseamnă că o parolă principală KeePass poate fi recuperată din datele de sistem chiar și după ce programul KeyPass a ieșit, deoarece informații suficiente despre parola dvs. (deși nu chiar parola brută în sine, pe care ne vom concentra activat într-un moment) ar putea fi lăsat în urmă în fișierele de schimb de sistem sau de repaus, unde memoria de sistem alocată poate ajunge să fie salvată pentru mai târziu.
Pe un computer Windows unde BitLocker nu este folosit pentru a cripta hard disk-ul atunci când sistemul este oprit, acest lucru ar oferi unui escroc care ți-a furat laptopul o șansă de luptă de a porni de pe o unitate USB sau CD și de a-ți recupera parola principală chiar și deși programul KeyPass în sine are grijă să nu-l salveze niciodată definitiv pe disc.
O scurgere de parolă pe termen lung în memorie înseamnă, de asemenea, că parola ar putea fi, teoretic, recuperată dintr-un depozit de memorie al programului KeyPass, chiar dacă acea descărcare a fost preluată mult după ce ați introdus parola și mult după KeePass. în sine nu mai avea nevoie să-l țină prin preajmă.
În mod clar, ar trebui să presupuneți că programele malware deja pe sistemul dvs. ar putea recupera aproape orice parolă introdusă printr-o varietate de tehnici de snooping în timp real, atâta timp cât acestea erau active în momentul în care ați tastat. Dar vă puteți aștepta în mod rezonabil ca timpul expus la pericol să fie limitat la scurta perioadă de tastare, nu extins la multe minute, ore sau zile după aceea sau poate mai mult, inclusiv după ce ați oprit computerul.
Ce rămâne în urmă?
Prin urmare, ne-am gândit să aruncăm o privire la nivel înalt asupra modului în care datele secrete pot rămâne în memorie în moduri care nu sunt direct evidente din cod.
Nu vă faceți griji dacă nu sunteți programator - vom păstra totul simplu și vă vom explica pe măsură ce mergem.
Vom începe prin a analiza utilizarea și curățarea memoriei într-un program simplu C care simulează introducerea și stocarea temporară a unei parole, făcând următoarele:
- Alocarea unei porțiuni dedicate de memorie special pentru a stoca parola.
- Inserarea unui șir de text cunoscut astfel încât să-l putem găsi cu ușurință în memorie dacă este necesar.
- Adăugarea a 16 caractere ASCII pseudo-aleatorie pe 8 biți din intervalul AP.
- Imprimarea tamponul de parole simulate.
- Eliberarea memoriei în speranța de a șterge tamponul de parole.
- Părăsirea programul.
Foarte simplificat, codul C ar putea arăta cam așa, fără verificarea erorilor, folosind numere pseudo-aleatoare de proastă calitate din funcția de rulare C rand()
, și ignorând orice verificări de depășire a tamponului (nu faceți niciodată nimic din toate acestea în cod real!):
// 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);
De fapt, codul pe care l-am folosit în cele din urmă în testele noastre include câteva bucăți și bucăți suplimentare prezentate mai jos, astfel încât să putem arunca întregul conținut al bufferului nostru temporar de parole pe măsură ce l-am folosit, pentru a căuta conținut nedorit sau rămas.
Rețineți că aruncăm în mod deliberat tamponul după apel free()
, care este, din punct de vedere tehnic, o eroare care nu poate fi folosită după utilizare, dar o facem aici ca o modalitate ascunsă de a vedea dacă ceva critic rămâne în urmă după ce ne-am predat tamponul înapoi, ceea ce ar putea duce la o gaură periculoasă de scurgere a datelor în viața reală.
Am introdus și două Waiting for [Enter]
solicită în cod pentru a ne oferi șansa de a crea depozite de memorie în punctele cheie ale programului, oferindu-ne date brute pentru a căuta mai târziu, pentru a vedea ce a rămas în urmă pe măsură ce programul rula.
Pentru a face depozite de memorie, vom folosi Microsoft Instrument Sysinternals procdump
cu -ma
opțiune (aruncă toată memoria), ceea ce evită necesitatea de a scrie propriul cod pentru a utiliza Windows DbgHelp
sistem și destul de complex MiniDumpXxxx()
funcții.
Pentru a compila codul C, am folosit propria noastră versiune mică și simplă a programului gratuit și open-source al lui Fabrice Bellard. Tiny C Compiler, disponibil pentru Windows pe 64 de biți în sursă și formă binară direct de pe pagina noastră GitHub.
În partea de jos a paginii apare textul care poate fi copiat și pasabil al întregului cod sursă ilustrat în articol.
Iată ce s-a întâmplat când am compilat și rulat programul de testare:
C:UsersduckKEYPASS> petcc64 -stdinc -stdlib unl1.c Compilator Tiny C - Copyright (C) 2001-2023 Fabrice Bellard Demontat de Paul Ducklin pentru a fi folosit ca instrument de învățare Versiunea petcc64-0.9.27 [0006] - generează 64 de biți Numai PE-uri -> 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 file size section 1000 200 438 .text 2000 800 2ac .data 3000 c00 24 .pdata -------- ----------------------- <- unl1.exe (3584 de octeți) C:UsersduckKEYPASS> unl1.exe Evacuarea bufferului „nou” la pornire 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 78cm 65 stem 00cm 44. exe.D 32F00B513: 0 72 69 76 65 72 44 61 74 61D 3 43A 3C 5 57 69E riverData=C:Win 6F00C513: 0 64F 6 77 73 5A 53C 79 73 74E riverData=C:Win 65F6C33: 32 5F 44 72 32C 00C 513 0D 69 76 dowsSystem65Dr 72F73D5: 44 72 69 76 65 72C 44 61 74 61 00 513 0 00 45 46 iversDriverData 43F5E34: 33 37 32 3 31F 00 46 50 53 5D 4372 1 00 513F 0 42 52F 4F57E53: F45F52: 5 41 50F 50 5 50 52 4F 46 00 51400 49F 4 45 5F 53 BROWSER_APP_PROF 54F52: 49 4C 47 3F 49 6 74 65 72E 00 51410D 6 65E 74 20 45 ILE_STRING=Inter 78F70 6 7 56 4 3 4C 00A 00 F00 51390C AC 75B 6 6 net ExplzV.< .K.. Șirul complet a fost: unlikelytextJHKNEJJCPOMDJHAN 69F6: 65 6E 79C 74 65B 78 74C 4 48 4 4 00 513A 0 45B 4E unlikelytextJHKN 4F43 50A 4A 4A 44A 4A 48A 41 4A 00 65 00E 44 00 513 0 EJJCPOMDJHAN.eD 72F69B76 : 65 72 44 61 74 61 3 43 3 5D 57 69A 6C 00 513 0E riverData=C:Win 64F6C77: 73 5F 53 79 73C 74 65 6 33 32C 5D 44D 72 32D 00C 513D 0D 69 76D 65Dr 72F73D5: 44 72 69 76 65 72C 44 61 74 61 00 513 0 00 45 46 iversDriverData 43F5E34: 33 37 32 3 31F 00 46 50 53 5D 4372 1 00 513 0 42F 52F 4 57 53 45F 52 5 41 50 50D 5 50 52 4 46 00F . 51400 49F 4 45 5 53 54F 52 49 4 47F 3 49 6F 74 BROWSER_APP_PROF 65F72: 00 51410C 6 65F 74 20 45 78 70E 6 7D 56 4E 3 4 00 ILE_STRING=Inter 00F00: 51390E 0E 67 5C 00 00 00 00 00 50 01C AC 5B 00 00 net ExplzV.<.K.. Se așteaptă [ENTER] pentru a elibera tamponul... Dumping tampon după eliberare() 00F00: A00 00 F513 0 45 4 4 43 50 4 F4 44 4 48 41 4 .g......P...... 00F65A00: 44 00A 513A 0 72 69F 76D 65 72A 44 61 74E 61 3 43 3 EJJCPOMDJHAN.eD 5F57B69: 6 00 513 0 64 6 77 73 5 53 79 73 74 65 6 33 32 5 44 72 32 00 513E riverData=C:Win 0F69C76: 65 72F 73 5 44C 72 69 76 65 72 44D 61 74 61C 00 513 dowsSystem0Dr 00F45D46: 43 5 34 33 37 32C 3 31 00 46 50 53 5 4372 1 00Dr 513F0D42 52: 4 57 53 45 52F 5 41 50 50 5D 50 52 4 46 00 51400F .EFC_49=4.FPS_ 45F5F53: 54 52 49F 4 47 3 49 6F 74 65 72 00F 51410 6 65F 74 BROWSER_APP_PROF 20F45: 78 70 6 4 00FC: 00 4 4 00 00 XNUMX XNUMXF XNUMX XNUMXD XNUMX XNUMXE XNUMX XNUMX XNUMX ILE_STRING=Inter XNUMXFXNUMX: XNUMXE XNUMX XNUMX XNUMX XNUMX XNUMX XNUMX XNUMXC XNUMXD XNUMX XNUMX XNUMXD AC XNUMXB XNUMX XNUMX net ExplM..MK. Se așteaptă ca [ENTER] să iasă din main()... C:UsersduckKEYPASS>
În această rulare, nu ne-am obosit să luăm nicio imagine de memorie de proces, deoarece am putut vedea imediat din rezultat că acest cod scurge date.
Imediat după apelarea funcției Windows C runtime library malloc()
, putem vedea că memoria tampon pe care o primim include ceea ce arată ca date variabile de mediu rămase din codul de pornire al programului, primii 16 octeți aparent modificați pentru a arăta ca un fel de antet de alocare a memoriei rămase.
(Rețineți cum acești 16 octeți arată ca două adrese de memorie de 8 octeți, 0xF55790
și 0xF50150
, care sunt imediat după și, respectiv, înainte de propriul nostru buffer de memorie.)
Când parola ar trebui să fie în memorie, putem vedea întregul șir clar în buffer, așa cum ne-am aștepta.
Dar după ce a sunat free()
, observați cum primii 16 octeți ai bufferului nostru au fost rescriși cu ceea ce arată ca adrese de memorie din apropiere din nou, probabil astfel încât alocatorul de memorie să poată urmări blocurile din memorie pe care le poate reutiliza...
… dar restul textului parolei noastre „șterse” (ultimele 12 caractere aleatorii EJJCPOMDJHAN
) a fost lăsat în urmă.
Nu numai că trebuie să ne gestionăm propriile alocări și dezalocari de memorie în C, dar trebuie să ne asigurăm că alegem funcțiile de sistem potrivite pentru bufferele de date dacă dorim să le controlăm cu precizie.
De exemplu, trecând la acest cod, obținem un pic mai mult control asupra a ceea ce este în memorie:
Prin trecerea de la malloc()
și free()
pentru a utiliza funcțiile de alocare Windows de nivel inferior VirtualAlloc()
și VirtualFree()
direct, obținem un control mai bun.
Cu toate acestea, plătim un preț în viteză, pentru că fiecare apel la VirtualAlloc()
face mai multă muncă decât un apel la malloc()
, care funcționează prin împărțirea și subdivizarea continuă a unui bloc de memorie de nivel scăzut prealocată.
Utilizarea VirtualAlloc()
în mod repetat, pentru blocuri mici, de asemenea, consumă mai multă memorie în general, deoarece fiecare bloc este distribuit de VirtualAlloc()
consumă de obicei un multiplu de 4KB de memorie (sau 2MB, dacă utilizați așa-numitul pagini mari de memorie), astfel încât memoria tampon de 128 de octeți de mai sus este rotunjită la 4096 de octeți, irosind cei 3968 de octeți de la sfârșitul blocului de memorie de 4KB.
Dar, după cum puteți vedea, memoria pe care o primim înapoi este automat eliminată (setată la zero), așa că nu putem vedea ce a fost acolo înainte, iar de data aceasta programul se blochează când încercăm să ne folosim după folosirea gratuită. truc, deoarece Windows detectează că încercăm să aruncăm o privire asupra memoriei pe care nu o mai deținem:
C:UsersduckKEYPASS> unl2 Dumping „noul” buffer la pornire 0000000000EA0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 ................ 0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 0000000000 0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000. .............. 0030EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 ................ 0040EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 ................ 0050EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0060 00 00 00 00 00 00 00 00 00 ................ 00EA00: 00 00 00 00 00 0000000000 0070 00 00 00 00 00 00 00 00 00 ................ 00EA00: 00 00 00 00 00 0000000000 0080 00 00 00 00 00 00 00 00 00 ................ 00EA00: 00 00 00 00 00 0000000000 0000 75 6 6 69 6 65 6 79 74 65 78 74 49 42 49 50 0000000000 0010 4 50 . 50 48 45 4F 50 4F 49 44 4C 4C 00 00 00 00 JPPPHEOPOIDLL .... 0000000000A : 0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA0000000000: 0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0040 00 00 00 00 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 0000000000 0050 00 00 00 00 00 00 ............... 00EA00: 00 00 00 00 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 00 00 00 00 00 00 00 00 00 00 0000000000 0080 ................ 00EA00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 0000 ............. ... Se așteaptă ca [ENTER] să elibereze bufferul... Dumping buffer after free() XNUMXEAXNUMX: [Programul s-a încheiat aici deoarece Windows ne-a prins folosirea după free]
Pentru că memoria pe care am eliberat-o va trebui realocată VirtualAlloc()
înainte de a putea fi folosit din nou, putem presupune că va fi eliminat înainte de a fi reciclat.
Cu toate acestea, dacă dorim să ne asigurăm că a fost golit, am putea apela funcția specială Windows RtlSecureZeroMemory()
chiar înainte de a-l elibera, pentru a garanta că Windows va scrie mai întâi zerouri în buffer-ul nostru.
Funcția aferentă RtlZeroMemory()
, dacă vă întrebați, face un lucru asemănător, dar fără garanția de a funcționa efectiv, deoarece compilatorii au voie să îl îndepărteze ca fiind teoretic redundanți dacă observă că tamponul nu este folosit din nou ulterior.
După cum puteți vedea, trebuie să avem o grijă deosebită să folosim funcțiile Windows potrivite dacă dorim să reducem timpul în care secretele stocate în memorie pot rămâne în jur pentru mai târziu.
În acest articol, nu ne vom uita la modul în care preveniți salvarea accidentală a secretelor în fișierul dvs. de schimb prin blocarea lor în memoria RAM fizică. (Aluzie: VirtualLock()
de fapt, nu este suficient în sine.) Dacă doriți să aflați mai multe despre securitatea memoriei Windows de nivel scăzut, anunțați-ne în comentarii și ne vom uita la asta într-un articol viitor.
Folosind gestionarea automată a memoriei
O modalitate bună de a evita alocarea, gestionarea și dealocarea memoriei de către noi înșine este să folosim un limbaj de programare care se ocupă de malloc()
și free()
, Sau VirtualAlloc()
și VirtualFree()
, automat.
Limbajul de scriptare cum ar fi Perl, Piton, Lua, JavaScript iar alții scapă de cele mai comune erori de siguranță ale memoriei care afectează codul C și C++, urmărind utilizarea memoriei pentru tine în fundal.
După cum am menționat mai devreme, exemplul nostru de cod C prost scris de mai sus funcționează bine acum, dar numai pentru că este încă un program super-simplu, cu structuri de date de dimensiuni fixe, unde putem verifica prin inspecție că nu vom suprascrie 128- buffer de octeți și că există o singură cale de execuție care începe cu malloc()
și se termină cu o corespondență free()
.
Dar dacă l-am actualizat pentru a permite generarea de parole cu lungime variabilă sau adăugăm funcții suplimentare în procesul de generare, atunci noi (sau oricine va menține codul în continuare) am putea ajunge cu ușurință la depășiri de buffer-uri, erori de utilizare după eliberare sau memorie care nu se eliberează niciodată și, prin urmare, lasă datele secrete mult timp după ce nu mai sunt necesare.
Într-o limbă precum Lua, putem lăsa mediul de rulare Lua, care face ceea ce este cunoscut în jargon ca colectare automată a gunoiului, se ocupă de achiziționarea de memorie din sistem și returnarea acesteia atunci când detectează că am încetat să o mai folosim.
Programul C pe care l-am enumerat mai sus devine mult mai simplu atunci când alocarea și dezalocarea memoriei sunt îngrijite pentru noi:
Alocăm memorie pentru a menține șirul s
pur și simplu prin atribuirea șirului 'unlikelytext'
să-l.
Mai târziu îi putem sugera lui Lua în mod explicit că nu ne mai interesează s
atribuindu-i valoarea nil
(toate nils
sunt în esență același obiect Lua), sau nu mai folosiți s
și așteptați ca Lua să detecteze că nu mai este necesar.
Oricum, memoria folosită de s
va fi în cele din urmă recuperat automat.
Și pentru a preveni depășirile de buffer sau gestionarea greșită a dimensiunii atunci când se adaugă șirurilor de text (operatorul Lua ..
, pronunțat concat, în esență adaugă două șiruri împreună, cum ar fi +
în Python), de fiecare dată când extindem sau scurtăm un șir, Lua alocă magic spațiu pentru un șir nou-nouț, mai degrabă decât să îl modifice sau să îl înlocuiască pe cel original în locația de memorie existentă.
Această abordare este mai lentă și duce la vârfuri de utilizare a memoriei care sunt mai mari decât ați obține în C din cauza șirurilor intermediare alocate în timpul manipulării textului, dar este mult mai sigură în ceea ce privește depășirile de buffer.
Dar acest tip de management automat al șirurilor (cunoscut în jargon ca inflexibilitate, pentru că șirurile nu ajung niciodată mutant, sau modificate la locul lor, odată ce au fost create), aduce noi bătăi de cap în domeniul securității cibernetice.
Am rulat programul Lua de mai sus pe Windows, până la a doua pauză, chiar înainte de a ieși din program:
C:UsersduckKEYPASS> lua s1.lua Șirul complet este: unlikelytextHLKONBOJILAGLNLN Se așteaptă [ENTER] înainte de a elibera șirul... Se așteaptă [ENTER] înainte de a ieși...
De data aceasta, am efectuat o descărcare a memoriei de proces, astfel:
C:UsersduckKEYPASS> procdump -ma lua lua-s1.dmp ProcDump v11.0 - Utilitar de descărcare a procesului Sysinternals Copyright (C) 2009-2022 Mark Russinovich și Andrew Richards Sysinternals - www.sysinternals.com [00:00:00] Dump 1 inițiat: C:UsersduckKEYPASSlua-s1.dmp [00:00:00] Scrierea Dump 1: Dimensiunea estimată a fișierului dump este de 10 MB. [00:00:00] Dump 1 complet: 10 MB scris în 0.1 secunde [00:00:01] Dump count atins.
Apoi am rulat acest script simplu, care citește înapoi fișierul dump, găsește peste tot în memorie că acel șirul cunoscut unlikelytext
a apărut și îl tipărește, împreună cu locația sa în fișierul de descărcare și caracterele ASCII care au urmat imediat:
Chiar dacă ați folosit limbaje de script înainte sau ați lucrat în orice ecosistem de programare care are așa-numitele șiruri gestionate, unde sistemul ține evidența alocărilor și dealocațiilor de memorie pentru dvs. și le gestionează așa cum crede de cuviință...
… ați putea fi surprins să vedeți rezultatul pe care îl produce această scanare a memoriei:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp 006D8AFC: unlikelytextALJBNGOAPLLBDEB 006D8B3C: unlikelytextALJBNGOA 006D8B7C: unlikelytextALJBNGO 006D8BFC: unlikelytextALJBNGOAPLLBDEB 006D8BNGOA: unlikelytextALJBNGOA 006D8B7C: unlikelytextALJBNGO 006D903BFC: unlikelytext006BNGOAPLLBDEB90JB006 unlikelytext90BNGOA 006D913D006C: unlikelytextALJBNGOAP 91D006C: unlikelytextALJBNGOAPL 91D006BC: unlikelytextALJBNGOAPLL 923D006FC: unlikelytextALJBNG 70D006C: unlikelytextALJBNGOAPLLB: unlikelytextALJBNGOAPLL8: unlikelytextALJBNGOAPLL006D0FC: unlikelytextALJBNGOAP: lytextALJBNGOAPLLBD XNUMXDXNUMXC : improbabiltextALJBNGOAPLLBDE XNUMXDBXNUMXC: improbabiltextALJ XNUMXDBBXNUMXC: improbabiltextAL XNUMXDBDXNUMXC: improbabiltextA
Iată, în momentul în care ne-am luat gheața de memorie, deși terminasem cu sfoara s
(și i-a spus Luei că nu mai avem nevoie de el spunând s = nil
), toate șirurile pe care codul le crease pe parcurs erau încă prezente în RAM, nerecuperate sau șterse încă.
Într-adevăr, dacă sortăm rezultatul de mai sus după șirurile în sine, mai degrabă decât să urmăm ordinea în care au apărut în RAM, veți putea să vă imaginați ce s-a întâmplat în timpul buclei în care am concatenat câte un caracter la șirul nostru de parolă:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp | sort /+10 006DBD0C: text improbabilA 006DBB8C: text improbabilAL 006DB70C: text improbabilALJ 006D91BC: text improbabilALJB 006D8CBC: text improbabilALJBN 006D90FC: text improbabilALJ006DBNG8C7D006BC: improbabiltextALJB8BNG B3C: unlikelytextALJBNGOA 006D8D7C: unlikelytextALJBNGOAP 006D903C: unlikelytextALJBNGOAPL 006D90BC: unlikelytextALJBNGOAPLL 006D913C: unlikelytextALJBNGOAPLLB 006D91D006BC: unlikelytextALJBNGOAPLLB 923D006BC: unlikelytext probabletextALJBNGOAPLLBDE 8D006AFC: improbabiltextALJBNGOAPLLBDEB 8DXNUMXBFC : unlikelytextALJBNGOAPLLBDEBJ
Toate acele șiruri intermediare temporare sunt încă acolo, așa că chiar dacă am fi șters cu succes valoarea finală a s
, încă am scăpa totul, cu excepția ultimului caracter.
De fapt, în acest caz, chiar și atunci când am forțat în mod deliberat programul nostru să elimine toate datele care nu sunt necesare apelând funcția specială Lua collectgarbage()
(Majoritatea limbajelor de scripting au ceva asemănător), majoritatea datelor din acele șiruri temporare plictisitoare s-au blocat oricum în RAM, pentru că am compilat Lua pentru a-și gestiona automat memoria folosind vechiul vechi. malloc()
și free()
.
Cu alte cuvinte, chiar și după ce Lua și-a revendicat blocurile de memorie temporare pentru a le folosi din nou, nu am putut controla cum sau când vor fi reutilizate acele blocuri de memorie și, astfel, cât timp vor sta în interiorul procesului cu stânga- peste date care așteaptă să fie adulmecate, aruncate sau scurse în alt mod.
Introduceți .NET
Dar cum rămâne cu KeePass, de unde a început acest articol?
KeePass este scris în C# și folosește runtime .NET, astfel încât evită problemele de gestionare greșită a memoriei pe care programele C le aduc cu ele...
… dar C# își gestionează propriile șiruri de text, mai degrabă ca Lua, ceea ce ridică întrebarea:
Chiar dacă programatorul a evitat să stocheze întreaga parolă principală într-un singur loc după ce a terminat cu ea, ar putea atacatorii cu acces la un depozit de memorie să găsească totuși suficiente date temporare rămase pentru a ghici sau a recupera parola principală oricum, chiar dacă atacatorii au avut acces la computerul tău la minute, ore sau zile după ce ai introdus parola?
Mai simplu spus, există rămășițe detectabile, fantomatice ale parolei tale principale care supraviețuiesc în RAM, chiar și după ce te-ai aștepta să fi fost șterse?
Enervant, ca utilizator Github a descoperit Vdohney, răspunsul (cel puțin pentru versiunile KeePass anterioare 2.54) este „Da”.
Pentru a fi clar, nu credem că parola dvs. principală reală poate fi recuperată ca un singur șir de text dintr-un depozit de memorie KeePass, deoarece autorul a creat o funcție specială pentru introducerea parolei principale, care iese din greu pentru a evita stocarea completă. parola de unde ar putea fi ușor de găsit și adulmecat.
Ne-am mulțumit de acest lucru setând parola noastră principală la SIXTEENPASSCHARS
, tastând-o și apoi luând imagini de memorie imediat, la scurt timp și mult timp după aceea.
Am căutat în depozite cu un script Lua simplu care a căutat oriunde acel text al parolei, atât în format ASCII de 8 biți, cât și în format UTF-16 (Windows widechar) pe 16 biți, astfel:
Rezultatele au fost încurajatoare:
C:UsersduckKEYPASS> lua searchknown.lua kp2-post.dmp Se citește în fișierul dump... TERMINAT. Se caută SIXTEENPASSCHARS ca ASCII pe 8 biți... nu a fost găsit. Se caută SIXTEENPASSCHARS ca UTF-16... nu a fost găsit.
Dar Vdohney, descoperitorul CVE-2023-32784, a observat că pe măsură ce introduceți parola principală, KeePass vă oferă feedback vizual prin construirea și afișarea unui șir de substituent format din caractere „blob” Unicode, până la și inclusiv lungimea dvs. parola:
În șirurile de text widechar pe Windows (care constau din doi octeți pe caracter, nu doar un octet fiecare ca în ASCII), caracterul „blob” este codificat în RAM ca octet hexadecimal 0xCF
urmată de 0x25
(care se întâmplă să fie un semn de procent în ASCII).
Deci, chiar dacă KeePass are mare grijă cu caracterele brute pe care le introduceți atunci când introduceți parola în sine, s-ar putea să ajungeți cu șiruri rămase de caractere „blob”, ușor de detectat în memorie, ca rulări repetate, cum ar fi CF25CF25
or CF25CF25CF25
...
… și, dacă da, cea mai lungă serie de caractere blob pe care le-ați găsit ar da probabil lungimea parolei dvs., care ar fi o formă modestă de scurgere de informații despre parolă, dacă nu altceva.
Am folosit următorul script Lua pentru a căuta semne de șiruri de substituenți de parolă rămase:
Rezultatul a fost surprinzător (am șters linii succesive cu același număr de blob-uri sau cu mai puține blob-uri decât linia anterioară, pentru a economisi spațiu):
C:UsersduckKEYPASS> lua findblobs.lua kp2-post.dmp 000EFF3C: * [. . .] 00BE621B: ** 00BE64C7: *** [. . .] 00BE6E8F: **** [. . .] 00BE795F: ***** [. . .] 00BE84F7: ****** [. . .] 00BE8F37: ******* [ continuă în mod similar pentru 8 blob-uri, 9 blob-uri etc. ] [ până la două linii finale de exact 16 blob-uri fiecare ] 00C0503B: ************* *** 00C05077: **************** 00C09337: * 00C09738: * [toate meciurile rămase au o lungime de un blob] 0123B058: *
La adrese de memorie apropiate, dar în continuă creștere, am găsit o listă sistematică de 3 blob-uri, apoi 4 blob-uri și așa mai departe până la 16 blob-uri (lungimea parolei noastre), urmate de multe cazuri împrăștiate aleatoriu de șiruri cu un singur blob. .
Deci, acele șiruri de „blob” de substituent par într-adevăr să se scurgă în memorie și să rămână în urmă pentru a scurge lungimea parolei, mult după ce software-ul KeePass a terminat cu parola principală.
Urmatorul pas
Am decis să săpăm mai departe, la fel cum a făcut Vdohney.
Ne-am schimbat codul de potrivire a modelului pentru a detecta lanțuri de caractere blob urmate de orice caracter ASCII unic în format de 16 biți (caracterele ASCII sunt reprezentate în UTF-16 ca codul lor obișnuit ASCII de 8 biți, urmat de un octet zero).
De data aceasta, pentru a economisi spațiu, am suprimat rezultatul pentru orice potrivire care se potrivește exact cu cea anterioară:
Surpriza surpriza:
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
Uite ce obținem din regiunea de memorie de șiruri gestionată de .NET!
Un set strâns grupat de „șiruri blob” temporare care dezvăluie caracterele succesive din parola noastră, începând cu al doilea caracter.
Aceste șiruri care ne scurg sunt urmate de potriviri cu un singur caracter distribuite pe scară largă, despre care presupunem că au apărut întâmplător. (Un fișier de descărcare KeePass are o dimensiune de aproximativ 250 MB, așa că există mult spațiu pentru ca caracterele „blob” să apară ca din noroc.)
Chiar dacă luăm în considerare acele patru potriviri suplimentare, în loc să le eliminăm ca nepotriviri probabile, putem ghici că parola principală este una dintre:
?XTEENPASSCHARS ?NXTEENPASSCHARS ?WXTEENPASSCHARS ?SXTEENPASSCHARS
Evident, această tehnică simplă nu găsește primul caracter în parolă, deoarece primul „șir blob” este construit doar după ce primul caracter a fost introdus.
Rețineți că această listă este frumoasă și scurtă, deoarece am filtrat potrivirile care nu se termină cu caractere ASCII.
Dacă ați căutat caractere dintr-o gamă diferită, cum ar fi caractere chinezești sau coreene, s-ar putea să ajungeți la mai multe accesări accidentale, deoarece există mult mai multe caractere posibile pentru a potrivi...
…dar bănuim că oricum te vei apropia destul de mult de parola ta principală, iar „șirurile blob” care se referă la parolă par să fie grupate împreună în RAM, probabil pentru că au fost alocate aproximativ în același timp de aceeași parte a timpul de rulare .NET.
Și aici, într-un cuvânt lung și discursiv, este povestea fascinantă a lui CVE-2023-32784.
Ce să fac?
- Dacă sunteți utilizator KeePass, nu intrați în panică. Deși aceasta este o eroare și, din punct de vedere tehnic, este o vulnerabilitate exploatabilă, atacatorii de la distanță care au vrut să spargă parola folosind această eroare ar trebui să implanteze mai întâi malware pe computer. Acest lucru le-ar oferi multe alte modalități de a vă fura parolele direct, chiar dacă această eroare nu a existat, de exemplu, înregistrând apăsările de la taste pe măsură ce introduceți. În acest moment, puteți pur și simplu să fiți atenți la actualizarea viitoare și să o luați când este gata.
- Dacă nu utilizați criptarea completă a discului, luați în considerare activarea acesteia. Pentru a extrage parolele rămase din fișierul dvs. de schimb sau din fișierul de hibernare (fișierele de disc ale sistemului de operare utilizate pentru a salva temporar conținutul memoriei în timpul încărcării grele sau când computerul este „dormit”), atacatorii ar avea nevoie de acces direct la hard disk. Dacă aveți activat BitLocker sau echivalentul acestuia pentru alte sisteme de operare, aceștia nu vor putea accesa fișierul dvs. de schimb, fișierul de hibernare sau orice alte date personale, cum ar fi documente, foi de calcul, e-mailuri salvate și așa mai departe.
- Dacă sunteți programator, mențineți-vă informat despre problemele legate de gestionarea memoriei. Nu presupune asta doar pentru că fiecare
free()
se potrivește cu corespunzătoare acestuiamalloc()
că datele dumneavoastră sunt în siguranță și bine gestionate. Uneori, poate fi necesar să luați măsuri de precauție suplimentare pentru a evita să lăsați date secrete în preajmă, și aceste măsuri de precauție foarte de la sistem de operare la sistem de operare. - Dacă sunteți un tester QA sau un examinator de cod, gândiți-vă întotdeauna „în spatele scenei”. Chiar dacă codul de gestionare a memoriei pare ordonat și bine echilibrat, fiți conștienți de ceea ce se întâmplă în culise (deoarece programatorul original ar putea să nu fi știut să facă acest lucru) și pregătiți-vă să faceți niște lucrări în stil pentesting, cum ar fi monitorizarea timpului de rulare și memoria dumping pentru a verifica că codul securizat într-adevăr se comportă așa cum trebuie.
COD DIN ARTICOL: UNL1.C
#include #include #include void hexdump(unsigned char* buff, int len) { // Imprimă buffer-ul în bucăți de 16 octeți pentru (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Afișează 16 octeți ca valori hexadecimale pentru (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Repetați acei 16 octeți ca caractere pentru (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) { // Obține memorie pentru a stoca parola și arată ce // este în buffer când este oficial „nou”... char* buff = malloc(128); printf("Dumping 'noul' buffer la pornire"); hexdump(buff,128); // Folosește adresa tampon pseudoaleatorie ca srand((unsigned)buff) aleatoriu; // Începe parola cu un text fix, care poate fi căutat strcpy(buff,"unlikelytext"); // Adăugați 16 litere pseudoaleatoare, una câte una pentru (int i = 1; i <= 16; i++) { // Alegeți o literă de la A (65+0) la P (65+15) char ch = 65 + (rand() & 15); // Apoi modificați șirul de buff în locul strncat(buff,&ch,1); } // Parola completă este acum în memorie, așa că tipăriți-o // ca șir și afișați întregul buffer... printf("Șirul complet a fost: %sn",buff); hexdump(buff,128); // Întrerupeți acum pentru a descărca memoria RAM de proces (încercați: 'procdump -ma') puts("Se așteaptă ca [ENTER] să elibereze bufferul..."); getchar(); // Eliberează() în mod formal memoria și arată tamponul // din nou pentru a vedea dacă a rămas ceva în urmă... liber(buff); printf("Dumping tampon după free()n"); hexdump(buff,128); // Pauză pentru a descărca din nou RAM pentru a inspecta diferențele puts("Se așteaptă ca [ENTER] să iasă din main(..."); getchar(); returnează 0; }
COD DIN ARTICOL: UNL2.C
#include #include #include #include void hexdump(unsigned char* buff, int len) { // Imprimă buffer-ul în bucăți de 16 octeți pentru (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Afișează 16 octeți ca valori hexadecimale pentru (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Repetați acei 16 octeți ca caractere pentru (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) { // Obține memorie pentru a stoca parola și arată ce // este în buffer când este oficial „nou”... char* buff = VirtualAlloc(0,128,MEM_COMMIT,PAGE_READWRITE); printf("Dumping 'noul' buffer la pornire"); hexdump(buff,128); // Folosește adresa tampon pseudoaleatorie ca srand((unsigned)buff) aleatoriu; // Începe parola cu un text fix, care poate fi căutat strcpy(buff,"unlikelytext"); // Adăugați 16 litere pseudoaleatoare, una câte una pentru (int i = 1; i <= 16; i++) { // Alegeți o literă de la A (65+0) la P (65+15) char ch = 65 + (rand() & 15); // Apoi modificați șirul de buff în locul strncat(buff,&ch,1); } // Parola completă este acum în memorie, așa că tipăriți-o // ca șir și afișați întregul buffer... printf("Șirul complet a fost: %sn",buff); hexdump(buff,128); // Întrerupeți acum pentru a descărca memoria RAM de proces (încercați: 'procdump -ma') puts("Se așteaptă ca [ENTER] să elibereze bufferul..."); getchar(); // Eliberează() în mod formal memoria și arată din nou tamponul // pentru a vedea dacă a rămas ceva în urmă... VirtualFree(buff,0,MEM_RELEASE); printf("Dumping tampon după free()n"); hexdump(buff,128); // Pauză pentru a descărca din nou RAM pentru a inspecta diferențele puts("Se așteaptă ca [ENTER] să iasă din main(..."); getchar(); returnează 0; }
COD DIN ARTICOL: S1.LUA
-- Începeți cu un text fix, care poate fi căutat s = „text improbabil” -- Adăugați 16 caractere aleatorii de la „A” la „P” pentru i = 1,16 do s = s .. string.char(65+math.random( 0,15)) end print('Șirul complet este:',s,'n') -- Pauză pentru a descărca procesul RAM print('Se așteaptă [ENTER] înainte de a elibera șirul...') io.read() - - Ștergeți șirul și marcați variabila nefolosită s = nil -- Dump RAM din nou pentru a căuta diffs print('Se așteaptă [ENTER] înainte de a ieși...') io.read()
COD DIN ARTICOL: FINDIT.LUA
-- citiți în fișierul de descărcare local f = io.open(arg[1],'rb'):read('*a') -- căutați textul marcatorului urmat de unul -- sau mai multe caractere ASCII aleatorii local b,e ,m = 0,0,nil în timp ce adevărat do -- căutați următoarea potrivire și amintiți-vă decalajul b,e,m = f:find('(unlikelytext[AZ]+)',e+1) -- ieșiți când nu mai se potrivește dacă nu b, atunci break end -- raportează poziția și șirul găsit print(string.format('%08X: %s',b,m)) end
COD DIN ARTICOL: SEARCHKNOWN.LUA
io.write('Se citesc în fișierul dump... ') local f = io.open(arg[1],'rb'):read('*a') io.write('DONE.n') io. write('Se caută SIXTEENPASSCHARS ca ASCII pe 8 biți... ') local p08 = f:find('SIXTEENPASSCHARS') io.write(p08 și 'FOUND' sau 'not found','.n') io.write ('Se caută SIXTEENPASSCHARS ca UTF-16... ') local p16 = f:find('Sx00Ix00Xx00Tx00Ex00Ex00Nx00Px00'.. 'Ax00Sx00Sx00Cx00Hx00Ax00Rx00Rx00Tx16ExXNUMXExXNUMXNxXNUMXPxXNUMX'.. 'AxXNUMXSxXNUMXSxXNUMXCxXNUMXHxXNUMXAxXNUMXRxXNUMXRxXNUMXTxXNUMXS, 'w'XNUMXRxXNUMXTxXNUMX' sau 'write') '.n')
COD DIN ARTICOL: FINDBLOBS.LUA
-- citește în fișierul de descărcare specificat pe linia de comandă local f = io.open(arg[1],'rb'):read('*a') -- Căutați unul sau mai multe blob-uri de parolă, urmate de orice non-blob -- Rețineți că caracterele blob (●) codifică în caractere late Windows -- ca coduri UTF-16 litte-endian, care apar ca CF 25 în hex. local b,e,m = 0,0,nil while true do -- Dorim unul sau mai multe blob-uri, urmate de orice non-blob. -- Simplificăm codul căutând un CF25 explicit -- urmat de orice șir care are doar CF sau 25 în el, -- așa că vom găsi CF25CFCF sau CF2525CF precum și CF25CF25. -- Vom filtra „false pozitive” mai târziu, dacă există. -- Trebuie să scriem „%%” în loc de x25, deoarece caracterul x25 -- (semnul de procente) este un caracter special de căutare în Lua! b,e,m = f:find('(xCF%%[xCF%%]*)',e+1) -- ieșire când nu mai se potrivește dacă nu b, atunci break end -- CMD.EXE nu poate imprima blobs, așa că le convertim în stele. print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) end
COD DIN ARTICOL: SEARCHKP.LUA
-- citește în fișierul dump specificat pe linia de comandă local f = io.open(arg[1],'rb'):read('*a') local b,e,m,p = 0,0,nil,nil în timp ce true do -- Acum, vrem unul sau mai multe blob-uri (CF25) urmate de cod -- pentru A..Z urmat de un octet 0 pentru a converti ACSCII în UTF-16 b,e,m = f:find(' (xCF%%[xCF%%]*[AZ])x00',e+1) -- ieșire când nu mai există potriviri dacă nu b, apoi break end -- CMD.EXE nu poate imprima blob-uri, așa că le convertim în stele. -- Pentru a economisi spațiu, suprimăm potrivirile succesive dacă m ~= p apoi print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) p = m sfârşitul sfârşitului
- Distribuție de conținut bazat pe SEO și PR. Amplifică-te astăzi.
- PlatoAiStream. Web3 Data Intelligence. Cunoștințe amplificate. Accesați Aici.
- Mintând viitorul cu Adryenn Ashley. Accesați Aici.
- Cumpărați și vindeți acțiuni în companii PRE-IPO cu PREIPO®. Accesați Aici.
- Sursa: https://nakedsecurity.sophos.com/2023/05/31/serious-security-that-keepass-master-password-crack-and-what-we-can-learn-from-it/
- :are
- :este
- :nu
- :Unde
- ][p
- $UP
- 1
- 10
- 12
- 15%
- 20
- 200
- 2023
- 24
- 250
- 27
- 31
- 3d
- 49
- 50
- 67
- 70
- 72
- 77
- 8
- 9
- a
- Capabil
- Despre Noi
- mai sus
- Absolut
- AC
- acces
- Cont
- dobândi
- dobândirea
- activ
- curent
- de fapt
- adăugat
- Suplimentar
- adresa
- adrese
- Adaugă
- După
- după aceea
- din nou
- TOATE
- alocate
- alocari
- alocare
- alocări
- permite
- singur
- de-a lungul
- deja
- de asemenea
- modificate
- Cu toate ca
- mereu
- an
- și
- Andrew
- răspunde
- Orice
- nimic
- orice critic
- apărea
- a apărut
- abordare
- aprobat
- SUNT
- în jurul
- articol
- bunuri
- AS
- At
- autor
- Auto
- Automat
- în mod automat
- disponibil
- evita
- evitat
- conştient
- departe
- înapoi
- fundal
- imagine de fundal
- BE
- deoarece
- devine
- fost
- înainte
- Început
- în spatele
- în spatele scenelor
- de mai jos
- Mai bine
- Pic
- Bloca
- Blocuri
- frontieră
- atât
- De jos
- marca
- Brand nou
- Pauză
- scurt
- aduce
- tampon
- tampon de depășire
- Bug
- gandaci
- construi
- dar
- by
- C ++
- apel
- apel
- CAN
- Poate obține
- pasă
- caz
- prins
- CD
- Centru
- cu siguranță
- lanţuri
- șansă
- si-a schimbat hainele;
- caracter
- caractere
- control
- Verificări
- chinez
- Alege
- clar
- clar
- Închide
- cod
- culoare
- COM
- vine
- venire
- comentariu
- comentarii
- Comun
- Completă
- complex
- calculator
- Lua în considerare
- considerabil
- luate în considerare
- Constând
- construirea
- conţinut
- conținut
- continuu
- continuă
- Control
- converti
- drepturi de autor
- Corespunzător
- ar putea
- acoperi
- crăpa
- crea
- a creat
- creator
- critic
- Securitate cibernetică
- PERICOL
- Periculos
- de date
- scurgeri de date
- Zi
- afacere
- hotărât
- dedicat
- descris
- FĂCUT
- diferenţele
- diferit
- Dificultate
- SĂPA
- digital
- direcționa
- Acces direct
- direct
- Afişa
- afișarea
- are
- do
- documente
- face
- Nu
- face
- făcut
- Dont
- jos
- conduce
- două
- descărca
- în timpul
- e
- fiecare
- Mai devreme
- cu ușurință
- ecosistem
- oricare
- altfel
- e-mailuri
- permițând
- Fii încurajator.
- criptare
- capăt
- se încheie
- suficient de
- asigura
- asigurare
- Intrați
- intrarea
- Întreg
- intrare
- Mediu inconjurator
- Echivalent
- eroare
- În esență,
- estimativ
- etc
- Eter (ETH)
- Chiar
- în cele din urmă
- tot mai mare
- Fiecare
- tot
- exact
- exemplu
- Cu excepția
- Excitare
- execuție
- exista
- existent
- Ieşire
- Părăsirea
- aștepta
- Explica
- Exploata
- expus
- extinde
- suplimentar
- extrage
- fapt
- fals
- fascinant
- DESCRIERE
- feedback-ul
- mai puține
- luptă
- Fișier
- Fişiere
- filtru
- final
- În cele din urmă
- Găsi
- descoperire
- descoperiri
- capăt
- First
- fixată
- Concentra
- a urmat
- următor
- Pentru
- formă
- Oficial
- format
- viitor
- găsit
- patru
- Gratuit
- din
- Complet
- complet
- funcţie
- funcții
- mai mult
- viitor
- generează
- generaţie
- obține
- obtinerea
- GitHub
- Da
- dat
- oferă
- Oferirea
- Go
- Merge
- merge
- bine
- Guvern
- apuca
- mare
- garanta
- HAD
- Mânere
- sa întâmplat
- lucru
- se întâmplă
- Greu
- Avea
- având în
- dureri de cap
- greu
- înălțime
- aici
- HEX
- la nivel înalt
- superior
- hit-uri
- deţine
- Gaură
- speranţă
- ORE
- planare
- Cum
- Cum Pentru a
- HTTPS
- vânătoare
- i
- identificator
- if
- imediat
- important
- in
- include
- Inclusiv
- informații
- informat
- in schimb
- interesat
- Intermediar
- Internet
- în
- probleme de
- IT
- ESTE
- în sine
- jargon
- iunie
- doar
- doar unul
- A pastra
- Cheie
- Cunoaște
- cunoscut
- Coreeană
- limbă
- Limbă
- laptop
- Nume
- mai tarziu
- conduce
- Conduce
- scăpa
- Scurgeri
- AFLAȚI
- învăţare
- cel mai puțin
- lăsând
- stânga
- Lungime
- scrisoare
- Bibliotecă
- Viaţă
- ca
- Probabil
- Limitat
- Linie
- linii
- Listă
- listat
- ll
- încărca
- local
- locaţie
- logare
- Lung
- pe termen lung
- mai lung
- Uite
- arată ca
- uitat
- cautati
- Se pare
- Lot
- noroc
- susține
- face
- malware
- administra
- gestionate
- administrare
- manager
- gestionează
- Manipulare
- multe
- Margine
- marca
- marcator
- maestru
- Meci
- potrivire
- max-width
- Mai..
- mijloace
- Memorie
- menționat
- Microsoft
- ar putea
- minute
- modest
- modificată
- modifica
- moment
- Monitorizarea
- mai mult
- cele mai multe
- mult
- multiplu
- ordinat
- Nevoie
- necesar
- net
- nu
- cu toate acestea
- Nou
- ştiri
- următor
- frumos
- Nu.
- normală.
- nimic
- Înștiințare..
- acum
- număr
- numere
- obiect
- evident
- of
- de pe
- oficial
- Oficial
- compensa
- Vechi
- on
- dată
- ONE
- afară
- open-source
- de operare
- sistem de operare
- sisteme de operare
- operator
- Opțiune
- or
- comandă
- original
- Altele
- Altele
- in caz contrar
- al nostru
- ne
- afară
- producție
- peste
- global
- propriu
- pagină
- Panică
- parte
- Parolă
- manager de parole
- Parolele
- cale
- Model
- Paul
- pauză
- Plătește
- la sută
- poate
- perioadă
- permanent
- personal
- date personale
- fizic
- imagine
- piese
- Loc
- înlocuitor
- Ciumă
- Plato
- Informații despre date Platon
- PlatoData
- mulțime
- Punct
- puncte
- Popular
- poziţie
- posibil
- postări
- potenţial
- tocmai
- prezenta
- destul de
- împiedica
- precedent
- preţ
- printuri
- probabil
- probleme
- proces
- Program
- Programator
- Programatorii
- Programare
- Programe
- pronunţat
- pune
- Piton
- Q & A
- întrebare
- ridică
- RAM
- aleator
- gamă
- mai degraba
- Crud
- date neprelucrate
- RE
- atins
- Citeste
- Citind
- gata
- real
- viata reala
- în timp real
- într-adevăr
- recunoaşte
- Recupera
- recuperare
- legate de
- rămas
- minte
- la distanta
- scoate
- repeta
- repetat
- REPETAT
- raportează
- reprezentate
- respect
- respectiv
- REST
- REZULTATE
- reveni
- revenind
- dezvălui
- Scăpa
- dreapta
- Risc
- Riscurile
- Cameră
- Alerga
- funcţionare
- monitorizarea timpului de rulare
- s
- sigur
- mai sigur
- acelaşi
- satisfăcut
- Economisiți
- spunând
- scanare
- risipit
- scene
- Caută
- căutare
- Al doilea
- secunde
- Secret
- Secțiune
- sigur
- securitate
- vedea
- sămânţă
- vedere
- părea
- văzut
- vede
- serie
- serios
- set
- instalare
- Pantaloni scurți
- Pe scurt
- să
- Arăta
- indicat
- semna
- Semne
- asemănător
- asemănător
- simplu
- simplificată
- simplifica
- pur şi simplu
- singur
- Mărimea
- dormi
- mic
- Meschin
- IGMP
- So
- Software
- solid
- unele
- ceva
- Curând
- Sursă
- cod sursă
- Spaţiu
- special
- special
- specificată
- viteză
- Stele
- Începe
- început
- Pornire
- începe
- lansare
- Încă
- furat
- Stop
- oprit
- stoca
- stocate
- Poveste
- Şir
- puternic
- Studiu
- Reușit
- astfel de
- suficient
- a presupus
- surpriză
- uimit
- surprinzător
- supravieţui
- SVG
- schimba
- sistem
- sisteme
- Lua
- luate
- ia
- luare
- vorbesc
- tehnic
- tehnici de
- temporar
- test
- teste
- decât
- acea
- Sursa
- lor
- Lor
- se
- apoi
- teorie
- Acolo.
- prin urmare
- ei
- lucru
- crede
- acest
- aceste
- deşi?
- gândit
- timp
- Titlu
- la
- împreună
- a luat
- instrument
- top
- urmări
- Urmărire
- tranziţie
- transparent
- adevărat
- încerca
- transformat
- Două
- tip
- tipic
- înţelege
- unicode
- până la
- nefolosit
- nedorit
- Actualizează
- actualizat
- URL-ul
- us
- guvernul SUA
- Folosire
- USB
- utilizare
- utilizare-după-gratuit
- utilizat
- Utilizator
- utilizări
- folosind
- utilitate
- valoare
- Valori
- varietate
- verifica
- versiune
- foarte
- de
- vulnerabilitate
- W
- aștepta
- Aşteptare
- vrea
- dorit
- a fost
- Ceas
- Cale..
- modalități de
- we
- săptămâni
- BINE
- au fost
- Ce
- cand
- dacă
- care
- în timp ce
- OMS
- oricine
- întreg
- de ce
- voi
- câştiga
- ferestre
- sterge
- cu
- fără
- întrebam
- cuvinte
- Apartamente
- a lucrat
- de lucru
- fabrică
- face griji
- ar
- ar da
- scrie
- scris
- scris
- încă
- tu
- Ta
- te
- zephyrnet
- zero