Análisis Packer 01 - Parte III

Tamaño de letra:

¿Qué es la INIT TABLE?

La INIT TABLE es una tabla de direcciones usada en programas compilados en Delphi que apuntan hacia subrutinas de código que son necesarias para la correcta ejecución del programa. Si observas con curiosidad el OEP de un programa compilado en Delphi podrás ver que eax es cargado con una dirección que apunta a esta INIT TABLE, mira:

00483DB0 > $  55              push ebp                            ; OEP - Delphi 7
00483DB1   .  8BEC            mov ebp,esp
00483DB3   .  83C4 F0         add esp,-10
00483DB6   .  53              push ebx
00483DB7   .  B8 703B4800     mov eax,483B70 ; eax = puntero a INIT TABLE
00483DBC   .  E8 9B26F8FF     call 0040645C ; Primera call

Ahora voy a entrar en la primera call para que veas qué ocurre con el valor de la INIT TABLE:

0040645C  /$  53              push ebx
0040645D  |.  8BD8            mov ebx,eax                        ; ebx = puntero a INIT TABLE
0040645F  |.  33C0            xor eax,eax
00406461  |.  A3 A0404800     mov dword ptr ds:[4840A0],eax
00406466  |.  6A 00           push 0
00406468  |.  E8 2BFFFFFF     call 00406398 ; <jmp.&kernel32.GetModuleHandleA>

Como puedes observar, si nos detenemos en 00406468 en la call a GetModuleHandleA, la dirección que apunta a la INIT TABLE se encuentra en ebx.

 

Cómo es la INIT TABLE

El primer valor indica el número de direcciones a llamar y si es la primera vez que lo ves, seguramente te parecerá un poco curioso y difícil de entender ya que si miras debajo de este párrafo el valor de llamadas es 47h (71dec) pero no se corresponde con el número total de funciones...¿por qué? Esto es así porque la INIT TABLE tiene llamadas de inicialización y finalización (de esto le doy las gracias a un tute que hizo Rain y es muy importante que lo entiendas). Para que lo veas, simplemente pon en un Delphi cualquiera parado en el OEP un BP on acces memory en toda la INIT TABLE y te darás cuenta cómo se accede a la misma y esto es muy importante para poder comprender después los script reparadores. En la figura siguiente he puesto el orden en que se rellenan (1ºI = 1º Inicialización, 1ºF = 1º Finalización). Observa también que el puntero a la  INIT TABLE que comenté arriba, comienza en 00483B70 y curiosamente el final de la INIT TABLE se encuentra justo antes del OEP (en Delphi 6 y 7, en 2010 ya no es así). También verás que la primera función ¡apunta a la segunda!. Para que veas cómo es en un programa compilado en Delphi 7 subo un poco más arriba del OEP y te muestro el inicio:

00483B70      47              db 47 ; Número de direcciones a llamar
00483B71      00              db 00
00483B72      00              db 00
00483B73      00              db 00
00483B74      783B4800        dd Delphi_7.00483B78 ; 1ºI, 1ºF - Inicio INIT TABLE
00483B78      D0644000        dd Delphi_7.004064D0 ; 2ºI
00483B7C      A0644000        dd Delphi_7.004064A0 ; 72ºF y última de Finalización
00483B80      004062C4        dd Delphi_7.004062C4 ; 3ºI
00483B84      00406270        dd Delphi_7.00406270 ; 71ºF
...

Y ahora el final:

...
00483D80      28234800        dd Delphi_7.00482328 ; 67ºI
00483D84      F8224800        dd Delphi_7.004822F8 ; 7ºF
00483D88      5C274800        dd Delphi_7.0048275C ; 68ºI
00483D8C      2C274800        dd Delphi_7.0048272C ; 6ºF
00483D90      94254800        dd Delphi_7.00482594 ; 69ºI
00483D94      64254800        dd Delphi_7.00482564 ; 5ºF
00483D98      A0394800        dd Delphi_7.004839A0 ; 70ºI
00483D9C      70394800        dd Delphi_7.00483970 ; 4ºF
00483DA0      403B4800        dd Delphi_7.00483B40 ; 71ºI
00483DA4      103B4800        dd Delphi_7.00483B10 ; 3ºF
00483DA8      00000000        dd          00000000 ; 72ºI y última de Inicialización
00483DAC      483B4800        dd Delphi_7.00483B48 ; 2ºF - Fin INIT TABLE
00483DB0 > $  55              push ebp                           ; OEP - Delphi 7

