cobalt strike server端登录流程分析
前言:为了以后有机会能够二开cs,这里先开始学习关于cobalt strike server端登录流程分析
参考文章:https://xz.aliyun.com/t/10752
前置知识点
ssl.SecureServerSocket:通信之间封装的套接字,并且其中提供了acceptAndAuthenticate认证的功能
aggressor.Aggressor:客户端
aggressor.dialogs.Connect:连接服务端的时候进行使用的,主要用于收发aggressor.authenticate阶段和收发aggressor.metadata阶段
common.TeamQueue:在跟服务端之间通信的时候提供网络操作,其中TeamReader专门接收读取服务端请求过来的信息(common.Reply对象)和TeamWriter专门发送请求服务端信息(common.Request对象)
server.TeamServer服务端
server.ManageUser:客户端发送过来的请求,在服务端专门通过ManageUser对象的process方法来进行处理
服务端
首先读取cobaltstrike.auth,并且进行验证是否授权

因为走的默认是两个参数,所以这里的profile走的是默认的

接着就是进入到go函数中进行相关的配置初始化

接着就是创建SecureServerSocket对象,后续开始监听客户端的连接请求

接着就会通过acceptAndAuthenticate来对请求过来的连接密码进行校验,其中通过PostAuthentication的clientAuthenticated方法来进行配合使用

