468x60 Ads

Mostrando entradas con la etiqueta Ingenieria-Inversa. Mostrar todas las entradas
Mostrando entradas con la etiqueta Ingenieria-Inversa. Mostrar todas las entradas

jueves, 19 de diciembre de 2013

Introducción: Estructura PE (Portable Executable)


Introducción: Estructura PE (Portable Executable)

Que es la estructura PE?

El archivo PE (Portable Executable) es el formato que utiliza windows para los ejecutables. En fin, en pocas palabras es un .exe o un .dll

Para que sirve conocerlo?

En Ing inversa muchas veces no es necesario conocer con mucho detalle la estructura que tiene este archivo, sin embargo, para problemas más complicados si resultará util conocerlo. Nos permite separar las secciones del exe, analizarlas por separado, crear nuevas secciones, o modificar las existentes.

Las aplicaciones son bastantes, pero lo mas importante es que con esto se entenderá mejor porque pasan algunas cosas y como podemos resolverlas

Estructura basica:

Vamos por parte, primero con una división a grandes razgos de las partes del archivo, y despues vamos ir entrando en detalle en las mas importantes

El archivo PE simplemente un archivo binario, que tiene una estructura, que es basicamente esta:

--------------------
|  ENCABEZADO DOS  |
--------------------
|  STUB DOS        |
--------------------
|  ENCABEZADO NT   |
--------------------
|  ENCABEZADO SEC  |
--------------------
|  STUB NT         |
--------------------

Como se ve el archivo tiene 5 partes, asi de simple XD, los encabezados tienen un tamaño constante y en ellos están definidas todas las propiedades del archivo. Los stub contienen al programa en sí, los analizaremos por parte.

Sobre el Encabezado DOS y el Stub DOS

Tanto el encabezado DOS como el stub DOS no nos interesará tanto, están ahi por una especie de compatibilidad, si estamos ejecutando el programa en windows se ignorará toda esta parte, generalmente eso contiene un pequeño programa en DOS que solamente imprime en pantalla algo como esto:

"This program cannot be run in DOS mode."para indicar que estamos tratando de ejecutar un archivo que no corre en DOS.

Encabezado DOS

El encabezado DOS, asi como ya dije, es una estructura de datos que contiene variables que definen el programa, las variables que contiene este encabezado las podemos sacar de la ayuda de windows (msdn.microsoft.com).

