<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Game on</title><link>https://synacklabs.github.io/tags/game/</link><description>Recent content in Game on</description><generator>Hugo -- gohugo.io</generator><language>es-es</language><lastBuildDate>Wed, 11 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://synacklabs.github.io/tags/game/index.xml" rel="self" type="application/rss+xml"/><managingEditor>admin@synacklabs.github.io (SynAckLabs)</managingEditor><webMaster>admin@synacklabs.github.io (SynAckLabs)</webMaster><item><title>Ingeniería Inversa del Solitario (Windows XP): Forzando victorias y manipulando la visibilidad del tablero con Ghidra</title><link>https://synacklabs.github.io/posts/solitario/</link><pubDate>Wed, 11 Mar 2026 00:00:00 +0000</pubDate><guid>https://synacklabs.github.io/posts/solitario/</guid><description>&lt;h2 id="1-introducción"&gt;1. Introducción&lt;/h2&gt;
&lt;p&gt;En esta entrada voy a explicar el proceso de análisis de dos aspectos del Solitario de Windows mediante ingeniería inversa con Ghidra. El objetivo es documentar cómo identificar y alterar comportamientos específicos del juego directamente desde el binario.&lt;/p&gt;
&lt;p&gt;El write-up se divide en dos objetivos principales:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Localización de funciones ocultas:&lt;/strong&gt; Identificar y activar el comando &lt;code&gt;&amp;quot;Force a win&amp;quot;&lt;/code&gt;, una funcionalidad no documentada que permite forzar la victoria. Veremos cómo rastrear este comando desde los strings del programa hasta su implementación en el código.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modificación de la lógica de renderizado:&lt;/strong&gt; Analizar cómo el programa gestiona la visibilidad de las cartas en el tablero. El reto aquí es modificar la lógica que controla las columnas para que todas las cartas se muestren boca arriba, eliminando la restricción que solo permite ver la última carta de cada pila.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-parte-i-activando-el-comando-oculto"&gt;&lt;strong&gt;2. Parte I: Activando el comando oculto &amp;ldquo;Force a win&amp;rdquo;&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id="21-análisis-de-strings-primer-contacto-con-la-lista-de-cadenas-definidas"&gt;&lt;strong&gt;2.1. Análisis de strings:&lt;/strong&gt; Primer contacto con la lista de cadenas definidas.&lt;/h3&gt;
&lt;p&gt;Lo primero que hice al abrir el binario en Ghidra fue ir a lo seguro. Como no sabía por dónde empezar a buscar funciones ocultas, decidí echar un vistazo a los strings del programa. Me fui al menú &lt;strong&gt;Window&lt;/strong&gt; y seleccioné &lt;strong&gt;Defined Strings&lt;/strong&gt;. Mi idea era ver si los desarrolladores habían dejado algún mensaje de debug o algún nombre interesante que me diera una pista de qué hace el juego por detrás.&lt;/p&gt;</description><content>&lt;h2 id="1-introducción"&gt;1. Introducción&lt;/h2&gt;
&lt;p&gt;En esta entrada voy a explicar el proceso de análisis de dos aspectos del Solitario de Windows mediante ingeniería inversa con Ghidra. El objetivo es documentar cómo identificar y alterar comportamientos específicos del juego directamente desde el binario.&lt;/p&gt;
&lt;p&gt;El write-up se divide en dos objetivos principales:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Localización de funciones ocultas:&lt;/strong&gt; Identificar y activar el comando &lt;code&gt;&amp;quot;Force a win&amp;quot;&lt;/code&gt;, una funcionalidad no documentada que permite forzar la victoria. Veremos cómo rastrear este comando desde los strings del programa hasta su implementación en el código.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modificación de la lógica de renderizado:&lt;/strong&gt; Analizar cómo el programa gestiona la visibilidad de las cartas en el tablero. El reto aquí es modificar la lógica que controla las columnas para que todas las cartas se muestren boca arriba, eliminando la restricción que solo permite ver la última carta de cada pila.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-parte-i-activando-el-comando-oculto"&gt;&lt;strong&gt;2. Parte I: Activando el comando oculto &amp;ldquo;Force a win&amp;rdquo;&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id="21-análisis-de-strings-primer-contacto-con-la-lista-de-cadenas-definidas"&gt;&lt;strong&gt;2.1. Análisis de strings:&lt;/strong&gt; Primer contacto con la lista de cadenas definidas.&lt;/h3&gt;
&lt;p&gt;Lo primero que hice al abrir el binario en Ghidra fue ir a lo seguro. Como no sabía por dónde empezar a buscar funciones ocultas, decidí echar un vistazo a los strings del programa. Me fui al menú &lt;strong&gt;Window&lt;/strong&gt; y seleccioné &lt;strong&gt;Defined Strings&lt;/strong&gt;. Mi idea era ver si los desarrolladores habían dejado algún mensaje de debug o algún nombre interesante que me diera una pista de qué hace el juego por detrás.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-80fe-a0f3-c72cd4a28419.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;Al abrir la lista de strings, me puse a investigar un poco. La mayoría parecían nombres de funciones estándar de Windows, pero de repente vi algo que me llamó la atención: &lt;strong&gt;&amp;ldquo;Force a win&amp;rdquo;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-8017-a63d-c54be4bd68ba.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h3 id="22-identificación-del-recurso-id-1010"&gt;&lt;strong&gt;2.2. Identificación del recurso:&lt;/strong&gt; ID 1010.&lt;/h3&gt;
&lt;p&gt;La presencia de esta cadena sugiere la existencia de una funcionalidad no documentada que fuerza la victoria. Al hacer doble clic sobre ella, Ghidra me llevó a su ubicación en la sección de recursos. Allí se observa que pertenece a una tabla de strings con identificadores numéricos. Concretamente, &amp;ldquo;Force a win&amp;rdquo; tiene asignado el &lt;strong&gt;ID 1010&lt;/strong&gt; (Rsrc String ID 1010).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-809a-813b-d1b756b6d989.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h3 id="23-conversión-de-bases-localizando-el-valor-hexadecimal-0x03f2"&gt;&lt;strong&gt;2.3. Conversión de bases:&lt;/strong&gt; Localizando el valor hexadecimal 0x03F2.&lt;/h3&gt;
&lt;p&gt;Antes de seguir buscando en el código, me detuve un momento. Como Ghidra y el lenguaje ensamblador suelen trabajar con valores hexadecimales, necesitaba saber cómo se escribe 1010 en hex. Convertimos el ID a hexadecimal (0x3F2). Ya tengo el valor que debo buscar en las instrucciones del programa: &lt;code&gt;0x3f2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-801c-96f3-c94802d2a655.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h3 id="24-rastreo-en-el-desensamblado-localización-del-manejador-de-comandos"&gt;&lt;strong&gt;2.4. Rastreo en el desensamblado:&lt;/strong&gt; Localización del manejador de comandos&lt;/h3&gt;
&lt;p&gt;Con el valor &lt;code&gt;0x3F2&lt;/code&gt; como objetivo, realicé una búsqueda en el código desensamblado. En la dirección &lt;code&gt;010019d2&lt;/code&gt; aparecía la instrucción &lt;code&gt;CMP EAX, 0x3f2&lt;/code&gt;, justo antes de un salto condicional &lt;code&gt;JZ&lt;/code&gt; hacia la etiqueta &lt;code&gt;LAB_01001a35&lt;/code&gt;. Esta comparación es típica de un manejador de comandos: el programa evalúa si el parámetro recibido coincide con el ID 1010 y, en caso afirmativo, desvía el flujo hacia la rutina correspondiente.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-80fc-8d45-eec13e97fdb3.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-8053-b3e1-e7f9ef7fc5ac.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-80b1-8861-ebe344112a0f.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h3 id="25-análisis-del-flujo-de-victoria-y-el-gatillo-del-deal"&gt;2.5. Análisis del flujo de victoria y el &amp;ldquo;gatillo&amp;rdquo; del Deal&lt;/h3&gt;
&lt;p&gt;Seguí el salto hasta la etiqueta &lt;code&gt;LAB_01001a35&lt;/code&gt; para ver qué pasaba allí. Me encontré con un bloque de código que hace un &lt;code&gt;XOR ESI, ESI&lt;/code&gt; (una forma rápida de poner a cero un registro) y luego hace varios &lt;code&gt;PUSH&lt;/code&gt;, incluyendo el valor &lt;code&gt;0x12&lt;/code&gt;. Aunque todavía me cuesta entender exactamente qué hace cada instrucción de la pila, este parece ser el camino que toma el juego para saltar directamente a la pantalla de victoria.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-8026-a708-f9eb55b6c5ff.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;Investigando el código cercano para entender qué más podíamos controlar, identifiqué el valor &lt;strong&gt;1000&lt;/strong&gt; (en hex &lt;code&gt;0x3E8&lt;/code&gt;). Este ID es el &amp;ldquo;gatillo&amp;rdquo; que se activa cuando el usuario pulsa &lt;strong&gt;&amp;ldquo;Deal&amp;rdquo;&lt;/strong&gt; (Repartir). En condiciones normales, este evento llama a una función que yo mismo renombré como &lt;code&gt;RESET_TIME&lt;/code&gt;, encargada de limpiar el tablero para una partida nueva. También vi menciones a &lt;code&gt;0x3e9&lt;/code&gt; y &lt;code&gt;0x3ea&lt;/code&gt;. Estos IDs corresponden probablemente a otras funciones internas, como reiniciar el tiempo o activar modos de depuración.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-8047-8c10-e99fe45b4e82.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h3&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;2.6. Secuestro de flujo: Convirtiendo el reinicio en victoria&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Con el conocimiento de que el ID &lt;strong&gt;1000&lt;/strong&gt; dispara la función de repartir (&lt;code&gt;RESET_TIME&lt;/code&gt;), decidí realizar un secuestro de flujo (hijacking) utilizando la herramienta &lt;strong&gt;Patch Instruction&lt;/strong&gt; de Ghidra. El objetivo era simple: redirigir una acción legítima del usuario hacia la función de victoria que acababa de descubrir.&lt;/p&gt;
&lt;p&gt;El proceso técnico consistió en lo siguiente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Identificación del salto:&lt;/strong&gt; Localicé la instrucción de salto condicional que se ejecuta cuando el programa detecta el ID 1000.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redirección del destino:&lt;/strong&gt; Modifiqué la dirección de memoria de ese salto para que, en lugar de apuntar a la rutina de reinicio, saltara directamente a la etiqueta &lt;code&gt;LAB_01001a35&lt;/code&gt; (el bloque que activa la animación de victoria).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gracias a este parche, el comportamiento del binario cambia por completo: ahora, cada vez que se pulsa &amp;ldquo;Nueva partida&amp;rdquo;, el programa omite la lógica de inicio y ejecuta la rutina de victoria. Esto demuestra cómo, alterando un solo salto en el desensamblado, podemos forzar al software a ejecutar funciones fuera de su contexto original.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-80fd-9dce-c6915cd90323.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-801c-9387-d0007f410b21.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-801d-9bda-e830d3ca73a5.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-809c-b07d-e856165dd88b.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/303888fa-f353-805c-a8fa-e33bd0ab0725.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h2 id="3-parte-ii-ingeniería-de-la-visibilidad-de-las-cartas"&gt;&lt;strong&gt;3. Parte II: Ingeniería de la visibilidad de las cartas&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id="31-descartando-el-motor-gráfico-limitaciones-de-cardsdll-y-cdt_draw"&gt;&lt;strong&gt;3.1. Descartando el motor gráfico:&lt;/strong&gt; Limitaciones de Cards.dll y CDT_Draw.&lt;/h3&gt;
&lt;p&gt;Para entender cómo el juego muestra las cartas, empecé por lo más lógico: buscar qué librerías externas utiliza para dibujar. En la tabla de importaciones encontré &lt;code&gt;Cards.dll&lt;/code&gt; y, dentro de ella, la función &lt;code&gt;CDT_Draw&lt;/code&gt;. Mi razonamiento fue que, si encontraba dónde se llama a esta función, podría interceptar el momento en que se decide si una carta se dibuja boca arriba o boca abajo. Sin embargo, al profundizar en las llamadas, me di cuenta de que &lt;code&gt;cdtDraw&lt;/code&gt; solo recibe parámetros y dibuja; la lógica que decide si una carta va boca arriba o boca abajo está en otro lado. Necesitaba encontrar el código que toma esas decisiones.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-804c-a179-f0b92604d9b7.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-806d-bbd7-e0b76d4bdc93.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-801a-acd5-c7fcf8bf2c5a.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h3 id="32-la-técnica-de-la-constante-conocida-buscando-el-7-en-la-lógica-de-columnas"&gt;&lt;strong&gt;3.2. La técnica de la constante conocida:&lt;/strong&gt; Buscando el &amp;ldquo;7&amp;rdquo; en la lógica de columnas.&lt;/h3&gt;
&lt;p&gt;Decidí cambiar de enfoque: en lugar de seguir el rastro de las funciones gráficas, me centré en la lógica del tablero. El solitario clásico tiene &lt;strong&gt;7 columnas&lt;/strong&gt; en la zona de juego, así que el programa seguramente itera sobre esas columnas. Si hay un bucle que recorre las columnas, debe haber una comparación con el número 7. Busqué en todo el código desensamblado la instrucción &lt;code&gt;CMP&lt;/code&gt; con el valor &lt;code&gt;0x7&lt;/code&gt; y encontré varios candidatos.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-80ba-a11b-cf2b475242a5.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;¡Bingo! Entre los resultados, encontré una sección de código donde se comparaba el registro &lt;code&gt;EAX&lt;/code&gt; con &lt;code&gt;0x7&lt;/code&gt;. Esta comparación (&lt;code&gt;CMP EAX, 7&lt;/code&gt;) no parecía ser un valor aleatorio; estaba justo antes de un salto condicional que controlaba un bucle importante. Sabía que estaba en el lugar correcto: esta es la función que gestiona el barrido de las 7 columnas del tablero.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-803d-ad3a-c7ac6f49f0fa.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h3 id="33-anatomía-del-bucle-de-renderizado-cómo-el-juego-barre-el-tablero"&gt;&lt;strong&gt;3.3. Anatomía del bucle de renderizado:&lt;/strong&gt; Cómo el juego barre el tablero.&lt;/h3&gt;
&lt;p&gt;Al analizar la función que barre las 7 columnas, identifiqué un bucle interno encargado de procesar las cartas de cada pila. La lógica es interesante: el programa recupera el número total de cartas en la columna y utiliza un contador para iterar sobre ellas.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-80c0-b8b9-dfa83eed9a1f.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;La clave de la visibilidad reside en una comparación crítica: el juego contrasta el índice de la carta actual con el índice de la última carta de la pila.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;La condición:&lt;/strong&gt; Si el índice es igual al total (la carta de la cima), el flujo continúa hacia la rutina de visibilidad.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El salto condicional:&lt;/strong&gt; En ensamblador, esto es un &lt;code&gt;CMP&lt;/code&gt; seguido de un &lt;code&gt;JNZ&lt;/code&gt; (&lt;em&gt;Jump if Not Zero&lt;/em&gt;). Si la carta no es la última, el &lt;code&gt;JNZ&lt;/code&gt; hace que el programa &amp;ldquo;salte&amp;rdquo; por encima del &lt;code&gt;CALL&lt;/code&gt; que marca la carta como visible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Al neutralizar este &lt;code&gt;JNZ&lt;/code&gt;, obligo al programa a ejecutar el &lt;code&gt;CALL&lt;/code&gt; que da visibilidad para &lt;strong&gt;todas&lt;/strong&gt; las cartas, sin importar su posición.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-8046-9bad-fb7dfc020a36.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-80c5-b3ee-fd2219a0a62d.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h2 id="4-el-parche-final-neutralización-del-salto-uso-de-nops-para-revelar-todo-el-tablero"&gt;&lt;strong&gt;4. El Parche Final: Neutralización del salto:&lt;/strong&gt; Uso de NOPs para revelar todo el tablero.&lt;/h2&gt;
&lt;p&gt;El parche consiste en sustituir los bytes del &lt;code&gt;JNZ&lt;/code&gt; por dos instrucciones &lt;strong&gt;&lt;code&gt;NOP&lt;/code&gt;&lt;/strong&gt; (&lt;em&gt;No Operation&lt;/em&gt;). Al hacer esto, el flujo de ejecución se vuelve lineal: el &amp;ldquo;portero&amp;rdquo; que impedía mostrar las cartas inferiores desaparece, y el motor gráfico recibe la instrucción de renderizar la cara de cada carta de la columna. El resultado es un tablero completamente destapado desde el inicio de la partida.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-8054-9db8-d96c6a20b064.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-8050-8ac1-d8c1ac86fe16.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://synacklabs.github.io/images/notion/320888fa-f353-8020-a9d0-db7aa6f6fe17.png" alt="Imagen del post"&gt;&lt;/p&gt;
&lt;h2 id="conclusión"&gt;&lt;strong&gt;Conclusión&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Hemos recorrido dos caminos diferentes dentro del ejecutable del Solitario: primero localizamos y activamos un comando oculto que fuerza la victoria, y después modificamos la lógica de dibujado para hacer visibles todas las cartas. A lo largo del proceso hemos puesto en práctica varias técnicas fundamentales de ingeniería inversa.&lt;/p&gt;
&lt;p&gt;Estas técnicas nos han permitido descubrir mecanismos internos del juego que no están visibles en la interfaz normal, demostrando cómo el análisis directo del binario puede revelar funcionalidades ocultas y ofrecer un control completo sobre el comportamiento de la aplicación.&lt;/p&gt;</content><author>admin@synacklabs.github.io (SynAckLabs)</author></item></channel></rss>