Karpoff Spanish Tutor 1999-2002

Juego:

Messiah


 

PROTECCIÓN: LaserLock 5
Descripción: Protección comercial
Dificultad: Media
Herramientas: SoftIce 4.01, IceDump 6.020, Hiew 6.55, ADump, Hex WorkShop
CRACKER: GáDiX & SkUaTeR   FECHA: 24/05/2001

 

 

 INTRODUCCIÓN

Hola amigos! Tras unos meses de farras, estudios y demás proyectos, vamos a eliminar una vieja cuenta pendiente: el LaserLock

Primero de todo vamos a hacer un breve resumen de las características de esta protección:

- No encripta ninguna sección del ejecutable protegido
- No lleva antidebug 
- No aumenta el tamaño del ejecutable, la protección se basa en una DLL la cual no tiene nombre fijo (en este juego se llama 'Mes3ddll.dll'). El nombre de ésta lo podéis averiguar con un editor PE y es el primer IMAGE_IMPORT_DESCRIPTOR. Esta librería siempre tiene una función que es 'CallDLL'.
- REDIRECCIONA las llamadas a las API'S

Así pues si corregimos las llamadas a las API'S tendremos nuestro juego libre de LaserLock ;) Vamos a ver 2 métodos de hacerlo. La elección final, queda en vuestras manos

Pero primero vamos a estudiar un poco por encima la protección. 

Si cargamos MESSIAHD3D.EXE con el Loader del Sice vemos lo siguiente:

:004DD7C0    PUSH EBP <--- Entry Point 
:004DD7C1    MOV EBP,ESP 
:004DD7C3    PUSH FF 
:004DD7C5    PUSH 004E7430 
:004DD7CA   PUSH 004E12E8 
:004DD7CF    MOV EAX,FS:[00000000] 
:004DD7D5    PUSH EAX 
:004DD7D6    MOV FS:[00000000],ESP 
:004DD7DD   SUB ESP,58 
:004DD7E0    PUSH EBX 
:004DD7E1    PUSH ESI 
:004DD7E2    PUSH EDI 
:004DD7E3    MOV [EBP-18],ESP 

:004DD7E6    CALL [004E8868] <--- Llamada a LaserLock  
:004DD7EC   XOR EDX,EDX <--- Dirección de retorno 

Si trazamos un poco en la llamada a LaserLock encontramos esto:

:10001D4B    PUSH EAX 
:10001D4C    PUSH EBP 
:10001D4D    MOV EBP,ESP 
:10001D4F     PUSH EAX 
:10001D50     PUSH EBX 
:10001D51     PUSH ECX 
:10001D52     PUSH EDX 
:10001D53     PUSH ESI 
:10001D54     PUSH EDI 
:10001D55     MOV EAX,SS:[EBP+08] <------------- (0) 
:10001D59     PUSH EAX 
:10001D5A    CALL 100018E1 <-----------------------  (1)
:10001D5F     ADD SP,04 <------------------------------ (2) 
:10001D63     MOV EAX,DS:[EAX] <----------------- (3) 
:10001D66     CMP BYTE PTR [100168EC],01 <--- (4) 
:10001D6D     JZ 10001D7F 
:10001D73      MOV SS:[EBP+04],EAX 
:10001D77     POP EDI 
:10001D78     POP ESI 
:10001D79     POP EDX 
:10001D7A    POP ECX 
:10001D7B    POP EBX 
:10001D7C    POP EAX 
:10001D7D    POP EBP 
:10001D7E     RET 
:10001D7F     MOV SS:[EBP+08],EAX 
:10001D83     POP EDI 
:10001D84     POP ESI 
:10001D85     POP EDX 
:10001D86     POP ECX 
:10001D87     POP EBX 
:10001D88     POP EAX 
:10001D89     POP EBP 
:10001D8A    ADD ESP,04 
:10001D8D    RET  

(0) Aquí EAX=004DD7EC (dirección desde donde hemos sido llamados+6) 

(1) Esta es la gran llamada a LaserLock. Esta rutina se encarga de todo: de mostrar el LOGO, 
de pedir el CD, pero sobretodo de devolver la API correcta. La primera vez que se pasa por la rutina se muestra el LOGO del juego (no en todos los juegos) y llama a la primera API (normalmente GetVersion). La segunda vez que pasa comprueba que esté insertado el CD original (atentos a este dato que como veremos más adelante es importantísimo) y llama a la siguiente API. Las sucesivas veces solo devuelve la API correcta 

