CODAN Selcall Part 2 - Pagecall Magic

In part one I left you with a cliff hanger regarding page calls. And I particularly like this little note:

This shouldn’t be hard

Like it’s a radio from 1993. Easy right?

That was nearly 7 months ago. That being said I haven’t been working on that project full time and up until recently have only really just been chipping a way at it here and there. In the last few weeks I’ve been using the project as a bit of an escape. Since there’s so many facets to reverse engineering, different pathways and different things to discover the project has provided many simple tasks for me to work on that don’t require much capacity. I can just put some music on then label some functions, decompile code, run emulator tasks. Just poke random memory locations to see what they do.

On Sunday I was able to replicate the magic hashing function used in the page call protocol in Python, and hence why I can write Part 2!

What is page call and why is it different?

Page calls are text messages sent between radios. Essentially SMS for radios. Or I guess WhatsApp(?) messages for radios. The protocol is very similar to the selcall protocol, just with the message spliced in…. however…..

Head unit for Codan 9323 showing a pagecall “rawr nuzzles”

Codan decided that page calls should be locked down. My assumption here is that the 93xx series of radios were the first by Codan to be advanced enough to allow paging and to keep a market edge they wanted to make it harder for competitors to provide interoperability (pretty sad imho). So page calls include two extra bytes that are validated before receiving.

screenshot of code with two bytes being commented as “not sure what these are”
Freeselcall up until now has only been able to receive messages. Messages sent would not be received by radios using the magic bytes.

Surely someones worked it out?

I’m not sure if anyone actually has. I can’t imagine I’m the first but I can’t really find any resources online about these two bytes. I found one groups.io post about someone asking about it - but no responses (shame I can’t find that post anymore! I’d love to respond! I found the post!)

But doesn’t Barrett radio support sending pages? Well some more modern radios do, except they somewhat side stepped the problem by working with Codan.

Screenshot of the intellectual property exchange deed which lists out how codan and barrett are working together

What about Skippy? Well it claims it can send page calls but if you look at the details it can only send them to other Skippy users - likely because of these two bytes not being known. I’d love to know if Skippy to Freeselcall worked.

skippy compatability matrix where barret and ngt can’t receive pagecall
And to be honest, I’m not all that surprised. There’s not a lot of demand of sending messages like this. There’s limited users and the people who do need this functionality will just pay for the radio.

But these two bytes were like holding a red flag to bull for me. I needed to know.

The basics

Apart from doing prior research and asking ex-Codan employees I checked a few things. How do the bytes change when I change the selcall id by 1. What happens when I change it by 1000. What happens if I change the message. What happens if I flip the to and from address.

This data quickly builds up test cases. Lets you validate assumptions. I only ended up with a few of these tests though as it requires a lot of tedious work to do, along with waiting for transmission. But I had enough to rule out basic single XOR or just swapping parts of bytes. It was certainly cascading in some way since changing one bit change a lot. It also told me that since the bytes didn’t change with the message that the bytes weren’t used for confirmation or recovery. Purely as a security check.

Effort to not touch our radio

Even though the radio was designed in 1993, the Codan 9323 is still a 100W HF radio. And a good one. This means they still sell for $400+ on eBay. Buying a replacement one would be annoying and expensive.

I wanted to minimise fucking around and finding out on our radio. No pulling eeproms, no tapping into the memory bus.

Luckily there are dumps of the firmware online now. Bless these people. This allows us to look at just the software side of things. Likewise there’s a bunch of service manuals, PCB and circuit diagrams for many of the units allowing us to paint a picture of how the system works.

At this point I blindly fired up Ghidra and pretended I knew what I was doing. Then I spent about 3 hours reverse engineering a supposed crypto function that turned out to be 16bit long division.

Update

In between writing the first draft of this post and today, I managed to find a bug where a malformed pagecall packet caused our radio to hang and on reboot crash / act odd. I was luckily able to fix this. After trying so hard to ensure I didn’t break out radio that was an extremely close call.

