Skip to content
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
145 changes: 145 additions & 0 deletions capemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "unhook.h"
#include "bson.h"
#include "Shlwapi.h"
#ifdef _WIN64
#include <delayimp.h>
#endif

// Allow debug mode to be turned on at compilation time.
#ifdef CUCKOODBG
Expand Down Expand Up @@ -565,6 +568,136 @@ extern CRITICAL_SECTION readfile_critsec, g_mutex, g_writing_log_buffer_mutex, g
BOOLEAN g_dll_main_complete;
OSVERSIONINFOA g_osverinfo;

//
// Returns TRUE if the current process is a WoW64 process (i.e. a 32-bit image
// hosted by the 64-bit kernel). Valid for both the 32-bit and the 64-bit build
// of the monitor: ProcessWow64Information yields the PEB32 address, which is
// non-NULL only under WoW64.
//
// NtQueryInformationProcess is resolved at runtime because we do not link
// against ntdll.lib, and we cannot use the hooked Old_/New_ variants here as
// this may run before set_hooks().
//
static BOOL is_wow64_process(void)
{
static _NtQueryInformationProcess pQueryInfo;
ULONG_PTR wow64_peb = 0;
ULONG ret_len = 0;

if (!pQueryInfo) {
HMODULE ntdll = GetModuleHandleA("ntdll");
if (!ntdll)
return FALSE;
*(FARPROC *)&pQueryInfo = GetProcAddress(ntdll, "NtQueryInformationProcess");
if (!pQueryInfo)
return FALSE;
}

if (pQueryInfo(GetCurrentProcess(), ProcessWow64Information, &wow64_peb, sizeof(wow64_peb), &ret_len) < 0)
return FALSE;

return wow64_peb != 0;
}

#ifdef _WIN64
//
// The x64 build delay-loads its high-level dependencies (see DelayLoadDLLs in
// capemon.vcxproj) so that injection does not fail during import resolution in
// a process that has not mapped them yet.
//
// Delay loading on its own only moves the load to the first call, which may
// well be from inside one of our own hooks - that re-enters the hooked
// LoadLibrary/LdrLoadDll path and can run under a loader lock we do not
// control. So resolve them explicitly here instead. This runs before
// set_hooks(), which is what makes the LoadLibrary calls below safe.
//
// Policy for WoW64 targets: only the modules the monitor itself needs during
// bring-up are forced in. A WoW64 process normally has no 64-bit modules
// besides ntdll and the wow64 layer, and force-mapping the full set into it is
// exactly what the delay-load change is meant to avoid. Anything else stays
// lazy.
//
static const char *g_delay_loaded_dlls[] = {
"advapi32.dll",
"user32.dll",
"ws2_32.dll",
"crypt32.dll",
"shlwapi.dll",
"ole32.dll",
"shell32.dll",
"setupapi.dll",
"oleaut32.dll",
"netapi32.dll",
"bcrypt.dll",
};

// needed by hkcu_init() and log_environ() during DllMain
static const char *g_delay_loaded_dlls_minimal[] = {
"advapi32.dll",
};

static const char *g_delay_load_failures[ARRAYSIZE(g_delay_loaded_dlls)];
static unsigned int g_delay_load_failure_count;

static void resolve_delay_loaded_dlls(void)
{
const char **dlls;
unsigned int count, i;

if (is_wow64_process()) {
dlls = g_delay_loaded_dlls_minimal;
count = ARRAYSIZE(g_delay_loaded_dlls_minimal);
}
else {
dlls = g_delay_loaded_dlls;
count = ARRAYSIZE(g_delay_loaded_dlls);
}

for (i = 0; i < count; i++) {
if (GetModuleHandleA(dlls[i]))
continue;
if (!LoadLibraryA(dlls[i]) && g_delay_load_failure_count < ARRAYSIZE(g_delay_load_failures))
g_delay_load_failures[g_delay_load_failure_count++] = dlls[i];
}
}

