Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
gemma_crackme_tutorial — Tutorial paso a paso sobre el uso del modelo de IA local Gemma 4 E4B de Google para realizar ingeniería inversa de un crackme de Windows con Ghidra, incluyendo la configuración para inferencia local y el renombrado automático de funciones/variables. | Kitploit
Herramientas/GitHubGitHub/markoglasgow/gemma_crackme_tutorial
Ingeniería InversaDepuradoresAnálisis de BinariosAprendizaje AutomáticoAprendizaje y EducaciónReversing Asistido por IA
GitHubmarkoglasgow/gemma_crackme_tutorial

gemma_crackme_tutorial

Tutorial paso a paso sobre el uso del modelo de IA local Gemma 4 E4B de Google para realizar ingeniería inversa de un crackme de Windows con Ghidra, incluyendo la configuración para inferencia local y el renombrado automático de funciones/variables.

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Ver Repositorio
353hace 3 mesesRevisado por Kitploit

Usando el modelo de IA local Google Gemma 4 E4B para realizar ingeniería inversa de un Crackme simple

Estaba jugando con el nuevo modelo local de pesos abiertos Gemma E4B que Google lanzó, y para mi sorpresa estaba viendo un gran éxito al usarlo para escenarios locales de ingeniería inversa sin conexión. Quería escribir este tutorial para difundir que la IA local ahora es lo suficientemente buena para muchas tareas básicas de reversa, y que las cosas probablemente mejorarán rápidamente de aquí en adelante.

Ingeniería inversa e IA

Una de las partes más tediosas de hacer reversa a un nuevo binario es al principio, cuando no tienes información sobre cuáles son las funciones y variables importantes. Hay muchos trucos que los reversers usan para empezar, incluyendo mirar referencias a cadenas, diferencia de binarios, o emparejar funciones similares.

La IA es muy buena ayudando aquí, y personalmente he tenido gran éxito usando la API de OpenAI para marcar un binario o limpiar la salida del descompilador. Sin embargo, hay varias desventajas al usar estas APIs:

  • Costo: La descompilación y el desensamblado generan toneladas de tokens. Las APIs cobran $ por token, por lo que los binarios más grandes pueden requerir bastantes $$$ para analizar. Si estás lidiando con un objetivo grande con muchos binarios que se actualizan cada semana, estos costos pueden acumularse rápidamente y son prohibitivos para los ingenieros inversos aficionados.

  • Privacidad: Cuando usas una API remota, el anfitrión de la IA tiene visibilidad de lo que estás haciendo. Esto es un factor excluyente en algunos escenarios profesionales.

  • Control: Cuando dependes de una API remota, no tienes control sobre qué modelos se te están sirviendo, ni sobre cuál es la calidad de los modelos. Si dependes de ellos para cosas críticas, esto puede ser un problema cuando se vuelven lentos o se caen cuando los necesitas, o cuando la calidad de su salida se degrada hasta el punto de ser inútiles.

Ejecutar tus propios modelos de IA locales soluciona algunos de estos puntos problemáticos:

  • Costo: Puede ser mucho más barato ejecutar un modelo local que depender de un servicio alojado. Si bien tu modelo local probablemente sea más pequeño y lento que el de un buen proveedor, si es lo suficientemente bueno y se ejecuta en un tiempo razonable, puede tener sentido ahorrar dinero ejecutando tu propio modelo, especialmente si estás procesando grandes cantidades de datos para tareas simples.

  • Privacidad: Cuando ejecutas el modelo localmente, no se realizan llamadas de red y tienes control completo sobre tu privacidad. Nadie puede ver para qué estás usando el modelo en tu propia máquina.

  • Control: La belleza de un modelo de pesos abiertos es que nadie puede quitártelo. OpenAI o Anthropic podrían algún día hacer que sus modelos SotA no estén disponibles, ya sea mediante aumentos de precio o eliminando explícitamente sus APIs. Pero con un modelo de pesos abiertos, tienes el control de tu destino, para bien o para mal.

