Compare commits

..

22 Commits

Author SHA1 Message Date
besendorf
6ba6d2ebb8 Merge branch 'main' into fix/stix2-hash-key-normalization 2026-04-07 20:40:01 +02:00
Janik Besendorf
e80a05992e Fix STIX2 hash key parsing to accept spec-compliant algorithm names
The STIX2 specification requires single quotes around hash algorithm
names that contain hyphens (e.g. file:hashes.'SHA-256'). MVT only
accepted a non-standard lowercase form (file:hashes.sha256), silently
dropping any indicators using the spec-correct spelling.

Normalize hash algorithm keys in _process_indicator by stripping quotes
and hyphens from the algorithm portion before matching, so all of the
following are accepted for SHA-256, SHA-1 and MD5:

  file:hashes.'SHA-256'   (STIX2 spec)
  file:hashes.SHA-256
  file:hashes.SHA256
  file:hashes.sha256      (previously the only accepted form)

The same normalization is applied to app:cert.* keys.

Update generate_stix.py to use the spec-compliant quoted forms, and add
test_parse_stix2_hash_key_variants to cover all spelling variants.
2026-04-07 20:38:37 +02:00
besendorf
fd31f31aae Fix betterproto2 migration: update generated proto code and callers (#765)
The dependency switch from betterproto to betterproto2 was incomplete.
This updates all affected files to use the betterproto2 API:

- tombstone.py: rewrite generated code to use betterproto2.field() with
  explicit TYPE_* constants, repeated/optional/group flags, and map_meta()
  for map fields
- tombstone_crashes.py: update import and fix to_dict() call to use
  keyword-only casing= argument required by betterproto2
- pyproject.toml: replace betterproto[compiler] dev dep with betterproto2-compiler
- Makefile: update protoc plugin flag to --python_betterproto2_out
2026-04-07 14:09:38 +02:00
Janik Besendorf
47330e4e45 Fix betterproto2 migration: update generated proto code and callers
The dependency switch from betterproto to betterproto2 was incomplete.
This updates all affected files to use the betterproto2 API:

- tombstone.py: rewrite generated code to use betterproto2.field() with
  explicit TYPE_* constants, repeated/optional/group flags, and map_meta()
  for map fields
- tombstone_crashes.py: update import and fix to_dict() call to use
  keyword-only casing= argument required by betterproto2
- pyproject.toml: replace betterproto[compiler] dev dep with betterproto2-compiler
- Makefile: update protoc plugin flag to --python_betterproto2_out
2026-04-07 14:07:19 +02:00
dependabot[bot]
9305c655bb Bump rich from 14.2.0 to 14.3.3 (#760)
Bumps [rich](https://github.com/Textualize/rich) from 14.2.0 to 14.3.3.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v14.2.0...v14.3.3)

---
updated-dependencies:
- dependency-name: rich
  dependency-version: 14.3.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 18:31:44 +02:00
dependabot[bot]
ec43f93eb9 Bump click from 8.3.1 to 8.3.2 (#762)
Bumps [click](https://github.com/pallets/click) from 8.3.1 to 8.3.2.
- [Release notes](https://github.com/pallets/click/releases)
- [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/click/compare/8.3.1...8.3.2)

---
updated-dependencies:
- dependency-name: click
  dependency-version: 8.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 18:29:22 +02:00
dependabot[bot]
7e398310b6 Bump tzdata from 2025.3 to 2026.1 (#761)
Bumps [tzdata](https://github.com/python/tzdata) from 2025.3 to 2026.1.
- [Release notes](https://github.com/python/tzdata/releases)
- [Changelog](https://github.com/python/tzdata/blob/master/NEWS.md)
- [Commits](https://github.com/python/tzdata/compare/2025.3...2026.1)

---
updated-dependencies:
- dependency-name: tzdata
  dependency-version: '2026.1'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: besendorf <janik@besendorf.org>
2026-04-06 18:25:44 +02:00
besendorf
600e6dcf8f Replace betterproto with betterproto2 in dependencies (#763) 2026-04-06 18:23:33 +02:00
dependabot[bot]
3d1407b78c Bump rich from 14.1.0 to 14.2.0 (#728)
Bumps [rich](https://github.com/Textualize/rich) from 14.1.0 to 14.2.0.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v14.1.0...v14.2.0)

---
updated-dependencies:
- dependency-name: rich
  dependency-version: 14.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: besendorf <janik@besendorf.org>
2026-04-06 17:27:15 +02:00
dependabot[bot]
d41ff6d604 Bump cryptography from 46.0.5 to 46.0.6 (#755)
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.5 to 46.0.6.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.5...46.0.6)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.6
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 17:21:00 +02:00
Donncha Ó Cearbhaill
ccd563f2ba Improve docker images tags based on PR #740 (#754)
* Modify docker image deployment behavior

* Use build-push-action to create image provenance & sbom attestations

* Upgrade github actions

* Fix inconsistent capitialization in dockerfiles

---------

Co-authored-by: scribblemaniac <scribblemaniac@users.noreply.github.com>
2026-04-01 18:42:30 +02:00
dependabot[bot]
c681d264b3 Bump tzdata from 2025.2 to 2025.3 (#729)
Bumps [tzdata](https://github.com/python/tzdata) from 2025.2 to 2025.3.
- [Release notes](https://github.com/python/tzdata/releases)
- [Changelog](https://github.com/python/tzdata/blob/master/NEWS.md)
- [Commits](https://github.com/python/tzdata/compare/2025.2...2025.3)

---
updated-dependencies:
- dependency-name: tzdata
  dependency-version: '2025.3'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: besendorf <janik@besendorf.org>
2026-04-01 10:57:30 +02:00
dependabot[bot]
261b0ae000 Bump pydantic-settings from 2.10.1 to 2.13.1 (#750)
Bumps [pydantic-settings](https://github.com/pydantic/pydantic-settings) from 2.10.1 to 2.13.1.
- [Release notes](https://github.com/pydantic/pydantic-settings/releases)
- [Commits](https://github.com/pydantic/pydantic-settings/compare/2.10.1...v2.13.1)

---
updated-dependencies:
- dependency-name: pydantic-settings
  dependency-version: 2.13.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: besendorf <janik@besendorf.org>
2026-04-01 10:54:19 +02:00
dependabot[bot]
89d30e84f4 Bump packaging from 25.0 to 26.0 (#751)
Bumps [packaging](https://github.com/pypa/packaging) from 25.0 to 26.0.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/25.0...26.0)

---
updated-dependencies:
- dependency-name: packaging
  dependency-version: '26.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: besendorf <janik@besendorf.org>
2026-04-01 10:51:50 +02:00
dependabot[bot]
557d0a0cd6 Bump requests from 2.32.5 to 2.33.1 (#752)
Bumps [requests](https://github.com/psf/requests) from 2.32.5 to 2.33.1.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.33.1)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 10:48:45 +02:00
github-actions[bot]
134bfce90f Add new iOS versions and build numbers (#743)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2026-03-26 12:16:59 -04:00
Tek
0141da4293 Fixes bug in IOC import (#749) 2026-03-25 23:23:08 +01:00
dependabot[bot]
5cba61b180 Bump mkdocstrings from 0.30.1 to 1.0.0 (#730)
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.30.1 to 1.0.0.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.30.1...1.0.0)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: besendorf <janik@besendorf.org>
2026-03-25 15:24:07 +01:00
dependabot[bot]
29475acb47 Bump click from 8.3.0 to 8.3.1 (#731)
Bumps [click](https://github.com/pallets/click) from 8.3.0 to 8.3.1.
- [Release notes](https://github.com/pallets/click/releases)
- [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/click/compare/8.3.0...8.3.1)

---
updated-dependencies:
- dependency-name: click
  dependency-version: 8.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: besendorf <janik@besendorf.org>
2026-03-25 14:37:59 +01:00
dependabot[bot]
1d5c83582c Bump pydantic from 2.12.3 to 2.12.5 (#732)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.12.3 to 2.12.5.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.12.3...v2.12.5)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-version: 2.12.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: besendorf <janik@besendorf.org>
2026-03-25 14:26:09 +01:00
dependabot[bot]
2dd1428787 Bump cryptography from 46.0.3 to 46.0.5 (#747)
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.3 to 46.0.5.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/46.0.3...46.0.5)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 08:49:46 +01:00
besendorf
f2d9f420f2 Detect uninstall and downgrade in battery daily (#736) 2026-03-16 12:32:54 +01:00
18 changed files with 314 additions and 202 deletions

View File

@@ -11,7 +11,7 @@ jobs:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v0.5.0
- uses: actions/add-to-project@v1
with:
# You can target a project in a different organization
# to the issue

View File

@@ -7,14 +7,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: 3.9
cache: 'pip'
- name: Checkout
uses: actions/checkout@master
- name: Install Dependencies
run: |
pip install mypy

View File

@@ -4,6 +4,8 @@ name: Create and publish a Docker image
# Configures this workflow to run every time a release is published.
on:
workflow_dispatch:
push:
branches: [main]
release:
types: [published]
@@ -23,9 +25,18 @@ jobs:
attestations: write
id-token: write
#
strategy:
matrix:
platform:
- dockerfile: "Dockerfile"
tag-suffix: ""
- dockerfile: "Dockerfile.ios"
tag-suffix: "-ios"
- dockerfile: "Dockerfile.android"
tag-suffix: "-android"
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
@@ -36,26 +47,33 @@ jobs:
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
uses: docker/metadata-action@v5
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 }}
# This step sets up some additional capabilities to generate the provenance and sbom attestations
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# 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.
- name: Build and push Docker image
id: push
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
uses: docker/build-push-action@v6
with:
file: ${{ matrix.platform.dockerfile }}
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)."
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
provenance: mode=max
sbom: true

View File

@@ -11,14 +11,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: 3.9
cache: 'pip'
- name: Checkout
uses: actions/checkout@master
- name: Install Dependencies
run: |
pip install ruff

View File

@@ -15,9 +15,9 @@ jobs:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install Python dependencies

View File

@@ -16,7 +16,7 @@ jobs:
- name: Run script to fetch latest iOS releases from Apple RSS feed.
run: python3 .github/workflows/scripts/update-ios-releases.py
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
uses: peter-evans/create-pull-request@v8
with:
title: '[auto] Update iOS releases and versions'
commit-message: Add new iOS versions and build numbers
@@ -27,4 +27,4 @@ jobs:
add-paths: |
*.json
labels: |
automated pr
automated pr

View File

@@ -1,6 +1,6 @@
# Base image for building libraries
# ---------------------------------
FROM ubuntu:22.04 as build-base
FROM ubuntu:22.04 AS build-base
ARG DEBIAN_FRONTEND=noninteractive
@@ -22,7 +22,7 @@ RUN apt-get update \
# libplist
# --------
FROM build-base as build-libplist
FROM build-base AS build-libplist
# Build
RUN git clone https://github.com/libimobiledevice/libplist && cd libplist \
@@ -32,7 +32,7 @@ RUN git clone https://github.com/libimobiledevice/libplist && cd libplist \
# libimobiledevice-glue
# ---------------------
FROM build-base as build-libimobiledevice-glue
FROM build-base AS build-libimobiledevice-glue
# Install dependencies
COPY --from=build-libplist /build /
@@ -45,7 +45,7 @@ RUN git clone https://github.com/libimobiledevice/libimobiledevice-glue && cd li
# libtatsu
# --------
FROM build-base as build-libtatsu
FROM build-base AS build-libtatsu
# Install dependencies
COPY --from=build-libplist /build /
@@ -58,7 +58,7 @@ RUN git clone https://github.com/libimobiledevice/libtatsu && cd libtatsu \
# libusbmuxd
# ----------
FROM build-base as build-libusbmuxd
FROM build-base AS build-libusbmuxd
# Install dependencies
COPY --from=build-libplist /build /
@@ -72,7 +72,7 @@ RUN git clone https://github.com/libimobiledevice/libusbmuxd && cd libusbmuxd \
# libimobiledevice
# ----------------
FROM build-base as build-libimobiledevice
FROM build-base AS build-libimobiledevice
# Install dependencies
COPY --from=build-libplist /build /
@@ -88,7 +88,7 @@ RUN git clone https://github.com/libimobiledevice/libimobiledevice && cd libimob
# usbmuxd
# -------
FROM build-base as build-usbmuxd
FROM build-base AS build-usbmuxd
# Install dependencies
COPY --from=build-libplist /build /
@@ -103,7 +103,7 @@ RUN git clone https://github.com/libimobiledevice/usbmuxd && cd usbmuxd \
# Create main image
FROM ubuntu:24.04 as main
FROM ubuntu:24.04 AS main
LABEL org.opencontainers.image.url="https://mvt.re"
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"

View File

@@ -1,5 +1,5 @@
# Create main image
FROM python:3.10.14-alpine3.20 as main
FROM python:3.10.14-alpine3.20 AS main
LABEL org.opencontainers.image.url="https://mvt.re"
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"

View File

@@ -1,6 +1,6 @@
# Base image for building libraries
# ---------------------------------
FROM ubuntu:22.04 as build-base
FROM ubuntu:22.04 AS build-base
ARG DEBIAN_FRONTEND=noninteractive
@@ -22,7 +22,7 @@ RUN apt-get update \
# libplist
# --------
FROM build-base as build-libplist
FROM build-base AS build-libplist
# Build
RUN git clone https://github.com/libimobiledevice/libplist && cd libplist \
@@ -32,7 +32,7 @@ RUN git clone https://github.com/libimobiledevice/libplist && cd libplist \
# libimobiledevice-glue
# ---------------------
FROM build-base as build-libimobiledevice-glue
FROM build-base AS build-libimobiledevice-glue
# Install dependencies
COPY --from=build-libplist /build /
@@ -45,7 +45,7 @@ RUN git clone https://github.com/libimobiledevice/libimobiledevice-glue && cd li
# libtatsu
# --------
FROM build-base as build-libtatsu
FROM build-base AS build-libtatsu
# Install dependencies
COPY --from=build-libplist /build /
@@ -58,7 +58,7 @@ RUN git clone https://github.com/libimobiledevice/libtatsu && cd libtatsu \
# libusbmuxd
# ----------
FROM build-base as build-libusbmuxd
FROM build-base AS build-libusbmuxd
# Install dependencies
COPY --from=build-libplist /build /
@@ -72,7 +72,7 @@ RUN git clone https://github.com/libimobiledevice/libusbmuxd && cd libusbmuxd \
# libimobiledevice
# ----------------
FROM build-base as build-libimobiledevice
FROM build-base AS build-libimobiledevice
# Install dependencies
COPY --from=build-libplist /build /
@@ -88,7 +88,7 @@ RUN git clone https://github.com/libimobiledevice/libimobiledevice && cd libimob
# usbmuxd
# -------
FROM build-base as build-usbmuxd
FROM build-base AS build-usbmuxd
# Install dependencies
COPY --from=build-libplist /build /
@@ -104,7 +104,7 @@ RUN git clone https://github.com/libimobiledevice/usbmuxd && cd usbmuxd \
# Main image
# ----------
FROM python:3.10.14-alpine3.20 as main
FROM python:3.10.14-alpine3.20 AS main
LABEL org.opencontainers.image.url="https://mvt.re"
LABEL org.opencontainers.image.documentation="https://docs.mvt.re"

View File

@@ -23,7 +23,7 @@ test-requirements:
generate-proto-parsers:
# Generate python parsers for protobuf files
PROTO_FILES=$$(find src/mvt/android/parsers/proto/ -iname "*.proto"); \
protoc -Isrc/mvt/android/parsers/proto/ --python_betterproto_out=src/mvt/android/parsers/proto/ $$PROTO_FILES
protoc -Isrc/mvt/android/parsers/proto/ --python_betterproto2_out=src/mvt/android/parsers/proto/ $$PROTO_FILES
clean:
rm -rf $(PWD)/build $(PWD)/dist $(PWD)/src/mvt.egg-info

View File

@@ -2,4 +2,4 @@ mkdocs==1.6.1
mkdocs-autorefs==1.4.3
mkdocs-material==9.6.20
mkdocs-material-extensions==1.3.1
mkdocstrings==0.30.1
mkdocstrings==1.0.0

View File

@@ -17,25 +17,25 @@ classifiers = [
"Programming Language :: Python",
]
dependencies = [
"click==8.3.0",
"rich==14.1.0",
"click==8.3.2",
"rich==14.3.3",
"tld==0.13.1",
"requests==2.32.5",
"requests==2.33.1",
"simplejson==3.20.2",
"packaging==25.0",
"packaging==26.0",
"appdirs==1.4.4",
"iOSbackup==0.9.925",
"adb-shell[usb]==0.4.4",
"libusb1==3.3.1",
"cryptography==46.0.3",
"cryptography==46.0.6",
"PyYAML>=6.0.2",
"pyahocorasick==2.2.0",
"betterproto==1.2.5",
"pydantic==2.12.3",
"pydantic-settings==2.10.1",
"betterproto2==0.9.1",
"pydantic==2.12.5",
"pydantic-settings==2.13.1",
"NSKeyedUnArchiver==1.5.2",
"python-dateutil==2.9.0.post0",
"tzdata==2025.2",
"tzdata==2026.1",
]
requires-python = ">= 3.10"
@@ -57,7 +57,7 @@ dev = [
"stix2>=3.0.1",
"ruff>=0.1.6",
"mypy>=1.7.1",
"betterproto[compiler]",
"betterproto2-compiler",
]
[build-system]
@@ -81,8 +81,8 @@ addopts = "-ra -q --cov=mvt --cov-report html --junitxml=pytest.xml --cov-report
testpaths = ["tests"]
[tool.ruff]
select = ["C90", "E", "F", "W"] # flake8 default set
ignore = [
lint.select = ["C90", "E", "F", "W"] # flake8 default set
lint.ignore = [
"E501", # don't enforce line length violations
"C901", # complex-structure
@@ -95,10 +95,10 @@ ignore = [
# "E203", # whitespace-before-punctuation
]
[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # unused-import
[tool.ruff.mccabe]
[tool.ruff.lint.mccabe]
max-complexity = 10
[tool.setuptools]

View File

@@ -7,7 +7,7 @@ import datetime
from typing import List, Optional, Union
import pydantic
import betterproto
import betterproto2
from dateutil import parser
from mvt.common.utils import convert_datetime_to_iso
@@ -124,7 +124,7 @@ class TombstoneCrashArtifact(AndroidArtifact):
"""Parse Android tombstone crash files from a protobuf object."""
tombstone_pb = Tombstone().parse(data)
tombstone_dict = tombstone_pb.to_dict(
betterproto.Casing.SNAKE, include_default_values=True
casing=betterproto2.Casing.SNAKE, include_default_values=True
)
# Add some extra metadata

View File

@@ -1,13 +1,12 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# sources: tombstone.proto
# plugin: python-betterproto
# plugin: python-betterproto2
from dataclasses import dataclass
from typing import Dict, List
import betterproto
import betterproto2
class Architecture(betterproto.Enum):
class Architecture(betterproto2.Enum):
ARM32 = 0
ARM64 = 1
X86 = 2
@@ -16,12 +15,12 @@ class Architecture(betterproto.Enum):
NONE = 5
class MemoryErrorTool(betterproto.Enum):
class MemoryErrorTool(betterproto2.Enum):
GWP_ASAN = 0
SCUDO = 1
class MemoryErrorType(betterproto.Enum):
class MemoryErrorType(betterproto2.Enum):
UNKNOWN = 0
USE_AFTER_FREE = 1
DOUBLE_FREE = 2
@@ -30,179 +29,179 @@ class MemoryErrorType(betterproto.Enum):
BUFFER_UNDERFLOW = 5
@dataclass
class CrashDetail(betterproto.Message):
@dataclass(eq=False, repr=False)
class CrashDetail(betterproto2.Message):
"""
NOTE TO OEMS: If you add custom fields to this proto, do not use numbers in
the reserved range.
"""
name: bytes = betterproto.bytes_field(1)
data: bytes = betterproto.bytes_field(2)
name: "bytes" = betterproto2.field(1, betterproto2.TYPE_BYTES)
data: "bytes" = betterproto2.field(2, betterproto2.TYPE_BYTES)
@dataclass
class StackHistoryBufferEntry(betterproto.Message):
addr: "BacktraceFrame" = betterproto.message_field(1)
fp: int = betterproto.uint64_field(2)
tag: int = betterproto.uint64_field(3)
@dataclass(eq=False, repr=False)
class StackHistoryBufferEntry(betterproto2.Message):
addr: "BacktraceFrame | None" = betterproto2.field(1, betterproto2.TYPE_MESSAGE, optional=True)
fp: "int" = betterproto2.field(2, betterproto2.TYPE_UINT64)
tag: "int" = betterproto2.field(3, betterproto2.TYPE_UINT64)
@dataclass
class StackHistoryBuffer(betterproto.Message):
tid: int = betterproto.uint64_field(1)
entries: List["StackHistoryBufferEntry"] = betterproto.message_field(2)
@dataclass(eq=False, repr=False)
class StackHistoryBuffer(betterproto2.Message):
tid: "int" = betterproto2.field(1, betterproto2.TYPE_UINT64)
entries: "list[StackHistoryBufferEntry]" = betterproto2.field(2, betterproto2.TYPE_MESSAGE, repeated=True)
@dataclass
class Tombstone(betterproto.Message):
arch: "Architecture" = betterproto.enum_field(1)
guest_arch: "Architecture" = betterproto.enum_field(24)
build_fingerprint: str = betterproto.string_field(2)
revision: str = betterproto.string_field(3)
timestamp: str = betterproto.string_field(4)
pid: int = betterproto.uint32_field(5)
tid: int = betterproto.uint32_field(6)
uid: int = betterproto.uint32_field(7)
selinux_label: str = betterproto.string_field(8)
command_line: List[str] = betterproto.string_field(9)
@dataclass(eq=False, repr=False)
class Tombstone(betterproto2.Message):
arch: "Architecture" = betterproto2.field(1, betterproto2.TYPE_ENUM, default_factory=lambda: Architecture(0))
guest_arch: "Architecture" = betterproto2.field(24, betterproto2.TYPE_ENUM, default_factory=lambda: Architecture(0))
build_fingerprint: "str" = betterproto2.field(2, betterproto2.TYPE_STRING)
revision: "str" = betterproto2.field(3, betterproto2.TYPE_STRING)
timestamp: "str" = betterproto2.field(4, betterproto2.TYPE_STRING)
pid: "int" = betterproto2.field(5, betterproto2.TYPE_UINT32)
tid: "int" = betterproto2.field(6, betterproto2.TYPE_UINT32)
uid: "int" = betterproto2.field(7, betterproto2.TYPE_UINT32)
selinux_label: "str" = betterproto2.field(8, betterproto2.TYPE_STRING)
command_line: "list[str]" = betterproto2.field(9, betterproto2.TYPE_STRING, repeated=True)
# Process uptime in seconds.
process_uptime: int = betterproto.uint32_field(20)
signal_info: "Signal" = betterproto.message_field(10)
abort_message: str = betterproto.string_field(14)
crash_details: List["CrashDetail"] = betterproto.message_field(21)
causes: List["Cause"] = betterproto.message_field(15)
threads: Dict[int, "Thread"] = betterproto.map_field(
16, betterproto.TYPE_UINT32, betterproto.TYPE_MESSAGE
process_uptime: "int" = betterproto2.field(20, betterproto2.TYPE_UINT32)
signal_info: "Signal | None" = betterproto2.field(10, betterproto2.TYPE_MESSAGE, optional=True)
abort_message: "str" = betterproto2.field(14, betterproto2.TYPE_STRING)
crash_details: "list[CrashDetail]" = betterproto2.field(21, betterproto2.TYPE_MESSAGE, repeated=True)
causes: "list[Cause]" = betterproto2.field(15, betterproto2.TYPE_MESSAGE, repeated=True)
threads: "dict[int, Thread]" = betterproto2.field(
16, betterproto2.TYPE_MAP, map_meta=betterproto2.map_meta(betterproto2.TYPE_UINT32, betterproto2.TYPE_MESSAGE)
)
guest_threads: Dict[int, "Thread"] = betterproto.map_field(
25, betterproto.TYPE_UINT32, betterproto.TYPE_MESSAGE
guest_threads: "dict[int, Thread]" = betterproto2.field(
25, betterproto2.TYPE_MAP, map_meta=betterproto2.map_meta(betterproto2.TYPE_UINT32, betterproto2.TYPE_MESSAGE)
)
memory_mappings: List["MemoryMapping"] = betterproto.message_field(17)
log_buffers: List["LogBuffer"] = betterproto.message_field(18)
open_fds: List["FD"] = betterproto.message_field(19)
page_size: int = betterproto.uint32_field(22)
has_been_16kb_mode: bool = betterproto.bool_field(23)
stack_history_buffer: "StackHistoryBuffer" = betterproto.message_field(26)
memory_mappings: "list[MemoryMapping]" = betterproto2.field(17, betterproto2.TYPE_MESSAGE, repeated=True)
log_buffers: "list[LogBuffer]" = betterproto2.field(18, betterproto2.TYPE_MESSAGE, repeated=True)
open_fds: "list[FD]" = betterproto2.field(19, betterproto2.TYPE_MESSAGE, repeated=True)
page_size: "int" = betterproto2.field(22, betterproto2.TYPE_UINT32)
has_been_16kb_mode: "bool" = betterproto2.field(23, betterproto2.TYPE_BOOL)
stack_history_buffer: "StackHistoryBuffer | None" = betterproto2.field(26, betterproto2.TYPE_MESSAGE, optional=True)
@dataclass
class Signal(betterproto.Message):
number: int = betterproto.int32_field(1)
name: str = betterproto.string_field(2)
code: int = betterproto.int32_field(3)
code_name: str = betterproto.string_field(4)
has_sender: bool = betterproto.bool_field(5)
sender_uid: int = betterproto.int32_field(6)
sender_pid: int = betterproto.int32_field(7)
has_fault_address: bool = betterproto.bool_field(8)
fault_address: int = betterproto.uint64_field(9)
@dataclass(eq=False, repr=False)
class Signal(betterproto2.Message):
number: "int" = betterproto2.field(1, betterproto2.TYPE_INT32)
name: "str" = betterproto2.field(2, betterproto2.TYPE_STRING)
code: "int" = betterproto2.field(3, betterproto2.TYPE_INT32)
code_name: "str" = betterproto2.field(4, betterproto2.TYPE_STRING)
has_sender: "bool" = betterproto2.field(5, betterproto2.TYPE_BOOL)
sender_uid: "int" = betterproto2.field(6, betterproto2.TYPE_INT32)
sender_pid: "int" = betterproto2.field(7, betterproto2.TYPE_INT32)
has_fault_address: "bool" = betterproto2.field(8, betterproto2.TYPE_BOOL)
fault_address: "int" = betterproto2.field(9, betterproto2.TYPE_UINT64)
# Note, may or may not contain the dump of the actual memory contents.
# Currently, on arm64, we only include metadata, and not the contents.
fault_adjacent_metadata: "MemoryDump" = betterproto.message_field(10)
fault_adjacent_metadata: "MemoryDump | None" = betterproto2.field(10, betterproto2.TYPE_MESSAGE, optional=True)
@dataclass
class HeapObject(betterproto.Message):
address: int = betterproto.uint64_field(1)
size: int = betterproto.uint64_field(2)
allocation_tid: int = betterproto.uint64_field(3)
allocation_backtrace: List["BacktraceFrame"] = betterproto.message_field(4)
deallocation_tid: int = betterproto.uint64_field(5)
deallocation_backtrace: List["BacktraceFrame"] = betterproto.message_field(6)
@dataclass(eq=False, repr=False)
class HeapObject(betterproto2.Message):
address: "int" = betterproto2.field(1, betterproto2.TYPE_UINT64)
size: "int" = betterproto2.field(2, betterproto2.TYPE_UINT64)
allocation_tid: "int" = betterproto2.field(3, betterproto2.TYPE_UINT64)
allocation_backtrace: "list[BacktraceFrame]" = betterproto2.field(4, betterproto2.TYPE_MESSAGE, repeated=True)
deallocation_tid: "int" = betterproto2.field(5, betterproto2.TYPE_UINT64)
deallocation_backtrace: "list[BacktraceFrame]" = betterproto2.field(6, betterproto2.TYPE_MESSAGE, repeated=True)
@dataclass
class MemoryError(betterproto.Message):
tool: "MemoryErrorTool" = betterproto.enum_field(1)
type: "MemoryErrorType" = betterproto.enum_field(2)
heap: "HeapObject" = betterproto.message_field(3, group="location")
@dataclass(eq=False, repr=False)
class MemoryError(betterproto2.Message):
tool: "MemoryErrorTool" = betterproto2.field(1, betterproto2.TYPE_ENUM, default_factory=lambda: MemoryErrorTool(0))
type: "MemoryErrorType" = betterproto2.field(2, betterproto2.TYPE_ENUM, default_factory=lambda: MemoryErrorType(0))
heap: "HeapObject | None" = betterproto2.field(3, betterproto2.TYPE_MESSAGE, optional=True, group="location")
@dataclass
class Cause(betterproto.Message):
human_readable: str = betterproto.string_field(1)
memory_error: "MemoryError" = betterproto.message_field(2, group="details")
@dataclass(eq=False, repr=False)
class Cause(betterproto2.Message):
human_readable: "str" = betterproto2.field(1, betterproto2.TYPE_STRING)
memory_error: "MemoryError | None" = betterproto2.field(2, betterproto2.TYPE_MESSAGE, optional=True, group="details")
@dataclass
class Register(betterproto.Message):
name: str = betterproto.string_field(1)
u64: int = betterproto.uint64_field(2)
@dataclass(eq=False, repr=False)
class Register(betterproto2.Message):
name: "str" = betterproto2.field(1, betterproto2.TYPE_STRING)
u64: "int" = betterproto2.field(2, betterproto2.TYPE_UINT64)
@dataclass
class Thread(betterproto.Message):
id: int = betterproto.int32_field(1)
name: str = betterproto.string_field(2)
registers: List["Register"] = betterproto.message_field(3)
backtrace_note: List[str] = betterproto.string_field(7)
unreadable_elf_files: List[str] = betterproto.string_field(9)
current_backtrace: List["BacktraceFrame"] = betterproto.message_field(4)
memory_dump: List["MemoryDump"] = betterproto.message_field(5)
tagged_addr_ctrl: int = betterproto.int64_field(6)
pac_enabled_keys: int = betterproto.int64_field(8)
@dataclass(eq=False, repr=False)
class Thread(betterproto2.Message):
id: "int" = betterproto2.field(1, betterproto2.TYPE_INT32)
name: "str" = betterproto2.field(2, betterproto2.TYPE_STRING)
registers: "list[Register]" = betterproto2.field(3, betterproto2.TYPE_MESSAGE, repeated=True)
backtrace_note: "list[str]" = betterproto2.field(7, betterproto2.TYPE_STRING, repeated=True)
unreadable_elf_files: "list[str]" = betterproto2.field(9, betterproto2.TYPE_STRING, repeated=True)
current_backtrace: "list[BacktraceFrame]" = betterproto2.field(4, betterproto2.TYPE_MESSAGE, repeated=True)
memory_dump: "list[MemoryDump]" = betterproto2.field(5, betterproto2.TYPE_MESSAGE, repeated=True)
tagged_addr_ctrl: "int" = betterproto2.field(6, betterproto2.TYPE_INT64)
pac_enabled_keys: "int" = betterproto2.field(8, betterproto2.TYPE_INT64)
@dataclass
class BacktraceFrame(betterproto.Message):
rel_pc: int = betterproto.uint64_field(1)
pc: int = betterproto.uint64_field(2)
sp: int = betterproto.uint64_field(3)
function_name: str = betterproto.string_field(4)
function_offset: int = betterproto.uint64_field(5)
file_name: str = betterproto.string_field(6)
file_map_offset: int = betterproto.uint64_field(7)
build_id: str = betterproto.string_field(8)
@dataclass(eq=False, repr=False)
class BacktraceFrame(betterproto2.Message):
rel_pc: "int" = betterproto2.field(1, betterproto2.TYPE_UINT64)
pc: "int" = betterproto2.field(2, betterproto2.TYPE_UINT64)
sp: "int" = betterproto2.field(3, betterproto2.TYPE_UINT64)
function_name: "str" = betterproto2.field(4, betterproto2.TYPE_STRING)
function_offset: "int" = betterproto2.field(5, betterproto2.TYPE_UINT64)
file_name: "str" = betterproto2.field(6, betterproto2.TYPE_STRING)
file_map_offset: "int" = betterproto2.field(7, betterproto2.TYPE_UINT64)
build_id: "str" = betterproto2.field(8, betterproto2.TYPE_STRING)
@dataclass
class ArmMTEMetadata(betterproto.Message):
@dataclass(eq=False, repr=False)
class ArmMTEMetadata(betterproto2.Message):
# One memory tag per granule (e.g. every 16 bytes) of regular memory.
memory_tags: bytes = betterproto.bytes_field(1)
memory_tags: "bytes" = betterproto2.field(1, betterproto2.TYPE_BYTES)
@dataclass
class MemoryDump(betterproto.Message):
register_name: str = betterproto.string_field(1)
mapping_name: str = betterproto.string_field(2)
begin_address: int = betterproto.uint64_field(3)
memory: bytes = betterproto.bytes_field(4)
arm_mte_metadata: "ArmMTEMetadata" = betterproto.message_field(6, group="metadata")
@dataclass(eq=False, repr=False)
class MemoryDump(betterproto2.Message):
register_name: "str" = betterproto2.field(1, betterproto2.TYPE_STRING)
mapping_name: "str" = betterproto2.field(2, betterproto2.TYPE_STRING)
begin_address: "int" = betterproto2.field(3, betterproto2.TYPE_UINT64)
memory: "bytes" = betterproto2.field(4, betterproto2.TYPE_BYTES)
arm_mte_metadata: "ArmMTEMetadata | None" = betterproto2.field(6, betterproto2.TYPE_MESSAGE, optional=True, group="metadata")
@dataclass
class MemoryMapping(betterproto.Message):
begin_address: int = betterproto.uint64_field(1)
end_address: int = betterproto.uint64_field(2)
offset: int = betterproto.uint64_field(3)
read: bool = betterproto.bool_field(4)
write: bool = betterproto.bool_field(5)
execute: bool = betterproto.bool_field(6)
mapping_name: str = betterproto.string_field(7)
build_id: str = betterproto.string_field(8)
load_bias: int = betterproto.uint64_field(9)
@dataclass(eq=False, repr=False)
class MemoryMapping(betterproto2.Message):
begin_address: "int" = betterproto2.field(1, betterproto2.TYPE_UINT64)
end_address: "int" = betterproto2.field(2, betterproto2.TYPE_UINT64)
offset: "int" = betterproto2.field(3, betterproto2.TYPE_UINT64)
read: "bool" = betterproto2.field(4, betterproto2.TYPE_BOOL)
write: "bool" = betterproto2.field(5, betterproto2.TYPE_BOOL)
execute: "bool" = betterproto2.field(6, betterproto2.TYPE_BOOL)
mapping_name: "str" = betterproto2.field(7, betterproto2.TYPE_STRING)
build_id: "str" = betterproto2.field(8, betterproto2.TYPE_STRING)
load_bias: "int" = betterproto2.field(9, betterproto2.TYPE_UINT64)
@dataclass
class FD(betterproto.Message):
fd: int = betterproto.int32_field(1)
path: str = betterproto.string_field(2)
owner: str = betterproto.string_field(3)
tag: int = betterproto.uint64_field(4)
@dataclass(eq=False, repr=False)
class FD(betterproto2.Message):
fd: "int" = betterproto2.field(1, betterproto2.TYPE_INT32)
path: "str" = betterproto2.field(2, betterproto2.TYPE_STRING)
owner: "str" = betterproto2.field(3, betterproto2.TYPE_STRING)
tag: "int" = betterproto2.field(4, betterproto2.TYPE_UINT64)
@dataclass
class LogBuffer(betterproto.Message):
name: str = betterproto.string_field(1)
logs: List["LogMessage"] = betterproto.message_field(2)
@dataclass(eq=False, repr=False)
class LogBuffer(betterproto2.Message):
name: "str" = betterproto2.field(1, betterproto2.TYPE_STRING)
logs: "list[LogMessage]" = betterproto2.field(2, betterproto2.TYPE_MESSAGE, repeated=True)
@dataclass
class LogMessage(betterproto.Message):
timestamp: str = betterproto.string_field(1)
pid: int = betterproto.uint32_field(2)
tid: int = betterproto.uint32_field(3)
priority: int = betterproto.uint32_field(4)
tag: str = betterproto.string_field(5)
message: str = betterproto.string_field(6)
@dataclass(eq=False, repr=False)
class LogMessage(betterproto2.Message):
timestamp: "str" = betterproto2.field(1, betterproto2.TYPE_STRING)
pid: "int" = betterproto2.field(2, betterproto2.TYPE_UINT32)
tid: "int" = betterproto2.field(3, betterproto2.TYPE_UINT32)
priority: "int" = betterproto2.field(4, betterproto2.TYPE_UINT32)
tag: "str" = betterproto2.field(5, betterproto2.TYPE_STRING)
message: "str" = betterproto2.field(6, betterproto2.TYPE_STRING)

View File

@@ -52,9 +52,7 @@ class Indicators:
if os.path.isfile(path) and path.lower().endswith(".stix2"):
self.parse_stix2(path)
elif os.path.isdir(path):
for file in glob.glob(
os.path.join(path, "**", "*.stix2", recursive=True)
):
for file in glob.glob(os.path.join(path, "**", "*.stix2"), recursive=True):
self.parse_stix2(file)
else:
self.log.error(
@@ -102,6 +100,17 @@ class Indicators:
key, value = indicator.get("pattern", "").strip("[]").split("=")
key = key.strip()
# Normalize hash algorithm keys so that both the STIX2-spec-compliant
# form (e.g. file:hashes.'SHA-256', which requires quotes around
# algorithm names that contain hyphens) and the non-standard lowercase
# form (e.g. file:hashes.sha256) are accepted. Strip single quotes and
# hyphens from the algorithm name only, then lowercase it.
for sep in ("hashes.", "cert."):
if sep in key:
prefix, _, algo = key.partition(sep)
key = prefix + sep + algo.replace("'", "").replace("-", "").lower()
break
if key == "domain-name:value":
# We force domain names to lower case.
self._add_indicator(

View File

@@ -907,6 +907,10 @@
"version": "15.8.6",
"build": "19H402"
},
{
"version": "15.8.7",
"build": "19H411"
},
{
"build": "20A362",
"version": "16.0"
@@ -1020,6 +1024,10 @@
"version": "16.7.14",
"build": "20H370"
},
{
"version": "16.7.15",
"build": "20H380"
},
{
"version": "17.0",
"build": "21A327"
@@ -1188,6 +1196,10 @@
"version": "18.7.6",
"build": "22H320"
},
{
"version": "18.7.7",
"build": "22H333"
},
{
"version": "26",
"build": "23A341"
@@ -1215,5 +1227,9 @@
{
"version": "26.3.1",
"build": "23D8133"
},
{
"version": "26.4",
"build": "23E246"
}
]

View File

@@ -82,7 +82,7 @@ def generate_test_stix_file(file_path):
for h in sha256:
i = Indicator(
indicator_types=["malicious-activity"],
pattern="[file:hashes.sha256='{}']".format(h),
pattern="[file:hashes.'SHA-256'='{}']".format(h),
pattern_type="stix",
)
res.append(i)
@@ -91,7 +91,7 @@ def generate_test_stix_file(file_path):
for h in sha1:
i = Indicator(
indicator_types=["malicious-activity"],
pattern="[file:hashes.sha1='{}']".format(h),
pattern="[file:hashes.'SHA-1'='{}']".format(h),
pattern_type="stix",
)
res.append(i)

View File

@@ -94,6 +94,78 @@ class TestIndicators:
)
assert ind.check_file_hash("da0611a300a9ce9aa7a09d1212f203fca5856794")
def test_parse_stix2_hash_key_variants(self, tmp_path):
"""STIX2 spec requires single-quoted algorithm names that contain hyphens,
e.g. file:hashes.'SHA-256'. Verify MVT accepts both spec-compliant and
non-standard lowercase spellings for MD5, SHA-1 and SHA-256."""
import json
sha256_hash = "570cd76bf49cf52e0cb347a68bdcf0590b2eaece134e1b1eba7e8d66261bdbe6"
sha1_hash = "da0611a300a9ce9aa7a09d1212f203fca5856794"
md5_hash = "d41d8cd98f00b204e9800998ecf8427e"
variants = [
# (pattern_key, expected_bucket)
("file:hashes.'SHA-256'", "files_sha256"),
("file:hashes.SHA-256", "files_sha256"),
("file:hashes.SHA256", "files_sha256"),
("file:hashes.sha256", "files_sha256"),
("file:hashes.'SHA-1'", "files_sha1"),
("file:hashes.SHA-1", "files_sha1"),
("file:hashes.SHA1", "files_sha1"),
("file:hashes.sha1", "files_sha1"),
("file:hashes.MD5", "files_md5"),
("file:hashes.'MD5'", "files_md5"),
("file:hashes.md5", "files_md5"),
]
hash_for = {
"files_sha256": sha256_hash,
"files_sha1": sha1_hash,
"files_md5": md5_hash,
}
for pattern_key, bucket in variants:
h = hash_for[bucket]
stix = {
"type": "bundle",
"id": "bundle--test",
"objects": [
{
"type": "malware",
"id": "malware--test",
"name": "TestMalware",
"is_family": False,
},
{
"type": "indicator",
"id": "indicator--test",
"indicator_types": ["malicious-activity"],
"pattern": f"[{pattern_key}='{h}']",
"pattern_type": "stix",
"valid_from": "2024-01-01T00:00:00Z",
},
{
"type": "relationship",
"id": "relationship--test",
"relationship_type": "indicates",
"source_ref": "indicator--test",
"target_ref": "malware--test",
},
],
}
stix_file = tmp_path / "test.stix2"
stix_file.write_text(json.dumps(stix))
ind = Indicators(log=logging)
ind.load_indicators_files([str(stix_file)], load_default=False)
assert len(ind.ioc_collections[0][bucket]) == 1, (
f"Pattern key '{pattern_key}' was not parsed into '{bucket}'"
)
assert ind.check_file_hash(h) is not None, (
f"check_file_hash failed for pattern key '{pattern_key}'"
)
def test_check_android_property(self, indicator_file):
ind = Indicators(log=logging)
ind.load_indicators_files([indicator_file], load_default=False)