Επόμενο Προηγούμενο Περιεχόμενα
Παρακάτω παρουσιάζονται εν συντομία δύο πολύ χρήσιμα
εργαλεία που υπάρχουν σε κάθε σύγχρονο linux σύστημα.
Βασίζονται και τα δύο στο ptrace() system call που
επιτρέπει τον έλεγχο διεργασιών στο linux.
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) και το
πρόγραμμα τερματίζει.
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) +++
Επόμενο Προηγούμενο Περιεχόμενα