I løpet av de siste to ukene har vi sett en serie artikler som snakker om det som er blitt beskrevet som et "masterpassord-knekk" i den populære åpen kildekode-passordbehandleren KeePass.
Feilen ble ansett som viktig nok til å få en offisiell identifikator for amerikanske myndigheter (den er kjent som CVE-2023-32784, hvis du vil jakte på det), og gitt at hovedpassordet til passordbehandleren er ganske mye nøkkelen til hele det digitale slottet ditt, kan du forstå hvorfor historien provoserte mye spenning.
Den gode nyheten er at en angriper som ønsker å utnytte denne feilen, nesten helt sikkert må ha infisert datamaskinen din med skadelig programvare allerede, og vil derfor kunne spionere på tastetrykk og kjørende programmer uansett.
Med andre ord kan feilen betraktes som en letthåndterlig risiko inntil skaperen av KeePass kommer ut med en oppdatering, som skal vises snart (tilsynelatende i begynnelsen av juni 2023).
Som avslører av feilen passer på peke ut:
Hvis du bruker full diskkryptering med et sterkt passord og systemet ditt er [fritt for skadelig programvare], bør du ha det bra. Ingen kan stjele passordene dine eksternt over internett med dette funnet alene.
Risikoen forklart
Tungt oppsummert koker feilen ned til vanskeligheten med å sikre at alle spor av konfidensielle data blir slettet fra minnet når du er ferdig med dem.
Vi vil her ignorere problemene med hvordan man unngår å ha hemmelige data i minnet i det hele tatt, selv kort.
I denne artikkelen vil vi bare minne programmerere overalt om at koden godkjent av en sikkerhetsbevisst anmelder med en kommentar som "ser ut til å rydde opp riktig etter seg"...
...kan faktisk ikke rydde opp i det hele tatt, og den potensielle datalekkasjen er kanskje ikke åpenbar fra en direkte studie av selve koden.
Enkelt sagt betyr CVE-2023-32784-sårbarheten at et KeePass-hovedpassord kan gjenopprettes fra systemdata selv etter at KeyPass-programmet har avsluttet, fordi tilstrekkelig informasjon om passordet ditt (om enn ikke selve råpassordet, som vi vil fokusere på) på om et øyeblikk) kan bli etterlatt i systembytte- eller dvalefiler, der tildelt systemminne kan ende opp med å lagres til senere.
På en Windows-datamaskin der BitLocker ikke brukes til å kryptere harddisken når systemet er slått av, vil dette gi en kjeltring som stjal den bærbare datamaskinen en kjempesjanse til å starte opp fra en USB- eller CD-stasjon, og til og med gjenopprette hovedpassordet ditt. selv om KeyPass-programmet passer på å aldri lagre det permanent på disken.
En langsiktig passordlekkasje i minnet betyr også at passordet i teorien kan gjenopprettes fra en minnedump av KeyPass-programmet, selv om den dumpen ble hentet lenge etter at du skrev inn passordet, og lenge etter KeePass. selv hadde ikke lenger behov for å holde den rundt.
Det er klart at du bør anta at skadelig programvare som allerede er på systemet ditt kan gjenopprette nesten alle innskrevne passord via en rekke sanntids snooping-teknikker, så lenge de var aktive på det tidspunktet du skrev. Men du kan med rimelighet forvente at tiden din som er utsatt for fare vil være begrenset til den korte perioden med å skrive, ikke utvidet til mange minutter, timer eller dager etterpå, eller kanskje lenger, inkludert etter at du har slått av datamaskinen.
Hva blir igjen?
Vi tenkte derfor at vi skulle se på et høyt nivå på hvordan hemmelige data kan bli etterlatt i minnet på måter som ikke er direkte åpenbare fra koden.
Ikke bekymre deg hvis du ikke er en programmerer – vi holder det enkelt, og forklarer mens vi går.
Vi starter med å se på minnebruk og opprydding i et enkelt C-program som simulerer inntasting og midlertidig lagring av et passord ved å gjøre følgende:
- Tildeling av en dedikert del av minnet spesielt for å lagre passordet.
- Sette inn en kjent tekststreng slik at vi enkelt kan finne den i minnet om nødvendig.
- Legger til 16 pseudo-tilfeldige 8-biters ASCII-tegn fra området AP.
- Skriver ut den simulerte passordbufferen.
- Frigjør minnet i håp om å fjerne passordbufferen.
- avslutter programmet.
Svært forenklet kan C-koden se omtrent slik ut, uten feilkontroll, ved å bruke pseudo-tilfeldige tall av dårlig kvalitet fra C runtime-funksjonen rand()
, og ignorer eventuelle bufferoverløpskontroller (aldri gjør noe av dette i ekte kode!):
// 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);
Faktisk inkluderer koden vi til slutt brukte i testene våre noen ekstra biter og deler vist nedenfor, slik at vi kunne dumpe hele innholdet i vår midlertidige passordbuffer slik vi brukte den, for å lete etter uønsket eller gjenværende innhold.
Merk at vi bevisst dumper bufferen etter å ha ringt free()
, som teknisk sett er en bruk-etter-fri feil, men vi gjør det her som en snikende måte å se om noe kritisk blir igjen etter å ha levert bufferen tilbake, noe som kan føre til et farlig datalekkasjehull i det virkelige liv.
Vi har også satt inn to Waiting for [Enter]
spør inn i koden for å gi oss selv en sjanse til å lage minnedumper på nøkkelpunkter i programmet, og gir oss rådata for å søke senere, for å se hva som ble igjen mens programmet kjørte.
For å gjøre minnedumper bruker vi Microsoft Sysinternals verktøy procdump
med -ma
alternativ (dumpe alt minne), som unngår behovet for å skrive vår egen kode for å bruke Windows DbgHelp
systemet og det ganske komplisert MiniDumpXxxx()
funksjoner.
For å kompilere C-koden brukte vi vår egen lille og enkle konstruksjon av Fabrice Bellards gratis og åpen kildekode Tiny C Compiler, tilgjengelig for 64-bits Windows i kilde og binær form direkte fra vår GitHub-side.
Kopier og limbar tekst av all kildekoden avbildet i artikkelen vises nederst på siden.
Dette er hva som skjedde da vi kompilerte og kjørte testprogrammet:
C:UsersduckKEYPASS> petcc64 -stdinc -stdlib unl1.c Tiny C Compiler - Copyright (C) 2001-2023 Fabrice Bellard strippet ned av Paul Ducklin for bruk som læringsverktøy Versjon petcc64-0.9.27 [0006] - Genererer 64-bit Bare PEer -> 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 filstørrelse seksjon 1000 200 438 .text 2000 800 2ac .data 3000 c00 24 .pdata -------- ----------------------- <- unl1.exe (3584 byte) C:UsersduckKEYPASS> unl1.exe Dumper 'ny' buffer ved start 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 65d. exe.D 00F44B32: 00 513 0 72 69 76 65 72 44 61D 74 61A 3C 43 3 5E riverData=C:Win 57F69C6: 00 513F 0 64 6 77 73 5 53 79 73 74 65 C 6 33 dowsSystem32Dr 5F44D72: 32 00 513 0 69 76C 65 72 73 5 44 72 69 76 65 72 iversDriverData 44F61E74: 61 00 513 0 00F 45 46 43 5 34D 33 37 32 _ 3. 31F00: 46 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 47 3F49 6C 74A 65 F72 00C AC 51410B 6 65 netto ExplzV.< .K.. Full streng var: unlikelytextJHKNEJJCPOMDJHAN 74F20: 45 78E 70C 6 7B 56 4C 3 4 00 00 00 51390A 75 6B 6E unlikelytextJHKN 69F6A 65A 6F 79D 74 65 78A 74 4 48E 4 4 00 513 EJJCPOMDJHAN.eD 0F45B4 : 4 43 50 4 4 44 4 48 41 4D 00 65A 00C 44 00 513E riverData=C:Win 0F72C69: 76 65F 72 44 61C 74 61 3 43 3 5 d 57Dr 69F6D00: 513 0 64 6 77 73C 5 53 79 73 74 65 6 33 32 5 iversDriverData 44F72E32: 00 513 0 69 76F 65 72 73 5 44D 72 69 76 65 72 44F .EFC61_74F .EFC61=00F .EFC513_0F 00F .EFC45=46 43F 5 34 33 37 32F 3 31 00 46F 50 53 5F 4372 BROWSER_APP_PROF 1F00: 513 0C 42 52F 4 57 53 45 52E 5 41D 50 50E 5 50 52 ILE_STRING=Inter 4F46: 00E 51400 49 4A 45C C AC 5B 53 54 netto ExplzV.<.K.. Venter på at [ENTER] skal frigjøre buffer... Dumper buffer etter free() 52F49: A4 47 F3 49 6 74 65 72 00 51410 F6 65 74 20 45 78 .g......P...... 70F6A7: 56 4A 3A 4 00 00F 00D 51390 0A 67 5 00E 00 00 00 00 EJJCPOMDJHAN.eD 50F01B5: 00 00 00 00 00 00 513 0 45 4 4 43 50 4 4 44 4 48E riverData=C:Win 41F4C00: 65 00F 44 00 513C 0 72 69 76 65 72D 44 61 74C 61 3 dowsSystem43Dr 3F5D57: 69 6 00 513 0 64C 6 77 73 5 53 79 73 74 Driver:E Dato 65 6 33 32 5F 44 72 32 00 513D 0 69 76 65 72 73F .EFC_5=44.FPS_ 72F69F76: 65 72 44F 61 74 61 00 513F 0 00 45 46F 43 5 34F 33 Browser_APP_PROF 37F32: 3 31 00 E 46 50 53 5D 4372 1E 00 513 0 ILE_STRING=Inter 42F52: 4E 57 53 45 52 5 41 50C 50D 5 50 52D AC 4B 46 00 netto ExplM..MK. Venter på at [ENTER] skal avslutte main()... C:UsersduckKEYPASS>
I denne kjøringen gadd vi ikke ta noen prosessminnedumper, fordi vi kunne se med en gang fra utdataene at denne koden lekker data.
Rett etter å ha kalt Windows C runtime library-funksjonen malloc()
, kan vi se at bufferen vi får tilbake inkluderer det som ser ut som miljøvariabeldata som er igjen fra programmets oppstartskode, med de første 16 bytene tilsynelatende endret til å se ut som en slags gjenværende minneallokeringshode.
(Merk hvordan de 16 byte ser ut som to 8-byte minneadresser, 0xF55790
og 0xF50150
, som er henholdsvis rett etter og like før vår egen minnebuffer.)
Når passordet er ment å være i minnet, kan vi se hele strengen tydelig i bufferen, som vi forventer.
Men etter å ha ringt free()
, legg merke til hvordan de første 16 bytene av bufferen vår har blitt skrevet om med noe som ser ut som nærliggende minneadresser igjen, antagelig slik at minneallokatoren kan holde styr på blokker i minnet som den kan gjenbruke ...
… men resten av passordteksten som er slettet (de siste 12 tilfeldige tegnene EJJCPOMDJHAN
) har blitt etterlatt.
Ikke bare må vi administrere våre egne minneallokeringer og deallokeringer i C, vi må også sørge for at vi velger riktige systemfunksjoner for databuffere hvis vi ønsker å kontrollere dem nøyaktig.
For eksempel, ved å bytte til denne koden i stedet, får vi litt mer kontroll over hva som er i minnet:
Ved å bytte fra malloc()
og free()
for å bruke Windows-tildelingsfunksjonene på lavere nivå VirtualAlloc()
og VirtualFree()
direkte får vi bedre kontroll.
Imidlertid betaler vi en pris i hastighet, fordi hver samtale til VirtualAlloc()
gjør mer arbeid enn en oppfordring til malloc()
, som fungerer ved kontinuerlig å dele og underinndele en blokk med forhåndstildelt lavnivåminne.
Ved hjelp av VirtualAlloc()
gjentatte ganger for små blokker bruker også mer minne totalt sett, fordi hver blokk disket ut av VirtualAlloc()
bruker vanligvis et multiplum av 4KB minne (eller 2MB, hvis du bruker s.k. store minnesider), slik at bufferen på 128 byte ovenfor rundes opp til 4096 byte, og kaster bort de 3968 bytene på slutten av 4KB-minneblokken.
Men, som du kan se, blir minnet vi får tilbake automatisk tømt (satt til null), så vi kan ikke se hva som var der før, og denne gangen krasjer programmet når vi prøver å gjøre vår bruk-etter-fri triks, fordi Windows oppdager at vi prøver å kikke på minnet vi ikke lenger eier:
C:UsersduckKEYPASS> unl2 Dumper 'ny' buffer ved start 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 00 00 0000000000 0020 00 ................ 00EA00: 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 ................ 00EA0000000000: 0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA0000000000: 0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA0000000000: 0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA0000000000: 0070 00 00 00 00 00 00 00 00 00 00 00 00 ................ Hele strengen var: unlikelytextIBIPJPPHEOPOIDLL 00EA00: 00 00E 0000000000C 0080 00B 00 00C 00 00 00 00 00 00 00 00 00 unlikelytext 00IP 00A 00 00 0000000000 0000 75 6F 6 69F 6 65 6C 79C 74 65 78 74 JPPHEOPOIDLL.... 49EA42: 49 50 0000000000 0010 4 50 50 48 45 4 50 4 49 44 4 4 00 00 00 00 0000000000 0020 00 00 00 00 00 00 00 00 : 00 00 00 00 00 00 00 00 0000000000 0030 00 00 00 00 00 00 ................ 00EA00: 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 ................ 0000000000EA0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ............. ... Venter på at [ENTER] skal frigjøre buffer... Dumper buffer etter free() 0000000000EA0080: [Program avsluttet her fordi Windows fanget vår bruk-etter-fri]
Fordi minnet vi frigjorde, må tildeles på nytt VirtualAlloc()
før den kan brukes igjen, kan vi anta at den vil bli nullet før den resirkuleres.
Men hvis vi ønsket å forsikre oss om at den ble tømt, kunne vi kalle den spesielle Windows-funksjonen RtlSecureZeroMemory()
rett før du frigjør den, for å garantere at Windows vil skrive nuller i bufferen vår først.
Den relaterte funksjonen RtlZeroMemory()
, hvis du lurte, gjør en lignende ting, men uten garanti for å faktisk fungere, fordi kompilatorer har lov til å fjerne det som teoretisk overflødig hvis de merker at bufferen ikke brukes igjen etterpå.
Som du kan se, må vi passe mye på å bruke de riktige Windows-funksjonene hvis vi ønsker å minimere tiden som hemmeligheter lagret i minnet kan ligge rundt for senere.
I denne artikkelen skal vi ikke se på hvordan du forhindrer at hemmeligheter blir lagret ved et uhell i byttefilen din ved å låse dem inn i fysisk RAM. (Hint: VirtualLock()
er faktisk ikke nok alene.) Hvis du ønsker å vite mer om lavnivå Windows-minnesikkerhet, gi oss beskjed i kommentarfeltet, så vil vi se på det i en fremtidig artikkel.
Bruker automatisk minnebehandling
En fin måte å unngå å måtte allokere, administrere og deallokere minne selv er å bruke et programmeringsspråk som tar vare på malloc()
og free()
eller VirtualAlloc()
og VirtualFree()
, automatisk.
Skriptspråk som f.eks Perl, Python, Lua, Javascript og andre blir kvitt de vanligste minnesikkerhetsfeilene som plager C- og C++-koden, ved å spore minnebruk for deg i bakgrunnen.
Som vi nevnte tidligere, fungerer vår dårlig skrevne eksempel C-kode ovenfor fint nå, men bare fordi det fortsatt er et superenkelt program, med datastrukturer med fast størrelse, der vi kan bekrefte ved inspeksjon at vi ikke vil overskrive vår 128- byte buffer, og at det kun er én utførelsesbane som starter med malloc()
og avsluttes med en tilsvarende free()
.
Men hvis vi oppdaterte den for å tillate generering av passord med variabel lengde, eller la til tilleggsfunksjoner i genereringsprosessen, kan vi (eller den som vedlikeholder koden neste gang) lett ende opp med bufferoverløp, bruk-etter-frie feil eller minne som blir aldri frigjort og lar derfor hemmelige data henge lenge etter at de ikke lenger er nødvendige.
På et språk som Lua kan vi la Lua kjøre-tid-miljøet, som gjør det som er kjent på sjargongen som automatisk søppelhenting, håndtere å hente minne fra systemet og returnere det når det oppdager at vi har sluttet å bruke det.
C-programmet vi listet opp ovenfor blir veldig mye enklere når minnetildeling og deallokering blir tatt hånd om for oss:
Vi tildeler minne for å holde strengen s
ganske enkelt ved å tilordne strengen 'unlikelytext'
til det.
Vi kan senere enten hinte til Lua eksplisitt at vi ikke lenger er interessert i s
ved å tildele den verdien nil
(alle nils
er i hovedsak det samme Lua-objektet), eller slutte å bruke s
og vent til Lua oppdager at det ikke lenger er nødvendig.
Uansett, minnet som brukes av s
vil til slutt gjenopprettes automatisk.
Og for å forhindre bufferoverløp eller feilhåndtering av størrelse når du legger til tekststrenger (Lua-operatøren ..
, uttales concat, legger i hovedsak to strenger sammen, som +
i Python), hver gang vi utvider eller forkorter en streng, tildeler Lua på magisk vis plass til en helt ny streng, i stedet for å modifisere eller erstatte den originale på dens eksisterende minneplassering.
Denne tilnærmingen er tregere, og fører til minnebrukstopper som er høyere enn du ville fått i C på grunn av de mellomliggende strengene som tildeles under tekstmanipulering, men det er mye tryggere med tanke på bufferoverløp.
Men denne typen automatisk strenghåndtering (kjent på sjargongen som uforanderlighet, fordi strenger aldri får mutert, eller modifisert på plass, når de først er opprettet), bringer nye cybersikkerhetshodepine i seg selv.
Vi kjørte Lua-programmet ovenfor på Windows, opp til den andre pausen, like før programmet gikk ut:
C:UsersduckKEYPASS> lua s1.lua Hele strengen er: unlikelytextHLKONBOJILAGLNLN Venter på [ENTER] før du frigjør streng... Venter på [ENTER] før du avslutter...
Denne gangen tok vi en prosessminnedump, slik:
C:UsersduckKEYPASS> procdump -ma lua lua-s1.dmp ProcDump v11.0 - Sysinternals prosessdumpverktøy Copyright (C) 2009-2022 Mark Russinovich og Andrew Richards Sysinternals - www.sysinternals.com [00:00:00] Dump initiert: C:UsersduckKEYPASSlua-s1.dmp [1:00:00] Dump 00-skriving: Estimert dumpfilstørrelse er 1 MB. [10:00:00] Dump 00 fullført: 1 MB skrevet på 10 sekunder [0.1:00:00] Dumptelling nådd.
Så kjørte vi dette enkle skriptet, som leser dumpfilen inn igjen, finner overalt i minnet at den kjente strengen unlikelytext
dukket opp, og skriver den ut sammen med plasseringen i dumpfilen og ASCII-tegnene som umiddelbart fulgte:
Selv om du har brukt skriptspråk før, eller jobbet i et hvilket som helst programmeringsøkosystem som har såkalte administrerte strenger, hvor systemet holder styr på minneallokeringer og deallokeringer for deg, og håndterer dem slik det passer...
...du kan bli overrasket over å se utdataene som denne minneskanningen produserer:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp 006D8AFC: unlikelytextALJBNGOAPLLBDEB 006D8B3C: unlikelytextALJBNGOA 006D8B7C: unlikelytextALJBNGO 006D8BNGOAPLLBDEB: unlikelytextALJBNGO 006D8BNGOAPLLBDEB 006D8B7C: unlikelytextALJBNGOA 006D903B006C: unlikelytextALJBNGO 90D006BNGOAPLLBDEB textALJBN 90D006D913C: unlikelytextALJBNGOAP 006D91C: unlikelytextALJBNGOAPL 006D91BC: unlikelytextALJBNGOAPLL 006D923FC: unlikelytextALJBNG 006D70C: unlikelytextALJBNGOAPL 006D8BC: unlikelytextALJBNGOAPLL 006D0FC: usannsynligtekstALJBNG XNUMXDXNUMXC: unlikely XNUMXDXNUMXFC: usannsynlig tekstALJBNGOAPLLBD XNUMXDXNUMXC : unlikelytextALJBNGOAPLLBDE XNUMXDBXNUMXC: unlikelytextALJ XNUMXDBBXNUMXC: unlikelytextAL XNUMXDBDXNUMXC: unlikelytextA
Se, på den tiden tok vi minnedumpen vår, selv om vi var ferdige med strengen s
(og sa til Lua at vi ikke trengte det mer ved å si s = nil
), alle strengene som koden hadde opprettet underveis, var fortsatt til stede i RAM, ennå ikke gjenopprettet eller slettet.
Faktisk, hvis vi sorterer utdataene ovenfor etter strengene selv, i stedet for å følge rekkefølgen de dukket opp i RAM, vil du kunne se for deg hva som skjedde under loopen der vi koblet sammen ett tegn om gangen til passordstrengen vår:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp | sorter /+10 006DBD0C: unlikelytextA 006DBB8C: unlikelytextAL 006DB70C: unlikelytextALJ 006D91BC: unlikelytextALJB 006D8CBC: unlikelytextALJBN 006D90FC: unlikelytextALJ 006BD8Nlikely 7B006C: unlikelytextALJBNGOA 8D3D006C: unlikelytextALJBNGOAP 8D7C: unlikelytextALJBNGOAPL 006D903BC: unlikelytextALJBNGOAPLL 006D90C: unlikelytextALJBNGOAPLLB006BNGOAPLL913BNGOAPLL006BNGOAPLL91BNGOAPLL006BNGOAPLL923 006C: unlikelytextALJBNGOAPLLBDE 8D006AFC: unlikelytextALJBNGOAPLLBDEB 8DXNUMXBFC : usannsynligtekstALJBNGOAPLLBDEBJ
Alle de midlertidige, mellomliggende strengene er fortsatt der, så selv om vi hadde klart å slette den endelige verdien av s
, ville vi fortsatt lekke alt bortsett fra den siste karakteren.
Faktisk, i dette tilfellet, selv når vi bevisst tvang programmet vårt til å kaste all unødvendig data ved å ringe den spesielle Lua-funksjonen collectgarbage()
(de fleste skriptspråk har noe lignende), mesteparten av dataene i de irriterende midlertidige strengene satt fast i RAM uansett, fordi vi hadde kompilert Lua for å gjøre dens automatiske minnebehandling ved å bruke gode gamle malloc()
og free()
.
Med andre ord, selv etter at Lua selv tok tilbake sine midlertidige minneblokker for å bruke dem igjen, kunne vi ikke kontrollere hvordan eller når disse minneblokkene ville bli gjenbrukt, og dermed hvor lenge de ville ligge inne i prosessen med venstre- over data som venter på å bli snuset opp, dumpet eller på annen måte lekket.
Skriv inn .NET
Men hva med KeePass, som er der denne artikkelen startet?
KeePass er skrevet i C#, og bruker .NET-runtime, så det unngår problemene med feilhåndtering av minnet som C-programmer fører med seg...
…men C# administrerer sine egne tekststrenger, akkurat som Lua gjør, noe som reiser spørsmålet:
Selv om programmereren unngikk å lagre hele hovedpassordet på ett sted etter at han var ferdig med det, kunne angripere med tilgang til en minnedump likevel finne nok gjenværende midlertidige data til å gjette på eller gjenopprette hovedpassordet uansett, selv om de angripere fikk tilgang til datamaskinen din minutter, timer eller dager etter at du skrev inn passordet?
Enkelt sagt, er det gjenkjennelige, spøkelsesaktige rester av hovedpassordet ditt som overlever i RAM, selv etter at du forventer at de skulle ha blitt slettet?
Irriterende, som Github-bruker Vdohney oppdaget, svaret (i det minste for KeePass-versjoner tidligere enn 2.54) er "Ja."
For å være tydelig, tror vi ikke at det faktiske hovedpassordet ditt kan gjenopprettes som en enkelt tekststreng fra en KeePass-minnedump, fordi forfatteren opprettet en spesiell funksjon for inntasting av hovedpassord som gjør alt for å unngå å lagre hele passordet. passord der det lett kunne oppdages og snuses opp.
Vi tilfredsstilte oss med dette ved å sette hovedpassordet vårt til SIXTEENPASSCHARS
, skrive det inn, og deretter ta minnedumper umiddelbart, kort tid og lenge etterpå.
Vi søkte i dumpene med et enkelt Lua-skript som lette overalt etter den passordteksten, både i 8-bits ASCII-format og i 16-biters UTF-16 (Windows widechar) format, som dette:
Resultatene var oppmuntrende:
C:UsersduckKEYPASS> lua searchknown.lua kp2-post.dmp Leser i dumpfil... FERDIG. Søker etter SIXTEENPASSCHARS som 8-bit ASCII... ikke funnet. Søker etter SIXTEENPASSCHARS som UTF-16... ikke funnet.
Men Vdohney, oppdageren av CVE-2023-32784, la merke til at mens du skriver inn hovedpassordet ditt, gir KeePass deg visuell tilbakemelding ved å konstruere og vise en plassholderstreng bestående av Unicode "blob"-tegn, opp til og inkludert lengden på passord:
I widechar tekststrenger på Windows (som består av to byte per tegn, ikke bare én byte hver som i ASCII), er "blob"-tegnet kodet i RAM som hex-byte 0xCF
etterfulgt av 0x25
(som tilfeldigvis er et prosenttegn i ASCII).
Så selv om KeePass er veldig forsiktig med de rå tegnene du skriver inn når du skriver inn selve passordet, kan du ende opp med gjenværende strenger med "blob"-tegn, lett gjenkjennelige i minnet som gjentatte kjøringer som f.eks. CF25CF25
or CF25CF25CF25
...
…og i så fall ville den lengste serien med blob-tegn du fant sannsynligvis gi bort lengden på passordet ditt, som ville være en beskjeden form for passordinformasjonslekkasje, om ikke annet.
Vi brukte følgende Lua-skript for å se etter tegn på gjenværende passordplassholderstrenger:
Utgangen var overraskende (vi har slettet påfølgende linjer med samme antall blobs, eller med færre blobs enn forrige linje, for å spare plass):
C:UsersduckKEYPASS> lua findblobs.lua kp2-post.dmp 000EFF3C: * [. . .] 00BE621B: ** 00BE64C7: *** [. . .] 00BE6E8F: **** [. . .] 00BE795F: ***** [. . .] 00BE84F7: ****** [. . .] 00BE8F37: ******* [ fortsetter på samme måte for 8 blobs, 9 blobs osv. ] [ inntil to siste linjer med nøyaktig 16 blobs hver ] 00C0503B: ************* *** 00C05077: **************** 00C09337: * 00C09738: * [ alle gjenværende kamper er en blob lange] 0123B058: *
Ved tett sammen men stadig økende minneadresser fant vi en systematisk liste med 3 blobs, deretter 4 blobs, og så videre opptil 16 blobs (lengden på passordet vårt), etterfulgt av mange tilfeldig spredte forekomster av enkeltblob-strenger .
Så det ser ut til at disse plassholder-"blob"-strengene lekker inn i minnet og blir igjen for å lekke passordlengden, lenge etter at KeePass-programvaren er ferdig med hovedpassordet ditt.
Det neste steget
Vi bestemte oss for å grave videre, akkurat som Vdohney gjorde.
Vi endret mønstermatchingskoden vår for å oppdage kjeder av blob-tegn etterfulgt av et enkelt ASCII-tegn i 16-bits format (ASCII-tegn er representert i UTF-16 som deres vanlige 8-bits ASCII-kode, etterfulgt av en null byte).
Denne gangen, for å spare plass, har vi undertrykt utdataene for alle treff som samsvarer nøyaktig med den forrige:
Overraskelse, overraskelse:
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
Se hva vi får ut av .NETs administrerte strengminneregion!
Et tett sammensatt sett med midlertidige "blob-strenger" som avslører de påfølgende tegnene i passordet vårt, og starter med det andre tegnet.
De lekke strengene følges av vidt distribuerte enkeltkaraktermatcher som vi antar oppsto ved en tilfeldighet. (En KeePass-dumpfil er omtrent 250 MB stor, så det er god plass til at "blob"-tegn vises som ved flaks.)
Selv om vi tar de fire ekstra treffene i betraktning, i stedet for å forkaste dem som sannsynlige feil, kan vi gjette at hovedpassordet er ett av:
?IXTENPASSCHARS ?NXTEENPASSCHARS ?WXTEENPASSCHARS ?SXTEENPASSCHARS
Tydeligvis finner ikke denne enkle teknikken det første tegnet i passordet, fordi den første "blob-strengen" kun er konstruert etter at det første tegnet er skrevet inn
Merk at denne listen er fin og kort fordi vi filtrerte ut treff som ikke endte på ASCII-tegn.
Hvis du lette etter tegn i et annet område, for eksempel kinesiske eller koreanske tegn, kan du ende opp med flere tilfeldige treff, fordi det er mange flere mulige tegn å matche på...
…men vi mistenker at du uansett vil komme ganske nær hovedpassordet ditt, og «blob-strengene» som er knyttet til passordet ser ut til å være gruppert i RAM, antagelig fordi de ble tildelt omtrent samtidig av samme del av .NET-kjøringen.
Og der, i et riktignok langt og diskursivt nøtteskall, er den fascinerende historien om CVE-2023-32784.
Hva gjør jeg?
- Hvis du er en KeePass-bruker, ikke få panikk. Selv om dette er en feil, og teknisk sett er en utnyttbar sårbarhet, må eksterne angripere som ønsker å knekke passordet ditt ved å bruke denne feilen først implantere skadevare på datamaskinen din. Det ville gi dem mange andre måter å stjele passordene dine direkte på, selv om denne feilen ikke fantes, for eksempel ved å logge tastetrykkene dine mens du skriver. På dette tidspunktet kan du bare se opp for den kommende oppdateringen, og ta den når den er klar.
- Hvis du ikke bruker full-disk-kryptering, bør du vurdere å aktivere det. For å trekke ut gjenværende passord fra swap-filen eller dvalefilen (operativsystemdiskfiler som brukes til å lagre minneinnhold midlertidig under tung belastning eller når datamaskinen "sover"), vil angripere trenge direkte tilgang til harddisken. Hvis du har BitLocker eller tilsvarende for andre operativsystemer aktivert, vil de ikke ha tilgang til byttefilen din, dvalefilen din eller andre personlige data som dokumenter, regneark, lagrede e-poster og så videre.
- Hvis du er en programmerer, hold deg informert om problemer med minneadministrasjon. Ikke anta at bare fordi hver
free()
samsvarer med dens tilsvarendemalloc()
at dataene dine er trygge og godt administrert. Noen ganger kan det hende du må ta ekstra forholdsregler for å unngå å etterlate hemmelige data liggende, og disse forholdsreglene veldig fra operativsystem til operativsystem. - Hvis du er en QA-tester eller en kodeanmelder, tenk alltid "bak kulissene". Selv om minneadministrasjonskoden ser ryddig og velbalansert ut, vær oppmerksom på hva som skjer bak kulissene (fordi den opprinnelige programmereren kanskje ikke visste å gjøre det), og gjør deg klar til å gjøre litt pentesting-stil arbeid som kjøretidsovervåking og minne dumping for å bekrefte at sikker kode virkelig oppfører seg som den skal.
KODE FRA ARTIKKELEN: UNL1.C
#inkludere #inkludere #inkludere void hexdump(usignert char* buff, int len) { // Skriv ut buffer i 16-byte biter for (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Vis 16 byte som hex-verdier for (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Gjenta disse 16 bytene som tegn for (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) { // Skaff minne for å lagre passord, og vis hva // er i bufferen når den offisielt er "ny"... char* buff = malloc(128); printf("Dumper 'ny' buffer ved start"); hexdump(buff,128); // Bruk pseudotilfeldig bufferadresse som tilfeldig frø srand((usignert)buff); // Start passordet med en fast, søkbar tekst strcpy(buff,"unlikelytext"); // Legg til 16 pseudotilfeldige bokstaver, én om gangen for (int i = 1; i <= 16; i++) { // Velg en bokstav fra A (65+0) til P (65+15) char ch = 65 + (rand() & 15); // Deretter endre buff-strengen på plass strncat(buff,&ch,1); } // Det fullstendige passordet er nå i minnet, så skriv ut // det som en streng, og vis hele bufferen... printf("Full streng var: %sn",buff); hexdump(buff,128); // Pause for å dumpe prosess-RAM nå (prøv: 'procdump -ma') puts("Venter på at [ENTER] skal frigjøre buffer..."); getchar(); // Formelt frigjør() minnet og vis bufferen // igjen for å se om noe var igjen... free(buff); printf("Dumping buffer etter free()n"); hexdump(buff,128); // Pause for å dumpe RAM igjen for å inspisere forskjeller puts("Waiting for [ENTER] to exit main(..."); getchar(); returner 0; }
KODE FRA ARTIKKELEN: UNL2.C
#inkludere #inkludere #inkludere #inkludere void hexdump(usignert char* buff, int len) { // Skriv ut buffer i 16-byte biter for (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Vis 16 byte som hex-verdier for (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Gjenta disse 16 bytene som tegn for (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) { // Skaff minne for å lagre passord, og vis hva // er i bufferen når den offisielt er "ny"... char* buff = VirtualAlloc(0,128,MEM_COMMIT,PAGE_READWRITE); printf("Dumper 'ny' buffer ved start"); hexdump(buff,128); // Bruk pseudotilfeldig bufferadresse som tilfeldig frø srand((usignert)buff); // Start passordet med en fast, søkbar tekst strcpy(buff,"unlikelytext"); // Legg til 16 pseudotilfeldige bokstaver, én om gangen for (int i = 1; i <= 16; i++) { // Velg en bokstav fra A (65+0) til P (65+15) char ch = 65 + (rand() & 15); // Deretter endre buff-strengen på plass strncat(buff,&ch,1); } // Det fullstendige passordet er nå i minnet, så skriv ut // det som en streng, og vis hele bufferen... printf("Full streng var: %sn",buff); hexdump(buff,128); // Pause for å dumpe prosess-RAM nå (prøv: 'procdump -ma') puts("Venter på at [ENTER] skal frigjøre buffer..."); getchar(); // Formelt frigjør() minnet og vis bufferen // igjen for å se om noe var igjen... VirtualFree(buff,0,MEM_RELEASE); printf("Dumping buffer etter free()n"); hexdump(buff,128); // Pause for å dumpe RAM igjen for å inspisere forskjeller puts("Waiting for [ENTER] to exit main(..."); getchar(); returner 0; }
KODE FRA ARTIKKELEN: S1.LUA
-- Start med en fast, søkbar tekst s = 'unlikelytext' -- Legg til 16 tilfeldige tegn fra 'A' til 'P' for i = 1,16 do s = s .. string.char(65+math.random( 0,15)) end print('Full streng er:',s,'n') -- Pause for å dumpe prosess RAM-utskrift('Venter på [ENTER] før du frigjør streng...') io.read() - - Tørk av streng og merk variabel ubrukt s = null -- Dump RAM igjen for å se etter diffs print('Venter på [ENTER] før du avslutter...') io.read()
KODE FRA ARTIKKELEN: FINDIT.LUA
-- les i dumpfil lokal f = io.open(arg[1],'rb'):read('*a') -- se etter markørtekst etterfulgt av ett -- eller flere tilfeldige ASCII-tegn lokale b,e ,m = 0,0,nil mens sant gjør -- se etter neste treff og husk offset b,e,m = f:find('(unlikelytext[AZ]+)',e+1) -- avslutt når ikke mer samsvarer hvis ikke b, så bryte slutt -- rapportposisjon og streng funnet print(string.format('%08X: %s',b,m)) end
KODE FRA ARTIKKELEN: SEARCHKNOWN.LUA
io.write('Leser i dumpfil... ') local f = io.open(arg[1],'rb'):read('*a') io.write('DONE.n') io. write('Søker etter SIXTEENPASSCHARS som 8-bit ASCII... ') local p08 = f:find('SEXTEENPASSCHARS') io.write(p08 og 'FOUND' eller 'not found','.n') io.write ('Søker etter SIXTEENPASSCHARS som UTF-16... ') local p16 = f:find('Sx00Ix00Xx00Tx00Ex00Ex00Nx00Px00'.. 'Ax00Sx00Sx00Cx00Hx00Ax00Rx00Sx, 'Funnet' eller 'Ikke funnet,'FOUND,'00Sx16RxXNUMXSx,'XNUMX. '.n')
KODE FRA ARTIKKELEN: FINDBLOBS.LUA
-- les inn dump-fil spesifisert på kommandolinjen lokal f = io.open(arg[1],'rb'):read('*a') -- Se etter en eller flere passord-blobber, etterfulgt av en ikke-blob -- Merk at blob-tegn (●) koder inn i Windows widechars -- som litte-endian UTF-16-koder, som kommer ut som CF 25 i hex. local b,e,m = 0,0,nil mens true do -- Vi vil ha en eller flere blobs, etterfulgt av en ikke-blob. -- Vi forenkler koden ved å se etter en eksplisitt CF25 -- etterfulgt av en hvilken som helst streng som bare har CF eller 25 i den, -- så vi finner CF25CFCF eller CF2525CF samt CF25CF25. -- Vi filtrerer ut "falske positive" senere hvis det er noen. -- Vi må skrive '%%' i stedet for x25 fordi x25 ---tegnet (prosenttegn) er et spesielt søketegn i Lua! b,e,m = f:find('(xCF%%[xCF%%]*)',e+1) -- avslutt når det ikke er flere treff hvis ikke b så bryte slutt -- CMD.EXE kan ikke skrive ut blobs, så vi konverterer dem til stjerner. print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) slutt
KODE FRA ARTIKKELEN: SEARCHKP.LUA
-- les inn dumpfil spesifisert på kommandolinjen lokal f = io.open(arg[1],'rb'):read('*a') lokal b,e,m,p = 0,0,nil,nil mens sant gjør -- Nå vil vi ha en eller flere blobs (CF25) etterfulgt av koden -- for A..Z etterfulgt av en 0 byte for å konvertere ACSCII til UTF-16 b,e,m = f:find(' (xCF%%[xCF%%]*[AZ])x00',e+1) -- avslutt når det ikke er flere treff hvis ikke b så bryte slutt -- CMD.EXE kan ikke skrive ut blobs, så vi konverterer dem til stjerner. -- For å spare plass undertrykker vi påfølgende treff hvis m ~= p deretter print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) p = m slutt slutt
- SEO-drevet innhold og PR-distribusjon. Bli forsterket i dag.
- PlatoAiStream. Web3 Data Intelligence. Kunnskap forsterket. Tilgang her.
- Minting the Future med Adryenn Ashley. Tilgang her.
- Kjøp og selg aksjer i PRE-IPO-selskaper med PREIPO®. Tilgang her.
- kilde: https://nakedsecurity.sophos.com/2023/05/31/serious-security-that-keepass-master-password-crack-and-what-we-can-learn-from-it/
- : har
- :er
- :ikke
- :hvor
- ][s
- $OPP
- 1
- 10
- 12
- 15%
- 20
- 200
- 2023
- 24
- 250
- 27
- 31
- 3d
- 49
- 50
- 67
- 70
- 72
- 77
- 8
- 9
- a
- I stand
- Om oss
- ovenfor
- Absolute
- AC
- adgang
- Logg inn
- erverve
- anskaffe
- aktiv
- faktiske
- faktisk
- la til
- Ytterligere
- adresse
- adresser
- Legger
- Etter
- etterpå
- en gang til
- Alle
- allokert
- Tildeler
- allokering
- bevilgninger
- tillate
- alene
- langs
- allerede
- også
- endret
- Selv
- alltid
- an
- og
- Andrew
- besvare
- noen
- hva som helst
- noe kritisk
- vises
- dukket opp
- tilnærming
- godkjent
- ER
- rundt
- Artikkel
- artikler
- AS
- At
- forfatter
- auto
- Automatisk
- automatisk
- tilgjengelig
- unngå
- unngås
- klar
- borte
- tilbake
- bakgrunn
- background-image
- BE
- fordi
- blir
- vært
- før du
- Begynnelsen
- bak
- Bak scenen
- under
- Bedre
- Bit
- Blokker
- Blocks
- grensen
- både
- Bunn
- merke
- Brand New
- Break
- kort
- bringe
- buffer
- bufferoverløp
- Bug
- bugs
- bygge
- men
- by
- C + +
- ring
- ringer
- CAN
- Kan få
- hvilken
- saken
- fanget
- CD
- sentrum
- Gjerne
- kjeder
- sjanse
- endret
- karakter
- tegn
- kontroll
- Sjekker
- Kinesisk
- Velg
- fjerne
- klart
- Lukke
- kode
- farge
- COM
- kommer
- kommer
- kommentere
- kommentarer
- Felles
- fullføre
- komplekse
- datamaskin
- Vurder
- betydelig
- ansett
- Består
- konstruere
- innhold
- innhold
- kontinuerlig
- fortsetter
- kontroll
- konvertere
- copyright
- Tilsvarende
- kunne
- dekke
- crack
- skape
- opprettet
- skaperen
- kritisk
- Cybersecurity
- FARE
- Dangerous
- dato
- datalekkasje
- Dager
- avtale
- besluttet
- dedikert
- beskrevet
- gJORDE
- forskjeller
- forskjellig
- Vanskelighetsgrad
- DIG
- digitalt
- direkte
- Direkte adgang
- direkte
- Vise
- visning
- har
- do
- dokumenter
- gjør
- ikke
- gjør
- gjort
- ikke
- ned
- stasjonen
- to
- dumpe
- under
- e
- hver enkelt
- Tidligere
- lett
- økosystem
- enten
- ellers
- e-post
- muliggjør
- oppmuntrende
- kryptering
- slutt
- slutter
- nok
- sikre
- sikrer
- Enter
- går inn
- Hele
- entry
- Miljø
- Tilsvarende
- feil
- hovedsak
- anslått
- etc
- Eter (ETH)
- Selv
- etter hvert
- stadig økende
- Hver
- alt
- nøyaktig
- eksempel
- Unntatt
- Kjøreglede
- gjennomføring
- eksisterer
- eksisterende
- Utgang
- avslutter
- forvente
- Forklar
- Exploit
- utsatt
- utvide
- ekstra
- trekke ut
- Faktisk
- falsk
- fascinerende
- Egenskaper
- tilbakemelding
- færre
- slåss
- filet
- Filer
- filtrere
- slutt~~POS=TRUNC
- Endelig
- Finn
- finne
- funn
- slutt
- Først
- fikset
- Fokus
- fulgt
- etter
- Til
- skjema
- Formelt
- format
- kommende
- funnet
- fire
- Gratis
- fra
- fullt
- fullt
- funksjon
- funksjoner
- videre
- framtid
- genererer
- generasjonen
- få
- få
- GitHub
- Gi
- gitt
- gir
- Giving
- Go
- Går
- skal
- god
- Regjeringen
- grip
- flott
- garantere
- HAD
- Håndterer
- skjedde
- Skjer
- skjer
- Hard
- Ha
- å ha
- hodepine
- tung
- høyde
- her.
- HEX
- høyt nivå
- høyere
- Hits
- hold
- Hole
- håp
- TIMER
- hover
- Hvordan
- Hvordan
- HTTPS
- jakten
- i
- identifikator
- if
- umiddelbart
- viktig
- in
- inkluderer
- Inkludert
- informasjon
- informert
- i stedet
- interessert
- Mellom
- Internet
- inn
- saker
- IT
- DET ER
- selv
- sjargong
- juni
- bare
- bare én
- Hold
- nøkkel
- Vet
- kjent
- Koreansk
- Språk
- språk
- laptop
- Siste
- seinere
- føre
- Fører
- lekke
- Lekkasjer
- LÆRE
- læring
- minst
- forlater
- venstre
- Lengde
- brev
- Bibliotek
- Life
- i likhet med
- Sannsynlig
- Begrenset
- linje
- linjer
- Liste
- oppført
- ll
- laste
- lokal
- plassering
- logging
- Lang
- langsiktig
- lenger
- Se
- ser ut som
- så
- ser
- UTSEENDE
- Lot
- flaks
- opprettholder
- gjøre
- malware
- administrer
- fikk til
- ledelse
- leder
- forvalter
- Manipulasjon
- mange
- Margin
- merke
- markør
- Master
- Match
- matchende
- max bredde
- Kan..
- midler
- Minne
- nevnt
- Microsoft
- kunne
- minutter
- beskjeden
- modifisert
- modifisere
- øyeblikk
- overvåking
- mer
- mest
- mye
- flere
- Ryddig
- Trenger
- nødvendig
- nett
- aldri
- likevel
- Ny
- nyheter
- neste
- fint
- Nei.
- normal
- ingenting
- Legge merke til..
- nå
- Antall
- tall
- objekt
- Åpenbare
- of
- off
- offisiell
- offisielt
- offset
- Gammel
- on
- gang
- ONE
- bare
- åpen kildekode
- drift
- operativsystem
- operativsystemer
- operatør
- Alternativ
- or
- rekkefølge
- original
- Annen
- andre
- ellers
- vår
- oss selv
- ut
- produksjon
- enn
- samlet
- egen
- side
- Panic
- del
- Passord
- Password Manager
- passord
- banen
- Mønster
- paul
- pause
- Betale
- prosent
- kanskje
- perioden
- permanent
- personlig
- personlig informasjon
- fysisk
- bilde
- stykker
- Sted
- placeholder
- Pest
- plato
- Platon Data Intelligence
- PlatonData
- Plenty
- Point
- poeng
- Populær
- posisjon
- mulig
- innlegg
- potensiell
- nettopp
- presentere
- pen
- forebygge
- forrige
- pris
- Skrive ut
- utskrifter
- sannsynligvis
- problemer
- prosess
- program
- Programmerer
- programmerere
- Programmering
- programmer
- uttales
- sette
- Python
- Q & A
- spørsmål
- hever
- RAM
- tilfeldig
- område
- heller
- Raw
- rådata
- RE
- nådd
- Lese
- Lesning
- klar
- ekte
- ekte liv
- sanntids
- virkelig
- kjenne igjen
- Gjenopprette
- utvinne
- i slekt
- gjenværende
- husker
- fjernkontroll
- fjerne
- gjenta
- gjentatt
- GJENTATTE GANGER
- rapporterer
- representert
- respekt
- henholdsvis
- REST
- Resultater
- retur
- retur
- avsløre
- Kvitt
- ikke sant
- Risiko
- risikoer
- rom
- Kjør
- rennende
- kjøretidsovervåking
- s
- trygge
- sikrere
- samme
- fornøyd
- Spar
- sier
- skanne
- spredt
- Scener
- Søk
- søker
- Sekund
- sekunder
- Secret
- Seksjon
- sikre
- sikkerhet
- se
- seed
- se
- synes
- sett
- Sees
- Serien
- alvorlig
- sett
- innstilling
- Kort
- Om kort tid
- bør
- Vis
- vist
- undertegne
- Skilt
- lignende
- på samme måte
- Enkelt
- forenklet
- forenkle
- ganske enkelt
- enkelt
- Størrelse
- sove
- liten
- Sneaky
- snooping
- So
- Software
- solid
- noen
- noe
- Snart
- kilde
- kildekoden
- Rom
- spesiell
- spesielt
- spesifisert
- fart
- Stjerner
- Begynn
- startet
- Start
- starter
- oppstart
- Still
- stjal
- Stopp
- stoppet
- oppbevare
- lagret
- Story
- String
- sterk
- Studer
- vellykket
- slik
- tilstrekkelig
- ment
- overraskelse
- overrasket
- overrask
- overleve
- SVG
- swap
- system
- Systemer
- Ta
- tatt
- tar
- ta
- snakker
- teknisk sett
- teknikker
- midlertidig
- test
- tester
- enn
- Det
- De
- Kilden
- deres
- Dem
- seg
- deretter
- teori
- Der.
- derfor
- de
- ting
- tror
- denne
- De
- selv om?
- trodde
- tid
- Tittel
- til
- sammen
- tok
- verktøy
- topp
- spor
- Sporing
- overgang
- gjennomsiktig
- sant
- prøve
- snudde
- to
- typen
- typisk
- forstå
- unicode
- til
- ubrukt
- uønsket
- Oppdater
- oppdatert
- URL
- us
- oss regjering
- bruk
- usb
- bruke
- bruk-etter-fri
- brukt
- Bruker
- bruker
- ved hjelp av
- verktøyet
- verdi
- Verdier
- variasjon
- verifisere
- versjon
- veldig
- av
- sårbarhet
- W
- vente
- venter
- ønsker
- ønsket
- var
- Se
- Vei..
- måter
- we
- uker
- VI VIL
- var
- Hva
- når
- om
- hvilken
- mens
- HVEM
- hvem som helst
- hele
- hvorfor
- vil
- vinne
- vinduer
- tørke
- med
- uten
- lurer
- ord
- Arbeid
- arbeidet
- arbeid
- virker
- bekymring
- ville
- ville gitt
- skrive
- skriving
- skrevet
- ennå
- du
- Din
- deg selv
- zephyrnet
- null