mirror of
https://github.com/mvt-project/mvt.git
synced 2026-10-27 08:16:25 +01:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d684feb219 | ||
|
|
db809cbc46 | ||
|
|
f4bfc39c27 | ||
|
|
a645913376 | ||
|
|
4990b22259 | ||
|
|
2a858a2a05 | ||
|
|
4a7100e0c9 |
No files matched your search
@@ -14,10 +14,10 @@ env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
|
||||
# Build each architecture natively, then publish the combined image manifests.
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.architecture.runner }}
|
||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -26,7 +26,15 @@ jobs:
|
||||
id-token: write
|
||||
#
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-24.04
|
||||
name: amd64
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
name: arm64
|
||||
platform:
|
||||
- dockerfile: "Dockerfile"
|
||||
tag-suffix: ""
|
||||
@@ -61,24 +69,95 @@ jobs:
|
||||
type=raw,enable=${{ github.ref_type == 'tag' }},value=${{ github.ref_name }},suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=sha,suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=sha,format=long,suffix=${{ matrix.platform.tag-suffix }}
|
||||
# Register emulators so Buildx can build the ARM64 images on the AMD64 runner.
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
# This step sets up some additional capabilities to generate the provenance and sbom attestations
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||
# Push by digest so architecture builds do not overwrite shared image tags.
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
file: ${{ matrix.platform.dockerfile }}
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
platforms: ${{ matrix.architecture.platform }}
|
||||
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
- name: Export image digest
|
||||
env:
|
||||
DIGEST: ${{ steps.push.outputs.digest }}
|
||||
run: |
|
||||
mkdir -p "${RUNNER_TEMP}/digests"
|
||||
touch "${RUNNER_TEMP}/digests/${DIGEST#sha256:}"
|
||||
- name: Upload image digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ matrix.platform.dockerfile }}-${{ matrix.architecture.name }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
publish-manifests:
|
||||
needs: build-and-push-image
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- dockerfile: "Dockerfile"
|
||||
tag-suffix: ""
|
||||
- dockerfile: "Dockerfile.ios"
|
||||
tag-suffix: "-ios"
|
||||
- dockerfile: "Dockerfile.android"
|
||||
tag-suffix: "-android"
|
||||
steps:
|
||||
- name: Download image digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: digests-${{ matrix.platform.dockerfile }}-*
|
||||
path: ${{ runner.temp }}/digests
|
||||
merge-multiple: true
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{ is_default_branch }},suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=raw,enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }},value=stable,suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=raw,enable=${{ github.event_name == 'release' }},value=${{ github.event.release.tag_name }},suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=raw,enable=${{ github.ref_type == 'tag' }},value=${{ github.ref_name }},suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=sha,suffix=${{ matrix.platform.tag-suffix }}
|
||||
type=sha,format=long,suffix=${{ matrix.platform.tag-suffix }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Publish multi-platform image
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
env:
|
||||
METADATA_JSON: ${{ steps.meta.outputs.json }}
|
||||
run: |
|
||||
mapfile -t tags < <(jq -r '.tags[]' <<< "$METADATA_JSON")
|
||||
tag_args=()
|
||||
for tag in "${tags[@]}"; do
|
||||
tag_args+=(--tag "$tag")
|
||||
done
|
||||
images=()
|
||||
for digest in *; do
|
||||
images+=("${REGISTRY}/${IMAGE_NAME}@sha256:${digest}")
|
||||
done
|
||||
test "${#images[@]}" -eq 2
|
||||
docker buildx imagetools create "${tag_args[@]}" "${images[@]}"
|
||||
docker buildx imagetools inspect "${tags[0]}"
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
|
||||
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.15']
|
||||
env:
|
||||
# Takes precedence over .python-version, which otherwise makes `uv run`
|
||||
# rebuild the venv with 3.10 and test every matrix entry on 3.10.
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Create main image
|
||||
FROM ubuntu:24.04 AS main
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.11.8 /uv /uvx /usr/local/bin/
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/
|
||||
|
||||
LABEL org.opencontainers.image.url="https://mvt.re"
|
||||
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
|
||||
@@ -62,4 +62,4 @@ WORKDIR /home/cases
|
||||
RUN echo 'echo "Mobile Verification Toolkit @ Docker\n------------------------------------\n\nYou can find information about how to use this image for Android (https://github.com/mvt-project/mvt/tree/master/docs/android) and iOS (https://github.com/mvt-project/mvt/tree/master/docs/ios) in the official docs of the project.\n"' >> ~/.bashrc \
|
||||
&& echo 'echo "Note that to perform the debug via USB you might need to give the Docker image access to the USB using \"docker run -it --privileged -v /dev/bus/usb:/dev/bus/usb mvt\" or, preferably, the \"--device=\" parameter.\n"' >> ~/.bashrc
|
||||
|
||||
CMD /bin/bash
|
||||
CMD [ "/bin/bash" ]
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
# Create main image
|
||||
FROM python:3.10.14-alpine3.20 AS main
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.11.8 /uv /uvx /usr/local/bin/
|
||||
FROM python:3.11.17-alpine3.23 AS main
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/
|
||||
|
||||
LABEL org.opencontainers.image.url="https://mvt.re"
|
||||
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
|
||||
@@ -8,7 +8,7 @@ LABEL org.opencontainers.image.source="https://github.com/mvt-project/mvt"
|
||||
LABEL org.opencontainers.image.title="Mobile Verification Toolkit (Android)"
|
||||
LABEL org.opencontainers.image.description="MVT is a forensic tool to look for signs of infection in smartphone devices."
|
||||
LABEL org.opencontainers.image.licenses="MVT License 1.1"
|
||||
LABEL org.opencontainers.image.base.name=docker.io/library/python:3.10.14-alpine3.20
|
||||
LABEL org.opencontainers.image.base.name=docker.io/library/python:3.11.17-alpine3.23
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache \
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# Main image
|
||||
# ----------
|
||||
FROM ubuntu:24.04 AS main
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.11.8 /uv /uvx /usr/local/bin/
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/
|
||||
|
||||
LABEL org.opencontainers.image.url="https://mvt.re"
|
||||
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
|
||||
|
||||
+1
-1
@@ -66,4 +66,4 @@ docker run --rm -it \
|
||||
ghcr.io/mvt-project/mvt:latest-ios lockdown info
|
||||
```
|
||||
|
||||
If you built the image from source, replace `ghcr.io/mvt-project/mvt` with `mvt`.
|
||||
If you built the image from source, replace `ghcr.io/mvt-project/mvt` with `mvt`.
|
||||
@@ -145,7 +145,7 @@ class TombstoneCrashArtifact(AndroidArtifact):
|
||||
|
||||
# Add some extra metadata
|
||||
tombstone_dict["timestamp"] = self._parse_timestamp_string(
|
||||
tombstone_pb.timestamp, file_timestamp
|
||||
tombstone_pb.timestamp
|
||||
)
|
||||
tombstone_dict["file_name"] = file_name
|
||||
tombstone_dict["file_timestamp"] = convert_datetime_to_iso(file_timestamp)
|
||||
@@ -288,21 +288,11 @@ class TombstoneCrashArtifact(AndroidArtifact):
|
||||
|
||||
def _load_timestamp_line(self, line: str, tombstone: dict) -> bool:
|
||||
timestamp = line.split(":", 1)[1].strip()
|
||||
tombstone["timestamp"] = self._parse_timestamp_string(timestamp, None)
|
||||
tombstone["timestamp"] = self._parse_timestamp_string(timestamp)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _parse_timestamp_string(
|
||||
timestamp: str, fallback_timestamp: Optional[datetime.datetime]
|
||||
) -> str:
|
||||
"""Parse timestamp string, using fallback if timestamp is empty."""
|
||||
# Handle empty or whitespace-only timestamps
|
||||
if not timestamp or not timestamp.strip():
|
||||
if fallback_timestamp:
|
||||
return convert_datetime_to_iso(fallback_timestamp)
|
||||
else:
|
||||
raise ValueError("Empty timestamp with no fallback provided")
|
||||
|
||||
def _parse_timestamp_string(timestamp: str) -> str:
|
||||
timestamp_parsed = parser.parse(timestamp)
|
||||
return convert_datetime_to_iso(timestamp_parsed)
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ from mvt.common.module import MVTModule
|
||||
|
||||
from .dumpsys_accessibility import DumpsysAccessibility
|
||||
from .dumpsys_activities import DumpsysActivities
|
||||
from .dumpsys_adb_state import DumpsysADBState
|
||||
from .dumpsys_appops import DumpsysAppops
|
||||
from .dumpsys_battery_daily import DumpsysBatteryDaily
|
||||
from .dumpsys_battery_history import DumpsysBatteryHistory
|
||||
@@ -16,6 +15,7 @@ from .dumpsys_getprop import DumpsysGetProp
|
||||
from .dumpsys_packages import DumpsysPackages
|
||||
from .dumpsys_platform_compat import DumpsysPlatformCompat
|
||||
from .dumpsys_receivers import DumpsysReceivers
|
||||
from .dumpsys_adb_state import DumpsysADBState
|
||||
from .fs_timestamps import BugReportTimestamps
|
||||
from .tombstones import Tombstones
|
||||
from .mounts import Mounts
|
||||
|
||||
@@ -68,11 +68,6 @@ class Tombstones(TombstoneCrashArtifact, BugReportModule):
|
||||
}
|
||||
try:
|
||||
data = self._get_file_content(file_path)
|
||||
if not data:
|
||||
self.log.debug("Skipping empty tombstone file %s (0 bytes)", file_path)
|
||||
source_info["error"] = "empty tombstone"
|
||||
parsed_sources[source] = source_info
|
||||
continue
|
||||
if source == "protobuf":
|
||||
record = self.parse_protobuf_record(
|
||||
file_name, file_timestamp, data
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# Copyright (c) 2021-2023 The MVT Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
import datetime
|
||||
import os
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -130,83 +130,3 @@ class TestTombstoneCrashArtifact:
|
||||
|
||||
# Tombstones include an explicit offset, so normalize them to UTC.
|
||||
assert tombstone_result.get("timestamp") == "2023-04-12 10:32:40.518290"
|
||||
|
||||
def test_tombstone_pb_empty_timestamp(self):
|
||||
"""Test parsing a protobuf tombstone with an empty timestamp."""
|
||||
tombstone_artifact = TombstoneCrashArtifact()
|
||||
artifact_path = "android_data/bugreport_tombstones/tombstone_empty_timestamp.pb"
|
||||
file = get_artifact(artifact_path)
|
||||
with open(file, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
file_name = os.path.basename(artifact_path)
|
||||
file_timestamp = datetime.datetime(2024, 1, 15, 10, 30, 45, 123456)
|
||||
tombstone_artifact.parse_protobuf(file_name, file_timestamp, data)
|
||||
|
||||
assert len(tombstone_artifact.results) == 1
|
||||
result = tombstone_artifact.results[0]
|
||||
|
||||
# When tombstone has empty timestamp, should use file modification time
|
||||
assert result.get("timestamp") == "2024-01-15 10:30:45.123456"
|
||||
assert result.get("pid") == 12345
|
||||
assert result.get("uid") == 1000
|
||||
assert result.get("signal_info", {}).get("name") == "SIGSEGV"
|
||||
|
||||
def test_tombstone_pb_empty_timestamp_with_threads(self):
|
||||
"""Test parsing a protobuf tombstone with empty timestamp and thread info."""
|
||||
tombstone_artifact = TombstoneCrashArtifact()
|
||||
artifact_path = "android_data/bugreport_tombstones/tombstone_empty_timestamp_with_threads.pb"
|
||||
file = get_artifact(artifact_path)
|
||||
with open(file, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
file_name = os.path.basename(artifact_path)
|
||||
file_timestamp = datetime.datetime(2024, 2, 20, 14, 15, 30, 0)
|
||||
tombstone_artifact.parse_protobuf(file_name, file_timestamp, data)
|
||||
|
||||
assert len(tombstone_artifact.results) == 1
|
||||
result = tombstone_artifact.results[0]
|
||||
|
||||
# Verify timestamp fallback
|
||||
assert result.get("timestamp") == "2024-02-20 14:15:30.000000"
|
||||
assert result.get("pid") == 9876
|
||||
assert result.get("uid") == 10001
|
||||
assert result.get("signal_info", {}).get("name") == "SIGABRT"
|
||||
assert result.get("process_name") == "ExampleThread"
|
||||
|
||||
def test_tombstone_pb_whitespace_timestamp(self):
|
||||
"""Test parsing a protobuf tombstone with whitespace-only timestamp."""
|
||||
tombstone_artifact = TombstoneCrashArtifact()
|
||||
artifact_path = (
|
||||
"android_data/bugreport_tombstones/tombstone_whitespace_timestamp.pb"
|
||||
)
|
||||
file = get_artifact(artifact_path)
|
||||
with open(file, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
file_name = os.path.basename(artifact_path)
|
||||
file_timestamp = datetime.datetime(2024, 3, 10, 8, 0, 0, 0)
|
||||
tombstone_artifact.parse_protobuf(file_name, file_timestamp, data)
|
||||
|
||||
assert len(tombstone_artifact.results) == 1
|
||||
result = tombstone_artifact.results[0]
|
||||
|
||||
# Verify whitespace timestamp is treated as empty
|
||||
assert result.get("timestamp") == "2024-03-10 08:00:00.000000"
|
||||
assert result.get("pid") == 11111
|
||||
assert result.get("uid") == 2000
|
||||
assert result.get("signal_info", {}).get("name") == "SIGILL"
|
||||
|
||||
def test_tombstone_pb_empty_file(self):
|
||||
"""Test that empty (0 bytes) tombstone files are handled gracefully."""
|
||||
artifact_path = "android_data/bugreport_tombstones/tombstone_empty_file.pb"
|
||||
file = get_artifact(artifact_path)
|
||||
with open(file, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
# Verify the file is actually empty
|
||||
assert len(data) == 0, "Test file should be empty (0 bytes)"
|
||||
|
||||
# Empty files should be skipped in the module (not parsed)
|
||||
# The actual skipping happens in the Tombstones module's run() method
|
||||
# This test verifies that empty data is detectable
|
||||
Whitespace-only changes.
@@ -1 +0,0 @@
|
||||
5test/build/fingerprint:12/TEST/V1.0:user/release-keystest-rev-001(¹`0º`8èBu:r:system_app:s0J/system/bin/test_process <RSIGSEGV"SEGV_MAPERR
|
||||
-1
@@ -1 +0,0 @@
|
||||
5test/build/fingerprint:13/TEST/V2.0:user/release-keystest-rev-002(”M0•M8‘NBu:r:untrusted_app:s0J-/data/app/com.example.app/lib/arm64/libapp.so xRSIGABRT"SI_TKILL‚•M•M␍ExampleThread
|
||||
@@ -1,2 +0,0 @@
|
||||
5test/build/fingerprint:11/TEST/V3.0:user/release-keystest-rev-003" (çV0èV8ÐBu:r:shell:s0J/system/bin/app_process64 RSIGILL"
|
||||
ILL_ILLOPN
|
||||
Reference in new issue
Block a user