Reverse Engineering a TCP protocol

For fun and… nothing else

But… why?

[skip to Step 0 for the technical stuff]

It’s Christmas break and I’m bored, so like any normal human being I decided to make a bit of progress on that app idea I’ve had for a year. I, like many others, enjoy the occasional video game. PC is my platform of choice, mainly due to the extra horsepower, versatility and tweak-ability. As I recently got into overclocking, one thing that I would like to do is keep an eye on various parameters while gaming like temperature, voltages, frequencies, thermal throttling and so on. Kinda like how a race car has gauges for oil and water temperatures, boost pressures and whatnot.

After searching around for a bit (surely someone must have built this already, no?), I couldn’t actually find anything… satisfactory. So, like any self-respectable software engineer, I decided to build it myself. After all, how hard could it be?

Introducing HWiNFO64

I don’t know how to do any Windows programming, and I don’t really feel like learning. There must already be some software out there that will act as a server for this information, right? Turns out, not really, or at least I couldn’t find it. There might be some paid options but I was born in a poor country so I’ll go to unimaginable lengths to avoid them. How unimaginable? You’ll see.

HWiNFO is a software I’d been using for a while. I like it because (a) it’s free and (b) it shows more information that I know what to do with.

SO. MUCH. DATA.

Now, HWiNFO has a remote monitoring feature. You enable server mode on the PC you want to monitor, then use HWiNFO on a different computer to connect to it and display its information.

All I need to do now is figure out how the instances talk to each other, tap into it and write my app. It’s probably just sending JSONs over HTTP, right? Heh. The fun begins.

Step 0: Getting it to talk to itself

HWiNFO won’t let you start multiple instances on the same machine, and I don’t have another Windows machine in the house. So, naturally, this calls for a VM. Less than 10 minutes later (thanks, hyperoptic), I got all I need: VirtualBox and the latest Windows Insider (again, I don’t like paying for software) installation media. After getting the VM set up, HWiNFO installed and faffing about with IPs for a bit, I managed to get my VM HWiNFO displaying the host’s data.

(Yes, my PC is named Vulture, after a ship in Elite:Dangerous, no you’re a nerd shut up)

Now all we gotta do is figure out how it’s getting that information across.

Step 1: Figure out basic protocol info

First, we need to figure out two basic things:

What protocol it’s using. This is most likely going to be TCP, but hey we gotta be ready for anything. What port(s) it’s listening on.

On Linux/UNIX I’d conjure up a quick lsof incantation to figure this out. But this is Windows, so we have to use a… uh… GUI. *shudders*

Luckily, Resource Monitor is pretty nifty actually. Run it on the host, hit the Network tab and take a gander at the helpful section named Listening Ports.

I think they call this “Bob’s your uncle”

We have our answer: TCP on port 27007.

Step 2: Sniff some packets

It had been a while (10ish years?) since I’d used Wireshark, but it turned out to be much like riding a bicycle. It helped that the UI is pretty much unchanged since then, don’t fix it if it ain’t broke I suppose. I quickly figure out the two essential things I needed:

Which network interface I should be sniffing. There are so many of them so I just tried them all, finally had luck with Adapter for loopback traffic capture. I don’t really understand why this worked since I was connecting over my WiFi IP, but whatever, it’s working. VirtualBox networking is black magic voodoo. What capture filter to use. This one is going to be really hard, I’m not sure you’re ready for it. OK, here we go. Are you sitting down? Do you have water? Cool. Here it is: tcp port 27007 .

We have packets!

Step 3: Figure out basic message structure

Now that we can see what it’s sending back and forth, let’s have a look at the data and see if we can make sense of it. The first thing that we notice is that it’s not HTTP. Or JSON. Great. Just great.

This is the first message sent across is coming from the client. It’s 128 bytes long, but it looks like only the first 8 matter. They are probably two ints, or one double, not sure yet. Let’s keep looking.

The server now replies with this. Those first bytes look similar, almost as if it’s saying “I’m replying to your earlier request.” But there’s another int at bytes 12–16. Let’s see what it means:

>>> struct.unpack('=i', b'\x48\x00\x00\x00')

(72,)

Interestingly, the server follows up with another packet, that is 72 bytes long. It contains hostname and HWiNFO version as two 32-byte strings.

So far, I can speculate that the first message was either a handshake or request for information. As a reply the server sends the size of the response in a pre-determined 132-byte long message, followed by the response itself. Let’s keep looking and see what happens next.

The client now sends this. It looks very similar to the first message it sent, the only difference being that the 5th byte is now \x02 , instead of \x01 .

The response looks familiar as well. Except that int now translates to 73068. It’s a big one!

As expected, the server follows up with 73068 bytes worth of data, split across two packets, as it didn’t fit into one. It seems to contain all the sensor data that’s displayed, although it’s still a bit hard to tell at this point. We’ll delve into this in the next section. Wireshark is a bit annoying here because it shows the entire packet length in the table view. This is more than the data length due to TCP overhead and whatnot, which made me question whether my assumption that the first message from the server contains the length of the response a couple times:

OK, let’s recap what we know so far:

It looks like a basic request/response system. We know of two requests: CRWH\x01 for hostname and software version and CRWH\x02 for sensor data. Replies always start with a 132 byte message containing the length of the response which follows.

Step 4: decoding sensor data

This is, by far, the hardest and most tedious part. But it’s also cool cause my screen would look like something from The Matrix at times.

Follow the white rabbit.

I won’t go into all of the details as this post is already long af. It was mainly messing around with offsets and data types until I’d get things that looked like the data I wanted to see. I’ll just kinda skip to my conclusions and make notes of things I found especially interesting or tricky to figure out.

The first part of the data contains category/section names. These are things like “System” or “CPU #0” and don’t have any values. They’re sent as 264-byte structs containing:

Two ints. No idea what these are. Two 128-byte strings. Why the name is repeated, I have no idea.

The second part of the data contains actual values. These are 316 bytes long each and contain:

Three ints. The second one seems to be the index of the section that this value belongs to. No idea on the others, however it looks like the first one is fairly small (always less than 10). The first int for section names (above) is always very large (or potentially negative if this is signed?) — I’m going to use this to determine when I’ve reached the second part of the response. The label as a 128-byte string. The label again as a 128-byte string. Perhaps this is to support customizing label names? I’ll check it out. Maybe. For now I’m just ignoring the second value. The unit as a 16-byte string. This can be MB , % , V , MHz and so on. The interesting one is °C. Python would throw a decode error on these values, which led me to find out that these strings are encoded using latin_1 . Current, minimum, maximum and average values, encoded as doubles.

I should say at this point that there’s some stuff in the beginning of the message, before the section names even, that I have no idea what it means. It might contain useful things like number of sections and number of values, but I’ve been too lazy to figure it out. It could save me from the jankiness of looking at some int that I don’t know what it means in order to determine what kind of struct I’m looking at, but alas.

Tying it all together

…by writing the world’s jankiest Python script. It connects to the HWiNFO server, gets all the data and prints it out. I’m pretty sure this only works on my machine™, especially since my computer’s WLAN IP is hardcoded in there. But hey, it works, and that’s all that matters.

Now it’s time to finally write that app I was talking about in the beginning. Or not, since the interesting part is done and this bores me now. Maybe next Christmas…