Sin embargo, los modelos de IA locales tienen sus desventajas:

  • Tamaño: Cuanto más grande es tu modelo, más inteligente es. Sin embargo, la mayoría de los modelos grandes no caben en hardware de consumo. Debido a eso, si ejecutas un modelo local, lo más probable es que estés ejecutando un modelo 10x-100x más pequeño que un modelo SotA (State-of-the-Art, estado del arte). Esta disminución en tamaño conduce directamente a una disminución en la inteligencia del modelo, haciéndolos inadecuados para muchas tareas que la gente da por sentadas en modelos SotA como ChatGPT/Codex o Claude.

  • Velocidad: Los modelos locales probablemente se ejecutarán más lentamente en tu máquina que cuando se usa una API de IA. Nuevamente, esto se debe a las limitaciones del hardware de consumo y a algunos de los trucos que los proveedores de API pueden hacer y que generalmente no están disponibles para ti.

  • Configuración: Ejecutar modelos locales es como intentar ejecutar Linux en una laptop reacondicionada versus entrar a la Apple Store y comprar una Macbook Air nueva. La experiencia de Codex y Claude Code es la experiencia Apple Store de la IA. La experiencia del modelo de IA local es el tipo con un fedora en su garaje golpeando una computadora endeble tratando de hacerla funcionar. Como mínimo, tienes que preocuparte por las siguientes cosas:

    • Adquirir el hardware adecuado
    • Controladores de GPU
    • Elegir y configurar el servidor de inferencia correcto
    • Elegir un modelo que quepa en tu hardware, que se ejecute lo suficientemente rápido como para que sea práctico y que sea lo suficientemente inteligente para la tarea en cuestión
    • Usar la plantilla de chat correcta con tu modelo
    • Explorar cuantizaciones del modelo para encontrar el equilibrio adecuado entre tamaño/velocidad versus inteligencia del modelo
    • Indicar correctamente al modelo
    • Elegir el arnés correcto, o hacer tu propio arnés si ninguno de los existentes funciona.

No es un viaje fácil, y mucha gente se rinde y asume que los modelos de IA locales no están a la altura de la tarea, porque nunca encontraron la combinación correcta de hardware/modelo/configuración/indicaciones/arnés para hacerlos funcionar para su tarea. Si bien en muchos casos tienen razón, espero que este tutorial al menos arroje luz sobre lo lejos que han llegado los modelos locales, cómo pueden ayudar con la ingeniería inversa, e inspire a las personas a darle una oportunidad a la IA local.

Configuración de ingeniería inversa

  • El crackme es un crackme de Windows extremadamente simple descargado de Crackmes.one aquí: https://crackmes.one/crackme/69e13f938afd9d6c48b488fd

(la contraseña del archivo es crackmes.one). He alojado un enlace alternativo aquí en el repositorio por si el enlace original deja de funcionar.

  • Ghidra 12.04 se utiliza para desensamblado y descompilación. Necesitarás instalar OpenJDK 21 para usarlo: https://github.com/nationalsecurityagency/ghidra

  • Mientras trabajaba en este tutorial, hice vibe-coding con Claude Code de un plugin de Ghidra para renombrar funciones y variables con IA. Puedes descargar el plugin desde aquí: https://github.com/markoglasgow/Ghidra_FastAIRenamer_Plugin

Para instalarlo, simplemente mueve el archivo zip ghidra_12.0.4_PUBLIC_20260427_FastAIRenamerPlugin.zip a ${GHIDRA_HOME}\Extensions\Ghidra, luego ejecuta Ghidra ejecutando ${GHIDRA_HOME}\ghidraRun.bat. Para activar el plugin, en la pantalla inicial de Ghidra en el menú superior selecciona File -> Install Extensions, luego en el navegador de plugins marca la casilla junto a FastAIRenamerPlugin, luego haz clic en Ok. Ghidra te pedirá que reinicies, hazlo de inmediato.

Para configurar el plugin, la próxima vez que Ghidra se inicie, en el menú superior ve a Tools -> Run Tool -> CodeBrowser. Ghidra dirá "New Extensions detected. Would you like to configure them?". Haz clic en sí, luego marca nuevamente la casilla junto a FastAIRenamerPlugin, luego haz clic en Ok. Cuando se abra CodeBrowser, en el menú superior haz clic en Window -> Fast AI Renamer, luego haz clic en el botón Config. Aquí podrás configurar tu modelo de IA. Cierra la ventana del plugin y la ventana vacía de CodeBrowser una vez terminado.

Nota: si tienes problemas para cargar el plugin, es posible que necesites habilitar el modo Desarrollador en Ghidra (File -> Configure -> casilla junto a Developer).

Nota: siempre puedes verificar si el plugin está cargado yendo a CodeBrowser, haciendo clic en File -> Configure -> Ghidra Core -> haz clic en el botón azul de configuración -> filtrar por "FastAIRenamer" -> asegúrate de que la casilla junto a su nombre esté marcada.

Nota: para desinstalar el plugin, primero abre CodeBrowser, File -> Configure -> Ghidra Core -> haz clic en el botón azul de configuración -> filtrar por "FastAIRenamer" -> desmarcar -> ok. Cierra CodeBrowser, luego en la ventana inicial de Ghidra, File -> Install Extensions -> desmarca "FastAIRenamer". Finalmente cierra Ghidra y elimina ghidra_12.0.4_PUBLIC_20260427_FastAIRenamerPlugin.zip de ${GHIDRA_HOME}\Extensions\Ghidra. Para asegurarte de que la extensión se ha eliminado, la próxima vez que ejecutes Ghidra, en la ventana inicial ve a Help -> Runtime Information -> Extension Points -> filtrar por "FastAIRenamer" y asegúrate de que no aparezca nada. Uf.

