Unpacking script2exe Malware
Updated: Jul 25
Recently we were provided a malware sample that contained some interesting strings that indicated it was packed with script2exe. Script2exe is a utility that can take a VBScript file and pack it into an executable. It is not in and of itself malicious, but can be used to package scripts that can do bad things. For this reason, many antivirus engines detect script2exe files as malicious regardless of their actual function. Use caution when investigating AV or EDR alerts on script2exe based malware.
Script2exe functions by packaging the original VBScript as an encrypted resource within a PE stub. The original script is retained in the EXE stub's resources section as a Bitmap resource numbered "129" after it is encrypted with RC4. Upon execution of the PE Stub, the resource is loaded into memory, decrypted, and executed.
Figure 1: Resources for a file packed with script2exe
This type of packing has been seen recently by ESET, purportedly used by the Telebots threat actor to deploy backdoors to targeted systems. Fortunately for us it is trivial to unpack. Furthermore, since it appears that the script2exe program uses the same EXE stub to deploy any script, the techniques used in this post should be broadly applicable to other samples that are packed with script2exe.
There are some telltale signs that a sample is packed with script2exe:
PDB path string "C:\\Projets\\vbsedit_source\\script2exe\\Release\\mywscript.pdb"
Resource of type Bitmap with #129
Import hash 596baa240d06336d7a812c3e19d7d6ac
Several COM API calls which are used to invoke the VBScript after decryption
When executed, the program will access the RC4 encrypted bitmap resource #129, decrypt that resource back into the original script text, and then execute that script. The following techniques will help us recover the decrypted script text for analysis.
First we need to find the call to an inline function, _rand. Once you open the executable in IDA Pro, you should be able to find this function in the Functions window. Once you locate the function you can find its cross-references. There should be only one call to the inlined _rand() function.
Figure 2: The _rand function
Figure 3: Cross references to the _rand function
The _rand function is called after the random number generator is seeded with a static value via a call to _srand. This allows the script2exe stub to generate the RC4 decryption key. Immediately after the call to _rand, you will see a loop where the RC4 decryption key is rebuilt.
Figure 4: RC4 key construction
Once this loop finishes, the program flows to the RC4 decryption function. The very next CALL instruction after the CALL to _rand is the call to the RC4 decryption function. We can put a breakpoint on this CALL and then execute the program (I STRONGLY recommend only doing so in a virtual machine).
Figure 5: Call to decryption function
Pay attention to the stack arguments as well as the value of the EAX register when the breakpoint is hit. The EAX register before the CALL contains the overall size of the encrypted memory buffer that was read in from the executable files Resource #129. Additionally, the first argument that is pushed on the stack via the PUSH EDX instruction is a pointer to the decryption key. In all of the samples I have seen, the decryption key is:
Figure 6: Decryption key in memory
If you are so inclined, you could write a quick python script to use RC4 to decrypt the resource file directly, rather than running the malicious sample through a debugger, with one caveat. More on this later.
The function at sub_407430 is an implementation of the RC4 algorithm. If we step over this function in the debugger, then the value at EAX after the step will point to a memory buffer that contains our newly-decrypted script.
Figure 7: Decrypted script in memory
As I mentioned previously, it is possible to write a python script to manually decrypt the resource object without running the executable file. The one caveat is that the resource object is prepended with 60 bytes of binary data that needs to be trimmed before decryption. The last DWORD of these 60 bytes is the length of the encrypted resource data.
Figure 8: Resource header data
In the example shown in Figure 7, you can see the 60 bytes that precede the start of the encrypted data. The DWORD that contains the size of the ciphertext is highlighted; in this case the ciphertext is 0xFF6 (4086) bytes in length. We select the 4086 bytes starting immediately after the length DWORD and dump those bytes to a file, then decrypt those bytes using the RC4 algorithm and the key shown in Figure 5 and you should be able to decrypt the original VBScript for analysis.
Polito has created an IDA Python script to assist malware analysts with unpacking the plaintext original VBScript for further analysis. The script is available on our GitHub page.
Polito, Inc. offers a wide range of security consulting services including penetration testing, vulnerability assessments, incident response, digital forensics, and more. If your business or your clients have any cyber security needs, contact our experts and experience what Masterful Cyber Security is all about.