Análisis Packer 01 - Parte IV

Tamaño de letra:

Las calls intermodular

Si nos detenemos en el OEP y vamos a la sección de código de "protegido.exe" y pulsamos botón derecho Search for -> All intermodular calls veremos cosas como esta:

...
0040876C   call 01C40004
00408774   call 01C60004
00408BEC   call 01CA0004
00408CA4   call 01CC0004
00408CC4   call 01CE0004
00408CD4   call 01BB0000
00408D44   call 01BB0000
00408D5C   call 01D20004
0040910C   call 01D40004
0040915C   call 01BB0000
004091CC   call 01D60004
004093CC   call 01D80004
00409404   call 01DA0004
00409448   call 004085A4        kernel32.GetTickCount
00409472   call 004085D4        kernel32.GlobalAlloc
00409478   call 004085F4        kernel32.GlobalLock
00409483   call 004085FC        kernel32.GlobalHandle
...

Un montón de calls redirigidas al packer, son todas llamadas a API y algunas ¡ni aparecen! ya que OllyDBG no las reconoce como tales, por ejemplo ya sabemos de antemano que en 00407ED4 hay una call 01BB0000 que corresponde a la API GetModuleHandleA. Sin embargo si vemos en All intermodular calls no la reconoce y no existe:

00407E8C   call 0040147C        kernel32.GetVersion
00407EF7   call 00407EDC        kernel32.LocalAlloc
00407F45   call 00407EEC        kernel32.TlsSetValue
00407F71   call 00407EE4        kernel32.TlsGetValue
00407F82   call 00407EE4        kernel32.TlsGetValue

Así que voy otra vez al código y des-analizo(Remove analisis from module) y vuelvo a hacer lo mismo y ya aparecen todas. Realmente, como ya comenté al principio, este packer es muy completo y requiere precisión y entender todo.

 

Reparar los saltos a la IAT

Reparar todas verás que lleva trabajo. La dificultad es descubrir qué dirección y a qué API corresponde y después hay que indicar la dirección de la API en la IAT. Hay varios caminos posibles pero quiero que entiendas lo que vamos a hacer, mira:

1.- Partimos de aquí:
00407ED4   .  E8 27817A01     call 01BB0000
00407ED9   .  0C 8B           or al,8B
00407EDB      C0              db C0
00407EDC   $- FF25 54D37600   jmp near dword ptr ds:[76D354] ; kernel32.LocalAlloc

2.- Ahora debemos encontrar a qué API corresponde, que en este caso ya sabemos que es GetModuleHandleA:
00407ED4   call GetModuleHandleA

3.- Ahora hay que fijarse en la IAT (que debe estar arreglada) para ver dónde está dicha API:
0076D2B0  7C80B731  kernel32.GetModuleHandleA

4.- Finalmente hay que poner el formato adecuado:
00407ED4    - FF25 B0D27600   jmp near dword ptr ds:[76D2B0] ; kernel32.GetModuleHandleA
00407EDA      8BC0            mov eax,eax
00407EDC   $- FF25 54D37600   jmp near dword ptr ds:[76D354] ; kernel32.LocalAlloc

 

1A.- Encontrar las call malas A

Para encontrar todas las llamadas "malas" (como call 01BB0000) algunos recurren a sacar la tabla completa de las calls desde OllyDBG: desde el desensamblado click derecho -> search for -> all commands y ahí pones call 01BB0000 o lo que sea. Después la exportas click derecho -> copy to clipboard -> whole table y así vas encontrando todas las calls malas. Es una buen opción. Otra opción es ver "All intermodular calls" como vimos al principio e ir analizando una a una. Hoy voy a analizar un poquito.

Después de analizar un buen rato descubrí que este packer en general, tiene dos redirecciones de calls malas: una que siempre van a 01BB0000 (calls malas A) y otra que van a otras direcciones del packer (calls malas B). Para empezar a conocerlo vamos a tratar las 01BB0000: vamos a poner un  HBP-W en los bytes de 00407ED4 que forman la call 01BB0000, reinicio OllyDBG y llego a este sitio:

00BB4CBA    45              inc ebp                      ; ebp = 0407ED4
00BB4CBB    8945 00         mov dword ptr ss:[ebp],eax   ; pone call 01BB0000
00BB4CBE    6A 0A           push 0A
00BB4CC0    E8 C3B7FEFF     call 00BA0488

