Continuamos con esta tercera entrega...

Otra técnica común para realizar la inyección de DLL es utilizar la función [Enlace externo eliminado para invitados]. Como sugiere su nombre, esta función crea un hilo que comienza su ejecución en el espacio de direcciones de otro proceso. La función CreateRemoteThreadEx tiene el siguiente prototipo:

Código: Seleccionar todo

HANDLE WINAPI CreateRemoteThreadEx(HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
LPDWORD lpThreadId);
Esta función parece desalentadora al principio, pero la mayoría de los parámetros son opcionales. A los efectos de la inyección de DLL, donde solo le importa crear e iniciar el subproceso (y no preocuparse por cosas como las diversas personalizaciones del subproceso), estos parámetros opcionales no se utilizarán.

Los únicos parámetros que son importantes aquí son, el identificador del proceso (hProcess), la dirección de inicio del hilo (lpStartAddress).lpParameter) y el parámetro para la dirección inicial (

Usar CreateRemoteThreadEx para realizar la inyección de DLL se vuelve intuitivo una vez que ve que puede iniciar este hilo en cualquier dirección y con un parámetro de su elección. Dado el control sobre estos dos parámetros, ¿qué pasaría si configura la dirección de inicio del subproceso en la dirección de la función LoadLibraryA y pasa un puntero al ¿Ruta de la biblioteca como parámetro? Su subproceso remoto estaría realizando una llamada LoadLibraryA, que cargaría su DLL, en el espacio de direcciones de su proceso de destino.

Obtener el identificador del proceso objetivo

Habiendo entendido cómo funcionará la inyección de DLL, es hora de comenzar con la implementación. Se necesitan tres cosas: el identificador del proceso de destino, la dirección de la función LoadLibraryA y un puntero en el proceso de destino a una cadena con el Ruta DLL. La obtención del identificador del proceso se realiza con la llamada OpenProcess.

Código: Seleccionar todo

HANDLE GetTargetProcessHandle(const DWORD processId) {

const auto processHandle{ OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE,
false, processId) };
if (processHandle == nullptr) {
PrintErrorAndExit("OpenProcess");
}
return processHandle;
}
Abrir un identificador de proceso para el proceso de destino.

El identificador necesitará PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, y PROCESS_VM_WRITE derechos de acceso. El primer permiso es necesario para CreateRemoteThreadEx para crear el subproceso remoto. Los dos últimos permisos son necesarios para escribir la ruta a la DLL en el espacio de direcciones del proceso de destino.

Obteniendo la dirección de LoadLibraryA
Habiendo obtenido un identificador del proceso, el siguiente paso es encontrar la dirección de la función LoadLibraryA. Esta función se encuentra en kernel32.dll, que es una DLL del sistema que en Windows tiene la misma dirección de carga en todos los procesos. Esto significa que puede obtener la dirección de LoadLibraryA en su proceso y confiar en que será la misma dirección en el proceso de destino. La implementación para esto se muestra a continuación:

Código: Seleccionar todo

void* GetLoadLibraryAddress() {
return GetProcAddress(GetModuleHandleA(
"kernel32.dll"), "LoadLibraryA");
}
Obtención de la dirección de la función LoadLibraryA.

Es suficiente usar la dirección devuelta por esta función como dirección de inicio del hilo en CreateRemoteThreadEx. Sin embargo, también me gustaría presentar una implementación que es un poco más genérica y funcionará para recuperar la dirección de una función en cualquier DLL sin necesidad de cargar una DLL en la misma dirección en cada proceso.

Código: Seleccionar todo

void* GetRemoteModuleFunctionAddress(const std::string moduleName,
    const std::string functionName, const DWORD processId) {

    void* localModuleBaseAddress{ GetModuleHandleA(moduleName.c_str()) };
    if (localModuleBaseAddress == nullptr) {
        localModuleBaseAddress = LoadLibraryA(moduleName.c_str());
        if (localModuleBaseAddress == nullptr) {
            PrintErrorAndExit("LoadLibraryA");
        }
    }

    const void* const localFunctionAddress{
        GetProcAddress(static_cast<HMODULE>(localModuleBaseAddress),
            functionName.c_str()) };

    if (localFunctionAddress == nullptr) {
        PrintErrorAndExit("GetProcAddress");
    }

    const auto functionOffset{ PointerToRva(
        localFunctionAddress, localModuleBaseAddress) };

    const auto snapshotHandle{ CreateToolhelp32Snapshot(
        TH32CS_SNAPMODULE, processId) };
    if (snapshotHandle == INVALID_HANDLE_VALUE) {
        PrintErrorAndExit("CreateToolhelp32Snapshot");
    }

    MODULEENTRY32 module {
        .dwSize = sizeof(MODULEENTRY32)
    };

    if (!Module32First(snapshotHandle, &module)) {
        PrintErrorAndExit("Module32First");
    }

    do {
        auto currentModuleName{ std::string{module.szModule} };

        std::transform(currentModuleName.begin(), currentModuleName.end(),
            currentModuleName.begin(),
            (unsigned char letter) { return std::tolower(letter); });
        if (currentModuleName == moduleName) {
            return reinterpret_cast<void*>(module.modBaseAddr + functionOffset);
        }

     } while (Module32Next(snapshotHandle, &module));

     return nullptr;
 }
Una implementación que puede obtener la dirección de una función exportada desde cualquier DLL.

Esta función funciona obteniendo primero la dirección base de la DLL en su proceso; ya sea obteniendolo con [Enlace externo eliminado para invitados] si la DLL ya está cargada, o cargando explícitamente la DLL con LoadLibraryAUna vez conocido el desplazamiento, el siguiente paso es encontrar la dirección base de la DLL en el proceso de destino. Esto se logra utilizando [Enlace externo eliminado para invitados] para crear una instantánea de los módulos cargados de los procesos de destino. Los módulos cargados se pueden iterar con las funciones [Enlace externo eliminado para invitados] y [Enlace externo eliminado para invitados]. . Estas llamadas completan una estructura [Enlace externo eliminado para invitados] con información sobre el módulo actual, incluido un modBaseAddr campo que indica la dirección base de la DLL en el proceso de destino. Mientras itera, si el módulo actual coincide con el módulo que está buscando, devuelva la dirección base del módulo más el desplazamiento de la función de destino. Esta será la dirección absoluta en el espacio de direcciones virtuales de los procesos de destino de su función de destino.

Escribir la ruta de la DLL inyectadaCon un identificador de proceso, y ahora la dirección de la función LoadLibraryA, hay un último valor que debe obtenerse: un puntero a la ruta de la DLL que se inyectará. El proceso de destino no sabrá nada acerca de su DLL, por lo que definitivamente no encontrará un puntero a su ruta en ningún lugar del espacio de direcciones del proceso de destino. Eso significa que debe escribir la ruta a su DLL. Afortunadamente, la combinación de las funciones [Enlace externo eliminado para invitados] y [Enlace externo eliminado para invitados] puede realizar esta tarea. 

Código: Seleccionar todo

template <typename T>
void* WriteBytesToTargetProcess(const HANDLE processHandle,
    const std::span<T> bytes, bool makeExecutable = false) {

    static_assert(sizeof(T) == sizeof(uint8_t), "Only bytes can be written.");

    const auto remoteBytesAddress{ VirtualAllocEx(processHandle, nullptr,
    bytes.size(), MEM_RESERVE | MEM_COMMIT,
        makeExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE) };
    if (remoteBytesAddress == nullptr) {
        PrintErrorAndExit("VirtualAllocEx");
    }

    size_t bytesWritten{};
    const auto result{ WriteProcessMemory(processHandle, remoteBytesAddress,
        bytes.data(), bytes.size(), &bytesWritten) };
    if (result == 0) {
        PrintErrorAndExit("WriteProcessMemory");
    }

    return remoteBytesAddress;
}
Escribir en un lapso de bytes en un proceso.

La función VirtualAllocEx se utiliza para asignar un bloque de memoria en el proceso de destino. El valor devuelto por VirtualAllocEx contendrá un puntero en el espacio de direcciones del proceso de destino donde se asignó la memoria. Luego, este puntero se puede pasar a WriteProcessMemory para escribir en un lapso de bytes, que será la ruta del archivo en la que se encuentra la DLL que se va a inyectar.La ruta del archivo se puede pasar como un valor codificado o, como opción más flexible, se puede recuperar en tiempo de ejecución.

Código: Seleccionar todo

std::string GetInjectedDllPath(const std::string& moduleName) {

    char imageName[MAX_PATH]{};
    DWORD bytesWritten{ MAX_PATH };
    auto result{ QueryFullProcessImageNameA(GetCurrentProcess(),
        0, imageName, &bytesWritten) };
    if (result == 0) {
        PrintErrorAndExit("QueryFullProcessImageNameA");
    }

    std::string currentDirectoryPath{ imageName, bytesWritten };
    const auto fullModulePath{ currentDirectoryPath.substr(
        0, currentDirectoryPath.find_last_of('\\') + 1)
        + moduleName };

    return fullModulePath;
}
Obteniendo la ruta absoluta de la DLL que será inyectada.

