Malware comes in all shapes, sizes, and languages to make defending against attacks more difficult. One of the nefarious ways malware attempts to hide is by masquerading as a legitimate installer or application.

A recent sample encountered by the Carbon Black Threat Research Team used a compiled AutoIT script that pretends to be an installer for Photoshop CS6 portable. The application installs itself and runs as intended on the target system, while stealthily injecting a Remote Desktop tool known as LuminosityLink into a .NET process behind the scenes.  The process for reversing AutoIT scripts can be more difficult since there are far fewer and less developed tools for performing the analysis. Since recovery of source code is trivial for script-to-exe programs, the authors rely heavily on string and execution flow obfuscation.

In this blog we will walk through the techniques used by this malware to increase the difficulty of discovering the final payload and the methods used to maintain persistence on the system. We will also look at the tools, tactics, and techniques for reverse engineering this type of AutoIT script.

Executive Summary

The sample AutoIT script is compiled into a Windows portable executable using a tool provided by the AutoIT creators. This tool, Aut2exe, converts an AutoIT script to a more ubiquitous Windows executable by packaging the runtime environment with the script. This conversion allows the attackers to target any Windows system by removing the dependency of having the AutoIT runtime installed.

In the case of this malware, any additional resources referenced in the script are also packaged into the malware, causing the sample to weigh in at over 115MB. The author of this script compiled a legitimate Photoshop portable installer as well as several malicious scripts necessary for infecting the target system with a powerful remote access tool.

The malicious scripts are heavily obfuscated to make it more difficult to analyze the contents and the malware attempts to hide by setting the attributes of the file to system and hidden. This prevents a typical user from seeing the file unless uncommon options are used in the explorer window.

The malware also remains persistent on the system by installing itself to the registry so the malware executes each time the system starts up.  When the malware starts, the payload is injected into the .NET command line compiler application (vbc.exe) to appear legitimate on the system. However, the actual code running in the process is the backdoor allowing the attacker full access to the target system.


The packaged malware starts its life by pretending to be a legitimate Photoshop Portable CS6 application. In this case, it actually does contain the legitimate multi installer for both 32-bit and 64-bit systems. Unfortunately for the victim, it also contains a malicious AutoIT script that infects the system.

AutoIT is a BASIC-like language that is often used to automate tasks with a focus on GUI driven apps embedded with their runtime. Because it’s based on BASIC, the scripts are often easy to read and very descriptive. However, because of this, malware authors typically go to great lengths to obfuscate their code against reverse engineering. In order to get the code out of the compiled binary we can use a tool called Exe2Aut, which retrieves the source code and writes it to disk.

Disclaimer: The malicious binary is launched in memory in a neutered format, however if the malware uses any kind of pre-execution tricks such as TLS, they could potentially execute.  Therefore, you should always run this tool inside an isolated or sandboxed system. Dragging and dropping the binary will decompile the binary into source code. It is also important to note that this only works on 32-bit AutoIT compiled binaries:

This provides readable source code and is often enough to determine the purpose of the malware.  In this case, the obfuscation makes this static analysis daunting. The way I attack this type of obfuscation is a combination of static analysis and debugging the script. The AutoIT creators provide a script debugger that works similar to other debuggers, although it can be quite buggy and certain functions can crash the application. It is enough to be able to step through the code and set input arguments in order to test out various inputs to the script.

By stepping through the code with the debugger, some functions can be named, modified, and sometimes eliminated altogether to clean up the source code. I also write Python parsing scripts to be able to quickly see what a line of code is going to be. Several of these types of samples use a random function name, pass it a number, and return the corresponding character. By parsing a long line of these integer to ASCII conversions, I can quickly see what string that line of code is trying to build. Using this quick Python script, I pass it the line of code and the function name that I’d like to parse for characters:

from os import sys
import re

# Print usage
if len(sys.argv) != 3:
	print "usage: %s [string_to_decode] [function]" % sys.argv[0]

# get the name of the function to find
function_name = sys.argv[2]

# compile the regex
cap = re.compile(function_name + r"\((\d{2,3})\)")

# look for all the matches in the given text that have the function
matches = cap.findall(sys.argv[1])

# print out the result
for match in matches:
	print chr(int(match)),

This can be fleshed out more, but there were only a few uses in this script.  It did help on a few occasions, though, turning obfuscated lines of code into something more readable:

We now have a way to do both some static and dynamic analysis of the malicious script and we will be referring to this script as the Stage1 script for the remainder of the writeup. The first line of Stage1 causes the the Photoshop executable to be dropped on disk. It appears the intended destination is in the %TEMP% directory of the target system, but a forgotten ‘\’ in the code caused the temp directory to be concatenated to the Photoshop name.

This didn’t affect the execution of the malware. After being dropped, Stage1 checks to see if a file exists in the %TEMP% directory: TGgtjCDKADvaH8BUQkZKWfKWj.  This file acts as a semaphore, causing the dropped Photoshop installer to execute if it does not exist.  On the first run, the Photoshop installer is executed and the flag file is created in the %TEMP% directory.

This file works and after install, Photoshop can be used on the system. Next, a six-digit number is randomly generated for use later in the script.  At this point Stage1 begins using string reversal, replacement, and obfuscation to drop three additional files in the %TEMP% folder:

  1. SBIXinQrad -> renamed to the random six-digit number above
    1. This file is a obfuscated script that looks like a large amount of gibberish. Once it is decoded, it will be the second stage of the malware
  2. TVsusCoFv2 -> renamed incl1
    1. This file is a series of obfuscated strings that will be used in conjunction with the second stage of malware
  3. Incl2
    1. This file is a base64 encoded and RC4 encrypted payload. It will be the final payload for the malware

Incl2 is contained in the Stage1 script as a base64 encoded string. It is decoded and written to disk as an encrypted blob. The SBIXinQrad file is read in and decoded using a custom string deobfuscation routine that builds upon itself and then inverts the result. The following Python code will decode the file:

from os import sys

with open(sys.argv[1], "r") as encoded_file:
    data =

decoded = data.replace("bqQik", "#").replace("#A#", "a").replace("#B#", "b").replace("#E#", "e").replace("RSlkDJIgA-qV62m", "")[::-1]

with open('decoded_incl2.au3', "w") as decoded_file:

The above code turns this junk:

Into Stage2 of the malware:

At this point the malware has dropped the final payload, an obfuscated string file, and the second stage of the malware. It is ready to move onto Stage2 and accomplishes this by calling the API ShellExecuteW with the arguments:

  • /AutoIt3ExecuteScript
  • %TEMP%\<random 6 digit number>
  • path\to\<original binary>.exe

This causes the runtime environment built into the original executable to execute Stage2 instead of the original Stage1 script.


The job of Stage2 is to set up the environment for the malware. First, the malware would like to be running as %APPDATA%\svchost.exe and a check is performed to see if this was the argument that was passed from the previous script. Since this is the first run, it will not be running as this target process name, which causes the original binary to be copied into the %APPDATA% as svchost.exe and the attributes of the file set to System and Hidden. The new svchost.exe is executed with similar arguments to the first run except now the paths match the target location:

  • /AutoIt3ExecuteScript
  • %TEMP%\<random 6 digit number>
  • %APPDATA%\svchost.exe

A batch script that is responsible for deleting the original dropped binary and itself is created as %TEMP%\67.bat and executed:

ping -n 0127.0.0.1 > nul
del "%TEMP%\"
if exist "%TEMP%\" goto loop
del %TEMP%\67.bat

The ping command is actually invalid and improperly concatenated when the script is created. However, since it is piped to nul, the error is suppressed. The author is just looking for a delay in execution using a dummy command until the original binary stops executing. It will then delete the original binary as well as itself. Stage2 also looks for Zone.Identifier files associated with the original dropped binary and deletes that file as well. This file is an Alternate Data Stream that contains information about where the file was downloaded from.

Now that the Stage2 is running in its preferred location, it is ready to install persistence in the ever so common CurrentVersion\Run key:

Value: %APPDATA%\svchost.exe

Stage2 is now ready to decrypt the final payload and inject it into the target process on the system. The decryption of the binary is accomplished in an interesting manner. The API CallWindowProc is called with the decryption routine, a pointer to the encrypted blob, and the decryption key. The decryption routine is pulled from the encoded string library (File: incl1) that was discussed above in the Stage1 writeup. An encoded string is accessed by calling a function with the number of the string requested. The entire file is decoded and the string is returned via the number passed. The string encoding uses several replacements in the file to build a hex encoding of the ASCII representations of the characters. The result is converted into ASCII characters and then inverted. Here is Python code for decoding these files:

import sys
import binascii

with open(sys.argv[1], "r") as encoded_file:
    data =

result = data.replace("_", "2").replace("?", "0").replace("fdy","3").replace("#", "1").replace("32t", "4")

result = binascii.unhexlify(result.split('0x')[1])[::-1]

with open('decoded_incl1.txt', "w") as decoded:

This code turns the encoded gibberish:

Into a string lookup table for the malware:

1=dword cbSize;ptr Reserved;ptr Desktop;ptr Title;dword X;dword Y;dword XSize;dword YSize;dword XCountChars;dword YCountChars;dword FillAttribute;dword Flags;word ShowWindow;word Reserved2;ptr Reserved2;ptr hStdInput;ptr hStdOutput;ptr hStdError
2=ptr Process;ptr Thread;dword ProcessId;dword ThreadId
5=align 16; uint64 P1Home; uint64 P2Home; uint64 P3Home; uint64 P4Home; uint64 P5Home; uint64 P6Home;dword ContextFlags; dword MxCsr;word SegCS; word SegDs; word SegEs; word SegFs; word SegGs; word SegSs; dword EFlags;uint64 Dr0; uint64 Dr1; uint64 Dr2; uint64 Dr3; uint64 Dr6; uint64 Dr7;uint64 Rax; uint64 Rcx; uint64 Rdx; uint64 Rbx; uint64 Rsp; uint64 Rbp; uint64 Rsi; uint64 Rdi; uint64 R8; uint64 R9; uint64 R10; uint64 R11; uint64 R12; uint64 R13; uint64 R14; uint64 R15;uint64 Rip;uint64 Header[4]; uint64 Legacy[16]; uint64 Xmm0[2]; uint64 Xmm1[2]; uint64 Xmm2[2]; uint64 Xmm3[2]; uint64 Xmm4[2]; uint64 Xmm5[2]; uint64 Xmm6[2]; uint64 Xmm7[2]; uint64 Xmm8[2]; uint64 Xmm9[2]; uint64 Xmm10[2]; uint64 Xmm11[2]; uint64 Xmm12[2]; uint64 Xmm13[2]; uint64 Xmm14[2]; uint64 Xmm15[2];uint64 VectorRegister[52]; uint64 VectorControl;uint64 DebugControl; uint64 LastBranchToRip; uint64 LastBranchFromRip; uint64 LastExceptionToRip; uint64 LastExceptionFromRip
6=dword ContextFlags;dword Dr0; dword Dr1; dword Dr2; dword Dr3; dword Dr6; dword Dr7;dword ControlWord; dword StatusWord; dword TagWord; dword ErrorOffset; dword ErrorSelector; dword DataOffset; dword DataSelector; byte RegisterArea[80]; dword Cr0NpxState;dword SegGs; dword SegFs; dword SegEs; dword SegDs;dword Edi; dword Esi; dword Ebx; dword Edx; dword Ecx; dword Eax;dword Ebp; dword Eip; dword SegCs; dword EFlags; dword Esp; dword SegSs;byte ExtendedRegisters[512]
7=char Magic[2];word BytesOnLastPage;word Pages;word Relocations;word SizeofHeader;word MinimumExtra;word MaximumExtra;word SS;word SP;word Checksum;word IP;word CS;word Relocation;word Overlay;char Reserved[8];word OEMIdentifier;word OEMInformation;char Reserved2[20];dword AddressOfNewExeHeader
8=word Machine;word NumberOfSections;dword TimeDateStamp;dword PointerToSymbolTable;dword NumberOfSymbols;word SizeOfOptionalHeader;word Characteristics
9=word Magic;byte MajorLinkerVersion;byte MinorLinkerVersion;dword SizeOfCode;dword SizeOfInitializedData;dword SizeOfUninitializedData;dword AddressOfEntryPoint;dword BaseOfCode;dword BaseOfData;dword ImageBase;dword SectionAlignment;dword FileAlignment;word MajorOperatingSystemVersion;word MinorOperatingSystemVersion;word MajorImageVersion;word MinorImageVersion;word MajorSubsystemVersion;word MinorSubsystemVersion;dword Win32VersionValue;dword SizeOfImage;dword SizeOfHeaders;dword CheckSum;word Subsystem;word DllCharacteristics;dword SizeOfStackReserve;dword SizeOfStackCommit;dword SizeOfHeapReserve;dword SizeOfHeapCommit;dword LoaderFlags;dword NumberOfRvaAndSizes
10=word Magic;byte MajorLinkerVersion;byte MinorLinkerVersion;dword SizeOfCode;dword SizeOfInitializedData;dword SizeOfUninitializedData;dword AddressOfEntryPoint;dword BaseOfCode;uint64 ImageBase;dword SectionAlignment;dword FileAlignment;word MajorOperatingSystemVersion;word MinorOperatingSystemVersion;word MajorImageVersion;word MinorImageVersion;word MajorSubsystemVersion;word MinorSubsystemVersion;dword Win32VersionValue;dword SizeOfImage;dword SizeOfHeaders;dword CheckSum;word Subsystem;word DllCharacteristics;uint64 SizeOfStackReserve;uint64 SizeOfStackCommit;uint64 SizeOfHeapReserve;uint64 SizeOfHeapCommit;dword LoaderFlags;dword NumberOfRvaAndSizes
11=char Name[8];dword UnionOfVirtualSizeAndPhysicalAddress;dword VirtualAddress;dword SizeOfRawData;dword PointerToRawData;dword PointerToRelocations;dword PointerToLinenumbers;word NumberOfRelocations;word NumberOfLinenumbers;dword Characteristics
12=byte InheritedAddressSpace;byte ReadImageFileExecOptions;byte BeingDebugged;byte Spare;ptr Mutant;ptr ImageBaseAddress;ptr LoaderData;ptr ProcessParameters;ptr SubSystemData;ptr ProcessHeap;ptr FastPebLock;ptr FastPebLockRoutine;ptr FastPebUnlockRoutine;dword EnvironmentUpdateCount;ptr KernelCallbackTable;ptr EventLogSection;ptr EventLog;ptr FreeList;dword TlsExpansionCounter;ptr TlsBitmap;dword TlsBitmapBits[2];ptr ReadOnlySharedMemoryBase;ptr ReadOnlySharedMemoryHeap;ptr ReadOnlyStaticServerData;ptr AnsiCodePageData;ptr OemCodePageData;ptr UnicodeCaseTableData;dword NumberOfProcessors;dword NtGlobalFlag;byte Spare2[4];int64 CriticalSectionTimeout;dword HeapSegmentReserve;dword HeapSegmentCommit;dword HeapDeCommitTotalFreeThreshold;dword HeapDeCommitFreeBlockThreshold;dword NumberOfHeaps;dword MaximumNumberOfHeaps;ptr ProcessHeaps;ptr GdiSharedHandleTable;ptr ProcessStarterHelper;ptr GdiDCAttributeList;ptr LoaderLock;dword OSMajorVersion;dword OSMinorVersion;dword OSBuildNumber;dword OSPlatformId;dword ImageSubSystem;dword ImageSubSystemMajorVersion;dword ImageSubSystemMinorVersion;dword GdiHandleBuffer[34];dword PostProcessInitRoutine;dword TlsExpansionBitmap;byte TlsExpansionBitmapBits[128];dword SessionId
15=dword a; dword b

