{"id":4900,"date":"2026-06-30T21:12:56","date_gmt":"2026-06-30T19:12:56","guid":{"rendered":"https:\/\/dmgmit.eu\/blog\/?p=4900"},"modified":"2026-06-30T21:21:48","modified_gmt":"2026-06-30T19:21:48","slug":"simplevm-resolviendo-una-maquina-virtual-mediante-ingenieria-inversa-y-criptoanalisis-diferencial","status":"publish","type":"post","link":"https:\/\/dmgmit.eu\/blog\/2026\/06\/30\/simplevm-resolviendo-una-maquina-virtual-mediante-ingenieria-inversa-y-criptoanalisis-diferencial\/","title":{"rendered":"SimpleVM: Resolviendo una M\u00e1quina Virtual mediante Ingenier\u00eda Inversa y Criptoan\u00e1lisis Diferencial"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">En este art\u00edculo analizaremos <strong>SimpleVM<\/strong> (lo puedes encontrar aqu\u00ed <a href=\"https:\/\/reversing.kr\/\">https:\/\/reversing.kr\/<\/a>), un cl\u00e1sico reto tipo <em>Crackme<\/em> que implementa una arquitectura basada en una m\u00e1quina virtual propia. Veremos c\u00f3mo pasar de la ceguera inicial frente a un binario aparentemente opaco hasta la obtenci\u00f3n de la clave correcta mediante una combinaci\u00f3n de ingenier\u00eda inversa, an\u00e1lisis din\u00e1mico y criptoan\u00e1lisis diferencial.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Herramientas utilizadas:<\/strong> GDB, IDA Pro<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Introducci\u00f3n<\/h1>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">1. Entendiendo el escenario<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Al ejecutar el binario en Linux nos encontramos con una interfaz extremadamente sencilla:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ .\/SimpleVM\nInput : VM_is_fun\nWrong<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Cualquier cadena que introduzcamos devuelve un contundente <strong>Wrong<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Un an\u00e1lisis preliminar muestra que el programa no utiliza funciones de comparaci\u00f3n est\u00e1ndar como <code>strcmp()<\/code>. En su lugar, delega toda la validaci\u00f3n a un int\u00e9rprete interno que ejecuta bytecodes de una m\u00e1quina virtual.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Al cargar el binario en IDA observamos adem\u00e1s un detalle importante: el ejecutable parece estar empaquetado. Gran parte del c\u00f3digo visible inicialmente no corresponde a la l\u00f3gica real del programa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dado que se trata de un binario Linux, la forma m\u00e1s c\u00f3moda de obtener el c\u00f3digo desempaquetado consiste en ejecutarlo bajo GDB y realizar un volcado de memoria una vez finalizado el proceso de desempaquetado.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ sudo gdb .\/SimpleVM\n\n(gdb) catch syscall write\nCatchpoint 1 (syscall 'write')\n\n(gdb) run\nStarting program: \/SimpleVM\n\nCatchpoint 1 (call to syscall write)\n\n(gdb) finish\nInput :\n\nCatchpoint 1 (returned from syscall write)\n\n(gdb) info proc mappings<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Entre los distintos segmentos mapeados encontramos uno especialmente interesante:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>0x08048000 - 0x0804c000<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esa regi\u00f3n contiene el c\u00f3digo ya desempaquetado de la aplicaci\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Realizamos entonces un volcado:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>(gdb) dump memory volcado.bin 0x08048000 0x0804c000<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Una vez cargado el fichero resultante en IDA, ya podemos comenzar el an\u00e1lisis real.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">2. Ingenier\u00eda inversa del int\u00e9rprete<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Al examinar el binario desempaquetado identificamos r\u00e1pidamente el n\u00facleo de la m\u00e1quina virtual: la funci\u00f3n <code>sub_8048C6D()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La arquitectura de la VM est\u00e1 compuesta por varios elementos fundamentales:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Program Counter (PC)<\/strong>: gestionado por <code>sub_8048A48()<\/code>, encargado de obtener la siguiente instrucci\u00f3n.<\/li>\n\n\n\n<li><strong>Buffer de bytecodes ofuscado<\/strong>: las instrucciones son le\u00eddas aplicando una m\u00e1scara XOR <code>0x10<\/code>.<\/li>\n\n\n\n<li><strong>Despachador de opcodes<\/strong>: una estructura similar a un <code>switch-case<\/code> que procesa los distintos bytecodes.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">C\u00f3digo simplificado:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>int sub_8048C6D()\n{\n    while (1)\n    {\n        sub_8048A48();      \/\/ Fetch Opcode\n\n        switch (dword_804B190)\n        {\n            case 6:\n                sub_8048ABB(); \/\/ XOR\n                continue;\n\n            case 7:\n                sub_8048B31(); \/\/ Comparaci\u00f3n\n                continue;\n\n            case 9:\n                sub_8048BCE(); \/\/ Salto condicional\n                continue;\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Durante el an\u00e1lisis observamos que los opcodes m\u00e1s relevantes son:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><th>Opcode<\/th><th>Funci\u00f3n<\/th><\/tr><tr><td>6<\/td><td>Operaci\u00f3n XOR<\/td><\/tr><tr><td>7<\/td><td>Comparaci\u00f3n<\/td><\/tr><tr><td>9<\/td><td>Salto condicional<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">El opcode que m\u00e1s nos interesa es el <strong>7<\/strong>, implementado por <code>sub_8048B31()<\/code>, ya que es el encargado de verificar si los valores calculados a partir de nuestra entrada coinciden con los esperados por la VM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si la comparaci\u00f3n falla, el opcode 9 detecta el error y redirige la ejecuci\u00f3n hacia la rutina que imprime <strong>Wrong<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">3. Localizando la comparaci\u00f3n cr\u00edtica con GDB<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Conociendo la funci\u00f3n responsable de la validaci\u00f3n, centramos el an\u00e1lisis din\u00e1mico en <code>sub_8048B31()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tras seguir su ejecuci\u00f3n encontramos la comparaci\u00f3n definitiva:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>0x8048b5c: mov 0x804b198,%edx\n0x8048b62: mov 0x804b194,%eax\n0x8048b67: cmp %eax,%edx\n0x8048b69: jne 0x8048b77<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aqu\u00ed se produce el momento decisivo:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>EAX<\/strong> contiene el valor objetivo almacenado por la VM.<\/li>\n\n\n\n<li><strong>EDX<\/strong> contiene el valor derivado de nuestra entrada.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Si ambos registros coinciden, la ejecuci\u00f3n contin\u00faa por la ruta correcta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para interceptar la comparaci\u00f3n utilizamos el siguiente script de GDB (script.gdb):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>set follow-fork-mode parent\ncatch syscall write\nrun\ndelete 1\nbr *0x08048B67\nc<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Lanzamos el programa con la clave 1111111 (veremos, posteriormente, que la clave tiene una longitud m\u00e1xima de 7 caracteres):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ sudo gdb-multiarch -x script.gdb .\/SimpleVM<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Y registramos los valores de ambos registros:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>info registers eax edx<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Para acelerar el proceso forzamos temporalmente la comparaci\u00f3n:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>set $eax = $edx\nc\ninfo registers eax edx<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Repitiendo la operaci\u00f3n varias veces obtenemos todos los pares de valores necesarios para el an\u00e1lisis.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Realizamos el mismo proceso con la clave AAAAAAA y tomamos nota de los registros eax y edx.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">4. Criptoan\u00e1lisis diferencial<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Nuestro siguiente objetivo consiste en descubrir qu\u00e9 transformaci\u00f3n aplica la VM sobre cada car\u00e1cter de entrada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para ello utilizamos dos cadenas de prueba controladas, ambas de longitud siete:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1111111\nAAAAAAA<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Interceptando la ejecuci\u00f3n en el breakpoint obtenemos los siguientes resultados.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Prueba 1: 1111111<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">ASCII <code>'1' = 0x31<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>EAX<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>09 02 26 2d 22 07 10<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>EDX<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>51 57 24 36 7d 52 49<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Prueba 2: AAAAAAA<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">ASCII <code>'A' = 0x41<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>EAX<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>09 02 26 2d 22 07 10<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>EDX<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>21 27 54 46 0d 22 39<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La primera observaci\u00f3n es inmediata: <strong>EAX permanece constante<\/strong>, independientemente de la entrada suministrada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eso confirma que EAX representa el valor objetivo que debemos alcanzar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Analizando la relaci\u00f3n entre la entrada y los valores generados en EDX descubrimos una transformaci\u00f3n XOR por posici\u00f3n:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>EDX = Input XOR M\u00e1scara<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Por tanto:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>M\u00e1scara = Input XOR EDX<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Calculamos cada valor utilizando la primera prueba:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>Posici\u00f3n<\/td><td>C\u00e1lculo<\/td><td>M\u00e1scara<\/td><\/tr><tr><td>0<\/td><td>0x31 ^ 0x51<\/td><td>0x60<\/td><\/tr><tr><td>1<\/td><td>0x31 ^ 0x57<\/td><td>0x66<\/td><\/tr><tr><td>2<\/td><td>0x31 ^ 0x24<\/td><td>0x15<\/td><\/tr><tr><td>3<\/td><td>0x31 ^ 0x36<\/td><td>0x07<\/td><\/tr><tr><td>4<\/td><td>0x31 ^ 0x7d<\/td><td>0x4c<\/td><\/tr><tr><td>5<\/td><td>0x31 ^ 0x52<\/td><td>0x63<\/td><\/tr><tr><td>6<\/td><td>0x31 ^ 0x49<\/td><td>0x78<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">La m\u00e1scara completa queda:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>60 66 15 07 4c 63 78<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">5. Reconstrucci\u00f3n de la clave<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Ya conocemos la transformaci\u00f3n utilizada por la VM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si queremos que:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>EDX = EAX<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">debemos despejar el valor de entrada:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Input = EAX XOR M\u00e1scara<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Realizando el c\u00e1lculo posici\u00f3n por posici\u00f3n:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>Posici\u00f3n<\/td><td>Operaci\u00f3n<\/td><td>Resultado<\/td><\/tr><tr><td>0<\/td><td>0x09 ^ 0x60<\/td><td>0x69&nbsp;<\/td><\/tr><tr><td>1<\/td><td>0x02 ^ 0x66<\/td><td>0x64&nbsp;<\/td><\/tr><tr><td>2<\/td><td>0x26 ^ 0x15<\/td><td>0x33&nbsp;<\/td><\/tr><tr><td>3<\/td><td>0x2d ^ 0x07<\/td><td>0x2a&nbsp;<\/td><\/tr><tr><td>4<\/td><td>0x22 ^ 0x4c<\/td><td>0x6e&nbsp;<\/td><\/tr><tr><td>5<\/td><td>0x07 ^ 0x63<\/td><td>0x64&nbsp;<\/td><\/tr><tr><td>6<\/td><td>0x10 ^ 0x78<\/td><td>0x68&nbsp;<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Uniendo todos los caracteres obtenemos la soluci\u00f3n.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">6. Conclusi\u00f3n<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Este reto demuestra que no siempre es necesario reconstruir completamente una m\u00e1quina virtual o desarrollar un emulador propio para resolver un <em>Crackme<\/em>. En muchas ocasiones resulta m\u00e1s eficiente identificar el punto exacto donde se produce la validaci\u00f3n y utilizar an\u00e1lisis diferencial para obligar al binario a revelar las transformaciones que aplica sobre la entrada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una vez localizada la comparaci\u00f3n cr\u00edtica, la VM deja de ser una caja negra y pasa a convertirse en un simple sistema de ecuaciones. A partir de ah\u00ed, la resoluci\u00f3n se reduce a aplicar unas pocas operaciones XOR y reconstruir la clave correcta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>En este art\u00edculo analizaremos SimpleVM (lo puedes encontrar aqu\u00ed https:\/\/reversing.kr\/), un cl\u00e1sico reto tipo Crackme que implementa una arquitectura basada en una m\u00e1quina virtual propia. Veremos c\u00f3mo pasar de la ceguera inicial frente a un binario aparentemente opaco hasta la obtenci\u00f3n de la clave correcta mediante una combinaci\u00f3n de ingenier\u00eda inversa, an\u00e1lisis din\u00e1mico y criptoan\u00e1lisis diferencial. Herramientas utilizadas: GDB, IDA Pro Introducci\u00f3n 1. Entendiendo el escenario Al ejecutar el binario en Linux nos encontramos con una interfaz extremadamente sencilla: Cualquier&#8230;<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/dmgmit.eu\/blog\/2026\/06\/30\/simplevm-resolviendo-una-maquina-virtual-mediante-ingenieria-inversa-y-criptoanalisis-diferencial\/\"> Leer m\u00e1s<span class=\"screen-reader-text\">  Leer m\u00e1s<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[],"class_list":["post-4900","post","type-post","status-publish","format-standard","hentry","category-informatica"],"_links":{"self":[{"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/posts\/4900","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/comments?post=4900"}],"version-history":[{"count":6,"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/posts\/4900\/revisions"}],"predecessor-version":[{"id":4907,"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/posts\/4900\/revisions\/4907"}],"wp:attachment":[{"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/media?parent=4900"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/categories?post=4900"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dmgmit.eu\/blog\/wp-json\/wp\/v2\/tags?post=4900"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}