lunes, 23 de julio de 2007

Ingeniería Inversa

H0ax [hoax_ws@yahoo.es]

Recientemente, hablando con un colega del lugar donde trabajo, acerca de las mejores maneras de proteger un producto contra las técnicas de cracking, me vino el recuerdo de aquellos días en los que me interesé 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, ¿quién ha pagado aquí por un software?). Claro que en aquel entonces mi enfoque era muy distinto al de ahora.

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é cómo las usan, y más aún, las he usado. La verdad, hace ya tiempo que no practico el cracking; antes lo hacía por diversión -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í que si escribo este artículo 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ía inversa.

Lo que vamos a hacer es una práctica muy sencilla y bastante elemental. Probablemente muchos hayan oído hablar del método "74/75", una técnica muy básica, pero increíblemente útil y, particularmente yo, no sólo la he usado para quebrantar sistemas de verificación de números de serie, sino para muchas cosas más.

Primero lo primero, ¿a qué nos enfrentamos? Bueno, a partir de ahora nuestro enemigo número uno será 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ágicamente, por una razón que parece contradecir las leyes naturales que rigen nuestro mundo, casi siempre nos dice lo mismo: "Los datos suministrados no son los correctos, verifique y vuelva a intentarlo". Este es un escenario clásico, lo del nombre de usuario y el serial pude cambiar, pues algunos sólo 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álogo.

Segundo paso: conoce a tu enemigo, y aquí surge la segunda pregunta, ¿cómo funciona? Como ya he dicho, la variedad se manifiesta en todo su esplendor, pero limitándonos al escenario clásico, el funcionamiento es el siguiente: el programa recibe un nombre de usuario; luego, partiendo del mismo, genera un código que comparará con el número de serie; de ser iguales, ¡felicidades!, puedes usar el producto, si no, tendrás que sentarte a leer uno de esos tutoriales de cracking, lo que me recuerda por qué estás leyendo este artículo.

También está el otro caso que te pide sólo el serial, difícil de encontrar, demasiado pobre e ineficiente, muy fácil de crackear, así que ya casi nadie lo usa. Más tarde hablo de él.

Hasta ahora sabemos que al proporcionarle mi nombre al programa, éste genera el serial a partir del mismo y luego compara el que obtuvo con el que yo le dí. Llevemos eso a los códigos.

El esquema básico puede verse así:

// si el serial es bueno
if ( Serial_Entrado == GetSerial( Nombre_Entrado ) ) {
    // gracias por usar un buen keygen
  MessageBox( NULL, "Gracias por usar nuestro programa, bla bla", "Registro oK", NULL );
    // quita restricciones
  ...
} else { //si no
    // el serial no sirve
  MessageBox( NULL, "Los datos entrados no son correctos.", "Error", NULL );
}

Los códigos pueden cambiar, pero en esencia, la mayoría lucirán como éste, en especial si el programador no quiere pasar trabajo con algoritmos complicados.

Aquí vemos cómo el nombre de usuario ( Nombre_Entrado ) es pasado como parámetro a la función imaginaria GetSerial. Esta función se encarga de generar a partir de cada caracter de Nombre_Entrado un string, que es nuestro número de serie. Obviamente, cada nombre tendrá su propio serial.

Pasamos ahora al plan de ataque. Ya sabemos más, sabemos que nuestra felicidad depende en estos momentos de un if, o sea, una condicional. Por muchos intentos que tengamos disponibles, las posibilidades de acertar con el verdadero código son irrisorias, por lo tanto, ¿qué hacemos? Pues invertimos la condicional de la siguiente manera:

//si el serial NO es bueno
if ( Serial_Entrado != GetSerial( Nombre_Entrado ) ) {
    //gracias por registrarte
  MessageBox( NULL, "Gracias por usar nuestro programa bla bla", "Registro oK", NULL );
    //quita restricciones
  ...
} else { //si es bueno
    //mensaje de error
  MessageBox( NULL, "Los datos entrados no son correctos.", "Error", NULL );
}