Finalmente, asegúrate de tener Visual Studio o algún otro entorno de desarrollo C++ configurado para que puedas hacer vibe coding de una solución al crackme cuando llegue el momento.

Configuración de IA local

Las configuraciones de IA local de las personas variarán enormemente dependiendo del hardware y $$$ que tengan disponible. Para mí:

  • Personalmente estoy usando una Nvidia GTX 3080, con Cuda 13.2 instalado. Puedes verificar tu versión de Cuda ejecutando nvcc --version en tu Terminal de Windows.

  • Estoy ejecutando los quants de https://huggingface.co/bartowski/google_gemma-4-E4B-it-GGUF/blob/main/google_gemma-4-E4B-it-Q8_0.gguf de bartowski. Puede ser superstición de mi parte, pero para tareas analíticas trato de ejecutar con la menor cantidad de cuantización posible, y evitarla por completo si puedo.

  • Estoy usando llama.cpp como mi servidor de inferencia, con esta versión específica: llama-b8893-bin-win-cuda-13.1-x64.

  • Estoy ejecutando llama.cpp con las siguientes configuraciones de CLI:``` ..\llama-b8893-bin-win-cuda-13.1-x64\llama-server.exe ^ --port 8090 ^ --threads 12 ^ --n-gpu-layers 256 ^ --no-mmap ^ --model "google_gemma-4-E4B-it-Q8_0.gguf" ^ --ctx-size 32768 ^ --temp 1.0 ^ --top-k 64 ^ --top-p 0.95 ^ --offline

root@kitploit:~
Esto produce `75 tokens/sec`, lo cual es bastante rápido para IA local. 

## ¿Sin hardware? Sin problema 

Si no tienes el hardware disponible o tienes problemas con la configuración, pero aún así quieres seguir este tutorial, puedes obtener un inicio de sesión en OpenRouter y usar una de las APIs gratuitas:
https://openrouter.ai/models/?q=free

Google está ofreciendo específicamente `Gemma 4 31B` y `Gemma 4 26B-A4B` de forma gratuita en este momento (solo por tiempo limitado):
https://openrouter.ai/google/gemma-4-31b-it:free
https://openrouter.ai/google/gemma-4-26b-a4b-it:free

... solo ten en cuenta que con estas APIs gratuitas, son lentas, poco confiables, y todos los datos que les envíes probablemente serán registrados en sus análisis internos y utilizados en su próxima ejecución de entrenamiento. Aun así, para los propósitos de este tutorial, estas APIs deberían permitirte seguir el curso. 

Para configurar el plugin de Ghidra para usar tu inicio de sesión de OpenRouter, abre la configuración del plugin e ingresa lo siguiente:```
Base URL: https://openrouter.ai/api/
API Key: <your OpenRouter API Key>
Model Name: qwen/qwen3-235b-a22b-2507

Este ejemplo ejecutará el plugin contra el modelo qwen3 aquí

Inspeccionando el Crackme

