Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Reverse Engineering the eQSO Protocol

#Reverse Engineering The eQSO Protocol#

Hello reddit. Today I reverse engineered the eQSO protocol. If you didn't know, eQSO is a small program that allows radio amateurs to talk to each other online. Sadly this program isn't as popular as it used to be (Well, neither is the radio).

You can find the programs Wikipedia page here and the programs download page here.

I didn't reverse engineer the whole protocol, just joining a channel and changing your callsign/comment. This was enough for my little project but you can reverse engineer sending/receiving audio if you want. It's all unencrypted.

eQSO Screenshot

The comment is shown next to your callsign(username) in the user list. This seemed easy to reverse engineer and make fun projects with. One example that I might do is making a forex ticker that shows EURUSD and other popular pairs.

So I opened Wireshark, started capturing, and joined a server. Then I changed my comment a couple times and disconnected from the server. I save the capture in Wireshark and start analyzing it.

To find the correct TCP stream, I press CTRL+F and search for my first comment. If we view this TCP stream in the Hex View, we will see that it starts with the bytes

0D
0A FA 00 00 00

and gets the response 0A FA 00 00 00. The responses to the commands we send aren't important since we are only interested in changing the comment and the callsign. But if you wanted to add more functionality you could implement all the commands and the responses.

To make it easier to use our code afterwards, I will make a python class.

import struct
import socket

class EqsoServerConnection:
    def __init__(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    def connect(self, address, port=10024): #The default port is 10024
        self.socket.connect((address, port))
        self.socket.send("\x0D")
        self.socket.send("\x0A\xFA\x00\x00\x00")

Now we can connect to the server. If we look at the end of the TCP stream, we can see that the client sent the byte 03 before disconnecting. Doing this is not necessary, but still a good practice to follow the original client.

Let's implement that as well.

def disconnect(self):
    self.socket.send("\x03")

Well, that was easy. Let's do something a bit harder and try to change our comment. Look at the data to find your first comment and the other ones. If we hadn't changed the comment, identifying the command to do it would be harder, but since we did it 3 times, we can easily see that the client sends

1A +
The length of the nick as a byte + The nick +
The length of the channel name as a byte + The channel name +
The length of the comment + The comment +
00

Let's make a function for that as well.

def set_channel_nick_comment(self, channel, nick, comment):
    self.socket.send("\x1A")
    self.socket.send(struct.pack("B{}s".format(len(nick)), len(nick), nick))
    self.socket.send(struct.pack("B{}s".format(len(channel)), len(channel), channel))
    self.socket.send(struct.pack("B{}s".format(len(comment)), len(comment), comment))
    self.socket.send("\x00")

We will send this packet after the connect command and anytime we want to change either the channel, the callsign(username) or the comment line. The original client doesn't allow changing the callsign after connecting but the protocol allows it.

Putting it all together:

import struct
import socket

class EqsoServerConnection:
    def __init__(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    def connect(self, address, port=10024): #The default port is 10024
        self.socket.connect((address, port))
        self.socket.send("\x0D")
        self.socket.send("\x0A\xFA\x00\x00\x00")

    def set_channel_nick_comment(self, channel, nick, comment):
        self.socket.send("\x1A")
        self.socket.send(struct.pack("B{}s".format(len(nick)), len(nick), nick))
        self.socket.send(struct.pack("B{}s".format(len(channel)), len(channel), channel))
        self.socket.send(struct.pack("B{}s".format(len(comment)), len(comment), comment))
        self.socket.send("\x00")

    def disconnect(self):
        self.socket.send("\x03")

Thanks for reading...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.