/*
* THIS ENTIRE CODE COMES FROM THE RAPHAEL'S MODUGE GITHUB REPOSITORY NAMED "unhook-bof":
*   https://github.com/rsmudge/unhook-bof
*
* Also, parts of this code contain functions implemented by Jeff Tang <jtang@cylance.com> in his apisetmap module.
* 
* I have only adapted it for needs of this project!
*
*/

#include "refresher.h"


ModulesRefresher::ModulesRefresher()
{

}

ModulesRefresher::_PPEB ModulesRefresher::GetProcessEnvironmentBlock()
{
    ULONG_PTR pPeb;
#ifdef _WIN64
    pPeb = __readgsqword(0x60);
#else
#ifdef WIN_ARM
    pPeb = *(DWORD*)((BYTE*)_MoveFromCoprocessor(15, 0, 13, 0, 2) + 0x30);
#else _WIN32
    pPeb = __readfsdword(0x30);
#endif
#endif
    return (_PPEB)pPeb;
}

ModulesRefresher::PLDR_DATA_TABLE_ENTRY ModulesRefresher::GetInMemoryOrderModuleList()
{
    return (PLDR_DATA_TABLE_ENTRY)GetProcessEnvironmentBlock()->pLdr->InMemoryOrderModuleList.Flink;
}

PWCHAR ModulesRefresher::GetRedirectedName(const PWSTR wszImportingModule, const PWSTR wszVirtualModule, size_t* stSize)
{
    PAPI_SET_NAMESPACE_ARRAY_V2 pApiSetMap;
    pApiSetMap = (PAPI_SET_NAMESPACE_ARRAY_V2)GetProcessEnvironmentBlock()->pFreeList;
    *stSize = 0;

    if (pApiSetMap->Version == 6)
        return GetRedirectedName_V6(wszImportingModule, wszVirtualModule, stSize);
    else if (pApiSetMap->Version == 4)
        return GetRedirectedName_V4(wszImportingModule, wszVirtualModule, stSize);
    else if (pApiSetMap->Version == 2)
        return GetRedirectedName_V2(wszImportingModule, wszVirtualModule, stSize);
    else
        return NULL;
}

PWCHAR ModulesRefresher::GetRedirectedName_V6(const PWSTR wszImportingModule, const PWSTR wszVirtualModule, size_t* stSize)
{
    PAPI_SET_NAMESPACE_ARRAY_V6 pApiSetMap;
    PAPI_SET_NAMESPACE_ENTRY_V6 pApiEntry;
    PAPI_SET_VALUE_ENTRY_V6 pApiValue;
    PAPI_SET_VALUE_ENTRY_V6 pApiArray;
    DWORD dwEntryCount;
    LONG dwSetCount;
    PWSTR wsEntry;
    PWSTR wsName;
    PWSTR wsValue;

    pApiSetMap = (PAPI_SET_NAMESPACE_ARRAY_V6)GetProcessEnvironmentBlock()->pFreeList;

    // Loop through each entry in the ApiSetMap to find the matching redirected module entry
    for (dwEntryCount = 0; dwEntryCount < pApiSetMap->Count; dwEntryCount++)
    {
        pApiEntry = &pApiSetMap->Array[dwEntryCount];
        wsEntry = (PWSTR)((PCHAR)pApiSetMap + pApiEntry->NameOffset);

        // Skip this entry if it does not match
        if (_wcsnicmp(wsEntry, wszVirtualModule, pApiEntry->NameLength / 2) != 0)
            continue;

        pApiArray = (PAPI_SET_VALUE_ENTRY_V6)((PCHAR)pApiSetMap + pApiEntry->DataOffset);

        // Loop through each value entry from the end and find where the importing module matches the ``Name`` entry
        // If the ``Name`` entry is empty, it is the default entry @ index = 0
        for (dwSetCount = pApiEntry->Count - 1; dwSetCount >= 0; dwSetCount--)
        {
            // pApiValue = (PAPI_SET_VALUE_ENTRY_V6)((PCHAR)pApiSetMap + pApiEntry->DataOffset + (dwSetCount * sizeof(API_SET_VALUE_ENTRY_V6)));
            pApiValue = &pApiArray[dwSetCount];
            wsName = (PWSTR)((PCHAR)pApiSetMap + pApiValue->NameOffset);
            wsValue = (PWSTR)((PCHAR)pApiSetMap + pApiValue->ValueOffset);

            if (pApiValue->NameLength == 0 || _wcsnicmp(wsName, wszImportingModule, pApiValue->NameLength / 2) == 0)
            {
                *stSize = pApiValue->ValueLength / 2;
                return wsValue;
            }
        }
    }
    return NULL;
}

