# WireLurker Detector — User Guide

WireLurker Detector is a short Python script that scans a Mac OS X system for files and applications infected by the **WireLurker** malware family, a 2014 OS X / iOS threat discovered by Palo Alto Networks. It also ships a small companion script that can decrypt the command-and-control (C&C) traffic used by the Windows-capable variant of WireLurker.

> [!IMPORTANT]
> This tool is a historical artifact. It was written in 2014 against **Python 2**, targets the specific on-disk signatures of the 2014 WireLurker campaign, and has not been updated to detect newer malware. Use it for historical forensics, research, or incident-response work on legacy systems — not as a general-purpose antivirus.

---

## 1. Quick Start

The detector is a single file. You download it, run it with Python 2, and read its output.

=== "macOS"

    ```bash
    curl -O https://raw.githubusercontent.com/PaloAltoNetworks/WireLurkerDetector/master/WireLurkerDetectorOSX.py
    python2 WireLurkerDetectorOSX.py
    ```

=== "Clone the repo"

    ```bash
    git clone https://github.com/PaloAltoNetworks/WireLurkerDetector.git
    cd WireLurkerDetector
    python2 WireLurkerDetectorOSX.py
    ```

Expected output on a clean system:

```text
WireLurker Detector (version 1.2.0)
Copyright (c) 2014, Palo Alto Networks, Inc.

[+] Scanning for known malicious files ...
[-] Nothing is found.
[+] Scanning for known suspicious files ...
[-] Nothing is found.
[+] Scanning for infected applications ... (may take minutes)
[-] Nothing is found.
[+] Your OS X system isn't infected by the WireLurker. Thank you!
```

> [!NOTE]
> The application scan walks every `.app` bundle in `/Applications` and `~/Applications`. On systems with many installed apps it can take several minutes. This is expected.

---

## 2. Requirements

| Requirement | Value | Notes |
|---|---|---|
| Operating system | macOS (Darwin) | The detector refuses to run on anything else and exits with code `-1`. |
| Python interpreter | **Python 2** (tested with 2.7) | The source uses Python-2 `print` statement syntax. Python 3 raises `SyntaxError` at parse time. |
| Privileges | Unprivileged user is enough to read `/Applications`, `/Library`, `/System/Library`, `/usr/bin`, and `/usr/local/`. `sudo` is not required by the script itself. | |
| Network | None for the OS X detector. `curl` only needs the internet when you first download the script. | |
| External library (decryptor only) | `pyDes` | Pure-Python DES implementation. Only needed if you run `decryptor.py`. |

> [!WARNING]
> Modern macOS ships Python 3 as `python3`, and the `python` command may be absent or aliased to `python3`. Run the detector explicitly with `python2` and confirm the version first:
>
> ```bash
> python2 --version
> ```
>
> ```text
> Python 2.7.18
> ```

---

## 3. Installation

There is no packaging, no `setup.py`, and no PyPI release. "Installation" is just obtaining the script.

### 3.1 Option A — Download a single file

```bash
curl -O https://raw.githubusercontent.com/PaloAltoNetworks/WireLurkerDetector/master/WireLurkerDetectorOSX.py
```

```text
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  5143  100  5143    0     0  22000      0 --:--:-- --:--:-- --:--:-- 22000
```

The file is ~5 KB. Place it anywhere you can execute it from.

### 3.2 Option B — Clone the repository

```bash
git clone https://github.com/PaloAltoNetworks/WireLurkerDetector.git
cd WireLurkerDetector
ls
```

```text
HOWTO-Windows.md
LICENSE
README.md
WireLurkerDetectorOSX.py
decryptor.py
```

### 3.3 Installing `pyDes` (only if you need the decryptor)

```bash
pip2 install pyDes
```

```text
Collecting pyDes
  Using cached pyDes-2.0.1.tar.gz
Installing collected packages: pyDes
Successfully installed pyDes-2.0.1
```

> [!TIP]
> The script's own error message recommends the flags `--allow-external pyDes --allow-unverified pyDes`. Those flags were removed from pip years ago and are not needed anymore. Plain `pip2 install pyDes` works on any currently maintained Python 2 environment.

### 3.4 Installation checklist

- [ ] `python2 --version` prints `Python 2.7.x`.
- [ ] You are on macOS (check with `uname -s` → `Darwin`).
- [ ] `WireLurkerDetectorOSX.py` is downloaded and readable.
- [ ] (Decryptor only) `python2 -c "import pyDes"` returns without error.

---

## 4. Using the Detector — `WireLurkerDetectorOSX.py`

### 4.1 Running a scan

Run the script from any directory. It takes no command-line arguments.

```bash
python2 WireLurkerDetectorOSX.py
```

The scan runs three passes, in this fixed order:

1. **Known malicious files.** The script checks for the presence of 18 hardcoded absolute paths (e.g. `/Users/Shared/run.sh`, `/Library/LaunchDaemons/com.apple.machook_damon.plist`). Any path that exists is printed as `[!] Found malicious file: <path>`.
2. **Known suspicious files.** A shorter list of 4 paths that can be associated with the WireLurker installer but are not by themselves conclusive (e.g. `/etc/manpath.d/`, `~/Library/Caches/com.maiyadi.appinstaller/`). Matches print as `[!] Found suspicious file: <path>`.
3. **Infected applications.** The script walks `/Applications` and `~/Applications` recursively, opens the `Contents/Info.plist` of every `.app` bundle, and applies the infection heuristics in §4.3.

### 4.2 Interpreting the exit status

| Exit code | Meaning |
|---|---|
| `0` | No malicious files, suspicious files, or infected applications were detected. The system is considered clean by this tool. |
| `1` | At least one indicator was found. The final block of output repeats the warning and points to the Palo Alto Networks research-center article. |
| `-1` | The script was executed on a non-Darwin system and refused to run, or it could not be launched (e.g. wrong Python version, missing file). |

You can branch on the exit code in a shell pipeline:

```bash
python2 WireLurkerDetectorOSX.py
if [ $? -eq 0 ]; then
    echo "Clean."
else
    echo "Review output — indicators were found."
fi
```

### 4.3 The `.app` infection heuristic

Every `.app` bundle under `/Applications` or `~/Applications` is inspected. A bundle is flagged as infected if **any one** of the three heuristics below is true.

| # | Heuristic | What it looks for |
|---|---|---|
| 1 | **Hidden legitimate executable swap.** | Reads `Contents/Info.plist`, resolves `CFBundleExecutable`, and checks whether both `Contents/MacOS/<exec>` and `Contents/MacOS/<exec>_` have the hidden flag set (`UF_HIDDEN` on the filesystem, or `ls -ldO … hidden` fallback). |
| 2 | **Hidden trojan start script.** | Checks whether both `Contents/Resources/start.sh` and `Contents/Resources/FontMap1.cfg` are present and hidden. |
| 3 | **Embedded installer + payload.** | Checks whether both `Contents/MacOS/appinstaller` and `Contents/Resources/infoplistab` exist as files. |

If the `Info.plist` is unreadable or `CFBundleExecutable` is missing, the bundle is silently skipped. The script never modifies or quarantines anything — it only reports.

> [!TIP]
> To see the full list of files the detector watches for, grep the source:
>
> ```bash
> grep -E "^\s*'/(Users|Library|System|usr|etc)" WireLurkerDetectorOSX.py
> ```

### 4.4 Example — infection detected

If an infected bundle or known file is present, output looks like this:

```text
WireLurker Detector (version 1.2.0)
Copyright (c) 2014, Palo Alto Networks, Inc.

[+] Scanning for known malicious files ...
[!] Found malicious file: /Library/LaunchDaemons/com.apple.machook_damon.plist
[!] Found malicious file: /usr/bin/globalupdate
[+] Scanning for known suspicious files ...
[-] Nothing is found.
[+] Scanning for infected applications ... (may take minutes)
[!] Found infected application: /Applications/SomeApp.app
[!] WARNING: Your OS X system is highly suspicious of being infected by the WireLurker.
[!] You may need to delete all malicious or suspicious files and/or applications above.
[!] For more information about the WireLurker, please refer:
[!] http://researchcenter.paloaltonetworks.com/2014/11/wirelurker-new-era-os-x-ios-malware/
```

> [!CAUTION]
> The detector **never removes anything**. Before deleting a flagged file or application, capture it for forensics (`tar`, `dd`, or preserve the original `.app` bundle) and verify the match by hand. Deleting without preservation destroys evidence that may be needed for further analysis.

---

## 5. Using the Decryptor — `decryptor.py`

### 5.1 Purpose

`decryptor.py` decrypts a single base64-encoded message produced by the **version C** variant of WireLurker when it talks to its C&C server. It is a research helper, not part of the detection loop. You only use it when you already have captured encrypted C&C traffic and want to read it.