In order to retrieve the RC4 decoding routine, the number 26 is passed to the string decoding function: U335BXtWJ(26):

Here is the decoding function:

As you can see, these functions can be difficult to read with all the concatenation, string replacement, function calls, and obfuscated strings. There are also several calls to that function requesting strings:

This function is building the decryption routine as a byte array so that it can be passed to the CallWindowProc API. The encrypted blob is decrypted in place and Stage2 is now ready to inject it into the target process.  

This malware chose to look for vbc.exe, which is the command line compiler for the .NET framework.  The target process does not matter much, but the final payload is a .NET binary, so perhaps they thought it would be a good place to hide.  If it fails to find that particular file, the fallback is to inject into itself.  The process is started suspended, space is allocated in the target process, the malicious code is mapped into its memory, and the suspended thread is resumed.  

The code continues on and does some very basic sandbox detection, checking to see if either of these two processes are running:

  1. SandboxieRpcSs.exe
  2. SandboxieDcomLaunch.exe

The script will exit if either process exists, except the injection has already occurred and Stage2 will exit soon anyways. It also retrieves a process list and does nothing with it. There is a decent amount of dead code and my guess is that this was a part of some other malware or planned for future versions of the malware.  At this point, Stage2 finishes execution.      


The final payload is a Remote Desktop Tool known as LuminosityLink that is sold legitimately. It is often found in conjunction with heavily obfuscated AutoIT scripts. I previously wrote a blog detailing a similar sample. The current sample is capable of doing all the things a typical Remote Access Trojan can do for the price of about $40.

LuminosityLink can be built on the fly with the options that control the behavior of the RAT.  Palo Alto published an in-depth analysis of the LumnosityLink family and configuration.

The configuration for this particular sample had all the bells and whistles turned on: C&C URL
3000 C&C Port
Disabled Backup DNS
Disabled Filename
Disabled Folder Name
224c026ae78465524818b86bb34999b6342413cb Mutex
dfsanLMV Config options


Malware authors go to great lengths to hide their malware and increase the length of time it is present on the infected system. String obfuscation, esoteric language choice, and masquerading as a legitimate installer are all ways this malware attempted to remain on the infected system.  

Performing in-depth reverse engineering and sharing accumulated knowledge on the inner workings of the malware, tactics for reversing, and scripts for future samples adds to the body of knowledge in the security community.  

By providing this information, we become stronger as a community and able to respond to threats quickly.