点击上方“蓝字”,关注更多精彩
可关注回复"xxxaq"加群一起学习,欢迎师傅们一起研究,有错误的地方也请师傅们指出。后续还有一篇Cobalt Strike 逆向系列文章,师傅们点个关注吧。
前言
之前看鸡哥文章针对beacon进行逆向,了解了beacon指令号的含义。还有WBG对beacon协议的分析,受益匪浅。因此重复的部分我就不过多赘述了。本文是从cs的源码出发进行分析,得到了更为详尽的信息,我在这里补全beacon的全部指令含义,希望能对大家有所帮助。
基础运行流程
aggressor 运行后启动本地web服务,输入与teamserver相同的账号密码后进入主界面。
teamserver 等待 aggressor 发送的加密指令,解密后进行处理。
teamserver 设置监听器(例如beacon_name https 443端口
),将指令号放至端口等待beacon取(根据beacon的元数据来区别beacon)。
beacon 取回后设置 jobs,任务轮流执行。
此处以jquery.profile中的get为例:
get:
跟get的流程一样,post在profile中也可进行设置:
密钥解密 :
https://the-x.cn/cryptography/Rsa.aspx
元数据使用RSA加密,任务回传数据使用的是AES加密,其中AES的密钥是协商进行传递的。
cna脚本注册源码事件流程
我是通过cna脚本事件来追踪指令号发送的流程的,在这里举例两个追踪流程的原理,第二个举例则为指令追踪的原理。
举例一
内置的cna脚本对接源码的原理如下:
针对上图的补充:
举例二
default.cna默认Access选项中的openElevateDialog:(用于提权)
对应在BeaconBridge.java中:
先是脚本注册:
public void scriptLoaded(ScriptInstance var1) {
...
Cortana.put(var1, "&openElevateDialog", this);
...
}
脚本触发函数:
对应进入ElevateDialog界面(ElevateDialog.java):
可以看到下发指令号的类依靠的是TaskBeacon,
针对代码进行进一步分析:
-
this.client:AggressorClient 的一个实例,是一个客户端对象。
-
this.bids:一个字符串数组,是当前 TaskBeacon 实例将要操作的信标(beacons)的ID。
-
var5.input("elevate " + var3 + " " + var4); // 调用 input 方法,发送 "elevate" 命令和两个参数(var3 和 var4)
-
var5.Elevate(var3, var4); // 调用 var5 实例的 Elevate 方法,传递之前获取的 "exploit" 和 "listener" 字符串。
进入TaskBeacon.java,可以看到Elevate函数:
这个函数调用了ServiceEXE.java类(注意:不是BeaconExploits.java中的Elevate函数):
可以看到此处主要使用的是TaskBeacon.PsExec函数:
从这里开始进行下发指令号,下发顺序为:10 -> 56
对应执行的操作是upload file和delete file。
Elevate操作涉及到的指令号为 10 -> 100 -> 56,其中100指令号的实现是通过beacon/PostExInlineObject.java类实现的:
在这里涉及到100指令号的操作有两个,通过debug发现是属于PostExInlineObject.java类中的go函数。
可以看到go函数在PsExec中进行了调用:
go函数的作用是用来发送一个BOF进行执行:
运行原理如下:
-
获取远程beacon的架构(32位或64位)
-
getObjectFile 方法获取与beacon架构相对应的对象文件的字节码。
-
如果对象文件为空,则报告错误。
-
使用 OBJExecutable 类解析对象文件。
-
如果对象文件解析出现错误,则报告错误。
-
检查对象文件的目标架构与beacon的架构是否一致,如果不一致,则报告错误。
-
从 OBJExecutable 实例中获取代码段、数据段以及重定位信息。
-
使用 CommandBuilder 构建命令,设置命令号(可能是用于BOF任务的特定命令号),并添加入口点、代码、数据和重定位段。
-
如果有链接错误,则报告错误。
-
如果一切正常,则使用 this.client.getConnection().call 方法将构建完成的命令发送到beacon。
我们自己重构beacon需要掌握如何发送beacon指令号,teamserver端调用指令号只需要实现下面的代码就可以了:
CommandBuilder var = new CommandBuilder();
var.setCommand(100);
自实现指令发放
根据elevate的流程,需要在BeaconBridge.java中注册事件(注意不是AggressorBridge.java)中注册:
Cortana.put(var1, "&openElevateDialog", this);
...
if (var1.equals("&openElevateDialog")) {
var11 = bids(var3);
(new ElevateDialog(this.client, var11)).show();
return SleepUtils.getEmptyScalar();
}
...
我们在default.cna中添加一个事件:
menu "&Geacon Free"{
item("&Load Shellcode",{{ loadShellcodeDialog($1); }});
}
BeaconBridge.java注册一个响应:
Cortana.put(var1, "&loadShellcodeDialog", this);
...
if (var1.equals("&loadShellcodeDialog")) {
var11 = bids(var3);
(new LoadShellcodeDialogEx(this.client, var11)).show();
return SleepUtils.getEmptyScalar();
}
...
TaskBeacon.java 中新建函数:
public void RunShellcode() { // var1:beacon id
this.builder.setCommand(1000);
byte[] var5 =this.builder.build();
for(int var6 = 0; var6 < this.bids.length; ++var6) {
this.log_task(this.bids[var6], "Load Shellcode");
this.conn.call("beacons.task", CommonUtils.args(this.bids[var6], var5));
}
}
在自己新建的LoadShellcodeDialogEx.java中dialoagAction进行调用RunShellcode函数:
public void dialogAction(ActionEvent var1, Map var2) {
TaskBeacon var5 = new TaskBeacon(this.client, this.bids);
if (this.bids.length == 1) {
DialogUtils.openOrActivate(this.client, this.bids[0]);
}
var5.input("LoadShellcode running..."); // 控制台打印指令的指令
var5.RunShellcode(); // 核心函数下发指令号,调用TaskBeacon.java中的函数
}
aggressor 控制台实现指令号发放
具体实现在windows/BeaconConsole.java中,根据execute-assembly:
else if (var3.is("execute-assembly")) {
if (var3.verify("pZ")) {
var4 = var3.popString();
var10 = var3.popString();
this.master.ExecuteAssembly(var10, var4);
} else if (var3.isMissingArguments() && var3.verify("F")) {
var4 = var3.popString();
this.master.ExecuteAssembly(var4, "");
}
}
ExecuteAssembly的具体实现在TaskBeacon.java代码中:
public void ExecuteAssembly(String var1, String var2) {
PEParser var3 = PEParser.load(CommonUtils.readFile(var1));
if (!var3.isProcessAssembly()) {
this.error("File " + var1 + " is not a process assembly (.NET EXE)");
} else {
for(int var4 = 0; var4 < this.bids.length; ++var4) {
BeaconEntry var5 = DataUtils.getBeacon(this.data, this.bids[var4]);
if (var5.is64()) {
(new ExecuteAssemblyJob(this, var1, var2, "x64")).spawn(this.bids[var4]);
} else {
(new ExecuteAssemblyJob(this, var1, var2, "x86")).spawn(this.bids[var4]);
}
}
}
}
beacon 指令号说明
此处结合鸡哥之前提供的指令号进行扩充:
指令号 |
功能 |
1 |
spawn派生会话,原理是挂起线程rundll32线程注入dll |
2 |
执行shell功能时进行调用 |
3 |
Exit退出功能,修改dwMilliseconds时间为0,如果为0就退出程序 |
4 |
Sleep设置beacon睡眠时间,接收数据包,取得修改的时间并进行修改dwMilliseconds |
5 |
切换目录,使用SetCurrentDirectory切换当前进程的当前工作目录 |
6 |
Jitter设置 |
7 |
不存在 |
8 |
不存在 |
9 |
指定已打开进程来注入会话,原理就是远程线程注入,dllinject、shinject之类也会走这个case |
10 |
upload上传文件 |
11 |
download 下载文件,先分割数据包获得需要下载的文件名,然后打开文件,不断读取文件内容,然后加密返回给teamserver |
12 |
execute 执行程序,但不回显 |
13 |
spawnto,设置Beacon派生会话时使用的程序,当再执行spawn时,会判断启用哪个程序进行注入,而不是再注入默认的rundll32.exe |
14 |
不存在 |
15 |
没有命令参数,是与rportfwd端口转发相关的case,首先接收到访问目标机器的请求信息,然后发送给目标机器,然后中转机通过select模型等待信息返回,最后把rportfwd端口转发的信息返回 |
16 |
没有命令参数,是与rportfwd端口转发相关的case,首先接收到访问目标机器的请求信息,然后发送给目标机器,然后中转机通过select模型等待信息返回,最后把rportfwd端口转发的信息返回 |
17 |
不存在 |
18 |
desktop VNC远程桌面,x86 desktop VNC远程桌面(不注入进程),需要由vnc的dll,不建议用有点卡 |
19 |
download_cancel,取消相关下载文件 |
20 |
不存在 |
21 |
不存在 |
22 |
没有相关命令行,负责中转子beacon的传输数据 |
23 |
调用shutdow()断开与子Beacon的连接 |
24 |
beacon进程回调函数解密 |
25 |
不存在 |
26 |
不存在 |
27 |
获取当前令牌关联的用户ID |
28 |
恢复Beacon原始令牌 |
29 |
修改文件时间戳 |
30 |
不存在 |
31 |
从目标进程中窃取访问令牌 |
32 |
PS,显示进程列表 |
33 |
结束指定进程 |
34 |
不存在 |
35 |
不存在 |
36 |
不存在 |
37 |
导入Powershell脚本以便后续调用 |
38 |
以其他用户权限执行程序,调用CreateProcessWithLogonW()函数,以某用户身份执行指定程序 |
39 |
显示当前所在目录 |
40 |
Job执行后数据的回传,当job执行后产生数据会用管道回传给beacon |
41 |
查看Beacon中的所有任务,在list读取后台进行中的任务 |
42 |
jobkill命令,用于停止指定的已运行任务 |
43 |
Inject(x64) 指定已打开进程来注入会话,原理就是远程线程注入,dllinject、shinject之类也会走这个case ,流程与case 9 一样。 |
44 |
0原理也是挂起线程rundll32线程注入dll,流程都是一样的,只是在不同文件夹在rundll32.exe |
45 |
VNC (x64) desktop VNC远程桌面(注入进程),需要由vnc的dll |
46 |
VNC (x86) desktop VNC远程桌面(注入进程),需要由vnc的dll |
47 |
暂停指定job |
48 |
不存在 |
49 |
创建令牌 |
50 |
端口转发数据 |
51 |
暂停端口转发 |
52 |
开启stageTCP服务 |
53 |
TaskBeacon回调函数 |
54 |
mkdir,创建目录 |
55 |
drives,GetLogicalDrivers()列出目标机上所有的磁盘盘符 |
56 |
删除文件,先用GetFileAttributesA()获取到文件属性,进行判断是文件还是文件夹,如果是文件夹就遍历删除里面文件再删文件夹,如果是文件则直接删 |
57 |
开启stage管道服务 |
58 |
不存在 |
59 |
One-liner:启服务器,执行相应的ps命令,返回一个会话 |
60 |
PassTheHash |
61 |
mimikatz的执行 |
62 |
Mimikatz Job执行后数据的回传 |
63 |
不存在 |
64 |
不存在 |
65 |
不存在 |
66 |
不存在 |
67 |
UploadRaw,上传raw |
68 |
link通过管道连接SMB形式的Beacon |
69 |
Spawnto (x64) 设置Beacon派生会话时使用的程序,执行流程与任务号13 spawnto x86一样 |
70 |
内存执行C#的可执行文件,首先传输invokeassembly.dll并注入到rundll32.exe,实现了在其内存中创建CLR环境,然后通过管道再将C#可执行文件读取到内存中,最后执行。 https://github.com/b4rtik/metasploit-execute-assembly/ |
71 |
流程与任务号70一样,区别只是传输dll的版本是x64的 |
72 |
putenv()设置环境变量 |
73 |
调用CopyFileA()复制文件 |
74 |
调用MoveFileA()移动文件 |
75 |
伪造子进程(jobs)的父进程为指定进程,jobs是指portscan等操作,不是派生会话操作 |
76 |
父进程欺骗,即指定所创建程序的父进程pid值,并执行该进程 |
77 |
getprivs,启用当前访问令牌所拥有的特权 |
78 |
在目标上执行程序(输出回显) |
79 |
powershell任务getImportCradle |
80 |
不存在 |
81 |
不存在 |
82 |
当在Pivoting->Listener(Reserve TCP Beacon)或oneliner,创建完成后再执行一条命令rportfwd 4444 windows/beacon_reverse_tcp,会调用此任务号,原理与rportfwd一致 |
83 |
进程参数欺骗:增加欺骗的参数 |
84 |
进程参数欺骗:删除欺骗的参数 |
85 |
进程参数欺骗:查询设置了哪些参数 |
86 |
Connect连接bind形式的TCP Beacon |
87 |
流程与任务号70一样,作用也是内存执行C#的可执行文件,流程一样:首先传输invokeassembly.dll并注入到rundll32.exe,实现了在其内存中创建CLR环境,然后通过管道再将C#可执行文件读取到内存中,最后调用执行。区别是,不使用ImpersonateLoggedOnUser()当前线程模拟登陆用户进行操作,TokenHandle要在pth后才会生成并保存 |
88 |
流程与任务号71一样,区别是不使用ImpersonateLoggedOnUser()当前线程模拟登陆用户进行操作,TokenHandle要在pth后才会生成并保存 |
89 |
spawn 派生会话,跟指令号1对应,区别在于是否忽略token |
90 |
Spawn (x64) 派生会话,跟指令号44对应,区别在于是否忽略token |
91 |
ReflectiveDLL,patchDOSHeader |
92 |
不存在 |
93 |
不存在 |
94 |
不存在 |
95 |
获取system权限 |
96 |
不存在 |
97 |
不存在 |
98 |
不存在 |
99 |
不存在 |
100 |
BOF内存执行 |
101 |
Screenshot |
往期精选
好看的人都“在看”
原文始发于微信公众号(安全笔记):Cobalt Strike 逆向系列:beacon 指令分析篇
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论