Interesante: TODAS las call 01BB0000 pasan por ahí. Si ponemos un HBP en 0BB4CBA podemos obtener dónde se encuentran todas las call 01BB0000. Analizo ese código, lo traceo con F7 y pierdo tiempo pero no descubro forma de saber a qué API se corresponde, así que decido crear una tabla con todas las call 01BB0000 malas que existen. Para que sepas lo que voy a hacer, voy a crear una tabla del siguiente modo:

00407ED4 - 7C80B731

A la izquierda la dirección donde está y a la derecha (4 bytes más) la dirección de la API que corresponde. Como de momento no sabemos la dirección, le pondré ceros. Voy a crear un sencillo script que cree esta tabla:

//Definir variables
var tabla_calls
var oep
var obtiene_dato
var contador

//Inicializar variables
mov tabla_calls, 680000
mov oep, 01B903DE
mov obtiene_dato, 00BB4CBA

//Hardware breakpoints
bphws oep, "x"
bphws obtiene_dato, "x"

inicio:
run
eob para_en_hbp

para_en_hbp:
cmp eip, oep
jne sigue
mov [tabla_calls], #FFFFFFFF#
msg contador
ret

sigue:
mov [tabla_calls], ebp
add tabla_calls, 4
mov [tabla_calls], #00000000#
add tabla_calls, 4
inc contador

jmp inicio

He puesto la dirección 680000 por probar, no tiene más motivo y el FFFFFFFF para saber el final. Tras el script, me dice que ha encontrado 106 calls 01BB0000. Me voy a esta dirección y ya veo la tabla tan bonita:

00680000 00401314  protegid.00401314
00680004 00000000
00680008 0040131C  protegid.0040131C
0068000C 00000000
00680010 00401324  protegid.00401324
00680014 00000000
00680018 00401344  protegid.00401344
0068001C 00000000
00680020 0040135C  protegid.0040135C
00680024 00000000
00680028 00401374  protegid.00401374
0068002C 00000000
...
00680338 0040915C  protegid.0040915C
0068033C 00000000
00680340 00442F08  protegid.00442F08
00680344 00000000
00680348 00442F68  protegid.00442F68
0068034C 00000000
00680350 FFFFFFFF

 

2A.- Saber a qué API corresponden las calls malas A

Para saber a qué API corresponde cada call 01BB0000 de la tabla anterior, pues voy a fijarme en la que ya conocemos:

006800C0 00407ED4  protegid.00407ED4
006800C4 00000000

Que sabemos que va a GetModuleHandleA. Así que pongo el eip en 407ED4 y empiezo a tracear con F7 y F8 un largo rato y llego al siguiente sitio:

00BB3BE0    50                push eax
00BB3BE1    8D45 CC           lea eax,dword ptr ss:[ebp-34]
00BB3BE4    50                push eax
00BB3BE5    8D45 DC           lea eax,dword ptr ss:[ebp-24]
00BB3BE8    50                push eax
00BB3BE9    8B45 FC           mov eax,dword ptr ss:[ebp-4]
00BB3BEC    50                push eax                                     ; kernel32.GetModuleHandleA
00BB3BED    8B45 F8           mov eax,dword ptr ss:[ebp-8]
00BB3BF0    83C0 04           add eax,4
00BB3BF3    50                push eax
00BB3BF4    8B45 10           mov eax,dword ptr ss:[ebp+10]
00BB3BF7    50                push eax
00BB3BF8    8B45 F4           mov eax,dword ptr ss:[ebp-C]
00BB3BFB    50                push eax

¡Qué bien!, en 00BB3BEC nos da la dirección que buscamos.

 

3A.- Completando la tabla A

Ya podemos completar la tabla A, para ello he creado un simple script:

//Declara variables
var tabla_calls
var hbp_obtiene_api
var fin_tabla

//Inicializa variables
mov tabla_calls, 680000
mov hbp_obtiene_api, 00BB3BEC
mov fin_tabla, 680350

//Hardware breakpoint
bphws hbp_obtiene_api, "x"

//Código
//Ponemos eip en la dirección mala:
Inicio:
mov eip, [tabla_calls]
run

parado_hbp:
add tabla_calls, 4
mov [tabla_calls], eax
add tabla_calls, 4
cmp tabla_calls, fin_tabla
jne Inicio
ret

Ejecuto el script y miro la tabla:

00680000 00401314  protegid.00401314
00680004 7C810EE1  kernel32.GetFileType
00680008 0040131C  protegid.0040131C
0068000C 7C810B07  kernel32.GetFileSize
00680010 00401324  protegid.00401324
00680014 7C812FC9  kernel32.GetStdHandle
00680018 00401344  protegid.00401344
0068001C 7C83205E  kernel32.SetEndOfFile
00680020 0040135C  protegid.0040135C
00680024 7C810E17  kernel32.WriteFile
...
00680334 7E3AD312  user32.DestroyIcon
00680338 0040915C  protegid.0040915C
0068033C 7E3D07EA  user32.MessageBoxA
00680340 00442F08  protegid.00442F08
00680344 7E714C31  shell32.SHGetPathFromIDListA
00680348 00442F68  protegid.00442F68
0068034C 76362563  comdlg32.GetFileTitleA
00680350 FFFFFFFF

Realmente queda una sin reparar porque no sé cuál puede ser. El problema es que si la sigues nos lleva a un sólo ret. Algo curioso pero me dispongo a mostrarte cómo lo solucioné.

 

4A .- Reparando la última call mala tipo A

Como dije, nos queda una sóla call mala tipo A por reparar, es esta:

00680300  0043EAD4  protegid.0043EAD4
00680304  AAAAAAAA

Corresponde a la call que está en 0043EAD4 (ya verás qué curiosa). Vamos a intentar repararla. Desde el packer desde cero, pongo un hbp en 43EAD4 y ejecuto el programa. Se ejecuta pero sale una ventana del packer diciendo que el periodo de prueba ya ha pasado, esto ya lo estudié hace años pero no lo recuerdo exactamente. Al principio dije que simplemente borrando una clave de Windows funcionaría pero ahora no funciona. Es muy sencillo llegar al punto donde se comparan las fechas, así que evito que el programa se detenga por 30 días de uso (esto último no lo voy a explicar aquí porque requiere otro tute completo y no es la intención de éste, si no se hará muy largo y seguramente poco acertado), y empiezo a usar el programa pulsando todos los botones que veo y finalmente (después de probar un buen rato) salta al HBP:

0043EAD3   .  C3                   retn
0043EAD4   $  E8 2B159A01 call 01DE0004
0043EAD9      63                   db 63                                        ; CHAR 'c'
0043EADA      8BC0                 mov eax,eax

Algo me hace sospechar ese código tan extraño de 43EAD9. Umm!. Sigo a la call:

01DE0004    8D9C23 645288C3        lea ebx,dword ptr ds:[ebx+C3885264]
01DE000B    8D6424 04              lea esp,dword ptr ss:[esp+4]
01DE000F    64:EB 02               jmp short 01DE0014                           ; Superfluous prefix
01DE0012    CD20 8D9B9CAD          vxdjump AD9C9B8D
01DE0018    77 3C                  ja short 01DE0056
01DE001A    68 0000DD01            push 1DD0000
01DE001F    C3                     retn

Un poquito de código ofuscado que desofusco rápidamente con Code Doctor:

01DE0004    83C4 04                add esp,4
01DE0007  - E9 F4FFFEFF            jmp 01DD0000

Qué curioso. Quita el retorno de la call 01DE0004 y hace un jump directo a 1DD0000. Lo sigo:

//Código
01DD0000    C3                     retn

//Pila
0012F40C
00668381  RETURN to protegid.00668381 from protegid.0043EAD4

Asombrado veo que retorna a 668381:

00668381    8B55 F8                mov edx,dword ptr ss:[ebp-8]
00668384    8B45 FC                mov eax,dword ptr ss:[ebp-4]
00668387    E8 98F5E1FF            call 00487924 ; protegid.00487924
0066838C    8B45 F8                mov eax,dword ptr ss:[ebp-8]
0066838F    8348 04 0B             or dword ptr ds:[eax+4],0B
00668393    59                     pop ecx
00668394    59                     pop ecx
00668395    5D                     pop ebp
00668396    C3                     retn

La verdad que me quedo un poco perplejo. Por la lógica que estoy viendo, estudia tú también el sencillo código, debería modificar:

0043EAD4    E8 2B159A01            call 01DE0004
por:
0043EAD4    E8 2B159A01            retn

Poniendo el retn ya funciona correctamente todo, pero?? ¿No te preguntas por qué es un retn? Tendría que ser una API. Yo no me voy a quedar con las ganas así que voy a ver un Delphi 7 cualquiera a ver esa zona que es similar en todos y me encuentro con esto:

0042BE87   .  C3                   retn
0042BE88   $- FF25 80B74800        jmp near dword ptr ds:[48B780] ; comctl32.InitCommonControls
0042BE8E      8BC0                 mov eax,eax

Qué curioso, ¿verdad? Me voy a la API InitCommonControls:

58C365CF >  8BC0                   mov eax,eax
58C365D1    C3                     retn

