☰
Sliver 中的 OPFOR:纯 Go 的 Sleep 语言与 Aggressor Script(.cna)运行时解析与实战
2026/9/25 2:51:11 网站建设 项目流程
  • 网络安全

【免费下载链接】sliver

Adversary Emulation Framework

项目地址:https://gitcode.com/gh_mirrors/sl/sliver
点击查看免费下载

OPFOR 是随 Sliver 仓库一起 vendored 的独立、纯 Go 的 Sleep 语言与 Aggressor Script(.cna)运行时,同一定位既可作为可嵌入的 Go 包,也可作为离线opforCLI 使用。本文以vendor/github.com/sliverarmory/opfor/README.md为骨架,结合其源码(api.go、internal/opfor/)与 Sliver 客户端集成层(client/command/opfor/),完整讲解如何在应用中嵌入 OPFOR、如何用 CLI 校验与执行 CNA 脚本、如何打通 Sleep 与 Go 的双向调用,以及它在 Sliver 客户端中承载 CNA 别名(alias)的实战用法。读完本文,你将能够独立完成 OPFOR 的嵌入、脚本校验、别名注册与底层调用链分析。

OPFOR 是什么:无需 JVM 的 Sleep/CNA 运行时

OPFOR 是一个独立的、纯 Go 实现的运行时,目标是让 Sleep 语言和 Aggressor Script(.cna)脚本在不需要 JVM的环境中运行。它把同一套引擎同时提供给两种使用形态:

  • 可嵌入的 Go 包:应用程序可以托管脚本,把宿主自身的状态与能力通过显式的 Aggressor Script 扩展与 provider API 注入运行时;
  • 离线的opforCLI:操作员可以离线评估(eval)、校验(check)和执行(run)脚本。

从 doc.go 的包级注释可以看到 OPFOR 的完整能力边界:实现了解析、编译、执行、可移植的 Sleep 内建函数、脚本生命周期、事件(events)、钩子(hooks)与回调(callbacks);嵌入应用通过Host、ObjectHost等扩展边界提供宿主数据与效果。需要特别强调的是,OPFOR 是独立的兼容实现,它不包含 Cobalt Strike 客户端或 Team Server 的实现。

在 Sliver 项目中,OPFOR 以 vendor 依赖的形式存在(vendor/github.com/sliverarmory/opfor/),并由客户端的client/command/opfor/包深度集成——这是阅读本文时最值得关注的真实落地场景。

嵌入 OPFOR:三步跑起第一段 Sleep 代码

原文档给出的最小嵌入示例完整且可直接运行。它创建一个运行时,然后对一段源码字符串求值:

package main import ( "context" "log" "github.com/sliverarmory/opfor" ) func main() { ctx := context.Background() runtime, err := opfor.New() if err != nil { log.Fatal(err) } defer runtime.Close(ctx) if _, err := runtime.Eval(ctx, "hello.sl", `println("hello from OPFOR");`); err != nil { log.Fatal(err) } }

这段程序会打印hello from OPFOR。其中涉及两个关键 API 语义:

  • Eval(ctx, name, code)将编译与执行合并为一次调用,适合一次性求值;
  • 当程序需要被复用时,应改用CompileString(或Runtime.Compile)得到不可变的Program,再交给Runtime.Execute执行。Program是不可变对象,可以被多个相互独立的 Runtime 重复加载,这一点在 compile.go 的源码注释中有明确说明。

println默认写入进程标准输出。如果想要替换进程的三个标准流,OPFOR 提供了对应的函数式选项WithStdin、WithStdout与WithStderr。从 runtime.go 的New实现可以看到:默认配置下 stdin 为os.Stdin、stdout 为os.Stdout、stderr 为os.Stderr,这些选项允许嵌入方把输出重定向到任意io.Writer(Sliver 客户端正是用这一机制把脚本输出导向自己的控制台)。

Runtime 构造选项概览(源码依据)

从 runtime.go 中可以整理出以下常用With*选项:

