Análisis Packer 01 - Parte VI

Tamaño de letra:

Los stolen bytes

Como su nombre indica, los stolen bytes son bytes faltantes, bytes que han sido emulados por el packer y cuando el packer pasa la ejecución al programa ya no existen. Para este artículo voy a denominar stolen bytes a los bytes que hay entre el OEP y el salto a GetModuleHandleA. Este packer para la emulación de los stolen bytes hace uso de una máquina virtual para emular distintas instrucciones por ejemplo: jmp, call, jne, je etc... Analizando la máquina virtual veremos cuales son e iremos resolviendo todo este misterio.

Normalmente para resolver los stolen bytes se recurre a dos formas:

  • Copiando directamente todo la máquina virtual y código ofuscado y pegándolo en el mismo ejecutable
  • Desofuscando el código instrucción por instrucción.

Aquí lo voy a hacer de la segunda forma y por supuesto hay que entender cómo trabaja la VM, si no no vale para nada. Verás que lleva mucho trabajo.

Analizando el OEP

Vamos a partir lógicamente desde el OEP del packer y como ya conozco su dirección le voy a poner un HBP para que se detenga ahí:

OEP

Por otro lado abriremos otro OllyDBG con el dumpeado (dumpe_.exe) para ir rellenando los bytes que encontremos y yo voy a abrir además un tercer OllyDBG con un Delphi 7 para orientarme. Sé que es un Delphi 7 porque cuando analicé el dumpeado con DeDe, éste me lo dijo. Pero antes de comenzar, vamos a analizar el OEP un poco por encima. Si seguimos con F7 el OEP (la imagen anterior) llegamos rápidamente a una call, pero como el código está ofuscado voy a hacer un trace into y te lo muestro:

01B903DE Main     push ebp
01B903DF Main     prefix rep:
01B903E4 Main     lea ebp,dword ptr ds:[esi+ebx-5C]
01B903E8 Main     rol ebp,5
01B903EB Main     xor ebp,dword ptr ss:[esp+8]
01B903EF Main     mov ebp,43FCF2
01B903F4 Main     jmp short 01B903F8
01B903F8 Main     xor ebp,FFFFFFBC
01B903FB Main     lea ebp,dword ptr ss:[esp+eax+30]
01B903FF Main     sub ebp,eax
01B90401 Main     lea ebp,dword ptr ss:[ebp+ecx-30]
01B90405 Main     jmp short 01B90409
01B90409 Main     sub ebp,ecx
01B9040B Main     jmp 01B9070F
01B9070F Main     add esp,-10
01B90712 Main     mov eax,47273A
01B90717 Main     sub eax,59
01B9071A Main     lea eax,dword ptr ds:[ecx+edx*2+6D]
01B9071E Main     sub eax,6D
01B90721 Main     lea eax,dword ptr ss:[ebp+esi*2+53]
01B90725 Main     lea eax,dword ptr ss:[ebp+ecx+74BB20]
01B9072C Main     sub eax,ecx
01B9072E Main     sub eax,ebp
01B90730 Main     push 1B90A18
01B90735 Main     call 02250000                           ; Se dirige a la máquina virtual

Como puedes ver es un código ofuscado hasta una call dirigida a 2250000 que a partir de ahora la voy a llamar call VM. Lo primero que voy a hacer es desofuscar el código hasta esa call, para eso uso el fantástico plugin Code Doctor. Mira lo que encuentro:

//Packer
01520000    55                     push ebp
01520001    8BEC                   mov ebp,esp
01520003    83EC 10                sub esp,10
01520006    B8 20BB7400            mov eax,74BB20
0152000B    68 180AB901            push 1B90A18
01520010    E8 EBFFD200            call 02250000

A veces el plugin Code Doctor no lo hace correctamente así que hay que hacerlo a mano, por eso hay que intentar coger códigos pequeños. 

Inciso antes de seguir: Efectivamente como acabo de decir no hay que fiarse del plugin Code Doctor al 100% porque puede fallar. Hay que usar Code Doctor, tracear a mano con F7 y tener un Delphi 7 al lado, es un buen consejo. Simplemente quiero comentarte que perdí más de una semana buscando un error en el dumpeado y fue porque dicho plugin se equivocó en el análisis: ¡más de una semana! analizándo errores... horroroso.

Sigo con lo de antes: Me fijo en un Delphi 7 y veo que el OEP es idéntico. Fíjate también el puntero de la INIT TABLE (74BB20), ahora mira en el dumpeado y verás que está correcto. Genial.

Ya podemos escribir en el dumpeado. Me voy a él, al OEP real y voy ensamblando las 4 primeras instrucciones que ya conocemos:

