Compare commits

..
15 changed files with 102 additions and 121 deletions

No files matched your search

+88 -9
View File
@@ -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]}"
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -31,3 +31,4 @@ clean:
dist:
$(UV) build
+1 -1
View File
@@ -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`.
+3 -13
View File
@@ -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
+1 -81
View File
@@ -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-keys test-rev-001(¹`0º`8èBu:r:system_app:s0J/system/bin/test_process <R SIGSEGV" SEGV_MAPERR
@@ -1 +0,0 @@
5test/build/fingerprint:13/TEST/V2.0:user/release-keys test-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-keys test-rev-003" (çV0èV8ÐB u:r:shell:s0J/system/bin/app_process64 RSIGILL"
ILL_ILLOPN