Descomprime y ejecuta crackmepls.exe, serás recibido por una pantalla de inicio de sesión estándar. Escribe cualquier contraseña aleatoria y obtendrás un mensaje de 'Acceso Denegado':``` User: marko Pass: 123 Access denied

root@kitploit:~
Abre Ghidra ejecutando `ghidraRun.bat`. En la barra de herramientas selecciona `File -> New Project`, deja seleccionado `Non-Shared Project` y haz clic en `Next >>`, luego selecciona un directorio de proyecto vacío y asigna un nombre al proyecto. Luego haz clic en `Finish`.

A continuación, haz clic en `File -> Import File`, luego selecciona `crackmepls.exe` para agregarlo al proyecto. Ghidra mostrará algunos detalles sobre el archivo, indicándote que es un archivo `Portable Executable (PE)` para `x86:LE:64:default:windows`. Simplemente haz clic en `OK` para aceptarlo sin cambiar nada. Tras una breve demora, aparecerán más detalles sobre el archivo; nuevamente, haz clic en `OK` para aceptarlos. Finalmente, haz doble clic en `crackmepls.exe` en el proyecto para abrir el Navegador de Código y comenzar a desensamblarlo.

Al principio, Ghidra mostrará un mensaje que dice `crackmepls.exe has not been analyzed. Would you like to analyze it now?`. Haz clic en `Yes` y luego en el botón `Analyze` en la siguiente ventana, y espera a que Ghidra localice, desensamble y descompile todas las funciones del binario. Puede que recibas uno o dos errores durante el análisis sobre archivos PDB no encontrados; simplemente haz clic en `Ok` e ignóralos.

Una vez que Ghidra termine de analizar el archivo, deberías ver algo como lo siguiente:

![alt text](https://assets.kitploit.com/production/public/readmes/13760/2be69c6e539fa74e1ac31eec4304bcd7e8952ba96b91fab51add36ed6c1440ee.png "Entrada del programa")

Este es el punto de entrada estándar de MSVC. Haz doble clic en `FUN_14000200c` y desplázate hacia abajo.

Deberías ver referencias en la ventana del descompilador a `__p___argv`, `__p___argc` y luego una llamada a la función `FUN_140001290` que las acepta como parámetros. Esta es probablemente la función `main()` del crackme, así que haz doble clic en ella.

![alt text](https://assets.kitploit.com/production/public/readmes/13760/577b01a818727ee43010a24d03236157d22a69f94599f4117420436a641745ac.png "Encontrando Main")

Una vez dentro de la función `FUN_140001290`, desplázate un poco hacia abajo en la ventana del descompilador. Verás referencias a las cadenas `User:` y `Pass:`, junto con referencias a `basic_istream` (flujo de entrada) y `basic_ostream` (flujo de salida).

![alt text](https://assets.kitploit.com/production/public/readmes/13760/dda6baaa2d284402bd8ce61c776810a0c154c6861e0012a56391fccbeb71a0c5.png "Impresión y entradas")

Estas cadenas coinciden con las declaraciones de impresión y entrada que vimos cuando ejecutamos el crackme por primera vez, por lo que sabemos que estamos en la función `main()` del crackme.

Ahora, en este punto, comenzaría el trabajo tedioso donde tendríamos que sentarnos y renombrar cada nombre de variable y llamada a función con algo significativo, a medida que analizamos lo que hace el binario para resolver el crackme. Los ingenieros inversos solían tener que hacer esto manualmente en el desensamblado, pero afortunadamente la tecnología y los impuestos nos han dado este práctico descompilador que se puede programar para trabajar con IA.

Así que, en lugar de hacer trabajo real, relajémonos, apaguemos nuestros cerebros y dejemos que nuestra IA local haga todo el trabajo por nosotros.

Durante la configuración de este tutorial, deberías haber instalado un plugin de Ghidra para ayudar con el renombrado de funciones y variables. Ahora es el momento de usarlo. En Ghidra, en la barra de herramientas superior, haz clic en `Window` y luego en `Fast AI Renamer` para abrir el plugin.

![alt text](https://assets.kitploit.com/production/public/readmes/13760/7511b88d33bb13e45a830295f42d1fceda2eb5897beffbec6ca5e6a3bd52f46e.png "Abrir Fast AI Renamer")

Deberías ver la interfaz de usuario del plugin, que consiste en varios botones y un área de texto. Comienza haciendo clic en el botón "Config", y luego asegúrate de tener todo configurado para comunicarte correctamente con tu IA local. Así es como se ve en mi computadora:

![alt text](https://assets.kitploit.com/production/public/readmes/13760/4807636497952b764fc6dad362d0defc2804df0d5ba353c6938e266864c676cc.png "Config")

Puedes cerrar la ventana de configuración haciendo clic en "Save", y luego haz clic en el botón `Rename Variables`. Aparecerá una ventana de progreso con un dragón, y es posible que escuches a tu computadora empezar a esforzarse mientras trabaja para ejecutar el modelo de IA local y renombrar todas las variables en la ventana del descompilador:

![alt text](https://assets.kitploit.com/production/public/readmes/13760/1f9e6ac902cce19f63319621adaabef504ad5a94fa84db2ec39782b74e2f18f6.png "Renombrar Variables")

Una vez que la IA termine (tarda entre 10 y 20 segundos en mi máquina), deberías ver una descripción de qué variables renombró en el área de texto del plugin, y las variables mismas deberían estar renombradas en la ventana del descompilador:

![alt text](https://assets.kitploit.com/production/public/readmes/13760/3454e5e42d7a4d84dffffbc6343e9579d33b3241b42e0be06bdb068eb91ed1e3.png "Resultados del Renombrado")

Todos los modelos de IA, pero especialmente los pequeños locales, son inherentemente poco fiables, por lo que podrías ver algunos errores en este último paso. Volveremos a tratar esto más adelante. Mientras tanto, siempre puedes volver a ejecutar la IA haciendo clic en el botón `Rename Variables` nuevamente, hasta que obtengas resultados sin errores que te parezcan satisfactorios.

## Resolviendo el Crackme

En este punto, tenemos una salida del descompilador bien marcada, donde todos los nombres de variables han sido renombrados a algo significativo. Tradicionalmente, un ingeniero inverso podría ahora leer este código y comenzar a redactar una solución para el Crackme. Sin embargo, pensé que sería interesante intentar que la IA local resolviera el crackme por nosotros. Recuerda, hoy no usaremos nuestro cerebro.

Primero, hice que la IA terminara de marcar todo el binario haciendo clic en el botón `Rename ALL Functions and Variables`. Esto sirve para asignar nombres a todas las llamadas a funciones que se utilizan en esta función del crackme, lo que limpia aún más la salida del descompilador. En mi máquina, esto toma alrededor de 15 minutos.

Cuando estés usando `llama.cpp` con los parámetros de CLI que enumeré en `Configuración de IA Local`, puedes abrir una interfaz de chat para tu modelo local navegando en el navegador a [http://localhost:8090/](http://localhost:8090/)

Aquí, ingresé la salida del descompilador de la función del crackme (puedes obtenerla haciendo clic en el botón "Decompile Function" en la interfaz del plugin, o simplemente copiándola de la ventana del descompilador en el lado derecho) y le pedí a la IA que escribiera una solución para nosotros:

======================================================================
======================================================================
======================================================================

Quiero que me ayudes a escribir una solución para un crackme. A continuación se muestra el listado del descompilador de una función de un crackme, donde el usuario escribe un nombre de usuario y una contraseña, y luego se concede acceso si proporcionó la contraseña correcta. La contraseña se calcula en la función.```


