【软件逆向-CS 4.4 二开笔记:增强篇】此文章归类为:软件逆向。
本文为CS 4.4 二开笔记系列第二篇,主要是针对aggressor客户端进行功能分析与二开。感觉现在的查到的文章较多在于beacon的协议的分析和针对特征的修改,在aggressor的分析还是比较少的,而aggressor又是使用者进行一系列操作的入口,只掌握sleep来编写cna脚本是远远不够的,因此本文将着重讨论针对aggressor功能的分析与扩充,文中可能会对部分步骤进行省略,主要是提供足够的思路来分析aggressor的源码和增强。
之前在实战中遇到的问题是经常忘了连接的是哪台服务器的teamserver,后来分析了一下cscat 4.5的源码,他的设置方式为通过aggressor进行设置,因为我在开发时没有打算在aggressor处设置配置文件,因此使用了不同的方式实现显示,先看一下效果:
此处涉及到cna脚本的编写及aggressor的源码,cna脚本的编写可以参考下面的官方链接及狼组的中文文档,本文中不再赘述:
cna脚本会在点击时检索脚本中是否存在相应的函数,如果不存在则进行函数提交,交给java端进行判断及相应,在这里我修改了默认的default.cna脚本,新增了serverip()
函数来进行显示:
这个函数的实现并不在cna脚本中,因此在触发时脚本会向aggressor发送serverip
这个字符串,然后aggressor会判断是否等于这个字符串来进行响应。aggressor的代码与下面两部分有关:
DataBridge.java
的scriptLoaded()
函数中,需要先注册这个函数的键值:DataBridge.java
的evaluate()
函数中,当接收到这个键值传来信号时则做出响应:aggressorRemoteIp
这个字符串是我在connect.java
中设置的public static
变量,当我们在connect界面连接teamserver时则将对应的host传递给event log,最后达到了我们想要的效果:钉钉提醒网上常用的方案为调用agscript来执行python操作,再进行提醒。我感觉这样过于麻烦,因此直接将此功能集成至server端中,只需要在server端的配置文件中填入sever端的配置文件,就可以默认启动,类似
server端日志显示:
创建方法如下:
新建一个类:
接着在server/Beacons.java
中调用这个类,我这里是通过配置文件进行的设置,其他方式大家可以自己研究:
上线效果就是这样:
这个功能需要涉及到数据库的保存,这里使用的是jdbc,效果如下:
在新增这个功能时,发现了cscat 4.5的一个bug,就是无法正确的显示last,后来发现主要原因在于server/Beacons.java的checkin
函数循环导致无法正确刷新map。目前更改后的checkin代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
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
}
}
|
数据库的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
// 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);
}
}
}
|
在护网时遇到比较多的一个问题,就是开着热点上的cs,中途去吃饭了结果回来再连上热点就会显示该用户已使用,导致就要重启客户端,因此在此处新增用户名后缀防止重复登录。
主要修改点在server/ManageUser.java
中,在process函数中新增:
这个随机字符串我控制在长度为3,对应实现的类为:
最终效果就是这样,终于解决了这个烦人的问题:
在原版CS中,每次新增监听器时默认值为本地的IP地址,还需要手动改为远程IP地址,因此在这里对这部分代码进行修改:Connect.java
:新增public static String aggressorRemoteIp = "";
然后在ScListenerDialog.java修改显示参数:
show_http
和show_https
都改掉:
最后更改效果为:
这个操作就跟上面 Event log显示远程IP差不多,主要单独列出来方便直接看。default.cna
:
1
|
item(
"&New Connection"
, { openConnectDialog(); });
|
调用的是openConnectDialog(),这个在java端的aggressor/bridges/AggressorBridges.java
中进行判断,如果点击了这个,则调用这个文件中的代码。AggressorBridges.java
先是注册,再是响应:
注册:
响应:
最后执行的代码为:
1
|
(
new
ConnectDialog(
this
.window)).show();
|
这个代码是在aggressor/dialogs/ConnectDialog.java
中的类。实际上aggressor界面上的那些按钮的触发都是这个套路,因此我们后续如果不想用纯粹的cna脚本,就可以用cna脚本触发,函数写死在java端,好处就是把一些不改动的cna脚本写死在里面,不用拷贝给队友时还需要额外附带脚本。cna脚本写死并加载的地方在aggressor/AggressorClient.java
中:
1
2
3
|
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());
// 监听器端口
|
我之所以弄这个,是之前想将免杀模块放在本地来进行操作,后来这个方案被我废弃了,打算将免杀流程放在teamserver端进行操作,再回传至aggressor。
发送信号我是分析的钓鱼模块,aggressor交互都在aggressor/dialogs里面,发送信号使用的是TeamQueue类,CS的通信主要用的就是这个类:
1
2
|
protected
TeamQueue conn =
null
;
this
.conn.call(
"SendSign.test"
, CommonUtils.args(var1, var2, var3, checksum));
|
其中"SendSign.test"
代表的是发送数据的标签,服务端需要根据这个标签进行判断,后面的几个都是传递的参数。我自己实现的调用逻辑为:dialogAction() -> send() -> final_send()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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);
}
|
与dialog.java成对存在的就是server目录中的文件,我这自己新建了一个类,具体接收信号的函数为:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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为四个参数。
广播的代码为:
1
|
this
.resources.broadcast(
"genCrossC2"
, (String) var1.arg(
3
),
true
);
|
这个true的含义貌似是aggressor重连后还会接收到这个信息,我改为false也没什么变化,广播的数据为Map类型。
接收广播部分有个巨坑,如果直接使用TeamQueue提供的setSubscriber回调函数来接收广播,则会导致CS的UI不会刷新。存在问题的代码为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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'!"
);
}
}
}
}
});
|
这个代码虽然可以正确接收,但是UI不刷新肯定是存在问题的,后来换了一个方案:
1
2
3
4
5
6
|
this
.conn.setSubscriber(
this
.data);
String response = data.getDataSafe(
"genCrossC2"
).toString();
if
(checksum.equals(response)){
System.out.println(
"接收到服务端广播的内容是:"
+ checksum);
break
;
}
|
在使用这个时,一定要注意是否data中存在这个键值对,如果不存在就会空指针异常,因此我在dialogAction中去检索data数据前先使用this.data.put("key","value");
进行了置空操作,问题就解决了。
以上是基于下面的模型进行讲解的,掌握这个模型一系列操作方式后CS的可玩性还是非常高的。
本人也是刚刚学习java开发,文中可能存在诸多问题,欢迎大家指出,最后祝大家国庆快乐~
更多【软件逆向-CS 4.4 二开笔记:增强篇】相关视频教程:www.yxfzedu.com