-
Notifications
You must be signed in to change notification settings - Fork 18
/
merge_signatures.py
156 lines (125 loc) · 4.87 KB
/
merge_signatures.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
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
import sys
import uuid
import re
from plyara import Plyara
import argparse
# Appears that Ply needs to read the source, so disable bytecode.
sys.dont_write_bytecode
def get_strings_and_conditions(rule):
segment_headers = \
{
"strings": "^\s*strings:\s*\r?\n?",
"condition": "^\s*condition:\s*\r?\n?",
}
segments = \
{
"strings": [],
"condition": [],
}
SEGMENT = None
for line in rule.splitlines():
segment_change = False
for header, rx in list(segment_headers.items()):
if re.match(rx, line):
SEGMENT = header
segment_change = True
if SEGMENT and not segment_change:
segments[SEGMENT].append(line)
if segments.get("strings", None):
segments["strings"][-1] = segments["strings"][-1].rstrip(" }") if not "=" in segments["strings"][-1] else \
segments["strings"][-1]
if segments.get("condition", None):
segments["condition"][-1] = segments["condition"][-1].rstrip().rstrip(" }\n\t")
return "\n".join(segments["strings"]) if segments.get("strings", None) else "", "\n".join(
[segment for segment in segments["condition"] if segment.strip(" }")])
def parse_yara_rules_text(text):
return Plyara().parse_string(text)
def parse_yara_rules_file(filename):
return parse_yara_rules_text(open(filename, "r").read())
def get_imports_from_string(imports_string):
if not imports_string:
return ""
return "\n".join([imp.strip() for imp in
set(re.findall(r'^import[\t\s]+\"[A-Za-z0-9_]+\"[\t\s]*$', imports_string, re.MULTILINE))])
def extract_yara_rules_text(text):
text = text.strip()
imports = get_imports_from_string(text)
split_regex = "\n[\t\s]*\}[\s\t]*(rule[\t\s][^\r\n]+(?:\{|[\r\n][\r\n\s\t]*\{))"
parse_regex = r"^[\t\s]*rule[\t\s][^\r\n]+(?:\{|[\r\n][\r\n\s\t]*\{).*?condition:.*?\r?\n?[\t\s]*\}[\s\t]*(?:$|\r?\n)"
yara_rules = re.sub(split_regex, "\n}\n\\1", text, re.MULTILINE | re.DOTALL)
yara_rules = re.compile(parse_regex, re.MULTILINE | re.DOTALL).findall(yara_rules)
extracted = []
for yara_rule_original in yara_rules:
try:
parsed_rule = parse_yara_rules_text(yara_rule_original.strip())[0]
strings, condition = get_strings_and_conditions(yara_rule_original)
extracted.append(
{"parsed_rule": parsed_rule, "strings": strings, "condition": condition, "imports": imports})
except Exception as e:
pass
return extracted
def merge_signatures(signatures):
if not signatures:
return None
rules = extract_yara_rules_text(signatures)
rule_name = f"Merged_Rule_{str(uuid.uuid4())[:8]}"
description = f"\"Merger of {', '.join([rule['parsed_rule']['rule_name'] for rule in rules])}\""
strings = ""
condition = ""
imports = ""
r = 1
for rule in rules:
old_new = {}
current_strings = []
postfix = f"r{r}"
for s in rule['parsed_rule']['strings']:
old_name = s['name'].strip()
if old_name.startswith('$'):
name = f"${s['name'].strip()[1:]}_{postfix}"
else:
name = f"{s['name'].strip()}_{postfix}"
if s['value'].startswith("{") or s['value'].startswith("/"):
value = s['value'].strip()
else:
value = f"\"{s['value'].strip()}\""
old_new[old_name] = name
modifier = ' '.join(s.get('modifiers', []))
current_strings.append(
f"{name} = {value} {modifier}"
)
strings += '\n\t\t' + '\n\t\t'.join(current_strings)
this_condition = rule['condition'].strip()
for old, new in old_new.items():
this_condition = this_condition.replace(old, new)
condition += f"(\n\t{this_condition}\n\t)\n\tor\n\t"
imports += rule['imports'] + "\n" if not rule['imports'] in imports else ""
r += 1
condition = condition.rstrip("\n\tor\n\t")
full_rule = f"""
{imports}
rule {rule_name}
{{
meta:
description = {description}
strings:{strings}
condition:
{condition}
}}
"""
return full_rule.strip(" ").strip()
def main(args):
parser = argparse.ArgumentParser(
description="merge_signatures.py is a cli tool and library for intelligently merging yara signatures."
)
# Define accepted arguments and metadata.
parser.add_argument('--file',
action='store',
type=str,
dest='file',
required=False)
args = parser.parse_args()
text = open(args.file, "r").read() if args.file else sys.stdin.read()
print(merge_signatures(text))
sys.exit(0)
if __name__ == "__main__":
main(sys.argv)