martes, 22 de marzo de 2011

Recorrer la Import Table en C

En el post del otro día mostré una función de C para transformar de una dirección RVA a un offset del fichero en disco. Está función nos será útil para el objetivo de hoy: recorrer la Import Table de un ejecutable.

Ya lo hicimos manualmente es estos dos post: http://el-blog-de-thor.blogspot.com/2011/03/localizar-direcciones-en-la-iat-mano-12.html http://el-blog-de-thor.blogspot.com/2011/03/localizar-direcciones-en-la-iat-mano-22.html

Resumiendo:

  1. Debemos buscar la tabla de importaciones en el directorio de datos (PE+0x80), transformarla de RVA a Offset.
  2. Al inicio de la IT leeremos las distintas estructuras “IMAGE IMPORT DESCRIPTOR” hasta encontrar una vacía y en cada una de ellas:
    1. Leemos el nombre de la dll a la que corresponde
    2. Recorremos la lista de punteros u ordinales apuntados por OriginalFirstThunk o si esta estuviera vacía la de FirstThunk.
    3. Si es un ordinal, empieza por 0x8…, le mostramos
    4. Si no, es una RVA de una estructura “IMAGE_IMPORT_BY_NAME” que contiene el nombre de la función importada, mostramos dicho valor.

Os dejo el código:

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

DWORD RVAToOffset(byte *buf, DWORD RVA);

int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Pasale un parametro !");
return EXIT_FAILURE;
}
HANDLE fichero = CreateFile((LPCTSTR)argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(fichero == INVALID_HANDLE_VALUE)
{
printf("Error abriendo el fichero");
return EXIT_FAILURE;
}
printf("IAT de %s\n", argv[1]);
DWORD size = GetFileSize(fichero, NULL);
byte *buf = (byte *)malloc(size);
DWORD bytesleidos;
ReadFile(fichero, buf, size, &bytesleidos, NULL);
CloseHandle(fichero);
//Si se consigue leer en el buffer todo el fichero continuar
if(size == bytesleidos)
{
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)buf;
if(pIDH->e_magic == IMAGE_DOS_SIGNATURE) //MZ
{
 PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)&buf[pIDH->e_lfanew];
 //Dirección de la Import Table
 DWORD ITdir = pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
 //El primer Image Import Descriptor está al principio de la IT
 PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)&buf[RVAToOffset(buf, ITdir)];
 while(pIID->Name != 0)
 {
  printf("%s:\n", &buf[RVAToOffset(buf, pIID->Name)]);
  PIMAGE_THUNK_DATA pITD;
  //Se lee la INT (Import Name Table), si existe, sino la IAT
  if(pIID->OriginalFirstThunk != 0)  //En los exe's de borland OFT es 0, o no existe INT
   pITD = (PIMAGE_THUNK_DATA)&buf[RVAToOffset(buf, pIID->OriginalFirstThunk)];
  else
   pITD = (PIMAGE_THUNK_DATA)&buf[RVAToOffset(buf, pIID->FirstThunk)];
  while(pITD->u1.AddressOfData != NULL){
   //Por ordinal o por nombre?
   if((DWORD)pITD->u1.AddressOfData &  IMAGE_ORDINAL_FLAG32) //0x80000000
    printf("  Ord: %d\n", (DWORD)pITD->u1.AddressOfData & 0x7FFFFFFF);
   else{
    PIMAGE_IMPORT_BY_NAME pIIBN = (PIMAGE_IMPORT_BY_NAME)&buf[RVAToOffset(buf, pITD->u1.AddressOfData)];
    printf("  %s\n", pIIBN->Name);
   }
   pITD++;
  }
  pIID++;
 }
}
}
free(buf);
return EXIT_SUCCESS;
}

//Dada una dirección relativa virtual, RVA, la transforma a una posición exacta en el fichero
DWORD RVAToOffset(byte *buf, DWORD RVA)
{
//Se lee la cabecera DOS que está al principio
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)buf;
//Se lee la cabecera PE, el campo e_lfanew nos indica donde se encuentra dentro del fichero
PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)&buf[pIDH->e_lfanew];
//Buscamos a que sección pertenece la RVA para hacer los calculos correctos
for(DWORD i = 0; i < pINH->FileHeader.NumberOfSections; i++){
//Nos vamos desplazando por las secciones
PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)&buf[pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + i*sizeof(IMAGE_SECTION_HEADER)];
//Si la RVA está dentro del rango
if (pISH->VirtualAddress <= RVA && pISH->VirtualAddress + pISH->SizeOfRawData > RVA){
 //Se realizan los cálculos
 return RVA - pISH->VirtualAddress + pISH->PointerToRawData;
}
}
//Si no encuentra en ninguna sección retornamos -1
return -1;
}

