Featured image of post Cómo realizar una inyección de DLL y un API hooking en VC++.

Cómo realizar una inyección de DLL y un API hooking en VC++.

Que es una inyección de DLL?

La inyección de dll es una técnica que permite inyectar código en procesos legítimos, esto se hace para modificar el comportamiento de un ejecutable o para ocultar código malicioso en procesos.

para poder explicar que es una inyección de dll debemos crear un ejecutable que nos permita ver que es lo que pasa.

este programa va a ser util como para hacer una inyección dll y hacer api hooking.

#include <iostream>
#include <windows.h> 
#include <thread>
#include <chrono>
#include <fstream>
#include <string>

int main()
{
    std::cout << "Running loop. Press Ctrl+C to exit.\n";
    while (true) {
        Sleep(5000);
        std::cout << "Sleep\n";
        std::ifstream file("example.txt");
        if (!file.is_open()) {
            std::cerr << "Error: Could not open the file." << std::endl;
            continue;
        }
        std::string line;
        while (std::getline(file, line)) {
            std::cout << line << '\n';
        }
       
    }

}

Para este caso vamos a crear una DLL simple que solo tenga un msgbox.


#include "pch.h"
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MessageBoxA(NULL, "Hello World!", "DLL de prueba", MB_OK | MB_ICONINFORMATION);
        break;

    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Este es el programa que se va a usar para aplicar la inyección DLL; al leer el archivo, se hace uso de WINAPI, más en específico el uso de la WINAPI ReadFile; esto nos va a ser de utilidad más adelante para el API Hooking.

#include <iostream>
#include <windows.h>
#include <string>
#include <thread>
#include <libloaderapi.h>
#include <filesystem>

using namespace std;
namespace fs = std::filesystem;

bool dll_exists(const std::string& file_name)
{
    if (file_name.empty())
        return false;

    std::error_code ec;
    return std::filesystem::is_regular_file(file_name, ec) &&
        std::filesystem::path(file_name).extension() == ".dll";
}


int main()
{
    DWORD proc_id = 13568;
    char dll_path[MAX_PATH];
    const char* dll_name = "nulldll.dll";
    if (!dll_exists(dll_name)) {
        OutputDebugStringA( "Err:DLL Doesn't exist!");
        return 0;
    }

    if (!GetFullPathNameA(dll_name, MAX_PATH, dll_path, nullptr)) {
        OutputDebugStringA( "Err:GetFullPathNameA");
        return 0;
    }
    HANDLE h_process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, proc_id);
    if (!h_process) {
        OutputDebugStringA( "Err:OpenProcess");
        return 0;
    }

    void* allocated_memory = VirtualAllocEx(h_process, nullptr, MAX_PATH, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (!allocated_memory) {
        OutputDebugStringA("Err:VirtualAllocEx");
        return 0;
    }
    char buffer[512];
    sprintf_s(buffer, "Direccion: 0x%p\n", allocated_memory);
    OutputDebugStringA(buffer);

    if (!WriteProcessMemory(h_process, allocated_memory, dll_path, MAX_PATH, nullptr)) {
        OutputDebugStringA("Err:WriteProcessMemory");
        return 0;
    }
    HANDLE h_thread = CreateRemoteThread(h_process, nullptr, NULL, LPTHREAD_START_ROUTINE(LoadLibraryA), allocated_memory, NULL, nullptr);
    if (!h_thread) {
        OutputDebugStringA("Err:CreateRemoteThread");
        return 0;
    }
    CloseHandle(h_process);
    VirtualFreeEx(h_process, allocated_memory, NULL, MEM_RELEASE);
    CloseHandle(h_process);
}

En este código hay tres WinAPI que son de importancia: VirtualAllocEx, WriteProcessMemory, CreateRemoteThread ` VirtualAllocEx: Reserva una región de memoria dentro del espacio virtual del proceso objetivo y devuelve su dirección base

WriteProcessMemory: Copia bytes desde el proceso que llama hacia una dirección del proceso objetivo.

CreateRemoteThread: Crea un hilo cuya ejecución comienza en el espacio de direcciones del proceso objetivo ` abrimos un el programa que se va a usar para la inyección DLL

x86dbg dll module symbols

Vamos al apartado de symbols, donde podemos observar los DLL cargados. Ejecutamos como administrador el programa que se encarga de hacer la inyección DLL. En la parte de x86dbg, vamos a Options -> Preferences y User DLL load la marcamos. Cuando se haga la inyección, x86dbg se va a detener, como se observa en la imagen.

dll injection

También podemos observar que se cargan más DLL; esto es importante porque en las inyecciones DLL nos puede ocasionar problemas.

Modulos Cargados por dll

x86dbg dll load

API Hooking

El API hooking es un método por el cual interceptamos una WINAPI en la ejecución de un programa; con esto podemos modificar el comportamiento del programa o podemos observar lo que ocurre con el programa. Como se mencionó al inicio del artículo, vamos a ver qué ocurre con la WINAPI ReadFile. Para esto, lo más fácil es simplemente inyectar una DLL con MinHook, que es una librería que simplifica el hook.

https://github.com/TsudaKageyu/minhook

git clone https://github.com/microsoft/vcpkg
.\vcpkg\bootstrap-vcpkg.bat
.\vcpkg\vcpkg integrate install
.\vcpkg\vcpkg install minhook

Con esto instalamos MinHook; una vez instalada, creamos una DLL. .


BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hModule);

        OutputDebugStringW(L"DLL_PROCESS_ATTACH ok");
        if (CreateThread(NULL, 0, HookThread, NULL, 0, NULL) == NULL) {
            OutputDebugStringW(L"Err HookThread");
        }
        break;

    case DLL_PROCESS_DETACH:
        OutputDebugStringW(L"DLL_PROCESS_DETACH fail");
        break;
    }

    return TRUE;
}

En la parte del código debemos tener en cuenta que ocupamos DisableThreadLibraryCalls y debemos crear un hilo para no monopolizar el programa; para eso usamos CreateThread.

Para hacer un hook podemos usar el código de ejemplo de MinHook; si se usa el ejemplo, queda de esta forma.

DWORD WINAPI HookThread(LPVOID)
{
    if (MH_Initialize() != MH_OK) {
        OutputDebugStringW(L"MinHook failed");
        return 1;
    }

    HMODULE hKernelBase = GetModuleHandleW(L"kernelbase.dll");
    if (!hKernelBase) hKernelBase = LoadLibraryW(L"kernelbase.dll");

    if (hKernelBase) {
        void* pReadFile = (void*)GetProcAddress(hKernelBase, "ReadFile");
        if (pReadFile) {
            MH_CreateHook(pReadFile, &HookedReadFile, reinterpret_cast<void**>(&TargetReadFile));
        }
        void* pCreateFileW = (void*)GetProcAddress(hKernelBase, "CreateFileW");
        if (pCreateFileW) {
            MH_CreateHook(pCreateFileW, &HookCreateFileW, reinterpret_cast<void**>(&fpCreateFileW));
        }

        void* pLoadLibraryW = (void*)GetProcAddress(hKernelBase, "LoadLibraryW");
        if (pLoadLibraryW) {
            MH_CreateHook(pLoadLibraryW, &HookLoadLibraryW, reinterpret_cast<void**>(&fpLoadLibraryW));
        }
        if (MH_EnableHook(MH_ALL_HOOKS) == MH_OK) {
            OutputDebugStringW(L"hooks init");
        }
    }

    return 0;
}

Aquí vemos que se hace hook a las WinAPI de ReadFile, CreateFileW, LoadLibraryW; para cada uno debemos hacer una función.

  void* pReadFile = (void*)GetProcAddress(hKernelBase, "ReadFile");
        if (pReadFile) {
            MH_CreateHook(pReadFile, &HookedReadFile, reinterpret_cast<void**>(&TargetReadFile));
        }

Creamos una función para read file; debemos tener en cuenta que los parámetros originales deben ser pasados mediante la función.

` ReadFile function (fileapi.h)

Reads data from the specified file or input/output (I/O) device. Reads occur at the position specified by the file pointer if supported by the device.

BOOL ReadFile( [in] HANDLE hFile, [out] LPVOID lpBuffer, [in] DWORD nNumberOfBytesToRead, [out, optional] LPDWORD lpNumberOfBytesRead, [in, out, optional] LPOVERLAPPED lpOverlapped ); `

Por lo que la funcion debe quedar asi.

BOOL WINAPI HookedReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
    LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
{
    BOOL ok = TargetReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);

    if (ok && GetFileType(hFile) == FILE_TYPE_DISK) {
        wchar_t path[MAX_PATH];
        DWORD r = GetFinalPathNameByHandleW(hFile, path, _countof(path), FILE_NAME_NORMALIZED);
        if (r && r < _countof(path)) {
            OutputDebugStringW(L"ReadFile: ");
            if (wcsncmp(path, L"\\\\?\\", 4) == 0) {
                OutputDebugStringW(path + 4);
            }
            else {
                OutputDebugStringW(path);
            }
        }
    }
    return ok;
}

 Mediante OutputDebugStringW vamos a leer los resultados y también se va a hacer el uso de ida; hacemos esto con todas las funciones que se vayan a usar.

Abrimos el ejecutable, que es la prueba.

x86dbg

Nos vamos al apartado de symbols y ponemos el DLL que acabamos de generar. x86dbg tiene una opción para hacer la inyección; por el momento nos va a ser de utilidad, con eso hacemos la inyección.

x86dbg

Una vez cargada la DLL, vemos que hay una DLL que se llama minhook.x64.dll y dllhook.dll; esto significa que está bien cargada la DLL. Vamos a ver qué nos manda dbgview.

x86dbg

Como podemos observar en la salida, tenemos la ruta del archivo y el nombre de la WinAPI que se llama, por lo que nuestro API hooking está funcionando bien.

Eliminacion de minhook.dll

En términos prácticos, si se quiere hacer una inyección mediante AppInit_DLLs, todo debe quedar en un solo DLL; no debemos tener minhook.x32.dll ni minhook.x64.dll. Como sabemos, para procesos de 32 bits debemos usar DLL únicamente en 32 bits; lo mismo pasa para 64 bits.

Cabe mencionar que también se puede hacer una inyección permanente en user-mode usando el registro.

32 bits regjump "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows"

64 bits regjump "HKLM\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows"

ida disassembler

Como podemos observar, la DLL está como dependencia en los DLL importados.

IDA dll import

Para esto debemos instalar


.\vcpkg\vcpkg install minhook:x86-windows-static
.\vcpkg\vcpkg install minhook:x64-windows-static

minhook x64-windows-static

minhook x86-windows-static

Y se debe configurar lo siguiente en VS Studio.

Una vez configurado esto, ya debemos tener una sola DLL y no depender de minhook.x32.dll.

Built with Hugo
Theme Stack designed by Jimmy