Maybe we should emulate it?

At its core it’s an 8051. Surely there are a ton of of 8051 emulators. I could just dump the ROM into one and go.

Well, that’s partly true. There are a ton of 8051 emulators, however it’s not just the CPU that needs emulating, its all the supporting hardware around it. So out come the schematics, service manuals and we begin collecting datasheets for all the random ICs scattered on the board.

Then you realise that the ROM is 128k, and an 8051 can only support 64k. How does that work? The magic of bank switching. The code will use additional pins to switch in and out banks of memory. The code has to support this and know what its doing. Since this is done in software the emulator needs to support this as well, and everyone does this differently so there isn’t a standard way to emulate the bank switching.

So I ended up starting with py8051, which is a python wrapper for emu8051. I really enjoyed working in Python for this project. I didn’t need speed - just something I could quickly modify to get the job done.

The emulator was super handy in understanding what code was, what functions were doing and allowed me to continue reverse engineering in Ghidra. Eventually the emulator progressed so far in hardware emulation that I was able to get the boot screen to display on a virtual display, and with some memory poking I could even just some of the normal displays to show up.

virtual display of emulated codan 9323 - shows “no channels fitted, lsb, call, rx”

I never got the emulator(s) to a point where they would run normally though.

Emulators? With an s?

Yes. Many of you probably already gathered but our Codan 9323 radio has two parts, a remote head and a base. (Other units are sold with a front panel on the base, however this is effectively the head unit packed inside the base - other fun fact I found out is that you can have multiple head units connected!)

So this poses the question - does the magic get calculated on the base or the head unit.

  • Surely the base unit since the head would just be a dumb interface right?
  • Except you can set a privacy code on the head unit
  • Except maybe it just sends that back to the base unit
  • But maybe the base unit a dumb device
  • But you can also send a page call from the serial port?
  • Maybe the base unit just asks the head unit to encode the packet?

I continued chipping away at both. When I got stuck with one unit and I switched to the other. Was this the fastest way to work. No. Certainly not. But its what I could fit in to random spare gaps in time and days where I didn’t really want to go out to play.

Maybe I should just check?

At this point I had emulated both the bit banged I2C and the hardware based I2C… hold up… hardware based I2C?

Oh yeah this is not a standard 8051, It’s a Philips 80C552. This means there’s a bunch of extra functionality, which is more logic to handle in the emulator sigh.

Anyway, at this point I had emulated all the I2C. Not to the point that the two emulators could talk to each other, but enough for me to send packets by hand and see what happens. Reverse engineering the code gave me an idea of what the packets looked like, but not enough to get anything to work.

base unit of codan 9323 with logic analyser connected to some pins

Eventually I gave in and decided on a safe way to capture the I2C data between the head unit and the base unit. The front panel connector that wasn’t used on our 9323 was perfect for tapping into the I2C between head and base unit.

pulseview decoding the i2c packets

These packet captures done with PulseView were super useful. I could replay the messages in the emulator and see the results. It also revealed very quickly that the me magic bytes were not sent from the head unit to the base unit when sending a page.

My effort focused again on reverse engineering the base unit. But more importantly building out the emulator for the base to a point that I could the page call I2C messages.

With some bugs fixed in the emulator I finally had the emulated base station processing the page call messages. It even tried to transmit them - however I hadn’t emulated the tone generator….

It didn’t matter though because the important part was that it generated the bitstream for the transmission including the secret bytes.

hex editor view showing the pagecall encoded

At this point we have some options

  • Ship a 8051 emulator and a copy of the 9323 base station firmware in Freeselcall
  • Pre-calculate all possible value pairs (this is actually pretty possible, and not tooooo big of a lookup table)
  • Work out the algorithm

The algorithm

