lunes, 19 de diciembre de 2011

Cifrando un ejecutable en C, problemas (2ª parte)

En esta entrada expliqué los posibles problemas que pueden suceder cuando ciframos un ejecutable y en la última entrada mejoré el “crypter” detectando estas situaciones.

Pero hay otra situación en la que no había caído.

Para empezar veamos la estructura típica de un ejecutable:

image

La sección que nos interesa es la de código que es la que vamos a cifrar, el resto de secciones no las tocaremos.

Pueden existir mas secciones por ejemplo .idata donde se almacena la IT, Import Table y la IAT, Import Address Table. En muchos ejecutables esta sección no existe y la IT y la IAT se almacenan en otras secciones.

Por ejemplo es habitual encontrarse con la IT en la cabecera del ejecutable y la IAT en la sección de código o ambas en la sección de código. Y esta última es la peor situación que nos puede suceder para nuestro crypter:

image

El problema es que el crypter que estoy haciendo en su estado actual hace un XOR a toda la sección de código. Y esto sobre un ejecutable que tiene la IT en la sección de código hace que la IT quede inválida, el loader de Windows no pueda leerla y no pueda cargar el ejecutable.

Este problema ya lo había explicado en la otra entrada, el otro problema, es que la IAT se encuentre en la sección de código.

Supongamos que tenemos un ejecutable que tiene la IT en la cabecera (así no nos molesta) y la IAT en la sección de código:

image

A este ejecutable le pasamos el crypter, el cual hará un XOR a toda la sección de código y agregara una nueva sección para descifrar la sección de código:

image

En esta situación el loader de Windows puede leer correctamente la IT y rellena correctamente la IAT con las direcciones de las funciones utilizadas por el ejecutable.

image

Lo siguiente que sucede es que se ejecuta el código de la nueva sección que es el encargado de descifrar la sección de código, volviendo a hacer XOR a la misma y salta al Entry Point original.

image

Al hacer XOR a la sección de código para descifrarla también se ha hecho XOR a la IAT que había sido rellenada correctamente por el loader y se ha dejado la IAT con direcciones de funciones incorrectas. De modo que el ejecutable cifrado falla en cuando llama a una función de una dll, vamos…siempre.

¿Cómo solucionar esto?

Hay dos opciones, una fácil y otra difícil:

  • La fácil: Si la IT o la IAT se encuentran en la sección de código, no hacer XOR sobre la zona donde se encuentra.
  • La difícil: Si la IT o la IAT se encuentra en la sección de código, moverlas a la nueva sección agregada por el crypter.

En la siguiente entrada implementará la primera opción, la segunda para mas adelante.

Buenas noches!

martes, 15 de noviembre de 2011

Cifrando un ejecutable en C (2º intento)

Hace algunos meses hice un programa que cifraba ejecutables en C, mas tarde comente los problemas que podía dar y qué ejecutables no era capaz de cifrar.

Ahora quiero seguir con ello y para retomar el tema tranquilamente he reorganizado el código del “crypter” utilizando los ficheros de funciones que comencé en esta entrada y ya de paso he añadido código para detectar cuando no es posible cifrar el programa. Para recordar esto podía suceder en las siguientes situaciones:

  • No hay espacio suficiente en la cabecera PE para agregar una sección.
  • La tabla de importaciones está en la sección de código (.text, .code) que es la que cifra mi programa. Esto hace que la tabla quede cifrada y no pueda ser leída por el cargador de Windows.
  • La tabla de importaciones o la “Bound Import Table” está justo después de la tabla de secciones, de modo que si se agrega una sección sobrescribe estas tablas dañándolas.

A continuación pongo el contenido del fichero main.cpp

#include <stdio.h>
#include <windows.h>
#include "PECore.h"

//Rutina encargada de descifrar la sección cifrada
byte uncryptroutine[] =    //21 bytes
 "\xB8\x00\x10\x40\x00"  //B8 00104000  MOV EAX, 401000    Section Start  ||Este valor será modificado
 "\x80\x30\xBB"          //8030 BB      XOR BYTE[EAX], BB  Key
 "\x40"                  //40           INC EAX
 "\x3D\x00\x45\x40\x00"  //3D 00454000  CMP EAX, 404500    Section End  ||Este valor será modificado
 "\x75\xF5"              //75 F5        JNZ SHORT -11      (Complemento a dos de 11 = F5)
 "\xB8\x00\x20\x40\x00"  //B8 00204000  MOV EAX, 402000    Pone en EAX el OEP || Este valor será modificado
 "\xFF\xE0";    //FFE0         JMP EAX     Salta al OEP

//Posiciones de la rutina que tienen que ser corregidas
const int SectionStartPos = 1;
const int KeyPos = 7;
const int SectionEndPos = 10;
const int OEPPos = 17;

const byte CipherKey = 0xBB;

//Modifica la rutina de descifrado para introducirle los datos que cambian
void ChangeUncryptRoutine(byte *uncryptroutine, DWORD VirtualStart, DWORD VirtualEnd, DWORD OEP, byte CipherKey)
{
 memcpy(&uncryptroutine[SectionStartPos], &VirtualStart, sizeof(DWORD));
 memcpy(&uncryptroutine[SectionEndPos], &VirtualEnd, sizeof(DWORD));
 memcpy(&uncryptroutine[OEPPos], &OEP, sizeof(DWORD));
 memcpy(&uncryptroutine[KeyPos], &CipherKey, sizeof(byte));
}

int main(int argc, char *argv[])
{
 if(argc != 2)
 {
  printf("Pasame un archivo como argumento, ej: %s program.exe\n", *argv);
  return EXIT_FAILURE;
 }
 //Necesitamos saber el pINH->OptionalHeader.FileAlignment para saber exactamente el tamaño del nuevo ejecutable una vez cifrado
 DWORD fileAlignment = GetFileAlignment(argv[1]);
 if (fileAlignment == -1)
  return EXIT_FAILURE;

 DWORD fileSize = GetFileSize(argv[1]);
 if (fileSize == -1)
  return EXIT_FAILURE;
 //El nuevo tamaño es el original mas el tamaño de la sección alineada
 DWORD newSize = fileSize + Align(sizeof(uncryptroutine) ,fileAlignment);
 MapInfo mapInfo;
 if (OpenAndMapFile(&mapInfo, argv[1], newSize) == -1)
  return EXIT_FAILURE;
 if (!IsValidExe(mapInfo.viewMappedFile))
 {
  printf("%s no es un archivo ejecutable valido\n", *argv);
  return EXIT_FAILURE;
 }
 PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)mapInfo.viewMappedFile;
 PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)&mapInfo.viewMappedFile[pIDH->e_lfanew];
 PIMAGE_SECTION_HEADER pISH = RVAToSection(mapInfo.viewMappedFile, pINH->OptionalHeader.AddressOfEntryPoint);
 printf("EP apunta a la seccion: %s\n", pISH->Name);

 //Comprobamos si la IT está también en la sección de código, donde apunta el EP, si es así no podemos cifrar el ejecutable
 PIMAGE_SECTION_HEADER pIT = RVAToSection(mapInfo.viewMappedFile, pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
 if (pISH == pIT)
 {
  printf("La tabla de importaciones está en la sección de código, el crypter de momento no puede cifrar estos ejecutables\n");
  return EXIT_FAILURE;
 }

 //Añadimos permisos de escritura a la sección que se va a encriptar
 pISH->Characteristics |= IMAGE_SCN_MEM_WRITE;
 printf("Añadidos permisos de escritura a la seccion: %s\n", pISH->Name);

 //Ciframos la sección donde apunta el EP
 CipherSectionXOR(mapInfo.viewMappedFile, pISH, CipherKey);
 printf("Seccion %s cifrada\n", pISH->Name);

 //Añadimos una sección para incluir en ella la rutina de descifrado
 PIMAGE_SECTION_HEADER pNewISH = AddSection(mapInfo.viewMappedFile, ".crypt", sizeof(uncryptroutine));
 if ((int)pNewISH < 0)
 {
  CloseAndUnmapFile(&mapInfo);
  PrintfAddSectionError(pNewISH);
  return EXIT_FAILURE;
 }
 printf("Seccion %s agregada\n", pNewISH->Name); 

 DWORD OEP = pINH->OptionalHeader.AddressOfEntryPoint;
 //El nuevo entry point estará al comienzo de la nueva sección
 pINH->OptionalHeader.AddressOfEntryPoint = pNewISH->VirtualAddress;
 printf("EP cambiado a: %#x\n", pNewISH->VirtualAddress);

 //Modificamos la rutina de desencriptación
 DWORD VirtualStart = pINH->OptionalHeader.ImageBase + pISH->VirtualAddress;
 DWORD VirtualEnd = VirtualStart + pISH->Misc.VirtualSize;
 OEP += pINH->OptionalHeader.ImageBase;
 //Modifica la rutina de descifrado cambiando los datos necesarios para que funcione
 ChangeUncryptRoutine(uncryptroutine, VirtualStart, VirtualEnd, OEP, CipherKey);
 printf("Rutina de descifrado modificada con los valores: %#x-%#x, %#x, %d\n", VirtualStart, VirtualEnd, OEP, CipherKey);

 if (fileSize != pNewISH->PointerToRawData)
 {
  printf("Existen datos EOF, se copiaran al final del fichero\n");
  //Los datos EOF se desplazan al final del fichero
  memcpy(&mapInfo.viewMappedFile[pNewISH->PointerToRawData + pNewISH->SizeOfRawData], &mapInfo.viewMappedFile[pNewISH->PointerToRawData], fileSize - pNewISH->PointerToRawData);
 }

 //Escribimos la rutina de desencriptación al inicio de la nueva sección
 memcpy(&mapInfo.viewMappedFile[pNewISH->PointerToRawData], uncryptroutine, sizeof(uncryptroutine));
 //Rellenamos de 00s el resto de la sección
 memset(&mapInfo.viewMappedFile[pNewISH->PointerToRawData + sizeof(uncryptroutine)], 0, pNewISH->SizeOfRawData - sizeof(uncryptroutine));
 printf("Rutina de descifrado escrita\n");
 
 //Cerramos la vista mapeada
 CloseAndUnmapFile(&mapInfo);

 printf("Archivo encriptado!\n");
 return EXIT_SUCCESS;
}

