Wednesday, May 16, 2018

ProcDOT GeoIP plugin

Today I would like to introduce to you my first event handler plugin. The plugin is designed to run after you click on the refresh button in ProcDOT. You will need an Internet connection on first run because GeoIP needs to download the MindMax databases to get the location information on the IP address. The GeoIP information is then added to the details view on a server node.

Details view without GeoIP plugin

Details view with GeoIP plugin

There is a pretty interesting side effect that I happened to come across. The plugin also creates variables that you can call with other plugins.

Variables without plugin

Variables with plugin

Because of this discovery, I currently developing a clone of Christian's Server List plugin that includes the GeoIP information. 

GeoIP binaries can be found here for easy install.











Thursday, April 5, 2018

Remotely grab Symantec logs with Log Parser

Are you adding Symantec Endpoint Protection logs to your investigations? If not, there could be some information you are missing. These logs are located at C:\ProgramData\Symantec\Symantec Endpoint Protection\CurrentVersion\Data\Logs. Some of the logs contained in the folder path include:

AVMan.log - AV Management plugin log (contains copies of all AV events)
GUProxy.log - GUP plugin log (if you have a GUP enabled)
LUMan.log - SEP Client LiveUpdate plugin log
processlog.log - Application and Device Control log
rawlog.log - Firewall Packet log
seclog.log - Security log (IPS events mainly)
syslog.log - System log
tralog.log - Firewall Traffic log

Using Microsoft's Log Parser and Log Parser Studio, I created a couple of queries to parse these logs. And the best part is, you can query the logs on a remote system. The only thing left for you to do is export the results into your timeline. The library file for Log Parser Studio can be found here.

Enjoy!

AVMan.log









































Daily AV Logs:







































syslog.log:

























Thursday, March 1, 2018

Symantec Endpoint Protection VBN files

My goal originally, was to improve the way DeXRAYextracted files from Symantec Endpoint Protection (SEP) vbn quarantine files. I decided to dig a little deeper into the vbn format because it is not documented well. What I ended up finding out is that there is a lot of information contained inside vbn files. All vbn files are not created equal. Some contain the quarantined files, while others do not. It all depends on where they are in the quarantine folder structure. The vbn file structure has undergone two revisions, from what I can tell. Version 1 of the format was used up to SEP 11. Version 2 is used in SEP 12 and 14.

I would like to give a special thank you to @Hexacorn for getting me some older samples to work with.

The file format for version 1 can be found here. Version 2 here. There is still quit a bit that is unknown. If you find any mistakes or know what some of the structures are, please let me know and I will update them.

Quarantine File Folder Structure


Folder structure makes a difference in what is contained in the vbn file. SEP quarantine files are located in C:\ProgramData\Symantec\Symantec Endpoint Protection\CurrentVersion\Data\Quarantine. In the quarantine folder, there is a vbn file and a folder with the same name as the vbn file.


The vbn files and folders get their name from the Vbin Session ID/Record ID in hex format.

VBN V1


VBN V2

I have not dug into these vbn files too much yet. If we search for Vbin Session ID/Record ID, depending on version, we come across a series of values that start with 01 07 03. The first one, in green, is our folder name. The second one, in blue, is the name of the vbn file in the folder. This is the vbn that will contain the quarantined file.


VBN V1


VBN V2
The vbn files that contain the quarantined file get their name from the unix 32-bit time value (0x560 in v1, 0x7D0 in v2).

Log Line


Inside the vbn, there is a comma delimited list at 0x184. This list is the log line. It may not be clear at first but, there is a wealth of information in this list. We'll start by taking a look at version 1. Version 1 has the following fields:

Time,Event,Category,Logger,Computer,User,Virus,File,Wanted Action 1,Wanted Action 2,Real Action,Virus Type,Flags,Description,ScanID,New_Ext,Group ID,Event Data,VBin_ID,Virus ID,Quarantine Forward Status,Access,SND_Status,Compressed,Depth,Still Infected,Def Info,Def Sequence Number,Clean Info,Delete Info,Backup ID,Parent,GUID,Client Group,Address,Domain Name,NT Domain,MAC Address,Version,Remote Machine,Remote Machine IP,Action 1 Status,Action 2 Status,License Feature Name,License Feature Version,License Serial Number,License Fulfillment ID,License Start Date,License Expiration Date,License LifeCycle,License Seats Total,License Seats,Error Code,License Seats Delta,Status,Domain GUID,Log Session GUID,VBin Session ID,Login Domain,Event Data 2