Curiosamente como dije antes, la primera dirección (Inicio Init Table) se redirige a la segunda dirección y eso se produce en todos los Delphi 6 y 7 que he visto. Observa cómo se accede a la INIT TABLE: si no contamos la dirección del inicio que es la primera que se accede tanto en inicialización como en finalización tenemos la cifra de 71dec llamadas que son 47h. ¿A que ahora ya entiendes por qué el valor tiene que ser 47h? Matemáticamente lo podríamos obtener así:

//Los números en formato hexadecimal:
Final de la Init Table: 00483DB0
Inicio de la Init Table: 00483B74
Número total de bytes: 00483DB0 - 00483B74 = 23C
Cada función se compone de 4 bytes, por lo tanto hay: 23C / 4 = 8F funciones
Eliminamos la primera función: 8F - 1 = 8E
Y como hay inicialización y finalización, tenemos: 8E / 2 = 47 llamadas

 

La INIT TABLE en el Packer

Como expliqué en el primer párrafo de este artículo, si nos detenemos en el "supuesto OEP" en la llamada a kernel32.GetModuleHandleA, verás que en el registro ebx tenemos el puntero a la INIT TABLE. ¿Probamos en el Packer a ver si tenemos suerte? Ya sabemos llegar a este punto porque lo expliqué en la parte I, así que paramos en la llamada a GetModuleHandleA:

00407ED4    E8 27817A01 call 01BB0000
00407ED9    0C 8B             or al,8B
00407EDB    90                nop
00407EDC  - FF25 54D37600     jmp near dword ptr ds:[76D354]            ; kernel32.LocalAlloc
00407EE2    8BC0              mov eax,eax
00407EE4  - FF25 50D37600     jmp near dword ptr ds:[76D350]            ; kernel32.TlsGetValue
00407EEA    8BC0              mov eax,eax
00407EEC  - FF25 4CD37600     jmp near dword ptr ds:[76D34C]            ; kernel32.TlsSetValue
00407EF2    8BC0              mov eax,eax
00407EF4    50                push eax
00407EF5    6A 40             push 40
00407EF7    E8 E0FFFFFF       call 00407EDC ; jmp to kernel32.LocalAlloc
00407EFC    C3                retn

Y observamos el registro ebx:

ebx 0074BB20     ; protegid.0074BB20

Y dumpeamos ese valor a ver:

0074BB20 00000001
0074BB24 0074C0C8  protegid.0074C0C8
0074BB28 45B6DC28
0074BB2C ED220635
0074BB30 7A478A19
0074BB34 EECCDAB8
...

El packer ha modificado toda la INIT TABLE como puedes ver, pero nos ha dejado el camino a seguir. Podemos ver que ha modificado el número de llamadas a 1 (uno) y seguidamente tiene una dirección que vamos a estudiar. Si dumpeamos la dirección 0074C0C8, veremos que el packer nos redirecciona a dos direcciones del código del packer:

0074C0C8 00BA99F8  protegid.00BA99F8     ;Llamadas de Inicialización
0074C0CC 00BA9A24  protegid.00BA9A24     ;Llamadas de Finalización
0074C0D0 01811DEB
0074C0D4 70F74536
0074C0D8 E21BA68C
0074C0DC 177126B1

Según lei en un magnífico tutorial de Rain, esas dos llamadas son las de inicialización y finalización de la  INIT TABLE como ya he explicado.

 

La redirección del Packer a 00BA99F8

Vamos a analizar este código al que nos redirecciona el packer, así que pongo un HBP en 00BA99F8 (te recuerdo que todavía estoy parado en el "supuesto OEP" =  00407ED4) pulso F9:

00BA99F8    53                push ebx
00BA99F9    B3 01             mov bl,1
00BA99FB    803D 9085BB00 00  cmp byte ptr ds:[BB8590],0
00BA9A02    74 1A             je short 00BA9A1E ; protegid.00BA9A1E
00BA9A04    A1 2889BB00       mov eax,dword ptr ds:[BB8928]
00BA9A09    8B00              mov eax,dword ptr ds:[eax]
00BA9A0B    50                push eax
00BA9A0C    A1 8885BB00       mov eax,dword ptr ds:[BB8588]
00BA9A11    50                push eax
00BA9A12    A1 8485BB00       mov eax,dword ptr ds:[BB8584]
00BA9A17    50                push eax
00BA9A18    FF15 8C85BB00     call near dword ptr ds:[BB858C] ; Entro en esta subrutina
00BA9A1E    8BC3              mov eax,ebx
00BA9A20    5B                pop ebx
00BA9A21    C3                retn ; Guardo esta dirección

Voy a guardar la dirección 00BA9A21 que es cuando terminará todo el proceso de inicialización. Este dato es importante después. Entro en la call de 00BA9A18:

02230000    56                push esi
02230001    C1CE 01           ror esi,1
02230004    BE FADC4000       mov esi,40DCFA
02230009    EB 02             jmp short 0223000D
0223000B    CD20 3EEB02CD     vxdjump CD02EB3E

Ahí hay bastante código ofuscado. Simplemente tracea con F7 o F8 y cuando te encuentres con una call pásala con F9 hasta que llegues a una call edx. Mostrar el código ofuscado no vale para nada, así que he usado el plugin CodeDoctor y he desofuscado el código solamente para que veas el camino a seguir:

Código desofuscado

La idea es pararse en la call edx, algo que ya mostró Rain. Simplemente parados en 002230050 observamos dónde nos manda el registro edx y lo hace ¡en el código del programa!. Si en la imagen anterior pulsas F7 y comparas el código que se ejecuta con un Delphi original, verás que estamos ejecutando código de la INIT TABLE. Mira:

022300F4    FFD2              call near edx                         ; protegid.0040800C

Pulso F9:

022300F4    FFD2              call near edx                         ; protegid.00407E00

Y así podríamos continuar con las llamadas de inicialización y después con las de finalización.

 

Reparar las llamadas de inicialización INIT TABLE

En este momento ya podríamos ir reparando la INIT TABLE una a una pero es un trabajo costoso, así que vamos a crear un sencillo script. Tienes que tener clara la idea y quiero que lo entiendas, para ello recuerda estos datos y verifícalos en este tutorial:

Número de llamadas       => 0074BB20
Inicio de la INIT TABLE  => 0074BB24 pero la primera dirección que hay que reparar es: 0074BB28. Lo entiendes, ¿no?
HBPx para obtener datos  => 022300F4
HBPx al acabar todo      => 00BA9A21

El script que voy a crear es el siguiente:

// Repara las llamadas de inicialización de la INIT TABLE

//Definición de variables
var call_near_edx
var final
var init_table
var contador

//Inicializo las variables
mov init_table, 0074BB28
mov call_near_edx, 022300F4
mov final, 00BA9A21
mov contador, 0

//Pongo 2 hardware breakpoints
bphws call_near_edx, "x"
bphws final, "x"

//Código
Inicio_codigo:
run
eob repara_init_table

repara_init_table:
cmp eip, final
jne sigue
msg contador
sub init_table, 8
msg init_table
ret
sigue:
mov [init_table], edx
add init_table, 8
inc contador
jmp Inicio_codigo

Reinicio OllyDBG, quito todos los HBP y ejecuto el script. Veo cómo se reparan todas las llamadas de inicialización y el resultado del contador = 187h. La última llamada reparada: 0074C758.

0074BB20 00000001
0074BB24 0074C0C8  protegid.0074C0C8
0074BB28 0040800C  protegid.0040800C
0074BB2C ED220635
0074BB30 00407E00  protegid.00407E00
0074BB34 EECCDAB8
0074BB38 00408188  protegid.00408188
0074BB3C 22FC44E6
0074BB40 00409788  protegid.00409788
0074BB44 72972E3C
0074BB48 004097C0  protegid.004097C0
..................
0074C750 0070A704  protegid.0070A704
0074C754 8773EE89
0074C758 00729F20  protegid.00729F20     ;Última llamada de Inicialización
0074C75C 23ACE93B
0074C760 D6435D69
0074C764 0C4B2E65

Si recuerdas el principio del tutorial, la última llamada de inicialización es un dword 0, por lo tanto, realmente la última llamada de Inicialización está en 0074C760 y el número de llamadas tiene que ser: 188 hex.

 

