Reverse-Engineering an IP camera - Part 4

In the previous parts of this work I've gained access to the camera operating system and found the main program, "IPC", which controls everything related to video transmission. This part will document my attempts to reverse engineer this software.

CPU Information

The exact CPU used is an important information for correctly disassembling the code. However, this information is not easy to find. According to the information previously gathered in part 3 and 2, the software runs on a Realtek RTS3903 SoC, which contains a RX5281 processor. The Realtek website has some datasheets for similar SoC but not about this one in particular. It seems to be deprecated. All I could identify is that it is a 32 bit MIPS running at 500 Hz.

Since I couldn't find much information about this hardware and also could not find other cameras with the exact same hardware for my camera system project, I decided on doing this reverse-engineering for self-educational purposes only, and gave up changing the camera software, moving to a Raspberry Pi solution. So, future updates on this series of articles will probably take longer than I expected.

Reverse-Engineering the IPC program

In order to analyze IPC I'm using Ghidra, a great reverse-engineering tool released by NSA a few months ago. It is open-source and multi-platform. Despite I've used some disassemblers and debuggers like ICE and x64dbg in the past, I'm not a Ghidra expert. In fact this is my first adventure on it, so I'll probably miss a few things. If you have any tips, please contact me!

So, this is the Ghidra CodeBrowser window after loading and analyzing the IPC program. CodeBrowser is the main window in Ghidra, where we spend most of our time looking the disassembled code. Ghidra also includes a great decompiler that provides the C code equivalent to the disassembled code, and that helps a lot. I'll keep in mind that trusting 100% on this code is not an option, since I didn't know the exact processor to configure Ghidra. But it seems to be ok, for now.

On the right side of the window we can see the IPC main() function:

Reverse-engineering the camera-server protocol

One thing I'd like to do was to replace the Chinese servers the camera connects to by my own servers. So I tried to analyze the protocol the camera uses to exchange data with the servers

Each minute, more or less, the camera sends a ping to the server as a keep-alive, i.e, the camera keeps sending small packets of data to the server in order to make the server aware that the camera is still powered-on and running.

This is detectable by capturing traffic with WireShark but is also visible at terminal, since IPC prints the following information for every keep-alive packet it sends and receives:

And the data exchanged by camera and server during this keep-alive session can be seen on WireShark, as shown here:

So, lets look where in the code IPC is sending this string to the terminal. Ghidra has a great resource of listing all strings found inside the program:

It also allows us to pin-point the exact piece of code that uses that string:

So, this formatting string "keepalive_2_p2psrv, devType=%d, devstatus=%d waitcnt=%d p2psrv_ip=%s" is being passed to a function that will, probably, print it on the terminal (as we saw in the terminal screen shot at the beginning of this article). The keep-alive UDP datagram that IPC sends this data is probably being mounted around here. Let's dig deeper here.

A good way to find out where, exactly, on the code, the keep-alive UDP datagram is being sent is to look for the Linux standard socket functions. In this case it would be send() or sendto(), the standard functions to send UDP datagrams. Ghidra is also great on helping us to see the call diagram of a sequence of functions.

Here, by filtering the name "sendto" in Ghidra Symbol Tree, we can see that there's a function wrapper (which I renamed to ipc_sendto() ), that is called by several functions inside IPC program ("Incoming calls" at the bottom left). By expanding each of these calls in the incoming calls tree we can easily find where the heart-beat code calls sendto(), by matching one of the sendto() calls to the same function we found using the terminal string (above). Please note that during the code analysis process I renamed a lot of the functions. That's the usual reverse-engineering process: You start with just generic functions names and go adding comments and renaming the functions as you start to figure out what they actually do.

So, here is the piece of code where the datagram is assembled before being passed to sendto() function

And this is also the piece of code we shall analyze in order to reverse-engineer the datagram sent by the camera

But, if you look closely you'll see that the sendto() arguments doesnt match with the traffic data captured by WireShark. According to the captured data the heart-beat datagram contains 36 bytes of data (Not considering the UDP/IP headers). However, the code seems to be sending only 20 bytes (0x14). I'll need to take a deeper look on that. Thats a task for Part 5.