Bienvenido a OMSTD (Open Methodology for Security Tool Developers)¶

OMSTD (Open Methodology for Security Tool Developers) es una serie de casos de estudio, agrupados y categorizados a modo de guía, con los que lograr desarrollar herramientas bien construidas.
Aunque puede ser usada para crear cualquier tipo de herramientas y en cualquier lenguaje, se centra principalmente en el desarrollo de herramientas de hacking escritas en Python.
Sería muy recomendable para el lector leer detenidamente la sección Qué es OMSTD , ya que le ayudará a comprender esta guía.
Autor¶
Esta guía ha sido escrita e ideada por Daniel García (A.K.A. cr0hn).
Web oficial y Twitter¶
Esta guía, así como su código de ejemplo y presentaciones están publicadas de forma gratuita en su repositorio de Github:
Puedes seguir los avances, novedades y noticias de esta guía en nuestra cuenta de twitter:
Licencias¶
Código¶
Todo el código aquí expuesto se distribuye bajo licencia BSD 3-clausule. Puedes copiarlo y redistribuirlo sin restricción, conservando la autoría del mismo y sin obtener beneficio económico directo del mismo.
Texto¶
Esta guía, y todo el texto que la contiene, se distribuye bajo la licencia: Creative Commons 4.0 - Attribution-NonCommercial 4.0 International (CC BY-NC 4.0).
Colaborar con OMSTD¶
Cualquier idea, crítica (constructiva, por favor) o colaboración es muy bienvenida. Puedes ponerte en contacto por las vías:
- Correo electrónico (cr0hn<<–AT–>>.cr0hn.com).
- Usando los issues de github.
- Haciendo un fork de proyecto y enviando un parche.
Si estás interesado en ayudar, puedes echar un vistazo a TODO.rst y ver las ideas pendientes de implementar.
Una de las finalidades es portar esta guía a otros idiomas. SI DOMINAS CUALQUIER OTRO IDIOMA ADEMÁS DEL ESPAÑOL, ANÍMATE A ECHAR UNA MANO.
¿A quién va dirigida esta guía?¶
Esta guía está dirigida a auditores de seguridad y pentesters que necesitan desarrollar sus propias herramientas (muchas veces on-the-fly) y quieren que éstas sean algo más que un simple script.
Un pentester puede ser muy bueno en tareas de hacking, pero no tiene porque tener tanto conocimiento en desarrollo, buenas prácticas, patrones de diseño, etc.
Para poder usar esta guía se recomienda:
- Tener conocimientos básicos de programación en Python 3 (Si solo sabes Python 2, también podrás seguir la guía, no te preocupes).
- Tener conocimientos básicos de seguridad informática
Organización de la guía¶
Bloques¶
Los casos de estudio se dividen en los siguientes bloques:
- Desarrollo
- Organización y estructura (ST): Cómo se organizan los proyectos.
- Comportamiento (BH): Forma de interactuar con diferentes frameworks.
- Interacción (IT): Interacción de usuario, con otros sistemas o con otros entornos.
- Específico de lenguaje (LS): Trucos, buenas prácticas y usos concretos del lenguaje de desarrollo. Python en este caso.
- Entrada/salida de información (IO): Generar de informes (Word, Excel...), exportar datos, importar información externa de XML/JSON...
- Redistribución (RD): Crear de paquetes, redistribuirlos, compilarlos para varios sistemas, portarlos a varios entornos, uso correcto de sistemas de control de versiones, etc.
- Despliegue (DP): Cómo poner en producción de forma correcta nuestra aplicación.
- Hacking (HH): Ejemplos de casos concretos de hacking.
- Cracking (CH): Ejemplos de casos concretos de cracking.
- Malware (MH): Ejemplos de casos concretos de malware.
- Forensic (FH): Ejemplos de casos concretos de forensic.
- Hardening (DH): Ejemplos de casos concretos de hardening.
Identificación de los casos¶
A fin de hacer más re-usable este texto, se identificarán los casos de la siguiente forma:
- XX[-EX]-YYY
- Problema: Presentación y descripción del caso de estudio.
- Solución: Solución propuesta.
- Cómo: Cómo llevar a cabo la solución.
- Anexo: Esta sección puede estar disponible o no. En ella se aclararán cuestiones muy concretas del caso de estudio.
- Donde:
- XX : Identificador de bloque.
- YYY: Valor numérico con el formato: 001, 002, 003...
- EX: Si el identificador tiene este sufijo, significa que se trata de un ejemplo completo o mini proyecto.
Índice¶
Inicio¶
Qué es OMSTD¶
OMSTD (O pen M ethodology for S ecurity T ool D evelopers) es una propuesta que intenta ser una guía de buenas prácticas fácil, intuitiva y práctica, para el desarrollo de aplicaciones de hacking.
Hacer herramientas de hacking no tiene porque ser tan complicado como puede parecer a priori, tan solo hay que seguir una serie de pautas de forma correcta.
Esta guía surge a partir de mi charla “El Poder de los reptiles: Cómo hacer herramientas de seguridad en Python” en IV Navaja Negra Conference.
El objetivo de este proyecto es crear una metodología abierta y colaborativa con la que poder servir de apoyo al desarrollo de nuevas herramientas.
Estado actual¶
Actualmente esta guía recoge un número bastante limitado de casos. Hay mucho por hacer y muchas ideas por documentar.
Poco a poco, y con la ayuda de todo el que quiera contribuir, espero que el número de casos de estudio y ejemplos crezca.
¡¡Aviso!!¶
Este texto es fruto de la experiencia, investigación propia y de los errores más comunes que me he encontrado cuando he tratado de desarrollar herramientas de hacking o usar otras en mi propio código.
El objetivo de este texto es ser una pequeña guía de buenas prácticas (y que espero ampliar en un futuro) para la creación de herramientas portables, bien diseñadas y mantenibles.
Las soluciones presentadas pueden no ser las mejores o más óptimas, son solo mis propuestas. Cualquier mejora o sugerencia es bienvenida.
Cómo usar esta guía¶
Teoría¶
Si es la primera vez que lees esta guía, te recomiendo que la leas los casos de estudio en el orden que están planteados.
Una vez estudiado y familiarizado con ellos tan solo tendrás que buscar el caso específico que necesites.
Ejemplos¶
Todos los ejemplos se encuentran colgando del directorio examples. En él podrás encontrar una serie de carpetas que coinciden con los códigos de los bloques (mirad más abajo ) y una sub-carpeta con valores numéricos de 3 dígitos, que corresponde con un caso de estudio concreto, por ejemplo:
example/bh/001/
Corresponderá con el caso de estudio 001 del tipo comportamiento (BH).
Instrucciones para empezar¶
Al igual que se explica en esta guía, ella misma está estructurada siguiendo el mismo modelo que plantea.
Tal vez sea adelantar acontecimientos pero, para ponértelo más fácil, estas son las instrucciones que deberías de seguir para poder ejecutar todos los casos de estudio:
1 - Instalar las dependencias de sistema¶
sudo python3.4 -m pip install virtualenvwrapper
2 - Configurar virtualenvwrapper¶
echo "export WORKON_HOME=$HOME/.virtualenvs" > ~/.bashrc
echo "export PROJECT_HOME=$HOME/Devel" >> ~/.bashrc
echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.bashrc
3 - Buscar el intérprete de Python 3.4¶
locate python3.4 | grep bin/python | grep python3
...
/opt/local/Library/Frameworks/Python.framework/Versions/3.4/bin/python3.4
/opt/local/Library/Frameworks/Python.framework/Versions/3.4/bin/python3.4-config
/opt/local/Library/Frameworks/Python.framework/Versions/3.4/bin/python3.4m
/opt/local/Library/Frameworks/Python.framework/Versions/3.4/bin/python3.4m-config
/opt/local/bin/python3.4
/opt/local/bin/python3.4-config
/opt/local/bin/python3.4m
4 - Crear el entorno virtual (o sandbox) de pruebas¶
mkvirtualenv -p /opt/local/bin/python3.4 omstd
5 - Instalar las dependencias globales de OMSTD¶
Situados en el directorio raiz del proyecto de OMSTD ejecutamos:
pip install -r requirements.txt
6 - Instalar las dependencias locales de cada ejemplo¶
Cada caso de estudio puede tener su propio fichero requirements.txt con sus propias dependencias. Esto es así para no obligar al lector a instalar todas las dependencias del proyecto, ya que puede que no las necesite todas.
Para instalar las dependencias de cada ejemplo ha de proceder como en el punto anterior con cada fichero listado de dependencias.
Cómo colaborar¶
OMSTD es un proyecto de carácter abierto y gratuito. Si te apetece compartir tu experiencia y conocimiento con otros, serás muy bienvenido.
Principalmente existen los siguientes tipos de colaboraciones:
- Corrección de errores y mejoras:.
- Propuestas de nuevos casos de estudios.
- Envío de un nuevo caso de estudio.
- Ayuda a la traducción.
Corrección de errores y mejoras¶
Si detectas cualquier fallo o algún punto mejorable puedes:
Propuestas de nuevos casos de estudios¶
Si deseas sugerir un nuevo caso de estudio, tan solo tienes que abrir un ticket con tu propuesta (Anexo 2).
Éste será clasificada y catalogada en función de la naturaleza del mismo.
Envío de un nuevo caso de estudio¶
El medio preferible para enviar nuevos casos de estudio es el siguiente:
De esta forma todo quedará registrado, para que todo el mundo pueda seguirlo, además de llevar un mejor orden.
Ayuda a la traducción¶
Este caso es muy parecido al anterior. pero con un cierto matiz:
- Crear un fork del proyecto (Anexo 3).
- Copiar la carpeta doc/es al directorio doc/LANG, donde LANG es el código ISO del lenguaje de la traducción. Por ejemplo: Si se está traduciendo a inglés sería doc/en. Sobre este directorio será sobre el que se trabajará y traducirá.
- Enviar un parche con el nuevo caso de estudio (Anexo 1).
Anexos¶
Anexo 1: Envío de parches usando GitHub¶
Una vez hemos forkeado y hechos los cambios pertinentes en el código, para enviar un parche siga las siguientes instrucciones:
- Pulsamos en la opción de Pull Request:

- Creamos un nueva nueva propuesta de parche pulsando en New pull request:

- GitHub detectará los cambios realizados, extraídos de los commits que hayamos realizado, y preparará el request. Para finalizar el envío tan solo tenemos que pulsar en Create pull request:

Anexo 2: Apertura de incidencias en GitHub¶
La apertura de incidencias en GitHub es muy sencilla, tan solo tenemos que utilizar su sistema de ticketing:
- Podemos ir directamente a los issues siguiendo el link https://github.com/cr0hn/OMSTD/issues, o podemos ir al home del proyecto, https://github.com/cr0hn/OMSTD, y pulsar en Issues, del menú de la derecha:

- En esta pantalla nos aparecerán todas las incidencias y propuestas abiertas. Para crear una nueva pulsaremos en New Issue:

- Para la apertura de la incidencia es necesario un título y una descripción. Es muy conveniente ser conciso en el título y explicar en detalle la incidencias, mejora o propuesta.

Anexo 3: Crear un fork de un proyecto en GitHub¶
Crear un fork de un proyecto en GitHub es realmente fácil. Tan solo tendrás que:
- Identificarte con tu usuario,
- Ir al repositorio oficial del proyecto: https://github.com/cr0hn/OMSTD
- Hacer click en el botón superior derecho con el texto Fork. La siguiente imagen muestra cómo:

Conceptos de desarrollo¶
Organización y estructura¶
En este bloque se tratan los casos de estudio relacionados con la organización y estructura de proyectos.
ST-001 - Estructura de proyecto¶
Problema¶
Muchas herramientas no contemplan la opción de ser usadas como librería (con un “import”) además de por linea de comandos, o la interfaz que tengan definida.
Su uso puede ser complicado y, muchas veces, tan solo pueden ser usadas por linea de comandos, lo que implica llamar a un proceso externo del sistema y parsear su salida.
Solución¶
Una estructura correcta en la organización de archivos, pensada para ser re-utilizable.
Cómo¶
En la imagen se puede ver la estructura propuesta:

Donde:
- LICENCE: Fichero de licencia. Contiene el texto legal con el que queremos redistribuir nuestro código.
- README.rst: Fichero índice de documentación del proyecto. Será el primero en mostrarse y leerse por defecto.
- requirements.txt: Contiene las dependencias de librerías externas de nuestro proyecto.
- setup.py: Contiene información para la instalación, configuración y redistribución de nuestro software.
- TODO.rst: Ideas futuras a implementar en nuestro proyecto. Es buena idea tenerlo por si alguien quiere colaborar con nuestro proyecto, ya que podrá encontrar tareas e ideas por hacer.
- app_name: Carpeta, que actúa como paquete Python, con el nombre de nuestra aplicación.
- app_name/bin: Ejecutables disponibles en nuestra aplicación.
- app_name/doc: Archivos de documentación, usualmente escrita con Shpinx. Esta guía es un ejemplo de su uso.
- app_name/lib: Librerías propias que genere nuestra aplicación.
- app_name/test: Contiene los diferentes test de la aplicación: Unitarios, de integración, rendimiento...
ST-002 - Entrada de parámetros globales¶
Problema¶
Una vez preparada la estructura para nuestra aplicación, ahora queremos “llamar” o ejecutar el programa en cuestión. Problemas:
- Añadir nuevos parámetros de entrada al programa, requiere muchos cambios.
- El código se hace cada vez más enrevesado e inmanejable.
- Cambios de versiones de un mismos programas son incompatibles.
Este ejemplo de programa que divide dos números. Acepta 2 parámetros por linea de comandos:
- Nominado
- Denominador
Si ocurre una división por 0 devuelve -1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import argparse
# ----------------------------------------------------------------------
def divide(param1, param2):
try:
return param1 / param2
except ZeroDivisionError:
return -1
# ----------------------------------------------------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-p1", dest="param1", help="parameter1")
parser.add_argument("-p2", dest="param1", help="parameter2")
params = parser.parse_args()
d = divide(params.param1, params.param2)
print(d)
|
Ahora queremos añadir otro parámetro, “verbosity”, como opción a nuestro programa.
Vemos que hay que modificar el código en 2 sitios diferentes. En todos aquellos donde se tenga que pasar información de las opciones de ejecución:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # ----------------------------------------------------------------------
def divide(param1, param2, verbosity):
try:
return param1 / param2
except ZeroDivisionError as e:
if verbosity > 0:
print(e)
return -1
# ----------------------------------------------------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-p1", dest="param1", help="parameter1")
parser.add_argument("-p2", dest="param1", help="parameter2")
parser.add_argument("-v", dest="verbosity", type="int", help="verbosity")
params = parser.parse_args()
d = divide(params.param1, params.param2, params.verbosity)
print(d)
|
Solución¶
Hacer un objeto global que sea el que contenga los parámetros globales de ejecución (proyectos grandes, como nmap, proyecto lo hacen de esta forma):
Cómo¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | # --------------------------------------------------------------------------
class Parameters:
"""Program parameters"""
# ----------------------------------------------------------------------
def __init__(self, **kwargs):
self.param1 = kwargs.get("param1")
self.param2 = kwargs.get("param2")
self.verbosity = kwargs.get("verbosity")
# ----------------------------------------------------------------------
def divide(input_params):
try:
return input_params.param1 / input_params.param2
except ZeroDivisionError as e:
if input_params.verbosity > 0:
print(e)
return -1
# ----------------------------------------------------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-p1", dest="param1", help="parameter1")
parser.add_argument("-p2", dest="param1", help="parameter2")
parser.add_argument("-v", dest="verbosity", type="int", help="verbosity")
params = parser.parse_args()
input_parameters = Parameters(param1=params.param1,
param2=params.param2,
verbosity=params.verbosity)
d = divide(input_parameters)
print(d)
|
ST-003 - Gestión de resultados¶
Problema¶
Recuperar la información de salida de una aplicación es muy complicado:
- No está estandarizada
- Hay que parsear muchos XML/JSON.
- La herramienta solo reporta resultados por consola.
Usando el mismo ejemplo que en el anterior caso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import argparse
# ----------------------------------------------------------------------
def divide(param1, param2):
try:
return param1 / param2
except ZeroDivisionError:
return -1
# ----------------------------------------------------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-p1", dest="param1", help="parameter1")
parser.add_argument("-p2", dest="param1", help="parameter2")
params = parser.parse_args()
d = divide(params.param1, params.param2)
print(d)
|
Cuando queremos añadir un parámetro nuevo en la devolución, vemos que hay que modificar varias lineas de código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # ----------------------------------------------------------------------
def divide(param1, param2):
try:
results = param1 / param2
is_2_divisor = results % 2
return results, is_2_divisor
except ZeroDivisionError as e:
return -1, False
# ----------------------------------------------------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-p1", dest="param1", help="parameter1")
parser.add_argument("-p2", dest="param1", help="parameter2")
params = parser.parse_args()
result, is_divisor = divide(params.param1, params.param2)
print("Results: %s - %s" % (result, is_divisor))
|
Solución¶
Usar objetos genéricos que contengan la información resultante de la ejecución del a herramienta.
Además, estos objetos, nos permiten abstraer el almacenamiento de información, de cómo ésta es exportada o transformada: XML, JSON, HTML, PDF ...
Cómo¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | # --------------------------------------------------------------------------
class Results:
"""Program parameters"""
# ----------------------------------------------------------------------
def __init__(self, **kwargs):
self.result = kwargs.get("result", 0)
self.is_2_divisor = kwargs.get("is_2_multiple", False)
# ----------------------------------------------------------------------
def divide(param1, param2):
try:
results = param1 / param2
is_2_divisor = results % 2
return Results(result=results,
is_2_divisor=is_2_divisor)
except ZeroDivisionError:
return Results()
# ----------------------------------------------------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='ktcal2 - SSH brute forcer')
parser.add_argument("-p1", dest="param1", help="parameter1")
parser.add_argument("-p2", dest="param1", help="parameter2")
parser.add_argument("-v", dest="verbosity", type="int", help="verbosity")
params = parser.parse_args()
result = divide(params.param1, params.param2)
print("Results: %s - %s" % (result.result, result.is_2_divisor))
|
ST-004 - Unificar puntos de entrada¶
Problema¶
Tengo que cambiar mucho código y rehacer gran parte de mi aplicación, cada vez que quiero hacer una nueva UI (User Interface):
- Linea de comandos.
- Interfaz gráfica.
- Web.
- Que se use como librería.

Solución¶
Centralizar el punto de entrada a la ejecución de tu aplicación en un único punto y que todas las UI usen el mismo.

Cómo¶
Incluir el concepto de api.py y enseñar como el command line y el import funcionan igual.

Tras incluir el fichero “api.py”, la UI de linea de comandos (st-004-s1.py) nos quedará como sigue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from .api import Parameters, run
# ----------------------------------------------------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-p1", dest="param1", help="parameter 1")
parser.add_argument("-p2", dest="param2", help="parameter 2")
params = parser.parse_args()
input_parameters = Parameters(param1=params.param1,
param2=params.param2,
verbosity=params.verbosity)
run(params)
|
Si echamos un vistazo a api.py, podemos observar que este fichera centraliza las llamadas al resto de librerías:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # Import data
from .lib.data import *
# Import libs
from .lib.operations import divide
from .lib.exports import display_results
# ----------------------------------------------------------------------
def run(input_parameters):
# Run operation
r = divide(input_parameters)
# Display results
display_results(r)
|
ST-005 - La linea de comandos¶
Problema¶
Crear una linea de comandos avanzada puede no resultar sencilla, y mucho menos intutiva.
Estos son algunos de los problemas más comunes que podemos encontrarnos:
- Crear opciones posicionales: main.py -n -i 9 POSITIONAL_PARAM.
- Crear una ayuda personalizada, cuando se ejecute la opción -h: main.py -h.
- Los tipos de datos recogidos por la linea de comandos son incorrectos: main.py -t 4, por defecto 4 será tratado como un string, no como un int.
- Ausencia de opciones por defecto.
- Establecer ciertas opciones como obligatorios.
- Establecer un opciones y una versión abreviada de las mismas: main.py -t 8 equivalente a main.py --threads 8.
- Opciones sin parámetros, tratados como bool: main.py -j.
- Opciones que puedan usarse como acumuladores: main.py -v -> main.py -vv -> main.py -vvv.
Solución¶
En este caso de prueba la solución no es otra que usar correctamente la libraría de Python argparse
Cómo¶
Para que sea más ilustrativo, vamos a partir de una linea de comandos básica y vamos ir mejorándola poco a poco.
La linea de comandos de la que partiremos la que sigue, de un posible escaneador de puertos:
1 2 3 4 5 6 7 8 9 | if __name__ == "__main__":
parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-t", dest="targets", help="target")
parser.add_argument('-v', dest='verbosity level')
parser.add_argument("--open", dest="only_open", help="only display open ports")
parser.add_argument("--port", dest="port")
params = parser.parse_args()
|
Que mostrará el siguiente resultado por consola, cuando ejecutamos st-005-p1.py -h:
usage: st-005-p1.py [-h] [-t TARGETS] [-v VERBOSITY LEVEL] [--open ONLY_OPEN]
[-p PORTS_RANGE]
OMSTD Example
optional arguments:
-h, --help show this help message and exit
-t TARGETS target
-v VERBOSITY LEVEL
--open ONLY_OPEN only display open ports
--port PORT
Nota
Nótese que se puede ejecutar sin parámetros y no se devolvería ningún error a pesar de que, por razones obvias, necesitamos como mínimo un parámetros: targets.
En primer lugar vamos a solventar el problema del parámetro obligatorio y vamos a obligar a especificar un target:
1 2 3 4 5 6 7 | parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-t", dest="targets", help="target", required=True)
parser.add_argument('-v', dest='verbosity' help='verbosity level', type=int)
parser.add_argument("--open", dest="only_open", help="only display open ports", default=0)
parser.add_argument("--port", dest="port", type=int, help="port to scan")
params = parser.parse_args()
|
Vemos cómo, tras en cambio, se obliga al usuario a especificar un target:
usage: st-005-p1.py [-h] -t TARGETS [-v VERBOSITY LEVEL] [--open ONLY_OPEN]
[-p PORTS_RANGE]
st-005-p1.py: error: the following arguments are required: -t
Vemos que las opciones verbosity (0-2) y port son tratadas como string (por defecto), pero realmente son del tipo int.
Añadimos la mejora:
1 2 3 4 5 6 7 | parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-t", dest="targets", help="target", required=True)
parser.add_argument('-v', dest='verbosity' help='verbosity level', type=int)
parser.add_argument("--open", dest="only_open", help="only display open ports", default=0)
parser.add_argument("--port", dest="port", type=int, help="port to scan")
params = parser.parse_args()
|
Sería interesante contar con valores por defecto para cada tipo de parámetro.
De esta forma evitaremos errores, por ausencia de dichos valores por parte del usuarios, e introduciremos configuración predeterminada, lo que hará el uso de la herramienta más fácil:
1 2 3 4 5 6 7 8 | parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-t", dest="targets", help="target", required=True)
parser.add_argument('-v', dest='verbosity' help='verbosity level', type=int, default=0)
parser.add_argument("--open", dest="only_open", help="only display open ports", default=0)
parser.add_argument("--port", dest="port", type=int, default=80,
help="port to scan. Default: 80.")
params = parser.parse_args()
|
Si observamos, la opción –open, es realmente un booleano. Es decir, que solo necesitamos saber si está a True o a False.
Actualizamos para que dicha opción sea tratada como un booleano.
1 2 3 4 5 6 7 8 9 | parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-t", dest="targets", help="target", required=True)
parser.add_argument('-v', dest='verbosity' help='verbosity level', type=int, default=0)
parser.add_argument("--open", dest="only_open", action="store_true",
help="only display open ports", default=False)
parser.add_argument("--port", dest="port", type=int, default=80,
help="port to scan. Default: 80.")
params = parser.parse_args()
|
Nota
Los datos booleanos no necesitan indicarles el tipo, cuando se usan con la opción action="store_true|false. El tipo está implícido cuando usamos el parámetro de configuración action="store_XXXX".
Cuando tenemos muchas opciones en la linea de comandos, es intersante poder poner la opciones en formato más abrevidado.
Para nuestro ejemplo, cuando indicamos del puerto, podríamos crear la opción abreviada -p, además de --port como sigue:
1 2 3 4 5 6 7 8 9 | parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-t", dest="targets", help="target", required=True)
parser.add_argument('-v', dest='verbosity' help='verbosity level', type=int, default=0)
parser.add_argument("--open", dest="only_open", action="store_true",
help="only display open ports", default=False)
parser.add_argument("-p", "--port", dest="port", type=int, default=80,
help="port to scan. Default: 80.")
params = parser.parse_args()
|
Existen ciertas opciones que tienen más sentido que su valor vaya incrementando en función de las veces que se repite. Por ejemplo:
- -v en lugar de 0.
- -vv en lugar de 1.
- -vvv en lugar de 2.
En nuestro ejemplo, dicho comportamiento es perfecto para el opación verbosity:
1 2 3 4 5 6 7 8 9 10 | parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("-t", dest="targets", help="target", required=True)
parser.add_argument('-v', dest='verbosity' help='verbosity level: -v, -vv, -vvv.',
action="count", type=int, default=0)
parser.add_argument("--open", dest="only_open", action="store_true",
help="only display open ports", default=False)
parser.add_argument("-p", "--port", dest="port", type=int, default=80,
help="port to scan. Default: 80.")
params = parser.parse_args()
|
Por último, puede que nos interese tener parámetro qeu no van precedidos de ninguna opción que empiece por -. Por ejemplo:
- main.py 127.0.0.1 en lugar de main.py -t 127.0.0.1.
Esta forma de configurar las opciones de la linea de comandos se conoce como opciones posicionales.
Entenderemos mejor esto con un ejemplo:
1 2 3 4 5 6 7 8 9 10 11 | parser = argparse.ArgumentParser(description='OMSTD Example')
parser.add_argument("targets", metavar='TARGETS', help="targets to scan", nargs="+",
required=True)
parser.add_argument('-v', dest='verbosity' help='verbosity level: -v, -vv, -vvv.',
action="count", type=int, default=0)
parser.add_argument("--open", dest="only_open", action="store_true",
help="only display open ports", default=False)
parser.add_argument("-p", "--port", dest="port", type=int, default=80,
help="port to scan. Default: 80.")
params = parser.parse_args()
|
La linea de comando ahora se verá así:
usage: st-005-s1.py [-h] [-v] [--open] [-p PORT] TARGETS [TARGETS ...]
OMSTD Example
positional arguments:
TARGETS targets to scan
optional arguments:
-h, --help show this help message and exit
-v verbosity level: -v, -vv, -vvv.
--open only display open ports
-p PORT, --ports PORT
port to scan. Defaul: 80.
Una ver parseada la linea de comandos, en el objeto params, podremos obtener la lista de los parámetros posicionales leyendo el parámetro params.targets.
Suele ser muy útil para nuestros programas poner pequeños ejemplos al final de la ayuda que nos proporciona la opción -h. Para esto, usamos el parámetro epilog, del módulo argparse:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | footed_description = """
Usage examples:
+ Basic scan:
%(name)s 127.0.0.1
+ Specifing port to test:
%(name)s -p 443 127.0.0.1
+ Only diplay open ports
%(name)s -p 139 127.0.0.1
""""" % dict(name="st-005-s1.py")
parser = argparse.ArgumentParser(description='OMSTD Example', epilog=footed_description,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("targets", metavar='TARGETS', help="targets to scan", nargs="+")
parser.add_argument('-v', dest='verbosity', default=0, action="count",
help="verbosity level: -v, -vv, -vvv.")
parser.add_argument("--open", dest="only_open", action="store_true",
help="only display open ports", default=False)
parser.add_argument("-p", "--ports", dest="port", type=int,
help="port to scan. Defaul: 80.", default=80)
params = parser.parse_args()
|
Este es el aspecto que tendría al ejecutar st-005-s1.py -h:
usage: st-005-s1.py [-h] [-v] [--open] [-p PORT] TARGETS [TARGETS ...]
OMSTD Example
positional arguments:
TARGETS targets to scan
optional arguments:
-h, --help show this help message and exit
-v verbosity level: -v, -vv, -vvv.
--open only display open ports
-p PORT, --ports PORT
port to scan. Defaul: 80.
Usage examples:
+ Basic scan:
st-005-s1.py 127.0.0.1
+ Specifing port to test:
st-005-s1.py -p 443 127.0.0.1
+ Only diplay open ports
st-005-s1.py -p 139 127.0.0.1
Interacción¶
En este bloque se tratan los casos de estudio relacionados con:
- Interacción de usuario
- Interacción otros sistemas y entornos.
IT-001 - Linea de comandos como entrada¶
TODO
Específicos del lenguaje¶
En este bloque se tratan los casos de estudio relacionados con los aspectos específicos del lenguaje Python:
- Trucos
- Buenas prácticas
- Consejos
- Nuevas características introducidas por Python 3
LP-001 - Tareas no bloqueantes¶
Problema¶
Python es mucho más lento que otros frameworks o lenguajes, como nodejs, que usa conexiones no “bloqueantes” o “corrutinas”.
Nota
¿Qué es una corrutina (coroutine en inglés)?
Una corrutina es una compontente (función, clase o método) capaz de suspender su ejecución hasta recibir un cierto estímulo:

Solución¶
Desde Python 3.4 existen lo que se llaman “corrutinas” a través de la librería, incluida en el framework 3.4, asyncio (también conocido como Tulip).
Cómo¶
Este ejemplo muestra como descargar el contenido de una URL, usando la librería estándar de Python 3.4:
1 2 3 4 5 6 7 8 9 10 11 12 13 | import urllib.request
# ----------------------------------------------------------------------
def main():
req = urllib.request.Request('http://www.navajanegra.com')
response = urllib.request.urlopen(req)
the_page = response.read() # <---- BLOCKING POINT
print(the_page)
if __name__ == '__main__':
main()
|
En el siguiente podemos ver cómo, usando Tulip y la librería aiohttp, podemos hacer peticiones no bloqueantes muy fácilmente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import asyncio
import aiohttp
# ----------------------------------------------------------------------
@asyncio.coroutine
def print_page(url):
response = yield from aiohttp.request('GET', url)
body = yield from response.read_and_close(decode=True)
print(body)
# ----------------------------------------------------------------------
def main():
"""Comment"""
loop = asyncio.get_event_loop()
loop.run_until_complete(print_page('http://navajanegra.com'))
if __name__ == '__main__':
main()
|
Nota
aioHTTP es una librería independiente, construida usando asyncIO a bajo nivel, y que nos facilitará el manejo de conexiones HTTP.
LP-002 - Multithreading y procesamiento paralelo¶
Problema¶
Python no tiene hilos ni multithreads real debido al GIL (Global Interpreter Lock). Éste restringe la ejecución a un único hilo corriendo a la vez. Esto es así porque, cuando se diseñó python, se prefirió que el motor fuera más simple su implementación, a costa de sacrificar la eficiente.
Nota
Para entender mejor cómo funciona el GIL, se recomienda al lector las charlas y estudios de David Beazley.
Solución¶
Esto es cierto y no se puede hacer nada a día de hoy. Es el modo de funcionamiento de la VM de Python por defecto, CPython, no se puede lograr, multithreading real.
La solución, para lograr la ejecución pararela en Python, es usar multiprocessing.
Nota
Existen otras implementaciones de la máquina virtual de Python: Diferentes implementaciones de la máquina virtual de PYthon
En el resto de implementaciones SI que existe el mutithread real, pero tienen multitud de incompatibilidades y no es recomendable su uso para propósito general.
Cómo¶
El siguiente código muestra un ejemplo típico de multithreading en Python, en el que solo puede haber 10 threads en ejecución concurrente (que no paralela) a la vez:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import threading
sem = threading.Semaphore(10)
# ----------------------------------------------------------------------
def hello(i):
print(i)
sem.release()
# ----------------------------------------------------------------------
def main():
threads = []
for x in range(50):
# Create thread
t = threading.Thread(target=hello, args=(x,))
# Start thread
sem.acquire()
t.start()
threads.append(t)
# Wait for end of all threads
map(threading.Thread.join, threads)
if __name__ == '__main__':
main()
|
El siguiente ejemplo muestra el mismo resultado, pero con multiprocessing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from multiprocessing.pool import Pool
# ----------------------------------------------------------------------
def hello(i):
print(i)
# ----------------------------------------------------------------------
def main():
p = Pool(10)
p.map(hello, range(50))
if __name__ == '__main__':
main()
|
Nota
El código anterior, además de ser multiproceso real, tiene las ventajas:
- Al usar CTRL+C funcionará correctamente.
- Usar una librería del framework, que gestiona internamente, un pool de procesos. Con esto evitamos tener que llevar el control manual del número de procesos concurrentes que pueden ejecutarse.
LP-003 - Callback, número de parámetros y partials¶
Problema¶
Cuando usamos librerías externas o de terceros, tenemos que adaptarnos a su API, pero no siempre es fácil. Supongamos la siguiente situación:
Tenemos una función que ha de invocarse con 3 parámetros a través de un callback externo, pero este callback solo pasa uno de los parámetros requeridos por nuestra función. Con un ejemplo se verá mejor:
Tenemos una librería, llamada external.api, de la que queremos ejecutar una función action_with_callback y cuyo código es:
1 2 3 4 5 6 7 8
def action_with_callback(callback_func=None): from time import sleep from random import randint time_sleep = randint(10, 100) sleep(time_sleep) callback_func(time_sleep)
En nuestro código queremos ejecutar diferentes acciones, en función lo que el usuario indice en la linea de comandos, pero no podemos por el modo de funcionamiento del API externo: No podemos pasar como parámetro la operación y el primer dato:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
from externa.api import action_with_callback def actions(operation, data1, data2): if operation == 0: return data1 / data2 elif operation == 1: return data1 * data2 elif operation == 2: return data1 % data2 if __name__ == "__main__": import sys # Get first and second command line parameters: # arg 0 -> operation # arg 1 -> first value for operation operation = sys.argv[0] data1 = sys.argv[1] action_with_callback(callback_func=actions) # Wrong!!!! -> operation and data1 missing
Solución¶
Usar los partials de Python.
Un partial en una función que se puede construida por partes, dejando una parte fija y otra variable. Siguiendo con el ejemplo anterior:
deberíamos dejar como parte “fija”:
- La operación a realizar
- El primer dato
Y como parte variable, el valor que devuelva el callback.
Es decir, que a partir de la primera función actions(operation, data1, data2), tenemos que generar una segunda con un solo parámetro:
actions(operation, data1, data2) -> TRANSFORM -> new_actions(data2)
Cómo¶
Para construir un partial en Python es muy sencillo, para ello debemos usar la librería functools:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | from functools import partial
from externa.api import action_with_callback
def actions(operation, data1, data2):
if operation == 0:
return data1 / data2
elif operation == 1:
return data1 * data2
elif operation == 2:
return data1 % data2
if __name__ == "__main__":
import sys
# Get first and second command line parameters:
# arg 0 -> operation
# arg 1 -> first value for operation
operation = sys.argv[0]
data1 = sys.argv[1]
new_actions = partial(actions, operation, data1)
action_with_callback(callback_func=new_actions) # Well!
|
LP-004 - “__main__” y ejecuciones accidentales¶
Problema¶
Cuando Python se invoca, por como está concebido, lee y ejecuta todas los los ficheros y dependencias.
Esto puede implicar:
- Llamadas accidentales a métodos, funciones o variables.
- Al no saber el orden exacto de carga de cada fichero, puede provocar errores muy difíciles de detectar.
Por ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class _ListNumbers:
"""Singleton with list numbers"""
def __init__(self):
self.numbers = [x for x in range(100)]
def get_number(self):
return self.numbers[randint(0, len(self.numbers))]
ListNumbers = _ListNumbers()
def hello_word(number):
print("Hello world with number:", number)
hello_word(ListNumbers.get_number())
|
Como podemos ver en el ejemplo, la clase con el listado de números debería de ejecutarse y cargarse antes, pero no podemos estar seguros. Ello depende de la implementación de la máquina virtual de Python.
Es sencillo imaginar por el lector que ocurriría si el orden de carga no fuera el “natural”: Se produciría una condición de carrera y nuestro código fallaría. Las condiciones de carrera son sumamente complicadas de detectar.
Solución¶
Usar la “etiqueta” __name__ para indicar que dicha sección contiene el punto de entrada principal, o función main, a nuestra aplicación.
Cómo¶
En el siguiente código podemos apreciar que el cambio es mínimo. Con este sencillo cambio nos ahorraremos multitud de problemas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class _ListNumbers:
"""Singleton with list numbers"""
def __init__(self):
self.numbers = [x for x in range(100)]
def get_number(self):
return self.numbers[randint(0, len(self.numbers))]
ListNumbers = _ListNumbers()
def hello_word(number):
print("Hello world with number:", number)
if __name__ == "__main__":
hello_word(ListNumbers.get_number())
|
LP-005 - Apertura, cierre y olvidos en descriptores¶
Problema¶
Cuando trabajamos con handlers (o descriptores) ya sean de archivo, red o de cualquier otro tipo, sobre ellos hay 3 acciones que siempre llevaremos a cabo:
- Apertura del descriptor.
- Uso del descriptor
- Cierre del descriptor.
El paso 3, cierre del descriptor, es uno de los grandes olvidados por:
- Se omite por descuido del programador.
- No se cierra adecuadamente: a causa de algún problema y errores.
Esto dejará descriptores de sistema abiertos y huérfanos, ocupando recursos del sistema operativo y degradando el rendimiento
El siguiente ejemplo ilustra esta situación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from tempfile import TemporaryFile
def main(number):
"""
Generates 100 temporal files with random numbers
"""
for x in range(100):
f = TemporaryFile(mode="w")
f.write(randint(100, 10000))
if __name__ == "__main__":
main()
|
Como se puede ver, en el ejemplo se crean 100 archivos temporales pero no se cierran.
Solución¶
Python dispone de la palabra reservada with que se asegurará de:
- Garantizar una sintaxis sencilla y legible.
- Cerrar el descriptor, y re-intentando o forzando su cierre cuando sea necesario.
with funciona también con sockets, acceso a bases de datos, o cualquier estructura compatible.
Cómo¶
Para usar with tan solo tendremos usar la sintaxis:
1 2 3 | with open("...", ".") as f:
# ACTIONS
f.write("my text")
|
Donde:
- “f” será el descriptor de fichero que usaremos en nuestro código.
- Cualquier acceso que tengamos que hacer sobre el fichero solo podremos hacerlo debajo del bloque del with.
En el momento que la ejecución del bloque finalice, el descriptor se cerrará automáticamente por la VM de Python
Aquí podemos ver el ejemplo anterior usando with:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from tempfile import TemporaryFile
def main(number):
"""
Generates 100 temporal files with random numbers
"""
for x in range(100):
with TemporaryFile(mode="w") as f:
f.write(randint(100, 10000))
if __name__ == "__main__":
main()
|
LP-006 - import relativos, absolutos, paquetes y demás hierbas¶
Problema¶
Cuando desarrollamos en Python es muy habitual toparnos rápidamentente con mensajes como:
...
ValueError: Attempted relative import in non-package
...
SystemError: Parent module '' not loaded, cannot perform relative import
Estos errores son muy abituales en Python 3.x. Esto es debido a que se está tratando de hacer un import cómo si se tratara de un paquete. Pero... ¿Qué significa esto?
Nota
Python 3.4 introdujo cambios en comportamiento interno cuando importa módulos y dependencias.
Puede leer más sobre este tema consultando el PEP-0366.
Se recomienda la lectura del post de taherh en StackOverFlow: http://stackoverflow.com/a/6655098 , sobre este tema.
Un par de conceptos:
En Python es un proyecto que puede ser importado, para ser usado como librería.
Éstos deberían tener import relativos, para asegurarse que la librería importada es del propio paquete, y no otra del sistema que tenga el mismo nombre.
Podemos ver la importancia de las rutas relativas en el siguiente ejemplo:
Supongamos que nuestra aplicación de ejemplo lp-006-p1.py
1 2 3 | from lp_006_p1.bad import *
lp_006_p1_fn()
|
El ejemplo tiene la estrutura:
1 2 3 4 5 6 7 | lp_006_p1
\__ random
\__ __init__.py
\__ __init__.py
\__ bad.py
\__ good.py
\__ lp-006-p1.py
|
El paquete random tiene el mismo nombre que el incluido en Python, por lo que si escribimos el siguiente código:
1 2 3 4 5 6 | from random import *
# ----------------------------------------------------------------------
def lp_006_p1_fn():
print(HELLO_VAR)
|
Cuantro se trata de mostrar la variable HELLO_VAR, no será encontrada porque realmente estamos importando es el paquete global de Python, y éste no contiene dicha variable. Por tanto se nos mostrará el siguiente error:
Traceback (most recent call last):
File "lp-006-p1.py", line 26, in <module>
lp_006_p1_fn()
File "examples/develop/lp/006/lp_006_p1/bad.py", line 30, in lp_006_p1_fn
print(HELLO_VAR)
NameError: name 'HELLO_VAR' is not defined
Una definición informa de aplicación de Python es aquella que usa como programa independiente y que tiene como finalidad ser importado por una aplicación externa.
El fichero que contiene el punto de entrada a la aplicación, o main, no puede usar rutas relativas.
¿Por qué sucede esto?
Porque el uso de import relativos solo está permitido para paquetes y han de ser llamados desde paquetes externos, no pueden ser lanzados desde el propio paquete (por defecto).
Es decir, que si tenemos la estructura de directorios usada más arriba, el siguiente código no funcionará:
1 | lp_006_p1_fn()
|
Y nos devolverá el error:
File "lp-006-p2.py", line 24, in <module>
from .lp_006_p1.good import *
SystemError: Parent module '' not loaded, cannot perform relative import
A modo de resumen: No funciona porque se está usando una aplicación como un paquete.
Solución¶
Por su puesto existen soluciones para ambos casos. A modo de conceptual son los siguientes:
Usar import relativos, en lugar de absolutos, cuando queramos importar paquetes locales y que éstos no se confundan con los globales u otros que podamos tener instalados.
Hay ocasiones en los que querremos usar una aplicación como un paquete como, por ejemplo:
Cuando queramos crear un paquete que, además, pueda ser usado como aplicación.
La solución: forzar la aplicación a que se comporte como un paquete.
Cómo¶
La solución es la impotanción relativa o, lo que es lo mismo, indicar a Python que use el paquete local en lugar del global del sistema:
1 2 3 4 5 6 | from .random import *
# ----------------------------------------------------------------------
def lp_006_p1_fn():
print(HELLO_VAR)
|
hello!
Transformar una aplicación en un paquete no es nada intuitivo ni trivial. Tendremos que tener controlar:
- Detectar si la aplicación es parte de un paquete o no.
- Mover los import de la parte global al ámbido de la función que haga de punto de entrada. Es ha de ser así para que, cuando el intérprete de Python importe todo el código no ejecute ni cargue ninguna librería hasta que no hayamos transformado la aplicación en paquete.
El siguiente código solucionará nuestro problema:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # Detect if applitaion is calling out of the box or inside a package.
if __name__ == "__main__" and __package__ is None:
import sys
import os
# Load path of main program file (this file) as a part of path of python interpreter
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(1, parent_dir)
# Load parent package as a common package
import lp_006_p1
# Set environment package to this package
__package__ = str("lp_006_p1")
del sys, os
# Continue normally
from .lp_006_p1.good import *
lp_006_p1_fn()
|
Explicación line a linea:
- 1: Detectamos si el fichero es el punto de entrada a la aplicación (main) y si es un paquete.
- 7-8: Cargamos el directorio desde el que es llamado el programa en la lista de paquetes disponibles de Python.
- 11: Una vez añadido en el entorno de Python el directorio donde se encuentra nuestro fichero, cargamos el paquete padre, el que contiene el ejecutable, o .py.
- 14: Establecemos la variable de entorno __package__, indicándole a Python que si que existe un paquete.
- 18: Cargamos nuestra librerías con import relativos. Ahora ya no tendremos problemas.
- 20: Continuamos la ejecución de nuestro código, como lo haríamos normalmente.
Entrada / Salida¶
En este bloque se tratan los casos de estudio relacionados con la entrada / salida de información
- Generar de informes: Word, Excel...
- Exportación / importación de información
- Transformación de datos
- Exportar información de forma portable: XML, JSON, YAML...
IO-001 - Envío de información por múltiples canales¶
Problema¶
Mi aplicación muestra información por pantalla pero he de cambiar mucho código cuando, posteriormente, quiero añadir nuevas funciones en esos mismos puntos nuevas acciones como:
- Añadir niveles de logging.
- Volcar esa misma información a un fichero.
- Enviar esa información a más de una ubicación.
Este es el código normal, con un print(...):
1 2 3 4 5 6 7 8 9 | # ----------------------------------------------------------------------
def hello():
"""Display a hello world text"""
print("hello")
# ----------------------------------------------------------------------
if __name__ == '__main__':
hello()
|
Vemos realmente el problema cuando queremos añadir más localizaciones donde enviar el texto:
1 2 3 4 5 6 7 8 9 10 11 12 | # ----------------------------------------------------------------------
def hello():
"""Display a hello world text"""
print("hello")
with open("my_file.txt", "") as f:
f.write("hello")
# ----------------------------------------------------------------------
if __name__ == '__main__':
hello()
|
Y todavía se complica más si queremos añadir niveles de verbosidad:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # ----------------------------------------------------------------------
def hello(verbosity):
"""Display a hello world text"""
print("hello")
if verbosity > 0:
with open("my_file.txt", "") as f:
f.write("hello")
if verbosity > 0:
print("verbosity 1")
# ----------------------------------------------------------------------
if __name__ == '__main__':
hello(1)
|
Solución¶
Usar un objeto estático y global que será el encargado de mostrar la información y que podremos configurar en función de lo que necesitemos.
Cómo¶
Para ello tenemos que declarar una clase estática global, que siga el patrón Singleton en Python, configurarla y llamarla de forma adecuada:
Nota
El patrón Singleton nos asegura que solo haya corriendo una instancia de un determinad objeto a la vez.
Si se crea otra nueva instancia, internamente no creará un nuevo objeto, sino que “rescatará” de la memoria el primer objeto que se creó y se reutilizará.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | # ----------------------------------------------------------------------
class Displayer:
instance = None
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = object.__new__(cls, *args, **kwargs)
cls.__initialized = False
return cls.instance
def config(self, **kwargs):
self.out_file = kwargs.get("out_file", None)
self.out_screen = kwargs.get("out_screen", True)
self.verbosity = kwargs.get("verbosity", 0)
if self.out_file:
self.out_file_handler = open(self.out_file, "w")
def display(self, message):
if self.verbosity > 0:
self.__display(message)
def display_verbosity(self, message):
if self.verbosity > 1:
self.__display(message)
def display_more_verbosity(self, message):
if self.verbosity > 2:
self.__display(message)
def __display(self, message):
if self.out_screen:
print(message)
if self.out_file_handler:
self.out_file_handler.write(message)
def __init__(self):
if not self.__initialized:
self.__initialized = True
self.out_file = None
self.out_file_handler = None
self.out_screen = True
self.verbosity = 0
# ----------------------------------------------------------------------
def hello():
"""Display a hello world text"""
# Use displayer
out = Displayer()
out.display("hello")
out.display_verbosity("hello")
# This will not be displayed by the verbosity level to 1
out.display_more_verbosity("hello")
# ----------------------------------------------------------------------
if __name__ == '__main__':
# Config displayer
d = Displayer()
d.config(out_screen=True,
out_file="~/my_log.txt",
verbosity=1)
# Call function
hello(1)
|
Comportamiento¶
En este bloque se tratan los casos de estudio relacionados con la integración con las formas de comportarse el entorno y sus frameworks asociados.
BH-001 - Procesamiento de tareas costosas¶
Problema¶
No es fácil implementar un sistema de gestión y procesamiento de tareas en asíncronas o en background.
La finalidad de este tipo de tareas es poder realizar acciones de manera desatendida y, una vez finalizadas, informar de los resultados obtenidos.
Ejemplo:
Tenemos un sitio web con un formulario de contacto. Este formulario envía un correo al administrador del sitio web.
Problema:
Si esperamos a que el mail sea enviado, estaremos haciendo esperar al usuario y puede que este tiempo sea muy largo (Sobre todo si el envío de mail falla y hay que tratar de reenviar).
Solución¶
Usar Celery. Celery es un gestor de tareas hecho en Python y que alcanza muy buenos niveles de rendimiento.
Celery está formado por 3 piezas principales:
- Un broker.
- El servicio de Celery.
- La aplicación que interactúa con Celery, que será nuestra aplicación.
Esta animación puede que te ayude a comprender mejor su funcionamiento:

Nota
Aspectos importantes a tener en cuenta:
- Posibles problemas de portabilidad: Necesita un broker .
- Posible solución para portabilidad (¡no para rendimiento!): Usar Django ORM + SQLite.
Cómo¶
Una forma de estructurar Celery es (¡puede haber más!):

Nota
Las carpetas deben de convertirse en paquetes, incluyendo un fichero __init__.py, en caso contrario no será reconocido y cargado por Celery.
Como se puede ver la imagen anterior, tenemos las siguientes carpetas clave:
- framework/
- framework/tasks/: Contiene nuestro código. Es el que contendrá los ficheros con los fuentes de nuestras tareas que queramos ejecutar.
- framework/celery/: Contiene el único fichero celery.py con la información sobre cómo ha de ejecutarse Celery.
Archivos importantes:
Este fichero contiene un ejemplo de código que se ejecutará como parte de una tarea. Dicha tarea se quedará a la espera que se le mande información para procesar.
En este ejemplo, cuando la tarea sea llamada, solamente mostrará un mensaje. Ésta es la forma más sencilla de crear tareas. Usando el decorador de Python @celery.task una función será convertida en una tarea.
Contiene la información necesaria para cargar y configurar Celery. A continuación se muestran la lineas más importantes de este fichero:
Contiene la llamada a la tarea tipo Celery.
Nota
En Celery, para llamar a una tarea de forma asíncrona, debemos de hacerlo como en el ejemplo. Aunque nuestro código sea una función, celery se encargará internamente de convertirla en un objeto con métodos.
Las llamadas (o métodos) para llamar a la tarea son, según el API oficial:
- my_task.delay(...)
- my_task.apply_async(...)
Configuración:
Por defecto, Celery solo importa las tareas que tienen como nombre de fichero tasks.py. Usando un un pequeño truco, podemos localizar todos los ficheros con tareas de Celery, independientemente del nombre de fichero:
Invocar tareas por su nombre:
Otra forma, algo más avanzada, de invocar una tarea de Celery (además de my_task.delay() y my_task.apply_async()) consiste en realizar una llamada usando el nombre relativo de la tarea, en lugar de usar código Python de programación.
Veámoslo con un ejemplo:
Supongamos que una tarea está situada en: framework.tasks.send_mails, con el nombre de send_mail(). Conforme lo hemos visto hasta ahora la invocación sería como sigue:
1 2 3 4 framework.tasks.send_mails import send_mail if __name__ == '__main__': send_mail.delay()Con este nuevo método quedría como sigue:
1 2 3 4 5 6 7 from framework.celery.celery import celery if __name__ == '__main__': celery.send_task("framework.tasks.send_mails.send_mail") # Without params celery.send_task("framework.tasks.send_mails.send_mail", ("From my@my.com")) # With params
Anexo BH-001: Arrancar un entorno Celery¶
Poner en funcionamiento un entorno que use Celery no es trivial. Han de arrancarse los servicios en el orden adecuado, esto es:
- Arrancar el broker
- Arrancar Celery
- Arrancar nuestra aplicación
A continuación se explican los pasos a seguir para arrancar un entorno que funcione con Celery:
Cada Broker tiene su propio método de arranque. En esta guía solo se cubrirá, de momento, RabbitMQ:
Para arrancar el servidor de RabbitMQ tenemos que escribir en una consola:
sudo rabbitmq-server &
RabbitMQ 3.1.5. Copyright (C) 2007-2013 GoPivotal, Inc. ## ## Licensed under the MPL. See http://www.rabbitmq.com/ ## ## ########## Logs: /opt/local/var/log/rabbitmq/rabbit@localhost.log ###### ## /opt/local/var/log/rabbitmq/rabbit@localhost-sasl.log ########## Starting broker... completed with 0 plugins.
Celery es un software que corre como servicio, actuando de orquestador entre nuestra aplicación y el sistema de mensajería. Debemos arrancar una instancia de Celery por cada aplicación que queramos correr.
La forma de arrancarlo está condicionada por la estructura de nuestro proyecto. La siguiente es una propuesta para de organización para nuestro código:
celery -A framework.celery.celery workerSi lo queremos con más información de depuración:
celery -A framework.celery.celery worker --loglevel=info
[2014-11-03 16:35:54,223: WARNING/MainProcess] /Users/XXX/.virtualenvs/omstd/lib/python3.4/site-packages/celery/apps/worker.py:161: CDeprecationWarning: Starting from version 3.2 Celery will refuse to accept pickle by default. The pickle serializer is a security concern as it may give attackers the ability to execute any command. It's important to secure your broker from unauthorized access when using pickle, so we think that enabling pickle should require a deliberate action and not be the default choice. If you depend on pickle then you should set a setting to disable this warning and to be sure that everything will continue working when you upgrade to Celery 3.2:: CELERY_ACCEPT_CONTENT = ['pickle', 'json', 'msgpack', 'yaml'] You must only enable the serializers that you will actually use. warnings.warn(CDeprecationWarning(W_PICKLE_DEPRECATED)) -------------- celery@localhost v3.1.16 (Cipater) ---- **** ----- --- * *** * -- Darwin-14.0.0-x86_64-i386-64bit -- * - **** --- - ** ---------- [config] - ** ---------- .> app: __main__:0x1085dba90 - ** ---------- .> transport: amqp://guest:**@localhost:5672// - ** ---------- .> results: disabled - *** --- * --- .> concurrency: 4 (prefork) -- ******* ---- --- ***** ----- [queues] -------------- .> celery exchange=celery(direct) key=celery [tasks] . framework.tasks.export_results_task.export_to_csv . framework.tasks.yara_task.yara_task [2014-11-03 16:35:54,258: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672// [2014-11-03 16:35:54,272: INFO/MainProcess] mingle: searching for neighbors [2014-11-03 16:35:55,297: INFO/MainProcess] mingle: all alone [2014-11-03 16:35:55,309: WARNING/MainProcess] celery@localhost ready.
Por último, lanzar nuestra aplicación será como ejecutar un script normal en Python:
python start.py
BH-002 - Tareas programadas¶
Problema¶
La ejecución de forma programática o que se ejecuten cada X tiempo tiene los siguientes problemas (entre otros):
- No es trivial de implementar.
- No suele ser portable entre plataformas.
- La implementación de métodos tradicionales abusa del uso de hilos o la suspensión del flujo de la ejecución de forma manual.
Solución¶
Celery Beat para ejecutar tareas programadas o temporizadas.
Cómo¶
Tan solo tenemos que cambiar la configuración de Celery y añadir la tarea que queremos ejecutar y la repetición del mismo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from .utils import find_tasks_in_project # Celery taskes package CELERY_MAIN_FOLDER = "framework" # -------------------------------------------------------------------------- # Celery common properties # -------------------------------------------------------------------------- CELERY_TASK_RESULT_EXPIRES = 3600 CELERY_IMPORTS = find_tasks_in_project("framework") BROKER_URL = 'amqp://guest:guest@localhost:5672/' # -------------------------------------------------------------------------- # Celery scheduled tasks # -------------------------------------------------------------------------- CELERY_TIMEZONE = 'UTC' # CELERYBEAT_SCHEDULE = {} CELERYBEAT_SCHEDULE = { 'add-every-30-seconds': { 'task': 'framework.tasks.main_task', 'schedule': timedelta(seconds=30), # 'args': (16, 16) }, }
Para llamar a Celery Beat, tan solo tendremos que ejecutar Celery como se describió en la sección anterior, pero añadiendo el parámetro -B:
celery -A framework.celery.celery worker -B --loglevel=info
Redistribución¶
En este bloque se tratan los casos de estudio relacionados con la redistribución del software y las diferentes formas de hacerlo:
- Sistemas de control de versiones y correcto uso.
- Creación de paquetes.
- Inclusión en repositorios públicos.
- Creación de binarios.
- Portabilidad entre sistemas.
RD-001 - Inclusión de dependencias externas¶
Problema¶
Mi proyecto tiene dependencias y no se cómo hacer que sean fácilmente instalables para que se puede redistribuirlos de forma sencilla.
Solución¶
Usar pip + el fichero requirements.txt.
- Pip es un gestor de paquetes Python, al más puro estilo “apt-get” de Debian.
- requirements.txt: Fichero donde se detallan todas las dependencias del proyecto.
Cómo¶
En este link se puede encontrar el ejemplo: Ejemplo requirements.txt
Para instalar todas las dependencias usando pip:
pip -r requirements.txt
RD-002 - Sandbox y entornos virtuales¶
Problema¶
Las dependencias entre proyectos y tener que instalarlo todo en el sistema lo “ensucia” y hace que todo sea un caos.
Solución¶
Usar virtualenv:
- Virtualenv: es una “sandbox” donde se instalarán todas las dependencias de tu software.
- Además, podemos usar virtualenvwrapper, para añadir más funcionalidad y utilidades a virtualenv.
Despliegue¶
En este bloque se tratan los casos de estudio relacionados con el despliegue y puesta en producción de una aplicación.
TODO
Hacking¶
Este bloque recopila una serie de proyectos de ejemplo, que aplican los conceptos de la guía.
HH-001: Sniffing de redn (TODO)¶
Problema¶
La interpretación de protocolos de red, análisis, almacenamiento de resultados y, posteriormente, llevar a cabo un datamining para obtener información relevante no es trivial.
Construir un proyecto escalable, bien diseñado y eficiente es la clave.
Solución¶
Sniffing de protocolos de red, usando las librerías adecuadas: - Rip - Web - Telnet - DHCP - ...
Realizar un sistema de tiempo real, pero desatendido.
Implementar una abstracción con los sistemas de almacenamiento, a fin de lograr escalabilidad y portabilidad.
Cómo¶
TODO
Cracking¶
Este bloque recopila una serie de proyectos de ejemplo, que aplican los conceptos de la guía.
CH-001: Cracking: GPUs + CPU (TODO)¶
Problema¶
Cracking en modo cluster y paralelizado.
Solución¶
Hacer un diccionarios y enviar bloques de procesamiento a nodos que prueben dichos bloques. Estos nodos pueden ser remotos o locales.
Usando este método podemos conseguir: GPUs + CPUs + PCs viejos + red -> Win!
Cómo¶
TODO
CH-002: SSH Bruteforcing (TODO)¶
Problema¶
Bruteforcing de protocolos autenticados: SSH, Telnet, HTTP Basic Auth...
Solución¶
Propuesta de pequeño PoC de bruteforcing, en Python 3, con conexiones no bloqueantes.
Explicación del funcionamiento de ktcal2 (bruteforcer SSH con estas características)
Cómo¶
TODO
Malware¶
MH-EX-001: Análisis básico de malware con Yara¶
En este ejemplo práctico crearemos una pequeña herramienta que nos permita analizar, de forma muy básica, malware usando Yara.
Yara es una herramienta que tiene como objetivo ayudar a los investigadores de malware a clasificar y detectar malware.
El uso de Yara puede ser tan sencillo o complejo como queramos. La herramienta propuesta tiene como objetivo ser un PoC simple, funcional y práctico. Siéntase libre de copiar el código y ampliarlo como necesite.
Problema¶
Analizar muestras de malware en batch puede puede no resultar sencillo, sobre todo si las muestras son de gran tamaño o tenemos que aplicar muchas reglas.
La principal dificultad suele ser el correcto diseño, implementación y documentación de la aplicación.
Solución¶
La solución propuesta implementa un sistema de procesamiento paralelo, distribuido y automatizado de análisis de muestras, en el que se pueden añadir nuevas muestras en cualquier momento, quedando en cola de espera para ser atendidas.
La aplicación generará un resultado en formato CSV compatible con Excel.
Las soluciones aquí propuestas siguen los casos de estudio propuestos en el bloque de desarrollo.
Cómo¶
Funcionamiento¶
Nuestra aplicación se ejecuta a través de la linea de comandos. Para ver todas las opciones disponibles tan solo tenemos que escribir:
python start.py --helpY como resultado:
usage: start.py [-h] -p SAMPLE_PATH [-v VERBOSITY] [-o OUTPUT_FILE] [--rules-path RULES_PATH] OMSTD Malware optional arguments: -h, --help show this help message and exit -p SAMPLE_PATH, --sample-path SAMPLE_PATH binary sample path -v VERBOSITY enable verbose mode -o OUTPUT_FILE output file name --rules-path RULES_PATH yara rules path (default .rules/)
El funcionamiento es muy simple. El analizador está hecho usando Celery, por lo que hay que seguir los mismos pasos explicados en el anexo del caso de estudio BH-001 para ejecutar la aplicación: Pasos para lanzar Celery.
Una vez lanzado el servicio de Celery, así como sus dependencias, nuestra aplicación ya está a la espera de recibir muestras para ser analizadas. Para esto ejecutaremos:
python start.py -p samples/sample.txt -o resultsAquí podemos ver la salida generada: Un CSV con la información:
HelloWorld,True,Data: 'Hello world' (flags: 19 # offset: 0),1,Rule HelloWorld was found a match in binary 'sample.txt' with tags: No tags HelloWorld,True,Data: 'Hello world' (flags: 19 # offset: 0),0,Rule HelloWorld was found a match in binary 'sample.txt' with tags: No tags Another,False,,1,Rule Another was NOT found a match in binary 'sample.txt' with tags: No tags SeeYou,True,Data: 'See you' (flags: 19 # offset: 12),1,Rule SeeYou was found a match in binary 'sample.txt' with tags: No tags Another,False,,0,Rule Another was NOT found a match in binary 'sample.txt' with tags: No tags
Desglose de las piezas¶
Las piezas que compondrán la herramienta son las siguientes:
- Servicio que recibe nuevas muestras.
- Generador de resultados.
- Medio para añadir nuevas muestras
Todo el código fuente lo puedes descargar y probar aquí.
La estructura y organización del proyecto sigue las directrices de ST-001.
El servicio de recepción de muestras es un tarea de Celery, a la espera de recibir nueva información para analizar de forma asíncrona y en background.
El motor de análisis es muy sencillo. El análisis se hace en dos partes:
- El análisis con Yara.
- La interpretación y transformación de resultados.
Ambas partes están definidas como dos funciones en el fichero yara_task.py.
Analizador con Yara
La función yara_task, convertida en tarea con el decorador @celery.task, es la encargada de hacer la llamada a Yara y:
- Cargar las reglas Yara, con el método yara.compile(), indicándole el listado de ficheros “*.yara” con las reglas.
- Usando partials (LP-003) construimos la llamada al callback. Dicho callback será llamado por la librería de Yara, cada vez que ésta ejecute con éxito una regla.
- Finalmente se lanza la orden para hacer el “match”, rules.match(...), indicándole el partial anteriormente construido.
En el siguiente código se puede ver estos puntos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @celery.task def yara_task(input_parameters): """ Celery task that process a binary sample. :param input_parameters: Parameters object with global input parameters :type input_parameters: Parameters """ # Load Yara rules and match with binary rules = yara.compile(filepaths=input_parameters.yara_rules_files) # Make custom callback function callback_function = partial(yara_callback, input_parameters) # Run Yara rules! rules.match(input_parameters.sample_path, callback=callback_function)Analizador de resultados
La función yara_callback actúa como callback que Yara llamará cuando termine de procesar cada una de las reglas.
En ella, y tras comprobarse la validez de los parámetros de entrada, se llevan a cabo las siguientes acciones: #. Transformar los datos de entrada del formato Yara al formato interno, del tipo Results, según SP-003 #. Enviar la información, de forma asíncrona, a la tarea que se encarga de almacenar los resultados: celery.send_task("framework.tasks.export_results_task.export_to_csv", ...).
En el siguiente código se puede ver estos puntos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 # ---------------------------------------------------------------------- def yara_callback(input_parameters, yara_results): """ Call back for Yara match. Retreive Yara yara_results, as dictionary, and call Celery task that stores yara_results. :param input_parameters: Parameters object with global input parameters :type input_parameters: Parameters :param yara_results: Raw Yara match yara_results in format: {'matches': True, 'rule': 'HelloWorld', 'namespace': '0', 'tags': [], 'meta': {}, 'strings': [{'identifier': '$a', 'flags': 19, 'data': 'Hello world', 'offset': 0}]} :type yara_results: dict """ # Inputs validations if not isinstance(yara_results, dict): raise TypeError("Expected dict, got '%s' instead" % type(yara_results)) if yara_results is None: print("Yara rules returned not yara_results") return # Yara input format: # # {'matches': True, # 'meta': {}, # 'namespace': '0', # 'rule': 'HelloWorld', # 'strings': [{'data': 'Hello world', # 'flags': 19, # 'identifier': '$a', # 'offset': 0}], # 'tags': []} # r_rule = yara_results['rule'] r_matches = yara_results['matches'] r_tags = ",".join(yara_results['tags']) if yara_results['tags'] else "No tags" r_namespace = int(yara_results['namespace']) # Fixing payload r_payload = "#".join(["Data: '%s' (flags: %s # offset: %s)" % (x['data'], x['flags'], x['offset']) for x in yara_results['strings']]) # Make Results structure r = Results(rule=r_rule, matches=r_matches, payload=r_payload, namespace=r_namespace, description="Rule %s was %sfound a match in " "binary '%s' with tags: %s" % (r_rule, "" if r_matches else "NOT ", input_parameters.sample_file_name, r_tags)) # Send info to exporter celery.send_task("framework.tasks.export_results_task.export_to_csv", (input_parameters, r))
Los resultados serán generados de forma asíncrona, al igual que la recepción de éstos. Para ello, se ha creado una tarea de Celery, a la espera de recibir nueva información, con la que generar los resultados deseados.
La herramienta genera los resultados a través de la función yara_callback(...). Ésta, una vez hecha la transformación, hace una llamada la a la tarea generadora de resultados, llamada: export_to_csv(...).
Hay varias formas de llamar a una tarea de Celery, como se puede estudiar en BH-001. En este caso nos hemos decantado por la opción my_task.send_task("..."), como se ha visto en la :ref:` sección anterior <mh-001-results-analysis-section>`.
La función, convertida en tarea, export_to_csv(...) hace 3 cosas muy sencillas:
- Una comprobación mínima de los parámetros de entrada.
- Abre un fichero, siguiendo las recomendaciones de LP-005, en modo “append” o para añadir información al final de éste.
- Escribe una nueva linea en el fichero en formato csv
Tal y como podemos ver en la lineas señaladas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @celery.task def export_to_csv(input_params, results): """ Export results to CSV file. :param input_params: Global Parameters instance with input configuration :type input_params: Parameters :param results: Results object with analysis results. :type results: Results """ if not isinstance(input_params, Parameters): raise TypeError("Expected Parameters, got '%s' instead" % type(input_params)) with open("%s.csv" % input_params.output_file, "a") as f: csv_writer = csv.writer(f) # Title # csv_writer.writerow(["# Rule", "matches", "payload", "description"]) csv_writer.writerow([ results.rule, results.matches, results.payload, results.namespace, results.description ])
Añadir nuevas muestras, es equivalente a decir: enviar nuevas muestras a la cola de análisis, para que sean procesadas cuando toque su turno.
Para este PoC se ha optado por algo sencillo: un linea de comandos (en secciones anteriores se muestra cómo ejecutarse).
Hemos tenido en cuenta las siguientes medidas o recomendaciones:
- Hemos usado la librería standard argparser, siguiendo la sintaxis de la documentación oficial, y teniendo en cuenta las buenas prácticas especificadas en IT-001.
- Además, hemos prevenido la ejecución accidental o a destiempo aplicando LP-004.
- Hemos centralizado la ejecución en un punto, usando ST-004, dejando preparado el proyecto para otras interfaces de usuario.
- Hemos usado un punto central para almacenar los parámetros de ejecución del usuario, como se recomienda en ST-002.
Tal y como podemos ver en la lineas señaladas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import argparse from api import Parameters, run_all # ---------------------------------------------------------------------- if __name__ == '__main__': parser = argparse.ArgumentParser(description='OMSTD Malware') parser.add_argument("-p", "--sample-path", type=str, dest="sample_path", help="binary sample path", required=True) parser.add_argument("-v", dest="verbosity", type=int, help="enable verbose mode", default=False) parser.add_argument("-o", dest="output_file", type=str, help="output file name", default=None) parser.add_argument("--rules-path", dest="rules_path", type=str, help="yara rules path (default .rules/)", default=None) params = parser.parse_args() input_parameters = Parameters(sample_path=params.sample_path, output_file=params.output_file, verbosity=params.verbosity, rules_path=params.rules_path) run_all(input_parameters)La ejecución tiene lugar a través de la función run_all(...), incluido en el fichero api.py. Si observamos el código, podemos comprobar que lo que hace dicha función es una llamada a la tarea de análisis, con sintaxis Celery:
1 2 3 4 5 6 7 8 9 10 from lib.data import Parameters, Results from framework.celery.celery import celery from framework.tasks.yara_task import yara_task # ---------------------------------------------------------------------- def run_all(input_parameters): # Display results yara_task.delay(input_parameters)
Anexo¶
Se puede encontrar más información, así como reglas Yara predefinidas en las siguientes URL:
- https://github.com/arbor/yara
- https://github.com/1aN0rmus/Yara
- https://github.com/0pc0deFR/YaraRules
- https://github.com/kevthehermit/yaraMail
- https://github.com/3vangel1st/Yara
- https://github.com/frankenwino/yara-rules/
- https://github.com/nxdamian/YARA-Public
- https://github.com/jackcr/yara-memory
- https://github.com/0pc0deFR/YaraRules
- https://github.com/sysforensics/YaraRules
- https://github.com/Neo23x0/Yara-BRG
Otros enlaces interesantes:
Forense¶
Este bloque recopila una serie de proyectos de ejemplo, que aplican los conceptos de la guía.
Hardening¶
Este bloque recopila una serie de proyectos de ejemplo, que aplican los conceptos de la guía.