Mostrando entradas con la etiqueta Ingenieria Inversa. Mostrar todas las entradas
Mostrando entradas con la etiqueta Ingenieria Inversa. Mostrar todas las entradas

Reconociendo estructuras comunes en ingeniería reversa [Parte II]


Hace algunos días estuvimos analizando las estructuras comunes en ingeniería reversa, así como también los beneficios que podemos obtener de la utilización de herramientas adecuadas. En esta oportunidad, volvemos para completar este análisis con más patrones que serán de gran ayuda en la tarea de reversing.


Para esta segunda parte, continuaremos con el ejemplo planteado anteriormente. En la imagen de arriba observamos el programa escrito en C. Por su parte, en la imagen que sigue a continuación recordamos cómo luce ese programa una vez que ha sido desensamblado:


Ya analizamos el significado de las líneas resaltadas en rojo. Pero también hemos marcado unas líneas con color naranja: si prestamos atención, veremos que estas líneas se encuentran después de una llamada a subrutina call. Antes de seguir, debe mencionarse que existen diversas convenciones para la llamada a subrutinas. Es necesario que el código que llama y el que es llamado sepan dónde colocar y buscar los datos, respectivamente. En este sentido, podemos mencionar dos de las convenciones más importantes:

  • cdecl: esta es la forma más común de manejar el pasaje de parámetros a una subrutina. Se caracteriza por hacer el pasaje a través del stack, introduciendo los parámetros según aparecen en la llamada, de derecha a izquierda (en nuestro ejemplo, primero argv y luego argc). Además, el resultado devuelto por la subrutina es colocado en eax (o edx:eax, de ser necesario). Pero lo más importante a mencionar es que la rutina que llama (no la que es llamada) es la que debe encargarse de limpiar lo introducido en el stack una vez que se ha finalizado la llamada.
  • stdcall: esta convención se utiliza en menor medida; mayormente la vemos en funciones de la API Win32. Es exactamente igual a cdecl, excepto por la limpieza del stack: en este caso es la subrutina invocada quien debe llevar a cabo la tarea.

Entonces, ¿cómo se traduce esto en el código? Si se utiliza cdecl, inmediatamente después de la llamada a la subrutina se observan instrucciones para realizar la limpieza del stack. Esto es justamente lo que se marca con color naranja en nuestro ejemplo: luego de la llamada a atoi, se limpia el espacio que se reservó en el stack para pasar el parámetro (4 bytes); luego de la llamada a sub, se hace lo mismo, pero con los dos parámetros pasados (8 bytes). Si tenemos en cuenta que el registro esp apunta al tope del stack, y que éste crece de direcciones de memoria más grandes a direcciones más chicas, la suma de un valor positivo a esp hará que los valores de la cima queden fuera del stack, logrando el efecto de limpieza o borrado. Pero… ¿qué pasa si se utiliza stdcall?


Al forzar la convención stdcall para la llamada a sub, se observan dos cosas. En primer lugar, que en el cuerpo de main, y después de la llamada a sub, no hay operaciones para limpiar los parámetros introducidos en el stack; si comparamos este código con el anterior, vemos que la línea add esp,8 ya no está. En segundo lugar, al observar el código de sub vemos que la instrucción ret ha cambiado. Ahora es responsable sub de limpiar su propio stack frame, para lo cual ejecuta ret con la cantidad de bytes a limpiar, tomando 4 bytes por parámetro.

Para cerrar este análisis, haremos referencia a las operaciones realizadas sobre el registro ebp y la diferencia entre variables locales y argumentos de llamada a subrutina. En nuestra primer imagen del código desensamblado, hemos marcado en amarillo la operación push ecx. Sin embargo, podemos notar que ecx no está inicializado. Esto nos indica que el valor que está siendo introducido en el stack es indeterminado, lo que significa que se está guardando espacio en el stack. Si además observamos que esta línea no está presente en sub, entendemos que el espacio que se guarda es para variables locales (en el caso de main, una sola variable, a). Entonces, de manera general, podemos decir que si una rutina cuenta con variables locales, luego del código de inicialización encontraremos las líneas que guardan este espacio para las variables en el stack.

Si ahora nos detenemos en el resto de las operaciones realizadas que hacen referencia al registro ebp, notaremos que algunas le suman un valor, y otras se lo restan. En general, nos resultará muy útil en la práctica considerar lo siguiente: cuando a ebp le sumamos un valor para referenciar una posición dentro del stack, estaremos accediendo a uno de los argumentos del código que está siendo ejecutado. Por su parte, si le sustraemos un valor, estamos dentro del stack frame local, con lo cual se hace referencia a variables locales. En otras palabras, si vemos que a ebp se le suma algo, se está tratando de recuperar un parámetro; si se le resta algo, se quiere acceder a una variable local. Esto se observa en la otra parte del código que se ha marcado con color amarillo: se guarda en la pila el valor de la variable a, para la cual antes se había reservado espacio con push ecx.

