Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/source/quark_script.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,19 @@ applicationInstance.isDebuggable(none)
- **params**: none
- **return**: True/False

getProviders(samplePath)
==========================
- **Description**: Get provider elements from the manifest file of the target sample.
- **params**:
1. samplePath: the file path of target sample
- **return**: python list containing provider elements

providerInstance.isExported(none)
==================================
- **Description**: Check if the provider element set ``android:exported=true``.
- **params**: none
- **return**: True/False

Analyzing real case (InstaStealer) using Quark Script
------------------------------------------------------

Expand Down
14 changes: 14 additions & 0 deletions quark/core/interface/baseapkinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,20 @@ def receivers(self) -> List[XMLElement] | None:

return root.findall("application/receiver")

@property
def providers(self) -> List[XMLElement] | None:
"""Get provider elements from the manifest file.

:return: python list containing provider elements
"""
if self.ret_type != "APK":
return None

with AxmlReader(self._manifest) as axml:
root = axml.get_xml_tree()

return root.findall("application/provider")

@property
@abstractmethod
def android_apis(self) -> Set[MethodObject]:
Expand Down
34 changes: 34 additions & 0 deletions quark/script/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,28 @@ def isExported(self) -> bool:
return str(exported).lower() == 'true'


class Provider:
def __init__(self, xml: XMLElement) -> None:
self.xml: XMLElement = xml

def __str__(self) -> str:
return self._getAttribute("name")

def _getAttribute(self, attributeName: str) -> Any:
realAttributeName = (
f"{{http://schemas.android.com/apk/res/android}}{attributeName}"
)
return self.xml.get(realAttributeName, None)

def isExported(self) -> bool:
"""Check if the provider element set ``android:exported=true``.

:return: True/False
"""
exported = self._getAttribute("exported")
return exported


class Method:
def __init__(
self,
Expand Down Expand Up @@ -634,6 +656,18 @@ def getApplication(samplePath: PathLike) -> Application:
return Application(apkinfo.application)


def getProviders(samplePath: PathLike) -> List[Provider]:
"""Get provider elements from the manifest file of the target sample.

:param samplePath: the file path of target sample
:return: python list containing provider elements
"""
quark = _getQuark(samplePath)
apkinfo = quark.apkinfo

return [Provider(xml) for xml in apkinfo.providers]


def findMethodInAPK(
samplePath: PathLike,
targetMethod: Union[List[str], Method]
Expand Down
11 changes: 11 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
"/raw/master/vulnerable-samples/pivaa.apk"
),
"fileName": "pivaa.apk"
},
{
"sourceUrl": (
"https://github.com/quark-engine/apk-samples"
"/raw/master/vulnerable-samples/Vuldroid.apk"
),
"fileName": "Vuldroid.apk"
}
]

Expand Down Expand Up @@ -74,3 +81,7 @@ def SAMPLE_PATH_Ahmyth(tmp_path_factory: pytest.TempPathFactory) -> str:
@pytest.fixture(scope="session")
def SAMPLE_PATH_pivaa(tmp_path_factory: pytest.TempPathFactory) -> str:
return downloadSample(tmp_path_factory, SAMPLES[3])

@pytest.fixture(scope="session")
def SAMPLE_PATH_Vuldroid(tmp_path_factory: pytest.TempPathFactory) -> str:
return downloadSample(tmp_path_factory, SAMPLES[4])
61 changes: 61 additions & 0 deletions tests/core/test_apkinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ def dex_file(SAMPLE_PATH_13667):
os.remove(APK_NAME)


@pytest.fixture(scope="session")
def dex_file_pivaa(tmp_path_factory, SAMPLE_PATH_pivaa):
APK_NAME = SAMPLE_PATH_pivaa
DEX_NAME = "classes.dex"
DEX_DIR = tmp_path_factory.mktemp("dex_pivaa")
DEX_PATH = str(os.path.join(DEX_DIR, "classes.dex"))

with zipfile.ZipFile(APK_NAME, "r") as zip:
zip.extract(DEX_NAME, path=DEX_DIR)

yield DEX_PATH

if os.path.exists(DEX_PATH):
os.remove(DEX_PATH)

if os.path.exists(DEX_PATH):
os.remove(DEX_PATH)


def __generateTestIDs(testInput: Tuple[BaseApkinfo, Literal["DEX", "APK"]]):
return f"{testInput[0].__name__} with {testInput[1]}"

Expand Down Expand Up @@ -80,6 +99,32 @@ def apkinfo_with_R2Imp_only(request, SAMPLE_PATH_13667, dex_file):
yield apkinfo


@pytest.fixture(
scope="function",
params=(
(AndroguardImp, "DEX"),
(AndroguardImp, "APK"),
(RizinImp, "DEX"),
(RizinImp, "APK"),
(R2Imp, "DEX"),
(R2Imp, "APK"),
(ShurikenImp, "DEX"),
(ShurikenImp, "APK"),
),
ids=__generateTestIDs,
)
def apkinfoPivaa(request, SAMPLE_PATH_pivaa, dex_file_pivaa):
apkinfoClass, fileType = request.param

fileToBeAnalyzed = SAMPLE_PATH_pivaa
if fileType == "DEX":
fileToBeAnalyzed = dex_file_pivaa

apkinfo = apkinfoClass(fileToBeAnalyzed)

yield apkinfo


class TestApkinfo:
def test_init_with_invalid_type(self):
filepath = None
Expand Down Expand Up @@ -208,6 +253,22 @@ def test_receivers(apkinfo):
== "com.example.google.service.MyDeviceAdminReceiver"
)

@staticmethod
def test_providers(apkinfoPivaa):
match apkinfoPivaa.ret_type:
case "DEX":
assert apkinfoPivaa.providers is None
case "APK":
providers= apkinfoPivaa.providers

assert len(providers) == 1
assert (
providers[0].get(
"{http://schemas.android.com/apk/res/android}name"
)
== "com.htbridge.pivaa.handlers.VulnerableContentProvider"
)

def test_android_apis(self, apkinfo):
api = {
MethodObject(
Expand Down
11 changes: 11 additions & 0 deletions tests/script/test_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
getActivities,
getReceivers,
getApplication,
getProviders,
runQuarkAnalysis,
findMethodInAPK,
findMethodImpls,
Expand Down Expand Up @@ -132,6 +133,16 @@ def testIsExported(SAMPLE_PATH_13667):
receiver = getReceivers(SAMPLE_PATH_13667)[0]
assert receiver.isExported() is True

class TestProvider:
@staticmethod
def testIsNotExported(SAMPLE_PATH_Vuldroid):
provider = getProviders(SAMPLE_PATH_Vuldroid)[0]
assert provider.isExported() is False

@staticmethod
def testIsExported(SAMPLE_PATH_pivaa):
provider = getProviders(SAMPLE_PATH_pivaa)[0]
assert provider.isExported() is True

class TestMethod:
@staticmethod
Expand Down
Loading