tutorials6 min read
Were You a Proxy? Grep Your DNS Logs Against the QTFY IOC List
Yesterday the DOJ bricked QTFY's botnet. Today: one afternoon of grep against your own DNS logs tells you whether your cameras and plugs were quietly routing a Chinese state actor's traffic. Here's the exact pipeline, verified against the live IOC CSVs.
Pull the two QTFY IOC CSVs from IC3, extract the 45 domain indicators from QTFY_IOC_Infrastructure.csv (comma-separated — the Files CSV is hashes, not domains), grep your DNS/proxy logs for them, cross-check the Lumen telemetry names (yotocloud.com, qt-proxy.org, fastlink.ws), and plant a test domain to prove the pipeline catches what's there. ~1-2 hours, no hardware, one requirement: logs you actually kept.
Yesterday, the DOJ seized the domains that QTFY's QScan and QTRouter hard-code and bricked the whole toolset. The companion FBI/NSA/CNMF advisory (JCSA-20260826-01) ships with downloadable IOC CSVs — and if you run your own DNS log or proxy log, that means you can check whether your IoT fleet was part of the machine.
One afternoon of grep tells you whether your cameras and plugs were quietly routing a Chinese state actor's traffic.
Before you start//
- DNS logs you actually keep. Pi-hole or AdGuard Home with log retention, or a forward-proxy log. This is the one hard requirement — if your logs rotated out, the hunt is limited. Heads-up on retention: Pi-hole's text log (
/var/log/pihole.log) is rotated daily and kept for only a few days by default (per the Pi-hole Discourse), so if you want a deeper window, export the query log from the web UI first — it retains far longer than the text log. curlandawk(or any equivalent). No hardware, no special access.- Time: 1–2 hours, most of it waiting for greps and triaging hits.
- Blast radius: this is a read-only hunt against your own logs. Nothing on the network changes until you act on a hit. The only thing that could break is your patience.
- Retention reality check: QTFY activity in the wild dates to 2018, but the active campaign window is what matters — the CSV's
first_seen/last_seencolumns tell you which domains were live when. A domain that was actor-controlled in 2019 and dead in 2020 is worth a different level of alarm than one marked Present.
The steps//
1. Pull the IOC files
curl -O https://www.ic3.gov/CSA/2026/QTFY_IOC_Infrastructure.csv
curl -O https://www.ic3.gov/CSA/2026/QTFY_IOC_Files.csvThese are the two indicator files published with the JCSA-20260826-01 advisory. Why: the Infrastructure CSV is what you'll grep DNS logs with; the Files CSV is for host-level hash checks (step 6).
2. Extract the domain list from the Infrastructure CSV
The gotcha: the domains live in QTFY_IOC_Infrastructure.csv, not the Files CSV. The Infrastructure CSV is comma-separated with columns type,indicator,first_seen,last_seen,description; the Files CSV is type,name,sha256 — binary/script hashes with no domain column at all. Filter the type == domain rows and take the indicator column:
awk -F, '$1=="domain" {print $2}' QTFY_IOC_Infrastructure.csv > ioc-domains.txt
wc -l ioc-domains.txt # 45 domains as of 2026-08-31Why: grep -F -f wants one pattern per line. The 45 domain rows include the whole QTFY estate — qt-proxy.org, qt-team.com, qtproxy.xyz, qtcyber.com, the mq-task/mq-result broker and result hosts, and a long tail of throwaway domains with first_seen dates going back to 2017.
3. Grep your DNS log for every IOC domain
grep -F -f ioc-domains.txt /var/log/pihole.logThe log path is /var/log/pihole.log — pihole -t is just sudo tail -F /var/log/pihole.log per the Pi-hole Discourse. Why: -F treats each line as a fixed string (no regex surprises from dots in domains), -f reads the pattern list. Every hit is a client on your network that resolved a QTFY domain — and Pi-hole's log lines include the client IP, which is the point of the whole hunt.
If you're on AdGuard Home or a forward proxy, point the same grep at that log (adjust the pattern to your log format — the domain is what you're matching, not the line shape).
4. Cross-check the Lumen telemetry names
Lumen's Black Lotus Labs report names infrastructure that is not necessarily in the IOC CSV, so grep for it separately:
grep -E -i 'yotocloud\.com|qt-proxy\.org|fastlink\.ws' /var/log/pihole.logyotocloud.com— Fast Labyrinth egress; Lumen's telemetry showsflanycast-xxxx.yotocloud.comandflnode-xxxx.yotocloud.comsubdomains initiating traffic toward high-value targets.qt-proxy.org— the task broker and results backend (mq-task.qt-proxy.org,mq-result.qt-proxy.org).fastlink.ws— the commercial "Airport" proxy service whose egress nodes QTFY co-opted.
Why: these are Lumen's telemetry findings, not official IOC CSV entries — attribute them accordingly. A hit on yotocloud.com is a stronger signal than most rows in the CSV, because Lumen watched those specific subdomains talking to targets.
5. For any hit: identify the device, then decide
- Identify the client. The Pi-hole log line gives you the client IP; map it to a device via your DHCP table or the Pi-hole web UI.
- Check its outbound connections — is it sustained traffic (repeated resolutions, connections over days) or a single query?
- Decide: patch it (firmware update if one exists), re-image it (fresh flash, new credentials), or VLAN it into the junkyard (IoT segment with outbound filtering).
Why: a QTFY hit on a device that also has outbound connections to the same infrastructure is a very different animal from a hit on a device that never talks to the outside world.
6. Bonus: confirm the device's WAN exposure
From a mobile hotspot (so you're actually coming from outside), scan the device's public exposure:
nmap -Pn -sV <device-ip-or-wan-ip>Why: the same before-scan we use in the 10-minute router hygiene drill — if the hit device is answering on the WAN, it wasn't just a proxy, it was a front door.
7. Host-level check (optional, for Linux boxes)
The Files CSV is type,name,sha256 — binary and script hashes (the agent_linux_* payloads and friends). On any Linux host that might have been a QScan worker, hash the suspect binaries and compare against the IOC hashes:
Careful: 2>/dev/null in the second line just silences sha256sum's "no such file or directory" noise when a glob matches nothing — it is not writing to a device. The only file this step creates is ioc-hashes.txt in your working directory.
awk -F, 'NR>1 {print $3}' QTFY_IOC_Files.csv > ioc-hashes.txt
sha256sum /usr/local/bin/* /opt/* 2>/dev/null | awk '{print $1}' | grep -F -x -f ioc-hashes.txtWhy: QScan's workers are small Linux agents; if one of your boxes has a binary whose sha256 matches agent_linux_amd64 (or a sibling), you've found the worker, not just a proxy. An empty result means no match — which is the answer you want to verify, not assume.
Verify it worked//
A pipeline that can't catch a known-present IOC proves nothing. Plant a test domain in your own logs:
- Pick a domain from
ioc-domains.txt(or, cleaner, append a throwaway test domain to a copy of the pattern file). - Query it from a test device:
nslookup <test-domain>or justcurl -sI https://<test-domain>. - Run the step-3 grep. It must return a hit with your test device's client IP.
- Remove the test domain from the pattern file (or delete the planted log line if you edited a copy).
If the planted domain didn't show up, your log path, log format, or grep flags are wrong — fix the pipeline before you trust a clean result.
When it breaks//
- Logs rotated out. The most common failure: your text log only goes back a few days and the campaign window you care about is older. Rollback: nothing to roll back — you just have a narrower answer. Export the FTL query log (web UI) next time before you hunt.
- Shared-DNS false positives. A neighbor's camera on your guest SSID resolving a QTFY domain is not your breach. Rollback: check the client IP against your own DHCP table; if it's not yours, note it and move on.
- A hit that's just a scanner passing through. One resolution of a throwaway domain by a device that never connects outbound is probably noise. Rollback: look for sustained connections (step 5) before you re-image anything.
Version discipline//
Verified against: Pi-hole's current log path /var/log/pihole.log and pihole -t behavior (per Pi-hole Discourse); both IOC CSVs' column layouts verified live on 2026-08-31 (Infrastructure: type,indicator,first_seen,last_seen,description with 45 domain rows; Files: type,name,sha256); Lumen telemetry names per the Black Lotus Labs report. The nmap command is the same one from the router hygiene drill.
Filed under
Sources
5 cited · 3 primary- ic3.gov/CSA/2026/260826.pdfPrimary
- ic3.gov/CSA/2026/QTFY_IOC_Infrastructure.csvPrimary
- ic3.gov/CSA/2026/QTFY_IOC_Files.csvPrimary
- lumen.com/blog/en-us/the-infrastructure-quartermaster-inside-a-china-nexus-state-enablement-modelReporting
- discourse.pi-hole.net/t/how-do-i-watch-and-interpret-the-pihole-log-file/276Reporting
Related dispatches
- intelThe IoT Botnet That Hid Chinese State Hacks Is Dead — And Your Camera Might Have Been Its Proxy — 30 AUG 2026
- intelYour $50 PoE Camera's Bootloader Has a Hard-Coded Password (CVE-2026-85083) — 15 SEP 2026
- exploitsThe $63 Print Server With Two Unauthenticated 9.8s: Xiiaozet LK100W, CISA ICSA-26-239-01 — 31 AUG 2026