Buffer Overflow (part I)

Buffer Overflow (part I)

Το συγκεκριμένο άρθρο είναι το πρώτο μέρος μιας σειράς που θα περιγράψει την αδυναμία Buffer Overflow. Σε αυτό το πρώτο μέρος, θα δώσουμε την απαιτούμενη (στοιχειώδη) γνώση ή σε απλά Ελληνικά: “το απαιτούμενο background”, που χρειάζεται σε κάποιον, ώστε να μπορέσει κατανοήσει τον βαθύτερο λόγο που μπορεί να συμβεί μια τέτοια επίθεση, ώστε να μπορέσει να εφαρμόσει μέτρα (ακόμα και ο ίδιος) μήπως και καταφέρει να μειώσει την πιθανότητα υλοποίησης αυτής της απειλής.

Κάθε εφαρμογή που τρέχουμε στον υπολογιστή μας όσο απλή ή σύνθετη κι αν είναι γράφτηκε από κάποιον προγραμματιστή. Οι αρχές εκτέλεσης ενός προγράμματος είτε πρόκειται για το ίδιο το λειτουργικό σύστημα είτε για ένα ταπεινό πρόγραμμα πρόσθεσης δύο αριθμών είναι παρόμοιες ή σχεδόν παρόμοιες.

Πρώτα απ’ όλα έχουμε το πρόγραμμα που φτιάχνει ο προγραμματιστής το οποίο αποτελείται από εντολές κατανοητές μόνο από ανθρώπους και ονομάζεται πηγαίος κώδικας ή απλά κώδικας. Αυτός ο κώδικας λοιπόν περνάει από μια ειδική επεξεργασία από άλλα προγράμματα που ονομάζονται μεταφραστές (compilers) ή διερμηνευτές (interpreters) για να μεταφραστεί σε κάτι που είναι κατανοητό μόνο (μόνο; Χμ, τέλος πάντων) από τον επεξεργαστή του υπολογιστή μας.

Θα μας πείτε τώρα ποια η διαφορά ενός compiler από έναν interpreter. Χμ, αρκετά μεγάλη: Οι compilers μεταφράζουν όλο τον πηγαίο κώδικα και παράγουν ένα νέο πρόγραμμα που ονομάζεται εκτελέσιμο (το γνωστό exe ή com). Το εκτελέσιμο αρχείο αποτελείται από εντολές μηχανής (όπως λέγονται) και που αντιστοιχούν σε αυτές που γράφτηκαν στον πηγαίο κώδικα. Είναι αυτό το αρχείο που όταν εκτελεστεί θα υλοποιήσει τις εντολές του αντίστοιχου πηγαίου του κώδικα. Μεταγλωττιστές χρησιμοποιούνται συνήθως για να μεταφράσουν κώδικα σε γλώσσες όπως οι C, C++, Pascal, κλπ.

Οι διερμηνευτές (interpreters) από την άλλη, μεταφράζουν τον πηγαίο κώδικα εντολή-εντολή: Τον διαβάζουν στην μνήμη και τον εκτελούν (συνήθως) γραμμή – γραμμή. Δεν παράγουν (…πάντα) κάποιο εκτελέσιμο αρχείο, αφού ο ίδιος ο πηγαίος κώδικας μεταφράζεται και εκτελείτε εκείνη την χρονική στιγμή που διαβάζεται. Διερμηνευτές υπάρχουν για τις γνωστές γλώσσες ανάπτυξης web εφαρμογών όπως η PHP και η ASP.

