Skip to content

Commit 08e609e

Browse files
authored
Add Quark Script APIs to detect CWE-359 (#802)
1 parent 852dfd3 commit 08e609e

File tree

6 files changed

+144
-0
lines changed

6 files changed

+144
-0
lines changed

docs/source/quark_script.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,19 @@ applicationInstance.isDebuggable(none)
339339
- **params**: none
340340
- **return**: True/False
341341

342+
getProviders(samplePath)
343+
==========================
344+
- **Description**: Get provider elements from the manifest file of the target sample.
345+
- **params**:
346+
1. samplePath: the file path of target sample
347+
- **return**: python list containing provider elements
348+
349+
providerInstance.isExported(none)
350+
==================================
351+
- **Description**: Check if the provider element set ``android:exported=true``.
352+
- **params**: none
353+
- **return**: True/False
354+
342355
Analyzing real case (InstaStealer) using Quark Script
343356
------------------------------------------------------
344357

quark/core/interface/baseapkinfo.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,20 @@ def receivers(self) -> List[XMLElement] | None:
152152

153153
return root.findall("application/receiver")
154154

155+
@property
156+
def providers(self) -> List[XMLElement] | None:
157+
"""Get provider elements from the manifest file.
158+
159+
:return: python list containing provider elements
160+
"""
161+
if self.ret_type != "APK":
162+
return None
163+
164+
with AxmlReader(self._manifest) as axml:
165+
root = axml.get_xml_tree()
166+
167+
return root.findall("application/provider")
168+
155169
@property
156170
@abstractmethod
157171
def android_apis(self) -> Set[MethodObject]:

quark/script/__init__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,28 @@ def isExported(self) -> bool:
154154
return str(exported).lower() == 'true'
155155

156156

157+
class Provider:
158+
def __init__(self, xml: XMLElement) -> None:
159+
self.xml: XMLElement = xml
160+
161+
def __str__(self) -> str:
162+
return self._getAttribute("name")
163+
164+
def _getAttribute(self, attributeName: str) -> Any:
165+
realAttributeName = (
166+
f"{{http://schemas.android.com/apk/res/android}}{attributeName}"
167+
)
168+
return self.xml.get(realAttributeName, None)
169+
170+
def isExported(self) -> bool:
171+
"""Check if the provider element set ``android:exported=true``.
172+
173+
:return: True/False
174+
"""
175+
exported = self._getAttribute("exported")
176+
return exported
177+
178+
157179
class Method:
158180
def __init__(
159181
self,
@@ -634,6 +656,18 @@ def getApplication(samplePath: PathLike) -> Application:
634656
return Application(apkinfo.application)
635657

636658

659+
def getProviders(samplePath: PathLike) -> List[Provider]:
660+
"""Get provider elements from the manifest file of the target sample.
661+
662+
:param samplePath: the file path of target sample
663+
:return: python list containing provider elements
664+
"""
665+
quark = _getQuark(samplePath)
666+
apkinfo = quark.apkinfo
667+
668+
return [Provider(xml) for xml in apkinfo.providers]
669+
670+
637671
def findMethodInAPK(
638672
samplePath: PathLike,
639673
targetMethod: Union[List[str], Method]

tests/conftest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
"/raw/master/vulnerable-samples/pivaa.apk"
3737
),
3838
"fileName": "pivaa.apk"
39+
},
40+
{
41+
"sourceUrl": (
42+
"https://github.com/quark-engine/apk-samples"
43+
"/raw/master/vulnerable-samples/Vuldroid.apk"
44+
),
45+
"fileName": "Vuldroid.apk"
3946
}
4047
]
4148

@@ -74,3 +81,7 @@ def SAMPLE_PATH_Ahmyth(tmp_path_factory: pytest.TempPathFactory) -> str:
7481
@pytest.fixture(scope="session")
7582
def SAMPLE_PATH_pivaa(tmp_path_factory: pytest.TempPathFactory) -> str:
7683
return downloadSample(tmp_path_factory, SAMPLES[3])
84+
85+
@pytest.fixture(scope="session")
86+
def SAMPLE_PATH_Vuldroid(tmp_path_factory: pytest.TempPathFactory) -> str:
87+
return downloadSample(tmp_path_factory, SAMPLES[4])

