Reverse Engineering VARA FM Part 1 – Connection Request
Legal Disclaimer and Reasoning
The VARA FM Licence states :
“You agree that you will not attempt to reverse compile, modify, translate, or disassemble the SOFTWARE in whole or in part.”
Thousands of amateur radio operators across the United States are using VARA FM, and VARA HF. This issue is that in the United States is that we also have an FCC regulation (FCC Part 97.309(a)(4))
An amateur station transmitting a RTTY or data emission using a digital code specified in this paragraph may use any technique whose technical characteristics have been documented publicly, such as CLOVER, G-TOR, or PacTOR, for the purpose of facilitating communications.
Being self policing means that we must also be able to decode messages. Without the ability to decode messages, how could we possibly enforce FCC Part 97.113(a)(4).
(4) Music using a phone emission except as specifically provided elsewhere in this section; communications intended to facilitate a criminal act; messages encoded for the purpose of obscuring their meaning, except as otherwise provided herein; obscene or indecent words or language; or false or deceptive messages, signals or identification.
How are we reliably able to state that we can decode messages from a protocol we don’t understand? VARA also supports encryption, and to my knowledge, even the VARA FM monitor will not show raw data frames as readable information.
So what is published as an official reference?
The official VARA software website, https://rosmodem.wordpress.com/ contains three links to VARAs implementation. These resources are hosted on a file upload website called mega.nz. We have this link, which is a zip file containing some modulation information, and speed levels. Its never really straightforward what parts of these documents may apply to VARA FM, and VARA HF or both. We also have a document here detailing the compression method, Huffman compression. Again, no real implementation information as it applies to VARA FM, VARA HF, or both. Just a vague description of the compression, along with a snippet of source code within the pdf document with no indicator as to the language it is in, or how it’s used. The third link has some information specific to how VARA FM is used, and its speed table. All in all, we find out VARA uses OFDM, some form of PSK, and ARQ(Automatic Repeat Request), along with a description of Huffman Encoding.
Alright, now take those documents and create a program that would enable you to listen and decode VARA FM transmissions. Well, that’s what I set out to do. The thing is, the protocol isn’t published, the modulation itself isn’t detailed online enough to recreate, and even we knew those things, we can’t gather how VARA assembles the bits in each frame in respect to the Huffman encoding, FEC (forward error correction), or the CRC (Cyclic Redundancy Check)
Until now…
The EU Software Directive (2009/24/EC), Article 6 permits decompilation for the purpose of achieving interoperability, provided you don’t have access to the information by other means. US DMCA § 1201(f) permits reverse engineering for the same reason. As it stands, I would love to use VARA, but arguably, due to the lack of information on the proprietary protocol, it doesn’t seem to be completely fine to use here in the United States unless someone can decode the frames based on public data. So lets build a program for the purpose of interoperability, with the intent to use for amateur radio.
A high level overview of the VARA FM Program.
VARA at its core is a digital modem with Orthogonal Frequency-Division Multiplexing (OFDM) with adaptive modulation, scaling from 4PSK up through 256QAM to achieve data rates from roughly 550 bps to over 25 kbps. VARA operates as a TCP server, listening on one port known as the “Control Port”, and another port known as the “Data Port” (8300, 8301 by default.) The modem handles all modulation, demodulation, error correction, and ARQ protocol internally.
The application seems to have been written in Visual Basic 6 by Jose Alberto Nieto Ros (EA5HVK).
The basic idea, is that an application (like Winlink Express) can simply connect to VARA port 8300, and setup some basic parameters (Listen callsign, connecting callsign, bandwidth mode etc. The application can start and stop connections by sending special commands to this port. Once connected, the applicaito can then send data to the data port on 8301, and also read data from it as well. The great part is that the application doesn’t really need to know much about how the modem itself works. It just sends the connection request, (Or listens for one), and reacts with data meant for the other side.
Starting the Connection Request
I will start by saying that all of the details may not be 100% accurate here. I have no source code after all. BUT I am able to follow the below process and create my own connection frames that VARA understands over the radio with any source/dest callsign, and I am able to decode connection requests as well. I can produce a bit for bit match after all steps of the frame building process, and generate the working audio as well.
So lets walk through how to build a connection request piece by piece. Here, we are assuming that we are the station connecting to a distant station. Lets say in this example I am WinLink express, opened as user N0CALL-1, connecting to a gateway KN4MKB-1. Lets see what that looks like on the TCP layer. If you open a packet capture, this is what you would see. First, I setup the VARA FM modem by sending the correct data to the command port:
APP → MYCALL N0CALL-1\r
VARA ← OK\r
VARA ← REGISTERED N0CALL-1\r
VARA ← ENCRYPTION DISABLED\r
APP → LISTEN ON\r
VARA ← OK\r
APP → COMPRESSION TEXT\r
VARA ← OK\rHere we have setup our callsign, VARA is telling us it is using it, and that encryption is disabled. We set listen mode on, to listen for connections, and then also enable the Huffman encoding. Then we make the connection request.
CONNECT N0CALL-1 KN4MKB-1\rBuilding the Connection Request Frame
Now, the first packet goes out, a OFDM modulated 39-byte frame consisting of our callsign, and the distant ends callsign. So what does this frame look like?
[0] 0x00 separator
[1-7] source call 7 chars, space-padded ASCII
[8] SSID single byte 0-15
[9-15] via digi 1 7 spaces if unused
[16] 0x00 separator
[17-23] via digi 2 7 spaces if unused
[24] 0x00 separator
[25-31] dest call 7 chars, space-padded ASCII
[32] SSID single byte 0-15
[33-36] control 0x00, 0x08, 0x07, 0x00
[37-38] CRC-16 high byte, low byte Now, the control bytes I’m still unaware of the purpose. They are consistent across all of my connection attempts, no matter what callsigns are used. I THINK they may be different on licensed VARA FM versions, but I’m unsure. We obviously know our callsign, and the destination callsign, so what about the other frame content?
Building the CRC
The CRC at byte [37-38] is CRC-16-CCITT polynomial 0x1021, init 0xFFFF, final XOR 0xFFFF, computed over bytes 0-36, stored big-endian. This seems to hold true using different versions of the connection request, AAAAAA→ZZZZZZ (0xEB0C), BBBBBB→DDDDDD (0x6837).
Creation a bit array from the 38 byte frame.
After the CRC is computed, we take the entire frame, and turn it into a flat array of individual bits. This is done “MSB(Most significant bit)-first style. (We are just reading the binary of the byte left to right) We read each byte from left to right in binary, bit 7 down to bit 0. For example, we have the source callsign “KN4MKB”, the letter “K” would be in position [1] of the above frame. K is 0x4B in hex. 0x4B in binary is: 0 1 0 0 1 0 1 1.
for &byte in frame.iter() {
for bit in (0..8).rev() { // 7, 6, 5, 4, 3, 2, 1, 0
bits.push((byte >> bit) & 1);
}
}Pack the Frame to 316 bytes, and XOR Scramble the Result
The result is 312 bits (39bytes x 8). After this is done, we append 4 zeros. Why? Well the next stage of this (XOR Scramble) takes 316 bits as input. The frame is XOR scrambled I assume to help introduce “randomness” to the frame data, or to stop long runs of the same bits. In order for the encoder to work in the next step, (and parity bits for decode to be most effective), it helps to eliminate long runs of the same bits with this “whitening” step. So, here is the actual XOR_SCRAMBLE used by VARA FM to “scramble” these 316 bits before the next step.
XOR_SCRAMBLE = [
0,0,1,1,1,0,0,1,1,1,0,0,1,0,0,1,0,1,1,0,1,1,1,1,0,1,1,1,0,0,1,1,
1,1,0,1,1,0,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,0,0,0,1,0,0,1,0,0,0,0,
0,1,0,1,1,1,0,1,0,1,1,1,1,1,1,0,1,0,0,0,1,0,1,0,1,1,0,1,0,1,1,1,
0,0,0,1,0,0,1,0,0,1,1,1,0,0,0,1,1,1,1,1,1,0,1,1,0,1,0,0,1,1,1,0,
1,0,1,1,0,0,1,1,1,0,0,1,0,1,1,0,0,0,1,1,1,1,1,1,0,0,0,1,0,0,1,0,
1,1,1,1,1,1,0,1,1,1,0,1,0,0,1,0,1,0,1,0,1,1,0,1,0,0,0,1,1,0,0,0,
0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,0,1,1,1,1,0,0,1,0,0,0,1,0,
1,1,0,1,1,0,1,0,0,1,1,0,0,1,1,1,0,1,0,0,0,0,1,1,0,1,1,1,1,0,0,0,
0,0,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1,1,1,1,0,1,1,0,0,0,1,1,1,0,
1,0,0,0,0,1,1,0,1,0,1,1,1,0,1,1,1,1,0,1,0,1,0,1,1,0,1,1,
]
def xor_scramble(bits):
"""XOR 316 bits with the static scramble sequence."""
return [bits[i] ^ XOR_SCRAMBLE[i] for i in range(316)] The same function undoes the scramble in reverse on the receive side.
Apply Turbo Encoding, and keep both copies of the Frame
After the XOR scramble, we take our 316 bits and shuffle them around using a lookup table. This process is known as a “Turbo Interleave Shuffle”. It rearranges the 316 scrambled bits into a different order using a lookup table. So lets say you obtain/dump this lookup table form the VARA memory. A function such as this can be used to shuffle the bits around to get a bit for bit match as to how VARA has formed the connection request packet to this point:
for k in 0..TOTAL_BITS {
let src = tables.turbo_interleaver[k];
interleaved[k] = scrambled[src];
} This lookup table can be captured from VARA FMs running memory. I have the table, and the table seems consistent across callsigns, and restarts. But I have yet to know how it is derived in the first place. The table seems to be shared across 17 speed levels interleaved as index = 17*k + level. Position 0 of the table pulls from the middle (118), position 1 pulls from further along (156), position 2 jumps back to 35. Adjacent output positions draw from distant input positions, which is exactly what makes the turbo code effective. Level 0’s interleave is every 17th entry starting at offset 0. “Level 0” is what we are referring to today, because that seems to be what the connection request frame uses. We will see why VARA FM shuffles these bits like this in just a moment. Just know one copy of the “non shuffled” is retained, and a copy of the shuffled one is as well.
Dual convolutional encoding → 644 bits (Forward Error Correction)
Now that we have the non turbo interleaved copy of the frame, and the turbo interlaved copy, we will run two encoders on both versions. Two identical 8-state convolutional encoders run in parallel:
Encoder 1 processes the scrambled data (316 bits)
Encoder 2 processes the interleaved data (316 bits)
Each encoder is an 8-state machine (3-bit shift register). For each input bit, it outputs one parity bit and transitions to a new state, governed by two lookup tables:
const OUTPUT_TABLE: [[u8; 2]; 8] = [
[0, 1], [1, 0], [1, 0], [0, 1],
[0, 1], [1, 0], [1, 0], [0, 1],
];
const STATE_TABLE: [[u8; 2]; 8] = [
[0, 1], [2, 3], [5, 4], [7, 6],
[1, 0], [3, 2], [4, 5], [6, 7],
]; The lookup tables correspond to parameter value 1 (a7=4, fec_type=1 from the speed level table). After processing 316 bits, each encoder is in some final state. he TAIL_TABLE (seen below) provides 3 bits per state that drive the encoder back to state 0. This is done by looking up the final state and appending those 3 predetermined bits. Critically, all 3 tail lookups use the same state (no state update between them). Rather than transmitting the final state separately, you force the encoder back to state 0 by feeding it specific bits that you know will navigate from whatever state it’s in back to zero. Then both sides know: it always ends at 0.
const TAIL_TABLE: [[u8; 3]; 8] = [
[0, 0, 0], // state 0 → already at 0
[0, 1, 1], // state 1 → 0
[1, 1, 0], // state 2 → 0
[1, 0, 1], // state 3 → 0
[1, 0, 0], // state 4 → 0
[1, 1, 1], // state 5 → 0
[0, 1, 0], // state 6 → 0
[0, 0, 1], // state 7 → 0
]; 8 rows (one per possible ending state), 3 columns (3 tail bits). For any state, these 3 bits will navigate the encoder to state 0. fter encoder 1 finishes processing the 316 scrambled bits, it’s in some state (say state 5). The code looks up TAIL_TABLE[5] = [1, 1, 1] and writes those 3 bits into positions 316, 317, 318 of the scrambled array.
for i in 0..3 {
output.push(scrambled[316 + i]); // encoder 1's tail input
output.push(s1_tail[i]); // encoder 1's tail parity
output.push(interleaved[316 + i]); // encoder 2's tail input
output.push(s2_tail[i]); // encoder 2's tail parity
} 3 iterations × 4 bits = 12 tail bits, appended after the 632 data+parity bits, totaling 644.
Post-encoder interleave
After the FEC is applied above, we actually go back and apply a post encoder interleave, much like we did in the turbo encoding step. Only now we are applying it to the 644 bytes instead of the 316. The post-encoder interleave exists to protect the encoded bits against the radio channel. At this point the FEC is done, and we have 644 bits that are about to be mapped onto OFDM carriers across time symbols. Without this shuffle, neighboring bits in the encoded stream would land on neighboring carriers or neighboring time symbols.
The post-encoder interleave scatters them so that:
– Bits that are adjacent in the encoded stream end up on different carriers (frequency diversity)
– Bits that are adjacent in the encoded stream end up in different time symbols (time diversity)
If it were HF, maybe that would matter. From what I understand this step has no real purpose for FM. Though I don’t really have much to go off of here huh?
Differential 4PSK modulation + dat_ref
This maps the 644 bits onto 14 OFDM carriers across 28 data symbols (I believe). It’s close enough to forge my own packet, and decode others anyways.
const QPSK_CONSTELLATION: [(f32, f32); 4] = [
(1.0, 0.0), // 00 → 0°
(0.0, 1.0), // 01 → 90°
(0.0, -1.0), // 10 → −90°
(-1.0, 0.0), // 11 → 180°
];The indices map 2-bit values to complex points at 90° intervals. The constellation mapping can be dumped form the VARA FM memory.
- Start with reference v143 = (1.0, 0.0) at symbol 0
- For each subsequent symbol, consume 2 bits → look up constellation point → multiply running state by that point
- The transmitted value is the product of all prior constellation rotations, not the constellation point itself
let mut v143: Complex = (1.0, 0.0);
for v138 in 2..30 {
if enc_path == 0 { // data position
let qpsk_val = (v142[bit_ptr] * 2 + v142[bit_ptr + 1]) as usize;
bit_ptr += 2;
v143 = cmul(v143, QPSK_CONSTELLATION[qpsk_val % 4]);
} else if enc_path == 1 { // pilot: renormalize magnitude
v143 = v143 / |v143|;
}
}enc_path determines which positions carry data (0) vs. pilot normalization (1). The normalization prevents magnitude drift from accumulating across the differential chain.
Each carrier/symbol position is multiplied by the conjugate of a reference phase from the .dat file. For Level 0 (connect frame), the dat_ref uses a fixed seed of 43 meaning it’s the same regardless of callsigns. This is essential because the receiver needs to decode the connection request to learn who’s calling. the .dat reference seems to come from some VB6 Random Number Generator that needed to be recreated from decompiling MSVBVM60.DLL. This is actually really strange. Typically, standard OFDM systems use well-documented sequences for PAPR reduction or pilot scrambling. Using the internal state machine of Visual Basic 6’s LCG as the scrambling sequence means nobody can generate compatible frames without reverse-engineering the exact VB6 RNG. For data frames it’s seeded from a CRC of the callsigns.
The algorithm:
- Rnd(-1) → always resets RNG state to 0x395886
- Randomize(43) → modifies state to 0xC04586
- For 128 carriers × 201 symbols: phase = Int(Rnd() * 33) % 30, write (cos, sin)(phase × 2π/30)
Audio Generation
For each of the 98 OFDM symbols (21 TX delay + 28 data, repeated ×2):
- Create 1024-element complex frequency array, all zeros
- Place 14 carrier values at bins [10, 14, 18, …, 62]
- Set conjugate symmetry at bins [1024−10, 1024−14, …] so IFFT output is real
- 1024-point IFFT → 1024 real time-domain samples
- Prepend cyclic prefix: last 128 samples copied to front → 1152 samples (= 24ms at 48kHz)
First we have a TX Delay. Random carrier phases (from captured table) at 0.5 amplitude to give the receiver AGC time to settle and the sync algorithm time to lock onto symbol boundaries via CP correlation. Afterwards comes the Data (28 symbols) from the Encoding above. That puts us at 49 symbols. 49 × 1152 = 56,448 samples ≈ 1.176 seconds frame.Then we apply IFFT scaling raw output divided by 4, and then Normalization(Scale to 64% peak amplitude.)
Total: 98 symbols × 1152 samples = 112,896 samples at 48 kHz = ~2.35 seconds.
Conclusion
This was step one for me in building a modem that could be interoperable with the VARA FM modem. Using the information on this page, I am able to build my own connection request frames, as well as decode them.
I have actually already created a working modem that can connect to VARA FM over the air, which I have all of the source code for myself. This blog post is about creating a discussion around the topic. As it stands now, I may have the only other piece of software in the world that can decode VARA data transmissions. I think that is problematic for for most regulators.
I’ve always wanted to communicate in privacy over amateur radio. I think you can look at the documentation produced around the VARA, modem and then read the article here and easily agree that this isn’t truly publicly documented in a way the regulation is intended for it to be. I’ve left out content from VARA FM memory dumps here. But the truth is, many of the tables within the VARA memory at runtime are required to encode and decode packets in a way that can be understood. That information is not in any online sources. This I can tell you.
At what point between an undocumented frame structure, then XORing the frame bits, 2 different levels of encoding using data tables not viewable from disk(must be dumped from VARA as it is running), and then RNG based constellation mapping that can only be done when you seed the callsigns into a reverse engineered VB6 RNG algorithm do we say this isn’t publicly documented? You are going to keep using something like this, and shake your fist when someone says the word encryption? There is a reason nobody out there up until now has created a decoder for VARA data frames, and its not due to lack of trying. The details of this encoding/decoding is not documented anywhere. To me, there’s virtually no difference between that, and encryption itself. Anyways, this is part one. Next we will talk about the frame that comes from the listening station in response to the connect frame.
At the point of writing, I have a fully functional modem that can complete a basic connection cycle with VARA FM. It can generate its own compatible frames, and decode the ones sent back from VARA FM as well. It lacks the automatic speed change based on SNR at this time. I made this out of necessity to stay within the FCC regulations here in the United States, and maintain interoperability from systems other than Windows. I do plan to release it open source at some point throughout this series. At this time, I don’t plan to disclose how the paid features of VARA FM work. I think sharing the protocol information with the public is good for the spirit of amateur radio however, so I will continue doing this as long as nobody else is publicly documenting it.