¿Cómo haría para reconstruir el IAT de un ejecutable empaquetado?

10

Cuando los ejecutables están empaquetados con una herramienta como UPX, el código real y las secciones de datos están cifrados u ofuscados, y luego se cargan en la memoria utilizando un código auxiliar de descifrado inyectado. Esto hace imposible el análisis estático.

Para evitar esto, normalmente ejecutaría el ejecutable, adjuntaría un depurador, tomaría un volcado de memoria y luego usaría ese volcado para producir un ejecutable desempaquetado. Desafortunadamente, esto destruye la tabla de direcciones de importación (IAT).

Soy consciente de ciertas herramientas que se pueden usar para parchear el IAT, pero no sé cómo funcionan internamente. ¿Cómo haría para recrear manualmente el IAT?

    
pregunta Polynomial 12.10.2012 - 22:48
fuente

2 respuestas

10

Bien, dije que buscaría una respuesta y aquí está, como prometí.

En primer lugar, quería crear un objetivo real para jugar. No hay nada como un ejemplo funcional, que se pueda tocar, y esto es en gran medida un proceso manual. Entonces, sin más dilación, compilé esto en MSVC con /MT :

#define WINVER 0x501
#define _WIN32_WINNT   0x0501
#define _WIN32_WINDOWS 0x0501
#define _WIN32_IE      0x0501
#define UNICODE

#include <wchar.h>
#include <windows.h>

int APIENTRY wWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    MessageBox(NULL, L"Hello, world.", L"A messagebox", MB_OK | MB_ICONEXCLAMATION);
    return 0;
}

Esperamos que esta bestia importe user32.dll!MessageBoxW y, efectivamente, lo hace.

Para encontrar esto dentro del ejecutable mismo, usemos WinDbg. Uso WinDbg por dos razones: en primer lugar, no puedo pagar IDA y, en segundo lugar, es el mismo depurador que usaría para un controlador de kernel (incluso con IDA, que usa su motor), por lo que es bueno, aunque un poco no intuitivo.

Compilé mi ejecutable a UnpackSimple.exe para la arquitectura x86, ya que UPX no es compatible con Win64. Ahora, lo primero que notará cuando ejecute esto bajo WinDbg es esta línea:

ModLoad: 01360000 01370000   UnpackSimple.exe

Allí mismo, vemos el rango de direcciones al que se ha asignado UnpackSimple.exe . Esto es importante, porque todas las direcciones en su encabezado son relativas a 01360000 .

Así que ahora, para encontrar la información del encabezado, simplemente podemos solicitarla:

!dh UnpackSimple.exe
OPTIONAL HEADER VALUES

    10B magic #
   11.00 linker version
    5E00 size of code
    6C00 size of initialized data
       0 size of uninitialized data
    1193 address of entry point
    1000 base of code
         ----- new -----
01360000 image base            <--- this is the image base address.
    1000 section alignment
     200 file alignment
       2 subsystem (Windows GUI)
    6.00 operating system version
    0.00 image version

    ... blah blah blah ....

   0 [       0] address [size] of Export Directory
8D54 [      3C] address [size] of Import Directory
D000 [     1E0] address [size] of Resource Directory
   0 [       0] address [size] of Exception Directory
   0 [       0] address [size] of Security Directory
E000 [     6D0] address [size] of Base Relocation Directory
7150 [      38] address [size] of Debug Directory
   0 [       0] address [size] of Description Directory
   0 [       0] address [size] of Special Directory
   0 [       0] address [size] of Thread Storage Directory
89D8 [      40] address [size] of Load Configuration Directory
   0 [       0] address [size] of Bound Import Directory
7000 [     108] address [size] of Import Address Table Directory
   0 [       0] address [size] of Delay Import Directory
   0 [       0] address [size] of COR20 Header Directory
   0 [       0] address [size] of Reserved Directory

Oh mira, un IAT! Vive en 01360000+7000 y podemos persuadir a WinDbg para que nos brinde esa información de una manera bastante sencilla. El comando dps que estamos a punto de usar es corto para dump pointers, dereferencing with symbols , toma una dirección de argumento y un espacio por dirección de entrada.

dps 01360000+7000 L108/4
01367000  75b3d7ea kernel32!TerminateProcessStub
01367004  75b23f3c kernel32!CreateFileWImplementation
01367008  75b2520b kernel32!GetCommandLineWStub
... <snip> ...
013670f4  75b47ab2 kernel32!WriteConsoleW
013670f8  75b21410 kernel32!CloseHandleImplementation
013670fc  00000000
01367100  76bafd3f USER32!MessageBoxW
01367104  00000000

