Malware Execution
According to the Twitter post by ESET the wiper is deployed by group policy to the infected system. Once run, as administrator, the system will crash and the following screen will be displayed.
Once the computer is rebooted it crashes and will not start anymore and prompt that it cannot locate the operating system.
Static Analysis
Investigating the time stamp for the sample, it indicates that is compiled on March 14, 2022, showing that it was done just before the attack was conducted.
Looking at the Import Address table, there is only one function called, DsRoleGetPrimaryDomainInformation, indicating that there are more functionalities in the malware that are hidden from static tools.
If the sample is opened in a disassembler, in this case Ghidra, it can be seen that it uses a lot of stack strings for obfuscation.
To investigate the stack strings, and reveal what they are hiding, first the tool FLOSS was run on the sample that gave the following output.
FLOSS static ASCII strings !This program cannot be run in DOS mode. Rich% .text `.rdata @.reloc DsRoleGetPrimaryDomainInformation NETAPI32.dll FLOSS static Unicode strings jjjjjjjj0040113A jjjjjj FLOSS decoded 13 strings C:Users C:Users* FindFirstFileA kernel32.dll D:\ D:\* WriteFile advapi32.dll SetEntriesInAclA LookupPrivilegeValueA DeviceIoControl CreateFileW Wkernel32.dll FLOSS extracted 38 stackstrings C:Users netapi32.dll kernel32.dll advapi32.dll CreateFileA kernel32.dll FindFirstFileA OpenProcessToken CreateFileW AdjustTokenPrivileges Wkernel32.dll FreeSid SetEntriesInAclA AllocateAndInitializeSid LocalFree SetFilePointer LookupPrivilegeValueA LocalAlloc LoadLibraryA GetLastError advapi32.dll FindClose kernel32.dll DeviceIoControl CloseHandle CloseHandle kernel32.dll CloseHandle SeTakeOwnershipPrivilege advapi32.dll \.PHYSICALDRIVE9 kernel32.dll LocalFree FindNextFileA GetFileSize GetCurrentProcess WriteFile SetNamedSecurityInfoA
To give context for the stacked strings the tool CAPA was used to find the different locations in the code where stacked strings are used.
contain obfuscated stackstrings (8 matches) namespace anti-analysis/obfuscation/string/stackstring scope basic block matches 0x401000 0x40114A 0x4011D0 0x401750 0x401A10 0x402025 0x40215E 0x4022A0
To get an overview of the intent of each function in relation to where the different stack strings are used for obfuscation, API calls and libraries are mapped to every function that CAPA found in the sample.
0x401000 kernel32.dll, advapi32.dll, LoadLibraryA, netapi32.dll 0x40114A netapi32.dll, netapi32.dll 0x4011D0 DeviceIoControl, kernel32.dll, CreateFileW, CloseHandle, \.PHYSICALDRIVE9 0x401750 advapi32.dll, LookupPrivilegeValueA, AdjustTokenPrivileges, GetLastErrorc 0x401A10 advapi32.dll, SetEntriesInAclA, AllocateAndInitializeSid, SetNamedSecurityInfoA, kernel32.dll, GetCurrentProcess, OpenProcessToken 0x402025 SeTakeOwnershipPrivilege, FreeSid, LocalFree, CloseHandle 0x40215E FreeSid, LocalFree, CloseHandle 0x4022A0 FindFirstFileA, kernel32.dll, FindNextFileA, CreateFileA, GetFileSize, LocalAlloc, SetFilePointer, WriteFile, LocalFree, CloseHandle, FindClose
Execution Flow
Upon start the wiper uses the API call DsRoleGetPrimaryDomainInformation to check if the computer is the primary domain controller by comparing to the hard coded value 0x5, that comes from the struct DSROLE_MACHINE_ROLE. If it is the primary domain controller it will exit. This is probably done because the threat actor is using the domain controller as the source of distribution of the wiper and not to ruin its own foothold.
The next part of the wiper is the file destruction part. It calls the function 0x4022A0 that iterates over the files, using the API calls that are resolved from the stack strings, and writes over the first 0xA00000 bytes with zeros.
Then the wiper loops through the alphabet (0x18), starting with D all the way up to Z and then one additional iteration, and applies the data destruction from the function in 0x4022A0 to the files in every partition it finds.
This is the last iteration and has gone past Z to Z+1.
Lastly the wiper loops through a list of open raw access to \\.\PHYSICALDRIVE9 – \\.\PHYSICALDRIVE0 and writing to it using IOCTL_DISK_SET_DRIVE_LAYOUT_EX (0x7c054) by using the API DeviceIoControl. By doing so it erases the Master Boot Record.
Detection
Since the wiper is using stack strings for obfuscation of the part that interacts with the disk, that part can be used as Yara rule for detection.
rule caddy_wiper { meta: description = "Search for caddy wiper" author = "Truesec" reference = "truesec.se" date = "2022-03-14" hash1 = "a294620543334a721a2ae8eaaf9680a0786f4b9a216d75b55cfd28f39e9430ea" strings: $x1 = {c6 45 ?? 5c c6 45 ?? 00 c6 45 ?? 5c c6 45 ?? 00 c6 45 ?? 2e c6 45 ?? 00 c6 45 ?? 5c c6 45 ?? 00 c6 45 ?? 50 c6 45 ?? 00 c6 45 ?? 48 c6 45 ?? 00 c6 45 ?? 59 c6 45 ?? 00 c6 45 ?? 53 c6 45 ?? 00 c6 45 ?? 49 c6 45 ?? 00 c6 45 ?? 43 c6 45 ?? 00 c6 45 ?? 41 c6 45 ?? 00 c6 45 ?? 4c c6 45 ?? 00 c6 45 ?? 44 c6 45 ?? 00 c6 45 ?? 52 c6 45 ?? 00 c6 45 ?? 49 c6 45 ?? 00 c6 45 ?? 56 c6 45 ?? 00 c6 45 ?? 45} //Stack strings for \.PHYSICALDRIVE $x2 = {c6 45 ?? 44 c6 45 ?? 65 c6 45 ?? 76 c6 45 ?? 69 c6 45 ?? 63 c6 45 ?? 65 c6 45 ?? 49 c6 45 ?? 6f c6 45 ?? 43 c6 45 ?? 6f c6 45 ?? 6e c6 45 ?? 74 c6 45 ?? 72 c6 45 ?? 6f c6 45 ?? 6c c6 45 ?? 00 c6 45 ?? 6b c6 45 ?? 00 c6 45 ?? 65 c6 45 ?? 00 c6 45 ?? 72 c6 45 ?? 00 c6 45 ?? 6e c6 45 ?? 00 c6 45 ?? 65 c6 45 ?? 00 c6 45 ?? 6c c6 45 ?? 00 c6 45 ?? 33 c6 45 ?? 00 c6 45 ?? 32 c6 45 ?? 00 c6 45 ?? 2e c6 45 ?? 00 c6 45 ?? 64 c6 45 ?? 00 c6 45 ?? 6c c6 45 ?? 00 c6 45 ?? 6c c6 45 ?? 00 c6 45 ?? 00 c6 45 ?? 00 c6 45 ?? 43 c6 45 ?? 72 c6 45 ?? 65 c6 45 ?? 61 c6 45 ?? 74 c6 45 ?? 65 c6 45 ?? 46 c6 45 ?? 69 c6 45 ?? 6c c6 45 ?? 65 c6 45 ?? 57} //Stack strings for DeviceIoControl, kernel32.dll, CreateFileW $a1 = {c6 85 ?? fe ff ff 61 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 64 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 76 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 61 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 70 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 69 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 33 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 32 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 2e c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 64 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 6c c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 6c c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 00 c6 85 ?? fe ff ff 00 c6 85 ?? ff ff ff 53 c6 85 ?? ff ff ff 65 c6 85 ?? ff ff ff 74 c6 85 ?? ff ff ff 45 c6 85 ?? ff ff ff 6e c6 85 ?? ff ff ff 74 c6 85 ?? ff ff ff 72 c6 85 ?? ff ff ff 69 c6 85 ?? ff ff ff 65 c6 85 ?? ff ff ff 73 c6 85 ?? ff ff ff 49 c6 85 ?? ff ff ff 6e c6 85 ?? ff ff ff 41 c6 85 ?? ff ff ff 63 c6 85 ?? ff ff ff 6c c6 85 ?? ff ff ff 41} //Stack strings for advapi32.dll SetEntriesinAclA $a2 = {c6 85 ?? ff ff ff 41 c6 85 ?? ff ff ff 6c c6 85 ?? ff ff ff 6c c6 85 ?? ff ff ff 6f c6 85 ?? ff ff ff 63 c6 85 ?? ff ff ff 61 c6 85 ?? ff ff ff 74 c6 85 ?? ff ff ff 65 c6 85 ?? ff ff ff 41 c6 85 ?? ff ff ff 6e c6 85 ?? ff ff ff 64 c6 85 ?? ff ff ff 49 c6 85 ?? ff ff ff 6e c6 85 ?? ff ff ff 69 c6 85 ?? ff ff ff 74 c6 85 ?? ff ff ff 69 c6 45 ?? 61 c6 45 ?? 6c c6 45 ?? 69 c6 45 ?? 7a c6 45 ?? 65 c6 45 ?? 53 c6 45 ?? 69 c6 45 ?? 64} //Stack strings for AllocateAndInitializeSid condition: uint16(0) == 0x5A4D and any of ($x*) or all of ($a*) and filesize < 50000 }