L’analisi di malware ELF (Executable and Linkable Format), tipicamente mirato a sistemi Linux, rappresenta una sfida complessa a causa delle sofisticate tecniche di offuscamento e anti-analisi impiegate dagli attaccanti. Questo articolo si concentra sull’analisi statica e la decostruzione di uno stager particolarmente insidioso che utilizza self-modifying code e una chiave XOR polimorfica per mascherare il suo vero contenuto.

L’obiettivo è trasformare un binario non analizzabile staticamente in un formato decrittato e leggibile, superando i blocchi iniziali imposti da istruzioni che generano badcode nei disassemblatori come radare2 e Ghidra. Attraverso un approccio misto che combina analisi dinamica temporanea e successiva ispezione statica del codice dumpato, si rivela la vera natura del malware: un loader in-memory che alloca risorse, stabilisce una connessione Command and Control (C2) con l’indirizzo 10.5.31.54:4445 e scarica un payload secondario dinamico da eseguire immediatamente.

“everything is open source if you can reverse engineer” HCF

Step Fondamentali dell’Analisi Statica Avanzata

  • Stub di decodifica con chiave xor polimorfica
  • Bypass dell’Offuscamento a Runtime (Self-Modifying Code)
  • Analisi Statica del Codice Decrittato
  • Simulazione dell’Infrastruttura C2 con Script Python
  • Estrazione di Indicatori di Compromissione (IoC)
  • Conclusioni

Stub di decodifica con chiave xor polimorfica

Attraverso radare2 si prova ad andalizzare l’unica porzione di disassemblato che abbiamo a disposizione. radare2 (spesso abbreviato in r2) è un framework completo e open source per il reverse engineering e l’analisi di file binari. Non è un singolo strumento, ma una suite di potenti utility a riga di comando che possono essere usate insieme per disassemblare, analizzare, modificare e sfruttare qualsiasi tipo di file binario, come eseguibili, librerie, firmware e malware.

Questa porzione del codice, l’unica valida che abbiamo inizialmente, esegue una decodifica in-place di una sezione di codice successiva utilizzando una chiave XOR che si automodifica (polimorfica)

Il decompilatore di Ghidra traduce il codice macchina a basso livello (assembly) di un programma in un linguaggio ad alto livello, come il C. Il suo scopo è ricostruire una versione del codice sorgente che sia leggibile e comprensibile per un essere umano, facilitando enormemente l’analisi del comportamento di un software senza averne il codice originale.

La particolarità di questo malware è che staticamente non è analizzabile. radare2 e ghidra vedono solo badcode dopo poche istruzioni. L’analisi dinamica mostra che a partire da 0x00400091 il malware si autoriscrive tramite un loop di xor. Per poter ottenere il codice reale ho usato gdb: ho lasciato che il malware completasse la fase di riscrittura in memoria e solo dopo ho analizzato il dump del codice risultante, evitando di continuare l’esecuzione dinamica e avendo così il corpo completo da studiare staticamente.

Bypass dell’Offuscamento a Runtime (Self-Modifying Code)

fcmovnu è un’istruzione di riempimento usata per garantire che ci sia un’istruzione FPU da cui fnstenv possa leggere l’IP (Instruction Pointer). L’indirizzo nella FPU viene aggiornato solo e soltanto quando un’istruzione FPU viene eseguita. Se prima di fnstenv non ci fossero istruzioni FPU, il puntatore salvato potrebbe essere vecchio e completamente inutile, riferito a qualche operazione precedente. Inserendo fcmovnu (che è un’istruzione FPU a tutti gli effetti) subito prima, il malware si assicura che la FPU si “attivi”. Questa attivazione forza l’aggiornamento del suo puntatore interno all’indirizzo di fcmovnu.

Viene utilizzata l’istruzione fnstenv per salvare lo stato della FPU sullo stack. La struttura salvata contiene l’Instruction Pointer (IP)dell’ultima istruzione FPU (fcmovnu). fnstenv [mem] salva lo stato dell’FPU nella memoria indicata. È la vera istruzione che calcola indirettamente l’indirizzo corrente del codice (rip).

pop rdx recupera questo valore, ottenendo un indirizzo relativo a RIP necessario per il calcolo degli offset del codice da modificare.

In x86_64, se vuoi fare self-modifying code o leggere dati vicini al codice stesso, ti serve conoscere l’indirizzo corrente (Instruction Pointer IP). Non esiste un’istruzione diretta mov rax, rip su x86_64. Quindi si usano trick, tra cui fnstenv o call/pop.