Se admiten críticas al código, un saludo!

martes, 15 de marzo de 2011

Detección de información extra en ejecutables por Th3 Sw4sh

Mi amigo Th3 Sw4sh ha realizado un documento donde explica que es y para que se usa la información extra en ejecutables.

A continuación copio la introducción del documento:

La redacción de este documento tiene como fin explicar una forma de lograr detectar información extra no-original de un archivo ejecutable, que bien puede ser la parte binaria de un archivo quizá su información se encuentre encriptada, sumado a otro para luego darle cualquier uso podemos decir posiblemente (Stub – servidor), Técnica utilizada por los actuales “Encriptadores” .

Empezaremos hablando de la utilidad y hablando del aprovechamiento de esta información respecto al malware, mostraré unos cuantos ejemplos y espero de una u otra forma les llegue a ser de ayuda.

El documento y el código fuente en C para realinear secciones y así evitar que sea sencillo localizar esta información extra lo subo por aquí.

Gracias Th3 Sw4sh!

Código para pasar de RVA a Offset

Buenas noches! Es hora de comenzar a programar un poquillo con esto del PE.

Algo que seguro habéis leído en casi todos los documentos que he ido poniendo como referencia es cómo convertir de una dirección relativa virtual, RVA, a una posición física en el fichero, llamémoslo offset.

Primero manualmente

Lo primero que debemos hacer es encontrar a que sección del ejecutable pertenece esa RVA, para ello en cada sección nos fijamos en los valores VirtualAddress y VirtualSize. Una vez localizada la sección restamos a la RVA la VirtualAddress para saber el desplazamiento dentro de la sección y por último sumamos el PointerToRawData para localizar ese desplazamiento en disco.

Veámoslo con un ejemplo que sino no se entiende nada. Tomemos como ejemplo este post: http://el-blog-de-thor.blogspot.com/2011/03/localizar-direcciones-en-la-iat-mano-12.html

En él buscábamos la IT en disco, mediante un editor hexadecimal vimos que esta se encontraba en la RVA 0x7604. Mirando la secciones nos encontramos con la primera, .text con los siguientes valores:

Name: .text VirtualSize: 0x7748 VirtualAddress: 0x1000 SizeOfRawData: 0x7800 PointerToRawData: 0x400

La sección empieza en 0x1000 y acaba en 0x8748 (0x1000+0x7748), por lo que la RVA 0x7604 se encuentra dentro de ella.

Ahora debemos hacer los cálculos. RVA – comienzo de la sección(VirtualAddress) + comienzo de la sección en disco (PointerToRawData). 0x7604 – 0x1000 + 0x400 = 0x6A04

Y ahí está:

image

A programarlo

Vamos a programarlo en C, al programa se le pasaran 2 parámetros, el primero el nombre del ejecutable y el segundo la RVA en hexadecimal. Para pasar de una cadena en hexadecimal a un número usaremos la función strtoul.

Así que verificamos que lo que nos pasan como argumentos es lo que esperamos:

if(argc != 3 || strtoul(argv[2], NULL, 16) == 0) 
{
printf("Se esperaban 2 parametros, el primero un fichero y el segundo una RVA en hexadecimal\n");  
printf("Ej: %s notepad.exe 3A03\n", argv[0]);  
return EXIT_FAILURE;  
}

Después leemos la RVA recibida, abrimos el fichero y lo leemos entero en un buffer:

 //Convierte la cadena que contiene el numero en hexadecimal a DWORD   
DWORD RVA = strtoul(argv[2], NULL, 16);    
HANDLE fichero = CreateFile((LPCTSTR)argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);    
if(fichero == INVALID_HANDLE_VALUE)    
{    
 printf("Error abriendo el fichero\n");    
 return EXIT_FAILURE;    
}    
//Se lee todo el fichero en buf    
DWORD size = GetFileSize(fichero, NULL);    
byte *buf = (byte *)malloc(size);    
DWORD bytesleidos;    
ReadFile(fichero, buf, size, &bytesleidos, NULL);    
CloseHandle(fichero);    
if (bytesleidos != size)    
{    
 printf("Error leyendo el fichero\n");    
 return EXIT_FAILURE;    
}

Invocamos a la función mágica que convertirá de RVA a Offset, su contenido después.

