Skip to content

Commit 817bfe2

Browse files
committed
Update Quark class to support this feature
1 parent c56f61a commit 817bfe2

File tree

4 files changed

+96
-17
lines changed

4 files changed

+96
-17
lines changed

quark/core/axmlreader/__init__.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import enum
22
import functools
33
import os.path
4-
import pkg_resources
54

5+
import pkg_resources
66
import rzpipe
77

88
# Resource Types Definition
@@ -71,7 +71,19 @@ class AxmlReader(object):
7171
A Class that parses the Android XML file
7272
"""
7373

74-
def __init__(self, file_path, structure_path=None):
74+
def __init__(self, file_path, structure_path=None, rizin_path=None):
75+
"""
76+
Create an AxmlReader object to parse the given Android XML file like
77+
AndroidManifest.xml.
78+
79+
:param file_path: a file in the Android XML format
80+
:param structure_path: a plain text file defining the structures of
81+
the Android XML format. Defaults to None
82+
:param rizin_path: a PathLike object to specify a Rizin executable to
83+
use. Defaults to None
84+
:raises AxmlException: if the given file is an invalid Android XML
85+
file
86+
"""
7587
if structure_path is None:
7688
structure_path = pkg_resources.resource_filename(
7789
"quark.core.axmlreader", "axml_definition"
@@ -83,7 +95,7 @@ def __init__(self, file_path, structure_path=None):
8395
f" of Rizin in {structure_path}"
8496
)
8597

86-
self._rz = rzpipe.open(file_path)
98+
self._rz = rzpipe.open(file_path, rizin_home=rizin_path)
8799
self._rz.cmd(f"pfo {structure_path}")
88100

89101
self._file_size = int(self._rz.cmd("i~size[1]"), 16)

quark/core/parallelquark.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
# This file is part of Quark-Engine - https://github.com/quark-engine/quark-engine
33
# See the file 'LICENSE' for copying permission.
44

5-
from multiprocessing.pool import Pool
65
from multiprocessing import cpu_count
6+
from multiprocessing.pool import Pool
77

88
from quark.core.analysis import QuarkAnalysis
99
from quark.core.quark import Quark
@@ -13,12 +13,33 @@
1313

1414
class ParallelQuark(Quark):
1515
@staticmethod
16-
def _worker_initializer(apk, core_library):
16+
def _worker_initializer(apk, core_library, rizin_path):
17+
"""
18+
An initializer that creates multiple Quark object for a subprocess
19+
pool.
20+
21+
:param apk: an APK for Quark to analyze
22+
:param core_library: a string indicating which analysis library Quark
23+
should use
24+
:param rizin_path: a PathLike object to specify a Rizin executable for
25+
the Rizin-based analysis library
26+
"""
27+
1728
global _quark
18-
_quark = Quark(apk, core_library)
29+
_quark = Quark(apk, core_library, rizin_path)
1930

2031
@staticmethod
2132
def _worker_analysis(rule_obj):
33+
"""
34+
A function for the subprocesses in the pool to analyze the target APK
35+
with the specified rule.
36+
37+
:param rule_obj: a Quark rule that the subprocess will analyze
38+
:return: a tuple of the analysis result, including the reached stage,
39+
the parent functions, the detected behavior (rule), and the
40+
corresponding bytecodes.
41+
"""
42+
2243
_quark.quark_analysis = QuarkAnalysis()
2344
_quark.run(rule_obj)
2445

@@ -32,7 +53,8 @@ def to_raw_method(methodobject):
3253

3354
reached_stage = rule_obj.check_item.count(True)
3455
level_4_result = tuple(
35-
to_raw_method(method) for method in _quark.quark_analysis.level_4_result
56+
to_raw_method(method)
57+
for method in _quark.quark_analysis.level_4_result
3658
)
3759
behavior_list = [
3860
(
@@ -51,6 +73,13 @@ def to_raw_method(methodobject):
5173
)
5274

5375
def _apply_analysis_result(self, rule_obj):
76+
"""
77+
Append the result returned by the subprocesses into the QuarkAnalysis
78+
object.
79+
80+
:param rule_obj: a Quark rule specifying which result this method
81+
should append
82+
"""
5483
async_result = self._result_map[id(rule_obj)]
5584
result = async_result.get()
5685

@@ -112,21 +141,43 @@ def _apply_analysis_result(self, rule_obj):
112141
]
113142
)
114143

115-
def __init__(self, apk, core_library, num_of_process=1):
144+
def __init__(self, apk, core_library, num_of_process=1, rizin_path=None):
145+
"""
146+
Create a ParallelQuark object to run the Quark analysis parallelly.
147+
148+
:param apk: an APK for Quark to analyze
149+
:param core_library: a string indicating which analysis library Quark
150+
should use
151+
:param num_of_process: a value indicating the maximal number of
152+
available processes for the analysis. Defaults to 1
153+
:param rizin_path: a PathLike object to specify a Rizin executable for
154+
the Rizin-based analysis library. Default to None
155+
"""
116156
self._result_map = {}
117157
self._pool = Pool(
118-
min(num_of_process, cpu_count() - 1), self._worker_initializer,
119-
(apk, core_library)
158+
min(num_of_process, cpu_count() - 1),
159+
self._worker_initializer,
160+
(apk, core_library, rizin_path),
120161
)
121162

122-
super().__init__(apk, core_library)
163+
super().__init__(apk, core_library, rizin_path=rizin_path)
123164

124165
def apply_rules(self, rule_obj_list):
166+
"""
167+
Add Quark rules to this pool.
168+
169+
:param rule_obj_list: a list of Quark rules
170+
"""
125171
for rule_obj in rule_obj_list:
126172
result = self._pool.apply_async(self._worker_analysis, (rule_obj,))
127173
self._result_map[id(rule_obj)] = result
128174

129175
def run(self, rule_obj):
176+
"""
177+
Wait for all rules to be analyzed.
178+
179+
:param rule_obj: _description_
180+
"""
130181
self._apply_analysis_result(rule_obj)
131182

132183
def close(self):

quark/core/quark.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,18 @@
3838
class Quark:
3939
"""Quark module is used to check quark's five-stage theory"""
4040

