c0de
Administrador
- Desde
- 19 Abr 2020
- Mensajes
- 329
- Tema Autor
- #1
¿Qué es “CodeCave”?
Una técnica muy usada en el Game Hacking, un CodeCave, se describe literalmente a su traducción, es hacer un hueco, túnel o cueva dentro del código normal, cambiando el flujo, hacia una rutina arbitraria, de esa manera se modifica el compartimiento normal, de una aplicación (en la demostración).
¿Qué veremos en esta charla?
Dos formas de hacer un CodeCave, la primera usando opcodes y reservando espacio en la memoria, esto puede ser muy tedioso si tenemos un código largo, la segunda es la manera fácil será saltar hacia nuestro propio espacio (para usar instrucciones), donde tendremos la rutina arbitraria.
Planteando el Problema
Ya mencionamos cómo funciona el CodeCave, pero, ¿en qué momento debemos usarlo?
Pasos Básicos
1er Paso: Obtener la dirección indicada que modificará el flujo hacía una rutina creada por nosotros.
2do Paso: Calcular el desplazamiento desde donde se hará el cambio de flujo, hasta nuestra rutina.
3er Paso: Crear la rutina que modificará el comportamiento arbitrariamente.
4to Paso: Regresar al flujo, como normalmente lo haría la aplicación, como si nada hubiera pasado.
Funcionamiento del CodeCave
En pocas palabras si por alguna acción la vida disminuye en 1, con el CodeCave podemos modificar el flujo, hacer que valla hacia nuestra rutina y en vez de disminuir, incrementará en 2 la vida, esto es en pocas palabras, el concepto del CodeCave.
PRIMERA PARTE (usando opcodes y reservando espacio)
1er Paso. Obtener la dirección indicada.
Primero necesitamos encontrar la dirección que disminuya la vida en 1. Para esto podemos usar un debugger cualquiera, o para mayor facilidad el CheatEngine (desde ahora CE), que fue concebido para estas labores.
1er Paso. Dirección/Address
Usaremos está instrucción ficticia y diremos que la instrucción que disminuye la vida es dec [ebx+00000454]:
Y Nuestro objetivo es cambiar la instrucción dec [ebx+00000454] (es la cual decrementa en 1 la vida) por add [ebx+00000454], 2 (es la cual sumará en 2 la vida).
2do Paso. Calculando el Desplazamiento
2do Paso. Calcular el desplazamiento hacia nuestra rutina arbitraria.
Necesitamos encontrar el desplazamiento que hay entre la instrucción que disminuye la vida hasta nuestra rutina.
El código que resuelve esto:
Siendo lpMemory la variable que contiene la dirección donde comienza nuestra rutina.
Calculamos el desplazamiento que hay de la dirección, 004226BCh hacia la dirección contenida en lpMemory, el desplazamiento lo tenemos en EDX.
Restamos los 5 bytes del JMP (JUMP, es el salto que va a cambiar el flujo hacia nuestra rutina) esta ocupa 5 bytes y la tenemos que restar. EDX = Desplazamiento u Offset.
3er Paso. Rutina
3er Paso: Crear la rutina que modificará el comportamiento arbitrariamente.
Esta Rutina contiene tanto la rutina que cambia una instrucción, así como el código que reserva espacio del HEAP, lo cual veremos en el siguiente código:
Lo primero que hacemos es reservar memoria donde podamos escribir; el puntero hacia dicho lugar en memoria lo contiene la variable lpMemory.
Especificamos el flag GMEM_MOVEABLE, este deja a Windows mover el bloque de memoria, para consolidar la memoria. La bandera (flag) GMEM_ZEROINIT le dice a GlobalAlloc que rellene el nuevo bloque de memoria reservado con ceros.
Cuando GlobalAlloc vuelve satisfactoriamente, EAX contiene el manejador (handle) al bloque de memoria reservado. Le pasamos este manejador (handle) a la función GlobalLock que nos devuelve un puntero al bloque de memoria.
Esto lo hacemos por seguridad ya que no todas las páginas de memoria tienen permiso de lectura o escritura.
Explicado en el Paso2.
A partir de aquí estamos modificando la instrucción dec [ebx + 00000454], por un JMP [lpMemory].
0E9h (Opcode) = JMP
EDX = Desplazamiento hacia la dirección allocada ([lpMemory]).
Como la instrucción dec [ebx + 00000454], tiene de tamaño 6 bytes, el JMP 5 bytes, hay una que sobra y esa la NOPeo.
Pasamos el puntero de nuestro espacio de memoria reservada a EDX.
Cambiamos los primeros opcodes FF 8B a 83 83, siendo los opcodes 83 83 = add [ebx…….
Seguimos armando la instrucción dándole el offset 00000454, y tendríamos: add [ebx + 00000454]
Esta última instrucción es el valor con que será sumando el primero operando, con esto tendríamos la instrucción de esta manera: add [ebx + 00000454], 2
Cambió la instrucción : dec [ebx + 00000454] por add [ebx + 00000454], 2
Muchos dirán porque ha puesto 00000454 del offset y no 54040000, y así para los demás opcodes, pues fácil “LittleEndian” tiene la culpa, es el formato en que se almacenan los datos, en palabras fáciles lo que hace el microprocesador es almacenar los valores pero al revés (Little Endian es ese formato).
Ejemplo: 004226BC - FF 8B 54040000 - dec [ebx+00000454] Donde el offset que ha sido almacenado es 54040000. Entonces si yo muevo el valor de 00000454, lo que hace el microprocesador es voltear ese valor a 54040000, y como estamos trabajando con opcodes, será interpretado como 00000454.
4to Paso. Regresando el Flujo
4to Paso: Regresar al flujo, como normalmente lo haría la aplicación, como si nada hubiera pasado.
Calculando el tamaño que ocupamos en la rutina arbitraria, obtendremos el desplazamiento desde el final del código arbitrario, hasta la address que ejecutaría normalmente el GAME.
Ocupamos 7 bytes en la anterior instrucción, y tenemos que sumarle 5 bytes del JMP del regreso del flujo, en total 12 bytes usaremos y la dirección final de la rutina está en EAX. EAX = Dirección final de la rutina arbitraria.
La siguiente dirección que se ejecutaría como si nada hubiera pasado:
PRIMERA PARTE - Resumen
En resumen esto fue lo que hicimos:
Hicimos un JMP, hacia nuestra rutina (CodeCave), ejecutamos la instrucción que modifica el comportamiento arbitrariamente, y regresamos el flujo, como si el comportamiento de la aplicación no hubiera cambiado, así nos evitamos del crash.
PRIMERA PARTE - Código
CODIGO
SEGUNDA PARTE (Usando nuestro propio espacio)
Después de hacer crear la primera parte, está segunda fue concebida al día siguiente luego de pensar porque hacer todo en opcodes y no usar mi propio espacio, quien sabe porque no se me ocurrió esto antes, o estaba tan absorto depurando el código anterior, que no tenía espacio para pensar en otra cosa, pero pensando que lo primero no es factible para códigos largos, así qué se concibió lo siguiente. Les pego el code, gran parte ya está explicado, los pasos son los mismos, lo único que cambia es el 3er Paso y 4to Paso, veamos como.
Segunda Parte - Código
3er Paso. Rutina
Lo que hacemos ya no es saltar a un espacio de memoria reservada desde el heap, por la api GlobalAlloc, si no saltar a nuestro propio espacio así nos evitamos de liar con opcodes e imaginarnos el dolor de cabeza si fuera un código largo.
Al usar nuestro propio espacio, podemos escribir la instrucción tal cual la deseamos.
¿Por qué pushad y popad?, Esto es por seguridad y buena costumbre ya que esto es un CodeCave y la rutina arbitraria que codeamos podría ser mucho más larga que esta, seguramente usaremos registros que necesitan preservar su valor inicial, y seguramente esta aplicación daría error si no hubiera preservado sus registros.
La idea es dejar intactos todos los registros y aunque solo usamos EBX. Ahora, ¿por qué esta en comentarios?, es que en los microprocesadores x64 los mnemónicos pushad y popad quedaron por decirlo así obsoletos, no son reconocidos ya que imagínense cuanto sería el tamaño que ocuparía en la pila, así que deberíamos usar otros métodos para preservar los registros que usemos.
4to Paso. Regresando el Flujo
Sí tanto hable de registros y preservarlos, ¿Por qué usa ESI, esto puede influir en algún error de la aplicación?, pudiera influir si antes no somos observadores, recordemos como está conformado las instrucciones ficticias que usamos para efecto del paper.
Esta vez ya no se calcula el desplazamiento dejamos ese trabajo al microprocesador hacienda esto:
A ESI, se mueve algún valor, así que no importa el valor de ESI en la dirección 004226BCh, porque en la siguiente instrucción va hacer modificada, así que lo usamos.
Este comentario, nos da otra idea de cómo hacer que salte hacia el flujo normal de la aplicación, por si por alguna razón no se modifica algún registro y necesitamos otra forma de regresar. Al estilo de un CALL, un PUSH y un RET, es una técnica muy usada y nos da una buena alternativa. Ocupa 6 bytes, un byte más que al usar el JMP.
Descarga en documento PDF
Creditos
Nox
Una técnica muy usada en el Game Hacking, un CodeCave, se describe literalmente a su traducción, es hacer un hueco, túnel o cueva dentro del código normal, cambiando el flujo, hacia una rutina arbitraria, de esa manera se modifica el compartimiento normal, de una aplicación (en la demostración).
![1587858553197.png 1587858553197.png](https://gamerzhacking.com/data/attachments/0/8-efb597a5d8dc86a46e3a7c904164f591.jpg)
¿Qué veremos en esta charla?
Dos formas de hacer un CodeCave, la primera usando opcodes y reservando espacio en la memoria, esto puede ser muy tedioso si tenemos un código largo, la segunda es la manera fácil será saltar hacia nuestro propio espacio (para usar instrucciones), donde tendremos la rutina arbitraria.
Planteando el Problema
Ya mencionamos cómo funciona el CodeCave, pero, ¿en qué momento debemos usarlo?
Por favor, Acceder o Regístrate ¡para ver el contenido de la cita!
Pasos Básicos
1er Paso: Obtener la dirección indicada que modificará el flujo hacía una rutina creada por nosotros.
2do Paso: Calcular el desplazamiento desde donde se hará el cambio de flujo, hasta nuestra rutina.
3er Paso: Crear la rutina que modificará el comportamiento arbitrariamente.
4to Paso: Regresar al flujo, como normalmente lo haría la aplicación, como si nada hubiera pasado.
Funcionamiento del CodeCave
![1587858801712.png 1587858801712.png](https://gamerzhacking.com/data/attachments/0/9-b55872139553ab077d28c7bb4b735344.jpg)
![1587858817345.png 1587858817345.png](https://gamerzhacking.com/data/attachments/0/10-6421ba1238daa086fbf02a8b7c7b9e05.jpg)
En pocas palabras si por alguna acción la vida disminuye en 1, con el CodeCave podemos modificar el flujo, hacer que valla hacia nuestra rutina y en vez de disminuir, incrementará en 2 la vida, esto es en pocas palabras, el concepto del CodeCave.
PRIMERA PARTE (usando opcodes y reservando espacio)
1er Paso. Obtener la dirección indicada.
Primero necesitamos encontrar la dirección que disminuya la vida en 1. Para esto podemos usar un debugger cualquiera, o para mayor facilidad el CheatEngine (desde ahora CE), que fue concebido para estas labores.
1er Paso. Dirección/Address
Usaremos está instrucción ficticia y diremos que la instrucción que disminuye la vida es dec [ebx+00000454]:
![1587876967894.png 1587876967894.png](https://gamerzhacking.com/data/attachments/0/11-224d84452383d388938492dbdd73e51f.jpg)
Y Nuestro objetivo es cambiar la instrucción dec [ebx+00000454] (es la cual decrementa en 1 la vida) por add [ebx+00000454], 2 (es la cual sumará en 2 la vida).
2do Paso. Calculando el Desplazamiento
2do Paso. Calcular el desplazamiento hacia nuestra rutina arbitraria.
Por favor, Acceder o Regístrate ¡para ver el contenido de la cita!
Necesitamos encontrar el desplazamiento que hay entre la instrucción que disminuye la vida hasta nuestra rutina.
El código que resuelve esto:
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Siendo lpMemory la variable que contiene la dirección donde comienza nuestra rutina.
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Calculamos el desplazamiento que hay de la dirección, 004226BCh hacia la dirección contenida en lpMemory, el desplazamiento lo tenemos en EDX.
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Restamos los 5 bytes del JMP (JUMP, es el salto que va a cambiar el flujo hacia nuestra rutina) esta ocupa 5 bytes y la tenemos que restar. EDX = Desplazamiento u Offset.
3er Paso. Rutina
3er Paso: Crear la rutina que modificará el comportamiento arbitrariamente.
Esta Rutina contiene tanto la rutina que cambia una instrucción, así como el código que reserva espacio del HEAP, lo cual veremos en el siguiente código:
![1587877247266.png 1587877247266.png](https://gamerzhacking.com/data/attachments/0/12-0bfada2f2558ab49f47b1877b1037d95.jpg)
Lo primero que hacemos es reservar memoria donde podamos escribir; el puntero hacia dicho lugar en memoria lo contiene la variable lpMemory.
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Especificamos el flag GMEM_MOVEABLE, este deja a Windows mover el bloque de memoria, para consolidar la memoria. La bandera (flag) GMEM_ZEROINIT le dice a GlobalAlloc que rellene el nuevo bloque de memoria reservado con ceros.
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Cuando GlobalAlloc vuelve satisfactoriamente, EAX contiene el manejador (handle) al bloque de memoria reservado. Le pasamos este manejador (handle) a la función GlobalLock que nos devuelve un puntero al bloque de memoria.
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Esto lo hacemos por seguridad ya que no todas las páginas de memoria tienen permiso de lectura o escritura.
![1587877511339.png 1587877511339.png](https://gamerzhacking.com/data/attachments/0/13-8e36ff229fef0769a55c5c2a098863a7.jpg)
Explicado en el Paso2.
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
A partir de aquí estamos modificando la instrucción dec [ebx + 00000454], por un JMP [lpMemory].
0E9h (Opcode) = JMP
EDX = Desplazamiento hacia la dirección allocada ([lpMemory]).
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Como la instrucción dec [ebx + 00000454], tiene de tamaño 6 bytes, el JMP 5 bytes, hay una que sobra y esa la NOPeo.
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Pasamos el puntero de nuestro espacio de memoria reservada a EDX.
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Cambiamos los primeros opcodes FF 8B a 83 83, siendo los opcodes 83 83 = add [ebx…….
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Seguimos armando la instrucción dándole el offset 00000454, y tendríamos: add [ebx + 00000454]
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Esta última instrucción es el valor con que será sumando el primero operando, con esto tendríamos la instrucción de esta manera: add [ebx + 00000454], 2
Cambió la instrucción : dec [ebx + 00000454] por add [ebx + 00000454], 2
Muchos dirán porque ha puesto 00000454 del offset y no 54040000, y así para los demás opcodes, pues fácil “LittleEndian” tiene la culpa, es el formato en que se almacenan los datos, en palabras fáciles lo que hace el microprocesador es almacenar los valores pero al revés (Little Endian es ese formato).
Ejemplo: 004226BC - FF 8B 54040000 - dec [ebx+00000454] Donde el offset que ha sido almacenado es 54040000. Entonces si yo muevo el valor de 00000454, lo que hace el microprocesador es voltear ese valor a 54040000, y como estamos trabajando con opcodes, será interpretado como 00000454.
4to Paso. Regresando el Flujo
4to Paso: Regresar al flujo, como normalmente lo haría la aplicación, como si nada hubiera pasado.
Calculando el tamaño que ocupamos en la rutina arbitraria, obtendremos el desplazamiento desde el final del código arbitrario, hasta la address que ejecutaría normalmente el GAME.
![1587880345718.png 1587880345718.png](https://gamerzhacking.com/data/attachments/0/14-2c507c7d4ec6b2d5716d29f40a07274e.jpg)
Ocupamos 7 bytes en la anterior instrucción, y tenemos que sumarle 5 bytes del JMP del regreso del flujo, en total 12 bytes usaremos y la dirección final de la rutina está en EAX. EAX = Dirección final de la rutina arbitraria.
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
La siguiente dirección que se ejecutaría como si nada hubiera pasado:
![1587882720846.png 1587882720846.png](https://gamerzhacking.com/data/attachments/0/15-4511d25c04e81f9d1769960cdc1a9d36.jpg)
PRIMERA PARTE - Resumen
En resumen esto fue lo que hicimos:
C++:
Por favor,
Acceder
o
Regístrate para ver el contenido de los códigos!
Hicimos un JMP, hacia nuestra rutina (CodeCave), ejecutamos la instrucción que modifica el comportamiento arbitrariamente, y regresamos el flujo, como si el comportamiento de la aplicación no hubiera cambiado, así nos evitamos del crash.
PRIMERA PARTE - Código
CODIGO
SEGUNDA PARTE (Usando nuestro propio espacio)
Después de hacer crear la primera parte, está segunda fue concebida al día siguiente luego de pensar porque hacer todo en opcodes y no usar mi propio espacio, quien sabe porque no se me ocurrió esto antes, o estaba tan absorto depurando el código anterior, que no tenía espacio para pensar en otra cosa, pero pensando que lo primero no es factible para códigos largos, así qué se concibió lo siguiente. Les pego el code, gran parte ya está explicado, los pasos son los mismos, lo único que cambia es el 3er Paso y 4to Paso, veamos como.
Segunda Parte - Código
![1587882933851.png 1587882933851.png](https://gamerzhacking.com/data/attachments/0/16-22793bbbd51fd73555f360a15dad881e.jpg)
![1587885331810.png 1587885331810.png](https://gamerzhacking.com/data/attachments/0/17-f2a47b8b3b770b5451be753e8108ca7e.jpg)
3er Paso. Rutina
Lo que hacemos ya no es saltar a un espacio de memoria reservada desde el heap, por la api GlobalAlloc, si no saltar a nuestro propio espacio así nos evitamos de liar con opcodes e imaginarnos el dolor de cabeza si fuera un código largo.
![1587885353154.png 1587885353154.png](https://gamerzhacking.com/data/attachments/0/18-6eba3eae230ba4c12dac612dc1eb3f31.jpg)
Al usar nuestro propio espacio, podemos escribir la instrucción tal cual la deseamos.
¿Por qué pushad y popad?, Esto es por seguridad y buena costumbre ya que esto es un CodeCave y la rutina arbitraria que codeamos podría ser mucho más larga que esta, seguramente usaremos registros que necesitan preservar su valor inicial, y seguramente esta aplicación daría error si no hubiera preservado sus registros.
La idea es dejar intactos todos los registros y aunque solo usamos EBX. Ahora, ¿por qué esta en comentarios?, es que en los microprocesadores x64 los mnemónicos pushad y popad quedaron por decirlo así obsoletos, no son reconocidos ya que imagínense cuanto sería el tamaño que ocuparía en la pila, así que deberíamos usar otros métodos para preservar los registros que usemos.
4to Paso. Regresando el Flujo
![1587885455138.png 1587885455138.png](https://gamerzhacking.com/data/attachments/0/19-769fdc1e8c9af57b30db323e7811f991.jpg)
Sí tanto hable de registros y preservarlos, ¿Por qué usa ESI, esto puede influir en algún error de la aplicación?, pudiera influir si antes no somos observadores, recordemos como está conformado las instrucciones ficticias que usamos para efecto del paper.
Esta vez ya no se calcula el desplazamiento dejamos ese trabajo al microprocesador hacienda esto:
![1587885562008.png 1587885562008.png](https://gamerzhacking.com/data/attachments/0/20-a7091d0ce7994ab622c054dc8c51085d.jpg)
A ESI, se mueve algún valor, así que no importa el valor de ESI en la dirección 004226BCh, porque en la siguiente instrucción va hacer modificada, así que lo usamos.
![1587885638242.png 1587885638242.png](https://gamerzhacking.com/data/attachments/0/21-f0aac4bac7fa53bbaffea8d813a738a1.jpg)
Este comentario, nos da otra idea de cómo hacer que salte hacia el flujo normal de la aplicación, por si por alguna razón no se modifica algún registro y necesitamos otra forma de regresar. Al estilo de un CALL, un PUSH y un RET, es una técnica muy usada y nos da una buena alternativa. Ocupa 6 bytes, un byte más que al usar el JMP.
Descarga en documento PDF
![www.mediafire.com](/proxy.php?image=https%3A%2F%2Fstatic.mediafire.com%2Fimages%2Ffiletype%2Fdownload%2Fpdf.jpg&hash=5d6fe29c65774ead1f8b806eb0cc1cca&return_error=1)
CodeCave_LimaHack
MediaFire is a simple to use free service that lets you put all your photos, documents, music, and video in a single place so you can access them anywhere and share them everywhere.
www.mediafire.com
Creditos
Nox
Última edición: