Reverse Engineering σε περιβάλλον Linux, Μέρος 1 : Άλλα χρήσιμα εργαλεία
Description: This is issue 33 of Magaz, the Hellenic LinuxMagazine
Keywords: magaz hellenic greek hellas greece hellug linux magazine

Magaz, The Greek Linux Magazine
Τεύχος #33
Περιληπτικά Περιεχόμενα Αναλυτικά Περιεχόμενα Mirrors Links Πληροφορίες Συντάκτες Αναζήτηση Επικοινωνία
> Επόμενο Προηγούμενο Περιεχόμενα ### 3. Άλλα χρήσιμα εργαλεία Παρακάτω παρουσιάζονται εν συντομία δύο πολύ χρήσιμα εργαλεία που υπάρχουν σε κάθε σύγχρονο linux σύστημα. Βασίζονται και τα δύο στο ptrace() system call που επιτρέπει τον έλεγχο διεργασιών στο linux. ### 3.1 strace To strace (system call trace) καταγράφει τα system calls ενός προγράμματος. Από default καταγράφει όλα τα syscalls αλλά υπάρχει η δυνατότητα να προσδιοριστούν μόνο κάποια συγκεκριμένα. Δέχεται πολλές παραμέτρους και ρυθμίσεις αλλά εδώ θα ασχοληθούμε μόνο με τις πιο βασικές (εξάλλου δεν υπάρχει λόγος να επαναλαμβάνουμε τη manpage!) Η βασική σύνταξη είναι strace [options] [-o outputfile] [objfile [args]]. Αν δεν καθορίσουμε κάποιο αρχείο για output τα syscalls εμφανίζονται στo stderr. Χρήσιμες παράμετροι είναι: -e call1,call2,... ή -e trace=call1,call2,...,callΝ Καθορίζει ποια syscalls να παρακολουθήσει το πρόγραμμα. Αν πριν από κάποιο όνομα υπάρχει το '!' σημαίνει να μην παρακολουθηθεί το syscall αυτό. H default τιμή είναι -e trace=all. -p pid καθορίζει σε ποια διεργασία να "αγκιστρωθεί" το πρόγραμμα ώστε να αρχίσει να παρακολουθεί. Προφανώς στην περίπτωση αυτή είναι περιττό να καθοριστεί το objfile. -i Πριν από κάθε syscall εμφανίζεται η τιμή του IP (instruction pointer) τη στιγμή της κλήσης. Η επιλογή αυτή είναι λιγότερο χρήσιμη από ότι φαίνεται, διότι τα syscalls καλούνται μέσα από wrapper συναρτήσεις που βρίσκονται σε βιβλιοθήκες, και έτσι οι διευθύνσεις που θα λαμβάνουμε δεν θα είναι και πολύ χρήσιμες. πχ
>     bash$ strace -e trace=write rce1
>     write(1, "Usage: rce1 <number>\n", 21Usage: rce1 <number>
>     )  = 21
> Το παραπάνω είναι λίγο μπερδεμένο διότι το strace αρχίζει να καταγράφει το syscall write στο stderr(που από default είναι η οθόνη), ύστερα εκτυπώνεται το κείμενο μας και μετά τελειώνει η καταγραφή με την τιμή επιστροφής της write. Αν είχαμε χρησιμοποιήσει το option -ο δε θα υπήρχε πρόβλημα.
>     bash$ strace -o rce1.trace -e trace=write rce1
>     Usage: rce1 <number>
>     bash$ cat rce1.trace
>     write(1, "Usage: rce1 <number>\n", 21)  = 21
> Ας δούμε τώρα το πλήρες trace από το αγαπημένο μας rce1.
>     bash$ strace -o rce1.trace rce1
>     Usage: rce1 <number>
>     bash$ cat -n rce1.trace
>          1  execve("./rce1", ["rce1"], [/* 51 vars */]) = 0
>          2  brk(0)                                  = 0x8049598
>          3  open("/etc/ld.so.preload", O_RDONLY)    = -1 ENOENT (No such file or
>     directory)
>          4  open("/etc/ld.so.cache", O_RDONLY)      = 3
>          5  fstat64(3, {st_mode=S_IFREG|0644, st_size=64466, ...}) = 0
>          6  old_mmap(NULL, 64466, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40015000
>          7  close(3)                                = 0
>          8  open("/lib/libc.so.6", O_RDONLY)        = 3
>          9  read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\\\1\000"...,
>     1024) = 1024
>         10  fstat64(3, {st_mode=S_IFREG|0755, st_size=1435624, ...}) = 0
>         11  old_mmap(NULL, 1256740, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) =
>     0x40025000
>         12  mprotect(0x4014f000, 36132, PROT_NONE)  = 0
>         13  old_mmap(0x4014f000, 20480, PROT_READ|PROT_WRITE,
>     MAP_PRIVATE|MAP_FIXED, 3, 0x12a000) = 0x4014f000
>         14  old_mmap(0x40154000, 15652, PROT_READ|PROT_WRITE,
>     MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40154000
>         15  close(3)                                = 0
>         16  old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,
>     -1, 0) = 0x40158000
>         17  munmap(0x40015000, 64466)               = 0
>         18  fstat64(1, {st_mode=S_IFCHR|0700, st_rdev=makedev(136, 0), ...}) = 0
>         19  old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,
>     -1, 0) = 0x40015000
>         20  write(1, "Usage: rce1 <number>\n", 21)  = 21
>         21  munmap(0x40015000, 4096)                = 0
>         22  semget(1, 4096, IPC_CREAT|0x40150140|0400) = -1 ENOSYS (Function not
>     implemented)
>         23  _exit(1)                                = ?
> 1: Εδώ καλείται η execve() ώστε να εκτελεστεί το πρόγραμμα μας. Η επόμενη γραμμή είναι πιο ενδιαφέρουσα. 2: Η brk() χρησιμοποιείται για να αλλάξει το μέγεθος του data segment. Παίρνει ως παράμετρο το νέο τέλος του data segment και επιστρέφει 0 αν όλα πήγαν καλά και -1 σε περίπτωση λάθους (όλα αυτά σύμφωνα με την man page που έχει ξεμείνει απο το linux 0.9.11). Εδώ η παράμετρος είναι 0 και η τιμή επιστροφής είναι ένας μια διεύθυνση. Μόλις μάθαμε ότι η brk(NULL) επιστρέφει το τρέχον τέλος του data segment :) Τώρα ο dynamic linker αναλαμβάνει δράση. 3-4: Παρατηρούμε μια προσπάθεια να ανοίξουν δύο αρχεία: το "/etc/ld.so.preload" και το "/etc/ld.so.cache". Το πρώτο περιέχει μια λίστα με το ποιες βιβλιοθήκες να φορτωθούν πριν από οποιαδήποτε βιβλιοθήκη του συγκεκριμένου εκτελέσιμου και στο δικό μου σύστημα δεν υπάρχει (θα ήταν ύποπτο αν υπήρχε...). Το δεύτερο περιέχει μια λίστα με όλες τις βιβλιοθήκες που γνωρίζει ο dynamic linker (δες man ldconfig) και ανοίγει επιτυχώς με file descriptor 3. 5-7: Με την fstat ο linker πληροφορείται για το αρχείο που μόλις άνοιξε. Η πληροφορία που τον ενδιαφέρει πιο πολύ είναι το μέγεθος st_size=64466 (δες man fstat). Ύστερα το αρχείο γίνεται mapped(αντιστοιχείται) στη μνήμη στη διεύθυνση 0x40015000 (δες man mmap) και το αρχείο κλείνει. 8-15: Ο linker βλέπει πως το εκτελέσιμο χρειάζεται τη βιβλιοθήκη libc.so.6. Έχοντας τις πληροφορίες από το προηγούμενο βήμα βρίσκει το path και το ανοίγει. Ύστερα διαβάζει τα πρώτα 1024 bytes που περιέχουν τον ELF(Executable and Linkable Format) header του αρχείου και αφού επιβεβαιώσει ότι όντως πρόκειται για shared βιβλιοθήκη φορτώνει τα διάφορα sections της. 16-17: Γίνονται map ανώνυμα 4Kbytes και ελευθερώνεται(unmapped) ο χώρος που αντιστοιχεί στο "/etc/ld.so.cache". 18-19: Λαμβάνονται πληροφορίες για τον file descriptor 1 (stdout) και γίνονται map ανώνυμα άλλα 4Kbytes 20: Εδώ γράφεται στον fd 1 το κείμενο μας. Εμείς δεν χρησιμοποιήσαμε την write κατευθείαν αλλά η printf τελικά καλεί την write. 21-23: Ελευθερώνεται το block που δεσμεύτηκε στη γραμμή 19, καλείται η semget() η οποία δεν έχει υλοποιηθεί(!) ακόμα στον δικό μου πυρήνα (2.4.20) και το πρόγραμμα τερματίζει. ### 3.2 ltrace To ltrace είναι αντίστοιχο με το strace αλλά όπως δηλώνει και το όνομα του (library trace) παρακολουθεί κλήσεις συναρτήσεων από βιβλιοθήκες. Πρόκειται για ένα αρκετά χρήσιμο πρόγραμμα που δίνει μια εποπτική εικόνα της ροής του προγράμματος. Βέβαια όπως και το strace μας δίνει απλώς μια ακολουθία από συμβάντα και καθόλου πληροφορίες για τα κομβικά σημεία της διεργασίας (πχ κάποιος έλεγχος). H σύνταξη είναι : ltrace [options] [-o outputfile] [objfile [args]] Χρήσιμες παράμετροι είναι: -e call1,call2,... Το πρόγραμμα παρακολουθεί μόνο τις call1,call2... Αν πριν από κάποια συνάρτηση υπάρχει '!' τότε να μην παρακολουθηθεί. -p pid Καθορίζει σε ποια διεργασία να "αγκιστρωθεί" το πρόγραμμα ώστε να αρχίσει να παρακολουθεί. Προφανώς στην περίπτωση αυτή είναι περιττό να καθοριστεί το objfile. -i Πριν από κάθε call εμφανίζεται η τιμή του IP (instruction pointer) τη στιγμή της κλήσης. Σε αντίθεση με την strace η επιλογή είναι πολύ χρήσιμη, διότι οι κλήσεις συναρτήσεων βιβλιοθήκης γίνονται συνήθως κατευθείαν από τον κώδικα που μας ενδιαφέρει και όχι από κάποια μυστική γωνιά κάποιας απέραντης βιβλιοθήκης. -l libfilename Καθορίζει τις συναρτήσεις ποιων βιβλιοθηκών να παρακολουθεί η ltrace. Για περισσότερες από μια βιβλιοθήκες πρέπει να επαναληφθεί το '-l' πχ -l lib1 -l lib2 ...
>     πχ
>     bash$ ltrace -orce1.ltrace  -i rce1
>     Usage: rce1 <number>
>     bash$ cat rce1.ltrace
>     [080482fd] __libc_start_main(0x0804838c, 1, 0xbffff784, 0x08048274, 0x08048440
>     <unfinished ...>
>     [080483b4] printf("Usage: %s <number<\n", "rce1")
>     = 21
>     [080483c1] exit(1 <unfinished ...>
>     [ffffffff] +++ exited (status 1) +++
Επόμενο Προηγούμενο Περιεχόμενα