tests/core/test_apkinfo.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,25 @@ def dex_file(SAMPLE_PATH_13667):
3030
os.remove(APK_NAME)
3131

3232

33+
@pytest.fixture(scope="session")
34+
def dex_file_pivaa(tmp_path_factory, SAMPLE_PATH_pivaa):
35+
APK_NAME = SAMPLE_PATH_pivaa
36+
DEX_NAME = "classes.dex"
37+
DEX_DIR = tmp_path_factory.mktemp("dex_pivaa")
38+
DEX_PATH = str(os.path.join(DEX_DIR, "classes.dex"))
39+
40+
with zipfile.ZipFile(APK_NAME, "r") as zip:
41+
zip.extract(DEX_NAME, path=DEX_DIR)
42+
43+
yield DEX_PATH
44+
45+
if os.path.exists(DEX_PATH):
46+
os.remove(DEX_PATH)
47+
48+
if os.path.exists(DEX_PATH):
49+
os.remove(DEX_PATH)
50+
51+
3352
def __generateTestIDs(testInput: Tuple[BaseApkinfo, Literal["DEX", "APK"]]):
3453
return f"{testInput[0].__name__} with {testInput[1]}"
3554

@@ -80,6 +99,32 @@ def apkinfo_with_R2Imp_only(request, SAMPLE_PATH_13667, dex_file):
8099
yield apkinfo
81100

82101

102+
@pytest.fixture(
103+
scope="function",
104+
params=(
105+
(AndroguardImp, "DEX"),
106+
(AndroguardImp, "APK"),
107+
(RizinImp, "DEX"),
108+
(RizinImp, "APK"),
109+
(R2Imp, "DEX"),
110+
(R2Imp, "APK"),
111+
(ShurikenImp, "DEX"),
112+
(ShurikenImp, "APK"),
113+
),
114+
ids=__generateTestIDs,
115+
)
116+
def apkinfoPivaa(request, SAMPLE_PATH_pivaa, dex_file_pivaa):
117+
apkinfoClass, fileType = request.param
118+
119+
fileToBeAnalyzed = SAMPLE_PATH_pivaa
120+
if fileType == "DEX":
121+
fileToBeAnalyzed = dex_file_pivaa
122+
123+
apkinfo = apkinfoClass(fileToBeAnalyzed)
124+
125+
yield apkinfo
126+
127+
83128
class TestApkinfo:
84129
def test_init_with_invalid_type(self):
85130
filepath = None
@@ -208,6 +253,22 @@ def test_receivers(apkinfo):
208253
== "com.example.google.service.MyDeviceAdminReceiver"
209254
)
210255

256+
@staticmethod
257+
def test_providers(apkinfoPivaa):
258+
match apkinfoPivaa.ret_type:
259+
case "DEX":
260+
assert apkinfoPivaa.providers is None
261+
case "APK":
262+
providers= apkinfoPivaa.providers
263+
264+
assert len(providers) == 1
265+
assert (
266+
providers[0].get(
267+
"{http://schemas.android.com/apk/res/android}name"
268+
)
269+
== "com.htbridge.pivaa.handlers.VulnerableContentProvider"
270+
)
271+
211272
def test_android_apis(self, apkinfo):
212273
api = {
213274
MethodObject(

tests/script/test_script.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
getActivities,
1616
getReceivers,
1717
getApplication,
18+
getProviders,
1819
runQuarkAnalysis,
1920
findMethodInAPK,
2021
findMethodImpls,
@@ -132,6 +133,16 @@ def testIsExported(SAMPLE_PATH_13667):
132133
receiver = getReceivers(SAMPLE_PATH_13667)[0]
133134
assert receiver.isExported() is True
134135

136+
class TestProvider:
137+
@staticmethod
138+
def testIsNotExported(SAMPLE_PATH_Vuldroid):
139+
provider = getProviders(SAMPLE_PATH_Vuldroid)[0]
140+
assert provider.isExported() is False
141+
142+
@staticmethod
143+
def testIsExported(SAMPLE_PATH_pivaa):
144+
provider = getProviders(SAMPLE_PATH_pivaa)[0]
145+
assert provider.isExported() is True
135146

136147
class TestMethod:
137148
@staticmethod

0 commit comments

Comments
 (0)