One of the super neat things about CANVAS and MOSDEF is that it provides a vehicle to write code that executes in memory of an exploited host, meaning it doesn't have to touch disk if you don't want it to. This is a boon for covertness as it requires any defensive measures to do in memory forensics. So today we'll take a look at a quick post exploitation command that I wrote up for CANVAS based on a Windows kernel quirk discovered by Walied Assar.
Walied discovered a signedness error in NtSetInformationThread, I'll let his blog cover the specifics but what it means for us is that we can set a thread's I/O and memory priority to max. While this isn't terribly relevant in a security context (you could leverage a more efficient DoS on the box you have code exec on, but that's silly), writing up a quick module is demonstrative so let's go ahead and dive in!
When you start out on this path I would strongly encourage you to have working C/C++ code to base the module on because debugging in this scenario can be a frustrating process. So you will need:
1) Working C/C++ code
2) A Windows 7 VM with Immunity Debugger installed
3) A standard callback trojan deployed on the Win7 VM
When writing a new CANVAS module you'll need to create a new directory under CANVAS_ROOT/exploits with the module's name (note '-' is not allowed in module names so use '_' instead), and then within that directory you'll need a dialog.glade2 file as well as a .py with the module name. So my directory structure looks like:
CANVAS_ROOT/exploits/threadio
dialog.glade2
threadio.py
Normally we recommend that customers take an existing module and adapt it to their needs, I used windows_sniffer for this purpose but it required a lot of work since windows_sniffer is fairly complex and threadio is very simple. If you plan on writing your own commands using MOSDEF-C I'd recommend using threadio as your example, so just copy it into your exploit module's directory and give it the proper name and start modifying. Let's take a look at some souce:
#! /usr/bin/env python # Proprietary CANVAS source code - use only under the license agreement # specified in LICENSE.txt in your CANVAS distribution # Copyright Immunity, Inc, 2002-2013 # http://www.immunityinc.com/CANVAS/ for more information import sys if "." not in sys.path: sys.path.append(".") from localNode import localNode from timeoutsocket import Timeout from MOSDEF.mosdefutils import intel_order from ExploitTypes.localcommand import LocalCommand NAME = "threadio" DESCRIPTION = "Set thread I/O and memory priority to max" VERSION = "0.1" GTK2_DIALOG = "dialog.glade2" DOCUMENTATION = {} DOCUMENTATION["References"] = "http://waleedassar.blogspot.com/2013/02/kernel-bug-0-threadiopriority.html" DOCUMENTATION["Notes"] = """ Tested on Win7 x86 A module to demonstrate MOSDEF-C, if you're looking to pass simple values (int) back from the host this is a good demonstration It will attempt to set a thread's I/O and memory priority to the maximum assignable values """ PROPERTY = {} PROPERTY['SITE'] = "Local" PROPERTY['TYPE'] = "Commands" PROPERTY['ARCH'] = [ ["Windows"] ]
I generally call this the preamble to the module, where we take care of imports, the documentation dictionary and the properties dictionary. All of this should be easy to understand if you've done any Python so I'll just touch on a few points. 1) All the variables are required (NAME, DOCUMENTATION, etc), 2) it is worth your while to fill these out completely when you begin writing the module especially the references section. Finding that blog post you want to remember two months from now is not very fun.
class theexploit(LocalCommand): def __init__(self): LocalCommand.__init__(self) self.result = "" self.name = NAME def run(self): self.setInfo("%s (in progress)" % (NAME)) node = self.argsDict['passednodes'][0] type = node.nodetype.lower() nodename = node.getname() if isinstance(node, localNode): self.log('Node of type %s not supported.' % type) return 0 if type not in ['win32node']: self.log('Node of type %s not supported yet.' % type) return 0
"thexploit" class is the standard class from which all modules are run, by passing LocalCommand (in lieu of say tcpexploit) we tell CANVAS what type of module this is. Next we get into the run function which is another required function and where in this case all of our heavy lifting occurs.
With all command type modules it's always a good plan to put in some helpful error checking which you see with our first two if statements. node = self.argsDict['passednodes'][0] provides us a node object which we can compare against another object type, localNode. If you've ever used the CANVAS GUI localNode is the red circle that represents your CANVAS host, so here we check to ensure that isn't selected, because commands are meant to be run on compromised hosts rather than your CANVAS host. Next we get our node type with type = node.nodetype.lower() and check it against a list of node types this will work against. Since this is a Windows kernel issue it makes sense that we only allow the module to be run on Windows nodes.
code = """ #import "remote", "ntdll.dll|ZwSetInformationThread" as "ZwSetInformationThread" #import "remote", "ntdll.dll|ZwQueryInformationThread" as "ZwQueryInformationThread" #import "remote", "kernel32.dll|GetCurrentThread" as "GetCurrentThread" #import "remote", "kernel32.dll|GetCurrentThreadId" as "GetCurrentThreadId" #import "local", "sendint" as "sendint" void main() { int success; int threadId; int setResult; int queryResult; unsigned long p1; unsigned long p2; success = 42; p1 = 0xFF3FFF3C; p2 = 0; threadId = GetCurrentThreadId(); sendint(threadId); setResult = ZwSetInformationThread(GetCurrentThread(), 0x16, &p1, 4); sendint(setResult); queryResult = ZwQueryInformationThread(GetCurrentThread(),0x16, &p2,4,0); sendint(queryResult); sendint(success); } """ # Compile the code and ship it over vars = {} node.shell.clearfunctioncache() request = node.shell.compile(code, vars) node.shell.sendrequest(request)
This is the meat of our module. I'll leave the specifics of the C code to the blog post referenced in the first paragraph. But I do want to point out a few things. First, this example is tied to MOSDEF-C for win32, if you're interested in MOSDEF-C for win64 I'll refer you to the windows_sniffer module, the changes are important but not difficult. In MOSDEF-C you have to import all your functions, you do this with a line like: #import "remote", "ntdll.dll|ZwSetInformationThread" as "ZwSetInformationThread". So a few things to note here, MSDN is your friend for determining which DLLs should contain which functions but save yourself the aggravation and check that this is the case by using Immunity Debugger. Open a program that has your DLL loaded, alt+e to get the imports list, right click your DLL and choose View Names, find your function name. Additionally, it is wise (though not required) to import the function into MOSDEF-C with the same name as it exists in the Windows API.
Having a line like: #import "remote" "ntdll.dll|ZwSetInformationThread" as "ZwSetInformationThreat"; is very annoying to debug if you use ZwSetInformationThread() later. Which brings me to my next point: yacc will give you some help when compiling on the CANVAS side before shipping it over to the target host but if it passes compilation any host side errors you will have to use your cunning, savvy and a debugger to find.
Variables do take a bit to get used to. Declaring a variable via: int ret = 4; gave me headaches. So I declared all my variables at the top then assigned them values after they'd all been declared. It may not be your style but I stopped getting wonky yacc errors after I followed this method.
sendint is a mosdef built in that allows you to, as you expected, send an integer back to your CANVAS host. This is incredibly useful for localizing where your MOSDEF-C might be failing. I make use of it in multiple locations, you'll note that the success variable isn't strictly required as no additional instructions are executed after the ZwQueryInformationThread call. This is a remnant of development but having a final send after all substantive instructions have been executed allows you to know that all of your code ran.
# Handle the responses threadId = 0 success = 0 threadId = node.shell.readint(signed=True) # recv threadId setResult = node.shell.readint(signed=False) # recv ZwQueryInformationThread result queryResult = node.shell.readint(signed=False) # recv new thread priority success = node.shell.readint(signed=True) # recv success, not strictly needed node.shell.leave()
As you may expect the CANVAS has a corresponding readint() for receiving these values. I found it helpful to have my CANVAS python variable names and my MOSDEF-C variable names be consistent and to label my readint()'s with enough information that I could easily figure out which one wasn't firing. When you get into more complex code, like a readint() within a conditional statement, keeping things labeled will help immensely with debugging.
# Lets have some verbose error handling try: if threadId == 0: self.log("Unable to get current thread ID, this will likely fail") setResult = hex(setResult) if setResult != "0x0": self.log("Received an error when attempting to call ZwSetInformationThread") self.log("Error no: %s"%(setResult)) self.log("Check here for error details: http://msdn.microsoft.com/en-us/library/cc704588.aspx") raise ValueError if queryResult != 0: self.log("Error when attemping to call ZwQueryInformationThread, the module may have worked but unable to confirm") raise ValueError if success != 42: self.log("Encountered an error before the module exited") raise ValueError except ValueError: self.setInfo("%s - Done (failed)"%(NAME)) return 0 except Exception as e: self.log("Encountered an unhandled exception") self.setInfo("%s - Done (failed)"%(NAME)) print e.message return 0
This may not be the most elegant or Pythonic way to do error handling but I found it made sense to me. The more effort you put into having good error handling now means debugging this module in six months when Microsoft has adjusted something is much easier. A few functions here that are useful: self.log() will generate CANVAS log events, I recommend using this over print for debugging. self.setInfo() will set the module's status in the Current Status GUI tab and is helpful to set if others will be using your code.
self.log("Thead Id: %d"%threadId) self.log("ZwSetinformationThread: %s"%setResult) self.log("ZwQueryInformationThread: %s"%queryResult) self.log("Success: %d"%success) self.setInfo("%s - Done (success)"%(NAME)) return 1
Finally we dump some information to the user and tell CANVAS the module has completed by returning. As you can probably guess return 0 will tell CANVAS the module failed, return 1 the module succeeded.
I think the benefit to MOSDEF-C is that it quickly allows you to interface with the Windows API without touching the remote file system. There's no DLL to load, if you have a Windows CANVAS Node this code is inserted and run into the running process. A defender may be able to determine that a machine was compromised but determining what specifically was done to that machine if you make use of techniques like this can be substantially more difficult. After all, hooking the entire Windows API isn't practical.
MOSDEF-C is a bit of a labor of love. If you're interested in starting to use it I would seriously suggest starting to read through the ./MOSDEF/ directory in CANVAS and then proceeding to ./MOSDEF/MOSDEFlibc. It is a powerful tool but it's important to note some of the current implementation limitations and some of the language quirks before you start doing anything too complicated.
The link to the complete module source can be found at: http://partners.immunityinc.com/threadio.tar
No comments:
Post a Comment