Skip to content

Commit

Permalink
Land #686, Add Windows Memory Search support using regex
Browse files Browse the repository at this point in the history
  • Loading branch information
adfoster-r7 authored Jan 4, 2024
2 parents 1731613 + 4f19a1c commit 2430d20
Show file tree
Hide file tree
Showing 10 changed files with 882 additions and 0 deletions.
1 change: 1 addition & 0 deletions c/meterpreter/source/common/common_command_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@
#define COMMAND_ID_STDAPI_AUDIO_MIC_START 1115
#define COMMAND_ID_STDAPI_AUDIO_MIC_STOP 1116
#define COMMAND_ID_STDAPI_AUDIO_MIC_LIST 1117
#define COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_SEARCH 1119
#define COMMAND_ID_PRIV_ELEVATE_GETSYSTEM 2001
#define COMMAND_ID_PRIV_FS_BLANK_DIRECTORY_MACE 2002
#define COMMAND_ID_PRIV_FS_BLANK_FILE_MACE 2003
Expand Down
1 change: 1 addition & 0 deletions c/meterpreter/source/extensions/stdapi/server/stdapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Command customCommands[] =
COMMAND_REQ(COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_PROTECT, request_sys_process_memory_protect),
COMMAND_REQ(COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_LOCK, request_sys_process_memory_lock),
COMMAND_REQ(COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_UNLOCK, request_sys_process_memory_unlock),
COMMAND_REQ(COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_SEARCH, request_sys_process_memory_search),

// Thread
COMMAND_REQ(COMMAND_ID_STDAPI_SYS_PROCESS_THREAD_OPEN, request_sys_process_thread_open),
Expand Down
304 changes: 304 additions & 0 deletions c/meterpreter/source/extensions/stdapi/server/sys/process/memory.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,89 @@
#include "precomp.h"
#include "common_metapi.h"
#include "../tiny-regex-c/re.h"

#ifndef __kernel_entry
#define __kernel_entry
#endif

typedef __kernel_entry NTSTATUS(WINAPI* NTQUERYINFORMATIONPROCESS) (HANDLE ProcessHandle, DWORD ProcessInformationClass, LPVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength);

typedef SIZE_T(WINAPI* VIRTUALQUERYEX) (HANDLE hProcess, LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength);

typedef BOOL(WINAPI* CLOSEHANDLE) (HANDLE hObject);

typedef HANDLE(WINAPI* OPENPROCESS) (DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);

typedef FARPROC(WINAPI* GETPROCADDRESS) (HMODULE hModule, LPCSTR lpProcName);

// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FNtReadVirtualMemory.html
// https://ntdoc.m417z.com/ntreadvirtualmemory
typedef NTSTATUS(NTAPI* NTREADVIRTUALMEMORY) (HANDLE ProcessHandle, LPCVOID BaseAddress, LPVOID Buffer, SIZE_T NumberOfBytesToRead, PSIZE_T NumberOfBytesRead);

// http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FMemory%20Management%2FVirtual%20Memory%2FMEMORY_INFORMATION_CLASS.html
typedef enum _MEMORY_INFORMATION_CLASS {
MemoryBasicInformation
} MEMORY_INFORMATION_CLASS, * PMEMORY_INFORMATION_CLASS;

typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING* PUNICODE_STRING;
typedef const UNICODE_STRING* PCUNICODE_STRING;

// https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_object_attributes
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;

typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[16];
PVOID Reserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS;

typedef
VOID
(NTAPI* PPS_POST_PROCESS_INIT_ROUTINE) (
VOID
);

typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
BYTE Reserved4[104];
PVOID Reserved5[52];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved6[128];
PVOID Reserved7[1];
ULONG SessionId;
} PEB, * PPEB;

