Buenas a todos, en la primera parte vimos un pequeño sistema anti-cheat que no permitía ejecutar Cheat Engine. Tras revisarlo, nos dimos cuenta de que la manera que tenía para detectarlo era mirando el título de las ventanas, por lo que era tan sencillo como cambiarle el título y ya estaría.
El problema es que este sistema no es que sea el mejor del mundo xD, me explico:
El sistema busca si el título contiene unas palabras que están en una lista negra definida en un array, y dos de esas palabras es “Injection” e “Injector”. Como busques por Internet alguna de esas palabras, el sistema lo detecta como que estas hackeando y te cierra el cliente por lo que ir cambiando el título no es que sea muy eficiente por lo que sí o sí tocará parchear el binario xD
Parcheando el binario con IDA Pro
A lo largo de todo este tiempo he probado un montón de desensambladores, Ghidra, IDA Pro, Radare2 (En su versión con GUI y su CLI) etc. Pero sin lugar a duda me quedo con IDA.
Con IDA podemos parchear el binario así que recuperaremos el contenido de la primera parte de esta serie y nos dirigiremos a la función AC_PrintMessageBox ya que, aparte de mostrar el mensaje, también cierra la aplicación como se muestra en la siguiente imagen
Una primera aproximación sería ir parcheando uno a uno, todos los exitProcess que hay, el problema es que cada vez que usemos el Cheat Engine siempre aparecerá el mensaje y nos interrumpiría todo el rato por lo que mejor es parchear directamente la llamada a esta función, para ello iremos a la función EnumFunc.
Después de muchas pruebas y ensayo y error, he encontrado la manera para poder parchear el binario ya que según que regiones, no podemos parchear.
Estas regiones son conocidas como Relocation Regions y por lo que tengo entendido son regiones que se construyen de manera dinámica al ejecutar un binario, me explico.
Imaginemos las siguientes instrucciones en ensamblador:
mov edx, 0
call kernel32.somefunction
Como puedes observar, aquí se está llamando a una función dentro del kernel32. Debido al ASLR que tiene el sistema operativo por defecto, que permite randomizar las direcciones de memoria para evitar exfiltrar información, esto incluye también las DLL, la única manera de saber dónde está una función dentro de una librería es durante el tiempo de ejecución.
En pocas palabras, si la dirección de memoria de kernel32.someFunction es 0x45B00, durante el tiempo de ejecución, todas las regiones marcadas como Relocations sustituirán lo que haya por 0x45B00 quedando algo tal que así:
mov edx, 0
call 0x45B00 ; Correspondiente a kernel32.somefunction
Esto es importante porque una de las cosas que pensé fue: Voy a parchear la función EnumWindows de Kernel32 para que no se llame nunca y ya está XD Pues va a ser que no XD, si hacía eso me daba unos Access Violation que daba miedo ya que cambiaba de manera dinámica lo que había parcheado…
Moraleja: Hay que tener cuidado con las llamadas que se hacen a DLLs XD
Dicho eso seguimos con el procedimiento y vamos a la función donde se hace el CALL a AC_PrintMessageBox y buscaremos al chico malo, es decir ese JMP que salta al fragmento de código que llama al CALL de la función y lo parchearemos, en este caso es el siguiente:
Si nos fijamos, loc_45BC60 es una región donde está la función AC_PrintMessageBox que queremos evitar, así que lo mejor es parchearlo con NOPS
Ahora seleccionamos la línea y hacemos Edit > Patch Program > Assemble y ahí ponemos la instrucción NOP para indicar que no haga nada, es importante añadirla tantas veces hasta llegar a la línea MOV eax, [ebp-118h]
Ahora para guardar el binario hay que ir a Edit > Patch Program > Apply paches to input files y guardamos el fichero
RECOMENDACIÓN: Os recomiendo que conservéis el fichero original por si acaso la liamos al parchear el binario xD. Así que recomiendo que marquéis la casilla Create Backup
Ahora vamos a ejecutar el juego a ver si funcionan los cambios y…
¿Os ha pasado lo mismo que a mí? El cliente se ha cerrado super rápido O.O
Parece que el servidor nos ha cerrado la conexión, la ventaja de hacerlo todo en local es que tenemos acceso a los logs por lo que podemos ver porque nos han echado xD
Si nos fijamos bien en los logs, parece que nos han echado porque el binario ACEOnline.dat no coincide con el configurado por el servidor…
Al parecer el cliente lo que hace es constantemente enviar un checksum al servidor y lo comprueba con el que tiene el servidor marcado como legítimo, si no coincide, el servidor directamente te cierra la conexión. Vaya, parece ser que hacer el bypass para ejecutar Cheat Engine no va a ser tan trivial como uno piensa xD (Vaya por dios, yo que pensaba que lo habíamos conseguido…)
Lo que se me ocurre es que parcheemos el binario para evitar que se envíe esta verificación, al menos la que corresponda con el binario, a ver si hay suerte
Parcheando el Checksum que se envía al servidor
Para entender lo que está pasando, tenemos que ver que hay por la parte del servidor y cómo funciona este juego.
Con el fin de evitar que la gente modifique según qué cosas, tú puedes decirle al servidor que ficheros quieres que se mantenga su integridad. Estos se colocan en 3 carpetas diferentes:
- Res-Obj
- Res-Exe
- Res-Tex
Si te fijas, excepto la de Res-Exe, son carpetas que coinciden con el cliente:
La carpeta Res-Obj contiene los assets gráficos mientras que la carpeta Res-Tex contiene entre otros ficheros la base de datos del juego (Con esto volveremos más adelante porque es importante xD)
Por otro lado, Res-Exe como su nombre puede indicar contiene los ejecutables del cliente, es decir Launcher.atm y ACEOnline.dat.
Para verificar la integridad de los ficheros, cada X tiempo, el cliente envía el paquete T_FC_INFO_CHECK_RESOBJ_CHECKSUM con un hash MD5 al servidor para que este verifique la integridad de los ficheros. Si coinciden, es decir, tienen el mismo hash, el fichero es legítimo, si no pues te cierra la conexión.
Honestamente para evitar esto podríamos hacerlo a nivel de red, es decir, podemos montar un Proxy y cada vez que veamos el paquete TT_FC_INFO_CHECK_RESOBJ_CHECKSUM, simplemente dropearlo, pero dejaremos eso para otro capítulo ya que habría que estudiar el protocolo y demás por lo que haremos la aproximación más “complicada”. Efectivamente, parcheando el binario xD.
Para ello lo que vamos a pensar es en como creemos que podría funcionar esta función.
Una cosa tenemos segura y es que el juego de algún modo tiene que obtener el nombre del proceso y ¿Cual creéis que es la manera más fácil de obtener el nombre del proceso? Efectivamente mediante Strings o mediante la API de Windows GetModuleFileNameA pero si buscamos ACEOnline.dat, no aparece nada:
Esto quiere decir que la única manera de obtener el nombre del proceso es con la función GetModuleFileNameA por lo que vamos a ir a esa función y ver todas las referencias
Al parecer hay 4 referencias, 2 en la función sub_497350 y 2 en TopLevelExceptionFilter. Obviamente la que más nos interesa es la sub_497350 xD asi que generamos el pseudocódigo de este:
Si analizamos la función podemos decir que hace lo siguiente:
- Obtiene el nombre del proceso con GetModuleFileNameA
- Divide el nombre del proceso en Localidad, Directorio, Nombre y extensión
- La función sub_41E5D0 parece una especie de sprintf donde junta solamente el Nombre y la extensión
- La función sub_4CCE90 si la analizamos aparece lo siguiente:
Esto parece que lo que hace es obtener un hash a partir del fichero, esto lo se porque lee todo el fichero con fopen y seguramente el resto de funciones sirve para crear el hash MD5 por lo que la llamaré GetHashFromFile
- En el caso de que no se pueda crear el hash o falle algo envia a la ventana del juego que se cierre con SendMessageA (El 0x10 que aparece como segundo parámetro corresponde a WM_CLOSE)
- El resto bien bien no sé qué es, pero parece que esté preparando el paquete para enviárselo al servidor, esto lo sé por la función sub_71D020 xD
El motivo de porque la función sub_71D020 es la encargada de enviar el paquete al servidor es debido a que si profundizas dentro de la función al final del código generado por IDA aparece lo siguiente:
La función WSASend viene de Windows Socket API y esta función es la encargada de enviar un paquete a un socket por lo que voy a renombrar esta función por SendPacketToServer
Dicho esto, volvemos a la función sub_497350 y la renombrare por SendClientChecksum y podemos observar que se llama dos veces en la función sub_466C30 y en la función sub_493890:
Vamos a cambiar dentro de estas dos funciones el CALL a esta función y lo substituiremos por NOPs:
Ahora procedemos a ejecutar el juego y abrir el Cheat Engine y… ¡¡¡Voila!!! Ya podemos usar el Cheat Engine libremente ^^
Una cosa curiosa es que, si abro el juego y luego el Cheat Engine, el juego se me cierra, pero en cambio sí abro el Cheat Engine primero y luego el juego ya puedo utilizarlo sin problemas y no sé por qué sucede eso xD. Lo importante es que ya lo tenemos funcionando.
Conclusiones
Como conclusión podemos ver que se ha conseguido parchear el binario para que funcione el Cheat Engine, esto se debe a que hemos conseguido parchear la verificación de la firma de los ficheros. Esta verificación se realiza a través de una petición al servidor enviando un hash para que este luego lo compruebe contra el hash del fichero original que se encuentra almacenado dentro de sus carpetas.
Al parchear la función que se encarga de enviar ese paquete, el juego no se cierra al modificarse el binario.
Es importante recalcar que parchear el binario sigue siendo a día de hoy una técnica totalmente viable para hacer bypasses de anticheats.
En siguientes posts empezaremos a hacer un pequeño tutorial rápido de que cosas se pueden hacer con Cheat Engine porque así podremos hacer nuestros cheats sin depender de este.
Muchísimas gracias y nos vemos en el siguiente post ^^