DWORD Offset = RVAToOffset(buf, RVA);   
if (Offset == -1)    
{    
 printf("No se encontró el offset correspondiente a dicha RVA\n");    
 return EXIT_FAILURE;    
}    
//Se imprime el resultado
printf("RVA:    0x%08x\n", RVA);   
printf("Offset: 0x%08x\n", Offset);
free(buf); 
return EXIT_SUCCESS;  
}

Y ahora la función mágica la que tiene todo lo importante.

//Dada un dirección relativa virtual, RVA, la transforma a una posición exacta en el fichero 
DWORD RVAToOffset(byte *buf, DWORD RVA)  
{  
//Se lee la cabecera DOS que está al principio
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)buf;  
//Se lee la cabecera PE, el campo e_lfanew nos indica donde se encuentra dentro del fichero  
PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)&buf[pIDH->e_lfanew];  
//Buscamos a que sección pertenece la RVA para hacer los cálculos correctos  
for(DWORD i = 0; i < pINH->FileHeader.NumberOfSections; i++){  
 //Nos vamos desplazando por las secciones  
 PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)&buf[pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + i*sizeof(IMAGE_SECTION_HEADER)];  
 //Si la RVA está dentro del rango  
 if (pISH->VirtualAddress <= RVA && pISH->VirtualAddress + pISH->SizeOfRawData > RVA){  
   //Se realizan los cálculos  
   return RVA - pISH->VirtualAddress + pISH->PointerToRawData;  
 }  
}  
//Si no encuentra en ninguna sección retornamos -1  
return -1;  
}

Admito que así visto el código asusta un poco, con Visual Studio se ve mas bonito:

image

Y así se usa:

image

Cuelgo aquí el .c y el compilado para que lo podáis ver bien, compilar, probar o incluso usar.

El programa en sí no sirve de mucho, pero es algo necesario (pasar de RVA a Offset) para cuando hagamos cosas mas complicadas y así de paso vamos practicando.
Se esperan críticas al código. Un saludo!

domingo, 13 de marzo de 2011

Documentación interesante

En estos días ando leyendo distinta documentación sobre el formato PE, la tabla de importación de los ejecutables, etc…

Os dejo 3 documentos interesantes sobre el tema. El primero trata sobre la estructura del formato PE, explica algo de la tabla de importaciones y de la de exportaciones. 044-ESTUDIO DE LOS ENCABEZADOS PE parte 1 POR SICK TROEN.zip 048-ESTUDIO DE LOS ENCABEZADOS PE parte 2 POR SICK TROEN.zip

En estas 4 “teorias” Ricardo Narvaja explica lo de que es la Import Table, como recomponerla a mano tras haber dumpeado un archivo comprimido con UPX e incluye un script para reparar la IAT. Realmente bueno.
253-IMPORT TABLES A MANO (parte 1).rar
254-IMPORT TABLES A MANO (parte 2).rar
255-IMPORT TABLES A MANO (parte 3).rar
256-IMPORT TABLES A MANO (parte 4).rar
Y el mas interesante y completo de todos es el artículo de la revista Codebreakers “Portable Executable File Format – A Reverse Engineer View” basta con ver el índice:
1 Introduction 2 Basic Structure 3 The DOS Header 4 The PE Header 5 The Data Directory 6 The Section Table 7 The PE File Sections 8 The Export Section 9 The Import Section 10 The Loader 11 Navigating Imports on Disk 12 Adding Code to a PE File 13 Adding Import to an Executable 14 Introduction to Packers 15 Infection of PE Files by Viruses 16 Conclusion 17 Relative Virtual Addressing Explained 18 References & Bibliography 19 Tools Used 20 Appendix: Complete PE Offset Reference
Felices lecturas!

miércoles, 9 de marzo de 2011

Charla interesante en la Rooted sobre virii

El viernes pasado en la RootedCon 2011 Alberto García realizó una presentación que parece muy interesante: Virus, el arte no debería ser negocio. En las diapositivas comenta varias cosas interesantes.

Una de ellas es que la forma mas típica de infectar un ejecutable es añadiendo una nueva sección, cambiando el EP a la nueva sección, ejecutar el código malicioso y mas tarde regresar al EP original. Y otra forma que comenta para que no sea muy evidente que el EP ha sido modificado es poner como primera instrucción un salto a la nueva sección.

También habla de cifrar un ejecutable y que este a su vez cree nuevas copias cifradas con distintas claves o con distintos algoritmos de cifrado para que cada copia del mismo sea diferente.

Otra de las cosas que comenta es utilizar el protocolo DNS o ICMP para controlar los bots y pasar mas desapercibidos a los cortafuegos.