### 5.2 Syntax

```bash
python2 decryptor.py <encrypted_message>
```

- `<encrypted_message>` — A single base64-ish string captured from WireLurker C&C traffic. The first 10 characters are metadata that seed the session key; the remainder is the base64-encoded DES-ECB ciphertext.

### 5.3 How the key is derived

The script:

1. Takes the first 10 characters of the input, interprets each as a decimal digit, and sums them.
2. Formats that sum as a decimal string and appends the literal suffix `dksyel` to get the DES key.
3. Decrypts the remaining characters as base64-decoded DES-ECB with PKCS#5 padding.

You do not need to know this to use the tool, but it explains why the first 10 characters of a captured packet are required.

### 5.4 Example

```bash
python2 decryptor.py 0123456789BASE64ENCODEDPAYLOAD=
```

The plaintext (if the input is a valid WireLurker-C message) is printed to stdout. Malformed input typically raises a Python traceback from `pyDes` or `base64`.

> [!NOTE]
> The decryptor is specific to the 2014 "version C" strain. It will not decrypt traffic from unrelated malware, and it is not a general DES utility.

---

## 6. Common Tasks

### 6.1 Run on an offline or air-gapped Mac

Copy the single `.py` file onto removable media, mount it on the target Mac, and run it. The detector does not touch the network.

```bash
cp /Volumes/USB/WireLurkerDetectorOSX.py ~/Desktop/
python2 ~/Desktop/WireLurkerDetectorOSX.py
```

### 6.2 Capture scan output to a log file

```bash
python2 WireLurkerDetectorOSX.py | tee ~/Desktop/wirelurker-scan-$(date +%Y%m%d-%H%M%S).log
```

```text
WireLurker Detector (version 1.2.0)
Copyright (c) 2014, Palo Alto Networks, Inc.
...
```

### 6.3 Scan multiple Macs with one command

Use SSH plus the raw GitHub URL:

```bash
ssh admin@mac-host \
    'curl -s https://raw.githubusercontent.com/PaloAltoNetworks/WireLurkerDetector/master/WireLurkerDetectorOSX.py \
     | /usr/bin/python2 -'
```

> [!WARNING]
> Piping a remote script into the interpreter trusts GitHub's TLS and the repo's integrity. For incident-response work, prefer to pin a known-good commit hash by downloading once, verifying its SHA-256, and then distributing the file.

### 6.4 Decrypt a list of captured messages in batch

```bash
while IFS= read -r msg; do
    printf '%s => ' "$msg"
    python2 decryptor.py "$msg"
done < captured_messages.txt
```

### 6.5 Remove WireLurker artifacts after a confirmed hit

The detector does not clean up. If the scan confirms infection, a reasonable cleanup sequence — performed by a human, with backups — is:

```bash
# 1. Preserve evidence first.
sudo tar czf ~/wirelurker-evidence.tar.gz \
    /Library/LaunchDaemons/com.apple.machook_damon.plist \
    /Users/Shared/run.sh 2>/dev/null

# 2. Unload LaunchDaemons before deleting plists.
sudo launchctl unload /Library/LaunchDaemons/com.apple.machook_damon.plist 2>/dev/null

# 3. Delete each flagged malicious file.
sudo rm -f /Library/LaunchDaemons/com.apple.machook_damon.plist
sudo rm -f /Users/Shared/run.sh
# ...and so on for every path the detector reported.

# 4. Re-run the detector and confirm a clean report.
python2 WireLurkerDetectorOSX.py
```

> [!CAUTION]
> Do not run these commands blindly. Only delete the specific paths that the detector actually reported on *this* system, and only after you understand why each file was flagged.

---

## 7. Windows Variant

