Manual Unpacking de Asprotect 1.0+
by LuTiN NoIR
Published by Tsehp, Aug 2000.

Traducido por Caos reptante

I_Introducción

Christal me ha pedido que rehiciera mi escrito sobre el volcado de PLEditor, que no ha sido ciertamente demasiado explícito. Pero esta vez me ha pedido que lo haga sobre otro programa, con el fin de verificar si funciona siempre de la misma manera. Así pues, este ensayo tendrá como finalidad obtener un volcado funcional de un programa protegido con Asprotect 1.0 (o superior). De hecho, hay muchas versiones de Asprotect y los dos programas presentados aquí no utilizan la misma versión (aunque es casi la misma). Aprovecharé para hablar un poco más del propio Asprotec. TeeJi me propuso su ayuda y os explicará una parte del funcionamiento de Asprotect (ver su ensayo). Vamos a tratar sobre tres sistemas de volcado: para empezar, el que utilicé sobre PLEditor aunque revisado, corregido y puesto a punto de acuerdo con Tsehp. Otro método (de hecho, dos para ser exacto) explicará un medio de reconstruir una tabla de importación.

Al final de este tutorial os indicaré otros trabajos que podrán ayudaros a comprendelo mejor (Sobre Asprotect, Cdilla y sobre el formato Pe)

 

II_Tools

SoftIce 4.01
IceDump 6.01
ProcDump 1.6.2 (o Procinfos)
Ida 4.04 (no imprescindible)
Winhex 9.4 (u otro)
Pe-rebuilder (u otro rebuilder)
 
 

III_1er método: PLEditor

En el informe sobre Aspack/Asprotect yo había propuesto un método para obtener un volcado funcional de PLEditor 3.0 build 15.06.2000. Había dicho que este método no funcionaba, aunque de hecho, sí lo hace. Voy a hacerlo de nuevo pero de otra manera. Intentaré explicarlo más claramente.
¿Que es Asprotect? Es una protección. Permite incluir en el programa ciertas limitaciones (tiempo, utilización...), anti-debuggers, código polimórfico o superposición del código, comprimir o encriptar el programa e impedir el volcado del mismo. Hay igualmente otras funciones de las cuales no nos ocuparemos aquí (especialmente se puede encriptar cierta parte del programa que se desencripta con una clave).
¿Cómo salir adelante? Es sencillo. Cuando se sabe, todo es fácil J ).
En primer lugar ¿qué es lo que impide el volcado del programa? Asprotect no crea una tabla de importación y una IAT (import address table) correcta cuando se efectúa la descompresión del programa en la memoria (una parte de este tutorial está dedicada a ello).
 

1) Recogida de información y volcado.

Lo primero que hay que hacer es mirar las secciones de PLEditor. Esto nos permitirá saber donde Asprotect debería normalmente poner la tabla de importación del programa. Para ello, utilizaremos ProcDump y en PE Editor / Sections veremos:

Name
       V. Size        V. Offset      Raw Size       R. Offset      Charact.

CODE       000B3000       00001000       00042800       00000400       C0000040
DATA       00002000       000B4000       00000C00       00042C00       C0000040
BSS        00002000       000B6000       00000000       00043800       C0000040
.idata     00003000       000B8000       00000200       00043800       C0000040
.tls       00001000       000BB000       00000000       00043A00       C0000040
.rdata     00001000       000BC000       00000200       00043A00       C0000040
.reloc     0000D000       000BD000       00000000       00043C00       C0000040
.rsrc      00036000       000CA000       0000A200       00043C00       C0000040
.data      00015000       00100000       00014800       0004DE00       C0000040
.data      00001000       00115000       00000000       00062600       C0000040

Encontramos una sección .idata y es aquí donde se encuentra una tabla de importación (para los exe con secciones .idata). Así pues, la import address table corresponde al virtual offset de la sección .idata (B80000). Sabemos entonces que su tabla de importación estará en 4B8000 (virtual offset + image base). Habrá que poner un breakpoint para cuando escriba en esta parte: bpm 4b8000 w.

Ahora que sabemos donde va a poner Asprotect la tabla de importación, podemos empezar a tracear el programa. El punto de comienzo del mismo está en 500001, ahí es donde se encuentra la primera parte de Asprotect (y donde hay una multicompresión). Pero esta versión 1.0 de Asprotect permite descomprimir estas partes en memoria con direcciones distintas. Será necesario evitar esto (thx sword). Para ello, habrá que poner un bpx gettickcount y se ejecuta el programa. Aparece el SoftIce y pulsamos F12 para volver al código del programa y debemos llegar normalmente aquí:

015F:00500223  C3                 RET
015F:00500224  25FFFF0100         AND       EAX,0001FFFF   <-- llegamos aquí
015F:00500229  EB04               JMP       0050022F
015F:0050022B  E8EB04E9EB         CALL      EC39071B
015F:00500230  FB                 STI
015F:00500231  E950EB04E8         JMP       E854ED86
015F:00500236  EB04               JMP       0050023C
015F:00500238  E9EBFBE9E8         JMP       E939FE28
015F:0050023D  0300               ADD       EAX,[EAX]
015F:0050023F  0000               ADD       [EAX],AL

Para evitar el cambio de la dirección es necesario poner EAX a 0 (o un valor similar), enseguida se llega al final de la primera zona en 500c28:

015F:00500C17  E8EB04E9EB         CALL      EC391107
015F:00500C1C  FB                 STI
015F:00500C1D  E95BEB04E8         JMP       E854F77D
015F:00500C22  EB04               JMP       00500C28
015F:00500C24  E9EBFBE9C3         JMP       C43A0814
015F:00500C28  C3                 RET                      <-- fin de la 1ª zona
015F:00500C29  B0DD               MOV       AL,DD
015F:00500C2B  54                 PUSH      ESP
015F:00500C2C  004765             ADD       [EDI+65],AL
015F:00500C2F  7454               JZ        00500C85

Y aquí se pasa a la segunda zona, que se ha descomprimido en la memoria. Se llega a:

015F:0056407C  90                  NOP
015F:0056407D  60                  PUSHAD                  <-- llegamos aquí
015F:0056407E  E844060000          CALL      005646C7
015F:00564083  EB44                JMP       005640C9
015F:00564085  0000                ADD       [EAX],AL
015F:00564087  0000                ADD       [EAX],AL
015F:00564089  0000                ADD       [EAX],AL
015F:0056408B  0000                ADD       [EAX],AL
015F:0056408D  87DB                XCHG      EBX,EBX
015F:0056408F  90                  NOP

Es necesario tracear hasta el fin de esta segunda zona. Para llegar más rápidamente se puede poner un bpm eip+5bb x. Se llega aquí:

015F:00564632  0385A0304400        ADD       EAX,[EBP+004430A0]
015F:00564638  5B                  POP       EBX           <-- llegamos aquí
015F:00564639  0BDB                OR        EBX,EBX
015F:0056463B  8985D92E4400        MOV       [EBP+00442ED9],EAX
015F:00564641  61                  POPAD
015F:00564642  7508                JNZ       0056464C
015F:00564644  B801000000          MOV       EAX,00000001
015F:00564649  C20C00              RET       000C
015F:0056464C  68B8665500          PUSH      005566B8
015F:00564651  C3                  RET                     <-- fin de la 2ª zona

Partiendo de 564651 se va hacia la tercera zona de memoria. Vamos allá:

015F:005566B5  8D4000              LEA       EAX,[EAX+00]
015F:005566B8  55                  PUSH      EBP           <-- aquí estamos
015F:005566B9  8BEC                MOV       EBP,ESP
015F:005566BB  83C4F4              ADD       ESP,-0C
015F:005566BE  E86DFBFEFF          CALL      00546230
015F:005566C3  0F858F09FFFF        JNZ       00547058
015F:005566C9  E8460EFFFF          CALL      00547514
015F:005566CE  E84537FFFF          CALL      00549E18
015F:005566D3  E81C55FFFF          CALL      0054BBF4
015F:005566D8  E8A3AAFFFF          CALL      00551180
015F:005566D8  E8A3AAFFFF          CALL      00551180
015F:005566DD  E87609FFFF          CALL      00547058     <-- entramos en el call
015F:005566E2  8BE5                MOV       ESP,EBP
015F:005566E4  5D                  POP       EBP
015F:005566E5  C20C00              RET       000C
015F:005566E8  0000                ADD       [EAX],AL
015F:005566EA  0000                ADD       [EAX],AL
015F:005566EC  0000                ADD       [EAX],AL
015F:005566EE  0000                ADD       [EAX],AL
015F:005566F0  0000                ADD       [EAX],AL


He aquí el principio de la tercera zona de memoria de Asprotect, siempre es necesario entrar en el último call para ocuparse del anti-debugger. Una vez que se ha entrado en él, se entra en el segundo que se encuentra:

015F:00555674  8B1574C75500        MOV       EDX,[0055C774]
015F:0055567A  8B45F8              MOV       EAX,[EBP-08]
015F:0055567D  E832E6FFFF          CALL      00553CB4
015F:00555682  8A15DCB65500        MOV       DL,[0055B6DC]
015F:00555688  8B45F8              MOV       EAX,[EBP-08]
015F:0055568B  E890E6FFFF          CALL      00553D20      <-- call anti-debugger
015F:00555690  8945F4              MOV       [EBP-0C],EAX
015F:00555693  837DF400            CMP       DWORD PTR [EBP-0C],00
015F:00555697  7428                JZ        005556C1
015F:00555699  8B45F4              MOV       EAX,[EBP-0C]

El call en 55568B es el anti-debugger, a continuación pone EAX en EBP-0C y mira si es 0. Aquí reemplazo el CALL por MOV EAX, 0 (se trata simplemente de poner EAX a 0 al volver del call). A continuación se pulsa F5 y deberá aparecer de nuevo el SoftIce. Ya he hablado al principio de poner un bpm 4b8000 w para saber cuando escribirá en la zona de la tabla de importación. Se llega aquí:

015F:00545709  782A                JS        00545735
015F:0054570B  F3A5                REPZ MOVSD              <-- se llega aquí
015F:0054570D  89C1                MOV       ECX,EAX
015F:0054570F  83E103              AND       ECX,03
015F:00545712  F3A4                REPZ MOVSB
015F:00545714  5F                  POP       EDI
015F:00545715  5E                  POP       ESI
015F:00545716  C3                  RET
015F:00545717  8D740EFC            LEA       ESI,[ECX+ESI-04]
015F:0054571B  8D7C0FFC            LEA       EDI,[ECX+EDI-04]

Entonces, copia lo que hay en ESI a EDI. Según esto, EDI= 4B8004 y ESI= C72824. Aquí copia ceros hacia la zona donde debería estar la tabla de importación. Pero, ¿cómo es que utiliza una zona en C70000? Hay que ver con el SoftIce lo que hay:

0167:00C72500 97 1F FB 4D B4 B2 65 77-1B 59 3D 6C ED E4 00 D0  ...M..ew.Y=l....
0167:00C72510 88 0B 00 01 69 6D 6D 33-32 2E 64 6C 6C 00 01 18  ....imm32.dll...
0167:00C72520 8C 90 8F 6D 2A 4B F0 E9-95 CE F8 38 EC 02 E5 C9  ...m*K.....8....
0167:00C72530 CB 82 44 A9 C1 4D E5 EA-01 17 8C 90 8F 6D 2A 4B  ..D..M.......m*K

Es extraño encontrar referencias a DLLs. Hay que tenerlo en cuenta. Se puede quitar el breakpoint de escritura, ya que va a seguir escribiendo pero no nos va a llevar a ninguna parte. Hay que seguir traceando (F10) para llegar al punto en que Asprotect cede el turno al programa de descompresión en memoria. Normalmente, se llega aquí:

015F:00555DE9  8B4508              MOV       EAX,[EBP+08]
015F:00555DEC  8B10                MOV       EDX,[EAX]
015F:00555DEE  8B4508              MOV       EAX,[EBP+08]
015F:00555DF1  8B401C              MOV       EAX,[EAX+1C]
015F:00555DF4  E887F6FFFF          CALL      00555480      <-- se llega aquí
015F:00555DF9  5F                  POP       EDI
015F:00555DFA  5E                  POP       ESI
015F:00555DFB  5B                  POP       EBX
015F:00555DFC  5D                  POP       EBP
015F:00555DFD  C20400              RET       0004

