GNU/Linux >> Linux Esercitazione >  >> Linux

Strumenti di reverse engineering in Linux:strings, nm, ltrace, strace, LD_PRELOAD

Questo articolo spiega gli strumenti e i comandi che possono essere utilizzati per decodificare un eseguibile in un ambiente Linux.

Il reverse engineering è l'atto di capire cosa fa un software, per il quale non è disponibile codice sorgente. Il reverse engineering potrebbe non fornire i dettagli esatti del software. Ma puoi capire abbastanza bene come è stato implementato un software.

Il reverse engineering prevede i seguenti tre passaggi fondamentali:

  1. Raccolta delle informazioni
  2. Determinazione del comportamento del programma
  3. Intercettare le chiamate in biblioteca

Io. Raccolta delle informazioni

Il primo passo è raccogliere le informazioni sul programma target e su cosa fa. Per il nostro esempio, prenderemo il comando "chi". Il comando 'chi' stampa l'elenco degli utenti attualmente connessi.

1. Comando stringhe

Strings è un comando che stampa le stringhe di caratteri stampabili nei file. Quindi ora usiamo questo contro il nostro comando target (who).

# strings /usr/bin/who

Alcune delle stringhe importanti sono,

users=%lu
EXIT
COMMENT
IDLE
TIME
LINE
NAME
/dev/
/var/log/wtmp
/var/run/utmp
/usr/share/locale
Michael Stone
David MacKenzie
Joseph Arceneaux

Dall'output about, possiamo sapere che "chi" sta usando circa 3 file (/var/log/wtmp, /var/log/utmp, /usr/share/locale).

Ulteriori informazioni:Esempi di comandi di stringhe Linux (cerca testo in file binari UNIX)

2. nm Comando

nm comando, viene utilizzato per elencare i simboli dal programma di destinazione. Utilizzando nm, possiamo conoscere le funzioni locali e di libreria e anche le variabili globali utilizzate. nm non può funzionare su un programma di cui è stato eseguito lo striping usando il comando 'strip'.

Nota:per impostazione predefinita, il comando "chi" è rimosso. Per questo esempio, ho compilato ancora una volta il comando 'who'.

# nm /usr/bin/who

Questo elencherà quanto segue:

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

Nell'output sopra:

  • t|T – Il simbolo è presente nella sezione del codice .text
  • b|B – Il simbolo è nella sezione .data inizializzata dall'ONU
  • D|d – Il simbolo è nella sezione .data inizializzata.

La lettera maiuscola o minuscola determina se il simbolo è locale o globale.

Dall'output about, possiamo sapere quanto segue,

  • Ha la funzione globale (main,set_program_name,usage,ecc..)
  • Ha alcune funzioni locali (print_user, time_string ecc..)
  • Ha variabili inizializzate globali (Version,exit_failure)
  • Ha le variabili inizializzate dall'ONU (time_format, time_format_width, ecc.)

A volte, usando i nomi delle funzioni possiamo indovinare cosa faranno le funzioni.

Leggi di più:10 pratici esempi di comandi Linux nm

Gli altri comandi che possono essere utilizzati per ottenere informazioni sono

  • comando ldd
  • comando fusore
  • comando lsof
  • /file system proc

II. Determinazione del comportamento del programma

3. comando ltraccia

Traccia le chiamate alla funzione di libreria. Esegue il programma in quel processo.

# ltrace /usr/bin/who

L'output è mostrato di seguito.

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)

Puoi osservare che esiste un insieme di chiamate a getutxent e alla sua famiglia di funzioni di libreria. Puoi anche notare che ltrace fornisce i risultati nell'ordine in cui le funzioni sono chiamate nel programma.

Ora sappiamo che il comando "chi" funziona chiamando getutxent e la sua famiglia di funzioni per ottenere gli utenti che hanno effettuato l'accesso.

4. comando strace

Il comando strace viene utilizzato per tracciare le chiamate di sistema effettuate dal programma. Se un programma non utilizza alcuna funzione di libreria e utilizza solo chiamate di sistema, utilizzando ltrace semplice non possiamo tracciare l'esecuzione del programma.

# 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

Puoi osservare che ogni volta che viene chiamata la funzione malloc, chiama la chiamata di sistema brk(). La funzione della libreria getutxent chiama effettivamente la chiamata di sistema "open" per aprire "/var/run/utmp" e inserisce un blocco di lettura e legge il contenuto, quindi rilascia i blocchi.

Ora abbiamo confermato che il comando who ha letto il file utmp per visualizzare l'output.

Sia "strace" che "ltrace" hanno una serie di buone opzioni che possono essere utilizzate.

  • -p pid – Si collega al pid specificato. Utile se il programma è già in esecuzione e vuoi conoscerne il comportamento.
  • -n 2 – Rientra ogni chiamata nidificata di 2 spazi.
  • -f – Segui il fork

Per saperne di più:7 esempi di Strace per eseguire il debug dell'esecuzione di un programma in Linux

III. Intercettare le chiamate in biblioteca

5. LD_PRELOAD &LD_LIBRARY_PATH

LD_PRELOAD ci permette di aggiungere una libreria ad una particolare esecuzione del programma. La funzione in questa libreria sovrascriverà la funzione della libreria effettiva.

Nota:non possiamo usarlo con i programmi impostati con il bit "suid".

Prendiamo il seguente programma.

#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");
  }
}

Compila ed esegui il programma.

# cc -o my_prg my_prg.c
# ./my_prg

Stamperà "Le stringhe non sono abbinate".

Ora scriveremo la nostra libreria e vedremo come possiamo intercettare la funzione libreria.

#include <stdio.h>
int strcmp(const char *s1, const char *s2) {
  // Always return 0.
  return 0;
}

Compila e imposta la variabile LD_LIBRARY_PATH sulla directory corrente.

# cc -o mylibrary.so -shared library.c -ldl
# LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH

Ora verrà creato un file chiamato "library.so".
Imposta la variabile LD_PRELOAD su questo file ed esegui il programma di confronto delle stringhe.

# LD_PRELOAD=mylibrary.so ./my_prg

Ora stamperà "Le stringhe sono abbinate" perché utilizza la nostra versione della funzione strcmp.

Nota:se desideri intercettare qualsiasi funzione di libreria, la tua funzione di libreria dovrebbe avere lo stesso prototipo della funzione di libreria originale.

Abbiamo appena trattato le cose di base necessarie per decodificare un programma.

Per coloro che desiderano fare il passo successivo nel reverse engineering, la comprensione del formato di file ELF e del programma Assembly Language aiuterà in misura maggiore.


Linux
  1. 5 Strumenti Rust che vale la pena provare sulla riga di comando di Linux

  2. 5 strumenti da riga di comando per trovare rapidamente file in Linux

  3. Comando Linux mv

  4. Linux du comando

  5. Comando Linux per trovare stringhe in file binari o non ascii

Tutorial sui comandi delle stringhe Linux per principianti (5 esempi)

Tutorial sui comandi di Linux strace per principianti (8 esempi)

Come tracciare l'esecuzione del programma utilizzando il comando Strace di Linux

Comando 11 Strace con esempio in Linux

Strumenti Sysadmin:11 modi per usare il comando ls in Linux

Come usare il comando Linux Strace