(2) EAX=004E7130 
:what eax 
The value 4E7130 is (a) MESSIAH!.rdata+0130 
:d eax 
0177:004E7130    FD 33 F8 BF F6 E1 F6 BF-85 77 F6 BF 5E CA F7 BF    .3.......w..^... 
:what *eax 
The value BFF833FD is (a) KERNEL32!GetVersion 


(3) EAX pasa a valer BFF833FD (GetVersion). Aquí o en el punto 2 PODRÍAMOS llamar a nuestra
rutina correctora de API'S, ya que en EAX tenemos la dirección de .rdata que queremos y 
en [EBP+04] desde donde hemos sido llamados. Pero todo se nos jode por el punto (4) 

(4) si BYTE PTR [100168EC]=1 salta a 10001D7F. Esto es debido a LaserLock también redirige
"los saltos" a las API'S. Os hago un ejemplo con GetVersion:

Una llamada "normal" sería:

004DD7E6   FF1530714E00   call KERNEL32!GetVersion 

pero si BYTE PTR [100168EC]=1 sería:

004DD7E6   FF2530714E00   jmp KERNEL32!GetVersion 

Fíjaos bien en el FF25, porque nuestra rutina tendrá que cambiar FF15 por FF25 si se da este caso.

 

 

 

 AL ATAKE


MÉTODO GÁDIX

Bien con toda esta información ya podemos escribir nuestra rutina correctora. Primero de todo hemos de parchear el código del LaserLock para que salte a nuestra rutina:

:10001D59     PUSH EAX 
:10001D5A    CALL 100018E1 
:10001D5F     ADD SP,04 
:10001D63     MOV EAX,DS:[EAX] <--- Lo cambiamos a  JMP  ADump_Adress

Y la rutina es la siguiente:

ADUMP_ADRESS: 
      mov ecx,00401000    ; Inicio de .text 
      mov edx,004E7000   ; Fin de .text
LOOP_BUSCA: 
      cmp word ptr [ecx],15ffh   ; FF15 son los opcodes de "call [xxxxxxxx]"
      jnz SIGUE_BUSCANDO 
      push ecx     ; Metemos en la pila la siguiente llamada a LaserLock 
      jmp ecx      ; Saltamos a la llamada 
SIGUE_BUSCANDO: 
      inc ecx 
      cmp ecx,edx          ; Hemos llegado al final de .text ??? 
      jnz LOOP_BUSCA    ; Si no es así, sigue buscando
      int 3                       ; Cuando rompamos aquí ---> todo OK
CORRIGE: 
      pop ecx 
      mov ebx,[esp+8]                   ; EBX = dirección de "Call [LaserLock]"
      mov dword ptr[ebx+2],eax
      cmp byte ptr[100168ec],1      ; Miramos si era salto o call
      jnz ADUMP_ADRESS               ; Si es call no hay que corregir nada
      mov dword ptr[ebx+1],25h    ; Lo convertimos a JMP API
      jmp ADUMP_ADRESS              ; Vamos a por la siguiente llamada

Ahora:

"r eip ADump_Adress"
"BPINT 3"

Le damos al F5 y en cuestión de segundos aparecemos en el INT 3. Volcamos la sección .text y la sobreescribimos en el fichero. Ejecutamos y ZASH! Obtenemos un bonito fallo de protección general. Si investigamos el tema, nos damos cuenta de que LaserLock comprueba el CRC de su código:

BPM 10001D5F R

Y aparecemos en la rutina de chequeo del CRC. Para los interesados, aquí podeis ver la rutina

Tras dos F12, vemos lo siguiente:

:10008CC0   CALL 10008AB8 
:10008CC5   ADD ESP,04 
:10008CC8   MOVZX EAX,AX 
:10008CCB   MOV ECX,[EBP-08] ; [EBP-08] contiene el valor del CRC
:10008CCE   AND ECX,0000FFFF 
:10008CD4   XOR EAX,ECX  ; se XORea el valor de EAX con el valor del CRC (ECX)

Como podéis comprobar el valor del CRC en ningún momento se compara, lo xorea directamente con el valor de EAX. Además el chequeo se va haciendo por partes, con lo cual las posibilidades de "trapichear" con el CRC son escasas. Pero entonces......cómo redirigimos el código a nuestra rutina? Fácil,  con un BPM y una pequeña "macro":

BPM 10001D63 X do "r eip CORRIGE;g;"

