martes, 15 de marzo de 2011

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!

1 comentario:

  1. Realmente simple pero contundente, y para practica mejor, como te dije no conocía esa funcion de hex, algo más que se aprende.

    Saludos y un placer leerlo.

    ResponderEliminar