I took two approaches. Looking what happens to the emulator step by step, along with labelling and correcting Ghidra decompilation. Some parts I could only work out by watching the emulator. Eventually I came up with a solution that worked for 3 test cases. But failed on the next one - some tweaking and testing and all the test cases that I have recorded now work.

I then hastily set up freeselcall to use the new code and sent a test transmission from the IC7100 to the Codan 9323 and it worked! I then spent about 20 minutes reading the users manual to figure out how to actually open a page call (turns out you need the microphone, then press the recall button then call button). I found it hilarious that I knew how to generate the magic bytes but not know how to open the message on radio itself.

I generated a test file for Mark to transmit and it worked on their NGT radio!

codan ngt with a pagecall message: woof woof woof
Update It’s been been tested against a Codan Envoy!

envoy pagecall: vk5qi selcall test

Fin

Where does this leave us on this epic journey.

  • By the time you are reading this, freeselcall should be released on GitHub (and maybe pypy) with support for pagecalls - if not it will be very soon I promise
  • I specifically haven’t implemented privacy codes or 6 digit pagecalls. I have no use for these. It’s not hard work - but it is work. If someone makes a PR and tests for it I will support it.
  • I now know far too much 8051 assembly and Philips 8051 I2C information than I ever wanted to know
  • I have a copy of all the random IC datasheets now
  • The emulators won’t be released publicly nor the Ghidra annotated project
  • 6 months ago I tagged a series of functions as likely to be related to the pagecall magic - I was right, but I didn’t have a way of working out the inputs/outputs without the emulator

Side quests

Like all good projects there are side quests. I won’t document them here, but to give you a taste of where things lead to:

  • Figured out why the Codan 9323 gets stuck TXing
  • Made a script to colorise Ghidra based on logs
  • Found 3 different 8051 emulation quirks/bugs where my CPU didn’t match the emulators view of the world - these were annoying to troubleshoot. One of which was even present in the commerical Keil 8051 emulator
  • There is a 1-wire IC - DS2401 - that is packaged to look identical to a through hole transistor. It stores the serial number of the unit. I feel like the packaging is specifically to hide where the serial number is so people can’t reprogram / change them? I read ours by sniffing the 1 wire data pin on boot

Simpson Desert trip

Lizard laying on the track. It’s as red as the red sand its laying on

The plan

It’s simple, leave from Purni bore, and arrive at Birdsville… except its not that simple. The Simpson is a criss cross of different “lines”. You have the QAA, Rig Road, French line, K1 line, Madigan and others. Each serving a specific purpose in the past and today offering different challenges.

Your typical crossing picking a straight route across is probably around 3-4 days. This is 3-4 days without access to water, shops, or fuel. We however decided on route that touched on the French, Madigan, QAA and K1. The idea being that we wanted to see a bit of everything along with check out some of the interesting POIs like Poeppel’s corner (the corner of QLD, SA and NT).

map of the track showing a route from mt dare, along french line, north up the colson track, across east along the madigan then down the hayriver track and finally ending on the qaa line in birdsville

This resulted in a plan that was upwards of 12 days and 914km without services. We plan on days being low kilometers to ensure we have enough water and fuel in case something goes wrong or backtracking is required.

Adding to complications was the recent rainfall in the area. We had to keep a close eye on river levels / crossing locations and which properties we could enter and exit through.

In the end we completed the trip with 8 days in the desert. The second half of Hay River track was exceptionally good, and we breezed through the QAA line. A lot faster than the Madigan and French lines.

Permits

There’s a number of permits required when travelling the Simpson. For the Madigan line a Central Land Council entry permit is required as you are travelling through their land. Entry from SA and into Poepell’s corner requires South Australia Desert Parks pass. Camping in the QLD section requires a QLD parks camping permit.

The CLC permit can through very last minute. If we didn’t receive this we would have had to adjust our route. Luckily it was fine.

Australia is big….

