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.
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/sftxgdb -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
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 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.
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:
Before a dx-pedition, the group running the dx-pedition applies to Northern California DX Foundation to receive a “SuperFox key”
During the dx-pedition, the fox transmitter configures the “SuperFox key” into their WSJT-X application
When transmitting in SuperFox mode, WSJT-X will encode a verification signature into each message
Hounds will receive this verification signature and ensure its valid
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.
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.
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.
Last Sunday I took part in the Longest Run Gippsland. The Longest Run is a series of unofficial parkruns (5km) at 7 different locations completed near to the shortest day of the year (usually Monarchs birthday long weekend).
Gippsland’s Longest Run 2024 started at 7am in Warragul then moved through Newborough, Traralgon, Churchill, Grand Ridge Rail Trail, Koonwarra and finally finishing on the coast at Inverloch at 4:30pm. This gives about 1 hour to complete each run and 30ish minutes to have a snack + travel to the next location.
While North Melbourne Longest run probably made more sense for me, I first heard about the Longest Run Gippsland and the concept of doing some of the parkruns that Alex had told me about sounded super appealing. The idea of getting up at 5am to be ready in Warragul did not seem like a fun idea and as Alex and Geordie would be joining in we decided to share a hotel room in Warragul for the night. Luke decided to also join us in the morning.
We would all run at our own paces. My plan was for 6min/km - much slower than many of my runs recently. I packed several pairs of running clothes however I didn’t factor in how cold it would be on the day. I really need to get a running jacket.
After an introduction to the Longest Run and a quick briefing on the course we were off. I had actually completed this course officially once before during Antennapalooza. This course does a lap of the main park before turning north past the ovals towards the end of the park and returning. The northern section is performed twice before finishing near the start line.
I completed this course a little bit faster than I wanted but still kept it pretty easy. Afterwards some bananas and corn chips were consumed. My diet that day consisted mostly of corn chips.
This was a lovely course. A simple double out and back along the rail trail. The approach to the rail trail followed a lovely flowing stream that just looked so gorgeous this time of year.
Once again ran a bit faster than I really wanted but still felt good afterwards. This is also the first time I met Liz. We happened to be going about the same pace and chatted throughout most of the run. While everyone during this event was so friendly and supportive Liz was next level supportive and got me through some of the later parkruns.
I’ve run sections of the Traralgon parkrun before so I knew what to expect. This is another double out and back which follows the creek, however you don’t get much of a view of the creek due to the path placement. It’s a reasonably flat course. I certainly ran a lot faster than I should have, not sure if this is because I needed to go to the bathroom or because I was running with Alex.
Chomped on some chocolate along with some sugar coated nuts in the short break.
We only just got to Churchill in time before the “official” start. It should be worth noting that as these aren’t official parkruns there are no timers, no finish funnel, no tokens. You record your own time. This means that you can start the courses early or late. Many of the people walking the courses started them early.
Churchill caught me a bit offguard. Up until this point I had been running with my very normal, not designed for running, cotton hoodie. The combination of slightly better weather and a bit more ascent on this course meant I had to remove it mid run and the some what sharp elevation changes meant that I ended up taking two short little walking segments on this one.
The course starts by heading south down the park, u-turning then heading back north towards the very top of the park. Two laps of this are completed with the exception of the very north section. When finishing I was bit confused to find that the finish line is a short distance away from the start on the grassy section, however I think under normal parkrun conditions this would be easy to identify.
We had lunch at this point, some wraps with tabouli, corn chips, salad, salad dressing and probably some other fillings I’ve forgotten to mention.
At this point I was feeling a little tired and there was a bit of pain in my knee. I think some of the aggravation in the knee had actually come from driving segments.
This is a fun course. Well I think it would be a fun course in the dry. It’s a single out and back course with a slight down hill grade for the entire way out. I decided before I even started this course that I should take it super easy and walk if needed to save energy and my limbs to complete all 7 courses. The approach I ended up with was run the down hill, then walk at the turn around point for about 500m then run the rest of the return. Towards the end I was feeling pretty good so picked up the pace a bit. Felt really good.
This also marks the point where you’ve completed a half marathon worth of running - and for me that also meant the most running I’ve done in a single day.
I really want to try this course again in the dry. I felt like I spent a lot of time focusing on not twisting my ankle with the slippery mud and clay rather than enjoying the track. The other reason is that this course often only gets around 20 people attending which means I’m in with a shot for getting in the top 10!
This is a gorgeous course. Probably my favorite of all 7. I’m not sure exactly how well I would go under normal parkrun conditions as the bridges were quite wobbly and that usually makes me feel a bit unwell, but the views were amazing.
Koonwarra parkrun is an out and back starting from the town and following the Great Southern Rail Trail. The start takes you through a tunnel under the highway where you become surrounded by trees, eventually opening up in plains / farm land which great visibility across the bridges.
At this point I had pretty much hit my limit. I ran ran, walked, ran, walked, ran. Walking roughly half of the 5km. I tried to keep the walking pace fast as possible. My knee was starting to hurt a lot more. It wasn’t bad but it also wasn’t good. I didn’t want to push it.
Looking back at the stats I find it hard to believe that Inverloch only had 17m of ascent. Every little up section felt like pain to me. I had a secret weapon though. A progress pride flag worn as a cape to celebrate pride month.
I was pretty tired at this point and didn’t really understand the course at all, luckily I had people to follow otherwise I would have been utterly confused when I arrived back at the start line having only done 2km. The course starts in the middle and runs east, turns around back west past the start line for another 500m. Two laps are done to make the 5km.
The first 2km I ran eager to finish the final parkrun without walking but I realised quickly it was not going to happen and switched back to walking. When I reached 1km to go I decided to give running another shot and was able to complete the final kilometer running. Just.
With that it was done. 7 parkruns in a single day. Even though it was only 14°C I threw myself under the outdoor beach shower for a few minutes before changing into some fresh cloths.
I’m super happy with the outcome. I wasn’t sure if I could do all 7 but considering I was still running by the end that was a win for me. My knee was a bit sore for a few days after but it’s now good. 4 days later I did a reasonable 5km run (5:16/km) followed by a 10km PB (5:24/km) on the Saturday - so I think its safe to say that most of my body has recovered pretty quickly from what has been the most running in a single day I’ve ever done.
I’m pretty sure I’ll find myself doing another Longest Run in the future, I’m just not sure which one yet.