Flare-On CTF 2020 Challenge 4: report
Challenge
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.
Observations
This challenge provides us with an Excel file that obviously contains a macro. We can view the macro using the developer tab in Excel:
Private Declare Function InternetGetConnectedState Lib "wininet.dll" _
(ByRef dwflags As Long, ByVal dwReserved As Long) As Long
Private Declare PtrSafe Function mciSendString Lib "winmm.dll" Alias _
"mciSendStringA" (ByVal lpstrCommand As String, ByVal _
lpstrReturnString As Any, ByVal uReturnLength As Long, ByVal _
hwndCallback As Long) As Long
Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" _
(ByVal lpszLongPath As String, ByVal lpszShortPath As String, ByVal lBuffer As Long) As Long
Public Function GetInternetConnectedState() As Boolean
GetInternetConnectedState = InternetGetConnectedState(0&, 0&)
End Function
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 xertz As Variant
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)
wabbit = canoodle(F.T.Text, 0, 168667, xertz)
mf = Environ(rigmarole(onzo(0))) & rigmarole(onzo(1))
Open mf For Binary Lock Read Write As #fn
Put #fn, , wabbit
Close #fn
mucolerd = mciSendString(rigmarole(onzo(2)) & mf, 0&, 0, 0)
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
Reversing the Macro
The rigmarole
function appears to deobfuscate the interesting strings for the script.
We can rewrite it in Python and give it the constant data to decode.
fl = "9655B040B64667238524D15D6201.B95D4E01C55CC562C7557405A532D768C55FA12DD074DC697A06E172992CAF3F8A5C7306B7476B38.C555AC40A7469C234424.853FA85C470699477D3851249A4B9C4E.A855AF40B84695239D24895D2101D05CCA62BE5578055232D568C05F902DDC74D2697406D7724C2CA83FCF5C2606B547A73898246B4BC14E941F9121D464D263B947EB77D36E7F1B8254.853FA85C470699477D3851249A4B9C4E.9A55B240B84692239624.CC55A940B44690238B24CA5D7501CF5C9C62B15561056032C468D15F9C2DE374DD696206B572752C8C3FB25C3806.A8558540924668236724B15D2101AA5CC362C2556A055232AE68B15F7C2DC17489695D06DB729A2C723F8E5C65069747AA389324AE4BB34E921F9421.CB55A240B5469B23.AC559340A94695238D24CD5D75018A5CB062BA557905A932D768D15F982D.D074B6696F06D5729E2CAE3FCF5C7506AD47AC388024C14B7C4E8F1F8F21CB64"
def rigmarole(data):
furphy = ""
for i in range(0, len(data), 4):
c = int(data[i: i + 2], 16)
s = int(data[i + 2: i + 4], 16)
cc = c - s
furphy = furphy + chr(cc)
return furphy
onzo = fl.split(".")
for i in range(len(onzo)):
print("{}: {}".format(i, rigmarole(onzo[i])))
The unobfuscated strings are:
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
Cleaning up the folderol
function, we can see that the script first searches for any processes that might indicate it is running inside a VM.
It then uses the canoodle
function to decode what appears to be the contents of an MP3 file and attempts to play it.
Set fudgel = GetObject("winmgmts:\\.\root\CIMV2")
Set twattling = fudgel.ExecQuery("SELECT Name FROM Win32_Process", 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), "vbox")
If pos > 0 Then
MsgBox "Sorry, this machine is not supported.", vbCritical, "Error"
End
End If
Next
xertz = Array(&H11, &H22, &H33, &H44, &H55, &H66, &H77, &H88, &H99, &HAA, &HBB, &HCC, &HDD, &HEE)
wabbit = canoodle(F.T.Text, 0, 168667, xertz)
mf = Environ("AppData" & "\Microsoft\stomp.mp3")
Open mf For Binary Lock Read Write As #fn
Put #fn, wabbit
Close #fn
mucolerd = mciSendString("play" & mf, 0&, 0, 0)
We can rewrite the canoodle
function in Python and manually decode the stomp.mp3 file.
ft = "58c7661f00634702555f664b7756884c864edc4fef2d9c48881bac0911082214334e424f552f661d7752ce41d54deb70e9468949892db745545270fc333c44aa5525634f772d88699970983b8b18fe1eed3aba1d584c763201724431553e66295a2888269941aa20ef72a435b4359d36312b4b6f4048643d3b3b0927034ca846ee36c295da80b8d9fd3b97d37e51577113dc37cb3dd209a60246e43cfd488a42d938a953fd2a82ee7e8b4d9d582c2dc83b7101d057cee978ed008453950be22c89fdbea6548c106d33c344e4552e6ef87782880b9901fa5b95bcecc09e0c81ff75bd479033d24430558f66fe77b288b39961aabcbb9ccc42ddc5ee33112122e333d944ad55d4666277a78895998faaa6bbc5cca9dd12eeba112e22bf337844d9559066a077c288da998eaa3dbbb4cc38ddb9ee6011f2223233e3443d55f3664a77b2885699d4aa76bb2cccbfddeeeeda118122d0331b44bd552c66bf77af8845997baa32bb92cc7addadeebb11312211339944a855a6669577ec887699b6aab8bb8bccc0dd36ee03116f221833a344b655f366e17750882a9946aa82bbabccbdddb8eeb21152229233ed44de555766af7701888499d4aabbbb85ccb6dd37eebf11d1225833d244bf55ad66e8772e88ca99d3aaf6bb72cc49dd7ceedd1124229733bd443455fd661677b088a699f3aa87bb31ccdadd ...snip..."
def canoodle(panjandrum, offset, s, bibble):
kerfuffle = b""
quean = 0
for i in range(0, len(panjandrum), 4):
kerfuffle += bytes([int(panjandrum[i + offset : i + offset + 2], 16) ^ bibble[quean % len(bibble)]])
quean += 1
if len(kerfuffle) < quean:
break
return kerfuffle
xertz = [
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE
]
wabbit = canoodle(ft, 0, 168667, xertz)
with open("stomp.mp3", "wb") as f:
f.write(wabbit)
stomp.mp3 contains a short audio clip of some stomping sounds. The title warns us that we are not looking in the right place.
The use of the phrase stomp is probably a hint that VBA stomping is present in this macro. VBA macros can exist in three different executable forms that can conceal some of its functionality. We can use pcodedmp.py to view the p-code version.
None
stream : _VBA_PROJECT_CUR/VBA/ThisWorkbook - 1785 bytes
########################################
Sub Workbook_Open()
Sheet1.folderol
End Sub
Sub Auto_Open()
Sheet1.folderol
End Sub
stream : _VBA_PROJECT_CUR/VBA/Sheet1 - 10518 bytes
########################################
...no change...
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
stream : _VBA_PROJECT_CUR/VBA/F - 1388 bytes
########################################
In this version of the macro, we can see that slightly different arguments are passed to the canoodle
function.
It also appears that the decoded binary data is now displayed as a picture.
The image can be successfully decoded with some minor modifications to the Python script.
key = b"FLARE-ON"
buff = list(reversed(key))
wabbit = canoodle(ft, 2, 285729, buff)
with open("flag.png", "wb") as f:
f.write(wabbit)