/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */
/* **Reasoning:**
   The function takes user input (a username/input and a password) via standard input. It processes
   the user input by calculating a complex, custom checksum/hash. It then compares the provided
   password input against a target buffer (likely a stored hash or secret). Finally, it determines
   and outputs whether "Access granted" or "Access denied," indicating the function serves as an
   authentication routine. */

undefined8 authenticate_user(undefined8 param_1,undefined8 param_2,undefined8 max_len)

{
  uint user_checksum;
  int iVar1;
  undefined8 ****temp_free_ptr;
  ulonglong user_input_byte_index;
  char *result_message;
  undefined1 *status_buffer;
  undefined8 ****data_buffer_ptr;
  ulonglong pass_input_length_1;
  bool access_granted;
  undefined8 uStack_d0;
  undefined1 status_buffer_small [8];
  undefined1 status_buffer_large [32];
  undefined8 ***allocated_ptrs [2];
  size_t compare_length;
  ulonglong ptr_metadata_size;
  undefined8 ***user_input_buffer;
  undefined8 uStack_78;
  ulonglong user_input_length;
  ulonglong user_input_length_param;
  undefined8 ***pass_input_buffer;
  undefined8 uStack_58;
  size_t pass_input_length;
  ulonglong pass_input_length_param;
  ulonglong checksum_seed;
  code *code_pointer;
  undefined8 ***pass_input_buffer_ptr;
  undefined8 ***user_input_buffer_ptr;
  ulonglong user_input_length_1;
  
  status_buffer = status_buffer_small;
  checksum_seed = MAGIC_VALUE_1 ^ (ulonglong)status_buffer_small;
  user_input_byte_index = 0;
  uStack_78 = 0;
  user_input_length = 0;
  user_input_length_param = 0xf;
  user_input_buffer = (undefined8 ****)0x0;
  uStack_58 = 0;
  pass_input_length = 0;
  pass_input_length_param = 0xf;
  pass_input_buffer = (undefined8 ****)0x0;
  formatted_string_output((basic_ostream<char,struct_std::char_traits<char>_> *)cout_exref,"User: ");
  extract_token_from_stream((basic_istream<char,struct_std::char_traits<char>_> *)cin_exref,
                (longlong *)&user_input_buffer,max_len);
  formatted_string_output((basic_ostream<char,struct_std::char_traits<char>_> *)cout_exref,"Pass: ");
  extract_token_from_stream((basic_istream<char,struct_std::char_traits<char>_> *)cin_exref,
                (longlong *)&pass_input_buffer,max_len);
  user_input_length_1 = user_input_length_param;
  user_input_buffer_ptr = user_input_buffer;
  user_checksum = 0;
  pass_input_length_1 = user_input_byte_index;
  if (user_input_length != 0) {
    do {
      temp_free_ptr = &user_input_buffer;
      if (0xf < user_input_length_param) {
        temp_free_ptr = (undefined8 ****)user_input_buffer;
      }
      user_checksum =
           ((int)user_input_byte_index + 1) *
           (int)*(char *)((longlong)temp_free_ptr + user_input_byte_index) +
           (int)pass_input_length_1;
      user_checksum = user_checksum * 8 ^ user_checksum;
      user_input_byte_index = user_input_byte_index + 1;
      pass_input_length_1 = (ulonglong)user_checksum;
    } while (user_input_byte_index < user_input_length);
  }
  int_to_string_dynamic(allocated_ptrs,user_checksum * 0x539 ^ 0x5a5a);
  pass_input_length_1 = pass_input_length_param;
  pass_input_buffer_ptr = pass_input_buffer;
  temp_free_ptr = &pass_input_buffer;
  if (0xf < pass_input_length_param) {
    temp_free_ptr = (undefined8 ****)pass_input_buffer;
  }
  data_buffer_ptr = allocated_ptrs;
  if (0xf < ptr_metadata_size) {
    data_buffer_ptr = (undefined8 ****)allocated_ptrs[0];
  }
  if (compare_length == pass_input_length) {
    if (compare_length == 0) {
      access_granted = true;
    }
    else {
      iVar1 = memcmp(data_buffer_ptr,temp_free_ptr,compare_length);
      access_granted = iVar1 == 0;
    }
  }
  else {
    access_granted = false;
  }
  if (0xf < ptr_metadata_size) {
    temp_free_ptr = (undefined8 ****)allocated_ptrs[0];
    status_buffer = status_buffer_small;
    if (0xfff < ptr_metadata_size + 1) {
      temp_free_ptr = (undefined8 ****)allocated_ptrs[0][-1];
      data_buffer_ptr =
           (undefined8 ****)((longlong)allocated_ptrs[0] + (-8 - (longlong)temp_free_ptr));
      status_buffer = status_buffer_small;
      if ((undefined8 ****)0x1f < data_buffer_ptr) {
        code_pointer = (code *)swi(0x29);
        (*code_pointer)(5);
        temp_free_ptr = data_buffer_ptr;
        status_buffer = status_buffer_large;
      }
    }
    *(undefined8 *)(status_buffer + -8) = 0x140001424;
    free(temp_free_ptr);
  }
  result_message = "Access granted\n";
  if (!access_granted) {
    result_message = "Access denied\n";
  }
  *(undefined8 *)(status_buffer + -8) = 0x140001443;
  formatted_string_output((basic_ostream<char,struct_std::char_traits<char>_> *)cout_exref,result_message);
  if (0xf < pass_input_length_1) {
    temp_free_ptr = (undefined8 ****)pass_input_buffer_ptr;
    if (0xfff < pass_input_length_1 + 1) {
      temp_free_ptr = (undefined8 ****)pass_input_buffer_ptr[-1];
      data_buffer_ptr =
           (undefined8 ****)((longlong)pass_input_buffer_ptr + (-8 - (longlong)temp_free_ptr));
      if ((undefined8 ****)0x1f < data_buffer_ptr) {
        code_pointer = (code *)swi(0x29);
        (*code_pointer)(5);
        status_buffer = status_buffer + 8;
        temp_free_ptr = data_buffer_ptr;
      }
    }
    *(undefined8 *)(status_buffer + -8) = 0x14000147d;
    free(temp_free_ptr);
  }
  if (0xf < user_input_length_1) {
    temp_free_ptr = (undefined8 ****)user_input_buffer_ptr;
    if (0xfff < user_input_length_1 + 1) {
      temp_free_ptr = (undefined8 ****)user_input_buffer_ptr[-1];
      data_buffer_ptr =
           (undefined8 ****)((longlong)user_input_buffer_ptr + (-8 - (longlong)temp_free_ptr));
      if ((undefined8 ****)0x1f < data_buffer_ptr) {
        code_pointer = (code *)swi(0x29);
        (*code_pointer)(5);
        status_buffer = status_buffer + 8;
        temp_free_ptr = data_buffer_ptr;
      }
    }
    *(undefined8 *)(status_buffer + -8) = 0x1400014b7;
    free(temp_free_ptr);
  }
  *(undefined8 *)(status_buffer + -8) = 0x1400014c9;
  return 0;
}


Por favor, lea el listado del descompilador, y luego complete mi código C++ a continuación para calcular la contraseña correcta para un nombre de usuario ingresado:``` #include #include #include #include

int main() { std::string username; std::cout << "User: "; std::cin >> username;

root@kitploit:~
uint32_t password_value = 0;

std::cout << "Pass: " << password_value << "\n";
return 0;

}

root@kitploit:~
## La solución generada

La primera vez que generó una solución, fue incorrecta. La he incluido en [tut1.txt](https://github.com/markoglasgow/gemma_crackme_tutorial/blob/HEAD/tut1.txt) de este repositorio. A primera vista, la solución _parece_ correcta, pero falla cuando la probamos. Las IA son fundamentalmente poco fiables, y este es un ejemplo perfecto de ello.

No importa, simplemente podemos abrir un nuevo chat, copiar y pegar el prompt anterior, e intentar que la IA genere una nueva solución. En mi caso, obtuve una solución funcional en el segundo intento:```
#include <cstdio>
#include <cstdint>
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>

/**
 * @brief Simulates the custom checksum calculation defined in the crackme function.
 *
 * @param username The input string (username).
 * @return int The final calculated user_checksum.
 */
int calculate_username_checksum(const std::string& username) {
    // user_checksum is treated as an 'int' (32-bit signed integer) during the core calculation.
    int user_checksum = 0;

    // pass_input_length_1 acts as the state variable and is a ulonglong (64-bit).
    // It starts at 0, as pass_input_length_1 = user_input_byte_index (which is 0) initially.
    uint64_t state_pass_length_1 = 0;

    for (size_t i = 0; i < username.length(); ++i) {
        // i = user_input_byte_index

        // Step 1: Calculate the intermediate value T1.
        // The decompiler implies this calculation happens using standard int math 
        // (truncating/wrapping if intermediate values exceed 32 bits).
        // (int)user_input_byte_index + 1
        int index_term = (int)(i + 1);

        // (int)*(char *)((longlong)temp_free_ptr + user_input_byte_index)
        // This is just the ASCII value of the character.
        int char_term = (int)username[i];

        // (int)pass_input_length_1
        // The state is cast down to an int for this arithmetic operation.
        int state_term = (int)state_pass_length_1;

        // T1 = ((int)user_input_byte_index + 1) * (int)char + (int)state
        int intermediate_t1 = index_term * char_term + state_term;

        // Step 2: Apply the XOR/Multiplication hash function.
        // user_checksum = user_checksum * 8 ^ user_checksum;
        // Note: The original code structure suggests the update happens here, 
        // but since we are recalculating the hash step-by-step, we use the definition:
        // user_checksum = T1 * 8 ^ T1; 

        // Since the original code overwrites user_checksum completely with the hash 
        // of the current character, we use the T1 derived above.

        int current_checksum = intermediate_t1;
        current_checksum = current_checksum * 8 ^ current_checksum;

        // Update the final running checksum
        user_checksum = current_checksum;

        // Step 3: Update the state variable for the next iteration.
        state_pass_length_1 = (uint64_t)user_checksum;
    }

    return user_checksum;
}