Espero que pronto salga el video de la conferencia para verlo completo.

lunes, 7 de marzo de 2011

Localizar direcciones en la IAT a mano 2/2


Localizar direcciones en la IAT a mano 1/2

En la anterior entrada localizamos la estructura “Directorio de Datos Importados” correspondiente a la DLL User32.dll que contenía los siguientes valores:

image

dwRVAFunctionNameList: 0x00007854 dwUseless1: 0xFFFFFFFF dwUseless2: 0xFFFFFFFF dwRVAModuleName: 0x0000873C dwRVAFunctionAddressList: 0x00001188

Lo primero es recorrer el vector de nombres de las funciones importadas, dwRVAFunctionNameList, hasta encontrar la de MessageBoxW. Está en la dirección RVA 0x7854, que traducida a FileOffset como hicimos en el anterior entrada nos da: 0x7854-0x1000+0x400= 0x6C54

En esa dirección vemos una lista muy larga de punteros:

image

74 punteros, cada uno de ellos es una RVA donde encontrar el nombre de la función importada de la DLL User32.dll. Así que ahora toca un poco de trabajo sucio e ir uno a uno buscando a que nombre llevan. Por ejemplo el primero 0x00008524 que traducido a FileOffset es 0x7924 donde encontramos esto:

image

Los primeros 2 bytes corresponden al ordinal con el que se exporta la funcionen este caso 0xFF, los siguiente bytes corresponden al nombre de la función importada “GetClientRect”. Siguiente este proceso con el resto de punteros llegamos al que está en la posición 0xE0, en el FileOffset 0x6D34 (0x6C54+0x4*0xE0).

image

0x00839A, traducido a FileOffset: 0x779A (0x839A-0x1000+0x400), ahí es donde encontramos lo siguiente:

image

Que es la función buscada.

Sabemos que está en al posición 0xE0 de la lista de funciones importadas de user32.dll. Ahora tenemos que ir a esa posición de la lista de dirección de las funciones, dwRVAFunctionAddressList, y podremos saber en que dirección de memoria dentro de la IAT se encontrará una vez cargado el programa en memoria la dirección en nuestra versión de Windows de la funcion MesageBoxW.

La lista de direcciones se encuentra en al dirección RVA 0x00001188: dwRVAFunctionAddressList: 0x00001188

Sabiendo que la dirección de nuestra función (MessageBoxW) se encontraba en la posición 0xE0 deberemos sumar este valor al comienzo de la lista para obtener su dirección RVA exacta: 0x1188+0xE0=0x1268

Esta recordemos que es la dirección RVA, para obtener la dirección real en memoria debemos de sumarla la “ImageBase”, es un valor de la cabecera PE que en el ejecutable que estamos usando(Notepad de XP SP2 en español) es 0x01000000: 0x01000000+0x1268=0x01001268

Y efectivamente una vez cargado el programa en memoria en esa dirección podemos encontrar la dirección en nuestro sistema de MessageBoxW. Veámoslo en OllyDbg:

image

En esa dirección vemos el código en ensamblador de la función MessageBoxW, que a su vez llama a la función MessageBoxExW pasándole algún parámetro nulo:

image

Y esto es todo. Como decía en la primera entrada el proceso está resumido aconsejo leer el documento que puse inicialmente para mas detalles. Se puede hacer todo esto de una manera mucho mas sencilla utilizado programas que te facilitan la tarea. Pero mi objetivo es saberlo hacer todo a mano para algún día poder programar algo relacionado con todo esto.

Nos vemos pronto!

viernes, 4 de marzo de 2011

Localizar direcciones en la IAT a mano 1/2

En la anterior entrada una vez localizada la IAT(Import Address Table) nos ayudamos de OllyDbg para saber donde se almacena la dirección de la función MessageBoxW, para después usarla en nuestro código (CALL [Dir de MessageBoxW en la IAT]) y así no tener la dirección “hardcoded”.

image

Vamos hacer esta búsqueda a mano así nos familiarizamos con la estructura de la IT(Import Table) y la IAT. Aviso que el proceso que  sigo está resumido y da cosas por sabidas, recomiendo leer este documento Descabezando archivos ejecutables portables de nuMIT_or antes o durante se lee la entrada.

En el proceso usaré el notepad de la anterior entrada, aquí se puede descargar.

Lo primero es localizar la IT en disco. Para ello hago uso de 010 Editor, en la cabecera opcional dentro de la cabecera PE, está el directorio de datos, una de esos directorios es el directorio de Importaciones:

image

Como vemos podemos encontrar este directorio en la dirección virtual (de ahora en adelante VA) 7604h. Hay que tener en cuenta que está dirección es virtual y nosotros estamos buscando estos datos en el archivo en disco, así que necesitamos traducir de VA a dirección física. Primero necesitamos saber en que sección de todas ira a caer esta VA.

image

La sección text empieza en la VA 1000h y acaba en la 8748h. Así que la IT que está en la VA 7604h pertenece a la sección .text. Esto también nos lo muestra OllyDbg:

image

Ahora para pasar de la VA a la dirección física debemos restar a la VA la VA inicial de la sección text (1000h).
7604h-1000h=6604h
Y a este valor sumarle la dirección física donde comienza la sección, SizeOfRawData:
6604h+400h=6A04h

Vamos a esa dirección con el editor hexadecimal y vemos la IT, la he coloreado en rojo:

image

La IT es un vector de valores DW (Double word, 4 bytes) que sigue esta estructura:
dwRVAFunctionNameList DWORD ;
dwUseless1 DWORD ;
dwUseless2 DWORD ;
dwRVAModuleName DWORD ;
dwRVAFunctionAddressList DWORD ;

He coloreado en verde la primera de las estructuras:

image

Lo que nos interesa ahora es ver el campo dwRVAModuleName que es donde está el nombre de la DLL importada. En esta primera estructura tiene el valor 00007AACh. Este valor a su vez es una VA y debe ser traducida a dirección física. Aplicando el mismo procedimiento que antes tenemos que 7AACh->6EACh

image

Vemos entonces que la primera estructura de la IT se refiere a la DLL comdlg32.dll. Nosotros buscamos la función MessageBoxW que pertenece a la dll user32.dll, así que seguimos con las siguientes estructuras de la IT hasta encontrar justo al final la correspondiente a la DLL User32.dll:

image

Una vez localizada la entrada en la IT correspondiente a User32.dll nos falta recorrer el vector de funciones importadas para localizar MessageBoxW y buscar en el vector de direcciones la dirección de la función.

Eso en la siguiente entrada. (Que no podrá ser hasta el lunes ya que estaré alejado del ordenador).

martes, 1 de marzo de 2011

Añadir código portable a un ejecutable 2/2


Añadir código portable a un ejecutable 1/2

Continuando…

Debemos buscar la función MessageBoxA en la IAT de modo que podamos usar la dirección que haya puesto allí el cargador de Windows. La dirección de memoria donde está la IAT se puede saber mirando en la cabecera PE:

Sin título_thumb[1]

La dirección RVA es 1000h, la dirección en memoria, Virtual Address, será ese valor + ImageBase es decir 0x01001000 justo donde empieza la sección text de código. Podemos abrirlo en OllyDbg y en esa dirección veremos una serie de offsets, al lado nos muestra a la dirección de que función corresponde.

Sin título

Ahora debemos buscar la función MessageBoxA. En msdn podemos ver que es una función que está definida en User32.dll. Así que buscamos esa función y encontramos una parecido MessageBoxW, que es la versión Unicode, nos sirve Guiño.

Sin título

Pero debemos cambiar la cadena que teníamos a una cadena unicode, para que sea compatible con la función.

Sin título

Una vez con todo esto tenemos que adaptar el código ensamblador que pusimos al inicio de la sección para que lea la dirección de la función MessageBoxW desde la IAT, de la dirección 0x010001268.

image

01014017 E8 00000000 CALL 0101401C 0101401C 58 POP EAX 0101401D 6A 00 PUSH 0 0101401F 83E8 1C SUB EAX,1C 01014022 50 PUSH EAX 01014023 50 PUSH EAX 01014024 6A 00 PUSH 0 01014026 A1 68120001 MOV EAX,DWORD PTR DS:[01001268] 0101402B FFD0 CALL EAX 0101402D B8 9D730001 MOV EAX,0100739D 01014032 FFE0 JMP EAX

Y de este modo conseguimos un notepad “infectado”, que muestra un MessageBox al iniciarse y que funcionará correctamente en todos los Windows. Lo subo aquí por si alguien quiere ojearlo.

Pero hemos hecho un poco trampas, porque hemos usado la función MessageBoxW en vez de MessageBoxA, el problema es que notepad no usa MesasgeBoxA así que no Importa dicha función y no podemos obtener su dirección leyendo la IAT. Esto es un problema ya que solo podremos usar las APIs que ya utilice el programa y no todas las de Windows. Se puede solucionar utilizando las funciones GetProcAddress y GetModuleHandle, pero eso ya para otra entrada.

Saludos!