kevoreilly / capemon

capemon: CAPE's monitor
GNU General Public License v3.0
102 stars 49 forks source link

Windows 10 and PEB module hiding #11

Closed michaelweiser closed 3 years ago

michaelweiser commented 3 years ago

We're trying to use CAPEv2 to analyse malware on Windows 10. After we had various types of analyses fail with changing errors on Windows 10 but succeeding on Windows 7, we dug into a simple test case to try and determine the root cause. We used a simple downloadExe.bat which initially employed powershell.exe but eventually only consisted of an echo hi.

By itself it (obviously) runs fine. When run under observation by capemon.dll it reliably ends in a state where a console window is open and shows error message:

Not enough memory resources are available to process this command.

but no hi. This is when started from CAPE via its web frontend in a freshly resumed Windows 10 VM as well as when run from a debug setup with a manually set up analyzer, analysis.conf and various debug and devel tools at hand (particularly Visual Studio Community 2019 and x96dbg). We focused on only the 32-bit cmd.exe and capemon.dll for now.

Digging into this further we determined that this message is a misleading catch-all follow-up error and that the actual cause is a memory access violation exception in cmd.exe. Digging into this showed LdrResolveDelayLoadedAPI returning a Null Pointer for the ShellExecuteExW function which is indeed delay-loaded in cmd.exe. We were not able to determine a cause for this or reproduce it in simpler hand-written test cases involving a small C program using a delay-loaded ShellExecuteExW to run e.g. nodepad.exe.

By pure coincidence when doing a debug build we then found that disabling the call to hide_module_from_peb() in capemon.c makes our test case run successfully. https://github.com/kevoreilly/capemon/blob/de2c595dffcaf69ebdbfcfcd7fcd42476b8f42e6/capemon.c#L584

Further tests revealed that clearing the LDR_MODULE element using memset() after removing it from the various linked lists seems to cause the fallout we're seeing. https://github.com/kevoreilly/capemon/blob/5f9e800e33bbf78dd816fd107de2ac01be9cd264/misc.c#L799 Disabling it makes our small test-case run through or at least further (see below).

Considering the observed behaviour, it would make sense for LdrResolveDelayLoadedAPI to become confused if module bookkeeping became corrupted ever so slightly by the memset(). It seems unlikely though, that the structure became smaller with Windows 10. Rather I'd speculate there to be more references on it which then point to zeroed memory, perhaps as part of an explicit hardening measure.

What do you make of this? Are you aware of any security measure introduced with Windows 10 that would prevent modification of the PEB and could be disabled?

What might point to a security measure is a peculiar behaviour of Visual Studio we've observed: While Visual Studio is running (even just the startup project/solution selection screen), there seems to be some kind of DLL hosting and/or debugging aid provided by it which tolerates the memset() in capemon.dll and obscures the problem. While it runs, successive analysis runs not only succeed but also seem to share the same, seemingly preloaded capemon.dll even if it's been replaced on disk in the meantime. As soon as Visual Studio is closed, the error behaviour returns. We've not been able to determine what functionality we're dealing with there and any pointer would be welcome.

Also: Disabling the memset() doesn't solve all our problems. Analyses still fail, but increasingly randomly so. What's particularly confounding is that memory access violation exceptions seem to be silently ignored with release as well as debug builds of capemon.dll which greatly hinders debugging. In the case of cmd.exe they lead to above error message (Not enough resources...) but do not terminate the process so we can not get an automatic post-mortem debugger attach e.g. from WinDbg. The same seems to be the case on Windows 7. We've looked but not been able to find a Windows configuration setting for this. Is there any intentional compiler/linker setting or code causing this behaviour we could disable to trigger faulting on all access errors (not explicitly handled) so we can find more of these problems quicker?

raashidbhatt commented 3 years ago

I have had the same issue running capemon_32 bit on WINWORD 32bit , though on 64bit it runs fine

michaelweiser commented 3 years ago

Sorry for taking a long time to respond. Results of tests with Office are that both 32bit and 64bit Winword and Excel also crash with PEB hiding active on my Windows 10 test system. With PEB hiding disabled they get further but still don't fully work. E.g. 64-bit Winword runs into #12. 32-bit Winword seems to crash early on which I haven't dug into yet.

