Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cuckoo UM hooks protection #57

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bin/monitor.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ void monitor_init(HMODULE module_handle)
destroy_pe_header(module_handle);

misc_set_monitor_options(cfg.track, cfg.mode, cfg.trigger);

// This is the second part of the UM hook protection
// Register our exception handler
register_veh();
}

void monitor_hook(const char *library, void *module_handle)
Expand Down
1 change: 1 addition & 0 deletions inc/hooking.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ int wmi_win32_process_create_pre(
IWbemServices *services, IWbemClassObject *args, uint32_t *creation_flags
);
void ole_enable_hooks(REFCLSID refclsid);
void register_veh();

extern uintptr_t g_monitor_start;
extern uintptr_t g_monitor_end;
Expand Down
24 changes: 24 additions & 0 deletions sigs/process_native.rst
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,31 @@ Logging::
i heap_dep_bypass exploit_makes_heap_executable(ProcessHandle, orig_base_address, NewAccessProtection)
i process_identifier pid_from_process_handle(ProcessHandle)

Middle::

// This is first part of the implementation to tackle cuckoo's usermode hook removal by malware
if (ProcessHandle == GetCurrentProcess())
{
if (OldAccessProtection != NULL)
{
MEMORY_BASIC_INFORMATION_CROSS mbi;
memset(&mbi, 0, sizeof(mbi));

if (virtual_query(*BaseAddress, &mbi))
{
// TODO: Include other module where the UM hooks need to be protected
if ((size_t)mbi.AllocationBase == (size_t)GetModuleHandle("ntdll.dll") ||
(size_t)mbi.AllocationBase == (size_t)GetModuleHandle("kernel32.dll"))
{
// What we are trying to do here is to prevent all *write* to *read* on page protection
//__debugbreak();
if (NewAccessProtection == PAGE_EXECUTE_READWRITE || NewAccessProtection == PAGE_READWRITE)
virtual_protect(*BaseAddress, *NumberOfBytesToProtect, PAGE_EXECUTE_READ);
}
}
}
}

NtFreeVirtualMemory
===================

Expand Down
146 changes: 146 additions & 0 deletions src/hooking.c
Original file line number Diff line number Diff line change
Expand Up @@ -1071,3 +1071,149 @@ int hook_missing_hooks(HMODULE module_handle)
log_debug("Finished missing hooks @ %p\n", module_handle);
return 0;
}

//
// This is the second part of the UM hook protection
// This VEH handler attempts to resume the AV triggered
// when the hook restoration is failed as we deliberately protect our
// hooked API from being written via NtProtectVirtualMemory hook
//
static LONG WINAPI vector_handler_skip( EXCEPTION_POINTERS *ExceptionInfo)
{
PCONTEXT context;
uint32_t exception_code = 0;

if(ExceptionInfo != NULL)
{
context = ExceptionInfo->ContextRecord;
exception_code = ExceptionInfo->ExceptionRecord->ExceptionCode;
}

if(exception_code == STATUS_ACCESS_VIOLATION)
{
do
{
uintptr_t pc = 0;
#if __x86_64__
pc = context->Rip;
#else
pc = context->Eip;
#endif

// Bail out if the exception address is from cuckoo's own monitor.dll
if (pc >= g_monitor_start && pc < g_monitor_end)
copy_return();

// TODO: We should cover all the MOV opcode instructions
// F3 A4: repe movsb byte ptr es:[edi], byte ptr [esi]
// F0 0F B0/B1: lock cmpxchg [mem], reg
// 86/87 : xchg [mem], reg
// 66 89 : mov [mem], regword
uint8_t *target = (uint8_t*)pc;
if (*target == 0xC6 || *target == 0xC7 || *target == 0xF3 ||
(*target == 0x66 && *(target+1) == 0x89) ||
*target == 0x86 || *target == 0x87 || *target == 0x88 || *target == 0x89 ||
(*target == 0xF0 && *(target+1) == 0x0F && (*(target+2) == 0xB0 || *(target+2) == 0xB1)))
{
// Determine if the AV faulty instruction is related to UM hooks bypass (ie: restore the UM hook by checking the destination address)
char insn[DISASM_BUFSIZ];
if (disasm((void*)pc, insn) == 0)
{
MEMORY_BASIC_INFORMATION_CROSS mbi;
BOOLEAN skipped = FALSE;
void *write_to_address = NULL;
char *temp_insn = strchr(insn, ',');
*temp_insn = '\0';

if (!strchr(insn, '[') && !strchr(insn, ']'))
// Bail out
break;

memset(&mbi, 0, sizeof(mbi));

if (strstr(insn, "eax") || strstr(insn, "rax"))
#if __x86_64__
write_to_address = (void *)context->Rax;
#else
write_to_address = (void *)context->Eax;
#endif
else if (strstr(insn, "ebx") || strstr(insn, "rbx"))
#if __x86_64__
write_to_address = (void *)context->Rbx;
#else
write_to_address = (void *)context->Ebx;
#endif
else if (strstr(insn, "ecx") || strstr(insn, "rcx"))
#if __x86_64__
write_to_address = (void *)context->Rcx;
#else
write_to_address = (void *)context->Ecx;
#endif
else if (strstr(insn, "edx") || strstr(insn, "rdx"))
#if __x86_64__
write_to_address = (void *)context->Rdx;
#else
write_to_address = (void *)context->Edx;
#endif
else if (strstr(insn, "esi") || strstr(insn, "rsi"))
#if __x86_64__
write_to_address = (void *)context->Rsi;
#else
write_to_address = (void *)context->Esi;
#endif
else if (strstr(insn, "edi") || strstr(insn, "rdi"))
#if __x86_64__
write_to_address = (void *)context->Rdi;
#else
write_to_address = (void *)context->Edi;
#endif

if (write_to_address != NULL)
{
if(virtual_query(write_to_address, &mbi))
{
if ((size_t)mbi.AllocationBase == (size_t)GetModuleHandle("ntdll.dll") ||
(size_t)mbi.AllocationBase == (size_t)GetModuleHandle("kernel32.dll"))
// TODO: Should we report it to front end about the UM hook bypass?
skipped = TRUE;
}

// Finally we want to skip the UM hook bypass instruction
if (skipped)
{
// Skip to next instruction
int length = lde((void*)pc);
#if __x86_64__
context->Rip += length;
#else
context->Eip += length;
#endif
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
else
{
// Failed in diassembly
break;
}
}

} while (0);
}

return EXCEPTION_CONTINUE_SEARCH;
}


void register_veh()
{
// We don't need to save the handle to the exception handler
//__debugbreak();
OutputDebugStringA("Entered register_veh");
if (AddVectoredExceptionHandler(1, vector_handler_skip) == NULL)
{
pipe("CRITICAL:Failed to register VEH");
return;
}
}
43 changes: 43 additions & 0 deletions test/bypass_um_hooks.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* To compile: cl.exe bypass_um_hooks.c user32.lib */
#include <stdio.h>
#include <windows.h>

#define CAPTION "Bypass UM Hook"
typedef unsigned char uint8_t;

void main()
{
uint8_t *func = (uint8_t*)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtOpenThread");
uint8_t buffer[10] = { 0 };
SIZE_T read;
DWORD oldprotect = 0;

if (func == NULL)
{
MessageBoxA(NULL, "Failed to resolve NtOpenThread", CAPTION, MB_OK);
return;
}

// The VirtualProtect call should not be able to modify the target address to WRITE
if (!VirtualProtect(func, 0x10, PAGE_EXECUTE_READWRITE, &oldprotect))
{
MessageBoxA(NULL, "Failed to modify protection", CAPTION, MB_OK);
return;
}

ReadProcessMemory(GetCurrentProcess(), func, buffer, sizeof(buffer), &read);

// Simulate the malware's behavior attempting to detect the UM's hooks and then restore the hooked instructions
// Found PUSH xxxxx opcode
if (buffer[0] == 0x68 || buffer[0] == 0xe9)
{
// With the UM hooks protection enabled, it should trigger AV as the target address does not have write permission
*func = 0xb8;
}

// The UM hook protection should skip the AV above and proceed its main payload
// In other words,
// Protection enabled: Observed message box prompt
// Protection disabled: No message box will be prompted
MessageBoxA(NULL, "Done", CAPTION, MB_OK);
}