Si aprendemos a reconocer estos patrones entre las líneas de código, nuestro análisis será mucho más rápido y efectivo.

Fuente: ESET

Reconociendo estructuras comunes en ingeniería reversa [Parte I]


 Cuando realizamos reversing sobre una posible amenaza tenemos que acostumbrarnos a la idea de que vamos a revisar cientos de líneas de código en un lenguaje de bajo nivel, tan solo para comprender una parte del funcionamiento. Esto puede sonar fatal y desanimar a aquellos que se están iniciando; después de todo, a mí me ocurrió cuando empecé a aprenderlo. Sin embargo, uno no tarda en darse cuenta que con un poco de práctica y las herramientas adecuadas, la ingeniería reversa no tiene por qué ser una experiencia desagradable. Por ello, en este post compartiremos una introducción a ciertos patrones que suelen verse al realizar reversing en arquitecturas x86, lo cual nos permitirá ubicarnos rápidamente y comprender mejor el código.

Sin embargo, imaginemos que ha llegado el momento de desensamblar un archivo ejecutable. ¿Ahora qué hacemos? Conocemos las instrucciones, pero… ¿empezamos a leer cada línea secuencialmente, interpretando lo que hace cada instrucción?


En la imagen anterior vemos cómo un desensamblador (IDA, en este caso) no solo nos muestra el código, sino que además nos estructura el flujo de ejecución de forma gráfica. De esta forma, resulta mucho más sencillo seguir los saltos condicionales y los posibles escenarios de ejecución dentro de una subrutina. Adicionalmente, puede accederse a un diagrama con la interconexión y jerarquía de las diversas funciones o subrutinas en el ejecutable. Esto se observa en la imagen a continuación:


Pero más allá de todas las facilidades que brindan las herramientas, hay ciertas situaciones que se repiten en la ingeniería reversa y que vale la pena destacar. Para comenzar, analizaremos el siguiente programa:


Puede observarse que es muy sencillo: el programa recibe un argumento que se almacena como número entero en una variable local y se llama a la subrutina sub con esa variable local y el conteo de argumentos en argc. Esta subrutina simplemente retorna la suma de esos dos valores. Al desensamblar el ejecutable producido por estas líneas de código, se obtiene lo siguiente:


En color rojo se marcan unas líneas que aparecen al inicio de cada rutina (main y sub) y que en general deberíamos encontrar al inicio de cualquier rutina, ya que realizan tareas de inicialización de la sección de la pila o stack correspondiente. Cuando main invoca a sub, sub debe realizar el cambio de la sección del stack de main a la sección propia de sub, guardando el valor base de main para poder restablecerlo luego; este hecho se traduce en push ebp. Luego, mov ebp, esp realiza el cambio a la sección del stack de sub. También con color rojo se marcan las líneas que invierten ese proceso una vez que la rutina ha terminado de ejecutarse: mov esp, ebp retrocede el stack pointer hasta la base, limpiando así las variables locales o cualquier otro dato innecesario que haya quedado en la pila; mientras que pop ebp restablece el puntero base a la rutina previa. Se observa que sub sólo incluye pop ebp dado que no cuenta con variables locales u otros datos que limpiar del stack. En definitiva, si vemos estas instrucciones ahora, podemos saber dónde empieza y termina una rutina, y nos resultará particularmente útil si en algún momento nos perdemos en el código.

Por último, cabe destacar que en la imagen se han resaltado distintos patrones con otros colores, que están relacionados con limpieza del stack, convenciones de llamadas a subrutinas, asignación de espacio para variables locales y manipulación de parámetros y variables. No obstante, cubriremos estas cuestiones, y otras, en la segunda parte.

Fuente: ESET

Cracking en Java


Como muchos sabemos, un programa creado en java no es lo mismo que un ejecutable PE, esto radica en que los programitas compilados en java son un conjunto de bytecodes los cuales son interpretados por la JVM (Java Virtual Machine) que es la maquina virtual de java que interpreta estos bytescodes y realiza la función especifica, es por este motivo que java es un lenguaje multiplataforma, ya que es tan solo tener en el SO la JVM instalada para poder correr estos programas y listo. Pues bien, para entrarle a un programa creado en java, ya que como no es un ejecutable PE, no se usa el querido Olly sino un decompilador para java que lo que hará es mostrarnos el fuente real del programa, ya que como es un lenguaje interpretado es mas fácil la decompilación de su code a menos que el code no este ofuscado.