Aquí está toda la investigación. Realmente la API es InitCommonControls que debemos reparar y ya están todas las call malas A reparadas:

0043EAD3   .  C3                   retn
0043EAD4    - FF25 D8DC7600 jmp near dword ptr ds:[76DCD8] ; comctl32.InitCommonControls
0043EADA      8BC0                 mov eax,eax

Genial, ya tengo la tabla A reparada. La guardo en un archivo .txt (binary copy). Ahora queda la tabla más difícil: la tabla B

 

1B.- Encontrar las call malas B

Al igual que hicimos con las A, voy a poner un hbp-w en una call mala y ver cuándo se escribe, por ejemplo aquí tenemos unas call malas tipo B:

00442EE8    E8 17D1A501       call 01EA0004
00442EED    F5                cmc
00442EEE    8BC0              mov eax,eax
00442EF0    E8 0FD1A701       call 01EC0004
00442EF5    4E                dec esi
00442EF6    8BC0              mov eax,eax
00442EF8    E8 07D1A901       call 01EE0004

Voy a poner el hbp en la de 00442EE8 y me encuentro con este código:

00BB4CA2    45                inc ebp                                      ; ebp = dirección donde está la call mala B
00BB4CA3    8945 00           mov dword ptr ss:[ebp],eax
00BB4CA6    8B4424 10         mov eax,dword ptr ss:[esp+10]
00BB4CAA    8B00              mov eax,dword ptr ds:[eax]
00BB4CAC    894424 14         mov dword ptr ss:[esp+14],eax
00BB4CB0    EB 0C             jmp short 00BB4CBE                           ; protegid.00BB4CBE
00BB4CB2    8B43 2C           mov eax,dword ptr ds:[ebx+2C]
00BB4CB5    2BC5              sub eax,ebp
00BB4CB7    83E8 05           sub eax,5
00BB4CBA    45                inc ebp                                      ; Observa que aquí paraba en las calls malas A
00BB4CBB    8945 00           mov dword ptr ss:[ebp],eax
00BB4CBE    6A 0A             push 0A

Simplemente ponemos un hbp-x en 0BB4CA2 y tenemos todas las calls malas tipo B. Como antes, voy a hacer una pequeña tabla con la dirección de la call mala B y su dirección API aunque este último dato todavía no lo conocemos y lo pondré a cero... ¿entiendes todo hasta ahora? Para iniciar la tabla voy a crear otro simple script:

//Definir variables
var tabla_calls
var oep
var obtiene_dato
var contador

//Inicializar variables
mov tabla_calls, 680000
mov oep, 01B903DE
mov obtiene_dato, 00BB4CA2

//Hardware breakpoints
bphws oep, "x"
bphws obtiene_dato, "x"

inicio:
run
eob para_en_hbp

para_en_hbp:
cmp eip, oep
jne sigue
mov [tabla_calls], #FFFFFFFF#
msg contador
ret

sigue:
mov [tabla_calls], ebp
add tabla_calls, 4
mov [tabla_calls], #00000000#
add tabla_calls, 4
inc contador

jmp inicio

Reinicio OllyDBG quito todos los BP, ejecuto el script y voy a 680000:

00680000 00408724  protegid.00408724
00680004 00000000
00680008 0040875C  protegid.0040875C
0068000C 00000000
00680010 0040876C  protegid.0040876C
00680014 00000000
00680018 00408774  protegid.00408774
0068001C 00000000
...
006800E8 00442F70  protegid.00442F70
006800EC 00000000
006800F0 00442F88  protegid.00442F88
006800F4 00000000
006800F8 00442F90  protegid.00442F90
006800FC 00000000
00680100 FFFFFFFF

Ahí está nuestra tabla, esta vez el script me dice que sólo hay 32 calls malas tipo B

 

2B.- Saber a qué API corresponden las calls malas B

Esto es lo más trabajoso de este artículo y requiere precisión. Intenté muchas cosas: buscar algún punto donde apareciera el salto a la API, buscar con Import REconstructor pasándole la IAT en 680000 pero éste último se equivocaba casi siempre y no escontré una rápida solución. Todas estas calls emulan el principio de las API, no usan código ofuscado ni máquina virtual y cuando saltan a alguna API no lo hacen al principio de la misma (ya que está emulada) sino a mitad. Para entenderlo vamos a seguir la primera función: 00408724 y pongo el eip ahí:

00408724    E8 DB787F01       call 01C00004

Sigo la call:

01C00004    8DBC27 20ABB95D   lea edi,dword ptr ds:[edi+5DB9AB20]
01C0000B    8D6424 04         lea esp,dword ptr ss:[esp+4]
01C0000F    81C7 E05446A2     add edi,A24654E0
01C00015    68 0000BF01       push 1BF0000
01C0001A    C3                retn

