Análisis Packer 01 - Parte III
¿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:
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:
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.