选项作用源码位置
WithStdin / WithStdout / WithStderr替换进程标准流,控制脚本 I/O 去向runtime.go#L401-L433
WithHost(host Host)安装未解析函数调用的兜底边界,Aggressor 实现通常用它承载 Cobalt 专有函数与谓词runtime.go#L438-L446
WithObjectHost(host ObjectHost)安装 Java 风格对象语法的宿主边界;宿主返回UnsupportedError时 OPFOR 才处理其内置的极小java.lang标量子集runtime.go#L451-L459
WithFunction(name, fn)在构造阶段安装或替换一个 Go 原生函数,优先级高于同名可移植默认函数runtime.go#L494-L506
RegisterFunction(name, fn)运行时阶段安装或替换 Go 原生函数runtime.go#L509-L525
WithInstructionLimit(limit)限制单次顶层执行或回调消耗的 VM 指令数,0 表示不设限runtime.go#L344-L349
WithBindingObserver(observer)安装事件、钩子、命令、别名、菜单等声明的生命周期通知runtime.go#L464-L472
WithEnvironment(keyword, kind)注册嵌入方自定义的 Sleep 环境关键字(普通、filter、predicate)runtime.go#L477-L489

值得注意的是 doc.go 中的一句重要警示:这些扩展点不是安全沙箱。可移植的 Sleep 文件系统与进程函数默认会执行本地效果,除非嵌入方覆盖它们或在外部约束进程。嵌入方必须自行承担这一安全边界。

Sleep 与 Go 双向互调:WithFunction与Script.Call

OPFOR 的核心价值之一是在 Sleep 与 Go 之间建立双向调用。原文档给出了完整可运行的示例:

package main import ( "context" "fmt" "log" "github.com/sliverarmory/opfor" ) func main() { ctx := context.Background() runtime, err := opfor.New( opfor.WithFunction("hello_from_go", func(_ context.Context, call opfor.Invocation) (opfor.Value, error) { name := call.Arg(0).String() return opfor.String("hello " + name + " from Go"), nil }), ) if err != nil { log.Fatal(err) } defer runtime.Close(ctx) program, err := runtime.CompileString("bridge.sl", ` sub hello_from_sleep { return "hello " . $1 . " from Sleep"; } println(hello_from_go("Sleep")); `) if err != nil { log.Fatal(err) } script, err := runtime.Load(ctx, program) if err != nil { log.Fatal(err) } reply, err := script.Call(ctx, "hello_from_sleep", opfor.String("Go")) if err != nil { log.Fatal(err) } fmt.Println(reply.String()) }

这个示例展示了两个方向的调用:

  1. Go → Sleep:WithFunction把名为hello_from_go的 Go 函数暴露给 Sleep。脚本加载时执行hello_from_go("Sleep"),调用call.Arg(0).String()取出第一个参数,返回hello Sleep from Go,由println打印。
  2. Sleep → Go:Script.Call从 Go 侧调用脚本内声明的sub hello_from_sleep,传入opfor.String("Go"),得到hello Go from Sleep。

