Dylan's Blog

2020 Flare-On Challenge 4 Writeup: Report

Category: CTF

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

Tools Used

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”:

Message

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):

UserForm

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.

  1. First, it iterates through the input in 4 character chunks and splits them into 2 char pairs, interpreted as hexadecimal.
  2. Next, the second byte is subtracted from the first byte.
  3. 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.

v

Flag: thi5_cou1d_h4v3_b33n_b4d@flare-on.com