Τις τελευταίες δύο εβδομάδες, είδαμε μια σειρά άρθρων που αναφέρουν αυτό που περιγράφεται ως «κύριο σπάσιμο κωδικού πρόσβασης» στο δημοφιλές λογισμικό διαχείρισης κωδικών πρόσβασης ανοιχτού κώδικα KeePass.
Το σφάλμα θεωρήθηκε αρκετά σημαντικό για να αποκτήσει ένα επίσημο αναγνωριστικό της κυβέρνησης των ΗΠΑ (είναι γνωστό ως CVE-2023-32784, αν θέλετε να το κυνηγήσετε) και δεδομένου ότι ο κύριος κωδικός πρόσβασης στον διαχειριστή κωδικών πρόσβασης είναι σχεδόν το κλειδί για ολόκληρο το ψηφιακό σας κάστρο, μπορείτε να καταλάβετε γιατί η ιστορία προκάλεσε πολύ ενθουσιασμό.
Τα καλά νέα είναι ότι ένας εισβολέας που ήθελε να εκμεταλλευτεί αυτό το σφάλμα θα έπρεπε σχεδόν σίγουρα να έχει μολύνει τον υπολογιστή σας με κακόβουλο λογισμικό και, επομένως, θα μπορεί να κατασκοπεύει τις πληκτρολογήσεις και τα προγράμματα που εκτελούνται ούτως ή άλλως.
Με άλλα λόγια, το σφάλμα μπορεί να θεωρηθεί ένας εύκολα διαχειριζόμενος κίνδυνος μέχρι ο δημιουργός του KeePass να κυκλοφορήσει με μια ενημέρωση, η οποία θα εμφανιστεί σύντομα (προφανώς στις αρχές Ιουνίου 2023).
Όπως φροντίζει ο αποκαλυπτής του σφάλματος επισημαίνω:
Εάν χρησιμοποιείτε πλήρη κρυπτογράφηση δίσκου με ισχυρό κωδικό πρόσβασης και το σύστημά σας είναι [απαλλαγμένο από κακόβουλο λογισμικό], θα πρέπει να είστε εντάξει. Κανείς δεν μπορεί να κλέψει τους κωδικούς πρόσβασής σας εξ αποστάσεως μέσω του Διαδικτύου μόνο με αυτό το εύρημα.
Οι κίνδυνοι εξηγούνται
Συνοψίζοντας σε μεγάλο βαθμό, το σφάλμα συνοψίζεται στη δυσκολία διασφάλισης ότι όλα τα ίχνη εμπιστευτικών δεδομένων αφαιρούνται από τη μνήμη μόλις τελειώσετε με αυτά.
Θα αγνοήσουμε εδώ τα προβλήματα σχετικά με το πώς να αποφύγουμε την ύπαρξη μυστικών δεδομένων στη μνήμη καθόλου, έστω και για λίγο.
Σε αυτό το άρθρο, θέλουμε απλώς να υπενθυμίσουμε στους προγραμματιστές παντού ότι ο κώδικας έχει εγκριθεί από έναν αναθεωρητή με γνώμονα την ασφάλεια με ένα σχόλιο όπως "φαίνεται να καθαρίζεται σωστά μετά τον εαυτό του"…
…μπορεί στην πραγματικότητα να μην καθαριστεί πλήρως και η πιθανή διαρροή δεδομένων μπορεί να μην είναι προφανής από μια άμεση μελέτη του ίδιου του κώδικα.
Με απλά λόγια, η ευπάθεια CVE-2023-32784 σημαίνει ότι ένας κύριος κωδικός πρόσβασης KeePass μπορεί να είναι ανακτήσιμος από τα δεδομένα συστήματος ακόμη και μετά την έξοδο του προγράμματος KeyPass, επειδή επαρκείς πληροφορίες σχετικά με τον κωδικό πρόσβασής σας (αν και όχι ο ίδιος ο ακατέργαστος κωδικός πρόσβασης, τον οποίο θα εστιάσουμε ενεργοποιείται σε μια στιγμή) μπορεί να μείνει πίσω σε αρχεία ανταλλαγής συστήματος ή ύπνου, όπου η εκχωρημένη μνήμη συστήματος μπορεί να καταλήξει να αποθηκευτεί για αργότερα.
Σε έναν υπολογιστή με Windows όπου το BitLocker δεν χρησιμοποιείται για την κρυπτογράφηση του σκληρού δίσκου όταν το σύστημα είναι απενεργοποιημένο, αυτό θα έδινε σε έναν απατεώνα που έκλεψε τον φορητό υπολογιστή σας την ευκαιρία να εκκινήσει από μια μονάδα USB ή CD και να ανακτήσει τον κύριο κωδικό πρόσβασης ακόμη και αν και το ίδιο το πρόγραμμα KeyPass φροντίζει να μην το αποθηκεύσει ποτέ μόνιμα στο δίσκο.
Μια μακροπρόθεσμη διαρροή κωδικού πρόσβασης στη μνήμη σημαίνει επίσης ότι ο κωδικός πρόσβασης θα μπορούσε, θεωρητικά, να ανακτηθεί από μια ένδειξη αποθήκευσης μνήμης του προγράμματος KeyPass, ακόμα κι αν αυτή η ένδειξη καταγράφηκε πολύ μετά την πληκτρολόγηση του κωδικού πρόσβασης και πολύ μετά το KeePass ο ίδιος δεν είχε πια ανάγκη να το κρατήσει γύρω του.
Σαφώς, θα πρέπει να υποθέσετε ότι το κακόβουλο λογισμικό ήδη στο σύστημά σας θα μπορούσε να ανακτήσει σχεδόν κάθε πληκτρολογημένο κωδικό πρόσβασης μέσω μιας ποικιλίας τεχνικών παρακολούθησης σε πραγματικό χρόνο, εφόσον ήταν ενεργά τη στιγμή που πληκτρολογήσατε. Αλλά μπορεί εύλογα να περιμένετε ότι ο χρόνος που εκτίθεστε σε κίνδυνο θα περιοριστεί στη σύντομη περίοδο πληκτρολόγησης, χωρίς να παραταθεί σε πολλά λεπτά, ώρες ή ημέρες μετά ή ίσως περισσότερο, ακόμη και μετά τον τερματισμό της λειτουργίας του υπολογιστή σας.
Τι μένει πίσω;
Ως εκ τούτου, σκεφτήκαμε να ρίξουμε μια ματιά υψηλού επιπέδου στο πώς τα μυστικά δεδομένα μπορούν να μείνουν πίσω στη μνήμη με τρόπους που δεν είναι άμεσα προφανείς από τον κώδικα.
Μην ανησυχείτε αν δεν είστε προγραμματιστής – θα το κρατήσουμε απλό και θα σας εξηγήσουμε καθώς προχωράμε.
Θα ξεκινήσουμε εξετάζοντας τη χρήση της μνήμης και την εκκαθάριση σε ένα απλό πρόγραμμα C που προσομοιώνει την εισαγωγή και την προσωρινή αποθήκευση ενός κωδικού πρόσβασης κάνοντας τα εξής:
- Εκχώρηση ενός αποκλειστικού κομματιού μνήμης ειδικά για την αποθήκευση του κωδικού πρόσβασης.
- Εισαγωγή γνωστής συμβολοσειράς κειμένου ώστε να μπορούμε να το βρούμε εύκολα στη μνήμη αν χρειαστεί.
- Προσθήκη 16 ψευδοτυχαίων χαρακτήρων ASCII 8-bit από τη σειρά AP.
- Εκτύπωση την προσομοιωμένη προσωρινή μνήμη κωδικού πρόσβασης.
- Απελευθερώνοντας τη μνήμη με την ελπίδα να διαγραφεί η προσωρινή μνήμη κωδικού πρόσβασης.
- Έξοδος το πρόγραμμα.
Πολύ απλοποιημένος, ο κώδικας C μπορεί να μοιάζει κάπως έτσι, χωρίς έλεγχο σφαλμάτων, χρησιμοποιώντας ψευδοτυχαίους αριθμούς κακής ποιότητας από τη συνάρτηση χρόνου εκτέλεσης C rand()
, και αγνοώντας τυχόν ελέγχους υπερχείλισης buffer (μην κάνετε ποτέ τίποτα από αυτά σε πραγματικό κώδικα!):
// 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);
Στην πραγματικότητα, ο κώδικας που χρησιμοποιήσαμε τελικά στις δοκιμές μας περιλαμβάνει ορισμένα πρόσθετα κομμάτια που φαίνονται παρακάτω, έτσι ώστε να μπορούμε να απορρίψουμε το πλήρες περιεχόμενο της προσωρινής προσωρινής αποθήκευσης κωδικών πρόσβασης όπως το χρησιμοποιούσαμε, για να αναζητήσουμε ανεπιθύμητο ή εναπομείναν περιεχόμενο.
Σημειώστε ότι σκόπιμα απορρίπτουμε το buffer μετά την κλήση free()
, το οποίο είναι τεχνικά ένα σφάλμα χωρίς χρήση, αλλά το κάνουμε εδώ ως ύπουλος τρόπος για να δούμε αν κάτι κρίσιμο θα μείνει πίσω μετά την επιστροφή του buffer μας, το οποίο θα μπορούσε να οδηγήσει σε μια επικίνδυνη τρύπα διαρροής δεδομένων στην πραγματική ζωή.
Έχουμε εισάγει επίσης δύο Waiting for [Enter]
ζητά στον κώδικα για να δώσουμε στους εαυτούς μας την ευκαιρία να δημιουργήσουμε ενδείξεις μνήμης σε βασικά σημεία του προγράμματος, δίνοντάς μας ακατέργαστα δεδομένα για αναζήτηση αργότερα, προκειμένου να δούμε τι έμεινε πίσω κατά την εκτέλεση του προγράμματος.
Για την αποθήκευση μνήμης, θα χρησιμοποιήσουμε τη Microsoft Εργαλείο Sysinternals procdump
με -ma
επιλογής (απορρίψτε όλη τη μνήμη), το οποίο αποφεύγει την ανάγκη να γράψουμε τον δικό μας κώδικα για να χρησιμοποιήσουμε τα Windows DbgHelp
σύστημα και είναι μάλλον περίπλοκο MiniDumpXxxx()
λειτουργίες.
Για να μεταγλωττίσουμε τον κώδικα C, χρησιμοποιήσαμε τη δική μας μικρή και απλή κατασκευή του δωρεάν και ανοιχτού κώδικα του Fabrice Bellard Tiny C Compiler, διαθέσιμο για Windows 64-bit in πηγή και δυαδική μορφή απευθείας από τη σελίδα μας στο GitHub.
Το κείμενο με δυνατότητα αντιγραφής και επικολλήσεως όλου του πηγαίου κώδικα που απεικονίζεται στο άρθρο εμφανίζεται στο κάτω μέρος της σελίδας.
Αυτό συνέβη όταν μεταγλωττίσαμε και εκτελέσαμε το πρόγραμμα δοκιμής:
C:UsersduckKEYPASS> petcc64 -stdinc -stdlib unl1.c Tiny C Compiler - Πνευματικά δικαιώματα (C) 2001-2023 Fabrice Bellard Απογυμνώθηκε από τον Paul Ducklin για χρήση ως εργαλείο εκμάθησης Έκδοση petcc64-0.9.27 [0006] - Generates Μόνο PE -> unl64.c -> c:/users/duck/tcc/petccinc/stdio.h [. . . .] -> c:/users/duck/tcc/petcclib/libpetcc1_1.a -> C:/Windows/system64/msvcrt.dll -> C:/Windows/system32/kernel32.dll -------- ----------------------- τμήμα μεγέθους αρχείου virt 32 1000 200 .text 438 2000 800ac .data 2 c3000 00 .pdata -------- ----------------------- <- unl24.exe (1 byte) C:UsersduckKEYPASS> unl3584.exe Απόρριψη "νέου" buffer κατά την έναρξη 1F00: 51390 90 F57 5 00 00 00 00 00 50 F01 5 00 00 00 00 .W......P....... 00F00A513: 0 73 74 65D 6 33 32C 5 63D 6 64E 2 65 cm . exe.D 78F65B00: 44 32 00 513 0 72 69 76 65 72D 44 61A 74C 61 3 43E riverData=C:Win 3F5C57: 69 6F 00 513 0 64C C 6 77 dowsSystem73Dr 5F53D79: 73 74 65 6 33 32C 5 44 72 32 00 513 0 69 76 65 iversDriverData 72F73E5: 44 72 69 76 65F 72 44 61 74 61D 00 513 0E00. F45F46: 43 5 34F 33 37 32 3 31F 00 46 50 53F 5 4372 1F 00 BROWSER_APP_PROF 513F0: 42 52C 4 57F 53 45 52 5 41E 50 50D 5 50E 52 4 46 ILE_STRING: 00F51400 49 4 45C 5A 53 F54 52C AC 49B 4 47 καθαρά ExplzV.< .K.. Η πλήρης συμβολοσειρά ήταν: απίθανο κείμενοJHKNEJJCPOMDJHAN 3F49: 6 74E 65C 72 00B 51410 6C 65 74 20 45 78 70A 6 7B 56E απίθανο κείμενοJH4A3 4D 00 00A 00 51390 75E 6 6 69 6 EJJCPOMDJHAN.eD 65F6B79 : 74 65 78 74 4 48 4 4 00 513D 0 45A 4C 4 43 50E riverData=C:Win 4F4C44: 4 48F 41 4 00C 65 00 44 00 dos 513C 0Dr 72F69D76: 65 72 44 61 74 61C 3 43 3 5 57 69 6 00 513 0 iversDriverData 64F6E77: 73 5 53 79 73F 74 65 6 33 32D 5 44 72 32 00 513F .E0F69 .E76F65. 72 73F 5 44 72 69 76F 65 72 44 61F 74 61 00F 513 BROWSER_APP_PROF 0F00: 45 46C 43 5F 34 33 37 32 3E 31 00D 46 50E 53 5 4372 ILE_STRING = Ίντερ 1F00 513: 0C 42 F52 4C AC 57B 53 45 net ExplzV.<.K.. Αναμονή για το [ENTER] για να ελευθερώσει το buffer... Dumping buffer μετά το free() 52F5: A41 50 F50 5 50 52 4 46 00 51400 F49 4 45 5 53 54 .g......P...... . 52 49 4E riverData=C:Win 47F3C49: 6 74F 65 72 00C 51410 6 65 74 20 45D 78 70 6C 7 56 dowsSystem4Dr 3F4D00: 00 00 51390 0 67 5C 00 00 00 00 00 50Fver 01 : 5 00 00 00 00F 00 00 513 0 45D 4 4 43 50 4 4F .EFC_44=4.FPS_ 48F41F4: 00 65 00F 44 00 513 0 72F 69 76 65 72F 44 61 74F 61 BROWSER_APP_PROF 3F43 3E 5 57D 69 6E 00 513 0 ILE_STRING=Inter 64F6: 77E 73 5 53 79 73 74 65C 6D 33 32 5D AC 44B 72 32 net ExplM..MK. Αναμονή για έξοδο από το [ENTER] από το main()... C:UsersduckKEYPASS>
Σε αυτήν την εκτέλεση, δεν μπήκαμε στον κόπο να αρπάξουμε καμία ένδειξη μνήμης διεργασίας, επειδή μπορούσαμε να δούμε αμέσως από την έξοδο ότι αυτός ο κώδικας διαρρέει δεδομένα.
Αμέσως μετά την κλήση της λειτουργίας βιβλιοθήκης χρόνου εκτέλεσης των Windows C malloc()
, μπορούμε να δούμε ότι το buffer που λαμβάνουμε πίσω περιλαμβάνει κάτι που μοιάζει με δεδομένα μεταβλητής περιβάλλοντος που έχουν απομείνει από τον κώδικα εκκίνησης του προγράμματος, με τα πρώτα 16 byte να έχουν προφανώς τροποποιηθεί ώστε να μοιάζουν με κάποιο είδος κεφαλίδας εκχώρησης μνήμης που έχει απομείνει.
(Σημειώστε πώς αυτά τα 16 byte μοιάζουν με δύο διευθύνσεις μνήμης 8 byte, 0xF55790
και 0xF50150
, που βρίσκονται ακριβώς μετά και λίγο πριν από το δικό μας buffer μνήμης αντίστοιχα.)
Όταν ο κωδικός πρόσβασης υποτίθεται ότι είναι στη μνήμη, μπορούμε να δούμε ολόκληρη τη συμβολοσειρά καθαρά στο buffer, όπως θα περιμέναμε.
Αλλά μετά από κλήση free()
, σημειώστε πώς τα πρώτα 16 byte του buffer μας έχουν ξαναγραφτεί με ό,τι μοιάζουν με τις κοντινές διευθύνσεις μνήμης για άλλη μια φορά, πιθανώς έτσι ώστε ο εκχωρητής μνήμης να μπορεί να παρακολουθεί μπλοκ στη μνήμη που μπορεί να επαναχρησιμοποιήσει…
… αλλά το υπόλοιπο κείμενο του κωδικού πρόσβασης που έχει διαγραφεί (οι τελευταίοι 12 τυχαίοι χαρακτήρες EJJCPOMDJHAN
) έχει μείνει πίσω.
Όχι μόνο πρέπει να διαχειριζόμαστε τις δικές μας εκχωρήσεις μνήμης και αποκατανομές στο C, πρέπει επίσης να διασφαλίσουμε ότι επιλέγουμε τις σωστές λειτουργίες συστήματος για buffer δεδομένων εάν θέλουμε να τις ελέγξουμε με ακρίβεια.
Για παράδειγμα, μεταβαίνοντας σε αυτόν τον κώδικα, έχουμε λίγο περισσότερο έλεγχο του τι υπάρχει στη μνήμη:
Με εναλλαγή από malloc()
και free()
για να χρησιμοποιήσετε τις λειτουργίες εκχώρησης των Windows χαμηλότερου επιπέδου VirtualAlloc()
και VirtualFree()
άμεσα, έχουμε καλύτερο έλεγχο.
Ωστόσο, πληρώνουμε ένα τίμημα σε ταχύτητα, επειδή κάθε κλήση σε VirtualAlloc()
κάνει περισσότερη δουλειά από αυτή που απαιτείται malloc()
, το οποίο λειτουργεί με τη συνεχή διαίρεση και υποδιαίρεση ενός μπλοκ προκατανεμημένης μνήμης χαμηλού επιπέδου.
Χρησιμοποιώντας VirtualAlloc()
επανειλημμένα για μικρά μπλοκ καταναλώνει επίσης περισσότερη μνήμη συνολικά, επειδή κάθε μπλοκ καταναλώνεται από VirtualAlloc()
συνήθως καταναλώνει πολλαπλάσιο των 4KB μνήμης (ή 2MB, εάν χρησιμοποιείτε τα λεγόμενα μεγάλες σελίδες μνήμης), έτσι ώστε το buffer των 128 byte που έχουμε παραπάνω να στρογγυλοποιηθεί στα 4096 byte, χάνοντας τα 3968 byte στο τέλος του μπλοκ μνήμης 4KB.
Αλλά, όπως μπορείτε να δείτε, η μνήμη που λαμβάνουμε πίσω σβήνει αυτόματα (ορίζεται στο μηδέν), επομένως δεν μπορούμε να δούμε τι υπήρχε πριν, και αυτή τη φορά το πρόγραμμα διακόπτεται όταν προσπαθούμε να κάνουμε τη χρήση-μετά-δωρεάν κόλπο, επειδή τα Windows εντοπίζουν ότι προσπαθούμε να κοιτάξουμε τη μνήμη που δεν κατέχουμε πλέον:
C:UsersduckKEYPASS> unl2 Ντάμπινγκ "νέας" προσωρινής μνήμης κατά την έναρξη 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 00 00 ................ 00EA00: 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 ................ 00EA00: 00 00 0000000000 0060 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 00 00 0000000000 0070 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00 EA00: 00 00 0000000000 0080 00 00 00 00 00 00 ................ Η πλήρης συμβολοσειρά ήταν: unlikelytextIBIPJPPHEOPOIDLL 00EA00: 00 00E 00C 00 00B 00 00C 00 0000000000 0000 75 6 6 69 6 65 IBIP6ext: IBA79ext 74 65 78 74F 49 42F 49 50 0000000000C 0010C 4 50 50 48 JPPHEOPOIDLL.... 45EA4: 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 ................ 0030EA00: 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 00 0000000000 0040 00 00 00 00 00 00 00 00 00 00 00 00 00 ............... 00EA00: 00 0000000000 0050 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00EA00: 00 0000000000 0060 00 00 00 00 00 ................ 00EA00: 00 00 00 00 00 00 00 00 00 0000000000 0070 00 00 00 00 00 ............. ... Αναμονή για το [ENTER] για να ελευθερώσει το buffer... Dumping buffer after free() 00EA00: [Το πρόγραμμα τερματίστηκε εδώ επειδή τα Windows έπιασαν τη χρήση μας μετά τη δωρεάν]
Επειδή η μνήμη που ελευθερώσαμε θα χρειαστεί εκ νέου κατανομή VirtualAlloc()
προτού μπορέσει να χρησιμοποιηθεί ξανά, μπορούμε να υποθέσουμε ότι θα μηδενιστεί πριν ανακυκλωθεί.
Ωστόσο, αν θέλαμε να βεβαιωθούμε ότι ήταν κενή, θα μπορούσαμε να καλέσουμε την ειδική λειτουργία των Windows RtlSecureZeroMemory()
λίγο πριν το ελευθερώσουμε, για να εγγυηθούμε ότι τα Windows θα γράψουν πρώτα μηδενικά στο buffer μας.
Η σχετική λειτουργία RtlZeroMemory()
, αν αναρωτιέστε, κάνει κάτι παρόμοιο, αλλά χωρίς την εγγύηση ότι θα λειτουργήσει πραγματικά, επειδή οι μεταγλωττιστές επιτρέπεται να το αφαιρέσουν ως θεωρητικά περιττό εάν παρατηρήσουν ότι το buffer δεν χρησιμοποιείται ξανά μετά.
Όπως μπορείτε να δείτε, πρέπει να δείξουμε μεγάλη προσοχή για να χρησιμοποιήσουμε τις σωστές λειτουργίες των Windows, εάν θέλουμε να ελαχιστοποιήσουμε τον χρόνο που ενδέχεται να υπάρχουν αργότερα τα μυστικά που αποθηκεύονται στη μνήμη.
Σε αυτό το άρθρο, δεν πρόκειται να εξετάσουμε πώς θα αποτρέψετε την κατά λάθος αποθήκευση μυστικών στο αρχείο swap σας κλειδώνοντάς τα στη φυσική RAM. (Ιχνος: VirtualLock()
στην πραγματικότητα δεν αρκεί από μόνο του.) Εάν θέλετε να μάθετε περισσότερα σχετικά με την ασφάλεια της μνήμης των Windows χαμηλού επιπέδου, ενημερώστε μας στα σχόλια και θα το εξετάσουμε σε μελλοντικό άρθρο.
Χρήση αυτόματης διαχείρισης μνήμης
Ένας καλός τρόπος για να αποφύγουμε την εκχώρηση, διαχείριση και κατανομή μνήμης μόνοι μας είναι να χρησιμοποιήσουμε μια γλώσσα προγραμματισμού που φροντίζει malloc()
και free()
, ή VirtualAlloc()
και VirtualFree()
, αυτόματα.
Γλώσσα σεναρίου όπως Perl, Python, Λουά, το JavaScript και άλλοι θα απαλλαγούν από τα πιο κοινά σφάλματα ασφάλειας μνήμης που μαστίζουν τον κώδικα C και C++, παρακολουθώντας τη χρήση της μνήμης για εσάς στο παρασκήνιο.
Όπως αναφέραμε προηγουμένως, ο κακογραμμένος κώδικας C του παραπάνω δείγματος λειτουργεί καλά τώρα, αλλά μόνο επειδή εξακολουθεί να είναι ένα εξαιρετικά απλό πρόγραμμα, με δομές δεδομένων σταθερού μεγέθους, όπου μπορούμε να επαληθεύσουμε με επιθεώρηση ότι δεν θα αντικαταστήσουμε το 128- byte buffer, και ότι υπάρχει μόνο μία διαδρομή εκτέλεσης που ξεκινά με malloc()
και τελειώνει με ένα αντίστοιχο free()
.
Αλλά αν το ενημερώσαμε για να επιτρέψουμε τη δημιουργία κωδικού πρόσβασης μεταβλητού μήκους ή προσθέσαμε πρόσθετες λειτουργίες στη διαδικασία δημιουργίας, τότε (ή όποιος διατηρεί τον κώδικα στη συνέχεια) θα μπορούσαμε εύκολα να καταλήξουμε με υπερχείλιση buffer, σφάλματα μετά τη χρήση ή μνήμη που δεν ελευθερώνεται ποτέ και επομένως αφήνει τα μυστικά δεδομένα να κρέμονται πολύ καιρό αφού δεν είναι πλέον απαραίτητα.
Σε μια γλώσσα όπως η Lua, μπορούμε να αφήσουμε το περιβάλλον χρόνου εκτέλεσης Lua, το οποίο κάνει αυτό που είναι γνωστό στην ορολογία ως αυτόματη αποκομιδή σκουπιδιών, ασχολείται με την απόκτηση μνήμης από το σύστημα και την επιστροφή της όταν διαπιστώσει ότι έχουμε σταματήσει να τη χρησιμοποιούμε.
Το πρόγραμμα C που παραθέσαμε παραπάνω γίνεται πολύ πιο απλό όταν φροντίζουμε για την εκχώρηση και την αφαίρεση μνήμης:
Διαθέτουμε μνήμη για να κρατήσουμε τη συμβολοσειρά s
απλά εκχωρώντας τη συμβολοσειρά 'unlikelytext'
σε αυτό.
Μπορούμε αργότερα είτε να υποδείξουμε ρητά στον Λούα ότι δεν μας ενδιαφέρει πλέον s
αποδίδοντάς του την τιμή nil
(όλα nils
είναι ουσιαστικά το ίδιο αντικείμενο Lua), ή σταματήστε να χρησιμοποιείτε s
και περίμενε τον Λούα να διαπιστώσει ότι δεν χρειάζεται πλέον.
Είτε έτσι είτε αλλιώς, η μνήμη που χρησιμοποιείται από s
τελικά θα ανακτηθεί αυτόματα.
Και για την αποφυγή υπερχείλισης buffer ή κακής διαχείρισης μεγέθους κατά την προσάρτηση σε συμβολοσειρές κειμένου (ο τελεστής Lua ..
, προφέρεται concat, ουσιαστικά προσθέτει δύο χορδές μαζί, όπως +
στην Python), κάθε φορά που επεκτείνουμε ή συντομεύουμε μια συμβολοσειρά, ο Lua εκχωρεί μαγικά χώρο για μια ολοκαίνουργια συμβολοσειρά, αντί να τροποποιεί ή να αντικαθιστά την αρχική στην υπάρχουσα θέση μνήμης.
Αυτή η προσέγγιση είναι πιο αργή και οδηγεί σε αιχμές χρήσης μνήμης που είναι υψηλότερες από ό,τι θα παίρνατε στο C λόγω των ενδιάμεσων συμβολοσειρών που εκχωρούνται κατά τη διάρκεια της επεξεργασίας κειμένου, αλλά είναι πολύ πιο ασφαλής όσον αφορά τις υπερχειλίσεις buffer.
Αλλά αυτό το είδος αυτόματης διαχείρισης συμβολοσειρών (γνωστό στην ορολογία ως αμετάβλητο, γιατί οι χορδές δεν παίρνουν ποτέ μεταλλαγμένο, ή τροποποιηθούν στη θέση τους, αφού δημιουργηθούν), φέρνει από μόνος του νέους πονοκεφάλους στον τομέα της κυβερνοασφάλειας.
Τρέξαμε το παραπάνω πρόγραμμα Lua στα Windows, μέχρι τη δεύτερη παύση, λίγο πριν την έξοδο του προγράμματος:
C:UsersduckKEYPASS> lua s1.lua Η πλήρης συμβολοσειρά είναι: unlikelytextHLKONBOJILAGLNLN Αναμονή για [ENTER] πριν από την απελευθέρωση συμβολοσειράς... Αναμονή για [ENTER] πριν από την έξοδο...
Αυτή τη φορά, κάναμε μια ένδειξη μνήμης διεργασιών, ως εξής:
C:UsersduckKEYPASS> procdump -ma lua lua-s1.dmp ProcDump v11.0 - Sysinternals process dump utility Πνευματικά δικαιώματα (C) 2009-2022 Mark Russinovich and Andrew Richards Sysinternals - www.sysinternals.com [00:00:00] εκκίνηση: C:UsersduckKEYPASSlua-s1.dmp [1:00:00] Έντυπο 00 εγγραφή: Το εκτιμώμενο μέγεθος αρχείου ένδειξης ένδειξης είναι 1 MB. [10:00:00] Ολοκληρώθηκε το Dump 00: 1 MB γραμμένο σε 10 δευτερόλεπτα [0.1:00:00] Επιτεύχθηκε ο αριθμός dump.
Στη συνέχεια, εκτελέσαμε αυτό το απλό σενάριο, το οποίο διαβάζει ξανά το αρχείο dump, βρίσκει παντού στη μνήμη ότι η γνωστή συμβολοσειρά unlikelytext
εμφανίστηκε και το εκτυπώνει μαζί με τη θέση του στο dumpfile και τους χαρακτήρες ASCII που ακολούθησαν αμέσως:
Ακόμα κι αν έχετε χρησιμοποιήσει γλώσσες σεναρίου στο παρελθόν ή έχετε εργαστεί σε οποιοδήποτε οικοσύστημα προγραμματισμού που διαθέτει τα λεγόμενα διαχειριζόμενες χορδές, όπου το σύστημα παρακολουθεί τις εκχωρήσεις μνήμης και τις εκχωρήσεις για εσάς και τις χειρίζεται όπως κρίνει…
…μπορεί να εκπλαγείτε βλέποντας την έξοδο που παράγει αυτή η σάρωση μνήμης:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp 006D8AFC: unlikelytextALJBNGOAPLLBDEB 006D8B3C: unlikelytextALJBNGOA 006D8B7C: unlikelytextALJBJBNGO 006D8AFCTextALJBJBNGO 006Aplike 8CBC: απίθανο κείμενοALJBN 006D8D7C: απίθανο κείμενοALJBNGOAP 006D903C: απίθανο κείμενοALJBNGOAPL 006D90BC: απίθανο κείμενοALJBNGOAPLL 006D90FC: απίθανο κείμενοALJBNGOAP 006D913C: απίθανο κείμενοALJBNGOAPL 006D91BC: απίθανο κείμενοALJBNGOAPLL 006D91FC: απίθανο κείμενο006BNGOAP 923BC: unlikelytextALJB 006D70FC: unlikelytextALJBNGOAPLLBD 006D8C : απίθανο κείμενοALJBNGOAPLLBDE 006DB0C: απίθανο κείμενοALJ XNUMXDBBXNUMXC: απίθανο κείμενοAL XNUMXDBDXNUMXC: απίθανο κείμενοΑ
Ιδού, τη στιγμή που πιάσαμε τη μνήμη μας, παρόλο που είχαμε τελειώσει με το κορδόνι s
(και είπε στον Λούα ότι δεν το χρειαζόμασταν άλλο λέγοντας s = nil
), όλες οι συμβολοσειρές που είχε δημιουργήσει ο κώδικας στην πορεία εξακολουθούσαν να υπάρχουν στη μνήμη RAM, δεν έχουν ακόμη ανακτηθεί ή διαγραφεί.
Πράγματι, εάν ταξινομήσουμε την παραπάνω έξοδο από τις ίδιες τις συμβολοσειρές, αντί να ακολουθήσουμε τη σειρά με την οποία εμφανίστηκαν στη μνήμη RAM, θα μπορείτε να απεικονίσετε τι συνέβη κατά τη διάρκεια του βρόχου όπου συνδέσαμε έναν χαρακτήρα τη φορά με τη συμβολοσειρά του κωδικού πρόσβασης:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp | ταξινόμηση /+10 006DBD0C: απίθανο κείμενοA 006DBB8C: απίθανο κείμενοAL 006DB70C: απίθανο κείμενοALJ 006D91BC: απίθανο κείμενοALJB 006D8CBC: απίθανο κείμενοALJBN 006Β90D006text extALJBNGO 8D7B006C: απίθανο κείμενοALJBNGOA 8D3D006C: απίθανο κείμενοALJBNGOAP 8D7C: απίθανο κείμενοALJBNGOAPL 006D903BC: απίθανο κείμενοALJBNGOAPLL 006D90D006C: απίθανο κείμενοALJBNGOAPLL 913D006C απίθανο κείμενοALJBNGOAPLLBD 91D006C: απίθανο κείμενοALJBNGOAPLLBDE 923D006AFC: απίθανο κείμενοALJBNGOAPLLBDEB 8D006BFC : απίθανο κείμενοALJBNGOAPLLBDEBJ
Όλες αυτές οι προσωρινές, ενδιάμεσες συμβολοσειρές είναι ακόμα εκεί, έτσι ακόμα κι αν είχαμε εξαφανίσει επιτυχώς την τελική τιμή του s
, θα εξακολουθούσαμε να διαρρέουμε τα πάντα εκτός από τον τελευταίο του χαρακτήρα.
Στην πραγματικότητα, σε αυτήν την περίπτωση, ακόμα και όταν σκοπίμως αναγκάσαμε το πρόγραμμά μας να απορρίψει όλα τα περιττά δεδομένα καλώντας την ειδική συνάρτηση Lua collectgarbage()
(οι περισσότερες γλώσσες δέσμης ενεργειών έχουν κάτι παρόμοιο), τα περισσότερα από τα δεδομένα σε αυτές τις ενοχλητικές προσωρινές συμβολοσειρές έχουν κολλήσει στη μνήμη RAM ούτως ή άλλως, επειδή είχαμε μεταγλωττίσει το Lua για να κάνει την αυτόματη διαχείριση μνήμης χρησιμοποιώντας παλιά καλή malloc()
και free()
.
Με άλλα λόγια, ακόμη και αφού η ίδια η Lua ανακτούσε τα προσωρινά μπλοκ μνήμης για να τα χρησιμοποιήσει ξανά, δεν μπορούσαμε να ελέγξουμε πώς ή πότε αυτά τα μπλοκ μνήμης θα επαναχρησιμοποιούνταν και, επομένως, πόσο καιρό θα παραμείνουν στη διαδικασία με το αριστερό τους- πάνω από δεδομένα που περιμένουν να μυριστούν, να απορριφθούν ή να διαρρεύσουν με άλλο τρόπο.
Μπείτε στο .NET
Τι γίνεται όμως με το KeePass, από όπου ξεκίνησε αυτό το άρθρο;
Το KeePass είναι γραμμένο σε C# και χρησιμοποιεί το χρόνο εκτέλεσης .NET, ώστε να αποφεύγονται τα προβλήματα κακής διαχείρισης μνήμης που φέρνουν τα προγράμματα C…
…αλλά το C# διαχειρίζεται τις δικές του συμβολοσειρές κειμένου, μάλλον όπως κάνει ο Lua, γεγονός που θέτει το ερώτημα:
Ακόμα κι αν ο προγραμματιστής απέφευγε να αποθηκεύσει ολόκληρο τον κύριο κωδικό πρόσβασης σε ένα μέρος αφού είχε τελειώσει με αυτόν, οι εισβολείς με πρόσβαση σε μια ένδειξη αποθήκευσης μνήμης θα μπορούσαν ωστόσο να βρουν αρκετά προσωρινά δεδομένα που απομένουν για να μαντέψουν ή να ανακτήσουν τον κύριο κωδικό πρόσβασης ούτως ή άλλως, ακόμα κι αν οι εισβολείς είχαν πρόσβαση στον υπολογιστή σας λεπτά, ώρες ή ημέρες αφότου πληκτρολογήσατε τον κωδικό πρόσβασης;
Με απλά λόγια, υπάρχουν ανιχνεύσιμα, απόκοσμα υπολείμματα του κύριου κωδικού πρόσβασής σας που επιβιώνουν στη μνήμη RAM, ακόμη και αφού θα περιμένατε να έχουν διαγραφεί;
Ενοχλητικό, ως χρήστης Github Ο Vdohney ανακάλυψε, η απάντηση (για εκδόσεις KeePass παλαιότερες από 2.54, τουλάχιστον) είναι "Ναι".
Για να είμαστε σαφείς, δεν πιστεύουμε ότι ο πραγματικός κύριος κωδικός πρόσβασης μπορεί να ανακτηθεί ως μια ενιαία συμβολοσειρά κειμένου από μια ένδειξη μνήμης KeePass, επειδή ο συντάκτης δημιούργησε μια ειδική λειτουργία για την εισαγωγή κύριου κωδικού πρόσβασης που ξεφεύγει για να αποφύγει την αποθήκευση του πλήρους κωδικό πρόσβασης όπου θα μπορούσε εύκολα να εντοπιστεί και να μυριστεί.
Ικανοποιηθήκαμε με αυτό ορίζοντας τον κύριο κωδικό μας σε SIXTEENPASSCHARS
, πληκτρολογώντας το και, στη συνέχεια, λαμβάνοντας ένδειξη μνήμης αμέσως, σύντομα και πολύ αργότερα.
Αναζητήσαμε τα dumps με ένα απλό σενάριο Lua που έψαχνε παντού για αυτό το κείμενο κωδικού πρόσβασης, τόσο σε μορφή ASCII 8-bit όσο και σε μορφή UTF-16 16-bit (Windows widechar), ως εξής:
Τα αποτελέσματα ήταν ενθαρρυντικά:
C:UsersduckKEYPASS> lua searchknown.lua kp2-post.dmp Ανάγνωση σε αρχείο ένδειξης... ΤΕΛΟΣ. Η αναζήτηση για SIXTEENPASSCHARS ως 8-bit ASCII... δεν βρέθηκε. Η αναζήτηση για SIXTEENPASSCHARS ως UTF-16... δεν βρέθηκε.
Αλλά ο Vdohney, ο ανακάλυψε το CVE-2023-32784, παρατήρησε ότι καθώς πληκτρολογείτε τον κύριο κωδικό πρόσβασής σας, το KeePass σάς παρέχει οπτική ανατροφοδότηση κατασκευάζοντας και εμφανίζοντας μια συμβολοσειρά κράτησης θέσης που αποτελείται από χαρακτήρες Unicode "blob", μέχρι και το μήκος του Κωδικός πρόσβασης:
Σε συμβολοσειρές κειμένου ευρείας γραφής στα Windows (που αποτελούνται από δύο byte ανά χαρακτήρα, όχι μόνο ένα byte η καθεμία όπως στο ASCII), ο χαρακτήρας "blob" κωδικοποιείται στη μνήμη RAM ως δεκαεξαδικό byte 0xCF
ακολουθούμενη από 0x25
(το οποίο τυχαίνει να είναι σύμβολο ποσοστού στο ASCII).
Έτσι, ακόμα κι αν το KeePass προσέχει πολύ τους ακατέργαστους χαρακτήρες που πληκτρολογείτε όταν εισάγετε τον ίδιο τον κωδικό πρόσβασης, μπορεί να καταλήξετε με εναπομείνασες σειρές χαρακτήρων "blob", εύκολα ανιχνεύσιμες στη μνήμη καθώς επαναλαμβανόμενες εκτελέσεις όπως π.χ. CF25CF25
or CF25CF25CF25
...
…και, αν ναι, η μεγαλύτερη σειρά χαρακτήρων blob που βρήκατε θα έδινε πιθανότατα το μήκος του κωδικού πρόσβασής σας, κάτι που θα ήταν μια μέτρια μορφή διαρροής πληροφοριών κωδικού πρόσβασης, αν μη τι άλλο.
Χρησιμοποιήσαμε το ακόλουθο σενάριο Lua για να αναζητήσουμε σημάδια συμβολοσειρών κράτησης θέσης κωδικού πρόσβασης που έχουν απομείνει:
Το αποτέλεσμα ήταν εκπληκτικό (έχουμε διαγράψει διαδοχικές γραμμές με τον ίδιο αριθμό σταγόνων ή με λιγότερες σταγόνες από την προηγούμενη γραμμή, για να εξοικονομήσουμε χώρο):
C:UsersduckKEYPASS> lua findblobs.lua kp2-post.dmp 000EFF3C: * [. . .] 00BE621B: ** 00BE64C7: *** [. . .] 00BE6E8F: **** [. . .] 00BE795F: ***** [. . .] 00BE84F7: ****** [. . .] 00BE8F37: ******* [ συνεχίζει ομοίως για 8 blobs, 9 blobs, κ.λπ. *** 16C00: **************** 0503C00: * 05077C00: * [ όλοι οι υπόλοιποι αγώνες έχουν μήκος μίας σταγόνας] 09337B00: *
Σε κοντινές αλλά συνεχώς αυξανόμενες διευθύνσεις μνήμης, βρήκαμε μια συστηματική λίστα με 3 σταγόνες, μετά 4 σταγόνες και ούτω καθεξής έως και 16 σταγόνες (το μήκος του κωδικού πρόσβασής μας), ακολουθούμενες από πολλές τυχαία διάσπαρτες παρουσίες συμβολοσειρών μιας σταγόνας .
Έτσι, αυτές οι συμβολοσειρές "blob" του placeholder πράγματι φαίνεται να διαρρέουν στη μνήμη και να μένουν πίσω για να διαρρεύσουν το μήκος του κωδικού πρόσβασης, πολύ καιρό αφότου το λογισμικό KeePass έχει τελειώσει με τον κύριο κωδικό πρόσβασής σας.
Το επόμενο βήμα
Αποφασίσαμε να σκάψουμε περαιτέρω, όπως έκανε ο Vdohney.
Αλλάξαμε τον κώδικα αντιστοίχισης προτύπων για να ανιχνεύσουμε αλυσίδες χαρακτήρων blob ακολουθούμενες από οποιονδήποτε μεμονωμένο χαρακτήρα ASCII σε μορφή 16 bit (οι χαρακτήρες ASCII αντιπροσωπεύονται στο UTF-16 ως ο συνηθισμένος κωδικός ASCII των 8 bit, ακολουθούμενοι από ένα μηδενικό byte).
Αυτή τη φορά, για εξοικονόμηση χώρου, καταργήσαμε την έξοδο για κάθε αντιστοιχία που ταιριάζει ακριβώς με την προηγούμενη:
Έκπληξη, έκπληξη:
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
Δείτε τι αποκομίζουμε από τη διαχειριζόμενη περιοχή μνήμης συμβολοσειρών του .NET!
Ένα ομαδοποιημένο σύνολο προσωρινών "συμβολοσειρών blob" που αποκαλύπτουν τους διαδοχικούς χαρακτήρες στον κωδικό πρόσβασής μας, ξεκινώντας από τον δεύτερο χαρακτήρα.
Αυτές οι χορδές που διαρρέουν ακολουθούνται από ευρέως διαδεδομένες αντιστοιχίσεις μονού χαρακτήρων που υποθέτουμε ότι προέκυψαν τυχαία. (Ένα αρχείο ένδειξης σφαλμάτων KeePass έχει μέγεθος περίπου 250 MB, επομένως υπάρχει αρκετός χώρος για τους χαρακτήρες "blob" να εμφανίζονται σαν από τύχη.)
Ακόμα κι αν λάβουμε υπόψη αυτές τις επιπλέον τέσσερις αντιστοιχίσεις, αντί να τις απορρίψουμε ως πιθανές αναντιστοιχίες, μπορούμε να μαντέψουμε ότι ο κύριος κωδικός πρόσβασης είναι ένας από τους:
?IXTEENPASSCHARS ?NXTEENPASSCHARS ?WXTEENPASSCHARS ?SXTEENPASSCHARS
Προφανώς, αυτή η απλή τεχνική δεν βρίσκει τον πρώτο χαρακτήρα στον κωδικό πρόσβασης, επειδή η πρώτη "συμβολοσειρά blob" δημιουργείται μόνο αφού πληκτρολογηθεί αυτός ο πρώτος χαρακτήρας
Σημειώστε ότι αυτή η λίστα είναι ωραία και σύντομη επειδή φιλτράραμε αντιστοιχίσεις που δεν κατέληγαν σε χαρακτήρες ASCII.
Αν αναζητούσατε χαρακτήρες σε διαφορετικό εύρος, όπως κινεζικούς ή κορεάτικους χαρακτήρες, μπορεί να καταλήξετε με περισσότερες τυχαίες επιτυχίες, επειδή υπάρχουν πολύ περισσότεροι πιθανοί χαρακτήρες για να ταιριάξετε…
…αλλά υποψιαζόμαστε ότι θα πλησιάσετε πολύ κοντά στον κύριο κωδικό πρόσβασης ούτως ή άλλως, και οι "συμβολοσειρές blob" που σχετίζονται με τον κωδικό πρόσβασης φαίνεται να είναι ομαδοποιημένες στη μνήμη RAM, πιθανώς επειδή εκχωρήθηκαν περίπου την ίδια στιγμή από το ίδιο τμήμα του το χρόνο εκτέλεσης .NET.
Και εκεί, με μια ομολογουμένως μακροσκελή και περιληπτική, είναι η συναρπαστική ιστορία του CVE-2023-32784.
Τι να κάνω;
- Εάν είστε χρήστης του KeePass, μην πανικοβληθείτε. Αν και αυτό είναι ένα σφάλμα και είναι τεχνικά μια εκμεταλλεύσιμη ευπάθεια, οι απομακρυσμένοι εισβολείς που ήθελαν να σπάσουν τον κωδικό πρόσβασής σας χρησιμοποιώντας αυτό το σφάλμα θα πρέπει πρώτα να εμφυτεύσουν κακόβουλο λογισμικό στον υπολογιστή σας. Αυτό θα τους έδινε πολλούς άλλους τρόπους να κλέψουν τους κωδικούς πρόσβασής σας απευθείας, ακόμα κι αν αυτό το σφάλμα δεν υπήρχε, για παράδειγμα καταγράφοντας τα πλήκτρα σας καθώς πληκτρολογείτε. Σε αυτό το σημείο, μπορείτε απλά να προσέχετε για την επερχόμενη ενημέρωση και να την πάρετε όταν είναι έτοιμη.
- Εάν δεν χρησιμοποιείτε κρυπτογράφηση πλήρους δίσκου, εξετάστε το ενδεχόμενο να την ενεργοποιήσετε. Για να εξαγάγετε τους εναπομείναντες κωδικούς πρόσβασης από το αρχείο swap ή το αρχείο αδρανοποίησης (αρχεία δίσκου λειτουργικού συστήματος που χρησιμοποιούνται για την προσωρινή αποθήκευση περιεχομένου μνήμης κατά τη διάρκεια βαρέως φορτίου ή όταν ο υπολογιστής σας "κοιμάται"), οι εισβολείς θα χρειαστούν άμεση πρόσβαση στον σκληρό σας δίσκο. Εάν έχετε ενεργοποιημένο το BitLocker ή το αντίστοιχο για άλλα λειτουργικά συστήματα, δεν θα έχουν πρόσβαση στο αρχείο swap, στο αρχείο αδρανοποίησης ή σε άλλα προσωπικά δεδομένα, όπως έγγραφα, υπολογιστικά φύλλα, αποθηκευμένα email κ.λπ.
- Εάν είστε προγραμματιστής, κρατήστε τον εαυτό σας ενημερωμένο για θέματα διαχείρισης μνήμης. Μην το υποθέσετε ότι μόνο και μόνο επειδή κάθε
free()
ταιριάζει με το αντίστοιχοmalloc()
ότι τα δεδομένα σας είναι ασφαλή και καλά διαχειριζόμενα. Μερικές φορές, μπορεί να χρειαστεί να λάβετε επιπλέον προφυλάξεις για να αποφύγετε να αφήσετε μυστικά δεδομένα, και αυτές οι προφυλάξεις πολύ από λειτουργικό σύστημα σε λειτουργικό σύστημα. - Εάν είστε ελεγκτής QA ή αναθεωρητής κώδικα, σκέφτεστε πάντα "παρασκηνιακά". Ακόμα κι αν ο κώδικας διαχείρισης μνήμης φαίνεται τακτοποιημένος και ισορροπημένος, έχετε επίγνωση του τι συμβαίνει στα παρασκήνια (επειδή ο αρχικός προγραμματιστής μπορεί να μην το ήξερε) και ετοιμαστείτε να κάνετε κάποιες εργασίες σε στυλ pentesting, όπως παρακολούθηση χρόνου εκτέλεσης και μνήμη ντάμπινγκ για να επαληθευτεί ότι ο ασφαλής κώδικας πραγματικά συμπεριφέρεται όπως θα έπρεπε.
ΚΩΔΙΚΟΣ ΑΠΟ ΤΟ ΑΡΘΡΟ: UNL1.C
#περιλαμβάνω #περιλαμβάνω #περιλαμβάνω void hexdump(unsigned char* buff, int len) { // Εκτύπωση προσωρινής μνήμης σε κομμάτια 16 byte για (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Εμφάνιση 16 byte ως εξαγωνικές τιμές για (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Επαναλάβετε αυτά τα 16 byte ως χαρακτήρες για (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) { // Απόκτηση μνήμης για αποθήκευση κωδικού πρόσβασης και εμφάνιση του // στο buffer όταν είναι επίσημα "νέο"... char* buff = malloc(128); printf("Damping 'new' buffer in startn"); hexdump(buff,128); // Χρήση ψευδοτυχαίας διεύθυνσης buffer ως τυχαίας σποράς srand((unsigned)buff); // Ξεκινήστε τον κωδικό πρόσβασης με κάποιο σταθερό κείμενο με δυνατότητα αναζήτησης strcpy(buff,"unlikelytext"); // Προσθήκη 16 ψευδοτυχαίων γραμμάτων, ένα κάθε φορά για (int i = 1; i <= 16; i++) { // Επιλέξτε ένα γράμμα από A (65+0) έως P (65+15) char ch = 65 + (rand() & 15); // Στη συνέχεια τροποποιήστε τη συμβολοσειρά buff στη θέση strncat(buff,&ch,1); } // Ο πλήρης κωδικός πρόσβασης είναι τώρα στη μνήμη, επομένως εκτυπώστε τον // ως συμβολοσειρά και εμφανίστε ολόκληρο το buffer... printf("Full string was: %sn",buff); hexdump(buff,128); // Παύση για την απόρριψη της διαδικασίας RAM τώρα (δοκιμάστε: 'procdump -ma') puts("Αναμονή για [ENTER] στην ελεύθερη προσωρινή μνήμη..."); getchar(); // Επισήμως ελευθερώστε() τη μνήμη και εμφανίστε ξανά το buffer // για να δείτε αν έχει μείνει κάτι πίσω... free(buff); printf("Damping buffer after free()n"); hexdump(buff,128); // Παύση για απόρριψη μνήμης RAM ξανά για επιθεώρηση διαφορών puts("Waiting for [ENTER] to exit main(..."); getchar(); επιστροφή 0; }
ΚΩΔΙΚΟΣ ΑΠΟ ΤΟ ΑΡΘΡΟ: UNL2.C
#περιλαμβάνω #περιλαμβάνω #περιλαμβάνω #περιλαμβάνω void hexdump(unsigned char* buff, int len) { // Εκτύπωση προσωρινής μνήμης σε κομμάτια 16 byte για (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // Εμφάνιση 16 byte ως εξαγωνικές τιμές για (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Επαναλάβετε αυτά τα 16 byte ως χαρακτήρες για (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) { // Απόκτηση μνήμης για αποθήκευση κωδικού πρόσβασης και εμφάνιση του // στο buffer όταν είναι επίσημα "νέο"... char* buff = VirtualAlloc(0,128,MEM_COMMIT,PAGE_READWRITE); printf("Damping 'new' buffer in startn"); hexdump(buff,128); // Χρήση ψευδοτυχαίας διεύθυνσης buffer ως τυχαίας σποράς srand((unsigned)buff); // Ξεκινήστε τον κωδικό πρόσβασης με κάποιο σταθερό κείμενο με δυνατότητα αναζήτησης strcpy(buff,"unlikelytext"); // Προσθήκη 16 ψευδοτυχαίων γραμμάτων, ένα κάθε φορά για (int i = 1; i <= 16; i++) { // Επιλέξτε ένα γράμμα από A (65+0) έως P (65+15) char ch = 65 + (rand() & 15); // Στη συνέχεια τροποποιήστε τη συμβολοσειρά buff στη θέση strncat(buff,&ch,1); } // Ο πλήρης κωδικός πρόσβασης είναι τώρα στη μνήμη, επομένως εκτυπώστε τον // ως συμβολοσειρά και εμφανίστε ολόκληρο το buffer... printf("Full string was: %sn",buff); hexdump(buff,128); // Παύση για την απόρριψη της διαδικασίας RAM τώρα (δοκιμάστε: 'procdump -ma') puts("Αναμονή για [ENTER] στην ελεύθερη προσωρινή μνήμη..."); getchar(); // Επισήμως ελευθερώστε() τη μνήμη και εμφανίστε ξανά το buffer // για να δείτε αν έχει μείνει κάτι πίσω... VirtualFree(buff,0,MEM_RELEASE); printf("Damping buffer after free()n"); hexdump(buff,128); // Παύση για απόρριψη μνήμης RAM ξανά για επιθεώρηση διαφορών puts("Waiting for [ENTER] to exit main(..."); getchar(); επιστροφή 0; }
ΚΩΔΙΚΟΣ ΑΠΟ ΤΟ ΑΡΘΡΟ: S1.LUA
-- Ξεκινήστε με κάποιο σταθερό κείμενο με δυνατότητα αναζήτησης s = 'unlikelytext' -- Προσθέστε 16 τυχαίους χαρακτήρες από το 'A' στο 'P' για i = 1,16 do s = s .. string.char(65+math.random( . - Σκουπίστε τη συμβολοσειρά και επισημάνετε τη μεταβλητή αχρησιμοποίητη s = μηδέν -- Αποθέστε ξανά τη μνήμη RAM για να αναζητήσετε διαφορές εκτύπωσης ('Αναμονή για [ENTER] πριν από την έξοδο...') io.read()
ΚΩΔΙΚΟΣ ΑΠΟ ΤΟ ΑΡΘΡΟ: FINDIT.LUA
-- ανάγνωση στο αρχείο ένδειξης τοπικό f = io.open(arg[1],'rb'):read('*a') -- αναζητήστε κείμενο δείκτη ακολουθούμενο από έναν -- ή περισσότερους τυχαίους χαρακτήρες ASCII τοπικούς b,e ,m = 0,0,nil ενώ true do -- αναζητήστε την επόμενη αντιστοίχιση και θυμηθείτε τη μετατόπιση b,e,m = f:find('(unlikelytext[AZ]+)',e+1) -- έξοδος όταν δεν υπάρχει πλέον ταιριάζει αν όχι b, τότε τέλος διακοπής -- θέση αναφοράς και βρέθηκε η συμβολοσειρά print(string.format('%08X: %s',b,m)) τέλος
ΚΩΔΙΚΟΣ ΑΠΟ ΤΟ ΑΡΘΡΟ: SEARCHKNOWN.LUA
io.write('Ανάγνωση σε αρχείο dump... ') local f = io.open(arg[1],'rb'):read('*a') io.write('DONE.n') io. write('Αναζήτηση για SIXTEENPASSCHARS ως ASCII 8-bit... ') local p08 = f:find('SIXTEENPASSCHARS') io.write(p08 and 'FOUND' ή 'δεν βρέθηκε','.n') io.write ('Αναζήτηση για SIXTEENPASSCHARS ως UTF-16... ') local p16 = f:find('Sx00Ix00Xx00Tx00Ex00Ex00Nx00Px00». ή 'δεν βρέθηκε','.n')
ΚΩΔΙΚΟΣ ΑΠΟ ΤΟ ΑΡΘΡΟ: FINDBLOBS.LUA
-- ανάγνωση σε αρχείο ένδειξης που καθορίζεται στη γραμμή εντολών local f = io.open(arg[1],'rb'):read('*a') -- Αναζητήστε μία ή περισσότερες σταγόνες κωδικού πρόσβασης, ακολουθούμενες από οποιεσδήποτε μη κηλίδες -- Λάβετε υπόψη ότι οι χαρακτήρες blob (●) κωδικοποιούν σε ευρείες χαρακτήρες των Windows -- ως κώδικες UTF-16 litte-endian, που βγαίνουν ως CF 25 σε εξάγωνο. τοπικό b,e,m = 0,0,nil ενώ true do -- Θέλουμε ένα ή περισσότερα blobs, ακολουθούμενα από οποιοδήποτε non-blob. -- Απλοποιούμε τον κώδικα αναζητώντας ένα ρητό CF25 -- ακολουθούμενο από οποιαδήποτε συμβολοσειρά που έχει μόνο CF ή 25, -- έτσι θα βρούμε CF25CFCF ή CF2525CF καθώς και CF25CF25. -- Θα φιλτράρουμε τα "ψευδώς θετικά" αργότερα, εάν υπάρχουν. -- Πρέπει να γράψουμε "%%" αντί για x25 γιατί ο χαρακτήρας x25 -- (σύμβολο τοις εκατό) είναι ένας ειδικός χαρακτήρας αναζήτησης στο Lua! b,e,m = f:find('(xCF%%[xCF%%]*)',e+1) -- έξοδος όταν δεν υπάρχουν άλλες αντιστοιχίσεις, εάν όχι b, τότε τέλος διακοπής -- Το CMD.EXE δεν μπορεί να εκτυπώσει σταγόνες, οπότε τις μετατρέπουμε σε αστέρια. print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) τέλος
ΚΩΔΙΚΟΣ ΑΠΟ ΤΟ ΑΡΘΡΟ: SEARCHKP.LUA
-- ανάγνωση σε αρχείο ένδειξης που καθορίζεται στη γραμμή εντολών local f = io.open(arg[1],'rb'):read('*a') local b,e,m,p = 0,0,nil,nil ενώ true do -- Τώρα, θέλουμε ένα ή περισσότερα blobs (CF25) ακολουθούμενα από τον κωδικό -- για A..Z ακολουθούμενο από ένα byte 0 για να μετατρέψουμε το ACSCII σε UTF-16 b,e,m = f:find(' (xCF%%[xCF%%]*[AZ])x00',e+1) -- έξοδος όταν δεν υπάρχουν άλλες αντιστοιχίσεις, εάν όχι b, τότε τέλος σπασίματος -- Το CMD.EXE δεν μπορεί να εκτυπώσει blobs, επομένως τις μετατρέπουμε σε αστέρια. -- Για να εξοικονομήσουμε χώρο, αποκρύπτουμε τις διαδοχικές αντιστοιχίσεις εάν m ~= p και στη συνέχεια print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) p = m τέλος τέλος
- SEO Powered Content & PR Distribution. Ενισχύστε σήμερα.
- PlatoAiStream. Web3 Data Intelligence. Ενισχύθηκε η γνώση. Πρόσβαση εδώ.
- Minting the Future με την Adryenn Ashley. Πρόσβαση εδώ.
- Αγορά και πώληση μετοχών σε εταιρείες PRE-IPO με το PREIPO®. Πρόσβαση εδώ.
- πηγή: https://nakedsecurity.sophos.com/2023/05/31/serious-security-that-keepass-master-password-crack-and-what-we-can-learn-from-it/
- :έχει
- :είναι
- :δεν
- :που
- ][Π
- $UP
- 1
- 10
- 12
- 15%
- 20
- 200
- 2023
- 24
- 250
- 27
- 31
- 3d
- 49
- 50
- 67
- 7
- 70
- 72
- 77
- 8
- 9
- a
- Ικανός
- ΠΛΗΡΟΦΟΡΙΕΣ
- πάνω από
- Απόλυτος
- AC
- πρόσβαση
- Λογαριασμός
- αποκτούν
- απόκτηση
- ενεργός
- πραγματικός
- πραγματικά
- προστιθέμενη
- Πρόσθετος
- διεύθυνση
- διευθύνσεις
- Προσθέτει
- Μετά το
- κατόπιν
- πάλι
- Όλα
- διατεθεί
- κατανέμει
- κατανομή
- κατανομών
- επιτρέπουν
- alone
- κατά μήκος
- ήδη
- Επίσης
- μεταβάλλεται
- Αν και
- πάντοτε
- an
- και
- Ανδρέας
- απάντηση
- κάθε
- οτιδήποτε
- οτιδήποτε κρίσιμο
- εμφανίζομαι
- Εμφανίστηκε
- εμφανίζεται
- πλησιάζω
- εγκεκριμένη
- ΕΙΝΑΙ
- γύρω
- άρθρο
- εμπορεύματα
- AS
- At
- συγγραφέας
- αυτόματη
- Αυτόματο
- αυτομάτως
- διαθέσιμος
- αποφύγετε
- αποφεύγεται
- επίγνωση
- μακριά
- πίσω
- φόντο
- background-image
- BE
- επειδή
- γίνεται
- ήταν
- πριν
- Αρχή
- πίσω
- στα παρασκήνια
- παρακάτω
- Καλύτερα
- Κομμάτι
- Αποκλεισμός
- Μπλοκ
- σύνορο
- και οι δύο
- Κάτω μέρος
- μάρκα
- Καινούργια
- Διακοπή
- εν συντομία
- φέρω
- ρυθμιστικό
- υπερχείλισης buffer
- Έντομο
- σφάλματα
- χτίζω
- αλλά
- by
- C + +
- κλήση
- κλήση
- CAN
- Μπορεί να πάρει
- ο οποίος
- περίπτωση
- αλιεύονται
- CD
- Κέντρο
- σίγουρα
- αλυσίδες
- ευκαιρία
- άλλαξε
- χαρακτήρας
- χαρακτήρες
- έλεγχος
- έλεγχοι
- κινέζικο
- Επιλέξτε
- καθαρός
- σαφώς
- Κλεισιμο
- κωδικός
- χρώμα
- COM
- έρχεται
- ερχομός
- σχόλιο
- σχόλια
- Κοινός
- πλήρης
- συγκρότημα
- υπολογιστή
- Εξετάστε
- σημαντικός
- θεωρούνται
- Αποτελείται από
- κατασκευή
- περιεχόμενο
- περιεχόμενα
- συνεχώς
- συνεχίζεται
- έλεγχος
- μετατρέψετε
- πνευματική ιδιοκτησία
- Αντίστοιχος
- θα μπορούσε να
- κάλυμμα
- ρωγμή
- δημιουργία
- δημιουργήθηκε
- δημιουργός
- κρίσιμης
- Κυβερνασφάλεια
- ΚΙΝΔΥΝΟΣ
- Επικίνδυνες
- ημερομηνία
- διαρροή δεδομένων
- Ημ.
- συμφωνία
- αποφάσισε
- αφιερωμένο
- περιγράφεται
- DID
- διαφορές
- διαφορετικές
- Δυσκολία
- DIG
- ψηφιακό
- κατευθύνει
- Αμεση πρόσβαση
- κατευθείαν
- Display
- εμφάνιση
- έχει
- do
- έγγραφα
- κάνει
- Όχι
- πράξη
- γίνεται
- Μην
- κάτω
- αυτοκίνητο
- δυο
- χωματερή
- κατά την διάρκεια
- e
- κάθε
- Νωρίτερα
- εύκολα
- οικοσύστημα
- είτε
- αλλιώς
- ενεργοποίηση
- ενθάρρυνση
- κρυπτογράφηση
- τέλος
- τελειώνει
- αρκετά
- εξασφαλίζω
- εξασφαλίζοντας
- εισάγετε
- εισερχόμενοι
- Ολόκληρος
- καταχώριση
- Περιβάλλον
- Ισοδύναμος
- σφάλμα
- κατ 'ουσίαν,
- αναμενόμενη
- κ.λπ.
- Even
- τελικά
- συνεχώς αυξανόμενη
- Κάθε
- πάντα
- ακριβώς
- παράδειγμα
- Εκτός
- Έξαψη
- εκτέλεση
- υπάρχουν
- υφιστάμενα
- έξοδος
- Έξοδος
- αναμένω
- Εξηγήστε
- Εκμεταλλεύομαι
- εκτεθειμένος
- επεκτείνουν
- επιπλέον
- εκχύλισμα
- γεγονός
- ψευδής
- γοητευτικός
- Χαρακτηριστικά
- ανατροφοδότηση
- λιγότερα
- μαχητικός
- Αρχεία
- Αρχεία
- φιλτράρισμα
- τελικός
- Τελικά
- Εύρεση
- εύρεση
- ευρήματα
- τέλος
- Όνομα
- καθορίζεται
- Συγκέντρωση
- ακολουθείται
- Εξής
- Για
- μορφή
- Επίσημα
- μορφή
- προσεχής
- Βρέθηκαν
- τέσσερα
- Δωρεάν
- από
- πλήρη
- πλήρως
- λειτουργία
- λειτουργίες
- περαιτέρω
- μελλοντικός
- δημιουργεί
- γενεά
- παίρνω
- να πάρει
- GitHub
- Δώστε
- δεδομένου
- δίνει
- Δίνοντας
- Go
- πηγαίνει
- μετάβαση
- καλός
- Κυβέρνηση
- πιάσε
- εξαιρετική
- εγγύηση
- είχε
- Handles
- συνέβη
- Συμβαίνει
- συμβαίνει
- Σκληρά
- Έχω
- που έχει
- πονοκεφάλους
- βαριά
- ύψος
- εδώ
- HEX
- υψηλού επιπέδου
- υψηλότερο
- Επισκέψεις
- κρατήστε
- Τρύπα
- ελπίζω
- ΩΡΕΣ
- φτερουγίζω
- Πως
- Πώς να
- HTTPS
- κυνήγι
- i
- αναγνωριστικό
- if
- αμέσως
- σημαντικό
- in
- περιλαμβάνει
- Συμπεριλαμβανομένου
- πληροφορίες
- ενημερώνεται
- αντί
- ενδιαφερόμενος
- Internet
- σε
- θέματα
- IT
- ΤΟΥ
- εαυτό
- ορολογία
- Ιούνιος
- μόλις
- μόνο ένα
- Διατήρηση
- Κλειδί
- Ξέρω
- γνωστός
- Κορεάτικα
- Γλώσσα
- Γλώσσες
- laptop
- Επίθετο
- αργότερα
- οδηγήσει
- Οδηγεί
- διαρροή
- Διαρροές
- ΜΑΘΑΊΝΩ
- μάθηση
- ελάχιστα
- αφήνοντας
- αριστερά
- Μήκος
- ας
- επιστολή
- Βιβλιοθήκη
- ζωή
- Μου αρέσει
- Πιθανός
- Περιωρισμένος
- γραμμή
- γραμμές
- Λιστα
- Εισηγμένες
- ll
- φορτίο
- τοπικός
- τοποθεσία
- ξύλευση
- Μακριά
- μακροπρόθεσμος
- πλέον
- ματιά
- μοιάζει
- κοίταξε
- κοιτάζοντας
- ΦΑΊΝΕΤΑΙ
- Παρτίδα
- τύχη
- διατηρεί
- κάνω
- malware
- διαχείριση
- διαχειρίζεται
- διαχείριση
- διευθυντής
- διαχειρίζεται
- Χειρισμός
- πολοί
- Περιθώριο
- σημάδι
- δείκτη
- κύριος
- Ταίριασμα
- ταιριάζουν
- max-width
- Ενδέχεται..
- μέσα
- Μνήμη
- που αναφέρθηκαν
- Microsoft
- ενδέχεται να
- Λεπτ.
- μετριόφρων
- τροποποιημένο
- τροποποιήσει
- στιγμή
- παρακολούθηση
- περισσότερο
- πλέον
- πολύ
- πολλαπλούς
- Ανάγκη
- που απαιτούνται
- καθαρά
- ποτέ
- παρ 'όλα αυτά
- Νέα
- νέα
- επόμενη
- όμορφη
- Όχι.
- κανονικός
- τίποτα
- Ειδοποίηση..
- τώρα
- αριθμός
- αριθμοί
- αντικείμενο
- Εμφανή
- of
- off
- επίσημος ανώτερος υπάλληλος
- Επίσημα
- όφσετ
- Παλιά
- on
- μια φορά
- ONE
- αποκλειστικά
- ανοικτού κώδικα
- λειτουργίας
- το λειτουργικό σύστημα
- λειτουργικά συστήματα
- χειριστής
- Επιλογή
- or
- τάξη
- πρωτότυπο
- ΑΛΛΑ
- Άλλα
- αλλιώς
- δικός μας
- εμάς
- έξω
- παραγωγή
- επί
- φόρμες
- δική
- σελίδα
- Πανικός
- μέρος
- Κωδικός Πρόσβασης
- Διευθυντής κωδικού πρόσβασης
- Κωδικοί πρόσβασης
- μονοπάτι
- πρότυπο
- Παύλος
- παύση
- Πληρωμή
- τοις εκατό
- ίσως
- περίοδος
- μόνιμα
- προσωπικός
- προσωπικά δεδομένα
- φυσικός
- εικόνα
- κομμάτια
- Μέρος
- κράτησης θέσης
- Πανούκλα
- Πλάτων
- Πληροφορία δεδομένων Plato
- Πλάτωνα δεδομένα
- Αφθονία
- Σημείο
- σημεία
- Δημοφιλής
- θέση
- δυνατός
- Δημοσιεύσεις
- δυναμικού
- ακριβώς
- παρόν
- αρκετά
- πρόληψη
- προηγούμενος
- τιμή
- εκτυπώσεις
- πιθανώς
- προβλήματα
- διαδικασια μας
- Πρόγραμμα
- Προγραμματιστής
- Προγραμματιστές
- Προγραμματισμός
- Προγράμματα
- σαφής
- βάζω
- Python
- Ερωτήσεις και απαντήσεις
- ερώτηση
- αυξήσεις
- RAM
- τυχαίος
- σειρά
- μάλλον
- Ακατέργαστος
- RE
- φθάσει
- Διάβασε
- Ανάγνωση
- έτοιμος
- πραγματικός
- πραγματική ζωή
- σε πραγματικό χρόνο
- πραγματικά
- αναγνωρίζω
- Ανάκτηση
- ανάκτηση
- σχετίζεται με
- υπόλοιπα
- θυμάμαι
- μακρινός
- αφαιρέστε
- επαναλαμβάνω
- επανειλημμένες
- ΚΑΤ 'ΕΠΑΝΑΛΗΨΗ
- αναφέρουν
- εκπροσωπούνται
- σεβασμός
- αντίστοιχα
- ΠΕΡΙΦΕΡΕΙΑ
- Αποτελέσματα
- απόδοση
- επιστροφή
- αποκαλύπτω
- Απαλλάσσω
- δεξιά
- Κίνδυνος
- κινδύνους
- Δωμάτιο
- τρέξιμο
- τρέξιμο
- παρακολούθηση χρόνου εκτέλεσης
- s
- ένα ασφαλές
- Ασφαλέστερο
- ίδιο
- ικανοποιημένοι
- Αποθήκευση
- ρητό
- σάρωση
- διεσπαρμένος
- Σκηνές
- Αναζήτηση
- αναζήτηση
- Δεύτερος
- δευτερόλεπτα
- Μυστικό
- Τμήμα
- προστατευμένο περιβάλλον
- ασφάλεια
- δείτε
- σπόρος
- βλέποντας
- φαίνομαι
- δει
- βλέπει
- Σειρές
- σοβαρός
- σειρά
- τον καθορισμό
- Κοντά
- Σύντομα
- θα πρέπει να
- δείχνουν
- παρουσιάζεται
- υπογράψουν
- Σημάδια
- παρόμοιες
- Ομοίως
- Απλούς
- απλοποιημένη
- απλοποίηση
- απλά
- ενιαίας
- Μέγεθος
- ύπνος
- small
- Ύπουλος
- snooping
- So
- λογισμικό
- στέρεο
- μερικοί
- κάτι
- σύντομα
- Πηγή
- πρωτογενής κώδικας
- Χώρος
- ειδική
- ειδικώς
- καθορίζεται
- ταχύτητα
- Ηθοποιοί
- Εκκίνηση
- ξεκίνησε
- Ξεκινήστε
- ξεκινά
- εκκίνηση
- Ακόμη
- επιτραχήλιο
- στάση
- σταμάτησε
- κατάστημα
- αποθηκεύονται
- εναποθήκευση
- Ιστορία
- Σπάγγος
- ισχυρός
- Μελέτη
- Επιτυχώς
- τέτοιος
- επαρκής
- υποτιθεμένος
- έκπληξη
- έκπληκτος
- εκπληκτικός
- επιβιώσουν
- SVG
- ανταλλαγής
- σύστημα
- συστήματα
- Πάρτε
- λαμβάνεται
- παίρνει
- λήψη
- ομιλία
- τεχνικά
- τεχνικές
- προσωρινή
- δοκιμή
- δοκιμές
- από
- ότι
- Η
- Η Πηγη
- τους
- Τους
- τους
- τότε
- θεωρία
- Εκεί.
- επομένως
- αυτοί
- πράγμα
- νομίζω
- αυτό
- εκείνοι
- αν και?
- σκέψη
- ώρα
- Τίτλος
- προς την
- μαζι
- πήρε
- εργαλείο
- κορυφή
- τροχιά
- Παρακολούθηση
- μετάβαση
- διαφανής
- αληθής
- προσπαθώ
- Γύρισε
- δύο
- τύπος
- συνήθως
- καταλαβαίνω
- μέχρι
- αχρησιμοποίητος
- ανεπιθύμητος
- Ενημέρωση
- ενημερώθηκε
- URL
- us
- μας κυβέρνηση
- Χρήση
- usb
- χρήση
- χωρίς χρήση
- μεταχειρισμένος
- Χρήστες
- χρησιμοποιεί
- χρησιμοποιώντας
- χρησιμότητα
- αξία
- Αξίες
- ποικιλία
- επαληθεύει
- εκδοχή
- πολύ
- μέσω
- ευπάθεια
- W
- περιμένετε
- Αναμονή
- θέλω
- ήθελε
- ήταν
- Δες
- Τρόπος..
- τρόπους
- we
- Εβδ.
- ΛΟΙΠΌΝ
- ήταν
- Τι
- πότε
- αν
- Ποιό
- ενώ
- Ο ΟΠΟΊΟΣ
- Οποιοσδήποτε
- ολόκληρο
- WHY
- πλάτος
- θα
- νίκη
- παράθυρα
- σκουπίζω
- με
- χωρίς
- Αναρωτιούνται
- λόγια
- Εργασία
- εργάστηκαν
- εργαζόμενος
- λειτουργεί
- ανησυχία
- θα
- θα έδινα
- γράφω
- γραφή
- γραπτή
- ακόμη
- Εσείς
- Σας
- τον εαυτό σας
- zephyrnet
- μηδέν