文章总结: 本文详述了为AdaptixC2的Perlagent增加Terminate和Sleep命令的步骤。文章涵盖了Perl端逻辑实现、AxScriptUI配置及Go插件后端处理的三处同步更新。重点讲解了如何通过设置变量控制进程退出与休眠间隔,并实现了右键菜单集成与元数据同步,为C2开发提供了可复用的功能扩展模式。 综合评分: 93 文章分类: 红队,安全工具,安全开发
Lamperl 第 2 篇:为 Adaptix agent 增加 Terminate 与 Sleep 命令
Polar
securitainment
2026年1月1日 12:50 中国香港
大家好!本文会展示两个我认为“必不可少”,但上一篇没有加入的功能的实现过程:Terminate和 Sleep。Perl 端的实现非常简单,因此本文重点会放在 ax_config.axs和 pl_agent.go这两个文件里的 Adaptix agent 配置上。
在上一篇文章里,我们实现了一个基础 agent,只有三个命令:pwd、cd和 run。它虽然能用,但缺少 C2 agent 的关键能力:干净退出 (terminate) 与调整 beacon 间隔 (sleep)。支持终止 agent 可以避免留下孤儿进程;动态调整 sleep 则让操作员能根据行动阶段,在隐蔽性与响应速度之间取舍。
主函数已经会循环执行,直到 should_terminate为 true。因此 terminate 函数唯一要做的就是把这个变量置为 1。
while (!$should_terminate)
类似地,循环里已经包含了一个使用 jitter 计算 sleep 时长的函数,所以 sleep 函数只需要设置 sleep_time和 jitter_percent两个变量即可。
subcalculate_sleep {
return$sleep_timeunless$jitter_percent > 0;
return$sleep_time + int(rand($sleep_time * $jitter_percent / 100));
}
这篇文章的主要目的,是把“如何实现命令”的具体步骤讲清楚:上一篇没覆盖得很好,因为代码是在文章之前写出来的。
对 Adaptix agent 来说,每新增一个功能,都必须同步更新三处:
-
ax_config.axs -
pl_agent.go中的
CreateTask函数 -
pl_agent.go中的
ProcessTasksResult函数
实现 Terminate
先从 Perl 端开始,写法很直接:创建一个命令,把 should_terminate标志置位:
subcmd_terminate {
my ($task) = @_;
$should_terminate = 1;
return {
command=>'terminate',
status=>'terminating',
};
}
这个函数会:
- 接收 task 对象(这里未使用,但为了接口一致性仍保留)
- 设置
$should_terminate = 1,向主循环发出退出信号 - 返回一个 status 对象,用于确认终止流程已开始
函数写好后,更新 dispatch table,把它加入命令分发:
my%COMMANDS = (
pwd=> \&cmd_pwd,
cd=> \&cmd_cd,
run=> \&cmd_run,
terminate=> \&cmd_terminate,
);
到这里,Terminate 功能的 Perl 部分就完成了。
Adaptix 配置
接下来需要把命令注册到 Adaptix 中,让它能出现在 UI 里,并能被正确分发。涉及三处文件:
ax_config.axs
- UI 命令定义
pl_agent.go
- CreateTask (operator -> agent)
pl_agent.go
- ProcessTasksResult (agent -> operator)
ax_config.axs
首先,在 ax_config.axs中加入新命令:
letcmd_terminate=ax.create_command("terminate", "Terminate beacon", "terminate", "Task: terminate beacon");
这会创建一个命令定义,包含:
- 命令名:
terminate - 描述:
Terminate beacon - 用法示例:
terminate - 提示消息:
Task: terminate beacon
然后把它加入 agent 的 command group(位于文件底部):
if(listenerType=="LamperlHTTP") {
letcommands_external=ax.create_commands_group("Lamperl", [cmd_pwd, cmd_cd, cmd_terminate, cmd_run]);
return { commands_linux:commands_external }
}
pl_agent.go – CreateTask – Terminate
接着处理 pl_agent.go。由于 terminate 不需要参数,所以 CreateTask的更新很简单:
case"terminate":
// No additional parameters needed
把这个 case 添加到 CreateTask函数的 switch 语句里即可。因为 terminate 没有参数,只需匹配命令,其余交给默认的 task 构造逻辑处理。
pl_agent.go – ProcessTasksResult – Terminate
在 ProcessTasksResult里也加一个对应的 case,并在控制台给出提示:
case"terminate":
consoleOutput="Terminating beacon"
该 case 会从 agent 的响应中提取终止确认信息,并在 operator console 中显示。
构建与测试
接下来只需要构建插件并部署:
make
重启 Adaptix server,生成一个新的 agent,然后测试 terminate 命令。
添加右键菜单集成
目前,每次想终止 agent 都要手动输入命令并发送;当同时在处理一堆 agents 时,这会变得很烦。我们可以加一个右键菜单项,用于快速终止。
AxScript 提供了 menu 相关函数,允许我们向 agent 的右键菜单添加自定义操作。文档在这里:
- https://adaptix-framework.gitbook.io/adaptix-framework/development/axscript/axmenu-type
把下面内容添加到 ax_config.axs文件顶部:
letterminate_action=menu.create_action("Terminate", function(value) { value.forEach(v=>ax.execute_command(v, "terminate")) });
menu.add_session_agent(terminate_action, ["Lamperl"])
拆解一下:
-
menu.create_action("Terminate", ...)创建一个名为“Terminate”的菜单项
-
回调函数接收被选中的 agents 作为
value,并遍历它们 -
ax.execute_command(v, "terminate")会对每个被选中的 agent 执行 terminate 命令
-
menu.add_session_agent(...)会把这个操作添加到 agent 的右键菜单
-
["Lamperl"]将该操作限制为仅对 Lamperl agents 生效
这样就把 Terminate功能挂到 agent 的右键菜单上了,你可以通过右键一键终止:
重新构建插件并测试:
make
实现 Sleep
Perl 的怪癖与设计取舍
Perl 的 sleep 有个有趣的点:它以“秒”为单位。因此,如果按默认值设为 5 秒并配 10% jitter,实际会一直是 5 秒,因为 Perl 会向 0 截断取整。对于较小的 sleep 值,这相当于把 jitter 变相禁用了。
另外,当前实现只会在基准 sleep 时长之上增加 jitter,而不会减少。这是我刻意的选择(确保最小 sleep 时长)。如果你更喜欢双向 jitter,可以用下面版本替换 calculate_sleep:
subcalculate_sleep {
return$sleep_timeunless$jitter_percent > 0;
return$sleep_time + int(($sleep_time * $jitter_percent / 100) * (rand(2) - 1));
}
这个替代版本通过 rand(2) - 1生成一个介于 -1 与 +1 的数,从而实现双向 jitter。
Perl 端 (Agent Side)
sleep 函数的实现如下:
subcmd_sleep {
my ($task) = @_;
$sleep_time = $task->{duration} || 5;
$jitter_percent = $task->{jitter} || 0;
return {
command=>'sleep',
duration=>$sleep_time,
jitter=>$jitter_percent,
status=>'completed',
};
}
这个函数会:
- 从 task 对象中取出
duration(未提供则默认 5 秒) - 取出
jitter百分比(未提供则默认 0) - 更新全局变量
$sleep_time与$jitter_percent - 返回一个 status 对象,用于确认新的 sleep 配置
这种做法的好处是:这两个值是全局变量,主 beacon 循环会在每次结束时检查它们,因此修改会立刻生效。
同样地,更新 dispatch table:
my%COMMANDS = (
pwd=> \&cmd_pwd,
cd=> \&cmd_cd,
run=> \&cmd_run,
sleep=> \&cmd_sleep,
terminate=> \&cmd_terminate,
);
Adaptix 的 Sleep 配置
现在把它接入 Adaptix。与 terminate 不同,sleep 需要参数(duration 和可选 jitter),因此实现会稍微复杂一些。
ax_config.axs
先在 ax_config.axs中新增 sleep 命令定义。我们需要为 duration 和 jitter 配置参数输入;它们都是 int,其中 jitter 不是必填:
letcmd_sleep=ax.create_command("sleep", "Sleep for a duration with optional jitter", "sleep 10 20", "Task: sleep");
cmd_sleep.addArgInt("duration", true, "Duration to sleep in seconds");
cmd_sleep.addArgInt("jitter", false, "Jitter percentage (0-100)");
同样把它加入 agent 的 command group:
if(listenerType=="LamperlHTTP") {
letcommands_external=ax.create_commands_group("Lamperl", [cmd_pwd, cmd_cd, cmd_sleep, cmd_terminate, cmd_run]);
return { commands_linux:commands_external }
}
pl_agent.go – CreateTask – Sleep
在 CreateTask中,需要提取并校验参数:
case"sleep":
duration, ok:= args["duration"].(float64)
if!ok {
err= errors.New("parameter 'duration' must be set")
return taskData, messageData, err
}
commandData["duration"] =int(duration)
ifjitter, ok:= args["jitter"].(float64); ok {
commandData["jitter"] =int(jitter)
}
这里做了三件事:
- 从 args 中取出
duration并校验其存在(必填参数) - 如果提供了
jitter,则按需取出 - 把两者打包进 commandData map,供后续序列化
pl_agent.go – ProcessTasksResult – Sleep
在 ProcessTasksResult中,解析 agent 的响应并展示:
case"sleep":
duration:=getInt(outputData, "duration")
jitter:=getInt(outputData, "jitter")
consoleOutput= fmt.Sprintf("Sleep scheduled for %d seconds with %d%% jitter", duration, jitter)
重新构建并测试:
make
这能正常工作:命令执行后,beacon 会按指定时长进入 sleep。不过你很快会发现一个问题:Adaptix 客户端 UI 里的 Sleep列并不会更新。也就是说,agent 内部的 sleep 已经变了,但 Adaptix 的元数据仍然是旧的。
更新 Agent 元数据
为了让客户端能反映这个变化,我们还必须更新 agentData对象,并把它持久化回 teamserver:
case"sleep":
duration:=getInt(outputData, "duration")
jitter:=getInt(outputData, "jitter")
// Update agent data with new sleep configuration
agentData.Sleep=uint(duration)
agentData.Jitter=uint(jitter)
_= ts.TsAgentUpdateData(agentData)
consoleOutput= fmt.Sprintf("Sleep scheduled for %d seconds with %d%% jitter", duration, jitter)
关键新增点:
-
agentData.Sleep = uint(duration) -
更新 agent 的 sleep 元数据
-
agentData.Jitter = uint(jitter) -
更新 agent 的 jitter 值
-
ts.TsAgentUpdateData(agentData) -
将变更持久化回
agentData
这能确保 UI 始终反映 agent 的当前配置。
最终构建并部署:
make
生成新的 payload 并测试。一旦执行 sleep 命令,客户端里显示的 sleep 会实时更新。
要点总结
为每个新的 Adaptix agent 命令,请记住这套“三件套”:
- ax_config.axs
- UI 定义、参数表单、加入 command group
- CreateTask
- 操作员输入 -> agent task 序列化
- ProcessTasksResult
- agent 输出 -> 控制台展示 + 元数据同步
这套模式可以扩展到任何复杂度的命令。即便是更高级的功能,如文件上传/下载、socks proxy 或 process injection,本质上也遵循同样的结构,只是参数处理与结果解析更复杂。
下一篇文章会继续实现更高级的能力:上传/下载支持与异步 job 处理。
- AxScript Documentation
Lessons from Perlyite – Lamperl pt 2(Adding new functions)
免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:securitainment Polar《Lamperl 第 2 篇:为 Adaptix agent 增加 Terminate 与 Sleep 命令》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论