¿Dónde esta el cambio? El operador de la condicional cambió de == a != -para aquellos que no conozcan el lenguaje C, != equivale a <> en otros lenguajes-. ¡Listo!, ahora cualquier serial que escribamos será válido, cualquiera excepto el verdadero :0. Bueno, qué más da, si las posibilidades de acertar son despreciables.

Es aquí donde todos me acusan de loco y me recuerdan que no tengo el código fuente del programa. Lo cierto es que quizás no tengamos los códigos en C, ni en lenguajes de alto nivel, pero sin dunda alguna tenemos los códigos en ensamblador, y es allí donde vamos a hacer los cambios, directamente en el hexadecimal del programa.

Aquellos que conocen de ensamblador, sabrán que las condicionales se definen usando las instrucciones JE, JZ ("Jump If Equal", "Jump If Zero") y JNE, JNZ ("Jump If Not Equal", "Jump If Not Zero") seguido de la dirección de saltos y antecedido de un TEST o un CMP, que determina lo que se está comparando.

El caso es que JE, codificado en hexadecimal, es 74 y JNE, 75; de ahí el nombre "74/75" para esta técnica. Cambiaremos un JE por un JNE, o un JNE por un JE, 74 a 75 ó 75 a 74, la idea es invertir la condicional a nuestro favor.

Ya concluyendo la introducción del artículo y entrando en el área práctica, haré un comentario. Para tener éxito en el mundo de la ingeniería inversa, poseer conocimientos de ensamblador es fundamental; mientras mejor domines el ASM, mejor podrás rastrear el código de un programa, hasta el punto de sentirte tan cómodo leyendo el código 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é en el tema del cracking tampoco sabía nada de ensamblador, de hecho, mis inicios en ASM fueron gracias al cracking. Como principiante no necesitas saber escribir programas en ASM para tener éxito, pero sí es muy importante que sepas cómo funciona lo básico, 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.

Vamos a la práctica. Para empezar, permítanme presentarles a nuestro enemigo, un pequeño CrackMe que acabo de preparar para este fin; servirá bien de conejillo de indias. Este es el código en C:

/*
**
**Nombre: CrackMe.c
**Autor: h0aX
**Fecha: 17-julio-2007
**
**Descripción: Nuestro conejillo de indias
**
**
*/

#include
#pragma hdrstop
#pragma argsused

HWND hwndMain, hwndButton1,
     hwndButton2, hwndButton3,
     hwndEditUser, hwndEditSerial;

HINSTANCE hInst;

void GetSerial( char *szName, char *szSerial )
{
  int i, cont;
  memset( szSerial, 0, 255 );

  cont = 0;

    /* Aquí se genera el serial a partir del
    ** nombre de usuario. Este es un ejemplo
    ** sencillo, así que simplemente invertiremos
    ** el nombre de usuario y le ponemos un punto
    ** al principio de la cadena, ej:
    ** usuario: test serial: .tset
    */
  for ( i = strlen(szName) ; i >= 0; i-- ) {

    strcat(szSerial,".");
    szSerial[cont] = szName[i] ;
    cont++;
  }
}

LRESULT CALLBACK WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
  switch (uMsg) {
    case WM_COMMAND: {
 
    // botón registrar
    if (hwndButton1 == (HWND) lParam) {
      char szUserName[255];
// usuario entrado
      char szSerial[255];
// serial verdadero
      char szSerial_Entrado[255];
// serial entrado
     
// obtiene los datos
      GetWindowTextA( hwndEditUser, szUserName, 255 );
      GetWindowTextA( hwndEditSerial, szSerial_Entrado, 255 );
     
// chequea si no hay nada en blanco
      if ( ( !strcmp( szSerial_Entrado, "" ) ) || ( !strcmp( szUserName, "" ) ) ) {
        MessageBoxA( hwndMain, "Datos en blanco.", "Error", MB_ICONERROR );
        return 0;
      }

      // genera el serial a partir del usuario
      GetSerial( szUserName, szSerial );
      
// y he aquí nuestra preciosa condicional
      if ( !strcmp( szSerial_Entrado, szSerial ) ) {
        MessageBoxA( hwndMain, "Serial correcto, felicidades.", "Registro oK", MB_ICONINFORMATION );
 
           // quita restricciones, bla bla ...
      } else {
        MessageBoxA( hwndMain, "Los datos no son correctos.", "Error", MB_ICONERROR );
      }
    } else
  
    // botón cancelar
    if (hwndButton2 == (HWND) lParam) {
      PostQuitMessage(0);
    } else
 
    // botón acerca de
    if (hwndButton3 == (HWND) lParam) {
      MessageBoxA( hwndMain, "CrackMe por h0aX para \r\nla revista "
      "BlackHat,\r\npruebas de ingenieria inversa."
      "\r\n\r\n[17-julio-2007]",
      "CrackMe por h0aX", MB_ICONINFORMATION );
    }
    return 0;
  }
  case WM_DESTROY: {
    PostQuitMessage(0);
    return 0;
  }
 }
 return DefWindowProcA(hwnd,uMsg,wParam,lParam);
}

WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  MSG msg;
  WNDCLASS wndclass;
  char szClassName[] = "hXClass_CrackMe";
  hInst = hInstance;
  wndclass.style = CS_VREDRAW | CS_HREDRAW;
  wndclass.lpfnWndProc = WndProc;
  wndclass.cbClsExtra = 0;
  wndclass.cbWndExtra = 0;
  wndclass.hInstance = hInstance;
  wndclass.hIcon = LoadIconA(NULL, IDI_APPLICATION);
  wndclass.hCursor = LoadCursorA(NULL, IDC_ARROW);
  wndclass.hbrBackground = GetSysColorBrush(COLOR_BTNFACE);
  wndclass.lpszMenuName = NULL;
  wndclass.lpszClassName = szClassName;
  RegisterClassA(&wndclass);
  hwndMain = CreateWindow(szClassName,"CrackMe por h0aX", WS_SYSMENU |     WS_MINIMIZEBOX | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 290, 180, NULL, NULL, hInstance, 0);
 
CreateWindow("Static","Nombre de usuario:", WS_VISIBLE | WS_CHILD, 15,10, 250,15, hwndMain, 0, hInst, NULL);
  hwndEditUser = CreateWindowEx(WS_EX_CLIENTEDGE,"Edit","", WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL, 15,30, 250,22, hwndMain, 0, hInst, NULL);

   CreateWindow("Static","Serial:", WS_VISIBLE | WS_CHILD , 15,60, 250,15, hwndMain, 0, hInst, NULL);

  hwndEditSerial = CreateWindowEx(WS_EX_CLIENTEDGE,"Edit","", WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL , 15,80, 250,22, hwndMain, 0, hInst, NULL);

  hwndButton1 = CreateWindow("Button","Registrar", WS_VISIBLE | WS_CHILD, 15,110, 75,25, hwndMain, 0, hInst, NULL);

  hwndButton2 = CreateWindow("Button","Cancelar", WS_VISIBLE | WS_CHILD, 90, 110, 75,25, hwndMain, 0, hInst, NULL);

  hwndButton3 = CreateWindow("Button","Acerca de", WS_VISIBLE | WS_CHILD, 190, 110, 75,25, hwndMain, 0, hInst, NULL);

  ShowWindow(hwndMain, nCmdShow);

  while (GetMessageA(&msg, NULL,0,0)) {
   
TranslateMessage(&msg);
    DispatchMessageA(&msg);
  }
  return msg.wParam;
}

Seguimos con las herramientas. Nuestro arsenal personal debe estar compuesto básicamente de:

  • un desensamblador
  • un depurador
  • un editor hexadecimal

El desensamblador es una herramienta que, como su nombre indica, desensambla un programa, o sea, nos muestra su código ensamblador de modo que nosotros podamos estudiarlo. Particularmente yo uso el W32Dasm.

El depurador (o debugger) es una herramienta que nos permite no sólo desensamblar un programa, sino también correrlo, ponerle breakpoints en determinadas posiciones del código, efectuar cambios en el código cargado en memoria, observar los valores de los registros, la pila; todo al mismo tiempo que lo corremos. Como debugger uso y recomiendo el OlyDbg.

