Security Blog

Format String Attack

Η επίθεση αυτή εκμεταλλεύεται μια συγκεκριμένη αδυναμία που μπορεί να εμφανιστεί σε προγράμματα γραμμένα σε C/C++, Perl κλπ. Αυτή η αδυναμία έχει να κάνει με τον τρόπο που “τυπώνουμε” στο standard output κάποιες μεταβλητές του προγράμματος μας. Υλοποιείται δε, αν αυτές τις μεταβλητές που τυπώνουμε, τις ζητήσαμε από τον χρήστη κατά την διάρκεια εκτέλεσης του προγράμματος ή τις περάσαμε σαν παραμέτρους κατά την κλήση του προγράμματος. Κατά συνέπεια κάποιος χρήστης μπορεί να εξάγει από το πρόγραμμα δεδομένα που δεν θα έπρεπε να είναι προσβάσιμα από αυτόν! Το ακριβές πρόβλημα θα γίνει κατανοητό με το παρακάτω πρόγραμμα σε C:

#include <stdio.h>
int main(int argc, char *argv[])
{
if (argc != 2) // Αν ο χρήστης δεν έχει δώσει μία παράμετρο κλείσε.
return 0;

// Εμφάνισε τον χαιρετισμό.
printf("Format String Attack for tOtAlXaKeR 2010\n");

// Εμφάνισε την παράμετρο που έδωσε ο χρήστης.
printf(argv[1]);

// Πες αντίο…
printf("\nBye Bye!\n");

// Επέστρεψε στο λειτουργικό σύστημα.
return 1;
}

Το παραπάνω πρόγραμμα περιμένει από τον χρήστη να το καλέσει μαζί με μια παράμετρο την οποία και εμφανίζει μόλις κληθεί. Είναι ένα πάρα πολύ απλό πρόγραμμα.

Ας ξεκινήσουμε όμως από την αρχή: γράφουμε το παραπάνω πρόγραμμα σε ένα editor και το σώζουμε στον δίσκο με το όνομα fma.c
Μόλις το γράψουμε το μετατρέπουμε σε εκτελέσιμο αρχείο (σε executable) με την χρήση του compiler Microsoft C++ που δίνεται με το Visual Studio 2008.

Δεν θα καλέσουμε ολόκληρο το περιβάλλον του Visual Studio, αλλά εργαστούμε (the old good way) από command line. Η Microsoft ακόμα παρέχει αυτή την δυνατότητα αν επιλέξουμε start | programs | Visual Studio 2008 | Visual Studio Tools | Visual Studio 2008 Command Prompt (εικόνα 1).

Μπορούμε ακόμα να κάνουμε compilation από την γραμμή εντολών

Μόλις δώσουμε την παραπάνω επιλογή θα βγούμε σε γραμμή εντολών. Πρόκειται για την γνωστή γραμμή εντολών του λειτουργικού με την διαφορά ότι έχουν προστεθεί πληροφορίες σε κάποιες μεταβλητές συστήματος για την σωστή κλήση του compiler της Microsoft. Για να κάνουμε compile το πρόγραμμα μας δίνουμε:

C:\> cl fma.c

Αν έχουμε γράψει σωστά το πρόγραμμα και δεν έχουμε συντακτικά λάθη, ο compiler θα μας απαντήσει όπως φαίνεται στην παρακάτω εικόνα:

Κάνοντας compile το πρόγραμμα μας

Τώρα μπορούμε να καλέσουμε το πρόγραμμα μας. Για να δούμε: Όπως είμαστε στην γραμμή εντολών δίνουμε:

C:\> fma trelaras
Format String Attack for tOtAlXaKeR 2010
trelaras
Bye Bye!
C:\>_

Το πρόγραμμα, μετά τον χαιρετισμό εμφάνισε την λέξη που δώσαμε και μετά μας αποχαιρέτησε. Για να κάνουμε τώρα πονηρό. Δίνουμε το παρακάτω:

C:\> fma trelaras:%p:%p:%p
Format String Attack for tOtAlXaKeR 2010
Trelaras:0018FF88:00401226:00000002
Bye Bye!
C:\>_