Reparar las llamadas de Finalización INIT TABLE

El proceso es similar. La primera función (recuerda que ahora va al revés) a reparar está en 0074C764, nos tiene que dar un total de 188 h llamadas, por lo que ya podemos conocer el final y lo que es lo mismo ¡saber dónde está el OEP original! que ha sido emulado por la máquina virtual:

//En formato hexadecimal
188 x 2 = 310 funciones
310 x 4 = C40 bytes
C40 + 0074BB28 = 0074C768 => OEP original

Antes de nada te recuerdo los datos de la INIT TABLE que nos dejó el packer:

0074C0C8 00BA99F8  protegid.00BA99F8     ;Llamadas de inicialización
0074C0CC
00BA9A24  protegid.00BA9A24     ;Llamadas de finalización
0074C0D0 01811DEB
0074C0D4 70F74536
0074C0D8 E21BA68C
0074C0DC 177126B1

No es necesario que explique todo el proceso de nuevo, es lo mismo que para las llamadas de inicialización. Pongo en 00BA9A24 el eip. Muestro una imagen para que se vea que los dos código están muy próximos:

Llamadas Inicialización y Finalización

Muestro el script que he realizado basándome en el anterior:

// Repara las llamadas de Finalización de la INIT TABLE

//Definición de variables

var call_near_edx
var final
var init_table
var contador

//Inicializo las variables
mov init_table, 0074C764
mov call_near_edx, 022300F4
mov final, 00BA9A53
mov contador, 0

//Pongo 2 hardware breakpoints
bphws call_near_edx, "x"
bphws final, "x"

//Código
Inicio_codigo:
run
eob repara_init_table

repara_init_table:
cmp eip, final
jne sigue
msg contador
add init_table, 8
msg init_table
ret
sigue:
mov [init_table], edx
sub init_table, 8
inc contador
jmp Inicio_codigo

En este caso se modifican las 188 hex funciones y la última llamada de Finalización en: 0074BB2C

Veamos cómo ha quedado el inicio:

0074BB20 00000001                            ; Número de llamadas: 188h
0074BB24 0074C0C8  protegid.0074C0C8         ; Debe redirigirse a la siguiente
0074BB28 0040800C  protegid.0040800C
0074BB2C 00407FDC  protegid.00407FDC         ; Última llamada de Finalización
0074BB30 00407E00  protegid.00407E00
0074BB34 00407DAC  protegid.00407DAC
...

Y ahora veamos el final:

...
0074C750
0070A704  protegid.0070A704
0074C754 0070A6D4  protegid.0070A6D4
0074C758 00729F20  protegid.00729F20
0074C75C 00729D58  protegid.00729D58
0074C760 D6435D69
0074C764 0074BAA4  protegid.0074BAA4         ; Fin de la INIT TABLE
0074C768 443C71E9                            ; OEP Original

Como ves, nos quedan tres datos por resolver. Ya tenemos todo, así que los reparo a mano como he puesto en los comentarios:

0074BB20 00000188                            ; Número de llamadas
0074BB24 0074BB28  protegid.0074BB28         ; Inicio INIT TABLE
0074BB28 0040800C  protegid.0040800C
0074BB2C 00407FDC  protegid.00407FDC
0074BB30 00407E00  protegid.00407E00
0074BB34 00407DAC  protegid.00407DAC
...
0074C750
0070A704  protegid.0070A704
0074C754 0070A6D4  protegid.0070A6D4
0074C758 00729F20  protegid.00729F20
0074C75C 00729D58  protegid.00729D58
0074C760 00000000
0074C764 0074BAA4  protegid.0074BAA4         ; Fin de la INIT TABLE
0074C768 443C71E9                            ; OEP Original

 

Guardando la INIT TABLE

Después de todo el trabajo que ha costado reparar la INIT TABLE, para no tener que volver a repetir todo el proceso voy a guardar los datos. Selecciono toda la INIT TABLE en el dump, botón derecho del ratón binary -> binary copy. Lo puedes guardar donde quieras, puedes usar tu editor hexadecimal favorito o guardarlo en el bloc de notas.

Última actualización: Domingo, 10 Julio 2011

No tiene privilegios para responder a los comentarios.


 
Visitas: 8562259