关键的生命周期语义(源码见 script.go):

  • Runtime.Load执行Program并保留其全局变量、函数、事件、别名、命令与钩子,直到返回的Script被卸载;
  • Runtime.Close会卸载被保留的脚本及其函数;
  • Load传入的参数会填充 Sleep 的@ARGV启动数组,但不会成为顶层子程序参数——@_为空、$1~$n在顶层为 null(script.go#L324-L327注释)。

opfor CLI:安装与四种操作模式

安装

原文档给出了三种安装路径:

# 安装最新发布版(需要 Go 1.24 或更高) go install github.com/sliverarmory/opfor/cmd/opfor@latest # 从源码检出目录安装当前检出 go install ./cmd/opfor # 在仓库根目录构建 make

其中make会生成名为opfor(Windows 下为opfor.exe)的二进制,其目标定义见 Makefile。

四种操作模式

./opfor eval '2 + 2' ./opfor check examples/01-hello.sl ./opfor run examples/01-hello.sl operator ./opfor repl
  • eval:对表达式求值并打印结果;
  • check:只编译、不执行,用于校验脚本语法;
  • run:执行脚本,后续参数(如示例中的operator)作为 Sleep 的@ARGV传入;
  • repl:启动交互式 REPL。

REPL 在终端中显示彩色的opfor >提示符,评估错误以红色显示;当输入输出被重定向时,提示符会被省略,便于面向行的管道(pipeline)使用。

两个重要事实需要牢记:

  1. 不带任何参数运行./opfor会打印完整的命令帮助;
  2. CLI 是完全离线的解释器:它不连接、也不对外部 Aggressor Script 主机进行认证。

原文档提到的examples/目录(可运行脚本集合)与docs/兼容性参考在当前仓库的 vendor 裁剪中未包含,读者可直接查阅vendor/github.com/sliverarmory/opfor/api.go中约 3200 行的类型级文档,它详细记录了 provider 目录、生命周期契约与兼容性约定。

Makefile 中的差分测试目标(锦上添花)

Makefile 揭示了 OPFOR 的兼容性验证手段:

  • make test-sleep-java:以严格模式运行官方 Sleep 差分测试,要求通过OPFOR_SLEEP_JAR指定真实的 Sleep 2.1 JAR,测试会校验其固定的 SHA-256 摘要;
  • make bench-sleep:对全部BenchmarkSleep*基准做冒烟测试;
  • make bench-sleep-compare:在同一进程内把 OPFOR 与官方 Sleep 2.1 Java 解释器做等量工作负载对比(JAR 在测量前被验证)。

这些目标表明 OPFOR 以"官方 Sleep 2.1 行为"作为兼容基准,而非凭空定义语言语义。

范围界定:实现 Sleep 与 Aggressor Script,而非 Java

OPFOR 的兼容范围需要精确理解。它实现的是Sleep 语言与 Aggressor Script,而不是 Java 本身:

  • 一个**纯 Go 的小型兼容垫片(compatibility shim)**覆盖了受支持脚本所需的 Java 形态行为,包括字符串(String)、集合(Collections)、文件(File)、随机数(Random)与 UUID。这些在源码中对应objects_portable_*.go一系文件(如objects_portable_string_methods.go、objects_portable_random.go、objects_portable_uuid.go等);
  • 其他对象行为由嵌入应用提供:通过ObjectHost边界,嵌入方可以为脚本补充任意 Java 风格对象语法;
  • Java 序列化是可选兼容支持,只服务于显式使用它的脚本,正常的嵌入、执行与回调都不需要它。

从实现角度看,这个范围还意味着:文件系统与进程函数默认在本地执行效果,Host是扩展边界而非安全沙箱。OPFOR 不猜测 Cobalt Strike 未公开的 BOF 字节封装(见api.go中AggressorBOFExtractor的注释:绝不本地解析、链接、重定位或执行对象文件),这类行为一律交由 provider 实现。

在 Sliver 客户端中的实战集成:opfor命令

OPFOR 在 Sliver 中最有价值的落地是客户端命令集成,位于 client/command/opfor/commands.go。Package opfor integrates OPFOR-compatible CNA scripting with the Sliver client——它让客户端可以直接加载 CNA 脚本、把脚本中的 Beacon 别名注册为 Sliver 命令。

命令树

opfor根命令挂在 implant 菜单(SliverCoreHelpGroup)与 server 菜单(GenericHelpGroup)下,其子命令为(commands.go#L35-L107):

子命令用途
opfor load <script.cna>执行并保留CNA 的别名、钩子与回调
opfor run <script.cna> [arguments...]执行一次 CNA 并丢弃其注册(一次性 runtime)
opfor check <script.cna>只编译、不执行
opfor help <alias>查看已加载 CNA 别名的帮助
opfor unload <script.cna>卸载脚本并吊销其别名
opfor list按绝对路径列出已加载脚本

根命令opfor [script.cna]本身是便捷入口:单个以.cna结尾的参数会被当作load;否则第一个 token 被当作 CNA 别名来执行(runManagementCommand,commands.go#L110-L129)。所有子命令都支持--timeout/-t持久标志,默认超时为 10 分钟(aliasInvocationTimeout),且必须在动态别名名前放置。

动态别名机制(源码调用链)

加载脚本后,OPFOR 的BindingObserver会把脚本声明的别名发布给 Manager(manager.go#L130-L138 的Registered只关心BindingAlias类型)。随后syncRootLocked把这些别名动态挂到opfor命名空间下,并刻意用opfor <alias>形式命名以避免与 Sliver 内建命令(如cat)冲突(commands.go#L271-L373)。

执行别名时的关键链路是runAlias→invokeAlias→runtime.InvokeConsole(commands.go#L395-L481),最终通过:

manager.runtime.InvokeConsole(ctx, opforengine.ConsoleInvocation{ Kind: opforengine.BindingAlias, Name: name, RawInput: rawInput, ParsedArguments: parsedArguments, SessionID: opforengine.String(targetID), })

调用前要求存在活动的交互目标(Session 或 Beacon),否则报opfor: alias "..." requires an active target。这解释了为什么 CNA 别名必须面向当前活动目标执行。

Manager 常驻运行时与 Sliver Providers

manager.go 的注释点明了集成精髓:每个 Sliver 客户端进程持有唯一一个长生命周期(long-lived)OPFOR Runtime。保持 Runtime 存活至关重要,因为 CNA 的别名、钩子与回调都作用域限定在Runtime.Load返回的Script上。

newRuntime(manager.go#L101-L125)向引擎注入了整套 Sliver 侧 provider:

  • WithAggressorBeaconExecutionProvider:把beacon_execute_job、beacon_inline_execute等 Beacon 执行任务转译为 Sliver 的 Beacon 任务;
  • WithAggressorSessionQueryProvider:向脚本提供会话/Beacon 查询能力;
  • WithAggressorBeaconTranscriptSink:接收脚本产生的 Beacon 转录记录(btask 等);
  • WithAggressorPromptProvider:为脚本的提示(prompt)提供 UI;
  • WithStdout/WithStderr:把脚本的println输出重定向到 Sliver 控制台;
  • WithBOFPackByteOrder(opforengine.BOFPackLittleEndian):指定 BOF pack 的字节序。

provider 的具体实现(如executeBeacon、resolveTarget、beacon 任务轮询beaconTaskPollInterval = 250ms)集中在 providers.go。值得注意的细节是:run子命令使用独立的一次性 Runtime(不携带 BindingObserver),从而保证临时注册不会污染常驻 Manager(manager.go#L207-L240)。

测试语料

仓库自带可运行的 CNA 测试语料,位于 client/command/opfor/testdata/corpus/:bof_collection/cat.cna、firefoxdump/firefoxdump.cna、operatorskit/finddotnet.cna与findsysmon.cna,均附LICENSE/NOTICE。这些脚本可配合opfor load/opfor run直接实验。

源码级原理:从源码到字节码的编译管线

OPFOR 的编译过程在 compile.go 的Compile中一目了然:

  1. 拷贝源码:source.Data = append([]byte(nil), source.Data...)保证Program不可变,即使调用方随后复用了输入缓冲区;
  2. 词法解析:parser.ParseWithOptions(lexer.Source(source), config.parser)——默认使用CompatibilityOptions()(兼容模式),也可用WithStrictSyntax()强制要求文档化的逗号/分号分隔符,或用WithCompatibilityWarnings()在兼容解析的同时报告被接受的省略分隔符;
  3. 语义编译:compiler.Compile(parsed.Script)生成bytecode.Function;
  4. 静态 import 校验:validateStaticImports检查静态导入;
  5. 字面量预编译:compileProgramLiterals一次性完成数字与字符串字面量的解析(转义解码、静态性判定),错误挂载在对应 AST 节点上、在运行时相同的执行点抛出,从而保持 Sleep 可观察的执行边界。

编译失败时返回*CompileError(携带Diagnostics),非致命兼容性警告则通过Program.Diagnostics()单独暴露。

四个顶层执行入口的差异(script.go):

入口语义
Runtime.Eval(ctx, name, code)编译并执行一次,不保留声明(script.go#L496)
Runtime.Execute(ctx, program, args...)执行已编译的Program,参数进@ARGV(script.go#L479)
Runtime.Load(ctx, program, args...)执行并保留声明,返回*Script供后续Call/Unload(script.go#L328)
Script.Call(ctx, name, args...)调用脚本内声明的sub/inline 声明(script.go#L272)

从Runtime.Load的源码(script.go#L341-L359)还能看到一个工程细节:已关闭的 Runtime 会拒绝加载并返回ErrRuntimeClosed;而独立或跨 Runtime 的Program每次加载都会重新计入源码资源预算,防止预编译绕过源码限额。

结语

OPFOR 为 Sleep 语言与 Aggressor Script 提供了一个无需 JVM 的纯 Go 运行时:嵌入式 Go API(Eval/Compile/Load/Execute/Script.Call)支持应用托管脚本与双向互调,离线 CLI(eval/check/run/repl)支持操作员校验与执行,typed provider API 则把 Cobalt 专有行为以清晰的生命周期契约交给宿主实现。在 Sliver 中,它被深度集成于客户端:一个常驻 Runtime 承载 CNA 别名、钩子与回调,opfor load/run/check/help/unload/list命令让.cna脚本成为可操作的实战工具。项目采用 Apache License 2.0(见 LICENSE),对嵌入方与二次开发者都相当友好。

  • 网络安全

【免费下载链接】sliver

Adversary Emulation Framework

项目地址:https://gitcode.com/gh_mirrors/sl/sliver
点击查看免费下载
上一篇:Modular Monolith DDD日志脱敏:5大敏感数据保护实现策略 🛡️
下一篇:10分钟上手!Machine Learning Q and AI Book配套代码环境搭建指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询