Questa istruzione è o un trucco di offuscamento o per far sembrare l’istruzione più complicata. Serve a puntare al blocco di codice da riscrivere, regolando l’indirizzo calcolato con fnstenv. Alla fine in rdx avremo 0x00400081.

Inizialmente quel pop rdi non fa nulla di significativo, serve solo a consumare 8 byte dallo stack e a riempire la memoria come placeholder. Subito dopo, durante il loop xor, i byte di quell’istruzione vengono riscritti in memoria e trasformati in istruzioni valide. Il pop rdi originale sparisce completamente. In pratica era lì solo come dato grezzo per il processo di self-modifying code, non per eseguire realmente un pop utile. Questa tecnica è tipica nei malware che usano self-modifying code: alcune istruzioni iniziali servono solo da “seme” per generare il codice finale eseguibile dopo il decrypt loop:

Calcolo esempio:

Alla fine, si ottiene ‘loop 0x400088’ –> il loop si ripete ecx volte (33), riscrivendo il corpo del malware. Solo al termine il codice diventa eseguibile.

Subito dopo aver decodificato una porzione di codice, il malware modifica la chiave stessa. Questa istruzione prende il valore appena decodificato dalla memoria e lo somma alla chiave contenuta in edi. La chiave è di fatto polimorfica.

Analisi Statica del Codice Decrittato

Il comando pdf sta per Print Disassembly Function ed è uno dei comandi più importanti in Radare2. La sua funzione è quella di disassemblare e stampare l’intero codice assembly della funzione che si trova alla posizione corrente del seek (impostata con il comando s).

Prima syscall = mmap (rax = 9), crea area eseguibile in memoria. La system call mmap (”memory map”) è un meccanismo fondamentale che crea una mappatura tra un file (o un dispositivo) e lo spazio di memoria virtuale di un processo. Il passo logico successivo per questo malware, dopo aver preparato la memoria con mmap, è contattare il server C2 e scaricare il payload in questa nuova area. Un server C2 o C&C (Command and Control) è l’infrastruttura remota che un malintenzionato usa per comunicare e gestire il malware installato sui computer infetti. In pratica, è il quartier generale dell’operazione malevola.

La socket syscall (rax=41, push 0x29, pop rax) è la funzione che crea un nuovo punto di comunicazione (un socket) e ne restituisce un “handle” (un file descriptor). È il primo passo fondamentale per qualsiasi programma che voglia comunicare in rete.

Qui il malware costruisce l’indirizzo IP e la porta in un unico valore a 64-bit:

I byte sono disposti little-endian in memoria, quindi:

I primi 4 byte (0x0a 0x05 0x1f 0x36) rappresentano l’indirizzo IP 10.5.31.54
I successivi 2 byte (0x11 0x5d) rappresentano la porta 4445
In questo modo il malware evita di usare strutture C standard e prepara la sockaddr_in manualmente nello stack.

connect –> ip 10.5.31.54:4445. La syscall connect (rax=42), è la funzione che un programma client utilizza per stabilire una connessione a un server remoto, specificandone l’indirizzo IP e la porta. È il passo che unisce il socket locale, creato in precedenza, a un socket in ascolto su un’altra macchina.

Il codice prepara la struttura sockaddr_in direttamente nello stack, poi esegue la syscall. Se fallisce, il flow salta a 0x400100 per retry o exit. In pratica questa parte del malware stabilisce la connessione verso il C2 e prepara il canale per ricevere il payload.

La nanosleep syscall (rax=35) sospende l’esecuzione di un programma per un intervallo di tempo preciso, specificato in secondi e nanosecondi. È il modoefficiente per mettere in pausa un processo senza sprecare risorse della CPU.

Il codice aspetta 5 secondi e torna indietro per riprovare la connect.

Chiama la syscall exit (rax=60) se avvengono troppi fallimenti della connect.

Salta qui se rax=0 (connect() ritorna 0 in rax se la connessione va a buon fine), quindi prova la syscall read dal socket per scaricare presumibilmente 126 byte di shellcode.

jmp rsi –> esegue shellcode ricevuto

Il jmp rsi non è casuale: rsi contiene il puntatore all’area RWE allocata precedentemente con mmap (rax di mmap era stato salvato e poi passato a rsi). Quindi i 126 byte scaricati dal C2 vengono copiati lì dentro dalla syscall read, e subito dopo il malware trasferisce l’esecuzione a quell’indirizzo con jmp rsi. In pratica, rsi diventa l’entrypoint del payload secondario –> il malware è uno stager che esegue shellcode scaricato a runtime. Se la read non riceve tutti i 126 byte, rax resterebbe <126, ma il codice non fa controlli: salta comunque, quindi anche un buffer parziale verrebbe eseguito (probabilmente parte della logica del loader C2).