41-
def __init__(self, apk, core_library="androguard"):
41+
def __init__(self, apk, core_library="androguard", rizin_path=None):
4242
"""
43+
Create a Quark object.
4344
44-
:param apk: the filename of the apk.
45+
:param apk: an APK for Quark to analyze
46+
:param core_library: a string indicating which analysis library Quark should use. Defaults to "androguard"
47+
:param rizin_path: a PathLike object to specify a Rizin executable for the Rizin-based analysis library. Defaults to None
48+
:raises ValueError: if an unknown core library is specified
4549
"""
4650
core_library = core_library.lower()
4751
if core_library == "rizin":
48-
self.apkinfo = RizinImp(apk)
52+
self.apkinfo = RizinImp(apk, rizin_path=rizin_path)
4953
elif core_library == "androguard":
5054
self.apkinfo = AndroguardImp(apk)
5155
else:

quark/core/rzapkinfo.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
from quark.core.interface.baseapkinfo import BaseApkinfo
1919
from quark.core.struct.bytecodeobject import BytecodeObject
2020
from quark.core.struct.methodobject import MethodObject
21-
from quark.utils.tools import descriptor_to_androguard_format, remove_dup_list
21+
from quark.utils.tools import (descriptor_to_androguard_format,
22+
get_rizin_version, remove_dup_list)
2223

2324
RizinCache = namedtuple("rizin_cache", "address dexindex is_imported")
2425

@@ -51,9 +52,20 @@ def __init__(
5152
self,
5253
apk_filepath: Union[str, PathLike],
5354
tmp_dir: Union[str, PathLike] = None,
55+
rizin_path: PathLike = None,
5456
):
5557
super().__init__(apk_filepath, "rizin")
5658

59+
if rizin_path:
60+
if not get_rizin_version(rizin_path):
61+
raise ValueError(
62+
f"The file in {rizin_path} is not a valid Rizin executable."
63+
)
64+
65+
rizin_path = os.path.dirname(rizin_path)
66+
67+
self.rizin_path = rizin_path
68+
5769
if self.ret_type == "DEX":
5870
self._tmp_dir = None
5971
self._dex_list = [apk_filepath]
@@ -84,7 +96,7 @@ def __init__(
8496

8597
@functools.lru_cache
8698
def _get_rz(self, index):
87-
rz = rzpipe.open(self._dex_list[index])
99+
rz = rzpipe.open(self._dex_list[index], rizin_home=self.rizin_path)
88100
rz.cmd("aa")
89101
return rz
90102

@@ -227,7 +239,7 @@ def _get_methods_classified(self, dexindex):
227239

228240
@functools.cached_property
229241
def permissions(self) -> List[str]:
230-
axml = AxmlReader(self._manifest)
242+
axml = AxmlReader(self._manifest, rizin_path=self.rizin_path)
231243
permission_list = set()
232244

233245
for tag in axml:

0 commit comments

Comments
 (0)