← All hunts high TLP:CLEAR

Flutter Supply Chain Build Execution

An adversary has compromised developer and CI environments by injecting malicious Flutter packages that execute obfuscated shell scripts during native Android or iOS builds.

Based on research by Ossprey 2026-09-20 12 steps · 4 queries T1090.003 T1190 T1195 T1204.002

Brief

Why Now

The OSSPREY Report. The security community recently identified a supply chain compromise affecting the pub.dev package repository. As detailed by OSSPREY in their article "pub.dev compromise: malicious Dart/Flutter packages" (https://www.ossprey.com/blog/pub-dev-compromise), attackers uploaded compromised versions of Flutter packages like universal_file_viewer and surveyjs_flutter. These packages contain malicious code that triggers during the native build phase for Android or iOS. This represents a significant shift from typical dependency attacks that focus on application runtime. Instead, this attack targets the developer's environment and build pipelines to exfiltrate sensitive credentials and secrets.

How the Hunt Flows

The hunt begins with a scoping phase using the hb_software_inventory surface. We query for specific package names and version ranges identified in the OSSPREY report. This step identifies every developer workstation and CI host that has downloaded the malicious packages. Because these packages can be transitive dependencies, the inventory check provides a wide net for further investigation. We focus on versions 0.1.1 through 0.1.6 of the targeted packages. Once we have a list of candidate hosts, the hunt fans out into a behavioral analysis phase. The first pivot examines hb_process_activity. We stack-count rare processes spawned by common shell interpreters like sh, bash, and zsh. Malicious build scripts often spawn unusual child processes to perform exfiltration or download additional payloads. By filtering for low-prevalence process paths, we isolate suspicious activity that deviates from standard build behaviors across the environment. Simultaneously, the hunt checks hb_script_activity for specific obfuscation patterns. The adversary uses a combination of printf, tr, and sh to hide the contents of their malicious scripts. The hunt searches for this durable pattern in captured script telemetry. This is a high-confidence indicator because legitimate Flutter packages rarely use this specific form of obfuscation during a build. The final behavioral pivot focuses on hb_dns_activity. We check for DNS resolutions to the known C2 domain elvynforge.xyz and any domains using the .ru top-level domain. Attackers often use rotating infrastructure, so we specifically look for network activity originating from the build processes themselves. This connection between the process and the network destination helps confirm the malicious nature of the script execution.

What the Hunt Cannot See

This hunt has several blind spots. First, it relies on host inventory snapshots. If a malicious build executes on an ephemeral CI runner that terminates before the inventory is captured, the hunt will miss that execution. Second, the hunt detects the execution of the malicious scripts, not their presence in a latent state. We cannot see the malicious PBXBuildRule within an Xcode project file if the build has not been triggered. Finally, the network confirmation depends on the availability of DNS telemetry from the developer hosts. If DNS logging is missing, we may identify suspicious scripts without the ability to confirm C2 callbacks.

How to Run the Hunt

This hunt is provided as an open hunt.md playbook. It imports directly into Huntbase or any hunt.md-aware runtime. To execute the hunt, provide the lookback window and the list of affected packages as parameters. The playbook guides the analyst through the automated scoping, behavioral fan-out, and final triage. It includes automated agents to weigh the evidence and suggest a final verdict for each host.

Steps

  1. Identify hosts with malicious package versions

    Query · scoping

    Find developer workstations or CI runners that have downloaded the specific compromised versions of the target packages.

    reads hb_software_inventorysql
    SELECT device_hostname, package_name, package_version, install_path, provider FROM hb_software_inventory WHERE instr(',' || '{{affected_packages}}' || ',', ',' || LOWER(package_name) || ',') > 0 AND (package_version IN ('0.1.1', '0.1.2', '0.1.3', '0.1.5', '0.1.6'))

    What a hit looks like. Hosts and paths where the packages are present. Silence means the specific versions were not detected in current inventory.

  2. Assess lead scope

    Agent triage

    Evaluate the lead query results to prioritize hosts for behavioral analysis.

  3. Gate on discovery

    Decision

    Route to deeper behavioral queries only if affected packages are identified.

  4. Stack-count rare shell-spawned processes

    Query · baseline

    Identify rare child processes spawned by shell interpreters on developer hosts, which may indicate build-time injection.

    reads hb_process_activitysql
    SELECT process_path, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_process_activity WHERE LOWER(parent_process_name) IN ('sh', 'zsh', 'bash') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY process_path HAVING host_count <= 3

    What a hit looks like. A small set of processes; unexpected binaries spawned from shells during builds are highlights.

  5. Detect obfuscated build scripts

    Query · detection candidate

    Find the specific obfuscation pattern (printf/tr/sh) used in the Gradle and Xcode build-time injections.

    reads hb_script_activitysql
    SELECT device_hostname, process_name, script_content, time FROM hb_script_activity WHERE (LOWER(script_content) LIKE '%printf%tr%sh%' OR LOWER(script_content) LIKE '%a3ea261%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')

    What a hit looks like. A row containing the obfuscated shell command evaluated by sh. This is high-confidence evidence of the reported injection.

  6. Match C2 network activity

    Query · enrichment

    Check for DNS callbacks to the known C2 domain or rotating .ru domains from processes involved in the build.

    reads hb_dns_activitysql
    SELECT device_hostname, query_hostname, process_name, time FROM hb_dns_activity WHERE (instr(',' || '{{c2_domains}}' || ',', ',' || LOWER(query_hostname) || ',') > 0 OR LOWER(query_hostname) LIKE '%.ru') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')

    What a hit looks like. DNS resolutions for the named domain or .ru TLDs from shell or developer tools. Silence means the domain has rotated or no callback occurred.

  7. Final triage

    Agent triage

    Synthesize the inventory, script, and network evidence into a per-host verdict.

  8. Route on verdict

    Decision

    Isolate confirmed compromised hosts or route to analyst review for suspicious cases.

  9. Isolate host

    Response action

    Stop potential credential exfiltration from a confirmed compromised developer machine.

  10. Remediation tasks

    Analyst task

    Guide the analyst through manual verification and credential rotation.

  11. Close out

    Analyst task

    Finalize the hunt and document any coverage gaps.

Coverage

Scenario coverage

StageCoveredHow, or why not
Malicious Flutter package ingestion
T1195 · T1190
Yes find-affected-packages
Build-time code execution
T1204.002
Yes rare-process-baseline, build-script-activity
C2 payload download
T1090.003
Yes c2-dns-activity

Blind spots

  • Needs hb_software_inventory snapshot persistence. A malicious build could execute on a short-lived runner, exfiltrate credentials, and disappear without being recorded in the inventory surface. It would answer whether ephemeral CI runners executed the build and terminated before inventory was captured. Remediation: Implement real-time package monitoring or log pubspec.lock file touches.
  • Needs hb_file_activity with file content inspection. We can only see the execution of the script, not the latent build configuration itself, via current behavioral surfaces. It would answer whether the malicious PBXBuildRule exists in the project configuration without the script executing. Remediation: Deploy a scanner to audit project.pbxproj files for unauthorized script build phases.
  • Needs hb_dns_activity from developer hosts. We rely on DNS to confirm the rotating .ru domains; if telemetry is missing, we may miss the callback confirmation. It would answer whether the C2 callback occurred on hosts missing DNS telemetry. Remediation: Ensure the endpoint agent is deployed with network monitoring to all developer machines.

Parameters & data

Parameters

ParameterTypeDefaultWhat it is
affected_packageslist[string]universal_file_viewer, surveyjs_flutterNames of the compromised Flutter packages.
c2_domainslist[domain]elvynforge.xyzKnown C2 domains; the hunt also looks for generic .ru TLD traffic from build processes.
lookback_daysnumber30Days of history to examine, covering the known activity window.
scope_hostslist[host]Optional list of hosts from the lead query to focus the behavioral analysis.

Telemetry

SourceCategoryTelemetry
Endpoint telemetry (hb_ surfaces)endpointendpoint

Source

Download hunt.md Definition (JSON) An open hunt.md file; it runs anywhere that reads the format.
---
analysis: This hunt is superior to a single rule because it pivots between host inventory
  and behavioural prevalence. A single rule targeting the rotating C2 domains would
  fail as the attacker updates their infrastructure, but the hunt identifies the durable
  pattern of obfuscated shell execution originating from build tools.
blind_spots:
- id: ci-runner-ephemerality
  owner: Cloud Infrastructure
  question: whether ephemeral CI runners executed the build and terminated before
    inventory was captured
  remediation: Implement real-time package monitoring or log pubspec.lock file touches.
  requires: hb_software_inventory snapshot persistence
  risk: A malicious build could execute on a short-lived runner, exfiltrate credentials,
    and disappear without being recorded in the inventory surface.
  stage: initial-access-supply-chain-registry
- id: xcode-project-file-visibility
  owner: Security Engineering
  question: whether the malicious PBXBuildRule exists in the project configuration
    without the script executing
  remediation: Deploy a scanner to audit project.pbxproj files for unauthorized script
    build phases.
  requires: hb_file_activity with file content inspection
  risk: We can only see the execution of the script, not the latent build configuration
    itself, via current behavioral surfaces.
  stage: execution-native-build-injection
- id: no-dns-logging
  owner: Endpoint Engineering
  question: whether the C2 callback occurred on hosts missing DNS telemetry
  remediation: Ensure the endpoint agent is deployed with network monitoring to all
    developer machines.
  requires: hb_dns_activity from developer hosts
  risk: We rely on DNS to confirm the rotating .ru domains; if telemetry is missing,
    we may miss the callback confirmation.
  stage: c2-rotating-domain-callback
coverage:
- stage: initial-access-supply-chain-registry
  status: covered
  steps:
  - find-affected-packages
- stage: execution-native-build-injection
  status: covered
  steps:
  - rare-process-baseline
  - build-script-activity
- stage: c2-rotating-domain-callback
  status: covered
  steps:
  - c2-dns-activity
guardrails:
  claims: no_unsupported
  evidence: citation_required
  missing_data: not_benign
  telemetry: untrusted
hunt:
  applicability: campaign-specific
  handoff: promote-to-detection
  justification: Supply chain attacks against trusted maintainers bypass reputation-based
    filters; confirming the absence of these malicious build hooks protects high-value
    credentials in development pipelines.
  methodology: model-assisted
  trigger: intel-report
hypothesis: An adversary has compromised developer and CI environments by injecting
  malicious Flutter packages that execute obfuscated shell scripts during native Android
  or iOS builds.
labels:
- hunt
- attack.t1195
- attack.t1190
- attack.t1204.002
- attack.t1090.003
name: Flutter Supply Chain Build Execution
parameters:
  affected_packages:
    default:
    - universal_file_viewer
    - surveyjs_flutter
    description: Names of the compromised Flutter packages.
    from:
      kind: article
      observed: '2026-09-08'
      ref: ossprey-blog
    type: list[string]
  c2_domains:
    default:
    - elvynforge.xyz
    description: Known C2 domains; the hunt also looks for generic .ru TLD traffic
      from build processes.
    from:
      kind: article
      observed: '2026-09-08'
      ref: ossprey-blog
    type: list[domain]
  lookback_days:
    default: '30'
    description: Days of history to examine, covering the known activity window.
    from:
      kind: article
      observed: '2026-08-12'
      ref: ossprey-blog
    type: number
  scope_hosts:
    default: []
    description: Optional list of hosts from the lead query to focus the behavioral
      analysis.
    from:
      kind: manual
      observed: '2026-09-08'
      ref: scoping-output
    type: list[host]
provenance:
  authors:
  - name: Huntbase hunt generation
    org: huntbase.io
  generated:
    by: huntbase-hunt-generation
    from: https://www.ossprey.com/blog/pub-dev-compromise
    gates:
    - dry-run
    - lint
    - critic
    model: hb_google/gemini-3-flash-preview
rationale: Focus initial analysis on developer VLANs and CI/CD runners. While the
  download count was low, transitive dependencies mean these packages could appear
  in unexpected projects.
references:
- name: "OSSPREY \u2014 pub.dev compromise: malicious Dart/Flutter packages"
  url: https://www.ossprey.com/blog/pub-dev-compromise
related:
- hunt: dependency-confusion-npm-pypi
  reason: This hunt focuses specifically on the Flutter/Dart build-time injection;
    NPM confusion is a separate sibling hunt.
  relation: out-of-scope-alternative
scenario:
  stages:
  - name: Malicious Flutter package ingestion
    observables:
    - universal_file_viewer version 0.1.5
    - universal_file_viewer version 0.1.6
    - surveyjs_flutter version 0.1.1
    - surveyjs_flutter version 0.1.2
    - surveyjs_flutter version 0.1.3
    - pubspec.lock
    - pub.dev
    slug: initial-access-supply-chain-registry
    tactic: initial-access
    techniques:
    - T1195
    - T1190
  - name: Build-time code execution
    observables:
    - build.gradle.kts
    - project.pbxproj
    - example/android/app/build.gradle.kts
    - example/ios/Runner.xcodeproj/project.pbxproj
    - PBXBuildRule with filePatterns = "*.md"
    - build setting A3EA261
    - printf xAxd | tr -d A
    - printf bdase64 | tr -d d
    - sh
    slug: execution-native-build-injection
    tactic: execution
    techniques:
    - T1204.002
  - name: C2 payload download
    observables:
    - elvynforge.xyz
    - POST /a
    - body p=xcode_phase
    - body p=gradle
    - body p=xcode_rule
    - rotating .ru domains
    slug: c2-rotating-domain-callback
    tactic: command-and-control
    techniques:
    - T1090.003
  summary: Legitimate Flutter packages on the pub.dev registry were compromised after
    a maintainer's development environment was infected, leading to the injection
    of malicious code into native build files. This code executes during build-time
    on developer or CI machines to download and execute shell scripts from remote
    C2 domains, targeting sensitive credentials and secrets.
severity: high
targets:
  analyst:
    name: Tier-2 analyst
    role: analyst
  endpoint:
    category: endpoint
    name: Endpoint telemetry (hb_ surfaces)
    telemetry:
    - endpoint
  hunter:
    agent: true
    name: Hunt agent
tlp: clear
type: investigation
---


# Flutter Supply Chain Build Execution

This hunt targets a specific supply chain attack on the pub.dev ecosystem where malicious code was injected into native build files (Gradle and Xcode). The attack occurs at build time rather than runtime, making it invisible to standard application-level monitoring. The hunt first identifies hosts with the affected package versions, then fans out to look for characteristic obfuscated shell script execution, rare build-process activity, and C2 callbacks to rotating domains. An agent weighs the inventory, behavioral, and network evidence to determine if a host was compromised.

## find-affected-packages
<!-- Identify hosts with malicious package versions -->
Find developer workstations or CI runners that have downloaded the specific compromised versions of the target packages.

```sqlite target=endpoint role=scoping params=(affected_packages=affected_packages)
~~~yaml
expected: Hosts and paths where the packages are present. Silence means the specific
  versions were not detected in current inventory.
reads:
- device_hostname
- package_name
- package_version
- install_path
- provider
silence: not_evidence_of_absence
source: hb_software_inventory
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, package_name, package_version, install_path, provider FROM hb_software_inventory WHERE instr(',' || '{{affected_packages}}' || ',', ',' || LOWER(package_name) || ',') > 0 AND (package_version IN ('0.1.1', '0.1.2', '0.1.3', '0.1.5', '0.1.6'))
```

## assess-lead-scope
<!-- Assess lead scope -->
```agent target=hunter
cite: required
context:
- find-affected-packages
max_iterations: 3
objective: Determine if any hosts in the environment have the specific malicious Flutter
  package versions present.
success_criteria: A list of hostnames requiring deep inspection.
tools:
- endpoint
```

## gate-on-discovery
<!-- Gate on discovery -->
if~: "the assess-lead-scope agent identifies at least one host with a malicious package version" (confidence: high, judge=hunter)
then: → deep-inspection-fanout
indeterminate: → remediation-tasks
unavailable: → remediation-tasks (blind_spot: ci-runner-ephemerality)
else: → close-out

## deep-inspection-fanout
<!-- Deep inspection fan-out -->
parallel:
- → rare-process-baseline
- → build-script-activity
- → c2-dns-activity
join: → final-triage

## rare-process-baseline
<!-- Stack-count rare shell-spawned processes -->
Identify rare child processes spawned by shell interpreters on developer hosts, which may indicate build-time injection.

```sqlite target=endpoint role=baseline params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
baseline:
  compare: first_seen
  window: '{{lookback_days}}d'
expected: A small set of processes; unexpected binaries spawned from shells during
  builds are highlights.
prevalence:
  by: device_hostname
  key:
  - process_path
  rare_below: 3
reads:
- device_hostname
- process_path
- parent_process_name
- time
silence: not_evidence_of_absence
source: hb_process_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT process_path, COUNT(DISTINCT device_hostname) AS host_count, MIN(time) AS first_seen FROM hb_process_activity WHERE LOWER(parent_process_name) IN ('sh', 'zsh', 'bash') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY process_path HAVING host_count <= 3
```

## build-script-activity
<!-- Detect obfuscated build scripts -->
Find the specific obfuscation pattern (printf/tr/sh) used in the Gradle and Xcode build-time injections.

```sqlite target=endpoint role=detection-candidate params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
expected: A row containing the obfuscated shell command evaluated by sh. This is high-confidence
  evidence of the reported injection.
reads:
- device_hostname
- process_name
- script_content
- time
silence: not_evidence_of_absence
source: hb_script_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, process_name, script_content, time FROM hb_script_activity WHERE (LOWER(script_content) LIKE '%printf%tr%sh%' OR LOWER(script_content) LIKE '%a3ea261%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```

## c2-dns-activity
<!-- Match C2 network activity -->
Check for DNS callbacks to the known C2 domain or rotating .ru domains from processes involved in the build.

```sqlite target=endpoint role=enrichment params=(lookback_days=lookback_days, c2_domains=c2_domains, scope_hosts=scope_hosts)
~~~yaml
expected: DNS resolutions for the named domain or .ru TLDs from shell or developer
  tools. Silence means the domain has rotated or no callback occurred.
reads:
- device_hostname
- query_hostname
- process_name
- time
silence: not_evidence_of_absence
source: hb_dns_activity
verified: dry-run
verified_at: '2026-09-20'
~~~
SELECT device_hostname, query_hostname, process_name, time FROM hb_dns_activity WHERE (instr(',' || '{{c2_domains}}' || ',', ',' || LOWER(query_hostname) || ',') > 0 OR LOWER(query_hostname) LIKE '%.ru') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```

## final-triage
<!-- Final triage -->
```agent target=hunter
cite: required
context:
- assess-lead-scope
- rare-process-baseline
- build-script-activity
- c2-dns-activity
max_iterations: 5
objective: Determine if any host with the malicious packages also exhibits behavioral
  or network evidence of the build-time compromise.
success_criteria: A verdict citing script execution or C2 callbacks on a host with
  affected packages.
tools:
- endpoint
```

## route-on-verdict
<!-- Route on verdict -->
if~: "the final-triage verdict is malicious for at least one host" (confidence: high, judge=hunter)
then: → isolate-host
indeterminate: → remediation-tasks
unavailable: → remediation-tasks (blind_spot: no-dns-logging)
else: → close-out

## isolate-host
<!-- Isolate host -->
```action target=endpoint
~~~yaml
approval: required
~~~
Isolate the host from the network and revoke any CI secrets or cloud credentials stored on the machine.
```
→ remediation-tasks

## remediation-tasks
<!-- Remediation tasks -->
```manual target=analyst
Manually inspect the pubspec.lock files on the identified hosts. Review the full text of any scripts captured in hb_script_activity. Rotate all credentials that were active on the machine during the identified lookback window.
```
→ close-out

## close-out
<!-- Close out -->
```manual target=analyst
Document which hosts were inspected and whether additional forensic collection is required. Record any tuning notes if the obfuscated script check caught benign developer activity.
```
→ end

Run it

Take this hunt into your environment.

Open it in Huntbase to run every step against your own connections, with Scout weighing the evidence and your analysts in command. Or take the open hunt.md file anywhere that reads the format.

Machine-drafted by huntbase-hunt-generation using hb_google/gemini-3-flash-preview, gated by dry-run, lint, critic, then reviewed by a person.