Mi proceso para descubrir cómo usar Dropbox pythonNN.dll para ejecutar scripts Python sin Python


Introduction

Estaba jugando a Warzone y empecé a notar algunas pérdidas de paquetes, lo que no es "nada bueno" si estás jugando a un juego FPS. Entré en el administrador de tareas y busqué los procesos que estaban usando la red y empecé a matarlo todo, como Rambo. En Dropbox.exe hice click en "Open File Location" y una vez abierta la carpeta, noto algunos archivos relacionados con Python.

image

Cuando terminé de jugar, volví a la carpeta y vi un archivo que me llamó la atención: python38.dll

image

Rápidamente entré en Twitter y busqué "python.dll @byt3bl33d3r", cada vez que pienso en Python para uso ofensivo, su nombre (Marcello) me viene a la cabeza. Puedes leer más sobre él en su Twitter o GitHub. La búsqueda sólo muestra un resultado:

image Twitter image link

Así que pensé, ¡esto debería funcionar! Puedo ser capaz de utilizar python38.dll para ejecutar un script de Python sin Python en el sistema.

Empecé a buscar "cómo ejecutar Python con python.dll" y algunos enlaces de docs.python.org aparecieron en los resultados de mi búsqueda:
Python on Windows FAQ — Python 3.9.5 documentation
Embedding Python in Another Application — Python 3.9.5 documentation

La documentación dice: "La vinculación en tiempo de ejecución simplifica enormemente las opciones de vinculación; todo sucede en tiempo de ejecución. Tu código debe cargar pythonNN.dll usando la rutina de Windows LoadLibraryEx(). El código también debe usar rutinas de acceso y datos en pythonNN.dll (es decir, API's de Python en C) usando punteros obtenidos por la rutina de Windows GetProcAddress(). Las macros pueden hacer que el uso de estos punteros sea transparente para cualquier código C que llame a rutinas de la API C de Python"

Ok, puedo cargar Dropbox pythonNN.dll con LoadLibrary() en mi propio proceso y usar GetProcAddress() para acceder a sus métodos, interesante. Ahora, ¿cómo puedo ejecutar código usando python? Aquí es donde entra en juego el 2º enlace Embedding Python in Another Application

Con el ejemplo proporcionado 1.1. Incrustación de muy alto nivel sé que puedo utilizar la función PyRun_SimpleString() para ejecutar comandos de Python como:

PyRun_SimpleString("print('Hello from python')").

Construyendo el programa

Con la información que obtuve estaba listo para construir un programa en C# que:

  1. LoadLibrary() – to import pythonNN.dll from Dropbox into my program.
  2. GetProcAddress() – to retreive PyRun_SimpleString(), Py_Initialize() and Py_Finalize() functions address.
  3. Call those function using DInvoke.

Vamos a explicar cómo funciona todo:

1 - LoadLibrary() carga el módulo especificado (en nuestro caso Dropbox pythonNN.dll), en el espacio de direcciones del proceso llamante.

Para utilizar esta API dentro de C# necesitamos (1) importar la DLL que tiene la función (kernel32.dll) y (2) llamar a la función API. Puedes utilizar pinvoke.net website para ver ejemplos de cómo llamar a la API desde C#.

// (1) - Importar kernel32.dll y declarar la llamada a la API LoadLibrary
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string name);

// (2) – Llamar a la función API
var pyDll = LoadLibrary(PathToPythondll);

2 - GetProcAddress() - Recupera la dirección de una función o variable exportada de la biblioteca de vínculos dinámicos (DLL) especificada.

Para utilizar esta API dentro de C# necesitamos (1) importar la DLL que tiene la función (kernel32.dll) y (2) llamar a la función API guardando la ubicación de la dirección de la función en una variable.

// (1) - Importar kernel32.dll y declarar la llamada a la API LoadLibrary
[DllImport("kernel32.dll")]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

// (2) - Llamar a la función API
var pyrunAddr = GetProcAddress(pyDll, "PyRun_SimpleString");
var pyInitAddr = GetProcAddress(pyDll, "Py_Initialize");
var pyFinAddr = GetProcAddress(pyDll, "Py_FinalizeEx");

¿Qué hacen estas funciones?

Py_Initialize() - En una aplicación que incluya Python, la función Py_Initialize() debe ser llamada antes de usar cualquier otra función de la API Python/C; con la excepción de unas pocas funciones y las variables de configuración global.

PyRun_SimpleString() - Ejecuta el código fuente de Python desde el comando en el módulo main según el argumento flags. Si main no existe, se crea. Devuelve 0 en caso de éxito o -1 si se produjo una excepción. Si hubo un error, no hay forma de obtener la información de la excepción.

Py_FinalizeEx() - Deshace todas las inicializaciones hechas por Py_Initialize() y el uso posterior de las funciones de la API de Python/C, y destruye todos los sub-intérpretes que fueron creados y aún no destruidos desde la última llamada a Py_Initialize(). Idealmente, esto libera toda la memoria asignada por el intérprete de Python. Normalmente el valor devuelto es 0. Si hubo errores durante la finalización (vaciando los datos almacenados en el buffer), se devuelve -1.

Básicamente necesitamos llamar a la función Py_Initialize() antes de usar PyRun_SimpleString() y para finalizar nuestro script Python "correctamente" deberíamos usar Py_FinalizeEx().

3 - DInvoke - Ahora que tenemos la dirección de esas funciones, necesitamos una forma de invocarlas. Con DInvoke podemos invocar dinámicamente API no administradas sin PInvoke (el método que usamos para LoadLibrary() y GetProcAddress()). El caso de uso de DInvoke es que la ubicación de la DLL de Python puede cambiar en función de la versión de Dropbox.

Para usar DInvoke necesitamos (1) definir la función que queremos usar, (2) obtener la dirección de memoria de esas funciones (lo hicimos con GetProcAddress()), y (3) inicializar/invocar esas funciones.

// (1) - Define the functions
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int PyRun_SimpleString(string command);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void Py_Initialize();

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void Py_Finalize();

// (2) – Get the memory address of the functions
var pyrunAddr = GetProcAddress(pyDll, "PyRun_SimpleString");
var pyInitAddr = GetProcAddress(pyDll, "Py_Initialize");
var pyFinAddr = GetProcAddress(pyDll, "Py_FinalizeEx");

// (3) Initialize & Invoke Functions
Py_Initialize Py_Initialize = (Py_Initialize)Marshal.GetDelegateForFunctionPointer(pyInitAddr, typeof(Py_Initialize));
Py_Initialize(); 

PyRun_SimpleString PyRun_SimpleString = (PyRun_SimpleString)Marshal.GetDelegateForFunctionPointer(pyrunAddr, typeof(PyRun_SimpleString));
string pythonCode = @"print('Hello from python')";
int result = PyRun_SimpleString(pythonCode);

Py_Finalize Py_Finalize = (Py_Finalize)Marshal.GetDelegateForFunctionPointer(pyFinAddr, typeof(Py_Finalize));
Py_Finalize();

El código completo es el siguiente:

using System;
using System.Runtime.InteropServices;

namespace BYOPython
{
    class Program
    {

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate int PyRun_SimpleString(string command);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate void Py_Initialize();

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate void Py_FinalizeEx();

        static void Main(string[] args)
        {
            var pythonPath = @"C:\Program Files (x86)\Dropbox\Client\123.4.4832";

            string pythondll = $@"{pythonPath}\python38.dll";

            Console.WriteLine($"[+] Loading pythonNN.dll from Dropbox directory");
            var pyDll = LoadLibrary(pythondll);

            Console.WriteLine($"[+] Getting Functions Addresses.");
            var pyrunAddr = GetProcAddress(pyDll, "PyRun_SimpleString");
            var pyInitAddr = GetProcAddress(pyDll, "Py_Initialize");
            var pyFinAddr = GetProcAddress(pyDll, "Py_FinalizeEx");

            Py_Initialize Py_Initialize = (Py_Initialize)Marshal.GetDelegateForFunctionPointer(pyInitAddr, typeof(Py_Initialize));

            Console.WriteLine($"[+] Initializing Python.");
            Py_Initialize();

            Console.WriteLine($"[+] Setting Python Payload.");
            PyRun_SimpleString PyRun_SimpleString = (PyRun_SimpleString)Marshal.GetDelegateForFunctionPointer(pyrunAddr, typeof(PyRun_SimpleString));

            string pythonCode = @"print('Hello from python')";

            Console.WriteLine($"[+] Executing Python Payload.");
            int result = PyRun_SimpleString(pythonCode);

            Py_FinalizeEx Py_FinalizeEx = (Py_FinalizeEx)Marshal.GetDelegateForFunctionPointer(pyFinAddr, typeof(Py_FinalizeEx));

            Console.WriteLine($"[+] Finalizing Python.");
            Py_FinalizeEx();
        }

        [DllImport("kernel32.dll")]
        static extern IntPtr LoadLibrary(string name);

        [DllImport("kernel32.dll")]
        static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
    }
}

He compilado el código usando x86, igual que Dropbox python38.dll, y lo ejecuto, pero cuando llama a Py_Initialize() se bloquea, con el error "ModuleNotFoundError: No module named 'encoding'.

image

Ok ok, cuando yo' utilizo python, Normalmente resuelvo este problema conpip install ModuleName, pero ahora ' ¿que se supone que debemos hacer esto aquí?

Entré en el sitio web de Python y descargar el original Python Windows embeddable package para ver si mi código funciona y si el problema' está relacionado con la DLL de Dropbox. Después de descomprimir Python Windows embeddable package on C:\Python38, I changed the DLL PATH to C:\Python38\python38.dll and ran my code:

image

¡¡¡Si!!! Funcionó, pero ¿por qué no funciona con Dropbox python38.dll? He abiertoProcess Monitor y establecer un filtro para mi aplicación: CallingPython.exe

image

Mientras se utiliza el python38.dll, Process Monitor mostrar que accede C:\Python38\python38.zip, parece que asume que existe un archivo .zip con la misma extensión que la DLL.

image

Me preguntaba, ¿para qué sirve este archivo .zip? Buscando por ahí he encontrado esta información en Python documentation website: "Actualmente, sys.path es una lista de nombres de directorio como cadenas. Si se implementa este PEP, un elemento de sys.path puede ser una cadena que nombra un archivo zip. El archivo zip puede contener una estructura de subdirectorios para soportar importaciones de paquetes. El archivo zip satisface las importaciones exactamente como lo haría un subdirectorio"

Así, para nuestro propósito Python utiliza este zip para cargar módulos, incluyendo el módulo "encodings", el que antes nos daba error. En la siguiente imagen se puede ver parcialmente el contenido de python38.zip.

image

Python38.zip no existe en el directorio de Dropbox, pero hay un python-packages.zip que es el mismo, con un nombre diferente, como puedes ver en la siguiente imagen.

image

Para utilizar ese paquete con nuestro Dropbox python38.dll, establezco las variables de entorno del programa'igual que la ruta de python38.dll e incluyo python-packages.zip como PATH también.

Environment.SetEnviromentVariable("PYTHONHOME", pythonPath);
Environment.SetEnviromentVariable("PYTHONPATH", $@"{pythonPath}\python-packages.zip");

Después de cruzar los dedos y rezar a Dios, ¡¡¡funcionó!!! Pero mientras gritaba ¡¡¡Aleluya!!! Me dio otro error ModuleNotFoundError: No module named "site"!! Oh my God!!!!! ☹

image

Aquí es donde StackOverflow y CristiFativiene al rescate. CristiFati indica que el Py_Initialize() comprueba si Py_NoSiteFlag es 0 y, a continuación, carga el módulo site, por lo que para evitarlo menciona que se puede establecer Py_NoSiteFlag a 1 y el módulo del sitio no se cargará. Más información here.

Para cambiar Py_NoSiteFlag en la memoria lo que hice fue utilizar GetProcAddress() para recuperar la posición de memoria y Marshal.Copy para ponerlo a 1.

var pyNoSiteFlagAddr = GetProcAddress(pyDll, "Py_NoSiteFlag");

int[] variable = new int[1];
Marshal.Copy(pyNoSiteFlagAddr, variable, 0, 1); // copying Py_NoSiteFlag value to the variable.

variable[0] = 1; // 0 for False, 1 for True
Marshal.Copy(variable, 0, pyNoSiteFlagAddr, 1); // setting Py_NoSiteFlag to 1

Añadí este trozo de código, lo compilé, y ¡¡¡wowowo!!! Pude ejecutar Python usando los archivos de instalación de Dropbox 😊

image

Lo sé, lo sé, quieres ver una concha aparecer y. ¡Ahí lo tienes!

{% include video id="lZAZBkmIpY4" provider="youtube" %}

Trae tu propia Python.

Después de jugar un poco con este código, terminé dándome cuenta de que puedo usar cualquier programa que haga lo mismo que Dropbox, puedes ir a tu Program Files/Program Files (x86) directorios y buscar por "python*.dll" Encontré otro software on my computer que también trae python38.dll 😊

image

Pero, ¿y si no tengo ninguna aplicación que traiga su pythonNN.dll? Pues puedes Bring Your Own Python y ejecuta tus scripts 😊

En el siguiente ejemplo, estoy descargando Python 3.8 de python.org y ejecutando mi código Python 😊

{% include video id="rHkBq-ru-yw" provider="youtube" %}

En miGitHub vas a encontrar el código para abusar de Dropbox pythonNN.dll y una referencia sobre cómoBring Your Own Python, ten en cuenta que puede que tengas que cambiar la ruta de tu instalación de Dropbox para que funcione.

BYOPython Github repository

Si quieres ver un vídeo en directo en el que explico cómo lo descubro y lo reproduzco, echa un vistazo a HackTheBox - RedTeamRD Meetup - Spectra & Abuso de Dropbox python.dll (Spanish).

Note: Reporté esto a Dropbox pero estaba fuera del alcance: "Ataques que requieren acceso físico al dispositivo de un usuario'

.

Gracias especialmente a tekwizz123 por ayudarme revisando la entrada del blog.

God bless you!

Servir a Cristo no es una tarea, sino una relación. Amigos de Dios Jn 15: 15

Habla Con Nostros