Really big. Our plan was to start from Mt Dare to cross the desert, however just getting to Mt Dare from Melbourne is 23 hours of driving and 1,948km. To assist with this we decided to travel a week earlier than expected to Adelaide and start journey from there. This still left 1,237km and 15 hours driving which we split into two - stopping at William Creek over night.

swag setup at william creek in the stars next to the car and camp table

On the way back we spent some time in Longreach to check out the Qantas museum and give ourselves some recovery time. Then Cunnamulla, Griffith followed by arriving at our home in Melbourne. This totalled 2,627km’s from Birdsville.

selfie of myself and geordie standing on the wing of an aircraft at the qantas muesum

The crossing

Much like the CSR, the real challenge in these journey comes to preparation. If you nail your planning, packing and other prep work, the journey itself is uneventful and breeze. Take your time on all the dunes and don’t try to push yourself or the equipment.

We had a good idea from other peoples crossings what to expect and a good idea from our own previous journeys what works for us. We didn’t change much of our load out from the CSR which made prep super simple for us. Not much in the way of new equipment or requirements. The middle of the desert isn’t the place where you want to experiment.

view down one of the dunes towards another. There is low level spinfix but not trees. the track is orange and straight, with very little deviation

The dunes are so large and long and it’s impossible to express their size purely in a photo

I was amazed about how different the dunes were between the lines and how they changed as we progressed east. For the most part we just crawled up each dune. There were maybe 4-5 dunes (including “big red”) where we needed to use a little bit of momentum to get over - the rest we could do at very low speed. This protects our car, makes for a comfy ride and prevent further damage to the track.

eyre creek crossing showing just a small puddle with me tracks running around in the dry

Surprisingly Eyre Creek had completely dried by the time we arrived so we didn’t require any water crossings this trip!

the top of big red covered in just sand. the top is flat and a 4x4 is sitting on top with people standing around talking

‘Big Red’ is the final dune of a typical Simpson Desert crossing. 30ish meters tall this dune is one of the hardest to cross, but technically not very challenging when approached carefully. It’s probably best to try crossing this dune like you would usually would the first time to get and understanding of the challenge before making adjustments.

You’ll soon realise that crawling over isn’t viable for ‘Big Red’. Air down. Right down. Most vehicles will be fine down to like 8 psi, so don’t be afraid to drop those tyre pressures down to 12 or even 10. The top is very soft, you won’t rock around in wombat holes at the top so once you get passed the corrugated sections just put your foot down. The top is flat so you’ll be fine at the top.

I tried a few different configurations for the car (have some fun and do some laps). I found that traction control has zapping a bit of energy at the top - turning this entirely off helped a lot with some of the harder approaches. Diff locked, 4low, gogogo.

Weather

It turns out that while we expected fairly cool evenings and mornings the desert had other ideas. Many towns around the Simpson desert broke their hottest August temperature records. Our extra blankets weren’t used and rarely did I need to wear my hoodie. The middle of the days were extremely hot, as such we used that time for mostly travel. Even lunch breaks were brief. Unlike the CSR with wells and trees - there was very little shade.

Fuel

We used the final CSR fuel consumption readings to calculate our expected fuel usage (22.8l/100km). We rounded up to the nearest jerry can - this meant full tank + 4 jerry cans. We had some extra carrying capacity so took an additional 2 jerry cans just in case. 6 in total. Four of these were stored in the vehicle and 2 on the roof.

Priority was to get the roof jerry cans off, so as soon as fuel tank had room these went inside. You want the roof rack as light as possible as the forces applied when hitting wombat holes at the top of dunes are significant. Many people crossing the Simpson have stories of roof racks breaking and to prove the point some travellers we came across had just that happen. Most of their fuel was stored on the roof rack.

Using the final CSR fuel consumption readings were pretty much spot on for the Simpson. 204 litres over roughly 950km = 21.4l/100km.

