Improving Reliability of Sandbox Results
Improving Reliability of Sandbox Results
Improving Reliability of Sandbox Results
Improving Reliability of Sandbox Results
Cuckoo Sandbox is an popular system for automated malware analysis. Beginning in 2010 as a Google Summer of Code project, it has quickly grown in functionality due to its easily extended open-sourced Python architecture. For a few weeks, I've been evaluating Cuckoo Sandbox as an addition to the other tools we have for malware analysis at Accuvant. Over the course of that work, I've identified a number of problems and areas for improvement. Accuracy in malware analysis is very important to us at Accuvant, so instead of throwing a large number of samples at the sandbox, my strategy was to work with a small number of samples, heavily scrutinizing the results until I deemed them accurate when compared to information obtained through static analysis. What follows is a detailed summary of that work involving over 250 changes. If you're not interested in the details and just want to take advantage of the improved sandbox capabilities, I'm proud to mention that Accuvant is contributing all improvements back to the malware analysis community at https://github.com/brad-accuvant/.
Cuckoo Sandbox provides a DLL named "cuckoomon" to be injected into the malware being analyzed and all processes that malware creates or injects code into. Nearly all of the information provided to the analyst about the runtime behavior of the malware is derived from the results of cuckoomon, so it follows that improving its functionality and reliability has a huge impact on the overall analysis experience.
The first thing I noticed when using Cuckoo Sandbox was a lack of consistency in the summary results: long names were mixed with short names, disembodied file names and registry keys and values were scattered throughout the results. Not only does the human analyst rely on these results, but Cuckoo's signature modules depend on them as well – if they're not properly expanded out to the full names, we end up failing to detect certain malicious activity. An initial solution to this problem was to expand on what Cuckoo was already attempting to do to create full file and registry paths: tracking handles on the server end and concatenating base names with their associated directories where implied by specific APIs. The main source of error for these disembodied names was a lack of handling of the DirectoryRoot field of the OBJECT_ATTRIBUTES structure used in the Windows Native API. The DirectoryRoot field is a handle representing the base file path or key that is relative to the associated ObjectName string of OBJECT_ATTRIBUTES. Though this particular problem was easily solved on the server end by adding necessary information to the reporting, two other problems necessitated a change of strategy.
Consider the case where an already-existing process like explorer.exe is hijacked by a malicious binary. We can only begin logging once cuckoomon has injected itself into explorer.exe, and that only happens after the malware's injection attempt. Thus at the point logging begins, the process may have a number of open handles that the Cuckoo server knows nothing about, preventing Cuckoo from reconstructing full names for files and registry keys. Further, any short file names (e.g. C:\Progra~1) are impossible to recover on the server end. For these reasons, I decided to completely overhaul the handling of file and registry names in cuckoomon and the Cuckoo server.
For registry keys and values, we used the undocumented NtQueryKey API to convert any HKEY argument passed to a hooked function. NtQueryKey however returns results in a different string format that we need to normalize. Further, keys associated with HKEY_CURRENT_USER are represented by strings beginning with "\REGISTRY\USER\<SID>\". To normalize these, we simply pre-compute the SID string when loading cuckoomon and normalize each of these registry keys before reporting them to the server. For file paths, we used a combination of Cuckoo's existing code for converting handles to pathnames, along with the GetFullPathName and GetLongPathName APIs to achieve full normalization. Some additional work was needed to normalize \\SystemRoot and paths using DosDevice names. With this overhaul complete, a large amount of buggy and inconsistent code could be removed from the server end.
Several additional hooks were added to determine the algorithms employed in a malware's use of CryptoAPI quickly. Hooking was added to the creation of COM objects - the list of CLSIDs is easily extended to produce human-readable names for objects commonly used in malware. Hooking was also added to WSAConnect to fill out the coverage of interesting networking APIs. Hooks were added to NtDuplicateObject and NtClose so that signature modules could track handles more accurately when trying to match malicious behavior. Length information was provided for NtReadFile and NtWriteFile since cuckoomon truncates logged buffers to 256 bytes. The FileInformationClass argument of NtQueryInformationFile and NtSetInformationFile was also added to logging. Disposition information was added to the registry APIs that create keys to signal whether the key was actually created or whether an existing key was simply opened. A vectored exception handler was added to cuckoomon for easier debugging of the crashes it can cause in malware.
Compatibility with 32-bit binaries running on 64-bit guest VMs was improved by making cuckoomon aware of WoW64 filesystem redirection and rewriting paths appropriately. Without this rewriting, 64-bit binaries dropped by malware that disabled filesystem redirection were unable to be acquired by the Cuckoo analyzer script for processing and listing as dropped files.
Handling of file and directory renames was improved. If a new name is not specified as an argument to MoveFile-like APIs, it is treated as a deletion. I observed in the case of the Vawtrak malware that Cuckoo was unable to obtain dropped files because the malware created a new directory, dropped malware in it, and then renamed the containing directory. This important case is now handled by Cuckoo's analyzer script.
Finally, through the first set of changes made to cuckoomon, we rewrote the code so that it could be compiled fully with Microsoft compilers and built easily with Visual Studio. This involved, among other things, removing uses of nested functions, a C extension supported by GCC.
Several bugs were fixed in various hooks, including:
- A crash in HttpSendRequest where cuckoomon assumed the provided UNICODE_STRING had a Buffer field that was null-terminated.
- Cuckoomon's unhook watching thread was unaware of any library unloads - when a hooked library was unloaded, cuckoomon would continue to try to access its memory to look for modified hooks, causing an exception and taking the malware down with it.
- The NtSetInformationFile hook was using the Information field of IO_STATUS_BLOCK for the length of data used for the API rather than the provided Length argument - it was thus never logging any associated information.
- The DeleteFile hooks assumed the user-provided length of a filename to be less than MAX_PATH characters, causing an out-of-bounds string termination and stack corruption.
- Hooks were using a strange system of nested functions and macros for logging. A macro could be present at the top of a file to define what return values were considered successes or failures. The macro could be overridden by per-hook uses of another macro. This was often done inconsistently and incorrectly, leading successful conditions to be logged as failures and vice versa.
- Hooking was missing from GetSystemTimeAsFileTime, allowing malware to easily check for the presence of sleep skipping as implemented by cuckoomon
- Hooking was re-added for NtAllocateVirtualMemory - this was previously impossible without some clever trickery as cuckoomon's hooking code could itself trigger an early call to NtAllocateVirtualMemory in order to allocate space for a per-thread hook information structure, resulting in infinite stack recursion.
Signature Module Improvements
Cuckoo's signature modules provide the ability for analysts to write up detections for specific malware or generic techniques and provide that information immediately to viewers of the sandbox results. While many signature modules have been developed by the main Cuckoo Sandbox developers, others have been developed by members of the malware analysis community. Rather than digging through hundreds of API logs, the signature modules are responsible for the section of results on the summary page that looks like this:
Each signature module has the ability to drill down further into details and provide that information upon clicking on the specific signature results. Many improvements were made to existing signature modules and the overall API available to signature modules was improved. I discovered that a common source of errors arose from the development of Cuckoo and cuckoomon outpacing that of the signature modules. When changes were made to cuckoomon to add or remove new hooks, developers didn't take care to ensure signature modules depending on those hooks were properly updated. Thus it wasn't an uncommon occurrence to see several modules matching based on APIs that had not been hooked in years.
Accuvant has contributed the following signature modules thus far:
- antiav_srp - Detects modification of Software Restriction Policies as performed by malware like Vawtrak to cripple AV products.
- bootkit - Detects the device ioctls commonly used in the installation of bootkits.
- geodo_banking_trojan - Detects IP addresses, filenames, registry keys, and mutexes used by the Geodo/Emotet banking Trojan.
- mimics_file_time - Using our improved hooks and handle tracking, determines if malware sets the file times of a binary it creates to that of a system file.
- removes_zoneid_ads - Detects if the malware attempts to remove the Alternate Data Stream (ADS) on a file that is downloaded from the Internet.
- infostealer_email - Detects if malware is attempting to steal mail contents or credentials.
- infostealer_im - Detects if malware is attempting to steal credentials from several popular IM clients.
- rat_poisonivy_mutexes - Detects if the malware creates commonly used mutexes associated with the Poison Ivy RAT.
- static_authenticode - A weak check for the presence of an authenticode signature - valid or not.
- stealth_file - Detects the creation of hidden/system files or modification of existing files to be that are made hidden/system files.
- reads_self - Using several of our improvements to Cuckoo and cuckoomon, determines if the malware attempts to read out of its own binary image (e.g. to pull configuration data out of an overlay). Reports the offsets and lengths involved to focus follow-up static analysis.
- recon_programs - Detects if malware is enumerating installed applications.
- infostealer_keylog - Detects if malware is initiating a keylogger via the SetWindowsHookEx API.
- injection_rwx - Detects if a process creates a readable, writable, and executable allocation or changes an existing allocation to have those permissions.
- injection_needextension - Detects if malware is attempting to execute a copy of itself, but assumes the original binary has an exe extension (observed with Koobface.)
We made significant improvements to the FTP infostealer, CreateRemoteThread injection, RunPE injection, bind, autorun, service enumeration and unhook modules. All evented signatures (those that could be called on each API call unless properly filtered) have been updated to use proper filtering to reduce analysis time. Several modules that previously provided no additional information can now be clicked in the web interface to provide drill down details.
Improvements to Cuckoo that affected signature modules included:
- Addition of an argument to the signature module APIs that check summary results to return all results instead of just the first match.
- The ability to obtain the raw argument passed to an API. This was a necessary addition because Cuckoo was escaping API arguments before anything else could see them. Unfortunately, it was also escaping them improperly by not escaping the '\' character, meaning it was impossible to reverse accurately. With this improvement, we can convert buffers used in certain APIs into structure form for more detailed analysis.
- Separation of the files and registry keys summary lists into separate read and modify categories. This was needed to overcome false positives in the autorun signature module in particular, as simply checking for the existence of %WINDIR%\win.ini would trigger erroneous detection. We provide new APIs to access these new lists: check_read_key, check_write_key, check_read_file, and check_write_file.
- Cuckoo previously had not been logging registry values at all, only keys. Several modules needed to be updated in response to this change.
- Added overlay and export table information to static processing.
- Provide the full path of a monitored process to signature modules.
Notable bugs fixed in the signature modules included numerous errors in the state machines of the modules to detect process injection. For instance, if malware performed an OpenProcess against one process but didn't follow through with injection into it (e.g., if it was attempting injection against all enumerated processes), the state machine would be stuck in the base state and would never match on any of the real injections to follow. The generic service AntiVM detection was performing a case sensitive match against the "SYSTEM\ControlSet001\Services" string provided as an argument to the RegOpenKeyEx API. Due to a logic error, it also was never setting the handle value that it would later check against for enumeration. The autorun module had numerous false positives in addition to the ones discussed above - querying some TCP/IP parameters or modifying firewall settings would also trigger detection. The network bind module was reporting listening servers when the malware was simply binding its source to a specific address for outbound connections. The VirtualBox window AntiVM module would never have worked as it was matching on the non-existent "window" API category. The correct category name was "windows".
Changes to the main Cuckoo Sandbox code focused on its backend behavioral analysis and web interfaces. Cuckoo provides two separate interfaces, one in the form of a Django app and another using WSGI in combination with whatever webserver you happen to have installed. I prefer the look of the Django app, so most of my improvements have focused on that but many of the changes have been ported to the WSGI interface as well.
Web interface improvements included:
- A link to VirusTotal results for dropped files and IPs/domains.
- Additional color-coded API categories in the behavioral results.
- Display overlay information for PE files.
- The first page summary displays information on the split-out categories of read/written files and registry keys and values.
- The non-system-DLL caller of a given API is displayed.
- TCP/UDP connection logging was added.
- A new "loader" option was added to the DLL analysis module to specify the name of the process that will load the provided DLL (the German Bundestrojaner for instance checks to see if it is being loaded by a number of "interesting" processes: Skype, IE, etc.)
- Finally, a search box was added for the Behavior Analysis tab so users can search inclusive or exclusive of a list of API names. The API names should be comma-separated and include a "!" at the beginning of the list to denote exclusion.
Another major improvement that will be welcomed by less experienced users is pretty-printing of many API parameters using flags and certain return values. Here are some examples of the new display:
The non-system-DLL caller information deserves special discussion. Due to the mix of Win32 and Native API hooks, an analyst may wonder what code resulted in a particular API log. We've modified cuckoomon to keep track of the address ranges of loaded DLLs and walk stack frames (where available) on each API call to find the first return address that lies outside of those ranges. Generally, this will lead to the instruction after the call to a dynamically resolved API or an indirect call through the IAT. The benefit is that you can easily correlate the information of the behavioral analysis with your parallel static analysis and can also determine immediately whether multiple native APIs were the result of some higher-level imported API. To demonstrate this feature, let's take an example from the recently leaked FinFisher binaries. An early version of this added feature was only inspecting the EBP-based frames and so in cases where a hooked API was called directly, was reporting the parent of the caller of the API. Still, it resulted in the following interesting finding:
Here we can see two API calls resulting from a single call-site in the malware, an artifact of the above-mentioned early implementation. When we look at the disassembly though, it gets strange:
The API that resulted in the calls purports to be RegisterClassExW. Looking elsewhere in the binary, we begin to see the cause of the confusion - FinFisher is patching its IAT to redirect execution to another function:
What does FakeRegisterClassExW do? The decompiled form below mimics what we observed from the annotated API logs - the malware has patched its IAT to obscure duplication of its binary image.
Other than the bugs already mentioned that had partial involvement in the Cuckoo backend, it's notable to mention that files involved with the CopyFile and CopyFileEx APIs were never represented in the file summary listing.
I hope you've enjoyed this summary and are able to benefit from Accuvant's improvements to Cuckoo Sandbox. Feel free to submit suggestions or improvements at the URL in the introduction and look forward to even more improvements in the future!