We are introduced to the challenge with the following message:
Nobody likes analysing infected documents, but it pays the bills. Reverse this macro thrill-ride to discover how to get it to show you the key.
There is a single file
report.xls to analyze, which is an excel document with the old windows format. From the challenge description and the kind of file we are given, we can be sure that we will have to deal with an excel macro.
If we open the document we find the following image within the spreadsheet.
On a real scenario, the contents of this image would serve the purpose of tricking the victim into enabling and executing the embedded macro containing malicious code.
I don’t usually have to deal with macros on my reversing adventures, so I wanted to get the full experience from this challenge. Thus, I will actually allow it to run the embedded macro to see what happens.
WARNING: Never run an office macro outside a controled and isolated environment.
So, let’s enable macros and allow to run. It will inmediately throw an error.
From the title of this window, it appears that the embedded macro failing to run is coded in Visual Basic for Applications (VBA). Clicking in the
Ok button will bring us into a fancy internal VBA IDE where we can explore all the components of the macro.
ThisWorkbook contains very little code that just jumps into the function
Sheet1, which contains some amount of non-trivial code.
Also, we find the intersting form
F with very suspicious data inside that appears to be used within
Sheet1. We will export the
F form for later usage (
Right click ->
If we google for a couple minutes about VBA macros related to malware spread, we quickly find some recent information about a technique known as VBA stomping. Essentially, this technique leverages a mismatch between the p-code (the compiled intermediate represenation bytecode of the VBA macro) and the code that is presented in clear text on the editor.
If you are interested, you can read more about VBA stomping in the following references:
This really appears to be what is going on with
report.xls. Thus, we will continue by dumping the real p-code and obtaining its actual VBA representation so we can analyze/run it.
I decided to (re)construct a valid document to embed and launch the fixed VBA macro within
Sheet1. First of all, we create a new document, go to developer tab (will need to enable developer tab on excel) and click the first icon
Now, we import the figure
F saved before (Right click on project -> Import File… -> F.frm)
and copy-paste the contents of previous
ThisWorkBook into the newly created one.
Dump and decompile p-code
We dump the embedded p-code with pcodedmp into
pcodedmp -o macro.pcode report.xls
Among other info, the file
macro.pcode contains the p-code formatted output for the code in
Sheet1. Now we can decompile the p-code back into VBA with pcode2code.
pcode2code -p macro.pcode > decompiled
If we take a look into the decompiled code we will see some small parts that have not been sucessfully decompiled. Despite that, we observe enough difference with the code presented to us initially, confirming our hypothesis of mismatching between the high level language representation and the embedded p-code. Essentially,
folderol function now has a new buffer variable and some new code that plays with it.
There are also some minor errors in data types that we will need to fix if we want it to run. Moreover, we find a bunch of checks and protections that will have to be bypassed in order for the code to properly execute. Thus, let’s copy the decompiled code into
Sheet1 in the newly created document and fix stuff as we go.
Fix and clean decompiled code
- In the usage of
onzowe see that they need to be array based types, so we just add a pair of missing parentheses to their type in its declaration.
xertzis no longer used, so we can remove its declaration and assignment.
- The following body that failed to decompile:
Ld fn Sharp LitDefault Ld wabbit PutRec
essentially translates to
Put #fn, , wabbit.
- Return type has to be
kerfufflevariable needs to be an array as well, so we add parentheses to its type.
Note: Without knowing
nothingtoo much about VBA or p-code, we can guess most fixes from the contents of the VBA code displayed in the original file, and just check that the corresponding p-code makes sense.
Bypass checks and protections
Remove everything before
rigmarolefunction. We do not worry of internet checks and names, as we will bypass it.
Entirely remove the condition using
GetInternetConnectedStateto check for internet connection.
Then, we have a routine that appears to be doing some simple anti-vm checks. As nothing coming out from this part will be used, we can literally remove it entirely as well.
Now we see
firkinvariable being loaded (with device name) and compared to the string returned by calling
rigmarole(onzo(3)). We could be tempted to bypass the conditional, but we see that
firkinis actually used afterwards, so we better get this value right by simply assigning to it the correct value
firkin = rigmarole(onzo(3))
If you are curious, you could play with the integrated debugger to see what values are actually used and expected for the anti-vm checks and the
firkinwill be loaded with the string
After we have cleaned and fixed everything, as well as bypassing all the checks, we obtain the resulting code for
Function rigmarole(es As String) As String Dim furphy As String Dim c As Integer Dim s As String Dim cc As Integer furphy = "" For i = 1 To Len(es) Step 4 c = CDec("&H" & Mid(es, i, 2)) s = CDec("&H" & Mid(es, i + 2, 2)) cc = c - s furphy = furphy + Chr(cc) Next i rigmarole = furphy End Function Function folderol() Dim wabbit() As Byte Dim fn As Integer: fn = FreeFile Dim onzo() As String Dim mf As String Dim buff(0 To 7) As Byte onzo = Split(F.L, ".") firkin = rigmarole(onzo(3)) n = Len(firkin) For i = 1 To n buff(n - i) = Asc(Mid$(firkin, i, 1)) Next wabbit = canoodle(F.T.Text, 2, 285729, buff) mf = Environ(rigmarole(onzo(0))) & rigmarole(onzo(11)) Open mf For Binary Lock Read Write As #fn Put #fn, , wabbit Close #fn Set panuding = Sheet1.Shapes.AddPicture(mf, False, True, 12, 22, 600, 310) End Function Function canoodle(panjandrum As String, ardylo As Integer, s As Long, bibble As Variant) As Byte() Dim quean As Long Dim cattywampus As Long Dim kerfuffle() As Byte ReDim kerfuffle(s) quean = 0 For cattywampus = 1 To Len(panjandrum) Step 4 kerfuffle(quean) = CByte("&H" & Mid(panjandrum, cattywampus + ardylo, 2)) Xor bibble(quean Mod (UBound(bibble) + 1)) quean = quean + 1 If quean = UBound(kerfuffle) Then Exit For End If Next cattywampus canoodle = kerfuffle End Function
Taking a quick look at the code, it is clear that the data present in the form
F is being decrypted into a buffer that eventually gets written as an image into the spreadsheet itself.
One approach could have been to reimplement this decrypting algorithm externally, but as we already fixed the actual decompiled code and bypassed all checks, we will be able to simply run it and let it do all the work for us. Therefore, if we now just run the macro, we will get in the spreadsheet the resulting image with the flag:
Thus, we obtained the flag
[email protected] and completed the challenge.