️🏴 404 on the Dancefloor
As always, an announcement on Twitter kicks things off…
As inspired by Dan Kaminksy’s early Defcon TCP Blackops talks, we are initially releasing the song only as audio over DNS. Streaming over DNS is an exercise for the listener :).
Here’s something to get you started:
dig -t txt 404-on-the-dancefloor.projectmammoth . com
If you insist:
❯ dig -t txt 404-on-the-dancefloor.projectmammoth.com
; <<>> DiG 9.18.33 <<>> -t txt 404-on-the-dancefloor.projectmammoth.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62334
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;404-on-the-dancefloor.projectmammoth.com. IN TXT
;; ANSWER SECTION:
404-on-the-dancefloor.projectmammoth.com. 827 IN TXT "chunks=65816; filename=Project Mammoth - 404 on the Dancefloor - M1 (release).mp3; sha1sum=16dd20eca6f3ab3475893b741baa9d0323bbdcf3; chunk_start=chunk1.404-on-the-dancefloor.projectmammoth.com"
;; Query time: 163 msec
;; SERVER: 100.100.100.100#53(100.100.100.100) (UDP)
;; WHEN: Thu Jul 10 11:17:13 BST 2025
;; MSG SIZE rcvd: 274
So there’s a filename, a count of the “chunks” and another subdomain; taking a look at the latter:
❯ dig -t TXT chunk1.404-on-the-dancefloor.projectmammoth.com
; <<>> DiG 9.18.33 <<>> -t TXT chunk1.404-on-the-dancefloor.projectmammoth.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38224
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;chunk1.404-on-the-dancefloor.projectmammoth.com. IN TXT
;; ANSWER SECTION:
chunk1.404-on-the-dancefloor.projectmammoth.com. 955 IN TXT "SUQzBAAAAAAKTFRYWFgAAAAiAAADb3JpZ2luYXRvcl9yZWZlcmVuY2UAYWE0TVRoY2gjb0hrVERSQwAAAAUAAAMyMDI1VFhYWAAAAEgAAAN1bWlkADB4MDYwQTJCMzQwMTAxMDEwNTAxMDEwRjEwMTMwMDAwMDAxM0JFMUMyMUVEOUI4MDAwN0MxMjI0RkU3RTBFNzY5Q1RYWFgAAAAZAAADdGltZV9yZWZlcmVuY2UAMTU4NzYwMDAwVFNTRQA"
;; Query time: 183 msec
;; SERVER: 100.100.100.100#53(100.100.100.100) (UDP)
;; WHEN: Thu Jul 10 11:29:42 BST 2025
;; MSG SIZE rcvd: 344
There’s base64-encoded data! We’re definitely in CTF territory now!
Initially, my thought was to use rthalley/dnspython and simply loop chunks times, grabbing the base64 data:
#!/usr/bin/env python3
import dns.resolver
num_chunks = 65816
resolver = dns.resolver.Resolver()
resolver.timeout = 5
resolver.lifetime = 5
with open("Project Mammoth - 404 on the Dancefloor - M1 (release).mp3.b64", "wb") as out:
for i in range(1, num_chunks + 1):
print(f"{i} of {num_chunks:,}", flush=True)
subdomain = f"chunk{i}.404-on-the-dancefloor.projectmammoth.com"
try:
answers = resolver.resolve(subdomain, "TXT")
txt = answers[0].strings[0]
out.write(txt)
except Exception as e:
print(f"[!] Error on {subdomain}: {e}")
break
…which works! It’s slow but it works and I’m left with Project Mammoth - 404 on the Dancefloor - M1 (release).mp3 for my listening pleasure.
…but there’s got to be a simpler/quicker way, right?
It’s possible to grab just the TXT record via dig:
❯ dig +short -t TXT chunk1.404-on-the-dancefloor.projectmammoth.com
"SUQzBAAAAAAKTFRYWFgAAAAiAAADb3JpZ2luYXRvcl9yZWZlcmVuY2UAYWE0TVRoY2gjb0hrVERSQwAAAAUAAAMyMDI1VFhYWAAAAEgAAAN1bWlkADB4MDYwQTJCMzQwMTAxMDEwNTAxMDEwRjEwMTMwMDAwMDAxM0JFMUMyMUVEOUI4MDAwN0MxMjI0RkU3RTBFNzY5Q1RYWFgAAAAZAAADdGltZV9yZWZlcmVuY2UAMTU4NzYwMDAwVFNTRQA"
Implementing the above loop under that same premise:
for chunk in {1..65816}
do
dig +short -t TXT "chunk${chunk}.404-on-the-dancefloor.projectmammoth.com" | sed -r 's@"@@g'
done | base64 --decode > "Project Mammoth - 404 on the Dancefloor - M1 (release).mp3"
…and again, Project Mammoth - 404 on the Dancefloor - M1 (release).mp3 is decoded and marvellous. It’s still quite a slow process, however.
Instead, how about a zone transfer to grab the all the TXT records at once?
Checking from which authoritative nameserver the responses are coming:
❯ dig +nssearch 404-on-the-dancefloor.projectmammoth.com
SOA ns1.404-on-the-dancefloor.projectmammoth.com. hostmaster.404-on-the-dancefloor.projectmammoth.com. 2025062801 3600 1800 604800 300 from server 35.192.39.28 in 107 ms.
SOA ns1.404-on-the-dancefloor.projectmammoth.com. hostmaster.404-on-the-dancefloor.projectmammoth.com. 2025062801 3600 1800 604800 300 from server 35.192.39.28 in 107 ms.
Again, dig can be used to make the AXFR request:
❯ dig -t AXFR 404-on-the-dancefloor.projectmammoth.com @ns1.404-on-the-dancefloor.projectmammoth.com
; <<>> DiG 9.18.33 <<>> -t AXFR 404-on-the-dancefloor.projectmammoth.com @ns1.404-on-the-dancefloor.projectmammoth.com
;; global options: +cmd
404-on-the-dancefloor.projectmammoth.com. 3600 IN SOA ns1.404-on-the-dancefloor.projectmammoth.com. hostmaster.404-on-the-dancefloor.projectmammoth.com. 2025062801 3600 1800 604800 300
404-on-the-dancefloor.projectmammoth.com. 3600 IN NS ns1.404-on-the-dancefloor.projectmammoth.com.
404-on-the-dancefloor.projectmammoth.com. 3600 IN NS ns2.404-on-the-dancefloor.projectmammoth.com.
404-on-the-dancefloor.projectmammoth.com. 3600 IN TXT "chunks=65816; filename=Project Mammoth - 404 on the Dancefloor - M1 (release).mp3; sha1sum=16dd20eca6f3ab3475893b741baa9d0323bbdcf3; chunk_start=chunk1.404-on-the-dancefloor.projectmammoth.com"
chunk1.404-on-the-dancefloor.projectmammoth.com. 3600 IN TXT "SUQzBAAAAAAKTFRYWFgAAAAiAAADb3JpZ2luYXRvcl9yZWZlcmVuY2UAYWE0TVRoY2gjb0hrVERSQwAAAAUAAAMyMDI1VFhYWAAAAEgAAAN1bWlkADB4MDYwQTJCMzQwMTAxMDEwNTAxMDEwRjEwMTMwMDAwMDAxM0JFMUMyMUVEOUI4MDAwN0MxMjI0RkU3RTBFNzY5Q1RYWFgAAAAZAAADdGltZV9yZWZlcmVuY2UAMTU4NzYwMDAwVFNTRQA"
chunk10.404-on-the-dancefloor.projectmammoth.com. 3600 IN TXT "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
chunk100.404-on-the-dancefloor.projectmammoth.com. 3600 IN TXT "T6GwCUqxHNrevmGWBKPzKP48Ve/haAKRSjnoAAAMLoz4pWJ2zBikmMZwmmPUFRRCjgfJw8D3ZJ0fltEZlIXGB2ctF5RQrM9tHfxZHz/W1+JrWYqNOy9Pfs2g2b7PWes27tX6XmzUNls1eq65dfk0vZ3Lueveaj6zdctlnabHE1Y5vT1Ucp7rn+1CpyBDc/mB5+71TtiFtFZP/3Xc/NoGWGFV2oTu1ItOWEztDpbCsYxpmLqNtOXbm7J/HZ1CPlK"
…
chunk9998.404-on-the-dancefloor.projectmammoth.com. 3600 IN TXT "Wy8vHVsSDvFZ4As6W2N0xSMDIvPRtQ7Wl6vWudOw591Cdp+2VeWbW19ZvFdMmw8N0dDhYiMzxx5yqzIKu0UqmN+KaX9hx5a16AELkcLFQwwEjKAuMGDYxsIiYimRQaZBPhgoiAppGUhOZwWhy0VGaTcZoBRoAgGlCoY+GphcKmUyCZ8Hxi9FGSiYRWYyg9TIwxMgAwwuADE4fMaB0mDhioGoamqwiDBAVAQY5QYeGDgeRYM4yyKM0Qxldgxrjgb"
chunk9999.404-on-the-dancefloor.projectmammoth.com. 3600 IN TXT "wETGAEZowOGM4A3cEFADAYj4OIFiTRBM4cwjBsFQ1Y4y6OCGc6MosMNNSPglMygg68HDGCuXZUrKEgce3Iu8QkoHIjPM9YKYDDS0KXZeFPtMdARAKOj2PuogpujnI5trUNMhU1f5zVIuzCXKW60pkCfSaMBrnexxFg33T2ksTU7dl/FaojLWXRaIvo/9FMSYSLx0q+1oTRCi4kD7lzCbHIH0xJkhWVbFzCJ7m4EtmGVbQQQzjPNJlNmhVjJHB8F"
ns1.404-on-the-dancefloor.projectmammoth.com. 3600 IN A 35.192.39.28
ns2.404-on-the-dancefloor.projectmammoth.com. 3600 IN A 35.192.39.28
404-on-the-dancefloor.projectmammoth.com. 3600 IN SOA ns1.404-on-the-dancefloor.projectmammoth.com. hostmaster.404-on-the-dancefloor.projectmammoth.com. 2025062801 3600 1800 604800 300
;; Query time: 5751 msec
;; SERVER: 35.192.39.28#53(ns1.404-on-the-dancefloor.projectmammoth.com) (TCP)
;; WHEN: Thu Jul 10 11:32:57 BST 2025
;; XFR size: 65823 records (messages 1013, bytes 18444094)
Note that the chunks appear to be sorted non-numerically, prohibiting a simple +short in this case. However, it’s easy enough to grab the base64 TXT entries, sort them based on their subdomain and decode the final MP3:
❯ dig -t AXFR 404-on-the-dancefloor.projectmammoth.com @ns1.404-on-the-dancefloor.projectmammoth.com | \
sed -rn 's@^chunk([0-9]+).+ IN TXT "([^"]+)"$@\1 \2@p' | \
sort --numeric-sort | \
awk '{ print $2 }' | \
base64 --decode > "Project Mammoth - 404 on the Dancefloor - M1 (release).mp3"
Equally, as per the streaming “over DNS is an exercise for the listener” in the original challenge, that last > to a file can instead be piped into a player, e.g. | vlc - to stream directly!