Puede ver algunas características importantes del IAT aquí. Primero está la dirección en el lado izquierdo - dirección de entrada IAT. La dirección a la izquierda es la dirección en memoria de la DLL. Por último, tenga en cuenta que hay algunas entradas cero. Cada importación de DLL termina con una entrada cero, lo que indica al cargador que no hay más entradas para cargar.

Ahora, veremos el desmontaje de la función principal. Para hacer esto, escriba estos dos comandos

bp UnpackSimple!wWinMain
g

Luego, irá al ingreso al wWinMain anterior. Ahora, si te fijas bien:

UnpackSimple!wWinMain:
    01361000 6a30            push    30h
    01361002 68a0893601      push    offset UnpackSimple!'string' (013689a0)
    01361007 68bc893601      push    offset UnpackSimple!'string' (013689bc)
    0136100c 6a00            push    0
    0136100e ff1500713601    call    dword ptr [UnpackSimple!_imp__MessageBoxW (01367100)]
    01361014 33c0            xor     eax,eax
    01361016 c21000          ret     10h

Como era de esperar, existe nuestra función, _imp__MessageBoxW , que hace referencia a una buena entrada IAT :)

Así que ahora la pregunta es ¿cómo diablos vamos hacia atrás? Bueno, para construir primero algo que simplemente no funciona, necesitamos conseguir un ejecutable empaquetado.

Para ese fin, ejecuté upx.exe -o UnpackSimplePacked.exe UnpackSimple.exe para producir un binario empaquetado agradable. Cargue este binario en WinDbg y lo primero que notará es que:

!dh UnpackSimplePacked.exe

No hace absolutamente nada. Sin embargo, podemos tener en cuenta que:

ModLoad: 01220000 01233000   image01220000
Se ha visto

, por lo que la imagen que necesitamos está ahí, en la memoria. Simplemente no existe como una imagen de disco. En esta etapa, supongo que upx en realidad carga los DLL directamente, en lugar de usar el NT Loader. Podemos probar esto de dos maneras: en primer lugar, si escribe sxe ld en WinDbg, se interrumpirá en todas las cargas de módulos. No se producen roturas para user32.dll , ni está cargado.

En segundo lugar, como querías ver:

    0 [       0] address [size] of Import Address Table Directory

Está completamente vacío.

Sin embargo, si revisamos la lista de módulos cargados:

lm
start    end        module name
00210000 00223000   image00210000   (deferred)             
74c20000 74c2c000   CRYPTBASE   (deferred)             
74c30000 74c90000   SspiCli    (deferred)             
74d80000 74e20000   ADVAPI32   (deferred)             
74eb0000 74f4d000   USP10      (deferred)             
74f50000 74f5a000   LPK        (deferred)             
75030000 750dc000   msvcrt     (deferred)             
755a0000 755b9000   sechost    (deferred)             
75720000 757b0000   GDI32      (deferred)             
757e0000 758d0000   RPCRT4     (deferred)             
75b10000 75c20000   kernel32   (deferred)             
76ab0000 76af7000   KERNELBASE   (deferred)             
76b40000 76c40000   USER32     (deferred)             
77550000 776d0000   ntdll      (pdb symbols) 

Como puede ver, USER32 tiene espacio asignado listo para funcionar.

Ahora aquí hay algunos trucos. Abrí otra ventana de WinDbg con mi versión UnpackSimple.exe y encontré la dirección de user32!MessageBoxW . Una vez que obtuve esto, encontré que era 76bafd3f desde un desplazamiento base de 76b40000 , lo que me da un desplazamiento de 6FD3F . En mi versión empaquetada, establecí un punto de interrupción en bp 76b40000+6FD3F y, efectivamente, ¡había un seguimiento de la pila!

k
003cfd0c 013a1014 USER32!MessageBoxW
WARNING: Stack unwind information not available. Following frames may be wrong.
003cfd6c 75b233aa image013a0000+0x1014
003cfd78 77589ef2 kernel32!BaseThreadInitThunk+0xe
003cfdb8 77589ec5 ntdll!__RtlUserThreadStart+0x70
003cfdd0 00000000 ntdll!_RtlUserThreadStart+0x1b

El desmontaje en la ubicación en image0... se ve así:

013a100e ff1500713a01    call    dword ptr [image013a0000+0x7100 (013a7100)]
013a1014 33c0            xor     eax,eax
013a1016 c21000          ret     10h

¡Ahora estamos llegando a alguna parte! En esa dirección encontrarás algunos datos interesantes:

db 013a7100
013a7100  3f fd ba 76 00 00 00

Curiosamente, ahí está nuestra dirección! 76bafd3f en buena vieja endian pequeña. Retrocedamos un poco:

db 013a7098
013a7098  a0 22 57 77 60 22 57 77-c9 14 b2 75 ff 10 b2 75  ."Ww'"Ww...u...u
013a70a8  7b 44 b2 75 9c 17 b2 75-91 d1 b4 75 71 51 b2 75  {D.u...u...uqQ.u
013a70b8  45 49 b2 75 c4 d1 b4 75-13 49 b2 75 b3 d1 b4 75  EI.u...u.I.u...u
013a70c8  46 e0 57 77 f1 24 59 77-0d 17 b2 75 46 19 b2 75  F.Ww.$Yw...uF..u
013a70d8  2a 30 58 77 61 48 ba 75-83 46 b2 75 07 7c bc 75  *0XwaH.u.F.u.|.u
013a70e8  28 13 b2 75 bf 45 ba 75-ef c7 b3 75 b2 7a b4 75  (..u.E.u...u.z.u
013a70f8  10 14 b2 75 00 00 00 00-3f fd ba 76 00 00 00 00  ...u....?..v....

Veo punteros!

Este es el IAT que encontramos anteriormente, o al menos una matriz de IMAGE_IMPORT_DESCRIPTOR thunks.

Habiendo llegado tan lejos, ¿qué queda? Bueno, tendríamos que buscar en la memoria volcada para intentar encontrar funciones que coincidan con las DLL conocidas de Windows. Desde allí, podemos comenzar a reconstruir un IAT completo para nuestro nuevo archivo PE. Sospecho que el proceso ImpRec pasa, como se describe en la respuesta de Viv es intentar encontrar estas entradas como lo he hecho a mano. He optado por utilizar puntos de interrupción, pero me imagino que hay varias formas en que esto podría automatizarse de manera diferente. Nunca he escrito una herramienta automatizada para pasar el proceso a mano ...! Dicho esto, en una conjetura adivinando las instrucciones call ptr , mirar sus entradas fuera de una región asignada y luego tratar de encontrarlas contra las compensaciones de DLL conocidas funcionaría.

Dos notas:

  1. Escribí esta respuesta como un "así es como lo atacaría manualmente". Escriba la respuesta, con un fondo relevante, no un "uso de esta herramienta".
  2. Cuidado con ASLR. Esas direcciones de los módulos seguirán cambiando, así que esté atento a cambiar las bases de los módulos.
respondido por el user2213 16.10.2012 - 18:20
fuente
4

En primer lugar, para una descripción general del formato pe, recomendaré leer pecoff formato de archivo proporcionado por Microsoft. La tabla de importación se destruye parcial o completamente por la mayoría de los empacadores. Imprec suele ser la opción preferida para reconstruir el IAT (Tabla de direcciones de importación) pero si realmente desea entrar en los detalles, entonces puede leer este excelente artículo: Empaquetadores de PE utilizados en software malicioso por Paul Craig de la evaluación de seguridad equipo. También puedes probar una herramienta de código abierto scylla

En los ejecutables de Windows, la tabla de importación es la tabla (directorio de datos) que contiene toda la información de importación (nombre de Dll y funciones referidas en el espacio de direcciones) de ese imagen.

IMAGE_IMPORT_DESCRIPTOR tiene el siguiente formato:

struct IMAGE_IMPORT_DESCRIPTOR {
DWORD   OriginalFirstThunk; //same as FirstThunk
DWORD   TimeDateStamp;
DWORD   ForwarderChain;     //usually set to 0
DWORD   Name;               //name of the dll
DWORD   FirstThunk;         //before loading,contains the pointer to the api name thunk array 
};

Hay un descriptor para cada DLL importado y el último consiste en NULL. El cargador de Windows cargue todas las DLL referidas (recuerde una DLL para cada IMAGE_IMPORT_DESCRIPTOR ) en el ejecutable al ver el campo de nombre de la estructura. Entonces se intenta construir. El IAT de la siguiente manera. Para cada nombre en la matriz señalada por FirstThunk , sustituye esa dirección por la dirección real de la api. Si la dirección de esa api nombre es no se encuentra en el FirstThunk , irá al OriginalFirstThunk e intentará obtener información de allí (considérelo como una copia de seguridad de FirstThunk ). ¡El FirstThunk sobrescrito es su IAT! (todo esto sucede antes del puntero de instrucción ie. el eip alcanza el punto de entrada del exe).

Mientras crea la tabla de importación fresca (o arregla la antiguo), Imprec primero encontrará el IAT ya sea mediante la búsqueda automática a través del código o mediante el valor proporcionado por el usuario. nuevo IAT encontrará los nombres de api en sus direcciones y llenará IMAGE_IMPORT_DESCRIPTORs . Esto facilita nuestro trabajo, ya que el cargador ahora podrá encontrar toda la información en la tabla de importación. Esto se explica visualmente muy bien en lena's tutorial no. 21.

    
respondido por el viv 13.10.2012 - 20:31
fuente

Lea otras preguntas en las etiquetas