//
// Deferred because this runs before read_config(): DebugOutput() consults
// g_config to decide between OutputDebugString and the log pipe, so it cannot
// produce anything useful until the config has been parsed.
//
static void report_delay_load_failures(void)
{
unsigned int i;

for (i = 0; i < g_delay_load_failure_count; i++)
DebugOutput("resolve_delay_loaded_dlls: unable to load %s\n", g_delay_load_failures[i]);
}

//
// Reported when the delay-load helper cannot satisfy a call. Returning NULL
// leaves the helper to raise its exception as usual - there is nothing valid to
// return - but at least the failing module/import ends up in the log instead of
// surfacing as an opaque crash.
//
static FARPROC WINAPI capemon_delayload_failure_hook(unsigned int dliNotify, PDelayLoadInfo pdli)
{
if (dliNotify == dliFailLoadLib)
DebugOutput("delay-load: failed to load %s (error %d)\n", pdli->szDll, pdli->dwLastError);
else if (dliNotify == dliFailGetProc) {
if (pdli->dlp.fImportByName)
DebugOutput("delay-load: failed to resolve %s!%s (error %d)\n", pdli->szDll, pdli->dlp.szProcName, pdli->dwLastError);
else
DebugOutput("delay-load: failed to resolve %s ordinal %d (error %d)\n", pdli->szDll, pdli->dlp.dwOrdinal, pdli->dwLastError);
}

return NULL;
}