"CORRIGE" es pues eso, la dirección de memoria de "CORRIGE:" de nuestra rutina correctora. Bueno, pues otra vez ponemos el EIP en ADump_Adress, BPINT 3 y F5. Tras unos 3-5 minutillos llegamos otra vez al INT 3. (NOTA: es normal que parezca que el Sice se vuelva loco). Volcamos, pegamos, ejecutamos y....ZASH! otra vez!!! Bueno......antes que meterle dos patadas a nuestro querido ordena, decidimos irnos a la cama, mañana será otro día...

Sip. Con un poco más de calma, nos ponemos a investigar el tema.....a ver....problema de CRC no es porque no escribimos en su código (los BPM's al contrario que los BPX's no parchean en el código). Hacemos un rutina que deje la pila "intacta" y tampoco. Tras otro ratito más calentándonos la cabeza, damos con la solución: el muy cabrón comprueba si hemos corregido la anterior llamada!!! De nuevo para los interesados, aquí tenéis el código que se encarga de ello.

Tenemos dos opciones. La primera: LaserLock sólo comprueba si hemos parcheado la anterior, por tanto si hacemos que nuestra rutinilla vaya parcheando posteriormente asunto solucionado. 

La segunda se aprovecha de una gran "puerta abierta". Quiero pensar que lo han hecho para ganar velocidad porque sino....Os explico. LaserLock usa un contador para saber el número de veces que ha sido 

:10001C8B     CMP DWORD PTR [100168F8],32   ; ¿ Hemos pasado 32 veces ?
:10001C92      JLE 10001CD0   
; Si hemos pasado 32 o menos se va sin corregir
:10001C98      MOV EAX,[1001690C] 
:10001C9D     ADD [EBP+08],EAX 
:10001CA0     MOV EAX,[EBP+08] 
:10001CA3     SUB EAX,05 
:10001CA6     MOV [EBP-1C],EAX 
:10001CA9     XOR EAX,EAX 
:10001CAB     MOV AL,[100168EC] 
:10001CB0     CMP EAX,01        
<--- 0=CALL; 1=SALTO 
:10001CB3     JNZ 10001CBF                          
:10001CB9     MOV EAX,[EBP-1C]                 
:10001CBC     MOV BYTE PTR [EAX],25
<---  Corrige el FF25 (si es salto)
:10001CBF     MOV EAX,[EBP+08] 
:10001CC2     SUB EAX,04 
:10001CC5     MOV [EBP-18],EAX 
:10001CC8     MOV EAX,[EBP-04] 
:10001CCB     MOV ECX,[EBP-18] 
:10001CCE     MOV [ECX],EAX   
<--- Corrige la llamada a la API 
:10001CD0     MOV EAX,[EBP-04] 
:10001CD3     JMP 10001CD8 
:10001CD8     POP EDI 
:10001CD9     POP ESI 
:10001CDA     POP EBX 
:10001CDB     LEAVE 
:10001CDC     RET 

Alguien puede pensar que si NOPeamos el salto en 10001C92 asunto resuelto, pero NO. Recordad que no podemos parchear...pero tranquilos que esta vez la solución es fácil. Haremos que nuestra rutina ponga a 33 el contador del LaserLock (mov dword ptr[100168F8], 33h) o con el propio Sice ("e 100168F8 33").  Y aquí viene el gran "BackDoor" del que os hablaba:

:100019ED    CMP DWORD PTR [100168F8],00 
:100019F4     JNZ 10001A13 
:100019FA    CALL 100013AA 
:100019FF    MOV EAX,[10014594] 
:10001A04    PUSH EAX 
:10001A05    CALL 1000337A
  <--- Muestra el LOGO
:10001A0A   ADD ESP,04 
:10001A0D   ADD [10016914],EAX 
:10001A13    CMP DWORD PTR [100168F8],01 
:10001A1A    JNZ 10001A3D 
:10001A20    CALL 100057E2  
<--- Comprueba el CD
:10001A25    ADD [10016914],EAX 
:10001A2B   CMP DWORD PTR [100145AC],01 
:10001A32    JNZ 10001A3D 
:10001A38    CALL 10001340 
:10001A3D   CMP DWORD PTR [100168F8],02 
:10001A44    JNZ 10001A55 
:10001A4A   CALL 10008393 
:10001A4F   ADD [10016914],EAX 
:10001A55   CMP DWORD PTR [100168F8],02 
:10001A5C   JLE 10001A8E 
:10001A62   CMP DWORD PTR [100168F8],32 
:10001A69   JGE 10001A8E 
:10001A6F   CALL 100086A2  
<--- Se asegura de que ha comprobado el CD
:10001A74   ADD [10016914],EAX 
:10001A7A   XOR EBX,EBX 
:10001A7C   CALL 10008590 
:10001A81    LEA EAX,[EAX*2+EAX] 
:10001A84    SUB EBX,EAX 
:10001A86    NEG EBX 
:10001A88    SUB [10016914],EBX 
:10001A8E   CMP DWORD PTR [100168F8],32 
:10001A95    JNZ 10001AD3 
:10001A9B   XOR EAX,EAX 