Uno stager è un piccolo loader, cioè un malware minimale che serve solo a preparare l’ambiente e scaricare o ricevere un payload più grande (il vero malware) da eseguire successivamente. In genere fa pochissime operazioni: alloca memoria (mmap), apre un socket, si collega a un server C2, poi fa una read/recv per ricevere del codice da remoto e ci salta dentro (jmp rsi in questo caso). In questo modo il binario iniziale resta molto piccolo e difficile da analizzare staticamente. L’analisi completa del comportamento dipende dal payload remoto, che può cambiare dinamicamente.

Uno stager come in questo caso scarica ed esegue subito un payload (tipicamente shellcode) in memoria, non salva nulla su disco: il codice viene eseguito direttamente nello spazio allocato con mmap e raggiunto da jmp rsi. Un downloader invece scarica un file (exe, script, dll, ecc.), di solito lo scrive su disco e poi lo esegue in termini pratici questo malware rientra nella categoria stager perché carica il payload in memoria senza persistenza su file system, ma in senso più generico possiamo dire che si comporta come un downloader in-memory.

Simulazione dell’Infrastruttura C2 con Script Python

Questo frammento dello script Python avvia il server (server.py) sulla porta 4445. L’azione avviene dopo aver configurato la nostra interfaccia di rete con l’indirizzo IP 10.5.31.54. Il suo scopo è servire un payload di 126 byte, composto da una shellcode di 27 byte e dal necessario padding.

Eseguiamo il malware all’interno della nostra sandbox REMnux. Lo stager si connette quindi al nostro server, scarica i 126 byte e li esegue immediatamente.

Abbiamo ottenuto l’accesso alla shell /bin/sh.

Estrazione di Indicatori di Compromissione (IoC)

Self-modifying code
Le istruzioni fcmovnu/fnstenv servono a ottenere l’indirizzo istruzione corrente (trick FPU).

Il loop xor decritta e patcha sé stesso in memoria. Senza esecuzione dinamica non si può ottenere il codice vero.


Attività di rete
Il malware apre un socket tcp verso 10.5.31.54 porta 4445.
In caso di fallimento usa nanosleep per attendere e riprovare.
Se la connessione non va a buon fine dopo vari tentativi, esegue una exit(1).
Se la connessione riesce, fa una read e salta al codice ricevuto (payload dinamico).

Conclusioni

L’analisi avanzata di questo malware ELF ha rivelato uno stager estremamente compatto. La sua principale difesa non risiede in un algoritmo di cifratura complesso, ma nell’uso sapiente del self-modifying code e di tecniche FPU-based per ingannare gli strumenti di disassemblaggio statico.

Risultati Chiave

  • Tecnica Principale: Offuscamento tramite decodifica XOR polimorfica del codice a runtime (self-modifying code).
  • Obiettivo: Il malware è uno stager che alloca un’area RWE (mmap) e tenta di stabilire una connessione TCP verso il server C2 10.5.31.54:4445.
  • Payload: Dopo aver stabilito la connessione, esegue una read dal socket e trasferisce il controllo (jmp rsi) al codice scaricato, garantendo che il payload finale non sia mai presente sul disco, complicando l’analisi forense post-compromissione.

Autore

HCF: hacker artist, reverse engineer, battle programmer e malware & cybersecurity analyst.

Enter My World: https://entermyworld.substack.com
blog: spaghetti-hacker.blogfree.net

Halt and Catch Fire, indicato con la sigla mnemonica di HCF, è un’istruzione fittizia del linguaggio assembly, intesa prevalentemente come uno scherzo, che è stata usata per definire istruzioni solitamente non documentate che portano la CPU in uno stato da cui può essere fatta uscire solo con un reset; solitamente con la sigla HCF si descrive una o più istruzioni non documentate presenti in molte architetture e utilizzate per poter effettuare agevolmente particolari test durante lo sviluppo dei prodotti, ma che non sono da utilizzarsi nella normale operatività.

Il “Reverse Engineering” è l’esigenza, la pretesa da parte degli utenti di poter aprire, esplorare, modificare la tecnologia, secondo le proprie esigenze, ampliare e quindi sviluppare nuove caratteristiche per adattare ogni cosa al quadro tecnologico, che è sempre in evoluzione.

flag{we_beleaf_in_your_re_future}

Ti è piaciuto l’articolo? Seguici su Linkedin per sostenerci!

consigliati