Η κάθε μια προσέγγιση φυσικά έχει τα καλά της και τα κακά της: Συνήθως τα προγράμματα που έχουν προέλθει από κάποιον compiler είναι πολύ πιο γρήγορα και απαλλαγμένα από συντακτικά λάθη που μπορεί να έκανε ο προγραμματιστής όταν τα έφτιαχνε. Αυτό το τελευταίο ισχύει διότι η συντακτικός έλεγχος γίνεται όταν παράγεται το εκτελέσιμο και αν βρεθεί λάθος, εκτελέσιμο δεν έχει! 😉 Επίσης το εκτελέσιμο είναι (λέμε τώρα…!! πολύ δύσκολο να διαβαστεί από κάποιον τρίτο ο οποίος θα προσπαθήσει (για οποιονδήποτε λόγο) να «κλέψει» τον κώδικα που έγραψε ο προγραμματιστής. Από την  άλλη μεριά τα προγράμματα που περνάνε από διερμηνευτές είναι πολύ πιο εύκολα στην συντήρηση, αφού δεν απαιτείται η διαδικασία του compilation (όπως λέγεται) ούτε και απαιτείται κάποιο ξεχωριστό εκτελέσιμο. Για μικρές αλλαγές στον κώδικα είναι ιδανικά και γρήγορα!

Υπάρχει όμως και μια τρίτη κατηγορία που βρίσκεται κάπου στη μέση. Πρόκειται για τις λεγόμενες εικονικές μηχανές (Virtual Machines – όχι δεν εννοούμε το VMWare ή το Virtual Box, μην το μπερδέψετε!) που παράγουν ένα ψευτο-εκτελέσιμο κώδικα (intermediate code), ο οποίος απαιτεί την ύπαρξη μιας «εικονικής μηχανής» για να τρέξει. Τέτοιες προσεγγίσεις έχουν φτιαχτεί για τις γλώσσες Java και για το γνωστό και μη εξαιρετέο Microsoft .Net. Βέβαια μη νομίζετε ότι αυτή η ιστορία είναι και τόσο πολύ καινούρια! Την 10ετία του 1980 υπήρχε το περίφημο P-System της Pascal, και είχαν φτιαχτεί γι’ αυτό ουκ-ολίγες εφαρμογές! Τα πλεονεκτήματα της virtual machine είναι η φορητότητα, η ασφάλεια κλπ. Για την ταχύτητα θα έλεγε κανείς ότι σίγουρα είναι καλύτερα από τον interpreter αλλά ίσως λίγο χειρότερα από τον compiler.

Ας έρθουμε όμως στην εκτέλεση του προγράμματος, που είναι και αυτό που μας ενδιαφέρει. Θα ασχοληθούμε με την εκτέλεση ενός προγράμματος που έχει μεταγλωττιστεί από κάποιον compiler χωρίς αυτό να σημαίνει ότι οι υπόλοιπες προσεγγίσεις (interpreters και virtual machines) διαφέρουν πολύ σε υλοποίηση. Να πούμε οτι σε αυτό το άρθρο (τουλάχιστον) για αναφερθούμε για λόγους απλότητας  σε υλοποιήσεις που βασίζονται στους επεξεργαστές της Intel (AMD και συμβατούς) και σε αρχιτεκτονική 32-bit.

Κάθε πρόγραμμα που εκτελείται στην μνήμη του υπολογιστή μας χρησιμοποιεί τρία βασικά μέρη της μνήμης RAM:

  • Το μέρος του Κώδικα (code section).
  • Το μέρος των Δεδομένων (data section).
  • Το μέρος της Στοίβας (stack).
Δείτε επίσης:   Οδηγός Penetration Testing σε συσκευές Point Of Sale Device (POS)

Πριν αναλύσουμε ένα-ένα τα μέρη αυτά θα θέλαμε να σας πούμε λίγα λόγια για την μνήμη. Η μνήμη του υπολογιστή είναι σαν τις κλειδοθήκες στην reception ενός ξενοδοχείου. Οι κλειδοθήκες λοιπόν, έχουν απ’ έξω γραμμένο ένα αριθμό που είναι ο αριθμός δωματίου και μέσα περιέχουν (ή δεν περιέχουν) το κλειδί. Έτσι ακριβώς λειτουργεί και η μνήμη του υπολογιστή μας: Περιέχει εκατομμύρια κουτάκια με νούμερα τα οποία νούμερα τα ονομάζουμε διευθύνσεις και μέσα σε αυτές φυλάσσονται τα δεδομένα μας. Κάθε κουτάκι μπορεί να δεχτεί συγκεκριμένου μεγέθους δεδομένα. Αν τα δεδομένα που θέλουμε να αποθηκεύσουμε στην μνήμη είναι πολλά (που σχεδόν πάντα είναι) τότε μοιράζονται σε πολλά κουτάκια.  Όπως θα φαντάζεστε, πάρα πολλά κουτάκια παραμένουν άδεια διότι απλά δεν έχουν τοποθετηθεί ακόμα μέσα τους δεδομένα. Αυτό όμως δεν σημαίνει ότι δεν έχουν και διεύθυνση! Κρατήστε αυτές τις πληροφορίες διότι θα μας χρειαστούν παρακάτω.

Μέρος του Κώδικα (code section)

Σε αυτό το κομμάτι της μνήμης αποθηκεύονται όλες οι εντολές του προγράμματός μας. Κατά την διάρκεια εκτέλεσης του προγράμματος κανένα πρόγραμμα δεν μπορεί να γράψει δεδομένα σε αυτό το μέρος. Είναι μόνο για διάβασμα (Read Only).

Για παράδειγμα, όλες οι εντολές μηχανής που αντιστοιχούν στο παρακάτω κομμάτι πηγαίου κώδικα (σε γλώσσα C στο παράδειγμα μας) θα τοποθετηθούν στην μνήμη στο Code Section:


/* Θέσε τα στοιχεία της 1ης διαγώνιου ενός πίνακα 100×100 με 1, και τα υπόλοιπα με 0 */

for (i = 0; i < 100; i++)

         for (j = 0; j < 100; j++)

            if (i<>j)

               a[i][j] = 0

            else

               a[i][j] = 1;


Τα σχόλια /*…*/ φυσικά δεν περιλαμβάνονται στον εκτελέσιμο κώδικα, δηλαδή δεν θα μεταφραστούν σε κώδικα μηχανής αφού αφορούν μόνο  αυτόν που διαβάζει το πρόγραμμα και όχι τον επεξεργαστή που το εκτελεί.

Μέρος των Δεδομένων (data section)

Στο μέρος αυτό τοποθετούνται όλες οι καθολικές μεταβλητές (global variables) του προγράμματος μας. Καθολικές μεταβλητές είναι εκείνες που είναι προσβάσιμες από όλες τις συναρτήσεις (functions) και τα υποπρογράμματα (procedures) του τρέχοντος προγράμματος. Εδώ, μπορεί να γράψει και φυσικά να διαβάσει δεδομένα το πρόγραμμα μας (non Read-Only). Για παράδειγμα, οι παρακάτω μεταβλητές i, j και a θα τοποθετηθούν στο data section:


 int i;

 int j=0;

 int a[100][100];


Μέρος της Στοίβας (stack)

Όλες οι τοπικές μεταβλητές, δηλαδή αυτές που δηλώνονται στις συναρτήσεις ή στα υπο-προγράμματα καθώς επίσης και κάποιες διευθύνσεις μνήμης που χρησιμοποιεί το πρόγραμμα μας καταχωρούνται στην λεγόμενη στοίβα. Το μέρος αυτό της μνήμης αποτελεί στην πραγματικότητα μια δομή δεδομένων τύπου στοίβας. Για να γίνει κατανοητή η δομή αυτή θα αναφέρουμε το κλασικό παράδειγμα με τα πιάτα: Σκεφτείτε ότι πρέπει να πλύνουμε 10 πιάτα. Καθώς τα πλένουμε ένα-ένα τα τοποθετούμε σε μια στοίβα, το ένα επάνω στο άλλο. Μόλις τελειώσουμε και θέλουμε να τα σκουπίσουμε (λέμε τώρα…!) τότε θα πάρουμε πρώτα εκείνο που μπήκε τελευταίο στην στοίβα. Η μέθοδος αυτή που χρησιμοποιούμε την στοίβα ονομάζεται Last In First Out (LIFO) και πρόκειται για μια πολύ συνηθισμένη “κατάσταση” στον προγραμματισμό (και όχι μόνο – απ’ όσο είδαμε και στο παράδειγμα μας…). Το κύριο χαρακτηριστικό της είναι πως το στοιχείο που μπαίνει τελευταίο, βγαίνει πρώτο.

Να πούμε οτι σε αυτό το μέρος της μνήμης επιτρέπεται το γράψιμο, δηλαδή είναι κι αυτό (όπως και το data segment) Writable. Αντί για… πιάτα όμως, το πρόγραμμα μας τοποθετεί εδώ της μεταβλητές που χρησιμοποιούμε στις συναρτήσεις του προγράμματος μας. Βέβαια, πρέπει να έχει ένα τρόπο να γνωρίζει ποια μεταβλητή είναι η τελευταία στην στοίβα, αυτή δηλαδή που θα ληφθεί πρώτη, όταν χρειαστεί. Για να το γνωρίζει αυτό, χρησιμοποιεί έναν από τους λεγόμενους δείκτες. Σκεφτείτε τους δείκτες σαν θέσεις μνήμης αλλά μέσα στον επεξεργαστή: άμεσα και γρήγορα προσβάσιμους. Ο δείκτης που χρησιμοποιείται για να ξέρουμε ποιο είναι το τελευταίο στοιχείο (μεταβλητή) της στοίβας ονομάζεται Δείκτης Στοίβας (extended stack pointer – ESP ή σκέτο SP). Στην πραγματικότητα ο ESP κρατάει την διεύθυνση της μεταβλητής που βρίσκεται κάθε στιγμή στην κορυφή της στοίβας.

Στην στοίβα μπορούμε να βάζουμε (push) ή να παίρνουμε (pop) στοιχεία κατά βούληση. Είναι πολύ σημαντικό να γνωρίζετε δυο μικρά μυστικά:

  • Εξ’ αιτίας της αρχιτεκτονικής του 32μπιτου επεξεργαστή της Intel (ή των συμβατών με αυτόν όπως o AMD), οι μεταβλητές που καταχωρούνται αποτελούνται από μέρη των 4ων ψηφίων ή αλλιώς των 4ων bytes. Γιατί; Διότι το bus είναι 32 bits (binary digits). Στα 32 bits χωράνε 4 bytes αφού το 1 byte = 8 bits, 4 x 8 = 32bits.
  • Η στοίβα αυξάνεται προς τα κάτω. Δηλαδή ξεκινάει από ψηλές διευθύνσεις στην μνήμη και όσο βάζουμε στοιχεία αυτή “μεγαλώνει” τόσο “κατεβαίνει” προς τα κάτω. Δείτε για παράδειγμα το εξής:
Δείτε επίσης:   Δημιουργία FUD Payload | Msfvenom + Metasploit + Python

Έστω οτι ο καταχωρητής SP = 256. Δείχνει, δηλαδή,  στο κουτάκι 256 στην μνήμη. Αν δώσουμε την εντολή “push 34” (βάλε στο stack το 34) τότε ο ΕSP αυτόματα θα μειωθεί κατά 4 και θα γίνει 252 και στο κουτάκι 256 θα μπει ο αριθμός 34.

 

Έχουμε λοιπόν:

PUSH 34

Διεύθυνση Τιμή
256 34
252
248

ESP=256

Αν μετά δώσουμε την εντολή:

PUSH 50

θα έχουμε:

Διεύθυνση Τιμή
256 34
252 50
248

ESP=248

Για να πάρει το πρόγραμμα μας από το stack,  θα δώσει την εντολή:

POP Χ

Αυτό σημαίνει οτι ο επεξεργαστής θα πάρει από το STACK την πρώτη τμή και θα την απονείμει στην μεταβλητή Χ και αμέσως μετά ο ESP θα γίνει 252 και η μεταβλητή Χ θα έχει την τιμή 34.

Εδώ, να αναφέρουμε ένα μικρό μυστικό. Αν θέλαμε να πάρουμε από το STACK την τιμή 50 και όχι την 34 τότε θα έπρεπε να δώσουμε δύο φορές POP αφού δεν έχουμε κατευθείαν πρόσβαση στο 50. Δηλαδή:

POP Χ
POP Χ
Εδώ, το Χ θα έχει την τιμή 50 και ο ESP=248.

Πως εκτελούνται οι εντολές (instructions)

Για να καταλάβουμε αυτή την λειτουργία πρέπει πρώτα να μιλήσουμε για ένα καταχωρητή:  Τον EIP (Extended Instruction Pointer ή απλά Instruction Pointer). Οι καταχωρητές είναι ακριβώς όπως και οι δείκτες που αναφέραμε πιο πάνω: Είναι θέσεις μνήμης μέσα στον επεξεργαστή και χρησιμοποιούνται από αυτόν για να εκτελεί της εντολές των προγραμμάτων μας.  Αυτός ο καταχωρητής χρησιμοποιείται για να “κρατάει” πάντα την διεύθυνση που βρίσκεται η επόμενη προς εκτέλεση εντολή του προγράμματος μας. Για να εκτελέσει ο επεξεργαστής μια εντολή, διαβάζει από τον EIP την διεύθυνση της, πάει σε αυτήν την διεύθυνση και διαβάζει την εντολή, την εκτελεί και καταχωρεί στον EIP την επόμενη προς εκτέλεση εντολή, κοκ. Πώς όμως ο επεξεργαστής μας θα βρει ποια είναι η επόμενη προς εκτέλεση εντολή ώστε να την καταχωρήσει στον EIP; Χμ… εδώ πρέπει να διακρίνουμε δυο περιπτώσεις:

  1. Η επόμενη εντολή προς εκτέλεση βρίσκεται αμέσως μετά την προηγούμενη.
  2. Να έχουμε μια περίπτωση jump δηλαδή να υπάρχει (για παράδειγμα) μια συνάρτηση που καλείται προς σε ένα άλλο σημείο του προγράμματος και θα πρέπει ο επεξεργαστής μας να “πηδήσει” σε εκείνο το σημείο, ή αλλιώς σε εκείνη την διεύθυνση της μνήμης και να εκτελέσει τις εντολές εκεί.

Στην περίπτωση 1 τα πράγματα είναι απλά: Η διεύθυνση υπολογίζεται ως εξής: προσθέτουμε στον EIP το μήκος της τρέχουσας εντολής που εκτελέστηκε. Το αποτέλεσμα μιας τέτοιας πράξης θα είναι η διεύθυνση της αμέσως επόμενης εντολής. Για να το καταλάβετε αυτό, δείτε το εξής μικρό πρόγραμμα δύο εντολών:


100 push EDX
101 mov ESP 0


Η εντολή στην διεύθυνση 100 βάζει στο stack την τιμή του καταχωρητή EDX (αυτός είναι ένας καταχωρητής γενικής χρήσης – τον έχουμε σαν… πρόχειρο). Η εντολή στην διεύθυνση 101 δίνει στο δείκτη ESP την τιμή 0. Όταν ο επεξεργαστής μας εκτελεί την εντολή στην διεύθυνση 100 τότε θα “σκεφτεί” τα εξής:

Είναι κάποιο jump; Όχι, άρα υπολογίζω το μέγεθος της εντολής στην διεύθυνση 100 που έχω. Έστω οτι είναι 1 byte. Άρα η επόμενη εντολή προς εκτέλεση πρέπει να βρίσκεται στην διεύθυνση 100 + 1 = 101. Άρα βάζω στον  EIP το 101.

Απ’ ότι καταλάβατε εδώ, κάθε εντολή – instruction (push, mov κλπ) καταλαμβάνει μνήμη και άρα έχει και κάποιο μέγεθος. Όλες οι εντολές δεν έχουν το ίδιο μέγεθος. Άλλες μπορεί να είναι 1 byte άλλες 2 άλλα 4 κοκ.

Υπάρχει βέβαια και η πιο σύνθετη περίπτωση: Η περίπτωση JUMP, δηλαδή η περίπτωση που το πρόγραμμα μας συνεχίζεται σε μια άλλη διεύθυνση πολύ πιο… “μακρινή” από την αμέσως επόμενη στη σειρά. Αυτό μπορεί να συμβεί (όπως είπαμε) όταν το πρόγραμμα καλεί μια συνάρτηση ή ένα άλλο υποπρόγραμμα.  Στην πράξη γίνεται το εξής:  Πριν εκτελεστεί η εντολή JUMP ο επεξεργαστής μας κρατάει την αμέσως επόμενη εντολή (αυτή που θα βρίσκεται μετά το jump) και την τοποθετεί σε ένα καταχωρητή γενικής χρήσης, ας πούμε τον EDX. Αμέσως μετά πάει στην διεύθυνση που του λέει το JUMP (Π.χ. JUMP 35456)  και εκτελεί τις εντολές που βρίσκονται εκεί μία προς μία, μέχρι να συναντήσει το τέλος της συνάρτησης (που ορίζεται με μια εντολή RET, δηλαδή return), τότε απλά γράφει στο EIP το περιεχόμενο που είχε καταχωρήσει στον EDX. Κατά συνέπεια το πρόγραμμα συνεχίζει από την επόμενη εντολή που είχε σταματήσει για κάνει το jump. Η διαδικασία του  JUMP υλοποιείται από το πρόγραμμα με την χρήση 2 εντολών: της CALL και της RET. H CALL μεταφέρει την λειτουργικότητα του προγράμματος σε μια άλλη (μακρινή) διεύθυνση και η RET δηλώνει το τέλος της σειράς των εντολών που βρίσκονται σε αυτήν την άλλη διεύθυνση ώστε να ξέρει πότε να σταματήσει (ή όπως λέμε να επιστρέψει) ο επεξεργαστής από την εκτέλεση των εντολών σε αυτή την περιοχή της μνήμης.

Δείτε επίσης:   XSS μέθοδοι για να κάνετε bypass την ασφάλεια

Δείκτης Βάσης – base pointer (EBP)

Κάθε φορά  που εφαρμόζεται ένα jump  είπαμε οτι ο επεξεργαστής πάει να εκτελέσει ένα σύνολο εντολών (instructions) σε μια άλλη θέση μνήμης (μέχρι να συναντήσει ένα RET). Αυτό το σύνολο εντολών όπως είπαμε, ονομάζεται συνάρτηση (function) ή διαδικασία (procedure). Κάθε συνάρτηση (ή διαδικασία) έχει το δικό της stack το οποίο ονομάζεται stack frame. Ένα stack frame είναι μια στοίβα στην οποία θα καταχωρηθούν μεταβλητές και διευθύνσεις που αφορούν μόνο στην τρέχουσα συνάρτηση που εκτελείται. Κάθε διεύθυνση μέσα στο συγκεκριμένο stack είναι μια σχετική διεύθυνση σε σχέση με μια βάση. Η βάση αυτή είναι ο base pointer. Όλες λοιπόν οι αναφορές στις διευθύνσεις του stack γίνονται με βάση τον τρέχοντα δείκτη βάσης (base pointer).

Στην πράξη γίνεται το εξής: Πριν καλέσουμε μια συνάρτηση βάζουμε στο EBP τον τρέχον ESP. Από εκεί και μετά κάθε φορά που θέλουμε να αναφερθούμε σε μια διεύθυνση στο stack θα το κάνουμε με βάση τον EBP που στην ουσία είναι η αρχή του stack πριν την κλήση της συνάρτησης. Για να το καταλάβετε δείτε το εξής παράδειγμα:

Στο παρακάτω stack έχουμε ebp=256 και esp=256:

Διεύθυνση Τιμή
256 34
252
248

Έστω οτι καλούμε μια συνάρτηση η οποία βάζει στο stack τον αριθμό 89. Θα έχουμε:

Διεύθυνση Τιμή
256 34
252 89
248

Όπου ebp=256 και esp=252.

Η χρήση του ebp μας βολεύει πολύ όταν θέλουμε να αναφερθούμε στις τιμές του stack της  συγκεκριμένης συνάρτησης. Δηλαδή, η μεταβλητή που βρίσκεται στη θέση (ebp 256) είναι η πρώτη μεταβλητή που καταχώρησε στο stack η συνάρτηση μας.

Όταν τελειώσει αυτή η συνάρτηση το έργο της (με ένα RET), τότε ο EBP θα επανέλθει στον προηγούμενο EBP ο οποίος είχε φυλαχτεί σε κάποιον καταχωρητή  πριν την κλήση της συνάρτησης.

Συμπεράσματα

Δώσαμε μια βασική περιγραφή για το πώς λειτουργεί εσωτερικά ένα πρόγραμμα. Οι γνώσεις που αποκομίσατε από το συγκεκριμένο άρθρο θεωρούνται στοιχειώδεις γνώσεις για ένα reverser ή για κάποιον που θέλει να καταλάβει έννοιες και να μπορέσει να διαβάσει άρθρα λίγο πιο προχωρημένα όπως αυτά που «μιλάνε» για buffer overflow.

Θα πρέπει, αν θέλετε να καταλάβετε καλά ένα Stack Buffer Overflow Attack, θα πρέπει να δώσετε βάση και να μπορείτε να εξηγήσετε τα εξής:

  1. Τι είναι ο Instruction Pointer (EIP).
  2. Τι είναι το Stack.
  3. Τι είναι ο Stack Pointer (ESP).
  4. Πώς συνδέονται τα παραπάνω μεταξύ τους.

Στα επόμενα άρθρα αυτής της οικογένειας θα δούμε λίγο πιο… core πράγματα! Προς το παρόν, σκεφτείτε το εξής:

Τι θα συμβεί αν καταφέρουμε με κάποιο τρόπο να ελέγξουμε τον Instruction Pointer (EIP);!  😎 

Με πολύτιμη εμπειρία στον κόσμο της κυβερνοασφάλειας συμβάλλει ενεργά στην κοινότητά μας με αναλύσεις ευπαθειών και οδηγούς.

Με τη συνεισφορά του, προσφέρει στην κοινότητα μας ένα πλούσιο φάσμα γνώσεων και δεξιοτήτων που ενισχύουν την ανάπτυξη και την ασφάλεια του ψηφιακού μας κόσμου.

Security enthusiast | Technology & Information Security Manager
CISA, CEH, CEH Item Writer, ISO27001 LA, DPO Executive

0 0 votes
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x
Secured By miniOrange