Comprobadlo vosotros mismos....pone el dword 100168F8 a 33h....pasáis en algún momento por las comprobaciones del CD???  :d

 MÉTODO SKUATER

Buenas a todos ....

Como veis Gádix a explicado detalladamente esta protección, con lo que pensando un poco y recopilando datos podemos usar este método...

Primero recopilemos datos:

  Como dice Gádix esta protección tiene  crc´s y checkeos para saber si el código del programa ha sido  alterado  (por lo menos mientras dependa de la dll), aunque como veremos estos checkeos se puede decir que son INÚTILES ....

Ya que todo depende de la forma en la que planteemos el problema.

Así pues mi planteamiento es el siguiente:

  -  Desviar el EIP del programa a una rutina en memoria la cual se encargue de reconstruir y almacenar las apis.

Pues entonces echemos un vistazo dentro de la rutina de laserlock:

 

:10001D4B    PUSH EAX 
:10001D4C    PUSH EBP 
:10001D4D    MOV EBP,ESP 
:10001D4F     PUSH EAX 
:10001D50     PUSH EBX 
:10001D51     PUSH ECX 
:10001D52     PUSH EDX 
:10001D53     PUSH ESI 
:10001D54     PUSH EDI 
:10001D55     MOV EAX,SS:[EBP+08]

:10001D59     PUSH EAX 
:10001D5A    CALL 100018E1 <-----------------------  (1)
:10001D5F     ADD SP,04

:10001D63     MOV EAX,DS:[EAX]

:10001D66     CMP BYTE PTR [100168EC],01 <--- (2) 

(1)   Es la rutina encargada de devolvernos la api correcta (mas bien el puntero a la api correcta)

(2)   Es la comprobación que indica si es JMP  o CALL

 

Bien pues con estos 2 datos, el inicio y tamaño de text tenemos mas que suficiente para reconstruir todas las llamadas a las apis.

 

Este seria el código de reconstrucción:

   mov ecx,InicioDeTExt
   mov eax,TextFin
   xor edi,edi 
repite: 
   cmp word ptr[ecx],15ffh
   jnz incre

   pushad
   add ecx,6 
   call Corrige
   popad
   add edi,5
   inc ebx ; ebx se incrementa cada vez que corregimos 
incre:
   inc ecx
   cmp ecx,eax ; se comprueba si hemos llegado al final 
   jnz repite 
   Int 3 ; pararemos aquí si todo va bien.



;----------------------------------------------------------------------------
; Procedimiento: Corrige
; Descripción: Obtiene la llamada correcta y la deposita en la zona de 
; almacenamiento. jeje estamos de suerte y esta rutina nos
; devuelve la dirección de la api en .Rdata
;
; Entrada: ECX = Offset a pushear para que devuelva el valor correcto
; EDI = Puntero A la zona donde guardamos la api corregida
;
; Salida: 
;----------------------------------------------------------------------------


Corrige:

   push edi                  ; guardamos edi y ebp ya ke la dll se
   push ebp                  ; lo ventila


   push ecx                  ; pusheamos el offset que hemos obtenido
   call dll_corrige(1)       ; se llama a la rutina que corrige las apis 
   pop ecx                   ; corregimos la pila porque se queda 
                             ; insertado el Push ECX
   pop ebp                   ; restauramos ebp 
   pop edi                   ; restauramos edi 
   mov ecx,Codigo_Mem_fin    ; cargamos en ECX el puntero al final del 
                             ; código de memoria que será a partir de 
                             ; donde depositaremos las apis
   add ecx,edi               ; lo sumamos en ecx y ecx será nuestro 
                             ; puntero a la zona de almacenamiento
   mov [ecx],eax             ; guardamos en [ecx] el puntero a
                             ; rdata hacia la api correcta.

   mov eax,SaltoCall         ; cargamos en eax la variable que indica si
                             ; es salto o call

   cmp byte ptr [eax],1
   jz zero

   mov byte ptr[ecx+4],0     ; colocamos 0 si es call
   jmp fuera

zero:
   mov byte ptr[ecx+4],1     ; o 1 si es JMP

fuera:
   ret 

db "[APIS BOLCADAS]"

Codigo_Mem_fin:

 

(este código ha sido sacado de Delaserlock v1.0)

 

Con este código ensamblado en la memoria que reserva Adump ,y simplemente desviando el EP del ejecutable a esta zona de memoria , tendremos una lista de las apis correctas.

Con lo que solo nos queda volcar las apis reconstruidas a un fichero e inyectarlas en frío en el ejecutable protegido, para ello cuando paremos en la INT 3 solo tendremos que ver el numero de apis corregidas multiplicarlo por 5 (que es 1 dword para la api correcta + 1 byte para determinar si es salto o call) y volcar a partir de Codigo_Mem_fin con el resultado obtenido (/dump codigo_mem_fin  ebx*5 c:\listaapis.bin)

 

Ahora simplemente tenemos que inyectar las apis obtenidas, en el fichero original teniendo en cuenta que si el 4 byte de la apis recontruidas es 1 significa que hemos de cambiar el call por un salto el código sería algo así:

 

Ecx= Inicio de text

Ebx=rutina de laserlock

        

Ecx= Inicio de text
Ebx= rutina de laserlock

    xor     edx,edx 
bucle: 
    cmp word ptr[ecx],15ffh
    jnz inc_b
    cmp dword ptr[ecx+2],ebx
    jnz inc_b
  
    mov edi,Inicio_Apis_Recontruidas
    add edi,edx
    cmp byte ptr[edi+4],0
    jz es_call 
    mov byte ptr[ecx+1],25h 
es_call:
    mov edi,dword ptr[edi]
    mov dword ptr[ecx+2],edi
    add edx,5 
    inc conta
 
inc_b:
    inc ecx 
    cmp eax,ecx
    jnz bucle

Pues como veis haciendo estas cositas también obtenemos un archivo reconstruido que no necesita del CD original para funcionar.

Y ajustando un poco mas la rutina de reconstrucción teniendo en cuenta lo que dice Gádix, no habría problema para prescindir del cd original o incluso de este ultimo código encargado de inyectar las apis correctas en su sitio. Os preguntareis: ¿y porqué no lo has hecho así ....?  pues mu sencillo , como he dicho el código de reconstrucción lo he sacado de Delaserlock v 1.0, que es una utilidad de cosecha propia J y ya que tengo el código a mano, ¿porque no usarlo? Jeje, además si os lo damos todo hecho no tendríais el placer de experimentar y disfrutar. ¿se anima alguien ha hacer un Delaserlock? Es muy sencillito y no necesitareis el Cd original ....

 

Enga Salu2

y

Punk Not Dead  ....

 

REMATANDO LA FAENA

Nuestro crack funciona perfectamente, pero nosotros, que somos unos tíos elegantes, no vemos necesidad que nuestro crack dependa de la DLL de LaserLock. Así que vamos a eliminar esta dependencia de la IAT. Con PEditor 1.7 vemos lo siguiente:

Vemos que la siguiente librería a la del Laser es 'DINPUT.dll'. Fácil. Abrimos el fichero con el HexWorkShop  y buscamos la cadena 'DINPUT.dll'. La encontramos en dos offsets....aplicando un poco de ZEN deducimos que es la segunda (alrededor de la primera hay strings ;)  que está en el offset 000E7CF6h. Invertimos los bytes de ese offset ( F67C0E00) y buscamos esos valores. De nuevo nos salen dos offsets, cualquiera de los dos nos vale. Yo para explicároslo cogeré el del offset 000E799Ch

Conociendo la estructura IMAGE IMPORT DESCRIPTOR sabemos que:

-1- -2- -3- -4- -5-
  Original First Thunk   TimeDateStamp   ForwardChain   Name   First Thunk
607A 0E00 0000 0000 0000 0000 F67C 0E00 1C70 0E00

Pues ya lo tenemos: el 000E7990h es el RVA de nuestra tabla de importación. Lo corregimos con PEditor, ejecutamos y.......FUNCIONA!!! ;)

 

PALABRAS FINALES

El continuo retraso de la salida de este tutorial ha propiciado que saliese una nueva versión de LaserLock. Sabiendo crackear ésta no deberíais tener excesivos problemas con la nueva. De todas formas ya sabéis dónde encontrarnos: en el canal #cdcracking del IRC Hispano ;)

Esto es todo, un saludito para los amigos de K-FOR, WKT, EiThel y MYTH

Gádix & Skuater [GaKeSkSum Team]

 

 

Karpoff Spanish Tutor: Página dedicada a la divulgación de información en Castellano, sobre Ingeniería Inversa y Programación. Email "Colabora con tus Proyectos"
www.000webhost.com