El resto de ficheros necesarios (PECore.cpp y PECore.h) junto al propio main.cpp los subo aquí.

En las siguientes entradas modificaré el crypter para que no cifre la IT si la encuentra en la sección de código, mejoraré el código en ensamblador encargado de cifrar y otras cosas según vayan surgiendo.

Saludos!

jueves, 1 de septiembre de 2011

Cómo conseguir la IP de tus contactos de Windows Live Messenger

Buenas, en muchos sitios se dice cómo es posible obtener la IP de un contacto de Messenger enviándole un fichero y mirando con netstat las conexiones.

Aquí voy a mostrar cómo es posible hacerlo sin enviar ningún fichero. Y obtener no solo la IP pública sino también las IPs internas de sus adaptadores de red. Se hará de forma manual, usando netstat, después usando Wireshark y por último expondré un programa que he realizado que automáticamente muestra las IPs de los contactos según se van obteniendo.

Lo primero que debemos saber es cómo funciona Messenger por encima. La autenticación la realiza conectándose a los servidores de Microsoft vía SSL. Una vez autenticado todas las conversaciones pasan por los servidores de Microsoft sin ningún tipo de cifrado (así que cuidado con utilizar Messenger en redes inseguras).

Un tema aparte es como se envían entre los contactos ficheros, imágenes, avatares y emoticonos. Para ello Messenger utiliza un protocolo p2p. Así que cuando un contacto solicita por ejemplo nuestro avatar se inicia una negociación entre nosotros y el contacto para ver cómo es posible realizar la conexión. En este punto ambas partes se intercambian las IPs internas y externas, puertos, tipo de conexión, si usamos NAT, UPnP, etc…

En en este punto a donde podemos aprovecharnos para saber la IP del otro contacto.


Con netstat

La opción más fácil es utilizar netstat para monitorizar las conexiones y ver las nuevas conexiones que realiza el proceso del Messenger. Vamos a ello. Todo desde la consola.

El proceso encargado de las comunicaciones de Messenger es wlcomm.exe. Así que obtenemos su PID:

tasklist | find "wlcomm.exe"

Ahora con el PID podemos filtrar la salida de netstat para quedarnos solo con las conexiones que realiza dicho proceso(requiere permisos de administrador):

netstat -nabo | find "PID_ENCONTRADO"
image

Ahora en este punto iniciamos una conversación con el contacto del que nos interesa saber la IP y le enviamos un emoticono personalizado, para no levantar sospechas lo mejor es un emoticono transparente o blanco. Se iniciará la negociación entre los dos clientes y se iniciará la conexión. Volvemos a lanzar el comando anterior y veremos una nueva conexión de nuestro contacto.

image


Con Wireshark

Vamos a filtrar los paquetes obtenidos por Wireshark para quedarnos con aquellos donde se realiza la negociación entre dos contactos. De ahí cogeremos la IP externa y las internas.

Wireshark ofrece el filtro “msnms” para quedarnos con los paquetes del procolo de Messenger.

image

Mirando paquete a paquete en uno de ellos encontraremos algo así:

image

Fijaos en estas cadenas:
“srddA-lanretxE4vPI” Dando la vuelta a la cadena: IPv4External-Addrs
“srddA-lanretnI4vPI” –> IPv4External-Addrs

En esos campos se muestran las IPs y puertos donde se debe realizar la conexión. Claro que hay que darles la vuelta. Mirando el campo “From:” sabremos de que contacto es la IP.

Ir mirando los paquetes uno a uno buscando estos campos no parece muy divertido. Es mas cómodo hacer un filtro que busque en todo el paquete la cadena “srddA-lanretxE4vPI” o “stroPdnAsrddAlanretxE4vPI” (que es otra de las formas en las que aparece).

Este es el filtro que se queda con los paquetes que nos interesan:

frame[0:] contains 73:74:72:6f:50:64:6e:41:73:72:64:64:41:6c:61:6e:72:65:74:78:45:34:76:50:49:3a:20:36:34:37:33:3a:31:39:2e:35:30:31:2e:39:34:2e:35:38:0d:0a or frame[0:] contains 73:72:64:64:41:2d:6c:61:6e:72:65:74:78:45:34:76:50:49

image


Con mi programa “GetMSNIPs”

Usando Wireshark con el filtro se pueden obtener las IPs de una manera bastante cómoda. Pero me apetecía hacerlo un poco mas sencillo y ya de paso programar algo usando WinPcap.

El resultado es este programa que ahora expongo, le he llamado GetMSNIPs, me encantan los nombres originales. No pongo el código aquí que son 300 líneas.

GetMSNIPs: Source y binario.

Una captura de cómo funciona:

image

Modo de uso:

  1. Si no tiene instalado WinPcap, instálelo!.
  2. Arranque “GetMSNIPs” y seleccione la interfaz con la que se conecta a internet.
  3. Inicie Windows Live Messenger y cámbiese de avatar, utilice uno que no haya usado antes (así sus contactos no lo tendrán y se iniciará una conexión P2P para su envío).
  4. Espere un tiempo ó inicie conversaciones con sus contactos para forzar a que carguen su avatar y capturar así su IP.

 

Así que ya sabéis, si usáis Windows Live Messenger estas diciendo a tus contactos tu dirección IP. No es un gran problema, pero está bien saberlo.

Saludos!

martes, 30 de agosto de 2011

Hacer un ejecutable aun más pequeño

En el anterior post se mostraba cómo configurar Visual Studio para generar ejecutables pequeños de 1 kb. Es posible que un ejecutable sea todavía mas pequeño. Aunque ya hay que meterse en ensamblador y usar un editor hexadecimal.

Lo único que he encontrado al respecto es este artículo de Solar Eclipse.
http://www.phreedom.org/solar/code/tinype/

En él llega a un ejecutable de 97 bytes pero tras probarlo en diferentes versiones de Windows he constatado que solo funciona en versiones anteriores a Windows XP SP2, en esta versión y en posteriores no funciona, ni este ejecutable de 97 bytes, ni otros tantos del mismo artículo. También hay problemas con las versiones de Windows de 64 bits, donde el loader tiene comportamiento ligeramente diferente. Veámoslo.

En el artículo de Solar Eclipse, lo primero que hace es eliminar la librería Runtime de C para conseguir un ejecutable de 1k, como se hizo en el anterior artículo.

Lo siguiente que hace es reducir el valor de SectionAlignment mediante el parámetro /ALIGN:1 (Explicación en MSDN).

El problema es que VC++ 9.0 devuelve un error al intentar usar dicho parámetro:

Error    1    fatal error LNK1164: alineación de la sección 0x2 (2) superior al valor /ALIGN

El mínimo valor que deja usar es 4 y aun así modifica solo el valor de SectionAlignment dejándolo a 4, pero no modifica el valor de FileAlignment (como si hacía VC 6.0) para dejarlo también a 4.

image

Esto hace que el ejecutable no cumpla el estándar (Microsoft Portable Executable and Common Object File Format Specification), el cúal dice claramente:
”The section align must be greater than or equal to FileAlignment.”

“If the SectionAlignment is less than the architecture’s page size (0x1000), then FileAlignment must match SectionAlignment.”

En este caso en el que SectionAlignment es menor que 0x1000 FileAlignment también debe valer 4. Si cambiamos el valor de FileAlignment veremos como el ejecutable si funciona en todos los Windows >= Windows XP excepto en las versiones de x64 bits.

¿Por qué no funciona en las versiones de 64 bits? No se la razón exacta, solo se que para que funcione correctamente es necesario que SizeOfImage sea 0x1000 mayor de lo que debería ser (Extraño, si alguien sabe el por qué exacto que no dude en decirlo). Esto solo se da cuando SectionAlignment es menor que el page size.

Continuemos con el artículo de Solar Eclipse.

 

“Switching to assembly and removing the DOS stub”

El siguiente paso que toma para reducir el tamaño de un ejecutable es utilizar ensamblador, NASM. Es curioso que en el propio código en ensamblador del programa defina los campos de la cabecera PE. De este modo evita el uso del linker, que debería ser el que ensambla el ejecutable, crea la cabecera PE, las secciones, incluye los recursos, etc… Con esto logra una gran flexibilidad a la hora de generar un ejecutable con una cabecera personalizada.

Aunque de nuevo nos encontramos con problemas. Ya que en la primera revisión de 2009 de Nasm se introdujo el macro SECTALIGN. El código de Solar Eclipse, usa una variable con el mismo nombre, NASM nos da un error al ser una palabra reservada. Así que es necesario renombrar la variable SectAlign.

Este ejecutable, el de 356 bytes, no funciona en las versiones de 64 bits por lo que he explicado antes. Se puede solucionar cambiando esta línea:

dd round(filesize, sectalign) ; SizeOfImage

Por esta:

dd round(filesize, sectalign) + 0x1000 ; SizeOfImage

Así nos funcionará en todas las versiones de Windows >= XP tanto en 32 como en 64 bits.

tiny_356_mod.asm | tiny_356_mod.exe

 

“Collapsing the MZ header”

En este apartado superpone la cabecera PE con la cabecera MZ. Sitúa la cabecera Pe en el offset 4 haciendo que varios campos de ambas cabeceras se superpongan. El único campo importante de la cabecera MZ, aparte del magic number, es el e_lfanew, en el offset 0x3C. Este campo coincide con el campo de la cabecera PE, SectionAlignment. Da a ambos campos el valor de 4.

Para entender mejor esto en el desparecido blog de Ero Carrera se pueden ver algunos gráficos explicando los campos de este ejecutable.
http://web.archive.org/web/20081011023041/http://blog.dkbza.org/2007/03/tiny-and-crazy-pe.html

Como en el anterior apartado para que funcione en todos los Windows es necesario cambiar el valor del SizeOfImage.

tiny_296_mod.asm | tiny_296_mod.exe

 

“Removing the data directories”

Este apartado será el último que seremos capaces de realizar si queremos que el ejecutable funcione en todas las versiones de Windows.

La modificación que realiza aquí consiste en eliminar el directorio de datos, ya que no se están usando.

dd 0             ; NumberOfRvaAndSizes

Ahorrando así, 16x8=128 bytes.

Pero el ejecutable resultante no funciona en Windows XP SP2 y en siguientes versiones de Windows. El mensaje es curioso:

image
¿128 bytes demasiado extenso? :P

El problema en esta ocasión parece estar en que a pesar de que se ha puesto en el campo “NumberOfRvaAndSizes” un 0, el Loader espera que en disco esté toda la tabla de directorios, aunque esté vacía.

Así este ejecutable funciona:

image

Pero este otro no:

image

Fijaos que los últimos 4 ceros perteneces al Overlay del ejecutable. No pertenecen a ninguna sección, ni se ven reflejados en ningún campo del PE. Un tanto enigmático…

De este modo llegamos al ejecutable mas pequeño que funciona en todas las versiones de Windows superiores a XP, incluida.

; tiny.asm

BITS 32

;
; MZ header
;
; The only two fields that matter are e_magic and e_lfanew

mzhdr:
    dw "MZ"       ; e_magic
    dw 0          ; e_cblp UNUSED

;
; PE signature
;

pesig:
    dd "PE"       ; e_cp, e_crlc UNUSED       ; PE signature

;
; PE header
;

pehdr:
    dw 0x014C     ; e_cparhdr UNUSED          ; Machine (Intel 386)
    dw 0          ; e_minalloc UNUSED         ; NumberOfSections, 0
code:
    dd 0xC3582A6A ; e_maxalloc, e_ss UNUSED   ; TimeDateStamp UNUSED     ; Code (push byte 42, pop eax, ret)
    dd 0          ; e_sp, e_csum UNUSED       ; PointerToSymbolTable UNUSED
    dd 0          ; e_ip, e_cs UNUSED         ; NumberOfSymbols UNUSED
    dw opthdrsize ; e_lsarlc UNUSED           ; SizeOfOptionalHeader
    dw 0x103      ; e_ovno UNUSED             ; Characteristics

;
; PE optional header
;

filealign equ 4
sectalignn equ 4   ; must be 4 because of e_lfanew

%define round(n, r) (((n+(r-1))/r)*r)

opthdr:
    dw 0x10B      ; e_res UNUSED              ; Magic (PE32)
    db 8                                      ; MajorLinkerVersion UNUSED
    db 0                                      ; MinorLinkerVersion UNUSED
    dd round(4, filealign)                    ; SizeOfCode UNUSED
    dd 0          ; e_oemid, e_oeminfo UNUSED ; SizeOfInitializedData UNUSED
    dd 0          ; e_res2 UNUSED             ; SizeOfUninitializedData UNUSED
    dd code                                   ; AddressOfEntryPoint
    dd code                                   ; BaseOfCode UNUSED
    dd round(filesize, sectalignn)             ; BaseOfData UNUSED
    dd 0x400000                               ; ImageBase
    dd sectalignn ; e_lfanew                  ; SectionAlignment
    dd filealign                  ; FileAlignment
    dw 4                          ; MajorOperatingSystemVersion UNUSED
    dw 0                          ; MinorOperatingSystemVersion UNUSED
    dw 0                          ; MajorImageVersion UNUSED
    dw 0                          ; MinorImageVersion UNUSED
    dw 4                          ; MajorSubsystemVersion
    dw 0                          ; MinorSubsystemVersion UNUSED
    dd 0                          ; Win32VersionValue UNUSED
    dd round(filesize, sectalignn) + 0x1000; SizeOfImage
    dd round(hdrsize, filealign)  ; SizeOfHeaders
    dd 0                          ; CheckSum UNUSED
    dw 2                          ; Subsystem (Win32 GUI)
    dw 0x400                      ; DllCharacteristics UNUSED
    dd 0x100000                   ; SizeOfStackReserve UNUSED
    dd 0x1000                     ; SizeOfStackCommit
    dd 0x100000                   ; SizeOfHeapReserve
    dd 0x1000                     ; SizeOfHeapCommit UNUSED
    dd 0                          ; LoaderFlags UNUSED
    dd 0                          ; NumberOfRvaAndSizes UNUSED

;
; Data directories
;

    times 16 dd 0, 0 ; Empty Directory data, it's needed in Windows >= XP SP2
    dd 0, 0, 0, 0  ; Compatibility with x64 Windows

opthdrsize equ $ - opthdr

hdrsize equ $ - $$

filesize equ $ - $$

Una última cosa. Para que funcione en Windows de 64 bits requiere 16 bytes más a continuación de la tabla de directorios :|

tiny-Compatible.asm | tiny-Compatible.exe

268 bytes, esto es lo mínimo que puede ocupar un ejecutable que funcione en XP, Vista y W7 tanto en versiones de 32 como de 64 bits.

4 bytes de la cabera MZ + 24 bytes de la cabecera PE + 96 bytes de la cabecera PE Opcional + 128 bytes de la tabla de directorios (compatibilidad >= XP SP2) + 16 bytes (compatibilidad x64) = 268 bytes.

 

Documentación relacionada:

Después en el artículo para conseguir dejar el exe en 97 bytes superpone la tabla de secciones sobre la cabecera PE. Otra opción hubiese sido eliminar la tabla de secciones y utilizar un ejecutable sin ninguna sección. Esto está explicado en esta presentación de Alexander Liskin. PE: Specification vs. Loader.

Otro documento muy interesante en el que se lleva el formato PE es el white papper Undocumented PECOFF de ReversingLabs, presentado este año en la BlackHat USA.

 

Ha sido un artículo un poco pesado, con bastantes cosas “mágicas” que no se sabe muy bien por qué suceden. Para entender bien la implementación del loader de windows habría que realizar ingeniería inversa al mismo, ¿eso es legal?. Todas las demás conclusiones pueden estar equivocadas.

Si alguien logra un ejecutable menor de 268 bytes compatible con "todos" los Windows o hay alguna parte equivocada os animo a corregirme en los comentarios.
Saludos!

lunes, 22 de agosto de 2011

Generar ejecutables pequeños con VC++ 9.0 y Visual Studio 2008

En esta entrada voy a explicar cómo configurar Visual C++ 9.0, en el entorno Visual Studio 2008, para que genere ejecutables lo más pequeños posibles.

Me he basado en este artículo, que explica lo mismo para VC++ 6.0:
http://www.catch22.net/tuts/minexe

Para hacer las pruebas vamos a realizar un programa muy sencillo, que muestra un mensaje y se cierra.

#include <stdio.h>

int main(int argc, char* argv[])
{
	printf("Hola");
	return 0;
}

Este sencillo programa ocupa 30 Kb. Lo primero que hay que hacer cambiar la configuración del proyecto que por defecto está en modo DEBUG. Cambiándola a RELEASE conseguimos que el ejecutable baje a 7 kb. Ahora sobre esta configuración iremos cambiando cosas para disminuir aun mas el peso.

El compilador VC++ como se explica en el artículo que puse al principio, define su propio punto de entrada, donde hace distintas inicializaciones y ya después de todo eso ejecuta el main. Esquema de esta función:

int mainCRTStartup()
{
    int retval;
    init_heap();
    parse_command_line();
    init_global_vars();
    init_exception_handling();
    // finally call the user-defined main
    retval = main();
    // terminate all threads and exit
    ExitProcess(retval);
}

Esto supone código y dependencias extra. Podemos decir a VC++ que nuestro punto de entrada sea nuestra función main, de esta forma ignorará su función por defecto.

Para ello en Visual Studio, en las propiedades de nuestro proyecto en el apartado “Propiedades de configuración > Vinculador > Avanzadas” en “Punto de entrada” pondremos “main”.

image

Con esto reducimos el ejecutable hasta los 3.5 kb.

Hay que tener cuidado porque al saltarnos la función inicial de VC++ (donde se llamaba a “parse_command_line()”) ya no podemos acceder a los argumentos mediante argc y argv. Si necesitamos acceder a los mismos se puede hacer con la API GetCommandLine.

Sigamos adelgazando el ejecutable, veamos las secciones que tiene:

image

Una sección que nos podemos ahorrar es la de .reloc. Las relocalizaciones. Esta sección es mas típica de DLLs, que pueden ser cargadas en diferentes direcciones de memoria. En este ejecutable aparece porque esta activada la opción de DYNAMICBASE para que el ejecutable sea compatible con ASLR. Podemos confiar en que nuestro ejecutable conseguirá cargarse en la dirección base que tiene configurada por defecto y que no necesitará las relocalizaciones, en los ejecutables no suele dar problemas esto.

Para desactivar esta opción vamos a “Propiedades de configuración > Vinculador > Avanzadas”, “Dirección base aleatoria” y seleccionamos Deshabilitar.

image

Ahora el ejecutable pesa 3.37 kb.

Volvemos a ver las secciones y se ve como hay información de depuración en EOF. En la tabla de directorios también aparece:

image

Solo es una ruta hacia el fichero pdb de nuestro equipo el cual contiene toda la información de depuración, no nos hace ahorrar mucho, pero cuanto mas simple mantengamos el ejecutable mejor.

“Propiedades de configuración > Vinculador > Depuración”, “Generar información de depuración”, “No”.

Dejamos el ejecutable en los 3 kb.

Otra sección que se puede eliminar es la sección de recursos, .rsrc. se puede ver que solo contiene el archivo de manifiesto. En él se especifica los permisos con los que se necesita arrancar el ejecutable (UAC). Además también se incluye aquí algunas dependencias, ensamblados, como es el caso de MSVCR90.DLL, que solo se puede cargar desde el manifiesto (no entiendo muy bien el por qué, se admiten explicaciones en los comentarios y lo incorporo aquí).

El hecho de depender de MSVCR90.DLL hace además que sea necesario instalar el “Paquete redistribuible de Microsoft Visual C++ 2008” en aquellos equipos donde se vaya a ejecutar la aplicación.

¿Opciones?

  • Usar otra librería en tiempo de ejecución para C. Aquí podemos ver las alternativas:
    http://msdn.microsoft.com/en-us/library/abx4dbyh(v=VS.90).aspx
    Por defecto se usa “Multithreaded, dynamic link” /MD. La cual requiere la carga de la DLL MSVCR90.DLL.
    Se puede usar la opción “Multithreaded, static link”. La librería se “linka” estáticamente, nos libramos de DLLs externas pero el ejecutable incrementa su tamaño en 37 Kbytes.
  • Usar solamente la API de Windows, así no dependemos de librerías y no aumentamos el tamaño del ejecutable.

Como buscamos disminuir el tamaño del ejecutable elegimos la segunda opción. Así que nos toca buscar una equivalencia al printf con la API de Windows:

#include <windows.h>

int main()
{
	WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), "Hola", 4, NULL, NULL);
	return 0;
}

Como ya no se va a usar la librería en tiempo de ejecución para C, la quitamos. “Propiedades de configuración > Vinculador > Entrada” en “Omitir todas las bibliotecas predeterminadas” ponemos Si.

image

Ahora al compilar veremos un error:

image

Desaparece poniendo en “Propiedades de configuración > C/C++ > Generación de código” en “Comprobación de seguridad de buffer” No (/GS-).

image

Ya podremos quitar el manifiesto ya que no necesitamos permisos especiales para el UAC, ni la carga de ningún ensamblado. Con ello quitaremos también al sección .rsrc.

En “Propiedades de configuración > Vinculador > Archivo de manifiesto” en “Generar manifiesto” ponemos No.

image

Nos quitamos al sección .rsrc. 1 kb menos, el ejecutable pesa 2 kb.

Ahora solo tenemos 2 secciones .text y .rdata.

image

En .text tenemos el código mínimo para ejecutar nuestro programa. Sin inicializaciones ni cosas raras.

image

En .rdata están la cadena “Hola” y la Import Table.

Lo que sigue dando tamaño al ejecutable es la existencia de las 2 secciones alineadas. Para ello es posible combinar una en la otra con la siguiente directiva:

#include <windows.h>

#pragma comment(linker,"/merge:.rdata=.text")

int main()
{
	WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), "Hola", 4, NULL, NULL);
	return 0;
}

Con esto dejamos el ejecutable en 1 kb. Lo mas bajo que es posible dejarlo sin editar el ejecutable a mano. Subo aquí el proyecto de Visual Studio.

Para reducirlo mas, ~130 bytes, se puede leer este texto: Tiny PE, es una pasada, aunque el ejecutable generado no es compatible con Windows 7.

Saludos!

jueves, 18 de agosto de 2011

Sencillo programa para encontrar huecos en ejecutables

Seguramente muchos conozcáis la herramienta ToPo un programa, con 12 años a sus espaldas, para buscar huecos en los ejecutables y así poder insertar código sin necesidad de añadir nuevas secciones.

Estos huecos surgen cuando se alinean las secciones del ejecutable en disco con el valor FileAlignment que suele tomar el valor de 0x200. Por ejemplo si un programa tiene una sección que realmente ocupa 0x310 bytes, estará obligado a tener una sección en el ejecutable en disco de tamaño como mínimo 0x400 bytes y gracias a este alineamiento hay 0xF0 bytes al final de la sección que no son utilizados para nada.

El caso es que quería crearme un sencillo programa que encontrara estos huecos, para después usarlo en el crypter y si se puede no añadir una nueva sección aprovechando los huecos.

La idea es recorrer todas las secciones y comprobar una a una si el valor de VirtualSize, el tamaño real de al sección, es menor que el tamaño de al sección en disco, SizeOfRawData. En este caso existe un hueco en esa sección de tamaño SizeOfRawData – VirtualSize.

Aquí os dejo el código, la mayoría de la funcionalidad se encuentra en el fichero PECore.cpp, un fichero donde he sacado las funciones mas comunes ha realizar con ejecutables. Así es mas fácil entender el código.

#include <stdio.h>
#include <windows.h>
#include "PECore\PECore.h"

int main(int argc, char *argv[])
{
	if(argc != 2)
	{
		printf("Se esperaba 1 parametro, el fichero donde se buscarán los huecos\n");
		printf("Ej: %s notepad.exe\n", argv[0]);
		return EXIT_FAILURE;
	}
	MapInfo mapInfo;
	if (OpenAndMapFile(&mapInfo, argv[1]) == -1)
		return EXIT_FAILURE;
	//En cada sección se comprueba si el tamaño virtual es menor que el tamaño en disco de la sección
	//VirtualSize < SizeofRawData, en ese caso hay un hueco que ha surgido de las alineaciones
	PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)mapInfo.viewMappedFile;
	PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)&mapInfo.viewMappedFile[pIDH->e_lfanew];
	for(DWORD i = 0; i < pINH->FileHeader.NumberOfSections; i++)
	{
		PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)&mapInfo.viewMappedFile[pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + i*sizeof(IMAGE_SECTION_HEADER)];
		if (pISH->Misc.VirtualSize < pISH->SizeOfRawData)
		{
			//Hay un hueco, imprimimos su ubicación y tamaño
			DWORD Pos = pISH->VirtualAddress + pISH->Misc.VirtualSize;
			printf("Hueco encontrado!\n");
			printf("Section: %s\n", pISH->Name);
			printf("RVA:\t%#x\n", Pos);
			printf("Offset:\t%#x\n", RVAToOffset(mapInfo.viewMappedFile, Pos));
			printf("Size:\t%#x\n\n", pISH->SizeOfRawData - pISH->Misc.VirtualSize);
		}
	}
	CloseAndUnmapFile(&mapInfo);

	return EXIT_SUCCESS;
}

Subo los ficheros PETools.h y PETools.cpp aquí, espero ir mejorándolos con lo que necesite.

Por supuesto ToPo es mucho mas completo, da la posibilidad de redireccionar el EP al hueco, añadir secciones y modificar sus permisos.

Saludos!

viernes, 12 de agosto de 2011

Cifrando un ejecutable en C, problemas


Cifrando un ejecutable en C

El código que se mostró en la anterior entrada para cifrar un ejecutable haciendo XOR sobre toda su sección de código no funciona con todos los ejecutables. Vamos a ver algunas de las posibles situaciones en las que se me ocurre que puede fallar.

En el ejecutable del netcat la IT, Import Table, estaba en la sección .rdata. Para ver en que sección está la IT se puede mirar en la cabecera PE, en el directorio de datos, la VirtualAddress del directorio de importaciones.

image

Y mirar en que sección cae esta dirección. Claro que es mucho mas fácil abrir el ejecutable con OllyDbg y ver el mapa de memoria:

image

En el caso del netcat el crypter no tocaba en ningún momento la IT. Pero hay muchos ejecutables, por ejemplo la calculadora de windows, en el que la IT está en la sección de código:

image

¿Qué sucede en estos casos?

Que nuestro crypter cifra toda la sección de código incluida la IT. Y cuando Windows carga el ejecutable, y el loader trata de recorrer la IT para rellenar la IAT, Import Address Table, se encuentra con una tabla sin sentido, con punteros a zonas de memoria que no existen y BOOM!! Explota:

image

Deberíamos o bien, omitir a la hora de cifrar la zona donde está la IT y la IAT. O bien, invalidar la IT en el directorio de datos y cargarla desde la rutina que hemos añadido después de descifrar la sección.


Otra de las cosas que puede fallar es cuando la IT u otra tabla de datos se encuentra en la cabecera PE, aprovechando el espacio libre que surge de alinear el tamaño de la cabecera, este espacio está justo después de la tabla de secciones. Volviendo a la calculadora de nuevo, podemos observar como la tabla “BoundImportDirectory” está dentro de la cabecera en la posición 260h:

image

Está tabla es usada para cargar mas rápidamente un ejecutable, ya que digamos que tiene precargada toda la IAT para una determinada versión de librerias, DLLs, ahorrandose así el tiempo de recorrer toda la IT e ir buscando la dirección de cada función para compeltar la IT. Si ya conoce al versión de DLL que se va a cargar ya sabe exactamente en que dirección se va a encontrar la función.

El caso es que esta tabla está en la posición 260h, justo despues de la tabla de secciones:

image

Y cuando nuestro crypter de la anterior entrada añade una sección sobrescribe parte de está tabla dejándola con datos erróneos.

Al añadir una sección deberíamos tener cuidado de no sobrescribir ninguna tabla, y si es necesario relocalizar la tabla en otro lugar.


Otro problema, es que nuestra rutina de descifrado usa el registro EAX, el cual deja con el valor del Original Entry Point, así cuando se ejecuta el código original no está todo como debería estar si no se hubiese cifrado el ejecutable, ya que aunque la pila contiene exactamente lo mismo y la mayoría de los registros están sin cambiar, el registro EAX, que suele inicializarse a 0, en nuestro caso tendría otro valor. Esto puede dar problemas en muy raras ocasiones en las que se presuponga que inicialmente EAX vale 0 y el programa no se encargue de inicializar el registro. Pero si he visto código en ensamblador que usa el valor inicial de EBX, ya que el loader de Windows lo inicializa con la dirección del Process Enviroment Block, PEB, y es una forma mas cómoda de acceder al PEB, sin pasar antes por el TIB (fs:30).

Conclusión, el crypter debería dejar los valores de los registros y de la pila intactos tras ejecutar su rutina de descifrado. Para ello es muy habitual hacer uso de las instrucciones PUSHAD y POPAD que guardan y restablecen respectivamente todos los registros.

Y esto es lo que de momento se me ocurre que podría fallar, seguro que van surgiendo mas fallos según vaya probándolo.

Ahora toca mejorar el código del crypter para que tenga en cuenta estas cosas.

Saludos!

jueves, 11 de agosto de 2011

Cifrando un ejecutable en C


Cifrando un ejecutable a mano
Cifrando un ejecutable a mano, dos variaciones

En las dos entradas anteriores hemos visto como cifrar un ejecutable a mano y algunas posibles variaciones a la hora de hacerlo. Conociendo el proceso para realizarlo a mano hacer un programa que haga lo mismo es cuestión de paciencia.

Aquí os dejo el código fuente en C del programa que he realizado.
#include <stdio.h>
#include <windows.h>

//Rutina encargada de descifrar la sección cifrada
char uncryptroutine[] =		  //21 bytes
	"\xB8\x00\x10\x40\x00"  //B8 00104000  MOV EAX, 401000    Section Start  ||Este valor será modificado
	"\x80\x30\xBB"          //8030 BB      XOR BYTE[EAX], BB  Key
	"\x40"                  //40           INC EAX
	"\x3D\x00\x45\x40\x00"  //3D 00454000  CMP EAX, 404500    Section End  ||Este valor será modificado
	"\x75\xF5"              //75 F5        JNZ SHORT -11      (Complemento a dos de 11 = F5)
	"\xB8\x00\x20\x40\x00"  //B8 00204000  MOV EAX, 402000    Pone en EAX el OEP || Este valor será modificado
	"\xFF\xE0";				//FFE0         JMP EAX			  Salta al OEP

//Posiciones de la rutina que tienen que ser corregidas
const int sectionStartPos = 1;
const int sectionEndPos = 10;
const int OEPPos = 17;

//Dado un numero y un alineamiento, devuelve el numero alineado superior mas cercano
DWORD align(DWORD number, DWORD alignment)
{
  if(number % alignment == 0)
    return number;
  else
    return (number / alignment) * alignment + alignment;
}

int main(int argc, char *argv[])
{
	if(argc != 2)
	{
		printf("Pasame un archivo como argumento, ej: %s program.exe", *argv);
		return EXIT_FAILURE;
	}
	HANDLE file = CreateFile((LPCTSTR)argv[1], 
		GENERIC_READ | GENERIC_WRITE, 
		0, 
		NULL, 
		OPEN_EXISTING, 
		FILE_ATTRIBUTE_NORMAL, 
		NULL);
	if(file == INVALID_HANDLE_VALUE){
		fprintf(stderr, "No se pudo abrir el fichero: %s Error: %d\n", argv[1], GetLastError());
		return EXIT_FAILURE;
	}

	//Abrimos una vista de solo lectura para leer el fichero y saber cuanto ocupará el nuevo fichero cifrado
	HANDLE mappedFile = CreateFileMapping(file, 0, PAGE_READONLY, 0, 0, 0);
	if(mappedFile == INVALID_HANDLE_VALUE)
	{
		fprintf(stderr, "No se puede mapear el fichero en memoria. Error: %d\n" , GetLastError());
		CloseHandle(file);
		return EXIT_FAILURE;
	}
	byte *viewMappedFile = (byte*)MapViewOfFile(mappedFile, FILE_MAP_READ, 0, 0, 0);
	if(viewMappedFile == NULL)
	{
		fprintf(stderr, "No se puede mapear el fichero en memoria. Error: %d\n" , GetLastError());
		CloseHandle(mappedFile);
		CloseHandle(file);
		return EXIT_FAILURE;
	}
	PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)viewMappedFile;
	if(pIDH->e_magic != IMAGE_DOS_SIGNATURE) //MZ
	{
		fprintf(stderr, "No se encontro la cabecera MZ!\n");
		UnmapViewOfFile(viewMappedFile);
		CloseHandle(mappedFile);
		CloseHandle(file);
		return EXIT_FAILURE;
	}
	PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)&viewMappedFile[pIDH->e_lfanew];

	//Ahora con pINH->OptionalHeader.FileAlignment ya podemos saber exactamente el tamaño del nuevo ejecutable cifrado
	DWORD fileAlignment = pINH->OptionalHeader.FileAlignment;
	//Reabrimos las vistas con el nuevo tamaño (el viejo tamaño + el tamaño de la sección nueva)
	UnmapViewOfFile(viewMappedFile);
	CloseHandle(mappedFile);
		
	DWORD size = GetFileSize(file, NULL);
	mappedFile = CreateFileMapping(file,
		0,
        PAGE_READWRITE,
        0,
		size + align(sizeof(uncryptroutine) ,fileAlignment),
        0);
	if(mappedFile == INVALID_HANDLE_VALUE)
	{
		fprintf(stderr, "No se puede mapear el fichero en memoria. Error: %d\n" , GetLastError());
		CloseHandle(file);
		return EXIT_FAILURE;
	}
	viewMappedFile = (byte*)MapViewOfFile(mappedFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
	
	pIDH = (PIMAGE_DOS_HEADER)viewMappedFile;
	pINH = (PIMAGE_NT_HEADERS)&viewMappedFile[pIDH->e_lfanew];
	if(pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + pINH->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) > pINH->OptionalHeader.SizeOfHeaders)
	{
		fprintf(stderr, "No hay espacio en la cabecera PE para agregar una seccion y es tan lioso agrandarla, quizas en futuras versiones...");
		UnmapViewOfFile(viewMappedFile);
		CloseHandle(mappedFile);
		CloseHandle(file);
		return EXIT_FAILURE;
	}
	PIMAGE_SECTION_HEADER pISH;
	DWORD i;
	//Busca la sección donde apunta el EP, que es la que cifrará
	for(i = 0; i < pINH->FileHeader.NumberOfSections; i++)
	{
		pISH = (PIMAGE_SECTION_HEADER)&viewMappedFile[pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + i*sizeof(IMAGE_SECTION_HEADER)];
		if(pINH->OptionalHeader.AddressOfEntryPoint >=  pISH->VirtualAddress && 
		   pINH->OptionalHeader.AddressOfEntryPoint < pISH->VirtualAddress + pISH->Misc.VirtualSize)
		{
			printf("EP apunta a la seccion: %s\n", pISH->Name);
	        break;
		}
	}
	//Añadimos permisos de escritura a la sección que se va a encriptar
	pISH->Characteristics |= IMAGE_SCN_MEM_WRITE;

	//Ciframos la sección donde apunta el EP
	for(i = 0; i < pISH->Misc.VirtualSize; i++)
		viewMappedFile[pISH->PointerToRawData + i] ^= 0xBB;
	printf("Seccion %s cifrada\n", pISH->Name);

	//Obtenemos un puntero a la última sección
	PIMAGE_SECTION_HEADER pLastISH = (PIMAGE_SECTION_HEADER)&viewMappedFile[pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (pINH->FileHeader.NumberOfSections - 1) * sizeof(IMAGE_SECTION_HEADER)];
	//Puntero a la nueva sección
	PIMAGE_SECTION_HEADER pNewISH = pLastISH + 1;
	memset(pNewISH, 0, sizeof(IMAGE_SECTION_HEADER));
	sprintf((char *)pNewISH->Name, ".crypt");
	pNewISH->Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_CODE;
	pNewISH->VirtualAddress = align(pLastISH->VirtualAddress + pLastISH->Misc.VirtualSize, pINH->OptionalHeader.SectionAlignment);
	pNewISH->Misc.VirtualSize = sizeof(uncryptroutine);
	pNewISH->PointerToRawData = align(pLastISH->PointerToRawData + pLastISH->SizeOfRawData, pINH->OptionalHeader.FileAlignment);
	pNewISH->SizeOfRawData = align(sizeof(uncryptroutine), pINH->OptionalHeader.FileAlignment);

	//Corregimos los valores del PE para que tenga en cuenta la nueva sección
	pINH->FileHeader.NumberOfSections++;
	DWORD OEP = pINH->OptionalHeader.AddressOfEntryPoint;
	//El nuevo entry point estará al comienzo de la nueva sección
	pINH->OptionalHeader.AddressOfEntryPoint = pNewISH->VirtualAddress;
	pINH->OptionalHeader.SizeOfImage = align(pNewISH->VirtualAddress + pNewISH->Misc.VirtualSize, pINH->OptionalHeader.SectionAlignment);
	printf("Seccion %s agregada\n", pNewISH->Name);	

	//Modificamos la rutina de desencriptación
	DWORD VirtualStart = pINH->OptionalHeader.ImageBase + pISH->VirtualAddress;
	memcpy(&uncryptroutine[sectionStartPos], &VirtualStart, sizeof(DWORD));
	DWORD VirtualEnd = VirtualStart + pISH->Misc.VirtualSize;
	memcpy(&uncryptroutine[sectionEndPos], &VirtualEnd, sizeof(DWORD));
	OEP += pINH->OptionalHeader.ImageBase;
	memcpy(&uncryptroutine[OEPPos], &OEP, sizeof(DWORD));

	if (size != pNewISH->PointerToRawData)
	{
		printf("Existen datos EOF, se copiaran al final del fichero\n");
		//Los datos EOF se desplazan al final del fichero
		memcpy(&viewMappedFile[pNewISH->PointerToRawData + pNewISH->SizeOfRawData], &viewMappedFile[pNewISH->PointerToRawData], size - pNewISH->PointerToRawData);
	}
	//Escribimos la rutina de desencriptación al inicio de la nueva sección
	memcpy(&viewMappedFile[pNewISH->PointerToRawData], uncryptroutine, sizeof(uncryptroutine));
	//Rellenamos de 00s el resto de la sección
	memset(&viewMappedFile[pNewISH->PointerToRawData + sizeof(uncryptroutine)], 0, pNewISH->SizeOfRawData - sizeof(uncryptroutine));
	printf("Rutina de descifrado escrita\n");
	
	//Cerramos la vista mapeada
	UnmapViewOfFile(viewMappedFile);
	CloseHandle(mappedFile);
	CloseHandle(file);

	printf("Archivo encriptado!\n");
	return EXIT_SUCCESS;
}
El ejecutable conserva los datos EOF que puedan existir. La rutina de descifrar a sido cambiada levemente ("JMP dirección" por "MOV EAX, dirección; JMP EAX") para que sea mas fácil de modificar desde el código.

Al ejecutarlo muestra algo como esto:
image

Subo aquí el programa compilado.

Es posible volver a cifrar el ejecutable resultante. De modo que podemos llegar a tener 20 secciones “.crypt”, y una tras otra van descifrándose hasta que la última descifra el código original. Como curiosidad he probado ha cifrarlo 88 veces, obteniendo 91 secciones (inicialmente había 3) y funciona correctamente:
image

Este “crypter” no funciona con todos los ejecutables, de hecho da problemas con muchos de ellos, en siguientes entradas veremos los problemas que pueden surgir y como tratarlos.

Cualquier sugerencia será bienvenida. Saludos!

martes, 9 de agosto de 2011

Cifrando un ejecutable a mano, dos variaciones

En la anterior entrada hemos cifrado el netcat haciendo xor sobre toda la sección de código. Para ello hemos añadido una sección, en ella se ha escrito el código para descifrar la sección de código y se ha cambiado el EP a esta sección.

Se me ocurren 2 posibles variaciones al método anterior.

La primera es no añadir una nueva sección sino usar los huecos existentes en la sección “.text”. Es muy habitual que debido a la necesidad de que el tamaño de una sección en disco esté alineada con el valor FileAlignment, al final de todas las secciones sobren algunos bytes que solo están ahí para cumplir con el alineado. Podemos usar estos huecos para introducir nuestro código sin la necesidad de añadir una sección de código. Pero claro esto no siempre se puede hacer y lo que se busca a la hora de programar algo es que funcione la mayoría de las veces, así que este método queda descartado.

Aun así para ver que dicen los AV al respecto, he realizado la prueba y lo detectan 19/43, este cambio no confunde a ningún AV.

La segunda variación es la que hace Mati Aharoni en el video que puse en la anterior entrada. Y consiste en no modificar en ningún momento el EP. En cambio donde apunta el EP pone una instrucción de salto, JMP, a la nueva sección donde tenemos el código que descifra y una vez descifrado el código se encarga de ejecutar las instrucciones que había sobrescrito y retornar a la sección “.text” descifrada para continuar la ejecución normalmente.

Originalmente en el EP existen estas instrucciones: image

Tras el cambio quedara así:

image

Fijaos que el resto del código está cifrado con xor, solo se ha conservado el salto.

La sección que hemos añadido y donde salta ese JMP que veíamos quedaría así:

image

Las 2 instrucciones iniciales son las mismas que había en el exe original y que fueron reemplazas por la instrucción JMP.

Subo el exe modificado por aquí. Este es solo detectado por 13/43 antivirus. Que conste que el objetivo no es hacer indetectable el netcat, para eso existen mejores técnicas de las que ya hablé, sino ver diversas formas de cifrar un archivo y ya de paso comprobar cómo afectan a los antivirus.

Esta técnica que acabamos de ver tiene algunas dificultades para ser implantada en un programa. Y es que para insertar el JMP en el EP necesitamos 5 bytes que es lo que ocupa una instrucción JMP, y esos 5 bytes serán sobrescritos afectando a las instrucciones que estuviesen en ese lugar, pero a priori no sabemos cuantas instrucciones son ni que tamaño tienen. Es mas fácil verlo con el ejemplo anterior. Las instrucciones sobrescritas eran:

PUSH 18
PUSH 0040C098

Las cuales ocupan 7 opcodes: 6A 18 68 98 C0 40 00

Sería un grave error hacer el programa que solo copiase solo los 5 opcodes sobrescritos por el JMP. Necesitamos analizar las instrucciones afectadas por esos 5 bytes y conservarlas completamente. Programar esto ya es mas complicado, requiere conocer todas las instrucciones posibles, el tamaño de los operandos, etc… Así que de momento esta variación queda también descartada para realizar un programa automático que cifre un ejecutable, un crypter.

Saludos!

lunes, 8 de agosto de 2011

Cifrando un ejecutable a mano

Buenas!

En las entradas que se han visto hasta ahora sobre los ficheros ejecutables en Windows se explicaba el formato que tiene un exe, cómo añadir una sección, cómo añadir código ejecutable, qué es la IAT y cómo cargar funciones manualmente y alguna otra cosa relacionada. Todo esto tenía un objetivo, ir preparándonos para entender cómo funcionan los protectores de ejecutables e ir programando poco a poco uno.

Y para empezar que mejor que cifrar un ejecutable a mano de la forma mas sencilla posible para después ya desarrollar el programa que haga el proceso automáticamente. La idea es usar la función XOR para cifrar la sección de código de un ejecutable, después insertar al inicio un pequeño código en el mismo ejecutable que deshaga esta operación de cifrado y por último ejecute el código una vez descifrado.

El proceso puede verse en este video de la conferencia ShmooCon 2008 donde Mati Aharoni habla sobre cómo saltarse la detección de los antivirus. Para ello usa la herramienta netcat que es detectada por unos cuantos. Al final del proceso logra que su Antivirus, AVG, no detecte el netcat.

Voy a hacer en esencia lo mismo pero con algunos cambios.

Como Mati, voy a usar el netcat, que se puede descargar de aquí. Nada mas bajarlo, lo he subido a Virustotal y actualmente es detectado por 28/43 antivirus.

Lo siguiente que voy a hacer es añadir una sección al netcat. Para ello uso mi querido 010 Editor. Me ha quedado así:

image

No os olvidéis de cambiar el NumberOfSections, ImageSize y añadir físicamente 1000 bytes al final del fichero. Si tenéis algún problema añadiendo la sección revisad este post de Ferchu sobre cómo añadir una sección a un exe “y no morir en el intento”.

En esta sección que acabamos de crear incluiremos el código que descifre la sección de código del ejecutable y una vez finalizada esta tarea salte a la misma. Para que lo primero que se ejecute en el programa sea este código que ahora veremos es necesario modificar el EntryPoint del ejecutable de netcat para que el programa inicie la ejecución en nuestra sección.

image

En este ejemplo el código que se usará para cifrar la sección será el mismo que se use para descifrarla. Esto es posible porque vamos a usar la función XOR para cifrar los bytes de la sección de código, y esta función tiene una característica curiosa y es que: X XOR C = Y Y XOR C = X

C es la clave que por ejemplo puede ser 0xBB. X es uno de los bytes de la sección de código, una vez cifrado con la clave C obtenemos Y, que será el byte cifrado. Para descifrarlo solo tenemos que volver a aplicar la función XOR con la misma clave.

La sección de código en el ejecutable de netcat es la primera sección, llamada “.text”. Dicha sección se encuentra en este rango de memoria virtual: 401000h-40B000h.

Y con todo esta ahora ya vamos con el código ensamblador que cifrará/descifrará la sección. La idea es situarnos al comienzo de la sección e ir cifrando cada byte de la misma hasta que lleguemos al final. Este código realiza esa tarea:

MOV EAX, 401000  ;Inicio de la sección
XOR [EAX], BB ;Clave usada para la función xor BB
INC EAX
CMP EAX, 40B000 ;Fin de la sección
JNZ 00410005 ;Si no es el fin de la sección seguir en el bucle
JMP 00404AC3 ;EP original

Ahora tenemos el netcat con una nueva sección que contiene el código para cifrar/descifrar. Lo abrimos con OllyDbg y ejecutamos el bucle para que cifre toda la sección “.text”. Para ello lo mejor es poner un breakpoint en el último JMP y darle a ejecutar.

Una vez parado el Olly en el JMP, podemos ver como la sección ”.text” se encuentra cifrada:

image

A la izquierda vemos el netcat original, a la derecha el cifrado. Ahora solo nos queda guardar el ejecutable en este estado, con al sección cifrada. Para ello en OllyDbg selecionamos toda la sección y damos a “Copy to executable > Selection”:

image

Y en la nueva ventana “Save file”.

Ahora ya tendremos todo listo, el netcat con la sección de código cifrada, una nueva sección con el código que la descifrará y el EP apuntando allí. Ahora al ejecutarlo se descifrara la sección y la ejecutará como se haría normalmente.

Subo aquí el netcat tal cual me ha quedado a mi.

Esta versión cifrada la detectan 19/43 antivirus. Antes eran 28. Uno de los que no lo detectan es AVG, el mismo que utilizó Mati Aharoni en su conferencia. Hace años seguramente hubiesen sido mas los AV que no hubiesen detectado este netcat modificado, pero hoy en día muchos antivirus son capaces de mediante emulación o análisis heurísticos detectar que se usa un cifrado XOR y revertirlo para analizar el ejecutable. Otros tantos no detectaban el netcat, ya que en si es una herramienta para “hacking”, pero si detectan esta variante como un programa empaquetado “Malicious Packer”. Hay varias cosas en las que puede fijarse un antivirus para sospechar que es un programa cifrado, tiene 2 secciones con permisos de ejecución, lo cual no suele ser muy normal, la sección donde apunta el EP no es la primera, lo que tampoco es habitual y la sección “.text” no parece contener instrucciones ejecutables.

En siguientes entradas programaré esto en C y se verán los problemas que pueden surgir con este método.

Saludos!


Continuación: Dos posibles variaciones a este método


lunes, 4 de julio de 2011

Formato de ficheros ejecutables, Formato PE

@The_Swash ha redactado un completo documento en el que explica toda la estructura de un fichero ejecutable para Windows.

Es uno de los documentos mas completos en español sobre el tema.

Aquí os dejo el índice:

  1. ESTRUCTURA DE UN FICHERO EJECUTABLE
  2. DIRECCIONES FÍSICAS, VIRTUALES RELATIVAS Y VIRTUALES
  3. IMAGE FILE HADER
  4. IMAGE OPTIONAL HEADER
  5. IMAGE DATA DIRECTORY
  6. IMAGE SECTION HEADER
  7. EXPORT DIRECTORY
  8. IMPORT DIRECTORY
  9. RESOURCE DIRECTORY
  10. RELOCATION DIRECTORY
  11. DEBUG DIRECTORY
  12. TLS DIRECTORY (THREAD LOCAL STORAGE)
  13. DIRECTORIO LOAD CONFIG
  14. DELAY IMPORT DIRECTORY
  15. BOUND IMPORT DIRECTORY
  16. REFERENCIAS
  17. DESPEDIDA

Y el documento, como no.

Saludos!

viernes, 10 de junio de 2011

Me quieren infectar! Pero ¿cómo? y ¿quién?

En la anterior entrada mostré cómo alguien o algo trataba de infectar mi equipo.

¿Cómo?

Está claro que el problema estaba en que el DNS no era el esperado y tampoco el servidor DCHP. Razonando un poco llegué a la conclusión de que había un servidor DHCP falso que era el que me daba los datos para conectarme a internet y uno de esos datos era el servidor DNS malicioso.

Veámoslo en Wireshark. Me desconecto y reconecto a la red Wi-Fi, filtro los paquetes para ver solo el protocolo DHCP “bootp” y me encuentro con lo siguiente:

wireshark_yeah

En los dos primeros paquetes se ve como mi equipo recién conectado a la red inalámbrica solicita (DHCP Request) los datos necesarios para conectarse.

A continuación responden a la solicitud (DHCP ACK) desde 2 direcciones diferentes. 192.168.100.13, la IP de un equipo de mi red y 192.168.100.254, mi router, el único que debería responder. 

El primero en responder es la dirección IP 192.168.100.13, el equipo de mi red, así que es al que hace caso mi portátil. En la captura se puede ver como uno de los datos que nos da en la respuesta a la solicitud es el Domain Name Server (DNS) 188.229.88.8. Como curiosidad esa IP está en Bucarest, Rumania.

Pero no siempre responde antes el equipo “malicioso” que el router. De hecho la mayoría de las veces responde antes el router que el equipo:

wireshark_dhcp_competi

En estas ocasiones mi equipo ignora la segunda respuesta y todo funciona correctamente.

¿Quién?

La pregunta ahora es quién de mis compañeros de piso tiene la IP 192.168.100.13, ¿la belga, los franceses, el indio o serán las polacas? ¿Y será realmente él/ella quien intenta infectarme?

Usando el MAC Address Scanner de Cain se puede ver que equipos están activos en mi rango.

image
Ahí veo que el equipo en cuestión se llama Kwasek-Komputer. El nombre de una de las polacas que vive conmigo. Por supuesto no es ella la que deliberamente intenta infectarme. Es algún tipo de malware, llamémoslo virus que por lo que se ve hace las funciones de un falso servidor DCHP para intentar expandirse por la red de area local, LAN. Tendré que instalarla un antivirus.

La técnica se conoce como Rogue DHCP. Ya en 2009 hablaban de un malware que usaba esta técnica para expandirse por la red local. Seguro que es mas antigua aun.

Buscando en google he encontrado unas cuantas páginas que hablan sobre este virus:
http://malwaresurvival.net/2011/06/01/rogue-dhcp-routes-users-to-malware-attacks/
http://www.dyndnscommunity.com/questions/16870/my-router-provides-the-wrong-dns.html
http://www.bleepingcomputer.com/forums/topic402144.html
http://www.ryumaou.com/hoffman/netgeek/2011/05/dns-redirect-attack/

El siguiente paso sería analizar el ejecutable que se descarga, pero no se yo si lo lograré, suelen estar cifrados, ofuscados, se descargan a la vez otros ejecutables, etc…

Saludos!

Me quieren infectar!

Enciendo el portátil en mi casa, arranca Windows 7, se conecta a la red Wi-Fi, abro el navegador, se abre la página de inicio y ZAS:

fake

www.google.es me dice que para entrar a su página web debo actualizar el navegador. Que extraño…si Google Chrome se actualiza automáticamente, y aun así ¿www.google.es iba a mostrarme ese mensaje tan rádical “o actualizas, o no entras”? Y ese título de la página web tan cantoso: “WARNING!!!”…todo muy raro.

Por curiosidad pulso el botón “Browser update” y se descarga un ejecutable de 58 kbytes llamado “update.exe”, sin icono. Sería mas normal que una actualización se llamase algo como GoogleChrome_Update_11.231.exe y que el ejecutable tuviese el icono de Google Chrome, ¿no?

A estas alturas ya estoy seguro de que hay gato encerrado y que ese ejecutable no es ninguna actualización de Chrome, me la quieren colar…¿Por qué!!?

Pruebo a abrir otras páginas y en todas sale el mismo aviso. Lo intento con Internet Explorer y lo mismo, ¿qué demonios pasa?

Hago ping a www.google.es:

ping

Después hago ping a www.marca.com y la misma IP, todos los dominios me responden con la IP: 188.229.88.8. Abro esa IP en el navegador, http://188.229.88.8/, y veo el mismo mensaje que me salía al principio.

Parece claro que el problema es que mi servidor DNS resuelve todos los dominios con esa IP. ¿Se habrá vuelto loco?

Veamos que servidor DNS estoy usando: ipconfig /all

dnsvafanculo

Mi router tiene la dirección 192.168.100.254 y él mismo hace de servidor DHCP y de servidor DNS, como es normal en los router domésticos. Pero ahí me dice que el servidor DHCP es la 100.13 y el DNS es la misma IP que me salía antes al resolver cualquier dominio :S