Java por medio del JDK (conjunto de herramientas para poder programar en este lenguaje), toma el código fuente creado por el programador con extensión .java y genera un archivo compilado con extensión .class, que es el código fuente compilado y que a su vez contiene los bytescodes que serán interpretados por la JVM.

Pues bien, los ejecutables de java poseen una extensión .jar que es tan solo un archivo que contiene todas las clases del programa (.class), y otros recursos del mismo y que a su vez puede ser descomprimido con winrar u otro descompresor de archivos rar para obtener estos files.

Ahora, para poder entrarle a este programa debemos analizar primero como es su protección, así que lo ejecutamos y vemos el pantallazo del mismo:


No tiene ningun sitio donde ingresar serial ni nada de eso, así que adelantemos la fecha por un mes y cuando lo volvemos a ejecutar tenemos esto:


Como ven es un trial que pasados 30 dias de uso deja de funcionar y nos muestra este cartelito. Entonces ahora lo que haremos es descomprimir el archivo kotoba.jar en una carpeta aparte y así buscar la clase sospechosa de esta protección:


Aqui vemos una clase muy sospechosa de nombre LicenseChecker.class, entonces traemos nuestro decompilador de java, en este caso uso el Dj Java Decompiler, lo abrimos con el y obtenemos el fuente.


Y el código fuente completo de la clase es este:
Código: Java
  1. import java.io.FileInputStream;
  2. import java.io.IOException;
  3. import java.util.Enumeration;
  4. import java.util.Properties;0
  5.  
  6. public class LicenseChecker
  7. {
  8.  
  9.     public LicenseChecker()
  10.     {
  11.         Properties licenseFrase = new Properties();
  12.         try
  13.         {
  14.             FileInputStream LIstream = new FileInputStream("license.doc");
  15.             licenseFrase.load(LIstream);
  16.         }
  17.         catch(IOException e)
  18.         {
  19.             isLicensed = false;
  20.         }
  21.  
  22.         String s;
  23.         for(Enumeration enum = licenseFrase.propertyNames(); enum.hasMoreElements();)
  24.             s = (String)enum.nextElement();
  25.  
  26.         String license_key = new String(licenseFrase.getProperty("KEY", "non"));
  27.  
  28.         if(license_key.equals("exoir4K34SCHRVekf0y63gr"))
  29.             isLicensed = true;
  30.         else
  31.             isLicensed = false;
  32.     }
  33.  
  34.     public static boolean isLicensed;
  35. }

Allí vemos primero que abre un file "license.doc" y en caso de no existir, como genera una excepción, el programa asigna a la variable isLicensed el valor false; pero si el file existe, ejecuta un ciclo for que obtiene los elementos del file y enseguida de este la variable license_key toma el valor que se le asigna a la propiedad KEY escrita en el file, que finalmente es comparada con el valor "exoir4K34SCHRVekf0y63gr" y si son correctas asigna a isLicensed el valor true, de lo contrario flase.

Como vemos todo el embrollo se puede sanar asignando a la variable isLicensed el valor true que es cuando la KEY del file es la correcta, pues bien, tenemos dos opciones, cambiar el code de la clase o generar el file key para que quede registrado.

La primera opción seria modificar todo este code de la siguiente forma para que isLicensed tenga siempre el valor de true:

Código: Java
  1. import java.io.FileInputStream;
  2. import java.io.IOException;
  3. import java.util.Enumeration;
  4. import java.util.Properties;0
  5.  
  6. public class LicenseChecker
  7. {
  8.  
  9.     public LicenseChecker()
  10.     {
  11.             isLicensed = true;
  12.     }
  13.  
  14.     public static boolean isLicensed;
  15. }

Con esto conseguiremos que el programa al comprobar la lisencia siempre este correcta xD. Ahora para guardar los cambios debemos seleccionar File/Save y guardar el código fuente con el mismo nombre de la clase es decir "LicenseChecker.java", luego de esto nos vamos al sitio donde la guardamos a traves de la consola de win y compilamos el archivo fuente de la siguiente manera:


Si estamos en la misma carpeta donde esta el archivo "LicenseChecker.class", este será reemplazado por el nuevo que acabamos de crear. Ahora para probar nuestra clase modificada debemos insertarsela al prgrama "kotoba.jar", pero antes realicemos una copia de este para evitar problemas, luego lo abrimos con winrar y le añadimos la clase que acabamos de generar arrastrandola sobre el mismo.