PWCHAR ModulesRefresher::GetRedirectedName_V4(const PWSTR wszImportingModule, const PWSTR wszVirtualModule, size_t* stSize)
{
    PAPI_SET_NAMESPACE_ARRAY_V4 pApiSetMap;
    PAPI_SET_NAMESPACE_ENTRY_V4 pApiEntry;
    PAPI_SET_VALUE_ARRAY_V4 pApiArray;
    PAPI_SET_VALUE_ENTRY_V4 pApiValue;
    DWORD dwEntryCount;
    LONG dwSetCount;
    PWSTR wsEntry;
    PWSTR wsName;
    PWSTR wsValue;
    PWSTR wszShortVirtualModule;

    pApiSetMap = (PAPI_SET_NAMESPACE_ARRAY_V4)GetProcessEnvironmentBlock()->pFreeList;
    wszShortVirtualModule = (PWSTR)((PWCHAR)wszVirtualModule + 4);

    for (dwEntryCount = 0; dwEntryCount < pApiSetMap->Count; dwEntryCount++)
    {
        pApiEntry = &pApiSetMap->Array[dwEntryCount];
        wsEntry = (PWSTR)((PCHAR)pApiSetMap + pApiEntry->NameOffset);

        if (_wcsnicmp(wsEntry, wszShortVirtualModule, pApiEntry->NameLength / 2) != 0)
            continue;

        pApiArray = (PAPI_SET_VALUE_ARRAY_V4)((PCHAR)pApiSetMap + pApiEntry->DataOffset);

        for (dwSetCount = pApiArray->Count - 1; dwSetCount >= 0; dwSetCount--)
        {
            pApiValue = &pApiArray->Array[dwSetCount];
            wsName = (PWSTR)((PCHAR)pApiSetMap + pApiValue->NameOffset);
            wsValue = (PWSTR)((PCHAR)pApiSetMap + pApiValue->ValueOffset);

            if (pApiValue->NameLength == 0 || _wcsnicmp(wsName, wszImportingModule, pApiValue->NameLength / 2) == 0)
            {
                *stSize = pApiValue->ValueLength / 2;
                return wsValue;
            }
        }
    }
    return NULL;
}

PWCHAR ModulesRefresher::GetRedirectedName_V2(const PWSTR wszImportingModule, const PWSTR wszVirtualModule, size_t* stSize)
{
    PAPI_SET_NAMESPACE_ARRAY_V2 pApiSetMap;
    PAPI_SET_NAMESPACE_ENTRY_V2 pApiEntry;
    PAPI_SET_VALUE_ARRAY_V2 pApiArray;
    PAPI_SET_VALUE_ENTRY_V2 pApiValue;
    DWORD dwEntryCount;
    LONG dwSetCount;
    PWSTR wsEntry;
    PWSTR wsName;
    PWSTR wsValue;
    PWSTR wszShortVirtualModule;

    pApiSetMap = (PAPI_SET_NAMESPACE_ARRAY_V2)GetProcessEnvironmentBlock()->pFreeList;
    wszShortVirtualModule = (PWSTR)((PWCHAR)wszVirtualModule + 4);

    for (dwEntryCount = 0; dwEntryCount < pApiSetMap->Count; dwEntryCount++)
    {
        pApiEntry = &pApiSetMap->Array[dwEntryCount];
        wsEntry = (PWSTR)((PCHAR)pApiSetMap + pApiEntry->NameOffset);

        if (_wcsnicmp(wsEntry, wszShortVirtualModule, pApiEntry->NameLength / 2) != 0)
            continue;

        pApiArray = (PAPI_SET_VALUE_ARRAY_V2)((PCHAR)pApiSetMap + pApiEntry->DataOffset);

        for (dwSetCount = pApiArray->Count - 1; dwSetCount >= 0; dwSetCount--)
        {
            pApiValue = &pApiArray->Array[dwSetCount];
            wsName = (PWSTR)((PCHAR)pApiSetMap + pApiValue->NameOffset);
            wsValue = (PWSTR)((PCHAR)pApiSetMap + pApiValue->ValueOffset);

            if (pApiValue->NameLength == 0 || _wcsnicmp(wsName, wszImportingModule, pApiValue->NameLength / 2) == 0)
            {
                *stSize = pApiValue->ValueLength / 2;
                return wsValue;
            }
        }
    }
    return NULL;
}

BOOL ModulesRefresher::IsBeaconDLL(const char* stomp, size_t beaconDllLength, PWSTR wszBaseDllName, USHORT BaseDllLength)
{
    BOOL isBeacon = FALSE;
    if (beaconDllLength * 2 == BaseDllLength)
    {
        isBeacon = TRUE;
        for (int i = 0; i < beaconDllLength; i++)
        {
            // make them lower case
            char c1 = stomp[i];
            char c2 = wszBaseDllName[i];
            if (c1 >= 'A' && c1 <= 'Z')
                c1 += 32;
            if (c2 >= 'A' && c2 <= 'Z')
                c2 += 32;
            if (c1 != c2)
            {
                isBeacon = FALSE;
                break;
            }
        }
    }
    return isBeacon;
}

bool ModulesRefresher::refresh(const std::vector<std::string>& skipModules)
{
    HMODULE hModule;
    PWSTR wszFullDllName;
    PWSTR wszBaseDllName;
    ULONG_PTR pDllBase;

    PLDR_DATA_TABLE_ENTRY pLdteHead = NULL;
    PLDR_DATA_TABLE_ENTRY pLdteCurrent = NULL;

    pLdteHead = GetInMemoryOrderModuleList();
    pLdteCurrent = pLdteHead;

    do {
        BOOL dllToSkip = FALSE;
        if (!pLdteCurrent->BaseDllName.pBuffer) break;

        std::wstring module(pLdteCurrent->BaseDllName.pBuffer);

        for (size_t i = 0; i < skipModules.size(); i++)
        {
            if (IsBeaconDLL(skipModules[i].c_str(), skipModules[i].size(), pLdteCurrent->BaseDllName.pBuffer, pLdteCurrent->BaseDllName.Length))
            {
                dllToSkip = TRUE;
                break;
            }
        }

        if (module.find(L".exe") != std::wstring::npos) dllToSkip = true;

        if (pLdteCurrent->FullDllName.Length > 2 && !dllToSkip)
        {
            wszFullDllName = pLdteCurrent->FullDllName.pBuffer;
            wszBaseDllName = pLdteCurrent->BaseDllName.pBuffer;
            pDllBase = (ULONG_PTR)pLdteCurrent->DllBase;

            hModule = CustomLoadLibrary(wszFullDllName, wszBaseDllName, pDllBase);

            if (hModule)
            {
                ScanAndFixModule((PCHAR)hModule, (PCHAR)pDllBase, wszBaseDllName);
                VirtualFree(hModule, 0, MEM_RELEASE);
            }
        }

        pLdteCurrent = (PLDR_DATA_TABLE_ENTRY)pLdteCurrent->InMemoryOrderModuleList.Flink;

    } while (pLdteCurrent != pLdteHead);

    return true;
}


HMODULE ModulesRefresher::CustomLoadLibrary(const PWCHAR wszFullDllName, const PWCHAR wszBaseDllName, ULONG_PTR pDllBase)
{
    // File handles
    HANDLE hFile = INVALID_HANDLE_VALUE;
    HANDLE hMap = NULL;
    PCHAR pFile = NULL;

    // PE headers
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeader;
    PIMAGE_SECTION_HEADER pSectionHeader;

    // Library 
    PCHAR pLibraryAddr = NULL;
    DWORD dwIdx;

    // Relocation
    PIMAGE_DATA_DIRECTORY pDataDir;
    PIMAGE_BASE_RELOCATION pBaseReloc;
    ULONG_PTR pReloc;
    DWORD dwNumRelocs;
    ULONG_PTR pInitialImageBase;
    PIMAGE_RELOC pImageReloc;

    // Import
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
    PCHAR szDllName;
    size_t stDllName;
    PWSTR wszDllName = NULL;
    PWCHAR wsRedir = NULL;
    PWSTR wszRedirName = NULL;
    size_t stRedirName;
    size_t stSize;

    HMODULE hModule;
    PIMAGE_THUNK_DATA pThunkData;
    FARPROC* pIatEntry;

    // clr.dll hotpatches itself at runtime for performance reasons, so skip it
    if (wcscmp(L"clr.dll", wszBaseDllName) == 0)
        goto cleanup;

    // ----
    // Step 1: Map the file into memory
    // ----

    hFile = CreateFileW(wszFullDllName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
        goto cleanup;

    hMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (hMap == NULL)
        goto cleanup;

    pFile = (PCHAR)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
    if (pFile == NULL)
        goto cleanup;

    // ----
    // Step 2: Parse the file headers and load it into memory
    // ----
    pDosHeader = (PIMAGE_DOS_HEADER)pFile;
    pNtHeader = (PIMAGE_NT_HEADERS)(pFile + pDosHeader->e_lfanew);

    // allocate memory to copy DLL into
    pLibraryAddr = (PCHAR)VirtualAlloc(NULL, pNtHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

    // copy header
    __movsb((PBYTE)pLibraryAddr, (PBYTE)pFile, pNtHeader->OptionalHeader.SizeOfHeaders);

    // copy sections
    pSectionHeader = (PIMAGE_SECTION_HEADER)(pFile + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
    for (dwIdx = 0; dwIdx < pNtHeader->FileHeader.NumberOfSections; dwIdx++)
    {
        __movsb((PBYTE)(pLibraryAddr + pSectionHeader[dwIdx].VirtualAddress),
            (PBYTE)(pFile + pSectionHeader[dwIdx].PointerToRawData),
            pSectionHeader[dwIdx].SizeOfRawData);
    }

    // update our pointers to the loaded image
    pDosHeader = (PIMAGE_DOS_HEADER)pLibraryAddr;
    pNtHeader = (PIMAGE_NT_HEADERS)(pLibraryAddr + pDosHeader->e_lfanew);

    // ----
    // Step 3: Calculate relocations
    // ----

    pDataDir = &pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    pInitialImageBase = pNtHeader->OptionalHeader.ImageBase;
    // set the ImageBase to the already loaded module's base
    pNtHeader->OptionalHeader.ImageBase = pDllBase;

    // check if their are any relocations present
    if (pDataDir->Size)
    {
        // calculate the address of the first IMAGE_BASE_RELOCATION entry
        pBaseReloc = (PIMAGE_BASE_RELOCATION)(pLibraryAddr + pDataDir->VirtualAddress);

        // iterate through each relocation entry
        while ((PCHAR)pBaseReloc < (pLibraryAddr + pDataDir->VirtualAddress + pDataDir->Size) && pBaseReloc->SizeOfBlock)
        {
            // the VA for this relocation block
            pReloc = (ULONG_PTR)(pLibraryAddr + pBaseReloc->VirtualAddress);

            // number of entries in this relocation block
            dwNumRelocs = (pBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);

            // first entry in the current relocation block
            pImageReloc = (PIMAGE_RELOC)((PCHAR)pBaseReloc + sizeof(IMAGE_BASE_RELOCATION));

            // iterate through each entry in the relocation block
            while (dwNumRelocs--)
            {
                // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required.
                // we subtract the initial ImageBase and add in the original dll base
                if (pImageReloc->type == IMAGE_REL_BASED_DIR64)
                {
                    *(ULONG_PTR*)(pReloc + pImageReloc->offset) -= pInitialImageBase;
                    *(ULONG_PTR*)(pReloc + pImageReloc->offset) += pDllBase;
                }
                else if (pImageReloc->type == IMAGE_REL_BASED_HIGHLOW)
                {
                    *(DWORD*)(pReloc + pImageReloc->offset) -= (DWORD)pInitialImageBase;
                    *(DWORD*)(pReloc + pImageReloc->offset) += (DWORD)pDllBase;
                }
                else if (pImageReloc->type == IMAGE_REL_BASED_HIGH)
                {
                    *(WORD*)(pReloc + pImageReloc->offset) -= HIWORD(pInitialImageBase);
                    *(WORD*)(pReloc + pImageReloc->offset) += HIWORD(pDllBase);
                }
                else if (pImageReloc->type == IMAGE_REL_BASED_LOW)
                {
                    *(WORD*)(pReloc + pImageReloc->offset) -= LOWORD(pInitialImageBase);
                    *(WORD*)(pReloc + pImageReloc->offset) += LOWORD(pDllBase);
                }

                // get the next entry in the current relocation block
                pImageReloc = (PIMAGE_RELOC)((PCHAR)pImageReloc + sizeof(IMAGE_RELOC));
            }

            // get the next entry in the relocation directory
            pBaseReloc = (PIMAGE_BASE_RELOCATION)((PCHAR)pBaseReloc + pBaseReloc->SizeOfBlock);
        }
    }

    // ----
    // Step 4: Update import table
    // ----

    pDataDir = &pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    if (pDataDir->Size)
    {
        pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(pLibraryAddr + pDataDir->VirtualAddress);

        while (pImportDesc->Characteristics)
        {
            hModule = NULL;
            wszDllName = NULL;
            szDllName = (PCHAR)(pLibraryAddr + pImportDesc->Name);
            stDllName = strnlen(szDllName, MAX_PATH);
            wszDllName = (PWSTR)calloc(stDllName + 1, sizeof(WCHAR));

            if (wszDllName == NULL)
                goto next_import;

            mbstowcs_s(&stSize, wszDllName, stDllName + 1, szDllName, stDllName);

            // If the DLL starts with api- or ext-, resolve the redirected name and load it
            if (_wcsnicmp(wszDllName, L"api-", 4) == 0 || _wcsnicmp(wszDllName, L"ext-", 4) == 0)
            {
                // wsRedir is not null terminated
                wsRedir = GetRedirectedName(wszBaseDllName, wszDllName, &stRedirName);
                if (wsRedir)
                {
                    // Free the original wszDllName and allocate a new buffer for the redirected dll name
                    free(wszDllName);
                    wszDllName = (PWSTR)calloc(stRedirName + 1, sizeof(WCHAR));
                    if (wszDllName == NULL)
                        goto next_import;

                    __movsb((PBYTE)wszDllName, (PBYTE)wsRedir, stRedirName * sizeof(WCHAR));
                }
            }

            // Load the module
            hModule = CustomGetModuleHandleW(wszDllName);

            // Ignore libraries that fail to load
            if (hModule == NULL)
                goto next_import;

            if (pImportDesc->OriginalFirstThunk)
                pThunkData = (PIMAGE_THUNK_DATA)(pLibraryAddr + pImportDesc->OriginalFirstThunk);
            else
                pThunkData = (PIMAGE_THUNK_DATA)(pLibraryAddr + pImportDesc->FirstThunk);

            pIatEntry = (FARPROC*)(pLibraryAddr + pImportDesc->FirstThunk);

            // loop through each thunk and resolve the import
            for (; DEREF(pThunkData); pThunkData++, pIatEntry++)
            {
                if (IMAGE_SNAP_BY_ORDINAL(pThunkData->u1.Ordinal))
                    *pIatEntry = CustomGetProcAddressEx(hModule, (PCHAR)IMAGE_ORDINAL(pThunkData->u1.Ordinal), wszDllName);
                else
                    *pIatEntry = CustomGetProcAddressEx(hModule, ((PIMAGE_IMPORT_BY_NAME)(pLibraryAddr + DEREF(pThunkData)))->Name, wszDllName);
            }

        next_import:
            if (wszDllName != NULL)
            {
                free(wszDllName);
                wszDllName = NULL;
            }
            pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((PCHAR)pImportDesc + sizeof(IMAGE_IMPORT_DESCRIPTOR));
        }
    }

cleanup:
    if (pFile != NULL)
        UnmapViewOfFile(pFile);
    if (hMap != NULL)
        CloseHandle(hMap);
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    return (HMODULE)pLibraryAddr;
}

FARPROC WINAPI ModulesRefresher::CustomGetProcAddressEx(HMODULE hModule, const PCHAR lpProcName, PWSTR wszOriginalModule)
{
    UINT_PTR uiLibraryAddress = 0;
    UINT_PTR uiAddressArray = 0;
    UINT_PTR uiNameArray = 0;
    UINT_PTR uiNameOrdinals = 0;
    UINT_PTR uiFuncVA = 0;
    PCHAR cpExportedFunctionName;
    PCHAR szFwdDesc;
    PCHAR szRedirFunc;
    PWSTR wszDllName;
    size_t stDllName;
    PWCHAR wsRedir;
    PWSTR wszRedirName = NULL;
    size_t stRedirName;

    HMODULE hFwdModule;
    PIMAGE_NT_HEADERS pNtHeaders = NULL;
    PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
    PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;
    FARPROC fpResult = NULL;
    DWORD dwCounter;

    if (hModule == NULL)
        return NULL;

    // a module handle is really its base address
    uiLibraryAddress = (UINT_PTR)hModule;

    // get the VA of the modules NT Header
    pNtHeaders = (PIMAGE_NT_HEADERS)(uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew);

    pDataDirectory = (PIMAGE_DATA_DIRECTORY)&pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];

    // get the VA of the export directory
    pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(uiLibraryAddress + pDataDirectory->VirtualAddress);

    // get the VA for the array of addresses
    uiAddressArray = (uiLibraryAddress + pExportDirectory->AddressOfFunctions);

    // get the VA for the array of name pointers
    uiNameArray = (uiLibraryAddress + pExportDirectory->AddressOfNames);

    // get the VA for the array of name ordinals
    uiNameOrdinals = (uiLibraryAddress + pExportDirectory->AddressOfNameOrdinals);

    // test if we are importing by name or by ordinal...
#pragma warning(suppress: 4311)
    if (((DWORD)lpProcName & 0xFFFF0000) == 0x00000000)
    {
        // import by ordinal...

        // use the import ordinal (- export ordinal base) as an index into the array of addresses
#pragma warning(suppress: 4311)
        uiAddressArray += ((IMAGE_ORDINAL((DWORD)lpProcName) - pExportDirectory->Base) * sizeof(DWORD));

        // resolve the address for this imported function
        fpResult = (FARPROC)(uiLibraryAddress + DEREF_32(uiAddressArray));
    }
    else
    {
        // import by name...
        dwCounter = pExportDirectory->NumberOfNames;
        while (dwCounter--)
        {
            cpExportedFunctionName = (PCHAR)(uiLibraryAddress + DEREF_32(uiNameArray));

            // test if we have a match...
            if (strcmp(cpExportedFunctionName, lpProcName) == 0)
            {
                // use the functions name ordinal as an index into the array of name pointers
                uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
                uiFuncVA = DEREF_32(uiAddressArray);

                // check for redirected exports
                if (pDataDirectory->VirtualAddress <= uiFuncVA && uiFuncVA < (pDataDirectory->VirtualAddress + pDataDirectory->Size))
                {
                    szFwdDesc = (PCHAR)(uiLibraryAddress + uiFuncVA);

                    // Find the first character after "."
                    szRedirFunc = strstr(szFwdDesc, ".") + 1;
                    stDllName = (SIZE_T)(szRedirFunc - szFwdDesc);

                    // Allocate enough space to append "dll"
                    wszDllName = (PWSTR)calloc(stDllName + 3 + 1, sizeof(WCHAR));
                    if (wszDllName == NULL)
                        break;

                    mbstowcs_s(NULL, wszDllName, stDllName + 1, szFwdDesc, stDllName);
                    __movsb((PBYTE)(wszDllName + stDllName), (PBYTE)(L"dll"), 3 * sizeof(WCHAR));

                    // check for a redirected module name
                    if (_wcsnicmp(wszDllName, L"api-", 4) == 0 || _wcsnicmp(wszDllName, L"ext-", 4) == 0)
                    {
                        wsRedir = GetRedirectedName(wszOriginalModule, wszDllName, &stRedirName);
                        if (wsRedir)
                        {
                            // Free the original buffer and allocate a new one for the redirected dll name
                            free(wszDllName);

                            wszDllName = (PWSTR)calloc(stRedirName + 1, sizeof(WCHAR));
                            if (wszDllName == NULL)
                                break;

                            __movsb((PBYTE)wszDllName, (PBYTE)wsRedir, stRedirName * sizeof(WCHAR));
                        }
                    }

                    hFwdModule = GetModuleHandleW(wszDllName);
                    fpResult = CustomGetProcAddressEx(hFwdModule, szRedirFunc, wszDllName);
                    free(wszDllName);
                }
                else
                {
                    // calculate the virtual address for the function
                    fpResult = (FARPROC)(uiLibraryAddress + uiFuncVA);
                }

                // finish...
                break;
            }

            // get the next exported function name
            uiNameArray += sizeof(DWORD);

            // get the next exported function name ordinal
            uiNameOrdinals += sizeof(WORD);
        }
    }

    return fpResult;
}

HMODULE ModulesRefresher::CustomGetModuleHandleW(const PWSTR wszModule)
{
    HMODULE hModule = NULL;
    PLDR_DATA_TABLE_ENTRY pLdteHead = NULL;
    PLDR_DATA_TABLE_ENTRY pLdteCurrent = NULL;

    pLdteCurrent = pLdteHead = GetInMemoryOrderModuleList();

    do {
        if (pLdteCurrent->FullDllName.Length > 2 &&
            _wcsnicmp(wszModule, pLdteCurrent->BaseDllName.pBuffer, pLdteCurrent->BaseDllName.Length / 2) == 0)
        {
            return ((HMODULE)pLdteCurrent->DllBase);
        }
        pLdteCurrent = (PLDR_DATA_TABLE_ENTRY)pLdteCurrent->InMemoryOrderModuleList.Flink;
    } while (pLdteCurrent != pLdteHead);

    return LoadLibraryW(wszModule);
}

VOID ModulesRefresher::ScanAndFixModule(PCHAR pKnown, PCHAR pSuspect, PWCHAR wszBaseDllName)
{
    // PE headers
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeader;
    PIMAGE_SECTION_HEADER pSectionHeader;

    DWORD dwIdx;

    pDosHeader = (PIMAGE_DOS_HEADER)pKnown;
    pNtHeader = (PIMAGE_NT_HEADERS)(pKnown + pDosHeader->e_lfanew);

    // Scan PE header
    ScanAndFixSection(wszBaseDllName, pKnown, pSuspect, pNtHeader->OptionalHeader.SizeOfHeaders);

    // Scan each section
    pSectionHeader = (PIMAGE_SECTION_HEADER)(pKnown + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
    for (dwIdx = 0; dwIdx < pNtHeader->FileHeader.NumberOfSections; dwIdx++)
    {
        if (pSectionHeader[dwIdx].Characteristics & IMAGE_SCN_MEM_WRITE)
            continue;

        if (!((wcscmp(wszBaseDllName, L"clr.dll") == 0 && strcmp((const char*)pSectionHeader[dwIdx].Name, ".text") == 0)))
        {
            ScanAndFixSection(wszBaseDllName, pKnown + pSectionHeader[dwIdx].VirtualAddress, pSuspect + pSectionHeader[dwIdx].VirtualAddress,
                pSectionHeader[dwIdx].Misc.VirtualSize);
        }
    }
}

VOID ModulesRefresher::ScanAndFixSection(PWCHAR wszBaseDllName, PCHAR pKnown, PCHAR pSuspect, size_t stLength)
{
    DWORD ddOldProtect;

    if (memcmp(pKnown, pSuspect, stLength) != 0)
    {
        if (!VirtualProtect(pSuspect, stLength, PAGE_EXECUTE_READWRITE, &ddOldProtect))
            return;

        __movsb((PBYTE)pSuspect, (PBYTE)pKnown, stLength);

        VirtualProtect(pSuspect, stLength, ddOldProtect, &ddOldProtect);
    }
}