Me desconecto de la red Wi-Fi y me vuelvo a conectar (típica solución a todos los problemas informáticos, reconectar, resetear, reiniciar, etc…), vuelvo a mirar la configuración de mi tarjeta de red y sorpresa:

dnsok

Todo está perfecto. Pruebo a conectarme a algunas páginas y todo bien.

Ahora que funciona internet aprovecho a subir la supuesta actualización a VirusTotal para ver que dicen los antivirus al respecto. 23/42 antivirus lo detectan, definitivamente es un malware. Aunque los antivirus no se ponene muy de acuerdo, 4 de ellos lo llaman Gen.Variant.Kazy.

Pruebo a reconectarme varias veces para ver si vuelve a salir el mensaje pero nada. ¿Cómo han aparecido esos valores ahí? ¿Mi equipo está infectado? ¿El router se ha vuelto loco temporalmente?

En la siguiente entrada lo veremos.

Pd: Como curiosidad fijaos que la estética de la página está calcada al aviso de sitios maliciosos de Firefox:

domingo, 29 de mayo de 2011

Obtener la dirección base de kernel32.dll consultando el PEB

En la entrada de Usando GetProcAddress y LoadLibrary para localizar APIs vimos que una de las primeras cosas que hay que hacer es localizar la dirección de memoria donde se carga la dll kernel32.dll. Para ello se utilizaba un código que suponía que en la pila se encontraba una dirección de retorno a una función de dicha dll:

;Obtiene la dirección base de Kernel32
ObtenerKernel32 proc
 mov eax, dword ptr [esp+0Ch];Nos quedamos con la dirección de retorno hacia la funcion createprocess
 and eax, 0FFFFF000h   ;Paginas de 1000h
searchMZ:
 sub eax, 1000h
 cmp word ptr[eax], "ZM"
 jnz searchMZ
 ret

ObtenerKernel32 endp

Esta forma no parece muy correcta, puede que en alguna versión de Windows pasada o futura no funcione bien. Además se supone que se conoce en que posición de la pila estará el valor de retorno que comentaba, esto por ejemplo puede no saberse exactamente si el código se usa para explotar una vulnerabilidad mediante una shellcode que use ese método.

Por suerte existe otra forma de obtener la dirección base de kernel32.dll.

Citando a la wikipedia:

En computación, el Win32 Thread Information Block (TIB) es una estructura de datos en los sistemas Win32 específicamente en la arquitectura x86 que almacena información a cerca del thread que se esta ejecutando.

El TIB esta indocumentado oficialmente para Windows 9x.La serie Windows NT incluye una estructura NT TIB en winnt.h que lo documenta .Wine incluye declaraciones para extender (una parte especifica del subsistema) TIB

El TIB puede ser usado para obtener buena cantidad de información en el proceso sin llamar ninguna API de Win32.Por ejemplo la emulación de GetLastError() , GetVersion(). A través del puntero en el PEB se puede obtener acceso a la tabla de importación (IAT) , los argumentos pasados al proceso etc …

La dirección de esta estructura esta apuntada por el registro FS y en la misma página de wikipedia que he comentado antes pueden verse los campos que tiene. Uno de ellos es el Process Enviroment Block, PEB. Una estructura que contiene información sobre el proceso que ejecuta el hilo:

FS:[0x30] –> Linear address of Process Environment Block (PEB)

En MSDN podemos ver de que se compone la estructura PEB:

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;  //+0x0C
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  BYTE                          Reserved4[104];
  PVOID                         Reserved5[52];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved6[128];
  PVOID                         Reserved7[1];
  ULONG                         SessionId;
} PEB, *PPEB;

Mediante esta estructura podemos saber si un proceso está siendo depurado, los parámetros del proceso y lo que mas nos interesa los módulos que ha cargado el Loader de Windows. En estos módulos se encuentra el propio ejecutable y todas las DLLs que ha cargado Windows para poder ejecutarlo, entre ellas estará kernel32.dll. El campo que nos da esta información es el siguiente:

PPEB_LDR_DATA  Ldr;

Que se encuentra a un desplazamiento de 0x0C desde el principio de la estructura PEB.

La estructura PEB_LDR_DATA a su vez también está documentada en MSDN:

typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList; // +0x14
} PEB_LDR_DATA, *PPEB_LDR_DATA;

En MSDN describen esa lista cómo:

InMemoryOrderModuleList The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. For more information, see Remarks.

Con la siguiente estructura:

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

Y por último la tabla donde apuntan Flink y Blink:

typedef struct _LDR_DATA_TABLE_ENTRY {
  PVOID Reserved1[2];
->LIST_ENTRY InMemoryOrderLinks;
  PVOID Reserved2[2];
  PVOID DllBase; //+0x10 desde –>
  PVOID EntryPoint;
  PVOID Reserved3;
  UNICODE_STRING FullDllName; //+0x1C desde –>
  BYTE Reserved4[8];
  PVOID Reserved5[3];
  union {
    ULONG CheckSum;
    PVOID Reserved6;
  };
  ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

Hay que fijarse que los punteros Flink y Blink de la lista doblemente enlazada no apuntan al comienzo de la estructura LDR_DATA_TABLE_ENTRY sino al campo InMemoryOrderLinks, como he marcado con la flecha –>. Los desplazamientos los he puestos relativos a este punto.

De esta última estructura nos interesan los campos:

PVOID DllBase; 

Que contiene la dirección base donde se ha cargado el módulo y:

UNICODE_STRING FullDllName;

Que es la ruta y nombre del módulo. El tipo UNICODE_STRING está definido también en MSDN:

typedef struct _LSA_UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;

Perfecto! Ya tenemos todo lo necesario para lograr nuestro objetivo. La idea es primero acceder al TIB, desde este localizar la estructura PEB, después acceder al valor Ldr a la estructura PPEB_LDR_DATA, utilizar el valor InMemoryOrderModuleList para recorrer la lista mediante el puntero Flink(Follow link) y en cada tabla LDR_DATA_TABLE ver si corresponde al módulo kernel32.dll comparando con el campo FullDllName, si no es así ir a la siguiente tabla, si sí que corresponde con kernel32.dll quedarnos con la dirección base DllBase que es lo que buscamos.

A continuación dejo el código en ensamblador que realiza todas estas tareas. Para hacerlo lo mas genérico he comparado el nombre del módulo con kernel32.dll de forma que no sea sensible a minúsculas o mayúsculas lo que hace un poco mas largo el código:

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

.data
 Kernel32Name db 'K',0,'E',0,'R',0,'N',0,'E',0,'L',0,'3',0,'2',0,'.',0,'D',0,'L',0,'L',0
 Kernel32NameLength dw 24
.code
codigo:
 call ObtenerKernel32PEB
 ret

;Obtiene la dirección de memoria donde se encuentra cargada kernel32.dll y la guarda en eax
ObtenerKernel32PEB proc
 mov eax, [fs:30h]    ;PEB
 mov eax, [eax+0Ch]    ;PEB.Ldr, Loader data
 add eax, 14h     ;PEB.Ldr.InMemoryOrderModuleList.Flink, Puntero al primer modulo
NextTable:
 mov eax, [eax]      ;Nos movemos a la siguiente tabla LDR_DATA_TABLE_ENTRY
 mov ecx, 0
 mov esi, offset [Kernel32Name] ;En esi guardamos la dirección del nombre kernel32.dll en unicode
 mov cx, word ptr [eax+1Ch]  ;LDR_DATA_TABLE_ENTRY.FullDllName.Length, longitud de la cadena unicode
 mov edi, [eax+20h]    ;LDR_DATA_TABLE_ENTRY.FullDllName.Buffer, puntero a la cadena unicode con el nombre del módulo
 ;No nos interesa la ruta entera solo los últimos caracteres, el nombre del fichero
 add di, cx      ;Nos situamos al final de la cadena
 sub di, Kernel32NameLength  ;Y restamos el tamaño de la cadena a comparar
 mov cx, Kernel32NameLength
ComparaCadenas:
 mov bl, byte ptr [edi]
 cmp bl, 'a'      ;Si bl >= 'a' convertir a mayusculas
 jb Mayuscula
 sub bl, 20h      ;Pasa de minusculas a mayusculas
Mayuscula:
 cmp bl, byte ptr[esi]
 jnz NextTable     ;Si no es kernel32.dll mirar en la siguiente tabla
 inc edi
 inc esi
 dec cx
 jnz ComparaCadenas
 ;Si llega aquí es que la cadena era kernel32.dll, cogemos el dato que nos interesa del modulo
 mov eax, [eax+10h]    ;eax = LDR_DATA_TABLE_ENTRY.DllBase
 ret
 
ObtenerKernel32PEB endp

end codigo

Es bastante lioso manejar tantas estructuras y entender el código en ensamblador, lo mejor para entenderlo es seguirlo poco a poco con un depurador, ver las estructuras en memoria y muuucha paciencia.

Y así obtendríamos la dirección de memoria donde está cargada la librería kernel32.dll, ahora ya podríamos seguir con el proceso que realizábamos en la otra entrada de buscar las funciones GetProcAddress y LoadLibrary.

Aquí dejo una entrada de LordRNA donde hace lo mismo de forma muy similar Gracias a @The_Swash también por su solución al mismo problema.

Un saludo!