Another tidbit: cmd's message Not enough memory resources are available to process this command. is a ubiquitous catch-all for all kinds of unexpected situations and totally misleading most of the time. With PEB hiding disabled I dug into another instance of this message appearing which eventually turned out to be related to the fact that my cmd is elevated (i.e. implicitly "Run as Administrator" because UAC is fully disabled) and is therefore not allowed to query the current console title using GetConsoleTitleW to prepend Administrator: to it before displaying a prompt. Only after I went all the way down into remote Windows kernel debugging of the related syscall did I notice that this message also appears without capemon being present at all, so is a "normal" Windows bug or rather "works as designed".

kevoreilly commented 3 years ago

Firstly Michael a thank you for investing so much time and effort into diagnosing these Win10 issues. I haven't had time to look into this yet but today merged a PR (https://github.com/kevoreilly/capemon/commit/7697da441eec778f196dcbc29f22d93f68f82cc5) which addresses an issue parsing InLoadOrderModuleList (https://github.com/kevoreilly/capemon/issues/13) within hide_module_from_peb(). So I would ask you to try these fixes to see if this might by some stroke of luck boil down to the same issue.

michaelweiser commented 3 years ago

I was hoping the same when I saw #13 and tried and just retried it now after being merged. Unfortunately, it does not seem to have any direct positive impact on our problem here. I still need to comment out the memset(mod, 0, ...) in hide_module_from_peb() in order for my test case not to run into the access violation leading to that infamous resource message from cmd.exe. This is likely because with regards to the address pointed to by mod and the size of the structure, nothing has changed with the modifications of #13.

In desperation I just tried clearing each structure member individually and struck gold: I can clear every member but BaseAddress without problems. But as soon as I touch BaseAddress, the problem returns. So with the following change, I can more selectively avoid the issue:

@@ -811,7 +811,20 @@ void hide_module_from_peb(HMODULE module_handle)
                        // like InLoadOrderModuleList etc
                        CUT_LIST(mod->HashTableEntry);

-                       memset(mod, 0, sizeof(LDR_DATA_TABLE_ENTRY));
+                       //memset(mod, 0, sizeof(LDR_DATA_TABLE_ENTRY));
+                       memset(&mod->InLoadOrderModuleList, 0, sizeof(mod->InLoadOrderModuleList));
+                       memset(&mod->InMemoryOrderModuleList, 0, sizeof(mod->InMemoryOrderModuleList));
+                       memset(&mod->InInitializationOrderModuleList, 0, sizeof(mod->InMemoryOrderModuleList));
+                       //mod->BaseAddress = 0;
+                       mod->EntryPoint = 0;
+                       mod->SizeOfImage = 0;
+                       memset(&mod->FullDllName, 0, sizeof(mod->FullDllName));
+                       memset(&mod->BaseDllName, 0, sizeof(mod->BaseDllName));
+                       mod->Flags = 0;
+                       mod->LoadCount = 0;
+                       mod->TlsIndex = 0;
+                       memset(&mod->HashTableEntry, 0, sizeof(mod->HashTableEntry));
+                       mod->TimeDateStamp = 0;
                        break;
                }
        }

This would support my initial guess that someone else is still referencing that structure and is using BaseAddress in particular. I think it's not a stretch to point fingers at LdrResolveDelayLoadedAPI. Now we'd need to figure out, where that reference lives and how to clear it as well.

kevoreilly commented 3 years ago

The original code ref for this technique has commented out the memset and marked it optional: http://www.openrce.org/blog/view/844/How_to_hide_dll. Also removing the entry from the linked list is sufficient to achieve the function's goal, so the memset is technically superfluous. You must be right that something is still referencing this entry, but if removing the memset solves the issue and still means the module is removed from the peb's linked list, that's good enough for me. I'll push the fix soon.

kevoreilly commented 3 years ago

This is now pushed so hopefully that's the end of this issue - thanks as ever for the all the effort and feedback!