But our story about fuel doesn’t end here. It seems that the fuel we picked up back in Adelaide had some sort of contamination. The differential pressure sensor triggered on the fuel filter just past Dalhousie on the French line. We packed two fuel filters as we are well aware of the risks of filling up from jerry cans. The thing is though that the fuel filter warning had come on before we had emptied any jerry cans.

The light is only a warning, you’d typically just get it sorted as part of the next service. We tend to ignore this warning as working on the fuel system in a dusty environment in the middle of desert can be risky. We continued on without noticing much and in Birdsville replaced the filter - without clearing out the housing.

fuel filter housing showing dirt and goo inside where the filter is usually housed
The problem got a bit more serious between Birdsville and Longreach. Accelerating to highway speed resulting in the car lurching. After some thinking and troubleshooting we determined it was likely the fuel filter. Initially we just thought it might be dirt/dust build up in the filter and replaced it in the next town, along with cleaning out the housing. The housing was filled with a whitish goo. This solved the initial problem and we started monitoring the fuel rail pressure to provide an early warning system.

It wasn’t long however until the fuel filter warning came on again and eventually the same problem - fuel rail pressure preventing the car from accelerating quickly. We replaced the filter again. As we replaced bad fuel with good fuel the problem started occurring much further apart. At this stage we think we are mostly clear of the issue, however expect to go through a few more filters as the final crap works its way through the system.

Other faults

We had traction control fault occur several times. The timing of this aligned with when the HF APRS radio transmitted. Tightening up the sand flag antenna and adjusting the feed line seemed to resolve this mostly.

upper control arm ball joint boot covered in grease

Upper control arm ball joint boots seem to be oozing grease. These might be over packed or some other issue - still needs to be investigated.

grease covered area around the CV joint

Drivers side CV boot seems to be leaking grease. This also needs to be investigated - an obvious hole or puncture couldn’t be spotted this time.

Food

Something new for us this trip is my decision to eat entirely Vegan. Geordie being extremely supportive in this decision mostly ate the same meals as myself as we don’t like to cook two different things. An amazing side effect of this is that vegan produce often can be stored warm and has long shelf lives. This left our fridge/freeze much more empty than typical allowing us to freeze some bread and have plenty of room for cold drinks. Next time I might consider packing some ice-creams.

wraps loaded with filling with the desert sunset as a background

Some of the meals we had were:

  • Tacos
  • Nachos
  • Spaghetti
  • Rice
  • Wraps
  • Pancakes
  • Bagels
  • Burgers

None of these meals are vegan specific but since most of the core ingredients come in tins (veggies, beans, jackfruit, lentils) packing became a breeze.

Car to car wifi

Another interesting addition was Rusty’s Starlink. While I don’t see myself signing up to Musk’s service anytime soon I was very impressed with how well it worked. The dish was mounted flat on their roof rack and it was able to be used while moving.

Geordie came up with the idea of setting up a point to point wifi link between the two cars. This worked well when we had near line of sight (dropping past a dune or two meant that the signal would be lost but would quickly recover when the other vehicle reached the top of the dune) and provided internet throughout our journey.

An interesting side effect is that we could use ping -A to create an audible alert noise whenever we dropped out of range. A useful indicator to know if we were getting too far ahead and need to slow down.

A future experiment might be car to car messaging/warning service.

Tent rope lights

Finally one other little addition from an unlikely source. I bought some “Fishing rod lights” for another project that didn’t work out. These glow a nice green and red light and last an entire night. I placed these on the ropes on our swag which prevented tripping on them. Something I’m extremely good at doing.

swag with the ropes illuminated by the led lights

it’s not that bright in person!

Photos

That’s about it for my write up. I’m almost certain I’ve forgotten to mention a bunch of things - this adventure contained so much. If I remember anything I’ll be sure to make a follow up post here.

In the meantime if you wanted to check out some of the pictures I took of the journey I’ve uploaded them to flickr.


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.