typedef struct _IMAGE_DOS_HEADER
{
     WORD e_magic;
     WORD e_cblp;
     WORD e_cp;
     WORD e_crlc;
     WORD e_cparhdr;
     WORD e_minalloc;
     WORD e_maxalloc;
     WORD e_ss;
     WORD e_sp;
     WORD e_csum;
     WORD e_ip;
     WORD e_cs;
     WORD e_lfarlc;
     WORD e_ovno;
     WORD e_res[4];
     WORD e_oemid;
     WORD e_oeminfo;
     WORD e_res2[10];
     DWORD e_lfanew;
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

Esto es el encabezado, no nos interesa tanto, lo importante aqui es que, para leer todo el archivo debemos por lo menos saber el tamaño que ocupa este encabezado, y lo mas importante es que en este encabezado está la variable que indica donde comienza el encabezado del NT (que es realmente el encabezado que nos interesa) dentro de esta variable e_lfanew se indica donde comienza el encabezado NT, vamos a ver con un ejemplo, esto es lo que se ve con un editor hexadacimal.

"000000000  4D 5A 90 00 03 00 00 00-04 00 00 00 FF FF 00 00   |MZ##############|"
"000000010  B8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00   |########@#######|"
"000000020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   |################|"
"000000030  00 00 00 00 00 00 00 00-00 00 00 00 80 00 00 00   |################|"
"000000040  0E 1F BA 0E 00 B4 09 CD-21 B8 01 4C CD 21 54 68   |######.#!##L#!Th|"
"000000050  69 73 20 70 72 6F 67 72-61 6D 20 63 61 6E 6E 6F   |is program canno|"
"000000060  74 20 62 65 20 72 75 6E-20 69 6E 20 44 4F 53 20   |t be run in DOS |"
"000000070  6D 6F 64 65 2E 0D 0D 0A-24 00 00 00 00 00 00 00   |mode....$#######|"
"000000080  50 45 00 00 4C 01 05 00-24 A9 8A 4C 00 6C 00 00   |PE##L###$##L#l##|"

Ahora hay que fijarse en la estructura del DOS_HEADER y lo que se ve en el archivo el tamaño del encabezado podemos calcular 30*WORD+1*DWORD = 64 bytes el encabezado tiene 64 bytes = 0x40 bytes (resulta mas comodo leer todo en hexadecima) en el archivo separé con una linea, donde termina este encabezado, es exactamente en el offset 0x40.

Ahora vamos a leer la variable que nos interesa (DWORD e_lfanew), que son los ultimos 4 bytes del encabezado. en el archivo vemos que en esos 4 bytes tenemos "80 00 00 00" no hay que perder de vista que, dentro del archivo las variables se guardan con el byte menos significativo a la izquierda, asi que, si queremos leer esto como un numero hexa, debemos invertir este valor, tomando de 2 en 2, este valor en hexa será 0x00000080 (si no se entendió coloco aqui un ejemplo, si vemos en el archivo una variable asi "01 02 03 04" el valor en hexadecimal de esa variable es 0x04030201, se debe invertir los numero de 2 en 2)

Bueno, con esto ya podemos leer donde empieza el encabezado del NT, está en el offset 0x00000080

"000000070  6D 6F 64 65 2E 0D 0D 0A-24 00 00 00 00 00 00 00   |mode....$#######|"
"000000080  50 45 00 00 4C 01 05 00-24 A9 8A 4C 00 6C 00 00   |PE##L###$##L#l##|"
"000000090  31 03 00 00 E0 00 07 03-0B 01 02 38 00 5A 00 00   |1##########8#Z##|"
"0000000A0  00 68 00 00 00 0C 00 00-30 11 00 00 00 10 00 00   |#h######0#######|"

Stub DOS

El codigo en DOS realmente no nos interesa, pero solo para marcarlo, donde comienza y donde termina, está a continuación del Encabezado DOS y termina antes del encabezado del NT, asi que, en este caso, lo que tenemos en el stub del DOS es esto:

"000000040  0E 1F BA 0E 00 B4 09 CD-21 B8 01 4C CD 21 54 68   |######.#!##L#!Th|"
"000000050  69 73 20 70 72 6F 67 72-61 6D 20 63 61 6E 6E 6F   |is program canno|"
"000000060  74 20 62 65 20 72 75 6E-20 69 6E 20 44 4F 53 20   |t be run in DOS |"
"000000070  6D 6F 64 65 2E 0D 0D 0A-24 00 00 00 00 00 00 00   |mode....$#######|"

Como se puede ver, ahi esta el texto que dice que la aplicación no corre en DOS

Encabezado NT

El encabezado NT es un poco mas complicado que el de DOS, veamos su estructura.

    typedef struct _IMAGE_NT_HEADERS {
      DWORD                 Signature;
      IMAGE_FILE_HEADER     FileHeader;
      IMAGE_OPTIONAL_HEADER OptionalHeader;
    } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;     

Esta es su definición, la podemos encontrar en esta pagina IMAGE_NT_HEADERS

Esta estructura todavia no nos dice mucho, solo que tiene un DWORD, que es un valor de referencia, siempre vale 0x00004550, realmente conviene leer esta variable como un char* "50 45 00 00", en ascii significa 'PE\0\0', esto se puede ver facilmente y permite identificar donde comienza el encabezado NT sin tener que hacer el cálculo que hicimos hace un rato.

Esto lo vemos aqui, (ver los primeros 4 bytes)

"000000080  50 45 00 00 4C 01 05 00-24 A9 8A 4C 00 6C 00 00   |PE##L###$##L#l##|"

Veamos las estructuras contenidas en esta estructura: IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER

IMAGE_FILE_HEADER

typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;
  WORD  NumberOfSections;
  DWORD TimeDateStamp;
  DWORD PointerToSymbolTable;
  DWORD NumberOfSymbols;
  WORD  SizeOfOptionalHeader;
  WORD  Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;


No vamos a ver con detalle todas las variables de este encabezado, solo las que nos interesan WORD Machine: nos dice el tipo de maquina para la que fue compilado: 386,486,586,... DWORD NumberOfSections: numero de secciones del archivo, esto es importante, si es que queremos agregar secciones.

DWORD TimeDateStamp: la fecha en la que fue compilado DWORD PointerToSymbolTable: puntero a la tabla de simbolos DWORD NumberOfSymbols: numero de simbolos (despues veremos esto en detalle) WORD SizeOfOptionalHeader: tamaño del IMAGE_OPTIONAL_HEADER, esto es interesante ya que, haciendo unas operaciones podemos llegar al stub del NT (donde comienza realmente el codigo) del encabezado DOS tenemos el offset del inicio de esta estructura, si a eso le sumamos el tamaño del IMAGE_FILE_HEADER y del IMAGE_OPTIONAL_HEADER, llegaremos al inicio del Stub NT (despues veremos en detalle como hacer este calculo)

sigamos con el IMAGE_OPTIONAL_HEADER

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  DWORD                ImageBase;
  DWORD                SectionAlignment;
  DWORD                FileAlignment;
  WORD                 MajorOperatingSystemVersion;
  WORD                 MinorOperatingSystemVersion;
  WORD                 MajorImageVersion;
  WORD                 MinorImageVersion;
  WORD                 MajorSubsystemVersion;
  WORD                 MinorSubsystemVersion;
  DWORD                Win32VersionValue;
  DWORD                SizeOfImage;
  DWORD                SizeOfHeaders;
  DWORD                CheckSum;
  WORD                 Subsystem;
  WORD                 DllCharacteristics;
  DWORD                SizeOfStackReserve;
  DWORD                SizeOfStackCommit;
  DWORD                SizeOfHeapReserve;
  DWORD                SizeOfHeapCommit;
  DWORD                LoaderFlags;
  DWORD                NumberOfRvaAndSizes;
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

DWORD SizeOfCode; indica el tamaño total del code, osea, es la suma de los tamaños de las secciones que contienen codigo.
DWORD AddressOfEntryPoint; direccion del EntryPoint
DWORD BaseOfCode esto indica donde empieza la seccion que contiene el codigo (generalmente la seccion .text)
DWORD BaseOfData; indica donde empieza la seccion que contiene las constantes del programa (generalmente .data)
DWORD ImageBase; todas las direcciones virtuales están colocadas relativas a esta direccion, asi que a todas hay que sumarle este valor para sacar la direccion
DWORD SectionAlignment; Alineacion de las secciones, en memoria
DWORD FileAlignment; alineacion de las secciones, en el archivo
DWORD SizeOfImage; suma de los tamaños de las secciones
DWORD SizeOfHeaders; suma de los tamaños de los encabezados de las secciones
DWORD NumberOfRvaAndSizes; esto da el numero de secciones y tamaños de cada seccion, esto es necesario para leer la info de IMAGE_DATA_DIRECTORY DataDirector, esta estructura que contiene la direccion de las secciones en el archivo, y los tamaños de estas secciones.
estas secciones son secciones especiales, que siempre deben estar en los programas, ya vamos a ver que tienen una relacion con las secciones reales del programa, pero en nuestro programa podemos tener otras secciones que no estén en esta tabla.
las secciones de esta tabla son:
{"Export Table","Import Table","Resource Table","Exception Table","Certificate Table","Base Relocation Table","Debug","Architecture","Global Ptr","TLS Table","Load Config Table","Bound Import","IAT","Delay Import Descriptor","COM+ Runtime Header","Reserved"}
si alguna de estas secciones no existe en el programa se coloca su direccion 0x0 y tamaño 0x0, como podremos ver en los programas reales

Aqui se puede ver la definicion de esa estructura

    typedef struct _IMAGE_DATA_DIRECTORY {
      DWORD VirtualAddress;
      DWORD Size;
    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;  

El IMAGE_OPTIONAL_HEADER en el ejecutable de ejemplo se puede ver aqui: (comienza en el offset 0x000000098 y termina en el 000000177)

"000000090  31 03 00 00 E0 00 07 03-0B 01 02 38 00 5A 00 00   |1##########8#Z##|"
"0000000A0  00 68 00 00 00 0C 00 00-30 11 00 00 00 10 00 00   |#h######0#######|"
"0000000B0  00 70 00 00 00 00 40 00-00 10 00 00 00 02 00 00   |#p####@#########|"
"0000000C0  04 00 00 00 01 00 00 00-04 00 00 00 00 00 00 00   |################|"
"0000000D0  00 B0 00 00 00 04 00 00-A9 4C 01 00 03 00 00 00   |#########L######|"
"0000000E0  00 00 20 00 00 10 00 00-00 00 10 00 00 10 00 00   |## #############|"
"0000000F0  00 00 00 00 10 00 00 00-00 00 00 00 00 00 00 00   |################|"
"000000100  00 A0 00 00 B8 04 00 00-00 00 00 00 00 00 00 00   |################|"
"000000110  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   |################|"
"000000120  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   |################|"
"000000130  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   |################|"
"000000140  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   |################|"
"000000150  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   |################|"
"000000160  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   |################|"
"000000170  00 00 00 00 00 00 00 00-2E 74 65 78 74 00 00 00   |########.text###|"

Aqui vemos eso mismo, pero ordenando cada variable (visto con el ollydebg): hice un salto de linea donde comienza la tabla de secciones (esto no es el encabezado de las secciones, que está a continuación)

00400098        0B01         DW 010B              ; MagicNumber = PE32
0040009A        02           DB 02                ;  MajorLinkerVersion = 2
0040009B        38           DB 38                ;  MinorLinkerVersion = 38 (56.)
0040009C        005A0000     DD 00005A00          ;  SizeOfCode = 5A00 (23040.)
004000A0        00680000     DD 00006800          ;  SizeOfInitializedData = 6800 (26624.)
004000A4        000C0000     DD 00000C00          ;  SizeOfUninitializedData = C00 (3072.)
004000A8        30110000     DD 00001130          ;  AddressOfEntryPoint = 1130
004000AC        00100000     DD 00001000          ;  BaseOfCode = 1000
004000B0        00700000     DD 00007000          ;  BaseOfData = 7000
004000B4        00004000     DD 00400000          ; ImageBase = 400000
004000B8        00100000     DD 00001000          ;  SectionAlignment = 1000
004000BC        00020000     DD 00000200          ;  FileAlignment = 200
004000C0        0400         DW 0004              ;  MajorOSVersion = 4
004000C2        0000         DW 0000              ;  MinorOSVersion = 0
004000C4        0100         DW 0001              ;  MajorImageVersion = 1
004000C6        0000         DW 0000              ;  MinorImageVersion = 0
004000C8        0400         DW 0004              ;  MajorSubsystemVersion = 4
004000CA        0000         DW 0000              ;  MinorSubsystemVersion = 0
004000CC        00000000     DD 00000000          ;  Reserved
004000D0        00B00000     DD 0000B000          ;  SizeOfImage = B000 (45056.)
004000D4        00040000     DD 00000400          ;  SizeOfHeaders = 400 (1024.)
004000D8        A94C0100     DD 00014CA9          ;  CheckSum = 14CA9
004000DC        0300         DW 0003              ;  Subsystem = IMAGE_SUBSYSTEM_WINDOWS_CUI
004000DE        0000         DW 0000              ;  DLLCharacteristics = 0
004000E0        00002000     DD 00200000          ;  SizeOfStackReserve = 200000 (2097152.)
004000E4        00100000     DD 00001000          ;  SizeOfStackCommit = 1000 (4096.)
004000E8        00001000     DD 00100000          ;  SizeOfHeapReserve = 100000 (1048576.)
004000EC        00100000     DD 00001000          ;  SizeOfHeapCommit = 1000 (4096.)
004000F0        00000000     DD 00000000          ;  LoaderFlags = 0
004000F4        10000000     DD 00000010          ;  NumberOfRvaAndSizes = 10 (16.)
004000F8        00000000     DD 00000000          ;  Export Table address = 0
004000FC        00000000     DD 00000000          ;  Export Table size = 0
00400100        00A00000     DD 0000A000          ;  Import Table address = A000
00400104        B8040000     DD 000004B8          ;  Import Table size = 4B8 (1208.)
00400108        00000000     DD 00000000          ;  Resource Table address = 0
0040010C        00000000     DD 00000000          ;  Resource Table size = 0
00400110        00000000     DD 00000000          ;  Exception Table address = 0
00400114        00000000     DD 00000000          ;  Exception Table size = 0
00400118        00000000     DD 00000000          ;  Certificate File pointer = 0
0040011C        00000000     DD 00000000          ;  Certificate Table size = 0
00400120        00000000     DD 00000000          ;  Relocation Table address = 0
00400124        00000000     DD 00000000          ;  Relocation Table size = 0
00400128        00000000     DD 00000000          ;  Debug Data address = 0
0040012C        00000000     DD 00000000          ;  Debug Data size = 0
00400130        00000000     DD 00000000          ;  Architecture Data address = 0
00400134        00000000     DD 00000000          ;  Architecture Data size = 0
00400138        00000000     DD 00000000          ;  Global Ptr address = 0
0040013C        00000000     DD 00000000          ;  Must be 0
00400140        00000000     DD 00000000          ;  TLS Table address = 0
00400144        00000000     DD 00000000          ;  TLS Table size = 0
00400148        00000000     DD 00000000          ;  Load Config Table address = 0
0040014C        00000000     DD 00000000          ;  Load Config Table size = 0
00400150        00000000     DD 00000000          ;  Bound Import Table address = 0
00400154        00000000     DD 00000000          ;  Bound Import Table size = 0
00400158        00000000     DD 00000000          ;  Import Address Table address = 0
0040015C        00000000     DD 00000000          ;  Import Address Table size = 0
00400160        00000000     DD 00000000          ;  Delay Import Descriptor address = 0
00400164        00000000     DD 00000000          ;  Delay Import Descriptor size = 0
00400168        00000000     DD 00000000          ;  COM+ Runtime Header address = 0
0040016C        00000000     DD 00000000          ;  Import Address Table size = 0
00400170        00000000     DD 00000000          ;  Reserved
00400174        00000000     DD 00000000          ;  Reserved

Diferencia entre Dirección RAW y Virtual

Para poder entender el significado de algunas de estas variables, hace falta una pequeña introduccion de como las secciones se colocan en memoria.

Cuando se coloca un programa en memoria, para ejecutarlo, no se coloca exactamente como está en el archivo, en el archivo se tiene todo en forma mas compactada. En memoria se alinean las secciones, generalmente de a 0x1000 bytes (esto depende de la variable que se encuentra en este encabezado, SectionAlignment es la variable que dice el valor de la alineacion, solo que generalmente vale 0x1000), esto hace mas rapida la lectura, sin embargo las secciones en el archivo, no hace falta que se separen unas de otras, aunque tambien son alineadas (la variable FileAlignment tiene el valor al cual se alinea en el archivo)

Ademas de esta diferencia, el codigo del programa no comienza de la direccion 0x0 como pasa en el archivo, asi que una direccion en el archivo es totalmente distinta a una direccion en memoria. en el encabezado, cuando se refiere a memoria Rva se refiere a la memoria en el archivo, y cuando habla de memoria virtual se refiere al programa ya colocado en memoria para la ejecución eso es necesario diferenciar para entender el encabezado.

Si queremos encontrar, donde empieza una seccion en el archivo, debemos buscar su direccion RVA, si queremos ver donde empieza esa misma seccion en memoria, debemos buscar su direccion Virtual (mas adelante veremos como identificar eso, con unos ejemplos)

Seguido de esto viene el encabezado de las secciones esta es la estructura de cada encabezado de seccion

    typedef struct _IMAGE_SECTION_HEADER {
      BYTE  Name[IMAGE_SIZEOF_SHORT_NAME]; /* IMAGE_SIZEOF_SHORT_NAME = 8 bytes */
      union {
        DWORD PhysicalAddress;
        DWORD VirtualSize;
      } Misc;
      DWORD VirtualAddress;
      DWORD SizeOfRawData;
      DWORD PointerToRawData;
      DWORD PointerToRelocations;
      DWORD PointerToLinenumbers;
      WORD  NumberOfRelocations;
      WORD  NumberOfLinenumbers;
      DWORD Characteristics;
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

Por la union no hay que preocuparse, considerese solo una variable, que indica el tamaño (VirtualSize)

La cantidad de secciones tenemos ya, del encabezado NT, al ir leyendo todos los encabezados de las secciones, tendremos su posicion en el archivo (PointerToRawData), y en memoria (VirtualAddress), el tamaño es el mismo, la diferencia es que, estando en memoria, se llena de 0x0 hasta ajustarse a la alineación.

En fin, despues de esto solo se tiene que ir leyendo las secciones.

.text es generalmente la seccion que contiene el codigo .data y .rdata contiene constantes, son secciones de solo lectura. .bss es una seccion de lectura y escritura, se usa para las variables globales generalmente, las variables locales se colocan en el stack (eso lo veremos en otro tuto) .idata es la tabla de importacion de funciones .edata es la tabla de exportacion de funciones (es mas comun en los DLL, aunque un exe tambien lo puede tener)

En nuestro code de ejemplo aqui esta el encabezado de las secciones

"000000170  00 00 00 00 00 00 00 00-2E 74 65 78 74 00 00 00   |########.text###|"
"000000180  E8 59 00 00 00 10 00 00-00 5A 00 00 00 04 00 00   |#Y#######Z######|"
"000000190  00 00 00 00 00 00 00 00-00 00 00 00 60 00 50 60   |############`#P`|"
"0000001A0  2E 64 61 74 61 00 00 00-44 00 00 00 00 70 00 00   |.data###D####p##|"
"0000001B0  00 02 00 00 00 5E 00 00-00 00 00 00 00 00 00 00   |#####^##########|"
"0000001C0  00 00 00 00 40 00 30 C0-2E 72 64 61 74 61 00 00   |####@#0#.rdata##|"
"0000001D0  20 05 00 00 00 80 00 00-00 06 00 00 00 60 00 00   | ############`##|"
"0000001E0  00 00 00 00 00 00 00 00-00 00 00 00 40 00 60 40   |############@#`@|"
"0000001F0  2E 62 73 73 00 00 00 00-78 0A 00 00 00 90 00 00   |.bss####x.######|"
"000000200  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   |################|"
"000000210  00 00 00 00 80 00 40 C0-2E 69 64 61 74 61 00 00   |######@#.idata##|"
"000000220  B8 04 00 00 00 A0 00 00-00 06 00 00 00 66 00 00   |#############f##|"
"000000230  00 00 00 00 00 00 00 00-00 00 00 00 40 00 30 C0   |############@#0#|"

Seguido a esto (hay un espacio lleno de 0x0 donde se pueden agregar mas encabezados de secciones), y luego ya están las secciones en sí (a lo que le llamé Stub NT)

Esto es todo, se supone que con esto se debe entender como es el formato del ejectuable de windows.

A continuacion veremos un ejemplo de como encontrar una zona especifica de un programa, en memoria y en el archivo.

Para empezar buscaremos el famoso EntryPoint, esta direccion indica el lugar donde se comienza a ejecutar el code, asi que, se supone que debe caer dentro de la seccion de code, ( en el caso del ejemplo solo tiene una seccion con code .text )

Dentro del OptionalHeader tenemos una variable llamada AddressOfEntryPoint, para encontrarlo en el archivo, vamos al inicio de la estructura del OptionalHeader, y contamos los tamaños de las variables que están antes WORD+2*BYTE+3*DWORD = 0x10 bytes (16 en decimal) sabemos que el inicio de esa estructura esta en 0x000000098 (eso ya lo vimos mas arriba), asi que la direccion del EntryPint debe estar en el Offset 0x0000000A8, tambien sabemos que ocupa 1 DWORD = 4 bytes.

"0000000A0  00 68 00 00 00 0C 00 00-30 11 00 00 00 10 00 00   |#h######0#######|"
"0000000B0  00 70 00 00 00 00 40 00-00 10 00 00 00 02 00 00   |#p####@#########|"

Comos que en el archivo es este el valor "30 11 00 00", como ya dijimos hay que voltearlo de 2 en 2 para tener el valor en Hexa 0x00001130, listo esta es la direccion del entrypoint, pero, tambien como vimos, todas estas direcciones estan referidas al ImageBase, de la misma forma que buscamos el valor del EntryPoint busquemos el ImageBase, esta variable está 3 Dword abajo del entrypoint, osea 8bytes, 0x0000000A8 + 0xC = 0x0000000B4 y ocupa 4 bytes, este es el valor "00 00 40 00" volteando = 0x00400000, bien, asi que el EntryPoint está realmente en 0x00400000 + 0x00001130 = 0x00401130 (eso lo podemos comprobar con algun otro programa como el olly)

Que pasa si queremos ver ese entrypoint en el archivo, como lo vemos?
primero hay que saber a que seccion corresponde, en este caso, es evidente que corresponde a .text veamos El VirtualAdress de .text, y el tamaño para acegurarnos de que está en esa seccion este es el encabezado de esa seccion (comienza en 0x000000178)

"000000170  00 00 00 00 00 00 00 00-2E 74 65 78 74 00 00 00   |########.text###|"
"000000180  E8 59 00 00 00 10 00 00-00 5A 00 00 00 04 00 00   |#Y#######Z######|"
"000000190  00 00 00 00 00 00 00 00-00 00 00 00 60 00 50 60   |############`#P`|"

su VirtualAdress esta a 8 Bytes + 1 Dword del inicio del encabezado, osea a 0xC bytes "00 10 00 00" = 0x00001000, tambien nos hara falta conocer el PointerToRawData "00 04 00 00"= 0x00000400

El virtual adress sabemos que es relativo al ImageBase, pero solo nos interesa saber la distancia a la que está el EntryPoint del inicio de esa seccion. asi que ese Offset sera EntryPint - VirtualAdress = 0x130.

Ese es el offset desde el inicio de la seccion, en el archivo esa seccion inicia en el PointerToRawData, asi que, el entrypoint en el archivo está en PointerToRawData + 0x130 = 0x530
Vemos el archivo

"000000530  55 89 E5 83 EC 18 C7 04-24 01 00 00 00 FF 15 50   |U#######$######P|"
"000000540  A1 40 00 E8 D8 FE FF FF-90 8D B4 26 00 00 00 00   |#@#########&####|"
"000000550  55 89 E5 53 83 EC 14 8B-45 08 8B 00 8B 00 3D 91   |U##S####E#####=#|"
"000000560  00 00 C0 77 3B 3D 8D 00-00 C0 72 4B BB 01 00 00   |###w;=####rK####|"
"000000570  00 C7 44 24 04 00 00 00-00 C7 04 24 08 00 00 00   |##D$#######$####|"

y lo comparamos con el dump del Entrypoint en memoria (visto con el Ollydbg)

00401130  55 89 E5 83 EC 18 C7 04 24 01 00 00 00 FF 15 50  U.å.ì.Ç.$......P
00401140  A1 40 00 E8 D8 FE FF FF 90 8D B4 26 00 00 00 00  .@.èØþ....&....
00401150  55 89 E5 53 83 EC 14 8B 45 08 8B 00 8B 00 3D 91  U.åS.ì..E.....=.
00401160  00 00 C0 77 3B 3D 8D 00 00 C0 72 4B BB 01 00 00  ..Àw;=..ÀrK....
00401170  00 C7 44 24 04 00 00 00 00 C7 04 24 08 00 00 00  .ÇD$.....Ç.$....

Podemos ver que el code coincide exactamente, ya que estamos parados en el mismo lugar.

Enlaces:

ESTUDIO DE LOS ENCABEZADOS PE (parte 1) POR SICK TROEN
ESTUDIO DE LOS ENCABEZADOS PE (parte 2) POR SICK TROEN
Estudios sobre las cabeceras AE - 1 - Las Secciones
Estudios sobre las cabeceras AE-2 


Autor: Jep


viernes, 13 de diciembre de 2013

CrowdRE - Herramienta de Ingenieria Inversa


 CrowdRE - Herramienta de Ingenieria Inversa

Un nuevo proyecto llamado CrowdRE tiene como objetivo hacer más fácil la ingeniería inversa de aplicaciones complejas trabajando en colaboración con otros usuarios. Normalmente, el proceso de revertir software a partir de un complicado binario puede consumir mucho tiempo, CrowdRE ayudará a acelerar este proceso a través del trabajo en equipo.

CrowdRE, que actualmente se considera en una etapa "alfa" del desarrollo, está disponible como un plug-in para IDA Pro 6.3.120531. Con el plug-in, un grupo de desarrolladores puede aplicar ingeniería inversa en una o más funciones de un binario y subir los resultados a un servidor en la nube que hace un seguimiento de todo en una base de datos central. Esto permite a los clientes beneficiarse de un trabajo que otros desarrolladores ya han completado y compartir los progresos que hacen con el resto de la comunidad. La base de datos permite cualquier búsqueda y cada función puede tener diferentes "commits" concurrentes.




lunes, 9 de diciembre de 2013

Ingeniería inversa en aplicaciones Android II


Ingeniería inversa en aplicaciones Android II

Se puede definir que el código generado para las aplicaciones Android no es exactamente “Java Bytecode”, por eso los ejecutables para Android no contienen ficheros .class si no .dex que significa Dalvik Executable. De igual forma estos ficheros .dex se pueden “decompilar” y lo que veremos posteriormente será un lenguaje de bajo nivel (Dalvik Bytecode).

Aunque como tal con algunas aplicaciones específicas podemos ver en un lenguaje de alto nivel estos códigos (aplicaciones como Dex2Jar posteriormente decompilando con JD-GUI). Algunos enlaces interesantes sobre Dalvik y los OPCodes:

http://www.dalvikvm.com/
http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
http://developer.android.com/reference/dalvik/bytecode/Opcodes.html

Herramientas a utilizar y preparación del entorno

Para la preparación del entorno tendremos en cuenta todo lo necesario para empezar casi “desde cero”, los requisitos como siempre son saber la lógica de un lenguaje de programación (mejor si es Java) y bueno, lo demás se explicará poco a poco.

Como trabajaremos con Android necesitaremos usar la plataforma Java, ojo, no basta con el entorno de ejecución (JRE) necesitaremos todas las herramientas para desarrolladores (JDK – Java Development Kit) y un IDE cualquiera, puede ser Eclipse.
   
También necesitaremos las herramientas de desarrollo de Android o SDK, en el portal oficial hay una guía de instalación también debemos configurar un emulador o el AVD, que nos permitirá ejecutar una “máquina virtual” de Android en nuestros ordenadores (ver aquí).

Smali/Baksmali: se trata de un ensamblador/desemsamblador de ficheros apk que nos servirá para generar los fuentes en lenguaje de bajo nivel (aunque como tal no lo usaremos en esta guía). Se puede descargar de aquí

APKTool: Esta herramienta sustituirá el Smali/Baksmali ya que los integra dentro de su paquete. Como su nombre lo indica es un kit de herramientas para trabajar con ficheros APK. Con el generaremos el código de bajo nivel y luego recompilaremos.

Testsign.jar: Es una herramienta de terceros desarrollada para “firmar” o generar certificados aleatorios para indicarle al sistema que el fichero que hemos modificado se trata de una aplicación Android. Se descarga de aquí  (buscar el fichero testsign.jar).

zipalign: se trata de una herramienta que viene incluida en el SDK (PATH\android-sdk-windows\tools\zipalign.exe), que está hecha para preparar nuestro apk para el sistema Android, es decir, para que su rendimiento sea tal cual para dicho sistema ya que si no está “comprimida” con esta herramienta podría afectar el rendimiento de la misma en el dispositivo.

Instalar aplicaciones APK en el emulador de Android

El AVD Manager Tool es la herramienta que nos permite gestionar distintas “máquinas virtuales” de un sistema Android configuradas para un api específica (es decir, para distintas versiones del sistema operativo Android). Supondré que ya tienen instalado el Android SDK que viene con el SDK Manager pero aún falta configurar dicho “emulador” o máquina virtual. Por lo que aquí explicaré como crear una. Lo que necesitaremos es ir al directorio en donde hemos instalado el Android SDK, allí veremos el ícono del logo o mascota de Android llamado SDK Manager, desde allí podremos instalar diversas apis y claro, configurar nuestra nueva máquina virtual.

Allí hacemos clic en la opción “Virtual Devices” y si es la primera vez que lo usamos de seguro no habrá ninguna máquina virtual configurada, por lo que hacemos clic en el botón de la derecha que dice “New…”.


Luego tendremos que configurarla. Los parámetros son:

Name: El nombre de la máquina virtual. Esta no puede contener espacios.

Target: Es el api que utilizaremos para ejecutar la máquina virtual es decir, como si fuera la versión de Android. Recomiendo de momento la “API Level 8″ o Android 2.2 que es lo mismo

Lo demás ya se configura una vez hemos seleccionado el api, pero si queremos parámetros adicionales podemos explorar la herramienta. Por último le damos a Create AVD.

Listo ahora la seccionamos en el listado y en la parte derecha le damos “Start” y “Launch”. Se empezará a cargar la nueva unidad virtual lo que puede tardar varios minutos así que debemos ser pacientes en este paso. Cuando arranque veremos una pantalla como si fuese un móvil Android, con el sistema operativo completamente funcional. Pero como lo que vamos a hacer es un proceso de ingeniería inversa necesitaremos probar la aplicación, por lo que surge la necesidad de instalar aplicaciones APK en esta nueva unidad virtual.

Lo base para esto será saber que las aplicaciones Android se deben copiar a la carpeta “platform-tools” por ejemplo, la mía está en C:\Android SDK\android-sdk-windows\platform-tools, esto porque allí hay una herramienta llamada ADB (Android Debug Bridge) la cual es una herramienta que funciona por línea de comandos y que también sirve de conexión de nuestro móvil al ordenador y realizar tareas desde allí

En esta ocasión la utilizaremos para instalar APKs, por lo que una vez copiado el APK al directorio “Platform-tools” debemos ejecutar (con el emulador abierto):

adb install fichero.apk o para desinstalar adb uninstall fichero


Ahora si todo ha salido bien veremos la aplicación instalada en el emulador. Por cierto, algo importante a tener en cuenta es que el emulador o unidad virtual debe estar ejecutándose al momento de realizar este proceso de instalación del APK.

Instalar y usar ApkTool

Con el proceso anterior aprendimos a probar los APK. Bien, olvidemos por un rato eso y ahora iniciemos con el proceso de “ingeniería inversa” como tal. En esta ocasión usaremos APKTool, que básicamente sirve para cambiar algunas cosas de las aplicaciones Android pero además de ensamblar y desensamblarlas, que es nuestro uso principal en esta ocasión.

Para instalar APKTool deberemos bajar tanto el fichero apktool.jar (el principal) y si estamos en Windows debemos bajar las dependencias, veamos el proceso paso a paso mejor:

Descargar APKTool y dependencias (apktool1.4.1.tar.bz2 y apktool-install-windows-r04-brut1.tar.bz2).
Descomprimir todo en una sola carpeta.
Copiar estos ficheros al directorio principal de Windows, es decir, C:\Windows.

Listo ahora podemos ejecutar desde la consola de Windows el APKTool (Tecla Windows + R > cmd > Tecleamos apktool para ver todos sus parámetros). Para continuar con dicho proceso deberemos generar nuestro bytecode o fuentes en código de bajo nivel. Para “decompilar” nos ubicamos en la carpeta del APK y tecleamos en la consola:

apktool d fichero.apk nombre_nuevo_sin_extension

En la consola sería algo como esto:


Ya “desensamblamos” todo por lo que si revisamos la carpeta de nuestro APK hay un nuevo directorio el cual tiene el mismo nombre que le especificamos en la línea de comandos hace un momento. Lo que contiene son los ficheros fuente, el AndroidManifest.xml, entre otros recursos Lo que nos interesa es la carpeta smali, donde están los fuentes.

Si revisamos dicha carpeta y los ficheros generados, veremos unos archivos con extensión .smali, y lo primero que noto es un archivo llamado “main.smali“, pues sí, el código en lenguaje de bajo nivel.

En el tomo nº 1 de esta guía básica compartí un “crackme” o una aplicación Android que pedia un usuario y contraseña, no era más que una comparación de strings. Supongamos ahora que estamos revisando dicha aplicación y nos ponemos a buscar el mensaje de error que muestra cuando el usuario y contraseña son incorrectos.


Usaré ahora un editor de textos, de forma intuitiva uso el buscador para encontrar este mensaje y revisando un poco el código encuentro una comparación de strings, que no representan más que dicha comparación de usuario y contraseña. Pero estos string están declarados en un OPCode llamado const-string, por lo que ya sabemos la importancia de reconocerlos y que para buscar cadenas está el OPCode const-string.

Ahora, esto quiere decir que si cambio el const-string y la cadena encontrada en la comparación podré modificar los datos de acceso a la aplicación, si pongo por ejemplo “123” como contraseña podremos ver que pasa (sabiendo que la contraseña actual es “2014″ y el nombre de usuario como se puede ver es “ricadmin”):


Una vez modificado el siguiente paso será empaquetar nuevamente nuestro fichero APK, por lo que abrimos una consola de comandos, nos ubicamos en la carpeta donde estábamos trabajando y tecleamos:

apktool b carpeta_de_trabajo ficherocompilado.apk

Nótese que anteriormente yo había “desensamblado” y le llamé a mi carpeta “debug-crackmetest”. Tengan muy en cuenta los espacios que hay entre colocar el nombre de la carpeta y el del nuevo fichero compilado.

Y listo, con eso habremos generado un nuevo fichero APK con el nombre que le hemos acabado de poner (aparece en la misma carpeta de trabajo).


Firmando la aplicación o “generando certificados”

Es importante decirle a Android que nuestro APK en realidad si se trata de una aplicación para Android, pues estos tienen ciertas características que el sistema lee antes de su ejecución como tal.

Para esta tarea usaremos el “testsign.jar” una utilidad escrita para dicha finalidad y que podemos bajar en para empezar a usarla debemos colocarla en la misma carpeta de nuestro APK recién compilado y en la consola escribimos:

java -classpath testsign.jar testsign compilado-firmado.apk

Tener en cuenta que esto se le aplica a nuestro APK recién compilado. Si todo sale bien no saldrá nada por pantalla:


Generar un APK apto para Android

Listo, con el paso anterior “firmamos” la aplicación, ahora debemos optimizar el nuevo APK con una herramienta llamada “zipalign” que se encuentra en la carpeta “tools” de donde tenemos instalado el Android SDK.

El siguiente paso será copiar nuestro APK recién firmado a la carpeta “tools” que es donde se encuentra la aplicación “zipalign”, abrimos una consola, nos ubicamos en el directorio donde está la aplicación y nuestro fichero recién copiado y tecleamos:

zipalign -f 4 fichero-compilado.apk fichero-final.pak


Si todo sale bien tampoco saldrá nada. Ahora solo nos queda probar la aplicación y que los cambios se hayan realizado funcionen. Por lo que nuestro “fichero-final.apk” lo copiamos a “platform-tools” para instalarlo en nuestra unidad virtual que debe estar ejecutándose (por lo que en la consola escribimos “adb install fichero-final.apk”). Y el resultado es:



lunes, 2 de diciembre de 2013

Reversing Skype (bueno y malo)


Reversing Skype (bueno y malo)

Como se lo prometí a amigo torrescrack hoy publicaré unos de sus excelentes tutoriales: Reversing Skype (bueno y malo).

En este tutorial se probará y pondrá a prueba sus métodos de cifrado y se verá sus tecnicas anti-debug, también sirve para dar un ejemplo  conforme se vaya avanzando, veremos más adelante como se pueden aprovechar este tipo de técnicas y como podrían ser usadas por algunos desarrolladores de malware con el cual se podrían crear imitaciones de programas legítimos infectados con malware.

Analizando:

Empezamos, abrimos nuestro debugger y de primera instacia vemos que no es un Punto de un programa normal y si nos ponemos a ver algunas strings :


De principio, a primera vista me doy cuenta que el software está cifrado, estando en el entrypoint vemos instrucciones abajo, viendo un poco a detalle veremos que usa una rutina para desencriptar gran trozo del código que va a usar, esto es comun para evitar a los desensambladores y dejar al descubierto todo el codigo.


Aquí es muy sencillo explicar esta pequeña rutina:

CPU Disasm

Address   Hex dump          Command                                  Comments

01083DD9    B8 6893D3C2     MOV EAX,C2D39368 ; mueve al registro EAX un hardcoded que usara para la desencriptacion

01083DDE    B9 F0DF0601     MOV ECX,0106DFF0 ; mueve al registro ECX el offset desde donde empezara a desencriptar

01083DE3    8D99 E85D0100   LEA EBX,[ECX+15DE8] ; mueve al registro EBX el offset del entrypoint

01083DE9    8D91 40600100   LEA EDX,[ECX+16040]

LOOP:

01083DEF    3BCB            CMP ECX,EBX

01083DF1    75 03           JNE SHORT 01083DF6   ; desde estas tres instrucciones, compara el registro  ECX que este contiene el offset actual y de ser igual al entry point le suma 28, esto lo hace para no sobre escribir esta rutina

01083DF3    83C1 28         ADD ECX,28

CONTINUA:

01083DF6    3001            XOR BYTE PTR DS:[ECX],AL ; aplica un xor al contenido de ECX para empezar a desencriptar

01083DF8    C1C0 03         ROL EAX,3 ; rotation left 3

01083DFB    41              INC ECX ; incrementa ecx+ 1

01083DFC    3BCA            CMP ECX,EDX

01083DFE  ^ 75 EF           JNE SHORT 01083DEF ; compara si ha llegado al offset 01084030 de no ser asi pasa a la etiqueta loop

Habiendo analizado esto, como vimos al ir des-encriptado y encontrarse con el punto de entrada que es el principio de esta rutina, por lo tanto:



Si hacemos doble click desde el entrypoint podremos ver desde ahí   + 28 que será lo que se saltara y no sobreescribira esa parte asi quesi pongo un BreakPoint desde el  +29 y démosle RUN pra que temrine la rutina y asi termina de des-encriptar los datos , al parecer no es nada del otro mundo de hecho es muy sencillo, continuemos

Adelante podemos ver codigo limpio, pero al parecer este binario se va des-encriptando por fragmentos de codigo que va a utlizar.

Teniendo esta parte clara y para no alargarnos, les resumire, y es que veriamos mas adelante que hace algunos movimientos similares, con algunas rutinas similares a la anterior (con XOR)  desencriptara otro trozo pequeño de codigo que es una parte de la seccion .data unicamente


, pero la parte interesante es que este usa una rutina para crear una nueva tabla IAT (Import Address Table)



Primero obtiene el Handle de la libreria “kernel32.dll” con GetModuleHandle

sintaxis:

HMODULE WINAPI GetModuleHandle(

  _In_opt_  LPCTSTR lpModuleName

);

Despues pasa a sacar el nombre de algunas API’s que usaria mas adelante con GetProcAdress para obtener las direcciones (nueva IAT)

Algunas son:

GetCurrentProcess
ExitProcess
LoadLibrary
VirtualProtect
GetSystemInfo

Esta parte sobre la creacion de una nueva IAT (que es muy comun en malware), pueden apoyarse como ejemplo donde esta igual explicada, en mi analisis de malware en las paginas (17-18)

Encontraremos rutinas muy similares a estas con mucho codigo basura, pero en si, el metodo de cifrado para el codigo que se usa son simples operaciones XOR con algunos hardcoded, ahora bien se podria obtener un binario desempacado, pero seria un trabajo muy largo y tedioso ya que este conforme va ejecutandose tambien elimina algunas partes, esto es un metodo “anti-dump” por si a alguien se le ocurre hacerle un dump cuando esta corriendo en memoria, que en teoría estaria des-encriptado pero sin algunos trozos de codigo y por lo tanto se romperia la ejecucion al intentar correr el dumpeado, estas tecnicas son muy comunes en packers comerciales.

Ahora vayamos a ver y explicar muy rapido algunas de sus tecnicas anti-debug

1: Uso de SoftIce ( que segun recuerdo esta descontinuado)





Este realiza una busqueda de patrones y de cumplirse o encontrarlos muestra el mensaje “Skype is not compatible with system debuggers like SoftICE” , pero como en este caso esoty usando Olly Debugger no ahi problema.

2: IsDebuggerPresent , este es muy comun y a la vez muy sencillo burlar modificando algunos bytes 0,1 y aun asi tambien existen muchos plugins para burlarlo.

Bien pasemos a la parte que quiero probar y supongo para algunos podria ser la mas interesante y es intentar adjuntar codigo o una seccion de codigo y ver si es necesario parrchear chequeos CRC en caso de que este tenga algunos.

Usaremos la herramienta “ToPo”


Seleccionamos nuestro binario llenamos los datos y seleccionamos la casilla “Redirect Entrypoint” , esto no ahorra codigo ya que esto hara que empieze a ejecutar codigo desde nuestro injerto y solo basta con meter nuestro codigo y redirigir al lugar donde deberia iniciar o donde iniciaba antes de nuestro injerto y listo, ahora abrimos el debugger y veamos si funciono



Ahi etamos en el Entry Point y estamos en la nueva seccion, veamos las seciones y veremos que aparece nuestra nueva seccion llamada .topo, tenemos una zona vacia preparada para meter cuanto codigo queramos . 

Si ejecutamos Skype para probar si detecta este algun cambio:


Al parecer no detecto ningun cambio en el tamaño y demas, creo que basta un poco de imaginación como para poder tener un skype 100% funcional con un malware adicional injertado, como mencione anteriomente, y no bastaría mas que meterlo de nuevo al instalador y un poco de ingenieria social,  ubicarlo en un buen sitio en la red y estoy seguro que serian muchas las victimas.

Este escrito podría dar pie a una segunda parte donde se muestren a donde se realizan las conexiones “Login” y que tanto se puede jugar con el codigo de skype y sus conexiones ya que como vimos no es tan complejo ir descrifrando el codigo, por si alguien se anima a colaborar no dude en contactarme o si me queda algo de tiempo intentare trabajar en ello.

Alejandro Torres (TorresCrack)
@Torrescrack248


jueves, 28 de noviembre de 2013

IDAscope La Navaja Suiza Para Reversers

 
IDAscope La Navaja Suiza Para Reversers 
 
IDAscope es una extensión de IDAPro para facilitar la ingeniería inversa sobre malware.

Ofrece tres funcionalidades principales:

Function Inspection. Esta función implementa la capacidad de serie específica de etiqueta de llamada API. La API de seguimiento podría seleccionada y modificada en un archivo de configuración específico (config.json). IDAscope le alertará cuando ocurrirán patrones específicos de la API. La funcionalidad de control de función permite otra característica super llamado: bloque basic para colorear. Haber coloreado bloques en IDAPro es realmente útil para aprisa a entender cuál es el propósito de la pieace analizada del código. Lo que es común, ahora es colapsar bloques dando nombres de nivel altos. 

IDAscope ofrece la posibilidad de colorear automáticamente bloquea haciendo forma más eficiente en reconoce bloques inútiles. Código de función de conversión y cambio de nombre automático de funciones de envoltura son funcionalidades adicionales útiles así.

WinAPI Browsing. Obviamente para windows es increíblemente útil tener manual de referencia de la API de Windows. Aquí, usando la API de IDAscope la referencia está al lado de tus ojos para la búsqueda rápida e inteligente. La siguiente imagen muestra ambos:

Crypto Identification. Tal vez la funcionalidad más útil para todos los reversers, se utiliza para luchar con múltiples herramientas para averiguar qué algoritmo de cifrado se ha utilizado en el código.

IDAscope detecta el algoritmo usado basando su análisis en la relación de la aritmética / instrucciones de lógica para todas las instrucciones en un bloque básico.



martes, 8 de octubre de 2013

Ingeniería inversa en aplicaciones de Android


Ingeniería Inversa en Aplicaciones de Android

Formatos de archivo .DEX y .JAR

Como sabemos la mayoría de aplicaciones para Android están programadas en Java, los ejecutables de este sistema operativo tienen una extensión de tipo .APK, hasta aquí todo bien, pero debemos saber que esta extensión no es más que una variación de la extensión .JAR, por lo que si alguna vez hemos programado en Java sabemos que los .JAR funcionan como una especie de contenedor con varios archivos adentro, por lo que con cualquier compresor podemos extraer su contenido y ver una serie de archivos que son los que componen la aplicación.

Por lo tanto, si a una aplicación Android  (.APK) le extraemos los ficheros con un compresor cualquiera podremos ver todos los ficheros que componen la aplicación:


Uno de estos archivos es classes.dex que será uno de los más importantes pues este es el que contiene las clases y todo el código en general de la aplicación.
Herramientas necesarias:

Como mencionábamos más arriba, en Windows para poder ver el código en ensamblador de un archivo con extensión .exe usábamos el OllyDB, en esta ocasión haremos uso de una serie de herramientas que harán lo mismo y con esto podremos ver el código Java.

El proceso será sencillo, pasaremos el archivo .dex a .jar y luego descompilaremos este último. Para esto necesitaremos:

JD-GUI: Es una aplicación con interfaz gráfica muy intuitiva con la que podremos ver el código fuente de los archivos .class (básicamente .jar de Java). Con  esta herramienta podremos ver clases, métodos y otros componentes del programa.

Dex2Jar: Como su nombre nos lo dice, permite pasar de .dex a .jar, podemos usarlo individualmente o en conjunto con las herramientas que nombraremos aquí.

Eclipse: Es un IDE de desarrollo para varias plataformas y que funciona con java.

Configurar .Dex2Jar en Eclipse

Lo primero será configurar un nuevo proyecto, por lo que vamos a File > New > Java Proyect, le ponemos un nombre al proyecto y damos a finalizar.


Seleccionamos nuestro proyecto en explorador de paquetes, luego estando seleccionado vamos al menú Project > Properties > Java Build Path. Allí en la pestaña “Libraries” damos click en “Add external Jars”, allí en la ventana que sale ubicamos la carpeta de Dex2Jar y añadimos todos los .Jar de la carpeta a nuestro proyecto y seleccionamos Ok.


Ahora añadimos nuestro fichero .APK al proyecto, es decir, lo arrastramos hasta el explorador de  paquetes. 


Ahora hacemos click derecho en la carpeta de nuestro proyecto, seleccionamos Run As > Run Configurations y hacemos doble click en “Java application”, por lo que se añadirá una especie de página en la cual podemos pasar argumentos.

Aquí en la pestaña “Main“ añadimos el nombre de la clase Main de Dex2Jar, la cual es posible encontrarla haciendo click en el botón “search” de la misma pestaña, en mi caso tiene una estructura así: com.googlecode.dex2jar.v3.Main

Luego vamos a la pestaña arguments y en la primera caja de texto colocamos el nombre de nuestro fichero APK con la extensión:


Damos click en Aplicar (Apply) y luego en “Run”, para ver un resultado de este tipo:


Ahora actualizamos nuestro explorador de proyectos (Click derecho y luego le damos “Refresh”) y veremos un nuevo fichero con extensión .Jar, es el que necesitábamos.

Descompilar archivos .JAR

Con el proceso anterior obtuvimos un .JAR limpio, ahora debemos “decompilarlo” para así poder ver su código fuente, por lo que abrimos el JD-GUI y allí abrimos nuestro .JAR generado, lo primero que observo son los métodos y en especial uno que dice button1_click(), ya que de seguro allí habrá algo.

Pero bueno, ya a partir de aquí podemos seguir e investigar todo el código, obviamente los conocimientos de programación nunca están demás para entender los algoritmos implicados en la aplicación.