maco-extractor 1.2.18__py3-none-any.whl

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.
maco/__init__.py ADDED
File without changes
maco/exceptions.py ADDED
@@ -0,0 +1,33 @@
1
+ """Exception classes for extractors."""
2
+
3
+
4
+ # Can be raised by extractors to abort analysis of a sample
5
+ # ie. Can abort if preliminary checks at start of run indicate the file shouldn't be analyzed by extractor
6
+ class AnalysisAbortedException(Exception):
7
+ """Raised when extractors voluntarily abort analysis of a sample."""
8
+
9
+ pass
10
+
11
+
12
+ class ExtractorLoadError(Exception):
13
+ """Raised when extractors cannot be loaded."""
14
+
15
+ pass
16
+
17
+
18
+ class InvalidExtractor(ValueError):
19
+ """Raised when an extractor is invalid."""
20
+
21
+ pass
22
+
23
+
24
+ class NoHitException(Exception):
25
+ """Raised when the YARA rule of an extractor doesn't hit."""
26
+
27
+ pass
28
+
29
+
30
+ class SyntaxError(Exception):
31
+ """Raised when there's a syntax error in the YARA rule."""
32
+
33
+ pass
maco/extractor.py ADDED
@@ -0,0 +1,70 @@
1
+ """Base class for an extractor script."""
2
+
3
+ import logging
4
+ import textwrap
5
+ from typing import BinaryIO, List, Optional, Union
6
+
7
+ from maco import model, yara
8
+ from maco.exceptions import InvalidExtractor
9
+
10
+ DEFAULT_YARA_RULE = """
11
+ rule {name}
12
+ {{
13
+ condition:
14
+ true
15
+ }}
16
+ """
17
+
18
+
19
+ class Extractor:
20
+ """Base class for an analysis extractor with common entrypoint and metadata.
21
+
22
+ Override this docstring with a good description of your extractor.
23
+ """
24
+
25
+ family: Union[str, List[str]] = None # family or families of malware that is detected by the extractor
26
+ author: str = None # author of the extractor (name@organisation)
27
+ last_modified: str = None # last modified date (YYYY-MM-DD)
28
+ sharing: str = "TLP:WHITE" # who can this be shared with?
29
+ yara_rule: str = None # yara rule that we filter inputs with
30
+ reference: str = None # link to malware report or other reference information
31
+ logger: logging.Logger = None # logger for use when debugging
32
+
33
+ def __init__(self) -> None:
34
+ """Initialise the extractor.
35
+
36
+ Raises:
37
+ InvalidExtractor: When the extractor is invalid.
38
+ """
39
+ self.name = name = type(self).__name__
40
+ self.logger = logging.getLogger(f"maco.extractor.{name}")
41
+ self.logger.debug(f"initialise '{name}'")
42
+ if not self.family or not self.author or not self.last_modified:
43
+ raise InvalidExtractor("must set family, author, last_modified")
44
+ # if author does not set a yara rule, match on everything
45
+ if not self.yara_rule:
46
+ self.yara_rule = DEFAULT_YARA_RULE.format(name=name)
47
+ # unindent the yara rule from triple quoted string
48
+ # this is for friendly printing, yara handles the rule ok either way
49
+ self.yara_rule = textwrap.dedent(self.yara_rule)
50
+ # check yara rules conform to expected structure
51
+ # we throw away these compiled rules as we need all rules in system compiled together
52
+ try:
53
+ self.yara_compiled = yara.compile(source=self.yara_rule)
54
+ except yara.SyntaxError as e:
55
+ raise InvalidExtractor(f"{self.name} - invalid yara rule") from e
56
+ # need to track which plugin owns the rules
57
+ self.yara_rule_names = [x.identifier for x in self.yara_compiled]
58
+ if not len(list(self.yara_compiled)):
59
+ raise InvalidExtractor(f"{name} must define at least one yara rule")
60
+ for x in self.yara_compiled:
61
+ if x.is_global:
62
+ raise InvalidExtractor(f"{x.identifier} yara rule must not be global")
63
+
64
+ def run(self, stream: BinaryIO, matches: List[yara.Match]) -> Optional[model.ExtractorModel]:
65
+ """Run the analysis process and return dict matching.
66
+
67
+ :param stream: file object from disk/network/memory.
68
+ :param match: yara rule match information contains locations of strings.
69
+ """
70
+ raise NotImplementedError()
maco/model/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from maco.model.model import * # noqa: F403
maco/model/model.py ADDED
@@ -0,0 +1,606 @@
1
+ """Malware config extractor output model."""
2
+
3
+ from enum import Enum
4
+ from typing import Any, Dict, List, Optional, Union
5
+
6
+ from pydantic import BaseModel, ConfigDict
7
+
8
+
9
+ class ForbidModel(BaseModel):
10
+ """We want to forbid extra properties, so that the 'other' field is used instead."""
11
+
12
+ model_config = ConfigDict(extra="forbid", use_enum_values=True)
13
+
14
+
15
+ class ConnUsageEnum(str, Enum):
16
+ """Purpose of the connection."""
17
+
18
+ c2 = "c2" # issue commands to malware
19
+ upload = "upload" # get data out of the network
20
+ download = "download" # fetch dynamic config, second stage, etc
21
+ propagate = "propagate" # spread through the network
22
+ tunnel = "tunnel" # communicate through the network
23
+ ransom = "ransom" # payment
24
+ decoy = "decoy" # Decoy connections to obfuscate malicious
25
+ other = "other"
26
+
27
+
28
+ class Encryption(ForbidModel):
29
+ """Encryption usage."""
30
+
31
+ class UsageEnum(str, Enum):
32
+ """Purpose of the encryption."""
33
+
34
+ config = "config"
35
+ communication = "communication"
36
+ binary = "binary"
37
+ ransom = "ransom"
38
+ other = "other"
39
+
40
+ algorithm: Optional[str] = None
41
+ public_key: Optional[str] = None
42
+ key: Optional[str] = None # private key or symmetric key
43
+ provider: Optional[str] = None # encryption library used. openssl, homebrew, etc.
44
+
45
+ mode: Optional[str] = None # block vs stream
46
+ # base 64'd binary data for these details?
47
+ # TODO to confirm usage of these different properties
48
+ iv: Optional[str] = None # initialisation vector
49
+ seed: Optional[str] = None
50
+ nonce: Optional[str] = None
51
+ constants: List[str] = []
52
+
53
+ usage: Optional[UsageEnum] = None
54
+
55
+
56
+ class CategoryEnum(str, Enum):
57
+ """Category of the malware."""
58
+
59
+ # Software that shows you extra promotions that you cannot control as you use your PC.
60
+ # You wouldn't see the extra ads if you didn't have adware installed.
61
+ adware = "adware"
62
+
63
+ # Malware related to an Advanced Persistent Threat (APT) group.
64
+ apt = "apt"
65
+
66
+ # A backdoor Trojan gives malicious users remote control over the infected computer.
67
+ # They enable the author to do anything they wish on the infected computer including
68
+ # sending, receiving, launching and deleting files, displaying data and rebooting the computer.
69
+ # Backdoor Trojans are often used to unite a group of victim computers to form a botnet or
70
+ # zombie network that can be used for criminal purposes.
71
+ backdoor = "backdoor"
72
+
73
+ # Trojan Banker programs are designed to steal your account data for online banking systems,
74
+ # e-payment systems and credit or debit cards.
75
+ banker = "banker"
76
+
77
+ # A malware variant that modifies the boot sectors of a hard drive, including the Master Boot Record (MBR)
78
+ # and Volume Boot Record (VBR).
79
+ bootkit = "bootkit"
80
+
81
+ # A malicious bot is self-propagating malware designed to infect a host and connect back to a central server
82
+ # or servers that act as a command and control (C&C) center for an entire network of compromised devices,
83
+ # or botnet.
84
+ bot = "bot"
85
+
86
+ # A browser hijacker is defined as a form of unwanted software that modifies a web browser's settings without
87
+ # the user's permission. The result is the placement of unwanted advertising into the browser,
88
+ # and possibly the replacement of an existing home page or search page with the hijacker page.
89
+ browser_hijacker = "browser_hijacker"
90
+
91
+ # Trojan bruteforcer are trying to brute force website in order to achieve something else
92
+ # (EX: Finding WordPress websites with default credentials).
93
+ bruteforcer = "bruteforcer"
94
+
95
+ # A type of trojan that can use your PC to 'click' on websites or applications.
96
+ # They are usually used to make money for a malicious hacker by clicking on online advertisements
97
+ # and making it look like the website gets more traffic than it does.
98
+ # They can also be used to skew online polls, install programs on your PC, or make unwanted software
99
+ # appear more popular than it is.
100
+ clickfraud = "clickfraud"
101
+
102
+ # Cryptocurrency mining malware.
103
+ cryptominer = "cryptominer"
104
+
105
+ # These programs conduct DoS (Denial of Service) attacks against a targeted web address.
106
+ # By sending multiple requests from your computer and several other infected computers,
107
+ # the attack can overwhelm the target address leading to a denial of service.
108
+ ddos = "ddos"
109
+
110
+ # Trojan Downloaders can download and install new versions of malicious programs in the target system.
111
+ downloader = "downloader"
112
+
113
+ # These programs are used by hackers in order to install malware or to prevent the detection of malicious programs.
114
+ dropper = "dropper"
115
+
116
+ # Exploit kits are programs that contain data or code that takes advantage of a vulnerability
117
+ # within an application that is running in the target system.
118
+ exploitkit = "exploitkit"
119
+
120
+ # Trojan FakeAV programs simulate the activity of antivirus software.
121
+ # They are designed to extort money in return for the detection and removal of threat, even though the
122
+ # threats that they report are actually non-existent.
123
+ fakeav = "fakeav"
124
+
125
+ # A type of tool that can be used to allow and maintain unauthorized access to your PC.
126
+ hacktool = "hacktool"
127
+
128
+ # A program that collects your personal information, such as your browsing history,
129
+ # and uses it without adequate consent.
130
+ infostealer = "infostealer"
131
+
132
+ # A keylogger monitors and logs every keystroke it can identify.
133
+ # Once installed, the virus either keeps track of all the keys and stores the information locally,
134
+ # after which the hacker needs physical access to the computer to retrieve the information,
135
+ # or the logs are sent over the internet back to the hacker.
136
+ keylogger = "keylogger"
137
+
138
+ # A program that loads another application / memory space.
139
+ loader = "loader"
140
+
141
+ # A type of malware that hides its code and purpose to make it more difficult for
142
+ # security software to detect or remove it.
143
+ obfuscator = "obfuscator"
144
+
145
+ # Point-of-sale malware is usually a type of malware that is used by cybercriminals to target point of sale (POS)
146
+ # and payment terminals with the intent to obtain credit card and debit card information.
147
+ pos = "pos"
148
+
149
+ # This type of trojan allows unauthorized parties to use the infected computer as a proxy server
150
+ # to access the Internet anonymously.
151
+ proxy = "proxy"
152
+
153
+ # A program that can be used by a remote hacker to gain access and control of an infected machine.
154
+ rat = "rat"
155
+
156
+ # This type of malware can modify data in the target computer so the operating system
157
+ # will stop running correctly or the data is no longer accessible.
158
+ # The criminal will only restore the computer state or data after a ransom is paid to them
159
+ # (mostly using cryptocurrency).
160
+ ransomware = "ransomware"
161
+
162
+ # A reverse proxy is a server that receives requests from the internet and forwards them to a small set of servers.
163
+ reverse_proxy = "reverse_proxy"
164
+
165
+ # Rootkits are designed to conceal certain objects or activities in the system.
166
+ # Often their main purpose is to prevent malicious programs being detected
167
+ # in order to extend the period in which programs can run on an infected computer.
168
+ rootkit = "rootkit"
169
+
170
+ # This type of malware scan the internet / network(s) / system(s) / service(s) to collect information.
171
+ # That information could be used later to perpetuate an cyber attack.
172
+ scanner = "scanner"
173
+
174
+ # Scareware is a form of malware which uses social engineering to cause shock, anxiety,
175
+ # or the perception of a threat in order to manipulate users into buying unwanted software.
176
+ scareware = "scareware"
177
+
178
+ # Malware that is sending spam.
179
+ spammer = "spammer"
180
+
181
+ # Generic or Unknown Trojan
182
+ trojan = "trojan"
183
+
184
+ # A generic computer virus
185
+ virus = "virus"
186
+
187
+ # A type of malware that destroy the data.
188
+ wiper = "wiper"
189
+
190
+ # A web shell is a script that can be uploaded to a web server to enable remote administration of the machine.
191
+ webshell = "webshell"
192
+
193
+ # A type of malware that spreads to other PCs.
194
+ worm = "worm"
195
+
196
+
197
+ class ExtractorModel(ForbidModel):
198
+ r"""Captured config/iocs, unpacked binaries and other malware properties from a robo-analyst.
199
+
200
+ This model defines common fields for output of a script targeting a specific malware family.
201
+ Usage of this model will allow for easier sharing of scripts between different authors and systems.
202
+ The model will not define fields for all data that can be extracted from a binary, only the most common.
203
+ This is to make it easier for authors to understand and use the model.
204
+
205
+ This model can have new fields added in the future if they become more common,
206
+ but the intent is to avoid removing or modifying existing fields, for backwards compatibility.
207
+
208
+ Where data does not fit with the current model, the 'others' field should be used.
209
+ Contents in this field is not defined by the model and verification/normalisation is up to
210
+ the author and whatever systems run the scripts.
211
+ If many decoders define similar data in the 'others' field, that field should be migrated to this model.
212
+
213
+ The model must be kept relatively flat, with nested lists of dictionaries to be avoided.
214
+ This is to make queries simpler to write in sql, elasticsearch and other storage systems.
215
+
216
+ Malware and systems that investigate malware can do pretty much anything.
217
+ This model needs to be simple and flexible to make sharing easy.
218
+ Some things should be out of scope for this model.
219
+ Responsibility for these things are up to authors and systems that use this model.
220
+
221
+ Out of scope
222
+ * Verifying anything in the 'others' dict, including that it is json-compatible.
223
+ * We don't know anything about the structure
224
+ * checking is json compatible requires dumping to json string, which can be slow
225
+ * Connecting specific config items to malware behaviour catalog
226
+ * i.e. "Persistence::Modify Registry" with 'registry' item from model (SYSTEM\ControlSet001\Services\)
227
+ * due to complexity and normalisation difficulties
228
+ * much malware behaviour is not related to specific config items
229
+ * Normalisation/verification of individual properties
230
+ * i.e. lowercase filepaths - some filesystems are case sensitive
231
+ * i.e. checking registry hives match known - not enough SME and too complex for a simple model
232
+ * generally, this quickly becomes complex (validating a fully defined http item)
233
+ * calling systems are probably performing their own validation anyway
234
+ * requiring specific properties to be set
235
+ * i.e. if http item is defined, requiring hostname to be set
236
+ * Some use cases always seem to exist where a property should not be set
237
+ """
238
+
239
+ family: Union[str, List[str]] # family or families of malware that was detected
240
+ version: Optional[str] = None # version/variant of malware
241
+ category: List[CategoryEnum] = [] # capability/purpose of the malware
242
+ attack: List[str] = [] # mitre att&ck reference ids, e.g. 'T1129'
243
+
244
+ #
245
+ # simple config properties
246
+ #
247
+
248
+ # capabilities of the malware enabled/disabled in config
249
+ # note these are probably malware-specific capabilities so no attempt to normalise has been made
250
+ # note - av/sandbox detection should be noted by 'detect_<product>'
251
+ capability_enabled: List[str] = []
252
+ capability_disabled: List[str] = []
253
+
254
+ campaign_id: List[str] = [] # Server/Campaign Id for malware
255
+ identifier: List[str] = [] # UUID/Identifiers for deployed instance
256
+ decoded_strings: List[str] = [] # decoded strings from within malware
257
+ password: List[str] = [] # Any password extracted from the binary
258
+ mutex: List[str] = [] # mutex to prevent multiple instances
259
+ pipe: List[str] = [] # pipe name used for communication
260
+ sleep_delay: Optional[int] = None # time to sleep/delay execution (milliseconds)
261
+ # additional time applied to sleep_delay (milliseconds).
262
+ # Jitter implementations can vary but usually it is a value from which a random number is generated and
263
+ # added/subtracted to the sleep_delay to make behaviour more unpredictable
264
+ sleep_delay_jitter: Optional[int] = None
265
+ inject_exe: List[str] = [] # name of executable to inject into
266
+
267
+ # configuration or clustering/research data that doesnt fit the other fields
268
+ # * rarely used by decoders or specific to one decoder
269
+ # to prevent key explosion, the keys must not be dynamically generated
270
+ # e.g. api_imports, api_checksums, num_imports, import_hash + many more
271
+ # data stored here must always be JSON-serialisable
272
+ other: Dict[str, Any] = {}
273
+
274
+ #
275
+ # embedded binary data
276
+ #
277
+ class Binary(ForbidModel):
278
+ """Binary data extracted by decoder."""
279
+
280
+ class TypeEnum(str, Enum):
281
+ """Type of binary data."""
282
+
283
+ payload = "payload" # contained within the original file
284
+ config = "config" # sometimes malware uses json/formatted text for config
285
+ other = "other"
286
+
287
+ datatype: Optional[TypeEnum] = None # what the binary data is used for
288
+ data: bytes # binary data, not json compatible
289
+
290
+ # other information for the extracted binary rather than the config
291
+ # data stored here must always be JSON-serialisable
292
+ # e.g. filename, extension, relationship label
293
+ other: Dict[str, Any] = {}
294
+
295
+ # convenience for ret.encryption.append(ret.Encryption(*properties))
296
+ # Define as class as only way to allow for this to be accessed and not have pydantic try to parse it.
297
+ class Encryption(Encryption):
298
+ """Encryption usage."""
299
+
300
+ pass
301
+
302
+ encryption: Union[List[Encryption], Encryption, None] = None # encryption information for the binary
303
+
304
+ binaries: List[Binary] = []
305
+
306
+ #
307
+ # communication protocols
308
+ #
309
+ class FTP(ForbidModel):
310
+ """Usage of FTP connection."""
311
+
312
+ username: Optional[str] = None
313
+ password: Optional[str] = None
314
+ hostname: Optional[str] = None
315
+ port: Optional[int] = None
316
+
317
+ path: Optional[str] = None
318
+
319
+ usage: Optional[ConnUsageEnum] = None
320
+
321
+ ftp: List[FTP] = []
322
+
323
+ class SMTP(ForbidModel):
324
+ """Usage of SMTP."""
325
+
326
+ # credentials and location of server
327
+ username: Optional[str] = None
328
+ password: Optional[str] = None
329
+ hostname: Optional[str] = None
330
+ port: Optional[int] = None
331
+
332
+ mail_to: List[str] = [] # receivers
333
+ mail_from: Optional[str] = None # sender
334
+ subject: Optional[str] = None
335
+
336
+ usage: Optional[ConnUsageEnum] = None
337
+
338
+ smtp: List[SMTP] = [] # SMTP server for malware
339
+
340
+ class Http(ForbidModel):
341
+ """Usage of HTTP connection."""
342
+
343
+ # malware sometimes does weird stuff with uris so we don't want to force
344
+ # authors to break the uri into username, hostname, path, etc.
345
+ # as we lose that information.
346
+ # e.g. extra '?' or '/' when unnecessary.
347
+ # or something that is technically an invalid uri but still works
348
+ uri: Optional[str] = None
349
+
350
+ # on the other hand we might not have enough info to construct a uri
351
+ protocol: Optional[str] = None # http,https
352
+ username: Optional[str] = None
353
+ password: Optional[str] = None
354
+ hostname: Optional[str] = None # (A host/hostname can be an IP, domain or hostname)
355
+ port: Optional[int] = None
356
+ path: Optional[str] = None
357
+ query: Optional[str] = None
358
+ fragment: Optional[str] = None
359
+
360
+ user_agent: Optional[str] = None # user agent sent by malware
361
+ method: Optional[str] = None # get put delete etc
362
+ headers: Optional[Dict[str, str]] = None # custom/additional HTTP headers
363
+ max_size: Optional[int] = None
364
+
365
+ usage: Optional[ConnUsageEnum] = None
366
+
367
+ http: List[Http] = []
368
+
369
+ class SSH(ForbidModel):
370
+ """Usage of ssh connection."""
371
+
372
+ username: Optional[str] = None
373
+ password: Optional[str] = None
374
+ hostname: Optional[str] = None
375
+ port: Optional[int] = None
376
+
377
+ usage: Optional[ConnUsageEnum] = None
378
+
379
+ ssh: List[SSH] = []
380
+
381
+ class Proxy(ForbidModel):
382
+ """Usage of proxy connection."""
383
+
384
+ protocol: Optional[str] = None # socks5,http
385
+ username: Optional[str] = None
386
+ password: Optional[str] = None
387
+ hostname: Optional[str] = None
388
+ port: Optional[int] = None
389
+
390
+ usage: Optional[ConnUsageEnum] = None
391
+
392
+ proxy: List[Proxy] = []
393
+
394
+ class ICMP(ForbidModel):
395
+ """Usage of ICMP."""
396
+
397
+ type: Optional[int] = None
398
+ code: Optional[int] = None
399
+ header: Optional[str] = None # Some malware uses non-standard header fields
400
+ hostname: Optional[str] = None
401
+
402
+ usage: Optional[ConnUsageEnum] = None
403
+
404
+ icmp: List[ICMP] = []
405
+
406
+ #
407
+ # inter process communication (IPC)
408
+ #
409
+ class IPC(ForbidModel):
410
+ """Usage of named pipe communications."""
411
+
412
+ # A record stored on disk, or a record synthesized on demand by a file
413
+ # server, which can be accessed by multiple processes.
414
+ file: Optional[List[str]] = None
415
+ # Data sent over a network interface, either to a different process on
416
+ # the same computer or to another computer on the network. Stream
417
+ # oriented (TCP; data written through a socket requires formatting to
418
+ # preserve message boundaries) or more rarely message-oriented (UDP,
419
+ # SCTP).
420
+ socket: Optional[List[str]] = None
421
+ # Similar to an internet socket, but all communication occurs within
422
+ # the kernel. Domain sockets use the file system as their address
423
+ # space. Processes reference a domain socket as an inode, and multiple
424
+ # processes can communicate with one socket.
425
+ unix_domain_socket: Optional[List[str]] = None
426
+ # A file mapped to RAM and can be modified by changing memory
427
+ # addresses directly instead of outputting to a stream. This shares
428
+ # the same benefits as a standard file.
429
+ memory_mapped_file: Optional[Union[bytes, List[str]]] = None
430
+ # A data stream similar to a socket, but which usually preserves
431
+ # message boundaries. Typically implemented by the operating system,
432
+ # they allow multiple processes to read and write to the message queue
433
+ # without being directly connected to each other.
434
+ message_queue: Optional[List[str]] = None
435
+ # A unidirectional data channel using standard input and output. Data
436
+ # written to the write-end of the pipe is buffered by the operating
437
+ # system until it is read from the read-end of the pipe. Two-way
438
+ # communication between processes can be achieved by using two pipes
439
+ # in opposite "directions".
440
+ anonymous_pipe: Optional[List[str]] = None
441
+ # A pipe that is treated like a file. Instead of using standard input
442
+ # and output as with an anonymous pipe, processes write to and read
443
+ # from a named pipe, as if it were a regular file.
444
+ named_pipe: Optional[List[str]] = None
445
+ # The process names involved in the IPC communication
446
+ process_names: Optional[List[str]] = None
447
+ # Multiple processes are given access to the same block of memory,
448
+ # which creates a shared buffer for the processes to communicate with
449
+ # each other.
450
+ shared_memory: Optional[bytes] = None
451
+ usage: Optional[ConnUsageEnum] = None
452
+
453
+ ipc: List[IPC] = [] # Inter-Process Communications (similar to 'pipe' but more detailed)
454
+
455
+ class DNS(ForbidModel):
456
+ """Direct usage of DNS."""
457
+
458
+ class RecordTypeEnum(str, Enum):
459
+ """DNS record types."""
460
+
461
+ A = "A"
462
+ AAAA = "AAAA"
463
+ AFSDB = "AFSDB"
464
+ APL = "APL"
465
+ CAA = "CAA"
466
+ CDNSKEY = "CDNSKEY"
467
+ CDS = "CDS"
468
+ CERT = "CERT"
469
+ CNAME = "CNAME"
470
+ CSYNC = "CSYNC"
471
+ DHCID = "DHCID"
472
+ DLV = "DLV"
473
+ DNAME = "DNAME"
474
+ DNSKEY = "DNSKEY"
475
+ DS = "DS"
476
+ EUI48 = "EUI48"
477
+ EUI64 = "EUI64"
478
+ HINFO = "HINFO"
479
+ HIP = "HIP"
480
+ HTTPS = "HTTPS"
481
+ IPSECKEY = "IPSECKEY"
482
+ KEY = "KEY"
483
+ KX = "KX"
484
+ LOC = "LOC"
485
+ MX = "MX"
486
+ NAPTR = "NAPTR"
487
+ NS = "NS"
488
+ NSEC = "NSEC"
489
+ NSEC3 = "NSEC3"
490
+ NSEC3PARAM = "NSEC3PARAM"
491
+ OPENPGPKEY = "OPENPGPKEY"
492
+ PTR = "PTR"
493
+ RRSIG = "RRSIG"
494
+ RP = "RP"
495
+ SIG = "SIG"
496
+ SMIMEA = "SMIMEA"
497
+ SOA = "SOA"
498
+ SRV = "SRV"
499
+ SSHFP = "SSHFP"
500
+ SVCB = "SVCB"
501
+ TA = "TA"
502
+ TKEY = "TKEY"
503
+ TLSA = "TLSA"
504
+ TSIG = "TSIG"
505
+ TXT = "TXT"
506
+ URI = "URI"
507
+ ZONEMD = "ZONEMD"
508
+
509
+ ip: Optional[str] = None
510
+ port: Optional[int] = None # The default value is 53
511
+ hostname: Optional[str] = None # This is the query hostname
512
+ record_type: Optional[RecordTypeEnum] = None # The DNS record type that is queried
513
+ usage: Optional[ConnUsageEnum] = None
514
+
515
+ dns: List[DNS] = [] # custom DNS address to use for name resolution
516
+
517
+ class Connection(ForbidModel):
518
+ """Generic TCP/UDP usage."""
519
+
520
+ client_ip: Optional[str] = None
521
+ client_port: Optional[int] = None
522
+ server_ip: Optional[str] = None
523
+ server_domain: Optional[str] = None
524
+ server_port: Optional[int] = None
525
+
526
+ usage: Optional[ConnUsageEnum] = None
527
+
528
+ tcp: List[Connection] = []
529
+ udp: List[Connection] = []
530
+
531
+ #
532
+ # complex configuration properties
533
+ #
534
+ # convenience for ret.encryption.append(ret.Encryption(*properties))
535
+ # Define as class as only way to allow for this to be accessed and not have pydantic try to parse it.
536
+ class Encryption(Encryption):
537
+ """Encryption usage."""
538
+
539
+ pass
540
+
541
+ encryption: List[Encryption] = []
542
+
543
+ class Service(ForbidModel):
544
+ """OS service usage by malware."""
545
+
546
+ dll: Optional[str] = None # dll that the service is loaded from
547
+ name: Optional[str] = None # service/driver name for persistence
548
+ display_name: Optional[str] = None # display name for service
549
+ description: Optional[str] = None # description for service
550
+
551
+ service: List[Service] = []
552
+
553
+ class Cryptocurrency(ForbidModel):
554
+ """Cryptocoin usage (ransomware/miner)."""
555
+
556
+ class UsageEnum(str, Enum):
557
+ """Cryptocoin usage."""
558
+
559
+ ransomware = "ransomware" # request money to unlock
560
+ miner = "miner" # use gpu/cpu to mint coins
561
+ other = "other"
562
+
563
+ coin: Optional[str] = None # BTC,ETH,USDT,BNB, etc
564
+ address: Optional[str] = None
565
+ ransom_amount: Optional[float] = None # number of coins required (if hardcoded)
566
+
567
+ usage: UsageEnum
568
+
569
+ cryptocurrency: List[Cryptocurrency] = []
570
+
571
+ class Path(ForbidModel):
572
+ """Path used by malware."""
573
+
574
+ class UsageEnum(str, Enum):
575
+ """Purpose of the path."""
576
+
577
+ c2 = "c2" # file/folder issues commands to malware
578
+ config = "config" # config is loaded from this path
579
+ install = "install" # install directory/filename for malware
580
+ plugins = "plugins" # load new capability from this directory
581
+ logs = "logs" # location to log activity
582
+ storage = "storage" # location to store/backup copied files
583
+ other = "other"
584
+
585
+ # C:\User\tmp\whatever.txt or /some/unix/folder/path
586
+ path: str
587
+ usage: Optional[UsageEnum] = None
588
+
589
+ paths: List[Path] = [] # files/directories used by malware
590
+
591
+ class Registry(ForbidModel):
592
+ """Registry usage by malware."""
593
+
594
+ class UsageEnum(str, Enum):
595
+ """Registry usage."""
596
+
597
+ persistence = "persistence" # stay alive
598
+ store_data = "store_data" # generated encryption keys or config
599
+ store_payload = "store_payload" # malware hidden in registry key
600
+ read = "read" # read system registry keys
601
+ other = "other"
602
+
603
+ key: str
604
+ usage: Optional[UsageEnum] = None
605
+
606
+ registry: List[Registry] = []
maco/yara.py ADDED
@@ -0,0 +1,129 @@
1
+ """yara-python facade that uses yara-x."""
2
+
3
+ import re
4
+ from collections import namedtuple
5
+ from itertools import cycle
6
+ from typing import Dict, List, Union
7
+
8
+ import yara_x
9
+
10
+ from maco.exceptions import SyntaxError
11
+
12
+ RULE_ID_RE = re.compile("(\w+)? ?rule (\w+)")
13
+
14
+
15
+ # Create interfaces that resembles yara-python (but is running yara-x under the hood)
16
+ class StringMatchInstance:
17
+ """Instance of a string match."""
18
+
19
+ def __init__(self, match: yara_x.Match, file_content: bytes):
20
+ """Initializes StringMatchInstance."""
21
+ self.matched_data = file_content[match.offset : match.offset + match.length]
22
+ self.matched_length = match.length
23
+ self.offset = match.offset
24
+ self.xor_key = match.xor_key
25
+
26
+ def plaintext(self) -> bytes:
27
+ """Plaintext of the matched data.
28
+
29
+ Returns:
30
+ (bytes): Plaintext of the matched cipher text
31
+ """
32
+ if not self.xor_key:
33
+ # No need to XOR the matched data
34
+ return self.matched_data
35
+ else:
36
+ return bytes(c ^ k for c, k in zip(self.matched_data, cycle(self.xor_key)))
37
+
38
+
39
+ class StringMatch:
40
+ """String match."""
41
+
42
+ def __init__(self, pattern: yara_x.Pattern, file_content: bytes):
43
+ """Initializes StringMatch."""
44
+ self.identifier = pattern.identifier
45
+ self.instances = [StringMatchInstance(match, file_content) for match in pattern.matches]
46
+ self._is_xor = any([match.xor_key for match in pattern.matches])
47
+
48
+ def is_xor(self):
49
+ """Checks if string match is xor'd.
50
+
51
+ Returns:
52
+ (bool): True if match is xor'd
53
+ """
54
+ return self._is_xor
55
+
56
+
57
+ class Match:
58
+ """Match."""
59
+
60
+ def __init__(self, rule: yara_x.Rule, file_content: bytes):
61
+ """Initializes Match."""
62
+ self.rule = rule.identifier
63
+ self.namespace = rule.namespace
64
+ self.tags = list(rule.tags) or []
65
+ self.meta = dict()
66
+ # Ensure metadata doesn't get overwritten
67
+ for k, v in rule.metadata:
68
+ self.meta.setdefault(k, []).append(v)
69
+ self.strings = [StringMatch(pattern, file_content) for pattern in rule.patterns]
70
+
71
+
72
+ class Rules:
73
+ """Rules."""
74
+
75
+ def __init__(self, source: str = None, sources: Dict[str, str] = None):
76
+ """Initializes Rules.
77
+
78
+ Raises:
79
+ SyntaxError: Raised when there's a syntax error in the YARA rule.
80
+ """
81
+ Rule = namedtuple("Rule", "identifier namespace is_global")
82
+ if source:
83
+ sources = {"default": source}
84
+
85
+ try:
86
+ self._rules = []
87
+ compiler = yara_x.Compiler(relaxed_re_syntax=True)
88
+ for namespace, source in sources.items():
89
+ compiler.new_namespace(namespace)
90
+ for rule_type, id in RULE_ID_RE.findall(source):
91
+ is_global = True if rule_type == "global" else False
92
+ self._rules.append(Rule(namespace=namespace, identifier=id, is_global=is_global))
93
+ compiler.add_source(source)
94
+ self.scanner = yara_x.Scanner(compiler.build())
95
+ except yara_x.CompileError as e:
96
+ raise SyntaxError(e)
97
+
98
+ def __iter__(self):
99
+ """Iterate over rules.
100
+
101
+ Yields:
102
+ YARA rules
103
+ """
104
+ for rule in self._rules:
105
+ yield rule
106
+
107
+ def match(self, filepath: str = None, data: Union[bytes, bytearray] = None) -> List[Match]:
108
+ """Performs a scan to check for YARA rules matches based on the file, either given by path or buffer.
109
+
110
+ Returns:
111
+ (List[Match]): A list of YARA matches.
112
+ """
113
+ if filepath:
114
+ with open(filepath, "rb") as fp:
115
+ data = fp.read()
116
+
117
+ if isinstance(data, bytearray):
118
+ data = bytes(data)
119
+
120
+ return [Match(m, data) for m in self.scanner.scan(data).matching_rules]
121
+
122
+
123
+ def compile(source: str = None, sources: Dict[str, str] = None) -> Rules:
124
+ """Compiles YARA rules from source or from sources.
125
+
126
+ Returns:
127
+ (Rules): a Rules object
128
+ """
129
+ return Rules(source, sources)
@@ -0,0 +1,283 @@
1
+ Metadata-Version: 2.4
2
+ Name: maco-extractor
3
+ Version: 1.2.18
4
+ Summary: This package contains the essentials for creating Maco extractors and using them at runtime.
5
+ Author: sl-govau
6
+ Maintainer: cccs-rs
7
+ License: MIT License
8
+
9
+ Copyright (c) 2022 Crown Copyright, Government of Canada (Canadian Centre for Cyber Security / Communications Security Establishment) and Government of Australia (Australian Cyber Security Centre / Australian Signals Directorate)
10
+
11
+ Copyright title to all 3rd party software distributed with maco is held by the respective copyright holders as noted in those files. Users are asked to read the 3rd Party Licenses referenced with those assets.
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
+
19
+ Project-URL: Repository, https://github.com/CybercentreCanada/Maco
20
+ Project-URL: Issues, https://github.com/CybercentreCanada/Maco/issues
21
+ Classifier: Development Status :: 5 - Production/Stable
22
+ Classifier: Intended Audience :: Developers
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: License :: OSI Approved :: MIT License
25
+ Classifier: Programming Language :: Python :: 3.8
26
+ Classifier: Programming Language :: Python :: 3.9
27
+ Classifier: Programming Language :: Python :: 3.10
28
+ Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Programming Language :: Python :: 3.12
30
+ Requires-Python: >=3.8
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE.md
33
+ Requires-Dist: pydantic>=2.0.0
34
+ Requires-Dist: yara-x
35
+ Dynamic: license-file
36
+
37
+ # Maco - Malware config extractor framework
38
+
39
+ ## Maco is a framework for <ins>ma</ins>lware <ins>co</ins>nfig extractors.
40
+
41
+ It aims to solve two problems:
42
+
43
+ - Define a standardize ontology (or model) for extractor output. This greatly helps for databasing extracted values.
44
+ - Provide a standard way of identifying which parsers to run and how to execute them.
45
+
46
+ ## Maco components
47
+
48
+ - `model.py`
49
+ - A data model for the common output of an extractor
50
+ - `extractor.py`
51
+ - Base class for extractors to implement
52
+ - `collector.py`
53
+ - Utilities for loading and running extractors
54
+ - `cli.py`
55
+ - A CLI tool `maco` to assist with running your extractors locally
56
+ - `base_test.py`
57
+ - Assist with writing unit tests for your extractors
58
+
59
+ **Note: If you're interested in using only the model in your project, you can `pip install maco-model` which is a smaller package containing only the model definition**
60
+
61
+ ## Project Integrations 🛠️
62
+
63
+ This framework is actively being used by:
64
+
65
+ | Project | Description | License |
66
+ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
67
+ | <a href="https://cybercentrecanada.github.io/assemblyline4_docs/"><img src="https://images.weserv.nl/?url=cybercentrecanada.github.io/assemblyline4_docs/images/crane.png?v=4&h=100&w=100&fit=cover&maxage=7d"></a> | A malware analysis platform that uses the MACO model to export malware configuration extractions into a parseable, machine-friendly format | [![License](https://img.shields.io/github/license/CybercentreCanada/assemblyline)](https://github.com/CybercentreCanada/assemblyline/blob/main/LICENSE.md) |
68
+ | [configextractor-py](https://github.com/CybercentreCanada/configextractor-py) | A tool designed to run extractors from multiple frameworks and uses the MACO model for output harmonization | [![License](https://img.shields.io/github/license/CybercentreCanada/configextractor-py)](https://github.com/CybercentreCanada/configextractor-py/blob/main/LICENSE.md) |
69
+ | <a href="https://github.com/jeFF0Falltrades/rat_king_parser"><img src="https://images.weserv.nl/?url=raw.githubusercontent.com/jeFF0Falltrades/rat_king_parser/master/.github/logo.png?v=4&h=100&w=100&fit=cover&maxage=7d"/> </a> | A robust, multiprocessing-capable, multi-family RAT config parser/extractor that is compatible with MACO | [![License](https://img.shields.io/github/license/jeFF0Falltrades/rat_king_parser)](https://github.com/jeFF0Falltrades/rat_king_parser/blob/master/LICENSE) |
70
+ | <a href="https://github.com/CAPESandbox/community"><img src="https://images.weserv.nl/?url=github.com/CAPESandbox.png?v=4&h=100&w=100&fit=cover&maxage=7d0&mask=circle"/> </a> | A parser/extractor repository containing MACO extractors that's authored by the CAPE community but is integrated in [CAPE](https://github.com/kevoreilly/CAPEv2) deployments.<br>**Note: These MACO extractors wrap and parse the original CAPE extractors.** | [![License](https://img.shields.io/badge/license-GPL--3.0-informational)](https://github.com/kevoreilly/CAPEv2/blob/master/LICENSE) |
71
+
72
+ ## Model Example
73
+
74
+ See [the model definition](https://github.com/CybercentreCanada/Maco/blob/0f447a66de5e5ce8770ef3fe2325aec002842e63/maco/model.py#L127) for all the supported fields.
75
+ You can use the model independently of the rest of the framework.
76
+ This is still useful for compatibility between systems!
77
+
78
+ ```python
79
+ from maco import model
80
+ # 'family' is the only required property on the model
81
+ output = model.ExtractorModel(family="wanabee")
82
+ output.version = "2019" # variant first found in 2019
83
+ output.category.extend([model.CategoryEnum.cryptominer, model.CategoryEnum.clickfraud])
84
+ output.http.append(model.ExtractorModel.Http(protocol="https",
85
+ uri="https://bad-domain.com/c2_payload",
86
+ usage="c2"))
87
+ output.tcp.append(model.ExtractorModel.Connection(server_ip="127.0.0.1",
88
+ usage="ransom"))
89
+ output.campaign_id.append("859186-3224-9284")
90
+ output.inject_exe.append("explorer.exe")
91
+ output.binaries.append(
92
+ output.Binary(
93
+ data=b"sam I am",
94
+ datatype=output.Binary.TypeEnum.config,
95
+ encryption=output.Binary.Encryption(
96
+ algorithm="rot26",
97
+ mode="block",
98
+ ),
99
+ )
100
+ )
101
+ # data about the malware that doesn't fit the model
102
+ output.other["author_lunch"] = "green eggs and ham"
103
+ output.other["author_lunch_time"] = "3pm"
104
+ print(output.model_dump(exclude_defaults=True))
105
+
106
+ # Generated model
107
+ {
108
+ 'family': 'wanabee',
109
+ 'version': '2019',
110
+ 'category': ['cryptominer', 'clickfraud'],
111
+ 'campaign_id': ['859186-3224-9284'],
112
+ 'inject_exe': ['explorer.exe'],
113
+ 'other': {'author_lunch': 'green eggs and ham', 'author_lunch_time': '3pm'},
114
+ 'http': [{'uri': 'https://bad-domain.com/c2_payload', 'usage': 'c2', 'protocol': 'https'}],
115
+ 'tcp': [{'server_ip': '127.0.0.1', 'usage': 'ransom'}],
116
+ 'binaries': [{
117
+ 'datatype': 'config', 'data': b'sam I am',
118
+ 'encryption': {'algorithm': 'rot26', 'mode': 'block'}
119
+ }]
120
+ }
121
+ ```
122
+
123
+ And you can create model instances from dictionaries:
124
+
125
+ ```python
126
+ from maco import model
127
+ output = {
128
+ "family": "wanabee2",
129
+ "version": "2022",
130
+ "ssh": [
131
+ {
132
+ "username": "wanna",
133
+ "password": "bee2",
134
+ "hostname": "10.1.10.100",
135
+ }
136
+ ],
137
+ }
138
+ print(model.ExtractorModel(**output))
139
+
140
+ # Generated model
141
+ family='wanabee2' version='2022' category=[] attack=[] capability_enabled=[]
142
+ capability_disabled=[] campaign_id=[] identifier=[] decoded_strings=[]
143
+ password=[] mutex=[] pipe=[] sleep_delay=None inject_exe=[] other={}
144
+ binaries=[] ftp=[] smtp=[] http=[]
145
+ ssh=[SSH(username='wanna', password='bee2', hostname='10.1.10.100', port=None, usage=None)]
146
+ proxy=[] dns=[] tcp=[] udp=[] encryption=[] service=[] cryptocurrency=[]
147
+ paths=[] registry=[]
148
+ ```
149
+
150
+ ## Extractor Example
151
+
152
+ The following extractor will trigger on any file with more than 50 ELF sections,
153
+ and set some properties in the model.
154
+
155
+ Your extractors will do a better job of finding useful information than this one!
156
+
157
+ ```python
158
+ class Elfy(extractor.Extractor):
159
+ """Check basic elf property."""
160
+
161
+ family = "elfy"
162
+ author = "blue"
163
+ last_modified = "2022-06-14"
164
+ yara_rule = """
165
+ import "elf"
166
+
167
+ rule Elfy
168
+ {
169
+ condition:
170
+ elf.number_of_sections > 50
171
+ }
172
+ """
173
+
174
+ def run(
175
+ self, stream: BytesIO, matches: List[yara.Match]
176
+ ) -> Optional[model.ExtractorModel]:
177
+ # return config model formatted results
178
+ ret = model.ExtractorModel(family=self.family)
179
+ # the list for campaign_id already exists and is empty, so we just add an item
180
+ ret.campaign_id.append(str(len(stream.read())))
181
+ return ret
182
+ ```
183
+
184
+ ## Writing Extractors
185
+
186
+ There are several examples that use Maco in the '`demo_extractors`' folder.
187
+
188
+ Some things to keep in mind:
189
+
190
+ - The Yara rule names must be prefixed with the extractor class name.
191
+ - e.g. Class 'MyScript' has Yara rules named 'MyScriptDetect1' and 'MyScriptDetect2', not 'Detect1'
192
+ - You can load other scripts contained within the same folder via a Python relative import
193
+ - See `complex.py` for details
194
+ - You can standardise your usage of the '`other`' dict
195
+ - This is optional, see `limit_other.py` for details
196
+ - Consider instead making a PR with the properties you are frequently using
197
+
198
+ # Requirements
199
+
200
+ Python 3.8+.
201
+
202
+ Install this package with `pip install maco`.
203
+
204
+ All required Python packages are in the `requirements.txt`.
205
+
206
+ # CLI Usage
207
+
208
+ ```bash
209
+ > maco --help
210
+ usage: maco [-h] [-v] [--pretty] [--base64] [--logfile LOGFILE] [--include INCLUDE] [--exclude EXCLUDE] [-f] [--create_venv] extractors samples
211
+
212
+ Run extractors over samples.
213
+
214
+ positional arguments:
215
+ extractors path to extractors
216
+ samples path to samples
217
+
218
+ optional arguments:
219
+ -h, --help show this help message and exit
220
+ -v, --verbose print debug logging. -v extractor info, -vv extractor debug, -vvv cli debug
221
+ --pretty pretty print json output
222
+ --base64 Include base64 encoded binary data in output (can be large, consider printing to file rather than console)
223
+ --logfile LOGFILE file to log output
224
+ --include INCLUDE comma separated extractors to run
225
+ --exclude EXCLUDE comma separated extractors to not run
226
+ -f, --force ignore yara rules and execute all extractors
227
+ --create_venv Creates venvs for every requirements.txt found (only applies when extractor path is a directory)
228
+ ```
229
+
230
+ ## CLI output example
231
+
232
+ The CLI is helpful for using your extractors in a standalone system, such as in a reverse engineering environment.
233
+
234
+ ```bash
235
+ > maco demo_extractors/ /usr/lib --include Complex
236
+ extractors loaded: ['Complex']
237
+
238
+ complex by blue 2022-06-14 TLP:WHITE
239
+ This script has multiple yara rules and coverage of the data model.
240
+
241
+ path: /usr/lib/udev/hwdb.bin
242
+ run Complex extractor from rules ['ComplexAlt']
243
+ {"family": "complex", "version": "5", "decoded_strings": ["Paradise"],
244
+ "binaries": [{"datatype": "payload", "size": 9, "hex_sample": "736F6D652064617461", "sha256": "1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee",
245
+ "encryption": {"algorithm": "something"}}],
246
+ "http": [{"protocol": "https", "hostname": "blarg5.com", "path": "/malz/9956330", "usage": "c2"}],
247
+ "encryption": [{"algorithm": "sha256"}]}
248
+
249
+ path: /usr/lib/udev/hwdb.d/20-OUI.hwdb
250
+ run Complex extractor from rules ['ComplexAlt']
251
+ {"family": "complex", "version": "5", "decoded_strings": ["Paradise"],
252
+ "binaries": [{"datatype": "payload", "size": 9, "hex_sample": "736F6D652064617461", "sha256": "1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee",
253
+ "encryption": {"algorithm": "something"}}],
254
+ "http": [{"protocol": "https", "hostname": "blarg5.com", "path": "/malz/1986908", "usage": "c2"}],
255
+ "encryption": [{"algorithm": "sha256"}]}
256
+
257
+ path: /usr/lib/udev/hwdb.d/20-usb-vendor-model.hwdb
258
+ run Complex extractor from rules ['ComplexAlt']
259
+ {"family": "complex", "version": "5", "decoded_strings": ["Paradise"],
260
+ "binaries": [{"datatype": "payload", "size": 9, "hex_sample": "736F6D652064617461", "sha256": "1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee",
261
+ "encryption": {"algorithm": "something"}}],
262
+ "http": [{"protocol": "https", "hostname": "blarg5.com", "path": "/malz/1257481", "usage": "c2"}],
263
+ "encryption": [{"algorithm": "sha256"}]}
264
+
265
+
266
+ 15884 analysed, 3 hits, 3 extracted
267
+ ```
268
+
269
+ The demo extractors are designed to trigger when run over the '`demo_extractors`' folder.
270
+
271
+ e.g. `maco demo_extractors demo_extractors`
272
+
273
+ # Contributions
274
+
275
+ Please use ruff to format and lint PRs. This may be the cause of PR test failures.
276
+
277
+ Ruff will attempt to fix most issues, but some may require manual resolution.
278
+
279
+ ```
280
+ pip install ruff
281
+ ruff format
282
+ ruff check --fix
283
+ ```
@@ -0,0 +1,11 @@
1
+ maco/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ maco/exceptions.py,sha256=XBHUrs1kr1ZayPI9B_W-WejKgVmC8sWL_o4RL0b4DQE,745
3
+ maco/extractor.py,sha256=s36aGcsXSc-9iCik6iihVt5G1a1DZUA7TquvWYQNwdE,2912
4
+ maco/yara.py,sha256=y141t8NqDDXHY23uE1d6BDPeNmSuUuohR6Yr_LKa7GI,4067
5
+ maco/model/__init__.py,sha256=ULdyHx8R5D2ICHZo3VoCk1YTlewTok36TYIpwx__pNY,45
6
+ maco/model/model.py,sha256=DBHTmZXMzjpVq0s2mzZv3VCzPhwPnv7sH6u_QZCTcA4,24484
7
+ maco_extractor-1.2.18.dist-info/licenses/LICENSE.md,sha256=gMSjshPhXvV_F1qxmeNkKdBqGWkd__fEJf4glS504bM,1478
8
+ maco_extractor-1.2.18.dist-info/METADATA,sha256=-Hfk91VNhYm_ZIbqs8ke1qffq5eX4_nPeSvw5COYLAo,15208
9
+ maco_extractor-1.2.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ maco_extractor-1.2.18.dist-info/top_level.txt,sha256=JTYRldTIdoZJHXQU2LH0AKgD6Hm_azz5f_kOLuBorFU,5
11
+ maco_extractor-1.2.18.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,11 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Crown Copyright, Government of Canada (Canadian Centre for Cyber Security / Communications Security Establishment) and Government of Australia (Australian Cyber Security Centre / Australian Signals Directorate)
4
+
5
+ Copyright title to all 3rd party software distributed with maco is held by the respective copyright holders as noted in those files. Users are asked to read the 3rd Party Licenses referenced with those assets.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
+
9
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ maco