Version 2 has the same fields as version 1 plus some extras:

Time,Event,Category,Logger,Computer,User,Virus,File,Wanted Action 1,Wanted Action 2,Real Action,Virus Type,Flags,Description,ScanID,New_Ext,Group ID,Event Data,VBin_ID,Virus ID,Quarantine Forward Status,Access,SND_Status,Compressed,Depth,Still Infected,Def Info,Def Sequence Number,Clean Info,Delete Info,Backup ID,Parent,GUID,Client Group,Address,Domain Name,NT Domain,MAC Address,Version,Remote Machine,Remote Machine IP,Action 1 Status,Action 2 Status,License Feature Name,License Feature Version,License Serial Number,License Fulfillment ID,License Start Date,License Expiration Date,License LifeCycle,License Seats Total,License Seats,Error Code,License Seats Delta,Status,Domain GUID,Log Session GUID,VBin Session ID,Login Domain,Event Data 2,Eraser Category ID,Dynamic Categoryset ID,Dynamic Subcategoryset ID,Display Name To Use,Reputation Disposition,Reputation Confidence,First Seen,Reputation Prevalence,Downloaded URL,Creator For Dropper,CIDS State,Behavior Risk Level,Detection Type,Acknowledge Text,VSIC State,Scan GUID,Scan Duration,Scan Start Time,TargetApp Type,Scan Command GUID

The information on how to decipher what the various fields mean can be found here. There was too much information to put into this article.

Decryption

Finding and decrypting the quarantined file in version1 is fairly straight forward. Grab the first four bytes from the beginning of the file. It is in little endian. In this example, the hex value translates to E5C. This is the offset to the quarantined file.


If we go to this offset, we will fined our quarantined file XORed with 5A.


Version 2 is a little trickier. We start off the same way, by grabbing the first four bytes of the file. Again, little endian.


Instead of finding the quarantined file, we find the Quarantine File Meda (QFM) Header XORed with 5A.


Once the XOR is removed, we can see some values in the QFM header. At offset 0x1298 is the size of the QFM Header. 0x12A0 is the QFM size and 0x12B8 is the total size of the QFM and the QFM Header.


If we take the value at 0x12A0 (897) and add it to 1290, we come up with another offset, 0x1B27 in this case. The structure of this section is as follows:

0x1B27(6951) = 0x03030000000A010852000000
    • 0x1B2E(6958) = 0x8 datatype representing unicode
    • 0x1B2F(6959) = Unicode string size
    • 0x1B33(6963) = Unicode hash SHA1
0x1B85(7045) = 0x030200000003020000000908000000
0x1B94(7060) = Original quarantined file size


Following the quarantine file size will either be 0x08 or 0x09. If it is 0x09, we have arrived at the quarantined file. If it is 0x08, the following optional section is present. 

0x1B9C(7068) = 0x08
    • 0x1B9D(7069) = Size of Security Descriptor string
    • 0x1BA1(7073) = Security Descriptor
    • 0x1C5B(7259) = Unknown
    • 0x1C60(7264) = 0x4
    • 0x1C61(7265) = Original quarantined file size
    • 0x1C69(7273) = 0x9



The quarantine file is broken up into chunks. The size of the chunks can be figured out by looking at the chunk dividers. The chunk dividers start with 0x09. The next four bytes after are the size of the chunk.






The chunk itself is XORed with A5. Continue to the next chunk divider, get the size and wash, rinse, repeat.


If there is anything you would like me to expand upon or explain better, let me know and I will add it.

Sources 

http://www.securitybraindump.com/2011/08/carving-symantec-vbn-files.html
http://dofir.net/post/81425257003/a-study-of-symantecs-vbn-file-format
http://www.hexacorn.com/blog/2012/09/21/dexray-decrypting-vbn-files-part-2/
https://support.symantec.com/en_US/article.TECH100099.html

Monday, August 21, 2017

USB forensics with Logparser Studio

Pictures are worth a thousand words. Enjoy!!!!

HKLM\SYSTEM\CurrentControlSet\Enum\USBSTOR






























HKLM\SYSTEM\CurrentControlSet\Enum\USB






























HKLM\SYSTEM\MountedDevices

Thursday, August 17, 2017

Comparing Packet Captures to Procmon Traces

Trying to match Procmon entries to a packet capture can seem frustrating at first. There are two things that stand out to me in the Procmon entries:

1. The times are off ever so slightly compared to the packet capture
2. There is not an entry for every packet in Procmons output

We can solve this problem by looking at the times and lengths of the packets. To make things a little easier to read, I'll use the csv output from Procmon and use windump to filer the packets. The first thing I do is filter the Operation column so it contains tcp events.


And then I filter the Path column by the IP address I am trying to match.


The csv should look something like this:









Now that the Procmon csv is filtered, I can run Windump and filter on the IP in question. Piping the output to a file will make this easier. To do this, run the following command:

WinDump.exe -n -p -r malware.pcap host 103.24.1.54 > output.txt

The resulting file should look something like this:


The number in the parentheses is the length of the packet. Once we start matching the approximate times and the length of the packets, the picture becomes a lot clearer.


I hope this helps anyone trying to compare packet captures to Procmon output.

Tuesday, July 18, 2017

ProcDOT plugin writing. Part 4 - Context Menu and CanBeVerified

Throughout this tutorial, we learned how to write a plugin for the the Main Menu. Writing a plugin for the context menu isn't any different. There are a couple of options available for the context menu I would like to touch on though to help make your plugin a little more professional.

Context Menu plugins allow us to get more granular with the data we are after. Let's say we want a context menu item that is only available when we right click on a server node. The plugin engine offers this feature though the CanBeVerified switch.

Before we continue, lets alter our  cmd_line plugin so it becomes a Context Menu Item instead of being in the Main Menu. Open the cmd_line.pdp file in an editor and change the type from MainMenuItem to ContextMenuItemForGraph. Your pdp file should now look like this:

Name = cmd
Author = <your name>
Description = Open cmd prompt from ProcDOT's Main Menu
Version = 1
Type = ContextMenuItemForGraph
Architecture = WindowsBatchScript
File = cmd_line.bat
Priority = 9
RunHidden = 1
RunExclusively = 1
CanOverrideOtherPlugins = 0
CanOverrideProcdot = 0

Restart ProcDOT and load your graph again. If we right click anywhere on the graph, we should see cmd in the context menu. What we are trying to accomplish though is having a plugin show up if we right click on a server node. Lets right click on a server node an see what we need to do to have this happen.


In the command prompt enter the following:

 set | find /i "procdot"

You will notice that there are a lot more variables to choose from. Remember, this type of plugin allows us to get granular with what we are doing.


Looking through our list of options, it looks like PROCDOTPLUGIN_CurrentNode_name would be a good candidate for what we are doing. Notice it is telling us that this is a server node. With this information, we can try to get our  plugin to only show up if we right click on a server node.

To do this, we first have to set the CanBeVerified switch to 1 in our pdp file (add CanBeVerified = 1 to the end of the file). Lets stop ProcDOT and create our verify plugin. We'll start by creating a new pdp file called verify.pdp with the following content (Notice it has the CanBeVerified switch).

Name = Verify
Author = <your name>
Description = verify test
Version = 1
Type = ContextMenuItemForGraph
Architecture = WindowsBatchScript
File = verify.bat
Priority = 9
RunHidden = 0
RunExclusively = 1
CanOverrideOtherPlugins = 0
CanOverrideProcdot = 0
CanBeVerified = 1

RunHidden has also been set to 0 so we can see additional output. We can clean this up after our plugin is complete. When we set the CanBeVerified switch, a new ProcDOT variable called PROCDOTPLUGIN_VerificationRun is created and its initial value is set to 1. If the criteria for the verification passes, (in our case, is it a server node), this value will be set to 0 indicating the verification passed. If not, the value will remain 1. This will be a little easier to explain if I give you the code for the plugin and go through each part. Create a file called verify.bat with the following content:

 @setlocal enabledelayedexpansion && python -x "%~f0" %* & exit /b !ERRORLEVEL!

import os
import sys
verify = os.getenv('PROCDOTPLUGIN_VerificationRun')
   
if os.getenv('PROCDOTPLUGIN_VerificationRun') == '0':
    pass
   
else:   
    if os.getenv('PROCDOTPLUGIN_CurrentNode_name')[:6] == 'SERVER':
        print 'PROCDOTPLUGIN_VerificationRun = ' + verify
        raw_input('Yes. This is a server node.')
        sys.exit(1)
    else:
        print 'PROCDOTPLUGIN_VerificationRun = ' + verify
        raw_input('No. This is not a server node')
        sys.exit(0)

