mirror of
https://github.com/mvt-project/mvt.git
synced 2026-02-16 02:12:46 +00:00
Compare commits
7 Commits
main
...
update-che
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcc720fd13 | ||
|
|
7fe66e8d5a | ||
|
|
a41b772e9e | ||
|
|
f12cf7dec5 | ||
|
|
3b144b263b | ||
|
|
ab9abfaded | ||
|
|
ef2ffb0dda |
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
|
python-version: ['3.10', '3.11', '3.12', '3.13']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -1,9 +1,14 @@
|
|||||||
PWD = $(shell pwd)
|
PWD = $(shell pwd)
|
||||||
|
|
||||||
|
autofix:
|
||||||
|
ruff format .
|
||||||
|
ruff check --fix .
|
||||||
|
|
||||||
check: ruff mypy
|
check: ruff mypy
|
||||||
|
|
||||||
ruff:
|
ruff:
|
||||||
ruff check .
|
ruff format --check .
|
||||||
|
ruff check -q .
|
||||||
|
|
||||||
mypy:
|
mypy:
|
||||||
mypy
|
mypy
|
||||||
|
|||||||
59
SECURITY.md
59
SECURITY.md
@@ -2,61 +2,4 @@
|
|||||||
|
|
||||||
Thank you for your interest in reporting security issues and vulnerabilities! Security research is of utmost importance and we take all reports seriously. If you discover an issue please report it to us right away!
|
Thank you for your interest in reporting security issues and vulnerabilities! Security research is of utmost importance and we take all reports seriously. If you discover an issue please report it to us right away!
|
||||||
|
|
||||||
Please DO NOT file a public issue, instead send your report privately to the MVT maintainers at Amnesty International via `security [at] amnesty [dot] tech`.
|
Please DO NOT file a public issue, instead send your report privately to *nex [at] nex [dot] sx*. You can also write PGP-encrypted emails to [this key](https://keybase.io/nex/pgp_keys.asc?fingerprint=05216f3b86848a303c2fe37dd166f1667359d880).
|
||||||
|
|
||||||
You can also write PGP-encrypted emails to key `CFBF9698DCA8EB2A80F48ADEA035A030FA04ED13`. The corresponding PGP public key is lited below.
|
|
||||||
|
|
||||||
```
|
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
mQINBGlFPwsBEADQ+d7SeHrFPYv3wPOjWs2oMpp0DPdfIyGbg+iYWOC36FegZhKY
|
|
||||||
+WeK96GqJWt8wD6kwFUVwQI795WZrjSd1q4a7wR+kj/h7xlRB6ZfVICA6O5DOOm6
|
|
||||||
GNMvqy7ESm8g1XZDpb2u1BXmSS9X8f6rjB0e86kYsF1mB5/2USTM63jgDs0GGTkZ
|
|
||||||
Q1z4Mq4gYyqH32b3gvXkbb68LeQmONUIM3cgmec9q8/pNc1l7fcoLWhOVADRj17Q
|
|
||||||
plisa/EUf/SYqdtk9w7EHGggNenKNwVM235mkPcMqmE72bTpjT6XCxvZY3ByG5yi
|
|
||||||
7L+tHJU45ZuXtt62EvX03azxThVfSmH/WbRk8lH8+CW8XMmiWZphG4ydPWqgVKCB
|
|
||||||
2UOXm+6CQnKA+7Dt1AeK2t5ciATrv9LvwgSxk5WKc3288XFLA6eGMrTdQygYlLjJ
|
|
||||||
+42RSdK/7fCt/qk4q13oUw8ZTVcCia98uZFi704XuuYTH6NrntIB7j/0oucIS4Y9
|
|
||||||
cTWNO5LBerez4v8VI4YHcYESPeIWGFkXhvJzo0VMg1zidBLtiPoGF2JKZGwaK7/p
|
|
||||||
yY1xALskLp4H+5OY4eB1kf8kl4vGsEK8xA/NNzOiapVmwBXpvVvmXIQJE2k+olNf
|
|
||||||
sAuyB8+aO1Ws7tFYt3D+olC7iaprOdK7uA4GCgmYYhq6QQPg+cxfczgHfwARAQAB
|
|
||||||
tD1TZWN1cml0eSBMYWIgYXQgQW1uZXN0eSBJbnRlcm5hdGlvbmFsIDxzZWN1cml0
|
|
||||||
eUBhbW5lc3R5LnRlY2g+iQJRBBMBCAA7FiEEz7+WmNyo6yqA9IreoDWgMPoE7RMF
|
|
||||||
AmlFPwsCGwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQoDWgMPoE7RNr
|
|
||||||
2w//a88uP90uSN6lgeIwKsHr1ri27QIBbzCV6hLN/gZBFR2uaiOn/xfFDbnR0Cjo
|
|
||||||
5nMCJCT1k4nrPbMTlfmWLCD+YKELBzVqWlw4J2SOg3nznPl2JrL8QBKjwts0sF+h
|
|
||||||
QbRWDsT54wBZnl6ZJJ79eLShNTokBbKnQ7071dMrENr5e2P2sClQXyiIc51ga4FM
|
|
||||||
fHyhsx+GsrdiZNd2AH8912ljW1GuEi3epTO7KMZprmr37mjpZSUToiV59Yhl1Gbo
|
|
||||||
2pixkYJqi62DG02/gTpCjq9NH3cEMxcxjh4E7yCA8ggLG6+IN6woIvPIdOsnQ+Yj
|
|
||||||
d3H4rMNBjPSKoL+bdHILkCnp5HokcbVjNY3QAyOAF4qWhk4GtgpTshwxUmb4Tbay
|
|
||||||
tWLJC2bzjuUBxLkGzMVFfU3B96sVS4Fi0sBaEMBtHskl2f45X8LJhSq//Lw/2L/8
|
|
||||||
34uP/RxDSn+DPvj/yqMpekdCcmeFSTX1A19xkPcc0rVhMRde4VL338R86vzh0gMI
|
|
||||||
1LySDAhXZyVWzrQ5s3n6N3EvCaHCn3qu7ieyFJifCSR7gZqevCEznMQRVpkMTzUt
|
|
||||||
rk13Z6NOOb4IlTW7HFoY3omJG8Z5jV4kMIE7n6nb0qpNYQiG+YvjenQ3VrMoISyh
|
|
||||||
lpS2De8+oOtwrxBVX3+qKWvQqzufeE3416kw2Z+5mxH7bx25Ag0EaUU/CwEQALyZ
|
|
||||||
b+kwLN1yHObTm2yDBEn5HbCT3H1GremvPNmbAaTnfrjUngoKa8MuWWzbX5ptgmZR
|
|
||||||
UpYY/ylOYcgGydz58vUNrPlhIZT9UhmiifPgZLEXyd0uFpr/NsbRajHMkK10iEZf
|
|
||||||
h5bHNobiB7pGCu4Uj9e1cMiIZ4yEaYeyXYUoNHf6ISP39mJhHy6ov5yIpm9q0wzm
|
|
||||||
tGUQPupxGXmEZlOPr3lxqXQ3Ekdv6cWDY5r/oOq71QJ/HUQ13QUuGFIbhnMbT8zd
|
|
||||||
zaS6f/v772YKsWPc4NNUhtlf25VnQ4FuUtjCe3p6iYP4OVD8gJm0GvXyvyTuiQbL
|
|
||||||
CSk/378JiNT7nZzYXxrWchMwvEoMIU55+/UaBc50HI5xvDQ858CX7PYGiimcdsO1
|
|
||||||
EkQzhVxRfjlILfWrC2lgt+H5qhTn4Fah250Xe1PnLjXGHVUQnY/f3MFeiWQgf92b
|
|
||||||
02+MfvOeC5OKttP1z5lcx6RFWCIa1E/u8Nj7YrH9hk0ZBRAnBaeAncDFY8dfX2zX
|
|
||||||
VMoc0dV16gM7RrZ6i7D3CG3eLLkQlX0jbW9dzTuG/3f098EWB1p8vOfS/RbNCBRX
|
|
||||||
jqGiqacL/aFF3Ci3nQ4O5tSv1XipbgrUhvXnwm9pxrLPS/45iaO59WN4RRGWLLQ7
|
|
||||||
LHmeBxoa9avv0SdBYUL+eBxY46GXb/j5VLzHYhSnABEBAAGJAjYEGAEIACAWIQTP
|
|
||||||
v5aY3KjrKoD0it6gNaAw+gTtEwUCaUU/CwIbDAAKCRCgNaAw+gTtEyvsEACnyFFD
|
|
||||||
alOZTrrJTXNnUejuiExLh+qTO3T91p5bte597jpwCZnYGwkxEfffsqqhlY6ftEOf
|
|
||||||
d5tNWE5isai4v8XCbplWomz4KBpepxcn2b+9o5dSyr1vohEFuCJziZDsta1J2DX5
|
|
||||||
IE9U48kTgLDfdIBhuOyHNRkvXRHP2OVLCaiw4d9q+hlrraR8pehHt2BJSxh+QZoe
|
|
||||||
n0iHvIZCBIUA45zLEGmXFpNTGeEf2dKPp3xOkAXOhAMPptE0V1itkF3R7kEW4aFO
|
|
||||||
SZo8L3C1aWSz/gQ4/vvW5t1IJxirNMUgTMQFvqEkAwX3fm6GCxlgRSvTTRXdcrS8
|
|
||||||
6qyFdH1nkCNsavPahN3N2RGGIlWtODEMTO1Hjy0kZtTYdW+JH9sendliCoJES+yN
|
|
||||||
DjM125SgdAgrqlSYm/g8n9knWpxZv1QM6jU/sVz1J+l6/ixugL2i+CAL2d6uv4tT
|
|
||||||
QmXnu7Ei4/2kHBUu3Lf59MNgmLHm6F7AhOWErszSeoJKsp+3yA1oTT/npz67sRzY
|
|
||||||
VVyxz4NBIollna59a1lz0RhlWzNKqNB27jhylyM4ltdzHB7r4VMAVJyttozmIIOC
|
|
||||||
35ucYxl5BHLuapaRSaYHdUId1LOccYyaOOFF/PSyCu9dKzXk7zEz2HNcIboWSkAE
|
|
||||||
8ZDExMYM4WVpVCOj+frdsaBvzItHacRWuijtkw==
|
|
||||||
=JAXX
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -16,12 +16,6 @@ Now you can try launching MVT with:
|
|||||||
mvt-android check-adb --output /path/to/results
|
mvt-android check-adb --output /path/to/results
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! warning
|
|
||||||
The `check-adb` command is deprecated and will be removed in a future release.
|
|
||||||
Whenever possible, prefer acquiring device data using the AndroidQF project (https://github.com/mvt-project/androidqf/) and then analyze those acquisitions with MVT.
|
|
||||||
|
|
||||||
Running `mvt-android check-adb` will also emit a runtime deprecation warning advising you to migrate to AndroidQF.
|
|
||||||
|
|
||||||
If you have previously started an adb daemon MVT will alert you and require you to kill it with `adb kill-server` and relaunch the command.
|
If you have previously started an adb daemon MVT will alert you and require you to kill it with `adb kill-server` and relaunch the command.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
@@ -43,14 +37,6 @@ mvt-android check-adb --serial 192.168.1.20:5555 --output /path/to/results
|
|||||||
|
|
||||||
Where `192.168.1.20` is the correct IP address of your device.
|
Where `192.168.1.20` is the correct IP address of your device.
|
||||||
|
|
||||||
!!! warning
|
|
||||||
The `check-adb` workflow shown above is deprecated. If you can acquire an AndroidQF acquisition from the device (recommended), use the AndroidQF project to create that acquisition: https://github.com/mvt-project/androidqf/
|
|
||||||
|
|
||||||
AndroidQF acquisitions provide a more stable, reproducible analysis surface and are the preferred workflow going forward.
|
|
||||||
|
|
||||||
## MVT modules requiring root privileges
|
## MVT modules requiring root privileges
|
||||||
|
|
||||||
!!! warning
|
|
||||||
Deprecated: many `mvt-android check-adb` workflows are deprecated and will be removed in a future release. Whenever possible, prefer acquiring an AndroidQF acquisition using the AndroidQF project (https://github.com/mvt-project/androidqf/).
|
|
||||||
|
|
||||||
Of the currently available `mvt-android check-adb` modules a handful require root privileges to function correctly. This is because certain files, such as browser history and SMS messages databases are not accessible with user privileges through adb. These modules are to be considered OPTIONALLY available in case the device was already jailbroken. **Do NOT jailbreak your own device unless you are sure of what you are doing!** Jailbreaking your phone exposes it to considerable security risks!
|
Of the currently available `mvt-android check-adb` modules a handful require root privileges to function correctly. This is because certain files, such as browser history and SMS messages databases are not accessible with user privileges through adb. These modules are to be considered OPTIONALLY available in case the device was already jailbroken. **Do NOT jailbreak your own device unless you are sure of what you are doing!** Jailbreaking your phone exposes it to considerable security risks!
|
||||||
|
|||||||
@@ -17,21 +17,21 @@ classifiers = [
|
|||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click==8.3.0",
|
"click==8.2.1",
|
||||||
"rich==14.1.0",
|
"rich==14.1.0",
|
||||||
"tld==0.13.1",
|
"tld==0.13.1",
|
||||||
"requests==2.32.5",
|
"requests==2.32.4",
|
||||||
"simplejson==3.20.2",
|
"simplejson==3.20.1",
|
||||||
"packaging==25.0",
|
"packaging==25.0",
|
||||||
"appdirs==1.4.4",
|
"appdirs==1.4.4",
|
||||||
"iOSbackup==0.9.925",
|
"iOSbackup==0.9.925",
|
||||||
"adb-shell[usb]==0.4.4",
|
"adb-shell[usb]==0.4.4",
|
||||||
"libusb1==3.3.1",
|
"libusb1==3.3.1",
|
||||||
"cryptography==46.0.3",
|
"cryptography==45.0.6",
|
||||||
"PyYAML>=6.0.2",
|
"PyYAML>=6.0.2",
|
||||||
"pyahocorasick==2.2.0",
|
"pyahocorasick==2.2.0",
|
||||||
"betterproto==1.2.5",
|
"betterproto==1.2.5",
|
||||||
"pydantic==2.12.3",
|
"pydantic==2.11.7",
|
||||||
"pydantic-settings==2.10.1",
|
"pydantic-settings==2.10.1",
|
||||||
"NSKeyedUnArchiver==1.5.2",
|
"NSKeyedUnArchiver==1.5.2",
|
||||||
"python-dateutil==2.9.0.post0",
|
"python-dateutil==2.9.0.post0",
|
||||||
@@ -80,7 +80,7 @@ packages = "src"
|
|||||||
addopts = "-ra -q --cov=mvt --cov-report html --junitxml=pytest.xml --cov-report=term-missing:skip-covered"
|
addopts = "-ra -q --cov=mvt --cov-report html --junitxml=pytest.xml --cov-report=term-missing:skip-covered"
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff.lint]
|
||||||
select = ["C90", "E", "F", "W"] # flake8 default set
|
select = ["C90", "E", "F", "W"] # flake8 default set
|
||||||
ignore = [
|
ignore = [
|
||||||
"E501", # don't enforce line length violations
|
"E501", # don't enforce line length violations
|
||||||
@@ -95,10 +95,10 @@ ignore = [
|
|||||||
# "E203", # whitespace-before-punctuation
|
# "E203", # whitespace-before-punctuation
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"__init__.py" = ["F401"] # unused-import
|
"__init__.py" = ["F401"] # unused-import
|
||||||
|
|
||||||
[tool.ruff.mccabe]
|
[tool.ruff.lint.mccabe]
|
||||||
max-complexity = 10
|
max-complexity = 10
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
|
|||||||
@@ -9,30 +9,30 @@ import click
|
|||||||
|
|
||||||
from mvt.common.cmd_check_iocs import CmdCheckIOCS
|
from mvt.common.cmd_check_iocs import CmdCheckIOCS
|
||||||
from mvt.common.help import (
|
from mvt.common.help import (
|
||||||
HELP_MSG_ANDROID_BACKUP_PASSWORD,
|
HELP_MSG_VERSION,
|
||||||
|
HELP_MSG_OUTPUT,
|
||||||
|
HELP_MSG_SERIAL,
|
||||||
|
HELP_MSG_DOWNLOAD_APKS,
|
||||||
|
HELP_MSG_DOWNLOAD_ALL_APKS,
|
||||||
|
HELP_MSG_VIRUS_TOTAL,
|
||||||
HELP_MSG_APK_OUTPUT,
|
HELP_MSG_APK_OUTPUT,
|
||||||
HELP_MSG_APKS_FROM_FILE,
|
HELP_MSG_APKS_FROM_FILE,
|
||||||
|
HELP_MSG_VERBOSE,
|
||||||
HELP_MSG_CHECK_ADB,
|
HELP_MSG_CHECK_ADB,
|
||||||
HELP_MSG_CHECK_ANDROID_BACKUP,
|
|
||||||
HELP_MSG_CHECK_ANDROIDQF,
|
|
||||||
HELP_MSG_CHECK_BUGREPORT,
|
|
||||||
HELP_MSG_CHECK_IOCS,
|
|
||||||
HELP_MSG_DISABLE_INDICATOR_UPDATE_CHECK,
|
|
||||||
HELP_MSG_DISABLE_UPDATE_CHECK,
|
|
||||||
HELP_MSG_DOWNLOAD_ALL_APKS,
|
|
||||||
HELP_MSG_DOWNLOAD_APKS,
|
|
||||||
HELP_MSG_FAST,
|
|
||||||
HELP_MSG_HASHES,
|
|
||||||
HELP_MSG_IOC,
|
HELP_MSG_IOC,
|
||||||
|
HELP_MSG_FAST,
|
||||||
HELP_MSG_LIST_MODULES,
|
HELP_MSG_LIST_MODULES,
|
||||||
HELP_MSG_MODULE,
|
HELP_MSG_MODULE,
|
||||||
HELP_MSG_NONINTERACTIVE,
|
HELP_MSG_NONINTERACTIVE,
|
||||||
HELP_MSG_OUTPUT,
|
HELP_MSG_ANDROID_BACKUP_PASSWORD,
|
||||||
HELP_MSG_SERIAL,
|
HELP_MSG_CHECK_BUGREPORT,
|
||||||
|
HELP_MSG_CHECK_ANDROID_BACKUP,
|
||||||
|
HELP_MSG_CHECK_ANDROIDQF,
|
||||||
|
HELP_MSG_HASHES,
|
||||||
|
HELP_MSG_CHECK_IOCS,
|
||||||
HELP_MSG_STIX2,
|
HELP_MSG_STIX2,
|
||||||
HELP_MSG_VERBOSE,
|
HELP_MSG_DISABLE_UPDATE_CHECK,
|
||||||
HELP_MSG_VERSION,
|
HELP_MSG_DISABLE_INDICATOR_UPDATE_CHECK,
|
||||||
HELP_MSG_VIRUS_TOTAL,
|
|
||||||
)
|
)
|
||||||
from mvt.common.logo import logo
|
from mvt.common.logo import logo
|
||||||
from mvt.common.updates import IndicatorsUpdates
|
from mvt.common.updates import IndicatorsUpdates
|
||||||
@@ -201,11 +201,6 @@ def check_adb(
|
|||||||
cmd.list_modules()
|
cmd.list_modules()
|
||||||
return
|
return
|
||||||
|
|
||||||
log.warning(
|
|
||||||
"DEPRECATION: The 'check-adb' command is deprecated and may be removed in a future release. "
|
|
||||||
"Prefer acquiring device data using the AndroidQF project (https://github.com/mvt-project/androidqf/) and analyzing that acquisition with MVT."
|
|
||||||
)
|
|
||||||
|
|
||||||
log.info("Checking Android device over debug bridge")
|
log.info("Checking Android device over debug bridge")
|
||||||
|
|
||||||
cmd.run()
|
cmd.run()
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import logging
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.common.command import Command
|
from mvt.common.command import Command
|
||||||
from mvt.common.indicators import Indicators
|
|
||||||
|
|
||||||
from .modules.adb import ADB_MODULES
|
from .modules.adb import ADB_MODULES
|
||||||
|
|
||||||
@@ -20,12 +19,9 @@ class CmdAndroidCheckADB(Command):
|
|||||||
target_path: Optional[str] = None,
|
target_path: Optional[str] = None,
|
||||||
results_path: Optional[str] = None,
|
results_path: Optional[str] = None,
|
||||||
ioc_files: Optional[list] = None,
|
ioc_files: Optional[list] = None,
|
||||||
iocs: Optional[Indicators] = None,
|
|
||||||
module_name: Optional[str] = None,
|
module_name: Optional[str] = None,
|
||||||
serial: Optional[str] = None,
|
serial: Optional[str] = None,
|
||||||
module_options: Optional[dict] = None,
|
module_options: Optional[dict] = None,
|
||||||
hashes: Optional[bool] = False,
|
|
||||||
sub_command: Optional[bool] = False,
|
|
||||||
disable_version_check: bool = False,
|
disable_version_check: bool = False,
|
||||||
disable_indicator_check: bool = False,
|
disable_indicator_check: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -33,12 +29,9 @@ class CmdAndroidCheckADB(Command):
|
|||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
results_path=results_path,
|
results_path=results_path,
|
||||||
ioc_files=ioc_files,
|
ioc_files=ioc_files,
|
||||||
iocs=iocs,
|
|
||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
serial=serial,
|
serial=serial,
|
||||||
module_options=module_options,
|
module_options=module_options,
|
||||||
hashes=hashes,
|
|
||||||
sub_command=sub_command,
|
|
||||||
log=log,
|
log=log,
|
||||||
disable_version_check=disable_version_check,
|
disable_version_check=disable_version_check,
|
||||||
disable_indicator_check=disable_indicator_check,
|
disable_indicator_check=disable_indicator_check,
|
||||||
|
|||||||
@@ -9,41 +9,23 @@ import zipfile
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from mvt.android.cmd_check_backup import CmdAndroidCheckBackup
|
|
||||||
from mvt.android.cmd_check_bugreport import CmdAndroidCheckBugreport
|
|
||||||
from mvt.common.command import Command
|
from mvt.common.command import Command
|
||||||
from mvt.common.indicators import Indicators
|
|
||||||
|
|
||||||
from .modules.androidqf import ANDROIDQF_MODULES
|
from .modules.androidqf import ANDROIDQF_MODULES
|
||||||
from .modules.androidqf.base import AndroidQFModule
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NoAndroidQFTargetPath(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoAndroidQFBugReport(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoAndroidQFBackup(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CmdAndroidCheckAndroidQF(Command):
|
class CmdAndroidCheckAndroidQF(Command):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
target_path: Optional[str] = None,
|
target_path: Optional[str] = None,
|
||||||
results_path: Optional[str] = None,
|
results_path: Optional[str] = None,
|
||||||
ioc_files: Optional[list] = None,
|
ioc_files: Optional[list] = None,
|
||||||
iocs: Optional[Indicators] = None,
|
|
||||||
module_name: Optional[str] = None,
|
module_name: Optional[str] = None,
|
||||||
serial: Optional[str] = None,
|
serial: Optional[str] = None,
|
||||||
module_options: Optional[dict] = None,
|
module_options: Optional[dict] = None,
|
||||||
hashes: Optional[bool] = False,
|
hashes: bool = False,
|
||||||
sub_command: Optional[bool] = False,
|
|
||||||
disable_version_check: bool = False,
|
disable_version_check: bool = False,
|
||||||
disable_indicator_check: bool = False,
|
disable_indicator_check: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -51,12 +33,10 @@ class CmdAndroidCheckAndroidQF(Command):
|
|||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
results_path=results_path,
|
results_path=results_path,
|
||||||
ioc_files=ioc_files,
|
ioc_files=ioc_files,
|
||||||
iocs=iocs,
|
|
||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
serial=serial,
|
serial=serial,
|
||||||
module_options=module_options,
|
module_options=module_options,
|
||||||
hashes=hashes,
|
hashes=hashes,
|
||||||
sub_command=sub_command,
|
|
||||||
log=log,
|
log=log,
|
||||||
disable_version_check=disable_version_check,
|
disable_version_check=disable_version_check,
|
||||||
disable_indicator_check=disable_indicator_check,
|
disable_indicator_check=disable_indicator_check,
|
||||||
@@ -65,130 +45,27 @@ class CmdAndroidCheckAndroidQF(Command):
|
|||||||
self.name = "check-androidqf"
|
self.name = "check-androidqf"
|
||||||
self.modules = ANDROIDQF_MODULES
|
self.modules = ANDROIDQF_MODULES
|
||||||
|
|
||||||
self.__format: Optional[str] = None
|
self.format: Optional[str] = None
|
||||||
self.__zip: Optional[zipfile.ZipFile] = None
|
self.archive: Optional[zipfile.ZipFile] = None
|
||||||
self.__files: List[str] = []
|
self.files: List[str] = []
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
if os.path.isdir(self.target_path):
|
if os.path.isdir(self.target_path):
|
||||||
self.__format = "dir"
|
self.format = "dir"
|
||||||
parent_path = Path(self.target_path).absolute().parent.as_posix()
|
parent_path = Path(self.target_path).absolute().parent.as_posix()
|
||||||
target_abs_path = os.path.abspath(self.target_path)
|
target_abs_path = os.path.abspath(self.target_path)
|
||||||
for root, subdirs, subfiles in os.walk(target_abs_path):
|
for root, subdirs, subfiles in os.walk(target_abs_path):
|
||||||
for fname in subfiles:
|
for fname in subfiles:
|
||||||
file_path = os.path.relpath(os.path.join(root, fname), parent_path)
|
file_path = os.path.relpath(os.path.join(root, fname), parent_path)
|
||||||
self.__files.append(file_path)
|
self.files.append(file_path)
|
||||||
elif os.path.isfile(self.target_path):
|
elif os.path.isfile(self.target_path):
|
||||||
self.__format = "zip"
|
self.format = "zip"
|
||||||
self.__zip = zipfile.ZipFile(self.target_path)
|
self.archive = zipfile.ZipFile(self.target_path)
|
||||||
self.__files = self.__zip.namelist()
|
self.files = self.archive.namelist()
|
||||||
|
|
||||||
def module_init(self, module: AndroidQFModule) -> None: # type: ignore[override]
|
def module_init(self, module):
|
||||||
if self.__format == "zip" and self.__zip:
|
if self.format == "zip":
|
||||||
module.from_zip(self.__zip, self.__files)
|
module.from_zip_file(self.archive, self.files)
|
||||||
return
|
|
||||||
|
|
||||||
if not self.target_path:
|
|
||||||
raise NoAndroidQFTargetPath
|
|
||||||
|
|
||||||
parent_path = Path(self.target_path).absolute().parent.as_posix()
|
|
||||||
module.from_dir(parent_path, self.__files)
|
|
||||||
|
|
||||||
def load_bugreport(self) -> zipfile.ZipFile:
|
|
||||||
bugreport_zip_path = None
|
|
||||||
for file_name in self.__files:
|
|
||||||
if file_name.endswith("bugreport.zip"):
|
|
||||||
bugreport_zip_path = file_name
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
raise NoAndroidQFBugReport
|
|
||||||
|
|
||||||
if self.__format == "zip" and self.__zip:
|
|
||||||
handle = self.__zip.open(bugreport_zip_path)
|
|
||||||
return zipfile.ZipFile(handle)
|
|
||||||
|
|
||||||
if self.__format == "dir" and self.target_path:
|
|
||||||
parent_path = Path(self.target_path).absolute().parent.as_posix()
|
parent_path = Path(self.target_path).absolute().parent.as_posix()
|
||||||
bug_report_path = os.path.join(parent_path, bugreport_zip_path)
|
module.from_folder(parent_path, self.files)
|
||||||
return zipfile.ZipFile(bug_report_path)
|
|
||||||
|
|
||||||
raise NoAndroidQFBugReport
|
|
||||||
|
|
||||||
def load_backup(self) -> bytes:
|
|
||||||
backup_ab_path = None
|
|
||||||
for file_name in self.__files:
|
|
||||||
if file_name.endswith("backup.ab"):
|
|
||||||
backup_ab_path = file_name
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise NoAndroidQFBackup
|
|
||||||
|
|
||||||
if self.__format == "zip" and self.__zip:
|
|
||||||
backup_file_handle = self.__zip.open(backup_ab_path)
|
|
||||||
return backup_file_handle.read()
|
|
||||||
|
|
||||||
if self.__format == "dir" and self.target_path:
|
|
||||||
parent_path = Path(self.target_path).absolute().parent.as_posix()
|
|
||||||
backup_path = os.path.join(parent_path, backup_ab_path)
|
|
||||||
with open(backup_path, "rb") as backup_file:
|
|
||||||
backup_ab_data = backup_file.read()
|
|
||||||
return backup_ab_data
|
|
||||||
|
|
||||||
raise NoAndroidQFBackup
|
|
||||||
|
|
||||||
def run_bugreport_cmd(self) -> bool:
|
|
||||||
try:
|
|
||||||
bugreport = self.load_bugreport()
|
|
||||||
except NoAndroidQFBugReport:
|
|
||||||
self.log.warning(
|
|
||||||
"Skipping bugreport modules as no bugreport.zip found in AndroidQF data."
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
cmd = CmdAndroidCheckBugreport(
|
|
||||||
target_path=None,
|
|
||||||
results_path=self.results_path,
|
|
||||||
ioc_files=self.ioc_files,
|
|
||||||
iocs=self.iocs,
|
|
||||||
module_options=self.module_options,
|
|
||||||
hashes=self.hashes,
|
|
||||||
sub_command=True,
|
|
||||||
)
|
|
||||||
cmd.from_zip(bugreport)
|
|
||||||
cmd.run()
|
|
||||||
|
|
||||||
self.detected_count += cmd.detected_count
|
|
||||||
self.timeline.extend(cmd.timeline)
|
|
||||||
self.timeline_detected.extend(cmd.timeline_detected)
|
|
||||||
|
|
||||||
def run_backup_cmd(self) -> bool:
|
|
||||||
try:
|
|
||||||
backup = self.load_backup()
|
|
||||||
except NoAndroidQFBackup:
|
|
||||||
self.log.warning(
|
|
||||||
"Skipping backup modules as no backup.ab found in AndroidQF data."
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
cmd = CmdAndroidCheckBackup(
|
|
||||||
target_path=None,
|
|
||||||
results_path=self.results_path,
|
|
||||||
ioc_files=self.ioc_files,
|
|
||||||
iocs=self.iocs,
|
|
||||||
module_options=self.module_options,
|
|
||||||
hashes=self.hashes,
|
|
||||||
sub_command=True,
|
|
||||||
)
|
|
||||||
cmd.from_ab(backup)
|
|
||||||
cmd.run()
|
|
||||||
|
|
||||||
self.detected_count += cmd.detected_count
|
|
||||||
self.timeline.extend(cmd.timeline)
|
|
||||||
self.timeline_detected.extend(cmd.timeline_detected)
|
|
||||||
|
|
||||||
def finish(self) -> None:
|
|
||||||
"""
|
|
||||||
Run the bugreport and backup modules if the respective files are found in the AndroidQF data.
|
|
||||||
"""
|
|
||||||
self.run_bugreport_cmd()
|
|
||||||
self.run_backup_cmd()
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from mvt.android.parsers.backup import (
|
|||||||
parse_backup_file,
|
parse_backup_file,
|
||||||
)
|
)
|
||||||
from mvt.common.command import Command
|
from mvt.common.command import Command
|
||||||
from mvt.common.indicators import Indicators
|
|
||||||
|
|
||||||
from .modules.backup import BACKUP_MODULES
|
from .modules.backup import BACKUP_MODULES
|
||||||
|
|
||||||
@@ -33,12 +32,10 @@ class CmdAndroidCheckBackup(Command):
|
|||||||
target_path: Optional[str] = None,
|
target_path: Optional[str] = None,
|
||||||
results_path: Optional[str] = None,
|
results_path: Optional[str] = None,
|
||||||
ioc_files: Optional[list] = None,
|
ioc_files: Optional[list] = None,
|
||||||
iocs: Optional[Indicators] = None,
|
|
||||||
module_name: Optional[str] = None,
|
module_name: Optional[str] = None,
|
||||||
serial: Optional[str] = None,
|
serial: Optional[str] = None,
|
||||||
module_options: Optional[dict] = None,
|
module_options: Optional[dict] = None,
|
||||||
hashes: Optional[bool] = False,
|
hashes: bool = False,
|
||||||
sub_command: Optional[bool] = False,
|
|
||||||
disable_version_check: bool = False,
|
disable_version_check: bool = False,
|
||||||
disable_indicator_check: bool = False,
|
disable_indicator_check: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -46,12 +43,10 @@ class CmdAndroidCheckBackup(Command):
|
|||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
results_path=results_path,
|
results_path=results_path,
|
||||||
ioc_files=ioc_files,
|
ioc_files=ioc_files,
|
||||||
iocs=iocs,
|
|
||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
serial=serial,
|
serial=serial,
|
||||||
module_options=module_options,
|
module_options=module_options,
|
||||||
hashes=hashes,
|
hashes=hashes,
|
||||||
sub_command=sub_command,
|
|
||||||
log=log,
|
log=log,
|
||||||
disable_version_check=disable_version_check,
|
disable_version_check=disable_version_check,
|
||||||
disable_indicator_check=disable_indicator_check,
|
disable_indicator_check=disable_indicator_check,
|
||||||
@@ -64,34 +59,6 @@ class CmdAndroidCheckBackup(Command):
|
|||||||
self.backup_archive: Optional[tarfile.TarFile] = None
|
self.backup_archive: Optional[tarfile.TarFile] = None
|
||||||
self.backup_files: List[str] = []
|
self.backup_files: List[str] = []
|
||||||
|
|
||||||
def from_ab(self, ab_file_bytes: bytes) -> None:
|
|
||||||
self.backup_type = "ab"
|
|
||||||
header = parse_ab_header(ab_file_bytes)
|
|
||||||
if not header["backup"]:
|
|
||||||
log.critical("Invalid backup format, file should be in .ab format")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
password = None
|
|
||||||
if header["encryption"] != "none":
|
|
||||||
password = prompt_or_load_android_backup_password(log, self.module_options)
|
|
||||||
if not password:
|
|
||||||
log.critical("No backup password provided.")
|
|
||||||
sys.exit(1)
|
|
||||||
try:
|
|
||||||
tardata = parse_backup_file(ab_file_bytes, password=password)
|
|
||||||
except InvalidBackupPassword:
|
|
||||||
log.critical("Invalid backup password")
|
|
||||||
sys.exit(1)
|
|
||||||
except AndroidBackupParsingError as exc:
|
|
||||||
log.critical("Impossible to parse this backup file: %s", exc)
|
|
||||||
log.critical("Please use Android Backup Extractor (ABE) instead")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
dbytes = io.BytesIO(tardata)
|
|
||||||
self.backup_archive = tarfile.open(fileobj=dbytes)
|
|
||||||
for member in self.backup_archive:
|
|
||||||
self.backup_files.append(member.name)
|
|
||||||
|
|
||||||
def init(self) -> None:
|
def init(self) -> None:
|
||||||
if not self.target_path:
|
if not self.target_path:
|
||||||
return
|
return
|
||||||
@@ -99,8 +66,35 @@ class CmdAndroidCheckBackup(Command):
|
|||||||
if os.path.isfile(self.target_path):
|
if os.path.isfile(self.target_path):
|
||||||
self.backup_type = "ab"
|
self.backup_type = "ab"
|
||||||
with open(self.target_path, "rb") as handle:
|
with open(self.target_path, "rb") as handle:
|
||||||
ab_file_bytes = handle.read()
|
data = handle.read()
|
||||||
self.from_ab(ab_file_bytes)
|
|
||||||
|
header = parse_ab_header(data)
|
||||||
|
if not header["backup"]:
|
||||||
|
log.critical("Invalid backup format, file should be in .ab format")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
password = None
|
||||||
|
if header["encryption"] != "none":
|
||||||
|
password = prompt_or_load_android_backup_password(
|
||||||
|
log, self.module_options
|
||||||
|
)
|
||||||
|
if not password:
|
||||||
|
log.critical("No backup password provided.")
|
||||||
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
tardata = parse_backup_file(data, password=password)
|
||||||
|
except InvalidBackupPassword:
|
||||||
|
log.critical("Invalid backup password")
|
||||||
|
sys.exit(1)
|
||||||
|
except AndroidBackupParsingError as exc:
|
||||||
|
log.critical("Impossible to parse this backup file: %s", exc)
|
||||||
|
log.critical("Please use Android Backup Extractor (ABE) instead")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
dbytes = io.BytesIO(tardata)
|
||||||
|
self.backup_archive = tarfile.open(fileobj=dbytes)
|
||||||
|
for member in self.backup_archive:
|
||||||
|
self.backup_files.append(member.name)
|
||||||
|
|
||||||
elif os.path.isdir(self.target_path):
|
elif os.path.isdir(self.target_path):
|
||||||
self.backup_type = "folder"
|
self.backup_type = "folder"
|
||||||
@@ -119,6 +113,6 @@ class CmdAndroidCheckBackup(Command):
|
|||||||
|
|
||||||
def module_init(self, module: BackupExtraction) -> None: # type: ignore[override]
|
def module_init(self, module: BackupExtraction) -> None: # type: ignore[override]
|
||||||
if self.backup_type == "folder":
|
if self.backup_type == "folder":
|
||||||
module.from_dir(self.target_path, self.backup_files)
|
module.from_folder(self.target_path, self.backup_files)
|
||||||
else:
|
else:
|
||||||
module.from_ab(self.target_path, self.backup_archive, self.backup_files)
|
module.from_ab(self.target_path, self.backup_archive, self.backup_files)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from zipfile import ZipFile
|
|||||||
|
|
||||||
from mvt.android.modules.bugreport.base import BugReportModule
|
from mvt.android.modules.bugreport.base import BugReportModule
|
||||||
from mvt.common.command import Command
|
from mvt.common.command import Command
|
||||||
from mvt.common.indicators import Indicators
|
|
||||||
|
|
||||||
from .modules.bugreport import BUGREPORT_MODULES
|
from .modules.bugreport import BUGREPORT_MODULES
|
||||||
|
|
||||||
@@ -24,12 +23,10 @@ class CmdAndroidCheckBugreport(Command):
|
|||||||
target_path: Optional[str] = None,
|
target_path: Optional[str] = None,
|
||||||
results_path: Optional[str] = None,
|
results_path: Optional[str] = None,
|
||||||
ioc_files: Optional[list] = None,
|
ioc_files: Optional[list] = None,
|
||||||
iocs: Optional[Indicators] = None,
|
|
||||||
module_name: Optional[str] = None,
|
module_name: Optional[str] = None,
|
||||||
serial: Optional[str] = None,
|
serial: Optional[str] = None,
|
||||||
module_options: Optional[dict] = None,
|
module_options: Optional[dict] = None,
|
||||||
hashes: Optional[bool] = False,
|
hashes: bool = False,
|
||||||
sub_command: Optional[bool] = False,
|
|
||||||
disable_version_check: bool = False,
|
disable_version_check: bool = False,
|
||||||
disable_indicator_check: bool = False,
|
disable_indicator_check: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -37,12 +34,10 @@ class CmdAndroidCheckBugreport(Command):
|
|||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
results_path=results_path,
|
results_path=results_path,
|
||||||
ioc_files=ioc_files,
|
ioc_files=ioc_files,
|
||||||
iocs=iocs,
|
|
||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
serial=serial,
|
serial=serial,
|
||||||
module_options=module_options,
|
module_options=module_options,
|
||||||
hashes=hashes,
|
hashes=hashes,
|
||||||
sub_command=sub_command,
|
|
||||||
log=log,
|
log=log,
|
||||||
disable_version_check=disable_version_check,
|
disable_version_check=disable_version_check,
|
||||||
disable_indicator_check=disable_indicator_check,
|
disable_indicator_check=disable_indicator_check,
|
||||||
@@ -51,53 +46,35 @@ class CmdAndroidCheckBugreport(Command):
|
|||||||
self.name = "check-bugreport"
|
self.name = "check-bugreport"
|
||||||
self.modules = BUGREPORT_MODULES
|
self.modules = BUGREPORT_MODULES
|
||||||
|
|
||||||
self.__format: str = ""
|
self.bugreport_format: str = ""
|
||||||
self.__zip: Optional[ZipFile] = None
|
self.bugreport_archive: Optional[ZipFile] = None
|
||||||
self.__files: List[str] = []
|
self.bugreport_files: List[str] = []
|
||||||
|
|
||||||
def from_dir(self, dir_path: str) -> None:
|
|
||||||
"""This method is used to initialize the bug report analysis from an
|
|
||||||
uncompressed directory.
|
|
||||||
"""
|
|
||||||
self.__format = "dir"
|
|
||||||
self.target_path = dir_path
|
|
||||||
parent_path = Path(dir_path).absolute().as_posix()
|
|
||||||
for root, _, subfiles in os.walk(os.path.abspath(dir_path)):
|
|
||||||
for file_name in subfiles:
|
|
||||||
file_path = os.path.relpath(os.path.join(root, file_name), parent_path)
|
|
||||||
self.__files.append(file_path)
|
|
||||||
|
|
||||||
def from_zip(self, bugreport_zip: ZipFile) -> None:
|
|
||||||
"""This method is used to initialize the bug report analysis from a
|
|
||||||
compressed archive.
|
|
||||||
"""
|
|
||||||
# NOTE: This will be invoked either by the CLI directly,or by the
|
|
||||||
# check-androidqf command. We need this because we want to support
|
|
||||||
# check-androidqf to analyse compressed archives itself too.
|
|
||||||
# So, we'll need to extract bugreport.zip from a 'androidqf.zip', and
|
|
||||||
# since nothing is written on disk, we need to be able to pass this
|
|
||||||
# command a ZipFile instance in memory.
|
|
||||||
|
|
||||||
self.__format = "zip"
|
|
||||||
self.__zip = bugreport_zip
|
|
||||||
for file_name in self.__zip.namelist():
|
|
||||||
self.__files.append(file_name)
|
|
||||||
|
|
||||||
def init(self) -> None:
|
def init(self) -> None:
|
||||||
if not self.target_path:
|
if not self.target_path:
|
||||||
return
|
return
|
||||||
|
|
||||||
if os.path.isfile(self.target_path):
|
if os.path.isfile(self.target_path):
|
||||||
self.from_zip(ZipFile(self.target_path))
|
self.bugreport_format = "zip"
|
||||||
|
self.bugreport_archive = ZipFile(self.target_path)
|
||||||
|
for file_name in self.bugreport_archive.namelist():
|
||||||
|
self.bugreport_files.append(file_name)
|
||||||
elif os.path.isdir(self.target_path):
|
elif os.path.isdir(self.target_path):
|
||||||
self.from_dir(self.target_path)
|
self.bugreport_format = "dir"
|
||||||
|
parent_path = Path(self.target_path).absolute().as_posix()
|
||||||
|
for root, _, subfiles in os.walk(os.path.abspath(self.target_path)):
|
||||||
|
for file_name in subfiles:
|
||||||
|
file_path = os.path.relpath(
|
||||||
|
os.path.join(root, file_name), parent_path
|
||||||
|
)
|
||||||
|
self.bugreport_files.append(file_path)
|
||||||
|
|
||||||
def module_init(self, module: BugReportModule) -> None: # type: ignore[override]
|
def module_init(self, module: BugReportModule) -> None: # type: ignore[override]
|
||||||
if self.__format == "zip":
|
if self.bugreport_format == "zip":
|
||||||
module.from_zip(self.__zip, self.__files)
|
module.from_zip(self.bugreport_archive, self.bugreport_files)
|
||||||
else:
|
else:
|
||||||
module.from_dir(self.target_path, self.__files)
|
module.from_folder(self.target_path, self.bugreport_files)
|
||||||
|
|
||||||
def finish(self) -> None:
|
def finish(self) -> None:
|
||||||
if self.__zip:
|
if self.bugreport_archive:
|
||||||
self.__zip.close()
|
self.bugreport_archive.close()
|
||||||
|
|||||||
@@ -4,7 +4,15 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
from .chrome_history import ChromeHistory
|
from .chrome_history import ChromeHistory
|
||||||
|
from .dumpsys_accessibility import DumpsysAccessibility
|
||||||
|
from .dumpsys_activities import DumpsysActivities
|
||||||
|
from .dumpsys_appops import DumpsysAppOps
|
||||||
|
from .dumpsys_battery_daily import DumpsysBatteryDaily
|
||||||
|
from .dumpsys_battery_history import DumpsysBatteryHistory
|
||||||
|
from .dumpsys_dbinfo import DumpsysDBInfo
|
||||||
|
from .dumpsys_adbstate import DumpsysADBState
|
||||||
from .dumpsys_full import DumpsysFull
|
from .dumpsys_full import DumpsysFull
|
||||||
|
from .dumpsys_receivers import DumpsysReceivers
|
||||||
from .files import Files
|
from .files import Files
|
||||||
from .getprop import Getprop
|
from .getprop import Getprop
|
||||||
from .logcat import Logcat
|
from .logcat import Logcat
|
||||||
@@ -24,7 +32,15 @@ ADB_MODULES = [
|
|||||||
Getprop,
|
Getprop,
|
||||||
Settings,
|
Settings,
|
||||||
SELinuxStatus,
|
SELinuxStatus,
|
||||||
|
DumpsysBatteryHistory,
|
||||||
|
DumpsysBatteryDaily,
|
||||||
|
DumpsysReceivers,
|
||||||
|
DumpsysActivities,
|
||||||
|
DumpsysAccessibility,
|
||||||
|
DumpsysDBInfo,
|
||||||
|
DumpsysADBState,
|
||||||
DumpsysFull,
|
DumpsysFull,
|
||||||
|
DumpsysAppOps,
|
||||||
Packages,
|
Packages,
|
||||||
Logcat,
|
Logcat,
|
||||||
RootBinaries,
|
RootBinaries,
|
||||||
|
|||||||
49
src/mvt/android/modules/adb/dumpsys_accessibility.py
Normal file
49
src/mvt/android/modules/adb/dumpsys_accessibility.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArtifact
|
||||||
|
|
||||||
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysAccessibility(DumpsysAccessibilityArtifact, AndroidExtraction):
|
||||||
|
"""This module extracts stats on accessibility."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self._adb_connect()
|
||||||
|
output = self._adb_command("dumpsys accessibility")
|
||||||
|
self._adb_disconnect()
|
||||||
|
|
||||||
|
self.parse(output)
|
||||||
|
|
||||||
|
for result in self.results:
|
||||||
|
self.log.info(
|
||||||
|
'Found installed accessibility service "%s"', result.get("service")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Identified a total of %d accessibility services", len(self.results)
|
||||||
|
)
|
||||||
45
src/mvt/android/modules/adb/dumpsys_activities.py
Normal file
45
src/mvt/android/modules/adb/dumpsys_activities.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_package_activities import (
|
||||||
|
DumpsysPackageActivitiesArtifact,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysActivities(DumpsysPackageActivitiesArtifact, AndroidExtraction):
|
||||||
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = results if results else []
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self._adb_connect()
|
||||||
|
output = self._adb_command("dumpsys package")
|
||||||
|
self._adb_disconnect()
|
||||||
|
self.parse(output)
|
||||||
|
|
||||||
|
self.log.info("Extracted %d package activities", len(self.results))
|
||||||
45
src/mvt/android/modules/adb/dumpsys_adbstate.py
Normal file
45
src/mvt/android/modules/adb/dumpsys_adbstate.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_adb import DumpsysADBArtifact
|
||||||
|
|
||||||
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysADBState(DumpsysADBArtifact, AndroidExtraction):
|
||||||
|
"""This module extracts ADB keystore state."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self._adb_connect()
|
||||||
|
output = self._adb_command("dumpsys adb", decode=False)
|
||||||
|
self._adb_disconnect()
|
||||||
|
|
||||||
|
self.parse(output)
|
||||||
|
if self.results:
|
||||||
|
self.log.info(
|
||||||
|
"Identified a total of %d trusted ADB keys",
|
||||||
|
len(self.results[0].get("user_keys", [])),
|
||||||
|
)
|
||||||
46
src/mvt/android/modules/adb/dumpsys_appops.py
Normal file
46
src/mvt/android/modules/adb/dumpsys_appops.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact
|
||||||
|
|
||||||
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysAppOps(DumpsysAppopsArtifact, AndroidExtraction):
|
||||||
|
"""This module extracts records from App-op Manager."""
|
||||||
|
|
||||||
|
slug = "dumpsys_appops"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self._adb_connect()
|
||||||
|
output = self._adb_command("dumpsys appops")
|
||||||
|
self._adb_disconnect()
|
||||||
|
|
||||||
|
self.parse(output)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Extracted a total of %d records from app-ops manager", len(self.results)
|
||||||
|
)
|
||||||
44
src/mvt/android/modules/adb/dumpsys_battery_daily.py
Normal file
44
src/mvt/android/modules/adb/dumpsys_battery_daily.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
|
||||||
|
|
||||||
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, AndroidExtraction):
|
||||||
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self._adb_connect()
|
||||||
|
output = self._adb_command("dumpsys batterystats --daily")
|
||||||
|
self._adb_disconnect()
|
||||||
|
|
||||||
|
self.parse(output)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Extracted %d records from battery daily stats", len(self.results)
|
||||||
|
)
|
||||||
42
src/mvt/android/modules/adb/dumpsys_battery_history.py
Normal file
42
src/mvt/android/modules/adb/dumpsys_battery_history.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact
|
||||||
|
|
||||||
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, AndroidExtraction):
|
||||||
|
"""This module extracts records from battery history events."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self._adb_connect()
|
||||||
|
output = self._adb_command("dumpsys batterystats --history")
|
||||||
|
self._adb_disconnect()
|
||||||
|
|
||||||
|
self.parse(output)
|
||||||
|
|
||||||
|
self.log.info("Extracted %d records from battery history", len(self.results))
|
||||||
47
src/mvt/android/modules/adb/dumpsys_dbinfo.py
Normal file
47
src/mvt/android/modules/adb/dumpsys_dbinfo.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfoArtifact
|
||||||
|
|
||||||
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysDBInfo(DumpsysDBInfoArtifact, AndroidExtraction):
|
||||||
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
|
slug = "dumpsys_dbinfo"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self._adb_connect()
|
||||||
|
output = self._adb_command("dumpsys dbinfo")
|
||||||
|
self._adb_disconnect()
|
||||||
|
|
||||||
|
self.parse(output)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Extracted a total of %d records from database information",
|
||||||
|
len(self.results),
|
||||||
|
)
|
||||||
44
src/mvt/android/modules/adb/dumpsys_receivers.py
Normal file
44
src/mvt/android/modules/adb/dumpsys_receivers.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact
|
||||||
|
|
||||||
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysReceivers(DumpsysReceiversArtifact, AndroidExtraction):
|
||||||
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = results if results else {}
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self._adb_connect()
|
||||||
|
|
||||||
|
output = self._adb_command("dumpsys package")
|
||||||
|
self.parse(output)
|
||||||
|
|
||||||
|
self._adb_disconnect()
|
||||||
|
self.log.info("Extracted receivers for %d intents", len(self.results))
|
||||||
@@ -3,22 +3,42 @@
|
|||||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
from .aqf_files import AQFFiles
|
from .dumpsys_accessibility import DumpsysAccessibility
|
||||||
from .aqf_getprop import AQFGetProp
|
from .dumpsys_activities import DumpsysActivities
|
||||||
from .aqf_packages import AQFPackages
|
from .dumpsys_appops import DumpsysAppops
|
||||||
from .aqf_processes import AQFProcesses
|
from .dumpsys_battery_daily import DumpsysBatteryDaily
|
||||||
from .aqf_settings import AQFSettings
|
from .dumpsys_battery_history import DumpsysBatteryHistory
|
||||||
from .mounts import Mounts
|
from .dumpsys_dbinfo import DumpsysDBInfo
|
||||||
from .root_binaries import RootBinaries
|
from .dumpsys_packages import DumpsysPackages
|
||||||
|
from .dumpsys_receivers import DumpsysReceivers
|
||||||
|
from .dumpsys_adb import DumpsysADBState
|
||||||
|
from .getprop import Getprop
|
||||||
|
from .packages import Packages
|
||||||
|
from .dumpsys_platform_compat import DumpsysPlatformCompat
|
||||||
|
from .processes import Processes
|
||||||
|
from .settings import Settings
|
||||||
from .sms import SMS
|
from .sms import SMS
|
||||||
|
from .files import Files
|
||||||
|
from .root_binaries import RootBinaries
|
||||||
|
from .mounts import Mounts
|
||||||
|
|
||||||
ANDROIDQF_MODULES = [
|
ANDROIDQF_MODULES = [
|
||||||
AQFPackages,
|
DumpsysActivities,
|
||||||
AQFProcesses,
|
DumpsysReceivers,
|
||||||
AQFGetProp,
|
DumpsysAccessibility,
|
||||||
AQFSettings,
|
DumpsysAppops,
|
||||||
AQFFiles,
|
DumpsysDBInfo,
|
||||||
|
DumpsysBatteryDaily,
|
||||||
|
DumpsysBatteryHistory,
|
||||||
|
DumpsysADBState,
|
||||||
|
Packages,
|
||||||
|
DumpsysPlatformCompat,
|
||||||
|
Processes,
|
||||||
|
Getprop,
|
||||||
|
Settings,
|
||||||
SMS,
|
SMS,
|
||||||
|
DumpsysPackages,
|
||||||
|
Files,
|
||||||
RootBinaries,
|
RootBinaries,
|
||||||
Mounts,
|
Mounts,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ class AndroidQFModule(MVTModule):
|
|||||||
self.files: List[str] = []
|
self.files: List[str] = []
|
||||||
self.archive: Optional[zipfile.ZipFile] = None
|
self.archive: Optional[zipfile.ZipFile] = None
|
||||||
|
|
||||||
def from_dir(self, parent_path: str, files: List[str]) -> None:
|
def from_folder(self, parent_path: str, files: List[str]):
|
||||||
self.parent_path = parent_path
|
self.parent_path = parent_path
|
||||||
self.files = files
|
self.files = files
|
||||||
|
|
||||||
def from_zip(self, archive: zipfile.ZipFile, files: List[str]) -> None:
|
def from_zip_file(self, archive: zipfile.ZipFile, files: List[str]):
|
||||||
self.archive = archive
|
self.archive = archive
|
||||||
self.files = files
|
self.files = files
|
||||||
|
|
||||||
|
|||||||
51
src/mvt/android/modules/androidqf/dumpsys_accessibility.py
Normal file
51
src/mvt/android/modules/androidqf/dumpsys_accessibility.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArtifact
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysAccessibility(DumpsysAccessibilityArtifact, AndroidQFModule):
|
||||||
|
"""This module analyses dumpsys accessibility"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if not dumpsys_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
|
||||||
|
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE accessibility:")
|
||||||
|
self.parse(content)
|
||||||
|
|
||||||
|
for result in self.results:
|
||||||
|
self.log.info(
|
||||||
|
'Found installed accessibility service "%s"', result.get("service")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
"Identified a total of %d accessibility services", len(self.results)
|
||||||
|
)
|
||||||
50
src/mvt/android/modules/androidqf/dumpsys_activities.py
Normal file
50
src/mvt/android/modules/androidqf/dumpsys_activities.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_package_activities import (
|
||||||
|
DumpsysPackageActivitiesArtifact,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysActivities(DumpsysPackageActivitiesArtifact, AndroidQFModule):
|
||||||
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = results if results else []
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if not dumpsys_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get data and extract the dumpsys section
|
||||||
|
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
|
||||||
|
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
|
||||||
|
# Parse it
|
||||||
|
self.parse(content)
|
||||||
|
|
||||||
|
self.log.info("Extracted %d package activities", len(self.results))
|
||||||
51
src/mvt/android/modules/androidqf/dumpsys_adb.py
Normal file
51
src/mvt/android/modules/androidqf/dumpsys_adb.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_adb import DumpsysADBArtifact
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysADBState(DumpsysADBArtifact, AndroidQFModule):
|
||||||
|
"""This module extracts ADB keystore state."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if not dumpsys_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
full_dumpsys = self._get_file_content(dumpsys_file[0])
|
||||||
|
content = self.extract_dumpsys_section(
|
||||||
|
full_dumpsys,
|
||||||
|
b"DUMP OF SERVICE adb:",
|
||||||
|
binary=True,
|
||||||
|
)
|
||||||
|
self.parse(content)
|
||||||
|
if self.results:
|
||||||
|
self.log.info(
|
||||||
|
"Identified a total of %d trusted ADB keys",
|
||||||
|
len(self.results[0].get("user_keys", [])),
|
||||||
|
)
|
||||||
46
src/mvt/android/modules/androidqf/dumpsys_appops.py
Normal file
46
src/mvt/android/modules/androidqf/dumpsys_appops.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysAppops(DumpsysAppopsArtifact, AndroidQFModule):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if not dumpsys_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Extract section
|
||||||
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
|
section = self.extract_dumpsys_section(
|
||||||
|
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE appops:"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse it
|
||||||
|
self.parse(section)
|
||||||
|
self.log.info("Identified %d applications in AppOps Manager", len(self.results))
|
||||||
46
src/mvt/android/modules/androidqf/dumpsys_battery_daily.py
Normal file
46
src/mvt/android/modules/androidqf/dumpsys_battery_daily.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, AndroidQFModule):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if not dumpsys_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Extract section
|
||||||
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
|
section = self.extract_dumpsys_section(
|
||||||
|
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse it
|
||||||
|
self.parse(section)
|
||||||
|
self.log.info("Extracted a total of %d battery daily stats", len(self.results))
|
||||||
46
src/mvt/android/modules/androidqf/dumpsys_battery_history.py
Normal file
46
src/mvt/android/modules/androidqf/dumpsys_battery_history.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, AndroidQFModule):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if not dumpsys_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Extract section
|
||||||
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
|
section = self.extract_dumpsys_section(
|
||||||
|
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse it
|
||||||
|
self.parse(section)
|
||||||
|
self.log.info("Extracted a total of %d battery daily stats", len(self.results))
|
||||||
46
src/mvt/android/modules/androidqf/dumpsys_dbinfo.py
Normal file
46
src/mvt/android/modules/androidqf/dumpsys_dbinfo.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfoArtifact
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysDBInfo(DumpsysDBInfoArtifact, AndroidQFModule):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if not dumpsys_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Extract dumpsys DBInfo section
|
||||||
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
|
section = self.extract_dumpsys_section(
|
||||||
|
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE dbinfo:"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse it
|
||||||
|
self.parse(section)
|
||||||
|
self.log.info("Identified %d DB Info entries", len(self.results))
|
||||||
62
src/mvt/android/modules/androidqf/dumpsys_packages.py
Normal file
62
src/mvt/android/modules/androidqf/dumpsys_packages.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact
|
||||||
|
from mvt.android.modules.adb.packages import (
|
||||||
|
DANGEROUS_PERMISSIONS,
|
||||||
|
DANGEROUS_PERMISSIONS_THRESHOLD,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysPackages(DumpsysPackagesArtifact, AndroidQFModule):
|
||||||
|
"""This module analyse dumpsys packages"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if len(dumpsys_file) != 1:
|
||||||
|
self.log.info("Dumpsys file not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
|
||||||
|
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
|
||||||
|
self.parse(content)
|
||||||
|
|
||||||
|
for result in self.results:
|
||||||
|
dangerous_permissions_count = 0
|
||||||
|
for perm in result["permissions"]:
|
||||||
|
if perm["name"] in DANGEROUS_PERMISSIONS:
|
||||||
|
dangerous_permissions_count += 1
|
||||||
|
|
||||||
|
if dangerous_permissions_count >= DANGEROUS_PERMISSIONS_THRESHOLD:
|
||||||
|
self.log.info(
|
||||||
|
'Found package "%s" requested %d potentially dangerous permissions',
|
||||||
|
result["package_name"],
|
||||||
|
dangerous_permissions_count,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.log.info("Extracted details on %d packages", len(self.results))
|
||||||
44
src/mvt/android/modules/androidqf/dumpsys_platform_compat.py
Normal file
44
src/mvt/android/modules/androidqf/dumpsys_platform_compat.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_platform_compat import DumpsysPlatformCompatArtifact
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysPlatformCompat(DumpsysPlatformCompatArtifact, AndroidQFModule):
|
||||||
|
"""This module extracts details on uninstalled apps."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Optional[list] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if not dumpsys_file:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
|
||||||
|
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE platform_compat:")
|
||||||
|
self.parse(content)
|
||||||
|
|
||||||
|
self.log.info("Found %d uninstalled apps", len(self.results))
|
||||||
49
src/mvt/android/modules/androidqf/dumpsys_receivers.py
Normal file
49
src/mvt/android/modules/androidqf/dumpsys_receivers.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
|
from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact
|
||||||
|
|
||||||
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
|
class DumpsysReceivers(DumpsysReceiversArtifact, AndroidQFModule):
|
||||||
|
"""This module analyse dumpsys receivers"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
module_options: Optional[dict] = None,
|
||||||
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
|
results: Union[List[Any], Dict[str, Any], None] = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
file_path=file_path,
|
||||||
|
target_path=target_path,
|
||||||
|
results_path=results_path,
|
||||||
|
module_options=module_options,
|
||||||
|
log=log,
|
||||||
|
results=results,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = results if results else {}
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
|
if not dumpsys_file:
|
||||||
|
return
|
||||||
|
data = self._get_file_content(dumpsys_file[0])
|
||||||
|
|
||||||
|
dumpsys_section = self.extract_dumpsys_section(
|
||||||
|
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE package:"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.parse(dumpsys_section)
|
||||||
|
|
||||||
|
self.log.info("Extracted receivers for %d intents", len(self.results))
|
||||||
@@ -21,13 +21,8 @@ SUSPICIOUS_PATHS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AQFFiles(AndroidQFModule):
|
class Files(AndroidQFModule):
|
||||||
"""
|
"""This module analyse list of files"""
|
||||||
This module analyzes the files.json dump generated by AndroidQF.
|
|
||||||
|
|
||||||
The format needs to be kept in sync with the AndroidQF module code.
|
|
||||||
https://github.com/mvt-project/androidqf/blob/main/android-collector/cmd/find.go#L28
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.getprop import GetProp as GetPropArtifact
|
|||||||
from .base import AndroidQFModule
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
class AQFGetProp(GetPropArtifact, AndroidQFModule):
|
class Getprop(GetPropArtifact, AndroidQFModule):
|
||||||
"""This module extracts data from get properties."""
|
"""This module extracts data from get properties."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -13,10 +13,10 @@ from .base import AndroidQFModule
|
|||||||
from mvt.android.artifacts.file_timestamps import FileTimestampsArtifact
|
from mvt.android.artifacts.file_timestamps import FileTimestampsArtifact
|
||||||
|
|
||||||
|
|
||||||
class AQFLogTimestamps(FileTimestampsArtifact, AndroidQFModule):
|
class LogsFileTimestamps(FileTimestampsArtifact, AndroidQFModule):
|
||||||
"""This module creates timeline for log files extracted by AQF."""
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
slug = "aqf_log_timestamps"
|
slug = "logfile_timestamps"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -19,7 +19,7 @@ from mvt.android.utils import (
|
|||||||
from .base import AndroidQFModule
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
class AQFPackages(AndroidQFModule):
|
class Packages(AndroidQFModule):
|
||||||
"""This module examines the installed packages in packages.json"""
|
"""This module examines the installed packages in packages.json"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.processes import Processes as ProcessesArtifact
|
|||||||
from .base import AndroidQFModule
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
class AQFProcesses(ProcessesArtifact, AndroidQFModule):
|
class Processes(ProcessesArtifact, AndroidQFModule):
|
||||||
"""This module analyse running processes"""
|
"""This module analyse running processes"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.settings import Settings as SettingsArtifact
|
|||||||
from .base import AndroidQFModule
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
class AQFSettings(SettingsArtifact, AndroidQFModule):
|
class Settings(SettingsArtifact, AndroidQFModule):
|
||||||
"""This module analyse setting files"""
|
"""This module analyse setting files"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -19,13 +19,7 @@ from .base import AndroidQFModule
|
|||||||
|
|
||||||
|
|
||||||
class SMS(AndroidQFModule):
|
class SMS(AndroidQFModule):
|
||||||
"""
|
"""This module analyse SMS file in backup"""
|
||||||
This module analyse SMS file in backup
|
|
||||||
|
|
||||||
XXX: We should also de-duplicate this AQF module, but first we
|
|
||||||
need to add tests for loading encrypted SMS backups through the backup
|
|
||||||
sub-module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ class BackupExtraction(MVTModule):
|
|||||||
self.tar = None
|
self.tar = None
|
||||||
self.files = []
|
self.files = []
|
||||||
|
|
||||||
def from_dir(self, backup_path: Optional[str], files: List[str]) -> None:
|
def from_folder(self, backup_path: Optional[str], files: List[str]) -> None:
|
||||||
|
"""
|
||||||
|
Get all the files and list them
|
||||||
|
"""
|
||||||
self.backup_path = backup_path
|
self.backup_path = backup_path
|
||||||
self.files = files
|
self.files = files
|
||||||
|
|
||||||
@@ -55,16 +58,14 @@ class BackupExtraction(MVTModule):
|
|||||||
return fnmatch.filter(self.files, pattern)
|
return fnmatch.filter(self.files, pattern)
|
||||||
|
|
||||||
def _get_file_content(self, file_path: str) -> bytes:
|
def _get_file_content(self, file_path: str) -> bytes:
|
||||||
if self.tar:
|
if self.ab:
|
||||||
try:
|
try:
|
||||||
member = self.tar.getmember(file_path)
|
member = self.tar.getmember(file_path)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
handle = self.tar.extractfile(member)
|
handle = self.tar.extractfile(member)
|
||||||
elif self.backup_path:
|
|
||||||
handle = open(os.path.join(self.backup_path, file_path), "rb")
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("No backup path or tar file provided")
|
handle = open(os.path.join(self.backup_path, file_path), "rb")
|
||||||
|
|
||||||
data = handle.read()
|
data = handle.read()
|
||||||
handle.close()
|
handle.close()
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ class SMS(BackupExtraction):
|
|||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
sms_path = "apps/com.android.providers.telephony/d_f/*_sms_backup"
|
sms_path = "apps/com.android.providers.telephony/d_f/*_sms_backup"
|
||||||
for file in self._get_files_by_pattern(sms_path):
|
for file in self._get_files_by_pattern(sms_path):
|
||||||
self.log.debug("Processing SMS backup file at %s", file)
|
self.log.info("Processing SMS backup file at %s", file)
|
||||||
data = self._get_file_content(file)
|
data = self._get_file_content(file)
|
||||||
self.results.extend(parse_sms_file(data))
|
self.results.extend(parse_sms_file(data))
|
||||||
|
|
||||||
mms_path = "apps/com.android.providers.telephony/d_f/*_mms_backup"
|
mms_path = "apps/com.android.providers.telephony/d_f/*_mms_backup"
|
||||||
for file in self._get_files_by_pattern(mms_path):
|
for file in self._get_files_by_pattern(mms_path):
|
||||||
self.log.debug("Processing MMS backup file at %s", file)
|
self.log.info("Processing MMS backup file at %s", file)
|
||||||
data = self._get_file_content(file)
|
data = self._get_file_content(file)
|
||||||
self.results.extend(parse_sms_file(data))
|
self.results.extend(parse_sms_file(data))
|
||||||
|
|
||||||
|
|||||||
@@ -3,31 +3,31 @@
|
|||||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
from .dumpsys_accessibility import DumpsysAccessibility
|
from .accessibility import Accessibility
|
||||||
from .dumpsys_activities import DumpsysActivities
|
from .activities import Activities
|
||||||
from .dumpsys_appops import DumpsysAppops
|
from .appops import Appops
|
||||||
from .dumpsys_battery_daily import DumpsysBatteryDaily
|
from .battery_daily import BatteryDaily
|
||||||
from .dumpsys_battery_history import DumpsysBatteryHistory
|
from .battery_history import BatteryHistory
|
||||||
from .dumpsys_dbinfo import DumpsysDBInfo
|
from .dbinfo import DBInfo
|
||||||
from .dumpsys_getprop import DumpsysGetProp
|
from .getprop import Getprop
|
||||||
from .dumpsys_packages import DumpsysPackages
|
from .packages import Packages
|
||||||
from .dumpsys_platform_compat import DumpsysPlatformCompat
|
from .platform_compat import PlatformCompat
|
||||||
from .dumpsys_receivers import DumpsysReceivers
|
from .receivers import Receivers
|
||||||
from .dumpsys_adb_state import DumpsysADBState
|
from .adb_state import DumpsysADBState
|
||||||
from .fs_timestamps import BugReportTimestamps
|
from .fs_timestamps import BugReportTimestamps
|
||||||
from .tombstones import Tombstones
|
from .tombstones import Tombstones
|
||||||
|
|
||||||
BUGREPORT_MODULES = [
|
BUGREPORT_MODULES = [
|
||||||
DumpsysAccessibility,
|
Accessibility,
|
||||||
DumpsysActivities,
|
Activities,
|
||||||
DumpsysAppops,
|
Appops,
|
||||||
DumpsysBatteryDaily,
|
BatteryDaily,
|
||||||
DumpsysBatteryHistory,
|
BatteryHistory,
|
||||||
DumpsysDBInfo,
|
DBInfo,
|
||||||
DumpsysGetProp,
|
Getprop,
|
||||||
DumpsysPackages,
|
Packages,
|
||||||
DumpsysPlatformCompat,
|
PlatformCompat,
|
||||||
DumpsysReceivers,
|
Receivers,
|
||||||
DumpsysADBState,
|
DumpsysADBState,
|
||||||
BugReportTimestamps,
|
BugReportTimestamps,
|
||||||
Tombstones,
|
Tombstones,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArti
|
|||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysAccessibility(DumpsysAccessibilityArtifact, BugReportModule):
|
class Accessibility(DumpsysAccessibilityArtifact, BugReportModule):
|
||||||
"""This module extracts stats on accessibility."""
|
"""This module extracts stats on accessibility."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -13,7 +13,7 @@ from mvt.android.artifacts.dumpsys_package_activities import (
|
|||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysActivities(DumpsysPackageActivitiesArtifact, BugReportModule):
|
class Activities(DumpsysPackageActivitiesArtifact, BugReportModule):
|
||||||
"""This module extracts details on receivers for risky activities."""
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact
|
|||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysAppops(DumpsysAppopsArtifact, BugReportModule):
|
class Appops(DumpsysAppopsArtifact, BugReportModule):
|
||||||
"""This module extracts information on package from App-Ops Manager."""
|
"""This module extracts information on package from App-Ops Manager."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -39,7 +39,9 @@ class BugReportModule(MVTModule):
|
|||||||
self.extract_files: List[str] = []
|
self.extract_files: List[str] = []
|
||||||
self.zip_files: List[str] = []
|
self.zip_files: List[str] = []
|
||||||
|
|
||||||
def from_dir(self, extract_path: str, extract_files: List[str]) -> None:
|
def from_folder(
|
||||||
|
self, extract_path: Optional[str], extract_files: List[str]
|
||||||
|
) -> None:
|
||||||
self.extract_path = extract_path
|
self.extract_path = extract_path
|
||||||
self.extract_files = extract_files
|
self.extract_files = extract_files
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtif
|
|||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, BugReportModule):
|
class BatteryDaily(DumpsysBatteryDailyArtifact, BugReportModule):
|
||||||
"""This module extracts records from battery daily updates."""
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryA
|
|||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, BugReportModule):
|
class BatteryHistory(DumpsysBatteryHistoryArtifact, BugReportModule):
|
||||||
"""This module extracts records from battery daily updates."""
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfoArtifact
|
|||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysDBInfo(DumpsysDBInfoArtifact, BugReportModule):
|
class DBInfo(DumpsysDBInfoArtifact, BugReportModule):
|
||||||
"""This module extracts records from battery daily updates."""
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
slug = "dbinfo"
|
slug = "dbinfo"
|
||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.getprop import GetProp as GetPropArtifact
|
|||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysGetProp(GetPropArtifact, BugReportModule):
|
class Getprop(GetPropArtifact, BugReportModule):
|
||||||
"""This module extracts device properties from getprop command."""
|
"""This module extracts device properties from getprop command."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -12,7 +12,7 @@ from mvt.android.utils import DANGEROUS_PERMISSIONS, DANGEROUS_PERMISSIONS_THRES
|
|||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysPackages(DumpsysPackagesArtifact, BugReportModule):
|
class Packages(DumpsysPackagesArtifact, BugReportModule):
|
||||||
"""This module extracts details on receivers for risky activities."""
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_platform_compat import DumpsysPlatformCompatA
|
|||||||
from mvt.android.modules.bugreport.base import BugReportModule
|
from mvt.android.modules.bugreport.base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysPlatformCompat(DumpsysPlatformCompatArtifact, BugReportModule):
|
class PlatformCompat(DumpsysPlatformCompatArtifact, BugReportModule):
|
||||||
"""This module extracts details on uninstalled apps."""
|
"""This module extracts details on uninstalled apps."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact
|
|||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysReceivers(DumpsysReceiversArtifact, BugReportModule):
|
class Receivers(DumpsysReceiversArtifact, BugReportModule):
|
||||||
"""This module extracts details on receivers for risky activities."""
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -34,20 +34,6 @@ class DumpsysReceivers(DumpsysReceiversArtifact, BugReportModule):
|
|||||||
|
|
||||||
self.results = results if results else {}
|
self.results = results if results else {}
|
||||||
|
|
||||||
def check_indicators(self) -> None:
|
|
||||||
for result in self.results:
|
|
||||||
if self.indicators:
|
|
||||||
receiver_name = self.results[result][0]["receiver"]
|
|
||||||
|
|
||||||
# return IoC if the stix2 process name a substring of the receiver name
|
|
||||||
ioc = self.indicators.check_receiver_prefix(receiver_name)
|
|
||||||
if ioc:
|
|
||||||
self.results[result][0]["matched_indicator"] = ioc
|
|
||||||
self.detected.append(result)
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
content = self._get_dumpstate_file()
|
content = self._get_dumpstate_file()
|
||||||
if not content:
|
if not content:
|
||||||
@@ -22,8 +22,6 @@ class CmdCheckIOCS(Command):
|
|||||||
module_name: Optional[str] = None,
|
module_name: Optional[str] = None,
|
||||||
serial: Optional[str] = None,
|
serial: Optional[str] = None,
|
||||||
module_options: Optional[dict] = None,
|
module_options: Optional[dict] = None,
|
||||||
hashes: Optional[bool] = False,
|
|
||||||
sub_command: Optional[bool] = False,
|
|
||||||
disable_version_check: bool = False,
|
disable_version_check: bool = False,
|
||||||
disable_indicator_check: bool = False,
|
disable_indicator_check: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -34,8 +32,6 @@ class CmdCheckIOCS(Command):
|
|||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
serial=serial,
|
serial=serial,
|
||||||
module_options=module_options,
|
module_options=module_options,
|
||||||
hashes=hashes,
|
|
||||||
sub_command=sub_command,
|
|
||||||
log=log,
|
log=log,
|
||||||
disable_version_check=disable_version_check,
|
disable_version_check=disable_version_check,
|
||||||
disable_indicator_check=disable_indicator_check,
|
disable_indicator_check=disable_indicator_check,
|
||||||
|
|||||||
@@ -27,12 +27,10 @@ class Command:
|
|||||||
target_path: Optional[str] = None,
|
target_path: Optional[str] = None,
|
||||||
results_path: Optional[str] = None,
|
results_path: Optional[str] = None,
|
||||||
ioc_files: Optional[list] = None,
|
ioc_files: Optional[list] = None,
|
||||||
iocs: Optional[Indicators] = None,
|
|
||||||
module_name: Optional[str] = None,
|
module_name: Optional[str] = None,
|
||||||
serial: Optional[str] = None,
|
serial: Optional[str] = None,
|
||||||
module_options: Optional[dict] = None,
|
module_options: Optional[dict] = None,
|
||||||
hashes: Optional[bool] = False,
|
hashes: bool = False,
|
||||||
sub_command: Optional[bool] = False,
|
|
||||||
log: logging.Logger = logging.getLogger(__name__),
|
log: logging.Logger = logging.getLogger(__name__),
|
||||||
disable_version_check: bool = False,
|
disable_version_check: bool = False,
|
||||||
disable_indicator_check: bool = False,
|
disable_indicator_check: bool = False,
|
||||||
@@ -46,7 +44,6 @@ class Command:
|
|||||||
self.module_name = module_name
|
self.module_name = module_name
|
||||||
self.serial = serial
|
self.serial = serial
|
||||||
self.log = log
|
self.log = log
|
||||||
self.sub_command = sub_command
|
|
||||||
self.disable_version_check = disable_version_check
|
self.disable_version_check = disable_version_check
|
||||||
self.disable_indicator_check = disable_indicator_check
|
self.disable_indicator_check = disable_indicator_check
|
||||||
|
|
||||||
@@ -67,12 +64,8 @@ class Command:
|
|||||||
# Load IOCs
|
# Load IOCs
|
||||||
self._create_storage()
|
self._create_storage()
|
||||||
self._setup_logging()
|
self._setup_logging()
|
||||||
|
self.iocs = Indicators(log=log)
|
||||||
if iocs is not None:
|
self.iocs.load_indicators_files(self.ioc_files)
|
||||||
self.iocs = iocs
|
|
||||||
else:
|
|
||||||
self.iocs = Indicators(self.log)
|
|
||||||
self.iocs.load_indicators_files(self.ioc_files)
|
|
||||||
|
|
||||||
def _create_storage(self) -> None:
|
def _create_storage(self) -> None:
|
||||||
if self.results_path and not os.path.exists(self.results_path):
|
if self.results_path and not os.path.exists(self.results_path):
|
||||||
@@ -258,10 +251,6 @@ class Command:
|
|||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# We only store the timeline from the parent/main command
|
|
||||||
if self.sub_command:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._store_timeline()
|
self._store_timeline()
|
||||||
self._store_info()
|
self._store_info()
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ HELP_MSG_APKS_FROM_FILE = (
|
|||||||
"Instead of acquiring APKs from a phone, load an existing packages.json file for "
|
"Instead of acquiring APKs from a phone, load an existing packages.json file for "
|
||||||
"lookups (mainly for debug purposes)"
|
"lookups (mainly for debug purposes)"
|
||||||
)
|
)
|
||||||
HELP_MSG_CHECK_ADB = "Deprecated: Check an Android device over ADB. Prefer using the external AndroidQF project (https://github.com/mvt-project/androidqf) to acquire AndroidQF images for analysis."
|
HELP_MSG_CHECK_ADB = "Check an Android device over ADB"
|
||||||
HELP_MSG_CHECK_BUGREPORT = "Check an Android Bug Report"
|
HELP_MSG_CHECK_BUGREPORT = "Check an Android Bug Report"
|
||||||
HELP_MSG_CHECK_ANDROID_BACKUP = "Check an Android Backup"
|
HELP_MSG_CHECK_ANDROID_BACKUP = "Check an Android Backup"
|
||||||
HELP_MSG_CHECK_ANDROIDQF = "Check data collected with AndroidQF"
|
HELP_MSG_CHECK_ANDROIDQF = "Check data collected with AndroidQF"
|
||||||
|
|||||||
@@ -768,30 +768,6 @@ class Indicators:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def check_receiver_prefix(self, receiver_name: str) -> Union[dict, None]:
|
|
||||||
"""Check the provided receiver name against the list of indicators.
|
|
||||||
An IoC match is detected when a substring of the receiver matches the indicator
|
|
||||||
:param app_id: App ID to check against the list of indicators
|
|
||||||
:type app_id: str
|
|
||||||
:returns: Indicator details if matched, otherwise None
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not receiver_name:
|
|
||||||
return None
|
|
||||||
|
|
||||||
for ioc in self.get_iocs("app_ids"):
|
|
||||||
if ioc["value"].lower() in receiver_name.lower():
|
|
||||||
self.log.warning(
|
|
||||||
'Found a known suspicious receiver with name "%s" '
|
|
||||||
'matching indicators from "%s"',
|
|
||||||
receiver_name,
|
|
||||||
ioc["name"],
|
|
||||||
)
|
|
||||||
return ioc
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def check_android_property_name(self, property_name: str) -> Optional[dict]:
|
def check_android_property_name(self, property_name: str) -> Optional[dict]:
|
||||||
"""Check the android property name against the list of indicators.
|
"""Check the android property name against the list of indicators.
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
MVT_VERSION = "2.7.0"
|
MVT_VERSION = "2.6.1"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import logging
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.common.command import Command
|
from mvt.common.command import Command
|
||||||
from mvt.common.indicators import Indicators
|
|
||||||
|
|
||||||
from .modules.backup import BACKUP_MODULES
|
from .modules.backup import BACKUP_MODULES
|
||||||
from .modules.mixed import MIXED_MODULES
|
from .modules.mixed import MIXED_MODULES
|
||||||
@@ -21,12 +20,10 @@ class CmdIOSCheckBackup(Command):
|
|||||||
target_path: Optional[str] = None,
|
target_path: Optional[str] = None,
|
||||||
results_path: Optional[str] = None,
|
results_path: Optional[str] = None,
|
||||||
ioc_files: Optional[list] = None,
|
ioc_files: Optional[list] = None,
|
||||||
iocs: Optional[Indicators] = None,
|
|
||||||
module_name: Optional[str] = None,
|
module_name: Optional[str] = None,
|
||||||
serial: Optional[str] = None,
|
serial: Optional[str] = None,
|
||||||
module_options: Optional[dict] = None,
|
module_options: Optional[dict] = None,
|
||||||
hashes: bool = False,
|
hashes: bool = False,
|
||||||
sub_command: bool = False,
|
|
||||||
disable_version_check: bool = False,
|
disable_version_check: bool = False,
|
||||||
disable_indicator_check: bool = False,
|
disable_indicator_check: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -34,12 +31,10 @@ class CmdIOSCheckBackup(Command):
|
|||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
results_path=results_path,
|
results_path=results_path,
|
||||||
ioc_files=ioc_files,
|
ioc_files=ioc_files,
|
||||||
iocs=iocs,
|
|
||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
serial=serial,
|
serial=serial,
|
||||||
module_options=module_options,
|
module_options=module_options,
|
||||||
hashes=hashes,
|
hashes=hashes,
|
||||||
sub_command=sub_command,
|
|
||||||
log=log,
|
log=log,
|
||||||
disable_version_check=disable_version_check,
|
disable_version_check=disable_version_check,
|
||||||
disable_indicator_check=disable_indicator_check,
|
disable_indicator_check=disable_indicator_check,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import logging
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.common.command import Command
|
from mvt.common.command import Command
|
||||||
from mvt.common.indicators import Indicators
|
|
||||||
|
|
||||||
from .modules.fs import FS_MODULES
|
from .modules.fs import FS_MODULES
|
||||||
from .modules.mixed import MIXED_MODULES
|
from .modules.mixed import MIXED_MODULES
|
||||||
@@ -21,12 +20,10 @@ class CmdIOSCheckFS(Command):
|
|||||||
target_path: Optional[str] = None,
|
target_path: Optional[str] = None,
|
||||||
results_path: Optional[str] = None,
|
results_path: Optional[str] = None,
|
||||||
ioc_files: Optional[list] = None,
|
ioc_files: Optional[list] = None,
|
||||||
iocs: Optional[Indicators] = None,
|
|
||||||
module_name: Optional[str] = None,
|
module_name: Optional[str] = None,
|
||||||
serial: Optional[str] = None,
|
serial: Optional[str] = None,
|
||||||
module_options: Optional[dict] = None,
|
module_options: Optional[dict] = None,
|
||||||
hashes: bool = False,
|
hashes: bool = False,
|
||||||
sub_command: bool = False,
|
|
||||||
disable_version_check: bool = False,
|
disable_version_check: bool = False,
|
||||||
disable_indicator_check: bool = False,
|
disable_indicator_check: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -34,11 +31,9 @@ class CmdIOSCheckFS(Command):
|
|||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
results_path=results_path,
|
results_path=results_path,
|
||||||
ioc_files=ioc_files,
|
ioc_files=ioc_files,
|
||||||
iocs=iocs,
|
|
||||||
module_name=module_name,
|
module_name=module_name,
|
||||||
module_options=module_options,
|
module_options=module_options,
|
||||||
hashes=hashes,
|
hashes=hashes,
|
||||||
sub_command=sub_command,
|
|
||||||
log=log,
|
log=log,
|
||||||
disable_version_check=disable_version_check,
|
disable_version_check=disable_version_check,
|
||||||
disable_indicator_check=disable_indicator_check,
|
disable_indicator_check=disable_indicator_check,
|
||||||
|
|||||||
@@ -194,41 +194,5 @@
|
|||||||
{
|
{
|
||||||
"identifier": "iPhone16,2",
|
"identifier": "iPhone16,2",
|
||||||
"description": "iPhone 15 Pro Max"
|
"description": "iPhone 15 Pro Max"
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "iPhone17,1",
|
|
||||||
"description": "iPhone 16 Pro"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "iPhone17,2",
|
|
||||||
"description": "iPhone 16 Pro Max"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "iPhone17,3",
|
|
||||||
"description": "iPhone 16"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "iPhone17,4",
|
|
||||||
"description": "iPhone 16 Plus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "iPhone17,5",
|
|
||||||
"description": "iPhone 16e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "iPhone18,1",
|
|
||||||
"description": "iPhone 17 Pro"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "iPhone18,2",
|
|
||||||
"description": "iPhone 17 Pro Max"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "iPhone18,3",
|
|
||||||
"description": "iPhone 17"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "iPhone18,4",
|
|
||||||
"description": "iPhone Air"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -631,10 +631,6 @@
|
|||||||
"build": "16H81",
|
"build": "16H81",
|
||||||
"version": "12.5.7"
|
"version": "12.5.7"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"version": "12.5.8",
|
|
||||||
"build": "16H88"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"build": "17A577",
|
"build": "17A577",
|
||||||
"version": "13.0"
|
"version": "13.0"
|
||||||
@@ -903,10 +899,6 @@
|
|||||||
"version": "15.8.5",
|
"version": "15.8.5",
|
||||||
"build": "19H394"
|
"build": "19H394"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"version": "15.8.6",
|
|
||||||
"build": "19H402"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"build": "20A362",
|
"build": "20A362",
|
||||||
"version": "16.0"
|
"version": "16.0"
|
||||||
@@ -1016,10 +1008,6 @@
|
|||||||
"version": "16.7.12",
|
"version": "16.7.12",
|
||||||
"build": "20H364"
|
"build": "20H364"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"version": "16.7.14",
|
|
||||||
"build": "20H370"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"version": "17.0",
|
"version": "17.0",
|
||||||
"build": "21A327"
|
"build": "21A327"
|
||||||
@@ -1168,18 +1156,6 @@
|
|||||||
"version": "18.7",
|
"version": "18.7",
|
||||||
"build": "22H20"
|
"build": "22H20"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"version": "18.7.2",
|
|
||||||
"build": "22H124"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "18.7.3",
|
|
||||||
"build": "22H217"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "18.7.4",
|
|
||||||
"build": "22H218"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"version": "26",
|
"version": "26",
|
||||||
"build": "23A341"
|
"build": "23A341"
|
||||||
@@ -1187,17 +1163,5 @@
|
|||||||
{
|
{
|
||||||
"version": "26.0.1",
|
"version": "26.0.1",
|
||||||
"build": "23A355"
|
"build": "23A355"
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "26.1",
|
|
||||||
"build": "23B85"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "26.2",
|
|
||||||
"build": "23C55"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "26.2.1",
|
|
||||||
"build": "23C71"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -127,24 +127,6 @@ class WebkitSessionResourceLog(IOSExtraction):
|
|||||||
browsing_stats = file_plist["browsingStatistics"]
|
browsing_stats = file_plist["browsingStatistics"]
|
||||||
|
|
||||||
for item in browsing_stats:
|
for item in browsing_stats:
|
||||||
most_recent_interaction, last_seen = None, None
|
|
||||||
if "mostRecentUserInteraction" in item:
|
|
||||||
try:
|
|
||||||
most_recent_interaction = convert_datetime_to_iso(
|
|
||||||
item["mostRecentUserInteraction"]
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
self.log.error(
|
|
||||||
f'Error converting date of Safari resource"most recent interaction": {item["mostRecentUserInteraction"]}'
|
|
||||||
)
|
|
||||||
if "lastSeen" in item:
|
|
||||||
try:
|
|
||||||
last_seen = convert_datetime_to_iso(item["lastSeen"])
|
|
||||||
except Exception:
|
|
||||||
self.log.error(
|
|
||||||
f'Error converting date of Safari resource"last seen": {item["lastSeen"]}'
|
|
||||||
)
|
|
||||||
|
|
||||||
items.append(
|
items.append(
|
||||||
{
|
{
|
||||||
"origin": item.get("PrevalentResourceOrigin", ""),
|
"origin": item.get("PrevalentResourceOrigin", ""),
|
||||||
@@ -157,8 +139,10 @@ class WebkitSessionResourceLog(IOSExtraction):
|
|||||||
"subresourceUnderTopFrameOrigins", ""
|
"subresourceUnderTopFrameOrigins", ""
|
||||||
),
|
),
|
||||||
"user_interaction": item.get("hadUserInteraction"),
|
"user_interaction": item.get("hadUserInteraction"),
|
||||||
"most_recent_interaction": most_recent_interaction,
|
"most_recent_interaction": convert_datetime_to_iso(
|
||||||
"last_seen": last_seen,
|
item["mostRecentUserInteraction"]
|
||||||
|
),
|
||||||
|
"last_seen": convert_datetime_to_iso(item["lastSeen"]),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class TestBackupModule:
|
|||||||
for root, subdirs, subfiles in os.walk(os.path.abspath(backup_path)):
|
for root, subdirs, subfiles in os.walk(os.path.abspath(backup_path)):
|
||||||
for fname in subfiles:
|
for fname in subfiles:
|
||||||
files.append(os.path.relpath(os.path.join(root, fname), backup_path))
|
files.append(os.path.relpath(os.path.join(root, fname), backup_path))
|
||||||
mod.from_dir(backup_path, files)
|
mod.from_folder(backup_path, files)
|
||||||
run_module(mod)
|
run_module(mod)
|
||||||
assert len(mod.results) == 2
|
assert len(mod.results) == 2
|
||||||
assert len(mod.results[0]["links"]) == 1
|
assert len(mod.results[0]["links"]) == 1
|
||||||
|
|||||||
27
tests/android_androidqf/test_dumpsys_adbstate.py
Normal file
27
tests/android_androidqf/test_dumpsys_adbstate.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_adb import DumpsysADBState
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysADBModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysADBState(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 1
|
||||||
|
assert len(m.detected) == 0
|
||||||
|
|
||||||
|
adb_statedump = m.results[0]
|
||||||
|
assert "user_keys" in adb_statedump
|
||||||
|
assert len(adb_statedump["user_keys"]) == 1
|
||||||
24
tests/android_androidqf/test_dumpsys_battery_daily.py
Normal file
24
tests/android_androidqf/test_dumpsys_battery_daily.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_battery_daily import DumpsysBatteryDaily
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysBatteryDailyModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysBatteryDaily(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 3
|
||||||
|
assert len(m.timeline) == 3
|
||||||
|
assert len(m.detected) == 0
|
||||||
24
tests/android_androidqf/test_dumpsys_battery_history.py
Normal file
24
tests/android_androidqf/test_dumpsys_battery_history.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_battery_history import DumpsysBatteryHistory
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysBatteryHistoryModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysBatteryHistory(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 6
|
||||||
|
assert len(m.timeline) == 0
|
||||||
|
assert len(m.detected) == 0
|
||||||
24
tests/android_androidqf/test_dumpsys_dbinfo.py
Normal file
24
tests/android_androidqf/test_dumpsys_dbinfo.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_dbinfo import DumpsysDBInfo
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysDBInfoModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysDBInfo(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 6
|
||||||
|
assert len(m.timeline) == 0
|
||||||
|
assert len(m.detected) == 0
|
||||||
23
tests/android_androidqf/test_dumpsys_platform_compat.py
Normal file
23
tests/android_androidqf/test_dumpsys_platform_compat.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_platform_compat import DumpsysPlatformCompat
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysPlatformCompatModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysPlatformCompat(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 2
|
||||||
|
assert len(m.detected) == 0
|
||||||
23
tests/android_androidqf/test_dumpsysaccessbility.py
Normal file
23
tests/android_androidqf/test_dumpsysaccessbility.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_accessibility import DumpsysAccessibility
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysAccessibilityModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysAccessibility(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 4
|
||||||
|
assert len(m.detected) == 0
|
||||||
29
tests/android_androidqf/test_dumpsysappops.py
Normal file
29
tests/android_androidqf/test_dumpsysappops.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_appops import DumpsysAppops
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysAppOpsModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysAppops(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 12
|
||||||
|
assert len(m.timeline) == 16
|
||||||
|
|
||||||
|
detected_by_ioc = [
|
||||||
|
detected for detected in m.detected if detected.get("matched_indicator")
|
||||||
|
]
|
||||||
|
assert len(m.detected) == 1
|
||||||
|
assert len(detected_by_ioc) == 0
|
||||||
46
tests/android_androidqf/test_dumpsyspackages.py
Normal file
46
tests/android_androidqf/test_dumpsyspackages.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_packages import DumpsysPackages
|
||||||
|
from mvt.common.indicators import Indicators
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysPackagesModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysPackages(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 2
|
||||||
|
assert len(m.detected) == 0
|
||||||
|
assert len(m.timeline) == 6
|
||||||
|
assert (
|
||||||
|
m.results[0]["package_name"]
|
||||||
|
== "com.samsung.android.provider.filterprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_detection_pkgname(self, indicator_file):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysPackages(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
ind = Indicators(log=logging.getLogger())
|
||||||
|
ind.parse_stix2(indicator_file)
|
||||||
|
ind.ioc_collections[0]["app_ids"].append("com.sec.android.app.DataCreate")
|
||||||
|
m.indicators = ind
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 2
|
||||||
|
assert len(m.detected) == 1
|
||||||
|
assert len(m.timeline) == 6
|
||||||
|
assert m.detected[0]["package_name"] == "com.sec.android.app.DataCreate"
|
||||||
23
tests/android_androidqf/test_dumpsysreceivers.py
Normal file
23
tests/android_androidqf/test_dumpsysreceivers.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2023 The MVT Authors.
|
||||||
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_receivers import DumpsysReceivers
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysReceiversModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysReceivers(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 4
|
||||||
|
assert len(m.detected) == 0
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.aqf_files import AQFFiles
|
from mvt.android.modules.androidqf.files import Files
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_android_androidqf, list_files
|
from ..utils import get_android_androidqf, list_files
|
||||||
@@ -15,10 +15,10 @@ from ..utils import get_android_androidqf, list_files
|
|||||||
class TestAndroidqfFilesAnalysis:
|
class TestAndroidqfFilesAnalysis:
|
||||||
def test_androidqf_files(self):
|
def test_androidqf_files(self):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = AQFFiles(target_path=data_path, log=logging)
|
m = Files(target_path=data_path, log=logging)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 3
|
assert len(m.results) == 3
|
||||||
assert len(m.timeline) == 6
|
assert len(m.timeline) == 6
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import logging
|
|||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.aqf_getprop import AQFGetProp
|
from mvt.android.modules.androidqf.getprop import Getprop
|
||||||
from mvt.common.indicators import Indicators
|
from mvt.common.indicators import Indicators
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
@@ -17,10 +17,10 @@ from ..utils import get_android_androidqf, get_artifact, list_files
|
|||||||
class TestAndroidqfGetpropAnalysis:
|
class TestAndroidqfGetpropAnalysis:
|
||||||
def test_androidqf_getprop(self):
|
def test_androidqf_getprop(self):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = AQFGetProp(target_path=data_path, log=logging)
|
m = Getprop(target_path=data_path, log=logging)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 10
|
assert len(m.results) == 10
|
||||||
assert m.results[0]["name"] == "dalvik.vm.appimageformat"
|
assert m.results[0]["name"] == "dalvik.vm.appimageformat"
|
||||||
@@ -30,9 +30,9 @@ class TestAndroidqfGetpropAnalysis:
|
|||||||
|
|
||||||
def test_getprop_parsing_zip(self):
|
def test_getprop_parsing_zip(self):
|
||||||
fpath = get_artifact("androidqf.zip")
|
fpath = get_artifact("androidqf.zip")
|
||||||
m = AQFGetProp(target_path=fpath, log=logging)
|
m = Getprop(target_path=fpath, log=logging)
|
||||||
archive = zipfile.ZipFile(fpath)
|
archive = zipfile.ZipFile(fpath)
|
||||||
m.from_zip(archive, archive.namelist())
|
m.from_zip_file(archive, archive.namelist())
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 10
|
assert len(m.results) == 10
|
||||||
assert m.results[0]["name"] == "dalvik.vm.appimageformat"
|
assert m.results[0]["name"] == "dalvik.vm.appimageformat"
|
||||||
@@ -42,10 +42,10 @@ class TestAndroidqfGetpropAnalysis:
|
|||||||
|
|
||||||
def test_androidqf_getprop_detection(self, indicator_file):
|
def test_androidqf_getprop_detection(self, indicator_file):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = AQFGetProp(target_path=data_path, log=logging)
|
m = Getprop(target_path=data_path, log=logging)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
ind = Indicators(log=logging.getLogger())
|
ind = Indicators(log=logging.getLogger())
|
||||||
ind.parse_stix2(indicator_file)
|
ind.parse_stix2(indicator_file)
|
||||||
ind.ioc_collections[0]["android_property_names"].append("dalvik.vm.heapmaxfree")
|
ind.ioc_collections[0]["android_property_names"].append("dalvik.vm.heapmaxfree")
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class TestAndroidqfMountsModule:
|
|||||||
m = Mounts(target_path=data_path, log=logging)
|
m = Mounts(target_path=data_path, log=logging)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
|
|
||||||
run_module(m)
|
run_module(m)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.aqf_packages import AQFPackages
|
from mvt.android.modules.androidqf.packages import Packages
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_android_androidqf, list_files
|
from ..utils import get_android_androidqf, list_files
|
||||||
@@ -31,8 +31,8 @@ def file_list(data_path):
|
|||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def module(parent_data_path, file_list):
|
def module(parent_data_path, file_list):
|
||||||
m = AQFPackages(target_path=parent_data_path, log=logging)
|
m = Packages(target_path=parent_data_path, log=logging)
|
||||||
m.from_dir(parent_data_path, file_list)
|
m.from_folder(parent_data_path, file_list)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.aqf_processes import AQFProcesses
|
from mvt.android.modules.androidqf.processes import Processes
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_android_androidqf, list_files
|
from ..utils import get_android_androidqf, list_files
|
||||||
@@ -15,10 +15,10 @@ from ..utils import get_android_androidqf, list_files
|
|||||||
class TestAndroidqfProcessesAnalysis:
|
class TestAndroidqfProcessesAnalysis:
|
||||||
def test_androidqf_processes(self):
|
def test_androidqf_processes(self):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = AQFProcesses(target_path=data_path, log=logging)
|
m = Processes(target_path=data_path, log=logging)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 15
|
assert len(m.results) == 15
|
||||||
assert len(m.timeline) == 0
|
assert len(m.timeline) == 0
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def file_list(data_path):
|
|||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def module(parent_data_path, file_list):
|
def module(parent_data_path, file_list):
|
||||||
m = RootBinaries(target_path=parent_data_path, log=logging)
|
m = RootBinaries(target_path=parent_data_path, log=logging)
|
||||||
m.from_dir(parent_data_path, file_list)
|
m.from_folder(parent_data_path, file_list)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ class TestAndroidqfRootBinaries:
|
|||||||
# Test behavior when no root_binaries.json file is present
|
# Test behavior when no root_binaries.json file is present
|
||||||
empty_file_list = []
|
empty_file_list = []
|
||||||
m = RootBinaries(target_path=parent_data_path, log=logging)
|
m = RootBinaries(target_path=parent_data_path, log=logging)
|
||||||
m.from_dir(parent_data_path, empty_file_list)
|
m.from_folder(parent_data_path, empty_file_list)
|
||||||
|
|
||||||
run_module(m)
|
run_module(m)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.androidqf.aqf_settings import AQFSettings
|
from mvt.android.modules.androidqf.settings import Settings
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
from ..utils import get_android_androidqf, list_files
|
from ..utils import get_android_androidqf, list_files
|
||||||
@@ -14,10 +14,10 @@ from ..utils import get_android_androidqf, list_files
|
|||||||
class TestSettingsModule:
|
class TestSettingsModule:
|
||||||
def test_parsing(self):
|
def test_parsing(self):
|
||||||
data_path = get_android_androidqf()
|
data_path = get_android_androidqf()
|
||||||
m = AQFSettings(target_path=data_path)
|
m = Settings(target_path=data_path)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 1
|
assert len(m.results) == 1
|
||||||
assert "random" in m.results.keys()
|
assert "random" in m.results.keys()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class TestAndroidqfSMSAnalysis:
|
|||||||
m = SMS(target_path=data_path, log=logging)
|
m = SMS(target_path=data_path, log=logging)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 2
|
assert len(m.results) == 2
|
||||||
assert len(m.timeline) == 0
|
assert len(m.timeline) == 0
|
||||||
@@ -36,7 +36,7 @@ class TestAndroidqfSMSAnalysis:
|
|||||||
)
|
)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 1
|
assert len(m.results) == 1
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class TestAndroidqfSMSAnalysis:
|
|||||||
)
|
)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert prompt_mock.call_count == 1
|
assert prompt_mock.call_count == 1
|
||||||
assert len(m.results) == 1
|
assert len(m.results) == 1
|
||||||
@@ -67,7 +67,7 @@ class TestAndroidqfSMSAnalysis:
|
|||||||
)
|
)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 0
|
assert len(m.results) == 0
|
||||||
assert "Invalid backup password" in caplog.text
|
assert "Invalid backup password" in caplog.text
|
||||||
@@ -82,7 +82,7 @@ class TestAndroidqfSMSAnalysis:
|
|||||||
)
|
)
|
||||||
files = list_files(data_path)
|
files = list_files(data_path)
|
||||||
parent_path = Path(data_path).absolute().parent.as_posix()
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
m.from_dir(parent_path, files)
|
m.from_folder(parent_path, files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 0
|
assert len(m.results) == 0
|
||||||
assert (
|
assert (
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from mvt.android.modules.bugreport.dumpsys_appops import DumpsysAppops
|
from mvt.android.modules.bugreport.appops import Appops
|
||||||
from mvt.android.modules.bugreport.dumpsys_getprop import DumpsysGetProp
|
from mvt.android.modules.bugreport.getprop import Getprop
|
||||||
from mvt.android.modules.bugreport.dumpsys_packages import DumpsysPackages
|
from mvt.android.modules.bugreport.packages import Packages
|
||||||
from mvt.android.modules.bugreport.tombstones import Tombstones
|
from mvt.android.modules.bugreport.tombstones import Tombstones
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
@@ -26,12 +26,12 @@ class TestBugreportAnalysis:
|
|||||||
folder_files.append(
|
folder_files.append(
|
||||||
os.path.relpath(os.path.join(root, file_name), parent_path)
|
os.path.relpath(os.path.join(root, file_name), parent_path)
|
||||||
)
|
)
|
||||||
m.from_dir(fpath, folder_files)
|
m.from_folder(fpath, folder_files)
|
||||||
run_module(m)
|
run_module(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def test_appops_module(self):
|
def test_appops_module(self):
|
||||||
m = self.launch_bug_report_module(DumpsysAppops)
|
m = self.launch_bug_report_module(Appops)
|
||||||
assert len(m.results) == 12
|
assert len(m.results) == 12
|
||||||
assert len(m.timeline) == 16
|
assert len(m.timeline) == 16
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class TestBugreportAnalysis:
|
|||||||
assert len(detected_by_ioc) == 0
|
assert len(detected_by_ioc) == 0
|
||||||
|
|
||||||
def test_packages_module(self):
|
def test_packages_module(self):
|
||||||
m = self.launch_bug_report_module(DumpsysPackages)
|
m = self.launch_bug_report_module(Packages)
|
||||||
assert len(m.results) == 2
|
assert len(m.results) == 2
|
||||||
assert (
|
assert (
|
||||||
m.results[0]["package_name"]
|
m.results[0]["package_name"]
|
||||||
@@ -53,7 +53,7 @@ class TestBugreportAnalysis:
|
|||||||
assert len(m.results[1]["permissions"]) == 32
|
assert len(m.results[1]["permissions"]) == 32
|
||||||
|
|
||||||
def test_getprop_module(self):
|
def test_getprop_module(self):
|
||||||
m = self.launch_bug_report_module(DumpsysGetProp)
|
m = self.launch_bug_report_module(Getprop)
|
||||||
assert len(m.results) == 0
|
assert len(m.results) == 0
|
||||||
|
|
||||||
def test_tombstones_modules(self):
|
def test_tombstones_modules(self):
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ class TestCheckAndroidqfCommand:
|
|||||||
path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
|
path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
|
||||||
result = runner.invoke(check_androidqf, [path])
|
result = runner.invoke(check_androidqf, [path])
|
||||||
|
|
||||||
# Called twice, once in AnroidQF SMS module and once in Backup SMS module
|
assert prompt_mock.call_count == 1
|
||||||
assert prompt_mock.call_count == 2
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
|
|
||||||
def test_check_encrypted_backup_cli(self, mocker):
|
def test_check_encrypted_backup_cli(self, mocker):
|
||||||
|
|||||||
Reference in New Issue
Block a user