Desarrolla tus propias herramientas: “bruteforce”.

Anuncio
Desarrolla tus propias herramientas: “bruteforce”.
1. Responsabilidad
El autor declina toda responsabilidad sobre cualquier uso de la información presentada en el
mismo.
2. Introducción
En el presente artículo mostraremos cómo desarrollar una simple herramienta de fuerza bruta
en el lenguaje de programación: python (http://www.python.org).
Lo llevaremos a cabo paso por paso, incluyendo el código generado en cada etapa.
Entre los objetivos del texto, se encuentran (aunque no exclusivamente):
 Animar al lector a desarrollar sus propias herramientas: aunque a veces, existan otras
que podamos aplicar, esto no siempre es así; Además, ganamos en flexibilidad.
 Entender
un
algoritmo
recursivo
simple
(http://es.wikipedia.org/wiki/Algoritmo_recursivo): aunque no se trata de un texto
sobre programación, dado que utilizar un algoritmo recursivo es un método sencillo
para resolver este problema (aunque no necesariamente el más óptimo),
aprovechamos para utilizarlo de manera que el lector pueda ver su funcionamiento en
un lenguaje como python.
 Pasar un buen rato: :)
3. Aclaración
Qué no pretende este artículo:
 No, no es un texto dedicado a la programación, ni al estilo programando (para eso
existen otros textos). Así que si crees que se puede hacer mejor o más limpio,
cualquiera de los pasos, te animo a ello.
 Sí, existen herramientas que ya hacen lo que este ejemplo y quizá sean más eficientes
(aunque aquellas, no las hemos diseñado nosotros por lo que adaptarlas a nuestras
necesidades, puede ser complicado).
4. Prerequisitos
Se presupone unos conocimientos mínimos de programación. Al menos entender las
condiciones lógicas, bucles, etc. Unos conocimientos aunque sean básicos del lenguaje python,
son recomendables, si bien, no debería ser complicado para el lector, poder adaptar el código
a cualquier otro lenguaje con el que se sienta más cómodo, si lo considera necesario.
5. Primeros pasos, definición del algoritmo a crear
Intentaremos plasmar, en lenguaje natural, el objetivo del algoritmo a crear:
Se trata de un programa que podamos usar para atacar por fuerza bruta una contraseña, en
nuestro ejemplo, utilizaremos un md5 sin sal (“salt”).
Dada una serie de caracteres debemos ir probando uno a uno y las combinaciones posibles
hasta un límite (anchura de palabra) establecido.
Por ejemplo, suponiendo que el rango empezase en la letra “a”, sería algo similar a:
a, b, c, d…
aa, ab, ac…
Decidimos, para nuestro trabajo, limitar la lista de caracteres a aquellos comprendidos entre el
carácter ascii(32) y el ascii(127). Obviamente, el lector podrá probar cambiando el rango, por
ejemplo limitándolo a letras, números y caracteres especiales comúnmente utilizados, sólo
alfanuméricos, etc.
6. Enfoque recursivo
Pensamos en la siguiente manera de afrontarlo: partiendo de la cadena vacía (‘’) combinamos
esa cadena vacía con cada uno de los caracteres del rango. Comprobamos si esa palabra es la
que buscamos y, en caso contrario, ejecutamos el mismo proceso pero ahora partiendo con
nuestra nueva palabra en lugar de vacío. En caso de que nuestro proceso intente comprobar
una palabra con un ancho mayor al límite establecido, terminaremos.
Esta definición recursiva, se puede expresar de la siguiente manera:
Caso Base:
 Si ancho de palabra es mayor que el límite => termina
Caso Recursivo:
 Para cada carácter dentro del rango definido:
o nuevaBase = base + carácter
o verifica(nuevaBase)
o Si no es correcto:
 combina(nuevaBase, ancho + 1)
Para simplificar, de momento dejaremos como algoritmo para verificar (check) el código
necesario para imprimir la cadena a probar (así podemos comprobar que se está ejecutando
correctamente). Nuestro algoritmo recursivo se llamará combineChars y lo llamaremos, para
comenzar con una cadena vacía y un límite de 2 caracteres:
charRange = range(32,127)
def check(string):
print string
return False
# Caso base:
# -SI ancho a buscar > limite => Termina. (devuelve None)
# Caso Recursivo:
# -Para cada caracter posible
#
-SI Comprueba nuevaBase (base + caracter) == true => termina (devuelve
nuevaBase)
#
-SI NO => Combina(nuevaBase, ancho + 1)
def combineChars(base, width, maxLenght):
if width > maxLenght:
return
for char in charRange:
newBase = base + chr(char)
if check(newBase):
print "Encontrado: " + newBase
else:
combineChars(newBase, width + 1, maxLenght)
combineChars('', 1, 2)
Si lo ejecutamos veremos que obtenemos la lista de cadenas a probar:
!
"
#
$
%
&
'
(…)
~|
~}
~~
El siguiente paso que queremos dar es mejorar nuestro sistema de chequeo, lógicamente este
procedimiento dependerá del objetivo (podría tratarse de una petición web que analizaríamos
para comprobar un ataque de inyección ciega (SQL, XPath…), ataque por fuerza bruta a un
md5 o a un campo contraseña, etc).
Como comentamos al principio, en nuestro ejemplo usamos un md5 sin “salt”.
import hashlib
def check(string):
print "Probando: " + string + " \t" + hashlib.md5(string).hexdigest()
if (searchHash == hashlib.md5(string).hexdigest()):
return True
return False
Añadimos la cadena objetivo y, también, para evitar que el algoritmo continúe verificando
otras ramas, cuando ya haya encontrado la palabra a buscar, ejecutamos un sys.exit() (que
aunque no es lo más limpio, para nuestro ejemplo sobra. El lector podrá adaptarlo a sus
necesidades fácilmente si requiere que no pare la ejecución).
El código queda de la siguiente manera:
import hashlib
import sys
charRange = range(32,127)
searchHash = "187ef4436122d1cc2f40dc2b92f0eba0" #ab
def check(string):
print "Probando: " + string + " \t" + hashlib.md5(string).hexdigest()
if (searchHash == hashlib.md5(string).hexdigest()):
return True
return False
def combineChars(base, width, maxLenght):
if width > maxLenght:
return
for char in charRange:
newBase = base + chr(char)
if check(newBase):
print "Encontrado: " + newBase
sys.exit()
else:
combineChars(newBase, width + 1, maxLenght)
combineChars('', 1, 3)
Lo ejecutamos con como límite 3 caracteres y obtenemos el siguiente resultado (en esta
ocasión, lógicamente, tardará más):
(…)
Probando: aaz
Probando: aa{
Probando: aa|
Probando: aa}
Probando: aa~
Probando: ab
Encontrado: ab
18b79ceb98d2309a095a9168c2d48363
d4c50a8452fd9a8cdc2553c1081acbc8
0beeea38b67c5ecfecda20d352c4b121
2dae90b7e75a8489e9a6dc57db9e901b
1896cb9442ee07073c39676b6a88b412
187ef4436122d1cc2f40dc2b92f0eba0
Como retoques finales, eliminamos el texto de la prueba que estamos realizando (para limitar
el tiempo perdido), y añadimos una comprobación del tiempo utilizado.
El código resultante es el siguiente:
import time
import hashlib
import sys
charRange = range(32,127)
searchHash = "187ef4436122d1cc2f40dc2b92f0eba0" #ab
startTime = time.clock()
def check(string):
if (searchHash == hashlib.md5(string).hexdigest()):
return True
return False
def combineChars(base, width, maxLenght):
if width > maxLenght:
return
for char in charRange:
newBase = base + chr(char)
if check(newBase):
print "Encontrado: " + newBase
print "Tiempo: " + str(time.clock() - startTime)
sys.exit()
else:
combineChars(newBase, width + 1, maxLenght)
combineChars('', 1, 3)
En nuestra prueba (3 caracteres), obtenemos:
Encontrado: ab
Tiempo: 1.78526949312
Podemos realizar distintas pruebas para obtener los tiempos requeridos. Por ejemplo,
repetimos la prueba con un hash de una palabra de 4 caracteres (“hola”) y un límite de 4;
Obtenemos:
Encontrado: hola
Tiempo: 185.964875678
Para finalizar, animamos al lector a crear variaciones sobre el código:
 Cambiar el algoritmo de chequeo (check) por otro (sha, petición web a página de
autenticación, SQL-i…)
 Mejorar el código eliminando las salidas mediante sys.exit y el estilo de programación.
7. Sobre el Autor
Jesús Arnáiz es Consultor de Seguridad en Chase The Sun S.L., entre otros ha trabajado en
proyectos de auditoría y consultoría de seguridad, pruebas e intrusión/hacking ético, análisis
forense y cumplimiento normativo.
Descargar