def main():
    print 'PROCDOTPLUGIN_VerificationRun = ' + verify
    raw_input('Verification complete.')

if __name__ == '__main__':
    main()

Lets take a closer look at what is going on.

if os.getenv('PROCDOTPLUGIN_VerificationRun') == '0':
    pass

This part of the code is telling the plugin, that if everything is verified, to skip or "pass" everything else and go to the main function. Remember, PROCDOTPLUGIN_VerificationRun is initially set to 1, so we are going to have to create a condition to set it to 0. this is were the next part o f the code comes into play.

else:   
    if os.getenv('PROCDOTPLUGIN_CurrentNode_name')[:6] == 'SERVER':
        print 'PROCDOTPLUGIN_VerificationRun = ' + verify
        raw_input('Yes. This is a server node.')
        sys.exit(1)
    else:
        print 'PROCDOTPLUGIN_VerificationRun = ' + verify
        raw_input('No. This is not a server node')
        sys.exit(0)

This  part of the code is responsible for verifying a condition for our plugin. In the if statement, we are using  the PROCDOTPLUGIN_CurrentNode_name variable to check if we are right clicking on a server node. If this is true, the plugin sets the exit code to 1. This will tell ProcDOT to change PROCDOTPLUGIN_VerificationRun to 0. If it is not a server node, the plugin will run the else statement and set the exit code to 0, leaving PROCDOTPLUGIN_VerificationRun set to 1. The print and raw_input statements are there for our debugging purposes so we can see what the plugin is doing.

After doing this check, if it is a server node, ProcDOT will set  PROCDOTPLUGIN_VerificationRun to 0 and initialize our plugin. Our plugin can now run the rest of its code under main. Lets continue and see it in action. Start ProcDOT and  refresh your graph.

The first thing we are going to do is right click on a server node. When we do this, you should see the following command prompt come up:


This is the place in the code we are at now:

    if os.getenv('PROCDOTPLUGIN_CurrentNode_name')[:6] == 'SERVER':
        print 'PROCDOTPLUGIN_VerificationRun = ' + verify
        raw_input('Yes. This is a server node.')
        sys.exit(1)

From here, hit enter. You will see the same command prompt come up again (ProcDOT does a double check for some reason). Hit enter one more time and then you should see the verify entry in the context menu.


If we left click on Verify, we should be presented with the following command prompt:


This is the place in the code we are at now:

def main():
    print 'PROCDOTPLUGIN_VerificationRun = ' + verify
    raw_input('Verification complete.')

if __name__ == '__main__':
    main()

Hit enter to clear the command prompt. So far it seems to be working. Lets do one more test to make sure it only shows up when we click on a server node. Now, right click anywhere except for on a server node. You should see the following command prompt:





This is the place in the code we are now:

else:
        print 'PROCDOTPLUGIN_VerificationRun = ' + verify
        raw_input('No. This is not a server node')
        sys.exit(0)

Hit enter, and then hit enter again. If everything worked, Verify should not show up in the context menu:
As you can see, you can set conditions for when your plugin will show up in the context menu. This is not just for a server node, this can be applied to any conditions you want met. All that you need to do to give it some function is to add your code to the main function. I hope I did a good job of explaining how this works. It can seem a little confusing at first.

Tuesday, June 6, 2017

ProcDOT plugin writing. Part 3 - Creating a Main Menu plugin

In the last two posts (Part 1, Part 2), we created a simple plugin and explored some of the files that ProcDOT stores data in. We will now leverage both of these to create a plugin that lists the servers in the graph. This is not a tutorial on python but, I will try to explain some of the plugin to show where the information is coming from. Yes, I know, Christian has already made this plugin. But, by looking at this plugin, it helped me to figure some of this stuff out when I first started out writing plugins. If you remember from part 1, there was a reason I liked to use python. By rewriting this plugin in python, I only have to maintain one plugin (the serverslist plugin has a batch script for Windows and a bash script for Linux).

We'll start out by firing up ProcDOT and generate our graph.Looking at the graph, I have eight different server nodes (yours might be different). Make note of the servers listed.


Right click on one of the server nodes and select details.




Looking at the details for a server node, we can see there are five different keys and values. With this information, we can start to build our plugin.Go to the Plugin menu and launch the cmd plugin. So, we now know the information we are after is in the details file. If we type set | find /i "procdot" in the cmd prompt, we can see the variable that we will have to call is PROCDOTPLUGIN_GraphFileDetails. If we want to display output, we will also have to call PROCDOTPLUGIN_ResultCSV or PROCDOTPLUGIN_ResultXML because we are going to create a table. We will create our plugin output with PROCDOTPLUGIN_ResultCSV for this example.