主要是如下两步,authenticate方法会对传输过来的数据包进行标志位检查(检查是否是CS客户端发过来的请求,具体的标志位还得自己跟下,分别是48879和51966,代码位于ssl.SecureServerSocket#authenticate)和密码检查
如果都通过的话则会对数据流写入相关的字段用来给clientAuthenticated方法继续处理
public Socket acceptAndAuthenticate(final String pass, final PostAuthentication postAuth) {
String var3 = "unknown";
...
if (SecureServerSocket.this.authenticate(socket, pass, host)) {
postAuth.clientAuthenticated(socket);
return;
}

如果authenticate通过的话,这里就会进入PostAuthentication的clientAuthenticated方法中,开启ManageUser线程的run方法
try {
var1.setSoTimeout(0);
TeamSocket teamSocket = new TeamSocket(var1);
(new Thread(new ManageUser(teamSocket, TeamServer.this.resources, TeamServer.this.calls), "Manage: unauth'd user")).start();
} catch (Exception var3) {
MudgeSanity.logException("Start client thread", var3, false);
}

ManageUser类中的run方法首先会将对teamSocket的isConnected属性检查,然后将客户端请求过来相关的数据进行反序列化读取,如果反序列化成功的话则读取到一个Request对象,接着将Request作为参数调用process方法
public class Request implements Serializable {
protected String call;
protected Object[] args;
protected long callback_ref;
}

进入到process方法后,接收到是"aggressor.authenticate",为什么是这个?这个在后面客户端如何发起请求的时候再说明,接着进行判断最后创建了BroadcastWriter对象

接着就是继续开启BroadcastWriter对象的线程run方法,可以看到是一个死循环,到了这里其实就是不停的接收客户端发送过来的请求然后进行处理了
这里还会看到一个Thread.yield的操作,这个操作的作用就是让当前线程从运行状态转为就绪状态,以允许具有相同优先级的其他线程获得运行机会,个人理解是让给ManageUser线程对象,因为ManageUser的对存储过来的Request进行process处理的

主要看其中的server.ManageUser.BroadcastWriter#grabReply方法,它会不停的从replies属性中取出Reply对象,然后进行处理,那么我们这里其实就是可以猜到replies这个对象其实就是存储客户端发送过来的任务,然后BroadcastWriter对象不停的循环读取然后写入到ManageUser.Client中去,而我们知道server.ManageUser#run这个线程对象也是死循环,它会不停的读取然后通过process方法进行处理,具体的还得看客户端那边的代码逻辑才可以
private class BroadcastWriter implements Runnable {
protected LinkedList replies = new LinkedList();
protected Reply grabReply() {
synchronized(this) {
return (Reply)this.replies.pollFirst();
}
}
客户端
当发起connect连接的时候会调用aggressor.dialogs.Connect#dialogAction方法

首先还是和服务端一样创建了SecureSocket对象,接着发起ssl.SecureSocket#authenticate方法,参数为连接服务端的密码,写入特定的标志位48879

接着创建TeamSocket var8 = new TeamSocket(var7.getSocket());,将SecureSocket通信的套接字作为参数放入到TeamSocket对象中
接着继续用TeamQueue来进行封装,其中TeamQueue这个的话就是主要用于读取和写入套接字的对象

接着会看到 this.tqueue.call("aggressor.authenticate", CommonUtils.args(var3, var6, Aggressor.VERSION), this);
这个"aggressor.authenticate"操作就和方面的服务端接收到的"aggressor.authenticate"相对应了起来

细跟进去可以封装了Request对象然后进行传输,那么就和上面服务端对应起来了,服务端同样也是接收到了Request对象然后进行处理
synchronized(this.callbacks) {
++this.reqno;
this.callbacks.put(new Long(this.reqno), var3);
Request var5 = new Request(var1, var2, this.reqno);
this.writer.addRequest(var5);
}

知识点:TeamQueue这个类还需要细讲下,它的实例化中会启动两个线程分别是TeamReader对象和TeamWriter对象

TeamReader专门进行读取Reply对象,TeamWriter专门进行写入Request对象,如下图所示

当客户端通过TeamWriter发送aggressor.authenticate,然后服务端进行验证通过server.ManageUser#process,然后客户端接收数据,那么到这里的话TeamReader接着就是进行读取common.TeamQueue#processRead

其中的var2.result(var1.getCall(), var1.getContent());,根据对应的callback来调用对应的result方法,这里的话是connect对象,所以调用的就是aggressor.dialogs.Connect#result,如下图所示
因为接收到的还是"aggressor.authenticate"消息,此时登录成功,所以客户端接收到的就是SUCCESS,那么可以看到还会继续common.TeamQueue#call进行调用,并且继续发送关于"aggressor.metadata"的请求,同样的通过call方法来进行包装

这里再切回服务端,因为客户端发送过来的是关于"aggressor.metadata"的消息,所以走的就是server.ManageUser#process方法中的"aggressor.metadata"分支,如下图所示
此时会将相关的profile的信息以及一些配置项的属性都通过writeobject写入,然后发送给客户端
} else if ("aggressor.metadata".equals(var1.getCall()) && var1.size() == 1) {
HashMap var14 = new HashMap();
var14.put("nick", this.nickname);
ServerUtils.getProfile(this.resources).getPreview().summarize(var14);
long var10 = System.currentTimeMillis() - Long.parseLong(var1.arg(0) + "");
var14.put("clockskew", var10);
var14.put("signer", ServerUtils.getProfile(this.resources).getCodeSigner());
var14.put("validssl", ServerUtils.getProfile(this.resources).hasValidSSL() ? "true" : "false");
var14.put("amsi_disable", ServerUtils.getProfile(this.resources).option(".post-ex.amsi_disable") ? "true" : "false");
var14.put("postex_obfuscate", ServerUtils.getProfile(this.resources).option(".post-ex.obfuscate") ? "true" : "false");
var14.put("postex_smartinject", ServerUtils.getProfile(this.resources).option(".post-ex.smartinject") ? "true" : "false");
var14.put("c2profile", ServerUtils.getProfile(this.resources));
var14.put("pubkey", BeaconSetup.beacon_asymmetric().exportPublicKey());

那么此时客户端的流程中继续走到了aggressor.dialogs.Connect#result方法中,但是此时执行的分支就是关于"aggressor.metadata",如下图所示

其中aggressor.AggressorClient对象的aggressor.AggressorClient#setup方法就是对登录之后的窗口进行加载

aggressor.AggressorClient#setup方法执行完成之后,又发了一个请求"aggressor.ready"给服务端

这里的话继续切回服务端进行观察,同样是走到process方法中,可以看到其中就存在一个"aggressor.ready"方法,这里会通过服务端走到了"aggressor.ready"分支中,然后发起server.Resources#register注册和server.Resources#broadcast发起广播

其中server.Resources#register是进行同步的操作,跟进去会发现调用了server.Resources#playback方法,这个就是对应加载那边的进度条同步数据

server.Resources#broadcast则进行广播,通知哪个用户的加入服务端,到这里登录的流程就结束了

流程总结图


浙公网安备 33010602011771号