La función GetInjectedDllPath obtiene la ruta absoluta del proceso actual, que cargará la DLL. El nombre del proceso se elimina de esta ruta y el nombre de la DLL se coloca en su lugar. Esta función supone que el proceso del cargador y la DLL que se está inyectando están en el mismo directorio, pero esa limitación aún proporciona más flexibilidad que una ruta codificada.Creando el hilo remotoLa función CreateRemoteThreadEx finalmente se puede llamar ya que se han recuperado todos los parámetros.

Código: Seleccionar todo

void InjectWithRemoteThread(const DWORD processId, std::string& fullModulePath) {

    const auto processHandle{ GetTargetProcessHandle(processId) };

    const auto remoteStringAddress{ WriteBytesToTargetProcess (
        processHandle, fullModulePath) };

    const auto* const loadLibraryAddress{ GetRemoteModuleFunctionAddress(   
        "kernel32.dll", "LoadLibraryA", processId) };

    const auto threadHandle{ CreateRemoteThreadEx(processHandle, nullptr, 0, 
        reinterpret_cast (loadLibraryAddress),
        remoteStringAddress, 0, nullptr, nullptr) };
    if (threadHandle == nullptr) {
        PrintErrorAndExit("CreateRemoteThread");
    }

    CloseHandle(processHandle);
}

int main(int argc, char* argv) {

    auto fullModulePath{ GetInjectedDllPath("Ch10_GenericDll.dll") };

    const auto processId{ GetTargetProcessAndThreadId(
        "Untitled - Notepad").first };

    InjectWithRemoteThread(processId, fullModulePath);

    return 0;
}
Un cargador que inyecta una DLL con CreateRemoteThreadEx.

El cargador comienza abriendo un identificador para el proceso de destino. A continuación, la ruta completa a la DLL que se va a inyectar se escribe en el proceso de destino. Finalmente, se encuentra la dirección de la función LoadLibraryA. Luego se llama a CreateRemoteThreadEx, donde se le indica que cree un nuevo hilo en el proceso de destino que comienza su ejecución en . LoadLibraryA y tiene como argumento la dirección de la ruta completa a la DLL.Ejecutando la demostraciónNota: si está utilizando el nuevo Bloc de notas para UWP que se encuentra en la última versión de Windows, deberá [Enlace externo eliminado para invitados] para que la demostración funcione.El proyecto [Enlace externo eliminado para invitados] proporciona la implementación completa que se presentó en esta sección. Para probar esto localmente, cree el proyecto GenericDll y el cargador CreateRemoteThread. proyecto. Después de una compilación exitosa, inicie el Bloc de notas y luego la aplicación de carga. Debería observar que aparece un cuadro de mensaje que dice "¡DLL inyectada!", como se muestra a continuación. No descartes este cuadro de mensaje todavía.

[Enlace externo eliminado para invitados]
Imagen

El cuadro de mensaje que aparece después de que el cargador inyecta GenericDll.dll.


Para ver GenericDll.dll en notepad.exe espacio de direcciones, abra [Enlace externo eliminado para invitados], busque notepad.exe proceso y navegue hasta la pestaña Módulos. Verifique que GenericDll.dll se haya cargado; Sin embargo, esto debería ser evidente si aparece el cuadro de mensaje. A continuación, haga clic en la pestaña Hilos. Habrá un hilo cuya dirección de inicio aparece como kernel32.dll!LoadLibraryA.
Imagen
La pestaña Hilos o Threads del proceso notepad.exe.

Este es el hilo que fue creado por la llamada CreateRemoteThreadEx. Si descarta el cuadro de mensaje, verá que este hilo sale. La presencia de GenericDll.dll en el espacio de direcciones notepad.exe, y un nuevo hilo que se ejecuta en LoadLibraryA muestra que la DLL se inyectó correctamente y que el cuadro de mensaje se está ejecutando en el contexto del proceso del Bloc de notas.

Bueno, pues hasta aqui esta tercera entrega...nos vemos en la siguiente...

Saludos a tod@s...

cLn






 
Imagen


Solo lo mejor es suficiente...
Responder

Volver a “Manuales”