//Dumpeado
0074C768 > $  55                   push ebp
0074C769   .  8BEC                 mov ebp,esp
0074C76B   .  83EC 10              sub esp,10
0074C76E   .  B8 20BB7400          mov eax,74BB20
0074C773   .  F1                   int1
0074C774   .  EE                   out dx,al                                    ; I/O command

Ahora nos encontramos con esto:

//Packer
01B90730 Main     push 1B90A18
01B90735 Main     call 02250000                           ; Se dirige a la máquina virtual

La máquina virtual emula aquí una instrucción tipo call, jmp, j(condicional). Si observas cualquier Delphi 7 verás que ahí va una call.

Antes de entrar a analizarla, la paso y examino el siguiente código hasta otro nuevo call a la VM. El siguiente código estará lógicamente en 1B90A18 aunque la dirección que verás ahora es otra porque uso Code Doctor:

//Esta dirección 0152004B la crea Code Doctor
0152004B    8D5435 39              lea edx,dword ptr ss:[ebp+esi+39]
0152004F    8D5410 C7              lea edx,dword ptr ds:[eax+edx-39]
01520053    2BD0                   sub edx,eax
01520055    8D9439 B0884600        lea edx,dword ptr ds:[ecx+edi+4688B0]
0152005C    2BD7                   sub edx,edi
0152005E    8D95 FCC77400          lea edx,dword ptr ss:[ebp+74C7FC]
01520064    2BD5                   sub edx,ebp
01520066    68 4204B901            push 1B90442
0152006B    E8 90FFD200            call 02250000

Un poco dificil de digerir, así que uso Code Doctor y...

//Esta dirección 0152004B la crea Code Doctor
0152004B    8D15 FCC77400          lea edx,dword ptr ds:[74C7FC]
01520066    68 4204B901            push 1B90442
0152006B    E8 90FFD200            call 02250000

Lo que quiero mostrar es la forma de ir desofuscando el código, ir de call VM en call VM.

Entrando en la Máquina Virtual

Ahora viene lo más pesado de todo: analizar que hace esa call 2250000. Como dije antes la VM lo que hace es emular instrucciones tipo call, jmp y saltos condicionales. Dependiendo de la instrucción, veremos en memoria un determinado valor que suele ser: 0, 1 y 2. Voy a seguir la primera instrucción de antes, ésta:

//Packer
01B90730 Main     push 1B90A18
01B90735 Main     call 02250000                            ; Se dirige a la máquina virtual

El push es muy importante ya que es la dirección de retorno. Entramos en la call:

//Packer
02250000   /65:EB 01          jmp short 02250004           ; Superfluous prefix
02250003  -|E9 569C23F3       jmp F5489C5E
02250008    2E:EB 01          jmp short 0225000C           ; Superfluous prefix
...
//Código ofuscado. Traceo con F7 hasta el subrayado en verde oscuro:
02250145    83EE 78             sub esi,78
02250148    8DB421 F471BB00     lea esi,dword ptr ds:[ecx+BB71F4]
0225014F    2BF1                sub esi,ecx
02250151    FFD6                call near esi                          ; protegid.00BB71F4

Entro en 00BB71F4:

//Packer
00BB71F4    55                 push ebp                                ; protegid.00436B9E
00BB71F5    8BEC               mov ebp,esp
00BB71F7    83C4 F8            add esp,-8
00BB71FA    53                 push ebx
00BB71FB    56                 push esi
...
//Sigo traceando con F8 hasta:
...
00BB7283    E8 14FAFFFF        call 00BB6C9C                           ; protegid.00BB6C9C
00BB7288    4F                 dec edi
00BB7289    0373 6C            add esi,dword ptr ds:[ebx+6C]
00BB728C    85FF               test edi,edi
00BB728E  ^ 77 CB              ja short 00BB725B                       ; protegid.00BB725B
00BB7290    68 AC72BB00        push 0BB72AC                            ; ASCII "111"
00BB7295    E8 2EB4FEFF        call 00BA26C8                           ; protegid.00BA26C8
00BB729A    5F                 pop edi
00BB729B    5E                 pop esi
00BB729C    5B                 pop ebx
00BB729D    59                 pop ecx
00BB729E    59                 pop ecx
00BB729F    5D                 pop ebp
00BB72A0    C2 1400            retn 14

Como puedes ver, es un lugar característico ya que vemos un ASCII "111" que realmente es una ventana de error:

Ventana protección de error

Entro en 00BB6C9C:

//Packer
00BB6C9C    55                 push ebp
00BB6C9D    8BEC               mov ebp,esp
00BB6C9F    83C4 F4            add esp,-0C
00BB6CA2    53                 push ebx
00BB6CA3    56                 push esi
00BB6CA4    57                 push edi
00BB6CA5    8BF1               mov esi,ecx
00BB6CA7    8955 FC            mov dword ptr ss:[ebp-4],edx
00BB6CAA    8945 F8            mov dword ptr ss:[ebp-8],eax
...
//Fíjate en el código que sigue que es donde quería llegar:
00BB6CE6    FFD2               call near edx
00BB6CE8    2C 01              sub al,1                              ; al = 1 (significa que emula a una call)
00BB6CEA    72 79              jb short 00BB6D65
00BB6CEC    2C 02              sub al,2
00BB6CEE    72 07              jb short 00BB6CF7
00BB6CF0    74 2F              je short 00BB6D21
00BB6CF2    E9 C2000000        jmp 00BB6DB9
00BB6CF7    8B45 F8            mov eax,dword ptr ss:[ebp-8]
00BB6CFA    8B50 68            mov edx,dword ptr ds:[eax+68]
00BB6CFD    8BC2               mov eax,edx
00BB6CFF    03C7               add eax,edi
00BB6D01    83F8 FF            cmp eax,-1
00BB6D04    75 10              jnz short 00BB6D16
00BB6D06    8BC2               mov eax,edx
00BB6D08    0345 F4            add eax,dword ptr ss:[ebp-C]
00BB6D0B    8B55 F8            mov edx,dword ptr ss:[ebp-8]
00BB6D0E    0342 10            add eax,dword ptr ds:[edx+10]
00BB6D11    E9 A8000000        jmp 00BB6DBE
00BB6D16    8B55 F8            mov edx,dword ptr ss:[ebp-8]
00BB6D19    0342 18            add eax,dword ptr ds:[edx+18]
00BB6D1C    E9 9D000000        jmp 00BB6DBE                          ; eax = 01B90D22 (dirección donde será emulada)
...

Como he puesto en los comentarios, si paramos en 00BB6CE8 podemos observar el valor de al. Según valga 0, 1, o 2 emulará a una call, jmp o salto condicional. En este caso al = 1 y emula a una call. ¿Por qué a una call? Porque si comparo con un Delphi 7, lo siguiente que aparece es una call. Y ¿dónde se ejecuta esa call? Es sencillo, si paro en 00BB6D1C veré en eax la dirección donde para. Por este motivo pongo un simple Breakpoint en 01B90D22 y pulso F9:

//Packer
01B90D22    53                 push ebx
01B90D23  ^ E9 34FFFFFF        jmp 01B90C5C

Y aquí estamos de nuevo ante un código ofuscado. Ahora estamos dentro de la call y hay que actuar igual que al principio. Para que no te pierdas voy a poner de nuevo el OEP original:

//Dumpeado
0074C768 > $  55                   push ebp
0074C769   .  8BEC                 mov ebp,esp
0074C76B   .  83EC 10              sub esp,10
0074C76E   .  B8 20BB7400          mov eax,74BB20
0074C773   .                       call direccion_desconocida

El call ya lo he explicado y direccion_desconocida es así porque no puedo poner 1B90D22 ya que es una dirección del packer. ¿Cuánto vale direccion_desconocida? Pues tú puedes injertar código donde quieras y emular la función pero yo lo que voy a hacer es comparar el dumpeado con un Delphi 7 para orientarme y veré cuales son los bytes que el packer ha modificado.

Así de este modo, y comparando con un original veo que la direccion_desconocida es muy posible que vaya aquí:

//Dumpeado
00407F82  |.  E8 5DFFFFFF          call 00407EE4                                ; <jmp.&kernel32.TlsGetValue>
00407F87  |.  85C0                 test eax,eax
00407F89  |.^ 74 DB                je short 00407F66                            ; dumpe_03.00407F66
00407F8B  \.  C3                   retn
00407F8C      E9                   db E9
00407F8D      06                   db 06
00407F8E      88                   db 88
00407F8F      78                   db 78                                        ; CHAR 'x'
00407F90      01                   db 01
00407F91      AE                   db AE
00407F92      B9                   db B9
00407F93      8A                   db 8A
00407F94      23                   db 23                                        ; CHAR '#'

Tú no hace falta que te compliques tanto, simplemente crea una nueva sección con el programa Add PE bytes y redirige ahí el código. Para llegar ahí me he fijado en muchos detalles y estoy casi convencido que está bien, por lo tanto, ya puedo modificar el OEP original:

//OEP Original - Dumpeado
0074C768 > $  55                   push ebp
0074C769   .  8BEC                 mov ebp,esp
0074C76B   .  83EC 10              sub esp,10
0074C76E   .  B8 20BB7400          mov eax,74BB20
0074C773   .                       call 407F8C

Ahora ya puedo analizar la call 407F8C que si recuerdas está en 01B90D22. Este código igualmente llama a la call VM, por lo tanto voy a ir desofuscando código (no voy a poner el código ofuscado que no ayuda en nada):

//Packer
01B90D22    53                 push ebx
01B90C5C    8BD8               mov ebx,eax                                     ; protegid.0074BB20
01B90C5E    33C0               xor eax,eax
01B90C60    C705 CCD07400 0000>mov dword ptr ds:[74D0CC],0
01B90C6A    6A 00              push 0
01B90C6C    68 6E0AB901        push 1B90A6E
01B90C71    E8 8AF36B00        call 02250000

Sigo rellenando este código en el dumpeado:

//Dumpeado
00407F8C      53                   push ebx
00407F8D      8BD8                 mov ebx,eax
00407F8F      33C0                 xor eax,eax
00407F91      C705 CCD07400 000000>mov dword ptr ds:[74D0CC],0
00407F9B      6A 00                push 0
00407F9D      90                   nop
...

Queda muy poco para llegar a donde pretendo. Sigo analizando la call VM y observo que al = 1 por lo tanto en 407F9D hay una call. Veamos a dónde va...

OllyDBG call 00407ED4

¿Qué interesante verdad? Va a 407ED4. Se va a ejecutar la primera instrucción en el código del programa. Muchos consideran a este punto como el supuesto OEP pero ahora ya sabes lo que es: es la call a GetModuleHandleA que nos lo indica correctamente el dumpeado. Así que relleno el dumpeado y te muestro todo seguido lo que hemos conseguido hasta ahora:

//OEP Original - Dumpeado
0074C768 > $  55                   push ebp
0074C769   .  8BEC                 mov ebp,esp
0074C76B   .  83EC 10              sub esp,10
0074C76E   .  B8 20BB7400          mov eax,74BB20
0074C773   .                       call 407F8C ---------
...                                                    |
                                                       |
//Dumpeado
                                            |
00407F8C      53                   push ebx    <--------
00407F8D      8BD8                 mov ebx,eax
00407F8F      33C0                 xor eax,eax
00407F91      C705 CCD07400 000000>mov dword ptr ds:[74D0CC],0
00407F9B      6A 00                push 0
00407F9D      E8 32FFFFFF          call 00407ED4         ; <jmp.&kernel32.GetModuleHandleA>
00407FA2      90                   nop
00407FA3      90                   nop
00407FA4      90                   nop
00407FA5      90                   nop
...

Hasta aquí quería llegar. Realmente esta parte de recomponer los stolen bytes pues es muy trabajosa pero ya he explicado el método a seguir:

  • Desofuscar ese código, por ejemplo con el plugin Code Doctor
  • No usar sólo el plugin, tracear también con F7 y ten a mano un Delphi 7 que te ayude.
  • Para añadir los stolen bytes puedes usar el plugin Multiassembler.
  • Analizar las call VM como se ha explicado y observar el retorno de la call.

Reparar estos stolen bytes ha resultado fácil pero todavía quedan por resolver muchos más. Recuerda que hay que tener muchísima precisión. Desofuscar todo este código no es nada sencilllo y es muy fácil equivocarse. Muchas veces para saber exactamente el código tendrás que crear logs con trace into y después desofuscarlos.

Probando el desempacado

Te puedo asegurar que reparar los stolen bytes ha llevado muchísimo trabajo, no puedes equivocarte ni en un solo byte porque luego los errores son incomprensibles. De todos modos es muy interesante porque se aprende muchísimo y es otra forma diferente de reparar los stolen bytes.

Y ahora podrás decir: ¿Ya está todo? Yo pensaba que me tocaría reparar algún pequeño código más pero me encontré con algo muy curioso que no me esperaba: ejecuté el dumpeado y saltó una excepción porque no puede leer una dirección. Esto puede ser muy común y yo pensaba que sería código que me falta por desofuscar pero cuál fue mi sorpresa cuando veo que el packer inicializa variables antes de llegar al OEP. Verás cómo resolví este inconveniente que merece un artículo entero. En la siguiente parte veremos qué código se ejecuta antes del OEP.

Última actualización: Jueves, 28 Julio 2011

No tiene privilegios para responder a los comentarios.


 
Visitas: 8567588