Monday, October 16, 2017

Finding and Decoding Malicious PowerShell Scripts


PowerShell. It's everywhere. I've started coming across more and more malicious PowerShell scripts.
Why do attackers love using PowerShell? Because it's native to many versions of Windows, provides full access to the WMI and .Net Framework and can execute malicious code in memory thereby evading AV. Oh yeah - did I mention a lack of logging too?

During the course of my analysis on these types of cases, I have found several indications that PowerShell has been utilized by an attacker. These include installed services, registry entries and PowerShell scripts on disk. If logging is enabled, that can provide some nice artifacts as well. The perspective of my post is going to be from that of an analyst that may not be too familiar with PowerShell. I am going to discuss how I locate malicious PowerShell artifacts during my analysis, as well as some methods I use to decode obfuscated PowerShell scripts. This will be Part 1 in a 3 part series written over the next few weeks.

Part 1: PowerShell Scripts Installed as Services
First up to bat is my favorite - PowerShell scripts that I find as installed services in the System event log. To find these, one of the first things I do is look for Event ID 7045. This event occurs when a service is installed on a system.  An example of a PowerShell script installed as a service is shown below:



Of note are the following red flags:

1) Random Service Name
2) The Service File Name has "%COMSPEC%", which is the environment variable for cmd.exe
3) A reference to the powershell executable
4) Base 64 encoded data

So how might an entry like this make its way into an event log? While there are various ways to do this, one method would be to use the built in Windows Service Control Manger to create a service:

sc.exe create MyService binPath=%COMSPEC% powershell.exe -nop -w hidden -encodedcommand <insertbase64>

sc start MyService

The above commands create a service named "MyService" and uses the binPath=  option to launch cmd.exe which in turns executes the PowerShell code.  

An interesting thing to note - there may be some failed errors logged after the service is created in this manner. The errors do not mean that it was unsuccessful. Windows was just expecting a "real" service binary to be installed and "times out" waiting for the "service" to report back. How do I know this? In my testing I was able to set up a successful reverse shell using the above methodology, which generated a failed service error on the Windows machine. On the left is a Metaspolit session I started on an attack virtual machine. On the right is a Windows 7 host virtual machine. Although the Windows 7 machine states "The service did not respond to the start or control request in a timely fashion," a reverse shell was still opened in the Metatsploit session:



Below are the two corresponding event log entries, 7000 and 7009, made in the System event log. Although the 7009 message states "The FakeDriver service failed to start.." this does not mean that the command inside the binPath variable did not execute successfully. So beware, interpreting these as in indication that the PowerShell did not execute may be false:




The 7045 System event log PoweShell command is encoded in base64 and python can be used to decode it. Interesting note - this base64 code is in Unicode, so there will be extra parameter specified when decoding it. (For display reasons I have truncated the base64 text - you would need to include the full base64 text to decode it):

import base64
code="JABjACAAPQAgAEAAIgAKAFsARABsAGwASQBtAHAA...."
base64.b64decode(code).decode('UTF16')

Here is what the decoded PowerShell command looks like. A quick sweep of the code reveals some telling signs - references to creating a Net Socket with the TCP protocol and an IP address:


This is similar to the type of code that Meterpreter uses to set up a reverse shell. The above PowerShell code was pretty easy to decode, however, it's usually more involved.

Next up is another example - this time its just "regular" base64. Note again the %COMSPEC% variable and reference to powershell.exe:


Again, Python can be used to decode the base64 encoded PowerShell:
 


This time, the decoded output is less than helpful. If we go back and take a look at the System event log entry more closely, we can see that there are references to "Gzip" and "Decompress":


Ahh.. so thinking in reverse, this data may have been compressed with Gzip then encoded using base64. Using python, I am going to write out the decoded base64 into a file so I can try unzipping it:

import base64
code="H4sICCSPh1kCADEAT..."
decoded=base64.b64decode(code)
f=open("decoded.gzip",'wb')
f.write(decoded)
f.close

Using 7zip I am successfully able to unpack the gzip file! Since I did not get any errors, I may be on the right track:



Now if I open the unzipped file with a text editor, hopefully I will see some PowerShell code:



Ahh..what??? Ok - time to take a peek in a hex editor:


Not much help either. I am thinking this may be shellcode. As a next step, I am going to run it through PDF Stream Dumper's shellcode analysis tool, scdbg.exe:



Ta-Da! scdbg.exe was able to pull out some IOCs for me from the shellcode.

To summarize, here are the steps I took took to decode this PowerShell entry:
  • Decoded the base64 PowerShell string
  • Wrote out the decoded base64 to a zip file
  • Decompressed the Gzip file using 7zip
  • Ran the binary output through scdbg.exe
As demonstrated above, there can be several layers to get though before the golds strikes.

One final example:


This looks familiar. First step, decoding the Unicode base64 gives the following result - which contains more base64 code inside the base64 code! :


Obfuscated, then obfuscated again with compression. This is very typical to what I have seen in cases. This time because there is no reference to "gzip" in the compression text, I am just going to save the second round of base64 to a regular zip file and try to open again with 7zip:

decoded2="nVPvT9swEP2ev+IUR...."
f=open("decoded2.zip,"wb")
f.write(base64.b64decode(decoded2)
f.close()

When trying to open up the zipped file with 7Zip I get an error:


And the same with the built in Window's utility:


I also tried various python libraries to unzip the compressed file. After some research, I discovered that the compression used is related to some .Net libraries. Now, since I am a python gal, I wanted to figure out how to decompress this using Python so I could easily implement it into my scripting. Since Python is cross compatible with Linux, Windows and Mac, .Net is not native to its core. As such, I used Iron Python to do my bidding. (Now yes, you could absolutely use PowerShell to decode this, but what can I say - I wanted to do it Python)

According to the Iron Python website "IronPython is an open-source implementation of the Python programming language which is tightly integrated with the .NET Framework. IronPython can use the .NET Framework and Python libraries, and other .NET languages can use Python code just as easily." Neat. Installing it on Windows is a breeze - just an MSI. Once installed, you simple run the scripts calling ipy.exe (I'll show an example later).

Armed with this, I was able to write some python code (io_decompress.py) that decompressed the zip file using the python IO compression Library:

#import required .Net libraries

from System.IO import BinaryReader, StreamReader, MemoryStream
from System.IO.Compression import CompressionMode, DeflateStream
from System import Array, Byte
from System.IO import FileStream, FileMode
from System.Text import Encoding
from System.IO import File

#functions to decompress the data
def decompress(data):
    io_zip = DeflateStream(MemoryStream(data), CompressionMode.Decompress)
    str = StreamReader(io_zip).ReadToEnd()
    io_zip.Close()
    return str

print "Decompressing stream..."
compressedBytes = File.ReadAllBytes("decoded2.zip")
decompressedString = decompress(compressedBytes)

f = open("decompressed.txt", "wb")
f.write(decompressedString)
f.close()

To run the script using IronPython was easy: ipy.exe io_decompress.py:


I was able to open the decompressed.txt file created by the script and was rewarded with the following plain text PowerShell script. Once again, note the IP address:


To summarize the steps taken for this event log entry:
  • Decoded Unicode base64
  • Decoded embedded base64 code
  • Decompressed resulting decoded base64 code
As we have seen from the three examples above, there are various techniques attackers may use to obfuscate their PowerShell entries. These may be used in various combinations, some of which I have demonstrated above. The steps taken vary for each case, and within each case itself. I usually see 2-3 variations in each case that are pushed out to hundreds of systems over the course of several months. Sometimes the steps might be:base64, base64,decompress, shellcode. It might also be: base64, decompress, base64, code, base64, shellcode. See how quickly this becomes like a Matryoshka doll? When I wrap up the series, I will talk about ways to automate the process. If you are using something like Harlan Carvy's timelines scripts to get text outputs, it becomes pretty easy.

So how to go about finding these and decoding them in your exams?

  • Look for event log ID 7045 with "%COMSPEC%, powershell.exe, -encodedcommand, -w hidden , "From Base64String" etc.
  • Look for "Gzipstream" or "[IO.Compression.CompressionMode]::Decompress" for hints on what type of compression was used
  • Try running the resulting binary files through sdbg.exe, shellcode2exe or other malware analysis tools

Part 2 will be about PowerShell in the registry, followed by Part 3 on PowerShell logging and pulling information from memory.


Friday, February 24, 2017

Onion Peeler: Batch Tor Lookup Program

Logs, Logs, Logs. I see, IPs. When reviewing log files for suspect activity it can be helpful to look up information related to IP addresses. There is a great utility for this by Nirsoft called IPNetinfo. You can import a whole list of IP addresses and it will give you "the owner of the IP address, the country/state name, IP addresses range, contact information (address, phone, fax, and email), and more."

When I am reviewing log files, an IP address associated with a foreign country may peak my interest. Another check I like to do is look for activity associated with Tor nodes. In a corporate environment, a user accessing a system from a Tor exit node may be a red flag.

When I am checking an IP address to see if it is associated with a Tor exit node I will use a website like ExoneraTor. It lets me put in an IP address and a date, and lets me know if the IP address is associate with a Tor relay. While this is a great tool, if I have a list of IP addresses to check, it's not very efficient. To that end, I wrote a little program to help automate the process of checking a list of IP addresses against Tor Relays and Bridges, Onion Peeler.

Onion Peeler is written in Python and uses OnionPy. OnionPy is a wrapper for the OnionOO Tor Api. Using OnionPy, Onion Peeler caches a local copy of the Tor exit nodes and performs a check for a list of supplied IP addresses. What's nice is that if you have a list of sensitive IPs, the information is not shared and is kept locally:


It will output a list of matches:




Since it's in Python, the program is cross-platform compatible. I've tested it on Windows, Linux and Mac. It just requires OnionPy, which can be installed using "pip install OnionPy". I also have a compiled Windows Executable if you don't have Python installed. It requires an Internet connection as the initial query grabs the latest Tor nodes from OnionOO. I am thinking about adding in a way to store an offline copy in the next version as well as add in additional details about the Tor nodes (first seen, last seen etc.)

It took about a minute to check 8,000 IP addresses. Of course, a bigger list will take longer, so be patient.

Code and program are available on my github.

Monday, February 20, 2017

When Windows Lies

Wait, What? Windows lies? I believe so...

I worked a case where I checked the Windows Install date and it was a couple days before we received the system. GREAT....did the user reformat their drive and do a fresh install before handing over the laptop? Did they reinstall the OS? This would not have been the first time a laptop or system was rebuilt after an incident (either on purpose or by accident).

Checking basic information like the Operating System and Installation date can help a examiner prioritize the systems they need to examine and check for evidence spoliation issues. If you have 20 systems to go through, and the Operating System has been installed AFTER the date of the incident you may want to focus on some other systems first. In civil or criminal cases, an installation date right before you receive the evidence may raise some red flags.

So now that we have established why the Operating Installation date can be important, there is a registry key you can retrieve it from, HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion. RegRipper has a great plugin for this, named OSVersion that pulls not only the OS version, but the install date from the registry key . Running this against my test system I got the following output:

----------------------------------------
winver v.20081210
(Software) Get Windows version

ProductName = Windows 10 Home
InstallDate = Fri Feb  3 15:58:47 2017
----------------------------------------

What??? Install date of February 3rd, 2017?? 2 weeks ago??? Since this is my system, I know I did not install Windows 10 February 3rd. I have had Windows 10 Home since the roll out in 2015!

Just to be a thorough, I verified the date was being parsed correctly and looked at the raw data in the registry:
 



Install date is  0x5894a8b7 which is Fri, 03 February 2017 15:58:47 UTC.

Ok, one last check... running the systeminfo command it clearly shows "Original Install Date" as  2/3/2017 8:58:47 AM. I am UTC -7, so this information matches my above results.



OK - Windows, I think you are lying to me. I'm hurt. But, to add insult to injury, with some more digging around I find out that YOU MESSED WITH MY LOGS during this supposed install.

I have a snapshot of my system previous to the supposed install date of February 3rd, 2017. Note the created dates on the Event Logs - 12/12/2015 and a size of 20MB:


 Now I look at the current created dates and files sizes of my event logs. Note that the created date is the same of this supposed install date, 2/3/2017. Not only that, but my log files are much smaller, some about 2MB:


When I opened my logs, as expected, there are no entries before 2/3/2017, and the first entry matches with this supposed install date:


I was curious what may have caused this. Since Windows updates have caused issues with artifact timestamps before, such as USB devices, I checked the Windows Update history. Sure enough, there was a Windows update, "Feature update to Windows 10, version 1607" which ran on 2/3/2017.This date matches the supposed install date:



Since my update history contained more than one update ran on 2/3/17, I wanted to check some other Windows 10 systems to see what I could find out. I knew approximately when both of these systems  had their operating systems installed, and both had incorrect installation dates listed, as well as the Feature Update v. 1607 that ran on the same day.


System 1 (Windows 10 Home)
Registry Install Date: 9/27/2016 11:22:39
Event Log created Dates: 9/27/2016 11:11
Feature update to Windows 10, version 1607:  9/27/2016



System 2 (Window 10 Pro)
Registry Install Date: 10/1/2016 3:47
Log created Dates: 10/1/2016 3:42
Feature update to Windows 10, version 1607: 10/1/2016



So, my working hypothesis is that the Feature update to Windows 10, version 1607 is updating the Windows Installation time and deleting the logs.

This may just be a matter of semantics.. maybe "Operating Install Date" really means - latest major version update??? This artifact may be open to misinterpretation if that is the case.

Why is this important?

Possible Incorrect conclusion of evidence spoliation
Imagine you are working a civil case where the opposing side is supposed to produce and turn over a laptop. If you see the installation date was recent, you might incorrectly conclude that they installed the OS right before handing over the system. Or, in Incident Response you may incorrectly assume that the operating system was just installed and there may not be too many goodies to find.

 
You loose event logs
Event logs can make up a critical component of an exam. In the case I was working, this update happened right before I received the laptop. The event logs only had about a day in them. Yes, there may be backups, or a SIEM collecting them, but it just makes the exam more involved. 

Other Artifacts????
These are just the two inconsistencies I have found so far... there are probably  more...

I would like to test this more formally by setting up a virtual machine and tracking the updates to see what happens, however, based upon the systems I have looked at, I think Windows is lying.

As always, correlating findings against multiple artifacts could help determine if this install date is accurate.

Just a note and something else to be aware of - in many corporate environments the Operating System install date may be incorrect due to clones/images being used to  push out machines. However, I don't consider this as Windows lying because the date would reflect the install date of the original before it was cloned.


Wednesday, October 5, 2016

Quicklook thumbnails.data parser


Earlier this year at the request of a reader I wrote a tool to parse the Quicklook thumbnails index.sqlite file. This sqlite database stores information related to thumbnails that have been generated on a system. This information includes filename, paths, timestamps and more (see my previous blogpost for more details).  The file is located under /private/var/folders/<random>/<random>/C/com.apple.QuickLook.thumbnailcache.

Someone else recently reached out to me and asked about the thumbnails.data file in the same folder, which holds the actual thumbnails. They were having issues carving the images out of this file.

Research

With a hex viewer, I opened a thumbnails.data file from my Mac and scrolled though the file. I didn't notice any typical image file headers as I scrolled. Below is a screen shot of what I was seeing:



Normally I would expect to see something like the following in hex view:


Here the file header shown in red is for a PNG file. I tried looking for the file headers for various other images as well, such as jpg, gifs and bmps but no luck.

I placed the com.apple.QuickLook.thumbnailcache folder on a wiped SD card and used a couple of carving programs to try and carve the images out of the thumbnails.data file. While the carvers were able to carve out the sqlite database, they did not find any images.

Interesting. I started to do some research, and I found a reference on this blog post that the images are stored as "raw bitmaps". "Raw bitmaps" are not the same as .bmp files. Raw bitmaps do not have a file header or footer and can not be decoded without external information. According to this website  the following are characteristics of a typical raw bitmap:

  • No header or footer
  • Uncompressed
  • Does not use a color palette
  • Cannot be decoded without external information, such as:
    • Color type and sample order (RGB, BGR, grayscale, etc.)
    • Image width in pixels
    • Row padding logic
    • Byte order and/or bit order
This explains why the file carvers I used were not able to carve out the images. File carvers need a file header in order to identify and carve out files. So if we don't have a file header, how do we carve out these images?  Luckily, the Quicklook index.sqlite table stores the "external" information needed to carve the images in the thunmbnails.data file.

This external information includes the bitmap location in the file, the length of the bitmap, width, and height.



Manually Carving

Below is an example of how this data looks from the database. For this example, the file file3251255366828.jpg actually has two thumbnails associated with it. One the is 64 X 64, and a larger one that is 164 X 164:


I am going to walk through how to manually carve out the 64 X 64 thumbnail  using the information from the Quicklook index.sqlite database. While I have written a parser to automate these steps (covered further below), I think it's nice to know how to manually do it so you can validate any tools you may use to do this, or if a tool doesn't work.


The first thing is to open up the thumbnails.data file with FTK Imager using the  File >  Add Evidence Item > Contents of a Folder. To get to the file offset, choose the file, right click in the hex area, then choose Go to offset...  The offset we want is the value in the thumbnails table bitmapdata_location field, 993284:


FTK will take us to the file offset. Once this has been done, we need to select the next 16384 bytes - the value from the thumbnails bitmapdata_length field. To do this, we can right click and choose "Set selection length":


And then fill in the value from the thumbnails bitmapdata_length field:



Once this has been done, we can save the selection out to a file named "image.data":


Now that we have saved the bitmap out to a file - what do with do with it? Remember, it doesn't have a file header so just renaming it to .jpg or .png will not work.  So how do we view it? Gimp -  a free photo editing program has the ability to open up a raw bitmap and gives you the option to supply the width, height and image type.

Use Gimp File > Open and select the Raw Image Data type. Opening up the image.data file presents the following dialog box:


Notice how the image looks all funky? That is because we have to specify the correct values to render the image. The image type is RGB Alpha (which I determined from monkeying around), and the width and height are 64 (which comes from the thumbnails table width and height) Once these are entered, the image displays correctly:


The Script
Who wants to do this manually for each image? Like usual, python to the rescue. For this particular script, I used a python library called Tkinter. This library let me build a GUI app, in python, that works on multiple platforms! How cool is that? The script also works from the command line as well.


To use the GUI, simply launch the python script with no commands:



Just to prove it works, here are screen shots of the same script working on Linux and Mac (tested on Ubuntu 14.4 and Mac OSX):






Using the Gui is pretty easy - select the folder containing the com.apple.QuickLook.thumbnailcache, and a folder that will hold the outputs created by the script. The script will generate a report of the files and create a subfolder containing the images. The Excel option will generate an Excel spreadsheet with the images embedded in it.

The command line syntax is as follows for tsv output:

python quicklook.py -d "C:\case\com.apple.Quicklook.thubmnailcache" -o "C:\case\quicklook_report"



The command line syntax is as follows for Excel output:

python quicklook.py -d "C:\case\com.apple.Quicklook.thubmnailcache" -o "C:\case\quicklook_report" -t excel



In order to use the script, the biplist and Pillow library needs to be installed. biplist is a python library for binary plist files, and Pillow is used to work with images. Both libraries are easy to install.

To install biplist use easy_install:

Linux/Mac: sudo easy_install biplist
Windows: C:\<PYTHONDIR>\scripts\easy_install.exe biplist

To install Pillow:

Linux/Mac: sudo pip install Pillow
Windows: C:\<PYTHONDIR>\scripts\pip.exe install Pillow

The default output is TSV, however, if you would like an excel report the xlswriter python library needs to be installed:

Linux/Mac: easy_install xlswriter
Windows: C:\<PYTHONDIR>\scripts\easy_install.exe xlswriter 

I have also included a compiled Windows executable if you don't want to mess with installing all the libraries.

Download the quicklook parser

Thursday, September 22, 2016

Mac Live Imaging: Functionality Versus Speed

My series on imaging a Mac would not be complete without covering how to do a live acquisition of a Mac. Now that FileVault2 appears to be the default during installs with Sierra, a live image may be very useful moving forward:


If a hard drive is encrypted, a live image will allow you to create a logical image of the partition in an unencrypted state. In my previous posts I covered how to image a Mac using single user mode and a Linux USB boot disk. I've put off doing this blog post because there is a very detailed and well written post by Matt at 505Forensics that covers this topic. In his blog post, Matt walks though step by step how to image a Mac using the FTK Imager command line tool for Mac OS X operating systems. As such, I wanted to cover how to do a live image using the dd command as another option.

Out in the field, I've found that it seems to take a longer time when using FTK Imager. I finally had a chance to do some testing and found that it took FTK Imager almost 2 hours to image a drive to a raw image (no compression). It took just 15 minutes using dd with an MD5. My test system was a MacBook Air, Early 2015, OS X El Capitan with a 75GB partition that was being imaged.

Using FTK command line has some distinct advantages over dd. There are options to compress the image, choose e01 format and supply case information.  However, if time and speed are an issue, dd may be a better option. For example, I've been onsite when 10 Macs needed to be imaged - dd was nice to use so we could finish up in time for dinner. If you can leave an image running overnight  - it's probably not as critical. See below for the test data:

FTK Imager: Total image time 1 hour, 49 min and 04 sec:




dd image with md5: 15 minutes



Please note - this testing is not by any means extensive (unlike the recent testing by Eric Zimmerman on some forensic software). I created several images using both methods and the image times listed above were about the same.

The first step is to run diskutil to see what the disk layout looks like and to determine what to image. I like to do this before I plug in my external USB. This makes it easier to see what drive needs to be imaged.

diskutil list


No FileVault2/No Encryption


My system has both OS X and Windows (Bootcamp) installed. As you can see /dev/disk0 is my physical drive. Partition 2 is the Machintosh HD and Partition 4 is the Windows aka Bootcamp partition. The logical, active device I want to image is /dev/disk1. As you can see in the screenshot above, it is listed as the logical, unencrypted volume and refers back to disk0s2. (If you do run across a system with Bootcamp you will probably want to grab that partition as well, but for the purpose of this blog post I am focusing on the Mac partition)

Below is a screen shot of what the same system looks like with FileVault2 turned on. Note that it says "Unlocked Encrypted". In this scenario, /dev/disk1 is logical volume I want to image.





Each /dev/disk has a corresponding /dev/rdisk:


rdisk is supposed to be faster than /dev/disk. As such, we are going to use /dev/rdisk1 instead of /dev/disk1 in the dd command.

Now would be a good time to plug in the external drive that will hold the image. On my system it auto mounted under /Volumes/<Device Name>

For dd, I am going to use the syntax suggested by the Forensic Wiki Page. The syntax looks something like this:

sudo dd if=/dev/rdisk1 bs=4k conv=sync,noerror of=/Volumes/MAC-Images/my_image.dd

 
Lets break down this command:

  • sudo: run as super user
  • if=/dev/rdisk1: this stands for input file. This will be the disk that requires imaging
  • bs=4k : this is the block size used when creating an image. The Forensic Wiki recommends 4k
  • conv=sync,noerror: if there is an error, null fill the rest of the block; do not error out

Better yet - let's add in an MD5 so we can have a hash of the image to make it more "forensicky". In order to do this:

dd if=/dev/rdisk1 bs=4k conv=sync,noerror | tee /Volumes/MAC-Images/my-image.dd | md5 > /Volumes/MAC-Images/my-image-md5.txt



According to the forensic wiki:
"The above alternate imaging command uses dd to read the hard-drive being imaged and outputs the data to tee. tee saves a copy of the data as your image file and also outputs a copy of the data to md5sum. md5sum calculates the hash which gets saved in mybgifile.md"
Try not to fat finger the password like I did though...

That's it! Happy imaging whichever tool you use.