Locky/Zepto - Dissecting latest weaponized delivery and payload
We noticed a phishing campaign where locky downloader as HTML application (.hta) files were being sent across in files with extensions dzip or (x)zip to harvested email addresses, and the threat actor relied on the recipient's intuition to unzip the file and then run the .hta file which is named randomly
The .hta file is basically a couple of HTML tags and a multi-level obfuscated JScript, which involved multiple rounds of decoding strings required to run the malicious code and to contact the malicious domains for downloading the Locky payload.
I decided to remove tall HTML tags and place "debugger" keyword in the top of the script to kick in the script debugger
I used cscript with //X to execute the js file
Got prompted from the Visual Studio JIT debugger about an unhandled exception, where I say yes start debugging
I see multiple arrays blobs and string blobs, which obviously will be used to decode the required strings etc.
The SAMBADIGA function is responsible for the rounds of manipulation and concatenations to come up with strings:
We can see strings like %TEMP%, ExpandEnvironmentString, Run, .dll, ActiveXObject, substring etc coming into the picture, which indicates that there could be a dll involved, which might get downloaded in the appdata/local/temp folder of the user.
Another huge string blob called SIMOONA. This blob will be used to create another script on runtime which will be eval'd and executed as you will see ahead. We can see below in the watch window that a script is being created "function semkiel..."
We see first eval, where nothing interesting can be seen
We see ActiveX objects created with obfuscated names but from the methods we can figure out that this object is probably an XMLHTTPRequest object:
Here we see that the script created on runtime is being evaluated (eval) and the code flow is transferred inside it
We can see the malicious URL address to download the next stage binary. We can see another important string in clear text "User-Agent", which indicates that this is where the request is being created to download the file.
We can see the file which is going to be downloaded in the temp folder "dkqFsIImG1"
We can see ADODB.Stream object is being instantiated:
The threat actors seem to be fan of Frank Sinatra. The FrankSinatraLaa string which is still undefined will be of interest.
Here we see the open function of the stream object is being called
If download from the previous domain fails then the next malicious URL is created to download the malicious extension less file
Once the file is downloaded we can see its content in the fileString
We can see that the contents of the ADODB.Stream is being written to the file
We can see the response is 200 OK from the server
We can see data from the file is being read
I also found another domain contacted to download the file:
One troubling this at this point was that I was not able to proceed beyond this point as I was not able to see this downloaded file being either renamed with a dll extension and executed. I realized that there must be some anti-analysis or anti-debugging technique deployed in the code, which I was able to bypass. You can see the variable .....FrankSinatraLaa being checked if it is less than < 30000 and two other array members checked for some values and returning false.
I bypassed these and can see the name of the dll below
Next the code flows again into the script created on runtime:
Decrypting downloaded file into a dll
We can see the MZ header for binary being joined infront of the decrypted dll
In the end, the dll is executed using the Wscript.shell object's "Run" method
The dll is executed and the parameter passed on are "qwerty"
I killed the process and opened Olly and on some initial analysis of the binary I realized that this is some packed or encrypted code as not many sensible strings were returned on string search and also the IAT comprised only of one library: kernel32.dll.
I started debugging the dll. The first thing interesting I come across is a VirtualAlloc call where a new section of virtual memory is allocated within the process. The base address returned is 00730001. Unfortunately this will not remain consistent in the future screenshots as I had to terminate and start debugging again and the newly returned base address is different. Let us call this MMA1 (Malicious Memory Area 1). Below we see bytes being populated in this area to come up with malicious code
Basically the malware is traversing the below block of bytes in its data section and fetching bytes in a pattern of 1 bytes 1 byte and 5 bytes and placing them in the virtually allocated memory space MMA1. Basically skipping all "`" bytes:
Total no of bytes written are 73199 bytes in MMA1
Next we see a function called whereby the return value is interesting "905A384D", where we can see bytes 5A and 4D (M and Z), which could mean that there is another binary coming on the way.
Next we see that this return value is placed in the beginning of the MMA1
After some more rounds of deobfuscation we can see a binary emerging in the MMA1 with all the MZ, This Program .. section names rdata etc:
Next we see that another VirtualAlloc call is made where some more malicious code is placed, lets call this MMA2 (Malicious Memory Area 2). This area basically contains the code which is responsible for decoding the APIs and calling them to perform the next malicious steps in the infection cycle:
MMA2 starts with a function prologue
So basically looking at Olly's memory map screen we can see the two MMA1 and MMA2 both RWE
The linear address of FS PEB is acquired to reach to the image base for the running dll
Now the code flow has to be transferred to MMA2, so that it can do its job. This is done by calling the function prologue present in the begininng of the MMA2. Please note that since I re-ran the debugger the MMA2 address is changed from 7600000 to 1370000 and MMA1 address is changed from 730000 to 1340000. When this function is called in MMA2 the parameter which is passed on is the Image base of the running DLL:
Once inside MMA2, we can see that PEB is again traversed to reach to the base address of Kernel32.dll 76000000h in the current memory space.
Now trying to get pointer to the GetProcAddress function of the kernel32.dll module by walking the export table of kernel32.dll
Next we see that GetProcAddress of kernerl32.dll is used to get pointer to the process "LoadLibrary" of also kernel32.dll. Once this pointer is acquired than any library can be loaded using this pointer address.
We see several libraries being loaded like advapi32.dll, user32.dll
Next we see the pointer to function "VirtualAlloc" of Kernel32.dll is acquired.
And then for VirtualProtect, VirtualFree, ExitThread, RegOpenKeyExA, RegQueryValueExA, RegCloseKey, GetSystemWindowsDirectoryA, GetVolumeInformation, GetUsernameA, CharUpperBuffA and many more to accomplish its malicious tasks:
Here we can see all three MMAs: MMA1 1340000 RWE (the still unformed binary starting with M8Z..), MMA2 1370000 RWE (the code which is running now to perform the evil workload) and MMA3 1380000 RW:
Interestingly we can see bytes being copied from MMA1 to MMA3 and a properly formed binary is being dumped in the MMA3 RW region:
Once the bytes are copied from the 1340000h memory allocation, then it is released using VirtualFree API call:
We see that VirualProtect is being used to change the protection starting from the image base of the currently running DLL till 155648 bytes onwards. This is to bypass Windows DEP (Data Execution Prevention) mechanism: You can see that in the below screenshot the PE header is R only, text is RE, data is RW and reloc is R only:
After the API call:
The malware memory resident code in MMA2 seems to be 00ing (emptying) its binary of origin which is the DLL loaded initially on imagebase 10000000!
We can see in the memory the zeroed section:
Next we see a function call where MMA3 01380000 (pointer to the binary created in memory) passed as parameter:
As expected the emptied out section is being populated by the new binary in MMA3 01380000:
The question is why need for MMA3, when evil code in MMA2 could have directly written from MMA1 to the emptied out original dll?
Now we see various sections being created for the new resident of the original emptied out dll:
Job of MMA3 RW is done, so it is freed:
The malware then loads kernel32.dll and gets the addresses of various functions and I believe it is trying to create IAT for the newly populated binary by using GetProcAddress. We can see kernel32.dll being loaded:
Why is this guy getting pointer to function GetProcAddress using GetProcAddress is beyond me!
Then it gets pointer to function GetModuleHandleA and places it in the supposedly IAT of the newly created binary:
Some other interesting procedures whose addresses are acquired:
FindNextFileW, GetLogicalDrives, GetTickCount, LoadLibraryA, GetVolumeNameForVolumePointA, CreatThread, Sleep, SetUnhandleExceptionFilter, SetFileTime, GetSystemTimeasFileTime, SetFilePointer, DeleteFile, MoveFileExW, VirtualAlloc, IsDebuggerPresent,
Similarly it iterates through User32.dll, GDI32 to get procedure addresses and then it loads ADVAPI32.DLL and the function it gets the address of from this module: CryptDestroyHash, AccessCheck, MapGenericMask, DuplicateToken, OpenThreaToken, GetFileSecurityW, CryptGetKeyParam, CryptSetHashParam, CryptHashData, SetTokenInformation, OpenProcessToken, CryptCreateHash, RegSetValueExa, FreeSID, SetSecurityDescriptorDacl, SetEntriesOnACLA, CryptAcquireContextA, CryptGenRandom, CryptReleaseContext, CryptEncrypt, CryptSetKeyParam, CryptImportKey, CryptDestroyKey,
Then comes SHELL32.DLL, OLEAUT32.DLL, WININET.DLL. Interesting functions in WININET.DLL: HttpSendRequestA, HttpEndRequestA, HttpQueryInfoA, InternetWriteFile, HttpSendRequestExA, HttpAddRequestHeaderA, HttpOpenRequestA, InternetCrackURLA, InternetOpenA, InternetSetOptionA, InternetReadFile, InternetConnectA
MPR.DLL (Multiple Provider Router): WNetEnumResourceW, WNetAddConnection2W, WnetOpenEnumW
Now we see a call to a location at the offset of the newly occupied binary 1000CA7A, entry point to the new dynamically created payload:
We see some signs of timestomping
I also see some classic anti-debugging related API calls like GetTickCount, QueryPerformanceCounter
I tried patching the return value of QueryPerformanceCounter but was not able to defeat the anti-debugging
So I knew that a new binary is inserted into the place of the original dll, which is gone now. Plus the IAT was also created by the malicious code in MMA2. I thought of dumping the newly created DLL and used Olly's plugin OllyDump to dump the newly created DLL from memory on the entry point which was called above:
The MD5 of all three (the original downloaded payload, the original dll and the now dumped DLL):
I took a look at the strings contained in the newly created dll and knew that this is not packed anymore as I can see all the interesting stuff like:
XMLHttpRequest object, affid parameter used in URL of ads
Cryptography related APIs
Internet Connection and HTTP requests related APIs. Some anti debugging calls
I also see Locky related strings like the zepto extension which is appended to the encrypted files, the "HELP instructions" to help the victim (basically instructions on how to get their files decrypted), and many file extensions which are probably going to be encrypted:
I also see string related to deletion of Volume Shadow Copies quietly so that the user has no backup left:
Since the anti-debugging was troubling me, even though I had tried patching return value of anti-debugging APIs, enabled Olly's plugins to defeat anti-debugging, I decided to run the dumped DLL from command prompt and attach Olly to the running DLL, hoping not to miss the good stuff:
While getting attached I was also noticing Process Monitor for signs of Locky activating and accessing hundreds of files and encrpyting them, but I was quick enough to attach and could not see any sign of that in Process Monitor:
I can see all the API imported in the rdata section of the dumped DLL in clear text:
Execution began from this point onwards
First crypto context is initialized and some random data is acquired using CryptGetRandom and then I can see CryptCreateHash being called to create an MD5 Hash (8003h) the second parameter to the function of the acquired random data:
I also see the ransomware trying to enumerate domain information to see if the victim machine is part of a domain
I see references to some interesting strings including some TLDs like ru, info, biz, work, pl, org, pw, xyz: But strangely this sample does not communicate at all on the internet and does not communicate with any C2. This could be a standalone variant.
It seem to be encrypting all sort of files including .class, media files, .wallet files,
I also observer the execution of vssadmin.exe to quietly delete the VSS copies:
I see some random text generate from a pool of characters using an algorithm. The below random text "IZDK8GUTEN84A8QN" is actually the personal identificaiton ID mentioned in the ransom note below. It is created randomly.
We can see that WNetEnumResources is called to enumerate network resources:
It tries to enumerate logical drives connected to the victim machine:
Returns 0x3 (fixed drive)
I see vssapi.dll being loaded, to delete shadow copies using an unusual method of using APIs exposed by the dll. However it can also use classic vssadmin command to delete the shadows as evident by the strings found.
There are some more anti-debugging related called GetTickCount and RDTSC in the middle of the code before execution of the encryption process:
I was forced to stop debugging from this point on-wards. I came up with another strategy where I executed the payload and waited for the process monitor to show me when the rundll32 process starts accessing several files to encrypt them. Once I saw that happening, I attached my debugger again to the process.
Some Threat Intelligence from Avast: "https://blog.avast.com/zepto-ransomware-now-introduces-new-features-to-better-encrypt-your-files"
So as per my observation and also as per the blog, this is a fully functional offline mode variant, whereby it does not communicate with C2 for commands but simply encrypts files sequentially and deliver the note.
The malware looks checks for file permissions before proceeding to encrypt the file:
I see a call to CryptImportKey where the key is being transferred to the MS CSP (Cryptographic Service Provider):
One of the parameters passed on to the CryptImportKey is pbData which is a BYTE array which contains a PUBLICKEYSTRUC BLOB header and the encrypted key.
When I looked at the structure definition, I see that the first element bType is the type of key which follows the structure.
This in our case is a public key. Also confirmed by the ALG_ID, showing that it is RSA encryption which is being used.
We can also see an interesting way to rename the files by using the MoveFile API call:
"Zepto uses an interesting method when renaming files and replacing the original file content via the MoveFileExW API function with set dwFlags=0x9 (MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH),which also provides a "lock" file during the encryption process." Ref: https://blog.avast.com/zepto-ransomware-now-introduces-new-features-to-better-encrypt-your-files
After setting the CryptSetKeyParam
We see call to the actual encryption function CryptEncrypt:
We can see the public key which is being used to encrypt the file in the below highlighted section of memory dump
The file buffer before encryption:
The file buffer after encryption:
We see files renamed along with the HELP instructions HTML file:
The encryption thread exits but the process rundll32.exe keeps running
Now within the rundll32.exe the ransomware find the HTML and BMP file for the Desktop presentation, so it uses the BMP file to set it as the background of the desktop (setting registry key value) and also drops the HTML help file on the desktop:
The file used as desktop screensaver
I decided to visit the TOR site which was mentioned in the ransom note using the TOR browser
The page is asking you to buy the decryptor which will cost 3 BTC and they provided a BTC address as well to make the payment to:
BC Address: 1LbUcGJpijAoeGekdFzzC8BxJLQBKXMoGs
Hash 160: d6ef941e6cdd7d48b1a08b0b24e8aec392a18237
I tried seeing in the blockchain info website on what this guy has earned, but could not find any transactions. Maltego also did not bring in any interesting results. I can only assume that this variant of Locky or Zepto is using highly obfuscated JSscript in an ancient .hta format as downloader, to thwart maybe extension blocking at gateways and is RSA public key algorithm for offline mode operations and that is why does not need to communicate with the C2 server and the threat actor has set up a new BTC wallet to receive payments.
I would highly appreciate some help in identifying the anti-debugging techniques deployed and how to defeat them. Hope you enjoy this post.