Το πρόγραμμα εδώ δεν συμπεριφέρθηκε με τον αναμενόμενο τρόπο. Κάτι συμβαίνει εδώ. Τι είναι αυτοί οι αριθμοί:
0018FF88:00401226:00000002
Εμείς δώσαμε ότι και πριν, με την διαφορά ότι προσθέσαμε τους λεγόμενους type field χαρακτήρες (http://is.gd/aKZn8). Οι type field χαρακτήρες είναι μια πολύ συνηθισμένη τακτική των προγραμματιστών που χρησιμοποιούν την συνάρτηση printf θέλοντας να εμφανίσουν τις τιμές κάποιον μεταβλητών. Εμείς, σαν type fields βάλαμε %p:%p:%p που σημαίνει ότι θέλουμε να εμφανίσουμε τις τιμές τριών μεταβλητών. Για την ακρίβεια, το “p” σημαίνει ότι θέλουμε να εμφανίσουμε μια μεταβλητή σε 16δική μορφή και συγκεκριμένα με την μορφή 16δικής διεύθυνσης μνήμης. Η σειρά χαρακτήρων που δώσαμε θα μπει στην μεταβλητή argv[1] και στην πραγματικότητα το πρόγραμμα θα εκτελέσει την εντολή printf(argv[1]) ως εξής:
printf(“trelaras:%p:%p:%p”);
Ωραία μέχρι εδώ. Δηλώσαμε ότι θέλουμε να πάρουμε τις διευθύνσεις μνήμες τριών μεταβλητών. Που είναι όμως αυτές οι μεταβλητές… οεο?
Χρησιμοποιούμε μεν το type field “%p” αλλά πρέπει να δηλώσουμε και τις μεταβλητές που θέλουμε να δούμε. Εμείς το… «ξεχάσαμε»! Μόλις πραγματοποιήσαμε μια επίθεση format string. Τι είναι όμως αυτοί οι περίεργοι αριθμοί που εμφανίζονται; Από πού προέρχονται; Μπορεί κάποιος κακός χρήστης να τις χρησιμοποιήσει για να παραβιάσει την ασφάλεια μας; Χμ… πρέπει να το ψάξουμε και να το αποδείξουμε αυτό. Πώς όμως; Μα, με την προσφιλή μας τακτική: Ας «λερώσουμε» τα χέρια μας με τον Olly Debugger (http://www.ollydbg.de/).

Θα ελέγξουμε το πρόγραμμα μας (με την χρήση του olly) κατά την εκτέλεση του για να δούμε από που προέρχονται αυτοί οι αριθμοί και αμέσως μετά θα προσπαθήσουμε να αποδείξουμε αν κάποιος μπορεί να χρησιμοποιήσει αυτήν την τεχνική για να παραβιάσει την ασφάλεια ενός προγράμματος μας.

Ξεκινάμε τον Olly και μετά File | Open και καλούμε το πρόγραμμα μας. Θέλουμε αμέσως να βάλουμε ένα break point ώστε μόλις πατήσουμε RUN να σταματήσει ο olly σε εκείνο το break point ώστε να ελέγξουμε όλες τις μεταβλητές του προγράμματος, μήπως βρούμε κάπου τις:
0018FF88:00401226:00000002.

Πού όμως θα βάλουμε αυτό το break point. Πρέπει να το βάλουμε στο σημείο που εμφανίζουμε την λέξη trelaras. Πατάμε right click | search for | All Referenced text strings (επόμενη εικόνα).

Βρίσκουμε το σημείο που θα βάλουμε το break point

Επιλέγουμε ένα γνωστό σημείο στον κώδικα το οποίο καλείτε λίγο πριν την κλήση της συνάρτησης που θέλουμε να ελέγξουμε. Θέλουμε να σταματήσει το πρόγραμμα μας στο σημείο που εμφανίζουμε τον χαιρετισμό. Το επόμενο βήμα μας είναι να εισάγουμε τους χαρακτήρες που περάσαμε σαν παραμέτρους κατά την κλήση του προγράμματος μας. Για να το κάνουμε αυτό πατάμε Debug | Arguments και εισάγουμε τις παραμέτρους που θέλουμε:

Εισαγωγή των παραμέτρων που θα περάσουν στο πρόγραμμα

Πατάμε το κουμπί RUN και περιμένουμε να σταματήσει ο olly στο break point που βάλαμε. Πράγματι μετά από λίγα χιλιοστά του δευτερολέπτου (αρκετός χρόνος, δεν συμφωνείτε;) ο olly σταματάει και περιμένει εμάς να κάνουμε κάποια ενέργεια. Στην συγκεκριμένη περίπτωση, το μόνο που θα κάνουμε είναι να πατήσουμε το πλήκτρο F8 διαδοχικά ώστε να κάνουμε το πρόγραμμα να εκτελεί μια-μια εντολή την φορά, ελέγχοντας σε κάθε βήμα όλες της μεταβλητές μας, το stack (κάτω δεξιά) και τους καταχωρητές μας (επάνω δεξιά). Πράγματι μετά από λίγα πατήματα του F8 φτάνουμε ένα βήμα πριν την εντολή που τυπώνει το “bye bye”.

Ψάχνοντας το πρόγραμμα μας με τον olly debugger

Παρατηρήστε το stack κάτω δεξιά. Περιέχει ακριβώς τις περίεργες τιμές εκείνες που εμφανίζονται όταν τρέξαμε το πρόγραμμα μας. Μπορούμε με ασφάλεια να πούμε ότι πρόκειται για μια αδυναμία του προγράμματος μας διότι μπορεί ο χρήστης να εμφανίσει κατά βούληση τις τιμές του stack. Θα σκεφτείτε τώρα μερικοί, και με το δίκιο σας: «Και λοιπόν? Τι είναι αυτό το τόσο σημαντικό που περιέχει αυτό το stack?”. Χμ… το stack, χοντρικά, περιέχει (εκτός των άλλων) τις τιμές των μεταβλητών του προγράμματος μας. Δεν μπορεί και δεν πρέπει κανένας χρήστης να μπορεί να διαβάσει τις τιμές αυτές διότι εκεί μπορεί να κρύβονται πολύ σημαντικές ή απόρρητες πληροφορίες, όπως κωδικοί, passwords, serials κλπ. Το όλο πρόβλημα δημιουργήθηκε από την παρακάτω εντολή:
printf(argv[1]);
Πάρα πολύ εύκολα αυτή η «τρύπα» θα ξέφευγε από τους περισσότερους προγραμματιστές (δυστυχώς) και αυτό διότι τα παραπάνω προβλήματα πολύ σπάνια διδάσκονται στις αίθουσες διδασκαλίας μιας και είναι άγνωστα ακόμα και στους ίδιους τους διδάσκοντες! Ο τρόπος για να ξεπεράσουμε αυτή την αδυναμία είναι πάρα πολύ απλός, και ακούει το όνομα: «Χρησιμοποιείτε πάντα type field χαρακτήρες μέσα στο printf, όταν θέλετε να εμφανίσετε μεταβλητές». Με βάση αυτόν τον κανόνα, η παραπάνω εντολή θα έπρεπε να γίνει:
printf(“%s”,argv[1]);
Δηλαδή, λέμε στο πρόγραμμα: Εμφάνισε την σειρά χαρακτήρων που βρίσκεται στην μεταβλητή argv[1]. Πράγματι, αν τρέξουμε το πρόγραμμα μας με αυτήν την διόρθωση θα πάρουμε το παρακάτω:

C:\> fma trelaras:%p:%p:%p
Format String Attack for tOtAlXaKeR 2010
trelaras:%p:%p:%p
Bye Bye!
C:\>_

Το θέμα που τίθεται τώρα είναι να αποδείξουμε και εμπράκτως ότι με την χρήση αυτής της αδυναμίας μπορεί ένας κακός χρήστης να προβάλει αλλά και να ξεσκεπάσει το περιεχόμενο κάποιον μεταβλητών ενός προγράμματος που δεν έπρεπε με κανέναν τρόπο να δει. Για τον λόγο αυτό θα κατασκευάσουμε άλλο ένα πρόγραμμα, λίγο πιο σύνθετο από το προηγούμενο, το οποίο θα δημιουργεί ένα serial number. Θα φτιάξουμε, φυσικά, το πρόγραμμα με τέτοιο τρόπο ώστε να πάσχει από την αδυναμία Format String. Μετά, θα μπούμε στην θέση του κακού χρήστη και θα προσπαθήσουμε να διαβάσουμε αυτό το serial, εκμεταλλευόμενοι αυτή την αδυναμία. Έχει ενδιαφέρον η δοκιμή μας αυτή διότι με αυτόν τον τρόπο θα δούμε και πως καταχωρούνται τα διάφορα δεδομένα στην μνήμη του υπολογιστή μας καθώς το πρόγραμμα μας τα δημιουργεί. Θα δούμε πως ακριβώς σκέφτονται και λειτουργούν οι σκοτεινοί τύποι, κλέβοντας κωδικούς, serials και άλλα τέτοια χαρωπά.

Έστω, λοιπόν, ότι έχουμε ένα πρόγραμμα το οποίο θα δέχεται από την γραμμή εντολών ένα user name και με βάση αυτό το user name θα δημιουργεί ένα σειριακό αριθμό το οποίο θα λειτουργεί σαν έλεγχος γνησιότητας του προγράμματος για τον τρέχον user. Ο αλγόριθμος είναι ο παρακάτω:
1. Ο χρήστης δίνει ένα κωδικό (το ελάχιστο 4ων χαρακτήρων).
2. Το πρόγραμμα παίρνει τα πρώτους τέσσερεις χαρακτήρες του κωδικού και τον κάθε χαρακτήρα του, τον «κολλάει» στο κάθε κομμάτι ενός σειριακού αριθμού.
Για παράδειγμα, ο χρήστης αν δώσει 1234, το πρόγραμμα θα κολλήσει το 1,2,3,4 σε κάθε ένα από τα μέρη του σειριακού: aaa-bbb-ccc-ddd. Έτσι ο σειριακός αριθμός θα γίνει: 1aaa-2bbb-3ccc-4ddd
Εντάξει, δεν είναι κατ’ ανάγκη αριθμός αφού δεν αποτελείται πάντα από ψηφία, επιτρέψτε μας όμως να τον λέμε σειριακό αριθμό γνησιότητας. Ο κώδικας του προγράμματος μας φαίνεται στην επόμενη εικόνα.

Το πρόγραμμα δημιουργίας του σειριακού αριθμού

Δεν θα σταθούμε στις λεπτομέρειες δημιουργίας του κώδικα του προγράμματος αλλά στα αποτελέσματα του. Πάμε να κατασκευάσουμε το exploit-άκι μας το οποίο θα εκμεταλλεύεται την αδυναμία που υπάρχει στην γραμμή 36 του προγράμματος μας. Θα χρησιμοποιήσουμε την τεχνική που αναπτύξαμε παραπάνω. Θα καλέσουμε το πρόγραμμα με μια αυθαίρετη σειρά χαρακτήρων δίνοντας σαν παράμετρο το 1234 μαζί με αρκετά “%p”, τόσα ώστε να μπορούμε να διαβάσουμε σχεδόν όλες τις μεταβλητές που υπάρχουν στο stack, και φυσικά μαζί με το serial number. Αποφασίσαμε να επαναλάβουμε το “%p” 30 φορές! Επειδή, όμως βαριόμαστε να γράφουμε όλα αυτά τα “%p” μαζί θα χρησιμοποιήσουμε την δύναμη της perl για να καλέσουμε το πρόγραμμα μας δίνοντας την παρακάτω εντολή:
c> perl -e ” system ‘fma2′.’ 1234′.’:%p’x30″
Τα αποτελέσματα της εκτέλεσης του προγράμματος fma2 (πριν να αναλύσουμε διεξοδικά) τα βλέπουμε στην παρακάτω εικόνα:

Οι δοκιμές μας στην εκτέλεση του προγράμματος μας

Εδώ έχουμε τρέξει το πρόγραμμα μας δύο φορές. Μια ανώδυνη, με το 1234, και μια πονηρή γράφοντας 30 φορές το %p. Όπως είπαμε, με το %p εμφανίζουμε τα περιεχόμενα της μνήμης όπως αυτά καταχωρούνται στο stack. Αυτά εμφανίζονται σε 16δική μορφή αριθμών τεσσάρων bytes ή αλλιώς ενός word (http://is.gd/aPayG). Μην ξεχνάτε, επίσης, ότι τα δεδομένα καταχωρούνται… ανάποδα λόγω LIFO (http://is.gd/aPaZT) μέσα στο word. Για παράδειγμα η σειρά χαρακτήρων “abcd” θα εμφανιστεί σαν bcda και πιο συγκεκριμένα με την ASCII (http://is.gd/aPaCQ) αντιστοιχία τους σε 16δικούς αριθμούς, θα δούμε το παρακάτω: 64636261.
Ας αρχίσουμε, λοιπόν, να παρατηρούμε τα αποτελέσματα του προγράμματος μας:
Κατ’ αρχάς εμφανίζεται (σωστά) το «1234». Αυτό είναι το πρώτο μέρος της σειράς χαρακτήρων που δώσαμε, άρα και το πρώτο μέρος της μεταβλητής usercode, που εμφανίζεται. Ακολουθεί η τιμή 3 (00000003), μήπως σας θυμίζει την μεταβλητή z; Ναι. Ακριβώς από κάτω εμφανίζονται και οι μεταβλητές x και y με τιμές 1 και 2 αντίστοιχα. Ερχόμαστε τώρα στο… “ψητό”: Στο κόκκινο πλαίσιο εμφανίζεται το serial number.
Έχουμε 5 words, τα οποία αντιστοιχούν στους εξής χαρακτήρες:

WORD      > [A]     [B]      [C]      [D]      [E]
TIMH      >61616131:6262322D:63332D62:342D6363:00646464:
ΧΑΡΑΤΗΡΕΣ >a a a 1  b b 2 - c 3 - b  4 - c c 0 d d d

Είπαμε όμως ότι οι χαρακτήρες είναι ανάποδα μέσα στο word [A]=1aaa, [B]=-2bb, [C]=b-3c, [D]=cc-4, [E]=ddd0.
Αν τους αναποδογυρίσω θα τους δω όπως τους έδωσε ο χρήστης, δηλαδή:

Ανάποδα τα A,B,C,D,E = 1aaa-2bbb-3ccc-4ddd0.
Σας θυμίζει τίποτα το 1aaa-2bbb-3ccc-4ddd; Εμάς πάντως… κάτι μας λέει ;)

Μόλις αποδείξαμε ότι με την χρήση της αδυναμίας Format String μπορεί ένας πονηρός χρήστης να δει πράγματα που δεν προορίζονται γι’ αυτόν, παραβιάζοντας την αρχή της εμπιστευτικότητας (http://is.gd/aPbH1) και κατά συνέπεια παραβίαση της ασφάλειας. Αντί επιλόγου, να αναφέρουμε ότι στην βασική επίθεση Format String αναφέρεται (στην… βιβλιογραφία) και η χρήση του type field χαρακτήρα %n ο οποίος γράφει σε συγκεκριμένη διεύθυνση μνήμης. Τα τελευταίας τεχνολογίας λειτουργικά συστήματα, όμως, αλλά και οι compilers, έχουν προσθέσει κάποιες προστασίες οι οποίες μας έκαναν να μην παρουσιάσουμε επίθεση με την χρήση του “%n” με το σκεπτικό ότι δεν είναι πλέον επίκαιρη. Για παράδειγμα η Microsoft το έχει απενεργοποιήσει (http://is.gd/aPco0) ή τα εκτελέσιμα προγράμματα στο linux (π.χ. στο fedora 12) κάθε φορά εκτελούνται σε διαφορετική base address κάνοντας το γράψιμο σε υπολογισμένη συγκεκριμένη διεύθυνση (π.χ. στην διεύθυνση του Instruction Pointer) από δύσκολη έως αδύνατη. Αυτό βέβαια δεν μειώνει την σημαντικότητα της επίθεσης σε προγράμματα που έχουν μεταγλωττιστεί με παλαιούς compilers ή με την επιπόλαια απενεργοποίηση των προστατευτικών παραμέτρων και η κλήση τους σε παλαιότερα λειτουργικά όπως πλέον το Windows XP (http://is.gd/aPflV).