[{"content":"\nWhy Build Your Own Lab? When you start getting serious about malware analysis, your options are usually a cloud sandbox like Any.run or Cuckoo, or a locally spun-up VM with Windows Defender disabled. Both work for basic analysis, but neither gives you the level of control and visibility a dedicated lab provides.\nA proper lab lets you:\nSee exactly what a sample does at the network level, including decrypted HTTPS traffic Correlate behavioral events across process, registry, file system, and network simultaneously Protect your real IP from appearing in malware C2 infrastructure logs Restore to a clean state in seconds and run the next sample Feed everything into a open-source SIEM for long-term pattern analysis This post walks through building exactly that, from bare metal to a fully operational lab with VPN routing, transparent TLS interception, and Wazuh monitoring across all VMs.\nArchitecture Internet | └── Mullvad WireGuard VPN | Host Machine (Linux Desktop) | ┌───────┴──────────────────────────────┐ │ libvirt default NAT (192.168.122.x) │ │ | │ │ PFSense VM │ │ WAN: 192.168.122.x (NAT) │ │ LAN: 10.0.1.1/24 │ │ VPN: Mullvad WireGuard │ │ | │ │ pfsense-lan (vmbr0) │ │ 10.0.1.0/24 │ │ | │ │ Remnux VM │ │ enp1s0: 10.0.1.x │ │ enp2s0: 10.0.2.1/24 │ │ DHCP/DNS for analysis LAN │ │ mitmproxy TLS intercept │ │ Wazuh agent │ │ | │ │ analysis-lan (vmbr1) │ │ 10.0.2.0/24 │ │ | │ │ Windows Analysis VM │ │ 10.0.2.10 (DHCP) │ │ FLARE-VM tool suite │ │ Wazuh agent │ └──────────────────────────────────────┘ Wazuh Manager: 192.168.2.137 (separate server) Key design principles:\nStatic analysis never happens on the host — only on Remnux Dynamic analysis only happens on the Windows VM All lab traffic exits via Mullvad — your real IP never appears in C2 logs Wazuh monitors every VM continuously TLS is intercepted transparently — you see exactly what malware sends over HTTPS What You Need Hardware:\nLinux desktop with Intel VT-x or AMD-V (check with kvm-ok) Minimum 32GB RAM, 500GB free disk Optional: separate 4TB SSD for VM backups and sample storage Software:\nvirt-manager + QEMU/KVM Python 3 ISOs to download:\nPFSense CE — free community edition (downloads as .gz, decompress with gunzip) Ubuntu 22.04 Desktop Windows 10 Enterprise Evaluation — 180 days free VirtIO drivers Accounts:\nMullvad VPN — anonymous, no account required, pay with cash/crypto Wazuh — free and open source Host Setup sudo apt install -y \\ virt-manager qemu-kvm qemu-utils \\ libvirt-daemon-system libvirt-clients \\ build-essential python3 bridge-utils \\ tcpdump git python-is-python3 sudo usermod -aG libvirt,kvm $USER sudo setfacl -m u:libvirt-qemu:rx /home/$USER sudo systemctl enable --now libvirtd newgrp libvirt SeaBIOS Hardening One of the techniques from c3rb3ru5d3d53c\u0026rsquo;s lab guide is replacing the default SeaBIOS with a modified version that makes the VM harder to fingerprint. Malware commonly checks BIOS strings to detect virtual environments — this reduces that signal.\ngit clone https://github.com/c3rb3ru5d3d53c/seabios.git cd seabios \u0026amp;\u0026amp; make sudo cp /usr/share/seabios/bios.bin /usr/share/seabios/bios.bin.bak sudo cp out/bios.bin /usr/share/seabios/bios.bin This only affects what QEMU presents to VMs — your actual motherboard BIOS is a physical chip and is completely untouched.\nFLARE-VM\u0026rsquo;s VM detection check will say \u0026ldquo;not on a VM\u0026rdquo; after this change. That\u0026rsquo;s the point.\nVirtual Networks Create two isolated networks — neither has a direct internet path. PFSense controls all routing.\n# PFSense LAN — between PFSense and Remnux cat \u0026gt; /tmp/pfsense-lan.xml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; \u0026lt;network\u0026gt; \u0026lt;name\u0026gt;pfsense-lan\u0026lt;/name\u0026gt; \u0026lt;forward mode=\u0026#39;none\u0026#39;/\u0026gt; \u0026lt;bridge name=\u0026#39;vmbr0\u0026#39; stp=\u0026#39;on\u0026#39; delay=\u0026#39;0\u0026#39;/\u0026gt; \u0026lt;/network\u0026gt; EOF # Analysis LAN — between Remnux and Windows cat \u0026gt; /tmp/analysis-lan.xml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; \u0026lt;network\u0026gt; \u0026lt;name\u0026gt;analysis-lan\u0026lt;/name\u0026gt; \u0026lt;forward mode=\u0026#39;none\u0026#39;/\u0026gt; \u0026lt;bridge name=\u0026#39;vmbr1\u0026#39; stp=\u0026#39;on\u0026#39; delay=\u0026#39;0\u0026#39;/\u0026gt; \u0026lt;/network\u0026gt; EOF virsh net-define /tmp/pfsense-lan.xml \u0026amp;\u0026amp; virsh net-start pfsense-lan \u0026amp;\u0026amp; virsh net-autostart pfsense-lan virsh net-define /tmp/analysis-lan.xml \u0026amp;\u0026amp; virsh net-start analysis-lan \u0026amp;\u0026amp; virsh net-autostart analysis-lan PFSense VM PFSense sits between the internet and everything else. It manages the Mullvad WireGuard VPN tunnel and the firewall rules that keep your real IP hidden and your home network unreachable from the lab.\nCreate the disk and VM qemu-img create -f qcow2 -o cluster_size=2M ~/malware_lab/vms/pfsense.qcow2 16G In virt-manager: create a new VM from the disk, OS type FreeBSD, 2GB RAM, 1 CPU. Add two NICs:\nNIC 1: default (libvirt NAT) — this becomes WAN NIC 2: pfsense-lan (vmbr0) — this becomes LAN Boot from the PFSense ISO.\nConsole configuration When PFSense boots, assign interfaces:\nVLANs? n WAN: vtnet0 LAN: vtnet1 Then option 2 to set LAN IP:\nLAN IP: 10.0.1.1/24 DHCP: yes, range 10.0.1.10 - 10.0.1.240 DNS Resolver Access the WebGUI at https://10.0.1.1. Under Services → DNS Resolver → Custom options, add upstream forwarders:\nforward-zone: name: \u0026#34;.\u0026#34; forward-addr: 8.8.8.8 forward-addr: 1.1.1.1 Mullvad WireGuard VPN Install the WireGuard package from System → Package Manager. Then generate a keypair from the PFSense shell (Option 8):\nmkdir -p /usr/local/etc/wireguard cd /usr/local/etc/wireguard wg genkey | tee privkey | wg pubkey \u0026gt; pubkey cat pubkey Download a WireGuard config from https://mullvad.net/en/account/wireguard-config — this generates a keypair and registers it with your account in one step. Use the values from that config to set up the tunnel and peer in PFSense under VPN → WireGuard.\nAssign the tunnel as a new interface (MULLVAD), set its IPv4 address to your assigned Mullvad tunnel IP, create a gateway pointing to that IP, and add a LAN firewall rule routing all traffic through that gateway.\nVerify from Remnux after setup:\ncurl -s https://am.i.mullvad.net/json | python3 -m json.tool # Should show: \u0026#34;mullvad_exit_ip\u0026#34;: true Wazuh bypass Since Wazuh manager lives on your local network (192.168.2.137) and all lab traffic routes through Mullvad, you need a static route to bypass the VPN for Wazuh specifically:\nSystem → Routing → Static Routes Destination: 192.168.2.137/32 Gateway: WAN_DHCP Firewall → Rules → LAN → Add (two rules) TCP, source LAN net, destination 192.168.2.137, port 1514, gateway WAN_DHCP TCP, source LAN net, destination 192.168.2.137, port 1515, gateway WAN_DHCP This allows Wazuh agents to report home without exposing anything else on your local network to the lab.\nRemnux VM (Ubuntu 22.04) Remnux serves two purposes: it\u0026rsquo;s the static analysis workstation and the network gateway for the Windows VM. Everything the Windows VM sends to the internet passes through Remnux first, which is why TLS interception works.\nqemu-img create -f qcow2 -o cluster_size=2M ~/malware_lab/vms/remnux.qcow2 60G Two NICs:\nNIC 1: pfsense-lan (uplink to PFSense/internet) NIC 2: analysis-lan (gateway for Windows VM) Post-install configuration sudo apt install -y \\ dnsmasq isc-dhcp-server docker.io \\ wireshark tshark tcpdump nmap netcat \\ python3-pip git curl wget vim net-tools \\ iptables-persistent pip3 install --user pefile capstone dnfile yara-python requests scapy sudo usermod -aG docker $USER docker pull remnux/remnux-distro:focal echo \u0026#39;alias remnux=\u0026#34;docker run --rm -it -v $(pwd):$(pwd) -w $(pwd) -u remnux remnux/remnux-distro:focal bash\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc git clone https://github.com/Yara-Rules/rules.git ~/yara-rules/community Network configuration # /etc/netplan/01-lab.yaml network: version: 2 renderer: networkd ethernets: enp1s0: dhcp4: yes enp2s0: addresses: - 10.0.2.1/24 DHCP for the analysis LAN # /etc/dhcp/dhcpd.conf subnet 10.0.2.0 netmask 255.255.255.0 { option routers 10.0.2.1; option domain-name-servers 10.0.2.1; range 10.0.2.10 10.0.2.254; } IP forwarding and NAT sudo sysctl -w net.ipv4.ip_forward=1 sudo iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE sudo iptables -A FORWARD -i enp2s0 -o enp1s0 -j ACCEPT sudo iptables -A FORWARD -i enp1s0 -o enp2s0 -m state --state RELATED,ESTABLISHED -j ACCEPT sudo netfilter-persistent save mitmproxy TLS interception wget https://downloads.mitmproxy.org/12.2.3/mitmproxy-12.2.3-linux-x86_64.tar.gz tar -xzvf mitmproxy-12.2.3-linux-x86_64.tar.gz sudo mv mitmdump mitmproxy mitmweb /usr/bin/ # Redirect HTTP and HTTPS from analysis LAN through mitmproxy sudo iptables -t nat -A PREROUTING -i enp2s0 -p tcp --dport 80 -j REDIRECT --to-port 8080 sudo iptables -t nat -A PREROUTING -i enp2s0 -p tcp --dport 443 -j REDIRECT --to-port 8080 sudo netfilter-persistent save # Generate CA cert mitmproxy --mode transparent --listen-host 0.0.0.0 --listen-port 8080 # Ctrl+C once started — cert is at ~/.mitmproxy/mitmproxy-ca-cert.cer Create a session startup script at ~/start-intercept.sh:\n#!/bin/bash NAME=${1:-capture_$(date +%Y%m%d_%H%M%S)} mkdir -p ~/pcaps ~/tls-secrets mitmproxy \\ --mode transparent \\ --listen-host 0.0.0.0 \\ --listen-port 8080 \\ --ssl-insecure \\ --save-stream-file ~/pcaps/${NAME}.pcap \\ --set hardump=~/tls-secrets/${NAME}.txt Install the CA cert on Windows by serving it from Remnux:\npython3 -m http.server 8888 --directory ~/.mitmproxy/ Then on Windows browse to http://10.0.2.1:8888/mitmproxy-ca-cert.cer and run:\ncertutil -addstore root mitmproxy-ca-cert.cer Windows Analysis VM (FLARE-VM) The Windows VM sits only on the analysis LAN — it has no path to your host or home network. All its internet traffic routes through Remnux → PFSense → Mullvad.\nqemu-img create -f qcow2 -o cluster_size=2M ~/malware_lab/vms/windows-analysis.qcow2 128G One NIC: analysis-lan only.\nVirtIO disk driver — the common gotcha The Windows installer won\u0026rsquo;t see a VirtIO disk. When the disk selection screen shows nothing, click Load driver and browse the VirtIO ISO to viostor\\w10\\amd64. Select Red Hat VirtIO SCSI pass-through controller and the disk appears. Windows will then have the driver baked in from first boot.\nPost-install hardening # Disable Tamper Protection first via Settings → Windows Security UI # Then via registry: reg add \u0026#34;HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\u0026#34; /v DisableAntiSpyware /t REG_DWORD /d 1 /f reg add \u0026#34;HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows Defender\\Real-Time Protection\u0026#34; /v DisableRealtimeMonitoring /t REG_DWORD /d 1 /f # Disable Windows Update Stop-Service wuauserv -Force Set-Service wuauserv -StartupType Disabled # Disable firewall netsh advfirewall set allprofiles state off # Static DNS — survives reboots # Control Panel → Network → Ethernet → IPv4 → Use following DNS # Preferred: 10.0.2.1 # Leave alternate blank FLARE-VM Set-ExecutionPolicy Unrestricted -Force Invoke-WebRequest -Uri \u0026#34;https://raw.githubusercontent.com/mandiant/flare-vm/main/install.ps1\u0026#34; -OutFile \u0026#34;$env:TEMP\\install.ps1\u0026#34; \u0026amp; \u0026#34;$env:TEMP\\install.ps1\u0026#34; FLARE-VM will say \u0026ldquo;not on a VM\u0026rdquo; — that\u0026rsquo;s the SeaBIOS modification working. Proceed anyway. The install takes 30-60 minutes with multiple automatic reboots.\nRecommended packages: x64dbg, Ghidra, Die, CFF Explorer, Procmon, Regshot, API Monitor, Wireshark, HxD, CyberChef, Python3.\nWazuh Integration With Wazuh agents on both Remnux and Windows, every process creation, registry modification, file system change, and network connection is captured and correlated in your SIEM.\nRemnux agent curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | sudo gpg --dearmor -o /usr/share/keyrings/wazuh.gpg echo \u0026#34;deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main\u0026#34; \\ | sudo tee /etc/apt/sources.list.d/wazuh.list sudo apt-get update sudo WAZUH_MANAGER=\u0026#34;192.168.2.137\u0026#34; apt-get install -y wazuh-agent # Register with a valid name — single letters are rejected sudo /var/ossec/bin/agent-auth -m 192.168.2.137 -A remnux-analysis sudo systemctl enable --now wazuh-agent Windows agent Invoke-WebRequest -Uri \u0026#34;https://packages.wazuh.com/4.x/windows/wazuh-agent-4.14.5-1.msi\u0026#34; -OutFile \u0026#34;$env:TEMP\\wazuh-agent.msi\u0026#34; Start-Process msiexec.exe -ArgumentList \u0026#34;/i $env:TEMP\\wazuh-agent.msi /q WAZUH_MANAGER=192.168.2.137\u0026#34; -Wait \u0026amp; \u0026#34;C:\\Program Files (x86)\\ossec-agent\\agent-auth.exe\u0026#34; -m 192.168.2.137 -A windows-analysis Start-Service wazuh Analysis Workflow Static analysis (always on Remnux, never the host) # Quick triage file sample.exe strings -n 8 sample.exe | head -100 md5sum sample.exe \u0026amp;\u0026amp; sha256sum sample.exe yara -r ~/yara-rules/community/ sample.exe # Full REMnux toolset remnux # Inside the container — pecheck, floss, capa and much more Dynamic analysis session # 1. Start TLS intercept on Remnux ~/start-intercept.sh session-name # 2. Serve sample to Windows python3 -m http.server 8000 --directory ~/samples/ # Windows: http://10.0.2.1:8000/sample.exe On Windows:\nOpen Procmon Take Regshot first snapshot Execute sample Observe behavior — watch Procmon, Wazuh dashboard, network Take Regshot second snapshot and compare Stop Procmon # 3. Analyse PCAP with TLS decryption wireshark ~/pcaps/session-name.pcap # Edit → Preferences → Protocols → TLS # Pre-Master-Secret log → ~/tls-secrets/session-name.txt # 4. Restore Windows to clean state python3 lab.py --restore windows-analysis flare-clean Backup Strategy VM disks and configs back up to a 4TB Samsung SSD formatted as BTRFS (for compression and checksumming). The lab.py manager script handles everything:\n# Format and mount (one time) sudo mkfs.btrfs -f -L malware-backup /dev/sdb1 sudo mount -o compress=zstd /dev/sdb1 /mnt/malware-ssd # Run backup (VMs must be shut down) python3 lab.py --stop-lab python3 lab.py --backup # Restore after OS wipe sudo mount /mnt/malware-ssd python3 lab.py --restore-from-backup Malware samples live permanently on the SSD — never on the NVMe. This means your collection survives an OS reinstall automatically.\nSecurity Posture Lab VMs Host Real IP exposed Never (Mullvad) Normal Home network accessible No Yes Wazuh monitored Yes No Internet access Via Mullvad only Direct Samples executed Windows VM only Never The only home network resource reachable from lab VMs is the Wazuh manager on ports 1514 and 1515 specifically — enforced by PFSense firewall rules. Everything else on your home network is unreachable from the lab.\nLab Manager Script All of this is managed by a Python script (lab.py) that wraps virsh and qemu-img:\npython3 lab.py --start-lab # PFSense → Remnux → Windows in order python3 lab.py --stop-lab python3 lab.py --status # full overview of VMs, snapshots, captures python3 lab.py --restore windows-analysis flare-clean python3 lab.py --tls-intercept --session-name wannacry-01 python3 lab.py --check-vpn-ip # verify Mullvad is active python3 lab.py --backup # compress and back up all VM disks python3 lab.py --restore-from-backup # full restore after OS wipe References c3rb3ru5d3d53c\u0026rsquo;s KVM Malware Lab — the approach this is based on c3rb3ru5d3d53c\u0026rsquo;s GitHub — modified SeaBIOS and helper scripts REMnux — Linux distribution for malware analysis FLARE-VM — Windows malware analysis distribution by Mandiant Mullvad VPN — anonymous VPN, no logs, no account required Wazuh — open source SIEM/XDR John Hammond — malware analysis content Vx Undergound — malware sample repository TCM Security - Practical Malware Analysis \u0026amp; Triage Course Built on Ubuntu 22.04 LTS with KVM/QEMU. All analysis traffic exits via WireGuard. Tested with WannaCry, NightSky ransomware, and BPFDoor variants.\n","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/malwarelab/","summary":"\u003cp\u003e\u003cimg alt=\"Hero image: Image\" loading=\"lazy\" src=\"/posts/malwarelab/images/1.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"why-build-your-own-lab\"\u003eWhy Build Your Own Lab?\u003c/h2\u003e\n\u003cp\u003eWhen you start getting serious about malware analysis, your options are usually a cloud sandbox like Any.run or Cuckoo, or a locally spun-up VM with Windows Defender disabled. Both work for basic analysis, but neither gives you the level of control and visibility a dedicated lab provides.\u003c/p\u003e\n\u003cp\u003eA proper lab lets you:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eSee \u003cstrong\u003eexactly\u003c/strong\u003e what a sample does at the network level, including decrypted HTTPS traffic\u003c/li\u003e\n\u003cli\u003eCorrelate behavioral events across process, registry, file system, and network simultaneously\u003c/li\u003e\n\u003cli\u003eProtect your real IP from appearing in malware C2 infrastructure logs\u003c/li\u003e\n\u003cli\u003eRestore to a clean state in seconds and run the next sample\u003c/li\u003e\n\u003cli\u003eFeed everything into a open-source SIEM for long-term pattern analysis\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis post walks through building exactly that, from bare metal to a fully operational lab with VPN routing, transparent TLS interception, and Wazuh monitoring across all VMs.\u003c/p\u003e","title":"Building a Professional KVM Malware Analysis Lab on Linux"},{"content":"\nTL;DR On 11 June 2026, attackers hijacked more than 400 Arch User Repository (AUR) packages and rewrote their build scripts to pull a malicious npm package that executed a Rust credential stealer — analyst-named deps — during the build. Sonatype calls the campaign Atomic Arch. Independent researcher Whanos reverse-engineered the payload, and that analysis is the source for most of the indicators here.\nI run Arch with a Wazuh agent, so I built a hunt around the IOCs and ran it against my own machine. The host came back clean. The interesting part isn\u0026rsquo;t the verdict — it\u0026rsquo;s that two checks looked like findings and weren\u0026rsquo;t: a PID-listing diff that screamed \u0026ldquo;hidden process,\u0026rdquo; and a loopback listener on a random high port that had exactly the shape of the malware\u0026rsquo;s local proxy. Both were artifacts of how the checks work, not evidence. This post is the hunt, why each check is trustworthy or not, the two head-fakes, and how to wire durable detections into Wazuh so the next variant trips an alert on its own.\nThis one earns a careful hunt rather than a quick hash check for two reasons: the affected-package list is large, still growing, and explicitly incomplete — so checking one package name isn\u0026rsquo;t enough — and the payload ships an optional eBPF rootkit that, on privileged runs, hides PIDs, process names, and socket inodes from the exact tools you\u0026rsquo;d reach for in live response. That second property reshapes everything: you can\u0026rsquo;t fully trust what the live system tells you, so the methodology has to lean on artifacts the rootkit can\u0026rsquo;t easily falsify.\nThe Setup Host: Arch Linux workstation, rolling, with yay and paru as AUR helpers EDR/telemetry: Wazuh agent (4.x) reporting to a self-hosted manager Container runtime present: containerd (relevant later — it\u0026rsquo;s one of the head-fakes) Threat: deps Linux ELF credential stealer + optional eBPF rootkit Delivery (wave 1): AUR build runs npm install atomic-lockfile, pulling atomic-lockfile@1.4.2, whose preinstall hook executes src/hooks/deps Delivery (wave 2): a separate set of accounts pushed bun install js-digest, a different payload binary from the same publisher Primary IOC: payload SHA-256 6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b The official Arch repositories were never affected — this hit the community-maintained AUR specifically.\nHow It Got In (And Why That Matters for the Hunt) This wasn\u0026rsquo;t an exploit or a zero-day. It was an attack on the trust model, which changes what you look for.\nThe attackers adopted orphaned packages — projects whose maintainers had walked away — kept the existing names and commit histories, and spoofed git metadata so the malicious edits looked like they came from a long-standing maintainer. The package looked exactly like the software you meant to install. Only the build recipe changed. A recently adopted package, or one that suddenly sprouts new install hooks after long dormancy, now deserves the same suspicion as a package from a stranger.\nThe practical consequence: you can\u0026rsquo;t hunt on \u0026ldquo;did I install something sketchy-looking.\u0026rdquo; The names are legitimate. You hunt on the payload, the delivery strings, and the lifecycle-hook pattern instead.\nWhat the Malware Actually Does Before hunting, it helps to internalize the behavior so the indicators make sense rather than being a list to grep blindly.\ndeps is a stripped, Rust-built ELF. Once it runs, it installs persistence, harvests credentials broadly, and exfiltrates. Per Whanos\u0026rsquo; analysis, the collection scope is wide: Chromium-family browser cookies and LocalStorage, Electron apps (Slack, Teams, Discord and its many forks), GitHub/npm/Vault tokens, OpenAI bearer material, SSH keys, shell histories, Docker/Podman credentials, VPN profiles, PuTTY keys.\nIt then validates stolen tokens against the real APIs for those services. That detail matters for hunting: connections to api.github.com, registry.npmjs.org, slack.com, teams.microsoft.com, discord.com, and api.openai.com are part of the malware\u0026rsquo;s behavior but are legitimate third-party endpoints. Treating them as attacker C2 sends you chasing ghosts. (I\u0026rsquo;ll come back to a real-world version of this trap later.)\nThe actual command-and-control is a Tor onion service the binary XOR-decodes at runtime, so it never appears as plaintext:\nolrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion Tasking flows over POST /api/agent to that host — but only after hopping through a local 127.0.0.1 SOCKS5-style relay the malware stands up itself. Files exfiltrate separately to temp.sh via POST /upload. And if the process holds root plus CAP_BPF or CAP_SYS_ADMIN, it loads the eBPF rootkit, whose pinned maps are the single most reliable on-host tell we have.\nA note on that rootkit, because early write-ups oversold it: it\u0026rsquo;s optional and not a privilege-escalation tool. It only loads when the binary already has root and the right capability. When it does activate, though, it materially raises live-response difficulty — which is exactly why the cleanup advice for a confirmed root execution is \u0026ldquo;reinstall,\u0026rdquo; not \u0026ldquo;remove the package.\u0026rdquo;\nIndicators at a Glance Type Indicator Payload SHA256 6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B Payload MD5 42B59FDBE1B72895B2951412222EBF40 Payload size 3,040,376 bytes Delivery (wave 1) npm install atomic-lockfile → atomic-lockfile@1.4.2 Delivery (wave 2) bun install js-digest (separate payload binary, different hash) Lifecycle hook \u0026quot;preinstall\u0026quot;: \u0026quot;./src/hooks/deps\u0026quot; Payload path src/hooks/deps Actor C2 olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion (XOR-decoded), POST /api/agent Exfil temp.sh + POST /upload Relay 127.0.0.1 SOCKS5-style transport → dest ports TCP/80, TCP/8080 Persistence /var/lib/\u0026lt;name\u0026gt;, /etc/systemd/system/\u0026lt;name\u0026gt;.service, ~/.config/systemd/user/\u0026lt;name\u0026gt;.service, Restart=always + RestartSec=30 Rootkit /sys/fs/bpf/hidden_pids, /sys/fs/bpf/hidden_names, /sys/fs/bpf/hidden_inodes Staging /usr/bin/monero-wallet-gui (cryptominer target) Two caveats before touching the keyboard: if root execution is even plausible, preserve evidence before cleanup — ideally an offline acquisition — because the rootkit makes a clean rebuild the only fully trustworthy remediation. And if the binary ran at all, treat the host as fully credential-compromised regardless of cleanup.\nTriage: Was My Box Even in Scope? Start cheap. If the host never received a compromised package, most of the heavier hunting is moot — though given the campaign\u0026rsquo;s scale I\u0026rsquo;d still glance at the eBPF and persistence checks. There were two delivery strings (npm install atomic-lockfile and bun install js-digest), so I hunted both plus the shared payload path.\n# Did any AUR helper or cache reference either malicious package? grep -rIlE \u0026#39;atomic-lockfile|js-digest\u0026#39; \\ ~/.cache/yay ~/.cache/paru /var/cache/pacman ~/.cache/pikaur 2\u0026gt;/dev/null # The give-away install lines, in build recipes and cached PKGBUILDs/.install hooks grep -rIlE \u0026#39;npm install atomic-lockfile|bun install js-digest\u0026#39; \\ ~/.cache/yay ~/.cache/paru /var/cache/pacman 2\u0026gt;/dev/null # The payload path pattern anywhere on disk find / -path /proc -prune -o -type f -name deps -path \u0026#34;*src/hooks/deps*\u0026#34; -print 2\u0026gt;/dev/null # npm caches / source trees carrying the malicious lifecycle hook grep -rIl \u0026#39;\u0026#34;preinstall\u0026#34;:\\s*\u0026#34;\\./src/hooks/deps\u0026#34;\u0026#39; / 2\u0026gt;/dev/null # package.json declaring the specific compromised version find / -path /proc -prune -o -name package.json -print 2\u0026gt;/dev/null | \\ xargs grep -lE \u0026#39;\u0026#34;atomic-lockfile\u0026#34;.*1\\.4\\.2\u0026#39; 2\u0026gt;/dev/null If you\u0026rsquo;ve installed or updated any AUR package on or after 11 June, the safest move is to check your foreign packages against the community-maintained affected-package lists and detection scripts on the aur-general mailing list, then run the checks below anyway. Treat that list as incomplete.\nThe Most Reliable Single Check: Hash Sweep The rootkit can lie about process and socket tables, but it does not rewrite file content on disk. That makes hashing the highest-confidence check available, and it catches the payload wherever it landed — npm cache, AUR build directory, or an installed persistence path.\n# Size-matched sweep on local filesystems (fast) find / -xdev -type f -size 3040376c 2\u0026gt;/dev/null | while read -r f; do h=$(sha256sum \u0026#34;$f\u0026#34; | awk \u0026#39;{print $1}\u0026#39;) if [ \u0026#34;${h^^}\u0026#34; = \u0026#34;6144D433F8A0316869877B5F834C801251BBB936E5F1577C5680878C7443C98B\u0026#34; ]; then echo \u0026#34;MATCH: $f\u0026#34; fi done If that finds nothing but suspicion remains (the second-wave binary differs, or a payload could be recompiled/padded), drop the size filter and hash everything — slower, but thorough:\nfind / -xdev -type f 2\u0026gt;/dev/null | while read -r f; do sha256sum \u0026#34;$f\u0026#34; 2\u0026gt;/dev/null; done \\ | grep -i \u0026#39;6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b\u0026#39; A match anywhere confirms the payload reached the host. Mine returned nothing.\nWhere I Almost Fooled Myself (Twice) Two checks in this hunt produce output that looks alarming until you read it properly. Both fooled me for a beat. They\u0026rsquo;re worth slowing down on, because they\u0026rsquo;re exactly the kind of thing that turns a clean hunt into a wasted evening.\nHead-fake #1: The \u0026ldquo;hidden process\u0026rdquo; that was just timing Because the rootkit hides PIDs from one view but rarely all of them, the classic detection is to diff two independent listings of running processes. I compared /proc against ps:\n$ ls -1 /proc | grep -E \u0026#39;^[0-9]+$\u0026#39; | sort -n \u0026gt; /tmp/proc_view.txt; \\ ps -eo pid --no-headers | tr -d \u0026#39; \u0026#39; | sort -n \u0026gt; /tmp/ps_view.txt $ diff /tmp/proc_view.txt /tmp/ps_view.txt 301,303c301,303 \u0026lt; 316248 \u0026lt; 316249 \u0026lt; 316250 --- \u0026gt; 316251 \u0026gt; 316252 \u0026gt; 316253 Three PIDs in one view, three different ones in the other. For a second that reads like something is being hidden.\nIt isn\u0026rsquo;t. This is the check catching its own tail. The two captures run a few milliseconds apart, and in that gap the short-lived processes from the first pipeline (ls, grep, sort) exit while the second pipeline (ps, tr, sort) spawns with fresh, higher PIDs. The tell is that the differing PIDs are adjacent and sequential (...248–250 vs ...251–253) and change on every run. A rootkit-hidden PID looks different: a stable number present in one view and absent from the other, persisting across repeated runs and not explained by timing.\nRunning both captures on a single line (semicolon, not two prompts) shrinks the window but won\u0026rsquo;t eliminate the artifact — and that\u0026rsquo;s fine, because now you know how to read it. The control here is conceptual rather than a second device: run it three times and watch the \u0026ldquo;discrepancy\u0026rdquo; move. Movement means timing; a fixed gap means investigate.\nThe check that actually decides it: eBPF pinned maps The high-confidence check is reliable precisely because the rootkit pins its maps to a filesystem it doesn\u0026rsquo;t hide:\n$ ls -la /sys/fs/bpf/hidden_pids /sys/fs/bpf/hidden_names /sys/fs/bpf/hidden_inodes 2\u0026gt;/dev/null $ Empty. None of the three maps exist. The presence of /sys/fs/bpf/hidden_* on a system you didn\u0026rsquo;t deliberately instrument is about as close to conclusive as on-host evidence gets — and the absence is what let me stop worrying about the rootkit entirely.\nHead-fake #2: The loopback listener that looked exactly like the malware\u0026rsquo;s relay The malware stands up a local SOCKS-style proxy on 127.0.0.1 and routes C2 through it. So an unexpected loopback listener on a random high port is — by shape — a textbook indicator. I found one:\n$ ss -tlnp | grep \u0026#39;127.0.0.1\u0026#39; LISTEN 0 0 127.0.0.1:41157 0.0.0.0:* $ ss -tanp | grep -E \u0026#39;:80\\b|:8080\\b\u0026#39; ESTAB 0 0 192.168.2.117:56078 34.158.255.62:80 users:((\u0026#34;spotify\u0026#34;,pid=4233,fd=119)) A loopback listener on a high port and an established outbound connection on port 80 — two of the network IOCs in the same screen. This is the moment the vacuum-investigation lesson kicked in: shape is not identity. Plenty of legitimate software binds loopback ports, and port 80 is used by approximately everything.\nThe first ss didn\u0026rsquo;t show an owning process because the socket belonged to another user, so I re-ran with privilege and a port filter: It\u0026rsquo;s containerd — the container runtime\u0026rsquo;s local API endpoint, entirely legitimate. And the Spotify connection to a Google Cloud IP on port 80 surfaced only because the malware also happens to use port 80 as a destination; it\u0026rsquo;s ordinary app traffic and an unrelated coincidence. This is the same trap as treating api.github.com as C2: a legitimate process matching one feature of the IOC set. Both ruled out the instant I identified the owner.\nHunting Persistence With the rootkit ruled out, I checked the persistence mechanism directly. The behavioral fingerprint — an aggressively restarting unit pointing at an executable under /var/lib — is specific enough to be a near-direct hit:\n# System units that restart every 30s and execute from /var/lib grep -rlE \u0026#39;Restart=always\u0026#39; /etc/systemd/system/ 2\u0026gt;/dev/null | \\ xargs grep -lE \u0026#39;RestartSec=30\u0026#39; 2\u0026gt;/dev/null | \\ xargs grep -lE \u0026#39;ExecStart=.*/var/lib/\u0026#39; 2\u0026gt;/dev/null # Per-user persistence for each human account for h in /home/* /root; do ls -la \u0026#34;$h/.config/systemd/user/\u0026#34; 2\u0026gt;/dev/null done # Any executable ELF under /var/lib that NO package owns for f in $(find /var/lib -maxdepth 3 -type f -executable 2\u0026gt;/dev/null); do if ! pacman -Qo \u0026#34;$f\u0026#34; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then file \u0026#34;$f\u0026#34; | grep -q ELF \u0026amp;\u0026amp; echo \u0026#34;UNOWNED ELF: $f\u0026#34; fi done The pacman -Qo trick is the Arch-specific lever worth highlighting: a legitimate binary under /var/lib is almost always owned by a package. An unowned ELF there is exactly what the root-install path produces. All three checks came back empty on my box. When you do find a suspect unit, dump its ExecStart target with systemctl cat \u0026lt;unit\u0026gt; and hash that binary against the IOC.\nNetwork and Staging # Unexpected loopback listeners (covered above) and outbound to the relay\u0026#39;s dest ports ss -tlnp | grep \u0026#39;127.0.0.1\u0026#39; ss -tanp | grep -E \u0026#39;:80\\b|:8080\\b\u0026#39; # Hunt the onion fragment and upload host in any logs you keep grep -ri \u0026#39;sneid.onion\u0026#39; /var/log 2\u0026gt;/dev/null grep -ri \u0026#39;temp.sh\u0026#39; /var/log 2\u0026gt;/dev/null # Staging target integrity pacman -Qo /usr/bin/monero-wallet-gui 2\u0026gt;/dev/null # should name an owning package if legit sha256sum /usr/bin/monero-wallet-gui 2\u0026gt;/dev/null stat /usr/bin/monero-wallet-gui 2\u0026gt;/dev/null # anomalous mtime/ctime? Nothing in the logs, and monero-wallet-gui isn\u0026rsquo;t installed on this box at all — so the staging target was a non-issue here.\nWiring It Into Wazuh A clean live hunt is a point-in-time answer. The Wazuh agent is how you make it durable: it gives you both retrospective telemetry — what already happened on the host — and a place to deploy detections so the next variant trips an alert instead of slipping by. I work it in that order: search what I already have, then harden.\nSearching what Wazuh already collected How much history you can search depends on whether JSON archive logging (\u0026lt;logall_json\u0026gt;) was enabled before the incident. Alerts (alerts.json) only retain events that already matched a rule; archives (archives.json) retain everything the agent shipped. For a fresh IOC like this, archives are far more useful, because the payload\u0026rsquo;s filenames and hashes wouldn\u0026rsquo;t have matched any pre-existing rule.\nFrom the Wazuh dashboard (Discover / Threat Hunting). Point the index pattern at wazuh-alerts-* (or wazuh-archives-* if archive indexing is on), set a wide time range, and run these one at a time. They use Wazuh field names, so they work in the Discover query bar and as saved searches:\nsyscheck.sha256 : \u0026#34;6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b\u0026#34; syscheck.path : *src\\/hooks\\/deps* syscheck.path : *\\/sys\\/fs\\/bpf\\/hidden_* data.audit.execve.* : (*atomic-lockfile* or *js-digest*) full_log : (*sneid.onion* or *temp.sh*) The first is the highest-value query — if FIM ever hashed the payload, syscheck.sha256 is an exact match and the rootkit can\u0026rsquo;t have touched that record once it left the host. The path queries catch the delivery artifact and the eBPF pinned-map directory. The full_log query sweeps any forwarded firewall/DNS/proxy data for the network IOCs.\nFrom the manager CLI, if you\u0026rsquo;d rather grep the raw logs directly (faster for a one-off, and independent of indexer health):\ngrep -iE \u0026#39;6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b|atomic-lockfile|js-digest|src/hooks/deps|sneid\\.onion|/sys/fs/bpf/hidden_\u0026#39; \\ /var/ossec/logs/archives/archives.json \\ /var/ossec/logs/alerts/alerts.json 2\u0026gt;/dev/null Archive files rotate into dated directories, so for an older window point the grep at the right tree:\nzgrep -iE \u0026#39;atomic-lockfile|js-digest|src/hooks/deps\u0026#39; \\ /var/ossec/logs/archives/2026/Jun/*.json.gz 2\u0026gt;/dev/null If archive logging wasn\u0026rsquo;t on and FIM never indexed the relevant directories, accept that the retrospective view is thin, fall back to the live hunt above, and treat the detections below as your forward-looking net.\nDeploying detections going forward File Integrity Monitoring. Point Syscheck at the directories the malware writes to, with hashing and realtime where it makes sense, in the agent\u0026rsquo;s ossec.conf:\n\u0026lt;syscheck\u0026gt; \u0026lt;directories check_all=\u0026#34;yes\u0026#34; realtime=\u0026#34;yes\u0026#34;\u0026gt;/etc/systemd/system\u0026lt;/directories\u0026gt; \u0026lt;directories check_all=\u0026#34;yes\u0026#34; realtime=\u0026#34;yes\u0026#34;\u0026gt;/var/lib\u0026lt;/directories\u0026gt; \u0026lt;directories check_all=\u0026#34;yes\u0026#34; realtime=\u0026#34;yes\u0026#34;\u0026gt;/usr/bin\u0026lt;/directories\u0026gt; \u0026lt;directories check_all=\u0026#34;yes\u0026#34; realtime=\u0026#34;yes\u0026#34;\u0026gt;/sys/fs/bpf\u0026lt;/directories\u0026gt; \u0026lt;directories check_all=\u0026#34;yes\u0026#34; realtime=\u0026#34;yes\u0026#34;\u0026gt;/home/USER/.config/systemd/user\u0026lt;/directories\u0026gt; \u0026lt;/syscheck\u0026gt; Hash blocklist. Drop the payload SHA256 into a CDB list on the manager (e.g. /var/ossec/etc/lists/malware-hashes) and reference it from a rule that matches Syscheck hash fields:\n6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b:aur_deps_stealer Rootkit check. Since you can\u0026rsquo;t trust live tooling, have the agent watch for the pinned maps and alert when the output is non-empty:\n\u0026lt;localfile\u0026gt; \u0026lt;log_format\u0026gt;command\u0026lt;/log_format\u0026gt; \u0026lt;command\u0026gt;ls /sys/fs/bpf/hidden_pids /sys/fs/bpf/hidden_names /sys/fs/bpf/hidden_inodes 2\u0026gt;/dev/null\u0026lt;/command\u0026gt; \u0026lt;frequency\u0026gt;3600\u0026lt;/frequency\u0026gt; \u0026lt;/localfile\u0026gt; You can extend the same command-monitor pattern to emit a line whenever ps and /proc disagree on a stable PID — giving you eBPF PID-hiding detection that doesn\u0026rsquo;t depend on a single trusted view. And if you forward firewall, DNS, or proxy logs into Wazuh, add rules matching the sneid.onion fragment and temp.sh POST /upload.\nValidating the detection in a throwaway agent I didn\u0026rsquo;t want to take the FIM rule on faith, so I stood up a disposable Wazuh agent to prove the pipeline end to end — without ever putting real malware on anything. The setup: an Arch container on the same host, the agent compiled from the v4.14.5 source tree (Arch isn\u0026rsquo;t an officially packaged target, so source build it was), registered to my manager, with one throwaway directory under FIM watch:\n\u0026lt;directories check_all=\u0026#34;yes\u0026#34; realtime=\u0026#34;yes\u0026#34; report_changes=\u0026#34;yes\u0026#34;\u0026gt;/root/ioctest\u0026lt;/directories\u0026gt; Then I dropped a benign file into the watched path and let syscheck hash it:\necho \u0026#34;atomic-lockfile deps payload - threat hunt FIM test\u0026#34; \u0026gt; /root/ioctest/deps sha256sum /root/ioctest/deps # 1edce3fec76fd27d9a4e0516b679404f0ed34eac7a3c22e1828516557ca23857 Within seconds the agent reported it, rule 554 (\u0026ldquo;File added to the system\u0026rdquo;) fired, and the event landed in the dashboard with the file\u0026rsquo;s full metadata — owner, permissions, inode, and all three hashes including SHA-256. That\u0026rsquo;s the mechanism the real hunt depends on: any file appearing in a monitored path gets fingerprinted and indexed, ready to match an IOC query.\nOne honest caveat worth stating plainly: you cannot make a file\u0026rsquo;s SHA-256 equal the real IOC by typing the hash into it — the hash is computed from content, so the only way to get a true match is to possess the actual malware, which you should not do. The test file above proves the plumbing; the real-IOC query in the previous screenshot returning zero hits is the verdict. Together they say: here\u0026rsquo;s the trap that would catch it, and here\u0026rsquo;s me running the actual hunt and finding nothing.\nWhen you\u0026rsquo;re done, tear the lab down so it doesn\u0026rsquo;t linger as a stale agent — remove it on the manager (manage_agents → R → the agent ID) and drop the container.\nThe exact ossec.conf syntax and default log paths vary across Wazuh versions; confirm against your installed version before deploying these.\nReading the Results Finding Confidence What it means File matching the IOC SHA256 Confirmed payload present Preserve evidence, clean, rotate everything preinstall: ./src/hooks/deps in a cached/installed package Confirmed delivery vector Hash the ELF to confirm /sys/fs/bpf/hidden_* maps present Confirmed rootkit (root exec) Highest severity; offline acquisition advised Unowned /var/lib ELF + Restart=always/RestartSec=30 unit Confirmed persistence Remove unit, hash binary, rotate monero-wallet-gui unowned or anomalous mtime Likely staging Compare to repo version, investigate ps/proc or socket-view discrepancy on a stable artifact Strong rootkit indicator Treat as compromised Everything clean Likely not impacted Deploy the Wazuh detections as an ongoing net My verdict: not impacted. Four independent angles agreed — no payload on disk, no delivery string in any cache, no rootkit maps, and the one suspicious-looking listener resolved to a known-good daemon. The two moments that looked like findings were both artifacts of how the checks work. That\u0026rsquo;s the normal texture of a clean hunt: the value is in knowing which surprises to discard.\nIf It\u0026rsquo;s Confirmed Isolate the host (capture memory first if the rootkit is suspected), acquire evidence with trusted tooling, then rebuild from known-good media — with an eBPF rootkit in play, reinstall is the only remediation I\u0026rsquo;d trust. Then rotate everything the stealer touches, treating all of it as exposed: Slack and Teams/M365 sessions, Discord tokens, GitHub PATs and SSH keys, npm tokens, Vault tokens, Docker/Podman registry credentials, SSH private keys and passphrases, VPN material, browser and Electron sessions, and anything that ever sat in a shell history. Finish by auditing those downstream accounts for actions taken with the stolen tokens — new GitHub keys, surprise npm publishes, fresh OAuth grants, mailbox rules.\nLessons Learned Hunt the pattern, not the package name. The names were legitimate and the list was incomplete by design. The payload hash, the delivery strings (atomic-lockfile and js-digest), and the src/hooks/deps lifecycle hook are what\u0026rsquo;s stable.\nTrust disk over live tooling when a rootkit is in scope. The hash sweep and the pinned-BPF-map check are reliable because the rootkit can\u0026rsquo;t falsify file content or hide its own maps from the bpf filesystem. The process and socket tables are exactly what it can lie about.\nShape is not identity. A loopback listener on a high port and an outbound :80 connection both matched the IOC set\u0026rsquo;s shape. One was containerd, the other was Spotify. Identify the owning process before you believe the indicator.\nRead your own check before you believe it. The PID diff \u0026ldquo;discrepancy\u0026rdquo; moved every run — that\u0026rsquo;s timing, not hiding. A check that produces alarming output isn\u0026rsquo;t the same as a finding.\nA clean hunt should still leave detections behind. Point-in-time clean is worth little if the next package drops the payload next week. The FIM hash rule and the eBPF-map monitor turn a one-off hunt into standing coverage — and a throwaway agent is enough to prove the rule fires before you rely on it.\nAcknowledgments To Whanos / ioctl.fail for the static reverse engineering that produced the IOC set this entire hunt is built on. To The Hacker News and Sonatype for the campaign-scope reporting, including the second js-digest wave I\u0026rsquo;d otherwise have missed. To the Arch aur-general community for the live affected-package tracking and detection scripts. To Claude (Anthropic), for talking me down off the two head-fakes instead of letting me write up a \u0026ldquo;confirmed compromise\u0026rdquo; before identifying a containerd socket. Appendix A: One-Shot Triage Script Read-only; modifies nothing. Run as root.\n#!/usr/bin/env bash IOC=\u0026#34;6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b\u0026#34; echo \u0026#34;== [1] eBPF pinned maps ==\u0026#34; ls -la /sys/fs/bpf/hidden_pids /sys/fs/bpf/hidden_names /sys/fs/bpf/hidden_inodes 2\u0026gt;/dev/null \\ \u0026amp;\u0026amp; echo \u0026#34;!!! eBPF ROOTKIT MAPS PRESENT !!!\u0026#34; echo \u0026#34;== [2] Aggressive-restart units from /var/lib ==\u0026#34; grep -rlE \u0026#39;Restart=always\u0026#39; /etc/systemd/system/ 2\u0026gt;/dev/null \\ | xargs grep -lE \u0026#39;RestartSec=30\u0026#39; 2\u0026gt;/dev/null \\ | xargs grep -lE \u0026#39;ExecStart=.*/var/lib/\u0026#39; 2\u0026gt;/dev/null echo \u0026#34;== [3] payload-path files ==\u0026#34; find / -path /proc -prune -o -type f -path \u0026#34;*src/hooks/deps*\u0026#34; -print 2\u0026gt;/dev/null echo \u0026#34;== [4] package references ==\u0026#34; grep -rIlE \u0026#39;atomic-lockfile|js-digest\u0026#39; ~/.cache/yay ~/.cache/paru ~/.npm/_logs /var/cache/pacman 2\u0026gt;/dev/null echo \u0026#34;== [5] hash sweep (size-matched, local FS) ==\u0026#34; find / -xdev -type f -size 3040376c 2\u0026gt;/dev/null | while read -r f; do [ \u0026#34;$(sha256sum \u0026#34;$f\u0026#34; 2\u0026gt;/dev/null | awk \u0026#39;{print $1}\u0026#39;)\u0026#34; = \u0026#34;$IOC\u0026#34; ] \u0026amp;\u0026amp; echo \u0026#34;MATCH: $f\u0026#34; done echo \u0026#34;== [6] monero-wallet-gui ownership ==\u0026#34; pacman -Qo /usr/bin/monero-wallet-gui 2\u0026gt;/dev/null || echo \u0026#34;NOT package-owned / absent\u0026#34; echo \u0026#34;== [7] loopback listeners ==\u0026#34; ss -tlnp 2\u0026gt;/dev/null | grep \u0026#39;127.0.0.1\u0026#39; echo \u0026#34;== done ==\u0026#34; Appendix B: IOC Reference Payload: deps (Rust ELF64, PIE, 3,040,376 bytes) SHA-256: 6144d433f8a0316869877b5f834c801251bbb936e5f1577c5680878c7443c98b MD5: 42b59fdbe1b72895b2951412222ebf40 Delivery packages: atomic-lockfile@1.4.2 (npm, wave 1), js-digest (wave 2, separate binary) Lifecycle hook: \u0026quot;preinstall\u0026quot;: \u0026quot;./src/hooks/deps\u0026quot; C2: olrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion, POST /api/agent Exfil: temp.sh, POST /upload eBPF maps: /sys/fs/bpf/hidden_pids, hidden_names, hidden_inodes Campaign tracking: Sonatype \u0026ldquo;Atomic Arch\u0026rdquo; (Sonatype-2026-003775, CVSS 8.7) Appendix C: Tooling Used Arch Linux with yay / paru find, sha256sum, grep (on-host hunting) ss, lsof, bpftool (network and eBPF inspection) pacman -Qo (package-ownership checks) Wazuh agent + manager (FIM, CDB lists, command monitors) — agent built from the v4.14.5 source tree for Arch A willingness to identify the process before believing the indicator References The Hacker News — Over 400 Arch Linux AUR Packages Hijacked to Deploy Infostealer and eBPF Rootkit (12 June 2026). ioctl.fail — Preliminary analysis of AUR malware (11 June 2026), by Whanos. Arch Linux aur-general mailing list — ongoing affected-package reporting. The affected-package list was still incomplete at the time of writing, and a second wave used a different payload binary. Hunt on the payload hash and the lifecycle-hook pattern rather than any single package name, and check both atomic-lockfile and js-digest.\n","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/arch_threat_hunt/","summary":"\u003cp\u003e\u003cimg alt=\"Hero image: terminal mid-hunt on the Arch box\" loading=\"lazy\" src=\"/posts/arch_threat_hunt/images/1.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"tldr\"\u003eTL;DR\u003c/h2\u003e\n\u003cp\u003eOn 11 June 2026, attackers hijacked \u003ca href=\"https://thehackernews.com/2026/06/over-400-arch-linux-aur-packages.html\"\u003emore than 400 Arch User Repository (AUR) packages\u003c/a\u003e and rewrote their build scripts to pull a malicious npm package that executed a Rust credential stealer — analyst-named \u003ccode\u003edeps\u003c/code\u003e — during the build. Sonatype calls the campaign \u003cstrong\u003eAtomic Arch\u003c/strong\u003e. Independent researcher Whanos \u003ca href=\"https://ioctl.fail/preliminary-analysis-of-aur-malware/\"\u003ereverse-engineered the payload\u003c/a\u003e, and that analysis is the source for most of the indicators here.\u003c/p\u003e\n\u003cp\u003eI run Arch with a Wazuh agent, so I built a hunt around the IOCs and ran it against my own machine. \u003cstrong\u003eThe host came back clean.\u003c/strong\u003e The interesting part isn\u0026rsquo;t the verdict — it\u0026rsquo;s that two checks \u003cem\u003elooked\u003c/em\u003e like findings and weren\u0026rsquo;t: a PID-listing diff that screamed \u0026ldquo;hidden process,\u0026rdquo; and a loopback listener on a random high port that had exactly the shape of the malware\u0026rsquo;s local proxy. Both were artifacts of how the checks work, not evidence. This post is the hunt, why each check is trustworthy or not, the two head-fakes, and how to wire durable detections into Wazuh so the next variant trips an alert on its own.\u003c/p\u003e","title":"How I Hunted the Atomic Arch AUR Stealer on My Own Box"},{"content":"5/18/2026 TL;DR UniFi IDS signature 2069175 (ET MALWARE BPFDoor ICMP Echo Request, X:[COMMAND]) fired on a Roborock Q10 robot vacuum on my IoT VLAN. After a week of \u0026ldquo;investigation\u0026rdquo; — packet captures, custom Scapy trigger scripts, isolated test VLANs, the works — the root cause turned out to be a Suricata false positive with an identifiable mathematical cause: the vacuum sends an ICMP heartbeat every 20 seconds whose payload includes a 32-bit microsecond timestamp counter, and that counter rolls through values containing 0x58 0x3a (\u0026ldquo;X:\u0026rdquo;) at the rule\u0026rsquo;s match offset approximately once every few thousand packets.\nThis post documents how I got there, why I was briefly convinced of the wrong answer, and how to determine if you\u0026rsquo;re seeing the same pattern on your own network.\nThe Setup Gateway: UniFi Dream Machine (UDM), firmware v5.0.16 IDS: Suricata 6.x with Emerging Threats ruleset, IPS mode (Detect and Block) Device of interest: Roborock Q10 robot vacuum (MAC 24:9e:7d:4c:72:7d) on isolated IoT VLAN Alert: IPS Alert 1: A Network Trojan was detected. Signature ET MALWARE BPFDoor ICMP Echo Request, X:[COMMAND] (Inbound). From: 192.168.3.244:0, to: 192.168.3.1:0, protocol: ICMP The alert grabs attention for obvious reasons. BPFDoor is a real, well-documented Linux backdoor attributed to Red Menshen / Earth Bluecrow, originally seen in espionage operations against telco and government targets. \u0026ldquo;Inbound ICMP backdoor on a Chinese-manufactured IoT device\u0026rdquo; is the kind of phrase that makes a security person sit up.\nWhat the Signature Actually Looks For The rule, in its essential form:\nalert icmp $EXTERNAL_NET any -\u0026gt; $HOME_NET any ( msg:\u0026#34;ET MALWARE BPFDoor ICMP Echo Request, X:[COMMAND] (Inbound)\u0026#34;; itype:8; content:\u0026#34;X:\u0026#34;; depth:2; ... metadata: signature_severity Critical, confidence High, malware_family BPFDoor, created_at 2026_05_05; sid:2069175; rev:2; ) Two important properties:\nContent match is just two bytes at depth:2 — the first two bytes of the ICMP payload must be 0x58 0x3a. Signature metadata declares \u0026ldquo;confidence: High\u0026rdquo; and \u0026ldquo;signature_severity: Critical.\u0026rdquo; ET signatures get these labels when their analysts believe the content match is specific enough to avoid common false positives. That made me take this seriously, and rightly so — but as we\u0026rsquo;ll see, \u0026ldquo;specific enough\u0026rdquo; depends on what traffic you\u0026rsquo;re inspecting. Why I Almost Filed an FBI Report Three things conspired to make this look real:\nConfounder #1: The \u0026ldquo;trigger test\u0026rdquo; that fooled itself.\nI wrote a Scapy script to send crafted ICMP packets to the vacuum: X:id, X:whoami, X:cat /etc/passwd, fifteen commands total. Every single reply contained the exact bytes I sent. My script flagged each one as [!!!] SUSPICIOUS RESPONSE.\n# What I thought I was detecting if b\u0026#34;X:\u0026#34; in payload: is_suspicious = True reason = \u0026#34;Contains \u0026#39;X:\u0026#39; BPFDoor magic bytes\u0026#34; What I was actually detecting was RFC 792, which has been the spec for ICMP since 1981: \u0026ldquo;The data received in the echo message must be returned in the echo reply message.\u0026rdquo; Every Linux device on Earth will faithfully echo back whatever bytes you put in a ping request. The vacuum was doing exactly what every other device does.\nThe lesson: any active test that involves sending crafted traffic to a device needs a control. Run the same script against your router, your laptop, a Raspberry Pi. They\u0026rsquo;ll all \u0026ldquo;respond suspiciously\u0026rdquo; the same way. If your control fires the same alert, you\u0026rsquo;re measuring how ping works, not malware.\nI eventually ran this exact test against an Arch Linux laptop to verify. The results were identical: 15 \u0026ldquo;suspicious responses\u0026rdquo; detected, all containing the X: bytes I had sent, all flagged for unusual payload lengths and non-standard patterns. The script\u0026rsquo;s detection heuristics fired on both the vacuum and the laptop equally — which proves the detection is measuring traffic shape abnormality, not device compromise.\nThis is actually correct behavior from the script. It detected three markers of non-standard ICMP:\nPresence of 0x58 0x3a (the \u0026ldquo;X:\u0026rdquo; magic bytes) Payload length differing from the standard 56-byte ping Payload content differing from standard Linux ping patterns (0x10 0x11 0x12...) When you intentionally send abnormal ICMP traffic (as my test did), all three heuristics fire — on any device. This proved the detection isn\u0026rsquo;t device-specific; it\u0026rsquo;s pattern-specific. And since both the vacuum and a known-good Linux laptop produced identical results, the pattern alone cannot indicate compromise.\nConfounder #2: Closed TCP ports felt like \u0026ldquo;hiding.\u0026rdquo;\nnmap against the vacuum returned all 65,535 TCP ports closed. I initially read this as backdoor evasion. It\u0026rsquo;s actually the correct posture for an IoT device — Roborocks talk outbound to cloud over TLS; they shouldn\u0026rsquo;t be running listening services on the LAN. Closed ports are good security, not suspicious behavior.\nConfounder #3: Confirmation bias.\nOnce a few hours into \u0026ldquo;this looks bad,\u0026rdquo; every benign observation became another data point. The legitimate Roborock root-password derivation file /opt/rockrobo/vinda (documented in Dennis Giese\u0026rsquo;s public Roborock research) became a \u0026ldquo;BPFDoor auth mechanism.\u0026rdquo; The marketing hostname AutoPack_Sweeper became \u0026ldquo;process masquerading.\u0026rdquo; None of these were ever evidence; they were vibes filtered through expectation.\nDoing the Actual Forensic Work After several rounds of being correctly pushed back on by an AI assistant who kept asking \u0026ldquo;but what does the data actually show?\u0026rdquo;, I ran the real investigation:\nStep 1: Inventory the alarms UniFi stores IPS alerts in MongoDB at /data/unifi/db/. Accessing the database directly:\nssh root@\u0026lt;your-udm-ip\u0026gt; mongo --port 27117 ace db.alarm.count() // 15 alarms total ever db.alarm.find({\u0026#34;msg\u0026#34;: /BPFDoor/i}).count() // 1 BPFDoor alert db.alarm.find({\u0026#34;msg\u0026#34;: /BPFDoor/i}).pretty() The full alert document revealed several things I\u0026rsquo;d missed in the dashboard view:\n{ \u0026#34;icmp_type\u0026#34; : 8, // echo REQUEST, not reply \u0026#34;src_ip\u0026#34; : \u0026#34;192.168.3.244\u0026#34;, // vacuum initiated \u0026#34;dest_ip\u0026#34; : \u0026#34;192.168.3.1\u0026#34;, // gateway as destination \u0026#34;flow\u0026#34; : { \u0026#34;bytes_toclient\u0026#34; : 200214, \u0026#34;bytes_toserver\u0026#34; : 200312, \u0026#34;pkts_toclient\u0026#34; : 2043, \u0026#34;pkts_toserver\u0026#34; : 2044, \u0026#34;start\u0026#34; : \u0026#34;2026-05-08T22:02:25-0400\u0026#34; }, \u0026#34;inner_alert_metadata\u0026#34; : { \u0026#34;confidence\u0026#34; : [\u0026#34;High\u0026#34;], \u0026#34;signature_severity\u0026#34; : [\u0026#34;Critical\u0026#34;], \u0026#34;created_at\u0026#34; : [\u0026#34;2026_05_05\u0026#34;], \u0026#34;malware_family\u0026#34; : [\u0026#34;BPFDoor\u0026#34;] } } The flow had been running for 15+ hours and exchanged ~4,000 ICMP packets in each direction. That\u0026rsquo;s not the gateway occasionally probing the device — that\u0026rsquo;s a sustained, high-volume, vacuum-initiated ICMP conversation. Worth understanding.\nStep 2: Capture spontaneous traffic I moved the vacuum to its own isolated VLAN (192.168.4.0/24) and ran two captures on the gateway, with filters that excluded my own test machine so I\u0026rsquo;d only see what the vacuum did spontaneously:\n# ICMP-only capture nohup tcpdump -i br400 -w /tmp/roborock_spontaneous.pcap -s 0 \\ \u0026#39;icmp and host 192.168.4.244 and not host 192.168.4.83\u0026#39; \u0026amp; # All traffic except ARP nohup tcpdump -i br400 -w /tmp/roborock_all.pcap -s 0 \\ \u0026#39;host 192.168.4.244 and not host 192.168.4.83 and not arp\u0026#39; \u0026amp; I left these running for 14 hours overnight. Crucially: I did not generate any traffic to the vacuum during this window. Clean baseline data is the whole point.\nStep 3: Analyze the ICMP capture Results:\n4,994 ICMP packets in 14 hours 2,497 from vacuum, 2,497 from gateway (perfect 1:1 echo request/reply ratio) The vacuum sends an echo request to the gateway every ~20 seconds, all night Bytes 0x58 0x3a (the BPFDoor \u0026ldquo;X:\u0026rdquo;) appear ZERO times in 14 hours of capture Wait — zero hits in 14 hours, but the alarm did fire once previously? Let\u0026rsquo;s look at the payload structure to understand why.\n0x0000: 4500 0054 2de0 4000 4001 8283 c0a8 04f4 E..T-.@.@....... 0x0010: c0a8 0401 0800 8004 0e88 0000 6df8 fb7a ............m..z \u0026lt;-- ICMP header + payload starts 0x0020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x0030: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x0040: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x0050: 0000 0000 .... The 56-byte ICMP payload has:\nBytes 0–3: Vary every packet (6d f8 fb 7a in this example) Bytes 4–55: All zeros That\u0026rsquo;s not the standard Linux ping payload (which fills bytes 0x10 through 0x37). It\u0026rsquo;s a custom-crafted ICMP packet from a minimal embedded ping implementation, padded with zeros.\nStep 4: Decode the varying bytes I extracted the first 4 bytes from each consecutive vacuum ping and looked at the differences:\nPacket # Bytes 0-3 (LE) Decimal Diff from prev Real time delta 0 6d f8 fb 7a 2,063,333,485 — — 1 1d d3 31 7c 2,083,640,093 +20,306,608 20.304s 2 83 ae 69 7d 2,104,077,955 +20,437,862 20.438s 3 28 04 9f 7e 2,124,350,504 +20,272,549 20.275s 4 ea 18 d5 7f 2,144,671,978 +20,321,474 20.318s The \u0026ldquo;differences\u0026rdquo; column, in microseconds, matches the wall-clock interval between pings almost exactly.\nThe first 4 bytes of the payload are a little-endian uint32 microsecond counter — almost certainly clock_gettime(CLOCK_MONOTONIC, ...).tv_nsec / 1000 (or tv_usec from gettimeofday), with the low 32 bits used as the ping payload\u0026rsquo;s \u0026ldquo;identifier.\u0026rdquo; This is a common pattern in minimalist ping implementations on embedded Linux — and it\u0026rsquo;s stable enough that you can derive the device\u0026rsquo;s uptime from the counter value.\nStep 5: Do the math on why the signature fired With a 4-byte rolling counter incrementing by ~20 million each packet, every possible 32-bit value will eventually appear. The signature looks for 0x58 0x3a at depth 2 — the first two bytes.\nProbability of 0x58 0x3a appearing in any specific 2-byte position: 1 / 65536.\nProbability of it appearing somewhere in the first 4 bytes: ~1 / 16384 (three possible windows, minus negligible overlap).\nAt one ping every 20 seconds: 4,320 pings per day. So you\u0026rsquo;d expect a hit roughly every 16384 / 4320 ≈ 3.8 days.\nOver a year, you\u0026rsquo;d expect about 95 false positives from this one device. The fact that you\u0026rsquo;d only see it once on a UniFi dashboard at any given time is because the dashboard ages off old events.\nThat matches my data exactly: the gateway has logged BPFDoor exactly once in its history (May 9), and after isolating the vacuum I observed zero hits in 14 hours — perfectly consistent with the predicted ~3.8-day average.\nStep 6: Verify the rest of the traffic is clean The non-ICMP capture (5,272 packets over 14 hours) had a single external destination:\n3440 192.168.4.244:39246 -\u0026gt; 34.202.57.108:8883 (vacuum -\u0026gt; AWS) 1830 34.202.57.108:8883 -\u0026gt; 192.168.4.244:39246 (AWS -\u0026gt; vacuum) 34.202.57.108 → ec2-34-202-57-108.compute-1.amazonaws.com (AWS US-East-1) Port 8883 → standard IANA-assigned port for MQTT over TLS Every TCP packet started with 0x17 0x03 0x03 — TLS 1.2 application data record. No plaintext anywhere. One long-lived MQTT/TLS connection to AWS. That is exactly how every cloud-connected IoT device on the planet works. Nothing else outbound, no DNS to suspicious domains, no second channel.\nThe network behavior is, in a word, boring.\nHow to Verify This Pattern on Your Own Network If you see SID 2069175 fire on your UniFi, here\u0026rsquo;s a 10-minute sanity check:\nStep 1: Identify the source IP ssh root@\u0026lt;udm-ip\u0026gt; mongo --port 27117 ace \u0026gt; db.alarm.find({\u0026#34;msg\u0026#34;: /BPFDoor/i}).pretty() If the source IP is internal and the destination is your gateway → you\u0026rsquo;re seeing the same pattern. Probably an IoT device with a custom ICMP heartbeat. If the source IP is external (from the public internet) and the destination is something on your network that doesn\u0026rsquo;t expose ICMP services → that warrants real investigation. Step 2: Capture spontaneous traffic from the suspected device tcpdump -i \u0026lt;your-iot-bridge\u0026gt; -w /tmp/dev.pcap -s 0 \\ \u0026#39;icmp and host \u0026lt;device-ip\u0026gt; and not host \u0026lt;your-workstation-ip\u0026gt;\u0026#39; Let it run for at least a few hours. Don\u0026rsquo;t ping the device yourself during the capture.\nStep 3: Check for \u0026ldquo;X:\u0026rdquo; in payloads # Convert pcap to hex, search for 58 3a tcpdump -r /tmp/dev.pcap -X 2\u0026gt;/dev/null | grep \u0026#34;58 3a\u0026#34; If you find any hits, look at what comes after the 0x58 0x3a. Real BPFDoor command packets contain readable shell commands (X:id, X:ls /, X:cat /etc/passwd). Random bytes look like random bytes.\nStep 4: Check the payload structure If the device sends ICMP echo requests with payloads that aren\u0026rsquo;t the standard Linux 0x10 0x11 0x12 ... 0x37 pattern, look at what the varying bytes encode. If they look like a monotonically incrementing counter — particularly a microsecond timer — you have the same false positive cause I did.\n# Quick check: extract first 4 bytes of each ICMP payload and look at diffs import struct from scapy.all import rdpcap pkts = rdpcap(\u0026#39;/tmp/dev.pcap\u0026#39;) vals = [] for p in pkts: if p.haslayer(\u0026#39;ICMP\u0026#39;) and p[\u0026#39;ICMP\u0026#39;].type == 8: payload = bytes(p[\u0026#39;ICMP\u0026#39;].payload) if len(payload) \u0026gt;= 4: vals.append((p.time, struct.unpack(\u0026#39;\u0026lt;I\u0026#39;, payload[:4])[0])) for i in range(1, min(10, len(vals))): t_diff = vals[i][0] - vals[i-1][0] v_diff = vals[i][1] - vals[i-1][1] print(f\u0026#34;time_delta={t_diff:.3f}s, payload_value_delta={v_diff} ({v_diff/1e6:.3f}s if usec)\u0026#34;) If v_diff / 1e6 matches t_diff, you\u0026rsquo;ve identified a microsecond counter in the payload, which means you\u0026rsquo;ll get periodic random hits on the rule.\nSuppressing the Rule Without Losing Coverage The cleanest mitigation is to allow-list the specific src/dst pair where the false positive occurs, not disable the rule entirely. Real BPFDoor C2 packets would originate from the public internet, not from devices on your IoT VLAN talking to your own gateway.\nIn the UniFi UI: Settings → Security → Threat Management → Allow List → add the IoT VLAN subnet and gateway IP.\nVia Suricata\u0026rsquo;s threshold.config (requires SSH, may be reset by firmware updates):\nsuppress gen_id 1, sig_id 2069175, track by_src, ip 192.168.3.0/24 This silences the rule for traffic originating from your IoT VLAN while preserving detection from external sources — which is the threat surface BPFDoor C2 would actually use.\nLessons Learned A few things I\u0026rsquo;d tell someone starting a similar investigation:\nLook at the source IP first. Internal-only source for a \u0026ldquo;C2 inbound\u0026rdquo; alert immediately changes the interpretation.\nRun controls. Any active test against a target needs an equivalent test against a known-good device. If both fire your detection, you\u0026rsquo;re measuring something else.\nSearch for the SID and rule history before assuming the alert is high-fidelity. ET signatures vary widely. Even \u0026ldquo;High confidence\u0026rdquo; rules have known false-positive patterns when applied to traffic profiles the rule author didn\u0026rsquo;t anticipate (here: embedded ICMP implementations using timestamp counters as payload).\nConsider base rates. State-sponsored implants in mass-market consumer vacuums are not impossible, but they\u0026rsquo;re vastly less likely than coincidental signature matches. Bayesian thinking applies to threat hunting.\nListen when something pushes back on your conclusion. I had an AI assistant repeatedly tell me my evidence didn\u0026rsquo;t support my conclusion. I argued. The AI was right. The hours I spent arguing were hours I should have spent capturing more data — which is what ultimately resolved the question.\nAcknowledgments To Claude (Anthropic), for refusing to draft the FBI report I would have regretted. The skepticism was warranted. To the Emerging Threats project for the open ruleset that gives home operators meaningful network visibility, even when (and especially when) individual rules need tuning. To Dennis Giese for the years of public Roborock security research that helped me distinguish \u0026ldquo;weird vacuum behavior\u0026rdquo; from \u0026ldquo;normal vacuum behavior.\u0026rdquo; To my Roborock, which is just a vacuum. Appendix A: Rule Reference Rule: ET MALWARE BPFDoor ICMP Echo Request, X:[COMMAND] (Inbound) SID: 2069175 Source: Emerging Threats OPEN ruleset Created: 2026-05-05 Updated: 2026-05-08 Severity: Critical (per rule metadata) Confidence: High (per rule metadata) Affected products: Linux Original BPFDoor research: PwC \u0026ldquo;Tracking Red Menshen\u0026rdquo; (2022), Sandfly Security BPFDoor analyses Public BPFDoor samples: Malware Bazaar, VirusTotal Appendix B: The Math, Spelled Out Given:\nPayload first 4 bytes = uint32 microsecond counter, effectively uniform random over time Rule content match: 0x58 0x3a at depth 2 (first 2 bytes of payload) Heartbeat rate: 1 ping every 20 seconds = 4,320 pings/day Probability of 0x58 0x3a appearing at offset 0–1 in a uniformly random 4-byte value:\nP(match) = 1 / 256^2 = 1 / 65,536 Expected pings between hits: 65,536 Expected days between hits: 65,536 / 4,320 ≈ 15.2 days\nIf you also count 0x58 0x3a appearing at offset 1–2 (i.e., the second and third bytes), and offset 2–3, that\u0026rsquo;s three windows. Approximately:\nP(match in any of first 4 bytes) ≈ 3 / 65,536 ≈ 1 / 21,845 Expected days between hits ≈ 21,845 / 4,320 ≈ 5.1 days The signature, however, has depth:2 — meaning it only matches at offset 0. So the more accurate prediction is one false positive every ~15 days per device with this payload pattern. Adjust your suppression rules accordingly.\nAppendix C: Tooling Used UniFi Dream Machine (UDM) with Suricata IPS tcpdump (on-gateway packet capture) mongo shell (UniFi alarm database queries) Python 3 with stdlib struct (custom pcap parsing) Wireshark (visual packet inspection) Patience and a willingness to be wrong ","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/vacuum/","summary":"\u003cp\u003e5/18/2026\n\u003cimg loading=\"lazy\" src=\"/posts/vacuum/images/1.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"tldr\"\u003eTL;DR\u003c/h2\u003e\n\u003cp\u003eUniFi IDS signature \u003ccode\u003e2069175\u003c/code\u003e (\u003ccode\u003eET MALWARE BPFDoor ICMP Echo Request, X:[COMMAND]\u003c/code\u003e) fired on a Roborock Q10 robot vacuum on my IoT VLAN. After a week of \u0026ldquo;investigation\u0026rdquo; — packet captures, custom Scapy trigger scripts, isolated test VLANs, the works — the root cause turned out to be a Suricata false positive with an identifiable mathematical cause: \u003cstrong\u003ethe vacuum sends an ICMP heartbeat every 20 seconds whose payload includes a 32-bit microsecond timestamp counter, and that counter rolls through values containing \u003ccode\u003e0x58 0x3a\u003c/code\u003e (\u0026ldquo;X:\u0026rdquo;) at the rule\u0026rsquo;s match offset approximately once every few thousand packets.\u003c/strong\u003e\u003c/p\u003e","title":"How I Chased a BPFDoor Backdoor in My Robot Vacuum (And Found a Microsecond Timer)"},{"content":"I will be providing new content here in the near future. I have a number of new projects I have been working on in my lab.\n","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/update/","summary":"\u003cp\u003eI will be providing new content here in the near future. I have a number of new projects I have been working on in my lab.\u003c/p\u003e","title":"Update"},{"content":" 05/25/2023:\nPhishing, Domain Abuse/Typo-Squatting, Chat-Bots, and C2 Deployment… This proof of concept is for educational purposes only. All resources are my own, and no individuals or sock puppets were harmed during the testing process.\nFrom my initial review of Impostoor Technology Services https://impostoor.com the company offers various global data storage services for its customers.\nI pursued an approach of active and passive reconnaissance techniques to gather valuable information about the target network and its users. In addition, I delivered test emails to its corporate addresses to understand the look and feel of company formatting and header information.\nI utilized The Harvester https://github.com/laramies/theHarvester to help identify corporate email addresses tied to the domain. One email address in particular, ksmith@impostoor.com, was reported in data leak that occurred on a large online retailer within the past two years. That leak detailed the name associated with the address (Karen Smith).\nOpen Source research quickly corroborated my user being an employee with Impostoor Technology Services. Additionally, plenty of social media evidence showed that I had the correct person.\nWhat was unique, however, was the following post.\nIndividuals are always the weakest link when targeting an organization.\nTypo-squatting | Phishing Utilizing a very similar domain to impostoor.com, like impostooor.com, I can pursue a typo-squatting attack. Notice the extra “o.”\nWith this domain; I could run a mail server with a custom-designed replica email of something employees at Impostoor Technologies would likely receive from their corporate headquarters. Before I added the body of the message, I decided to consult Karen’s trusty new friend, good ole ChatGPT.\nAs you can see, the response from my prompt entry could produce a better result. However, we can still utilize this with some minor edits. Before compiling this, though, let’s get our payload in order.\nDafrog C2 Standup Shout out to Joe Helle’s awesome book that helped me get this project going.\nhttps://github.com/MEISTSEC/dafrogc2\nFirst, I need to stand-up the framework and enable the listener.\nmv zfvccb.exe trustedsoftwareremovaltool.exe My C2 server is now up and listening on port 4444 and I have constructed a windows binary payload (trustedsoftwareremovaltool.exe) from my python source.\nI finally reach out to Karen with my Phishing email (I will detail this process at a later date). I utilize a large amount of the ChatGPT response and some screen shots to show her what exactly she needs to do.\nSubject: Urgent: Unathorized File Use Alert - Immediate Action Required Dear Karen Smith, I hope this email finds you well. I\u0026#39;m writing to address an urgent matter that requires your immediate attention. It has come to the ITS Security Teams attention that a potentially an unauthorized file \u0026lt;chatgptinstalleramd64.zip\u0026gt;, which is not intended for use on work computers, may be present on your system. To ensure the security and integrity of our workplace, it is of utmost importance that we locate and handle this file appropriately. Given the nature of the file in question, it is imperative that we take swift action to safeguard our organization\u0026#39;s confidentiality and adhere to our strict information security policies. To initiate the necessary steps, please follow the instructions below to search for and remove the file from your computer: 1. Launch Windows Explorer: You can do this by either clicking on the \u0026#34;File Explorer\u0026#34; icon on your taskbar or by pressing the Windows key + E on your keyboard. 2. Conduct a thorough search: Once Windows Explorer is open, navigate to the search bar located in the top-left corner of the window. Type in \u0026#34;chatgptinstalleramd64.zip\u0026#34;. Please ensure you enter the correct name to ensure an accurate search. This is how it plays out when she types the zip file “the domain” into the file explorer search bar.\nUpon hitting the enter button, Karen’s default browser opens, and chatgptinstalleramd64.zip receives a GET request. The domain does redirection to impostooor.com, our typo-squatted website. Karen is presented the following on her desktop in her default web browser. Karen clicked on the downloaded executable we created in the C2 framework, which we embedded into the domain via JavaScript.\nKaren is hit with a strange error. She double clicks and it goes away. She assumes all has been remediated and the corporate security team will no longer be bothering her. This action executes the payload.\nBack on the C2 machine. We receive a connection on our listener from Karen’s workstation.\nConclusion This was a fun little Proof of Concept to highlight the start of an attack chain. This is where we currently sit within the MITRE ATTACK Framework.\nIn phase 2, we will look at Defense Evasion, Establishing Persistence, Discovery, Exfiltration, and Lateral Movement. Phase 3 of this project includes establishing a second phase dropper developed in Rust, a new language I am learning, and deploying Python-written ransomware. DafrogC2 is continuously receiving new additions as I find mistakes and implement new features in the program. As I mentioned in previous posts, tackling a project like that is truly the best way to educate yourself in programming and security. The trial-and-error approach is more fun than reading a textbook, IMHO.\nCATEGORIES\n","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/attackchain1/attackchain/","summary":"\u003chr\u003e\n\u003cp\u003e05/25/2023:\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/posts/attackchain1/images/1.png\"\u003e\u003c/p\u003e\n\u003ch5 id=\"phishing-domain-abusetypo-squatting-chat-bots-and-c2-deployment\"\u003ePhishing, Domain Abuse/Typo-Squatting, Chat-Bots, and C2 Deployment…\u003c/h5\u003e\n\u003cp\u003e\u003cem\u003eThis proof of concept is for educational purposes only. All resources are my own, and no individuals or sock puppets were harmed during the testing process.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/posts/attackchain1/images/2.png\"\u003e\u003c/p\u003e\n\u003cp\u003eFrom my initial review of Impostoor Technology Services \u003ca href=\"https://impostoor.com\"\u003ehttps://impostoor.com\u003c/a\u003e the company offers various global data storage services for its customers.\u003c/p\u003e\n\u003cp\u003eI pursued an approach of active and passive reconnaissance techniques to gather valuable information about the target network and its users. In addition, I delivered test emails to its corporate addresses to understand the look and feel of company formatting and header information.\u003c/p\u003e","title":"Approaching the Attack Chain"},{"content":"\nNetwork Segmentation Testing with nmap Segmentation testing is the exercise of proving — from a defined source network — that traffic into a protected network is restricted as intended. Most commonly this is driven by PCI DSS Requirement 11.4.5 (annual segmentation testing for merchants, semi-annual for service providers) and analogous controls in HIPAA, NIST 800-53, and internal zoning standards.\nThe goal is not to find vulnerabilities. It is to confirm that the firewall/ACL/SDN rules between source and target zones match the documented design — no more, no less. Findings are usually \u0026ldquo;port X is reachable that shouldn\u0026rsquo;t be\u0026rdquo; or \u0026ldquo;this source IP can reach this CDE host.\u0026rdquo;\nBelow is the playbook I lean on with nmap. Run from a representative host inside the source segment.\nPhase 1 — Host discovery You first need to know what\u0026rsquo;s actually up in the target range without committing to a full port scan. -sn (\u0026ldquo;no port scan\u0026rdquo;) performs only discovery probes.\nSwitch Example What it does -sn nmap -sn 10.1.0.0/24 Disable port scanning. Host discovery only (ICMP echo, TCP SYN/ACK to 443/80, ARP on local). -PS nmap -PS22,80,443 10.1.0.0/24 TCP SYN ping to listed ports — useful when ICMP is blocked. -PA nmap -PA22,80,443 10.1.0.0/24 TCP ACK ping — sometimes traverses stateless filters that drop SYN. -PU nmap -PU53,161 10.1.0.0/24 UDP ping — useful where TCP probes are filtered. -Pn nmap -Pn ... Skip discovery, treat all hosts as up. Use as a fallback only — it slows things down and produces noise. When you run as root, nmap will also use ARP for any targets on the local link, which is the most reliable signal you have.\nsudo nmap -sn -PE -PS22,80,443 -PA22,80,443 -PU53,161 -iL targets.txt -oA discovery -oA \u0026lt;basename\u0026gt; writes normal, grepable, and XML output in one shot — keep all three for reporting.\nPhase 2 — Port coverage Once you have the live set, you need full port coverage. Do not rely on default top-1000 for a segmentation test; partial coverage will not pass a QSA review.\nTCP, all 65535 ports, with version detection and reason text (which packet flag drove each verdict — important evidence):\nsudo nmap -sS -p- -sV --reason -iL alive.txt -oA tcp_full If TCP SYN is being blocked outright by the source\u0026rsquo;s egress (e.g. an outbound proxy that only forwards established sessions), fall back to a TCP connect scan:\nnmap -sT -p- -sV --reason -iL alive.txt -oA tcp_connect UDP is slow and unreliable but required for a complete test. Cover the common service ports at minimum, and use --version-intensity 0 to keep runtime sane:\nsudo nmap -sU --top-ports 200 -sV --version-intensity 0 --reason -iL alive.txt -oA udp_top200 Phase 3 — Evidence What a reviewer (internal or external) will ask for:\nThe source IP(s) the scan was run from, with date/time. The target scope (targets.txt) and the alive list. The raw nmap output for each scan (-oA artifacts). A diff between observed open ports and the documented allow-list. Anything reachable that isn\u0026rsquo;t on the allow-list is a finding. For diffing, the grepable output (.gnmap) plus a one-liner like:\ngrep \u0026#34;/open/\u0026#34; tcp_full.gnmap | awk \u0026#39;{print $2}\u0026#39; | sort -u …gives you a clean \u0026ldquo;hosts with any open port\u0026rdquo; list to compare against expectations.\nNotes on timing and safety -T4 is usually fine on modern networks and a lot faster than the default -T3. Avoid -T5 against production unless you\u0026rsquo;ve cleared it — it will trip IDS and can knock over fragile devices. Coordinate with the SOC. A segmentation test that triggers an unannounced incident response is a bad day. If the target zone contains IDS/IPS or rate-limiting, your results may be incomplete by design. Document the controls observed; they\u0026rsquo;re part of the report, not a problem with your methodology. ","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/segtest/segtesting/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/posts/segtest/images/nmap.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"network-segmentation-testing-with-nmap\"\u003eNetwork Segmentation Testing with nmap\u003c/h1\u003e\n\u003cp\u003eSegmentation testing is the exercise of proving — from a defined source network — that traffic into a protected network is restricted as intended. Most commonly this is driven by \u003cstrong\u003ePCI DSS Requirement 11.4.5\u003c/strong\u003e (annual segmentation testing for merchants, semi-annual for service providers) and analogous controls in HIPAA, NIST 800-53, and internal zoning standards.\u003c/p\u003e\n\u003cp\u003eThe goal is not to find vulnerabilities. It is to confirm that the firewall/ACL/SDN rules between source and target zones match the documented design — no more, no less. Findings are usually \u0026ldquo;port X is reachable that shouldn\u0026rsquo;t be\u0026rdquo; or \u0026ldquo;this source IP can reach this CDE host.\u0026rdquo;\u003c/p\u003e","title":"Segmentation Testing"},{"content":"\n10/30/2022:\nI love participating in Capture The Flag (CTF) events. When I first became focused on pursuing a career in cybersecurity, I was encouraged by many in the community to compete in the numerous CTFs that are available for all skill levels. What I enjoy most about them, as opposed to standard IT/Security courses, is that the CTF revolves around critical thinking and researching solutions to each challenge. There is no curriculum to reference; it’s all on you!\nThis recent CTF I participated in presented me with a similar problem I had encountered in a previous event. This Web Challenge problem hosted in a Docker instance is displayed below:\nPreviously, I tried numerous techniques to solve this problem and receive the flag. First, I tried simply copying and pasting the output into the new URL. That was, of course, to no avail. So next, I tried capturing the GET response and utilizing the repeater function in Burp Suite to speed up my response time. Unfortunately, though I had faith this method would work, it also was not providing me with the flag.\nGET Response\nInserting the GET Response into the URL answer format\nFailure 🙁\nWhen I saw this problem return in this latest event, I decided to take a different approach and write a program in Python to achieve a solution. My code is displayed below:\n#!/bin/python3 # I created this script in order to successfully complete a challenge on a recent CTF I participated in # MEISTSEC # 10/2022 import requests x = requests.get(\u0026#39;http://10.10.100.200:40589/number/\u0026#39;) blow = x.content tango = blow.decode(\u0026#39;utf-8\u0026#39;) #print(tango) r = requests.get(f\u0026#39;http://10.10.100.200:40589/number/?answer={tango}\u0026#39;) print(r.content.decode(\u0026#39;utf-8\u0026#39;)) Utilizing the requests module, I can push HTTP requests to the server and harvest the response “string” into a variable I named “blow.” The response produced in the call needs to be more readable, “UTF-8” is used to format the string, and a new variable with the name change “tango” is produced. The script then makes the second GET request with the output of the variable “tango”, and takes it’s response and outputs it into a readable format.\nSummary Utilizing Python is an excellent way to solve security issues. The old me would go down a Google rabbit hole of trying to solve the problem by leveraging a security tool or methodology a competitor has previously utilized. Applying critical thinking, addressing what is happening in the problem, and programming my own solution is a more efficient and rewarding process. Being creative and building solutions aligns with my learning style.\nCheers, Meistsec!\n","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/pythonctf/pythonctf/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/posts/pythonctf/images/python.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e10/30/2022:\u003c/p\u003e\n\u003cp\u003eI love participating in Capture The Flag (CTF) events. When I first became focused on pursuing a career in cybersecurity, I was encouraged by many in the community to compete in the numerous CTFs that are available for all skill levels. What I enjoy most about them, as opposed to standard IT/Security courses, is that the CTF revolves around critical thinking and researching solutions to each challenge. There is no curriculum to reference; it’s all on you!\u003c/p\u003e","title":"Python \u0026 Capture the Flag"},{"content":"09/01/2022:\nThis is an analysis of a malware sample studied during TCM Security’s PMAT Course Practical Malware Analysis \u0026amp; Triage (PMAT)\nMalware Analysis Report\nunknown.exe Malware\nAug 2022 | Meistsec | v1.0\nExecutive Summary SHA256 Hash: 3ACA2A08CF296F1845D6171958EF0FFD1C8BDFC3E48BDD34A605CB1F7468213E\nTCM Security offers HuskyHacks Practical Malware Analysis \u0026amp; Triage Course. The PMAT course has been one of my most enjoyable experiences in learning cybersecurity thus far. The course teaches the fundamentals of malware analysis, reverse engineering, report writing, and establishing rules to prevent malware infections in a network. A sandboxed environment utilizing FlareVM and REMnux Linux on a compartmentalized network was utilized to perform the detailed analysis.\n[unkown.exe] is a malware sample first identified on Aug 31, 2022. A Nim compiled dropper, running on the x64 arch Windows Operating System.\n[unknown.exe] is a self-deleting binary in which, if it cannot make a call back to its initial callback domain, it will close that handle to that URL and delete itself from the disk. This proof of concept was demonstrated through a FlareVM instance executing the [unknown.exe] file while running the Inetsim application in a REMnux instance on a separate virtual machine and terminating the process of Inetsim during the malware execution.\nThere are no persistence mechanisms observed in the execution of this binary.\nYARA signature rules are provided at the end of this write-up. Additionally, the malware sample and hashes were submitted to VirusTotal to increase the Infosec community’s awareness of the threat.\nFig 1: Defanged binary staged to be executed in FlareVM\nHigh-Level Technical Summary [unknown.exe] consists of a single set call to a URL HTTP request to:\nhxxp://update.ec12-4-109-278-3-ubuntu20-04.local/\nwith a user-agent of Mozilla/5.0\\r\\n\nA strings extraction through Floss of the binary with the URL stated above produces no value when grepping for the value. However, in a more detailed analysis of the Wireshark Pcap sample within Inetsim, it does illuminate an exfiltration domain of hxxp://cdn.altimiter.local/feed?post=[data]\n[unknown.exe] deletes itself from the disk if it cannot make a complete TCP handshake to hxxp://update.ec12-4-109-278-3-ubuntu20-04.local/, as well as when the exfiltration of data has completed to the exfiltration domain.\nBinary contacts the initial callback domain Binary unpacks [passwrd.txt] file into C:\\Users\\Public\\ in order to encrypt the data. Binary opens a handle to targeted file/files on the victim’s desktop. In this instance, the file ‘cosmo.jpg’ is exfiltrated. Binary base64 encodes the contents of the file/files and encrypts the data with [passwrd.txt]. Encrypted data is exfiltrated through the hxxp://cdn.altimiter.local/feed?post=[data] URL. [data] being the base64 encoded data. File Name: unkown.exe~~\nSHA256 Hash 3aca2a08cf296f1845d6171958ef0ffd1c8bdfc3e48bdd34a605cb1f7468213e |\nunkown.exe\nThe initial executable that runs after a successful spearfishing of a victim via email\nFig 2: Cutter overview of [unknown.exe]\nBasic Static Analysis Fig 3: Hash output\nFig 4: VirusTotal analysis of the sha256 hash\nBasic Static Analysis Fig 5: Grep of “nim” from the strings extraction utilizing Floss\nFig 6: [passwd.txt] dropped in C:\\Users\\Public\\ directory for encryption\nAdvanced Static Analysis Fig 7: RC4 encryption protocol located in strings analysis\nFig 8: RC4 encryption function in Cutter analysis\nFig 9: sym.stealStuff_sikomode_130 function invokes the toRC4 function in Cutter when reverse engineering the binary\nFig 9: call houdini__sikomode_51 identifies networked traffic to the initial callback domain, without initial callback the function jumps to malware deletion .\nAdvanced Dynamic Analysis Fig 10: Inetsim utilized in REMnux in order to replicate a networked environment offering open services\nFig 11: Initial callback URL establishing communication\nFig 12: Exfiltration URL callback upon data encryption. Encrypted data is exfiltrated through the hxxp://cdn.altimiter.local/feed?post=[data] URL. The [data] portion being the base64 encoded data.\nIndicators of Compromise Network Indicators\nFig 13: WireShark Packet Capture of initial beacon check-in\nFig 14: WireShark Packet Capture of encryption/base64 data exfiltration\nHost-based Indicators\nFig 15: [passwrd.txt] upload to C:\\Users\\Public\\ directory\nFig 16: [passwrd.txt] the password to decrypt the data\nYara Signature Rule-set Callback URLs DOMAINS \u0026amp; PORT hxxps://****serv1.ec2-102-95-13-2-ubuntu.local:80 hxxps://****cdn.altimiter.local/feed?post=[data]:80\n","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/pmat/pmat/","summary":"\u003cp\u003e09/01/2022:\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/posts/pmat/images/1.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"this-is-an-analysis-of-a-malware-sample-studied-during-tcm-securitys-pmat-course\"\u003eThis is an analysis of a malware sample studied during TCM Security’s PMAT Course\u003c/h2\u003e\n\u003cp\u003ePractical Malware Analysis \u0026amp; Triage (PMAT)\u003c/p\u003e\n\u003cp\u003eMalware Analysis Report\u003c/p\u003e\n\u003cp\u003eunknown.exe Malware\u003c/p\u003e\n\u003cp\u003eAug 2022 | Meistsec | v1.0\u003c/p\u003e\n\u003ch2 id=\"executive-summary\"\u003eExecutive Summary\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eSHA256 Hash\u003c/strong\u003e: 3ACA2A08CF296F1845D6171958EF0FFD1C8BDFC3E48BDD34A605CB1F7468213E\u003c/p\u003e\n\u003cp\u003eTCM Security offers HuskyHacks Practical Malware Analysis \u0026amp; Triage Course. The PMAT course has been one of my most enjoyable experiences in learning cybersecurity thus far. The course teaches the fundamentals of malware analysis, reverse engineering, report writing, and establishing rules to prevent malware infections in a network. A sandboxed environment utilizing FlareVM and REMnux Linux on a compartmentalized network was utilized to perform the detailed analysis.\u003c/p\u003e","title":"Practical Malware Analysis \u0026 Triage"},{"content":"\nAfter years on Ubuntu, I switched my daily-driver workstation to Fedora (originally Fedora 36, around the time this post first went up). The piece I want to focus on here is firewall management — specifically, moving off of UFW and learning to drive firewalld properly on a host that runs a lot of virtual machines for security research.\nfirewalld provides a dynamically managed firewall with support for network/firewall zones that define the trust level of network connections or interfaces. It supports IPv4, IPv6, Ethernet bridges, and ipsets, and it cleanly separates runtime and permanent configuration. — firewalld.org\nThe zone model is the important idea. Each network interface (or source) is bound to a zone, and the zone determines which services and ports are reachable on that interface. This is a much better mental model than the flat ruleset I was used to with UFW or raw iptables.\nEnvironment The host runs Fedora with QEMU/KVM managed through virt-manager (the libvirt stack). The default install puts the primary NIC into the FedoraWorkstation zone and the virtual bridge into the libvirt zone. Out of the box FedoraWorkstation permits dhcpv6-client, mdns, samba-client, and ssh — note that permitted does not mean a service is running. The OpenSSH server is not enabled by default on Fedora Workstation, so port 22 only matters once you start sshd.service. Worth checking either way.\nThe use case that drove me to actually learn this: I wanted to serve files from the host to my VMs over an ad-hoc HTTP server (e.g. python3 -m http.server 8000) without exposing that port to anything else.\nUseful commands Run as root or with sudo.\nList active zones (which zones currently have an interface or source bound):\nfirewall-cmd --get-active-zones List everything allowed in a zone:\nfirewall-cmd --zone=FedoraWorkstation --list-all firewall-cmd --zone=libvirt --list-all Remove a service you don\u0026rsquo;t need (note the hyphen — it\u0026rsquo;s --remove-service, not --remove service):\nfirewall-cmd --zone=FedoraWorkstation --remove-service=dhcpv6-client firewall-cmd --zone=FedoraWorkstation --remove-service=samba-client firewall-cmd --zone=FedoraWorkstation --remove-service=mdns The inverse uses --add-service=\u0026lt;name\u0026gt;. To see the full catalog of named services: firewall-cmd --get-services.\nLetting specific VMs reach the host Rather than open a port to the world, bind your VM\u0026rsquo;s address (or subnet) into a trusted zone as a source. Anything from that source will be evaluated against that zone\u0026rsquo;s rules:\nfirewall-cmd --zone=trusted --add-source=192.168.122.50 Now 192.168.122.50 can hit any open port on the host. Repeat per VM, or use a CIDR (192.168.122.0/24) for the whole virtual network. Confirm:\nfirewall-cmd --get-active-zones firewall-cmd --zone=trusted --list-all Runtime vs. permanent This trips a lot of people up. Every command above modifies the runtime config — gone on reboot or systemctl reload firewalld. There are two ways to make changes stick:\nStage in runtime, verify, then commit: firewall-cmd --runtime-to-permanent Or write directly to permanent and reload: firewall-cmd --permanent --zone=FedoraWorkstation --remove-service=samba-client firewall-cmd --reload I prefer option 1 for anything non-trivial — you get to confirm the rule works before locking it in.\nSanity check after a reload systemctl reload firewalld firewall-cmd --get-active-zones firewall-cmd --zone=FedoraWorkstation --list-all firewall-cmd --zone=libvirt --list-all Conclusion firewalld is more verbose than UFW, but the zone model fits the way a research host actually works: a hostile-internet-facing zone, a trusted-VM-facing zone, and explicit rules about what crosses between them. Once you internalize runtime vs. permanent and the difference between adding a service and adding a source, day-to-day management is straightforward.\n","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/firewall/firewall_configurations/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/posts/firewall/images/1.png\"\u003e\u003c/p\u003e\n\u003cp\u003eAfter years on Ubuntu, I switched my daily-driver workstation to Fedora (originally Fedora 36, around the time this post first went up). The piece I want to focus on here is firewall management — specifically, moving off of UFW and learning to drive \u003ccode\u003efirewalld\u003c/code\u003e properly on a host that runs a lot of virtual machines for security research.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003efirewalld\u003c/strong\u003e provides a dynamically managed firewall with support for network/firewall \u003cem\u003ezones\u003c/em\u003e that define the trust level of network connections or interfaces. It supports IPv4, IPv6, Ethernet bridges, and ipsets, and it cleanly separates \u003cem\u003eruntime\u003c/em\u003e and \u003cem\u003epermanent\u003c/em\u003e configuration. — \u003ca href=\"https://firewalld.org/\"\u003efirewalld.org\u003c/a\u003e\u003c/p\u003e","title":"Firewall Configurations"},{"content":"3/30/2022\nSo, I initially tackled this target like most—reconnaissance, enumeration, and eventually getting stuck and seeking an external solution. In gaining an understanding of staging buffer overflow attacks, I learned that there is an operating procedure to direct the process—multiple scripts, leveraging Immunity Debugger for the proof of concept, and eventually securing a shell.\nSimilar methods are demonstrated in numerous walk-throughs on the Internet. However, these examples lack flexibility on replicating the process for like targets and vulnerabilities. So, I decided to dig a little deeper.\nOne of the best explanations of how this type of attack is staged can be studied in Heath Adam’s buffer overflow presentation –\nhttps://www.youtube.com/watch?v=ncBblM920jw\nHis explanation of the anatomy/composition of the “buffer stack” really puts this concept into perspective.\nInstead of following a similar procedure like many others who have reviewed this machine. I decided to tackle this challenge from an unconventional approach. I have been yearning to challenge myself in undertaking a Python project to give myself a better understanding of the scripting language.\nSo, I figured why not tackle two birds with one stone and automate the attack process, which I could easily modify for future encounters. In the coming future, I plan to further develop the Buffer Overflow Framework as I progress in my Python learning. Please review the code for a comprehensive understanding of how this attack procedure is implemented.\n“The Meist” A Buffer Overflow Framework\nhttps://github.com/MEISTSEC/THEMEIST\nThe Attack Process Initial Recon [sudo nmap -sC -sV -p- -pN] will give you a more detailed,but time-consuming enumeration with its nse scripting and full port analysis\nPort 9999 in browser\nPort 10000 in browser\nConduct a gobuster scan against port 10000 which reveals the [/bin] directory\nbrainpan.exe is being hosted within the /bin directory\nnetcat call of the application\nwget the application onto your Kali machine\nEnvironment Setup Start Microsoft Windows Virtual machine and within “virus and threat protection,” turn off everything to include real-time protection in manage settings. The intention is to place this executable in a controlled environment in our local network to stage a buffer overflow attack. Once our proof of concept is functional, we can leverage it against the TryHackMe server room. Within Kali, in the directory where brainpan.exe is located, run the following command to host the file: python3 -m http.server 8000 In windows, create a /brainpan directory to receive brainpan.exe from the hosted Kali directory in the previous step. Run the following command in powershell: certutil -urlcache -f http://LHOSTKALI:8000/brainpan.exe brainpan.exe Throughout the testing of this exploit you will continually reopen brainpan.exe and Immunity Debugger as an administrator.\nWith brainpan.exe running as an admin (each time). Also open Immunity Debugger as an admin (likewise throughout this process). In Immunity, select and attach brainpan.exe. Press the red arrow icon to start Immunity Debugger’s import of brainpan.exe. THE MEIST Programmed automation fuzzing the application\nSet the working folder in Immunity Debugger using Mona with the above command\nFuzzer crashes the app at 600 bytes\nCopy the generated pattern creation variable for step 3\nImport/paste the previously copied pattern generated data\nRun in immunity debugger\nTake note of the EIP. It contains the normal pattern line. Specifically the offset (524)\nEnter offset here\nEnter into Immunity Debugger\nType in the location of bytearray.bin\nStep 3\nCopy the Badchar generation and input it into the next field presented\nThe Badchar generation was inputted into THEMEIST.py script as a variable and placed in payload\nTake note of the ESP output\nRun the mona command with the ESP register point\nThis will display the BadChars needed to be identified\nEnter the Badchars\nInformation was rewritten to bytearray.bin\nOption 4\nEnter Badchars into Immunity Debugger\nNote the “Jump esp”\nThis part can be confusing. Ensure you document the retn variable and input it in little endian format “essentially reversed” at the prompt\nSelect Y if you did not download the github package or the individual SHELLPOPPER.py script\nOption 5\nOption 6\nIn your Kali instance, run [nc -lvnp 4444] to initiate a listener\nIn Kali run the following command in a new terminal window to setup a listener:\nnc -lvnp 4444\nAnd in another Kali terminal run:\npython SHELLPOPPER.py\nShell Popper in action with the TGT IP and Port\nThe Buffer Overflow proof of concept was successful. We have spawned a local shell from our testing Windows VM environment.\nAttacking the THM Hosted Application You will rerun the msfvenom command to create a new 32 bit payload for a Linux reverse shell to implement into the downloaded SHELLPOPPER.py script. This payload utilizes the tun0 interface established through your OpenVPN connection with TryHackME.\nCommand shell opened. Use the above commands to stabalize the shell\nPuck seems to be an unprivileged user\nPrivilege Escalation Do some OSINT on documented privilege escalation attacks against Ubuntu 12.10, as well as gtfobins.github.io for Unix binaries used to bypass local security restrictions.\nsudo -l reveals the anansi_util can be executed as root w/out a password\nA few options available. However, only the manual option seems to be accessible with current creds\nRun the manual “man” command with the less argument\nBy running the [!bash] command we elevate “puck’s” privileges to the root user\nElevated privs\n","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/brainpan/brainpan/","summary":"\u003cp\u003e3/30/2022\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/posts/brainpan/images/1.png\"\u003e\u003c/p\u003e\n\u003cp\u003eSo, I initially tackled this target like most—reconnaissance, enumeration, and eventually getting stuck and seeking an external solution. In gaining an understanding of staging buffer overflow attacks, I learned that there is an operating procedure to direct the process—multiple scripts, leveraging Immunity Debugger for the proof of concept, and eventually securing a shell.\u003c/p\u003e\n\u003cp\u003eSimilar methods are demonstrated in numerous walk-throughs on the Internet. However, these examples lack flexibility on replicating the process for like targets and vulnerabilities. So, I decided to dig a little deeper.\u003c/p\u003e","title":"The Meist- A Buffer Overflow Attack Framework"},{"content":"Hello World!! ","permalink":"https://6aa388fd.meistsec-blog.pages.dev/posts/hello/hello_world/","summary":"\u003ch1 id=\"hello-world\"\u003eHello World!!\u003c/h1\u003e","title":"Hello_World"}]