typedef struct _PROCESS_BASIC_INFORMATION {
PVOID Reserved1;
PPEB PebBaseAddress;
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;
typedef PROCESS_BASIC_INFORMATION* PPROCESS_BASIC_INFORMATION;

typedef enum _PROCESSINFOCLASS {
ProcessBasicInformation = 0,
ProcessWow64Information = 26
} PROCESSINFOCLASS;

/*!
* @brief Allocates memory in the context of the supplied process.
Expand Down Expand Up @@ -339,3 +423,223 @@ DWORD request_sys_process_memory_unlock(Remote *remote, Packet *packet)

return ERROR_SUCCESS;
}

BOOL can_read_memory(DWORD memory_protect)
{
const int page_execute_read = 0x20;
const int page_execute_readwrite = 0x40;
const int page_readonly = 0x02;
const int page_readwrite = 0x04;

return memory_protect == page_execute_read ||
memory_protect == page_execute_readwrite ||
memory_protect == page_readonly ||
memory_protect == page_readwrite;
}

typedef struct {
re_t compiled_regex[MAX_REGEXP_OBJECTS];
unsigned char buffer[MAX_CHAR_CLASS_LEN]; // Used for character strings when "[]" is used.
} RegexNeedle;

#define NEEDLES_MAX (size_t)5
#define MEMORY_BUFFER_SIZE (size_t)(64 * 1024 * 1024)

/// <summary>
/// Add the needle results to a packet. This automatically inserts each result into a new group. Returns ERROR_SUCCESS on success, or 1 on failure.
/// </summary>
/// <param name="packet">The packet to insert the needle group into</param>
/// <returns>ERROR_SUCCESS on success, else non-zero</returns>
NTSTATUS add_needle_results_to_packet(Packet* packet, const unsigned char* memory_buffer_ptr, size_t match_length, size_t match_address, size_t memory_base_address, size_t memory_region_size)
{
if (packet == NULL || memory_buffer_ptr == NULL) { return ERROR_INVALID_PARAMETER; }

dprintf("[MEM SEARCH] Creating results group");
Packet* search_results = met_api->packet.create_group();
if (search_results == NULL) { dprintf("[MEM SEARCH] Could not create search result group"); return ERROR_OUTOFMEMORY; }

dprintf("[MEM SEARCH] Adding results to packet group");
// Note: This raw data needs to be read from the buffer we copied. Trying to read it from mem.BaseAddress directly will make us crash.
met_api->packet.add_tlv_raw(search_results, TLV_TYPE_MEMORY_SEARCH_MATCH_STR, (LPVOID)memory_buffer_ptr, (DWORD)match_length + 1);
met_api->packet.add_tlv_qword(search_results, TLV_TYPE_MEMORY_SEARCH_MATCH_ADDR, match_address);
met_api->packet.add_tlv_qword(search_results, TLV_TYPE_MEMORY_SEARCH_START_ADDR, memory_base_address);
met_api->packet.add_tlv_qword(search_results, TLV_TYPE_MEMORY_SEARCH_SECT_LEN, memory_region_size);
met_api->packet.add_tlv_uint(search_results, TLV_TYPE_MEMORY_SEARCH_MATCH_LEN, (UINT)match_length);

met_api->packet.add_group(packet, TLV_TYPE_MEMORY_SEARCH_RESULTS, search_results);

return ERROR_SUCCESS;
}

static HMODULE hKernel32 = NULL;
static HMODULE hNTDLL = NULL;

static GETPROCADDRESS fGetProcAddress = NULL;
static OPENPROCESS fOpenProcess = NULL;
static CLOSEHANDLE fCloseHandle = NULL;
static VIRTUALQUERYEX fVirtualQueryEx = NULL;
static NTREADVIRTUALMEMORY fNtReadVirtualMemory = NULL;

NTSTATUS setup_handles()
{
if ((hKernel32 = GetModuleHandleA("kernel32.dll")) == NULL) { dprintf("[MEM SEARCH] Could not get kernel32.dll handle"); return ERROR_INVALID_HANDLE; }

if ((hNTDLL = GetModuleHandleA("ntdll.dll")) == NULL) { dprintf("[MEM SEARCH] Could not get ntdll.dll handle"); return ERROR_INVALID_HANDLE; }

if ((fGetProcAddress = (GETPROCADDRESS)GetProcAddress(hKernel32, "GetProcAddress")) == NULL) { dprintf("[MEM SEARCH] Could not get GetProcAddress handle"); return ERROR_INVALID_ADDRESS; }

if ((fVirtualQueryEx = (VIRTUALQUERYEX)fGetProcAddress(hKernel32, "VirtualQueryEx")) == NULL) { dprintf("[MEM SEARCH] Could not get VirtualQueryEx handle"); return ERROR_INVALID_ADDRESS; }

if ((fOpenProcess = (OPENPROCESS)fGetProcAddress(hKernel32, "OpenProcess")) == NULL) { dprintf("[MEM SEARCH] Could not get OpenProcess handle"); return ERROR_INVALID_ADDRESS; }

if ((fCloseHandle = (CLOSEHANDLE)fGetProcAddress(hKernel32, "CloseHandle")) == NULL) { dprintf("[MEM SEARCH] Could not get CloseHandle handle"); return ERROR_INVALID_ADDRESS; }

if ((fNtReadVirtualMemory = (NTREADVIRTUALMEMORY)fGetProcAddress(hNTDLL, "NtReadVirtualMemory")) == NULL ) { dprintf("[MEM SEARCH] Could not get NtReadVirtualMemory handle"); return ERROR_INVALID_ADDRESS; }

return ERROR_SUCCESS;
}

/*
* Read through all of a process's virtual memory in the search for regular expression needles.
*
* req: TLV_TYPE_PID - The target process ID.
* req: TLV_TYPE_MEMORY_SEARCH_NEEDLE - The regular expression needle to search for.
* req: TLV_TYPE_UINT - The minimum length of a match.
* req: TLV_TYPE_MEMORY_SEARCH_MATCH_LEN - The maximum length of a match.
*/
DWORD request_sys_process_memory_search(Remote* remote, Packet* packet)
{
Packet* response = met_api->packet.create_response(packet);
DWORD result = ERROR_SUCCESS;
unsigned char* memory_buffer = NULL;
size_t needle_enum_index = 0;
HANDLE process_handle = NULL;
RegexNeedle regex_needles[NEEDLES_MAX] = { NULL };

dprintf("[MEM SEARCH] Getting PID");
const DWORD pid = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_PID);
if (pid == 0) { result = ERROR_INVALID_PARAMETER; goto done; }
dprintf("[MEM SEARCH] Searching PID: %lu", pid);

Tlv needle_tlv = { 0 };
while (needle_enum_index < NEEDLES_MAX && met_api->packet.enum_tlv(packet, (DWORD)needle_enum_index, TLV_TYPE_MEMORY_SEARCH_NEEDLE, &needle_tlv) == ERROR_SUCCESS)
{
dprintf("[MEM SEARCH] Compiling needle regex from TLV");
const int result = re_compile(needle_tlv.buffer, needle_tlv.header.length - 1, (re_t)&regex_needles[needle_enum_index].compiled_regex, (unsigned char*)&regex_needles[needle_enum_index].buffer);
if (result != ERROR_SUCCESS)
{
dprintf("[MEM SEARCH] Failed to setup compile needle regex from TLV packet");
goto done;
}

needle_enum_index++;
}

dprintf("[MEM SEARCH] Getting Match Lengths");
const size_t min_match_length = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_UINT);
const size_t max_match_length = met_api->packet.get_tlv_value_uint(packet, TLV_TYPE_MEMORY_SEARCH_MATCH_LEN);
if (min_match_length > max_match_length || max_match_length == 0) { dprintf("[MEM SEARCH] Incorrect min or max match lengths"); result = ERROR_INVALID_PARAMETER; goto done; }
const size_t current_max_match_length = max_match_length;

dprintf("[MEM SEARCH] Getting handles & proc addresses");
if ((result = setup_handles()) != ERROR_SUCCESS)
{
dprintf("[MEM SEARCH] Could not set up all necessary handles & proc addresses");
goto done;
}

const DWORD process_vm_read = 0x0010;
const DWORD process_query_information = 0x0400;
const DWORD wanted_process_perms = process_vm_read | process_query_information;

dprintf("[MEM SEARCH] Opening process");
process_handle = fOpenProcess(wanted_process_perms, FALSE, pid);
if (process_handle == NULL) { dprintf("[MEM SEARCH] Could not get process handle"); result = ERROR_INVALID_HANDLE; goto done; }

MEMORY_BASIC_INFORMATION mem = { 0 };
dprintf("[MEM SEARCH] Allocating buffer for storing process memory");
memory_buffer = (unsigned char*)malloc(MEMORY_BUFFER_SIZE * sizeof(unsigned char));
if (memory_buffer == NULL) { dprintf("[MEM SEARCH] Could not allocate memory buffer"); result = ERROR_OUTOFMEMORY; goto done; }

for (size_t current_ptr = 0; fVirtualQueryEx(process_handle, (LPCVOID)current_ptr, &mem, sizeof(mem)); current_ptr += mem.RegionSize)
{
if (!can_read_memory(mem.Protect)) { continue; }

size_t memory_region_offset = 0;
// Note: This currently does not support regex'ing over multiple memory regions.
// e.g.
// regex = "my_password.*";
// | ....my_pas | sword.... |
while (mem.RegionSize > memory_region_offset)
{
const size_t leftover_bytes = mem.RegionSize - memory_region_offset;
const size_t bytes_to_read = min(leftover_bytes, MEMORY_BUFFER_SIZE * sizeof(unsigned char));
dprintf("[MEM SEARCH] Leftover Bytes count: %llu", leftover_bytes);
dprintf("[MEM SEARCH] Bytes to read: %llu", bytes_to_read);
size_t bytes_read = 0;

const size_t read_address = (size_t)mem.BaseAddress + memory_region_offset;
// Note: This will read up to a maximum of bytes_to_read OR to the end of the memory region if the end of it has been reached.
if (fNtReadVirtualMemory(process_handle, (LPCVOID)read_address, memory_buffer, bytes_to_read, &bytes_read) != ERROR_SUCCESS)
{
dprintf("[MEM SEARCH] Failed to read some virtual memory for process, skipping %u bytes", bytes_to_read);
memory_region_offset += bytes_to_read;
continue;
}

dprintf("[MEM SEARCH] Read %llu bytes", bytes_read);
// Note: Increment the offset so that we aren't stuck in an infinite loop, trying to read zero bytes from the same pointer.
if (bytes_read == 0) { dprintf("[MEM SEARCH] Read zero bytes from a readable memory region"); memory_region_offset += bytes_to_read; continue; }

for (size_t current_needle_index = 0; current_needle_index < needle_enum_index; current_needle_index++)
{
size_t current_buffer_offset = 0;
size_t match_length = 0;
int match_result = -1;

do
{
const unsigned char* current_buffer_ptr = memory_buffer + current_buffer_offset;
const size_t bytes_to_regex = bytes_read - current_buffer_offset;

match_result = re_matchp((re_t)&regex_needles[current_needle_index].compiled_regex, current_buffer_ptr, bytes_to_regex, current_max_match_length, &match_length);

if (match_result != -1)
{
const size_t match_address = read_address + current_buffer_offset + match_result;
dprintf("[MEM SEARCH] -- ! FOUND A REGEX MATCH ! --");
dprintf("[MEM SEARCH] Address: %p", match_address);

if (match_length < min_match_length)
{
dprintf("[MEM SEARCH] Match length was too short, skipping.");
current_buffer_offset += (match_result + match_length);
continue;
}

const unsigned char* memory_buffer_ptr = memory_buffer + current_buffer_offset + match_result;
if (add_needle_results_to_packet(response, memory_buffer_ptr, match_length, match_address, (size_t)mem.BaseAddress, mem.RegionSize) != ERROR_SUCCESS)
{
dprintf("[MEM SEARCH] Adding search results to packet was not successful");
}

current_buffer_offset += (match_result + match_length);
}
} while (result != -1);
}

memory_region_offset += bytes_to_read;
}
}

result = ERROR_SUCCESS;

done:
dprintf("[MEM SEARCH] Memory Search complete.");
if (memory_buffer != NULL) { dprintf("[MEM SEARCH] Freeing process memory buffer."); free(memory_buffer); }
if (process_handle != NULL) { dprintf("[MEM SEARCH] Closing process handle."); fCloseHandle(process_handle); }

dprintf("[MEM SEARCH] Transmitting response");
met_api->packet.transmit_response(result, remote, response);
return ERROR_SUCCESS;
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ DWORD request_sys_process_memory_query(Remote *remote, Packet *packet);
DWORD request_sys_process_memory_protect(Remote *remote, Packet *packet);
DWORD request_sys_process_memory_lock(Remote *remote, Packet *packet);
DWORD request_sys_process_memory_unlock(Remote *remote, Packet *packet);
DWORD request_sys_process_memory_search(Remote *remote, Packet *packet);

// Thread
DWORD request_sys_process_thread_open(Remote *remote, Packet *packet);
Expand Down
9 changes: 9 additions & 0 deletions c/meterpreter/source/extensions/stdapi/stdapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@
#define TLV_TYPE_REGISTER_VALUE_32 MAKE_CUSTOM_TLV( TLV_META_TYPE_UINT, TLV_TYPE_EXTENSION_STDAPI, 2542 )
#define TLV_TYPE_REGISTER MAKE_CUSTOM_TLV( TLV_META_TYPE_GROUP, TLV_TYPE_EXTENSION_STDAPI, 2550 )

// Memory - Taken from Mettle: https://github.com/rapid7/mettle/blob/master/mettle/src/tlv_types.h#L262
#define TLV_TYPE_MEMORY_SEARCH_NEEDLE MAKE_CUSTOM_TLV( TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_STDAPI, 2650 )
#define TLV_TYPE_MEMORY_SEARCH_RESULTS MAKE_CUSTOM_TLV( TLV_META_TYPE_GROUP, TLV_TYPE_EXTENSION_STDAPI, 2651 )
#define TLV_TYPE_MEMORY_SEARCH_MATCH_LEN MAKE_CUSTOM_TLV( TLV_META_TYPE_UINT, TLV_TYPE_EXTENSION_STDAPI, 2652 )
#define TLV_TYPE_MEMORY_SEARCH_START_ADDR MAKE_CUSTOM_TLV( TLV_META_TYPE_QWORD, TLV_TYPE_EXTENSION_STDAPI, 2653 )
#define TLV_TYPE_MEMORY_SEARCH_SECT_LEN MAKE_CUSTOM_TLV( TLV_META_TYPE_QWORD, TLV_TYPE_EXTENSION_STDAPI, 2654 )
#define TLV_TYPE_MEMORY_SEARCH_MATCH_ADDR MAKE_CUSTOM_TLV( TLV_META_TYPE_QWORD, TLV_TYPE_EXTENSION_STDAPI, 2655 )
#define TLV_TYPE_MEMORY_SEARCH_MATCH_STR MAKE_CUSTOM_TLV( TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_STDAPI, 2656 )

// Registry
#define TLV_TYPE_HKEY MAKE_CUSTOM_TLV( TLV_META_TYPE_QWORD, TLV_TYPE_EXTENSION_STDAPI, 1000 )
#define TLV_TYPE_ROOT_KEY TLV_TYPE_HKEY
Expand Down
3 changes: 3 additions & 0 deletions c/meterpreter/source/tiny-regex-c/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# tiny-regex-c

This library is taken from https://github.com/kokke/tiny-regex-c/tree/2d306a5a71128853d18292e8bb85c8e745fbc9d0 - with changes to support null-bytes.
Loading

0 comments on commit 2430d20

Please sign in to comment.