The repository does **not** ship a Windows detector binary. The file [`HOWTO-Windows.md`](https://github.com/PaloAltoNetworks/WireLurkerDetector/blob/master/HOWTO-Windows.md) in the repository describes the detection logic so that third parties can write their own tools.

The Palo Alto Networks-documented heuristic is:

1. Scan every PE executable on the system.
2. If the file contains the ZIP end-of-central-directory magic (`\x50\x4b\x05\x06`) **exactly four times**, continue — otherwise skip.
3. If the file also contains both of the literal strings `Payload/apps.app/sfbase.dylib` and `Payload/apps.app/sfbase.plist`, classify it as WireLurker.

The README explicitly points at two community-built Windows detectors. Those tools are not maintained by Palo Alto Networks and are not endorsed — treat them as reference implementations of the above logic.

> [!NOTE]
> The repository's Windows guidance is a text-only specification. To detect WireLurker on Windows you either write a scanner following the logic above or use one of the community tools linked from the upstream README.

---

## 8. Troubleshooting

Every error below is drawn from actual community reports on the upstream issue tracker or from the script's own source.

### Error: `SyntaxError: Missing parentheses in call to 'print'`

```text
File "WireLurkerDetectorOSX.py", line 120
    print 'WireLurker Detector (version %s)' % __version__
                                             ^
SyntaxError: Missing parentheses in call to 'print'
```

**Cause.** You ran the script with Python 3. WireLurker Detector is written in Python 2 and uses the Python-2 `print` statement. This exact failure is reported in community issues [#25](https://github.com/PaloAltoNetworks/WireLurkerDetector/issues/25), [#23](https://github.com/PaloAltoNetworks/WireLurkerDetector/issues/23), and [#17](https://github.com/PaloAltoNetworks/WireLurkerDetector/issues/17).

**Solution.** Run with Python 2 explicitly:

```bash
python2 WireLurkerDetectorOSX.py
```

**Prevention.** Check the interpreter before invoking: `python2 --version` should print `Python 2.7.x`. If Python 2 is not installed, use a legacy macOS VM that still ships it or install it via Homebrew from an archival tap — recent Homebrew revisions dropped the `python@2` formula.

### Error: `ERROR: The script should only be run in a Mac OS X system.`

```text
ERROR: The script should only be run in a Mac OS X system.
```

**Cause.** The detector checks `platform.system()` and refuses to run unless it returns `Darwin`. You invoked it on Linux, Windows (WSL included), or in a container that reports a non-Darwin uname.

**Solution.** Run on an actual macOS host. There is no `--force` flag; the platform guard is intentional because every hardcoded path in the script is macOS-specific.

**Prevention.** Gate your scripts with `uname -s` before invoking.

### Error: `ImportError: No module named pyDes` (decryptor only)

```text
ERROR: the script requires pyDes library installed! Please run this command to install it:
# pip install pyDes --allow-external pyDes --allow-unverified pyDes
```

**Cause.** `decryptor.py` imports `pyDes`. The module is not installed in your Python 2 environment.

**Solution.**

```bash
pip2 install pyDes
```

**Prevention.** Sanity-check the import in the interpreter you plan to use:

```bash
python2 -c "import pyDes; print(pyDes.__file__)"
```

### Error: `Usage: decryptor.py <encrypted_message>`

```text
Usage: decryptor.py <encrypted_message>
```

**Cause.** You invoked `decryptor.py` without exactly one argument.

**Solution.** Pass one positional argument — the captured base64 WireLurker-C message — as a single quoted string:

```bash
python2 decryptor.py 'CAPTURED_MESSAGE_HERE'
```

**Prevention.** Always quote the argument. Captured traffic often contains shell metacharacters.

### Error: antivirus flags `WireLurkerDetectorOSX.py` itself as a trojan

**Cause.** Some endpoint scanners (historically Avast — see community issue [#19](https://github.com/PaloAltoNetworks/WireLurkerDetector/issues/19)) fire heuristics on the hardcoded list of WireLurker paths embedded in the source.

**Solution.** This is a false positive. The script does not download, write, or execute any of those paths — it only checks whether they already exist. Verify the file's checksum against the upstream repository, and add an allowlist entry in your AV for the script's location.

**Prevention.** When distributing the script internally, store the SHA-256 hash next to the file and compare on each deployment.

### Error: `curl: (60) SSL certificate problem`

```text
curl: (60) SSL certificate problem: unable to get local issuer certificate
```

**Cause.** The macOS system you're running on is old enough that its root CA bundle no longer trusts GitHub's current certificate chain. This exact symptom is reported in community issue [#24](https://github.com/PaloAltoNetworks/WireLurkerDetector/issues/24).

**Solution.** Either update `curl` and the system root certificates, or download the script from a modern machine and transfer it via USB / SCP.

**Prevention.** For incident response on vintage Macs, prepare a signed, hash-verified copy of the detector on removable media in advance.

### Error: application scan appears to hang

**Cause.** The third pass walks every directory tree inside `/Applications` and `~/Applications` and opens an `Info.plist` for every `.app` bundle it encounters. On systems with large media-authoring suites this can take many minutes with no intermediate progress output.

**Solution.** Wait. If it runs for more than ~15 minutes, open another Terminal and check that the Python process is still advancing with `sudo fs_usage -f filesys python2`.

**Prevention.** Run the scan while you are doing something else; it is I/O-bound rather than interactive.

### Error: `UnicodeDecodeError` reading a plist

**Cause.** An unusual `Info.plist` encoding in one of the `.app` bundles chokes the Python 2 `plistlib.readPlist` call.

**Solution.** The `is_app_infected()` function wraps the plist read in a `try/except Exception: return False`, so the script continues to the next bundle. The affected bundle is silently treated as "not infected". If you need certainty, open that plist by hand:

```bash
plutil -convert xml1 -o - "/Applications/Suspect.app/Contents/Info.plist"
```

**Prevention.** None available from the tool; it is a limitation of Python 2's `plistlib`.

### Error: `python: command not found`

```text
-bash: python: command not found
```

**Cause.** Modern macOS removes the bare `python` command. Only `python3` and (if installed) `python2` are present.

**Solution.** Invoke `python2` explicitly everywhere:

```bash
python2 WireLurkerDetectorOSX.py
```

**Prevention.** Create a shell alias for incident-response work: `alias ir-python='python2'`.

### Error: `Permission denied` reading `/Library/LaunchDaemons/…`

**Cause.** Some LaunchDaemon plists are readable only by root; additionally, System Integrity Protection (SIP) restricts read access to certain paths on newer macOS versions.

**Solution.** Run the detector under `sudo`:

```bash
sudo python2 WireLurkerDetectorOSX.py
```

**Prevention.** For thorough forensic scans, always invoke with `sudo`. The script does not *require* root, but it produces more complete results with it.

### Error: the detector reports **no** infection on a host you believe is compromised

**Cause.** The hardcoded MALICIOUS_FILES / SUSPICIOUS_FILES lists are the exact paths observed in 2014. A different variant — or a modern macOS malware family that borrowed the WireLurker name — will not match.

**Solution.** Treat a clean result as "no 2014-era WireLurker indicators", not as "the host is malware-free". Follow up with a general-purpose macOS incident-response scan (KnockKnock, BlockBlock, or equivalent).

**Prevention.** Include the detector as *one* item in a wider incident-response checklist, never as the sole diagnostic.

### Error: `PermissionError` walking `~/Applications`

**Cause.** The current user does not own `~/Applications`, or that directory does not exist and the script's `os.walk` on it returns no bundles silently.

**Solution.** No action needed — `os.walk` on a missing directory produces an empty iterator. Optionally pre-create the directory if you want to confirm the scan actually traversed it.

**Prevention.** Check `ls -la ~/Applications` before running the scan if your workflow depends on per-user application directories.

### Error: `Avast detects WireLurkerDetectorOSX.py as trojan` (see #19)

See the earlier entry — false-positive on the hardcoded path list.

### Error: launch daemon won't unload during cleanup

```text
Could not find specified service
```

**Cause.** The plist was removed before `launchctl unload` was called, or the daemon is referenced by a different label.

**Solution.** Use `launchctl list | grep com.apple.machook` to find the actual loaded label, then `launchctl unload -w /Library/LaunchDaemons/<file>.plist`.

**Prevention.** Always `launchctl unload` before deleting the plist.

### Error: Time Machine restores reintroduce infected files

**Cause.** Time Machine backups from before the cleanup still contain the malicious files; when a user restores from those backups the infection can reappear. This is discussed in community issue [#18](https://github.com/PaloAltoNetworks/WireLurkerDetector/issues/18).

**Solution.** Exclude or purge the infected paths from Time Machine backups (System Settings → Time Machine → Options → add exclusions) before restoring, and re-scan any restored system immediately.

**Prevention.** Document cleanup dates so users can make informed restore decisions.

### Error: `decryptor.py` prints a Python traceback instead of plaintext

```text
Traceback (most recent call last):
  File "decryptor.py", line 23, in <module>
    plaintext = des_cryptor.decrypt(base64.b64decode(encrypted_data))
  ...
```

**Cause.** The input is not a valid WireLurker-C message (wrong first-10-character prefix, corrupted base64, or a message from a different malware family).

**Solution.** Double-check that you passed exactly the captured C&C payload, unchanged, as a single quoted argument.

**Prevention.** Capture C&C traffic with a tool that preserves byte-for-byte payloads (`tcpdump -w`, Wireshark raw export) and pass the string as-is.

### Error: `python2` missing on modern macOS

**Cause.** Apple removed the system Python 2 in macOS 12.3. Homebrew's `python@2` is long-archived.

**Solution.** Use a macOS VM with an older OS, or install Python 2.7 from source:

```bash
curl -O https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz
tar xzf Python-2.7.18.tgz
cd Python-2.7.18 && ./configure && make && sudo make install
```

**Prevention.** For any long-term IR workflow that depends on Python 2 tools, keep a pinned VM snapshot with `python2` pre-installed.

---

## 9. FAQ

**Q: Is this tool still useful in 2025?**

Only for forensics, research, and historical analysis. It matches the exact 2014 WireLurker indicators. It will not detect newer macOS malware, and modern macOS versions have closed several of the infection vectors WireLurker used. If you are cleaning up a legacy machine or writing a thesis on the 2014 campaign, yes — otherwise, prefer a modern endpoint tool.

**Q: Does it need `sudo`?**

Not strictly. The script itself never calls `sudo`. Some of the paths it checks may be restricted to root on modern macOS; running with `sudo` guarantees that the presence-check reaches them. For best results during incident response, run with `sudo`.

**Q: Will it remove anything it finds?**

No. The detector is read-only. Even the function that calls `ls -ldO` for the hidden-file fallback only reads metadata. You must remove flagged artifacts yourself.

**Q: Why does my antivirus flag the script?**

The file literally contains strings like `/Library/LaunchDaemons/com.apple.machook_damon.plist`. Signature-based scanners see the strings in the WireLurker IOC list and fire. This is a known false positive — see issue [#19](https://github.com/PaloAltoNetworks/WireLurkerDetector/issues/19). Whitelist the file by hash.

**Q: Why does the decryptor need the first 10 characters of the message?**

Those characters encode the session key. The decryptor sums them as integers, appends the literal string `dksyel`, and uses the result as the DES-ECB key. Without those 10 bytes the ciphertext cannot be keyed correctly.

**Q: Can I port this to Python 3?**

Community patches exist in forks. The core changes are `print` → `print(...)`, `plistlib.readPlist` → `plistlib.load(open(p, 'rb'))`, and byte-vs-string handling in the `subprocess.Popen.stdout.read()` comparison. The upstream repository remains the original Python 2 release.

**Q: Does it work on Apple Silicon (M1/M2/M3)?**

Yes, as long as you have a working Python 2 interpreter. The script uses only `os`, `stat`, `platform`, `plistlib`, `subprocess`, and `sys` — all architecture-independent.

**Q: What happens on macOS with APFS versus HFS+?**

Both are supported. The hidden-file check uses `UF_HIDDEN` via `os.stat(f).st_flags`, which works on both filesystems. On systems where `UF_HIDDEN` is not available, the script falls back to `ls -ldO … | grep hidden`, which also works.

**Q: Why is the `is_file_hidden()` fallback looking at `$5` from `ls -ldO`?**

`ls -ldO` prints file flags in the fifth column on BSD/macOS. The fallback path exists because very old Python builds did not expose `stat.UF_HIDDEN`. You will almost never hit this branch in practice.

**Q: Does it detect iOS infections?**

No. The original WireLurker campaign also infected iOS devices via USB pairing, but this tool only inspects the host macOS filesystem. Detecting a compromised iOS device requires inspecting it directly (provisioning profiles, installed IPAs).

**Q: Is there a GUI version?**

The README links to third-party Windows detectors, some with GUIs. There is no official Palo Alto Networks GUI build for macOS.

**Q: Why does the repository contain `HOWTO-Windows.md` instead of a Windows script?**

The repository documents the Windows detection *logic* (`HOWTO-Windows.md`) rather than shipping a binary. Two community implementations are linked from the README.

**Q: Where does the "suspicious" path `~/Library/Caches/com.maiyadi.appinstaller/` come from?**

`maiyadi` is the Chinese app distributor whose compromised installer vector WireLurker originally rode on. The cache directory indicates that the installer ran at some point, though not necessarily that the device is infected.

**Q: What license is this under?**

An ISC-style permissive license (Copyright © 2014, Palo Alto Networks, Inc.). You may copy, modify, and redistribute with attribution. See the `LICENSE` file for the exact wording.

**Q: How do I contribute a fix?**

The repository accepts pull requests; the last functional change was in 2014 and later commits are metadata changes. Open an issue before investing time in a large change.

**Q: What's the difference between "malicious" and "suspicious" files in the output?**

Malicious files are specific known-bad paths that WireLurker drops. Suspicious files are directories or paths associated with the installer's ecosystem (e.g. the `maiyadi` appinstaller cache) — they indicate elevated risk but can exist on clean systems that had the installer software briefly.

**Q: Does it write logs anywhere?**

No. All output goes to stdout. Redirect with `>` or `tee` if you need a log (see §6.2).

---

## 10. Reference

### 10.1 Hardcoded malicious paths (source: `WireLurkerDetectorOSX.py` → `MALICIOUS_FILES`)

| # | Path |
|---|---|
| 1 | `/Users/Shared/run.sh` |
| 2 | `/Library/LaunchDaemons/com.apple.machook_damon.plist` |
| 3 | `/Library/LaunchDaemons/com.apple.globalupdate.plist` |
| 4 | `/usr/bin/globalupdate` |
| 5 | `/usr/local/machook/` |
| 6 | `/usr/bin/WatchProc` |
| 7 | `/usr/bin/itunesupdate` |
| 8 | `/Library/LaunchDaemons/com.apple.watchproc.plist` |
| 9 | `/Library/LaunchDaemons/com.apple.itunesupdate.plist` |
| 10 | `/System/Library/LaunchDaemons/com.apple.appstore.plughelper.plist` |
| 11 | `/System/Library/LaunchDaemons/com.apple.MailServiceAgentHelper.plist` |
| 12 | `/System/Library/LaunchDaemons/com.apple.systemkeychain-helper.plist` |
| 13 | `/System/Library/LaunchDaemons/com.apple.periodic-dd-mm-yy.plist` |
| 14 | `/usr/bin/com.apple.MailServiceAgentHelper` |
| 15 | `/usr/bin/com.apple.appstore.PluginHelper` |
| 16 | `/usr/bin/periodicdate` |
| 17 | `/usr/bin/systemkeychain-helper` |
| 18 | `/usr/bin/stty5.11.pl` |

### 10.2 Hardcoded suspicious paths (source: `SUSPICIOUS_FILES`)

| # | Path |
|---|---|
| 1 | `/etc/manpath.d/` |
| 2 | `/usr/local/ipcc/` |
| 3 | `~/Library/Caches/com.maiyadi.appinstaller/` |
| 4 | `~/Library/Saved Application State/com.maiyadi.appinstaller.savedState/` |

`~` expands via `os.path.expanduser('~')`.

### 10.3 `.app` infection markers (source: `is_app_infected()`)

| Marker | Files checked | Trigger |
|---|---|---|
| Hidden executable swap | `Contents/MacOS/<CFBundleExecutable>`, `Contents/MacOS/<CFBundleExecutable>_` | Both hidden |
| Hidden trojan loader | `Contents/Resources/start.sh`, `Contents/Resources/FontMap1.cfg` | Both hidden |
| Embedded installer | `Contents/MacOS/appinstaller`, `Contents/Resources/infoplistab` | Both exist as files |

### 10.4 Exit codes

| Code | Meaning |
|---|---|
| `0` | Clean |
| `1` | Indicators found |
| `-1` | Wrong OS, or interpreter/import failure |

### 10.5 Files in the repository

| File | Purpose |
|---|---|
| `WireLurkerDetectorOSX.py` | The macOS detector script. |
| `decryptor.py` | DES-ECB decryptor for captured WireLurker-C C&C traffic. |
| `HOWTO-Windows.md` | Text-only specification of the Windows detection logic. |
| `README.md` | Upstream usage notes. |
| `LICENSE` | ISC-style license text. |

### 10.6 External references

- Palo Alto Networks research article: <http://researchcenter.paloaltonetworks.com/2014/11/wirelurker-new-era-os-x-ios-malware/>
- Windows variant write-up: <http://researchcenter.paloaltonetworks.com/2014/11/wirelurker-windows/>
- Community Windows cleaner: <https://github.com/ltfish/WireLurkerCleaner>
- Community Windows detector: <https://github.com/kaustubhsant/WireLurkerDetector>

---

_Generated May 2026 from PaloAltoNetworks/WireLurkerDetector at 0957460. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial, AI-generated guide for a Palo Alto Networks open-source repository; not reviewed by Palo Alto Networks._
