{"id":485,"date":"2026-03-08T21:20:56","date_gmt":"2026-03-08T21:20:56","guid":{"rendered":"https:\/\/hackcuba.net\/?p=485"},"modified":"2026-03-08T21:20:56","modified_gmt":"2026-03-08T21:20:56","slug":"ingenieria-inversa","status":"publish","type":"post","link":"https:\/\/hackcuba.net\/?p=485","title":{"rendered":"Ingenier\u00eda Inversa"},"content":{"rendered":"\n<p>Recientemente, hablando con un colega del lugar donde trabajo, acerca de las mejores maneras de proteger un producto contra las t\u00e9cnicas de <em>cracking<\/em>, me vino el recuerdo de aquellos d\u00edas en los que me interes\u00e9 por primera vez en el tema, no como programador que protege su producto, sino como usuario que lo desprotege para uso propio sin costo alguno (por Dios, \u00bfqui\u00e9n ha pagado aqu\u00ed por un <em>software<\/em>?). Claro que en aquel entonces mi enfoque era muy distinto al de ahora.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Resulta que una vez fui atacante, y ahora puedo ser atacado, lo cual no es un gran problema, si consideramos que conozco las armas de mi enemigo, s\u00e9 c\u00f3mo las usan, y m\u00e1s a\u00fan, las he usado. La verdad, hace ya tiempo que no practico el cracking; antes lo hac\u00eda por diversi\u00f3n -nunca por necesidad-, ya que todo lo que he crackeado ya ha sido trabajado. Lamentablemente, lo que no se ejercita se olvida y con el tiempo vamos perdiendo las facultades que menos usamos, as\u00ed que si escribo este art\u00edculo es para no olvidar y ya de paso les brindo a ustedes la posibilidad de conocer esta arma tan fina, precisa y poderosa, que es la ingenier\u00eda inversa.<\/p>\n\n\n\n<p>Lo que vamos a hacer es una pr\u00e1ctica muy sencilla y bastante elemental. Probablemente muchos hayan o\u00eddo hablar del m\u00e9todo \u00ab74\/75\u00bb, una t\u00e9cnica muy b\u00e1sica, pero incre\u00edblemente \u00fatil y, particularmente yo, no s\u00f3lo la he usado para quebrantar sistemas de verificaci\u00f3n de n\u00fameros de serie, sino para muchas cosas m\u00e1s.<\/p>\n\n\n\n<p>Primero lo primero, \u00bfa qu\u00e9 nos enfrentamos? Bueno, a partir de ahora nuestro enemigo n\u00famero uno ser\u00e1 aquel cuadro con dos cajas de texto y dos botones al cual le insertas algo como un nombre de usuario y un serial y m\u00e1gicamente, por una raz\u00f3n que parece contradecir las leyes naturales que rigen nuestro mundo, casi siempre nos dice lo mismo: \u00ab<em>Los datos suministrados no son los correctos, verifique y vuelva a intentarlo<\/em>\u00ab. Este es un escenario cl\u00e1sico, lo del nombre de usuario y el serial pude cambiar, pues algunos s\u00f3lo piden una sola cosa: el serial; el mensaje de error puede ser diferente, en fin, la variedad se manifiesta entre estos odiosos cuadros de di\u00e1logo.<\/p>\n\n\n\n<p>Segundo paso: conoce a tu enemigo, y aqu\u00ed surge la segunda pregunta, \u00bfc\u00f3mo funciona? Como ya he dicho, la variedad se manifiesta en todo su esplendor, pero limit\u00e1ndonos al escenario cl\u00e1sico, el funcionamiento es el siguiente: el programa recibe un nombre de usuario; luego, partiendo del mismo, genera un c\u00f3digo que comparar\u00e1 con el n\u00famero de serie; de ser iguales, \u00a1felicidades!, puedes usar el producto, si no, tendr\u00e1s que sentarte a leer uno de esos tutoriales de <em>cracking<\/em>, lo que me recuerda por qu\u00e9 est\u00e1s leyendo este art\u00edculo.<\/p>\n\n\n\n<p>Tambi\u00e9n est\u00e1 el otro caso que te pide s\u00f3lo el serial, dif\u00edcil de encontrar, demasiado pobre e ineficiente, muy f\u00e1cil de crackear, as\u00ed que ya casi nadie lo usa. M\u00e1s tarde hablo de \u00e9l.<\/p>\n\n\n\n<p>Hasta ahora sabemos que al proporcionarle mi nombre al programa, \u00e9ste genera el serial a partir del mismo y luego compara el que obtuvo con el que yo le d\u00ed. Llevemos eso a los c\u00f3digos.<\/p>\n\n\n\n<p>El esquema b\u00e1sico puede verse as\u00ed:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/\/ si el serial es bueno\nif ( Serial_Entrado == GetSerial( Nombre_Entrado ) ) {\n    \/\/ gracias por usar un buen keygen\n  MessageBox( NULL, &quot;Gracias por usar nuestro programa, bla bla&quot;, &quot;Registro oK&quot;, NULL );\n    \/\/ quita restricciones\n  ...\n} else { \/\/si no\n    \/\/ el serial no sirve\n  MessageBox( NULL, &quot;Los datos entrados no son correctos.&quot;, &quot;Error&quot;, NULL );\n}\n<\/pre><\/div>\n\n\n<p>Los c\u00f3digos pueden cambiar, pero en esencia, la mayor\u00eda lucir\u00e1n como \u00e9ste, en especial si el programador no quiere pasar trabajo con algoritmos complicados.<\/p>\n\n\n\n<p>Aqu\u00ed vemos c\u00f3mo el nombre de usuario <code>( Nombre_Entrado )<\/code> es pasado como par\u00e1metro a la funci\u00f3n imaginaria <code>GetSerial<\/code>. Esta funci\u00f3n se encarga de generar a partir de cada caracter de <code>Nombre_Entrado<\/code> un <em>string<\/em>, que es nuestro n\u00famero de serie. Obviamente, cada nombre tendr\u00e1 su propio serial.<\/p>\n\n\n\n<p>Pasamos ahora al plan de ataque. Ya sabemos m\u00e1s, sabemos que nuestra felicidad depende en estos momentos de un <code>if<\/code>, o sea, una condicional. Por muchos intentos que tengamos disponibles, las posibilidades de acertar con el verdadero c\u00f3digo son irrisorias, por lo tanto, \u00bfqu\u00e9 hacemos? Pues invertimos la condicional de la siguiente manera:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/\/si el serial NO es bueno\nif ( Serial_Entrado != GetSerial( Nombre_Entrado ) ) {\n    \/\/gracias por registrarte\n  MessageBox( NULL, &quot;Gracias por usar nuestro programa bla bla&quot;, &quot;Registro oK&quot;, NULL );\n    \/\/quita restricciones\n  ...\n} else { \/\/si es bueno\n    \/\/mensaje de error\n  MessageBox( NULL, &quot;Los datos entrados no son correctos.&quot;, &quot;Error&quot;, NULL );\n}\n<\/pre><\/div>\n\n\n<p>\u00bfD\u00f3nde esta el cambio? El operador de la condicional cambi\u00f3 de <code>==<\/code> a <code>!=<\/code> -para aquellos que no conozcan el lenguaje C, <code>!=<\/code> equivale a <code>&lt;&gt;<\/code> en otros lenguajes-. \u00a1Listo!, ahora cualquier serial que escribamos ser\u00e1 v\u00e1lido, cualquiera excepto el verdadero :0. Bueno, qu\u00e9 m\u00e1s da, si las posibilidades de acertar son despreciables.<\/p>\n\n\n\n<p>Es aqu\u00ed donde todos me acusan de loco y me recuerdan que no tengo el c\u00f3digo fuente del programa. Lo cierto es que quiz\u00e1s no tengamos los c\u00f3digos en C, ni en lenguajes de alto nivel, pero sin dunda alguna tenemos los c\u00f3digos en ensamblador, y es all\u00ed donde vamos a hacer los cambios, directamente en el hexadecimal del programa.<\/p>\n\n\n\n<p>Aquellos que conocen de ensamblador, sabr\u00e1n que las condicionales se definen usando las instrucciones <code>JE<\/code>, <code>JZ<\/code> (\u00ab<em>Jump If Equal<\/em>\u00ab, \u00ab<em>Jump If Zero<\/em>\u00ab) y <code>JNE<\/code>, <code>JNZ<\/code> (\u00ab<em>Jump If Not Equal<\/em>\u00ab, \u00ab<em>Jump If Not Zero<\/em>\u00ab) seguido de la direcci\u00f3n de saltos y antecedido de un <code>TEST<\/code> o un <code>CMP<\/code>, que determina lo que se est\u00e1 comparando.<\/p>\n\n\n\n<p>El caso es que <code>JE<\/code>, codificado en hexadecimal, es 74 y <code>JNE<\/code>, 75; de ah\u00ed el nombre \u00ab74\/75\u00bb para esta t\u00e9cnica. Cambiaremos un <code>JE<\/code> por un <code>JNE<\/code>, o un <code>JNE<\/code> por un <code>JE<\/code>, 74 a 75 \u00f3 75 a 74, la idea es invertir la condicional a nuestro favor.<\/p>\n\n\n\n<p>Ya concluyendo la introducci\u00f3n del art\u00edculo y entrando en el \u00e1rea pr\u00e1ctica, har\u00e9 un comentario. Para tener \u00e9xito en el mundo de la ingenier\u00eda inversa, poseer conocimientos de ensamblador es fundamental; mientras mejor domines el ASM, mejor podr\u00e1s rastrear el c\u00f3digo de un programa, hasta el punto de sentirte tan c\u00f3modo leyendo el c\u00f3digo en ASM como si lo estuvieras leyendo en C o en otro lenguaje de alto nivel. No obstante, si no sabes nada de ASM, sigue leyendo igual. Cuando yo empec\u00e9 en el tema del <em>cracking<\/em> tampoco sab\u00eda nada de ensamblador, de hecho, mis inicios en ASM fueron gracias al <em>cracking<\/em>. Como principiante no necesitas saber escribir programas en ASM para tener \u00e9xito, pero s\u00ed es muy importante que sepas c\u00f3mo funciona lo b\u00e1sico, que conozcas las principales instrucciones, los registros del microprocesador, etc. Luego, con el tiempo, puedes profundizar y, si te interesa, empezar a usarlo para programar.<\/p>\n\n\n\n<p>Vamos a la pr\u00e1ctica. Para empezar, perm\u00edtanme presentarles a nuestro enemigo, un peque\u00f1o <a href=\"file:\/\/\/home\/h0ax\/Hacking%20&amp;%20Programacion\/Rebista%20BlackHat\/BlackHat%2024\/_tools\/crack_me.exe\">CrackMe<\/a> que acabo de preparar para este fin; servir\u00e1 bien de conejillo de indias. Este es el c\u00f3digo en C:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/*\n**\n**Nombre: CrackMe.c\n**Autor: h0aX\n**Fecha: 17-julio-2007\n**\n**Descripci\u00f3n: Nuestro conejillo de indias\n**\n**\n*\/\n\n#include\n#pragma hdrstop\n#pragma argsused\n\nHWND hwndMain, hwndButton1,\n     hwndButton2, hwndButton3,\n     hwndEditUser, hwndEditSerial;\n\nHINSTANCE hInst;\n\nvoid GetSerial( char *szName, char *szSerial )\n{\n  int i, cont;\n  memset( szSerial, 0, 255 );\n\n  cont = 0;\n\n    \/* Aqu\u00ed se genera el serial a partir del\n    ** nombre de usuario. Este es un ejemplo\n    ** sencillo, as\u00ed que simplemente invertiremos\n    ** el nombre de usuario y le ponemos un punto\n    ** al principio de la cadena, ej:\n    ** usuario: test serial: .tset\n    *\/\n  for ( i = strlen(szName) ; i &gt;= 0; i-- ) {\n    strcat(szSerial,&quot;.&quot;);\n    szSerial&#x5B;cont] = szName&#x5B;i] ;\n    cont++;\n  }\n}\n\nLRESULT CALLBACK WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )\n{\n  switch (uMsg) {\n    case WM_COMMAND: {\n      \/\/ bot\u00f3n registrar\n    if (hwndButton1 == (HWND) lParam) {\n      char szUserName&#x5B;255]; \/\/ usuario entrado\n      char szSerial&#x5B;255]; \/\/ serial verdadero\n      char szSerial_Entrado&#x5B;255]; \/\/ serial entrado\n      \/\/ obtiene los datos\n      GetWindowTextA( hwndEditUser, szUserName, 255 );\n      GetWindowTextA( hwndEditSerial, szSerial_Entrado, 255 );\n      \/\/ chequea si no hay nada en blanco\n      if ( ( !strcmp( szSerial_Entrado, &quot;&quot; ) ) || ( !strcmp( szUserName, &quot;&quot; ) ) ) {\n        MessageBoxA( hwndMain, &quot;Datos en blanco.&quot;, &quot;Error&quot;, MB_ICONERROR );\n        return 0;\n      }\n      \/\/ genera el serial a partir del usuario\n      GetSerial( szUserName, szSerial );\n      \/\/ y he aqu\u00ed nuestra preciosa condicional\n      if ( !strcmp( szSerial_Entrado, szSerial ) ) {\n        MessageBoxA( hwndMain, &quot;Serial correcto, felicidades.&quot;, &quot;Registro oK&quot;, MB_ICONINFORMATION );\n             \/\/ quita restricciones, bla bla ...\n      } else {\n        MessageBoxA( hwndMain, &quot;Los datos no son correctos.&quot;, &quot;Error&quot;, MB_ICONERROR );\n      }\n    } else\n      \/\/ bot\u00f3n cancelar\n    if (hwndButton2 == (HWND) lParam) {\n      PostQuitMessage(0);\n    } else\n      \/\/ bot\u00f3n acerca de\n    if (hwndButton3 == (HWND) lParam) {\n      MessageBoxA( hwndMain, &quot;CrackMe por h0aX para \\r\\nla revista &quot;\n      &quot;BlackHat,\\r\\npruebas de ingenieria inversa.&quot;\n      &quot;\\r\\n\\r\\n&#x5B;17-julio-2007]&quot;,\n      &quot;CrackMe por h0aX&quot;, MB_ICONINFORMATION );\n    }\n    return 0;\n  }\n  case WM_DESTROY: {\n    PostQuitMessage(0);\n    return 0;\n  }\n }\n return DefWindowProcA(hwnd,uMsg,wParam,lParam);\n}\n\nWINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)\n{\n  MSG msg;\n  WNDCLASS wndclass;\n  char szClassName&#x5B;] = &quot;hXClass_CrackMe&quot;;\n  hInst = hInstance;\n  wndclass.style = CS_VREDRAW | CS_HREDRAW;\n  wndclass.lpfnWndProc = WndProc;\n  wndclass.cbClsExtra = 0;\n  wndclass.cbWndExtra = 0;\n  wndclass.hInstance = hInstance;\n  wndclass.hIcon = LoadIconA(NULL, IDI_APPLICATION);\n  wndclass.hCursor = LoadCursorA(NULL, IDC_ARROW);\n  wndclass.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);\n  wndclass.lpszMenuName = NULL;\n  wndclass.lpszClassName = szClassName;\n  RegisterClassA(&amp;wndclass);\n  hwndMain = CreateWindow(szClassName,&quot;CrackMe por h0aX&quot;, WS_SYSMENU |     WS_MINIMIZEBOX | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 290, 180, NULL, NULL, hInstance, 0);\n  CreateWindow(&quot;Static&quot;,&quot;Nombre de usuario:&quot;, WS_VISIBLE | WS_CHILD, 15,10, 250,15, hwndMain, 0, hInst, NULL);\n  hwndEditUser = CreateWindowEx(WS_EX_CLIENTEDGE,&quot;Edit&quot;,&quot;&quot;, WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL, 15,30, 250,22, hwndMain, 0, hInst, NULL);\n\n   CreateWindow(&quot;Static&quot;,&quot;Serial:&quot;, WS_VISIBLE | WS_CHILD , 15,60, 250,15, hwndMain, 0, hInst, NULL);\n\n  hwndEditSerial = CreateWindowEx(WS_EX_CLIENTEDGE,&quot;Edit&quot;,&quot;&quot;, WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL , 15,80, 250,22, hwndMain, 0, hInst, NULL);\n\n  hwndButton1 = CreateWindow(&quot;Button&quot;,&quot;Registrar&quot;, WS_VISIBLE | WS_CHILD, 15,110, 75,25, hwndMain, 0, hInst, NULL);\n\n  hwndButton2 = CreateWindow(&quot;Button&quot;,&quot;Cancelar&quot;, WS_VISIBLE | WS_CHILD, 90, 110, 75,25, hwndMain, 0, hInst, NULL);\n\n  hwndButton3 = CreateWindow(&quot;Button&quot;,&quot;Acerca de&quot;, WS_VISIBLE | WS_CHILD, 190, 110, 75,25, hwndMain, 0, hInst, NULL);\n\n  ShowWindow(hwndMain, nCmdShow);\n\n  while (GetMessageA(&amp;msg, NULL,0,0)) {\n    TranslateMessage(&amp;msg);\n    DispatchMessageA(&amp;msg);\n  }\n  return msg.wParam;\n}\n<\/pre><\/div>\n\n\n<p>Seguimos con las herramientas. Nuestro arsenal personal debe estar compuesto b\u00e1sicamente de:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>un desensamblador<\/li>\n\n\n\n<li>un depurador<\/li>\n\n\n\n<li>un editor hexadecimal<\/li>\n<\/ul>\n\n\n\n<p>El <strong>desensamblador<\/strong> es una herramienta que, como su nombre indica, desensambla un programa, o sea, nos muestra su c\u00f3digo ensamblador de modo que nosotros podamos estudiarlo. Particularmente yo uso el W32Dasm.<\/p>\n\n\n\n<p>El <strong>depurador<\/strong> (o <em>debugger<\/em>) es una herramienta que nos permite no s\u00f3lo desensamblar un programa, sino tambi\u00e9n correrlo, ponerle <em>breakpoints<\/em> en determinadas posiciones del c\u00f3digo, efectuar cambios en el c\u00f3digo cargado en memoria, observar los valores de los registros, la pila; todo al mismo tiempo que lo corremos. Como <em>debugger<\/em> uso y recomiendo el OlyDbg.<\/p>\n\n\n\n<p>El <strong>editor hexadecimal<\/strong> nos permite ver el c\u00f3digo en sus valores hexadecimales, adem\u00e1s de hacer cambios en ellos para luego guardarlos en disco. Yo uso WinHex.<\/p>\n\n\n\n<p>Y ahora que nuestras armas han sido introducidas procedemos a utilizarlas.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conociendo al enemigo::<\/h2>\n\n\n\n<p>Si ejecutamos nuestro conejillo de indias nos encontraremos con dos cajas de texto y tres botones; los textos para el nombre y el serial, un bot\u00f3n para registrar el programa, otro para cancelar y el \u00faltimo para los cr\u00e9ditos del programa.<\/p>\n\n\n\n<p>Como si fu\u00e9ramos due\u00f1os de la situaci\u00f3n, enviamos el foco del teclado a las cajas de texto, tecleamos nombre de usuario \u00ab<em>yo<\/em>\u00ab, serial \u00ab<em>1234567890<\/em>\u00ab, y le damos al bot\u00f3n <em>Registrar<\/em>. Tal como sospechamos, el programa no se intimid\u00f3 ante la confianza que mostramos, pues nos ha dado un mensaje de error con el texto \u00ab<em>Los datos no son correctos<\/em>\u00ab. Muchos pensar\u00e1n que este texto no les sirve de mucho.<\/p>\n\n\n\n<p>Entonces procedemos a desensamblar el programa con el desensamblador, a ver qu\u00e9 encontramos de inter\u00e9s. Lo que aparece es una cantidad de c\u00f3digo impresionante, mucho m\u00e1s de lo que hemos escrito en C. Entre tantas l\u00edneas en ASM ser\u00eda una locura encontrar la instrucci\u00f3n espec\u00edfica de la condicional que buscamos. Pues que no cunda el p\u00e1nico, en el men\u00fa superior del W32Dasm hay un bot\u00f3n llamado <em>StrnRef<\/em>.<\/p>\n\n\n\n<p>Si pulsamos en \u00e9l nos mostrar\u00e1 una ventana con una lista de mensajes, que no son m\u00e1s que todas las referencias de <em>strings<\/em> que usa el programa; es decir, todas las cadenas de caracteres que aparezcan en el programa se encontrar\u00e1n all\u00ed. Si damos doble clic sobre una de ellas, nos lleva exactamente a la instrucci\u00f3n donde se le hizo referencia. Si se hace referencia al mismo texto en m\u00e1s de una instrucci\u00f3n, por cada vez que se la haga doble clic ir\u00e1 saltando a la siguiente referencia. Ahora vemos que aquel texto de error no era tan inservible. Busquemos el texto; no es tan dif\u00edcil encontrarlo ya que la lista est\u00e1 ordenada alfab\u00e9ticamente.<\/p>\n\n\n\n<p>Una vez localizado, hacemos doble clic para que nos lleve directamente a la instrucci\u00f3n que la referenci\u00f3 y vamos a parar directamente a la direcci\u00f3n <code>0040135Ch<\/code> dentro del c\u00f3digo, la cual corresponde a una instrucci\u00f3n <code>PUSH<\/code>, que estaba metiendo en la pila la direcci\u00f3n de memoria que conten\u00eda el texto. En el desensamblador que usen ver\u00e1n que cada instrucci\u00f3n corresponde con una direcci\u00f3n en el c\u00f3digo; \u00e9sta se encuentra al extremo izquierdo con formato <code>00000000h<\/code> (la <code>h<\/code> del final es para saber que el n\u00famero est\u00e1 en hexadecimal). Normalmente, el c\u00f3digo de las aplicaciones Win32 comienza en <code>00400000h<\/code>. La segunda columna nos muestra los valores hexadecimales del c\u00f3digo, y la tercera el c\u00f3digo como tal en ASM.<\/p>\n\n\n\n<p>Importante saber que cada instrucci\u00f3n en ASM tiene un equivalente en hexadecimal, por ejemplo, la instrucci\u00f3n <code>PUSH EBX<\/code> siempre ser\u00e1 <code>51h<\/code>, es decir, 51 en hexadecimal.<\/p>\n\n\n\n<p>Si somos lo suficientemente curiosos para seguir explorando las referencias de <em>strings<\/em> encontraremos otra muy interesante, \u00ab<em>Serial correcto, felicidades<\/em>\u00ab. Si le damos doble clic, iremos a parar a <code>00401343h<\/code>, s\u00f3lo a unas pocas instrucciones m\u00e1s arriba que la referencia al texto de error. \u00bfQu\u00e9 me dice todo esto?: que estamos en la zona caliente. Comencemos a rastrear hacia atr\u00e1s a partir de las referencias y casi inmediatamente en la posici\u00f3n <code>0040133Ah<\/code> encontramos una instrucci\u00f3n <code>JNE<\/code>, \u00a1bingo!, sin analizarla podr\u00eda apostar por ella, no obstante, mejor estar seguro.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"484\" height=\"26\" src=\"https:\/\/hackcuba.net\/wp-content\/uploads\/2026\/03\/0x660013-1.png\" alt=\"\" class=\"wp-image-488\" srcset=\"https:\/\/hackcuba.net\/wp-content\/uploads\/2026\/03\/0x660013-1.png 484w, https:\/\/hackcuba.net\/wp-content\/uploads\/2026\/03\/0x660013-1-300x16.png 300w\" sizes=\"auto, (max-width: 484px) 100vw, 484px\" \/><\/figure>\n\n\n\n<p>La instrucci\u00f3n dice <code>JNE 00401355<\/code>, es decir, que si una determinada condici\u00f3n no se cumple salte a la direcci\u00f3n <code>00401335h<\/code>; luego, si buscamos esta nueva posici\u00f3n veremos que queda exactamente atr\u00e1s del mensaje de registro satisfactorio. En pocas palabras, si la condici\u00f3n no se cumple salta el mensaje bueno y ve al mensaje malo, de lo contrario muestra el mensaje bueno. Ahora, para ver esta situaci\u00f3n mucho m\u00e1s de cerca, copiemos la direcci\u00f3n de la condicional, <code>0040133A<\/code>, abrimos el depurador OlyDbg y cargamos el programa all\u00ed. Una vez ejecutado veremos la pantalla dividida en varias \u00e1reas, donde arriba a la izquierda se encuentra el c\u00f3digo ASM. Si all\u00ed hacemos click derecho, men\u00fa \u00ab<em>Go To<\/em>\u00ab, submen\u00fa \u00ab<em>Expression<\/em>\u00bb y nos aparecer\u00e1 un cuadro de di\u00e1logo. All\u00ed ponemos la direcci\u00f3n de la instrucci\u00f3n <kbd>JNE, 0040133A<\/kbd>, damos <em>OK<\/em> y nos env\u00eda directamente a la instrucci\u00f3n deseada.<\/p>\n\n\n\n<p>Ahora vamos a analizar el c\u00f3digo con detenimiento.<\/p>\n\n\n\n<p><code>00401338 |. 85C0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TEST EAX,EAX<br>0040133A |. 75 19&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JNZ SHORT 00401355<br>0040133C |. 6A 40&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PUSH 40 <\/code>; \/Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL<code><br>0040133E |. 68 EDA04000&nbsp;&nbsp; PUSH 0040A0ED <\/code>; |Title = \u00abRegistro oK\u00bb<code><br>00401343 |. 68 CFA04000&nbsp;&nbsp; PUSH 0040A0CF <\/code>; |Text = \u00abSerial correcto, felicidades.\u00bb<code><br>00401348 |. FF35 C8C64000 PUSH DWORD PTR DS:[40C6C8] <\/code>; |hOwner = NULL<code><br>0040134E |. E8 BB800000&nbsp;&nbsp; CALL JMP.&amp;USER32.MessageBoxA <\/code>; \\MessageBoxA<code><br>00401353 |. EB 4E&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JMP SHORT 004013A3<br>00401355 |&gt; 6A 10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PUSH 10 <\/code>; \/Style = MB_OK|MB_ICONHAND|MB_APPLMODAL<code><br>00401357 |. 68 15A14000&nbsp;&nbsp; PUSH 0040A115 <\/code>; |Title = \u00abError\u00bb<code><br>0040135C |. 68 F9A04000&nbsp;&nbsp; PUSH 0040A0F9 <\/code>; |Text = \u00abLos datos no son correctos.\u00bb<code><br>00401361 |. FF35 C8C64000 PUSH DWORD PTR DS:[40C6C8]<\/code> ; |hOwner = NULL<code><br>00401367 |. E8 A2800000&nbsp;&nbsp; CALL JMP.&amp;USER32.MessageBoxA <\/code>; \\MessageBoxA<\/p>\n\n\n\n<p>Para que no se pierda nadie, a los que no sepan ASM a\u00fan les recuerdo que <code>JNZ<\/code> es exactamente lo mismo que <code>JNE<\/code>, de hecho ambos se codifican como <code>75h<\/code>, al igual que <code>JE<\/code> y <code>JZ<\/code> se codifican ambos como <code>74h<\/code>.<\/p>\n\n\n\n<p>Aqu\u00ed vemos que al llegar a <code>JNZ<\/code>, en caso que la condicional resulte 1 salta a <code>00401355h<\/code> y muestra el mensaje de error; de lo contrario, si resulta 0, contin\u00faa la ejecuci\u00f3n sin saltar, hace lo que nos interesa y llega a un <code>JMP<\/code> en <code>00401353h<\/code> que se asegura de no ejecutar el mensaje de error despu\u00e9s de mostrar el de serial correcto (<code>JMP<\/code>: <em>jump<\/em>, salta a una direcci\u00f3n del c\u00f3digo).<\/p>\n\n\n\n<p>Ahora posicion\u00e9monos sobre la instrucci\u00f3n <code>TEST EAX,EAX<\/code> en OlyDbg y presionemos <kbd>F2<\/kbd>; con ello pondremos un <em>breakpoint<\/em> en ese lugar. Luego presionemos <kbd>F9<\/kbd>, el programa se abrir\u00e1 y nos mostrar\u00e1 el di\u00e1logo para introducir los datos. Una vez m\u00e1s llenamos las cajas de texto con cualquier cosa, d\u00e9mosle a <em>Registrar<\/em> y el programa se detiene justo en el <em>breakpoint<\/em>, debido a que la ejecuci\u00f3n ha llegado a esa instrucci\u00f3n del c\u00f3digo. Ahora, para ir corriendo el programa de instrucci\u00f3n en instrucci\u00f3n vamos presionando <kbd>F8<\/kbd>. Aqu\u00ed veremos detalladamente c\u00f3mo al llegar al <code>JNZ<\/code> salta a <code>401355h<\/code>, justo lo que no queremos que haga.<\/p>\n\n\n\n<p>Carguemos nuevamente el programa; en OlyDbg tienes un bot\u00f3n en la esquina superior izquierda con dos flechas \u00ab<em>&lt;&lt;<\/em>\u00bb que nos permite resetear la ejecuci\u00f3n. Una vez m\u00e1s nos dirigimos a la instrucci\u00f3n en cuesti\u00f3n, pero ahora vamos a hacer unos cambios en los c\u00f3digos antes de ejecutar el programa. Haciendo doble clic en una instrucci\u00f3n te permitir\u00e1 editarla; es importante saber que estos cambios se realizan en memoria, no en disco. Cambiemos <code>JNZ SHORT 00401355<\/code> por <code>JZ SHORT 00401355<\/code> y presionemos el bot\u00f3n <em>Assemble<\/em>, tecleemos <kbd>F9<\/kbd> e introduzcamos cualquier texto en ambos campos, \u00a1voil\u00e1!, ya est\u00e1: el programa ha aceptado el serial.<\/p>\n\n\n\n<p>Para finalizar, lo \u00fanico que nos queda es llevar los cambios al disco. Esto lo haremos con el editor hexadecimal. Primeramente retornemos al W32Dasm y posicion\u00e9monos sobre la instrucci\u00f3n que nos interesa cambiar (el <code>JNZ<\/code>). Si miramos abajo, en la barra de estado, podremos ver el <em>offset<\/em> de la instrucci\u00f3n (en este caso <code>93Ah<\/code>, los ceros a la izquierda no cuentan).<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"482\" height=\"19\" src=\"https:\/\/hackcuba.net\/wp-content\/uploads\/2026\/03\/0x660017-2.png\" alt=\"\" class=\"wp-image-487\" srcset=\"https:\/\/hackcuba.net\/wp-content\/uploads\/2026\/03\/0x660017-2.png 482w, https:\/\/hackcuba.net\/wp-content\/uploads\/2026\/03\/0x660017-2-300x12.png 300w\" sizes=\"auto, (max-width: 482px) 100vw, 482px\" \/><\/figure>\n\n\n\n<p>Abrimos el WinHex. En \u00e9l cargamos el programa, en el men\u00fa principal vamos a <em>Position<\/em> \u00bb <em>Go To Offset<\/em> y escribimos <kbd>93A<\/kbd>, presionamos <em>OK<\/em>, y nos lleva directo a un n\u00famero 75 entre todo ese c\u00f3digo.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"292\" height=\"127\" src=\"https:\/\/hackcuba.net\/wp-content\/uploads\/2026\/03\/0x660018-1.png\" alt=\"\" class=\"wp-image-486\"\/><\/figure>\n\n\n\n<p>\u00bfPues qu\u00e9 estamos esperando? Cambia el <code>75<\/code> por un <code>74<\/code> y guarda los cambios en <em>File<\/em> \u00bb <em>Save<\/em>. Luego abre el programa, introd\u00facele cualquier cosa y dale <em>Registrar<\/em>. Ya lo tenemos, el programa est\u00e1 parcheado.<\/p>\n\n\n\n<p>Con todo esto lo que hemos logrado es cambiar el c\u00f3digo del programa para que haga lo que nosotros queramos, no lo que su programador le orden\u00f3 hacer. Me gustar\u00eda decir que aqu\u00ed ten\u00edamos otras opciones adem\u00e1s del m\u00e9todo \u00ab74\/75\u00bb; podr\u00edamos haber sustituido la instrucci\u00f3n <code>JNZ SHORT 00401355<\/code> por <code>NOP NOP<\/code>. Esto vendr\u00eda a ser otra t\u00e9cnica: \u00abnopear\u00bb. Con los <code>NOP<\/code> le decimos al microprocesador que no haga nada, que pase a la siguiente instrucci\u00f3n; de este modo lograr\u00edamos que el programa se registrara incluso aunque se escribiera el serial correcto. En este caso habr\u00eda que cambiar dos n\u00fameros del hexadecimal y sustituirlos por <code>90 90<\/code>, que corresponde a decir <code>NOP NOP<\/code> en ASM.<\/p>\n\n\n\n<p>Tal como mencion\u00e9 al inicio, esta es una t\u00e9cnica elemental; un buen <em>cracker<\/em> nunca tratar\u00e1 de cambiar el c\u00f3digo del programa que est\u00e1 trabajando. En vez de eso, el trabajo m\u00e1s fino consiste en buscar la parte del c\u00f3digo que genera el serial, o sea, la funci\u00f3n <code>GetSerial()<\/code>, analizarla, estudiarla y entenderla para luego hacer un programa que haga todo lo que \u00e9sta hace, es decir, un <em>keygen<\/em> o <em>keymaker<\/em>. Aun as\u00ed, parchear a veces se vuelve necesario cuando la funci\u00f3n <code>GetSerial()<\/code> est\u00e1 demasiado protegida o complicada.<\/p>\n\n\n\n<p>Este es el escenario m\u00e1s sencillo que podemos encontrarnos. Aun as\u00ed, es bastante com\u00fan. La mayor\u00eda de los programadores no le prestan atenci\u00f3n a la seguridad de sus productos a la hora de protegerlos. De cualquier forma, cuando te detienes a parchear un programa puedes encontrar cualquier cosa: algunos est\u00e1n protegidos con algoritmos de compactaci\u00f3n (yo siempre uso UPX para hacerlos peque\u00f1os, m\u00e1s que por protegerlos), lo cual podr\u00eda ser un problema si eres principiante; otros programas hacen c\u00e1lculos al iniciarse para verificar que su c\u00f3digo no ha sido cambiado (esto creo que me lo encontr\u00e9 en el mIRC), en fin, los modos de protegerse son muchos. Sin embargo, nunca faltar\u00e1n esos programadores que de ingenier\u00eda inversa no han o\u00eddo hablar absolutamente nada.<\/p>\n\n\n\n<p>Por otra parte, en ocasiones aunque el programa no est\u00e9 protegido, te podr\u00e1 ser muy dif\u00edcil rastrear el c\u00f3digo hasta la condicional correcta. A veces hay que tener mucha paciencia y sentarse algunas horas a leer el ensamblador para comprender d\u00f3nde est\u00e1 la condicional que buscamos. La complicaci\u00f3n puede tener dos causas: o el programador tuvo siempre muy clara la idea de la ingenier\u00eda inversa o simplemente fue muy desordenado al escribir su c\u00f3digo. Pues s\u00ed, las chapucer\u00edas en lenguajes de alto nivel resultan en grandes cantidades inservibles de c\u00f3digos ASM.<\/p>\n\n\n\n<p>Respecto a los programas que s\u00f3lo te piden el serial, \u00bfno te imaginas la condicional?<\/p>\n\n\n\n<p><code>if ( Serial_Entrado == \"MI-SE-RI-AL\" ) {<br><\/code>\/* &#8230; *\/ <code><br>}<\/code><\/p>\n\n\n\n<p>Busca en las referencias de <em>strings<\/em> del W32Dasm; cuando encuentres un texto que parezca un serial se lo pones al programa y \u00a1listo!. Las aplicaciones que \u00abhardcodean\u00bb el serial son dif\u00edciles de encontrar; esto podr\u00eda darte una idea de c\u00f3mo quebrantar la seguridad del programa que hizo tu vecino y que te pide un <em>password<\/em>.<\/p>\n\n\n\n<p>Para los que piensen que estos conocimientos s\u00f3lo son \u00fatiles para quebrantar sistemas de verificaci\u00f3n de n\u00fameros de serie, hace un a\u00f1o, mientras cursaba el tercero de inform\u00e1tica, decidieron que las pr\u00e1cticas se har\u00edan en la escuela. La idea era poner a todos los estudiantes a llenar una base de datos de un censo. El trabajo se efectuaba desde un programa cliente que se encontraba en todas las computadoras y en \u00e9l se llenaban los formularios de datos. La tarea era realmente desagradable y tediosa y, para mayor fastidio, hab\u00eda que entrar al sistema con una cuenta propia de la cual quedaba un <em>log<\/em> de qui\u00e9n trabaj\u00f3 y qui\u00e9n no en el servidor. Los alumnos obviamente ten\u00edan cuentas limitadas, mientras los profesores ten\u00edan cuentas m\u00e1s abiertas que permit\u00edan hacer cambios en los <em>logs<\/em> y otras cosas. Para no aburrir, <em>usuario<\/em>\/<em>serial<\/em>, <em>usuario<\/em>\/<em>contrase\u00f1a<\/em>, \u00bfcu\u00e1l es la diferencia?, si tu contrase\u00f1a es correcta, \u00bfno depende de una condicional?. Mientras todos en mi escuela sufrieron meses de trabajo inhumano con este censo, yo solo trabaj\u00e9 un d\u00eda: el primero; al final, para mi sorpresa, sal\u00ed destacado, pues mis <em>logs<\/em> mostraban un trabajo infatigable, digno de reconocimientos.<\/p>\n\n\n\n<p>Como ver\u00e1n, el empleo de esta t\u00e9cnica no est\u00e1 limitado a seriales. En cuanto tenga otra tarde libre escribo m\u00e1s sobre t\u00e9cnicas de ingenier\u00eda inversa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Para saber m\u00e1s&#8230;<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"http:\/\/www.reverse-engineering.net\/\" target=\"_blank\" rel=\"noreferrer noopener\">Reverse Code Engineering Community<\/a><\/li>\n\n\n\n<li><a href=\"http:\/\/www.openrce.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">OpenRCE: Reverse Engineering Portal<\/a><\/li>\n\n\n\n<li><a href=\"http:\/\/www.reteam.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Recursos para efectuar Ingenier\u00eda Inversa de sofware<\/a><\/li>\n<\/ul>\n\n\n\n<p>Escrito por h0aX [<a href=\"mailto:hoax_ws@yahoo.es\">hoax_ws@yahoo.es<\/a>]<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recientemente, hablando con un colega del lugar donde trabajo, acerca de las mejores maneras de proteger un producto<\/p>\n","protected":false},"author":2,"featured_media":489,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[59,14,18,36],"tags":[88,4,150,58,38],"class_list":["post-485","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-codigos","category-hacking","category-programacion","category-proyecto-blackhat","tag-codigos","tag-hacking","tag-ingenieria-inversa","tag-programacion","tag-proyecto-blackhat"],"_links":{"self":[{"href":"https:\/\/hackcuba.net\/index.php?rest_route=\/wp\/v2\/posts\/485","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hackcuba.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hackcuba.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hackcuba.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/hackcuba.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=485"}],"version-history":[{"count":1,"href":"https:\/\/hackcuba.net\/index.php?rest_route=\/wp\/v2\/posts\/485\/revisions"}],"predecessor-version":[{"id":490,"href":"https:\/\/hackcuba.net\/index.php?rest_route=\/wp\/v2\/posts\/485\/revisions\/490"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/hackcuba.net\/index.php?rest_route=\/wp\/v2\/media\/489"}],"wp:attachment":[{"href":"https:\/\/hackcuba.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=485"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hackcuba.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=485"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hackcuba.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=485"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}