SPAGHETTI HACKER

  1. WHOAMI - bash
    log.013

    Tags
    re
    By AKIRA BASHO il 24 Feb. 2023
     
    0 Comments   43 Views
    .
    bqV1ycz

    ;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
      Share  
     
    .