WSJT-X SuperFox TOTP: Part 3

I feel a little bad delaying this blog post for so long. A few other things got into the pipeline. The tl;dr is WSJT-X 2.7.0-rc7 is pretty fine. It uses TOTP now and third parties can run their own verification.

In previous posts we looked into the now discontinued SuperFox verification scheme. Since that post rc7 has been released. Many various solutions were proposed with various trade offs. Remember that we are dealing with dozens of bits here, not bytes. The final solution was chosen for its simplicity.

TOTP

One of our first suggestions when digging into alternatives was using a time based one time password (TOTP), similar to the H40WA dxpedition. TOTP works by both parties having a shared secret key. Both parties combine the time and the secret key, then hash the result. This allows verification without sharing the secret key or being able to replay an old message.

At this point you’ve probably spotted a problem however, both parties need the key. The way this is worked around, both with the H40WA system and the WSJT-X system is that the receiving station doesn’t hold the key. Instead a server holds the key and the client can request the final value from the server.

% curl https://www.9dx.cc/check/K1JT.text
2024-11-22T08:49:00Z K1JT 229106
2024-11-22T08:49:30Z K1JT 305435
2024-11-22T08:50:00Z K1JT 806080
2024-11-22T08:50:30Z K1JT 869989
2024-11-22T08:51:00Z K1JT 699026
2024-11-22T08:51:30Z K1JT 250722
2024-11-22T08:52:00Z K1JT 485396
2024-11-22T08:52:30Z K1JT 429621
2024-11-22T08:53:00Z K1JT 450221
2024-11-22T08:53:30Z K1JT 763383
2024-11-22T08:54:00Z K1JT 182034

The important part that lets this work is that the server only return the TOTP values for already transmitted windows. A transmission using an old code won’t have the correct timestamp and the client won’t verify it.

Generally speaking WSJT-X stations have time set pretty well as transmission and reception only really works if clocks are within 1 second. If a client did have their time set in the past an attacker could use old codes, however in practice this is rare and the consequence is minimal.

WSJT-X actually uses the following request to perform checks

% curl https://www.9dx.cc/check/N9ADG/2024-09-05T19:07:00Z/866973.text
2024-09-05T19:07:00Z N9ADG 866973 VERIFIED

The actual code that validates the response is pretty basic and is just checking that it sees VERIFIED at the end

  if (verify_message.endsWith(" VERIFIED")) {
    return QString("%1   0  0.0 %2 ~  %3 verified").arg(rx_time.toString("hhmmss")).arg(hz).arg(callsign);
  } else
    if (verify_message.endsWith(" INVALID"))
    {
      return QString("%1   0  0.0 %2 ~  %3 invalid").arg(rx_time.toString("hhmmss")).arg(hz).arg(callsign);
    }
    else
      return QString{};

Side note here - The way WSJT-X handles data during a lot of the verification steps is passing through strings then checking if they contain certain substrings. I haven’t spotted a trivial way of doing due to the limited nature of an FT8 transmission (no lowercase, short freetext and no new lines) - but I wouldn’t be surprised if there is some fun to be had here. It might be possible to have the client process a crafted line with an old time.

WSJT-X have listened to feedback and allow running of SuperFox without verification keys. Not only that they do have configuration option to change the verification server. An example server might be something like:

from http.server import HTTPServer, BaseHTTPRequestHandler

class MyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(bytes("meow meow VERIFIED", "utf-8"))

httpd = HTTPServer(('localhost', 8999), MyHandler)
httpd.serve_forever()

Do note that you’ll need to be able to handle many amateur radio operators requesting tokens every 0 and 30 seconds.

It’s really good that the developers have listened to feedback. To recap:

  • Entirely open source
  • No secret sauce
  • Can be used without a third party
  • Can provide own verification service

I did quickly want to touch on some of the downsides of this approach. While the transmitting station doesn’t require an internet connection the receivers do. The server does allow checking of old logs so temporary internet outages can be patched over. I think this is a fair compromise and likely works for 99.9% of users. For the users it doesn’t work for - it just means waiting for a QSL card to confirm that it wasn’t a pirate.

Some other approaches used CSPRNG (Cryptographically secure pseudorandom number generator) stepped backwards. Occasionally the broadcasting station would transmit a signed message with the parameters for the CSPRNG which would allow verifying past messages. This was a pretty cool approach even if complicated.


WSJT-X SuperFox Verification is flawed: Part 2

isomer reached out on Discord that they smelt something funny about the SuperFox verification system. They were investigating the system in hopes of creating an open source implementation. The usual red flags presented themselves - closed binaries, very few bits, hardcoded magics. I originally assumed this system was just using TOTP like some of the other dxpeditions in the past, but in a more automated / online fashion, but it seemed not.