El editor hexadecimal nos permite ver el código en sus valores hexadecimales, además de hacer cambios en ellos para luego guardarlos en disco. Yo uso WinHex.

Y ahora que nuestras armas han sido introducidas procedemos a utilizarlas.

Conociendo al enemigo::

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ón para registrar el programa, otro para cancelar y el último para los créditos del programa.

Como si fuéramos dueños de la situación, enviamos el foco del teclado a las cajas de texto, tecleamos nombre de usuario "yo", serial "1234567890", y le damos al botón Registrar. Tal como sospechamos, el programa no se intimidó ante la confianza que mostramos, pues nos ha dado un mensaje de error con el texto "Los datos no son correctos". Muchos pensarán que este texto no les sirve de mucho.

Entonces procedemos a desensamblar el programa con el desensamblador, a ver qué encontramos de interés. Lo que aparece es una cantidad de código impresionante, mucho más de lo que hemos escrito en C. Entre tantas líneas en ASM sería una locura encontrar la instrucción específica de la condicional que buscamos. Pues que no cunda el pánico, en el menú superior del W32Dasm hay un botón llamado StrnRef.

Botón StrnRef

Si pulsamos en él nos mostrará una ventana con una lista de mensajes, que no son más que todas las referencias de strings que usa el programa; es decir, todas las cadenas de caracteres que aparezcan en el programa se encontrarán allí. Si damos doble clic sobre una de ellas, nos lleva exactamente a la instrucción donde se le hizo referencia. Si se hace referencia al mismo texto en más de una instrucción, por cada vez que se la haga doble clic irá saltando a la siguiente referencia. Ahora vemos que aquel texto de error no era tan inservible. Busquemos el texto; no es tan difícil encontrarlo ya que la lista está ordenada alfabéticamente.

Lista de cadenas de texto.

Una vez localizado, hacemos doble clic para que nos lleve directamente a la instrucción que la referenció y vamos a parar directamente a la dirección 0040135Ch dentro del código, la cual corresponde a una instrucción PUSH, que estaba metiendo en la pila la dirección de memoria que contenía el texto. En el desensamblador que usen verán que cada instrucción corresponde con una dirección en el código; ésta se encuentra al extremo izquierdo con formato 00000000h (la h del final es para saber que el número está en hexadecimal). Normalmente, el código de las aplicaciones Win32 comienza en 00400000h. La segunda columna nos muestra los valores hexadecimales del código, y la tercera el código como tal en ASM.

Importante saber que cada instrucción en ASM tiene un equivalente en hexadecimal, por ejemplo, la instrucción PUSH EBX siempre será 51h, es decir, 51 en hexadecimal.

Si somos lo suficientemente curiosos para seguir explorando las referencias de strings encontraremos otra muy interesante, "Serial correcto, felicidades". Si le damos doble clic, iremos a parar a 00401343h, sólo a unas pocas instrucciones más arriba que la referencia al texto de error. ¿Qué me dice todo esto?: que estamos en la zona caliente. Comencemos a rastrear hacia atrás a partir de las referencias y casi inmediatamente en la posición 0040133Ah encontramos una instrucción JNE, ¡bingo!, sin analizarla podría apostar por ella, no obstante, mejor estar seguro.

Hallando la condicional JNE

La instrucción dice JNE 00401355, es decir, que si una determinada condición no se cumple salte a la dirección 00401335h; luego, si buscamos esta nueva posición veremos que queda exactamente atrás del mensaje de registro satisfactorio. En pocas palabras, si la condición no se cumple salta el mensaje bueno y ve al mensaje malo, de lo contrario muestra el mensaje bueno. Ahora, para ver esta situación mucho más de cerca, copiemos la dirección de la condicional, 0040133A, abrimos el depurador OlyDbg y cargamos el programa allí. Una vez ejecutado veremos la pantalla dividida en varias áreas, donde arriba a la izquierda se encuentra el código ASM. Si allí hacemos click derecho, menú "Go To", submenú "Expression" y nos aparecerá un cuadro de diálogo. Allí ponemos la dirección de la instrucción JNE, 0040133A, damos OK y nos envía directamente a la instrucción deseada.

Instrucción 0040133A en el depurador OlyDbg.

Ahora vamos a analizar el código con detenimiento.

00401338 |. 85C0          TEST EAX,EAX
0040133A |. 75 19         JNZ SHORT 00401355
0040133C |. 6A 40         PUSH 40
; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
0040133E |. 68 EDA04000   PUSH 0040A0ED
; |Title = "Registro oK"
00401343 |. 68 CFA04000   PUSH 0040A0CF
; |Text = "Serial correcto, felicidades."
00401348 |. FF35 C8C64000 PUSH DWORD PTR DS:[40C6C8]
; |hOwner = NULL
0040134E |. E8 BB800000   CALL JMP.&USER32.MessageBoxA
; \MessageBoxA
00401353 |. EB 4E         JMP SHORT 004013A3
00401355 |> 6A 10         PUSH 10
; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL
00401357 |. 68 15A14000   PUSH 0040A115
; |Title = "Error"
0040135C |. 68 F9A04000   PUSH 0040A0F9
; |Text = "Los datos no son correctos."
00401361 |. FF35 C8C64000 PUSH DWORD PTR DS:[40C6C8]
; |hOwner = NULL
00401367 |. E8 A2800000   CALL JMP.&USER32.MessageBoxA
; \MessageBoxA

Para que no se pierda nadie, a los que no sepan ASM aún les recuerdo que JNZ es exactamente lo mismo que JNE, de hecho ambos se codifican como 75h, al igual que JE y JZ se codifican ambos como 74h.

Aquí vemos que al llegar a JNZ, en caso que la condicional resulte 1 salta a 00401355h y muestra el mensaje de error; de lo contrario, si resulta 0, continúa la ejecución sin saltar, hace lo que nos interesa y llega a un JMP en 00401353h que se asegura de no ejecutar el mensaje de error después de mostrar el de serial correcto (JMP: jump, salta a una dirección del código).

Ahora posicionémonos sobre la instrucción TEST EAX,EAX en OlyDbg y presionemos F2; con ello pondremos un breakpoint en ese lugar. Luego presionemos F9, el programa se abrirá y nos mostrará el diálogo para introducir los datos. Una vez más llenamos las cajas de texto con cualquier cosa, démosle a Registrar y el programa se detiene justo en el breakpoint, debido a que la ejecución ha llegado a esa instrucción del código. Ahora, para ir corriendo el programa de instrucción en instrucción vamos presionando F8. Aquí veremos detalladamente cómo al llegar al JNZ salta a 401355h, justo lo que no queremos que haga.

Carguemos nuevamente el programa; en OlyDbg tienes un botón en la esquina superior izquierda con dos flechas "<<" que nos permite resetear la ejecución. Una vez más nos dirigimos a la instrucción en cuestión, pero ahora vamos a hacer unos cambios en los códigos antes de ejecutar el programa. Haciendo doble clic en una instrucción te permitirá editarla; es importante saber que estos cambios se realizan en memoria, no en disco. Cambiemos JNZ SHORT 00401355 por JZ SHORT 00401355 y presionemos el botón Assemble, tecleemos F9 e introduzcamos cualquier texto en ambos campos, ¡voilá!, ya está: el programa ha aceptado el serial.

Para finalizar, lo único que nos queda es llevar los cambios al disco. Esto lo haremos con el editor hexadecimal. Primeramente retornemos al W32Dasm y posicionémonos sobre la instrucción que nos interesa cambiar (el JNZ). Si miramos abajo, en la barra de estado, podremos ver el offset de la instrucción (en este caso 93Ah, los ceros a la izquierda no cuentan).

Offset de la instrucción 93Ah

Abrimos el WinHex. En él cargamos el programa, en el menú principal vamos a Position » Go To Offset y escribimos 93A, presionamos OK, y nos lleva directo a un número 75 entre todo ese código.

Ubicación de la instrucción en el editor hexadecimal WinHex.