Le damos aceptar a esta ventana de winrar y añadirá la nueva clase que compilamos, luego ejecutamos el programa y vemos que aun con la fecha adelantada no nos muestra la nag


Bien, ahora el segundo método será crear el key file para que quede registrado limpiamente, pues bien, es tan solo crear un nuevo archivo "license.doc", con el bloc de notas ya que debe ser en texto plano y le añadimos la siguiente linea:

KEY exoir4K34SCHRVekf0y63gr


Guardamos los cambios y el archivo lo colocamos en la misma carpeta que el programa "kotoba.jar" pero el original, no el que acabamos de parchear, ya que no tendria sentido, y con esto lograremos registrarlo.



¡EUREKA!, estamos registrados!!

Autor: AmeRiK@nO

Crackeador MD5 por Fuerza bruta



Aca les dejo este crackeador por fuerza bruta. Funciona de maravilla aunque no es muy rapido por el lenguaje. Quizas les sirva como ejemplo.

Modo de uso:
Cracker.py -t md5 -h e10adc3949ba59abbe56e057f20f883e -w 1234567890 -a 9 -i 3 -v
Comandos:
-t: Tipo de Hash
-h: Hash a Crackear
-w: Teclas a usar 
-a: Maximo de Caracteres
-i: Minimo de Caracteres
-v: Modo Hacking
Codigo Fuente:
import sys , string , time
def main():
    "Main"
    title = "MD5 Cracker Brute Force"
    print "\n" + title.center(45) + "\n"
 
def usage():
    "Usage"
    print "[+] Ejemplo : Cracker.py -t md5 -h e10adc3949ba59abbe56e057f20f883e -w 1234567890 -a 9 -i 3 -v"
 
def usagelarge():
    "Usage Large"
    print "\n  Forma de Uso:"
    print "\n  [+]Ejemplo: Cracker.py -t md5 -h e10adc3949ba59abbe56e057f20f883e -w 1234567890 -a 9 -i 3 -v"
    print ""
    print "\t[Comandos]"
    print "\t   -t: Tipo de Hash"
    print "\t   -h: Hash a Crackear"
    print "\t   -w: Teclas a usar" 
    print "\t   -a: Maximo de Caracteres"
    print "\t   -i: Minimo de Caracteres"
    print "\t   -v: Modo Hacking\n"
 
def timer():
    "Time"
    now = time.localtime(time.time())
    return time.asctime(now)
 
if '__main__' == __name__ :
 
    if len(sys.argv) <= 5:
        main()
        usagelarge()
        sys.exit(1)
 
    hhash = words = maxw = minw = typeh = ""
    verbose = 0
 
    for arg in sys.argv[1:]:
        try:
            if arg.lower() == "-v" or arg.lower() == "-verbose":
                    verbose = 1
            if arg.lower() == "-h" or arg.lower() == "-hash":
                    hhash = sys.argv[int(sys.argv[1:].index(arg))+2]
            if arg.lower() == "-a" or arg.lower() == "-max":
                    maxw = sys.argv[int(sys.argv[1:].index(arg))+2]
            if arg.lower() == "-i" or arg.lower() == "-min":
                    minw = sys.argv[int(sys.argv[1:].index(arg))+2]
            if arg.lower() == "-w" or arg.lower() == "-words":
                    words = sys.argv[int(sys.argv[1:].index(arg))+2]
            if arg.lower() == "-t" or arg.lower() == "-type":
                    typeh = sys.argv[int(sys.argv[1:].index(arg))+2]
        except(IndexError):
            print "[+] Comandos Obligatorios -t(Tipo de Hash) -h(Hash) -w(Teclas) -a(Maximo de teclas) -a(Minimo de teclas)"
            usage()
            sys.exit(1)
 
    if minw == "" : minw = '1'
 
    main()
    for args in (typeh, hhash, words, maxw, minw):
        try:
            if args != "":
                if args == typeh :
                    if typeh.lower() != 'md5' or 'sha':
                        if typeh.lower() == "md5" :
                            typeh = '1'
                            print "[+] Tipo de Hash : MD5"
                        elif typeh.lower() == "sha" :
                            typeh = '2'
                            print "[+] Tipo de Hash : SHA1"
                    else:
                        print "[+] Tipo Invalido de Hash"
                        sys.exit(1)
                if args == hhash :
                    if typeh == '1' :
                        if len(hhash) == 32 :
                            print "[+] MD5 Hash : " + hhash
                        else:
                            print "[+] Tipo Invalido de Hash"
                            sys.exit(1)
                    if typeh == '2' :
                        if len(hhash) == 40 :
                            print "[+] SHA1 Hash : " + hhash
                        else:
                            print "[+] SHA1 Invalido"
                            sys.exit(1)
                if args == words :
                    print "[+] Teclas a usar : " + words
                if args == maxw :
                    if maxw.isdigit() != False :
                        if int(maxw) >= 15 :
                            print "[+] Maximo de Digitos : 15 : " + maxw
                            sys.exit(1)
                        else:
                            if int(maxw) > int(minw):
                                print "[+] Maximo de Digitos : " + maxw
                            else:
                                print "[+] El Maximo de Digitos debe ser mayor al Minimo de Digitos"
                                sys.exit(1)
                    else:
                        print "[+] Maximo de Teclas = Digitos"
                        sys.exit(1)
                if args == minw :
                        if minw.isdigit() != False :
                            if int(minw) < int(maxw) :
                                print "[+] Minimo de Digitos : " + minw
                            else:
                                print "[+] El minimo de Digitos debe ser menor al maximo de Digitos"
                                sys.exit(1)
                        else:
                            print "[+] Minimo de Teclas = Digitos"
                            sys.exit(1)
            else:
                print "[+] Comandos Obligatorios -t(Tipo de Hash) -h(Hash) -w(Teclas) -a(Maximo de teclas) -a(Minimo de teclas)"
                usage()
                sys.exit(1)
        except(ValueError):
            print "[+] Formato de comandos erroneo"
            sys.exit(1)
 
    f = open("CRACK.py",'w')
    f.write("#!/usr/bin/env python\n")
    if (typeh == '1') : f.write("import sys , md5 , string , time\n")
    if (typeh == '2') : f.write("import sys , sha , string , time\n")
    tab = '\t'
    f.write("def timer():\n")
    f.write(tab + "now = time.localtime(time.time())\n")
    f.write(tab + "return time.asctime(now)\n")
    f.write("def crackhash():\n")
 
    i = 0 ; inwords = ""
    f.write(tab + "hashh = '" + hhash.lower() +"'\n")
    f.write(tab + "try:\n")
 
    #print words
    for i in words:
        if i == str(words[int(len(words)-1)]) : 
            inwords += "'" + i + "'"
            break
        inwords += "'" + i + "',"
    #print inwords
 
    i = int(minw) ; iwords = "" ; a = 0
 
    while (int(i) != int(maxw) + 1) :
 
        for a in range(i):
            if int(i) != (int(a) + 1):
                iwords += "i" + str(i) + str(a) + " + "
            else:
                 iwords += "i" + str(i) + str(a)
 
            data = (int(a + 2) * tab + "for i" + str(i) + str(a) + " in (" + inwords + "):\n")
            f.write(data)
 
        f.write(int(i + 2) * tab + "word = '' ; value = ''\n")
        f.write(int(i + 2) * tab + "word = " + iwords + "\n")
 
        if typeh == '1' : f.write(int(i + 2) * tab + "hash = md5.new()\n")
 
        if typeh == '2' : f.write(int(i + 2) * tab + "hash = sha.new()\n")
 
        f.write(int(i + 2) * tab + "hash.update(str(word))\n")
        f.write(int(i + 2) * tab + "value = hash.hexdigest()\n") 
        if verbose == 1 : f.write(int(i + 2) * tab + "print " + iwords + ' + " : " + str(value)\n')
        f.write(int(i + 2) * tab + "if str(hashh) == str(value):\n")
        f.write(int(i + 3) * tab + "raise Exception\n")
        iwords = ""
        i += 1
 
    f.write(tab + "except(Exception):")
    f.write('\n'+ 2 * tab + "print '********************  Hash Crackeado ' + 20 * '*'")
    f.write('\n' + 2 * tab + "print '[+] HASH :', hashh")
    f.write('\n' + 2 * tab + "print '[+] PASS :', str(word)")
    f.write('\n' + 2 * tab + "print '[+] Hora de finalizacion :', timer()")
    f.write('\n' + 2 * tab + "sys.exit(1)")
    f.write('\n' + tab + "except(KeyboardInterrupt):")
    f.write('\n' + 2 * tab + "print '[+] Process Ended ',timer()")
    f.write('\n' + 2 * tab + "sys.exit(1)")
    f.write("\ncrackhash()")
    f.close()
 
    print '[+] Hora de inicio : ', timer()
 
    import CRACK
    CRACK.crackhash()