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

ToolReal timeResult
whois0.10sRegistrar/nameserver data
dig (×3, corrected)~0.3s totalA/MX/NS records, all Cloudflare
theHarvester (crtsh)69.00sZero hosts found
Sublist3r (crashed) / fixed3.11s crash / 12.37s fixedCrash, then a clean run once patched
amass enum552.31sZero subdomains beyond apex
dnsx0.31sConfirmed 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.