int main()
{
    std::string username;
    std::cout << "User: ";
    std::cin >> username;

    // 1. Calculate the final checksum using the username
    int final_user_checksum = calculate_username_checksum(username);

    // 2. Calculate the final hash/secret value
    // FinalHashValue = user_checksum * 0x539 ^ 0x5a5a
    // Note: The arithmetic here is done on the final 32-bit 'int' checksum.
    uint32_t final_hash_value = (uint32_t)final_user_checksum * 0x539 ^ 0x5a5a;

    // 3. Convert the final hash value into a string (as implied by int_to_string_dynamic)
    // Since the password needs to match the memory contents (memcmp), it must be the string representation.
    std::stringstream ss;
    ss << final_hash_value;
    std::string required_password = ss.str();


    // We output the required password string.
    std::cout << "Pass: " << required_password << "\n";
    return 0;
}

Incluí el chat que generó la solución como solution_chat.html en este repositorio. Incluía el flujo de razonamiento, lo cual me pareció bastante interesante para un modelo tan pequeño ejecutándose localmente.

De todas formas, podemos compilar la solución con Visual Studio y luego ejecutarla para generar una combinación válida de usuario/contraseña. Luego introducimos el usuario/contraseña en el crackme y verificamos que lo hemos resuelto:

texto alternativo

gg

Limitaciones, Alucinaciones y Errores

Como hemos visto varias veces a lo largo de este tutorial, la IA es inherentemente poco fiable. Considera las dos capturas de pantalla siguientes:

texto alternativo

texto alternativo

La primera captura es de antes en el tutorial, mientras que la segunda fue tomada en el mismo paso, pero con una ejecución diferente de la IA renombrando. Observa en la ventana del descompilador cómo en la captura superior el búfer de entrada de contraseña está etiquetado como &pass_input_buffer, mientras que en la captura inferior el mismo búfer está etiquetado como &username_buffer_1. En la captura inferior, la IA nos está mintiendo, y a esas mentiras las llamamos "alucinaciones".

Toda IA te mentirá y alucinará. Cuanto más pequeño es el modelo, más frecuentes son las alucinaciones. Cuanto más grande es el modelo, más suerte tendrás y menos alucinará. Cuanto más pagues, más suerte tendrás. Sin embargo, ningún modelo de IA es inmune a las alucinaciones, y nunca debes olvidar esto mientras trabajes con ellos.

La IA es más adecuada para tareas donde puedas verificar el resultado, o donde el resultado sea de bajo riesgo y no se requiera una precisión perfecta. En el caso de la ingeniería inversa, la IA puede ayudarnos a echar un vistazo rápido a un binario desconocido y ahorrarnos mucho trabajo manual tedioso. Pero como hemos visto con el mal renombrado que hizo, también puede llevarnos por el camino equivocado. Trata siempre los resultados generados por IA con precaución y, si puedes, verifícalos.

La mejor manera de usar la IA es verificar sus resultados de forma automática. Por ejemplo, si estás haciendo que la IA escriba código, sería genial tener un script que pruebe la salida de la IA contra algún conjunto de pruebas que hayas escrito a mano. En el caso de que la IA genere una solución para nuestro crackme, podríamos haber escrito un conjunto de pruebas que pruebe automáticamente cada solución generada por IA contra el crackme, y luego aceptar una solución que genere contraseñas correctas para miles de nombres de usuario diferentes. Entonces podríamos ejecutar la IA en un bucle hasta que escriba una solución que pase nuestras pruebas.

Limitaciones de los modelos pequeños

Resolvimos este crackme con un modelo relativamente pequeño (Gemma 4 E4B). Este modelo tiene 8 mil millones de parámetros, a diferencia de los modelos de última generación que existen que tienen cientos de miles de millones o incluso billones de parámetros. Un modelo más pequeño no podrá razonar bien, alucinará con más frecuencia y se volverá inútil más rápido a medida que se llene su ventana de contexto.

En un ejercicio real de ingeniería inversa, podríamos tener que invertir funciones que son más grandes que la ventana de contexto de nuestro modelo pequeño, o que tienen una lógica demasiado enrevesada para que su inteligencia limitada pueda razonar. Necesitamos conocer las limitaciones de nuestros modelos cuando los usamos, para entender de antemano dónde y cómo nos fallarán.

A medida que el hardware de consumo avanza junto con la arquitectura de los modelos, creo que podremos ejecutar modelos más grandes y mejores localmente, lo que pondrá tareas de ingeniería inversa aún más avanzadas al alcance de nuestros modelos locales.

Trabajo Futuro

El plugin de Ghidra "Fast AI Renamer" que escribí actualmente genera un resumen para cada función que analiza, y tiene la capacidad de exportar de forma masiva los listados de C y ensamblador del proyecto en el que está trabajando. Me gustaría escribir una herramienta que calcule un embedding para cada resumen de función y luego lo indexe en una base de datos vectorial o SQLite, y luego podamos consultar la base de datos para encontrar funciones según consultas en lenguaje natural.

El plugin de Ghidra actualmente utiliza un flujo de trabajo simple sin conversación de múltiples turnos para renombrar, y se basa en analizar un CSV de la salida de la IA para realizar su tarea. Implementé las cosas de esta manera porque no creía que un modelo de IA pequeño pudiera manejar las largas conversaciones y las múltiples llamadas a herramientas inherentes a un flujo de trabajo agéntico. Puede que me equivoque, y se deberían explorar otras formas de incitar a la IA. Aun así, para modelos locales pequeños, creo que es mejor limitar el tamaño de la ventana de contexto tanto como sea posible.

No estoy seguro de lo vulnerable que es el plugin de Ghidra a la inyección de instrucciones, y cualquier profesional de seguridad debería tener cuidado al usarlo para analizar malware o binarios hostiles. En general, esto se aplica a todas las herramientas de ingeniería inversa impulsadas por IA, que pueden tener servidores MCP sin autenticación en localhost, o herramientas disponibles para funciones sensibles que pueden lograr ejecución de código. Ten cuidado.

Dado que estamos usando un modelo de propósito general pequeño para ingeniería inversa, me pregunto cuánto se puede mejorar su rendimiento ajustándolo finamente con conversaciones destiladas de un modelo más grande e inteligente.

Finalmente, la funcionalidad de exportación de C/ensamblador del plugin de Ghidra funciona bien con Claude Code, y pude usar Claude Code + Sonnet para escribir una solución correcta para este crackme a la primera, simplemente alimentándolo con la descompilación anotada del crackme y diciéndole que encuentre una solución. Me pregunto si hay alguna habilidad que podamos escribir para Claude Code que lo haga mejor analizando un binario a través de los archivos de texto de su salida del descompilador, y no hacerlo pasar por un servidor MCP hacia Ghidra para realizar el análisis. Por ejemplo, podríamos introducir una habilidad de para Claude Code que le permita inspeccionar los bytes en la Memoria Virtual del binario, de modo que si ve algunos datos referenciados desde los listados de desensamblado/descompilación, pueda ver qué contiene esa memoria sin tener que pasar por Ghidra.

Agradecimientos y Reconocimientos

Gracias a Kryptos por hacer el Crackme, y a Crackmes.one por alojarlo. Les agradezco por retomar donde lo dejó crackmes.de (QEPD).

Gracias a Google por lanzar un modelo local sorprendente y elegante.

Gracias a la NSA por usar mis impuestos para hacer una herramienta de ingeniería inversa genial, y gracias a Ryan Kurtz por todo el apoyo que brinda en el Github de Ghidra.

Descargar herramienta