Challenge 4 - Report
Description
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.
Download - report.7z
Contents
Solution
Intro
tl;dr: Dump macro using pcode2code, extract strings from spreadsheet, convert VB -> Python, run Python script to get flag.
For this challenge, we’re given a single Excel file, report.xls
. The description hints at an infected document, but its always good practice to open files you don’t trust in a VM. Here, I continue to use FLARE-VM.
Initial Analysis
Opening up report.xls
in OpenOffice, we’re greeted with the following message prompting us to “Enable Content”:
This is a common technique I see in malicious Word/Excel documents attempting to trick users into enabling macros, which will allow the malicious macro to run.
I’m not going to enable macros just yet, I decide to perform some static analysis and see what I can find.
Dumping Code with pcode2code
I use pcode2code
to get back the original VBA code from the document. P-code or pseudo-code, is an intermediate language that VBA gets compiled to and what actually gets excuted. pcode2code decompiles p-code back to VBA. The VBA code is shown below, there were some decompilation errors and extra, unnecessary code added.
Sub Workbook_Open()
Sheet1.folderol
End Sub
Sub Auto_Open()
Sheet1.folderol
End Sub
########################################
Function rigmarole(es As String, id_FFFE 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(id_FFFE As Variant)
Dim wabbit As Byte
Dim fn As Integer: fn = FreeFile
Dim onzo As String
Dim mf As String
Dim xertz As Variant
Dim buff(0 To 7) As Byte
onzo = Split(F.L, ".")
If GetInternetConnectedState = False Then
MsgBox "Cannot establish Internet connection.", vbCritical, "Error"
End
End If
Set fudgel = GetObject(rigmarole(onzo(7)))
Set twattling = fudgel.ExecQuery(rigmarole(onzo(8)), , 48)
For Each p In twattling
Dim pos As Integer
pos = Instr(LCase(p.Name), "vmw") + Instr(LCase(p.Name), "vmt") + Instr(LCase(p.Name), rigmarole(onzo(9)))
If pos > 0 Then
MsgBox rigmarole(onzo(4)), vbCritical, rigmarole(onzo(6))
End
End If
Next
xertz = Array(&H11, &H22, &H33, &H44, &H55, &H66, &H77, &H88, &H99, &HAA, &HBB, &HCC, &HDD, &HEE)
Set groke = CreateObject(rigmarole(onzo(10)))
firkin = groke.UserDomain
If firkin <> rigmarole(onzo(3)) Then
MsgBox rigmarole(onzo(4)), vbCritical, rigmarole(onzo(6))
End
End If
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
' a generic exception occured at line 68: can only concatenate str (not "NoneType") to str
' # Ld fn
' # Sharp
' # LitDefault
' # Ld wabbit
' # PutRec
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, id_FFFE As ) As Append
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
There’s some obfuscation here in the form of random variable and function names, but nothing too crazy.
Analyzing VBA Code
Theres’ only 3 functions we need to analyze, folderol
, canoodle
, and rigamarole
. I start with folderol
since this function is the first to run when the workbook is opened, from the Workbook_Open()
function above.
folderol function
Function folderol(id_FFFE As Variant)
Dim wabbit As Byte
Dim fn As Integer: fn = FreeFile
Dim onzo As String
Dim mf As String
Dim xertz As Variant
Dim buff(0 To 7) As Byte
onzo = Split(F.L, ".")
If GetInternetConnectedState = False Then
MsgBox "Cannot establish Internet connection.", vbCritical, "Error"
End
End If
Set fudgel = GetObject(rigmarole(onzo(7)))
Set twattling = fudgel.ExecQuery(rigmarole(onzo(8)), , 48)
<snip>
There’s a lot to cover for this function, so I’ll break it up. The first thing I noticed was the Split
function on F.L
and assigning to the onzo
variable. So I open the excel sheet in OpenOffice again and after some digging, found a user form in the macro editor with label name L
and value of (I added line-breaks for readability):
9655B040B64667238524D15D6201.
B95D4E01C55CC562C7557405A532D768C55FA12DD074DC697A06E172992CAF3F8A5C7306B7476B38.
C555AC40A7469C234424.
853FA85C470699477D3851249A4B9C4E.
A855AF40B84695239D24895D2101D05CCA62BE5578055232D568C05F902DDC74D2697406D7724C2CA83FCF5C2606B547A73898246B4BC14E941F9121D464D263B947EB77D36E7F1B8254.
853FA85C470699477D3851249A4B9C4E.
9A55B240B84692239624.
CC55A940B44690238B24CA5D7501CF5C9C62B15561056032C468D15F9C2DE374DD696206B572752C8C3FB25C3806.
A8558540924668236724B15D2101AA5CC362C2556A055232AE68B15F7C2DC17489695D06DB729A2C723F8E5C65069747AA389324AE4BB34E921F9421.
CB55A240B5469B23.
AC559340A94695238D24CD5D75018A5CB062BA557905A932D768D15F982D.
D074B6696F06D5729E2CAE3FCF5C7506AD47AC388024C14B7C4E8F1F8F21CB64
Next, the function checks for an internet connection and if there is none, end the program. The next 2 set of lines assign values to fudgel
and twattling
based on output from the rigamarole
and onzo
. I’ll decipher these two next.
rigamarole and onzo
It was fairly straightforward to convert the VB code to Python, which I have below:
def rigmarole(es):
furphy = ""
c = 0
s = ""
cc = 0
for i in range(0, len(es), 4):
c = int(es[i:i+2],16)
s = int(es[i+2:i+4],16)
cc = c - s
furphy += chr(cc)
return furphy
Looking at the code, rigmarole
is a string decoder function.
- First, it iterates through the input in 4 character chunks and splits them into 2 char pairs, interpreted as hexadecimal.
- Next, the second byte is subtracted from the first byte.
- Finally, the unicode representation of the result is appended to the output string which is then returned.
Next, I decode the onzo
array and print out the decoded strings along with their indices.
0: AppData
1: \Microsoft\stomp.mp3
2: play
3: FLARE-ON
4: Sorry, this machine is not supported.
5: FLARE-ON
6: Error
7: winmgmts:\\.\root\CIMV2
8: SELECT Name FROM Win32_Process
9: vbox
10: WScript.Network
11: \Microsoft\v.png
Now I’m pretty confident what the code is doing and begin translating the rest of the code from VB to Python. The folderol
function also does other sandbox detection and anti-analysis techniques that I won’t convert to Python. For example, it checks if we’re in a VM/Virtual Box and if the computer is part of the FLARE-ON domain.
I was able to get the T
value by opening up the spreadsheet in LibreOffice instead of OpenOffice. For some reason I was running into memory issues and the T value was getting severely truncated in OpenOffice.
Also, in the original VB script, the code would insert the picture directly into the spreadsheet; however, I’ve modified the Python code to just save the picture to disk.
I snipped the F
and T
values as they were extremely long, you can find the full source code here
Solver Script
F = "9655B040B...."
T = "58c7661f0063470255...."
def rigmarole(es):
furphy = ""
c = 0
s = ""
cc = 0
for i in range(0, len(es), 4):
c = int(es[i:i+2],16)
s = int(es[i+2:i+4],16)
cc = c - s
furphy += chr(cc)
return furphy
def folderol():
wabbit = list()
onzo = ""
mf = ""
buff = [None] * 8
onzo = F.split(".")
fudgel = rigmarole(onzo[7])
twattling = rigmarole(onzo[8])
## Print out obfuscated instructions
for x in range(len(onzo)):
print(str(x) + ":" + " " + rigmarole(onzo[x]))
firkin = "FLARE-ON"
n = len(firkin)
for i in range(n):
buff[n-i-1] = ord(firkin[i])
wabbit = canoodle(T, 2, 285729*2, buff)
wabbit_bytes = bytearray(wabbit)
print(len(wabbit))
with open("v.png", "wb") as f:
f.write(wabbit_bytes)
def canoodle(panjandrum, ardylo, s, bibble):
## Don't initialize with None and then try to "fix" it by removing all None. Needed as spacing
kerfuffle = [0x00] * s
quean = 0
for i in range(0, len(panjandrum), 4):
offset = i + ardylo
kerfuffle[quean] = int(panjandrum[offset:offset+2], 16) ^ bibble[quean%(len(bibble))]
quean += 1
if quean == len(kerfuffle):
break
return kerfuffle
folderol()
Flag
Running the code above creates v.png
in the current directory, giving us the flag.
Flag: thi5_cou1d_h4v3_b33n_b4d@flare-on.com