// picked up by delayimp.lib
ExternC const PfnDliHook __pfnDliFailureHook2 = capemon_delayload_failure_hook;
#endif

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
{
lasterror_t lasterror;
Expand All @@ -587,6 +720,12 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
g_osverinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
GetVersionEx(&g_osverinfo);

#ifdef _WIN64
// before anything that touches a delay-loaded import, including the
// advapi32 lookup in resolve_runtime_apis() below
resolve_delay_loaded_dlls();
#endif

resolve_runtime_apis();

init_private_heap();
Expand All @@ -608,6 +747,11 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
// read the config settings
read_config();

#ifdef _WIN64
// deferred from resolve_delay_loaded_dlls(): DebugOutput needs g_config
report_delay_load_failures();
#endif

if (g_config.standalone) {
// initialize these because some hooks behave badly when they are empty
if (!g_config.w_analyzer[0]) {
Expand Down Expand Up @@ -667,6 +811,7 @@ BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
// initialise CAPE
CAPE_init();


// adds our own DLL range as well, since the hiding is done later
add_all_dlls_to_dll_ranges();

Expand Down
4 changes: 2 additions & 2 deletions capemon.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
<AdditionalLibraryDirectories>$(ProjectDir)x64\Debug;$(ProjectDir)\libyara\lib</AdditionalLibraryDirectories>
<BaseAddress>0x30000000</BaseAddress>
<SetChecksum>true</SetChecksum>
<DelayLoadDLLs>advapi32.dll;user32.dll;ws2_32.dll;crypt32.dll;shlwapi.dll;ole32.dll;shell32.dll;oleaut32.dll;netapi32.dll;bcrypt.dll</DelayLoadDLLs>
<DelayLoadDLLs>advapi32.dll;user32.dll;ws2_32.dll;crypt32.dll;shlwapi.dll;ole32.dll;shell32.dll;setupapi.dll;oleaut32.dll;netapi32.dll;bcrypt.dll</DelayLoadDLLs>
</Link>
<PreBuildEvent>
<Command>
Expand Down Expand Up @@ -206,7 +206,7 @@
<SetChecksum>true</SetChecksum>
<LinkErrorReporting>NoErrorReport</LinkErrorReporting>
<ModuleDefinitionFile>capemon.def</ModuleDefinitionFile>
<DelayLoadDLLs>advapi32.dll;user32.dll;ws2_32.dll;crypt32.dll;shlwapi.dll;ole32.dll;shell32.dll;oleaut32.dll;netapi32.dll;bcrypt.dll</DelayLoadDLLs>
<DelayLoadDLLs>advapi32.dll;user32.dll;ws2_32.dll;crypt32.dll;shlwapi.dll;ole32.dll;shell32.dll;netapi32.dll;bcrypt.dll</DelayLoadDLLs>
</Link>
<PreBuildEvent>
<Command>
Expand Down
31 changes: 30 additions & 1 deletion config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,35 @@ void parse_config_line(char* line)
}
}

//
// Local equivalent of shlwapi!PathRemoveFileSpec. read_config() runs very early
// in DllMain, before we have resolved the delay-loaded imports, so it must not
// be the first thing to touch shlwapi. Same semantics: strips the trailing path
// component in place, leaves a bare root ("C:\") alone.
//
static void strip_filespec(char *path)
{
char *sep, *p;

if (!path || !*path)
return;

sep = NULL;
for (p = path; *p; p++) {
if (*p == '\\' || *p == '/')
sep = p;
}

if (!sep)
return;

// keep the separator for a root path such as "C:\"
if (sep == path || (sep == path + 2 && path[1] == ':'))
sep[1] = '\0';
else
*sep = '\0';
}

void read_config(void)
{
char buf[32768], config_fname[MAX_PATH];
Expand Down Expand Up @@ -1517,7 +1546,7 @@ void read_config(void)
// look for the config in monitor directory
memset(g_config.analyzer, 0, MAX_PATH);
strncpy(g_config.analyzer, our_dll_path, strlen(our_dll_path));
PathRemoveFileSpec(g_config.analyzer); // remove filename
strip_filespec(g_config.analyzer); // remove filename
sprintf(config_fname, "%s\\%u.ini", g_config.analyzer, GetCurrentProcessId());

strcpy(g_config.results, g_config.analyzer);
Expand Down
24 changes: 22 additions & 2 deletions log.c
Original file line number Diff line number Diff line change
Expand Up @@ -1446,8 +1446,25 @@ void log_hook_restoration(const hook_t *h)
DWORD g_log_thread_id;
DWORD g_logwatcher_thread_id;


//
// log_init() one-shot state. Note we deliberately do *not* block a concurrent
// caller until initialization completes: log_init() can be reached from a hook
// (see LdrLoadDll in hook_special.c), and spinning there risks deadlocking
// against the loader lock. Losing a few early records is acceptable - every
// consumer of g_buffer already handles it being NULL.
//
#define LOG_INIT_NONE 0
#define LOG_INIT_RUNNING 1
#define LOG_INIT_DONE 2

volatile LONG g_log_initialized = LOG_INIT_NONE;

void log_init(int debug)
{
if (InterlockedCompareExchange(&g_log_initialized, LOG_INIT_RUNNING, LOG_INIT_NONE) != LOG_INIT_NONE)
return;

g_buffer = calloc(1, BUFFERSIZE);

g_log_flush = CreateEvent(NULL, FALSE, FALSE, NULL);
Expand All @@ -1460,7 +1477,7 @@ void log_init(int debug)
g_log_handle = CreateFileA(g_config.logserver, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (g_log_handle == INVALID_HANDLE_VALUE) {
pipe("CRITICAL:Error initializing logging!");
return;
goto out;
}
}

Expand All @@ -1470,7 +1487,7 @@ void log_init(int debug)
char* filename = GetResultsPath("API");
if (!filename) {
pipe("CRITICAL:Error initializing debug logging!");
return;
goto out;
}
num_to_string(pid, sizeof(pid), GetCurrentProcessId());
strcat(filename, "\\");
Expand All @@ -1485,6 +1502,9 @@ void log_init(int debug)
log_environ();
// flushing here so host can create files / keep timestamps
log_flush();

out:
InterlockedExchange(&g_log_initialized, LOG_INIT_DONE);
}

void log_free()
Expand Down
Loading