From here, drop into a python shell by typing python and hit enter.


We will need to import os into the python shell so we can get our ProcDOT variables and assign them in our plugin.After that, we will create some variables for our key data we are after.


Next we will open the details file in python and search it, line by line, for the Domain keys. For every hit we get on Domain, we'll have python print it out.


Hmmm. Looks like we are getting back the Domain key and then some. Looking at the format of the file, we can split the lines apart with a space. This will split the line into three parts. We can go back and search the first part for Domain and try it again.


Now we are getting somewhere! But there is another problem. Not every server node has a Domain tied to it. If we look back, we can see that we can also search for the IP-Address. I we assign these to our variables, we can print these together.


Looks good. We can now identify a server node by either its Domain or IP-Address. Wait a minute though. My graph had only eight server nodes in it. Lets list out the rest of our keys and see if we can narrow this list down.


So, looking at the output, we can see that the server nodes that are in the graph also contain an entry in the  RelevantBecauseOfProcmonLines key. And the server nodes that are marked yes in the OnlyInPCAP key are also in the graph. Lets parse this out a little more and strip out what we don't before we write our actual plugin. We'll reset some of our variables and finish this up.


Nice! We can now write our plugin. In order for our output to come out right, we will have to refer to the ProcDOT documentation to make sure the result csv is properly formatted.



So looking at this, the first line of the file contains the headers surrounded in quotation marks and separated by commas. The next line is the column widths, then finally our data. We are only going to have headers for the Domain and the IP-Address. We'll add some style by marking the server nodes that are only in the pcap with blue lettering. With our plan in place, we can create the plugin and the pdp file.

server.pdp

Name = server
Author = <your name>
Description = Open cmd prompt from ProcDOT's Main Menu
Version = 1
Type = MainMenuItem
Architecture = WindowsBatchScript
File = server.bat
Priority = 9
RunHidden = 1
RunExclusively = 1
CanOverrideOtherPlugins = 0
CanOverrideProcdot = 0

server.bat

@setlocal enabledelayedexpansion && python -x "%~f0" %* & exit /b !ERRORLEVEL!
import os

def main():

    data = os.getenv('PROCDOTPLUGIN_GraphFileDetails')
    out = os.getenv('PROCDOTPLUGIN_ResultCSV')
    outfile =open(out, 'w')
    domain = None
    ip = None
    onlyinpcap = None
    procmon = None

    outfile.write('"Domain","IP-Address"\n')
    outfile.write('"*","*"\n')

    with open(data) as f:
        for line in f:
            c = line.split(' ', 2)
            if c[0] == 'Domain':
                domain = ''.join(c[2:]).strip()
            if c[0] =='IP-Address':
                ip = ''.join(c[2:]).strip()
            if c[0] == 'OnlyInPCAP':
                onlyinpcap = ''.join(c[2:]).strip()
            if c[0] == 'RelevantBecauseOfProcmonLines':
                procmon = ''.join(c[2:]).strip()
                if domain != ip:
                    if procmon != '':
                        outfile.write('"' +domain + '","' + ip + '"\n')
                    if onlyinpcap == 'Yes':
                        outfile.write('{{color:blue}}' + '"' + domain + '","' + ip + '"\n')
                           
if __name__ == '__main__':
    main()

Save these to your plugin folder restart ProcDOT and generate a graph. From the Plugin menu, select server and you should see the results.



Success! We made a plugin to display all the servers in the graph. Before we conclude, the cmd plugin can be used to troubleshoot our plugin. Lets say we made a typo in our plugin and no results were returned. We can't see any of the error messages to see what happened. To test this out, open the server.bat file and change domain = None to dmain = None and save the file. If we run the server plugin again our results come back empty.


We don't know why because we cannot see the errors. Close out the results and launch the cmd plugin from the Plugin menu. From this command prompt, we can run the server plugin manually. Type server.bat in the command prompt and hit enter.


From the output, we can see the error UnboundLocalError: local variable 'domain' referenced before assignment. If we edit server.bat back to domain = None and save again, we can run the plugin manually and see that there are no more errors. Pretty neat!


Our simple cmd plugin turns out to be rather useful for writing and troubleshooting plugins. Now that we have a way to better develop our plugins, we will create a plugin for the context menu in the next tutorial.