"Ci sono 10 tipi di persone in questo mondo:quelli che capiscono il binario e quelli che non lo capiscono."
Più risorse Linux
- Comandi Linux cheat sheet
- Cheat sheet sui comandi avanzati di Linux
- Corso online gratuito:Panoramica tecnica RHEL
- Cheat sheet della rete Linux
- Cheat sheet di SELinux
- Cheat sheet dei comandi comuni di Linux
- Cosa sono i container Linux?
- I nostri ultimi articoli su Linux
Lavoriamo con i binari ogni giorno, ma ne capiamo così poco. Per binari intendo i file eseguibili che esegui quotidianamente, dagli strumenti della riga di comando alle applicazioni a tutti gli effetti.
Linux fornisce un ricco set di strumenti che rende l'analisi dei binari un gioco da ragazzi! Qualunque sia il tuo ruolo lavorativo, se lavori su Linux, conoscere le basi di questi strumenti ti aiuterà a capire meglio il tuo sistema.
In questo articolo, tratteremo alcuni dei più popolari di questi strumenti e comandi Linux, la maggior parte dei quali sarà disponibile in modo nativo come parte della tua distribuzione Linux. In caso contrario, puoi sempre utilizzare il tuo gestore di pacchetti per installarli ed esplorarli. Ricorda:imparare a usare lo strumento giusto nell'occasione giusta richiede molta pazienza e pratica.
file
Che cosa fa:aiuta a determinare il tipo di file.
Questo sarà il tuo punto di partenza per l'analisi binaria. Lavoriamo con i file ogni giorno. Non tutto è un tipo eseguibile; c'è un'intera vasta gamma di tipi di file là fuori. Prima di iniziare, è necessario comprendere il tipo di file che viene analizzato. È un file binario, un file di libreria, un file di testo ASCII, un file video, un file immagine, un PDF, un file di dati, ecc.?
Il file comando ti aiuterà a identificare il tipo di file esatto con cui hai a che fare.
$ 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
Che cosa fa:stampa le dipendenze degli oggetti condivisi.
Se hai già utilizzato il file comando sopra su un binario eseguibile, non puoi perdere il messaggio "collegato dinamicamente" nell'output. Cosa significa?
Quando il software viene sviluppato, cerchiamo di non reinventare la ruota. Esistono una serie di attività comuni richieste dalla maggior parte dei programmi software, come stampare l'output o leggere da standard in o aprire file, ecc. Tutte queste attività comuni sono astratte in una serie di funzioni comuni che tutti possono quindi utilizzare invece di scrivere le proprie varianti. Queste funzioni comuni vengono inserite in una libreria chiamata libc o glibc .
Come si trova da quali librerie dipende l'eseguibile? Ecco dove ldd il comando entra in scena. L'esecuzione su un binario collegato dinamicamente mostra tutte le sue librerie dipendenti e i loro percorsi.
$ 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)
$
traccia
Che cosa fa:un tracciatore di chiamate in libreria.
Ora sappiamo come trovare le librerie da cui dipende un programma eseguibile usando ldd comando. Tuttavia, una libreria può contenere centinaia di funzioni. Tra queste centinaia, quali sono le effettive funzioni utilizzate dal nostro binario?
La traccia comando visualizza tutte le funzioni che vengono chiamate in fase di esecuzione dalla libreria. Nell'esempio seguente, puoi vedere i nomi delle funzioni chiamati, insieme agli argomenti passati a quella funzione. Puoi anche vedere cosa è stato restituito da quelle funzioni all'estrema destra dell'output.
$ 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
Che cosa fa:Visualizza il contenuto del file in ASCII, decimale, esadecimale o ottale.
Spesso capita di aprire un file con un'applicazione che non sa cosa fare con quel file. Prova ad aprire un file eseguibile o un file video usando vim; vedrai solo parole senza senso sullo schermo.
L'apertura di file sconosciuti in Hexdump ti aiuta a vedere cosa contiene esattamente il file. Puoi anche scegliere di vedere la rappresentazione ASCII dei dati presenti nel file utilizzando alcune opzioni della riga di comando. Questo potrebbe aiutarti a darti alcuni indizi su che tipo di file è.
$ 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.@.............|
$
stringhe
Che cosa fa:stampa le stringhe di caratteri stampabili nei file.
Se Hexdump sembra un po' eccessivo per il tuo caso d'uso e stai semplicemente cercando caratteri stampabili all'interno di un binario, puoi utilizzare le stringhe comando.
Durante lo sviluppo del software, viene aggiunta una varietà di messaggi di testo/ASCII, come la stampa di messaggi informativi, informazioni di debug, messaggi di aiuto, errori e così via. A condizione che tutte queste informazioni siano presenti nel file binario, verranno scaricate sullo schermo utilizzando stringhe .
$ strings /bin/ls
rifarsi
Che cosa fa:mostra informazioni sui file ELF.
ELF (Executable and Linkable File Format) è il formato di file dominante per eseguibili o binari, non solo su Linux ma anche su una varietà di sistemi UNIX. Se hai utilizzato strumenti come il comando file, che ti dice che il file è in formato ELF, il passaggio logico successivo sarà quello di utilizzare il readelf comando e le sue varie opzioni per analizzare ulteriormente il file.
Avere a portata di mano un riferimento della specifica ELF effettiva quando si utilizza readelf può essere molto utile. Puoi trovare le specifiche qui.
$ 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
Che cosa fa:Visualizza le informazioni da un file oggetto.
I binari vengono creati quando scrivi codice sorgente che viene compilato utilizzando uno strumento chiamato, ovviamente, compilatore. Questo compilatore genera istruzioni in linguaggio macchina equivalenti al codice sorgente, che possono quindi essere eseguite dalla CPU per eseguire un determinato compito. Questo codice del linguaggio macchina può essere interpretato tramite mnemonici chiamati linguaggio assembly. Un linguaggio assembly è un insieme di istruzioni che aiutano a comprendere le operazioni eseguite dal programma e infine eseguite sulla CPU.
objdump l'utilità legge il file binario o eseguibile e scarica le istruzioni in linguaggio assembly sullo schermo. La conoscenza dell'assemblaggio è fondamentale per comprendere l'output di objdump comando.
Ricorda:il linguaggio assembly è specifico dell'architettura.
$ 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
$
traccia
Che cosa fa:traccia le chiamate e i segnali di sistema.
Se hai utilizzato trace , menzionato in precedenza, pensa a strace essere simili. L'unica differenza è che, invece di chiamare una libreria, la strace l'utilità traccia le chiamate di sistema. Le chiamate di sistema sono il modo in cui ti interfaccia con il kernel per portare a termine il lavoro.
Per fare un esempio, se vuoi stampare qualcosa sullo schermo, utilizzerai printf o put funzione dalla libreria standard libc; tuttavia, sotto il cofano, alla fine, una chiamata di sistema denominata scrivi sarà fatto per stampare effettivamente qualcosa sullo schermo.
$ 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
Che cosa fa:elenca i simboli dai file oggetto.
Se stai lavorando con un file binario che non è stato rimosso, nm comando ti fornirà le preziose informazioni che sono state incorporate nel binario durante la compilazione. non può aiutarti a identificare variabili e funzioni dal binario. Puoi immaginare quanto sarebbe utile se non avessi accesso al codice sorgente del binario analizzato.
Per mostrare nm , scriveremo rapidamente un piccolo programma e lo compileremo con -g opzione, e vedremo anche che il binario non viene rimosso utilizzando il comando file.
$ 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__
$
gb
Che cosa fa:il debugger GNU.
Bene, non tutto nel binario può essere analizzato staticamente. Abbiamo eseguito alcuni comandi che eseguivano il binario, come ltrace e strace; tuttavia, il software è costituito da una varietà di condizioni che potrebbero portare all'esecuzione di vari percorsi alternativi.
L'unico modo per analizzare questi percorsi è in fase di esecuzione, avendo la possibilità di interrompere o mettere in pausa il programma in una determinata posizione ed essere in grado di analizzare le informazioni e quindi spostarsi più in basso.
È qui che arrivano i debugger nell'immagine e su Linux, gdb è il debugger di fatto. Ti aiuta a caricare un programma, impostare punti di interruzione in punti specifici, analizzare memoria e registro della CPU e fare molto di più. Completa gli altri strumenti sopra menzionati e ti consente di eseguire molte più analisi di runtime.
Una cosa da notare è che una volta caricato un programma utilizzando gdb , ti verrà presentato il suo (gdb) richiesta. Tutti gli altri comandi verranno eseguiti in questo gdb prompt dei comandi finché non esci.
Utilizzeremo il programma "ciao" che abbiamo compilato in precedenza e utilizzeremo gdb per vedere come funziona.
$ 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
$
Conclusione
Una volta che avrai acquisito familiarità con l'utilizzo di questi strumenti di analisi binaria nativi di Linux e compreso l'output che forniscono, potrai passare a strumenti di analisi binaria open source più avanzati e professionali come radare2.