CAPE-parsers 0.1.47__tar.gz → 0.1.48__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/PKG-INFO +1 -1
  2. cape_parsers-0.1.48/cape_parsers/CAPE/community/Amadey.py +218 -0
  3. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/CobaltStrikeBeacon.py +3 -3
  4. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/LokiBot.py +11 -8
  5. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/Lumma.py +3 -3
  6. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/Snake.py +2 -2
  7. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/BumbleBee.py +5 -5
  8. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Remcos.py +1 -1
  9. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/__init__.py +1 -1
  10. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/pyproject.toml +39 -31
  11. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/LICENSE +0 -0
  12. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/README.md +0 -0
  13. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/__init__.py +0 -0
  14. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/AgentTesla.py +0 -0
  15. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/Arkei.py +0 -0
  16. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/AsyncRAT.py +0 -0
  17. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/AuroraStealer.py +0 -0
  18. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/Carbanak.py +0 -0
  19. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/CobaltStrikeStager.py +0 -0
  20. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/DCRat.py +0 -0
  21. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/Fareit.py +0 -0
  22. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/KoiLoader.py +0 -0
  23. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/NanoCore.py +0 -0
  24. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/Nighthawk.py +0 -0
  25. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/Njrat.py +0 -0
  26. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/PhemedroneStealer.py +0 -0
  27. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/QuasarRAT.py +0 -0
  28. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/README.md +0 -0
  29. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/SparkRAT.py +0 -0
  30. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/Stealc.py +0 -0
  31. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/VenomRAT.py +0 -0
  32. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/XWorm.py +0 -0
  33. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/XenoRAT.py +0 -0
  34. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/__init__.py +0 -0
  35. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/community/monsterv2.py +0 -0
  36. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/AdaptixBeacon.py +0 -0
  37. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Azorult.py +0 -0
  38. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/BitPaymer.py +0 -0
  39. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/BlackDropper.py +0 -0
  40. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Blister.py +0 -0
  41. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/BruteRatel.py +0 -0
  42. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/DarkGate.py +0 -0
  43. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/DoppelPaymer.py +0 -0
  44. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/DridexLoader.py +0 -0
  45. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Formbook.py +0 -0
  46. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/GuLoader.py +0 -0
  47. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/IcedID.py +0 -0
  48. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/IcedIDLoader.py +0 -0
  49. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Latrodectus.py +0 -0
  50. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Oyster.py +0 -0
  51. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/PikaBot.py +0 -0
  52. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/PlugX.py +0 -0
  53. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/QakBot.py +0 -0
  54. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Quickbind.py +0 -0
  55. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/README.md +0 -0
  56. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/RedLine.py +0 -0
  57. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Rhadamanthys.py +0 -0
  58. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/SmokeLoader.py +0 -0
  59. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Socks5Systemz.py +0 -0
  60. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/SquirrelWaffle.py +0 -0
  61. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Strrat.py +0 -0
  62. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/WarzoneRAT.py +0 -0
  63. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/Zloader.py +0 -0
  64. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/__init__.py +0 -0
  65. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/CAPE/core/test_cape.py +0 -0
  66. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/RATDecoders/README.md +0 -0
  67. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/RATDecoders/__init__.py +0 -0
  68. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/RATDecoders/test_rats.py +0 -0
  69. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/BackOffLoader.py +0 -0
  70. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/BackOffPOS.py +0 -0
  71. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/BlackNix.py +0 -0
  72. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/BuerLoader.py +0 -0
  73. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/ChChes.py +0 -0
  74. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/Emotet.py +0 -0
  75. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/Enfal.py +0 -0
  76. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/EvilGrab.py +0 -0
  77. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/Greame.py +0 -0
  78. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/Hancitor.py +0 -0
  79. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/HttpBrowser.py +0 -0
  80. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/JavaDropper.py +0 -0
  81. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/Nymaim.py +0 -0
  82. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/Pandora.py +0 -0
  83. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/PoisonIvy.py +0 -0
  84. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/PredatorPain.py +0 -0
  85. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/Punisher.py +0 -0
  86. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/RCSession.py +0 -0
  87. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/REvil.py +0 -0
  88. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/RedLeaf.py +0 -0
  89. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/Retefe.py +0 -0
  90. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/Rozena.py +0 -0
  91. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/SmallNet.py +0 -0
  92. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/TSCookie.py +0 -0
  93. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/TrickBot.py +0 -0
  94. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/UrsnifV3.py +0 -0
  95. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/_ShadowTech.py +0 -0
  96. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/_VirusRat.py +0 -0
  97. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/_jRat.py +0 -0
  98. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/unrecom.py +0 -0
  99. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/deprecated/xRAT.py +0 -0
  100. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/malduck/LICENSE +0 -0
  101. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/malduck/README.md +0 -0
  102. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/malduck/__init__.py +0 -0
  103. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/malduck/test_malduck.py +0 -0
  104. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/mwcp/README.md +0 -0
  105. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/mwcp/__init__.py +0 -0
  106. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/mwcp/test_mwcp.py +0 -0
  107. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/utils/__init__.py +0 -0
  108. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/utils/aplib.py +0 -0
  109. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/utils/blzpack.py +0 -0
  110. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/utils/blzpack_lib.so +0 -0
  111. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/utils/dotnet_utils.py +0 -0
  112. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/utils/lznt1.py +0 -0
  113. {cape_parsers-0.1.47 → cape_parsers-0.1.48}/cape_parsers/utils/strings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: CAPE-parsers
3
- Version: 0.1.47
3
+ Version: 0.1.48
4
4
  Summary: CAPE: Malware Configuration Extraction
5
5
  License: MIT
6
6
  Keywords: cape,parsers,malware,configuration
@@ -0,0 +1,218 @@
1
+ import base64
2
+ import yara
3
+ import pefile
4
+ import json
5
+ import struct
6
+ import re
7
+
8
+
9
+ RULE_SOURCE_KEY = """
10
+ rule Amadey_Key_String
11
+ {
12
+ meta:
13
+ author = "YungBinary"
14
+ description = "Find decryption key in Amadey."
15
+ strings:
16
+ $chunk_1 = {
17
+ 6A 20
18
+ 68 ?? ?? ?? ??
19
+ B9 ?? ?? ?? ??
20
+ E8 ?? ?? ?? ??
21
+ 68 ?? ?? ?? ??
22
+ E8 ?? ?? ?? ??
23
+ 59
24
+ C3
25
+ }
26
+ condition:
27
+ $chunk_1
28
+ }
29
+ """
30
+
31
+ RULE_SOURCE_ENCODED_STRINGS = """
32
+ rule Amadey_Encoded_Strings
33
+ {
34
+ meta:
35
+ author = "YungBinary"
36
+ description = "Find encoded strings in Amadey."
37
+ strings:
38
+ $chunk_1 = {
39
+ 6A ??
40
+ 68 ?? ?? ?? ??
41
+ B9 ?? ?? ?? ??
42
+ E8 ?? ?? ?? ??
43
+ 68 ?? ?? ?? ??
44
+ E8 ?? ?? ?? ??
45
+ 59
46
+ C3
47
+ }
48
+ condition:
49
+ $chunk_1
50
+ }
51
+ """
52
+
53
+
54
+ def contains_non_printable(byte_array):
55
+ for byte in byte_array:
56
+ if not chr(byte).isprintable():
57
+ return True
58
+ return False
59
+
60
+
61
+ def yara_scan_generator(raw_data, rule_source):
62
+ yara_rules = yara.compile(source=rule_source)
63
+ matches = yara_rules.match(data=raw_data)
64
+
65
+ for match in matches:
66
+ for block in match.strings:
67
+ for instance in block.instances:
68
+ yield instance.offset, block.identifier
69
+
70
+
71
+ def get_keys(pe, data):
72
+ image_base = pe.OPTIONAL_HEADER.ImageBase
73
+ keys = []
74
+ for offset, _ in yara_scan_generator(data, RULE_SOURCE_KEY):
75
+ try:
76
+ key_string_rva = struct.unpack('i', data[offset + 3 : offset + 7])[0]
77
+ key_string_dword_offset = pe.get_offset_from_rva(key_string_rva - image_base)
78
+ key_string = pe.get_string_from_data(key_string_dword_offset, data)
79
+
80
+ if b"=" not in key_string:
81
+ keys.append(key_string.decode())
82
+
83
+ if len(keys) == 2:
84
+ return keys
85
+
86
+ except Exception:
87
+ continue
88
+
89
+ return []
90
+
91
+
92
+ def get_encoded_strings(pe, data):
93
+ encoded_strings = []
94
+ image_base = pe.OPTIONAL_HEADER.ImageBase
95
+ for offset, _ in yara_scan_generator(data, RULE_SOURCE_ENCODED_STRINGS):
96
+
97
+ try:
98
+ encoded_string_size = data[offset + 1]
99
+ encoded_string_rva = struct.unpack('i', data[offset + 3 : offset + 7])[0]
100
+ encoded_string_dword_offset = pe.get_offset_from_rva(encoded_string_rva - image_base)
101
+ encoded_string = pe.get_string_from_data(encoded_string_dword_offset, data)
102
+
103
+ # Make sure the string matches length from operand
104
+ if encoded_string_size != len(encoded_string):
105
+ continue
106
+
107
+ encoded_strings.append(encoded_string.decode())
108
+
109
+ except Exception:
110
+ continue
111
+
112
+ return encoded_strings
113
+
114
+
115
+ def decode_amadey_string(key: str, encoded_str: str) -> bytes:
116
+ """
117
+ Decode Amadey encoded strings that look like base64
118
+ """
119
+ alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 "
120
+
121
+ decoded = ""
122
+ for i in range(len(encoded_str)):
123
+ if encoded_str[i] == "=":
124
+ decoded += "="
125
+ continue
126
+
127
+ index_1 = alphabet.index(encoded_str[i % len(encoded_str)])
128
+ index_2 = alphabet.index(key[i % len(key)])
129
+
130
+ index_result = (index_1 + (0x3F - index_2) + 0x3F) % 0x3F
131
+
132
+ decoded += alphabet[index_result]
133
+
134
+ decoded = base64.b64decode(decoded)
135
+
136
+ return decoded
137
+
138
+
139
+ def find_campaign_id(data):
140
+ pattern = br'\x00\x00\x00([0-9a-f]{6})\x00\x00'
141
+ matches = re.findall(pattern, data)
142
+ if matches:
143
+ return matches[0]
144
+
145
+
146
+ def extract_config(data):
147
+ pe = pefile.PE(data=data, fast_load=True)
148
+ # image_base = pe.OPTIONAL_HEADER.ImageBase
149
+
150
+ keys = get_keys(pe, data)
151
+ if not keys:
152
+ return {}
153
+
154
+ decode_key = keys[0]
155
+ rc4_key = keys[1]
156
+ encoded_strings = get_encoded_strings(pe, data)
157
+
158
+ decoded_strings = []
159
+ for encoded_string in encoded_strings:
160
+ try:
161
+ decoded_string = decode_amadey_string(decode_key, encoded_string)
162
+ if not decoded_string or contains_non_printable(decoded_string):
163
+ continue
164
+ decoded_strings.append(decoded_string.decode())
165
+ except Exception:
166
+ continue
167
+
168
+ if not decoded_strings:
169
+ return {}
170
+
171
+ decoded_strings = decoded_strings[:10]
172
+ final_config = {}
173
+ version = ""
174
+ install_dir = ""
175
+ install_file = ""
176
+ ip_pattern = r"^(?:\d{1,3}\.){3}\d{1,3}$"
177
+ version_pattern = r"^\d+\.\d{1,2}$"
178
+ install_dir_pattern = r"^[0-9a-f]{10}$"
179
+
180
+ i = 0
181
+ while i < len(decoded_strings):
182
+ s = decoded_strings[i]
183
+ if re.match(ip_pattern, s):
184
+ if i + 1 < len(decoded_strings):
185
+ path = decoded_strings[i+1]
186
+ final_config.setdefault("CNCs", []).append(f"http://{s}{path}")
187
+ i += 1 # Skip next element as it has been processed
188
+ elif re.match(version_pattern, s):
189
+ version = s
190
+ elif re.match(install_dir_pattern, s):
191
+ install_dir = s
192
+ elif s.endswith(".exe"):
193
+ install_file = s
194
+ i += 1
195
+
196
+ if version:
197
+ final_config["version"] = version
198
+ if install_dir:
199
+ final_config.setdefault("raw", {})["install_dir"] = install_dir
200
+ if install_file:
201
+ final_config.setdefault("raw", {})["install_file"] = install_file
202
+
203
+ final_config["cryptokey"] = rc4_key
204
+ final_config["cryptokey_type"] = "RC4"
205
+
206
+ campaign_id = find_campaign_id(data)
207
+ if campaign_id:
208
+ final_config["campaign_id"] = campaign_id.decode()
209
+
210
+ return final_config
211
+
212
+
213
+
214
+ if __name__ == "__main__":
215
+ import sys
216
+
217
+ with open(sys.argv[1], "rb") as f:
218
+ print(json.dumps(extract_config(f.read()), indent=4))
@@ -360,11 +360,11 @@ class cobaltstrikeConfig:
360
360
  if parsed_setting == "Not Found" and quiet:
361
361
  continue
362
362
  if not isinstance(parsed_setting, list):
363
- log.debug("{: <{width}} - {val}".format(conf_name, width=COLUMN_WIDTH - 3, val=parsed_setting))
363
+ log.debug("{: <{width}} - {val}".format(conf_name, width=COLUMN_WIDTH - 3, val=parsed_setting)) # noqa: G001
364
364
  elif parsed_setting == []:
365
- log.debug("{: <{width}} - {val}".format(conf_name, width=COLUMN_WIDTH - 3, val="Empty"))
365
+ log.debug("{: <{width}} - {val}".format(conf_name, width=COLUMN_WIDTH - 3, val="Empty")) # noqa: G001
366
366
  else:
367
- log.debug("{: <{width}} - {val}".format(conf_name, width=COLUMN_WIDTH - 3, val=parsed_setting[0]))
367
+ log.debug("{: <{width}} - {val}".format(conf_name, width=COLUMN_WIDTH - 3, val=parsed_setting[0])) # noqa: G001
368
368
  for val in parsed_setting[1:]:
369
369
  log.debug(" " * COLUMN_WIDTH, end="")
370
370
  print(val)
@@ -23,7 +23,7 @@
23
23
  import re
24
24
  import struct
25
25
  import sys
26
-
26
+ from contextlib import suppress
27
27
  import pefile
28
28
  from Cryptodome.Cipher import DES3
29
29
  from Cryptodome.Util.Padding import unpad
@@ -128,7 +128,8 @@ def decoder(data):
128
128
  else:
129
129
  x = bytearray(img)
130
130
 
131
- url_re = rb"https?:\/\/[a-zA-Z0-9\/\.:?\-_]+"
131
+ # ToDo add \.php
132
+ url_re = rb"https?:\/\/[a-zA-Z0-9\/\.:?\-_]+\.php"
132
133
  urls = re.findall(url_re, x)
133
134
  if not urls:
134
135
  for i in range(len(x)):
@@ -144,21 +145,23 @@ def decoder(data):
144
145
  confs = find_conf(img)
145
146
  if iv and iv not in (b"", -1) and confs != []:
146
147
  for conf in confs:
147
- try:
148
+ with suppress(ValueError):
148
149
  dec = DES3.new(key, DES3.MODE_CBC, iv)
149
150
  temp = dec.decrypt(conf)
150
151
  temp = unpad(temp, 8)
151
- urls.append(b"http://" + temp)
152
- except ValueError:
153
- # wrong padding
154
- pass
152
+ if not temp.endswith(b".php"):
153
+ continue
154
+ urls.append("http://" + temp.decod())
155
155
  return urls
156
156
 
157
157
 
158
158
  def extract_config(filebuf):
159
159
 
160
160
  urls = decoder(filebuf)
161
- return {"CNCs": [url.decode() for url in urls]}
161
+ if urls:
162
+ return {"CNCs": urls}
163
+ else:
164
+ return {}
162
165
 
163
166
 
164
167
  if __name__ == "__main__":
@@ -318,7 +318,7 @@ def extract_config(data):
318
318
  decoded_c2 = chacha20_xor(encrypted_string, key, nonce, counter).split(b"\x00", 1)[0]
319
319
  if contains_non_printable(decoded_c2):
320
320
  break
321
- config.setdefault("CNCs", []).append(decoded_c2.decode())
321
+ config.setdefault("CNCs", []).append("https://" + decoded_c2.decode())
322
322
  encrypted_strings_offset = encrypted_strings_offset + step_size
323
323
  counter += 2
324
324
 
@@ -351,7 +351,7 @@ def extract_config(data):
351
351
  decrypted = chacha20_xor(c2_encrypted, key, nonce, counter)
352
352
  c2 = extract_c2_domain(decrypted)
353
353
  if c2 is not None and len(c2) > 10:
354
- config["CNCs"].append(c2.decode())
354
+ config["CNCs"].append("https://" + c2.decode())
355
355
  break
356
356
 
357
357
  except Exception:
@@ -384,7 +384,7 @@ def extract_config(data):
384
384
  decoded_c2 = xor_data(encoded_c2, xor_key)
385
385
 
386
386
  if not contains_non_printable(decoded_c2):
387
- config.setdefault("CNCs", []).append(decoded_c2.decode())
387
+ config.setdefault("CNCs", []).append("https://" + decoded_c2.decode())
388
388
  except Exception as e:
389
389
  print(e)
390
390
  continue
@@ -133,7 +133,7 @@ def extract_config(data):
133
133
  try:
134
134
  dotnet_file = dnfile.dnPE(data=data)
135
135
  except Exception as e:
136
- log.debug(f"Exception when attempting to parse .NET file: {e}")
136
+ log.debug("Exception when attempting to parse .NET file: %s", str(e))
137
137
  log.debug(traceback.format_exc())
138
138
 
139
139
  # ldstr, stsfld
@@ -165,7 +165,7 @@ def extract_config(data):
165
165
  else:
166
166
  user_strings[field_name] = string_index
167
167
  except Exception as e:
168
- log.debug(f"There was an exception parsing user strings: {e}")
168
+ log.debug("There was an exception parsing user strings: %s", str(e))
169
169
  log.debug(traceback.format_exc())
170
170
 
171
171
  if c2_type is None:
@@ -50,7 +50,7 @@ def extract_key_data(data, pe, key_match):
50
50
  # Read arbitrary number of byes from key offset and split on null bytes to extract key
51
51
  key = data[key_offset : key_offset + 0x40].split(b"\x00")[0]
52
52
  except Exception as e:
53
- log.debug(f"There was an exception extracting the key: {e}")
53
+ log.debug("There was an exception extracting the key: %s", str(e))
54
54
  log.debug(traceback.format_exc())
55
55
  return False
56
56
  return key
@@ -70,7 +70,7 @@ def extract_config_data(data, pe, config_match):
70
70
  )
71
71
  campaign_id_ct = data[campaign_id_offset : campaign_id_offset + 0x10]
72
72
  except Exception as e:
73
- log.debug(f"There was an exception extracting the campaign id: {e}")
73
+ log.debug("There was an exception extracting the campaign id: %s", str(e))
74
74
  log.debug(traceback.format_exc())
75
75
  return False, False, False
76
76
 
@@ -84,7 +84,7 @@ def extract_config_data(data, pe, config_match):
84
84
  )
85
85
  botnet_id_ct = data[botnet_id_offset : botnet_id_offset + 0x10]
86
86
  except Exception as e:
87
- log.debug(f"There was an exception extracting the botnet id: {e}")
87
+ log.debug("There was an exception extracting the botnet id: %s", str(e))
88
88
  log.debug(traceback.format_exc())
89
89
  return False, False, False
90
90
 
@@ -99,7 +99,7 @@ def extract_config_data(data, pe, config_match):
99
99
  c2s_offset = pe.get_offset_from_rva(c2s_rva + int.from_bytes(config_match.group("c2s"), byteorder="little"))
100
100
  c2s_ct = data[c2s_offset : c2s_offset + 0x400]
101
101
  except Exception as e:
102
- log.debug(f"There was an exception extracting the C2s: {e}")
102
+ log.debug("There was an exception extracting the C2s: %s", str(e))
103
103
  log.debug(traceback.format_exc())
104
104
  return False, False, False
105
105
 
@@ -243,7 +243,7 @@ def extract_config(data):
243
243
  if c2s:
244
244
  cfg["CNCs"] = list(ARC4.new(key).decrypt(c2s).split(b"\x00")[0].decode().split(","))
245
245
  except Exception as e:
246
- log.error("This is broken: %s", str(e), exc_info=True)
246
+ log.exception("This is broken: %s", str(e))
247
247
 
248
248
  if not cfg:
249
249
  cfg = extract_2024(pe, data)
@@ -211,7 +211,7 @@ def extract_config(filebuf):
211
211
  config.setdefault("raw", {})[k] = v
212
212
 
213
213
  except Exception as e:
214
- logger.error(f"Caught an exception: {e}")
214
+ logger.error("Caught an exception: %s", str(e))
215
215
 