I was in quite a bit of pain and struggling to sleep due to running a half marathon with patellofemoral pain syndrome (do not recommend, the recovery sucks) so an opportunity struck.

isomer: WHY AREN’T YOU ASLEEP

One of the first things I like to do is poke around Git for clues. Commit messages, changed files, accidental additions. Commit 8b6744 stood out.

Replace executables for Windows and Linux by stripped versions.

This was a good start. While it wasn’t strictly required to make progress is certainly helped to quickly validate some findings later on. Opening foxchk in Ghidra revealed a lot of isomers initial findings. A hashing algorithm along with some bitwise shifts and xors. The initial focus was on foxchk as it was much smaller surface area to start with. Running foxchk directly it was able to produce output without any sort of external key/cert input so magic was happening entirely inside the foxchk app.

Rather than just trying to guess what the inputs and outputs were meant to be for foxchk I decided to focus on generating some end to end test data so that each part could be monitored with tools like strace to ensure our assumptions on how things worked was valid. This is when I shifted to looking at sftx.

The SuperFox mode requires keys which are provisioned by a third party. Without a key that matches your callsign the modulator will not modulate. Since I wanted to generate test data I needed to know what my SuperFox key would be. This is why I switched to looking at sftx - how was it validating the key?

From what we learnt in foxchk it was easy to pick out the hashing / signature functions also present in sftx. It was also pretty easy to follow the program flow with Ghidra, noting where arguments were passed and used. What stood out the most was a _memcmp between the provided key and one that’s generated. So we now know that sftx can validate the key - and it does so by generating one. At this point I knew that it didn’t matter if we never worked out how foxchk algo worked or how the signature was formed as it was likely we could just generate SuperFox keys using the application itself.

As a proof of concept I started poking in gdb and ended up with the below. This runs the sftx app and breaks when it performs the comparison. It then prints out the SuperFox key.

# Will only work on wsjtx_2.7.0-rc6_amd64.deb
# shasum ./usr/bin/sftx 
#c7f5171efca080ee98718ce977391f9d69fb8dae  ./usr/bin/sftx

gdb -batch-silent \
-ex "set logging file /dev/stdout" \
-ex "b _gfortran_getarg_i4" \
-ex "run" \
-ex "finish" \
-ex "delete" \
-ex "b *(\$pc+117)" \
-ex "continue" \
-ex "set logging enabled on" \
-ex "printf \"%.9s\n\", (char[9])*\$rdi" \
--args ./usr/bin/sftx "sfox_1.dat" N0CALL 123-456 # don't worry about the code here - its just a placeholder

I actually really like this GDB solution because it’s sort of a “no code” approach to obtaining the SuperFox key.

GDB also served helpful for validating our assumptions on how the hashing algorithm worked. We discovered that the hashing is done using Bob Jenkins lookup3 hashing function. This isn’t a secure hashing function. After which some scrambling is performed. A very clever puppy will likely have a write up on exactly how the scrambling works on their cohost.

It wasn’t too hard to figure out the process from reverse engineering with the help of Ghidra then write a replacement foxchk app. Made even easier by nhash.c being included in wsjt-x source code. The crew has since made rust, and javascript versions. The problem however was that a replacement open source foxchk essentially had to reveal how the signature/key was generated….

The options seemed a bit like:

  • Start releasing open source versions of SuperFox components so they can be packaged in Debian during the RC phase
    • Reveals how SuperFox works - would likely cause change prior to stable release making the work pointless
  • Wait until stable release, have open source versions ready to go
    • Still reveals how SuperFox works - makes the signature pointless but less likely to have last minute changes
  • Communicate that its flawed without exposing how prior for a period of time
    • It seemed like a lot of the SuperFox mode was focusing around Jaris Island dxpeditition
  • Release a keygen with funky midi beats
  • Pretend we didn’t see anything
    • This reverse engineering happening is a semi-public space so I don’t think this would fly long term

Decision was made to try to keep the details a secret until after Jarvis Island dxpedition. We knew however there would be a risk that others would likely start looking (if not already) and could easily replicate what we did. I have the assumption that the people with the skills to do this sort of reverse engineering likely wouldn’t do so in bad faith.

We also want to demonstrate that we had successfully cracked SuperFox, which is why we generated and displayed the SuperFox key for N0CALL.

It was also important for us to provide useful suggestions on better methods. Shout out to everyone involved here as this turned out to be a lot more work than I anticipated. There’s not many bits to play with in a transmission and coming up with a suitable solution is very tricky. Given the increase in processing speeds and memory size/speed I suspect we are right on the edge of viable security options using key signing for a low data mode like SuperFox. These suggestions have been sent to Joe directly.

The crack

A windows 98 looking window with a prompt for callsign and ouputting a SuperFox key

The way the SuperFox key was generated and validated reminded me of all the old software cracking. Firing up your favorite x86 debugger and stepping through code. Because of this I spent far too much time (mostly on/off in between things) and effort building my own “keygen” style webpage. Enjoy my SuperFox crack.

Not the first?

During writing of this post I checked in on wsjt-devel mailing list to find some discussion around HH2AA call using SuperFox. Checking dx spotting websites many spots list that SuperFox mode was used. It turns out that this station didn’t apply or was issued with a SuperFox key. To be clear I, nor any of group involved with this post or previous post were involved in generating a SuperFox key for this station - or providing details into how to do it. It’s not surprising that others would be looking into this.


WSJT-X SuperFox Verification is flawed

WSJT-X has published a release candidate which includes a new fox mode called SuperFox which promises a +10dB total system gain compared to the old fox mode. It also comes with a “SuperFox digital signature” in attempt to alleviate dx-pedition pirates. Verification of dx-peditions is an excellent idea, and I really want to see this problem be solved.

A screenshot of WSJT-X showing a VK callsign being used in SuperFox mode - freetext says “OC-001 Ultrarare DxPed”

There’s a few problems with the SuperFox signature system, however. Let’s talk about it.

How does SuperFox signatures work?

The basic flow is this:

  1. Before a dx-pedition, the group running the dx-pedition applies to Northern California DX Foundation to receive a “SuperFox key”
  2. During the dx-pedition, the fox transmitter configures the “SuperFox key” into their WSJT-X application
  3. When transmitting in SuperFox mode, WSJT-X will encode a verification signature into each message
  4. Hounds will receive this verification signature and ensure its valid
  5. If the message is valid, a “Verified” message is displayed in WSJT-X

Governance issues

The first problem to dive into is how the dx-peditions keys are distributed. We are reliant to a single org to distribute keys for all dx-peditions world wide. This can pose issues if the org doesn’t accept your dx-peditions credentials or is uncontactable for some reason.

Given the global reach of this hobby it’s frustrating to see this approach taken.

What could be done better

Public/private keys could be generated by any dx-peditions and used within WSJT-X. The public key could be uploaded to the dx-pedition website where users could check for validity. This allows anyone to use the new SuperFox mode. WSJT-X might want to have a certificate download system where wsjt-x could download certificates automatically that have been vetted.

GPL source code and amateur radio ethos

SuperFox decoders and the signature check are packaged as binary blobs and not covered by the GPL. This means that users installing through open source channels will not be able to use the SuperFox mode as the binary blobs need to be stripped from the packages.

Additionally, the use of binary blobs may not be in line with the project’s GPL licence, but I’ll leave that debate up to the legal people.

Regardless of the legal aspects, I’m a strong believer that amateur radio should focus on using open standards, protocols and modulation schemes where possible, and this very much goes against that. Keeping the modulation and verification scheme a secret prevents innovation and limits the lifetime of the service.

What could be done better

Develop the digital signature process in public with open source code. This allows feedback and improvements. It also ensures that the project can live on.

Security by obscurity is a bad idea

I’m sure you already know this one. Security by hiding the algorithm is a bad idea. You might have noticed that when I walked through how SuperFox is meant to work, that there was no step for where “WSJT-X downloads latest keys” or “user inserts public key”. That’s because the only security provided by this system is from the algorithm used to “sign” the messages.

The good news is that it’s no longer obscure…

mwheeler@foxbook superfox_keygen % gcc -I. main.c <censored>.c
mwheeler@foxbook superfox_keygen % ./a.out N0CALL
OP0C-COPY

I spent a little bit of time looking at how the binaries worked and made my own implementation of the key generator.

A public release of this code as GPL will be available after the Jarvis Island 2024 dx-pedition.

What could be done better

Including some people knowledgable in secure system design would be a great start. This system was very hand crafted without much experience and didn’t use any existing open standards for signing messages. Being amateur radio, we don’t need anything fancy. In fact, H40WA implemented a reasonably workable solution using TOTP tokens to verify that the station wasn’t a pirate. Alternatively, some basic public/private key cryptography could have helped here.

Oh no.

So what’s going on here. (note: I’m no cryptography expert)

The major problem with SuperFox is the system design for the digital “signature”. The system is symmetrical which means the receiver needs to know how the key and the process used to “sign” the message. This means the only thing protecting someone from generating a SuperFox key is client side security - a terrible place to be in.

Facepalming xssfox

In a future post after the Jarvis Island 2024 dx-pedition I’ll write up the steps I took in discovering how SuperFox and foxchk work. This work was done with the intention of creating open source versions of the closed binaries to allow SuperFox to work on Debian, however as the security issues were discovered it was important to document the issues publicly (while keeping the details private for now) to allow developers a chance to hopefully change their approach to something more open and sustainable.

With help/love from

  • the6p4c
  • isomer
  • kitty
  • my insomnia