Dieser Artikel erläutert die Tools und Befehle, die zum Reverse Engineering einer ausführbaren Datei in einer Linux-Umgebung verwendet werden können.
Reverse Engineering ist der Vorgang, herauszufinden, was eine Software tut, für die kein Quellcode verfügbar ist. Reverse Engineering gibt Ihnen möglicherweise nicht die genauen Details der Software. Aber man kann ziemlich gut nachvollziehen, wie eine Software implementiert wurde.
Das Reverse Engineering umfasst die folgenden drei grundlegenden Schritte:
- Sammeln der Informationen
- Ermitteln des Programmverhaltens
- Abfangen der Bibliotheksaufrufe
Ich. Sammeln der Informationen
Der erste Schritt besteht darin, Informationen über das Zielprogramm und seine Aufgaben zu sammeln. Für unser Beispiel nehmen wir den Befehl „who“. Der Befehl „who“ druckt die Liste der derzeit angemeldeten Benutzer.
1. Strings-Befehl
Strings ist ein Befehl, der die Zeichenfolgen druckbarer Zeichen in Dateien druckt. Lassen Sie uns dies jetzt gegen unseren Befehl target (who) verwenden.
# strings /usr/bin/who
Einige der wichtigen Strings sind,
users=%lu EXIT COMMENT IDLE TIME LINE NAME /dev/ /var/log/wtmp /var/run/utmp /usr/share/locale Michael Stone David MacKenzie Joseph Arceneaux
Aus der about-Ausgabe können wir erkennen, dass „who“ etwa 3 Dateien verwendet (/var/log/wtmp, /var/log/utmp, /usr/share/locale).
Lesen Sie mehr:Linux-Strings-Befehlsbeispiele (Text in UNIX-Binärdateien suchen)
2. nm-Befehl
nm-Befehl, wird verwendet, um die Symbole aus dem Zielprogramm aufzulisten. Durch die Verwendung von nm können wir die lokalen und Bibliotheksfunktionen sowie die verwendeten globalen Variablen kennenlernen. nm kann nicht mit einem Programm arbeiten, das mit dem Befehl „strip“ gestreift ist.
Hinweis:Standardmäßig ist der Befehl „who“ entfernt. Für dieses Beispiel habe ich den ‚who‘-Befehl noch einmal kompiliert.
# nm /usr/bin/who
Dies wird Folgendes auflisten:
08049110 t print_line 08049320 t time_string 08049390 t print_user 08049820 t make_id_equals_comment 080498b0 t who 0804a170 T usage 0804a4e0 T main 0804a900 T set_program_name 08051ddc b need_runlevel 08051ddd b need_users 08051dde b my_line_only 08051de0 b time_format 08051de4 b time_format_width 08051de8 B program_name 08051d24 D Version 08051d28 D exit_failure
In der obigen Ausgabe:
- t|T – Das Symbol ist im .text-Codeabschnitt vorhanden
- b|B – Das Symbol befindet sich im UN-initialisierten .data-Abschnitt
- D|d – Das Symbol befindet sich im initialisierten .data-Abschnitt.
Der Groß- oder Kleinbuchstabe bestimmt, ob das Symbol lokal oder global ist.
Aus der about-Ausgabe können wir Folgendes wissen:
- Es hat die globale Funktion (main,set_program_name,usage,etc..)
- Es hat einige lokale Funktionen (print_user,time_string etc..)
- Es hat global initialisierte Variablen (Version,exit_failure)
- Es hat die UN-initialisierten Variablen (time_format, time_format_width, etc..)
Manchmal können wir anhand der Funktionsnamen erraten, was die Funktionen tun werden.
Weiterlesen:10 praktische Linux-nm-Befehlsbeispiele
Die anderen Befehle, die zum Abrufen von Informationen verwendet werden können, sind
- ldd-Befehl
- Fuser-Befehl
- lsof-Befehl
- /proc Dateisystem
II. Bestimmen des Programmverhaltens
3. ltrace-Befehl
Es verfolgt die Aufrufe der Bibliotheksfunktion. Es führt das Programm in diesem Prozess aus.
# ltrace /usr/bin/who
Die Ausgabe wird unten gezeigt.
utmpxname(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0 setutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 1 getutxent(0x8050c6c, 0xb77068f8, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(NULL, 384) = 0x09ed59e8 getutxent(0, 384, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 768) = 0x09ed59e8 getutxent(0x9ed59e8, 768, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 1152) = 0x09ed59e8 getutxent(0x9ed59e8, 1152, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 1920) = 0x09ed59e8 getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 getutxent(0x9ed59e8, 1920, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 realloc(0x09ed59e8, 3072) = 0x09ed59e8 getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78) = 0x9ed5860 getutxent(0x9ed59e8, 3072, 0, 0xbfc5cdc0, 0xbfc5cd78)
Sie können beobachten, dass es eine Reihe von Aufrufen von getutxent und seiner Familie von Bibliotheksfunktionen gibt. Beachten Sie auch, dass ltrace die Ergebnisse in der Reihenfolge ausgibt, in der die Funktionen im Programm aufgerufen werden.
Jetzt wissen wir, dass der Befehl „who“ funktioniert, indem er getutxent und seine Funktionsfamilie aufruft, um die angemeldeten Benutzer abzurufen.
4. strace-Befehl
Der Befehl strace wird verwendet, um die Systemaufrufe des Programms zu verfolgen. Wenn ein Programm keine Bibliotheksfunktion verwendet und nur Systemaufrufe verwendet, können wir die Programmausführung nicht verfolgen, wenn wir einfaches ltrace verwenden.
# strace /usr/bin/who
[b76e7424] brk(0x887d000) = 0x887d000 [b76e7424] access("/var/run/utmpx", F_OK) = -1 ENOENT (No such file or directory) [b76e7424] open("/var/run/utmp", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 . . . [b76e7424] fcntl64(3, F_SETLKW, {type=F_RDLCK, whence=SEEK_SET, start=0, len=0}) = 0 [b76e7424] read(3, "\10\325"..., 384) = 384 [b76e7424] fcntl64(3, F_SETLKW, {type=F_UNLCK, whence=SEEK_SET, start=0, len=0}) = 0
Sie können beobachten, dass bei jedem Aufruf der malloc-Funktion der Systemaufruf brk() aufgerufen wird. Die getutxent-Bibliotheksfunktion ruft tatsächlich den „open“-Systemaufruf auf, um „/var/run/utmp“ zu öffnen, und setzt eine Lesesperre und liest den Inhalt und gibt dann die Sperren frei.
Jetzt haben wir bestätigt, dass der Befehl who die utmp-Datei liest, um die Ausgabe anzuzeigen.
Sowohl „strace“ als auch „ltrace“ haben eine Reihe guter Optionen, die verwendet werden können.
- -p pid – Hängt an die angegebene pid an. Nützlich, wenn das Programm bereits läuft und Sie wissen möchten, wie es sich verhält.
- -n 2 – Jeden verschachtelten Aufruf um 2 Leerzeichen einrücken.
- -f – Gabelung folgen
Weiterlesen:7 Strace-Beispiele zum Debuggen der Ausführung eines Programms unter Linux
III. Abfangen der Bibliotheksanrufe
5. LD_PRELOAD &LD_LIBRARY_PATH
LD_PRELOAD ermöglicht es uns, einer bestimmten Ausführung des Programms eine Bibliothek hinzuzufügen. Die Funktion in dieser Bibliothek überschreibt die eigentliche Bibliotheksfunktion.
Hinweis:Wir können dies nicht mit Programmen verwenden, die mit dem „suid“-Bit gesetzt sind.
Nehmen wir das folgende Programm.
#include <stdio.h> int main() { char str1[]="TGS"; char str2[]="tgs"; if(strcmp(str1,str2)) { printf("String are not matched\n"); } else { printf("Strings are matched\n"); } }
Kompilieren Sie das Programm und führen Sie es aus.
# cc -o my_prg my_prg.c # ./my_prg
Es wird „Strings are not matched“ ausgeben.
Jetzt werden wir unsere eigene Bibliothek schreiben und sehen, wie wir die Bibliotheksfunktion abfangen können.
#include <stdio.h> int strcmp(const char *s1, const char *s2) { // Always return 0. return 0; }
Kompilieren und setzen Sie die Variable LD_LIBRARY_PATH auf das aktuelle Verzeichnis.
# cc -o mylibrary.so -shared library.c -ldl # LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
Jetzt wird eine Datei namens „library.so“ erstellt.
Setzen Sie die Variable LD_PRELOAD auf diese Datei und führen Sie das String-Vergleichsprogramm aus.
# LD_PRELOAD=mylibrary.so ./my_prg
Jetzt wird „Strings are matched“ ausgegeben, da unsere Version der strcmp-Funktion verwendet wird.
Hinweis:Wenn Sie eine Bibliotheksfunktion abfangen möchten, sollte Ihre eigene Bibliotheksfunktion denselben Prototyp wie die ursprüngliche Bibliotheksfunktion haben.
Wir haben gerade die sehr grundlegenden Dinge behandelt, die zum Reverse Engineering eines Programms erforderlich sind.
Für diejenigen, die den nächsten Schritt im Reverse Engineering machen möchten, wird das Verständnis des ELF-Dateiformats und des Assembler-Sprachprogramms in größerem Umfang hilfreich sein.