Today Im going to talk about how I reverse engineered a packed and obfuscated RainbowSix cheat. Lets start with what a packer is. "A packer is a type of software that is commonly used by malware authors and hackers to compress or encrypt an executable in order to obfuscate its code and data" - google.com. Static analysis is not a possibility with these applications since the only readable assembly is the packers stub.
Before formulating any plans of attack its important to get a general understanding of the files/folders that the application uses. Since this application is a DLL its safe to assume that another application is going to load/run our DLL. This is the first time I've seen a loader in the form of a DLL.
When we open run.bat we can see that rundll32.exe is going to run "main" inside of unknown_sample.dll
Usually when I look at what an application is doing network wise I use procmon.exe with the networking filter. This will not always work for all packers. some packers like Themidia if configured correctly can detect process monitoring applications and refuse to work. Luckily for us this packer does not detect procmon.
A quick look at procmon reveals that this applications is connecting to a server on port 443. As I'm sure most of you know, this is one of the ports designated for HTTPS. Now just to be clear, the protocol itself doesnt necessarily need to be HTTPS, but its pretty safe to assume. Our next step will be to open up Charles, or httpdebugger, depending on what you like, and taking a look at what is being sent. This was also around the time I got a reply from the person who emailed this sample in. I responded and asked them if I could possibly get some networking data from them (since they have an active subscription), they aggreed
As we can see, there was an HTTPS request sent to wr-cheats.com/loader/mainldr.php. Looking at the query string data we can see what appears to be two hashes and a few other query strings. A closer look at these hashes reveals that both of their lengths are 32 hex charators long or 16 bytes (the same size as MD5). Lets break down each query string and what it appears to be used for.
Lets look at a networking sample from the person who submitted the sample. As you can see the server is returning a hash. I assumed that the server and the client both compute a hash and if they are equal then you have a valid hardware ID/active sub.
After that it seams that the loader is downloaded from the server and then a hash of the version is also downloaded.
I think its important that we always look at the networking that is going on before we jump into handling the binary. Almost all of the samples I get use HTTP(s) which offers little to no security. People who make malware/game cheats dont exhibit very good networking skills so altering an application via the network is usually one of the easier things to do. Nevertheless lets continue on to dumping the cheat and looking at it in ida.
API Monitor/IDA Pro/Dump Analysis
Since this application is packed we arent going to see much by just opening it in IDA. Lets dump it while its running and have a look at the imports. Since we saw some hashes earlier I'm going to keep my eyes open for anything MD5 hash related.
After IDA finished I was able to see what the application was importing. The first handfull of imports is exactly what I was looking for.
Now that we have an idea of how these hashs are created lets look at how they are made in Api Monitor. As we can see below, we are infact dealing with MD5 as suspected. Lets try and find how our guid is created. I have blurred some of this image since i cant remember if I spoofed my hardware ID or not.
CryptHashData: The CryptHashData function adds data to a specified hash object. This function and CryptHashSessionKey can be called multiple times to compute the hash of long or discontinuous data streams.
As you can see "123-" is prepended to our hwid (probably harddrive id) and then "lol" is appended. It is then hashed and the result is our "guid". This is shown below.
CryptGetHashParam : The CryptGetHashParam function retrieves data that governs the operations of a hash object. The actual hash value can be retrieved by using this function.
After this another hash is computed that will be the expected result from the server if our hwid/sub is active. As you can see it takes a previous hash and appends "ichfickallenutten" or "Ich fick alle Nutten" which means "I fuck all hookers" in english. Interesting.
Plan Of Attack
Now that we understand how the application works lets think of a few ways we can attack this application. Since this application is a DLL we can simply load it into our EXE with LoadLibrary. Since we know how the hashes are created we can simply hook the hash functions to return what we want. Putting all of this together we can make an executable that will import advapi.dll, hook specific functions and then include unknown_sample.dll and run it. The loader will also be effected by the hook since it is loaded into the same context/virtual memory as our executable.
One such way of hooking would be to hook the EAT (Export Address Table). The EAT table contains RVA's (Relative Virtual Addresses) to the addresses of the actual functions.
An example of how my exploit could work:
To bad this is not possible due to the fact that the address of the function I define (which will be called instead of real CryptGetHashParam) is behind the base address of advapi.dll. In another words the address of my CryptGetHashParam is less then the base address of advapi.dll. The RVA of to my function will be bigger then 4 bytes! No good!
Instead of writing an EAT hook im going to write a "trampoline" in assembly to make the execution of anything calling CryptGetHashParam jump to my CryptGetHashParam.
Here is how the exploit is going to work:
GetProcAddress(LoadLibrary("advapi.dll"), "CryptGetHashParam"), this will get the address of the real CryptGetHashParam
mov rax, [absolute address], we are going to write this assembly to the address of CryptGetHashParam, then we are going to write
jmp rax. This will give us the effect of a trampoline so everytime CryptGetHashParam is called it just calls my function. On another note the only way to call the original CryptGetHashParam is to write to bytes back in and then call it. I didnt ever need to actually call the original function since I already knew what I wanted the function to return.
auto loader_main = ((dll_main)GetProcAddress(LoadLibrary("unknown_sample.dll"), "main"));. I loaded the DLL loader and executed its main just as rundll32.exe would have.
And boom! All we need to do now is emulate the webserver (install self signed certs, add a line to the hosts file, etc). As you can see it works!
As you can see the cheat now things that my code is the server and fully trusts it. On the other hand preventing attacks like these can be done very easily. All one has to do is to hash the public key of the server at compiletime and then compare hashes at runtime.