216
216
  return config
217
217
 
@@ -129,7 +129,7 @@ def load_malwareconfig_parsers() -> Tuple[bool, dict, ModuleType]:
129
129
  except ImportError:
130
130
  log.info("Missed RATDecoders -> poetry run pip install malwareconfig")
131
131
  except Exception as e:
132
- log.error(e, exc_info=True)
132
+ log.exception(e)
133
133
  return False, False, False
134
134
 
135
135
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "CAPE-parsers"
3
- version = "0.1.47"
3
+ version = "0.1.48"
4
4
  description = "CAPE: Malware Configuration Extraction"
5
5
  authors = ["Kevin O'Reilly <kev@capesandbox.com>", "doomedraven <doomedraven@capesandbox.com>"]
6
6
  license = "MIT"
@@ -27,7 +27,6 @@ unicorn = ">=2.1.1"
27
27
  rat-king-parser = ">=4.1.0"
28
28
 
29
29
  ruff = ">=0.7.2"
30
- # isort = ">=5.13.2"
31
30
 
32
31
  [tool.distutils.bdist_wheel]
33
32
  universal = false
@@ -36,8 +35,6 @@ universal = false
36
35
  maco = ["maco"]
37
36
 
38
37
  [tool.poetry.group.dev.dependencies]
39
- black = "^24.3.0"
40
- isort = "^5.10.1"
41
38
  pytest = "7.2.2"
42
39
  pytest-pretty = "1.1.0"
43
40
  pytest-cov = "3.0.0"
@@ -51,37 +48,11 @@ httpretty = "^1.1.4"
51
48
  func-timeout = "^4.3.5"
52
49
  pre-commit = "^2.19.0"
53
50
 
54
- # Doesnt work on github CI
55
- # [tool.poetry.requires-plugins]
56
- # poetry-plugin-export = ">=1.8"
57
-
58
- [tool.ruff]
59
- exclude = [
60
- "./analyzer/linux/dbus_next",
61
- ]
62
-
63
- [tool.black]
64
- line-length = 132
65
- include = "\\.py(_disabled)?$"
66
-
67
- [tool.isort]
68
- profile = "black"
69
- no_lines_before = ["FUTURE", "STDLIB"]
70
- line_length = 132
71
- supported_extensions = ["py", "py_disabled"]
72
-
73
- [tool.flake8]
74
- max-line-length = 132
75
- exclude = ".git,__pycache__,.cache,.venv"
76
-
77
51
  [tool.pytest.ini_options]
78
52
  pythonpath = ["."]
79
53
  testpaths = ["tests"]
80
54
  norecursedirs = "tests/zip_compound"
81
-
82
- [lint]
83
- select = ["E", "F"]
84
- ignore = ["E402","E501"]
55
+ asyncio_mode = "auto"
85
56
 
86
57
  [tool.poetry-dynamic-versioning]
87
58
  enable = false # Master switch to enable the plugin
@@ -89,3 +60,40 @@ enable = false # Master switch to enable the plugin
89
60
  [build-system]
90
61
  requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
91
62
  build-backend = "poetry.core.masonry.api"
63
+
64
+ [tool.ruff]
65
+ line-length = 132
66
+
67
+ [tool.ruff.lint]
68
+ select = [
69
+ "F", # pyflakes
70
+ "E", # pycodestyle errors
71
+ "W", # pycodestyle warnings
72
+ # "I", # isort
73
+ # "N", # pep8-naming
74
+ "G", # flake8-logging-format
75
+ ]
76
+
77
+ ignore = [
78
+ "E501", # ignore due to conflict with formatter
79
+ "N818", # exceptions don't need the Error suffix
80
+ "E741", # allow ambiguous variable names
81
+ "E402",
82
+ "W605", # ToDo to fix - Invalid escape sequence
83
+ ]
84
+
85
+ fixable = ["ALL"]
86
+
87
+
88
+ [tool.ruff.format]
89
+ quote-style = "double"
90
+ indent-style = "space"
91
+ skip-magic-trailing-comma = false
92
+ line-ending = "auto"
93
+
94
+ [tool.ruff.lint.isort]
95
+ known-first-party = ["libqtile", "test"]
96
+ default-section = "third-party"
97
+
98
+ [tool.mypy]
99
+ warn_unused_configs = true
File without changes
File without changes