;reverse engineering della funzione main del comando linux whoami
checksec --file ./whoami
[*] '/home/re/whoami'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
;abbiamo tutte le lampadine accese
[0x000027c0]> s main
[0x000025c0]> pdf
;arg int argc @ rdi
;arg char **argv @ rsi
0x000025c0 endbr64
0x000025c4 push r14
0x000025c6 push r13
0x000025c8 push r12
0x000025ca push rbp
0x000025cb push rbx
;salva questi registri nello stack
0x000025cc 488b1e mov rbx, qword [rsi] ; argv
0x000025cf test rbx, rbx
0x000025d2 je 0x275a
;qui fa il check per vedere se argv[0] è valorizzato da shell o viene eseguito tramite execve, nel qual caso, fa un abort
0x000025d8 mov r13d, edi ;argc
0x000025db mov r12, rsi ;argv
0x000025de mov rdi, rbx ;const char *s, argv
0x000025e1 mov esi, 0x2f ;'/' ; int c
0x000025e6 call sym.imp.strrchr ;char *strrchr(const char *s, int c)
;strchr, strrchr, strchrnul - locate character in string
;char *strchr(const char *s, int c);
;restituisce un puntatore all'ultima occorrenza del carattere nella stringa C.
;il puntatore a questo carattere viene messo in rax
;cerca l'ultima occorrenza del carattere / nella stringa argv[0], come quando eseguiamo whoami con /bin/whoami
0x000025eb mov rbp, rax
0x000025ee test rax, rax
0x000025f1 je 0x2640
;qui controlla se è stato trovato o meno il carattere /, nel caso non è stato trovato, salta a 0x2640
0x000025f3 lea r14, [rax + 1]
;passa al carattere successivo, in caso fosse /bin/whoami, punterebbe a whoami
0x000025f7 mov rax, r14
0x000025fa sub rax, rbx
;in rbx c'è il puntatore a argv[0]; sottraendo rbx a rax, abbiamo il numero di caratteri del path del comando; se fosse /bin/whoami avremmo come risultato 5
0x000025fd cmp rax, 6
;questo numero viene comparato a 6, perchè qui vuole fare un controllo particolare, va a controllare se whoami è stato eseguito con /.libs/lt-whoami
0x00002601 jle 0x2640
;se è minore come nel caso di /bin/whoami salta e continua con il setlocale
;se è maggiore di 6
0x00002603 lea rdi, [rbp - 6] ;const char *s1
;cosa c'è in rbp-6?
0x00002607 mov edx, 7 ;size_t n
0x0000260c lea rsi, str._.libs_ ;0x5158 ;"/.libs/" ;const char *s2
0x00002613 call sym.imp.strncmp ;int strncmp(const char *s1, const char *s2, size_t n)
;fa una comparazione tra quello che c'è in rbp-6 e la stringa /.libs/; è una strncmp quindi ha come terzo parametro la size, che è impostata a 7 caratteri; da qui possiamo ipotizzare che :
;rbp-6 --> /.libs/lt-whoami
;rbp-4 --> libs/li-whoami
;rbp --> /lt-whoami
;rbp+4 --> whoami
0x00002618 test eax, eax
0x0000261a jne 0x2640
;se non sono uguali le due stringhe, salta, altrimenti continua
0x0000261c mov edx, 3 ;size_t n
0x00002621 lea rsi, [0x00005160] ;"lt-" ; const char *s2
;in r14 dovremmo avere il comando che in questo branch potrebbe essere lt-whoami
0x00002628 mov rdi, r14 ;const char *s1
0x0000262b mov rbx, r14
0x0000262e call sym.imp.strncmp ;int strncmp(const char *s1, const char *s2, size_t n)
;/.libs/lt-
;fa una comparazione tra i primi 3 caratteri del comando e lt-
;da quel che ho capito possono esistere applicativi whoami del tipo /.libs/lt-whoami, in questo caso salta a una procedura che estrae dalla stringa di comando solo la stringa whoami
0x00002633 test eax, eax
0x00002635 je 0x2720
;se è uguale salta
0x0000263b nop dword [rax + rax]
;è una multi-byte nop, in questo caso dovrebbero essere 5 byte di nop, viene inserita per questioni di allineamento
;qui continua se non è stato trovato il carattere "/", o comunque dopo aver ripulito argv[0]
0x00002640 mov rax, qword [reloc.program_invocation_name] ; [0x7fc8:8]=0
0x00002647 lea rsi, [0x00005021] ;const char *locale
0x0000264e mov edi, 6 ; int category
0x00002653 lea rbp, [0x0000509c] ;"coreutils"
0x0000265a mov qword [0x000081c0], rbx ;[0x81c0:8]=0
0x00002661 mov qword [rax], rbx
0x00002664 call sym.imp.setlocale ;char *setlocale(int category, const char *locale)
;setlocale - set the current locale
;char *setlocale(int category, const char *locale);
;view /usr/include/locale.h
;category 6 LC_ALL
;specifica la lingua in cui sono scritti i messaggi localizzati e le risposte affermative e negative della lingua (le stringhe sì e no e le espressioni).
;la funzione setlocale installa le impostazioni locali del sistema specificate o una sua parte come nuove impostazioni locali. Le modifiche rimangono in vigore e influenzano l'esecuzione di tutte le funzioni della libreria C sensibili alle impostazioni locali fino alla successiva chiamata a setlocale. Se locale è un puntatore nullo, setlocale interroga la locale C corrente senza modificarla.
;in rbx in precedenza abbiamo messo l'indirizzo di argv[0], che andiamo a mettere nella memoria puntata da rax, in program_invocation_name; o cmq viene valorizzato nel modo giusto quando siamo in presenza di /.libs/lt-whoami
;program_invocation_name, program_invocation_short_name : il nome del programma eseguito
0x00002669 lea rsi, str._usr_share_locale ;0x5164 ;"/usr/share/locale" ; char *dirname
0x00002670 mov rdi, rbp ; char *domainname
0x00002673 call sym.imp.bindtextdomain ;char *bindtextdomain(char *domainname, char *dirname)
;la funzione bindtextdomain imposta la directory di base nel filesystem contenente i cataloghi dei messaggi per un determinato dominio dei messaggi.
0x00002678 mov rdi, rbp ;char *domainname
0x0000267b call sym.imp.textdomain ;char *textdomain(char *domainname)
;la funzione textdomain imposta o recupera il dominio dei messaggi corrente.
;un dominio di messaggi è un insieme di messaggi _msgid_ traducibili. Di solito, ogni pacchetto software ha il proprio dominio dei messaggi. Il nome di dominio viene utilizzato per determinare il catalogo dei messaggi in cui viene cercata una traduzione; deve essere una stringa non vuota.
0x00002680 lea rdi, [0x00004ce0]
0x00002687 call fcn.00004d60
[0x000027c0]> s fcn.00004d60
[0x00004d60]> pdf
fcn.00004d60 ();
0x00004d60 endbr64
0x00004d64 mov rdx, qword [0x00008008] ;[0x8008:8]=0x8008
0x00004d6b xor esi, esi
0x00004d6d jmp sym.imp.__cxa_atexit
;__cxa_atexit -- registra una funzione da chiamare all'uscita o quando una libreria condivisa viene scaricata; _cxa_atexit() registra una funzione distruttore che deve essere chiamata da exit() o quando una libreria condivisa viene scaricata.
0x0000268c sub rsp, 8
0x00002690 mov rsi, r12
0x00002693 lea rax, str.Richard_Mlynarik ;0x517b ;"Richard Mlynarik"
0x0000269a push 0
0x0000269c mov r9d, 1 ; int64_t arg11
0x000026a2 lea r8, str.8.32 ; 0x5176 ;"8.32" ;int64_t arg10
0x000026a9 lea rcx, str.GNU_coreutils ;0x5098 ;"GNU coreutils" ;int64_t arg9
0x000026b0 push rax
0x000026b1 lea rax, [fcn.000029f0] ;0x29f0
0x000026b8 lea rdx, str.whoami ;0x5004 ;"whoami" ;int64_t arg8
0x000026bf mov edi, r13d ;int64_t arg5
0x000026c2 push rax
0x000026c3 xor eax, eax
0x000026c5 call fcn.00002fc0
;call fcn.00002fc0
;getopt_long : analizza le opzioni della riga di comando
;funzione dove stampa il Written by e quindi ha come parametri tutte le stringhe informative di sopra; è la funzione che gestisce le opzioni --v e --h, per la versione e l'help e chiama la funzione usage
;fcn.000029f0 : questa è la funzione usage
0x000026ca mov rax, qword [reloc.optind] ;[0x7fb8:8]=0
;la variabile optind è il valore di indice dell'argomento successivo che dovrebbe essere gestito dalla funzione getopt(). opterr ti permetterà di controllare se la funzione getopt() deve stampare errori sulla console
0x000026d1 add rsp, 0x20
0x000026d5 movsxd rax, dword [rax]
;movsxd : move with sign-extension
0x000026d8 cmp eax, r13d ;viene comparato a argc
0x000026db jne 0x277f
0x000026e1 call sym.imp.__errno_location
if (optind != argc)
{
error (0, 0, _("extra operand %s"), quote (argv[optind]));
usage (EXIT_FAILURE);
}
0x000026e6 mov dword [rax], 0
0x000026ec mov rbp, rax
0x000026ef call sym.imp.geteuid ;uid_t geteuid(void)
0x000026f4 mov ebx, eax
;ritorna l'euid dell'utente, che viene comparato al valore -1, se è uguale a -1 e salta in un blocco dove viene notificato che non è riuscito a trovare nessun utente
0x000026f6 cmp eax, 0xffffffff
0x000026f9 jne 0x2701
0x000026fb cmp dword [rbp], 0
0x000026ff jne 0x2733 ;salta nel blocco di errore
;qui invece continua con getpwuid
0x00002701 mov edi, ebx
;in ebx abbiamo messo il valore tornato da geteuid che copiato in edi deventa il parametro della funzione getpwuid
0x00002703 call sym.imp.getpwuid
;struct passwd *getpwuid(uid_t uid);
;la funzione getpwuid() restituisce un puntatore a una struttura contenente i campi scomposti del record nel database delle password che corrisponde all'ID utente uid.0x00002708 test rax, rax
0x0000270b je 0x2733
;salta nel blocco di errore se ottiene valore nullo
0x0000270d mov rdi, qword [rax] ;const char *s
0x00002710 call sym.imp.puts ;int puts(const char *s)
;int puts(const char *s);
;qui stampa la stringa dell'utente,perchè in rax c'è il puntatore alla struttura dell'utente
0x00002715 pop rbx
0x00002716 xor eax, eax
0x00002718 pop rbp
0x00002719 pop r12
0x0000271b pop r13
0x0000271d pop r14
0x0000271f ret
;esce, con valore 0 in rax, ripristinando i valori originali nei registri che in precedenza avevamo pushato nello stack
0x00002720 mov rax, qword [reloc.program_invocation_short_name] ; [0x7fd8:8]=0
0x00002727 lea rbx, [rbp + 4]
0x0000272b mov qword [rax], rbx
0x0000272e jmp 0x2640
;rbp+4 --> whoami
;estrae whoami da /.libs/lt-whoami e lo inserisce in [reloc.program_invocation_short_name]
;_program_invocation_name_ contiene il nome utilizzato per richiamare il programma chiamante. Corrisponde al valore di _argv[0]_ in _main_(), con la differenza che l'ambito di _program_invocation_name_ è globale.
;_program_invocation_short_name_ contiene il componente basename del nome utilizzato per richiamare il programma chiamante. Vale a dire, è lo stesso valore di _program_invocation_name_, con tutto il testo fino alla barra finale inclusa (/), se presente, rimossa.
;queste variabili vengono inizializzate automaticamente dal codice di avvio di runtime di glibc
0x00002733 mov edx, 5
0x00002738 lea rsi, str.cannot_find_name_for_user_ID__lu ;0x5598 ;"cannot find name for user ID %lu"
0x0000273f xor edi, edi
0x00002741 call sym.imp.dcgettext
;"cannot find name for user ID %lu"
;gettext, dgettext, dcgettext - traduce il messaggio in base al linguaggio settato attraverso il setlocale
0x00002746 mov esi, dword [rbp] ;int errname
0x00002749 mov ecx, ebx
0x0000274b mov edi, 1 ;int status
0x00002750 mov rdx, rax ;char *format
0x00002753 xor eax, eax
0x00002755 call sym.imp.error ;void error(int status, int errname, char *format)
;sym.imp.error, qui stampa l'errore "cannot find name for user ID %lu" tradotto
0x0000275a mov rax, qword [reloc.stderr] ;[0x7ff0:8]=0
0x00002761 mov edx, 0x37 ; '7' ;size_t nitems
0x00002766 mov esi, 1 ;size_t size
0x0000276b lea rdi, str.A_NULL_argv_0__was_passed_through_an_exec_system_call._n ;0x5560 ;"A NULL argv[0] was passed through an exec system call.\n" ;const void *ptr
0x00002772 mov rcx, qword [rax] ;FILE *stream
0x00002775 call sym.imp.fwrite ;size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream)
;A NULL argv[0] was passed through an exec system call.\n
;quando esegui il whoami cosi execve("/bin/whoami",NULL,NULL), non gli sta bene ed esce
0x0000277a call sym.imp.abort ;void abort(void)
0x0000277f mov rdi, qword [r12 + rax*8] ;int64_t arg1
0x00002783 lea rsi, [0x00008040] ;int64_t arg2
0x0000278a call fcn.00004b40
;funzione con malloc e memset
[0x00004b40]> pdfs
0x00004b54 call sym.imp.__errno_location
0x00004b8d call sym.imp.realloc
0x00004bc2 call sym.imp.memset
0x00004c00 call fcn.000036a0 fcn.000036a0
0x00004c37 call sym.imp.free
0x00004c53 call sym.imp.malloc
0x00004c84 call fcn.000036a0 fcn.000036a0
0x0000278f mov edx, 5
0x00002794 lea rsi, str.extra_operand__s ;0x518c ;"extra operand %s"
0x0000279b xor edi, edi
0x0000279d mov r12, rax
0x000027a0 call sym.imp.dcgettext
;traduzione extra operand %s
0x000027a5 xor edi, edi ;int status
0x000027a7 mov rcx, r12
0x000027aa xor esi, esi ;int errname
0x000027ac mov rdx, rax ;char *format
0x000027af xor eax, eax
0x000027b1 call sym.imp.error ;void error(int status, int errname, char *format)
;stampa errore tradotto
0x000027b6 mov edi, 1 ;int64_t arg1
0x000027bb call fcn.000029f0
;fcn.000029f0 : questa è la funzione usage
;nella funzione usage ho trovato due funzioni interessanti;
;__printf_chk
;__printf_chk -- formatta e stampa i dati, con controllo dello stack; L'interfaccia __printf_chk() funzionerà allo stesso modo dell'interfaccia printf(), tranne per il fatto che __printf_chk() verificherà l'overflow dello stack prima di calcolare un risultato, a seconda del valore del parametro _flag_. Se si prevede un overflow, la funzione verrà interrotta e il programma che la chiama verrà chiuso.
;In generale, maggiore è il valore di _flag_, maggiori saranno le misure di sicurezza che questa interfaccia dovrà adottare sotto forma di controllo dello stack, dei valori dei parametri e così via.
;la funzione __printf_chk() non è nello standard sorgente; è solo nello standard binario.
;fputs_unlocked
;Ognuna di queste funzioni ha lo stesso comportamento della sua controparte senza il suffisso "_unlocked", tranne per il fatto che non usano il lock (non impostano i loro lock e non verificano la presenza dei lock impostati da altri) e quindi sono thread-unsafe.
Edited by AKIRA BASHO - 21/11/2023, 20:41