I definitely had a lot of fun RE'ing PunkBuster. It was enlightening seeing the types of challenges Game Developers have to cope with to make their games fair. As expected, PunkBuster is totally doing it wrong. Building a system that is modifiable by an attacker will never be safe. Luckily, it appears there is now an alternative. Since the BattleField 4 release, a new kid has arrived on the block and they are doing anti-cheating the proper way. And what is that way? By monitoring actions and results *server side* using statistical and behavioral analysis. If this topic interests you, I highly recommend checking out Fair Fight's FAQ. So yeah soon cheating will be harder, provided GameBlock's implementation is sound and easy'ish to implement in games. So where does that leave me? Wanting to build my own game obviously.
Online game development is not easy, it requires a developer to be knowledgeable in a lot of very unique topics.(Of course in 99% of the cases, these topics would be covered by multiple people!)
Art & Design
Physics
Responsive interfaces
Combat & AI
Network Programming
Scaleability
Data structure and management
Security
I think what interests me the most of all of those above topics is Network Programming. Building a safe, fast, reliable and scaleable network protocol that can handle thousands of concurrent users is an extremely fascinating topic. So I think I will start with that and hope everything else just comes together (wishful thinking).
Here's my current thought process.
Study current MMO / FPS game's network implementations and code if available.
See if it is possible to exploit WebRTC's RTCDataChannel to get browsers to send data not peer to peer but get access to the UDP transport to implement a custom game protocol.
Bet you all thought I was dead right? Well, not really, I had to privatize my research until I felt I had made enough progress to publish. This last week at the 44con security conference in London I presented my research. All of the work I did had been documented over at github in a private wiki until I could release, which I was finally able to do. So head on over and read up on how it all works :).
Enjoy!
So I've been slowly working on hacking my pnkbstrk.sys tracer script. It's going pretty well, much better than I thought it would have. In this post I'm going to explain how I built my script along with some of the 'interesting' things I've seen the driver do.
Basically I'm tracing the functions I think are important. Out of the five or so that I created breakpoints for, only three really displayed anything of interest so far. One function that calls KeTickCount I could have *sworn* would be called for doing some anti-debugging checks in kernel mode never ended up being called. I guess it is something that is triggered either randomly, or by a PB admin? Anyways, the three which were interesting were; MD5Update, strlen and memcpy. To see them all check out the latest 'testing.py' in my auto_ghast github repo. (At least when github comes back up...)
In the mean time here is the pykd code next to the ida function and windbg result of MD5Update.
MD5Update bp + ida + windbg result
You should be able to guess how I got the various addresses by looking at the IDA function call and the pykd code. But just to make sure, I'll explain. First we need to get the RVA of where we will set the initial breakpoint. At first I buggered it up and set my bp at the function entry, which when looking up the address of values by using the offset + ebp, I got the totally wrong address. I needed to set the breakpoint after the function prolog. In this case ee00c373. You'll notice my RVA is 0x6373, this is due to subtracting from the base address, in IDA that would be ee006000 (so ee00c373-ee006000 = 0x6373). But the code will automatically determine the base address by looking up the PnkBstrK.sys driver information when it's loaded. This makes it so there is no need to re-calculate every time the driver is reloaded (which happens to be every time the game is started).
The first value that's retrieved is the length which we dereference ebp+0x10 to get the value. This ends up being 69 bytes. The second value is the buffer address. Which in IDA you can see as being called arg_buf. This is at ebp+0x0c, for this we don't have a value, but yet another address. So we run loadBytes at the address of arg_buf with our length value and print it out. As you can see in the windbg results this happens to be that mysterious MD5 sum I saw a long time ago. The rest is printing out the MD5Context structure which, if you look at the testing.py code you'll see how I extract the various members of the structure.
So yeah, that's MD5Update all nicely traced :). Next up was strlen. In this particular run, it basically strlen's the same weird md5 value we see in MD5Update.
strlen doin it's thang
Yeah, pretty obvious. Saving the best for last, we have memcpy. This was the most interesting as I saw it basically incrementing through 0x1000 bytes at a time, doing a memcpy *directly* from userland addresses into a kernel address (pretty sure that's a big no-no but whatever), where, I'm sure it's doing some analysis/checks. Check it out:
memcpy, now things are starting to get interesting...
So yeah I think tracing to see what it does with these blobs of memory after it memcpy's will reveal some very interesting things about PnkBstrK.sys. Until next time!
Wow crap, I've been slacking. Sorry about that. At the same time, I sort of haven't because I'm happy to release my really really alpha auto ghast tool. This is basically a framework that I will end up building out to trace punkbuster and any other driver I need to analyze. I plan on doing a more formal write up later but I figured some people who are semi-familiar with pykd will appreciate some of the stuff I'm working on :>. Basically the idea is to have a method to set breakpoints with callbacks where I can record various registers, values, structures or whatever else I need over the course of running through the driver. In particular for pnkbstrk.sys I want to record all IOCTLs along with various IRP information. You can sort of see what I'm doing in my 'testing.py' script which I walk through later in this post.
I've also included a built winxp sys driver which is actually just the generic WinDDK ioctl sample driver + exe. If you use the ioctlapp.exe it will install/load the sioctl.sys driver and call it with 4 different IOCTLs. My auto ghast tool was built by repeatedly running/testing with this.
Anyways, here's auto ghast in action recording a single breakpoint:
auto ghast automatically setting breakpoints/recording data
What's nice is all I need to do is set a breakpoint on driver load then run !py testing.py and it does the following:
It steps into the DriverEntry function
Grabs the DriverObject ptr and creates a custom 'driver' object that we can use in the program.
By calling driver.get_driver_by_address(esp) it will extract the pointer and give us access to the drivers properties
Prints out the base/end/entry addresses
Runs through the entire DriverEntry function
Extracts the IRP_MJ_DEVICE_CONTROL address (driver.get_device_control_address())
Creates a custom breakpoint object that I've designed. Set's up various information to record for the breakpoint when it's callback handler is called
Sets the breakpoint
Runs the program
What's nice about having a custom recorder is that we can extract/work with various registers, memory addresses or whatever we want for each time the breakpoint is hit. I'll fix up the documentation of it later but please consider this VERY ALPHA!
Finally, thanks to a blog post about pykdtrace which allowed me to figure out that I needed to return DEBUG_STATUS_GO from my debug handler (was banging my head!) to get the dang thing to continue to run.
So I'm pretty sick of doing stuff manually in WinDBG. Now that I have a decent understanding of how to use pykd (well, mostly anyways...) I'm going to start writing and releasing various scripts that help me automate some of my analysis. I figured I'd make a github git repo for all this code, which I've dubbed GHAST; Game Hacking Adventures Scripts & Tools.
One problem I've been having is that PnkBstrK.sys doesn't show up in the 'lm' output. Not exactly sure why this is but at first I suspected it was removing itself from the PsLoadedModuleList doubly linked list. This is a common rootkit behaviour and I pretty much consider PnkBstrK.sys to be a rootkit at this point. To confirm whether this was true, I wrote a pykd script to walk the PsLoadedModuleList and print out the name, entry point and base address of all modules. Turns out PnkBstrK.sys hasn't removed itself, but for some reason WinDBG isn't listing it.
PnkBstrK.sys in the PsLoadedModuleList, but not 'lm' output.
Now that I have at least the base address and driver entry point, I can start to automate setting breakpoints and dumping out argument values. My goal is to be able to trace and record all of my 'interesting' functions that I've RE'd from my static analysis. I'm halfway done but the above issue is affecting pykd as well so I needed an alternative way to find the base/entry addresses. The above code can be found here. Hopefully now I can get my other script to work. Anyways, keep your eye on my github repo as I'll update whenever I finish any scripts.
If you've been following my journey thus far, you may remember a while back when I first started I identified third party libraries in use in the target I was RE'ing due to the values of constants. Well today is my lucky day it appears. Well, technically yesterday, but whatever. While poking through some more functions in IDA I noticed a very long string of instructions that appeared to be doing some sort of hashing/crypto. I deduced this due to the fact that there were a lot of shr, shl, or, and, and not instructions in a pretty specific pattern.
Hmm, this looks crypto'y
I took a few of the above constants; 28955B88h 173848AAh 242070DBh 3E423112h and threw them in to Google to see what I could find.
We have a winner! :>
After seeing the reference to MD5 I quickly remembered my GUID related post where I found two values that appeared to be the stringified versions of two md5 hashes. I highly suspect this function is used to generate that GUID. I'm still doing static analysis at this point, my next post will probably clarify what is going on by setting bp's in the debugger while it runs. So we have md5, awesome. No one codes their own MD5, so I bet myself that I could find the source they used... For that I headed over to koders code search. I selected C from the language and converted the hex value (28955B88h) to it's decimal form which is 606105819d.
md5.c from RSA. Can't get any more obvious than that. By finding the direct source, I was able to re-label four functions that are the assembly versions of the C code. Here are snippits of the asm alongside the C source:
MD5Init
MD5Update
MD5Transform
MD5Final
So we now have four of the md5 functions accounted for and labeled. But continuing my search, I also noticed another function with some pretty unique looking values. Using the same technique above, I took the 9D2C5680h and 0EFC60000h values and koders searched them. To my surprise it turned out to be the Mersenne Twister algorithm, also known as rand() in some languages :>. So now I have two more functions re-labeled to sgenrand(seed) and genrand().
rand()? Why thank you, don't mind if I do!
Overall, a pretty successful find, took longer to write this post than it did to get everything discovered and re-labeled, but hey that's the price you pay for documentation!
So from my last post I had a fewpeople reach out to me about fixing up dumped modules. Unfortunately, I subscribe heavily to the NIH attitude and ended up writing my own quick python module using the pefile module (note you can pip install pefile as well). All my script really does is set the PointerToRawData to the VirutalAddress value and writes out the changes to a new file (prefixed by new_).
import os
import sys
import glob
import argparse
import pefile
def dump_directory(path):
for filename in glob.glob(path+os.sep+"*.sys"):
dump_file(filename)
def dump_file(filename):
print "Fixing up: %s"%filename
try:
pe = pefile.PE(filename)
for section in pe.sections:
print "Updating: %s PointerToRawData 0x%x to"\
" VirtualAddress: 0x%x"%(section.Name,
section.VirtualAddress,
section.PointerToRawData)
# Update the section.PointerToRawData to be equal to
# the VirtualAddress/
section.PointerToRawData = section.VirtualAddress
# write the changes
pe.write(filename='new_'+filename[filename.rindex(os.sep)+1:])
print "new_%s written to disk."%filename
except pefile.PEFormatError, msg:
print "Error %s file is not a PE file? msg: %s"%(filename, msg)
def main():
parser = argparse.ArgumentParser(
description='Fixes up the VirtualAddress of drivers dumped from memory.')
parser.add_argument('--directory',
'-d',
action='store',
help='Directory with *.sys driver files.')
parser.add_argument('--file',
'-f',
action='store',
help='Single file to fix up.')
args = parser.parse_args()
if args.directory is not None:
dump_directory(args.directory)
elif args.file is not None:
dump_file(args.file)
else:
parser.print_help()
if __name__ == '__main__':
main()
If you are curious about the recommendations I got. @skier_t recommended his tool rreat. The tool from @iMHLv2 was a pretty interesting looking framework/toolset for memory analysis of malware called volatility. I'll definitely play around with their tools more, but for now i'mma write my own junk :>.
So besides fixing up the image once dumped, I've also been working on looking at the various functions of the driver after it's loaded. I came across two very curious blocks of code. At first, IDA didn't flag them as being functions.
IDA listing just the code as is
But by selecting the start of the function and hitting P, IDA will define it for us.
woo, we have functions! :>
You'll notice in the above code the two comments I added. If you are not familiar with the SIDT and LIDT x86 operands, well they are for storing and loading the Interrupt Descriptor Table. I suggest reading materials (both from phrack) if you want to learn more about the IDT and how they are used for hooking. "Handling Interrupt Descriptor Table for fun and profit" article by kad for a deep technical dive into the IDT and the IDT hooking article by mxatone and ivanlef0u for a more 'windowsy' look.
Anyways, it appears that the above disassembly stores the IDT values in memory, does a modification (*notice the mov eax, dword_EE01033C...) then reloads the modified version back into the idt register. When doing run-time analysis I didn't see anything at that address except nulls, so i'm not really sure what the point of it is yet. Keep in mind i'm pretty new to this whole IDT business as well. I tried setting a breakpoint on the two functions which modify the IDT and I can't seem them being called at any point yet. I think I will need to do more work in this area to get a better understanding of it all.
One thing I did notice however is that I'd like an automated way of inspecting the various interrupt entries. If you love python and you use windbg, you should really take a look at pykd, it's pretty damn awesome. After a few minutes of poking through it's samples I found an old (non-working) script which read the IDT entries. I had to rewrite most of the sample script to run in the latest version, but it works now. It's pretty simple in that it just loops through the IDT entries, extracts the dispatch address, dispatch code and the symbol name and displays it. Here's the code:
from pykd import *
import sys
if __name__ == "__main__":
if isKernelDebugging():
dprintln( "check interrupt handlers...\n" )
idtr = reg( "idtr" )
nt = loadModule( "nt" )
ErrorCount = 0
dprintln("idtr is: %08x"%idtr)
for i in xrange(0, 255):
idtEntry = nt.typedVar("_KIDTENTRY", idtr+i*8)
if idtEntry.Selector == 8:
offset = ( idtEntry.ExtendedOffset * 0x10000 ) + idtEntry.Offset
InterruptHandler = offset
kinterrupt = nt.typedVar("_KINTERRUPT",InterruptHandler)
if InterruptHandler != 0x00:
try:
dprintln("IDT [%02x] InterruptHandler: 0x%08x "\
"DispatchAddress: 0x%08x "\
"KINTERRUPT.DispatchCode 0x%08x"\
" (symbol: %s)"%(i,InterruptHandler,
kinterrupt.DispatchAddress,
kinterrupt.DispatchCode,
findSymbol(InterruptHandler)))
except Exception, msg:
dprintln("IDT [%02x] empty"%i)
else:
dprintln( "we are not debugging the kernel..." )
And here's some output from it being run from WinDBG:
idt_dump.py pykd script, dumpin' some interrupt tables baby!
I think I'm going to become very well acquainted with pykd, because well, doing this kind of stuff manually kinda sucks.