Bien, se reconoce fácilmente este call por el hecho de que recupera el valor de los registros, inmediatamente después. Es necesario entrar, si no, el programa se lanza. Enseguida se llega aquí:

015F:005554A4  B854775400          MOV       EAX,00547754
015F:005554A9  E8AE3CFFFF          CALL      0054915C
015F:005554AE  E86D0AFFFF          CALL      00545F20      <-- se evita este call
015F:005554B3  33C0                XOR       EAX,EAX
015F:005554B5  5A                  POP       EDX
015F:005554B6  59                  POP       ECX
015F:005554B7  59                  POP       ECX
015F:005554B8  648910              MOV       FS:[EAX],EDX
015F:005554BB  EB0A                JMP       005554C7
015F:005554BD  E93208FFFF          JMP       00545CF4

Aquí es necesario evitar el paso por el segundo call, de lo contrario se lanza el programa y lo perdemos. Hay que nopear o bien hacer r eip eip+5 cuando se está encima. Un poco más lejos encontramos lo mismo:

015F:005554D9  B854775400          MOV       EAX,00547754
015F:005554DE  E8793CFFFF          CALL      0054915C
015F:005554E3  E8380AFFFF          CALL      00545F20      <-- se evita este call
015F:005554E8  33C0                XOR       EAX,EAX
015F:005554EA  5A                  POP       EDX
015F:005554EB  59                  POP       ECX
015F:005554EC  59                  POP       ECX
015F:005554ED  648910              MOV       FS:[EAX],EDX
015F:005554F0  EB0A                JMP       005554FC
015F:005554F2  E9FD07FFFF          JMP       00545CF4

Se hace lo mismo, r eip eip+5 o NOPs. Queda aún un doble call, se evita el segundo:

015F:00555534  648920              MOV       FS:[EAX],ESP
015F:00555537  33C9                XOR       ECX,ECX
015F:00555539  B201                MOV       DL,01
015F:0055553B  B854775400          MOV       EAX,00547754
015F:00555540  E8173CFFFF          CALL      0054915C
015F:00555545  E8D609FFFF          CALL      00545F20      <-- se evita
015F:0055554A  33C0                XOR       EAX,EAX
015F:0055554C  5A                  POP       EDX
015F:0055554D  59                  POP       ECX
015F:0055554E  59                  POP       ECX

Una vez más se hace r eip eip+5 cuando se está sobre el call. Continuamos traceando hasta llegar aquí:

015F:00555590  EBE8                JMP       0055557A
015F:00555592  61                  POPAD                   <-- EAX a EIP
015F:00555593  EB01                JMP       00555596
015F:00555595  E850EB02E9          CALL      E95840EA
015F:0055559A  17                  POP       SS
015F:0055559B  E802000000          CALL      005555A2
015F:005555A0  E91758803D          JMP       3DD5ADBC
015F:005555A5  88C7                MOV       BH,AL
015F:005555A7  55                  PUSH      EBP
015F:005555A8  0000                ADD       [EAX],AL

Aquí se encuentra el POPAD característico de un final de descompresión. En este momento EAX contiene el puntero EIP del programa descomprimido en memoria, un poco más adelante un RET nos envía allí. Pero antes de hacer nada veamos el estado de la zona 4B8000 donde debería estar la tabla de importación:

015F:004B8180 F4 49 C7 00 00 4A C7 00-0C 4A C7 00 18 4A C7 00  .I...J...J...J..
015F:004B8190 24 4A C7 00 30 4A C7 00-3C 4A C7 00 48 4A C7 00  $J..0J..<J..HJ..
015F:004B81A0 54 4A C7 00 60 4A C7 00-6C 4A C7 00 78 4A C7 00  TJ..`J..lJ..xJ..
015F:004B81B0 84 4A C7 00 90 4A C7 00-9C 4A C7 00 A8 4A C7 00  .J...J...J...J..

Aquí se encuentran muchas referencias a la zona C7xxxx. Utiliza el mismo principio (aunque de modo distinto) que Cdilla. Para llamar a una API hace un call [4B8180], que llama a lo que hay en C749F4 y allí a un código del tipo JMP API. De hecho, es más complejo (ver la parte del tutorial que habla de ello), desencripta ciertas cosas para tener la API. Pero lo hace cuando el programa tiene necesidad de esta API. Así pues, la idea es de añadir esta parte al programa, el cual la vuelca al llegar a la zona C7xxxx. Para hacer esto, es necesario saber donde comienza esta zona y donde termina. Se observa con el SoftIce y se ve (entre zonas de memoria no válidas) que comienza en C70000 y termina en C78000. Por consiguiente, se puede volcar esta zona utilizando el IceDump (extensión del SoftIce) y haciendo: pagein d c70000 8000 c:\Import.dat. Se obtendrá así un volcado de esta zona en el fichero import.dat.
Sin embargo, no hay que precipitarse y volcar el programa directamente. En efecto, si se analiza esto más de cerca y se deja desarrollar el programa, se puede ver que cuando llama a la zona C7xxxx, hace asimismo llamada a otra zona. He aquí un ejemplo:

:00C74C32  0000                ADD       [EAX],AL
:00C74C34  1E                  PUSH      DS
:00C74C35  0000                ADD       [EAX],AL
:00C74C37  006842              ADD       [EAX+42],CH
:00C74C38  68424CC700          PUSH      00C74C42
:00C74C3D  E84A048EFF          CALL      0055508C    <-- llama a una zona en memoria 55508C
:00C74C42  7A2C                JP        00C74C70
:00C74C44  C7009A2CC700        MOV       DWORD PTR [EAX],00C72C9A
:00C74C4A  C7002A560000        MOV       DWORD PTR [EAX],0000562A
:00C74C50  1E                  PUSH

Se puede ver que utiliza una zona de memoria situada en 55508C. Ésta corresponde al lugar en el que Asprotect se descomprime. Por consiguiente, para que el programa funcione, la zona de Asprotect debe también estar presente. Es necesario pues, volcarla también. En este momento se puede ver toda la zona entre zonas de memoria no válidas, pero aquí cierta parte aún no es válida, por lo que hay que observar con cuidado. Esta zona se extiende entre 520000 y 567000, es necesario volcarla como sigue: pagein d 520000 47000 c:\asprotect.dat. Ahora tenemos la zona de Asprotect en el fichero asprotect.dat.
Se puede ahora pasar al volcado del programa propiamente dicho. A la altura del POPAD se hace a eip para modificar la instrucción y se pone jmp eip para que el programa entre en un bucle sin fin. Inmediatamente pulsamos F5 para salir del programa y sólo resta hacer un volcado completo sobre el objetivo que está en memoria.
 

2) Reconstrucción del volcado

Ahora con todo esto hay que reconstruir un ejecutable válido. Anteriormente, yo utilizaba un método poco detallado y muy complicado. Un día TeeJi me preguntó por qué lo hacía de aquel modo en vez de utilizar las secciones. No lo dudé un instante, lo hice así y fue mucho más simple.

Bien, antes que nada, hay que hacer una pequeña manipulación. Hay que copiar la sección de Asprotec (la que corresponde a la que está ya en el fichero) del programa de origen (el protegido) a nuestro volcado. Debe hacerse así porque la tabla de importación (la que utiliza Asprotect) está estropeada y no se puede utilizar el programa volcado. Para saber donde está el principio de esta zona, se busca con un editor hexadecimal la cadena "90 60 E8 01 00 00 00 90 5D" que siempre es el inicio de la zona de Asprotect. Se copia desde aquí hasta el final del programa, y se la coloca en el lugar de la del programa volcado (se busca la misma cadena en éste para encontrar el inicio).

A continuación hay que unir al programa los otros dos volcados que se han hecho. Sin embargo, hay que ponerlos en las zonas de memoria donde se encontraban. Para esto, se debe utilizar ProcDump, ir a PE Editor y se abre nuestro programa volcado. A continuación, en Sections se hace Add section y se añade una sección de las siguientes características

Name: .aspr  V.Size: 00047000  V.Offset: 00120000  Raw Size: 00047000  R.Offset: 00115400  Charac.: E0000020

Esta es la sección para el fichero asprotect.dat. Se le da un virtual offset de 120000 (más la image base, 520000) para que se encuentre en un buen lugar de la memoria. El tamaño del volcado es de 47000h, así pues se pone 47000, el real offset lo da ProcDump y las características se pueden cambiar, aunque no es obligatorio. A continuación se añade la sección para el fichero import.dat:

Name: .import  V.Size: 00008000  V.Offset: 00870000  Raw Size: 00008000  R.Offset: 0015C400  Charac.:E0000020

La misma explicación que para el anterior. No hay que olvidar cambiar el tamaño de la imagen. Yo no me preocupo de ello, porque utilizo Procinfo (de TeeJi) que lo hace en mi lugar. Pero vosotros debéis hacerlo tomando los tamaños de las secciones añadidas y el tamaño de la imagen.

Ahora que se ha hecho esto, se ha de tomar el editor hexadecimal, se abre el fichero asprotect.dat y se copia todo. Se inserta esto al final del volcado de PLEditor. A continuación se hace lo mismo con el fichero import.dat. Se salva el volcado y se cambia el valor de EIP (el contenido en EAX a la altura del POPAD), aquí se hace 4B327C-400000 (image base)= B327C. Así pues, el nuevo valor de EIP será B327C. Se cambia con el editor hexadecimal o con el ProcDump.

Ahora se coge el volcado y se ejecuta. Tranquilos J . Resulta que nuestro volcado es ejecutable. Ya no nos queda más que crackear el volcado…

IV_2ºmétodo: Advanced mail list verify 2.0

No habiendo tenido éxito utilizando el método precedente sobre AMV, os voy a presentar un método que Tsehp ha utilizado sobre Commview. Tened en cuenta que este método no funciona con PLEditor (Tsehp lo ha cambiado: ver parte 4). Este método es utilizado especialmente con Cdilla: es lo que se llama un "call fixer". Permite poner las direcciones de las APIs (y de las funciones) en la tabla de importación (en la IAT). Así, no se tendrá siempre una IAT y una tabla de importación correcta en el verdadero sentido del término, pero no hará falta volcar la parte de la tabla de importación y la parte de Asprotect.

Veamos como se presentan las secciones del programa:

Name       V. Size        V. Offset      Raw Size       R. Offset      Charact.

.text      00028000       00001000       00012200       00001000       C0000040
.rdata
     00003000       00029000       00000C00       00013200       C0000040
.data
      0000D000       0002C000       00001400       00013E00       C0000040
.rsrc      00029000       00039000       00019000       00015200       C0000040
.data      00015000       00062000       00014E00       0002E200       C0000040
.data      00001000       00077000       00000000       00043000       C0000040

Aquí no hay sección .idata, pero sí hay una sección .rdata en la cual se hubiera encontrado la tabla de importación del programa descomprimido (si existiera). Hay que poner en ella un breakpoint dirigido al inicio de la sección (cuando va a escribir): bpm 429000 w. A continuación hay que tracear como antes (no lo explico de nuevo, ya que el proceso es idéntico) hasta el principio de la zona tres de Asprotect. Se procede de la misma manera con el debugger, y el SoftIce actúa igualmente en el breakpoint de escritura al principio de la zona .rdata. Nos encontramos esta vez que toda la parte de la import table está comprendida entre DE0000 y DE4000. Retiramos el breakpoint y traceamos hasta el final (igual que antes, excepto los dobles calls que no están). Se pasa a continuación al inicio del programa:

015F:00421BFB  C3                  RET
015F:00421BFC  55                  PUSH      EBP           <-- inicio del programa
015F:00421BFD  8BEC                MOV       EBP,ESP
015F:00421BFF  6AFF                PUSH      FF
015F:00421C01  6860944200          PUSH      00429460
015F:00421C06  68985B4200          PUSH      00425B98
015F:00421C0B  64A100000000        MOV       EAX,FS:[00000000]
015F:00421C11  50                  PUSH      EAX
015F:00421C12  64892500000000      MOV       FS:[00000000],ESP
015F:00421C19  83EC58              SUB       ESP,58

De paso, aprovechamos para tomar nota de que el EIP es de 421BFC. Continuamos traceando hasta llegar aquí:

015F:00421C19  83EC58              SUB       ESP,58
015F:00421C1C  53                  PUSH      EBX
015F:00421C1D  56                  PUSH      ESI
015F:00421C1E  57                  PUSH      EDI
015F:00421C1F  8965E8              MOV       [EBP-18],ESP
015F:00421C22  FF1574914200        CALL      [00429174]    <-- interesante
015F:00421C28  33D2                XOR       EDX,EDX
015F:00421C2A  8AD4                MOV       DL,AH
015F:00421C2C  8915B86B4300        MOV       [00436BB8],EDX
015F:00421C32  8BC8                MOV       ECX,EAX

Aquí está lo que había explicado antes para PLEditor. Llama a lo que se encuentra normalmente en la tabla de importación (aquí en 429174 al inicio de la sección .rdata) y en 429174, se encuentra DE2464. Así pues, llama a lo que hay en DE2464:

015F:00DE2464  E993091BBF          JMP       KERNEL32!GetVersion
015F:00DE2469  1B00                SBB       EAX,[EAX]
015F:00DE246B  000E                ADD       [ESI],CL
015F:00DE246D  0000                ADD       [EAX],AL
015F:00DE246F  00E9                ADD       CL,CH
015F:00DE2471  37                  AAA
015F:00DE2472  A11ABF1B00          MOV       EAX,[001BBF1A]

Y en DE2464, hay este salto a una API. He aquí como reencuentra sus funciones. Pero esto no es todo, ya he dicho que el programa se desencripta sobre la marcha. Continuamos traceando y llegamos a un lugar interesante (hay que buscar siempre un CALL [429xxx] ):

015F:0040B8DB  51                  PUSH      ECX
015F:0040B8DC  52                  PUSH      EDX
015F:0040B8DD  FF1508904200        CALL      [00429008]    <-- un call interesante
015F:0040B8E3  85C0                TEST      EAX,EAX
015F:0040B8E5  7408                JZ        0040B8EF
015F:0040B8E7  8B44241C            MOV       EAX,[ESP+1C]
015F:0040B8EB  33F6                XOR       ESI,ESI
015F:0040B8ED  8907                MOV       [EDI],EAX

¿Por qué este CALL [00429xxx] es más interesante que los otros? Veamos lo que hace a continuación:

015F:00DE2A14  681E2ADE00          PUSH      00DE2A1E  <-- se llega aquí si se entra en el                                                            call
015F:00DE2A19  E816246DFF          CALL      004B4E34  <-- llamada a una parte de Asprotect

015F:00DE2A1E  851F                TEST      [EDI],EBX
015F:00DE2A20  DE00                FIADD     WORD PTR [EAX]
015F:00DE2A22  AE                  SCASB
015F:00DE2A23  1F                  POP       DS
015F:00DE2A24  DE00                FIADD     WORD PTR [EAX]
015F:00DE2A26  DE00                FIADD     WORD PTR [EAX]

Se va a la misma zona de memoria que antes, pero no hace el JMP API, sino CALL 004B4E34. Es una parte que está en la zona de Asprotect. Aquí se encuentra el call que sirve para desencriptar las APIs. Es muy importante, hay que tomar nota de este punto.

Ahora se vuelve a empezar, se evita el cambio de dirección, se pasa la descompresión y se llega a la tercera zona; se elude el anti-debugger, sin olvidar colocar nuestro bpm 429000 w. Normalmente, el SoftIce deberá detenerse sobre el REPZ MOVSB de antes, allí donde copiaba ceros. Pero esta vez pulsaremos F5 para continuar. El SoftIce debería detenerse aquí:

015F:004B5138  87FE                XCHG      EDI,ESI
015F:004B513A  AC                  LODSB
015F:004B513B  08C0                OR        AL,AL
015F:004B513D  74E4                JZ        004B5123
015F:004B513F  4E                  DEC       ESI
015F:004B5140  56                  PUSH      ESI
015F:004B5141  53                  PUSH      EBX
015F:004B5142  80F802              CMP       AL,02
015F:004B5145  7407                JZ        004B514E
015F:004B5147  0FB64E01            MOVZX     ECX,BYTE PTR [ESI+01]
015F:004B514B  41                  INC       ECX
015F:004B514C  EB05                JMP       004B5153
015F:004B514E  B904000000          MOV       ECX,00000004
015F:004B5153  41                  INC       ECX
015F:004B5154  01CE                ADD       ESI,ECX
015F:004B5156  E8B5FDFFFF          CALL      004B4F10      <-- busca las APIs
015F:004B515B  AB                  STOSD
015F:004B515C  EBDC                JMP       004B513A      <-- vuelve al inicio
015F:004B515E  61                  POPAD

Y aquí entra en un bucle. Si se mira, se puede ver en EDI valores del tipo 429004. A grandes rasgos, esta rutina se utiliza para escribir en la tabla de importación normal (en 429000) las referencias a la tabla de importación en DE0000. Pues aquí la idea es que el programa no escriba en 429000 las referencias a la zona en DE0000, sino que ponga directamente las direcciones de las APIs que serán utilizadas. Es por esta razón que antes hemos buscado el CALL 4B4E34 que sirve para desencriptar las direcciones de las APIs.
Es pues aquí, donde va a ponerse el call fixer. Tened en cuenta que si os da pereza, Tsehp propone encontrar el CALL 4B4E34 haciendo s 0 l ffffffff '55 8b ec c4 f8 fe ff'. Este bucle, se puede encontrar con s 0 l ffffffff 'ac 08 c0 74 e4'. Pero es conveniente saber de donde proceden estos datos.
Ahora es necesario escribir un call fixer antes de que se ejecute este bucle. Es aquí donde hay que darle las gracias a Tsehp J . En efecto él propuso este método sobre el programa Commview. He aquí el principio:

015F:004B514E  B904000000          MOV       ECX,00000004
015F:004B5153  41                  INC       ECX
015F:004B5154  01CE                ADD       ESI,ECX
015F:004B5156  E8B5FDFFFF          CALL      004B4F10      <-- hay que desviarse aquí
015F:004B515B  AB                  STOSD
015F:004B515C  EBDC                JMP       004B513A      <-- salto al inicio del bucle

Lo que hay que hacer es desviarse a la altura del call y buscar un espacio libre en memoria (con ceros) para desviar el call y añadir nuestro código. Lo he desviado a 4B6500:

015F:004B5147  0FB64E01            MOVZX     ECX,BYTE PTR [ESI+01]
015F:004B514B  41                  INC       ECX
015F:004B514C  EB05                JMP       004B5153
015F:004B514E  B904000000          MOV       ECX,00000004
015F:004B5153  41                  INC       ECX
015F:004B5154  01CE                ADD       ESI,ECX
015F:004B5156  E9A5130000          JMP       004B6500       <-- nos desviamos
015F:004B515B  AB                  STOSD
015F:004B515C  EBDC                JMP       004B513A
015F:004B515E  61                  POPAD

Y en 4B6500 se coloca el call fixer que Tsehp nos propone (thx man J ). He aquí lo que él propone como call fixer:

190000 call 18a2f4               ; we use the legal call
190005 cmp ecx,40000000          ; ecx contains an api address ?
19000b jle 19001c                ; if not
19000d add ecx,eax               ; if yes convert the address***case 2
19000f add ecx,5                 ; to the absolute api address for a normal import table
190012 mov dword ptr [edi],ecx   ; put it in import table, pointed by edi
190014 add edi,4                 ; updates edi to the next import
190017 jmp 18a51e                ; back to normal
19001c cmp ecx,0                 ; is it case 4 ?
19001f jz 19002e
190021 cmp eax,40000000          ; eax contains an api address ?
190026 jle 19003c                ; if not go to 19003c
190028 stosd                     ; we're in case 1, direct copy of valid api address in import table
190029 jmp 18a51e                ; back to normal
19002e push dword ptr [eax+1]    ; we're in case 3, pushes the encrypted api address
190031 call 16df80               ;IMPORTANT
you have to locate this aspack address by doing a S 0 L FFFFFFFF 55 8b ec 81 c4 f8 fe ff ff 53 56
so we found as example 16df80, you MUST modify the code at offset 16df80+95 with three nops (90), if you don't
this call will generate an error 13 in aspack (the call doesn't return properly)
this call is used to decypher the encrypted api address, normally decrypted at run time

190036 stosd                     ; the api's address is then decrypted, copy it to import table
190037 jmp 18a51e                ; back to normal, don't forget that this is the loop back jump
19003c mov eax,KERNEL32!GetProcAddress     ; use softice to have your system's getprocaddress. this is case 4
190041 stosd                               ; copy to import table
190042 jmp 18a51e                ; back to normal

Está bien, pero aquí el call fixer no va a funcionar. Será necesario modificarlo, especialmente a la altura del CMP ECX, 0 que impida pasar por el CMP EAX, 40000000. Pero antes que esto, queda una cosa que impediría que funcionase. No nos preocupemos por el momento de la tabla de importación y hagamos el volcado del programa (como anteriormente, pero esta vez sólo del programa). Analicemos el volcado que hemos obtenido y veamos especialmente el inicio de la sección .rdata:

00029000 DC29 DE00 F829 DE00 142A DE00 302A DE00 .)...)...*..0*..
00029010 4C2A DE00 682A DE00 842A DE00 A02A DE00 L*..h*...*...*..
00029020 BC2A DE00 0000 0000 482B DE00 542B DE00 .*......H+..T+..
00029030 602B DE00 6C2B DE00 782B DE00 842B DE00 `+..l+..x+...+..
00029040 0000 0000 4228 F2BF FE24 F2BF D824 F2BF ....B(...$...$..
00029050 3A28 F2BF 8A28 F2BF 9322 F2BF 3551 F2BF :(...(..."..5Q..
00029060 144A F2BF 7B50 F2BF 8D14 F2BF 7F26 F2BF .J..{P.......&..
00029070 0000 0000 7021 DE00 7C21 DE00 8821 DE00 ....p!..|!...!..
00029080 9421 DE00 A021 DE00 AC21 DE00 B821 DE00 .!...!...!...!..
00029090 C421 DE00 D021 DE00 DC21 DE00 E821 DE00 .!...!...!...!..
000290A0 F421 DE00 0022 DE00 0C22 DE00 1822 DE00 .!..."..."..."..
000290B0 2422 DE00 3022 DE00 3C22 DE00 4822 DE00 $"..0"..<"..H"..
000290C0 5422 DE00 6022 DE00 203D 4B00 6C22 DE00 T"..`".. =K.l"..
000290D0 7822 DE00 8422 DE00 9022 DE00 9C22 DE00 x"..."..."..."..
000290E0 A822 DE00 B422 DE00 C022 DE00 CC22 DE00 ."..."..."..."..

Se ven claramente las referencias a la zona DExxxx. Pero en 290C8 (4290C8 en la memoria) se encuentra 4B3D20, que forma parte de la sección de Asprotect. ¿Por qué pone un vínculo con esta zona aquí? Para saberlo hay que relanzar el programa (se retira el cambio de dirección y el anti-debugger) y se pone un breakpoint en 4B3D20: bpm 4b3d20 x. Normalmente, el SoftIce aparecerá aquí:

015F:004B3D1F  C3                  RET
015F:004B3D20  55                  PUSH      EBP
015F:004B3D21  8BEC                MOV       EBP,ESP
015F:004B3D23  8B550C              MOV       EDX,[EBP+0C]
015F:004B3D26  8B4508              MOV       EAX,[EBP+08]
015F:004B3D29  3B0584B64B00        CMP       EAX,[004BB684]       <-- EAX= -1 ?
015F:004B3D2F  7509                JNZ       004B3D3A             <-- no salta
015F:004B3D31  8B0495E0B64B00      MOV       EAX,[EDX*4+004BB6E0] <-- se pone 4B3DA8 en EAX
015F:004B3D38  EB07                JMP       004B3D41             <-- se vuelve al programa
015F:004B3D3A  52                  PUSH      EDX
015F:004B3D3B  50                  PUSH      EAX
015F:004B3D3C  E83739FFFF          CALL      KERNEL32!GetProcAddress <-- sino se recupera                                                                     la dirección que quiere
015F:004B3D41  5D                  POP       EBP

015F:004B3D42  C20800              RET       0008                 <-- se vuelve al programa
015F:004B3D45  8D4000              LEA       EAX,[EAX+00]

015F:004B3D48  55                  PUSH      EBP

Interesante ¿no? Pues en nuestro call fixer habrá que tener en cuenta el hecho de que EAX puede ser igual a 4B3D20. El programa llama muchas veces a esta rutina, pero no pone más que una vez 4B3DA8 en EAX. Además, testeando el programa, se ve que en ningún momento llama a lo que hay en 4B3DA8 (quizá sirve para desencriptar una región del programa, pero aquí se necesitaría una clave, en fin, no sé para que sirve esta parte). A continuación de nuestro volcado será necesario poner también esta rutina, y visto que el programa no utiliza lo que hay en 4B3DA8, yo no lo he puesto (de todos modos habría sido mucho más complicado, pero creo que se utiliza para desencriptar una parte del programa si se tiene una clave).
Bien, ahora se puede rehacer el call fixer para que responda a lo siguiente:

015F:004B6500  E80BEAFFFF          CALL      004B4F10      <-- se pone el call
015F:004B6505  81F900000040        CMP       ECX,40000000  <-- ECX contiene una dir. de API
015F:004B650B  760F                JBE       004B651C      <-- no salta
015F:004B650D  03C8                ADD       ECX,EAX       <-- se calcula la dirección
015F:004B650F  83C105              ADD       ECX,05
015F:004B6512  890F                MOV       [EDI],ECX     <-la pone en la tabla de import.
015F:004B6514  83C704              ADD       EDI,04        <-- se aumenta EDI para la                                                                siguiente importación
015F:004B6517  E91EECFFFF          JMP       004B513A      <-- bucle
015F:004B651C  3D00000040          CMP       EAX,40000000  <-- EAX contiene una dir. de API

015F:004B6521  7606                JBE       004B6529      <-- no salta
015F:004B6523  AB                  STOSD      <-- copia la dirección de la tabla de import.
015F:004B6524  E911ECFFFF          JMP       004B513A      <-- bucle

015F:004B6529  3D203D4B00          CMP       EAX,004B3D20  <-- EAX = 4B3D20
015F:004B652E  7506                JNZ       004B6536      <-- no salta
015F:004B6530  AB                  STOSD                   <-- copia en la tabla de import. 015F:004B6531  E904ECFFFF          JMP       004B513A      <-- bucle
015F:004B6536  83F900              CMP       ECX,00        <-- ECX = 0
015F:004B6539  750E                JNZ       004B6549      <-- no salta
015F:004B653B  FF7001              PUSH      DWORD PTR [EAX+01] <-- se pone en la pila la                                                      la referencia a la dirección de la API
015F:004B653E  E8F1E8FFFF          CALL      004B4E34      <-- call de desencriptado

015F:004B6543  AB                  STOSD                   <-- copia EAX en tabla de imp.
015F:004B6544  E9F1EBFFFF          JMP       004B513A      <-- bucle

015F:004B6549  B8AC6DF7BF          MOV       EAX,KERNEL32!GetProcAddress  <-- sino se pone                                                             la dirección de GetProcAddress
015F:004B654E  AB                  STOSD                    en la tabla de importación

015F:004B654F  E9E6EBFFFF          JMP       004B513A      <-- bucle
015F:004B6554  0000                ADD       [EAX],AL
015F:004B6556  0000                ADD       [EAX],AL

He aquí el call fixer que va a poner directamente las direcciones de las APIs en la tabla de importación. Pero para poderlo utilizar hay que poner alguna cosa en el CALL 4B44E34 que hemos visto y que sirve para desencriptar estas direcciones:

004B4E34 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R U T I N A ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
004B4E34
004B4E34 ; Atributos: bp-based frame
004B4E34
004B4E34 sub_4B4E34      proc near               ; CODE XREF: .import:00DE29FD_p
004B4E34                                         ; .import:00DE2A19_p ...
004B4E34
004B4E34 var_108         = byte ptr -108h
004B4E34 var_8           = dword ptr -8
004B4E34 var_4           = dword ptr -4
004B4E34 arg_0           = dword ptr  8
004B4E34
004B4E34                 push    ebp
004B4E35                 mov     ebp, esp
004B4E37                 add     esp, 0FFFFFEF8h
004B4E3D                 push    ebx
004B4E3E                 push    esi
004B4E3F                 mov     ebx, [ebp+arg_0]
004B4E42                 mov     eax, [ebx]
004B4E44                 mov     [ebp+var_8], eax
004B4E47                 lea     eax, [ebp+var_108]
004B4E4D                 xor     ecx, ecx
004B4E4F                 mov     edx, 100h
004B4E54                 call    sub_4A5880
004B4E59                 mov     eax, ebx
004B4E5B                 mov     edx, [eax+4]
004B4E5E                 mov     dl, [edx]
004B4E60                 cmp     dl, ds:byte_4BB74C    <-- ya se que está mal desensamblado                                                            pero bueno...
004B4E66                 jz      short loc_4B4EB5
004B4E68                 mov     edx, [eax+4]
004B4E6B                 mov     ecx, edx
004B4E6D                 inc     ecx
004B4E6E                 mov     bl, [ecx]
004B4E70                 add     edx, 2
004B4E73                 mov     [ebp+var_4], edx
004B4E76                 mov     esi, ebx
004B4E78                 and     esi, 0FFh
004B4E7E                 mov     ecx, esi
004B4E80                 lea     eax, [ebp+var_108]
004B4E86                 mov     edx, [ebp+var_4]
004B4E89                 call    sub_4A76F0
004B4E8E                 mov     eax, [ebp+var_8]
004B4E91                 call    sub_4A817C
004B4E96                 sub     eax, 2
004B4E99                 push    eax
004B4E9A                 mov     edx, esi
004B4E9C                 lea     eax, [ebp+var_108]
004B4EA2                 mov     ecx, [ebp+var_8]
004B4EA5                 call    sub_4B3564
004B4EAA                 lea     eax, [ebp+var_108]
004B4EB0                 mov     [ebp+var_4], eax
004B4EB3                 jmp     short loc_4B4EBE
004B4EB5 ; ---------------------------------------------------------------------------
004B4EB5
004B4EB5 loc_4B4EB5:                             ; CODE XREF: sub_4B4E34+32_j
004B4EB5                 mov     eax, [eax+4]
004B4EB8                 inc     eax
004B4EB9                 mov     eax, [eax]
004B4EBB                 mov     [ebp+var_4], eax
004B4EBE
004B4EBE loc_4B4EBE:                             ; CODE XREF: sub_4B4E34+7F_j
004B4EBE                 push    [ebp+var_4]
004B4EC1                 push    [ebp+var_8]
004B4EC4                 call    sub_4B4C8C
004B4EC9                 mov     [ebp+4], eax        <-- hay que nopear esta instrucción
004B4ECC                 pop     esi
004B4ECD                 pop     ebx
004B4ECE                 mov     esp, ebp
004B4ED0                 pop     ebp
004B4ED1                 retn    4
004B4ED1 sub_4B4E34      endp
004B4ED1
004B4ED4

Es necesario nopear la instrucción en 4B44EC9 si no, dará un error 13 y el programa se detendrá. Ahora hay que continuar traceando el programa (se deja que nuestro call fixer desencripte las direcciones) y se llega hasta el lugar donde el programa cede el turno a lo que está descomprimido en memoria. Se vuelve a encontrar el call seguido del salvado de los registros, y se entra en él. De inmediato se encuentra el POPAD. Allí se anota el nuevo EIP: 421BFC, y se escribe a aip y se pone JMP EIP. Se deja el SoftIce (F5) y se efectúa el volcado con ProcDump. Atención: hay que usar la opción Don't rebuild import table o Use actual import, para el volcado.
Ahora, como con PLEditor, es necesario cambiar el EIP con el nuevo valor, es decir 21BFC (421BFC-40000). Hay que copiar igualmente la sección .aspr (aquí es la penúltima sección, la .data) del original sobre la del volcado. A continuación se edita con un editor hexadecimal lo que hay en 4290C8. Y si hay una referencia en 4B3D20, se ha de poner en una zona libre del programa.

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00029090   0D 0B F8 BF 56 F6 F9 BF  15 0A F8 BF 24 2E F9 BF   ..ø¿Vöù¿..ø¿$.ù¿
000290A0   C4 64 F7 BF 93 7B F7 BF  AD 73 F7 BF 2B 0B FA BF   Äd÷¿"{÷¿­s÷¿+.ú¿
000290B0   D5 6F F7 BF B4 5C F9 BF  2C 62 F9 BF FE D5 F9 BF   Õo÷¿´\ù¿,bù¿þÕù¿
000290C0   B4 20 F8 BF EF 62 F9 BF  00 70 47 00 A0 E0 F8 BF   ´ ø¿ïbù¿..w. àø¿
000290D0   B3 CA F8 BF 85 7D F7 BF  65 43 F7 BF 3C 43 F7 BF   ³Êø¿…}÷¿eC÷¿<C÷¿
000290E0   B8 48 F7 BF DB 6D F7 BF  1F 6E F7 BF 41 6E F7 BF   ¸H÷¿Ûm÷¿.n÷¿An÷¿

Hay una zona libre en 477000. Pues allí habrá que poner la rutina que se encuentra normalmente en 4B3D20:

015F:00477000  55                  PUSH      EBP
015F:00477001  8BEC                MOV       EBP,ESP
015F:00477003  8B550C              MOV       EDX,[EBP+0C]
015F:00477006  8B4508              MOV       EAX,[EBP+08]
015F:00477009  3DFFFFFFFF          CMP       EAX,FFFFFFFF
015F:0047700E  90                  NOP
015F:0047700F  7509                JNZ       0047701A
015F:00477011  B8A83D4B00          MOV       EAX,004B3DA8
015F:00477016  90                  NOP
015F:00477017  90                  NOP
015F:00477018  EB07                JMP       00477021
015F:0047701A  52                  PUSH      EDX
015F:0047701B  50                  PUSH      EAX
015F:0047701C  E88BFDAFBF          CALL      KERNEL32!GetProcAddress
015F:00477021  5D                  POP       EBP
015F:00477022  C20800              RET       0008

He aquí que normalmente el programa volcado es completamente funcional J . No falta más que crackearlo…

También existe un método más simple para obtener las direcciones: En primer lugar, hay que hacer el volcado del programa. A continuación se lanza el .exe original y se ejecuta el programa import_list (lo siento, he olvidado el nombre del autor) que nos dará un fichero con las direcciones correctas, ahora no queda más que copiar/pegar (tendremos incluso un texto con los cambios J ). En este programa también hay que arreglar el problema de 4B3D20…

De hecho, no he escogido el mejor programa para mostrar este ejemplo. En efecto, si se reinicia la máquina hay pocas probabilidades de que el volcado funcione, ¿por qué? porque alguna librería de Windows se carga en lugares distintos de la memoria. He aquí donde se encuentra el problema para este volcado:

00029000 AA19 EABF 4416 EABF 3415 EABF D114 EABF ....D...4.......
00029010 EA15 EABF 7D16 EABF 2B18 EABF 8717 EABF ....}...+.......
00029020 2A17 EABF 0000 0000 97D3 7282 FE43 7082 *.........r..Cp.
00029030 F205 7582 F4DB 7182 77D8 7182 FD4E 7282 ..u...q.w.q..Nr.
00029040 0000 0000 4228 F2BF FE24 F2BF D824 F2BF ....B(...$...$..
00029050 3A28 F2BF 8A28 F2BF 9322 F2BF 3551 F2BF :(...(..."..5Q..
00029060 144A F2BF 7B50 F2BF 8D14 F2BF 7F26 F2BF .J..{P.......&..
00029070 0000 0000 DF7A F7BF E250 F8BF A570 F7BF .....z...P...p..

Hay seis direcciones seleccionadas y he aquí a lo que corresponden:

00DE2B48->8272D397[2]=(00001028)COMCTL32.DLL!CreateToolbarEx:0017
00DE2B54->827043FE[2]=(0000102C)COMCTL32.DLL!InitCommonControls:0011
00DE2B60->827505F2[2]=(00001030)COMCTL32.DLL!CreateStatusWindow:0015
00DE2B6C->8271DBF4[2]=(00001034)COMCTL32.DLL!ImageList_ReplaceIcon:0046
00DE2B78->8271D877[2]=(00001038)COMCTL32.DLL!ImageList_Create:002D
00DE2B84->82724EFD[2]=(0000103C)COMCTL32.DLL!PropertySheet:0056

Pues para estas seis direcciones será necesario hacer una modificación. Voy simplemente a dar el principio, ya que el método de Tsehp que viene a continuación, hace lo mismo, pero es mejor. Estas seis direcciones sabemos a que funciones corresponden, así pues, el principio consiste en modificar el EIP del programa a un lugar donde se pueda poner nuestro código. Es necesario haber anotado previamente el nombre de la DLL y de las funciones utilizadas. Utilizando GetModuleHandle y después GetProcAddress se recupera la dirección de memoria y donde va a escribir su posición en la sección .rdata. Como en este caso, no habrá problema de dirección para estas funciones. No he explicado completamente esta parte, ya que mientras tanto, Tsehp ha puesto a punto un nuevo método de volcado… y en él comprenderéis de qué estoy hablando…  

V_3er método: Advanced mail… visto de otra manera

Ahora voy a tratar de describir un método que permite obtener un volcado que funcione sobre todas las máquinas (al menos en todas las versiones de Windows 9x, si el volcado está hecho sobre Windows 9x; y en las versiones de NT/2000, si el volcado… en fin, veamos J ). Voy a presentar un método que ha sido puesto a punto por Tsehp (very good work man): podeis encontrar su ensayo original sobre Commview en esta dirección: http://tsehp.cjb.net  en la sección news está el ensayo sobre Asprotect 1.05. Os lo digo porque el principio de su método (como obtener los nombres) me ha permitido elaborar a continuación el mío. Voy a intentar explicarlo con claridad, pero esto va a ser difícil, ya que el método en sí no es sencillo (de todos modos, cuando se sabe hacer, resulta más fácil J ) En principio, la idea es la siguiente: en lugar de hacer un volcado que tenga las direcciones de las funciones utilizadas, escritas en la sección .rdata (en el caso de AMV, o en .idata como PLEditor) lo que habrá que hacer antes que nada es recuperar el nombre de esta función (como CreateFileA), su módulo (como KERNEL32.DLL) y el lugar donde debe estar en la sección .rdata (o .idata)

Es suficiente con desviar el inicio del programa hacia un extremo del código que se encargará de recuperar las direcciones de estas funciones y de escribirlas en la sección .rdata, así el volcado funcionará bajo todas las versiones de Windows 9x (o NT/2000 si el volcado se ha hecho con estas). ¿Dónde se puede hacer esto? En el mismo lugar que antes, a la altura del call fixer. ¿Por qué aquí? Es muy sencillo: en principio se tiene cada dirección en la sección .rdata donde se ha escrito. Pero además para hacer estos vínculos es obligado conocer de que funciones se trata (de hecho, es más complicado que esto).

Ya hemos visto como se llega aquí en el capítulo precedente:

015F:004B5138  87FE                XCHG      EDI,ESI
015F:004B513A  AC                  LODSB
015F:004B513B  08C0                OR        AL,AL
015F:004B513D  74E4                JZ        004B5123
015F:004B513F  4E                  DEC       ESI
015F:004B5140  56                  PUSH      ESI
015F:004B5141  53                  PUSH      EBX
015F:004B5142  80F802              CMP       AL,02
015F:004B5145  7407                JZ        004B514E
015F:004B5147  0FB64E01            MOVZX     ECX,BYTE PTR [ESI+01]
015F:004B514B  41                  INC       ECX
015F:004B514C  EB05                JMP       004B5153
015F:004B514E  B904000000          MOV       ECX,00000004
015F:004B5153  41                  INC       ECX
015F:004B5154  01CE                ADD       ESI,ECX
015F:004B5156  E8B5FDFFFF          CALL      004B4F10                <-- busca las APIs
015F:004B515B  AB                  STOSD
015F:004B515C  EBDC                JMP       004B513A                <-- vuelve al inicio
015F:004B515E  61                  POPAD

A la altura del call 4B4F10, se ve que EDI contiene la dirección de destino en la sección .rdata y alguna vez EAX o EDX contienen la dirección de una función. Será pues interesante mirar un poco este call 4B4F10. No lo voy a analizar, pero os aconsejo leer el ensayo de TeeJi que lo hace muy bien. He aquí una pequeña parte:

004B4F10 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R U T I N A ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
004B4F10
004B4F10 ; Attributes: bp-based frame
004B4F10
004B4F10 sub_4B4F10      proc near               ; CODE XREF: sub_4B5094+C2_p
004B4F10
004B4F10 var_11D         = byte ptr -11Dh
004B4F10 var_1D          = byte ptr -1Dh
004B4F10 var_1C          = dword ptr -1Ch
004B4F10 var_18          = byte ptr -18h
004B4F10 var_17          = dword ptr -17h
004B4F10 var_13          = byte ptr -13h
004B4F10 var_12          = dword ptr -12h
004B4F10 var_E           = dword ptr -0Eh
004B4F10 var_A           = dword ptr -0Ah
004B4F10 arg_0           = dword ptr  8
004B4F10 arg_4           = dword ptr  0Ch
004B4F10
004B4F10                 push    ebp
004B4F11                 mov     ebp, esp
004B4F13                 add     esp, 0FFFFFEE0h
004B4F19                 push    ebx
004B4F1A                 push    esi
004B4F1B                 push    edi
004B4F1C                 mov     edi, [ebp+arg_4]
004B4F1F                 mov     esi, [ebp+arg_0]
004B4F22                 mov     bl, [esi]
004B4F24                 lea     eax, [ebp+var_11D]     <-- tomad nota de la dirección                                                             EBP-11D: en nuestro caso 6AFC6B
004B4F2A                 xor     ecx, ecx
004B4F2C                 mov     edx, 100h
004B4F31                 call    sub_4A5880
004B4F36                 sub     bl, 2
004B4F39                 jz      short loc_4B4F49
004B4F3B                 sub     bl, 2
004B4F3E                 jz      loc_4B4FE1
004B4F44                 jmp     loc_4B5035
004B4F49

............................................................    <-- aquí corto una parte

004B508A
004B508A loc_4B508A:                             ; CODE XREF: sub_4B4F10+9A_j
004B508A                                         ; sub_4B4F10+B9_j ...
004B508A                 pop     edi
004B508B                 pop     esi
004B508C                 pop     ebx
004B508D                 mov     esp, ebp
004B508F                 pop     ebp
004B5090                 retn    8
004B5090 sub_4B4F10      endp
004B5090

Bien, aquí he puesto el principio y el final del call (es largo), pero traceando y teniendo en cuenta la dirección indicada, se verá aparecer el nombre de la función (la primera es CreateFileA). Se sale del call (para comprender su utilidad, ver el ensayo de TeeJi): aquí el nombre de la función está siempre en 6AFC6B. Se puede ver esta dirección a la salida del call en EBP-15D pero esto está aún por verificar. Se obtiene pues el nombre de la función deseada, además, el nombre de la DLL asociada aparece haciendo d ebx, y en EDI está la dirección de destino en la sección .rdata. Se tiene todo, excepto las funciones encriptadas, entonces como en el capítulo anterior se llama a la función de desencriptado (ver el capítulo precedente para saber como localizarla) y se obtiene el nombre de la función. No queda más que un caso por tratar, es cuando las funciones son llamadas por su ordinal… vais a ver que contrariamente a Tsehp no me preocupo por ello… comprenderéis por qué.

Bueno, ahora ataquemos a la bestia J . En 4B5156 se desvía el call poniendo un JMP 4B6500 (ver capítulo 3º), donde hay espacio libre. A continuación, hay que encontrar un espacio de memoria libre para poder escribir el nombre de las funciones, de las DLLs y sus direcciones en la sección .rdata. Por mi parte, pongo el código siguiente en 4B6500, el cual se retira después para colocar el call fixer:

pushad                                     <-- guarda los registros
push 40                                    <-- rellena con ceros
push 00003000                              <-- se puede leer,escribir y ejecutar
push 00008000                              <-- tamaño de la memoria reservada
push 01000000                              <-- dirección de la memoria (pongo 1000000 ya                                                que no tiene nada pero es aleatorio)
call KERNEL32!VirtualAlloc                 <-- se reserva la memoria
popad                                      <-- se restauran los registros

Ahora que tenemos hecha la reserva de memoria en 1000000, vamos a nuestro código en 4B6500 (r eip 4b6500). Se coloca en este lugar el call fixer que se va a encargar de copiar los nombres de las funciones, DLLs y direcciones en la sección .rdata en 1000000. He aquí el call fixer de Tsehp ligeramente modificado:

015F:004B6500  E80BEAFFFF       CALL  004B4F10                <-- se restaura el call
015F:004B6505  60               PUSHAD                        <-- se guardan los registros
015F:004B6506  833D6BFC6A0000   CMP   DWORD PTR [006AFC6B],00 <-- mira si hay un nombre de                                                                          función
015F:004B650D  7456             JZ    004B6565               <-- si no lo hay, se va hacia
                                                                        el desencriptado / ordinal
015F:004B650F  6855FE6A00       PUSH  006AFE55             <-- pone el nombre de la función
015F:004B6514  FF35F0644B00     PUSH  DWORD PTR [004B64F0]   <-- la dirección donde se                                                                  quiere copiar (antes de                                                                  ejecutar el bucle hay que                                                                  poner 00000001 en 4B64F0)
015F:004B651A  E8DD0DACBF       CALL      KERNEL32!lstrcpy     <-- se copia
015F:004B651F  FF35F0644B00     PUSH      DWORD PTR [004B64F0] <-- se guarda la dirección                                                                    donde está el nombre
015F:004B6525  E8830EACBF       CALL      KERNEL32!lstrlen   <-- se calcula su longitud
015F:004B652A  40               INC       EAX                <-- para añadir un 0
015F:004B652B  43               INC       EBX                <-- para ponerse al inicio                                                                  del nombre de la DLL
015F:004B652C  0105F0644B00     ADD       [004B64F0],EAX     <-- se incrementa la                                                                  dirección de destino
015F:004B6532  53               PUSH      EBX                <-- pone el nombre de la DLL
015F:004B6533  FF35F0644B00     PUSH      DWORD PTR [004B64F0] <-- la direc. de destino
015F:004B6539  E8BE0DACBF       CALL      KERNEL32!lstrcpy     <-- se copia
015F:004B653E  FF35F0644B00     PUSH      DWORD PTR [004B64F0] <-- se guarda la dirección                                                                    donde está el nombre
015F:004B6544  E8640EACBF       CALL      KERNEL32!lstrlen <-- se calcula su longitud
015F:004B6549  40               INC       EAX              <-- para añadir un 0
015F:004B654A  0105F0644B00     ADD       [004B64F0],EAX   <-- se incrementa la                                                                dirección de destino
015F:004B6550  8B0DF0644B00     MOV       ECX,[004B64F0]   <-- se pone la dirección en ECX 015F:004B6556  8939             MOV       [ECX],EDI        <-- y se copia la dirección de                                                                .rdata que corresponde a la                                                                función
015F:004B6558  8305F0644B0005   ADD       DWORD PTR [004B64F0],05  <-- se incrementa la                                                                        dirección de destino
015F:004B655F  61               POPAD                      <-- se restauran los registros

015F:004B6560  E9D5EBFFFF       JMP       004B513A         <-- y se vuelve al bucle de                                                              Asprotect a la altura de STOSD
015F:004B6565  3D00000040       CMP       EAX,40000000     <-- EAX contiene la dirección                                                                de una API

015F:004B656A  7D14             JGE       004B6580         <-- hacia el tratamiento por                                                                ordinal
015F:004B656C  83F900           CMP       ECX,00           <-- ECX == 0
015F:004B656F  750F             JNZ       004B6580         <-- si: ordinal no: encriptado
015F:004B6571  FF7001           PUSH      DWORD PTR [EAX+01] <-- se pone el parámetro del                                                                  proceso de desencriptado
015F:004B6574  E8BBE8FFFF       CALL      004B4E34         <-- se desencripta (como se ha                                                                visto antes, hay que                                                                poner 3 NOP en 4B4E34+95)

015F:004B6579  686BFC6A00       PUSH      006AFC6B         <-- se pone la dir. del nombre
015F:004B657E  EB94             JMP       004B6514         <-- y se vuelve a la copia

015F:004B6580  83EE04           SUB       ESI,04           <-- si ordinal se pone 4 en ESI
015F:004B6583  56               PUSH      ESI              <-- se le pone en la pila

015F:004B6584  EB8E             JMP       004B6514         <-- y se vuelve a la copia
015F:004B6586  0000             ADD       [EAX],AL
015F:004B6588  0000             ADD       [EAX],AL

Bien, creo que esta función está suficientemente detallada. Es necesario poner un bpm 4b6579 después del call de desencriptado para verificar si el nombre del procedimiento está siempre en la misma dirección. Si no lo está, y no está muy lejos (una ojeada con SoftIce), se reemplaza la dirección y después se puede efectuar el bucle tranquilamente.

Una vez terminado el bucle, hay que copiar todo lo que está en 1000000 con IceDump, pagein d 1000000 2000 c:\noms.dat. A continuación Tsehp propone añadir una sección al volcado y poner en ella los nombres (con las direcciones) y escribir un procedimiento que se encargue de recuperar cada nombre (u ordinal) y de recuperar la dirección de la función con GetModuleHandle y GetProcAddress (ver su tutorial), y después poner esta dirección en .rdata. Haciéndolo así, habríamos reconstruido una falsa tabla de importación y el programa funcionaría.

Pero la descripción de su método se detiene aquí. En efecto, he cambiado todo esto, no para obtener una falsa tabla de importación para que el programa funcione, sino una verdadera, como si no se hubiese tocado el programa ( ningún añadido de sección o de código). Esto es posible porque por el momento Asprotect todavía no lo ha destruido todo… en la versión de AMV, veremos después que la última versión (PLEditor, Commview…) es más complicado… Empecemos por el principio:

015F:004B5138  87FE                XCHG      EDI,ESI
015F:004B513A  AC                  LODSB                              <-- estamos aquí
015F:004B513B  08C0                OR        AL,AL
015F:004B513D  74E4                JZ        004B5123
015F:004B513F  4E                  DEC       ESI
015F:004B5140  56                  PUSH      ESI
015F:004B5141  53                  PUSH      EBX
015F:004B5142  80F802              CMP       AL,02
015F:004B5145  7407                JZ        004B514E
015F:004B5147  0FB64E01            MOVZX     ECX,BYTE PTR [ESI+01]
015F:004B514B  41                  INC       ECX
015F:004B514C  EB05                JMP       004B5153
015F:004B514E  B904000000          MOV       ECX,00000004
015F:004B5153  41                  INC       ECX
015F:004B5154  01CE                ADD       ESI,ECX
015F:004B5156  E8B5FDFFFF          CALL      004B4F10                <-- busca las APIs
015F:004B515B  AB                  STOSD
015F:004B515C  EBDC                JMP       004B513A                <-- vuelve al inicio
015F:004B515E  61                  POPAD

Nos encontramos en 4B513A y todavía no se ha hecho nada. Veamos como es el inicio de la sección .rdata en este momento:

0030:00429000 72 B3 02 00 FC B2 02 00-0A B3 02 00 1E B3 02 00  r...............
0030:00429010 2E B3 02 00 40 B3 02 00-52 B3 02 00 62 B3 02 00  ....@...R...b...
0030:00429020 80 B3 02 00 00 00 00 00-2C B4 02 00 11 00 00 80  ........,.......
0030:00429030 06 00 00 80 3E B4 02 00-56 B4 02 00 6A B4 02 00  ....>...V...j...
0030:00429040 00 00 00 00 12 B2 02 00-44 B2 02 00 54 B2 02 00  ........D...T...
0030:00429050 38 B2 02 00 7A B2 02 00-86 B2 02 00 6A B2 02 00  8...z.......j...
0030:00429060 9E B2 02 00 AC B2 02 00-90 B2 02 00 22 B2 02 00  ............"...
0030:00429070 00 00 00 00 58 AB 02 00-66 AB 02 00 76 AB 02 00  ....X...f...v...
0030:00429080 4C AB 02 00 3E AB 02 00-88 AB 02 00 A8 AB 02 00  L...>...........
0030:00429090 B8 AB 02 00 C8 AB 02 00-DA AB 02 00 98 AB 02 00  ................
0030:004290A0 F6 AB 02 00 02 AC 02 00-EA AB 02 00 24 AC 02 00  ............$...
0030:004290B0 3E AC 02 00 4A AC 02 00-5E AC 02 00 72 AC 02 00  >...J...^...r...

En efecto, se encuentran referencias de tipo 2B372 o si se suma la image base 42B372… ¿no parece esto una import address table (en negrita los nombres de las funciones)? si, Asprotect no la ha destruido, está como nueva. Miremos un poco más lejos:

0030:00429300 E2 AF 02 00 CE AF 02 00-C2 AF 02 00 B4 AF 02 00  ................
0030:00429310 A0 AF 02 00 92 AF 02 00-84 AF 02 00 76 AF 02 00  ............v...
0030:00429320 60 AF 02 00 4A AF 02 00-36 AF 02 00 26 AF 02 00  `...J...6...&...
0030:00429330 12 AE 02 00 04 AE 02 00-8C B0 02 00 A6 B1 02 00  ................
0030:00429340 00 00 00 00 D2 B3 02 00-E4 B3 02 00 FA B3 02 00  ................
0030:00429350 00 00 00 00 14 00 00 80-03 00 00 80 13 00 00 80  ................
0030:00429360 10 00 00 80 02 00 00 80-12 00 00 80 16 00 00 80  ................
0030:00429370 06 00 00 80 74 00 00 80-73 00 00 80 33 00 00 80  ....t...s...3...
0030:00429380 0A 00 00 80 11 00 00 80-04 00 00 80 17 00 00 80  ................
0030:00429390 34 00 00 80 00 00 00 00-C6 B2 02 00 DA B2 02 00  4...............
0030:004293A0 00 00 00 00 00 00 00 00-53 6F 66 74 77 61 72 65  ........Software
0030:004293B0 5C 4D 69 63 72 6F 73 6F-66 74 5C 57 41 42 5C 44  \Microsoft\WAB\D

¿Por qué he puesto 14 00 00 80 en negrita? simplemente, porque aquí también está la tabla de importación, pero no es un vínculo hacia el nombre de la función -2, es el ordinal de la función utilizada. Vamos a ver la continuación: en 429000 hay 2B372, así pues normalmente en 42B372 debería estar la import table (con los nombres y el ordinal de cada función):

0030:0042B2F0 00 00 00 00 00 00 00 00-00 00 00 00 5B 01 00 00  ............[...
0030:0042B300 00 00 00 00 00 00 00 00-00 00 7B 01 00 00 00 00  ..........{.....
0030:0042B310 00 00 00 00 00 00 00 00-00 00 00 00 00 00 72 01  ..............r.
0030:0042B320 00 00 00 00 00 00 00 00-00 00 00 00 00 00 86 01  ................
0030:0042B330 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0030:0042B340 5F 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00  _...............
0030:0042B350 00 00 67 01 00 00 00 00-00 00 00 00 00 00 00 00  ..g.............
0030:0042B360 00 00 62 01 00 00 00 00-00 00 00 00 00 00 00 00  ..b.............
0030:0042B370 00 00 71 01 00 00 00 00-00 00 00 00 00 00 00 00  ..q.............
0030:0042B380 5E 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ^...............
0030:0042B390 00 00 00 00 00 00 00 00-00 00 00 00 00 00 72 00  ..............r.
0030:0042B3A0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

Aquí no hay nombres de función L. Pero en 42B372 hay sin embargo 7101 seguido de ceros. Bien, 7101 es el ordinal de la función y justo a continuación debería estar el nombre de la función. Esto es así: un word para el ordinal de la función y a continuación el nombre de la función. En efecto en la IAT las referencias apuntan hacia el nombre de la función -2.

Tenemos la IAT correcta y la supuesta conteniendo espacios vacíos con los words que se supone son el ordinal de la función. Es necesario verificarlo. Se hace un primer paso por el bucle hasta el call inclusive. Allí, si se mira en 6AFC3B está la función CreateFileA y en EDI hay 429074. Así pues, se mira en 429074 y hay 58 AB 02 00, o sea, si se le suma la image base (400000 para AMV) se obtiene 42AB58. Vamos a ver en 42AB58:

0042AB58 3400 0000 0000 0000 0000 0000 0000 1A01        4...............

Bien, en 42AB58 hay 3400 (el ordinal de CrateFileA) y justo el espacio para poner CreateFileA (con un 0 como carácter de final) antes de llegar al ordinal siguiente. O sea: es la IT sin los nombres.

Así, la idea es hacer un call fixer que se encargue de poner los nombres en sus lugares correspondientes. Y como EDI apunta hacia lo que hay en 429xxx, bastará con ver lo que hay y sumarle la image base y será el lugar donde debe estar el nombre de la función -2. Para el caso de funciones llamadas por el ordinal, no hay que hacer nada, ya que el ordinal está ya en la IAT. A continuación se volcará esta parte, que se unirá al volcado.

Bien, ahora hay que hacerlo J. Así pues, se vuelve a empezar y se llega de nuevo al inicio del bucle en 4B513A. Aquí se llega hasta el call y se desvía de nuevo:

015F:004B5147  0FB64E01            MOVZX     ECX,BYTE PTR [ESI+01]
015F:004B514B  41                  INC       ECX
015F:004B514C  EB05                JMP       004B5153
015F:004B514E  B904000000          MOV       ECX,00000004
015F:004B5153  41                  INC       ECX
015F:004B5154  01CE                ADD       ESI,ECX
015F:004B5156  E9A5130000          JMP       004B6500       <-- se desvía
015F:004B515B  AB                  STOSD
015F:004B515C  EBDC                JMP       004B513A
015F:004B515E  61                  POPAD

Ahora se puede poner en 4B6500 nuestro fixer:

015F:004B6500  E80BEAFFFF          CALL      004B4F10       <-- el call que se ha destruido
015F:004B6505  60                  PUSHAD                   <-- se guardan los registros
015F:004B6506  833D6BFC6A0000      CMP       DWORD PTR [006AFC6B],00     <-- si no está el                                                                      nombre salta
015F:004B650D  741C                JZ        004B652B     <-- y va hacia el encript/ordinal

015F:004B650F  686BFC6A00          PUSH      006AFC6B      <-- se pone el nombre
015F:004B6514  8B0F                MOV       ECX,[EDI]      <-- se pone la VA señalada por                                                                 EDI en .rdata
015F:004B6516  81C102004000        ADD       ECX,00400002   <-- se suma a la imagebase + 2                                                                 para pasar al ordinal
015F:004B651C  51                  PUSH      ECX            <-- se pone la dirección de                                                                 destino
015F:004B651D  E8DA0DACBF          CALL      KERNEL32!lstrcpy   <-- y se copia

015F:004B6522  61                  POPAD                    <-- se restauran los registros
015F:004B6523  83C704              ADD       EDI,04         <-- se pasa al siguiente                                                                 tratamiento
015F:004B6526  E90FECFFFF          JMP       004B513A       <-- efectúa el bucle
015F:004B652B  3D00000040          CMP       EAX,40000000   <-- EAX contiene la dirección                                                                 de una API
015F:004B6530  7DF0                JGE       004B6522       <-- si es ordinal se pasa al                                                                 siguiente, pues el ordinal                                                                 está ya en la IAT
015F:004B6532  83F900              CMP       ECX,00         <-- ECX= 0 si no, es ordinal

015F:004B6535  75EB                JNZ       004B6522       <-- y se pasa al siguiente
015F:004B6537  FF7001              PUSH      DWORD PTR [EAX+01] <-- o es encrip. y se pasa                                                                     el parámetro a la 015F:004B653A  E8F5E8FFFF          CALL      004B4E34           <-- función de desencrip.
015F:004B653F  6864FC6A00          PUSH      006AFC64       <-- se busca la dirección del                                                                 nombre que se ha puesto en                                                                 la pila
015F:004B6544  EBCE                JMP       004B6514   
    <-- y se escribe en la IT
 

Creo que este procedimiento está bien comentado. De todos modos, hay que poner igualmente 3 NOPs en 4B4E34+95 (el procedimiento de desencriptado) y tomar nota de la dirección del nombre al volver de este procedimiento (ver capítulo anterior).
Antes de entrar en el bucle hay que poner un bpm 4b515e x para detenerse después del bucle. Una vez que ha terminado es necesario volcar toda la parte que ha cambiado: pagein d 429000 2c00 c:\import.dat.

Ahora se toma el volcado del programa (obtenido en el sitio donde cede el turno al programa descomprimido, habiendo puesto JMP EIP, y después volcándolo con ProcDump o Procinfos). Si volcáis el programa con IceDump tened en cuenta que se debe reconstruir con Perebuilder (con la opción Fix Raw Offset) poniendo directamente el raw offset de cada sección igual a la virtual address.

Una cosa muy práctica es que la virtual address es igual al raw offset, como se nota fácilmente. Será necesario pegar la importación que se ha volcado en el volcado en 29000. Una vez hecho esto el volcado no es aún funcional: en efecto, tenemos la IT (los nombres de las funciones con el ordinal), la IAT (vínculos con los nombres de las funciones), pero falta todavía el import descriptor que dice que DLLs son utilizadas.

Nos toca a nosotros el reconstruirlo.

Para hacerlo es conveniente dar un vistazo al tutorial de TeeJi sobre como añadir una DLL a la IAT y se comprende mejor de lo que se habla J. Bien, empecemos por el principio: como se presenta el import descriptor:

IMAGE_IMPORT_DESCRIPTOR struct
      OriginalFirstThunk   dd 0  ;RVA to original unbound IAT
      TimeDateStamp        dd 0  ;not used here
      ForwarderChain       dd 0  ;not used here
      Name                 dd 0  ;RVA to DLL name sring
      FirstThunk           dd 0  ;RVA to IAT array
IMAGE_IMPORT_DESCRIPTOR ends

Para una DLL hay 4 dword: el primero corresponde al inicio de la zona de la IAT correspondiente, el segundo no nos interesa, el tercero no se utiliza aquí y se pone FFFFFFFF, el cuarto apunta hacia el nombre de la DLL, y el quinto hacia una tabla de dirección (como aquí no la hay se pondrá igual al OriginalFirstThunk).
Bien, comencemos: vamos al inicio de la sección .rdata en 429000 :

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00029000   72 B3 02 00 FC B2 02 00  0A B3 02 00 1E B3 02 00   r³..ü²...³...³..
00029010   2E B3 02 00 40 B3 02 00  52 B3 02 00 62 B3 02 00   .³..@³..R³..b³..
00029020   80 B3 02 00 00 00 00 00  2C B4 02 00 11 00 00 80   €³......,´.....€
00029030   06 00 00 80 3E B4 02 00  56 B4 02 00 6A B4 02 00   ...€>´..V´..j´..
00029040   00 00 00 00 12 B2 02 00  44 B2 02 00 54 B2 02 00   .....²..D²..T²..
00029050   38 B2 02 00 7A B2 02 00  86 B2 02 00 6A B2 02 00   8²..z²..†²..j²..
00029060   9E B2 02 00 AC B2 02 00  90 B2 02 00 22 B2 02 00   ž²..¬²..².."²..

He aquí como se encuentran las DLLs utilizadas: al principio de la sección en 29000 hay una serie de vínculos hacia los nombres-2. He seleccionado 17 ya que justo a continuación hay 00 00 00 00. Esto permite decir que el programa llama a 17 funciones de estas DLLs. Se sabe ya que el OriginalFirstThunk es 29000. El nombre de la DLL se puede poner en cualquier parte, pero es mejor hacerlo aquí: El primer vínculo es 2B372, procedamos:

Offset      0  1  2  3  4  5  6  7  8   9  A  B  C  D  E  F

0002B350   41 00 67 01 52 65 67 45  6E 75 6D 4B 65 79 45 78   A.g.RegEnumKeyEx
0002B360   41 00 62 01 52 65 67 44  65 6C 65 74 65 4B 65 79   A.b.RegDeleteKey
0002B370   41 00 71 01 52 65 67 4F  70 65 6E 4B 65 79 41 00   A.q.RegOpenKeyA.
0002B380   5E 01 52 65 67 43 72 65  61 74 65 4B 65 79 41 00   ^.RegCreateKeyA.
0002B390   00 00 00 00 00 00 00 00  00 00 00 00 00 00 72 00   ..............r.
0002B3A0   53 68 65 6C 6C 45 78 65  63 75 74 65 41 00 00 00   ShellExecuteA...

La dirección 2B390 se encuentra llena de 00, y encima se encuentran los nombres de las funciones de las DLLs, señalados por la parte de la IAT seleccionada. Estas funciones proceden de Advapi.dll. Así, en 2B390 se pone advapi.dll y con ello, el nombre de la DLL estará en 2B390. Si no se encuentran los nombres de las DLLs utilizadas, la prog import list da por orden las funciones, con su DLL asociada. Ahora se pasa a 29020 y se continua con la DLL siguiente. Por lo que respecta a Ordinales resulta que en este programa es por wsock32.dll (se la puede encontrar a la altura del bucle que se ha hecho y poniendo un breakpoint a la altura del tratamiento del ordinal y se verán la/las DLL(s) correspondientes). Para escribir el import descriptor se elige un lugar libre, he aquí el mío reconstruido:

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

0002A800   00 90 02 00 00 00 00 00  FF FF FF FF 90 B3 02 00   .......ÿÿÿÿ³..
0002A810   00 90 02 00 28 90 02 00  00 00 00 00 FF FF FF FF   ...(......ÿÿÿÿ
0002A820   7B B4 02 00 28 90 02 00  44 90 02 00 00 00 00 00   {´..(..D......
0002A830   FF FF FF FF BC B2 02 00  44 90 02 00 74 90 02 00   ÿÿÿÿ¼²..D..t..
0002A840   00 00 00 00 FF FF FF FF  2F AD 02 00 74 90 02 00   ....ÿÿÿÿ/­..t..
0002A850   04 92 02 00 00 00 00 00  FF FF FF FF AE B3 02 00   .’......ÿÿÿÿ®³..
0002A860   04 92 02 00 0C 92 02 00  00 00 00 00 FF FF FF FF   .’...’......ÿÿÿÿ
0002A870   05 B2 02 00 0C 92 02 00  44 93 02 00 00 00 00 00   .²...’..D"......
0002A880   FF FF FF FF 14 B4 02 00  44 93 02 00 54 93 02 00   ÿÿÿÿ.´..D"..T"..
0002A890   00 00 00 00 FF FF FF FF  D0 A8 02 00 54 93 02 00   ....ÿÿÿÿШ..T"..
0002A8A0   98 93 02 00 00 00 00 00  FF FF FF FF ED B2 02 00   ˜"......ÿÿÿÿí²..
0002A8B0   98 93 02 00 00 00 00 00  00 00 00 00 00 00 00 00   ˜"..............
0002A8C0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
0002A8D0   57 53 4F 43 4B 33 32 2E  44 4C 4C 00 00 00 00 00   WSOCK32.DLL.....                           .

He señalado las diferentes partes, aquí el programa utiliza 8 DLLs. Ahora, para que el volcado sea operativo, es necesario cambiar la import address RVA (con PEditor) en 2A800, y asimismo, poner el EIP original, el que se encuentra en el momento en que el programa cede el turno al programa descomprimido (aquí 421BFC).

Bien, pero AMV no funcionará ¿por qué? recordad que anteriormente he dicho que se ponía una dirección de Asprotect en la tabla de importación, que serviría para un desencriptado si se tiene la clave. En efecto, hace una llamada a GetProcAddress al pasar por un punto donde pone este valor. Así nuestro fixer ha puesto como valor para esto una referencia a GetProcAddress, y si el volcado no funciona es que por el paso al proceso de desencriptado hace una llamada a GetProcAddress, cuando debería poner un valor. Veamos el mensaje de error que aparece a la ejecución del volcado:

Crypt API not found. Please re-install

Se desensambla y se llega aquí: 

00417620 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R U T I N A ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
00417620
00417620
00417620 sub_417620      proc near               ; CODE XREF: _WinMain@16+EB_p
00417620                 push    4
00417622                 push    0FFFFFFFFh
00417624                 call    ds:off_4290C8
0041762A                 test    eax, eax
0041762C                 mov     ds:dword_433758, eax
00417631                 jnz     short loc_417649           <-- se fuerza
00417633                 push    10h
00417635                 push    offset aAdvancedMailLi ; "Advanced Mail List Verify"                                                                 <-- no es bueno
0041763A                 push    offset aCryptApiNotFou ; "Crypt API not found. Please re-                                                           install "...
0041763F                 push    eax
00417640                 call    ds:dword_4292E8
00417646                 xor     eax, eax
00417648                 retn
00417649 ; ---------------------------------------------------------------------------
00417649
00417649 loc_417649:                             ; CODE XREF: sub_417620+11_j
00417649                 mov     eax, 1
0041764E                 retn

Para evitar este mensaje de error y con él, la finalización del programa es suficiente con forzar el salto en 417361 y ya está hecho. Ahora el volcado es completamente funcional y como nuevo J . Ciertamente hay aún una pequeña parte encriptada a falta de la clave… pero bien, hemos obtenido un volcado con una verdadera tabla de importación.

Bien, si he descrito el principio del método de Tsehp es porque gracias a él, he podido elaborar este método, y hacía falta saber el nombre de las funciones…

VI_4º método: PLEditor

Lo que sería interesante es que el método anterior funcionara también con PLEditor (siendo que está protegido con una versión más reciente de Asprotect). Desgraciadamente no funciona, ya que ni la IAT ni la IT no existen, no hay más que 00. Sin embargo ¿es imposible alcanzar el mismo objetivo que anteriormente? no, pero habrá que poner a punto un método que va a crear una IAT, una IT y ¿por qué no? al mismo tiempo el import descriptor. Pero ¿cómo puede hacerse? es muy simple, ahora no se va a escribir un call fixer sino un import table rebuilder. ¿Cómo es posible? muy sencillo, porque Asprotect se traiciona a sí mismo… Está obligado a poner estos vínculos hacia las funciones (que haya un encriptado o simplemente un JMP no tiene importancia) en el mismo lugar donde debía encontrarse originalmente. En efecto, supongamos que en 429000 hay un vínculo hacia la API CreateFileA, entonces el programa llamará a lo que haya en 429000: CALL[429000] y como esto forma parte de la import table, Windows se encargará de reemplazarlo por la dirección de CreateFileA. Así pues, si Asprotect quiere reconstruir una import table, encriptada o cualquier otra cosa, está obligado a que lo que vinculará el programa a CreateFileA esté situado en 429000. Y así es como podremos llegar a reconstruir una IAT, IT y el import descriptor.

Bien, ahora pongámoslo en práctica. Se ejecuta PLEditor, se tracea, se quita el anti-debugger y se llega a este famoso bucle (ver como se llega en los capítulos precedentes):

015F:00555392  AC                  LODSB
015F:00555393  08C0                OR        AL,AL
015F:00555395  74E4                JZ        0055537B
015F:00555397  4E                  DEC       ESI
015F:00555398  56                  PUSH      ESI
015F:00555399  53                  PUSH      EBX
015F:0055539A  80F802              CMP       AL,02
015F:0055539D  7407                JZ        005553A6
015F:0055539F  0FB64E01            MOVZX     ECX,BYTE PTR [ESI+01]
015F:005553A3  41                  INC       ECX
015F:005553A4  EB05                JMP       005553AB
015F:005553A6  B904000000          MOV       ECX,00000004
015F:005553AB  41                  INC       ECX
015F:005553AC  01CE                ADD       ESI,ECX
015F:005553AE  E8B5FDFFFF          CALL      00555168    <-- se desvía el call mediante                                                                  jmp 556700
015F:005553B3  AB                  STOSD
015F:005553B4  EBDC                JMP       00555392
015F:005553B6  61                  POPAD
015F:005553B7  837D0800            CMP       DWORD PTR [EBP+08],00
015F:005553BB  7509                JNZ       005553C6
 

Como anteriormente, se desvía el call hacia un lugar libre, en este caso 556700. Ahora voy a intentar explicar el principio de este importante rebuilder. Supongamos que Asprotect quiere poner su vínculo en 4B8000 y ocupará un espacio para poner nuestra IT (los nombres ) en 4B8950 (por ejemplo) y escribe el nombre en 4B8950+2 (para administrar el ordinal que en este caso no existe) y se pone en 4B8000 un vínculo hacia 4B8950-image base ( en este caso B8950, ya que la image base es 400000) . A continuación, para el caso en que la función es llamada por el ordinal, pondrá directamente el ordinal en 4B8000 (la dirección a la que apunta Asprotect) y se pone en la IAT, pero para que Windows la reconozca será necesario añadir 80000000 para que sepa que es un ordinal y no la dirección del nombre de la función-2. Por lo que respecta al import descriptor, se reconstruirá en base a que EAX contiene una referencia al nombre de la DLL en curso, será suficiente comparar EBX a cada paso con su valor precedente para saber si se está siempre en la misma parte del import descriptor. Si no es así se reconstruirá una estructura:

IMAGE_IMPORT_DESCRIPTOR struct
      OriginalFirstThunk   dd 0  ;inicio de la zona donde van a escribirse los nombres para                                   esta estructura
      TimeDateStamp        dd 0  ;se pone 00000000
      ForwarderChain       dd 0  ;se pone FFFFFFFF
      Name                 dd 0  ;se escribe el nombre de la DLL con con el nombre de las                                   funciones y se pone un vínculo con este nombre
      FirstThunk           dd 0  ;se le pone igual al OriginalFirstThunk
IMAGE_IMPORT_DESCRIPTOR ends

Ya sé que esto sólo está explicado muy por encima, pero no sé demasiado bien cómo decirlo…
Ahora se puede poner en 556700 el import rebuilder (no está nada optimizado pero funciona bien J):

015F:00556700  E863EAFFFF          CALL      00555168       <-- se restaura el call
015F:00556705  60                  PUSHAD                   <-- se guardan los registros
015F:00556706  391DFC665500        CMP       [005566FC],EBX <-- siempre en la misma                                                                 estructura del                                                                 import descriptor
015F:0055670C  7470                JZ        0055677E       <-- se pasa a la continuación
015F:0055670E  60                  PUSHAD                   

015F:0055670F  891DFC665500        MOV       [005566FC],EBX <-- se pone EBX (apunta al                                                                 nombre de la DLL) en una                                                                 variable
015F:00556715  8B15F4665500        MOV       EDX,[005566F4] <-- se pone la dirección                                                                 actual de la IAT en EDX

015F:0055671B  893A                MOV       [EDX],EDI      <-- se pone el contenido de EDI                                                                 (es decir el inicio de la                                                                 IAT correspondiente)
015F:0055671D  812A00004000        SUB       DWORD PTR [EDX],00400000 <-- se resta el valor                                                                           de la image base
015F:00556723  8305F466550008      ADD       DWORD PTR [005566F4],08  <-- se actualiza el                                                                    puntero hacia el IAD
015F:0055672A  8B15F4665500        MOV       EDX,[005566F4]   <-- y se pone en EDX
015F:00556730  C702FFFFFFFF        MOV       DWORD PTR [EDX],FFFFFFFF <-- para poner FF...                                                                          en la estructura
015F:00556736  43                  INC       EBX             <-- se hace que EBX                                                                           apunte a la DLL
015F:00556737  53                  PUSH      EBX                    <-- se pone en la pila
015F:00556738  FF35F8665500        PUSH      DWORD PTR [005566F8]   <-- así como su destino                                                                         (dirección actual                                                                         de la IT)
015F:0055673E  E8B90BA2BF          CALL      KERNEL32!lstrcpy       <-- se copia
015F:00556743  FF35F8665500        PUSH      DWORD PTR [005566F8]   <-- se calcula la                                                                              longitud del nombre
015F:00556749  E85F0CA2BF          CALL      KERNEL32!lstrlen       <-- de la DLL
015F:0055674E  40                  INC       EAX                    <-- se incrementa para                                                                         el 0 de final
015F:0055674F  8B15F8665500        MOV       EDX,[005566F8]   <-- se pone la dirección                                                                        actual de la IT (contiene
                                                                  el nombre de la DLL)
015F:00556755  81EA00004000        SUB       EDX,             <-- se resta el valor de la                                                                   image base
015F:0055675B  8305F466550004      ADD       DWORD PTR [005566F4],04     <-- se actualiza                                                                              el IAD

015F:00556762  8B0DF4665500        MOV       ECX,[005566F4]   <-- y se pone su dirección en                                                                   ECX
015F:00556768  8911                MOV       [ECX],EDX        <-- para poner el puntero                                                                       hacia el nombre de la DLL

015F:0055676A  8B59F4              MOV       EBX,[ECX-0C]     <-- se recupera el                                                                           OriginalFirstThunk
015F:0055676D  895904              MOV       [ECX+04],EBX     <-- que se pone en el                                                                           FirstThunk
015F:00556770  8305F466550008      ADD       DWORD PTR [005566F4],08     <-- se actualiza                                                                              el IAD
015F:00556777  0105F8665500        ADD       [005566F8],EAX              <-- así como la IT
015F:0055677D  61                  POPAD                      <-- se ha terminado con el                                                                   IAD y se continúa
015F:0055677E  833D6BFC740000      CMP       DWORD PTR [0074FC6B],00   <-- se tiene un                                                                               nombre de función
015F:00556785  743C                JZ        005567C3         <-- si es no, va hacia                                                                       encriptado/ordinal

015F:00556787  8B15F8665500        MOV       EDX,[005566F8]   <-- si es si, se recupera la                                                                   dirección actual de la IT
015F:0055678D  81EA00004000        SUB       EDX,00400000     <-- se resta la image base

015F:00556793  8917                MOV       [EDI],EDX        <-- y se pone un vínculo en                                                                   la IAT
015F:00556795  686BFC7400          PUSH      0074FC6B         <-- se pone la dirección del                                                                   nombre de función

015F:0055679A  8B15F8665500        MOV       EDX,[005566F8]   <-- se pone la dirección                                                                       actual de la IT
015F:005567A0  83C202              ADD       EDX,02          <-- se añade 2 para el ordinal
015F:005567A3  52                  PUSH      EDX              <-- se pone en la pila
015F:005567A4  E8530BA2BF          CALL      KERNEL32!lstrcpy  <-- y se copia la procname
015F:005567A9  52                  PUSH      EDX               <-- se pone el nombre de la                                                                    función
015F:005567AA  E8FE0BA2BF          CALL      KERNEL32!lstrlen  <-- se calcula su longitud
015F:005567AF  0503000000          ADD       EAX,00000003      <-- se añade 3 para el cero                                                                    de final y el ordinal
015F:005567B4  0105F8665500        ADD       [005566F8],EAX    <-- se actualiza la IT
015F:005567BA  61                  POPAD                       <-- se recuperan los                                                                            registros
015F:005567BB  83C704              ADD       EDI,04            <-- se pasa al siguiente
015F:005567BE  E9CFEBFFFF          JMP       00555392          <-- se efectúa el bucle

015F:005567C3  3D00000040          CMP       EAX,40000000      <-- EAX contiene la                                                                            dirección de una API
015F:005567C8  7D22                JGE       005567EC          <-- si, ordinal

015F:005567CA  83F900              CMP       ECX,00            <-- ECX= 0
015F:005567CD  751D                JNZ       005567EC          <-- si, ordinal
015F:005567CF  FF7001              PUSH      DWORD PTR [EAX+01] <-- se pasa el parámetro                                                                     para la función
015F:005567D2  E8B5E8FFFF          CALL      0055508C          <--  de desencriptado
015F:005567D7  8B15F8665500        MOV       EDX,[005566F8]    <-- se pone la dirección                                                                        actual de la IT en EDX
015F:005567DD  81EA00004000        SUB       EDX,00400000      <-- se resta la image base

015F:005567E3  8917                MOV       [EDI],EDX         <-- se pone el vínculo en                                                                    la IAT
015F:005567E5  6864FC7400          PUSH      0074FC64          <-- se pone la dirección del                                                                    nombre de la función

015F:005567EA  EBAE                JMP       0055679A         <-- vuelve a ponerse en la IT
015F:005567EC  83EE04              SUB       ESI,04            <-- se recupera el ordinal
015F:005567EF  8B16                MOV       EDX,[ESI]         <-- en EDX

015F:005567F1  81C200000080        ADD       EDX, 80000000     <-- se prepara el ordinal                                                                    para Windows
015F:005667F7  8917                MOV       [EDI],EDX         <-- se pone en la IAT
015F:005667F9  61                  POPAD                       <-- se recuperan los                                                                    registros
015F:005567F2  83C704              ADD       EDI,04            <-- se pasa al siguiente
015F:005567F5  E998EBFFFF          JMP       00555392          <-- efectúa el bucle
015F:005567FA  0000                ADD       [EAX],AL
015F:005567FC  0000                ADD       [EAX],AL
 

Creo haberlo comentado exhaustivamente, pero de todas maneras es duro de explicar. Antes de ejecutar el bucle deberá ponerse en 5566F4 el inicio del lugar donde se quiere poner el IAD (yo he puesto 4B8950, porque cuando instala la falsa tabla de importación pone vínculos hasta aquí, ya que la IAT se llegará hasta aquí). A continuación, en 5566F8 se pone el sitio donde se quiere poner la IT (yo he puesto 5566FC pensando que 200h bytes para el IAD era suficiente. Hay que poner igualmente los tres NOPs en el call de desencriptado (ver capítulos anteriores), pero para descubrirlo hay que extender la búsqueda: s 0 l ffffffff 55 8b ec 81 c4 f8 fe ff ff 53 56 8b 5d 08 8b 03 89 45 f8 8d.

Ahora se deja tranquilamente que se ejecute el bucle y que el rebuilder nos reconstruya la import table. Se deberá tener en cuenta el poner un breakpoint a la salida del bucle al nivel del POPAD. Ahora se vuelca todo esto: pagein d 4b8000 3000 c:\import.dat. Deberá tenerse cuidado de efectuar primero el volcado del programa y luego unir nuestra nueva tabla de importaciones al volcado (como se ha visto anteriormente).
Aquí no tenemos una virtual address igual al raw offset, por lo que hay que buscar en el programa. Una vez hecho esto queda aún el cambiar el EIP (BF27C) y la import table RVA (B8950) que corresponde al inicio del import descriptor.

Ahora se ejecuta el programa y... un bonito mensaje de error: Asprotect API not found! Running in unregistered mode. Je je, el mismo problema que en AMV , una parte del programa está encriptado con una clave. Bien, para evitar esta pantalla hay que poner bpx messageboxa y se fuerza el salto que la precede.
Ahora ya no queda más que crackear el programa. Tenemos un volcado con una verdadera tabla de importaciones. Confieso que este import rebuilder no está especialmente optimizado, pero bueno... Además he intentado explicar su principio pero esto no es demasiado sencillo...

VII_Conclusión

Y bien, este ensayo sobre el desempaquetado manual de Asprotect 1.0+ ha terminado. He presentado distintas maneras de proceder, pero en mi opinión, el import rebuilder final es el más interesante porque reconstruye una verdadera tabla de importaciones.
Aquí tenéis unas cuantas lecturas suplementarias si tenéis valor para ello J:

http://assembly.citeweb.net/zip/IAT.htm         Texto muy interesante sobre el añadido de una DLL a la                                                   IAT
http://christal.citeweb.net/tutor/cdilla.htm    Estudio de Cdilla para comprender mejor el call fixer
http://tamambolo.free.fr                        Estudio de Cdilla para comprender mejor el call fixer
http://www.woodmann.com/tsehp_asprotect105.htm  El texto de Tsehp sobre el volcado de Asprotect                                                   (última versión)
http://tsehp.cjb.net/                             Para los textos sobre Asprotect y Cdilla
http://assembly.citeweb.net                       Para los textos sobre el parcheado de Asprotect ...

Debo dar las gracias a muchas personas: Christal por haberme relanzado en esta protección, TeeJi por haber aceptado estudiar esta protección y por las discusiones sobre ello en IRC, y +Tsehp por haber dedicado su tiempo para responderme y sobre todo por haber encontrado y puesto a punto (con un muy buen tutorial) un primer método de volcado que me ha permitido finalmente llevar a buen fin el import table rebuilder.
Un saludo especial a Alexey Solodovnikov, quien seguramente nos prepara una buena sorpresa para el futuro...

Amistosamente,

LuTiN NoIR
 

(c) 2000 . LuTiN NoIR - city_of_bitch@caramail.com

 

www.000webhost.com