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…..
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.
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.
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. 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.
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.
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.
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.
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!
Update It’s been been tested against a Codan Envoy!
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