mirror of
https://github.com/mvt-project/mvt
synced 2025-11-13 01:37:36 +01:00
Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99e14ad8b0 | ||
|
|
deaa68a2e0 | ||
|
|
07f819bf5f | ||
|
|
51fdfce7f4 | ||
|
|
41e05a107e | ||
|
|
e559fb223b | ||
|
|
b69bb92f3d | ||
|
|
42e8e41b7d | ||
|
|
00b7314395 | ||
|
|
39a8bf236d | ||
|
|
d268b17284 | ||
|
|
66c015bc23 | ||
|
|
ba0106c476 | ||
|
|
41826d7951 | ||
|
|
4e0a393a02 | ||
|
|
c3dc4174fc | ||
|
|
e1d1b6c5de | ||
|
|
d0a893841b | ||
|
|
d4e99661c7 | ||
|
|
6a00d3a14d | ||
|
|
a863209abb | ||
|
|
4c7db02da4 | ||
|
|
92dfefbdeb | ||
|
|
8988adcf77 | ||
|
|
91667b0ded | ||
|
|
2365175dbd | ||
|
|
528d43b914 | ||
|
|
f952ba5119 | ||
|
|
d61b2751f1 | ||
|
|
b4ed2c6ed4 | ||
|
|
3eed1d6edf | ||
|
|
83ef545cd1 | ||
|
|
5d4fbec62b | ||
|
|
fa7d6166f4 | ||
|
|
429b223555 | ||
|
|
e4b9a9652a | ||
|
|
134581c000 | ||
|
|
5356a399c9 | ||
|
|
e0f563596d | ||
|
|
ea5de0203a | ||
|
|
ace965ee8a | ||
|
|
ad8f455209 | ||
|
|
ae67b41374 | ||
|
|
5fe88098b9 | ||
|
|
d578c240f9 | ||
|
|
427a29c2b6 | ||
|
|
5e6f6faa9c | ||
|
|
74a3ecaa4e | ||
|
|
f536af1124 | ||
|
|
631354c131 | ||
|
|
7ad7782b51 | ||
|
|
f04f91e1e3 | ||
|
|
6936908f86 | ||
|
|
f3e5763c6a | ||
|
|
f438f7b1fb | ||
|
|
66a157868f | ||
|
|
a966b694ea | ||
|
|
c9dd3af278 | ||
|
|
82a60ee07c | ||
|
|
8bc5113bd2 | ||
|
|
00d82f7f00 | ||
|
|
2781f33fb5 | ||
|
|
271fe5fbee | ||
|
|
0f503f72b5 | ||
|
|
424b86a261 | ||
|
|
1fe595f4cc | ||
|
|
b8c59f1183 | ||
|
|
a935347aed | ||
|
|
661d0a8669 | ||
|
|
63ff5fd334 | ||
|
|
146b9245ab | ||
|
|
99d33922be | ||
|
|
c42634af3f | ||
|
|
6cb59cc3ab | ||
|
|
e0481686b7 | ||
|
|
804ade3a40 | ||
|
|
c5ccaef0c4 | ||
|
|
c4416d406a | ||
|
|
6b8a23ae10 | ||
|
|
872d5d766e | ||
|
|
f5abd0719c | ||
|
|
6462ffc15d | ||
|
|
6333cafd38 | ||
|
|
03c59811a3 | ||
|
|
cfd3b5bbcb | ||
|
|
97ab67240f | ||
|
|
7fc664185c | ||
|
|
93094367c7 | ||
|
|
e8fa9c6eea | ||
|
|
79a01c45cc | ||
|
|
a440d12377 | ||
|
|
8085888c0c | ||
|
|
c2617fe778 | ||
|
|
2e1243864c | ||
|
|
ba5ff9b38c | ||
|
|
3fccebe132 | ||
|
|
1265b366c1 | ||
|
|
c944fb3234 | ||
|
|
e6b4d17027 | ||
|
|
f55ac36189 | ||
|
|
550d6037a6 | ||
|
|
e875c978c9 | ||
|
|
fbf510567c | ||
|
|
94fe98b9ec | ||
|
|
a328d57551 | ||
|
|
a9eabc5d9d | ||
|
|
1ed6140cb6 | ||
|
|
efceb777f0 | ||
|
|
14bbbd9e45 | ||
|
|
3cdc6da428 | ||
|
|
459ff8c51c | ||
|
|
88665cf7dd | ||
|
|
0a749da85f | ||
|
|
f81604133a | ||
|
|
cdd9b74cbc | ||
|
|
3fb37b4f30 | ||
|
|
2fe8b58c09 | ||
|
|
61d0c4134d | ||
|
|
6b36fe5fca | ||
|
|
c9f54947e3 | ||
|
|
ae6fec5ac5 | ||
|
|
298726ab2b | ||
|
|
7222bc82e1 | ||
|
|
4a568835d2 | ||
|
|
f98282d6c5 | ||
|
|
f864adf97e | ||
|
|
8f6882b0ff | ||
|
|
b6531e3e70 | ||
|
|
ef662c1145 | ||
|
|
b8e5346660 | ||
|
|
aedef123c9 | ||
|
|
8ff8e599d8 | ||
|
|
815cdc0a88 | ||
|
|
b420d828ee | ||
|
|
7b92903536 | ||
|
|
2bde693c35 | ||
|
|
7daea737c6 | ||
|
|
0d75dc3ba0 | ||
|
|
0622357a64 | ||
|
|
c4f91ba28b | ||
|
|
5ade0657ac | ||
|
|
cca9083dff | ||
|
|
3f4ddaaa0c | ||
|
|
7024909e05 | ||
|
|
3899dce353 | ||
|
|
4830aa5a6c | ||
|
|
3608576417 | ||
|
|
043c234401 | ||
|
|
8663c78b63 | ||
|
|
b847683717 | ||
|
|
09400a2847 | ||
|
|
2bc6fbef2f | ||
|
|
b77749e6ba | ||
|
|
1643454190 | ||
|
|
c2f1fe718d | ||
|
|
444ecf032d | ||
|
|
dd230c2407 | ||
|
|
cd87b6ed31 | ||
|
|
6f50af479d | ||
|
|
36a67911b3 | ||
|
|
2dbfef322a | ||
|
|
fba4e27757 | ||
|
|
abc0f2768b | ||
|
|
e7fe30e201 | ||
|
|
c54a01ca59 | ||
|
|
a12c4e6b93 | ||
|
|
a9be771f79 | ||
|
|
a7d35dba4a | ||
|
|
3a6e4a7001 | ||
|
|
067402831a |
2
.github/workflows/python-package.yml
vendored
2
.github/workflows/python-package.yml
vendored
@@ -28,7 +28,7 @@ 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 stix2
|
python -m pip install flake8 pytest safety stix2 pytest-mock
|
||||||
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 .
|
python -m pip install .
|
||||||
- name: Lint with flake8
|
- name: Lint with flake8
|
||||||
|
|||||||
19
CONTRIBUTING.md
Normal file
19
CONTRIBUTING.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to Mobile Verification Toolkit (MVT)! Your help is very much appreciated.
|
||||||
|
|
||||||
|
|
||||||
|
## Where to start
|
||||||
|
|
||||||
|
Starting to contribute to a somewhat complex project like MVT might seem intimidating. Unless you have specific ideas of new functionality you would like to submit, some good starting points are searching for `TODO:` and `FIXME:` comments throughout the code. Alternatively you can check if any GitHub issues existed marked with the ["help wanted"](https://github.com/mvt-project/mvt/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) tag.
|
||||||
|
|
||||||
|
|
||||||
|
## Code style
|
||||||
|
|
||||||
|
When contributing code to
|
||||||
|
|
||||||
|
- **Indentation**: we use 4-spaces tabs.
|
||||||
|
|
||||||
|
- **Quotes**: we use double quotes (`"`) as a default. Single quotes (`'`) can be favored with nested strings instead of escaping (`\"`), or when using f-formatting.
|
||||||
|
|
||||||
|
- **Maximum line length**: we strongly encourage to respect a 80 characters long lines and to follow [PEP8 indentation guidelines](https://peps.python.org/pep-0008/#indentation) when having to wrap. However, if breaking at 80 is not possible or is detrimental to the readability of the code, exceptions are tolerated. For example, long log lines, or long strings can be extended to 100 characters long. Please hard wrap anything beyond 100 characters.
|
||||||
18
Dockerfile
18
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM ubuntu:20.04
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
# Ref. https://github.com/mvt-project/mvt
|
# Ref. https://github.com/mvt-project/mvt
|
||||||
|
|
||||||
@@ -7,13 +7,12 @@ LABEL vcs-url="https://github.com/mvt-project/mvt"
|
|||||||
LABEL description="MVT is a forensic tool to look for signs of infection in smartphone devices."
|
LABEL description="MVT is a forensic tool to look for signs of infection in smartphone devices."
|
||||||
|
|
||||||
ENV PIP_NO_CACHE_DIR=1
|
ENV PIP_NO_CACHE_DIR=1
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
# Fixing major OS dependencies
|
# Fixing major OS dependencies
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
RUN apt update \
|
RUN apt update \
|
||||||
&& apt install -y python3 python3-pip libusb-1.0-0-dev \
|
&& apt install -y python3 python3-pip libusb-1.0-0-dev wget unzip default-jre-headless adb \
|
||||||
&& apt install -y wget unzip\
|
|
||||||
&& DEBIAN_FRONTEND=noninteractive apt-get -y install default-jre-headless \
|
|
||||||
|
|
||||||
# Install build tools for libimobiledevice
|
# Install build tools for libimobiledevice
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
@@ -67,18 +66,9 @@ RUN mkdir /opt/abe \
|
|||||||
# Create alias for abe
|
# Create alias for abe
|
||||||
&& echo 'alias abe="java -jar /opt/abe/abe.jar"' >> ~/.bashrc
|
&& echo 'alias abe="java -jar /opt/abe/abe.jar"' >> ~/.bashrc
|
||||||
|
|
||||||
# Install Android Platform Tools
|
|
||||||
# ------------------------------
|
|
||||||
|
|
||||||
RUN mkdir /opt/android \
|
|
||||||
&& wget -q https://dl.google.com/android/repository/platform-tools-latest-linux.zip \
|
|
||||||
&& unzip platform-tools-latest-linux.zip -d /opt/android \
|
|
||||||
# Create alias for adb
|
|
||||||
&& echo 'alias adb="/opt/android/platform-tools/adb"' >> ~/.bashrc
|
|
||||||
|
|
||||||
# Generate adb key folder
|
# Generate adb key folder
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
RUN mkdir /root/.android && /opt/android/platform-tools/adb keygen /root/.android/adbkey
|
RUN mkdir /root/.android && adb keygen /root/.android/adbkey
|
||||||
|
|
||||||
# Setup investigations environment
|
# Setup investigations environment
|
||||||
# --------------------------------
|
# --------------------------------
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -8,3 +8,9 @@ dist:
|
|||||||
|
|
||||||
upload:
|
upload:
|
||||||
python3 -m twine upload dist/*
|
python3 -m twine upload dist/*
|
||||||
|
|
||||||
|
test-upload:
|
||||||
|
python3 -m twine upload --repository testpypi dist/*
|
||||||
|
|
||||||
|
pylint:
|
||||||
|
pylint --rcfile=setup.cfg mvt
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="./docs/mvt.png" width="200" />
|
<img src="https://docs.mvt.re/en/latest/mvt.png" width="200" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Mobile Verification Toolkit
|
# Mobile Verification Toolkit
|
||||||
|
|||||||
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Reporting security issues
|
||||||
|
|
||||||
|
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 *nex [at] nex [dot] sx*. You can also write PGP-encrypted emails to [this key](https://keybase.io/nex/pgp_keys.asc?fingerprint=05216f3b86848a303c2fe37dd166f1667359d880).
|
||||||
@@ -13,22 +13,16 @@ It might take several minutes to complete.
|
|||||||
!!! info
|
!!! info
|
||||||
MVT will likely warn you it was unable to download certain installed packages. There is no reason to be alarmed: this is typically expected behavior when MVT attempts to download a system package it has no privileges to access.
|
MVT will likely warn you it was unable to download certain installed packages. There is no reason to be alarmed: this is typically expected behavior when MVT attempts to download a system package it has no privileges to access.
|
||||||
|
|
||||||
Optionally, you can decide to enable lookups of the SHA256 hash of all the extracted APKs on [VirusTotal](https://www.virustotal.com) and/or [Koodous](https://koodous.com). While these lookups do not provide any conclusive assessment on all of the extracted APKs, they might highlight any known malicious ones:
|
Optionally, you can decide to enable lookups of the SHA256 hash of all the extracted APKs on [VirusTotal](https://www.virustotal.com). While these lookups do not provide any conclusive assessment on all of the extracted APKs, they might highlight any known malicious ones:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mvt-android download-apks --output /path/to/folder --virustotal
|
MVT_VT_API_KEY=<key> mvt-android download-apks --output /path/to/folder --virustotal
|
||||||
mvt-android download-apks --output /path/to/folder --koodous
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Or, to launch all available lookups:
|
Please note that in order to use VirusTotal lookups you are required to provide your own API key through the `MVT_VT_API_KEY` environment variable. You should also note that VirusTotal enforces strict API usage. Be mindful that MVT might consume your hourly search quota.
|
||||||
|
|
||||||
|
In case you have a previous extraction of APKs you want to later check against VirusTotal, you can do so with the following arguments:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mvt-android download-apks --output /path/to/folder --all-checks
|
MVT_VT_API_KEY=<key> mvt-android download-apks --from-file /path/to/folder/apks.json --virustotal
|
||||||
```
|
```
|
||||||
|
|
||||||
In case you have a previous extraction of APKs you want to later check against VirusTotal and Koodous, you can do so with the following arguments:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mvt-android download-apks --from-file /path/to/folder/apks.json --all-checks
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ However, not all is lost.
|
|||||||
|
|
||||||
Because malware attacks over Android typically take the form of malicious or backdoored apps, the very first thing you might want to do is to extract and verify all installed Android packages and triage quickly if there are any which stand out as malicious or which might be atypical.
|
Because malware attacks over Android typically take the form of malicious or backdoored apps, the very first thing you might want to do is to extract and verify all installed Android packages and triage quickly if there are any which stand out as malicious or which might be atypical.
|
||||||
|
|
||||||
While it is out of the scope of this documentation to dwell into details on how to analyze Android apps, MVT does allow to easily and automatically extract information about installed apps, download copies of them, and quickly lookup services such as [VirusTotal](https://www.virustotal.com) or [Koodous](https://koodous.com) which might quickly indicate known bad apps.
|
While it is out of the scope of this documentation to dwell into details on how to analyze Android apps, MVT does allow to easily and automatically extract information about installed apps, download copies of them, and quickly look them up on services such as [VirusTotal](https://www.virustotal.com).
|
||||||
|
|
||||||
|
!!! info "Using VirusTotal"
|
||||||
|
Please note that in order to use VirusTotal lookups you are required to provide your own API key through the `MVT_VT_API_KEY` environment variable. You should also note that VirusTotal enforces strict API usage. Be mindful that MVT might consume your hourly search quota.
|
||||||
|
|
||||||
## Check the device over Android Debug Bridge
|
## Check the device over Android Debug Bridge
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Using Docker simplifies having all the required dependencies and tools (including most recent versions of [libimobiledevice](https://libimobiledevice.org)) readily installed.
|
Using Docker simplifies having all the required dependencies and tools (including most recent versions of [libimobiledevice](https://libimobiledevice.org)) readily installed. Note that this requires a Linux host, as Docker for Windows and Mac [doesn't support passing through USB devices](https://docs.docker.com/desktop/faqs/#can-i-pass-through-a-usb-device-to-a-container).
|
||||||
|
|
||||||
Install Docker following the [official documentation](https://docs.docker.com/get-docker/).
|
Install Docker following the [official documentation](https://docs.docker.com/get-docker/).
|
||||||
|
|
||||||
@@ -10,11 +10,6 @@ cd mvt
|
|||||||
docker build -t mvt .
|
docker build -t mvt .
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, you may need to specify your platform to Docker in order to build successfully (Apple M1)
|
|
||||||
```bash
|
|
||||||
docker build --platform amd64 -t mvt .
|
|
||||||
```
|
|
||||||
|
|
||||||
Test if the image was created successfully:
|
Test if the image was created successfully:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -41,6 +41,6 @@ export MVT_STIX2="/home/user/IOC1.stix2:/home/user/IOC2.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))
|
- [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/generated/stalkerware.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/generated/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.
|
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.
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
If you have correctly [installed libimobiledevice](../install.md) you can easily generate an iTunes backup using the `idevicebackup2` tool included in the suite. First, you might want to ensure that backup encryption is enabled (**note: encrypted backup contain more data than unencrypted backups**):
|
If you have correctly [installed libimobiledevice](../install.md) you can easily generate an iTunes backup using the `idevicebackup2` tool included in the suite. First, you might want to ensure that backup encryption is enabled (**note: encrypted backup contain more data than unencrypted backups**):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
idevicebackup2 -i backup encryption on
|
idevicebackup2 -i encryption on
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that if a backup password was previously set on this device, you might need to use the same or change it. You can try changing password using `idevicebackup2 -i backup changepw`, or by turning off encryption (`idevicebackup2 -i backup encryption off`) and turning it back on again.
|
Note that if a backup password was previously set on this device, you might need to use the same or change it. You can try changing password using `idevicebackup2 -i changepw`, or by turning off encryption (`idevicebackup2 -i encryption off`) and turning it back on again.
|
||||||
|
|
||||||
If you are not able to recover or change the password, you should try to disable encryption and obtain an unencrypted backup.
|
If you are not able to recover or change the password, you should try to disable encryption and obtain an unencrypted backup.
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ If indicators are provided through the command-line, processes and domains are c
|
|||||||
|
|
||||||
### `backup_info.json`
|
### `backup_info.json`
|
||||||
|
|
||||||
!!! info "Availabiliy"
|
!!! info "Availability"
|
||||||
Backup: :material-check:
|
Backup: :material-check:
|
||||||
Full filesystem dump: :material-close:
|
Full filesystem dump: :material-close:
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
site_name: Mobile Verification Toolkit
|
site_name: Mobile Verification Toolkit
|
||||||
repo_url: https://github.com/mvt-project/mvt
|
repo_url: https://github.com/mvt-project/mvt
|
||||||
edit_uri: edit/main/docs/
|
edit_uri: edit/main/docs/
|
||||||
copyright: Copyright © 2021 MVT Project Developers
|
copyright: Copyright © 2021-2022 MVT Project Developers
|
||||||
site_description: Mobile Verification Toolkit Documentation
|
site_description: Mobile Verification Toolkit Documentation
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- attr_list
|
- attr_list
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
32
mvt/android/cmd_check_adb.py
Normal file
32
mvt/android/cmd_check_adb.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2022 Claudio Guarnieri.
|
||||||
|
# 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.common.command import Command
|
||||||
|
|
||||||
|
from .modules.adb import ADB_MODULES
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CmdAndroidCheckADB(Command):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
ioc_files: Optional[list] = None,
|
||||||
|
module_name: Optional[str] = None,
|
||||||
|
serial: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(target_path=target_path, results_path=results_path,
|
||||||
|
ioc_files=ioc_files, module_name=module_name,
|
||||||
|
serial=serial, fast_mode=fast_mode, log=log)
|
||||||
|
|
||||||
|
self.name = "check-adb"
|
||||||
|
self.modules = ADB_MODULES
|
||||||
32
mvt/android/cmd_check_androidqf.py
Normal file
32
mvt/android/cmd_check_androidqf.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2022 Claudio Guarnieri.
|
||||||
|
# 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.common.command import Command
|
||||||
|
|
||||||
|
from .modules.androidqf import ANDROIDQF_MODULES
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CmdAndroidCheckAndroidQF(Command):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
ioc_files: Optional[list] = None,
|
||||||
|
module_name: Optional[str] = None,
|
||||||
|
serial: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(target_path=target_path, results_path=results_path,
|
||||||
|
ioc_files=ioc_files, module_name=module_name,
|
||||||
|
serial=serial, fast_mode=fast_mode, log=log)
|
||||||
|
|
||||||
|
self.name = "check-androidqf"
|
||||||
|
self.modules = ANDROIDQF_MODULES
|
||||||
94
mvt/android/cmd_check_backup.py
Normal file
94
mvt/android/cmd_check_backup.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2022 Claudio Guarnieri.
|
||||||
|
# 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
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
|
from mvt.android.parsers.backup import (AndroidBackupParsingError,
|
||||||
|
InvalidBackupPassword, parse_ab_header,
|
||||||
|
parse_backup_file)
|
||||||
|
from mvt.common.command import Command
|
||||||
|
|
||||||
|
from .modules.backup import BACKUP_MODULES
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CmdAndroidCheckBackup(Command):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
ioc_files: Optional[list] = None,
|
||||||
|
module_name: Optional[str] = None,
|
||||||
|
serial: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(target_path=target_path, results_path=results_path,
|
||||||
|
ioc_files=ioc_files, module_name=module_name,
|
||||||
|
serial=serial, fast_mode=fast_mode, log=log)
|
||||||
|
|
||||||
|
self.name = "check-backup"
|
||||||
|
self.modules = BACKUP_MODULES
|
||||||
|
|
||||||
|
self.backup_type = None
|
||||||
|
self.backup_archive = None
|
||||||
|
self.backup_files = []
|
||||||
|
|
||||||
|
def init(self) -> None:
|
||||||
|
if os.path.isfile(self.target_path):
|
||||||
|
self.backup_type = "ab"
|
||||||
|
with open(self.target_path, "rb") as handle:
|
||||||
|
data = handle.read()
|
||||||
|
|
||||||
|
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.ask("Enter backup password", password=True)
|
||||||
|
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):
|
||||||
|
self.backup_type = "folder"
|
||||||
|
self.target_path = Path(self.target_path).absolute().as_posix()
|
||||||
|
for root, subdirs, subfiles in os.walk(os.path.abspath(self.target_path)):
|
||||||
|
for fname in subfiles:
|
||||||
|
self.backup_files.append(os.path.relpath(os.path.join(root, fname),
|
||||||
|
self.target_path))
|
||||||
|
else:
|
||||||
|
log.critical("Invalid backup path, path should be a folder or an "
|
||||||
|
"Android Backup (.ab) file")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def module_init(self, module: Callable) -> None:
|
||||||
|
if self.backup_type == "folder":
|
||||||
|
module.from_folder(self.target_path, self.backup_files)
|
||||||
|
else:
|
||||||
|
module.from_ab(self.target_path, self.backup_archive,
|
||||||
|
self.backup_files)
|
||||||
64
mvt/android/cmd_check_bugreport.py
Normal file
64
mvt/android/cmd_check_bugreport.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021-2022 Claudio Guarnieri.
|
||||||
|
# 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
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Callable, Optional
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from mvt.common.command import Command
|
||||||
|
|
||||||
|
from .modules.bugreport import BUGREPORT_MODULES
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CmdAndroidCheckBugreport(Command):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
ioc_files: Optional[list] = None,
|
||||||
|
module_name: Optional[str] = None,
|
||||||
|
serial: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(target_path=target_path, results_path=results_path,
|
||||||
|
ioc_files=ioc_files, module_name=module_name,
|
||||||
|
serial=serial, fast_mode=fast_mode, log=log)
|
||||||
|
|
||||||
|
self.name = "check-bugreport"
|
||||||
|
self.modules = BUGREPORT_MODULES
|
||||||
|
|
||||||
|
self.bugreport_format = None
|
||||||
|
self.bugreport_archive = None
|
||||||
|
self.bugreport_files = []
|
||||||
|
|
||||||
|
def init(self) -> None:
|
||||||
|
if os.path.isfile(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):
|
||||||
|
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: Callable) -> None:
|
||||||
|
if self.bugreport_format == "zip":
|
||||||
|
module.from_zip(self.bugreport_archive, self.bugreport_files)
|
||||||
|
else:
|
||||||
|
module.from_folder(self.target_path, self.bugreport_files)
|
||||||
|
|
||||||
|
def finish(self) -> None:
|
||||||
|
if self.bugreport_archive:
|
||||||
|
self.bugreport_archive.close()
|
||||||
@@ -6,8 +6,9 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
from tqdm import tqdm
|
from rich.progress import track
|
||||||
|
|
||||||
from mvt.common.module import InsufficientPrivileges
|
from mvt.common.module import InsufficientPrivileges
|
||||||
|
|
||||||
@@ -17,18 +18,6 @@ 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
|
|
||||||
# the number of dependencies. Need to investigate whether
|
|
||||||
# it's possible to have a similar callback system.
|
|
||||||
class PullProgress(tqdm):
|
|
||||||
"""PullProgress is a tqdm update system for APK downloads."""
|
|
||||||
|
|
||||||
def update_to(self, file_name, current, total):
|
|
||||||
if total is not None:
|
|
||||||
self.total = total
|
|
||||||
self.update(current - self.n)
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadAPKs(AndroidExtraction):
|
class DownloadAPKs(AndroidExtraction):
|
||||||
"""DownloadAPKs is the main class operating the download of APKs
|
"""DownloadAPKs is the main class operating the download of APKs
|
||||||
from the device.
|
from the device.
|
||||||
@@ -36,22 +25,26 @@ class DownloadAPKs(AndroidExtraction):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, output_folder=None, all_apks=False, log=None,
|
def __init__(
|
||||||
packages=None):
|
self,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
all_apks: Optional[bool] = False,
|
||||||
|
packages: Optional[list] = None
|
||||||
|
) -> None:
|
||||||
"""Initialize module.
|
"""Initialize module.
|
||||||
:param output_folder: Path to the folder where data should be stored
|
:param results_path: Path to the folder where data should be stored
|
||||||
:param all_apks: Boolean indicating whether to download all packages
|
:param all_apks: Boolean indicating whether to download all packages
|
||||||
or filter known-goods
|
or filter known-goods
|
||||||
:param packages: Provided list of packages, typically for JSON checks
|
:param packages: Provided list of packages, typically for JSON checks
|
||||||
"""
|
"""
|
||||||
super().__init__(output_folder=output_folder, log=log)
|
super().__init__(results_path=results_path, log=log)
|
||||||
|
|
||||||
self.packages = packages
|
self.packages = packages
|
||||||
self.all_apks = all_apks
|
self.all_apks = all_apks
|
||||||
self.output_folder_apk = None
|
self.results_path_apks = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, json_path):
|
def from_json(cls, json_path: str) -> Callable:
|
||||||
"""Initialize this class from an existing apks.json file.
|
"""Initialize this class from an existing apks.json file.
|
||||||
|
|
||||||
:param json_path: Path to the apks.json file to parse.
|
:param json_path: Path to the apks.json file to parse.
|
||||||
@@ -61,7 +54,7 @@ class DownloadAPKs(AndroidExtraction):
|
|||||||
packages = json.load(handle)
|
packages = json.load(handle)
|
||||||
return cls(packages=packages)
|
return cls(packages=packages)
|
||||||
|
|
||||||
def pull_package_file(self, package_name, remote_path):
|
def pull_package_file(self, package_name: str, remote_path: str) -> None:
|
||||||
"""Pull files related to specific package from the device.
|
"""Pull files related to specific package from the device.
|
||||||
|
|
||||||
:param package_name: Name of the package to download
|
:param package_name: Name of the package to download
|
||||||
@@ -75,7 +68,7 @@ class DownloadAPKs(AndroidExtraction):
|
|||||||
if "==/" in remote_path:
|
if "==/" in remote_path:
|
||||||
file_name = "_" + remote_path.split("==/")[1].replace(".apk", "")
|
file_name = "_" + remote_path.split("==/")[1].replace(".apk", "")
|
||||||
|
|
||||||
local_path = os.path.join(self.output_folder_apk,
|
local_path = os.path.join(self.results_path_apks,
|
||||||
f"{package_name}{file_name}.apk")
|
f"{package_name}{file_name}.apk")
|
||||||
name_counter = 0
|
name_counter = 0
|
||||||
while True:
|
while True:
|
||||||
@@ -83,47 +76,42 @@ class DownloadAPKs(AndroidExtraction):
|
|||||||
break
|
break
|
||||||
|
|
||||||
name_counter += 1
|
name_counter += 1
|
||||||
local_path = os.path.join(self.output_folder_apk,
|
local_path = os.path.join(self.results_path_apks,
|
||||||
f"{package_name}{file_name}_{name_counter}.apk")
|
f"{package_name}{file_name}_{name_counter}.apk")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with PullProgress(unit='B', unit_divisor=1024, unit_scale=True,
|
self._adb_download(remote_path, local_path)
|
||||||
miniters=1) as pp:
|
|
||||||
self._adb_download(remote_path, local_path,
|
|
||||||
progress_callback=pp.update_to)
|
|
||||||
except InsufficientPrivileges:
|
except InsufficientPrivileges:
|
||||||
log.warn("Unable to pull package file from %s: insufficient privileges, it might be a system app",
|
log.error("Unable to pull package file from %s: insufficient privileges, "
|
||||||
remote_path)
|
"it might be a system app", remote_path)
|
||||||
self._adb_reconnect()
|
self._adb_reconnect()
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as exc:
|
||||||
log.exception("Failed to pull package file from %s: %s",
|
log.exception("Failed to pull package file from %s: %s",
|
||||||
remote_path, e)
|
remote_path, exc)
|
||||||
self._adb_reconnect()
|
self._adb_reconnect()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return local_path
|
return local_path
|
||||||
|
|
||||||
def get_packages(self):
|
def get_packages(self) -> None:
|
||||||
"""Use the Packages adb module to retrieve the list of packages.
|
"""Use the Packages adb module to retrieve the list of packages.
|
||||||
We reuse the same extraction logic to then download the APKs.
|
We reuse the same extraction logic to then download the APKs.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.log.info("Retrieving list of installed packages...")
|
self.log.info("Retrieving list of installed packages...")
|
||||||
|
|
||||||
m = Packages()
|
m = Packages()
|
||||||
m.log = self.log
|
m.log = self.log
|
||||||
|
m.serial = self.serial
|
||||||
m.run()
|
m.run()
|
||||||
|
|
||||||
self.packages = m.results
|
self.packages = m.results
|
||||||
|
|
||||||
def pull_packages(self):
|
def pull_packages(self) -> None:
|
||||||
"""Download all files of all selected packages from the device."""
|
"""Download all files of all selected packages from the device.
|
||||||
log.info("Starting extraction of installed APKs at folder %s", self.output_folder)
|
"""
|
||||||
|
log.info("Starting extraction of installed APKs at folder %s",
|
||||||
if not os.path.exists(self.output_folder):
|
self.results_path)
|
||||||
os.mkdir(self.output_folder)
|
|
||||||
|
|
||||||
# If the user provided the flag --all-apks we select all packages.
|
# If the user provided the flag --all-apks we select all packages.
|
||||||
packages_selection = []
|
packages_selection = []
|
||||||
@@ -137,7 +125,7 @@ class DownloadAPKs(AndroidExtraction):
|
|||||||
if not package.get("system", False):
|
if not package.get("system", False):
|
||||||
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:
|
||||||
@@ -146,19 +134,19 @@ class DownloadAPKs(AndroidExtraction):
|
|||||||
|
|
||||||
log.info("Downloading packages from device. This might take some time ...")
|
log.info("Downloading packages from device. This might take some time ...")
|
||||||
|
|
||||||
self.output_folder_apk = os.path.join(self.output_folder, "apks")
|
self.results_path_apks = os.path.join(self.results_path, "apks")
|
||||||
if not os.path.exists(self.output_folder_apk):
|
if not os.path.exists(self.results_path_apks):
|
||||||
os.mkdir(self.output_folder_apk)
|
os.makedirs(self.results_path_apks, exist_ok=True)
|
||||||
|
|
||||||
counter = 0
|
for i in track(range(len(packages_selection)),
|
||||||
for package in packages_selection:
|
description=f"Downloading {len(packages_selection)} packages..."):
|
||||||
counter += 1
|
package = packages_selection[i]
|
||||||
|
|
||||||
log.info("[%d/%d] Package: %s", counter, len(packages_selection),
|
log.info("[%d/%d] Package: %s", i, len(packages_selection),
|
||||||
package["package_name"])
|
package["package_name"])
|
||||||
|
|
||||||
# Sometimes the package path contains multiple lines for multiple apks.
|
# Sometimes the package path contains multiple lines for multiple
|
||||||
# We loop through each line and download each file.
|
# apks. We loop through each line and download each file.
|
||||||
for package_file in package["files"]:
|
for package_file in package["files"]:
|
||||||
device_path = package_file["path"]
|
device_path = package_file["path"]
|
||||||
local_path = self.pull_package_file(package["package_name"],
|
local_path = self.pull_package_file(package["package_name"],
|
||||||
@@ -170,14 +158,12 @@ class DownloadAPKs(AndroidExtraction):
|
|||||||
|
|
||||||
log.info("Download of selected packages completed")
|
log.info("Download of selected packages completed")
|
||||||
|
|
||||||
def save_json(self):
|
def save_json(self) -> None:
|
||||||
"""Save the results to the package.json file."""
|
json_path = os.path.join(self.results_path, "apks.json")
|
||||||
json_path = os.path.join(self.output_folder, "apks.json")
|
|
||||||
with open(json_path, "w", encoding="utf-8") as handle:
|
with open(json_path, "w", encoding="utf-8") as handle:
|
||||||
json.dump(self.packages, handle, indent=4)
|
json.dump(self.packages, handle, indent=4)
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
"""Run all steps of fetch-apk."""
|
|
||||||
self.get_packages()
|
self.get_packages()
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
self.pull_packages()
|
self.pull_packages()
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# Mobile Verification Toolkit (MVT)
|
|
||||||
# Copyright (c) 2021-2022 Claudio Guarnieri.
|
|
||||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
|
||||||
# https://license.mvt.re/1.1/
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Mobile Verification Toolkit (MVT)
|
|
||||||
# Copyright (c) 2021-2022 Claudio Guarnieri.
|
|
||||||
# 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 requests
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.progress import track
|
|
||||||
from rich.table import Table
|
|
||||||
from rich.text import Text
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def koodous_lookup(packages):
|
|
||||||
log.info("Looking up all extracted files on Koodous (www.koodous.com)")
|
|
||||||
log.info("This might take a while...")
|
|
||||||
|
|
||||||
table = Table(title="Koodous Packages Detections")
|
|
||||||
table.add_column("Package name")
|
|
||||||
table.add_column("File name")
|
|
||||||
table.add_column("Trusted")
|
|
||||||
table.add_column("Detected")
|
|
||||||
table.add_column("Rating")
|
|
||||||
|
|
||||||
total_packages = len(packages)
|
|
||||||
for i in track(range(total_packages), description=f"Looking up {total_packages} packages..."):
|
|
||||||
package = packages[i]
|
|
||||||
for file in package.get("files", []):
|
|
||||||
url = f"https://api.koodous.com/apks/{file['sha256']}"
|
|
||||||
res = requests.get(url)
|
|
||||||
report = res.json()
|
|
||||||
|
|
||||||
row = [package["package_name"], file["path"]]
|
|
||||||
|
|
||||||
if "package_name" in report:
|
|
||||||
trusted = "no"
|
|
||||||
if report["trusted"]:
|
|
||||||
trusted = Text("yes", "green bold")
|
|
||||||
|
|
||||||
detected = "no"
|
|
||||||
if report["detected"]:
|
|
||||||
detected = Text("yes", "red bold")
|
|
||||||
|
|
||||||
rating = "0"
|
|
||||||
if int(report["rating"]) < 0:
|
|
||||||
rating = Text(str(report["rating"]), "red bold")
|
|
||||||
|
|
||||||
row.extend([trusted, detected, rating])
|
|
||||||
else:
|
|
||||||
row.extend(["n/a", "n/a", "n/a"])
|
|
||||||
|
|
||||||
table.add_row(*row)
|
|
||||||
|
|
||||||
console = Console()
|
|
||||||
console.print(table)
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
# Mobile Verification Toolkit (MVT)
|
|
||||||
# Copyright (c) 2021-2022 Claudio Guarnieri.
|
|
||||||
# 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 requests
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.progress import track
|
|
||||||
from rich.table import Table
|
|
||||||
from rich.text import Text
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def get_virustotal_report(hashes):
|
|
||||||
apikey = "233f22e200ca5822bd91103043ccac138b910db79f29af5616a9afe8b6f215ad"
|
|
||||||
url = f"https://www.virustotal.com/partners/sysinternals/file-reports?apikey={apikey}"
|
|
||||||
|
|
||||||
items = []
|
|
||||||
for sha256 in hashes:
|
|
||||||
items.append({
|
|
||||||
"autostart_location": "",
|
|
||||||
"autostart_entry": "",
|
|
||||||
"hash": sha256,
|
|
||||||
"local_name": "",
|
|
||||||
"creation_datetime": "",
|
|
||||||
})
|
|
||||||
headers = {"User-Agent": "VirusTotal", "Content-Type": "application/json"}
|
|
||||||
res = requests.post(url, headers=headers, json=items)
|
|
||||||
|
|
||||||
if res.status_code == 200:
|
|
||||||
report = res.json()
|
|
||||||
return report["data"]
|
|
||||||
else:
|
|
||||||
log.error("Unexpected response from VirusTotal: %s", res.status_code)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
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)")
|
|
||||||
|
|
||||||
unique_hashes = []
|
|
||||||
for package in packages:
|
|
||||||
for file in package.get("files", []):
|
|
||||||
if file["sha256"] not in unique_hashes:
|
|
||||||
unique_hashes.append(file["sha256"])
|
|
||||||
|
|
||||||
total_unique_hashes = len(unique_hashes)
|
|
||||||
|
|
||||||
detections = {}
|
|
||||||
|
|
||||||
def virustotal_query(batch):
|
|
||||||
report = get_virustotal_report(batch)
|
|
||||||
if not report:
|
|
||||||
return
|
|
||||||
|
|
||||||
for entry in report:
|
|
||||||
if entry["hash"] not in detections and entry["found"] is True:
|
|
||||||
detections[entry["hash"]] = entry["detection_ratio"]
|
|
||||||
|
|
||||||
batch = []
|
|
||||||
for i in track(range(total_unique_hashes), description=f"Looking up {total_unique_hashes} files..."):
|
|
||||||
file_hash = unique_hashes[i]
|
|
||||||
batch.append(file_hash)
|
|
||||||
if len(batch) == 25:
|
|
||||||
virustotal_query(batch)
|
|
||||||
batch = []
|
|
||||||
|
|
||||||
if batch:
|
|
||||||
virustotal_query(batch)
|
|
||||||
|
|
||||||
table = Table(title="VirusTotal Packages Detections")
|
|
||||||
table.add_column("Package name")
|
|
||||||
table.add_column("File path")
|
|
||||||
table.add_column("Detections")
|
|
||||||
|
|
||||||
for package in packages:
|
|
||||||
for file in package.get("files", []):
|
|
||||||
row = [package["package_name"], file["path"]]
|
|
||||||
|
|
||||||
if file["sha256"] in detections:
|
|
||||||
detection = detections[file["sha256"]]
|
|
||||||
positives = detection.split("/")[0]
|
|
||||||
if int(positives) > 0:
|
|
||||||
row.append(Text(detection, "red bold"))
|
|
||||||
else:
|
|
||||||
row.append(detection)
|
|
||||||
else:
|
|
||||||
row.append("not found")
|
|
||||||
|
|
||||||
table.add_row(*row)
|
|
||||||
|
|
||||||
console = Console()
|
|
||||||
console.print(table)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -6,35 +6,42 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
from mvt.common.utils import (convert_chrometime_to_unix,
|
from mvt.common.utils import (convert_chrometime_to_datetime,
|
||||||
convert_timestamp_to_iso)
|
convert_datetime_to_iso)
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
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."""
|
||||||
|
|
||||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
def __init__(
|
||||||
serial=None, fast_mode=False, log=None, results=[]):
|
self,
|
||||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
file_path: Optional[str] = None,
|
||||||
output_folder=output_folder, fast_mode=fast_mode,
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
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, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
def serialize(self, record):
|
def serialize(self, record: dict) -> Union[dict, list]:
|
||||||
return {
|
return {
|
||||||
"timestamp": record["isodate"],
|
"timestamp": record["isodate"],
|
||||||
"module": self.__class__.__name__,
|
"module": self.__class__.__name__,
|
||||||
"event": "visit",
|
"event": "visit",
|
||||||
"data": f"{record['id']} - {record['url']} (visit ID: {record['visit_id']}, redirect source: {record['redirect_source']})"
|
"data": f"{record['id']} - {record['url']} (visit ID: {record['visit_id']}, "
|
||||||
|
f"redirect source: {record['redirect_source']})"
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self) -> None:
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -42,7 +49,7 @@ class ChromeHistory(AndroidExtraction):
|
|||||||
if self.indicators.check_domain(result["url"]):
|
if self.indicators.check_domain(result["url"]):
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
|
|
||||||
def _parse_db(self, db_path):
|
def _parse_db(self, db_path: str) -> None:
|
||||||
"""Parse a Chrome History database file.
|
"""Parse a Chrome History database file.
|
||||||
|
|
||||||
:param db_path: Path to the History database to process.
|
:param db_path: Path to the History database to process.
|
||||||
@@ -68,18 +75,24 @@ class ChromeHistory(AndroidExtraction):
|
|||||||
"url": item[1],
|
"url": item[1],
|
||||||
"visit_id": item[2],
|
"visit_id": item[2],
|
||||||
"timestamp": item[3],
|
"timestamp": item[3],
|
||||||
"isodate": convert_timestamp_to_iso(convert_chrometime_to_unix(item[3])),
|
"isodate": convert_datetime_to_iso(
|
||||||
|
convert_chrometime_to_datetime(item[3])),
|
||||||
"redirect_source": item[4],
|
"redirect_source": item[4],
|
||||||
})
|
})
|
||||||
|
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
log.info("Extracted a total of %d history items", len(self.results))
|
self.log.info("Extracted a total of %d history items",
|
||||||
|
len(self.results))
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self._adb_connect()
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
try:
|
||||||
self._adb_process_file(os.path.join("/", CHROME_HISTORY_PATH),
|
self._adb_process_file(os.path.join("/", CHROME_HISTORY_PATH),
|
||||||
self._parse_db)
|
self._parse_db)
|
||||||
except Exception as e:
|
except Exception as exc:
|
||||||
self.log.error(e)
|
self.log.error(exc)
|
||||||
|
|
||||||
|
self._adb_disconnect()
|
||||||
|
|||||||
@@ -4,24 +4,30 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.android.parsers import parse_dumpsys_accessibility
|
from mvt.android.parsers import parse_dumpsys_accessibility
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DumpsysAccessibility(AndroidExtraction):
|
class DumpsysAccessibility(AndroidExtraction):
|
||||||
"""This module extracts stats on accessibility."""
|
"""This module extracts stats on accessibility."""
|
||||||
|
|
||||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
def __init__(
|
||||||
serial=None, fast_mode=False, log=None, results=[]):
|
self,
|
||||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
file_path: Optional[str] = None,
|
||||||
output_folder=output_folder, fast_mode=fast_mode,
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
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, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self) -> None:
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -32,7 +38,7 @@ class DumpsysAccessibility(AndroidExtraction):
|
|||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
output = self._adb_command("dumpsys accessibility")
|
output = self._adb_command("dumpsys accessibility")
|
||||||
self._adb_disconnect()
|
self._adb_disconnect()
|
||||||
@@ -40,6 +46,8 @@ class DumpsysAccessibility(AndroidExtraction):
|
|||||||
self.results = parse_dumpsys_accessibility(output)
|
self.results = parse_dumpsys_accessibility(output)
|
||||||
|
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
log.info("Found installed accessibility service \"%s\"", result.get("service"))
|
self.log.info("Found installed accessibility service \"%s\"",
|
||||||
|
result.get("service"))
|
||||||
|
|
||||||
self.log.info("Identified a total of %d accessibility services", len(self.results))
|
self.log.info("Identified a total of %d accessibility services",
|
||||||
|
len(self.results))
|
||||||
|
|||||||
@@ -4,26 +4,32 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.android.parsers import parse_dumpsys_activity_resolver_table
|
from mvt.android.parsers import parse_dumpsys_activity_resolver_table
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DumpsysActivities(AndroidExtraction):
|
class DumpsysActivities(AndroidExtraction):
|
||||||
"""This module extracts details on receivers for risky activities."""
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
def __init__(
|
||||||
serial=None, fast_mode=False, log=None, results=[]):
|
self,
|
||||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
file_path: Optional[str] = None,
|
||||||
output_folder=output_folder, fast_mode=fast_mode,
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
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, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
self.results = results if results else {}
|
self.results = results if results else {}
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self) -> None:
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -35,7 +41,7 @@ class DumpsysActivities(AndroidExtraction):
|
|||||||
self.detected.append({intent: activity})
|
self.detected.append({intent: activity})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
output = self._adb_command("dumpsys package")
|
output = self._adb_command("dumpsys package")
|
||||||
self._adb_disconnect()
|
self._adb_disconnect()
|
||||||
|
|||||||
@@ -4,27 +4,32 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
from typing import Optional, Union
|
||||||
|
|
||||||
from mvt.android.parsers.dumpsys import parse_dumpsys_appops
|
from mvt.android.parsers.dumpsys import parse_dumpsys_appops
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DumpsysAppOps(AndroidExtraction):
|
class DumpsysAppOps(AndroidExtraction):
|
||||||
"""This module extracts records from App-op Manager."""
|
"""This module extracts records from App-op Manager."""
|
||||||
|
|
||||||
slug = "dumpsys_appops"
|
slug = "dumpsys_appops"
|
||||||
|
|
||||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
def __init__(
|
||||||
serial=None, fast_mode=False, log=None, results=[]):
|
self,
|
||||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
file_path: Optional[str] = None,
|
||||||
output_folder=output_folder, fast_mode=fast_mode,
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
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, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
def serialize(self, record):
|
def serialize(self, record: dict) -> Union[dict, list]:
|
||||||
records = []
|
records = []
|
||||||
for perm in record["permissions"]:
|
for perm in record["permissions"]:
|
||||||
if "entries" not in perm:
|
if "entries" not in perm:
|
||||||
@@ -36,12 +41,13 @@ class DumpsysAppOps(AndroidExtraction):
|
|||||||
"timestamp": entry["timestamp"],
|
"timestamp": entry["timestamp"],
|
||||||
"module": self.__class__.__name__,
|
"module": self.__class__.__name__,
|
||||||
"event": entry["access"],
|
"event": entry["access"],
|
||||||
"data": f"{record['package_name']} access to {perm['name']} : {entry['access']}",
|
"data": f"{record['package_name']} access to "
|
||||||
|
f"{perm['name']}: {entry['access']}",
|
||||||
})
|
})
|
||||||
|
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self) -> None:
|
||||||
for result in self.results:
|
for result in self.results:
|
||||||
if self.indicators:
|
if self.indicators:
|
||||||
ioc = self.indicators.check_app_id(result.get("package_name"))
|
ioc = self.indicators.check_app_id(result.get("package_name"))
|
||||||
@@ -51,11 +57,12 @@ class DumpsysAppOps(AndroidExtraction):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for perm in result["permissions"]:
|
for perm in result["permissions"]:
|
||||||
if perm["name"] == "REQUEST_INSTALL_PACKAGES" and perm["access"] == "allow":
|
if (perm["name"] == "REQUEST_INSTALL_PACKAGES"
|
||||||
self.log.info("Package %s with REQUEST_INSTALL_PACKAGES permission",
|
and perm["access"] == "allow"):
|
||||||
result["package_name"])
|
self.log.info("Package %s with REQUEST_INSTALL_PACKAGES "
|
||||||
|
"permission", result["package_name"])
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
output = self._adb_command("dumpsys appops")
|
output = self._adb_command("dumpsys appops")
|
||||||
self._adb_disconnect()
|
self._adb_disconnect()
|
||||||
|
|||||||
@@ -4,32 +4,39 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
from mvt.android.parsers import parse_dumpsys_battery_daily
|
from mvt.android.parsers import parse_dumpsys_battery_daily
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DumpsysBatteryDaily(AndroidExtraction):
|
class DumpsysBatteryDaily(AndroidExtraction):
|
||||||
"""This module extracts records from battery daily updates."""
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
def __init__(
|
||||||
serial=None, fast_mode=False, log=None, results=[]):
|
self,
|
||||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
file_path: Optional[str] = None,
|
||||||
output_folder=output_folder, fast_mode=fast_mode,
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
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, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
def serialize(self, record):
|
def serialize(self, record: dict) -> Union[dict, list]:
|
||||||
return {
|
return {
|
||||||
"timestamp": record["from"],
|
"timestamp": record["from"],
|
||||||
"module": self.__class__.__name__,
|
"module": self.__class__.__name__,
|
||||||
"event": "battery_daily",
|
"event": "battery_daily",
|
||||||
"data": f"Recorded update of package {record['package_name']} with vers {record['vers']}"
|
"data": f"Recorded update of package {record['package_name']} "
|
||||||
|
f"with vers {record['vers']}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self) -> None:
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -40,11 +47,12 @@ class DumpsysBatteryDaily(AndroidExtraction):
|
|||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
output = self._adb_command("dumpsys batterystats --daily")
|
output = self._adb_command("dumpsys batterystats --daily")
|
||||||
self._adb_disconnect()
|
self._adb_disconnect()
|
||||||
|
|
||||||
self.results = parse_dumpsys_battery_daily(output)
|
self.results = parse_dumpsys_battery_daily(output)
|
||||||
|
|
||||||
self.log.info("Extracted %d records from battery daily stats", len(self.results))
|
self.log.info("Extracted %d records from battery daily stats",
|
||||||
|
len(self.results))
|
||||||
|
|||||||
@@ -4,24 +4,30 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.android.parsers import parse_dumpsys_battery_history
|
from mvt.android.parsers import parse_dumpsys_battery_history
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DumpsysBatteryHistory(AndroidExtraction):
|
class DumpsysBatteryHistory(AndroidExtraction):
|
||||||
"""This module extracts records from battery history events."""
|
"""This module extracts records from battery history events."""
|
||||||
|
|
||||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
def __init__(
|
||||||
serial=None, fast_mode=False, log=None, results=[]):
|
self,
|
||||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
file_path: Optional[str] = None,
|
||||||
output_folder=output_folder, fast_mode=fast_mode,
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
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, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self) -> None:
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -32,11 +38,12 @@ class DumpsysBatteryHistory(AndroidExtraction):
|
|||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
output = self._adb_command("dumpsys batterystats --history")
|
output = self._adb_command("dumpsys batterystats --history")
|
||||||
self._adb_disconnect()
|
self._adb_disconnect()
|
||||||
|
|
||||||
self.results = parse_dumpsys_battery_history(output)
|
self.results = parse_dumpsys_battery_history(output)
|
||||||
|
|
||||||
self.log.info("Extracted %d records from battery history", len(self.results))
|
self.log.info("Extracted %d records from battery history",
|
||||||
|
len(self.results))
|
||||||
|
|||||||
@@ -4,27 +4,32 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.android.parsers import parse_dumpsys_dbinfo
|
from mvt.android.parsers import parse_dumpsys_dbinfo
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DumpsysDBInfo(AndroidExtraction):
|
class DumpsysDBInfo(AndroidExtraction):
|
||||||
"""This module extracts records from battery daily updates."""
|
"""This module extracts records from battery daily updates."""
|
||||||
|
|
||||||
slug = "dumpsys_dbinfo"
|
slug = "dumpsys_dbinfo"
|
||||||
|
|
||||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
def __init__(
|
||||||
serial=None, fast_mode=False, log=None, results=[]):
|
self,
|
||||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
file_path: Optional[str] = None,
|
||||||
output_folder=output_folder, fast_mode=fast_mode,
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
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, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self) -> None:
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -37,7 +42,7 @@ class DumpsysDBInfo(AndroidExtraction):
|
|||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
output = self._adb_command("dumpsys dbinfo")
|
output = self._adb_command("dumpsys dbinfo")
|
||||||
self._adb_disconnect()
|
self._adb_disconnect()
|
||||||
|
|||||||
@@ -5,30 +5,36 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
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."""
|
||||||
|
|
||||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
def __init__(
|
||||||
serial=None, fast_mode=False, log=None, results=[]):
|
self,
|
||||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
file_path: Optional[str] = None,
|
||||||
output_folder=output_folder, fast_mode=fast_mode,
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
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, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
|
|
||||||
output = self._adb_command("dumpsys")
|
output = self._adb_command("dumpsys")
|
||||||
if self.output_folder:
|
if self.results_path:
|
||||||
output_path = os.path.join(self.output_folder, "dumpsys.txt")
|
output_path = os.path.join(self.results_path, "dumpsys.txt")
|
||||||
with open(output_path, "w", encoding="utf-8") as handle:
|
with open(output_path, "w", encoding="utf-8") as handle:
|
||||||
handle.write(output)
|
handle.write(output)
|
||||||
|
|
||||||
log.info("Full dumpsys output stored at %s", output_path)
|
self.log.info("Full dumpsys output stored at %s", output_path)
|
||||||
|
|
||||||
self._adb_disconnect()
|
self._adb_disconnect()
|
||||||
|
|||||||
@@ -4,13 +4,12 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.android.parsers import parse_dumpsys_receiver_resolver_table
|
from mvt.android.parsers import parse_dumpsys_receiver_resolver_table
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
INTENT_NEW_OUTGOING_SMS = "android.provider.Telephony.NEW_OUTGOING_SMS"
|
INTENT_NEW_OUTGOING_SMS = "android.provider.Telephony.NEW_OUTGOING_SMS"
|
||||||
INTENT_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
|
INTENT_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
|
||||||
INTENT_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED"
|
INTENT_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED"
|
||||||
@@ -21,15 +20,22 @@ 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."""
|
||||||
|
|
||||||
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
def __init__(
|
||||||
serial=None, fast_mode=False, log=None, results=[]):
|
self,
|
||||||
super().__init__(file_path=file_path, base_folder=base_folder,
|
file_path: Optional[str] = None,
|
||||||
output_folder=output_folder, fast_mode=fast_mode,
|
target_path: Optional[str] = None,
|
||||||
|
results_path: Optional[str] = None,
|
||||||
|
fast_mode: Optional[bool] = False,
|
||||||
|
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, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
self.results = results if results else {}
|
self.results = results if results else {}
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self) -> None:
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -45,19 +51,20 @@ class DumpsysReceivers(AndroidExtraction):
|
|||||||
self.log.info("Found a receiver to intercept incoming data SMS message: \"%s\"",
|
self.log.info("Found a receiver to intercept incoming data SMS message: \"%s\"",
|
||||||
receiver["receiver"])
|
receiver["receiver"])
|
||||||
elif intent == INTENT_PHONE_STATE:
|
elif intent == INTENT_PHONE_STATE:
|
||||||
self.log.info("Found a receiver monitoring telephony state/incoming calls: \"%s\"",
|
self.log.info("Found a receiver monitoring "
|
||||||
|
"telephony state/incoming calls: \"%s\"",
|
||||||
receiver["receiver"])
|
receiver["receiver"])
|
||||||
elif intent == INTENT_NEW_OUTGOING_CALL:
|
elif intent == INTENT_NEW_OUTGOING_CALL:
|
||||||
self.log.info("Found a receiver monitoring outgoing calls: \"%s\"",
|
self.log.info("Found a receiver monitoring outgoing calls: \"%s\"",
|
||||||
receiver["receiver"])
|
receiver["receiver"])
|
||||||
|
|
||||||
ioc = self.indicators.check_app_id(receiver["package_name"])
|
ioc = self.indicators.check_app_id(receiver["package_name"])
|
||||||
if ioc:
|
if ioc:
|
||||||
receiver["matched_indicator"] = ioc
|
receiver["matched_indicator"] = ioc
|
||||||
self.detected.append({intent: receiver})
|
self.detected.append({intent: receiver})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
|
|
||||||
output = self._adb_command("dumpsys package")
|
output = self._adb_command("dumpsys package")
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user