├── .gitignore ├── .gitmodules ├── LICENSE ├── README.rst ├── ReflectivePolymorphism.sln ├── ReflectivePolymorphism ├── Main.c ├── ReflectivePolymorphism.c ├── ReflectivePolymorphism.h ├── ReflectivePolymorphism.vcxproj ├── ReflectiveTransformer.c ├── ReflectiveTransformer.h ├── ReflectiveUnloader.c └── ReflectiveUnloader.h ├── docs └── source │ ├── conf.py │ ├── index.rst │ ├── reflective_polymorphism.rst │ ├── reflective_transformer.rst │ └── reflective_unloader.rst └── pe_patch.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exe 3 | *.suo 4 | *.vcxproj.filters 5 | *.vcxproj.user 6 | .vs/* 7 | Release/ 8 | Debug/ 9 | x64/ 10 | docs/html 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ReflectiveDLLInjection"] 2 | path = ReflectiveDLLInjection 3 | url = https://github.com/zeroSteiner/ReflectiveDLLInjection.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted provided that the following conditions are 3 | met: 4 | 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above 8 | copyright notice, this list of conditions and the following disclaimer 9 | in the documentation and/or other materials provided with the 10 | distribution. 11 | * Neither the name of the project nor the names of its 12 | contributors may be used to endorse or promote products derived from 13 | this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Reflective Polymorphism 2 | ======================= 3 | This project provides various utilities for the self-modification of PE images 4 | with the intention that they can be incorporated into external projects. 5 | 6 | The documentation is available `online`_. 7 | 8 | Overview 9 | -------- 10 | The Reflective Polymorphism projects is currently composed of the following two 11 | components each of which are contained within their respective ``.c`` / ``.h`` 12 | files and are capable of operating independently. 13 | 14 | **ReflectiveTransformer** 15 | Functionality to transform PE files between DLL and EXE formats. 16 | 17 | **ReflectiveUnloader** 18 | Functionality to copy a loaded PE image out of memory and reconstruct a byte 19 | for byte copy of the PE image as it would exist on disk. 20 | 21 | License 22 | ------- 23 | This project is released under the BSD 3-clause license, for more details see 24 | the `LICENSE`_ file. 25 | 26 | .. _online: https://zeroSteiner.github.io/reflective-polymorphism/ 27 | .. _LICENSE: https://github.com/zeroSteiner/reflective-unloader/blob/master/LICENSE 28 | -------------------------------------------------------------------------------- /ReflectivePolymorphism.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReflectivePolymorphism", "ReflectivePolymorphism\ReflectivePolymorphism.vcxproj", "{3F772497-869F-4FE4-A63C-85D8E87CEBFF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {3F772497-869F-4FE4-A63C-85D8E87CEBFF}.Debug|x64.ActiveCfg = Debug|x64 17 | {3F772497-869F-4FE4-A63C-85D8E87CEBFF}.Debug|x64.Build.0 = Debug|x64 18 | {3F772497-869F-4FE4-A63C-85D8E87CEBFF}.Debug|x86.ActiveCfg = Debug|Win32 19 | {3F772497-869F-4FE4-A63C-85D8E87CEBFF}.Debug|x86.Build.0 = Debug|Win32 20 | {3F772497-869F-4FE4-A63C-85D8E87CEBFF}.Release|x64.ActiveCfg = Release|x64 21 | {3F772497-869F-4FE4-A63C-85D8E87CEBFF}.Release|x64.Build.0 = Release|x64 22 | {3F772497-869F-4FE4-A63C-85D8E87CEBFF}.Release|x86.ActiveCfg = Release|Win32 23 | {3F772497-869F-4FE4-A63C-85D8E87CEBFF}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /ReflectivePolymorphism/Main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "ReflectiveDLLInjection.h" 6 | #include "ReflectiveTransformer.h" 7 | #include "ReflectiveUnloader.h" 8 | 9 | // https://support.microsoft.com/en-us/help/94248/how-to-use-the-c-run-time 10 | BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); 11 | DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(VOID); 12 | HMODULE g_hModule = NULL; 13 | 14 | static VOID DumpPEImage(LPTSTR pFile, PVOID pBaseAddress, SIZE_T dwSize) { 15 | HANDLE hFile; 16 | DWORD dwNumberOfBytesWritten; 17 | 18 | hFile = CreateFile(pFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 19 | if (hFile == INVALID_HANDLE_VALUE) { 20 | // MessageBox(NULL, _T("Could not open the file for writing."), _T("Failed"), MB_OK); 21 | return; 22 | } 23 | WriteFile(hFile, pBaseAddress, (DWORD)dwSize, &dwNumberOfBytesWritten, NULL); 24 | CloseHandle(hFile); 25 | } 26 | 27 | static VOID DumpDLLImage(PDOS_HEADER pDosHeader, SIZE_T dwSize) { 28 | DWORD dwChars; 29 | DWORD dwEntryRVA; 30 | TCHAR ctDllPath[MAX_PATH + 1]; 31 | 32 | ZeroMemory(ctDllPath, sizeof(ctDllPath)); 33 | #ifdef _WIN64 34 | dwChars = ExpandEnvironmentStrings(_T("%USERPROFILE%\\Desktop\\ReflectivePolymorphism.x64.dll"), ctDllPath, MAX_PATH + 1); 35 | #else 36 | #ifdef _WIN32 37 | dwChars = ExpandEnvironmentStrings(_T("%USERPROFILE%\\Desktop\\ReflectivePolymorphism.x86.dll"), ctDllPath, MAX_PATH + 1); 38 | #endif 39 | #endif 40 | if ((dwChars == 0) || (dwChars > MAX_PATH + 1)) { 41 | MessageBox(NULL, _T("Could not get the file path for writing."), _T("Failed"), MB_OK); 42 | return; 43 | } 44 | 45 | dwEntryRVA = RVAFromExportName(pDosHeader, "DllMain"); 46 | if (!dwEntryRVA) { 47 | MessageBox(NULL, _T("Failed to find the RVA of the DllMain export."), _T("Failed"), MB_OK); 48 | return; 49 | } 50 | if (!ReflectiveTransformerToDLL(pDosHeader, dwEntryRVA)) { 51 | MessageBox(NULL, _T("Failed to transform the file."), _T("Failed"), MB_OK); 52 | return; 53 | } 54 | DumpPEImage(ctDllPath, pDosHeader, dwSize); 55 | } 56 | 57 | static VOID DumpEXEImage(PDOS_HEADER pDosHeader, SIZE_T dwSize) { 58 | DWORD dwChars; 59 | DWORD dwEntryRVA; 60 | TCHAR ctExePath[MAX_PATH + 1]; 61 | 62 | ZeroMemory(ctExePath, sizeof(ctExePath)); 63 | #ifdef _WIN64 64 | dwChars = ExpandEnvironmentStrings(_T("%USERPROFILE%\\Desktop\\ReflectivePolymorphism.x64.exe"), ctExePath, MAX_PATH + 1); 65 | #else 66 | #ifdef _WIN32 67 | dwChars = ExpandEnvironmentStrings(_T("%USERPROFILE%\\Desktop\\ReflectivePolymorphism.x86.exe"), ctExePath, MAX_PATH + 1); 68 | #endif 69 | #endif 70 | if ((dwChars == 0) || (dwChars > MAX_PATH + 1)) { 71 | MessageBox(NULL, _T("Could not get the file path for writing."), _T("Failed"), MB_OK); 72 | return; 73 | } 74 | 75 | dwEntryRVA = RVAFromExportName(pDosHeader, "ExeMain"); 76 | if (!dwEntryRVA) { 77 | MessageBox(NULL, _T("Failed to find the RVA of the ExeMain export."), _T("Failed"), MB_OK); 78 | return; 79 | } 80 | if (!ReflectiveTransformerToEXE(pDosHeader, dwEntryRVA)) { 81 | MessageBox(NULL, _T("Failed to transform the file."), _T("Failed"), MB_OK); 82 | return; 83 | } 84 | DumpPEImage(ctExePath, pDosHeader, dwSize); 85 | } 86 | 87 | static VOID ProofOfConcept(HINSTANCE hInstance) { 88 | PDOS_HEADER pDosHeader = NULL; 89 | SIZE_T dwSize; 90 | 91 | MessageBox(NULL, _T("Select OK to proceed."), _T("Waiting"), MB_OK); 92 | 93 | pDosHeader = ReflectiveUnloader(hInstance, &dwSize); 94 | if (!pDosHeader) { 95 | MessageBox(NULL, _T("Unload failed."), _T("Failed"), MB_OK); 96 | return; 97 | } 98 | 99 | DumpDLLImage(pDosHeader, dwSize); 100 | DumpEXEImage(pDosHeader, dwSize); 101 | 102 | ReflectiveUnloaderFree(pDosHeader, dwSize); 103 | } 104 | 105 | BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD dwReason, LPVOID lpReserved) { 106 | #pragma comment(linker, "/EXPORT:"__FUNCTION__"="__FUNCDNAME__) 107 | if (dwReason == DLL_QUERY_HMODULE) { 108 | if (lpReserved) { 109 | *(HMODULE *)lpReserved = g_hModule; 110 | } 111 | } 112 | else { 113 | if (!_CRT_INIT(hInstDll, dwReason, lpReserved)) { 114 | return FALSE; 115 | } 116 | if ((dwReason == DLL_PROCESS_ATTACH) && (!g_hModule)) { 117 | g_hModule = hInstDll; 118 | // start a new thread so DllMain returns 119 | CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ProofOfConcept, hInstDll, 0, 0); 120 | } 121 | } 122 | return TRUE; 123 | } 124 | 125 | int WINAPI ExeMain(int argc, char **argv) { 126 | #pragma comment(linker, "/EXPORT:"__FUNCTION__"="__FUNCDNAME__) 127 | ProofOfConcept(GetModuleHandle(NULL)); 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /ReflectivePolymorphism/ReflectivePolymorphism.c: -------------------------------------------------------------------------------- 1 | #include "ReflectivePolymorphism.h" 2 | 3 | typedef struct { 4 | WORD offset : 12; 5 | WORD type : 4; 6 | } IMAGE_RELOC, *PIMAGE_RELOC; 7 | 8 | DWORD ImageSizeFromHeaders(PDOS_HEADER pDosHeader) { 9 | // Calculate the size of of a PE image from the specified DOS headers. 10 | // 11 | // PDOS_HEADER pDosHeader: The headers to use for the calculation. 12 | // Returns: The size of the PE image. 13 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 14 | PIMAGE_SECTION_HEADER pImgSecHeader = NULL; 15 | PIMAGE_SECTION_HEADER pImgSecHeaderLastRaw = NULL; 16 | PIMAGE_SECTION_HEADER pImgSecHeaderCursor = NULL; 17 | DWORD dwCursor = 0; 18 | 19 | pImgNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 20 | pImgSecHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)pImgNtHeaders + sizeof(IMAGE_NT_HEADERS)); 21 | pImgSecHeaderLastRaw = pImgSecHeader; 22 | for (dwCursor = 0; dwCursor < pImgNtHeaders->FileHeader.NumberOfSections; dwCursor++) { 23 | pImgSecHeaderCursor = &pImgSecHeader[dwCursor]; 24 | if (pImgSecHeaderLastRaw->PointerToRawData < pImgSecHeaderCursor->PointerToRawData) { 25 | pImgSecHeaderLastRaw = pImgSecHeaderCursor; 26 | } 27 | } 28 | return (pImgSecHeaderLastRaw->PointerToRawData + pImgSecHeaderLastRaw->SizeOfRawData); 29 | } 30 | 31 | BOOL RebaseImage(PDOS_HEADER pDosHeader, ULONG_PTR uiBaseFrom, ULONG_PTR uiBaseTo) { 32 | // Rebase the specified PE image by processing the relocation data as 33 | // necessary. 34 | // 35 | // PDOS_HEADER pDosHeader: Pointer to the DOS header of the blob to patch. 36 | // ULONG_PTR uiBaseFrom: The address to rebase the image from. 37 | // ULONG_PTR uiBaseTo: The address to rebase the image to. 38 | // Returns: The function returns TRUE on success. 39 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 40 | PIMAGE_DATA_DIRECTORY pImgDataDirectory = NULL; 41 | PIMAGE_BASE_RELOCATION pImgBaseReloc = NULL; 42 | PIMAGE_RELOC pImgReloc = NULL; 43 | DWORD dwBlockEntries; 44 | ULONG_PTR uiRebaseBlock; 45 | ULONG_PTR uiRebaseDelta; 46 | 47 | pImgNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 48 | pImgDataDirectory = &pImgNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; 49 | if (!pImgDataDirectory->Size) { 50 | return FALSE; 51 | } 52 | 53 | uiRebaseDelta = uiBaseFrom - uiBaseTo; 54 | if (uiRebaseDelta == 0) { 55 | return TRUE; 56 | } 57 | // pImgBaseReloc is now the first entry 58 | pImgBaseReloc = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pDosHeader + PAFromRVA(pDosHeader, pImgDataDirectory->VirtualAddress)); 59 | while (pImgBaseReloc->SizeOfBlock) { 60 | uiRebaseBlock = VAFromRVA(pDosHeader, pImgBaseReloc->VirtualAddress); 61 | if (uiRebaseBlock) { 62 | dwBlockEntries = (pImgBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC); 63 | pImgReloc = (PIMAGE_RELOC)((ULONG_PTR)pImgBaseReloc + sizeof(IMAGE_BASE_RELOCATION)); 64 | 65 | while (dwBlockEntries--) { 66 | if (pImgReloc->type == IMAGE_REL_BASED_DIR64) { 67 | *(ULONG_PTR *)(uiRebaseBlock + pImgReloc->offset) -= uiRebaseDelta; 68 | } 69 | else if (pImgReloc->type == IMAGE_REL_BASED_HIGHLOW) { 70 | *(DWORD *)(uiRebaseBlock + pImgReloc->offset) -= (DWORD)uiRebaseDelta; 71 | } 72 | else if (pImgReloc->type == IMAGE_REL_BASED_HIGH) { 73 | *(WORD *)(uiRebaseBlock + pImgReloc->offset) -= HIWORD(uiRebaseDelta); 74 | } 75 | else if (pImgReloc->type == IMAGE_REL_BASED_LOW) { 76 | *(WORD *)(uiRebaseBlock + pImgReloc->offset) -= LOWORD(uiRebaseDelta); 77 | } 78 | pImgReloc += 1; 79 | } 80 | } 81 | pImgBaseReloc = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pImgBaseReloc + pImgBaseReloc->SizeOfBlock); 82 | } 83 | return TRUE; 84 | } 85 | 86 | BOOL ShadowSectionCopy(PDOS_HEADER pDosHeader, BOOL bCopyTo) { 87 | // Copy data to or from the shadow section. Copying data from the shadow 88 | // section effectively restores content from the backup. Copying data to the 89 | // shadow section effectively updates backup content. 90 | // 91 | // PDOS_HEADER pDosHeader: Pointer to the DOS header of the blob to patch. 92 | // BOOL bCopyTo: Whether to copy to or from the shadow section. 93 | // Returns: The function returns TRUE on success. 94 | PIMAGE_SECTION_HEADER pImgSecHeaderCopy = NULL; 95 | PIMAGE_SECTION_HEADER pImgSecHeaderCursor = NULL; 96 | PIMAGE_SECTION_HEADER pImgSecHeader1 = NULL; 97 | PIMAGE_SECTION_HEADER pImgSecHeader2 = NULL; 98 | DWORD dwImageSize = 0; 99 | 100 | pImgSecHeaderCopy = SectionHeaderFromName(pDosHeader, SHADOW_SECTION_NAME); 101 | if (!pImgSecHeaderCopy) { 102 | return FALSE; 103 | } 104 | if (!pImgSecHeaderCopy->SizeOfRawData) { 105 | return FALSE; 106 | } 107 | 108 | dwImageSize = ImageSizeFromHeaders(pDosHeader); 109 | pImgSecHeaderCursor = (PIMAGE_SECTION_HEADER)((ULONG_PTR)pDosHeader + pImgSecHeaderCopy->PointerToRawData); 110 | while (memcmp(pImgSecHeaderCursor->Name, "\x00\x00\x00\x00\x00\x00\x00\x00", 8)) { 111 | pImgSecHeader2 = pImgSecHeaderCursor; 112 | pImgSecHeaderCursor += 1; 113 | 114 | if (!pImgSecHeader2->SizeOfRawData) { 115 | continue; 116 | } 117 | pImgSecHeader1 = SectionHeaderFromName(pDosHeader, pImgSecHeader2->Name); 118 | if (!pImgSecHeader1) { 119 | return FALSE; 120 | } 121 | if (pImgSecHeader1->SizeOfRawData != pImgSecHeader2->SizeOfRawData) { 122 | return FALSE; 123 | } 124 | if (dwImageSize < (pImgSecHeaderCursor->PointerToRawData + pImgSecHeaderCursor->SizeOfRawData)) { 125 | return FALSE; 126 | } 127 | if (bCopyTo) { 128 | // swap the pointers if we're copying to the shadow section 129 | (ULONG_PTR)pImgSecHeader1 ^= (ULONG_PTR)pImgSecHeader2; 130 | (ULONG_PTR)pImgSecHeader2 ^= (ULONG_PTR)pImgSecHeader1; 131 | (ULONG_PTR)pImgSecHeader1 ^= (ULONG_PTR)pImgSecHeader2; 132 | } 133 | CopyMemory( 134 | (PVOID)((ULONG_PTR)pDosHeader + pImgSecHeader1->PointerToRawData), 135 | (PVOID)((ULONG_PTR)pDosHeader + pImgSecHeader2->PointerToRawData), 136 | pImgSecHeader1->SizeOfRawData 137 | ); 138 | } 139 | return TRUE; 140 | } 141 | 142 | PIMAGE_SECTION_HEADER SectionHeaderFromRVA(PDOS_HEADER pDosHeader, ULONG_PTR pVirtualAddress) { 143 | // Retrieve the section header for the specified Relative Virtual Address 144 | // (RVA). 145 | // 146 | // PDOS_HEADER pDosHeader: A pointer to the associated DOS header. 147 | // ULONG_PTR pVirtualAddress: The RVA of the section header to retrieve. 148 | // Returns: A pointer to the section header or NULL if it could not be 149 | // found. 150 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 151 | PIMAGE_SECTION_HEADER pImgSecHeader = NULL; 152 | PIMAGE_SECTION_HEADER pImgSecHeaderCursor = NULL; 153 | DWORD dwCursor = 0; 154 | 155 | pImgNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 156 | pImgSecHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)pImgNtHeaders + sizeof(IMAGE_NT_HEADERS)); 157 | for (dwCursor = 0; dwCursor < pImgNtHeaders->FileHeader.NumberOfSections; dwCursor++) { 158 | pImgSecHeaderCursor = &pImgSecHeader[dwCursor]; 159 | if (!pImgSecHeaderCursor->SizeOfRawData) { 160 | continue; 161 | } 162 | if (pVirtualAddress < pImgSecHeaderCursor->VirtualAddress) { 163 | continue; 164 | } 165 | if (pVirtualAddress >= pImgSecHeaderCursor->VirtualAddress + pImgSecHeaderCursor->SizeOfRawData) { 166 | continue; 167 | } 168 | return pImgSecHeaderCursor; 169 | } 170 | return NULL; 171 | } 172 | 173 | PIMAGE_SECTION_HEADER SectionHeaderFromName(PDOS_HEADER pDosHeader, PVOID pName) { 174 | // Retrieve the section header for the specified name. 175 | // 176 | // PDOS_HEADER pDosHeader: A pointer to the associated DOS header. 177 | // PVOID pName: A pointer to the section header name to retrieve. 178 | // Returns: A pointer to the section header or NULL if it could not be 179 | // found. 180 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 181 | PIMAGE_SECTION_HEADER pImgSecHeader = NULL; 182 | PIMAGE_SECTION_HEADER pImgSecHeaderCursor = NULL; 183 | DWORD dwCursor = 0; 184 | 185 | pImgNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 186 | pImgSecHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)pImgNtHeaders + sizeof(IMAGE_NT_HEADERS)); 187 | for (dwCursor = 0; dwCursor < pImgNtHeaders->FileHeader.NumberOfSections; dwCursor++) { 188 | pImgSecHeaderCursor = &pImgSecHeader[dwCursor]; 189 | if (memcmp(pImgSecHeaderCursor->Name, pName, 8)) { 190 | continue; 191 | } 192 | return pImgSecHeaderCursor; 193 | } 194 | return NULL; 195 | } 196 | 197 | ULONG_PTR PAFromRVA(PDOS_HEADER pDosHeader, ULONG_PTR pVirtualAddress) { 198 | // Calculate the Physical Address (PA) from the specified Relative Virtual 199 | // Address (RVA). The Physical Address is the offset within the PE image in 200 | // relation to the DOS header. 201 | // 202 | // PDOS_HEADER pDosHeader: A pointer to the associated DOS header. 203 | // ULONG_PTR pVirtualAddress: The RVA to convert to a PA. 204 | // Returns: The physical address of the specified relative virtual address or 205 | // 0 on failure. 206 | PIMAGE_SECTION_HEADER pImgSecHeader = NULL; 207 | 208 | pImgSecHeader = SectionHeaderFromRVA(pDosHeader, pVirtualAddress); 209 | if (!pImgSecHeader) { 210 | return 0; 211 | } 212 | pVirtualAddress -= pImgSecHeader->VirtualAddress; 213 | pVirtualAddress += pImgSecHeader->PointerToRawData; 214 | return pVirtualAddress; 215 | } 216 | 217 | ULONG_PTR VAFromRVA(PDOS_HEADER pDosHeader, ULONG_PTR pVirtualAddress) { 218 | // Calculate the Virtual Address (VA) from the specified Relative Virtual 219 | // Address (RVA). 220 | // 221 | // PDOS_HEADER pDosHeader: A pointer to the associated DOS header. 222 | // ULONG_PTR pVirtualAddress: The RVA to convert to a VA. 223 | // Returns: The virtual address of the specified relative virtual address or 224 | // 0 on failure. 225 | PIMAGE_SECTION_HEADER pImgSecHeader = NULL; 226 | ULONG_PTR uiAddress = 0; 227 | 228 | pImgSecHeader = SectionHeaderFromRVA(pDosHeader, pVirtualAddress); 229 | if (pImgSecHeader) { 230 | uiAddress = (ULONG_PTR)pDosHeader; 231 | uiAddress += pVirtualAddress - pImgSecHeader->VirtualAddress; 232 | uiAddress += pImgSecHeader->PointerToRawData; 233 | } 234 | return uiAddress; 235 | } 236 | -------------------------------------------------------------------------------- /ReflectivePolymorphism/ReflectivePolymorphism.h: -------------------------------------------------------------------------------- 1 | // Redistribution and use in source and binary forms, with or without 2 | // modification, are permitted provided that the following conditions are 3 | // met : 4 | // 5 | // * Redistributions of source code must retain the above copyright 6 | // notice, this list of conditions and the following disclaimer. 7 | // * Redistributions in binary form must reproduce the above 8 | // copyright notice, this list of conditions and the following disclaimer 9 | // in the documentation and/or other materials provided with the 10 | // distribution. 11 | // * Neither the name of the project nor the names of its 12 | // contributors may be used to endorse or promote products derived from 13 | // this software without specific prior written permission. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | // A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT 19 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT 21 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | // Author: Spencer McIntyre (@zeroSteiner) 2018 28 | // Version: 1.1 29 | #ifndef _REFLECTIVE_POLYMORPHISM_H 30 | #define _REFLECTIVE_POLYMORPHISM_H 31 | #define WIN32_LEAN_AND_MEAN 32 | #include 33 | 34 | #define DEREF( name )*(UINT_PTR *)(name) 35 | #define DEREF_64( name )*(DWORD64 *)(name) 36 | #define DEREF_32( name )*(DWORD *)(name) 37 | #define DEREF_16( name )*(WORD *)(name) 38 | #define DEREF_8( name )*(BYTE *)(name) 39 | 40 | // See: https://msdn.microsoft.com/en-us/library/f7f5138s.aspx 41 | #ifdef _WIN64 42 | #define IMAGE_BASE_DLL 0x180000000 43 | #define IMAGE_BASE_EXE 0x140000000 44 | #else 45 | #define IMAGE_BASE_DLL 0x10000000 46 | #define IMAGE_BASE_EXE 0x400000 47 | #endif 48 | 49 | #define SHADOW_SECTION_NAME ".restore" 50 | 51 | typedef struct { 52 | // short is 2 bytes, long is 4 bytes 53 | WORD signature; 54 | WORD lastsize; 55 | WORD nblocks; 56 | WORD nreloc; 57 | WORD hdrsize; 58 | WORD minalloc; 59 | WORD maxalloc; 60 | WORD ss; 61 | WORD sp; 62 | WORD checksum; 63 | WORD ip; 64 | WORD cs; 65 | WORD relocpos; 66 | WORD noverlay; 67 | WORD reserved1[4]; 68 | WORD oem_id; 69 | WORD oem_info; 70 | WORD reserved2[10]; 71 | DWORD e_lfanew; 72 | } DOS_HEADER, *PDOS_HEADER; 73 | 74 | DWORD ImageSizeFromHeaders(PDOS_HEADER pDosHeader); 75 | BOOL RebaseImage(PDOS_HEADER pDosHeader, ULONG_PTR uiBaseFrom, ULONG_PTR uiBaseTo); 76 | 77 | // Shadow section functions 78 | BOOL ShadowSectionCopy(PDOS_HEADER pDosHeader, BOOL bCopyTo); 79 | #define ShadowSectionRestore(pDosHeader) ShadowSectionCopy(pDosHeader, FALSE) 80 | #define ShadowSectionUpdate(pDosHeader) ShadowSectionCopy(pDosHeader, TRUE) 81 | 82 | // Section Header retrieval functions 83 | PIMAGE_SECTION_HEADER SectionHeaderFromName(PDOS_HEADER pDosHeader, PVOID pName); 84 | PIMAGE_SECTION_HEADER SectionHeaderFromRVA(PDOS_HEADER pDosHeader, ULONG_PTR pVirtualAddress); 85 | 86 | // Relative Virtual Address (RVA) conversion functions 87 | ULONG_PTR PAFromRVA(PDOS_HEADER pDosHeader, ULONG_PTR pVirtualAddress); 88 | ULONG_PTR VAFromRVA(PDOS_HEADER pDosHeader, ULONG_PTR pVirtualAddress); 89 | 90 | #endif -------------------------------------------------------------------------------- /ReflectivePolymorphism/ReflectivePolymorphism.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {3F772497-869F-4FE4-A63C-85D8E87CEBFF} 24 | ReflectivePolymorphism 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140_xp 32 | 33 | 34 | DynamicLibrary 35 | false 36 | v140_xp 37 | 38 | 39 | Application 40 | true 41 | v140_xp 42 | 43 | 44 | DynamicLibrary 45 | false 46 | v140_xp 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | $(SolutionDir)$(Configuration)\ 68 | $(PlatformTarget)\$(Configuration)\ 69 | $(ProjectName).$(PlatformTarget) 70 | false 71 | 72 | 73 | $(SolutionDir)$(Configuration)\ 74 | $(PlatformTarget)\$(Configuration)\ 75 | $(ProjectName).$(PlatformTarget) 76 | false 77 | 78 | 79 | $(SolutionDir)$(Configuration)\ 80 | $(PlatformTarget)\$(Configuration)\ 81 | $(ProjectName).$(PlatformTarget) 82 | false 83 | 84 | 85 | $(SolutionDir)$(Configuration)\ 86 | $(PlatformTarget)\$(Configuration)\ 87 | $(ProjectName).$(PlatformTarget) 88 | false 89 | 90 | 91 | 92 | Level3 93 | MaxSpeed 94 | true 95 | true 96 | true 97 | $(SolutionDir)ReflectiveDLLInjection\dll\src;%(AdditionalIncludeDirectories) 98 | REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) 99 | MultiThreaded 100 | 101 | 102 | true 103 | true 104 | 105 | DllMain 106 | Windows 107 | 108 | 109 | python $(SolutionDir)pe_patch.py "$(TargetPath)" "$(TargetPath)" 110 | Patch in the .restore section 111 | 112 | 113 | 114 | 115 | Level3 116 | Disabled 117 | true 118 | $(SolutionDir)ReflectiveDLLInjection\dll\src;%(AdditionalIncludeDirectories) 119 | REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) 120 | MultiThreadedDebug 121 | 122 | 123 | 124 | ExeMain 125 | Windows 126 | 127 | 128 | python $(SolutionDir)pe_patch.py "$(TargetPath)" "$(TargetPath)" 129 | Patch in the .restore section 130 | 131 | 132 | 133 | 134 | Level3 135 | Disabled 136 | true 137 | $(SolutionDir)ReflectiveDLLInjection\dll\src;%(AdditionalIncludeDirectories) 138 | REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) 139 | MultiThreadedDebug 140 | 141 | 142 | 143 | ExeMain 144 | Windows 145 | 146 | 147 | python $(SolutionDir)pe_patch.py "$(TargetPath)" "$(TargetPath)" 148 | Patch in the .restore section 149 | 150 | 151 | 152 | 153 | Level3 154 | MaxSpeed 155 | true 156 | true 157 | true 158 | $(SolutionDir)ReflectiveDLLInjection\dll\src;%(AdditionalIncludeDirectories) 159 | REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) 160 | MultiThreaded 161 | 162 | 163 | true 164 | true 165 | 166 | DllMain 167 | Windows 168 | 169 | 170 | python $(SolutionDir)pe_patch.py "$(TargetPath)" "$(TargetPath)" 171 | Patch in the .restore section 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /ReflectivePolymorphism/ReflectiveTransformer.c: -------------------------------------------------------------------------------- 1 | #include "ReflectiveTransformer.h" 2 | 3 | static PIMAGE_NT_HEADERS ImageNTHeadersFromDOSHeader(PDOS_HEADER pDosHeader) { 4 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 5 | 6 | if (DEREF_32(pDosHeader) != 0x00905a4d) { 7 | return NULL; 8 | } 9 | pImgNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 10 | if (pImgNtHeaders->Signature != 0x4550) { 11 | return NULL; 12 | } 13 | return pImgNtHeaders; 14 | } 15 | 16 | BOOL DOSHeaderIsDLL(PDOS_HEADER pDosHeader) { 17 | // Check the FileHeader Characteristics field to determine whether the PE 18 | // image is marked as both executable (IMAGE_FILE_EXECUTABLE_IMAGE) and 19 | // a DLL (IMAGE_FILE_DLL). 20 | // 21 | // PDOS_HEADER pDosHeader: A pointer to the DOS header to analyze. 22 | // Returns: TRUE if pDosHeader is a DLL. 23 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 24 | WORD wCharacteristics = 0; 25 | 26 | pImgNtHeaders = ImageNTHeadersFromDOSHeader(pDosHeader); 27 | if (!pImgNtHeaders) { 28 | return FALSE; 29 | } 30 | 31 | wCharacteristics = pImgNtHeaders->FileHeader.Characteristics; 32 | wCharacteristics &= (IMAGE_FILE_DLL | IMAGE_FILE_EXECUTABLE_IMAGE); 33 | return wCharacteristics == (IMAGE_FILE_DLL | IMAGE_FILE_EXECUTABLE_IMAGE); 34 | } 35 | 36 | BOOL DOSHeaderIsEXE(PDOS_HEADER pDosHeader) { 37 | // Check the FileHeader Characteristics field to determine whether the PE 38 | // image is marked as both executable (IMAGE_FILE_EXECUTABLE_IMAGE) and 39 | // not a DLL (IMAGE_FILE_DLL). 40 | // 41 | // PDOS_HEADER pDosHeader: A pointer to the DOS header to analyze. 42 | // Returns: TRUE if pDosHeader is an EXE. 43 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 44 | WORD wCharacteristics = 0; 45 | 46 | pImgNtHeaders = ImageNTHeadersFromDOSHeader(pDosHeader); 47 | if (!pImgNtHeaders) { 48 | return FALSE; 49 | } 50 | 51 | wCharacteristics = pImgNtHeaders->FileHeader.Characteristics; 52 | wCharacteristics &= (IMAGE_FILE_DLL | IMAGE_FILE_EXECUTABLE_IMAGE); 53 | return wCharacteristics == IMAGE_FILE_EXECUTABLE_IMAGE; 54 | } 55 | 56 | BOOL ReflectiveTransformerToDLL(PDOS_HEADER pDosHeader, DWORD dwAddressOfEntryPoint) { 57 | // Transform the PE image pDosHeader into a DLL. This updates the FileHeader 58 | // Characteristics field as necessary, updates the OptionalHeader ImageBase 59 | // to the default value for DLL files and sets a new entry point. 60 | // 61 | // PDOS_HEADER pDosHeader: A pointer to the DOS header transform. 62 | // DWORD dwAddressOfEntryPoint: The RVA of the new entry point for the PE 63 | // image. 64 | // Returns: TRUE on success. 65 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 66 | 67 | pImgNtHeaders = ImageNTHeadersFromDOSHeader(pDosHeader); 68 | if (!pImgNtHeaders) { 69 | return FALSE; 70 | } 71 | 72 | if (RebaseImage(pDosHeader, (ULONG_PTR)(pImgNtHeaders->OptionalHeader.ImageBase), IMAGE_BASE_DLL)) { 73 | ShadowSectionUpdate(pDosHeader); 74 | } 75 | 76 | pImgNtHeaders->FileHeader.Characteristics |= IMAGE_FILE_DLL; 77 | pImgNtHeaders->FileHeader.Characteristics |= IMAGE_FILE_EXECUTABLE_IMAGE; 78 | pImgNtHeaders->OptionalHeader.ImageBase = IMAGE_BASE_DLL; 79 | pImgNtHeaders->OptionalHeader.AddressOfEntryPoint = dwAddressOfEntryPoint; 80 | return TRUE; 81 | } 82 | 83 | BOOL ReflectiveTransformerToEXE(PDOS_HEADER pDosHeader, DWORD dwAddressOfEntryPoint) { 84 | // Transform the PE image pDosHeader into an EXE. This updates the FileHeader 85 | // Characteristics field as necessary, updates the OptionalHeader ImageBase 86 | // to the default value for EXE files and sets a new entry point. 87 | // 88 | // PDOS_HEADER pDosHeader: A pointer to the DOS header transform. 89 | // DWORD dwAddressOfEntryPoint: The RVA of the new entry point for the PE 90 | // image. 91 | // Returns: TRUE on success. 92 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 93 | 94 | pImgNtHeaders = ImageNTHeadersFromDOSHeader(pDosHeader); 95 | if (!pImgNtHeaders) { 96 | return FALSE; 97 | } 98 | 99 | if (RebaseImage(pDosHeader, (ULONG_PTR)(pImgNtHeaders->OptionalHeader.ImageBase), IMAGE_BASE_EXE)) { 100 | ShadowSectionUpdate(pDosHeader); 101 | } 102 | 103 | pImgNtHeaders->FileHeader.Characteristics &= ~IMAGE_FILE_DLL; 104 | pImgNtHeaders->FileHeader.Characteristics |= IMAGE_FILE_EXECUTABLE_IMAGE; 105 | pImgNtHeaders->OptionalHeader.ImageBase = IMAGE_BASE_EXE; 106 | pImgNtHeaders->OptionalHeader.AddressOfEntryPoint = dwAddressOfEntryPoint; 107 | return TRUE; 108 | } 109 | 110 | DWORD RVAFromExportName(PDOS_HEADER pDosHeader, LPCSTR lpProcName) { 111 | // Get the relative virtual address (RVA) of an exported function by it's 112 | // name from an unloaded PE image. The return value can then be used as the 113 | // dwAddressOfEntryPoint argument to the ReflectiveTransformerTo* set of 114 | // functions. 115 | // 116 | // PDOS_HEADER pDosHeader: A pointer to the DOS header of the PE image to 117 | // resolve the export from. 118 | // LPCSTR lpProcName: A pointer to the name of the exported function to 119 | // resolve the RVA for. 120 | // Returns: The function returns a non-zero value on success. 121 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 122 | PIMAGE_EXPORT_DIRECTORY pImgExDir = NULL; 123 | PIMAGE_DATA_DIRECTORY pImgDataDir = NULL; 124 | PDWORD pdwExAddress = NULL; 125 | PDWORD pdwExName = NULL; 126 | LPCSTR lpExportName; 127 | DWORD dwCursor; 128 | 129 | pImgNtHeaders = ImageNTHeadersFromDOSHeader(pDosHeader); 130 | if (!pImgNtHeaders) { 131 | return 0; 132 | } 133 | 134 | pImgDataDir = (PIMAGE_DATA_DIRECTORY)&pImgNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; 135 | if (!pImgDataDir->Size) { 136 | return 0; 137 | } 138 | 139 | pImgExDir = (PIMAGE_EXPORT_DIRECTORY)PAFromRVA(pDosHeader, (ULONG_PTR)pImgDataDir->VirtualAddress); 140 | if (!pImgExDir) { 141 | return 0; 142 | } 143 | (ULONG_PTR)pImgExDir += (ULONG_PTR)pDosHeader; 144 | 145 | (ULONG_PTR)pdwExAddress = PAFromRVA(pDosHeader, pImgExDir->AddressOfFunctions); 146 | (ULONG_PTR)pdwExAddress += (ULONG_PTR)pDosHeader; 147 | 148 | (ULONG_PTR)pdwExName = PAFromRVA(pDosHeader, pImgExDir->AddressOfNames); 149 | (ULONG_PTR)pdwExName += (ULONG_PTR)pDosHeader; 150 | 151 | for (dwCursor = 0; dwCursor < pImgExDir->NumberOfFunctions; dwCursor++) { 152 | lpExportName = (LPSTR)PAFromRVA(pDosHeader, (ULONG_PTR)pdwExName[dwCursor]); 153 | if (!lpExportName) { 154 | continue; 155 | } 156 | lpExportName += (ULONG_PTR)pDosHeader; 157 | if (memcmp(lpProcName, lpExportName, strlen(lpProcName))) { 158 | continue; 159 | } 160 | return pdwExAddress[dwCursor]; 161 | } 162 | return 0; 163 | } 164 | -------------------------------------------------------------------------------- /ReflectivePolymorphism/ReflectiveTransformer.h: -------------------------------------------------------------------------------- 1 | // Redistribution and use in source and binary forms, with or without 2 | // modification, are permitted provided that the following conditions are 3 | // met : 4 | // 5 | // * Redistributions of source code must retain the above copyright 6 | // notice, this list of conditions and the following disclaimer. 7 | // * Redistributions in binary form must reproduce the above 8 | // copyright notice, this list of conditions and the following disclaimer 9 | // in the documentation and/or other materials provided with the 10 | // distribution. 11 | // * Neither the name of the project nor the names of its 12 | // contributors may be used to endorse or promote products derived from 13 | // this software without specific prior written permission. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | // A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT 19 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT 21 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | // Author: Spencer McIntyre (@zeroSteiner) 2018 28 | // Version: 1.0 29 | #ifndef _REFLECTIVE_TRANSFORMER_H 30 | #define _REFLECTIVE_TRANSFORMER_H 31 | 32 | #define WIN32_LEAN_AND_MEAN 33 | #include 34 | 35 | #include "ReflectivePolymorphism.h" 36 | 37 | BOOL DOSHeaderIsDLL(PDOS_HEADER pDosHeader); 38 | BOOL DOSHeaderIsEXE(PDOS_HEADER pDosHeader); 39 | 40 | BOOL ReflectiveTransformerToDLL(PDOS_HEADER pDosHeader, DWORD dwAddressOfEntryPoint); 41 | BOOL ReflectiveTransformerToEXE(PDOS_HEADER pDosHeader, DWORD dwAddressOfEntryPoint); 42 | 43 | DWORD RVAFromExportName(PDOS_HEADER pDosHeader, LPCSTR lpProcName); 44 | 45 | #endif -------------------------------------------------------------------------------- /ReflectivePolymorphism/ReflectiveUnloader.c: -------------------------------------------------------------------------------- 1 | #include "ReflectiveUnloader.h" 2 | 3 | static BOOL ReflectiveUnloaderUnimport(PDOS_HEADER pDosHeader) { 4 | // PDOS_HEADER pDosHeader: Pointer to the DOS header of the blob to patch. 5 | // Returns: TRUE on success. 6 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 7 | PIMAGE_DATA_DIRECTORY pImgDataDirectory = NULL; 8 | PIMAGE_IMPORT_DESCRIPTOR pImgImpDesc = NULL; 9 | ULONG_PTR uiValueA; 10 | ULONG_PTR uiValueD; 11 | 12 | pImgNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 13 | pImgDataDirectory = &pImgNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; 14 | if (!pImgDataDirectory->Size) { 15 | return FALSE; 16 | } 17 | 18 | pImgImpDesc = (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)pDosHeader + PAFromRVA(pDosHeader, pImgDataDirectory->VirtualAddress)); 19 | while (pImgImpDesc->Name) { 20 | uiValueD = VAFromRVA(pDosHeader, pImgImpDesc->OriginalFirstThunk); 21 | uiValueA = VAFromRVA(pDosHeader, pImgImpDesc->FirstThunk); 22 | while (DEREF(uiValueA) && DEREF(uiValueD)) { 23 | DEREF(uiValueA) = DEREF(uiValueD); 24 | uiValueA += sizeof(ULONG_PTR); 25 | uiValueD += sizeof(ULONG_PTR); 26 | } 27 | pImgImpDesc += 1; 28 | } 29 | return TRUE; 30 | } 31 | 32 | static BOOL ReflectiveUnloaderUnrelocate(PDOS_HEADER pDosHeader, ULONG_PTR pBaseAddress) { 33 | // PDOS_HEADER pDosHeader: Pointer to the DOS header of the blob to patch. 34 | // ULONG_PTR pBaseAddress: Pointer to the original loaded PE blob. 35 | // Returns: TRUE on success. 36 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 37 | PIMAGE_DATA_DIRECTORY pImgDataDirectory = NULL; 38 | 39 | pImgNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 40 | pImgDataDirectory = &pImgNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; 41 | if (!pImgDataDirectory->Size) { 42 | return FALSE; 43 | } 44 | 45 | return RebaseImage(pDosHeader, pBaseAddress, (ULONG_PTR)(pImgNtHeaders->OptionalHeader.ImageBase)); 46 | } 47 | 48 | VOID ReflectiveUnloaderFree(PVOID pAddress, SIZE_T dwSize) { 49 | // Free memory that was previously allocated by ReflectiveUnloader(). 50 | // 51 | // PVOID pAddress: Pointer to the blob returned by ReflectiveUnloader. 52 | // SIZE_T dwSize: Size of the blob returned by ReflectiveUnloader. 53 | SecureZeroMemory(pAddress, dwSize); 54 | #ifdef DEBUG 55 | VirtualFree(pAddress, dwSize, MEM_DECOMMIT | MEM_RELEASE); 56 | #else 57 | HeapFree(GetProcessHeap(), 0, pAddress); 58 | #endif 59 | return; 60 | } 61 | 62 | PVOID ReflectiveUnloader(HINSTANCE hInstance, PSIZE_T pdwSize) { 63 | // Unload the module indicated by hInstance and return a pointer to it's 64 | // location in memory. If this function fails, NULL is returned. 65 | // 66 | // HINSTANCE hInstance: Handle to the module instance to unload from memory. 67 | // PSIZE_T pdwSize: The size of the returned PE image. 68 | // Returns: A pointer to a blob of the unloaded PE image. 69 | PDOS_HEADER pDosHeader = NULL; 70 | PIMAGE_NT_HEADERS pImgNtHeaders = NULL; 71 | PIMAGE_SECTION_HEADER pImgSecHeader = NULL; 72 | PIMAGE_SECTION_HEADER pImgSecHeaderCursor = NULL; 73 | ULONG_PTR pBaseAddress = 0; 74 | SIZE_T dwImageSize = 0; 75 | DWORD dwCursor = 0; 76 | PVOID pCursor = NULL; 77 | 78 | if (pdwSize) { 79 | *pdwSize = 0; 80 | } 81 | pDosHeader = (PDOS_HEADER)hInstance; 82 | if (DEREF_32(pDosHeader) != 0x00905a4d) { 83 | return NULL; 84 | } 85 | pImgNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 86 | if (pImgNtHeaders->Signature != 0x4550) { 87 | return NULL; 88 | } 89 | 90 | dwImageSize = ImageSizeFromHeaders(pDosHeader); 91 | #ifdef DEBUG 92 | pBaseAddress = (ULONG_PTR)VirtualAlloc(NULL, dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 93 | #else 94 | pBaseAddress = (ULONG_PTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwImageSize); 95 | #endif 96 | if (!pBaseAddress) { 97 | return NULL; 98 | } 99 | 100 | CopyMemory((PVOID)pBaseAddress, (PVOID)pDosHeader, dwImageSize); 101 | pCursor = pDosHeader; 102 | pDosHeader = (PDOS_HEADER)pBaseAddress; 103 | pBaseAddress = (ULONG_PTR)pCursor; 104 | pImgNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)pDosHeader + pDosHeader->e_lfanew); 105 | pImgSecHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)pImgNtHeaders + sizeof(IMAGE_NT_HEADERS)); 106 | 107 | // 0x00400000 for EXEs and 0x10000000 for DLLs 108 | // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx 109 | if (pImgNtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL) { 110 | pImgNtHeaders->OptionalHeader.ImageBase = IMAGE_BASE_DLL; 111 | } 112 | else if (pImgNtHeaders->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) { 113 | pImgNtHeaders->OptionalHeader.ImageBase = IMAGE_BASE_EXE; 114 | } 115 | 116 | for (dwCursor = 0; dwCursor < pImgNtHeaders->FileHeader.NumberOfSections; dwCursor++) { 117 | pImgSecHeaderCursor = &pImgSecHeader[dwCursor]; 118 | if (!pImgSecHeaderCursor->SizeOfRawData) { 119 | continue; 120 | } 121 | pCursor = (PVOID)((ULONG_PTR)pDosHeader + pImgSecHeaderCursor->PointerToRawData); 122 | if (dwImageSize < (pImgSecHeaderCursor->PointerToRawData + pImgSecHeaderCursor->SizeOfRawData)) { 123 | ReflectiveUnloaderFree((PVOID)pDosHeader, dwImageSize); 124 | return NULL; 125 | } 126 | CopyMemory(pCursor, (PVOID)(pBaseAddress + pImgSecHeaderCursor->VirtualAddress), pImgSecHeaderCursor->SizeOfRawData); 127 | } 128 | 129 | ReflectiveUnloaderUnrelocate(pDosHeader, pBaseAddress); 130 | ReflectiveUnloaderUnimport(pDosHeader); 131 | // This step is optional 132 | ShadowSectionRestore(pDosHeader); 133 | 134 | if (pdwSize) { 135 | *pdwSize = dwImageSize; 136 | } 137 | return pDosHeader; 138 | } 139 | -------------------------------------------------------------------------------- /ReflectivePolymorphism/ReflectiveUnloader.h: -------------------------------------------------------------------------------- 1 | // Redistribution and use in source and binary forms, with or without 2 | // modification, are permitted provided that the following conditions are 3 | // met : 4 | // 5 | // * Redistributions of source code must retain the above copyright 6 | // notice, this list of conditions and the following disclaimer. 7 | // * Redistributions in binary form must reproduce the above 8 | // copyright notice, this list of conditions and the following disclaimer 9 | // in the documentation and/or other materials provided with the 10 | // distribution. 11 | // * Neither the name of the project nor the names of its 12 | // contributors may be used to endorse or promote products derived from 13 | // this software without specific prior written permission. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | // A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT 19 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT 21 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | // Author: Spencer McIntyre (@zeroSteiner) 2018 28 | // Version: 1.1 29 | #ifndef _REFLECTIVE_UNLOADER_H 30 | #define _REFLECTIVE_UNLOADER_H 31 | 32 | #include "ReflectivePolymorphism.h" 33 | 34 | // API functions 35 | PVOID ReflectiveUnloader(HINSTANCE hInstance, PSIZE_T pdwSize); 36 | VOID ReflectiveUnloaderFree(PVOID pAddress, SIZE_T dwSize); 37 | 38 | #endif -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/stable/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'Reflective Polymorphism' 23 | copyright = '2018, Spencer McIntyre' 24 | author = 'Spencer McIntyre' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '1.1.1' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | GITHUB_BRANCH = 'master' 42 | GITHUB_REPO = 'zeroSteiner/reflective-polymorphism' 43 | add_module_names = False 44 | 45 | extensions = [ 46 | 'sphinx.ext.autodoc', 47 | 'sphinx.ext.coverage', 48 | 'sphinx.ext.githubpages', 49 | 'sphinx.ext.intersphinx', 50 | 'sphinx.ext.viewcode', 51 | ] 52 | 53 | # Add any paths that contain templates here, relative to this directory. 54 | templates_path = ['_templates'] 55 | 56 | # The suffix(es) of source filenames. 57 | # You can specify multiple suffix as a list of string: 58 | # 59 | # source_suffix = ['.rst', '.md'] 60 | source_suffix = '.rst' 61 | 62 | # The master toctree document. 63 | master_doc = 'index' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # 68 | # This is also used if you do content translation via gettext catalogs. 69 | # Usually you set "language" from the command line for these cases. 70 | language = None 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | # This pattern also affects html_static_path and html_extra_path . 75 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 76 | 77 | # The name of the Pygments (syntax highlighting) style to use. 78 | pygments_style = 'sphinx' 79 | 80 | 81 | # -- Options for HTML output ------------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | html_theme = 'sphinx_rtd_theme' 87 | 88 | # Theme options are theme-specific and customize the look and feel of a theme 89 | # further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | # html_theme_options = {} 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | # Custom sidebar templates, must be a dictionary that maps document names 100 | # to template names. 101 | # 102 | # The default sidebars (for documents that don't match any pattern) are 103 | # defined by theme itself. Builtin themes are using these templates by 104 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 105 | # 'searchbox.html']``. 106 | # 107 | # html_sidebars = {} 108 | 109 | 110 | # -- Options for HTMLHelp output --------------------------------------------- 111 | 112 | # Output file base name for HTML help builder. 113 | htmlhelp_basename = 'ReflectivePolymorphismdoc' 114 | 115 | 116 | # -- Options for LaTeX output ------------------------------------------------ 117 | 118 | latex_elements = { 119 | # The paper size ('letterpaper' or 'a4paper'). 120 | # 121 | # 'papersize': 'letterpaper', 122 | 123 | # The font size ('10pt', '11pt' or '12pt'). 124 | # 125 | # 'pointsize': '10pt', 126 | 127 | # Additional stuff for the LaTeX preamble. 128 | # 129 | # 'preamble': '', 130 | 131 | # Latex figure (float) alignment 132 | # 133 | # 'figure_align': 'htbp', 134 | } 135 | 136 | # Grouping the document tree into LaTeX files. List of tuples 137 | # (source start file, target name, title, 138 | # author, documentclass [howto, manual, or own class]). 139 | latex_documents = [ 140 | (master_doc, 'ReflectivePolymorphism.tex', 'Reflective Polymorphism Documentation', 141 | 'Spencer McIntyre', 'manual'), 142 | ] 143 | 144 | 145 | # -- Options for manual page output ------------------------------------------ 146 | 147 | # One entry per manual page. List of tuples 148 | # (source start file, name, description, authors, manual section). 149 | man_pages = [ 150 | (master_doc, 'reflectivepolymorphism', 'Reflective Polymorphism Documentation', 151 | [author], 1) 152 | ] 153 | 154 | 155 | # -- Options for Texinfo output ---------------------------------------------- 156 | 157 | # Grouping the document tree into Texinfo files. List of tuples 158 | # (source start file, target name, title, author, 159 | # dir menu entry, description, category) 160 | texinfo_documents = [ 161 | (master_doc, 'ReflectivePolymorphism', 'Reflective Polymorphism Documentation', 162 | author, 'ReflectivePolymorphism', 'One line description of project.', 163 | 'Miscellaneous'), 164 | ] 165 | 166 | 167 | # -- Extension configuration ------------------------------------------------- 168 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Reflective Polymorphism Documentation 2 | ===================================== 3 | 4 | This project provides various utilities for the self-modification of PE images 5 | with the intention that they can be incorporated into external projects. 6 | 7 | The source code is available on the `GitHub homepage`_. 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | reflective_polymorphism.rst 14 | reflective_transformer.rst 15 | reflective_unloader.rst 16 | 17 | Overview 18 | -------- 19 | The Reflective Polymorphism projects is currently composed of the following two 20 | components each of which are contained within their respective ``.c`` / ``.h`` 21 | files and are capable of operating independently. 22 | 23 | **ReflectiveTransformer** 24 | Functionality to transform PE files between DLL and EXE formats. 25 | 26 | **ReflectiveUnloader** 27 | Functionality to copy a loaded PE image out of memory and reconstruct a byte 28 | for byte copy of the PE image as it would exist on disk. 29 | 30 | Proof of Concept 31 | ---------------- 32 | 33 | The proof of concept included in the project is the ``Main.c`` file. This can be 34 | compiled into a ``ReflectivePolymorphism.dll`` which is compatible with 35 | `Reflective DLL Injection`_. The resulting executable can then be injected into 36 | an arbitrary process (assuming premissions and architecture constraints are met) 37 | with the `inject.exe`_ utility. Take note of the hash of the DLL file before 38 | proceeding. See the `releases page`_ for pre-built binaries. 39 | 40 | Once the DLL is injected into a process, it will display a message box. This is 41 | used to present the user with an opportunity to delete the original PE file from 42 | disk. After the message box is closed, the following two new files will be 43 | created on the user's desktop. 44 | 45 | **ReflectivePolymorphism.dll** 46 | This is an identical copy of the injected DLL. 47 | 48 | **ReflectivePolymorphism.exe** 49 | This is an EXE version of the original, injected DLL. 50 | 51 | The user can then compare the hashes of the two DLL files to determine that they 52 | are identical. At that point the user can delete the DLLs and run the EXE 53 | version which will create the DLL version again at the same path. 54 | 55 | Indices and tables 56 | ================== 57 | 58 | * :ref:`genindex` 59 | * :ref:`modindex` 60 | * :ref:`search` 61 | 62 | .. _GitHub homepage: https://github.com/zeroSteiner/reflective-polymorphism 63 | .. _inject.exe: https://github.com/stephenfewer/ReflectiveDLLInjection/tree/master/bin 64 | .. _Reflective DLL Injection: https://github.com/stephenfewer/ReflectiveDLLInjection 65 | .. _releases page: https://github.com/zeroSteiner/reflective-unloader/releases 66 | -------------------------------------------------------------------------------- /docs/source/reflective_polymorphism.rst: -------------------------------------------------------------------------------- 1 | .. _Reflective Polymorphism: 2 | 3 | Reflective Polymorphism 4 | ======================= 5 | 6 | The ``ReflectivePolymorphism.c`` and ``ReflectivePolymorphism.h`` contain common 7 | functionality for use by other components in the project. This reduces the 8 | amount of code duplication but also requires users of other components to 9 | include these sources files. 10 | 11 | API Reference 12 | ------------- 13 | 14 | .. c:function:: DWORD ImageSizeFromHeaders(PDOS_HEADER pDosHeader) 15 | 16 | Calculate the size of of a PE image from the specified DOS headers. 17 | 18 | :param PDOS_HEADER pDosHeader: The headers to use for the calculation. 19 | :return: The size of the PE image. 20 | :rtype: DWORD 21 | 22 | .. c:function:: BOOL RebaseImage(PDOS_HEADER pDosHeader, ULONG_PTR uiBaseFrom, ULONG_PTR uiBaseTo) 23 | 24 | Rebase the specified PE image by processing the relocation data as 25 | necessary. 26 | 27 | :param PDOS_HEADER pDosHeader: Pointer to the DOS header of the blob to patch. 28 | :param ULONG_PTR uiBaseFrom: The address to rebase the image from. 29 | :param ULONG_PTR uiBaseTo: The address to rebase the image to. 30 | :return: The function returns ``TRUE`` on success. 31 | :rtype: BOOL 32 | 33 | .. c:function:: BOOL ShadowSectionCopy(PDOS_HEADER pDosHeader, BOOL bCopyTo) 34 | 35 | Copy data to or from the shadow section. Copying data from the shadow 36 | section effectively restores content from the backup. Copying data to the 37 | shadow section effectively updates backup content. See the 38 | :ref:`Shadow Section` description for more details. 39 | 40 | :param PDOS_HEADER pDosHeader: Pointer to the DOS header of the blob to patch. 41 | :param BOOL bCopyTo: Whether to copy to or from the shadow section. 42 | :return: The function returns ``TRUE`` on success. 43 | :rtype: BOOL 44 | 45 | .. c:function:: PIMAGE_SECTION_HEADER SectionHeaderFromRVA(PDOS_HEADER pDosHeader, ULONG_PTR pVirtualAddress) 46 | 47 | Retrieve the section header for the specified Relative Virtual Address 48 | (RVA). 49 | 50 | :param PDOS_HEADER pDosHeader: A pointer to the associated DOS header. 51 | :param ULONG_PTR pVirtualAddress: The RVA of the section header to retrieve. 52 | :return: A pointer to the section header or ``NULL`` if it could not be found. 53 | :rtype: PIMAGE_SECTION_HEADER 54 | 55 | .. c:function:: PIMAGE_SECTION_HEADER SectionHeaderFromName(PDOS_HEADER pDosHeader, PVOID pName) 56 | 57 | Retrieve the section header for the specified name. 58 | 59 | :param PDOS_HEADER pDosHeader: A pointer to the associated DOS header. 60 | :param PVOID pName: A pointer to the section header name to retrieve. 61 | :return: A pointer to the section header or ``NULL`` if it could not be found. 62 | :rtype: PIMAGE_SECTION_HEADER 63 | 64 | .. c:function:: ULONG_PTR PAFromRVA(PDOS_HEADER pDosHeader, ULONG_PTR pVirtualAddress) 65 | 66 | Calculate the Physical Address (PA) from the specified Relative Virtual 67 | Address (RVA). The Physical Address is the offset within the PE image in 68 | relation to the DOS header. 69 | 70 | :param PDOS_HEADER pDosHeader: A pointer to the associated DOS header. 71 | :param ULONG_PTR pVirtualAddress: The RVA to convert to a PA. 72 | :return: The physical address of the specified relative virtual address or 0 on failure. 73 | :rtype: ULONG_PTR 74 | 75 | .. c:function:: ULONG_PTR VAFromRVA(PDOS_HEADER pDosHeader, ULONG_PTR pVirtualAddress) 76 | 77 | Calculate the Virtual Address (VA) from the specified Relative Virtual 78 | Address (RVA). 79 | 80 | :param PDOS_HEADER pDosHeader: A pointer to the associated DOS header. 81 | :param ULONG_PTR pVirtualAddress: The RVA to convert to a VA. 82 | :return: The virtual address of the specified relative virtual address or 0 on failure. 83 | :rtype: ULONG_PTR 84 | -------------------------------------------------------------------------------- /docs/source/reflective_transformer.rst: -------------------------------------------------------------------------------- 1 | .. _Reflective Transformer: 2 | 3 | Reflective Transformer 4 | ====================== 5 | 6 | This is code that can be used to transform a PE image between the Dynamic Link 7 | Library (DLL) and Executable (EXE) formats. This can be combined with the 8 | :ref:`Reflective Unloader` to allow code to transform itself into another 9 | format. 10 | 11 | Usage 12 | ----- 13 | 14 | 1. The build environment is Visual Studio 2017. 15 | 16 | 2. Add the following files to the project: 17 | 18 | - ReflectivePolymorphism.c 19 | - ReflectivePolymorphism.h 20 | - ReflectiveTransformer.c 21 | - ReflectiveTransformer.h 22 | 23 | 3. Set the "Configuration Type" to "Dynamic Library (.dll)". 24 | 25 | API Reference 26 | ------------- 27 | 28 | .. c:function:: BOOL DOSHeaderIsDLL(PDOS_HEADER pDosHeader) 29 | 30 | Check the FileHeader Characteristics field to determine whether the PE image 31 | is marked as both executable (IMAGE_FILE_EXECUTABLE_IMAGE) and a DLL 32 | (IMAGE_FILE_DLL). 33 | 34 | :param PDOS_HEADER pDosHeader: A pointer to the DOS header to analyze. 35 | :return: ``TRUE`` if *pDosHeader* is a DLL. 36 | :rtype: BOOL 37 | 38 | .. c:function:: BOOL DOSHeaderIsEXE(PDOS_HEADER pDosHeader) 39 | 40 | Check the FileHeader Characteristics field to determine whether the PE image 41 | is marked as both executable (IMAGE_FILE_EXECUTABLE_IMAGE) and not a DLL 42 | (IMAGE_FILE_DLL). 43 | 44 | :param PDOS_HEADER pDosHeader: A pointer to the DOS header to analyze. 45 | :return: ``TRUE`` if *pDosHeader* is an EXE. 46 | :rtype: BOOL 47 | 48 | .. c:function:: BOOL ReflectiveTransformerToDLL(PDOS_HEADER pDosHeader, DWORD dwAddressOfEntryPoint) 49 | 50 | Transform the PE image pDosHeader into a DLL. This updates the FileHeader 51 | Characteristics field as necessary, updates the OptionalHeader ImageBase to 52 | the default value for DLL files and sets a new entry point. 53 | 54 | :param PDOS_HEADER pDosHeader: A pointer to the DOS header transform. 55 | :param DWORD dwAddressOfEntryPoint: The RVA of the new entry point for the PE image. 56 | :return: ``TRUE`` on success. 57 | :rtype: BOOL 58 | 59 | .. c:function:: BOOL ReflectiveTransformerToEXE(PDOS_HEADER pDosHeader, DWORD dwAddressOfEntryPoint) 60 | 61 | Transform the PE image pDosHeader into an EXE. This updates the FileHeader 62 | Characteristics field as necessary, updates the OptionalHeader ImageBase to 63 | the default value for EXE files and sets a new entry point. 64 | 65 | :param PDOS_HEADER pDosHeader: A pointer to the DOS header transform. 66 | :param DWORD dwAddressOfEntryPoint: The RVA of the new entry point for the PE image. 67 | :return: ``TRUE`` on success. 68 | :rtype: BOOL 69 | 70 | .. c:function:: DWORD RVAFromExportName(PDOS_HEADER pDosHeader, LPCSTR lpProcName) 71 | 72 | Get the relative virtual address (RVA) of an exported function by it's name 73 | from an unloaded PE image. The return value can then be used as the 74 | *dwAddressOfEntryPoint* argument to the ``ReflectiveTransformerTo*`` set of 75 | functions. 76 | 77 | :param PDOS_HEADER pDosHeader: A pointer to the DOS header of the PE image to resolve the export from. 78 | :param LPCSTR lpProcName: A pointer to the name of the exported function to resolve the RVA for. 79 | :return: The function returns a non-zero value on success. 80 | :rtype: DWORD 81 | -------------------------------------------------------------------------------- /docs/source/reflective_unloader.rst: -------------------------------------------------------------------------------- 1 | .. _Reflective Unloader: 2 | 3 | Reflective Unloader 4 | =================== 5 | 6 | This is code that can be used within a PE file to allow it to reflectively 7 | reconstruct itself in memory at runtime. The result is a byte for byte copy of 8 | the original PE file. This can be combined with `Reflective DLL Injection`_ to 9 | allow code to reconstruct itself after being loaded through an arbitrary means. 10 | 11 | The original PE file will not be modified in memory, this code makes a new copy 12 | of the unloaded target module. 13 | 14 | Usage 15 | ----- 16 | 17 | 1. The build environment is Visual Studio 2017. 18 | 19 | 2. Add the following files to the project: 20 | 21 | - ReflectivePolymorphism.c 22 | - ReflectivePolymorphism.h 23 | - ReflectiveUnloader.c 24 | - ReflectiveUnloader.h 25 | 26 | 3. Once the necessary files have been added, call ``ReflectiveUnloader()`` with 27 | a handle to the module to unload and reconstruct. 28 | 29 | - For an executable this could be ``GetModuleHandle(NULL)``\ :sup:`1` 30 | - For a DLL this could be ``hinstDLL`` from ``DllMain`` 31 | 32 | 4. After compiling the project, run ``pe_patch.py`` to patch in the necessary 33 | shadow section data to the PE file. Without this step, the writable sections 34 | of the PE file will be corrupted in the unloaded copy. (See 35 | `below <#visual-studio-build-event>`__ for how to automate this.) 36 | 37 | .. _Shadow Section: 38 | 39 | Shadow Section 40 | ^^^^^^^^^^^^^^ 41 | 42 | It’s necessary to patch a reflectively unloadable PE file to get a perfect 43 | byte-for-byte copy when it is reconstructed. The patching process creates a 44 | shadow copy of all writable sections in a new section named ``.restore``. This 45 | shadow section is then used when the ``ReflectiveUnloader`` function is called 46 | to restore the original contents of the writable sections. Reflectively 47 | unloadable PE files should be patched once after being compiled. 48 | 49 | If the shadow section is not present, the unloader will simply skip this step. 50 | This allows the unloader to perform the same task for arbitrary unpatched PE 51 | files, however **any modifications to segments made at runtime will be present 52 | in the unloaded PE file**. 53 | 54 | The shadow section is composed of a null-terminated list of 55 | ``IMAGE_SECTION_HEADER`` strcutures. The final, null-termination entry has no 56 | name and is ``sizeof(IMAGE_SECTION_HEADER)`` null bytes. Each section header's 57 | ``PointerToRawData`` field is updated to point to the associated data. Following 58 | the list of section headers is the data for each section. 59 | 60 | Shadow Section Contents 61 | ~~~~~~~~~~~~~~~~~~~~~~~ 62 | 63 | The following is a diagram illustrating an example layout of the shadow 64 | section's contents which includes a backup of a single section (the ``.data`` 65 | section). 66 | 67 | +-----------------------------------------+ 68 | | IMAGE_SECTION_HEADER (Name: .data\\x00) | 69 | +-----------------------------------------+ 70 | | IMAGE_SECTION_HEADER (Name: \\x00) | 71 | +-----------------------------------------+ 72 | | [ .data section contents ] | 73 | +-----------------------------------------+ 74 | 75 | Visual Studio Build Event 76 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 77 | 78 | The ``pe_patch.py`` script can be executed automatically for every build using a 79 | build event. Right click the project in Solution Explorer, then navigate to 80 | ``Configuration Properties > Build Events > Post Build Event`` and adjust the 81 | settings as follows: 82 | 83 | +--------------+---------------------------------------------------------------+ 84 | | Setting Name | Setting Value | 85 | +==============+===============================================================+ 86 | | Command Line | ``python $(SolutionDir)pe_patch.py "$(TargetPath)" | 87 | | | "$(TargetPath)"`` | 88 | +--------------+---------------------------------------------------------------+ 89 | | Description | Patch in the .restore section | 90 | +--------------+---------------------------------------------------------------+ 91 | | Use In Build | Yes | 92 | +--------------+---------------------------------------------------------------+ 93 | 94 | API Reference 95 | ------------- 96 | 97 | .. c:function:: PVOID ReflectiveUnloader(HINSTANCE hInstance, PSIZE_T pdwSize) 98 | 99 | Unload the module indicated by hInstance and return a pointer to it's 100 | location in memory. If this function fails, NULL is returned. 101 | 102 | :param HINSTANCE hInstance: Handle to the module instance to unload from memory. 103 | :param PSIZE_T pdwSize: The size of the returned PE image. 104 | :return: A pointer to a blob of the unloaded PE image. 105 | :rtype: PVOID 106 | 107 | .. c:function:: VOID ReflectiveUnloaderFree(PVOID pAddress, SIZE_T dwSize) 108 | 109 | Free memory that was previously allocated by ReflectiveUnloader(). 110 | 111 | :param PVOID pAddress: Pointer to the blob returned by ReflectiveUnloader. 112 | :param SIZE_T dwSize: Size of the blob returned by ReflectiveUnloader. 113 | 114 | .. _Reflective DLL Injection: https://github.com/stephenfewer/ReflectiveDLLInjection 115 | -------------------------------------------------------------------------------- /pe_patch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # pe_patch.py 5 | # 6 | # Copyright 2017 Spencer McIntyre 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions are 10 | # met: 11 | # 12 | # * Redistributions of source code must retain the above copyright 13 | # notice, this list of conditions and the following disclaimer. 14 | # * Redistributions in binary form must reproduce the above 15 | # copyright notice, this list of conditions and the following disclaimer 16 | # in the documentation and/or other materials provided with the 17 | # distribution. 18 | # * Neither the name of the nor the names of its 19 | # contributors may be used to endorse or promote products derived from 20 | # this software without specific prior written permission. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | # 34 | 35 | from __future__ import division 36 | from __future__ import print_function 37 | from __future__ import unicode_literals 38 | 39 | import argparse 40 | import copy 41 | import math 42 | import sys 43 | 44 | import pefile 45 | 46 | __version__ = '1.0' 47 | 48 | # .restore section contents: 49 | # IMAGE_SECTION_HEADER (Name: .data\x00) 50 | # IMAGE_SECTION_HEADER (Name: \x00) 51 | # [ .data backup ] 52 | 53 | IMAGE_SCN_CNT_INITLIALIZED_DATA = 1 << 6 54 | IMAGE_SCN_MEM_DISCARDABLE = 1 << 25 55 | IMAGE_SCN_MEM_READ = 1 << 30 56 | IMAGE_SCN_MEM_WRITE = 1 << 31 57 | SIZEOF_SECTION_HEADER = 40 58 | 59 | def align_up(number, multiple): 60 | return math.ceil(number / multiple) * multiple 61 | 62 | def pe_insert(pe, data, offset): 63 | pe.__data__ = pe.__data__[:offset] + data + pe.__data__[offset:] 64 | return pe 65 | 66 | def pe_patch(pe): 67 | writable_sections = [] 68 | for section in pe.sections: 69 | if section.Name == b'.restore': 70 | raise RuntimeError('the .restore section is already present in the file') 71 | if section.Characteristics & IMAGE_SCN_MEM_DISCARDABLE: 72 | section.Characteristics ^= IMAGE_SCN_MEM_DISCARDABLE 73 | if section.Characteristics & IMAGE_SCN_MEM_WRITE and section.SizeOfRawData: 74 | writable_sections.append(section) 75 | 76 | if not writable_sections: 77 | raise RuntimeError('no writable sections were found') 78 | 79 | first_raw_section = sorted([s for s in pe.sections if s.SizeOfRawData], key=lambda s: s.PointerToRawData)[0] 80 | last_raw_section = next(reversed(sorted([s for s in pe.sections if s.SizeOfRawData], key=lambda s: s.PointerToRawData))) # as the data is ordered in the file 81 | last_vir_section = next(reversed(sorted([s for s in pe.sections if s.SizeOfRawData], key=lambda s: s.VirtualAddress))) # as the data is ordered in memory 82 | backup_section = pefile.SectionStructure( 83 | pefile.PE.__IMAGE_SECTION_HEADER_format__, 84 | file_offset=pe.sections[-1].get_file_offset() + SIZEOF_SECTION_HEADER, 85 | pe=pe 86 | ) 87 | backup_section.Name = b'.restore' 88 | backup_section.Misc = 0 89 | backup_section.VirtualAddress = align_up(last_vir_section.VirtualAddress + last_vir_section.SizeOfRawData, pe.OPTIONAL_HEADER.SectionAlignment) 90 | backup_section.SizeOfRawData = 0 91 | backup_section.PointerToRawData = last_raw_section.PointerToRawData + last_raw_section.SizeOfRawData 92 | backup_section.PointerToRelocations = 0 93 | backup_section.PointerToLinenumbers = 0 94 | backup_section.NumberOfRelocations = 0 95 | backup_section.NumberOfLinenumbers = 0 96 | backup_section.Characteristics = (IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITLIALIZED_DATA) 97 | 98 | if last_raw_section.__file_offset__ + (SIZEOF_SECTION_HEADER * 2) > first_raw_section.PointerToRawData: 99 | offset = align_up(last_raw_section.__file_offset__ + (SIZEOF_SECTION_HEADER * 2), pe.OPTIONAL_HEADER.FileAlignment) - first_raw_section.PointerToRawData 100 | pe.OPTIONAL_HEADER.SizeOfImage += SIZEOF_SECTION_HEADER 101 | # need to insert data here to make room for the new section, then update 102 | # each section 103 | raise RuntimeError('insufficient space for the new section header') 104 | 105 | pe.sections.append(backup_section) 106 | pe.__structures__.append(backup_section) 107 | pe.FILE_HEADER.NumberOfSections += 1 108 | 109 | new_section = bytearray() 110 | for section_idx, section in enumerate(writable_sections, -1): # start at -1 to account for the null-terminator section header 111 | section = copy.copy(section) 112 | section.Characteristics ^= IMAGE_SCN_MEM_WRITE 113 | section.PointerToRawData = backup_section.PointerToRawData + ((len(writable_sections) - section_idx) * SIZEOF_SECTION_HEADER) 114 | new_section += section.__pack__() 115 | new_section += bytearray(SIZEOF_SECTION_HEADER) # add the null-terminator section header 116 | for section in writable_sections: 117 | new_section += section.get_data() 118 | backup_section.Misc = len(new_section) 119 | new_section += bytearray(int(align_up(len(new_section), pe.OPTIONAL_HEADER.FileAlignment) - len(new_section))) 120 | pe_insert(pe, new_section, backup_section.PointerToRawData) 121 | backup_section.SizeOfRawData = len(new_section) 122 | pe.OPTIONAL_HEADER.SizeOfImage = align_up(pe.OPTIONAL_HEADER.SizeOfImage + len(new_section), pe.OPTIONAL_HEADER.SectionAlignment) 123 | return pe 124 | 125 | def main(): 126 | parser = argparse.ArgumentParser(description='ReflectiveUnloader PE Patcher', conflict_handler='resolve') 127 | parser.add_argument('-v', '--version', action='version', version='%(prog)s Version: ' + __version__) 128 | parser.add_argument('input_file', help='the pe file to patch') 129 | parser.add_argument('output_file', help='the path to write the patched file to') 130 | arguments = parser.parse_args() 131 | 132 | pe_patch(pefile.PE(arguments.input_file)).write(arguments.output_file) 133 | return 0 134 | 135 | if __name__ == '__main__': 136 | sys.exit(main()) 137 | --------------------------------------------------------------------------------