Skip to content

Commit d9e1c08

Browse files
authored
Add radiocontrast feature (#326)
1 parent 3ffd257 commit d9e1c08

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed

quark/radiocontrast.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# This file is part of Quark-Engine - https://github.com/quark-engine/quark-engine
2+
# See the file 'LICENSE' for copying permission.
3+
4+
import os
5+
import json
6+
7+
from tqdm import tqdm
8+
from quark.core.quark import Quark
9+
from quark.core.struct.ruleobject import RuleObject
10+
11+
12+
class RadioContrast:
13+
"""
14+
This module is for generating rules with the APIs in a specific method.
15+
"""
16+
17+
def __init__(self, apk_path, target_method, output_dir, max_search_layer=3):
18+
self.quark = Quark(apk_path)
19+
self.apkinfo = self.quark.apkinfo
20+
21+
# Parse smali code into classname methodname and descriptor.
22+
classname = target_method.split("->")[0]
23+
methodname = target_method.split("->")[1].split("(")[0]
24+
descriptor = "(" + target_method.split("->")[1].split("(")[1]
25+
26+
self.method = self.apkinfo.find_method(
27+
class_name=classname, method_name=methodname, descriptor=descriptor,
28+
)
29+
30+
if self.method is None:
31+
raise ValueError("Target method not found!")
32+
33+
self.output_dir = output_dir
34+
self.api_set = set()
35+
self.max_search_layer = max_search_layer
36+
return
37+
38+
def method_recursive_search(self, method_set, depth=1):
39+
"""
40+
Find all APIs in the target method.
41+
42+
:param method_set: the list that contains each MethodAnalysis.
43+
:param depth: maximum number of recursive search functions.
44+
:return: a set of first_method_list ∩ second_method_list or None.
45+
"""
46+
47+
# Not found same method usage, try to find the next layer.
48+
depth += 1
49+
if depth > self.max_search_layer:
50+
return
51+
52+
# Append first layer into next layer.
53+
next_level_set = method_set.copy()
54+
55+
# Extend the xref from function into next layer.
56+
for md in next_level_set:
57+
if md[0].is_android_api():
58+
self.api_set.add(md[0])
59+
continue
60+
61+
self.method_recursive_search(self.apkinfo.lowerfunc(md[0]), depth)
62+
63+
def rule_generate(self):
64+
"""
65+
Generate rules and export them to the output directory.
66+
67+
:return: None
68+
"""
69+
# Rescursive search for apis in target method.
70+
lower_funcs = set(self.apkinfo.lowerfunc(self.method))
71+
self.method_recursive_search(lower_funcs)
72+
73+
first_apis_pool = list(self.api_set)
74+
second_apis_pool = list(self.api_set)
75+
76+
# Setup progress bar.
77+
second_api_pool_num = len(second_apis_pool)
78+
outter_loop = tqdm(first_apis_pool)
79+
80+
# The number of rule file.
81+
rule_number = 1
82+
83+
for api1 in first_apis_pool:
84+
outter_loop.update(1)
85+
86+
for num, api2 in enumerate(second_apis_pool, start=1):
87+
inner_desc = f"{num}/{second_api_pool_num}"
88+
outter_loop.set_postfix(inner_loop=inner_desc, refresh=True)
89+
90+
# Skip the case of same method.
91+
if api2.name == api1.name:
92+
continue
93+
94+
generated_rule = {
95+
"crime": "",
96+
"permission": [],
97+
"api": [
98+
{
99+
"class": api1.class_name,
100+
"method": api1.name,
101+
"descriptor": api1.descriptor,
102+
},
103+
{
104+
"class": api2.class_name,
105+
"method": api2.name,
106+
"descriptor": api2.descriptor,
107+
},
108+
],
109+
"score": 1,
110+
"label": [],
111+
}
112+
comb = RuleObject("test", json_data=generated_rule)
113+
114+
try:
115+
self.quark.run(comb)
116+
except KeyboardInterrupt:
117+
raise
118+
except Exception as e:
119+
tqdm.write(
120+
"{} and {} combination has some error when analyzing,\
121+
ERROR MESSAGE: {}".format(
122+
api1, api2, e,
123+
),
124+
)
125+
continue
126+
127+
if comb.check_item[4]:
128+
rule_name = f"{rule_number}.json"
129+
rule_path = os.path.join(self.output_dir, rule_name)
130+
with open(rule_path, "w") as rule_file:
131+
json.dump(generated_rule, rule_file, indent=4)
132+
133+
rule_number += 1
134+
135+
# Clear progress bar
136+
outter_loop.clear()
137+
outter_loop.close()
138+
return
139+
140+
141+
if __name__ == "__main__":
142+
pass

0 commit comments

Comments
 (0)