Sigo hasta el retn y voy como se puede ver hasta 1BF0000:

01BF0000    8BFF              mov edi,edi
01BF0002    55                push ebp
01BF0003    8BEC              mov ebp,esp
01BF0005    56                push esi
01BF0006    8B35 6C12807C     mov esi,dword ptr ds:[7C80126C]              ; ntdll.ZwUnmapViewOfSection
01BF000C    57                push edi
01BF000D    FF75 08           push dword ptr ss:[ebp+8]
01BF0010    6A FF             push -1
01BF0012    FFD6              call near esi
01BF0014    8BF8              mov edi,eax
01BF0016    85FF              test edi,edi
01BF0018    68 1CBA807C       push 7C80BA1C
01BF001D    C3                retn

En 01BF0000 está el comienzo de la API y como ves está emulada literalmente. Sigo naturalmente hasta 7C80BA1C y veo la call entera:

7C80BA04 >  8BFF              mov edi,edi                                  ; kernel32.UnmapViewOfFile
7C80BA06    55                push ebp
7C80BA07    8BEC              mov ebp,esp
7C80BA09    56                push esi
7C80BA0A    8B35 6C12807C     mov esi,dword ptr ds:[7C80126C]
7C80BA10    57                push edi
7C80BA11    FF75 08           push dword ptr ss:[ebp+8]
7C80BA14    6A FF             push -1
7C80BA16    FFD6              call near esi
7C80BA18    8BF8              mov edi,eax
7C80BA1A    85FF              test edi,edi
7C80BA1C    0F8C DE130300     jl 7C83CE00 ; kernel32.7C83CE00
7C80BA22    33C0              xor eax,eax
7C80BA24    40                inc eax
7C80BA25    5F                pop edi
7C80BA26    5E                pop esi
7C80BA27    5D                pop ebp
7C80BA28    C2 0400           retn 4

Como puedes ver hay que tener precisión y no fallar. En este ejemplo la API es UnmapViewOfFile. ¿Se podría haber hecho algo automático? Pues sí, pero puede resultar algo engorroso y en algunos casos no es sencillo, además como son sólo 32 llamadas pues aunque me lleve algo más de 30 minutos me divertiré un rato. (tuve que repasarlo porque me equivoqué en alguna...por eso dije precisión) y hay alguna difícil de buscar.

 

3B.- Completando la tabla B

Empiezo desde cero:

00680000 00408724  protegid.00408724
00680004 00000000
00680008 0040875C  protegid.0040875C
0068000C 00000000
...
006800F0 00442F88  protegid.00442F88
006800F4 00000000
006800F8 00442F90  protegid.00442F90
006800FC 00000000
00680100 FFFFFFFF

La primera:

00680000 00408724  protegid.00408724
00680004 7C80BA04 kernel32.UnmapViewOfFile
00680008 0040875C  protegid.0040875C
0068000C 00000000
...
006800F0 00442F88  protegid.00442F88
006800F4 00000000
006800F8 00442F90  protegid.00442F90
006800FC 00000000
00680100 FFFFFFFF

Y unos 35 minutos más tarde...

00680000 00408724  protegid.00408724
00680004 7C80BA04 kernel32.UnmapViewOfFile
00680008 0040875C  protegid.0040875C
0068000C 7C802530 kernel32.WaitForSingleObject
00680010 0040876C  protegid.0040876C
00680014 7C810E17 kernel32.WriteFile
00680018 00408774  protegid.00408774
...
006800F0 00442F88  protegid.00442F88
006800F4 7637C1C1 comdlg32.ChooseFontA
006800F8 00442F90  protegid.00442F90
006800FC 763846FD comdlg32.PrintDlgA
00680100 FFFFFFFF

Ya tengo todas las call malas B en la tabla. Lo que hago ahora es copy -> binary copy y pongo todas las call malas, tanto A como B juntas y las dejo en un archivo de .txt. Lo guardo como "todas_las_calls_malas.txt".

Ha costado trabajo pero ya están todas guardadas. En el siguiente artículo crearemos por fin el primer dumpeado.

Última actualización: Domingo, 10 Julio 2011
Escribir un comentario
Antes de publicar un comentario, usted debe aceptar nuestras condiciones de uso: Condiciones de uso. Debido al spam, todos los comentarios serán moderados. Normalmente se responde en unos minutos, refresca los comentarios para comprobarlo.



 
Visitas: 8492836