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

CODAN Selcall Part 1

We drive long distances in remote places. Sometimes we want people to be able to check-in on us while we are out and about. While cell coverage is getting better we still often find ourselves in places with no coverage.

Our 4x4 with large HF antennas mounted driving through a dry lake

Often we leave our HF radio tuned to a known frequency (usually 7045 kHz LSB) while we are driving and have VSC or power level squelch enabled. However this has false triggers. When driving through towns there is usually a lot of RF noise and even when we aren’t there’s noise from our car, other HF radios, or even just ionsondes or CW operators.

Random noises while driving are very frustrating. In the commercial space radios are fitted with selective calling (Selcall) functionality. This allows one radio to call another radio provided you know the ID number of the other radio.

The great thing about standards is that there are so many to chose from - Selcall being no exception. While there are many different Selcall variations today we are going to talk about the Codan specific Selcall - specifically the not the CCIR / UN / WA2 / RDD / Customs variants. Nor aircraft SelCal. The proprietary Codan “standard” is what we want to look at. The reason behind this is that there are numerous Selcall users on 7045kHz already using Codan Selcall and while it’s proprietary we can still interoperate with those users.

Modulating

The actual Codan Selcall process is actually pretty well documented with VK5QI already creating an open modulator including some of the less documented features such as channel test modes.

I decided to use freedvtnc2 as a skeleton project as it already has an FSK modem (Codec2), audio and rigctl components. From there I implemented the Selcall protocol while testing and researching some of the extra modes from our Codan 9323. Modulating a Selcall signal was now trivial - but we already knew and had that. What we really wanted was an opensource decoder. Something that could detect Selcalls.

Demodulating

Demodulating is a little bit harder, but given that we know what the signal looks like and Codec2 provides a good FSK demodulator it was not outside my skill level.

Codan Selcalls start with a preamble, usually quite long to allow radios scanning channels enough time to detect the signal. To an extent we can ignore the preamble as the Codec2 modem will handle locking onto the signal for us.

After the preamble there is a phasing pattern. This is a header that is at the start of every Codan Selcall. It’s nearly as simple as looking for this pattern to determine when a Selcall has started. But you don’t want to do an exact match as in HF radio its likely that noise has caused some bits to flip. I settled for a 85% threshold, however I think it could be even lower in practice.

Once the phasing pattern is found it’s matter of decoding the bits as they come in. There’s a few fun things in here. Codan uses a 7 bit word and 3 bits of parity, so you have to handle the incoming data as a series of bits rather than your typical bytes. Parity is only at the word level. There is no checksum on the entire message. The data is effectively sent twice (offset) so if parity fails on one word you might be able to recover it from the other.

The parity is a count of the number of 0’s in the word.

For my demodulator it works like this:

  • Store a rolling buffer long enough to fit a Selcall
  • Look for phasing pattern
  • If a phasing pattern is found try to decode each field - check parity - use the redundant field if required and available

Fun fact - if you are really unlucky both copies of a field could return a successful parity check but have different data - as there is no overall checksum for the message you won’t know which one is correct. When receiving a Selcall we just check if either field is our Selcall ID.

So you decoded a Selcall - what now?

Decoding the Selcall is only part of the process. We need a way of alerting the user that there’s a call for them. It would be really really really nice if ICOM allowed user defined messages (more on this later) and activating a beeper - but in the meantime lets aim for something simple. Rigctl.

When we send a Selcall we actually need to control the rig - turn the radio to data mode, trigger ptt, return the radio to voice mode. Since we already have rigctl for that, how about we use it for alerting the use as well.

The simplest solution I thought of is using the squelch adjust. On our rig you can configure the USB audio output to remain active even when the radio is squelched on the head unit. So the flow looks like this now.

  1. Squelch the radio to a fairly high level - unlikely to get false triggers
  2. Software listens for the Selcall
  3. When it receives a Selcall for our configured ID number use rigctl to unsquelch the radio
  4. (additionally) if the Selcall requested the channel test mode and the option is enabled send back a test signal

Freeselcall

I built all of this over a couple of days and have called it freeselcall. It’s had some over the air tests - but I wouldn’t say it’s battle hardened.

I realised that most people, myself included, wouldn’t want to use a terminal to perform selcalls so I’ve also added a web interface. It uses websockets so it should be easy to integrate with other systems as well.

Freeselcall web interface
(It even has browser notifications for those so inclined)

More than just Selcall

Freeselcall allows for sending and receiving Selcalls with various priority types along with the option for channel tests. However Codan radios can do more. They can do paging (short messages).

Today freeselcall can receive pages. It also has code to send pages however Codan decided to add some security words to process. As it stands a Codan radio will not receive the page messages from freeselcall.

Terminal prompt showing Codan 9323 boot up logo and some debug output

I’ve spent a lot of time in Ghidra along with building a Codan 9323 emulator (it can display the LCD boot up message!) to help reverse engineer how these security bytes work. Hopefully in part 2 I can share how the paging messages work. Emulating a unique flavour of 8051 along with the hardware it connects to has been fun.

The future (and a terrible idea)

As I alluded to earlier - being able to display a message on our ICOM 7100 would be ideal. However there’s no rigctl or CI-V way of displaying a message on the 7100. Or so I thought. It was only when I was dialing in a repeater on the 7100 that I came across an idea that might work. I haven’t tested this, but here’s the plan.

  1. Dedicate a memory channel for freeselcall.
  2. When a call comes, if the radio is in memory mode, store what memory the radio is in.
  3. Use CI-V command to save the current radio state as a memory in the predefined freeselcall memory channel overwriting any existing configuration in that slot.
  4. Update the channel name with the message / selcall id / ect….
  5. Tell the radio to load that memory
  6. After some time has passed and the radio hasn’t PTT’d in awhile, either restore the radio to the memory it was set to or turn off memory mode

I think this will work? But I have no idea until I try

The other addition I want to add is either implementing more sensible Selcall protocols or building a more modern robust one. This can run in parallel for backwards compatibility.

I haven’t even looked into 6 digit pagecall. I haven’t seen it used on amateur bands so I’m not overly interested.

A final note….

Part 2 won’t be coming out until I’ve either been able to emulate the Codan firmware (just because thats cool in its own right) or I’ve been able to figure out the magic for the secret words. The emulator is at the point where I need to have the base and the head unit emulated at the same time and talk via the I2C bus. This shouldn’t be hard, however it’s also not something that is trivial.

Please do not send me yet another a copy of the Codan CCIR 493-4 PDF. This afaik doesn’t contain any information on the secret words and I have enough copies of it from people being helpful.