¿Pues qué estamos esperando? Cambia el 75 por un 74 y guarda los cambios en File » Save. Luego abre el programa, introdúcele cualquier cosa y dale Registrar. Ya lo tenemos, el programa está parcheado.

Con todo esto lo que hemos logrado es cambiar el código del programa para que haga lo que nosotros queramos, no lo que su programador le ordenó hacer. Me gustaría decir que aquí teníamos otras opciones además del método "74/75"; podríamos haber sustituido la instrucción JNZ SHORT 00401355 por NOP NOP. Esto vendría a ser otra técnica: "nopear". Con los NOP le decimos al microprocesador que no haga nada, que pase a la siguiente instrucción; de este modo lograríamos que el programa se registrara incluso aunque se escribiera el serial correcto. En este caso habría que cambiar dos números del hexadecimal y sustituirlos por 90 90, que corresponde a decir NOP NOP en ASM.

Tal como mencioné al inicio, esta es una técnica elemental; un buen cracker nunca tratará de cambiar el código del programa que está trabajando. En vez de eso, el trabajo más fino consiste en buscar la parte del código que genera el serial, o sea, la función GetSerial(), analizarla, estudiarla y entenderla para luego hacer un programa que haga todo lo que ésta hace, es decir, un keygen o keymaker. Aun así, parchear a veces se vuelve necesario cuando la función GetSerial() está demasiado protegida o complicada.

Este es el escenario más sencillo que podemos encontrarnos. Aun así, es bastante común. La mayoría de los programadores no le prestan atención 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án protegidos con algoritmos de compactación (yo siempre uso UPX para hacerlos pequeños, más que por protegerlos), lo cual podría ser un problema si eres principiante; otros programas hacen cálculos al iniciarse para verificar que su código no ha sido cambiado (esto creo que me lo encontré en el mIRC), en fin, los modos de protegerse son muchos. Sin embargo, nunca faltarán esos programadores que de ingeniería inversa no han oído hablar absolutamente nada.

Por otra parte, en ocasiones aunque el programa no esté protegido, te podrá ser muy difícil rastrear el código hasta la condicional correcta. A veces hay que tener mucha paciencia y sentarse algunas horas a leer el ensamblador para comprender dónde está la condicional que buscamos. La complicación puede tener dos causas: o el programador tuvo siempre muy clara la idea de la ingeniería inversa o simplemente fue muy desordenado al escribir su código. Pues sí, las chapucerías en lenguajes de alto nivel resultan en grandes cantidades inservibles de códigos ASM.

Respecto a los programas que sólo te piden el serial, ¿no te imaginas la condicional?

if ( Serial_Entrado == "MI-SE-RI-AL" ) {
/* ... */
}

Busca en las referencias de strings del W32Dasm; cuando encuentres un texto que parezca un serial se lo pones al programa y ¡listo!. Las aplicaciones que "hardcodean" el serial son difíciles de encontrar; esto podría darte una idea de cómo quebrantar la seguridad del programa que hizo tu vecino y que te pide un password.

Para los que piensen que estos conocimientos sólo son útiles para quebrantar sistemas de verificación de números de serie, hace un año, mientras cursaba el tercero de informática, decidieron que las prácticas se harían 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 él se llenaban los formularios de datos. La tarea era realmente desagradable y tediosa y, para mayor fastidio, había que entrar al sistema con una cuenta propia de la cual quedaba un log de quién trabajó y quién no en el servidor. Los alumnos obviamente tenían cuentas limitadas, mientras los profesores tenían cuentas más abiertas que permitían hacer cambios en los logs y otras cosas. Para no aburrir, usuario/serial, usuario/contraseña, ¿cuál es la diferencia?, si tu contraseña es correcta, ¿no depende de una condicional?. Mientras todos en mi escuela sufrieron meses de trabajo inhumano con este censo, yo solo trabajé un día: el primero; al final, para mi sorpresa, salí destacado, pues mis logs mostraban un trabajo infatigable, digno de reconocimientos.

Como verán, el empleo de esta técnica no está limitado a seriales. En cuanto tenga otra tarde libre escribo más sobre técnicas de ingeniería inversa.

Para saber más...



Artículos relacionados


No hay comentarios: