"Es gibt 10 Arten von Menschen auf dieser Welt:diejenigen, die Binär verstehen und diejenigen, die dies nicht tun."
Weitere Linux-Ressourcen
- Spickzettel für Linux-Befehle
- Spickzettel für fortgeschrittene Linux-Befehle
- Kostenloser Online-Kurs:RHEL Technical Overview
- Spickzettel für Linux-Netzwerke
- SELinux-Spickzettel
- Spickzettel für allgemeine Linux-Befehle
- Was sind Linux-Container?
- Unsere neuesten Linux-Artikel
Wir arbeiten täglich mit Binärdateien, aber wir verstehen so wenig über sie. Mit Binärdateien meine ich die ausführbaren Dateien, die Sie täglich ausführen, direkt von Ihren Befehlszeilentools bis hin zu vollwertigen Anwendungen.
Linux bietet eine große Auswahl an Tools, die das Analysieren von Binärdateien zum Kinderspiel machen! Was auch immer Ihre berufliche Rolle sein mag, wenn Sie mit Linux arbeiten, hilft Ihnen die Kenntnis der Grundlagen dieser Tools dabei, Ihr System besser zu verstehen.
In diesem Artikel behandeln wir einige der beliebtesten dieser Linux-Tools und -Befehle, von denen die meisten nativ als Teil Ihrer Linux-Distribution verfügbar sein werden. Wenn nicht, können Sie sie jederzeit mit Ihrem Paketmanager installieren und erkunden. Denken Sie daran:Es erfordert viel Geduld und Übung, das richtige Werkzeug zur richtigen Gelegenheit einzusetzen.
Datei
Funktion:Hilft bei der Bestimmung des Dateityps.
Dies ist Ihr Ausgangspunkt für die binäre Analyse. Wir arbeiten täglich mit Dateien. Nicht alles ist ein ausführbarer Typ; Es gibt eine ganze Reihe von Dateitypen da draußen. Bevor Sie beginnen, müssen Sie den zu analysierenden Dateityp verstehen. Handelt es sich um eine Binärdatei, eine Bibliotheksdatei, eine ASCII-Textdatei, eine Videodatei, eine Bilddatei, ein PDF, eine Datendatei usw.?
Die Datei Der Befehl hilft Ihnen dabei, den genauen Dateityp zu identifizieren, mit dem Sie es zu tun haben.
$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=94943a89d17e9d373b2794dcb1f7e38c95b66c86, stripped
$
$ file /etc/passwd
/etc/passwd: ASCII text
$
ldd
Was es tut:Gemeinsame Objektabhängigkeiten drucken.
Wenn Sie die Datei bereits verwendet haben obigen Befehl auf einer ausführbaren Binärdatei, können Sie die Meldung "dynamisch verknüpft" in der Ausgabe nicht übersehen. Was bedeutet das?
Bei der Entwicklung von Software versuchen wir, das Rad nicht neu zu erfinden. Es gibt eine Reihe allgemeiner Aufgaben, die die meisten Softwareprogramme erfordern, wie z. B. das Drucken von Ausgaben oder das Lesen von Standarddateien oder das Öffnen von Dateien usw. Alle diese allgemeinen Aufgaben werden in einer Reihe allgemeiner Funktionen abstrahiert, die jeder dann verwenden kann, anstatt zu schreiben ihre eigenen Varianten. Diese gemeinsamen Funktionen sind in einer Bibliothek namens libc abgelegt oder glibc .
Wie findet man heraus, von welchen Bibliotheken die ausführbare Datei abhängig ist? Dort steht ldd Befehl kommt ins Bild. Wenn Sie es mit einer dynamisch verknüpften Binärdatei ausführen, werden alle abhängigen Bibliotheken und ihre Pfade angezeigt.
$ ldd /bin/ls
linux-vdso.so.1 => (0x00007ffef5ba1000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fea9f854000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007fea9f64f000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007fea9f446000)
libc.so.6 => /lib64/libc.so.6 (0x00007fea9f079000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fea9ee17000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fea9ec13000)
/lib64/ld-linux-x86-64.so.2 (0x00007fea9fa7b000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007fea9ea0e000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fea9e7f2000)
$
ltrace
Was es tut:Ein Tracer für Bibliotheksaufrufe.
Wir wissen jetzt, wie man die Bibliotheken findet, von denen ein ausführbares Programm abhängig ist, indem man ldd verwendet Befehl. Eine Bibliothek kann jedoch Hunderte von Funktionen enthalten. Welche von diesen Hunderten sind die eigentlichen Funktionen, die von unserer Binärdatei verwendet werden?
Der ltrace zeigt alle Funktionen an, die zur Laufzeit aus der Bibliothek aufgerufen werden. Im folgenden Beispiel sehen Sie die aufgerufenen Funktionsnamen zusammen mit den Argumenten, die an diese Funktion übergeben werden. Sie können auch ganz rechts in der Ausgabe sehen, was von diesen Funktionen zurückgegeben wurde.
$ ltrace ls
__libc_start_main(0x4028c0, 1, 0x7ffd94023b88, 0x412950 <unfinished ...>
strrchr("ls", '/') = nil
setlocale(LC_ALL, "") = "en_US.UTF-8"
bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale"
textdomain("coreutils") = "coreutils"
__cxa_atexit(0x40a930, 0, 0, 0x736c6974756572) = 0
isatty(1) = 1
getenv("QUOTING_STYLE") = nil
getenv("COLUMNS") = nil
ioctl(1, 21523, 0x7ffd94023a50) = 0
<< snip >>
fflush(0x7ff7baae61c0) = 0
fclose(0x7ff7baae61c0) = 0
+++ exited (status 0) +++
$
Hexdump
Was es tut:Dateiinhalte in ASCII, dezimal, hexadezimal oder oktal anzeigen.
Oft passiert es, dass Sie eine Datei mit einer Anwendung öffnen, die nicht weiß, was mit dieser Datei zu tun ist. Versuchen Sie, eine ausführbare Datei oder eine Videodatei mit vim zu öffnen; Alles, was Sie sehen werden, ist Kauderwelsch, das auf den Bildschirm geworfen wird.
Das Öffnen unbekannter Dateien in Hexdump hilft Ihnen zu sehen, was genau die Datei enthält. Mit einigen Befehlszeilenoptionen können Sie auch die ASCII-Darstellung der in der Datei vorhandenen Daten anzeigen. Dies könnte Ihnen helfen, Hinweise darauf zu geben, um welche Art von Datei es sich handelt.
$ hexdump -C /bin/ls | head
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 d4 42 40 00 00 00 00 00 |..>......B@.....|
00000020 40 00 00 00 00 00 00 00 f0 c3 01 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1f 00 1e 00 |[email protected]...@.....|
00000040 06 00 00 00 05 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000050 40 00 40 00 00 00 00 00 40 00 40 00 00 00 00 00 |@.@.....@.@.....|
00000060 f8 01 00 00 00 00 00 00 f8 01 00 00 00 00 00 00 |................|
00000070 08 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00 |................|
00000080 38 02 00 00 00 00 00 00 38 02 40 00 00 00 00 00 |8.......8.@.....|
00000090 38 02 40 00 00 00 00 00 1c 00 00 00 00 00 00 00 |8.@.............|
$
Zeichenfolgen
Was es tut:Gibt die Zeichenfolgen druckbarer Zeichen in Dateien aus.
Wenn Hexdump für Ihren Anwendungsfall etwas übertrieben erscheint und Sie einfach nach druckbaren Zeichen in einer Binärdatei suchen, können Sie die Strings verwenden Befehl.
Wenn Software entwickelt wird, wird ihr eine Vielzahl von Text-/ASCII-Meldungen hinzugefügt, z. B. Druckinfomeldungen, Debugging-Infos, Hilfemeldungen, Fehler und so weiter. Sofern alle diese Informationen in der Binärdatei vorhanden sind, werden sie mithilfe von Strings auf den Bildschirm ausgegeben .
$ strings /bin/ls
selbst lesen
Was es tut:Informationen zu ELF-Dateien anzeigen.
ELF (Executable and Linkable File Format) ist das vorherrschende Dateiformat für ausführbare Dateien oder Binärdateien, nicht nur unter Linux, sondern auch auf einer Vielzahl von UNIX-Systemen. Wenn Sie Tools wie den Dateibefehl verwendet haben, der Ihnen mitteilt, dass die Datei im ELF-Format vorliegt, besteht der nächste logische Schritt darin, das readelf zu verwenden Befehl und seine verschiedenen Optionen, um die Datei weiter zu analysieren.
Eine Referenz der aktuellen ELF-Spezifikation zur Hand haben, wenn readelf verwendet wird kann sehr nützlich sein. Die Spezifikation finden Sie hier.
$ readelf -h /bin/ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4042d4
Start of program headers: 64 (bytes into file)
Start of section headers: 115696 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 30
$
objdump
Was es tut:Zeigt Informationen aus einer Objektdatei an.
Binärdateien werden erstellt, wenn Sie Quellcode schreiben, der mit einem Tool kompiliert wird, das wenig überraschend Compiler genannt wird. Dieser Compiler generiert Anweisungen in Maschinensprache, die dem Quellcode entsprechen, die dann von der CPU ausgeführt werden können, um eine bestimmte Aufgabe auszuführen. Dieser Maschinensprachcode kann über Mnemonik, die als Assemblersprache bezeichnet wird, interpretiert werden. Eine Assemblersprache ist eine Reihe von Anweisungen, die Ihnen helfen, die Operationen zu verstehen, die von dem Programm durchgeführt und schließlich auf der CPU ausgeführt werden.
objdump liest die binäre oder ausführbare Datei und gibt die Assembler-Anweisungen auf dem Bildschirm aus. Assemblerkenntnisse sind entscheidend, um die Ausgabe von objdump zu verstehen Befehl.
Denken Sie daran:Assemblersprache ist architekturspezifisch.
$ objdump -d /bin/ls | head
/bin/ls: file format elf64-x86-64
Disassembly of section .init:
0000000000402150 <_init@@Base>:
402150: 48 83 ec 08 sub $0x8,%rsp
402154: 48 8b 05 6d 8e 21 00 mov 0x218e6d(%rip),%rax # 61afc8 <__gmon_start__>
40215b: 48 85 c0 test %rax,%rax
$
Strasse
Was es tut:Systemaufrufe und -signale verfolgen.
Wenn Sie ltrace verwendet haben , bereits erwähnt, denken Sie an strace ähnlich sein. Der einzige Unterschied besteht darin, dass anstelle einer Bibliothek der strace Dienstprogramm verfolgt Systemaufrufe. Systemaufrufe stellen eine Schnittstelle zum Kernel dar, um die Arbeit zu erledigen.
Um ein Beispiel zu geben, wenn Sie etwas auf dem Bildschirm drucken möchten, verwenden Sie die printf oder Puts Funktion aus der Standardbibliothek libc; Unter der Haube befindet sich jedoch letztendlich ein Systemaufruf namens write wird gemacht, um tatsächlich etwas auf den Bildschirm zu drucken.
$ strace -f /bin/ls
execve("/bin/ls", ["/bin/ls"], [/* 17 vars */]) = 0
brk(NULL) = 0x686000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f967956a000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=40661, ...}) = 0
mmap(NULL, 40661, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9679560000
close(3) = 0
<< snip >>
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9679569000
write(1, "R2 RH\n", 7R2 RH
) = 7
close(1) = 0
munmap(0x7f9679569000, 4096) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
$
nm
Was es tut:Symbole aus Objektdateien auflisten.
Wenn Sie mit einer nicht gestrippten Binärdatei arbeiten, wird die Datei nm Der Befehl liefert Ihnen die wertvollen Informationen, die während der Kompilierung in die Binärdatei eingebettet wurden. Nm kann Ihnen helfen, Variablen und Funktionen aus der Binärdatei zu identifizieren. Sie können sich vorstellen, wie nützlich dies wäre, wenn Sie keinen Zugriff auf den Quellcode der zu analysierenden Binärdatei haben.
Um nm zu präsentieren , schreiben wir schnell ein kleines Programm und kompilieren es mit dem -g Option, und wir werden auch sehen, dass die Binärdatei nicht durch die Verwendung des file-Befehls entfernt wird.
$ cat hello.c
#include <stdio.h>
int main() {
printf("Hello world!");
return 0;
}
$
$ gcc -g hello.c -o hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=3de46c8efb98bce4ad525d3328121568ba3d8a5d, not stripped
$
$ ./hello
Hello world!$
$
$ nm hello | tail
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
00000000004005b0 T __libc_csu_fini
0000000000400540 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
000000000040051d T main
U printf@@GLIBC_2.2.5
0000000000400490 t register_tm_clones
0000000000400430 T _start
0000000000601030 D __TMC_END__
$
gdb
Was es tut:Der GNU-Debugger.
Nun, nicht alles in der Binärdatei kann statisch analysiert werden. Wir haben einige Befehle ausgeführt, die die Binärdatei ausgeführt haben, wie ltrace und strace; Software besteht jedoch aus einer Vielzahl von Bedingungen, die dazu führen können, dass verschiedene alternative Pfade ausgeführt werden.
Die einzige Möglichkeit, diese Pfade zu analysieren, ist zur Laufzeit, da man die Möglichkeit hat, das Programm an jeder beliebigen Stelle zu stoppen oder anzuhalten, Informationen zu analysieren und dann weiter nach unten zu gehen.
Hier kommen Debugger ins Spiel ins Bild und unter Linux gdb ist der Defacto-Debugger. Es hilft Ihnen, ein Programm zu laden, Haltepunkte an bestimmten Stellen zu setzen, Speicher und CPU-Register zu analysieren und vieles mehr. Es ergänzt die anderen oben genannten Tools und ermöglicht Ihnen viel mehr Laufzeitanalysen.
Eine Sache, die Sie beachten sollten, ist, sobald Sie ein Programm mit gdb laden , wird Ihnen ein eigenes (gdb) angezeigt prompt. Alle weiteren Befehle werden in dieser gdb ausgeführt Eingabeaufforderung bis zum Beenden.
Wir verwenden das zuvor kompilierte "hello"-Programm und verwenden gdb um zu sehen, wie es funktioniert.
$ gdb -q ./hello
Reading symbols from /home/flash/hello...done.
(gdb) break main
Breakpoint 1 at 0x400521: file hello.c, line 4.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400521 in main at hello.c:4
(gdb) run
Starting program: /home/flash/./hello
Breakpoint 1, main () at hello.c:4
4 printf("Hello world!");
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
(gdb) bt
#0 main () at hello.c:4
(gdb) c
Continuing.
Hello world![Inferior 1 (process 29620) exited normally]
(gdb) q
$
Schlussfolgerung
Sobald Sie mit der Verwendung dieser nativen Linux-Binäranalysetools vertraut sind und die von ihnen bereitgestellten Ergebnisse verstanden haben, können Sie zu fortgeschritteneren und professionelleren Open-Source-Binäranalysetools wie radare2 übergehen.