1
mirror of https://github.com/mvt-project/mvt synced 2025-10-21 22:42:15 +02:00

Compare commits

...

112 Commits

Author SHA1 Message Date
Nex
1c912f68fe Bumped version 2022-01-28 22:25:41 +01:00
Nex
10a640d3f7 Temporary disabing VirusTotal lookup because of API issues 2022-01-28 22:25:21 +01:00
Nex
c3acc95e9e Bumped version 2022-01-28 20:08:14 +01:00
Nex
90d05336da Added check for additional outgoing call event 2022-01-28 17:21:28 +01:00
Nex
5513e6e9e3 Ordered imports 2022-01-28 16:36:24 +01:00
Nex
38116f8405 Catching device not found exception 2022-01-28 15:47:50 +01:00
Nex
59b069f006 Added lookups for non-system packages on check-adb too 2022-01-28 12:25:50 +01:00
Nex
28e1348aa7 Added check-iocs command to mvt-android 2022-01-27 18:23:19 +01:00
Nex
034338d1f4 Added iOS 15.3 2022-01-27 17:04:48 +01:00
Nex
09d5eabf2f Changing check logic for Android settings 2022-01-27 15:24:17 +01:00
Nex
a425d6c511 Added missing comma and ordered imports 2022-01-27 14:56:02 +01:00
Nex
f8897a4f8c Added more dangerous settings 2022-01-27 14:54:31 +01:00
Nex
86eae68bdb Added Android settings module 2022-01-27 13:33:06 +01:00
Nex
d2bf348b03 Merge branch 'main' of github.com:mvt-project/mvt 2022-01-27 12:51:14 +01:00
Nex
25c6c03075 Added Getprop module and cleaned Files and Packages Android modules 2022-01-27 12:50:37 +01:00
tek
cf88740f6a Fixes bugs in SafariBrowserState module and add tests 2022-01-26 14:50:34 +01:00
tek
eb4810b0ad Fixes bug in parsing of configuration profiles 2022-01-25 20:32:27 +01:00
Nex
cce9159eda Adding indicator to matched results 2022-01-23 15:01:49 +01:00
Nex
e1211991aa Bumped version 2022-01-23 14:17:43 +01:00
Nex
8ae9ca328c Added log line at the end to highlight number of detections 2022-01-21 16:50:32 +01:00
Nex
0e2eb51732 Fixed checking of indicators in filesystem module 2022-01-21 16:30:34 +01:00
Nex
b35cd4bc73 Added support for context-aware indicators.
This way when a detection is logged, the user can know which STIX2
file was matched by the module
2022-01-21 16:26:58 +01:00
Nex
1b4f99a31d Trying to catch missing argument error (ref: #211) 2022-01-21 12:20:22 +01:00
tek
e4e1716729 Bumped version 2022-01-20 15:28:42 +01:00
tek
083bc12351 Merge branch 'feature/check-file-path' 2022-01-20 15:19:37 +01:00
tek
cf6d392460 Adds more details on the download-iocs command 2022-01-20 13:29:50 +01:00
tek
95205d8e17 Adds indicators check to iOS TCC module 2022-01-18 17:12:20 +01:00
Nex
1460828c30 Uniforming style in test units 2022-01-18 16:33:13 +01:00
Nex
fa84b3f296 Revert "Testing with slightly older version of iOSbackup"
This reverts commit e1efaa5467.
2022-01-18 16:32:22 +01:00
Nex
e1efaa5467 Testing with slightly older version of iOSbackup 2022-01-18 16:27:14 +01:00
Nex
696d42fc6e Disabling tests for 3.7 due to iOSbackup requirements of >= 3.8 2022-01-18 16:22:29 +01:00
Nex
a0e1662726 Somehow mysteriously with >= pip doesn't find the version, with == does 2022-01-18 16:16:03 +01:00
Nex
51645bdbc0 Adding pip install for deps 2022-01-18 16:10:59 +01:00
Nex
bb1b108fd7 Cleaning build workflow 2022-01-18 16:09:01 +01:00
Nex
92f9dcb8a5 Tring to fix build 2022-01-18 16:08:14 +01:00
Nex
a6fd5fe1f3 Bumped version 2022-01-18 16:06:14 +01:00
Nex
3e0ef20fcd . 2022-01-18 16:05:01 +01:00
Nex
01f3acde2e Merge branch 'main' of github.com:mvt-project/mvt 2022-01-18 16:00:52 +01:00
Nex
b697874f56 Conforming the test files 2022-01-18 16:00:03 +01:00
Donncha Ó Cearbhaill
41d699f457 Add PyTest to Github actions 2022-01-18 15:59:16 +01:00
Donncha Ó Cearbhaill
6fcd40f6b6 Fix use of global list instance as self.results variable 2022-01-18 15:53:05 +01:00
tek
38bb583a9e Improves management of file path indicators 2022-01-18 15:50:31 +01:00
Donncha Ó Cearbhaill
48ec2d8fa8 Merge branch 'main' into tests 2022-01-18 15:30:40 +01:00
tek
798805c583 Improves Shortcut output 2022-01-18 13:06:35 +01:00
Nex
24be9e9570 Use default list of indicators files now that some default ones are automatically loaded 2022-01-14 16:26:14 +01:00
Nex
adbd95c559 Dots 2022-01-14 02:01:59 +01:00
Nex
8a707c288a Bumped version 2022-01-14 01:53:10 +01:00
Nex
4c906ad52e Renamed download iocs function 2022-01-14 01:52:57 +01:00
Nex
a2f8030cce Added new iOS versions 2022-01-14 01:41:48 +01:00
Nex
737007afdb Bumped version 2022-01-12 16:18:13 +01:00
Nex
33efeda90a Added TODO note 2022-01-12 16:10:15 +01:00
Nex
146f2ae57d Renaming check function for consistency 2022-01-12 16:02:13 +01:00
Nex
11bc916854 Sorted imports 2022-01-11 16:02:44 +01:00
Nex
3084876f31 Removing unused imports, fixing conditions, new lines 2022-01-11 16:02:01 +01:00
Nex
f63cb585b2 Shortened command to download-iocs 2022-01-11 15:59:01 +01:00
Nex
637aebcd89 Small cleanup 2022-01-11 15:53:10 +01:00
Nex
16a0de3af4 Added new module to highlight installed accessibility services 2022-01-11 15:16:26 +01:00
tek
15fbedccc9 Fixes a minor bug in WebkitResourceLoadStatistics 2022-01-10 18:09:31 +01:00
tek
e0514b20dd Catches exception in Shortcuts module if the table does not exist 2022-01-10 16:58:12 +01:00
Donncha Ó Cearbhaill
b2e9f0361b Fix repeated results due to global results[] variable 2022-01-07 18:24:24 +01:00
Donncha Ó Cearbhaill
e85c70c603 Generate stix2 for each test run 2022-01-07 17:51:21 +01:00
Donncha Ó Cearbhaill
3f8dade610 Move backup binary artifact to seperate folder 2022-01-07 17:08:46 +01:00
Donncha Ó Cearbhaill
54963b0b59 Update test PR to work with latest code, fix flake8 2022-01-07 17:03:53 +01:00
tek
513e2cc704 First test structure 2022-01-07 16:41:19 +01:00
tek
28d57e7178 Add command to download latest public indicators
Squashed commit of the following:

commit c0d9e8d5d188c13e7e5ec0612e99bfb7e25f47d4
Author: Donncha Ó Cearbhaill <donncha.ocearbhaill@amnesty.org>
Date:   Fri Jan 7 16:05:12 2022 +0100

    Update name of indicators JSON file

commit f719e49c5f942cef64931ecf422b6a6e7b8c9f17
Author: Donncha Ó Cearbhaill <donncha.ocearbhaill@amnesty.org>
Date:   Fri Jan 7 15:38:03 2022 +0100

    Do not set indicators option on module if no indicators were loaded

commit a289eb8de936f7d74c6c787cbb8daf5c5bec015c
Author: Donncha Ó Cearbhaill <donncha.ocearbhaill@amnesty.org>
Date:   Fri Jan 7 14:43:00 2022 +0100

    Simplify code for loading IoCs

commit 0804563415ee80d76c13d3b38ffe639fa14caa14
Author: Donncha Ó Cearbhaill <donncha.ocearbhaill@amnesty.org>
Date:   Fri Jan 7 13:43:47 2022 +0100

    Add metadata to IoC entries

commit 97d0e893c1a0736c4931363ff40f09a030b90cf6
Author: tek <tek@randhome.io>
Date:   Fri Dec 17 16:43:09 2021 +0100

    Implements automated loading of indicators

commit c381e14df92ae4d7d846a1c97bcf6639cc526082
Author: tek <tek@randhome.io>
Date:   Fri Dec 17 12:41:15 2021 +0100

    Improves download-indicators

commit b938e02ddfd0b916fd883f510b467491a4a84e5f
Author: tek <tek@randhome.io>
Date:   Fri Dec 17 01:44:26 2021 +0100

    Adds download-indicators for mvt-ios and mvt-android
2022-01-07 16:38:04 +01:00
Nex
dc8eeb618e Merge pull request #229 from NicolaiSoeborg/patch-1
Bump adb read timeout
2021-12-31 11:59:40 +01:00
Nicolai Søborg
c282d4341d Bump adb read timeout
Some adb commands (like `dumpsys`) are very slow and the default timeout is "only" 10s. 
A timeout of 200 seconds is chosen completely at random - works on my phone 🤷

Fixes https://github.com/mvt-project/mvt/issues/113
Fixes https://github.com/mvt-project/mvt/issues/228
2021-12-28 13:56:04 +01:00
tek
681bae2f66 Bump version to v1.4.1 2021-12-27 16:19:25 +01:00
tek
b079246c8a Fixes links to STIX files in the documentation 2021-12-22 16:18:28 +01:00
tek
82b57f1997 Fixes IOC issue in android CLI 2021-12-22 00:19:16 +01:00
Donncha Ó Cearbhaill
8f88f872df Bump to 1.4.0 to skip previously used PyPi versions 2021-12-17 12:52:06 +01:00
Donncha Ó Cearbhaill
2d16218489 Bump version to v1.3.2 2021-12-17 12:24:41 +01:00
Donncha Ó Cearbhaill
3215e797ec Bug fixes for config profile and shortcut module 2021-12-16 22:58:36 +01:00
Donncha Ó Cearbhaill
e65a598903 Add link to Cytrox indicators of compromise in docs 2021-12-16 21:01:56 +01:00
Donncha Ó Cearbhaill
e80c02451c Bump version to 1.3.1. Skipping 1.3 as a tag already exists 2021-12-16 19:27:58 +01:00
Donncha Ó Cearbhaill
5df50f864c Merge branch 'main' into main 2021-12-16 19:21:18 +01:00
Donncha Ó Cearbhaill
45b31bb718 Add support for indentifying known malicious file paths over ADB 2021-12-16 19:16:24 +01:00
Donncha Ó Cearbhaill
e10f1767e6 Update WhatsApp module to search for links in attachments 2021-12-16 18:46:31 +01:00
tek
d64277c0bf Adds missing iOS version 2021-12-16 18:39:22 +01:00
Donncha Ó Cearbhaill
3f3261511a Add module to search for known malicious or suspicious configuration profiles 2021-12-16 17:57:26 +01:00
Donncha Ó Cearbhaill
4cfe75e2d4 Add module to parse iOS Shortcuts and search for malicious actions 2021-12-16 17:47:08 +01:00
tek
cdd90332f7 Adds timeline support to TCC iOS module 2021-12-16 13:57:44 +01:00
tek
d9b29b3739 Fixes indicator issue in the android cli 2021-12-16 12:51:57 +01:00
tek
79bb7d1d4b Fixes indiator parsing bug 2021-12-13 18:37:05 +01:00
tek
a653cb3cfc Implements loading STIX files from env variable MVT_STIX2 2021-12-10 16:11:59 +01:00
tek
b25cc48be0 Fixes issue in Safari Browser State for older iOS versions 2021-12-06 15:04:52 +01:00
tek
40bd9ddc1d Fixes issue with different TCC database versions 2021-12-03 20:31:12 +01:00
Tek
deb95297da Merge pull request #219 from workingreact/main
Fix ConfigurationProfiles
2021-12-03 19:56:43 +01:00
tek
02014b414b Add warning for apple notification 2021-12-03 19:42:35 +01:00
tek
7dd5fe7831 Catch and recover malformed SMS database 2021-12-03 17:46:41 +01:00
workingreact
11d1a3dcee fix typo 2021-12-02 18:31:07 +01:00
workingreact
74f9db2bf2 fix ConfigurationProfiles 2021-12-02 16:55:14 +01:00
tek
356bddc3af Adds new iOS versions 2021-11-28 17:43:50 +01:00
Nex
512f40dcb4 Standardized code with flake8 2021-11-19 15:27:51 +01:00
Nex
b3a464ba58 Removed unused imports 2021-11-19 14:54:53 +01:00
Nex
529df85f0f Sorted imports 2021-11-04 12:58:35 +01:00
Nex
19a6da8fe7 Merge pull request #213 from panelmix/main
Replace NetworkingAnalytics with Analytics
2021-11-02 15:02:57 +01:00
panelmix
34c997f923 Replace NetworkingAnalytics with Analytics 2021-11-02 13:29:12 +01:00
Nex
02bf903411 Bumped version 2021-10-30 13:40:25 +02:00
Nex
7019375767 Merge pull request #210 from hurtcrushing/main
Search for entries in ZPROCESS but not in ZLIVEUSAGE
2021-10-27 14:22:40 +02:00
Nex
34dd27c5d2 Added iPhone 13 2021-10-26 18:33:07 +02:00
Nex
a4d6a08a8b Added iOS 15.1 2021-10-26 18:09:31 +02:00
hurtcrushing
635d3a392d change warning to info 2021-10-25 14:54:03 +02:00
hurtcrushing
2d78bddbba Search for entries in ZPROCESS but not in ZLIVEUSAGE 2021-10-25 14:34:18 +02:00
Nex
c1938d2ead Merge branch 'main' of github.com:mvt-project/mvt 2021-10-25 11:18:12 +02:00
Nex
104b01e5cd Fixed links to docs 2021-10-25 09:19:10 +02:00
Nex
7087e8adb2 Merge pull request #209 from mvt-project/dependabot/pip/docs/mkdocs-1.2.3
Bump mkdocs from 1.2.1 to 1.2.3 in /docs
2021-10-23 20:17:18 +02:00
dependabot[bot]
67608ac02b Bump mkdocs from 1.2.1 to 1.2.3 in /docs
Bumps [mkdocs](https://github.com/mkdocs/mkdocs) from 1.2.1 to 1.2.3.
- [Release notes](https://github.com/mkdocs/mkdocs/releases)
- [Commits](https://github.com/mkdocs/mkdocs/compare/1.2.1...1.2.3)

---
updated-dependencies:
- dependency-name: mkdocs
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-23 11:56:25 +00:00
Nex
6d8de5b461 Bumped version 2021-10-23 13:51:44 +02:00
Nex
b0177d6104 Upgraded adb-shell 2021-10-23 13:51:33 +02:00
tek
e0c9a44b10 Merge branch 'main' of github.com:mvt-project/mvt 2021-10-21 21:17:31 +02:00
tek
ef8c1ae895 Adds recent iOS versions 2021-10-21 21:17:09 +02:00
95 changed files with 1950 additions and 547 deletions

View File

@@ -16,7 +16,8 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: [3.7, 3.8, 3.9] # python-version: [3.7, 3.8, 3.9]
python-version: [3.8, 3.9]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -27,8 +28,9 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
python -m pip install flake8 pytest safety python -m pip install flake8 pytest safety stix2
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
python -m pip install .
- name: Lint with flake8 - name: Lint with flake8
run: | run: |
# stop the build if there are Python syntax errors or undefined names # stop the build if there are Python syntax errors or undefined names
@@ -37,7 +39,5 @@ jobs:
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Safety checks - name: Safety checks
run: safety check run: safety check
- name: Test with pytest
# - name: Test with pytest run: pytest
# run: |
# pytest

View File

@@ -15,15 +15,15 @@ It has been developed and released by the [Amnesty International Security Lab](h
## Installation ## Installation
MVT can be installed from sources or from [PyPi](https://pypi.org/project/mvt/) (you will need some dependencies, check the [documentation](https://docs.mvt.re/en/latest/install.html)): MVT can be installed from sources or from [PyPi](https://pypi.org/project/mvt/) (you will need some dependencies, check the [documentation](https://docs.mvt.re/en/latest/install/)):
``` ```
pip3 install mvt pip3 install mvt
``` ```
Alternatively, you can decide to run MVT and all relevant tools through a [Docker container](https://docs.mvt.re/en/latest/docker.html). Alternatively, you can decide to run MVT and all relevant tools through a [Docker container](https://docs.mvt.re/en/latest/docker/).
**Please note:** MVT is best run on Linux or Mac systems. [It does not currently support running natively on Windows.](https://docs.mvt.re/en/latest/install.html#mvt-on-windows) **Please note:** MVT is best run on Linux or Mac systems. [It does not currently support running natively on Windows.](https://docs.mvt.re/en/latest/install/#mvt-on-windows)
## Usage ## Usage
@@ -31,4 +31,4 @@ MVT provides two commands `mvt-ios` and `mvt-android`. [Check out the documentat
## License ## License
The purpose of MVT is to facilitate the ***consensual forensic analysis*** of devices of those who might be targets of sophisticated mobile spyware attacks, especially members of civil society and marginalized communities. We do not want MVT to enable privacy violations of non-consenting individuals. In order to achieve this, MVT is released under its own license. [Read more here.](https://docs.mvt.re/en/latest/license.html) The purpose of MVT is to facilitate the ***consensual forensic analysis*** of devices of those who might be targets of sophisticated mobile spyware attacks, especially members of civil society and marginalized communities. We do not want MVT to enable privacy violations of non-consenting individuals. In order to achieve this, MVT is released under its own license. [Read more here.](https://docs.mvt.re/en/latest/license/)

View File

@@ -28,10 +28,19 @@ The `--iocs` option can be invoked multiple times to let MVT import multiple STI
mvt-ios check-backup --iocs ~/iocs/malware1.stix --iocs ~/iocs/malware2.stix2 /path/to/backup mvt-ios check-backup --iocs ~/iocs/malware1.stix --iocs ~/iocs/malware2.stix2 /path/to/backup
``` ```
It is also possible to load STIX2 files automatically from the environment variable `MVT_STIX2`:
```bash
export MVT_STIX2="/home/user/IOC1.stix2:/home/user/IOC2.stix2"
```
## Known repositories of STIX2 IOCs ## Known repositories of STIX2 IOCs
- The [Amnesty International investigations repository](https://github.com/AmnestyTech/investigations) contains STIX-formatted IOCs for: - The [Amnesty International investigations repository](https://github.com/AmnestyTech/investigations) contains STIX-formatted IOCs for:
- [Pegasus](https://en.wikipedia.org/wiki/Pegasus_(spyware)) ([STIX2](https://raw.githubusercontent.com/AmnestyTech/investigations/master/2021-07-18_nso/pegasus.stix2)) - [Pegasus](https://en.wikipedia.org/wiki/Pegasus_(spyware)) ([STIX2](https://raw.githubusercontent.com/AmnestyTech/investigations/master/2021-07-18_nso/pegasus.stix2))
- [This repository](https://github.com/Te-k/stalkerware-indicators) contains IOCs for Android stalkerware including [a STIX MVT-compatible file](https://github.com/Te-k/stalkerware-indicators/blob/master/stalkerware.stix2). - [Predator from Cytrox](https://citizenlab.ca/2021/12/pegasus-vs-predator-dissidents-doubly-infected-iphone-reveals-cytrox-mercenary-spyware/) ([STIX2](https://raw.githubusercontent.com/AmnestyTech/investigations/master/2021-12-16_cytrox/cytrox.stix2))
- [This repository](https://github.com/Te-k/stalkerware-indicators) contains IOCs for Android stalkerware including [a STIX MVT-compatible file](https://raw.githubusercontent.com/Te-k/stalkerware-indicators/master/stalkerware.stix2).
You can automaticallly download the latest public indicator files with the command `mvt-ios download-iocs` or `mvt-android download-iocs`. These commands download the list of indicators listed [here](https://github.com/mvt-project/mvt/blob/main/public_indicators.json) and store them in the [appdir](https://pypi.org/project/appdirs/) folder. They are then loaded automatically by mvt.
Please [open an issue](https://github.com/mvt-project/mvt/issues/) to suggest new sources of STIX-formatted IOCs. Please [open an issue](https://github.com/mvt-project/mvt/issues/) to suggest new sources of STIX-formatted IOCs.

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
mkdocs==1.2.1 mkdocs==1.2.3
mkdocs-autorefs mkdocs-autorefs
mkdocs-material mkdocs-material
mkdocs-material-extensions mkdocs-material-extensions

View File

@@ -9,8 +9,10 @@ import os
import click import click
from rich.logging import RichHandler from rich.logging import RichHandler
from mvt.common.help import * from mvt.common.help import (HELP_MSG_FAST, HELP_MSG_IOC,
from mvt.common.indicators import Indicators, IndicatorsFileBadFormat HELP_MSG_LIST_MODULES, HELP_MSG_MODULE,
HELP_MSG_OUTPUT, HELP_MSG_SERIAL)
from mvt.common.indicators import Indicators, download_indicators_files
from mvt.common.logo import logo from mvt.common.logo import logo
from mvt.common.module import run_module, save_timeline from mvt.common.module import run_module, save_timeline
@@ -26,6 +28,7 @@ logging.basicConfig(level="INFO", format=LOG_FORMAT, handlers=[
RichHandler(show_path=False, log_time_format="%X")]) RichHandler(show_path=False, log_time_format="%X")])
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
#============================================================================== #==============================================================================
# Main # Main
#============================================================================== #==============================================================================
@@ -104,10 +107,11 @@ def download_apks(ctx, all_apks, virustotal, koodous, all_checks, output, from_f
default=[], help=HELP_MSG_IOC) default=[], help=HELP_MSG_IOC)
@click.option("--output", "-o", type=click.Path(exists=False), @click.option("--output", "-o", type=click.Path(exists=False),
help=HELP_MSG_OUTPUT) help=HELP_MSG_OUTPUT)
@click.option("--fast", "-f", is_flag=True, help=HELP_MSG_FAST)
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES) @click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
@click.option("--module", "-m", help=HELP_MSG_MODULE) @click.option("--module", "-m", help=HELP_MSG_MODULE)
@click.pass_context @click.pass_context
def check_adb(ctx, iocs, output, list_modules, module, serial): def check_adb(ctx, iocs, output, fast, list_modules, module, serial):
if list_modules: if list_modules:
log.info("Following is the list of available check-adb modules:") log.info("Following is the list of available check-adb modules:")
for adb_module in ADB_MODULES: for adb_module in ADB_MODULES:
@@ -125,13 +129,7 @@ def check_adb(ctx, iocs, output, list_modules, module, serial):
ctx.exit(1) ctx.exit(1)
indicators = Indicators(log=log) indicators = Indicators(log=log)
for ioc_path in iocs: indicators.load_indicators_files(iocs)
try:
indicators.parse_stix2(ioc_path)
except IndicatorsFileBadFormat as e:
log.critical(e)
ctx.exit(1)
log.info("Loaded a total of %d indicators", indicators.ioc_count)
timeline = [] timeline = []
timeline_detected = [] timeline_detected = []
@@ -139,14 +137,14 @@ def check_adb(ctx, iocs, output, list_modules, module, serial):
if module and adb_module.__name__ != module: if module and adb_module.__name__ != module:
continue continue
m = adb_module(output_folder=output, log=logging.getLogger(adb_module.__module__)) m = adb_module(output_folder=output, fast_mode=fast,
log=logging.getLogger(adb_module.__module__))
if indicators.total_ioc_count:
m.indicators = indicators
m.indicators.log = m.log
if serial: if serial:
m.serial = serial m.serial = serial
if iocs:
indicators.log = m.log
m.indicators = indicators
run_module(m) run_module(m)
timeline.extend(m.timeline) timeline.extend(m.timeline)
timeline_detected.extend(m.timeline_detected) timeline_detected.extend(m.timeline_detected)
@@ -179,31 +177,97 @@ def check_backup(ctx, iocs, output, backup_path, serial):
ctx.exit(1) ctx.exit(1)
indicators = Indicators(log=log) indicators = Indicators(log=log)
for ioc_path in iocs: indicators.load_indicators_files(iocs)
try:
indicators.parse_stix2(ioc_path)
except IndicatorsFileBadFormat as e:
log.critical(e)
ctx.exit(1)
log.info("Loaded a total of %d indicators", indicators.ioc_count)
if os.path.isfile(backup_path): if os.path.isfile(backup_path):
log.critical("The path you specified is a not a folder!") log.critical("The path you specified is a not a folder!")
if os.path.basename(backup_path) == "backup.ab": if os.path.basename(backup_path) == "backup.ab":
log.info("You can use ABE (https://github.com/nelenkov/android-backup-extractor) " \ log.info("You can use ABE (https://github.com/nelenkov/android-backup-extractor) "
"to extract 'backup.ab' files!") "to extract 'backup.ab' files!")
ctx.exit(1) ctx.exit(1)
for module in BACKUP_MODULES: for module in BACKUP_MODULES:
m = module(base_folder=backup_path, output_folder=output, m = module(base_folder=backup_path, output_folder=output,
log=logging.getLogger(module.__module__)) log=logging.getLogger(module.__module__))
if indicators.total_ioc_count:
m.indicators = indicators
m.indicators.log = m.log
if serial: if serial:
m.serial = serial m.serial = serial
if iocs:
indicators.log = m.log
m.indicators = indicators
run_module(m) run_module(m)
#==============================================================================
# Command: check-iocs
#==============================================================================
@cli.command("check-iocs", help="Compare stored JSON results to provided indicators")
@click.option("--iocs", "-i", type=click.Path(exists=True), multiple=True,
default=[], help=HELP_MSG_IOC)
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
@click.option("--module", "-m", help=HELP_MSG_MODULE)
@click.argument("FOLDER", type=click.Path(exists=True))
@click.pass_context
def check_iocs(ctx, iocs, list_modules, module, folder):
all_modules = []
for entry in BACKUP_MODULES + ADB_MODULES:
if entry not in all_modules:
all_modules.append(entry)
if list_modules:
log.info("Following is the list of available check-iocs modules:")
for iocs_module in all_modules:
log.info(" - %s", iocs_module.__name__)
return
log.info("Checking stored results against provided indicators...")
indicators = Indicators(log=log)
indicators.load_indicators_files(iocs)
total_detections = 0
for file_name in os.listdir(folder):
name_only, ext = os.path.splitext(file_name)
file_path = os.path.join(folder, file_name)
# TODO: Skipping processing of result files that are not json.
# We might want to revisit this eventually.
if ext != ".json":
continue
for iocs_module in all_modules:
if module and iocs_module.__name__ != module:
continue
if iocs_module().get_slug() != name_only:
continue
log.info("Loading results from \"%s\" with module %s", file_name,
iocs_module.__name__)
m = iocs_module.from_json(file_path,
log=logging.getLogger(iocs_module.__module__))
if indicators.total_ioc_count > 0:
m.indicators = indicators
m.indicators.log = m.log
try:
m.check_indicators()
except NotImplementedError:
continue
else:
total_detections += len(m.detected)
if total_detections > 0:
log.warning("The check of the results produced %d detections!",
total_detections)
#==============================================================================
# Command: download-iocs
#==============================================================================
@cli.command("download-iocs", help="Download public STIX2 indicators")
def download_indicators():
download_indicators_files(log)

View File

@@ -7,7 +7,6 @@ import json
import logging import logging
import os import os
import pkg_resources
from tqdm import tqdm from tqdm import tqdm
from mvt.common.module import InsufficientPrivileges from mvt.common.module import InsufficientPrivileges
@@ -17,6 +16,7 @@ from .modules.adb.packages import Packages
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# TODO: Would be better to replace tqdm with rich.progress to reduce # TODO: Would be better to replace tqdm with rich.progress to reduce
# the number of dependencies. Need to investigate whether # the number of dependencies. Need to investigate whether
# it's possible to have a similar callback system. # it's possible to have a similar callback system.
@@ -138,7 +138,7 @@ class DownloadAPKs(AndroidExtraction):
packages_selection.append(package) packages_selection.append(package)
log.info("Selected only %d packages which are not marked as system", log.info("Selected only %d packages which are not marked as system",
len(packages_selection)) len(packages_selection))
if len(packages_selection) == 0: if len(packages_selection) == 0:
log.info("No packages were selected for download") log.info("No packages were selected for download")

View File

@@ -13,6 +13,7 @@ from rich.text import Text
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def koodous_lookup(packages): def koodous_lookup(packages):
log.info("Looking up all extracted files on Koodous (www.koodous.com)") log.info("Looking up all extracted files on Koodous (www.koodous.com)")
log.info("This might take a while...") log.info("This might take a while...")

View File

@@ -13,6 +13,7 @@ from rich.text import Text
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def get_virustotal_report(hashes): def get_virustotal_report(hashes):
apikey = "233f22e200ca5822bd91103043ccac138b910db79f29af5616a9afe8b6f215ad" apikey = "233f22e200ca5822bd91103043ccac138b910db79f29af5616a9afe8b6f215ad"
url = f"https://www.virustotal.com/partners/sysinternals/file-reports?apikey={apikey}" url = f"https://www.virustotal.com/partners/sysinternals/file-reports?apikey={apikey}"
@@ -36,7 +37,12 @@ def get_virustotal_report(hashes):
log.error("Unexpected response from VirusTotal: %s", res.status_code) log.error("Unexpected response from VirusTotal: %s", res.status_code)
return None return None
def virustotal_lookup(packages): def virustotal_lookup(packages):
# NOTE: This is temporary, until we resolved the issue.
log.error("Unfortunately VirusTotal lookup is disabled until further notice, due to unresolved issues with the API service.")
return
log.info("Looking up all extracted files on VirusTotal (www.virustotal.com)") log.info("Looking up all extracted files on VirusTotal (www.virustotal.com)")
unique_hashes = [] unique_hashes = []
@@ -48,6 +54,7 @@ def virustotal_lookup(packages):
total_unique_hashes = len(unique_hashes) total_unique_hashes = len(unique_hashes)
detections = {} detections = {}
def virustotal_query(batch): def virustotal_query(batch):
report = get_virustotal_report(batch) report = get_virustotal_report(batch)
if not report: if not report:

View File

@@ -4,20 +4,23 @@
# 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_batterystats import DumpsysBatterystats from .dumpsys_batterystats import DumpsysBatterystats
from .dumpsys_full import DumpsysFull from .dumpsys_full import DumpsysFull
from .dumpsys_packages import DumpsysPackages from .dumpsys_packages import DumpsysPackages
from .dumpsys_procstats import DumpsysProcstats from .dumpsys_procstats import DumpsysProcstats
from .dumpsys_receivers import DumpsysReceivers from .dumpsys_receivers import DumpsysReceivers
from .files import Files from .files import Files
from .getprop import Getprop
from .logcat import Logcat from .logcat import Logcat
from .packages import Packages from .packages import Packages
from .processes import Processes from .processes import Processes
from .rootbinaries import RootBinaries from .root_binaries import RootBinaries
from .settings import Settings
from .sms import SMS from .sms import SMS
from .whatsapp import Whatsapp from .whatsapp import Whatsapp
ADB_MODULES = [ChromeHistory, SMS, Whatsapp, Processes, ADB_MODULES = [ChromeHistory, SMS, Whatsapp, Processes, Getprop, Settings,
DumpsysBatterystats, DumpsysProcstats, DumpsysAccessibility, DumpsysBatterystats, DumpsysProcstats,
DumpsysPackages, DumpsysReceivers, DumpsysFull, DumpsysPackages, DumpsysReceivers, DumpsysFull,
Packages, RootBinaries, Logcat, Files] Packages, RootBinaries, Logcat, Files]

View File

@@ -15,7 +15,7 @@ from adb_shell.adb_device import AdbDeviceTcp, AdbDeviceUsb
from adb_shell.auth.keygen import keygen, write_public_keyfile from adb_shell.auth.keygen import keygen, write_public_keyfile
from adb_shell.auth.sign_pythonrsa import PythonRSASigner from adb_shell.auth.sign_pythonrsa import PythonRSASigner
from adb_shell.exceptions import (AdbCommandFailureException, DeviceAuthError, from adb_shell.exceptions import (AdbCommandFailureException, DeviceAuthError,
UsbReadFailedError) UsbDeviceNotFoundError, UsbReadFailedError)
from usb1 import USBErrorAccess, USBErrorBusy from usb1 import USBErrorAccess, USBErrorBusy
from mvt.common.module import InsufficientPrivileges, MVTModule from mvt.common.module import InsufficientPrivileges, MVTModule
@@ -25,6 +25,7 @@ log = logging.getLogger(__name__)
ADB_KEY_PATH = os.path.expanduser("~/.android/adbkey") ADB_KEY_PATH = os.path.expanduser("~/.android/adbkey")
ADB_PUB_KEY_PATH = os.path.expanduser("~/.android/adbkey.pub") ADB_PUB_KEY_PATH = os.path.expanduser("~/.android/adbkey.pub")
class AndroidExtraction(MVTModule): class AndroidExtraction(MVTModule):
"""This class provides a base for all Android extraction modules.""" """This class provides a base for all Android extraction modules."""
@@ -64,7 +65,11 @@ class AndroidExtraction(MVTModule):
# If no serial was specified or if the serial does not seem to be # If no serial was specified or if the serial does not seem to be
# a HOST:PORT definition, we use the USB transport. # a HOST:PORT definition, we use the USB transport.
if not self.serial or ":" not in self.serial: if not self.serial or ":" not in self.serial:
self.device = AdbDeviceUsb(serial=self.serial) try:
self.device = AdbDeviceUsb(serial=self.serial)
except UsbDeviceNotFoundError:
log.critical("No device found. Make sure it is connected and unlocked.")
sys.exit(-1)
# Otherwise we try to use the TCP transport. # Otherwise we try to use the TCP transport.
else: else:
addr = self.serial.split(":") addr = self.serial.split(":")
@@ -89,7 +94,7 @@ class AndroidExtraction(MVTModule):
except OSError as e: except OSError as e:
if e.errno == 113 and self.serial: if e.errno == 113 and self.serial:
log.critical("Unable to connect to the device %s: did you specify the correct IP addres?", log.critical("Unable to connect to the device %s: did you specify the correct IP addres?",
self.serial) self.serial)
sys.exit(-1) sys.exit(-1)
else: else:
break break
@@ -111,7 +116,7 @@ class AndroidExtraction(MVTModule):
:returns: Output of command :returns: Output of command
""" """
return self.device.shell(command) return self.device.shell(command, read_timeout_s=200.0)
def _adb_check_if_root(self): def _adb_check_if_root(self):
"""Check if we have a `su` binary on the Android device. """Check if we have a `su` binary on the Android device.

View File

@@ -16,6 +16,7 @@ log = logging.getLogger(__name__)
CHROME_HISTORY_PATH = "data/data/com.android.chrome/app_chrome/Default/History" CHROME_HISTORY_PATH = "data/data/com.android.chrome/app_chrome/Default/History"
class ChromeHistory(AndroidExtraction): class ChromeHistory(AndroidExtraction):
"""This module extracts records from Android's Chrome browsing history.""" """This module extracts records from Android's Chrome browsing history."""

View File

@@ -0,0 +1,53 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 The MVT Project 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 io
import logging
import os
from .base import AndroidExtraction
log = logging.getLogger(__name__)
class DumpsysAccessibility(AndroidExtraction):
"""This module extracts stats on accessibility."""
def __init__(self, file_path=None, base_folder=None, output_folder=None,
serial=None, fast_mode=False, log=None, results=[]):
super().__init__(file_path=file_path, base_folder=base_folder,
output_folder=output_folder, fast_mode=fast_mode,
log=log, results=results)
def run(self):
self._adb_connect()
stats = self._adb_command("dumpsys accessibility")
in_services = False
for line in stats.split("\n"):
if line.strip().startswith("installed services:"):
in_services = True
continue
if not in_services:
continue
if line.strip() == "}":
break
service = line.split(":")[1].strip()
log.info("Found installed accessibility service \"%s\"", service)
if self.output_folder:
acc_path = os.path.join(self.output_folder,
"dumpsys_accessibility.txt")
with io.open(acc_path, "w", encoding="utf-8") as handle:
handle.write(stats)
log.info("Records from dumpsys accessibility stored at %s",
acc_path)
self._adb_disconnect()

View File

@@ -10,6 +10,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class DumpsysBatterystats(AndroidExtraction): class DumpsysBatterystats(AndroidExtraction):
"""This module extracts stats on battery consumption by processes.""" """This module extracts stats on battery consumption by processes."""
@@ -30,7 +31,7 @@ class DumpsysBatterystats(AndroidExtraction):
handle.write(stats) handle.write(stats)
log.info("Records from dumpsys batterystats stored at %s", log.info("Records from dumpsys batterystats stored at %s",
stats_path) stats_path)
history = self._adb_command("dumpsys batterystats --history") history = self._adb_command("dumpsys batterystats --history")
if self.output_folder: if self.output_folder:

View File

@@ -10,6 +10,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class DumpsysFull(AndroidExtraction): class DumpsysFull(AndroidExtraction):
"""This module extracts stats on battery consumption by processes.""" """This module extracts stats on battery consumption by processes."""
@@ -30,6 +31,6 @@ class DumpsysFull(AndroidExtraction):
handle.write(stats) handle.write(stats)
log.info("Full dumpsys output stored at %s", log.info("Full dumpsys output stored at %s",
stats_path) stats_path)
self._adb_disconnect() self._adb_disconnect()

View File

@@ -10,6 +10,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class DumpsysProcstats(AndroidExtraction): class DumpsysProcstats(AndroidExtraction):
"""This module extracts stats on memory consumption by processes.""" """This module extracts stats on memory consumption by processes."""

View File

@@ -4,16 +4,17 @@
# https://license.mvt.re/1.1/ # https://license.mvt.re/1.1/
import logging import logging
import os
from .base import AndroidExtraction from .base import AndroidExtraction
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
ACTION_NEW_OUTGOING_SMS = "android.provider.Telephony.NEW_OUTGOING_SMS" INTENT_NEW_OUTGOING_SMS = "android.provider.Telephony.NEW_OUTGOING_SMS"
ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED" INTENT_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
ACTION_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED" INTENT_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED"
ACTION_PHONE_STATE = "android.intent.action.PHONE_STATE" INTENT_PHONE_STATE = "android.intent.action.PHONE_STATE"
INTENT_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"
class DumpsysReceivers(AndroidExtraction): class DumpsysReceivers(AndroidExtraction):
"""This module extracts details on receivers for risky activities.""" """This module extracts details on receivers for risky activities."""
@@ -24,6 +25,24 @@ class DumpsysReceivers(AndroidExtraction):
output_folder=output_folder, fast_mode=fast_mode, output_folder=output_folder, fast_mode=fast_mode,
log=log, results=results) log=log, results=results)
def check_indicators(self):
for result in self.results:
if result["activity"] == INTENT_NEW_OUTGOING_SMS:
self.log.info("Found a receiver to intercept outgoing SMS messages: \"%s\"",
result["receiver"])
elif result["activity"] == INTENT_SMS_RECEIVED:
self.log.info("Found a receiver to intercept incoming SMS messages: \"%s\"",
result["receiver"])
elif result["activity"] == INTENT_DATA_SMS_RECEIVED:
self.log.info("Found a receiver to intercept incoming data SMS message: \"%s\"",
result["receiver"])
elif result["activity"] == INTENT_PHONE_STATE:
self.log.info("Found a receiver monitoring telephony state/incoming calls: \"%s\"",
result["receiver"])
elif result["activity"] == INTENT_NEW_OUTGOING_CALL:
self.log.info("Found a receiver monitoring outgoing calls: \"%s\"",
result["receiver"])
def run(self): def run(self):
self._adb_connect() self._adb_connect()
@@ -34,17 +53,20 @@ class DumpsysReceivers(AndroidExtraction):
activity = None activity = None
for line in output.split("\n"): for line in output.split("\n"):
# Find activity block markers. # Find activity block markers.
if line.strip().startswith(ACTION_NEW_OUTGOING_SMS): if line.strip().startswith(INTENT_NEW_OUTGOING_SMS):
activity = ACTION_NEW_OUTGOING_SMS activity = INTENT_NEW_OUTGOING_SMS
continue continue
elif line.strip().startswith(ACTION_SMS_RECEIVED): elif line.strip().startswith(INTENT_SMS_RECEIVED):
activity = ACTION_SMS_RECEIVED activity = INTENT_SMS_RECEIVED
continue continue
elif line.strip().startswith(ACTION_PHONE_STATE): elif line.strip().startswith(INTENT_PHONE_STATE):
activity = ACTION_PHONE_STATE activity = INTENT_PHONE_STATE
continue continue
elif line.strip().startswith(ACTION_DATA_SMS_RECEIVED): elif line.strip().startswith(INTENT_DATA_SMS_RECEIVED):
activity = ACTION_DATA_SMS_RECEIVED activity = INTENT_DATA_SMS_RECEIVED
continue
elif line.strip().startswith(INTENT_NEW_OUTGOING_CALL):
activity = INTENT_NEW_OUTGOING_CALL
continue continue
# If we are not in an activity block yet, skip. # If we are not in an activity block yet, skip.
@@ -65,19 +87,6 @@ class DumpsysReceivers(AndroidExtraction):
if package_name == "com.google.android.gms": if package_name == "com.google.android.gms":
continue continue
if activity == ACTION_NEW_OUTGOING_SMS:
self.log.info("Found a receiver to intercept outgoing SMS messages: \"%s\"",
receiver)
elif activity == ACTION_SMS_RECEIVED:
self.log.info("Found a receiver to intercept incoming SMS messages: \"%s\"",
receiver)
elif activity == ACTION_DATA_SMS_RECEIVED:
self.log.info("Found a receiver to intercept incoming data SMS message: \"%s\"",
receiver)
elif activity == ACTION_PHONE_STATE:
self.log.info("Found a receiver monitoring telephony state: \"%s\"",
receiver)
self.results.append({ self.results.append({
"activity": activity, "activity": activity,
"package_name": package_name, "package_name": package_name,

View File

@@ -3,31 +3,95 @@
# 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/
import datetime
import logging import logging
import os import stat
from mvt.common.utils import convert_timestamp_to_iso
from .base import AndroidExtraction from .base import AndroidExtraction
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Files(AndroidExtraction): class Files(AndroidExtraction):
"""This module extracts the list of installed packages.""" """This module extracts the list of files on the device."""
def __init__(self, file_path=None, base_folder=None, output_folder=None, def __init__(self, file_path=None, base_folder=None, output_folder=None,
serial=None, fast_mode=False, log=None, results=[]): serial=None, fast_mode=False, log=None, results=[]):
super().__init__(file_path=file_path, base_folder=base_folder, super().__init__(file_path=file_path, base_folder=base_folder,
output_folder=output_folder, fast_mode=fast_mode, output_folder=output_folder, fast_mode=fast_mode,
log=log, results=results) log=log, results=results)
self.full_find = False
def find_files(self, file_path):
if self.full_find:
output = self._adb_command(f"find '{file_path}' -printf '%T@ %m %s %u %g %p\n' 2> /dev/null")
for file_line in output.splitlines():
[unix_timestamp, mode, size, owner, group, full_path] = file_line.rstrip().split(" ", 5)
mod_time = convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(int(float(unix_timestamp))))
self.results.append({
"path": full_path,
"modified_time": mod_time,
"mode": mode,
"is_suid": (int(mode, 8) & stat.S_ISUID) == 2048,
"is_sgid": (int(mode, 8) & stat.S_ISGID) == 1024,
"size": size,
"owner": owner,
"group": group,
})
else:
output = self._adb_command(f"find '{file_path}' 2> /dev/null")
for file_line in output.splitlines():
self.results.append({"path": file_line.rstrip()})
def serialize(self, record):
if "modified_time" in record:
return {
"timestamp": record["modified_time"],
"module": self.__class__.__name__,
"event": "file_modified",
"data": record["path"],
}
def check_suspicious(self):
"""Check for files with suspicious permissions"""
for result in sorted(self.results, key=lambda item: item["path"]):
if result.get("is_suid"):
self.log.warning("Found an SUID file in a non-standard directory \"%s\".",
result["path"])
self.detected.append(result)
def check_indicators(self):
"""Check file list for known suspicious files or suspicious properties"""
self.check_suspicious()
if not self.indicators:
return
for result in self.results:
if self.indicators.check_file_path(result["path"]):
self.log.warning("Found a known suspicous file at path: \"%s\"", result["path"])
self.detected.append(result)
def run(self): def run(self):
self._adb_connect() self._adb_connect()
output = self._adb_command("find / -type f 2> /dev/null") output = self._adb_command("find '/' -maxdepth 1 -printf '%T@ %m %s %u %g %p\n' 2> /dev/null")
if output and self.output_folder: if output or output.strip().splitlines():
files_txt_path = os.path.join(self.output_folder, "files.txt") self.full_find = True
with open(files_txt_path, "w") as handle:
handle.write(output)
log.info("List of visible files stored at %s", files_txt_path) for data_path in ["/data/local/tmp/", "/sdcard/", "/tmp/"]:
self.find_files(data_path)
self.log.info("Found %s files in primary Android data directories", len(self.results))
if self.fast_mode:
self.log.info("Flag --fast was enabled: skipping full file listing")
else:
self.log.info("Processing full file listing. This may take a while...")
self.find_files("/")
self.log.info("Found %s total files", len(self.results))
self._adb_disconnect() self._adb_disconnect()

View File

@@ -0,0 +1,46 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 The MVT Project 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
import os
import re
from .base import AndroidExtraction
log = logging.getLogger(__name__)
class Getprop(AndroidExtraction):
"""This module extracts device properties from getprop command."""
def __init__(self, file_path=None, base_folder=None, output_folder=None,
serial=None, fast_mode=False, log=None, results=[]):
super().__init__(file_path=file_path, base_folder=base_folder,
output_folder=output_folder, fast_mode=fast_mode,
log=log, results=results)
self.results = {} if not results else results
def run(self):
self._adb_connect()
rxp = re.compile("\\[(.+?)\\]: \\[(.+?)\\]")
out = self._adb_command("getprop")
for line in out.splitlines():
line = line.strip()
if line == "":
continue
matches = re.findall(rxp, line)
if not matches or len(matches[0]) != 2:
continue
key = matches[0][0]
value = matches[0][1]
self.results[key] = value
self._adb_disconnect()
self.log.info("Extracted %d Android system properties", len(self.results))

View File

@@ -8,10 +8,14 @@ import os
import pkg_resources import pkg_resources
from mvt.android.lookups.koodous import koodous_lookup
from mvt.android.lookups.virustotal import virustotal_lookup
from .base import AndroidExtraction from .base import AndroidExtraction
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Packages(AndroidExtraction): class Packages(AndroidExtraction):
"""This module extracts the list of installed packages.""" """This module extracts the list of installed packages."""
@@ -41,30 +45,29 @@ class Packages(AndroidExtraction):
return records return records
def check_indicators(self): def check_indicators(self):
if not self.indicators:
return
root_packages_path = os.path.join("..", "..", "data", "root_packages.txt") root_packages_path = os.path.join("..", "..", "data", "root_packages.txt")
root_packages_string = pkg_resources.resource_string(__name__, root_packages_path) root_packages_string = pkg_resources.resource_string(__name__, root_packages_path)
root_packages = root_packages_string.decode("utf-8").split("\n") root_packages = root_packages_string.decode("utf-8").split("\n")
root_packages = [rp.strip() for rp in root_packages] root_packages = [rp.strip() for rp in root_packages]
for result in self.results: for result in self.results:
if result["package_name"] in root_packages: if result["package_name"] in root_packages:
self.log.warning("Found an installed package related to rooting/jailbreaking: \"%s\"", self.log.warning("Found an installed package related to rooting/jailbreaking: \"%s\"",
result["package_name"])
self.detected.append(result)
if result["package_name"] in self.indicators.ioc_app_ids:
self.log.warning("Found a malicious package name: \"%s\"",
result["package_name"]) result["package_name"])
self.detected.append(result) self.detected.append(result)
for file in result["files"]: continue
if file["sha256"] in self.indicators.ioc_files_sha256:
self.log.warning("Found a malicious APK: \"%s\" %s", ioc = self.indicators.check_app_id(result.get("package_name"))
result["package_name"], if ioc:
file["sha256"]) result["matched_indicators"] = ioc
self.detected.append(result) self.detected.append(result)
continue
for package_file in result["files"]:
ioc = self.indicators.check_file_hash(package_file["sha256"])
if ioc:
result["matched_indicators"] = ioc
self.detected.append(result)
def _get_files_for_package(self, package_name): def _get_files_for_package(self, package_name):
output = self._adb_command(f"pm path {package_name}") output = self._adb_command(f"pm path {package_name}")
@@ -95,6 +98,9 @@ class Packages(AndroidExtraction):
self._adb_connect() self._adb_connect()
packages = self._adb_command("pm list packages -U -u -i -f") packages = self._adb_command("pm list packages -U -u -i -f")
if packages.strip() == "Error: Unknown option: -U":
packages = self._adb_command("pm list packages -u -i -f")
for line in packages.split("\n"): for line in packages.split("\n"):
line = line.strip() line = line.strip()
if not line.startswith("package:"): if not line.startswith("package:"):
@@ -154,13 +160,19 @@ class Packages(AndroidExtraction):
if result["package_name"] == package_name: if result["package_name"] == package_name:
self.results[i][cmd["field"]] = True self.results[i][cmd["field"]] = True
packages_to_lookup = []
for result in self.results: for result in self.results:
if result["system"]: if result["system"]:
continue continue
packages_to_lookup.append(result)
self.log.info("Found non-system package with name \"%s\" installed by \"%s\" on %s", self.log.info("Found non-system package with name \"%s\" installed by \"%s\" on %s",
result["package_name"], result["installer"], result["timestamp"]) result["package_name"], result["installer"], result["timestamp"])
if not self.fast_mode:
virustotal_lookup(packages_to_lookup)
koodous_lookup(packages_to_lookup)
self.log.info("Extracted at total of %d installed package names", self.log.info("Extracted at total of %d installed package names",
len(self.results)) len(self.results))

View File

@@ -9,6 +9,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Processes(AndroidExtraction): class Processes(AndroidExtraction):
"""This module extracts details on running processes.""" """This module extracts details on running processes."""

View File

@@ -12,6 +12,7 @@ from .base import AndroidExtraction
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class RootBinaries(AndroidExtraction): class RootBinaries(AndroidExtraction):
"""This module extracts the list of installed packages.""" """This module extracts the list of installed packages."""

View File

@@ -0,0 +1,106 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 The MVT Project 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
import os
import re
from .base import AndroidExtraction
log = logging.getLogger(__name__)
ANDROID_DANGEROUS_SETTINGS = [
{
"description": "disabled Google Play Services apps verification",
"key": "verifier_verify_adb_installs",
"safe_value": "1",
},
{
"description": "disabled Google Play Protect",
"key": "package_verifier_enable",
"safe_value": "1",
},
{
"description": "disabled Google Play Protect",
"key": "package_verifier_user_consent",
"safe_value": "1",
},
{
"description": "disabled Google Play Protect",
"key": "upload_apk_enable",
"safe_value": "1",
},
{
"description": "enabled installation of non-market apps",
"key": "install_non_market_apps",
"safe_value": "0",
},
{
"description": "disabled confirmation of adb apps installation",
"key": "adb_install_need_confirm",
"safe_value": "1",
},
{
"description": "disabled sharing of security reports",
"key": "send_security_reports",
"safe_value": "1",
},
{
"description": "disabled sharing of crash logs with manufacturer",
"key": "samsung_errorlog_agree",
"safe_value": "1",
},
{
"description": "disabled applications errors reports",
"key": "send_action_app_error",
"safe_value": "1",
},
]
class Settings(AndroidExtraction):
"""This module extracts Android system settings."""
def __init__(self, file_path=None, base_folder=None, output_folder=None,
serial=None, fast_mode=False, log=None, results=[]):
super().__init__(file_path=file_path, base_folder=base_folder,
output_folder=output_folder, fast_mode=fast_mode,
log=log, results=results)
self.results = {} if not results else results
def check_indicators(self):
for namespace, settings in self.results.items():
for key, value in settings.items():
for danger in ANDROID_DANGEROUS_SETTINGS:
# Check if one of the dangerous settings is using an unsafe
# value (different than the one specified).
if danger["key"] == key and danger["safe_value"] != value:
self.log.warning("Found suspicious setting \"%s = %s\" (%s)",
key, value, danger["description"])
break
def run(self):
self._adb_connect()
for namespace in ["system", "secure", "global"]:
out = self._adb_command(f"cmd settings list {namespace}")
if not out:
continue
self.results[namespace] = {}
for line in out.splitlines():
line = line.strip()
if line == "":
continue
fields = line.split("=", 1)
try:
self.results[namespace][fields[0]] = fields[1]
except IndexError:
continue
self._adb_disconnect()

View File

@@ -15,12 +15,12 @@ log = logging.getLogger(__name__)
SMS_BUGLE_PATH = "data/data/com.google.android.apps.messaging/databases/bugle_db" SMS_BUGLE_PATH = "data/data/com.google.android.apps.messaging/databases/bugle_db"
SMS_BUGLE_QUERY = """ SMS_BUGLE_QUERY = """
SELECT SELECT
ppl.normalized_destination AS number, ppl.normalized_destination AS number,
p.timestamp AS timestamp, p.timestamp AS timestamp,
CASE WHEN m.sender_id IN CASE WHEN m.sender_id IN
(SELECT _id FROM participants WHERE contact_id=-1) (SELECT _id FROM participants WHERE contact_id=-1)
THEN 2 ELSE 1 END incoming, p.text AS text THEN 2 ELSE 1 END incoming, p.text AS text
FROM messages m, conversations c, parts p, FROM messages m, conversations c, parts p,
participants ppl, conversation_participants cp participants ppl, conversation_participants cp
WHERE (m.conversation_id = c._id) WHERE (m.conversation_id = c._id)
@@ -31,14 +31,15 @@ WHERE (m.conversation_id = c._id)
SMS_MMSSMS_PATH = "data/data/com.android.providers.telephony/databases/mmssms.db" SMS_MMSSMS_PATH = "data/data/com.android.providers.telephony/databases/mmssms.db"
SMS_MMSMS_QUERY = """ SMS_MMSMS_QUERY = """
SELECT SELECT
address AS number, address AS number,
date_sent AS timestamp, date_sent AS timestamp,
type as incoming, type as incoming,
body AS text body AS text
FROM sms; FROM sms;
""" """
class SMS(AndroidExtraction): class SMS(AndroidExtraction):
"""This module extracts all SMS messages containing links.""" """This module extracts all SMS messages containing links."""
@@ -62,7 +63,7 @@ class SMS(AndroidExtraction):
return return
for message in self.results: for message in self.results:
if not "text" in message: if "text" not in message:
continue continue
message_links = check_for_links(message["text"]) message_links = check_for_links(message["text"])
@@ -77,7 +78,7 @@ class SMS(AndroidExtraction):
""" """
conn = sqlite3.connect(db_path) conn = sqlite3.connect(db_path)
cur = conn.cursor() cur = conn.cursor()
if (self.SMS_DB_TYPE == 1): if (self.SMS_DB_TYPE == 1):
cur.execute(SMS_BUGLE_QUERY) cur.execute(SMS_BUGLE_QUERY)
elif (self.SMS_DB_TYPE == 2): elif (self.SMS_DB_TYPE == 2):

View File

@@ -16,6 +16,7 @@ log = logging.getLogger(__name__)
WHATSAPP_PATH = "data/data/com.whatsapp/databases/msgstore.db" WHATSAPP_PATH = "data/data/com.whatsapp/databases/msgstore.db"
class Whatsapp(AndroidExtraction): class Whatsapp(AndroidExtraction):
"""This module extracts all WhatsApp messages containing links.""" """This module extracts all WhatsApp messages containing links."""
@@ -39,7 +40,7 @@ class Whatsapp(AndroidExtraction):
return return
for message in self.results: for message in self.results:
if not "data" in message: if "data" not in message:
continue continue
message_links = check_for_links(message["data"]) message_links = check_for_links(message["data"])

View File

@@ -5,4 +5,4 @@
from .sms import SMS from .sms import SMS
BACKUP_MODULES = [SMS,] BACKUP_MODULES = [SMS]

View File

@@ -24,7 +24,7 @@ class SMS(MVTModule):
return return
for message in self.results: for message in self.results:
if not "body" in message: if "body" not in message:
continue continue
message_links = check_for_links(message["body"]) message_links = check_for_links(message["body"])

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ def logo():
try: try:
latest_version = check_for_updates() latest_version = check_for_updates()
except: except Exception:
pass pass
else: else:
if latest_version: if latest_version:

View File

@@ -10,18 +10,19 @@ import re
import simplejson as json import simplejson as json
from .indicators import Indicators
class DatabaseNotFoundError(Exception): class DatabaseNotFoundError(Exception):
pass pass
class DatabaseCorruptedError(Exception): class DatabaseCorruptedError(Exception):
pass pass
class InsufficientPrivileges(Exception): class InsufficientPrivileges(Exception):
pass pass
class MVTModule(object): class MVTModule(object):
"""This class provides a base for all extraction modules.""" """This class provides a base for all extraction modules."""
@@ -29,7 +30,7 @@ class MVTModule(object):
slug = None slug = None
def __init__(self, file_path=None, base_folder=None, output_folder=None, def __init__(self, file_path=None, base_folder=None, output_folder=None,
fast_mode=False, log=None, results=[]): fast_mode=False, log=None, results=None):
"""Initialize module. """Initialize module.
:param file_path: Path to the module's database file, if there is any :param file_path: Path to the module's database file, if there is any
@@ -50,7 +51,7 @@ class MVTModule(object):
self.fast_mode = fast_mode self.fast_mode = fast_mode
self.log = log self.log = log
self.indicators = None self.indicators = None
self.results = results self.results = results if results else []
self.detected = [] self.detected = []
self.timeline = [] self.timeline = []
self.timeline_detected = [] self.timeline_detected = []

View File

@@ -250,6 +250,7 @@ SHORTENER_DOMAINS = [
"zz.gd", "zz.gd",
] ]
class URL: class URL:
def __init__(self, url): def __init__(self, url):
@@ -273,7 +274,7 @@ class URL:
# TODO: Properly handle exception. # TODO: Properly handle exception.
try: try:
return get_tld(self.url, as_object=True, fix_protocol=True).parsed_url.netloc.lower().lstrip("www.") return get_tld(self.url, as_object=True, fix_protocol=True).parsed_url.netloc.lower().lstrip("www.")
except: except Exception:
return None return None
def get_top_level(self): def get_top_level(self):
@@ -288,7 +289,7 @@ class URL:
# TODO: Properly handle exception. # TODO: Properly handle exception.
try: try:
return get_tld(self.url, as_object=True, fix_protocol=True).fld.lower() return get_tld(self.url, as_object=True, fix_protocol=True).fld.lower()
except: except Exception:
return None return None
def check_if_shortened(self) -> bool: def check_if_shortened(self) -> bool:

View File

@@ -45,7 +45,7 @@ def convert_chrometime_to_unix(timestamp):
:returns: Unix epoch timestamp. :returns: Unix epoch timestamp.
""" """
epoch_start = datetime.datetime(1601, 1 , 1) epoch_start = datetime.datetime(1601, 1, 1)
delta = datetime.timedelta(microseconds=timestamp) delta = datetime.timedelta(microseconds=timestamp)
return epoch_start + delta return epoch_start + delta
@@ -64,6 +64,7 @@ def convert_timestamp_to_iso(timestamp):
except Exception: except Exception:
return None return None
def check_for_links(text): def check_for_links(text):
"""Checks if a given text contains HTTP links. """Checks if a given text contains HTTP links.
@@ -74,6 +75,7 @@ def check_for_links(text):
""" """
return re.findall("(?P<url>https?://[^\s]+)", text, re.IGNORECASE) return re.findall("(?P<url>https?://[^\s]+)", text, re.IGNORECASE)
def get_sha256_from_file_path(file_path): def get_sha256_from_file_path(file_path):
"""Calculate the SHA256 hash of a file from a file path. """Calculate the SHA256 hash of a file from a file path.
@@ -88,6 +90,7 @@ def get_sha256_from_file_path(file_path):
return sha256_hash.hexdigest() return sha256_hash.hexdigest()
# Note: taken from here: # Note: taken from here:
# https://stackoverflow.com/questions/57014259/json-dumps-on-dictionary-with-bytes-for-keys # https://stackoverflow.com/questions/57014259/json-dumps-on-dictionary-with-bytes-for-keys
def keys_bytes_to_string(obj): def keys_bytes_to_string(obj):

Some files were not shown because too many files have changed in this diff Show More