Old Kit, New Kit Part 3: Subdomain and DNS Recon — Amass and Dnsx vs Whois, Dig, theHarvester, and Sublist3r
Second pair in this series, and a different shape of comparison than Part 2’s port scanners: this one is largely passive on both sides, run externally with no lab target at all. Four classic tools, whois, dig, theHarvester, and Sublist3r, against two modern ones, amass and dnsx, all asked the same question about a real domain: what subdomains and DNS records exist, found without touching the target directly. Run against this site’s own domain, michealgarner.co.uk, from Kali’s internet-facing interface rather than the isolated lab bridge, the one deliberate exception to keeping this series’ scanning inside its own lab.
How this was verified
Every command below is a real run against the live domain, not a documented example. Nothing here is active enumeration, no directory brute-forcing, no port scanning, just what passive sources and DNS itself will hand over for free.
The classic baseline
whois first, instant and unglamorous:
$ time whois michealgarner.co.uk
Registrar:
Cloudflare, Inc. [Tag = CLOUDFLARE]
Relevant dates:
Registered on: 25-Jun-2014
Expiry date: 25-Jun-2027
Name servers:
gene.ns.cloudflare.com
karl.ns.cloudflare.com
real 0.10s
dig came with a genuine gotcha worth knowing about before anyone copies a “check all these record types at once” one-liner from a tutorial:
$ dig michealgarner.co.uk A MX NS TXT +noall +answer
;; Warning, extra type option
;; Warning, extra type option
;; Warning, extra type option
michealgarner.co.uk. 300 IN TXT "v=spf1 include:_spf.mx.cloudflare.net ~all"
dig’s type argument is positional and singular. Stacking four of them doesn’t check four record types, it silently keeps only the last one and treats the rest as ignored extras, with a warning easy to miss if you’re not reading closely. Getting the real picture means three separate calls:
$ dig michealgarner.co.uk A +noall +answer
michealgarner.co.uk. 300 IN A 104.21.18.89
michealgarner.co.uk. 300 IN A 172.67.181.133
$ dig michealgarner.co.uk MX +noall +answer
michealgarner.co.uk. 300 IN MX 40 route1.mx.cloudflare.net.
michealgarner.co.uk. 300 IN MX 45 route3.mx.cloudflare.net.
michealgarner.co.uk. 300 IN MX 5 route2.mx.cloudflare.net.
$ dig michealgarner.co.uk NS +noall +answer
michealgarner.co.uk. 86400 IN NS gene.ns.cloudflare.com.
michealgarner.co.uk. 86400 IN NS karl.ns.cloudflare.com.
Two Cloudflare anycast addresses, three Cloudflare mail routes, two Cloudflare nameservers, everything DNS-facing sits behind Cloudflare, as expected for a Cloudflare-registered domain.
theHarvester against certificate transparency logs, the classic passive subdomain-discovery method, came back empty:
$ time theHarvester -d michealgarner.co.uk -b crtsh -f theharvester-michealgarner.txt
[*] Target: michealgarner.co.uk
[*] Searching CRTsh.
[*] No IPs found.
[*] No emails found.
[*] No hosts found.
real 69.00s
Zero hosts isn’t a failed run, it’s a legitimate result: a Cloudflare-fronted personal domain doesn’t necessarily generate the kind of per-subdomain certificate history that crt.sh indexes, especially with Cloudflare’s universal SSL covering everything under one certificate.
Sublist3r was the fourth classic tool on the list, and it never got the chance to return a result at all:
$ time sublist3r -d michealgarner.co.uk -o sublist3r-michealgarner.txt
[-] Searching now in Baidu..
[-] Searching now in Yahoo..
...
[-] Searching now in PassiveDNS..
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/sublist3r.py", line 989, in main
enum.start()
File "/usr/lib/python3.14/multiprocessing/popen_forkserver.py", line 47, in _launch
reduction.dump(process_obj, buf)
TypeError: cannot pickle '_thread.lock' object
when serializing sublist3r.BaiduEnum state
real 3.11s
Sublist3r’s per-source enumeration objects carry a threading.Lock, and Python’s multiprocessing can’t pickle a lock to hand it to a forked worker process, an incompatibility between code written for an older Python multiprocessing default and Python 3.14’s current forkserver-based start method on this Kali install. Not a slow result, not an empty result, an outright crash before a single source finished. Sublist3r is old and has seen only sporadic maintenance, and this is exactly what that looks like in practice on current software.
The actual fix
The underlying cause is specific enough to fix rather than just note: Python 3.14 changed multiprocessing’s default start method on Linux from fork to forkserver, and only forkserver needs to pickle the objects it hands to a worker process, fork just clones the running process instead. Forcing the old start method back, before Sublist3r’s own code runs, sidesteps the crash entirely without touching Sublist3r itself:
$ time python3 -c "
import multiprocessing
multiprocessing.set_start_method('fork', force=True)
import sublist3r
sublist3r.main('michealgarner.co.uk', 40, 'sublist3r-michealgarner.txt', None, silent=False, verbose=False, enable_bruteforce=False, engines=None)
"
[-] Searching now in Baidu..
[-] Searching now in Yahoo..
...
[!] DNSDumpster module failed: Could not find CSRF token on DNSDumpster page
[!] Error: Virustotal probably now is blocking our requests
real 12.37s
No crash, clean run in 12.37 seconds. Two individual sources failed on their own terms rather than taking the whole tool down with them, DNSDumpster’s page apparently no longer has the CSRF token Sublist3r’s scraper expects, and VirusTotal is rejecting unauthenticated requests, both separate, smaller examples of the exact same aging-tool problem rather than anything the fix caused. The tool is old enough that half its data sources have quietly drifted out from under it, but with the start-method workaround in place it at least runs to completion and reports what it can, rather than dying before it starts.
The modern side, and its own real gotchas
amass was the first surprise before it even started scanning. The Kali package is v5.1.1, a CLI that’s drifted from what most existing amass tutorials describe. -passive triggers a deprecation notice, it’s the default now unless -active or -brute is passed, and the once-standard -o <file> flag is simply gone:
$ amass enum -passive -d michealgarner.co.uk -o amass-michealgarner.txt
flag provided but not defined: -o
amass enum -h showed the real current flags, -oA for an output-file prefix, -dir for an output directory. Before any of that, though, the very first invocation spent its opening seconds downloading data neither command mentioned needing:
Checking for new libpostal data file...
100 9.71M 100 9.71M ...
Checking for new libpostal parser data file...
Downloading multipart: https://github.com/openvenues/libpostal/releases/download/v1.0.0/parser.tar.gz, num_chunks=12
100 48.00M 100 48.00M ...
Close to 58MB of address-parsing machine learning data, fetched silently on first run before the actual domain enumeration begins. With the corrected flags:
$ time amass enum -d michealgarner.co.uk -oA amass-michealgarner
680 / 680 [=====================================] 100.00% 1 p/s
Session Scope
FQDN:
michealgarner.co.uk
real 552.31s
552.31 seconds, over nine minutes, working through 680 passive data sources, and the result is the domain itself with nothing added. Zero subdomains found, matching theHarvester’s own empty result exactly.
dnsx closed out the modern side, fed the one thing worth checking given amass’s own findings, the apex domain and the obvious www guess:
$ time printf "michealgarner.co.uk\nwww.michealgarner.co.uk\n" | dnsx -a -resp
www.michealgarner.co.uk [A] [172.67.181.133]
www.michealgarner.co.uk [A] [104.21.18.89]
michealgarner.co.uk [A] [172.67.181.133]
michealgarner.co.uk [A] [104.21.18.89]
real 0.31s
Fast, and it confirms exactly what dig already showed, both names resolve to the same two Cloudflare addresses.
At a glance
| Tool | Real time | Result |
|---|---|---|
| whois | 0.10s | Registrar/nameserver data |
| dig (×3, corrected) | ~0.3s total | A/MX/NS records, all Cloudflare |
| theHarvester (crtsh) | 69.00s | Zero hosts found |
| Sublist3r (crashed) / fixed | 3.11s crash / 12.37s fixed | Crash, then a clean run once patched |
| amass enum | 552.31s | Zero subdomains beyond apex |
| dnsx | 0.31s | Confirmed apex + www, same Cloudflare IPs |
Verdict
Neither generation of tooling comes out ahead on reliability in this particular session. Sublist3r, the classic side’s dedicated subdomain tool, couldn’t complete a run at all on current Python without a one-line workaround for a Python version compatibility change, and even once fixed, two of its data sources had independently rotted. Amass, the modern side’s flagship, ran fine but needed its own real correction pass for a CLI that’s moved on from its own documentation, plus an unannounced 58MB dependency download before doing anything useful. Once both actually ran, they agreed completely: no subdomains beyond the bare domain, the same answer theHarvester reached in a fraction of the time. Nine-plus minutes and 680 checked sources bought nothing that 69 seconds and one didn’t already establish. For a quiet, single-purpose, Cloudflare-fronted domain like this one, breadth of passive sources wasn’t the bottleneck, there was simply nothing more to find, and the modern tool’s design didn’t know that going in any better than the classic one did. This pairing was never going to be a detection story, there’s no host on the receiving end to log anything either tool did, and the honest finding here is about tooling maturity and dependency risk rather than stealth: age cuts both ways, and “actively maintained” is worth checking for both the tool you already trust and the one you’re reaching for instead.