Skip to content

False positive: check_receiver_prefix does unanchored substring match, triggers CRITICAL on AOSP com.android.phone for the TheOneSpy / com.android.services indicator #803

@vd-beep-beep

Description

@vd-beep-beep

Bug

In check-bugreport / check-androidqf runs, dumpsys_receivers_detected.json reports a CRITICAL match:

{
  "message": "Found a known suspicious receiver with name \"com.android.phone/com.android.services.telephony.sip.SipIncomingCallReceiver\" matching indicators from \"TheOneSpy\"",
  "event": {"package_name": "com.android.phone",
            "receiver":     "com.android.phone/com.android.services.telephony.sip.SipIncomingCallReceiver"},
  "matched_indicator": {"value": "com.android.services", "type": "app_ids", "name": "TheOneSpy"}
}

The matched com.android.phone is the AOSP SIP telephony receiver, not TheOneSpy. The indicator value com.android.services is a real TheOneSpy masquerade package id (AssoEchap data is correct) — but it appears here only as a substring of com.android.services.telephony.sip.SipIncomingCallReceiver inside system package com.android.phone.

Reproduces on MVT 2026.5.12 against any device still shipping SIP telephony in com.android.phone (Android ≤ 13 stock, plus OEM ROMs that retained SIP after the API 34 removal — SIP was deprecated in API 31 and removed in API 34). Verified on HUAWEI STK-L21 / EMUI 10.

Where

Introduced by #721 (merged 2026-01-10). Two files:

src/mvt/android/modules/bugreport/dumpsys_receivers.py:38-52 — the only caller of check_receiver_prefix in the codebase, and the only Android module that compares an indicator against the full package/class string instead of package_name:

receiver_name = self.results[result][0]["receiver"]
ioc_match = self.indicators.check_receiver_prefix(receiver_name)

src/mvt/common/indicators.py:730-751 — unanchored substring containment, despite the name check_receiver_prefix:

for ioc in self.get_iocs("app_ids"):
    if ioc.value.lower() in receiver_name.lower():    # ← unanchored
        return IndicatorMatch(ioc=ioc, ...)

Every other Android artifact (≈14 callsites) uses check_app_id (indicators.py:709-728), which does exact equality against package_name. That code path would not produce this FP.

Notably, the sibling artifact module src/mvt/android/artifacts/dumpsys_receivers.py:53 already does the right thing — it calls check_app_id(receiver["package_name"]). Only the bugreport wrapper in modules/bugreport/dumpsys_receivers.py was switched to the new check_receiver_prefix path in #721, so the two receiver-matching paths in the codebase now disagree.

Fix

Smallest change — drop check_receiver_prefix and call check_app_id on the package portion:

# modules/bugreport/dumpsys_receivers.py
package_name = receiver_name.split("/", 1)[0]
ioc_match = self.indicators.check_app_id(package_name)

If checking class paths is intentional (the goal of #721), anchor on a namespace boundary instead of using in:

def check_receiver_prefix(self, receiver_name: str):
    if not receiver_name:
        return None
    lower = receiver_name.lower()
    pkg = lower.split("/", 1)[0]
    for ioc in self.get_iocs("app_ids"):
        v = ioc.value.lower()
        if pkg == v or lower.startswith(v + "."):
            return IndicatorMatch(ioc=ioc, ...)
    return None

Under either fix, the canonical FP disappears (com.android.phonecom.android.services, and com.android.services is not at a namespace boundary inside the SIP class path), while a real com.android.services/... receiver still matches.

Regression test:

def test_check_receiver_prefix_does_not_match_aosp_namespace_substring():
    ind = ... # TheOneSpy / app_ids: ["com.android.services"]
    assert ind.check_receiver_prefix(
        "com.android.phone/com.android.services.telephony.sip.SipIncomingCallReceiver"
    ) is None
    assert ind.check_receiver_prefix(
        "com.android.services/com.example.SomeReceiver"
    ) is not None

Happy to send a PR for option 1 or 2 — preference welcome.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions