|
import re, sys, os |
|
import string |
|
import argparse |
|
import json |
|
|
|
class DispCallFuncGenerator: |
|
|
|
# & -> Long |
|
# $ -> String |
|
# % -> Integer |
|
# # -> Double |
|
# ! -> Single |
|
# @ -> Decimal |
|
SymbolNameRegexSingle = r'\w_\$\&' |
|
|
|
VariableNameRegexSingle = SymbolNameRegexSingle |
|
SymbolNameRegex = r'[' + SymbolNameRegexSingle + r']+' |
|
VariableNameRegex = r'[' + VariableNameRegexSingle + r']+' |
|
|
|
regexes = { |
|
'functionParams' : r'^(?:(?:Public|Protected|Private)\s+)?(?:(?<!End)(?:Function|Sub)\s+)' + SymbolNameRegex + r'\s*\((?![\(])(.*)(?<![\)])\)', |
|
|
|
'functionParamNameAndType' : r'(ByVal|ByRef)?\s*((?!ByVal|ByRef)' + VariableNameRegex + r')(?:\s+As\s+(\w+))?', |
|
|
|
'functionParamNameAndType2' : r'(?<=ByVal|ByRef)\s*(' + VariableNameRegex + r')\s+As\s+([\w_\.]+)', |
|
|
|
'declareFunction' : r'(?:Private|Protected|Public)?\s*Declare\s+(?:PtrSafe\s+)?(?:Sub|Function)\s+(' + SymbolNameRegex + r')\s+Lib\s*#?"([^"]+)"#?(?:\s*Alias\s*#?"([^"]+)"#?)?', |
|
|
|
'declareFunctionEnd' : r'.*\)\s*(?:As\s*([^\s]+)\s*)?\s*$', |
|
} |
|
|
|
Auto_Runs = ( |
|
'AutoOpen', 'Auto_Open', 'Document_', 'Workbook_', 'Document', 'Workbook' |
|
) |
|
|
|
callconv = { |
|
'fastcall': 0, |
|
'cdecl': 1, |
|
'mscpascal': 2, |
|
'pascal': 2, |
|
'macpascal': 3, |
|
'stdcall': 4, |
|
'fpfastcall': 5, |
|
'syscall': 6, |
|
'mpwcdecl': 7, |
|
'mpwpascal': 8, |
|
'max': 8, |
|
} |
|
|
|
def __init__(self): |
|
self.functions = {} |
|
self.addedResolveFuncsCall = False |
|
|
|
def extractDeclares(self, declares): |
|
lines = declares.split('\n') |
|
restOfCode = '' |
|
outlines = [] |
|
|
|
i = 0 |
|
lastDeclareLine = 0 |
|
openDeclare = False |
|
|
|
while i < len(lines): |
|
line = lines[i] |
|
|
|
if len(line) < 5: |
|
i += 1 |
|
continue |
|
|
|
if line.strip().startswith('end '): |
|
continue |
|
|
|
match = re.search(DispCallFuncGenerator.regexes['declareFunction'], line.strip(), re.I) |
|
match2 = re.search(DispCallFuncGenerator.regexes['declareFunctionEnd'], line.strip(), re.I) |
|
|
|
if match: |
|
outlines.append(line) |
|
openDeclare = True |
|
lastDeclareLine = i |
|
|
|
if match2: |
|
i += 1 |
|
lastDeclareLine = i |
|
openDeclare = False |
|
continue |
|
|
|
j = 1 |
|
found = False |
|
|
|
if openDeclare: |
|
while j + i < len(lines): |
|
line2 = lines[i+j] |
|
|
|
if len(line2) < 3: |
|
j += 1 |
|
continue |
|
|
|
outlines.append(line2) |
|
match2 = re.search(DispCallFuncGenerator.regexes['declareFunctionEnd'], line2.strip(), re.I) |
|
|
|
if match2: |
|
i += j |
|
lastDeclareLine = i |
|
found = True |
|
openDeclare = False |
|
break |
|
|
|
j += 1 |
|
|
|
if not found: |
|
i += 1 |
|
|
|
if lastDeclareLine == 0: lastDeclareLine = 1 |
|
restOfCode = '\r\n'.join(lines[lastDeclareLine-1:]) + '\r\n\r\n' |
|
|
|
out = ('\r\n'.join(outlines), restOfCode) |
|
return out |
|
|
|
def reformatDeclareLines(self, text): |
|
combined = text.replace(' _\r\n', ' ').replace(' _\n', ' ') |
|
combined = re.sub(r'[ ]{1,}', ' ', combined) |
|
out = '' |
|
|
|
for line in combined.split('\n'): |
|
if len(line.strip()) == 0 or line.strip().startswith('#'): |
|
continue |
|
|
|
line = line.strip() |
|
pos = line.find('(') |
|
pos2 = line.rfind(')') |
|
|
|
out += line[:pos + 1] + ' _\r\n' |
|
params = line[pos + 1:pos2].split(',') |
|
|
|
for i in range(len(params)): |
|
param = params[i] |
|
param = param.strip() |
|
if param.lower().startswith('byval') or param.lower().startswith('byref'): |
|
pass |
|
else: |
|
param = 'ByVal ' + param |
|
|
|
if i == len(params) - 1: |
|
out += f' {param} _\r\n' |
|
else: |
|
out += f' {param}, _\r\n' |
|
|
|
out += line[pos2:] + '\r\n\r\n' |
|
|
|
#out = re.sub(r'\s*\(\s*_\s*\r\n\s*', ' ( ', out) |
|
#out = out.replace('_\r\n)', ')') |
|
|
|
return out |
|
|
|
def parseDeclares(self, declares): |
|
declares = self.reformatDeclareLines(declares) |
|
lines = declares.split('\n') |
|
|
|
func = '' |
|
function = {} |
|
i = 0 |
|
|
|
while i < len(lines): |
|
line = lines[i] |
|
|
|
m = re.search(DispCallFuncGenerator.regexes['declareFunction'], line, re.I) |
|
if m: |
|
if len(function) > 0: |
|
self.functions[func] = function |
|
|
|
function = {} |
|
|
|
func = m.group(1) |
|
lib = m.group(2).lower() |
|
|
|
if not lib.endswith('.dll'): |
|
lib += '.dll' |
|
|
|
alias = '' |
|
if len(m.groups()) >= 3 and m.group(3) != None: |
|
alias = m.group(3) |
|
|
|
importName = func |
|
if len(alias) > 0: |
|
importName = alias |
|
|
|
function = { |
|
'name' : func, |
|
'symbol' : importName, |
|
'lib' : lib, |
|
'alias' : alias, |
|
'returns' : '', |
|
'params': [] |
|
} |
|
|
|
i += 1 |
|
continue |
|
|
|
params = [] |
|
for n in re.finditer(DispCallFuncGenerator.regexes['functionParamNameAndType'], line, re.I): |
|
paramRef = 'ByVal' |
|
|
|
if n.group(1) == None: |
|
break |
|
|
|
if n.group(1).lower() == 'byref': |
|
paramRef = 'ByRef' |
|
|
|
paramName = n.group(2) |
|
paramType = n.group(3) |
|
|
|
if not paramName.startswith('obf_'): |
|
paramName = 'obf_' + paramName |
|
|
|
param = { |
|
'ref' : paramRef, |
|
'name' : paramName, |
|
'type' : paramType, |
|
} |
|
params.append(param) |
|
function['params'].append(param) |
|
|
|
if len(params) > 0: |
|
i += len(params) |
|
continue |
|
|
|
o = re.search(DispCallFuncGenerator.regexes['declareFunctionEnd'], line, re.I) |
|
if o: |
|
returns = o.group(1) |
|
function['returns'] = returns |
|
|
|
i += 1 |
|
|
|
if func not in self.functions.keys(): |
|
self.functions[func] = function |
|
|
|
return self.functions |
|
|
|
def normalizeCode(self, code): |
|
out = code.replace('\r\n', '\n').replace('\n', '\r\n') |
|
out = re.sub(r'(?:\r\n){2,}', '\r\n\r\n', out).strip() |
|
|
|
return out |
|
|
|
def generate(self): |
|
code = '' |
|
|
|
code += self.reformatDeclareLines(f''' |
|
Private Declare PtrSafe Function obf_DispCallFunc Lib "oleaut32.dll" Alias "DispCallFunc" ( _ |
|
ByVal obf_pvInstance As LongPtr, _ |
|
ByVal obf_oVft As LongPtr, _ |
|
ByVal obf_cc As Long, _ |
|
ByVal obf_vtReturn As Integer, _ |
|
ByVal obf_cActuals As Long, _ |
|
ByRef obf_prgvt As Integer, _ |
|
ByRef obf_prgpvarg As LongPtr, _ |
|
ByRef obf_pvargResult As Variant _ |
|
) As Long |
|
''') |
|
|
|
code += self.reformatDeclareLines(f''' |
|
Private Declare PtrSafe Function obf_LoadLibrary Lib "kernel32" Alias "LoadLibraryA" ( _ |
|
ByVal obf_lpLibFileName As String _ |
|
) As LongPtr |
|
|
|
Private Declare PtrSafe Function obf_GetProcAddress Lib "kernel32" Alias "GetProcAddress" ( _ |
|
ByVal obf_hModule As LongPtr, _ |
|
ByVal obf_lpProcName As String _ |
|
) As LongPtr |
|
''') |
|
code += f'\r\n\r\n' |
|
|
|
for k, v in self.functions.items(): |
|
name = v['symbol'] |
|
code += f'Private obf_ptr_{name} As LongPtr\r\n' |
|
|
|
code += self.genResolveFunctionsSub() + '\r\n\r\n' |
|
|
|
for k, v in self.functions.items(): |
|
code += self.genWrapper(v) + '\r\n' |
|
|
|
return self.normalizeCode(code) |
|
|
|
def genResolveFunctionsSub(self): |
|
code = '' |
|
|
|
code += f'\r\n\r\nSub obf_ResolveFunctions()\r\n' |
|
|
|
libs = set() |
|
|
|
for k, v in self.functions.items(): |
|
libs.add(v['lib']) |
|
|
|
for lib in libs: |
|
libvar = lib.replace('.dll', '') |
|
code += f' Dim obf_{libvar}\r\n' |
|
|
|
for lib in libs: |
|
libvar = lib.replace('.dll', '') |
|
code += f' obf_{libvar} = obf_LoadLibrary("{lib}")\r\n' |
|
|
|
code += f' \r\n' |
|
|
|
for k, v in self.functions.items(): |
|
name = v['symbol'] |
|
libvar = v['lib'].replace('.dll', '') |
|
code += f' obf_ptr_{name} = obf_GetProcAddress(obf_{libvar}, "{name}")\r\n' |
|
|
|
code += f'End Sub\r\n\r\n' |
|
|
|
return code |
|
|
|
def genWrapper(self, function): |
|
funcName = function['symbol'] |
|
funcName2 = function['name'] |
|
|
|
|
|
code = f''' |
|
' |
|
' dynamically invokes {function['lib']}!{funcName} |
|
' |
|
''' |
|
code += f'Private Function obf_wrap_{funcName2} ( _' |
|
|
|
if len(function["params"]) == 0: |
|
code += ') ' |
|
|
|
else: |
|
code += '\r\n' |
|
|
|
for i in range(len(function['params'])): |
|
p = function['params'][i] |
|
if i == len(function['params']) - 1: |
|
code += f' {p["ref"]} {p["name"]} As {p["type"]}) ' |
|
else: |
|
code += f' {p["ref"]} {p["name"]} As {p["type"]}, _\r\n' |
|
|
|
code += f'As {function["returns"]}\r\n' |
|
|
|
paramNum = len(function['params']) |
|
if paramNum == 0: |
|
paramNum = 1 |
|
|
|
code += f""" |
|
Dim obf_lDispCallFuncResult As Long |
|
Dim obf_vFuncResult As Variant |
|
Dim obf_iVarTypes(0 To {paramNum-1}) As Integer |
|
Dim obf_lVarPntrs(0 To {paramNum-1}) As LongPtr |
|
obf_vFuncResult = Empty |
|
|
|
""" |
|
|
|
for p in function['params']: |
|
code += f' Dim {p["name"]}_vl As Variant\r\n' |
|
|
|
code += f' \r\n' |
|
|
|
if funcName.endswith('A'): |
|
for p in function['params']: |
|
if p['type'].lower() == 'string': |
|
code += f' {p["name"]}_vl = StrConv({p["name"]}_vl, vbFromUnicode)\r\n' |
|
else: |
|
if p['ref'] == 'ByVal': |
|
code += f' {p["name"]}_vl = {p["name"]}\r\n' |
|
else: |
|
code += f' {p["name"]}_vl = VarPtr({p["name"]})\r\n' |
|
else: |
|
for p in function['params']: |
|
if p['ref'] == 'ByVal': |
|
code += f' {p["name"]}_vl = {p["name"]}\r\n' |
|
else: |
|
code += f' {p["name"]}_vl = VarPtr({p["name"]})\r\n' |
|
|
|
num = 0 |
|
for p in function['params']: |
|
code += f''' |
|
obf_iVarTypes({num}) = VarType({p["name"]}_vl) |
|
obf_lVarPntrs({num}) = VarPtr({p["name"]}_vl) |
|
''' |
|
num += 1 |
|
|
|
if not self.addedResolveFuncsCall: |
|
code += f''' |
|
If obf_ptr_{funcName} = 0 Then |
|
obf_ResolveFunctions |
|
End If |
|
''' |
|
code += f''' |
|
|
|
obf_lDispCallFuncResult = obf_DispCallFunc( 0, _ |
|
obf_ptr_{funcName}, _ |
|
{DispCallFuncGenerator.callconv["stdcall"]}, _ |
|
VbVarType.vbLong, _ |
|
{len(function["params"])}, _ |
|
obf_iVarTypes(0), _ |
|
obf_lVarPntrs(0), _ |
|
obf_vFuncResult ) |
|
|
|
obf_wrap_{funcName} = obf_vFuncResult |
|
'''.replace('\r', '').replace('\n', '\r\n') |
|
|
|
code += 'End Function\r\n' |
|
|
|
return code |
|
|
|
def convert(self, code): |
|
(declares, restOfCode) = self.extractDeclares(code) |
|
|
|
callResolver = f''' |
|
obf_ResolveFunctions |
|
''' |
|
for autoRun in DispCallFuncGenerator.Auto_Runs: |
|
if autoRun.lower() in restOfCode.lower(): |
|
rex = r'(' + re.escape(autoRun) + r'\s*\(\s*\))\s*\r?\n' |
|
restOfCode = re.sub(rex, r'\1\r\n' + callResolver, restOfCode, re.I) |
|
self.addedResolveFuncsCall = True |
|
break |
|
|
|
self.parseDeclares(declares) |
|
|
|
code = self.generate() |
|
|
|
for funcName, func in self.functions.items(): |
|
name = func['name'] |
|
newName = f'obf_wrap_{name}' |
|
restOfCode = re.sub(r'\b' + re.escape(name) + r'\b', newName, restOfCode) |
|
|
|
return code + '\r\n\r\n' + restOfCode |
|
|
|
def getopts(argv): |
|
parse = argparse.ArgumentParser( |
|
usage = 'generateDispCallFunc.py [options] <code.vba>' |
|
) |
|
|
|
req = parse.add_argument_group('Required arguments') |
|
req.add_argument('infile', help = 'Input file containing VBA code with Declare Function used.') |
|
|
|
args = parse.parse_args() |
|
|
|
return args |
|
|
|
def main(argv): |
|
args = getopts(argv) |
|
|
|
code = '' |
|
with open(args.infile) as f: |
|
code = f.read() |
|
|
|
gen = DispCallFuncGenerator() |
|
converted = gen.convert(code) |
|
|
|
print(converted) |
|
|
|
if __name__ == '__main__': |
|
main(sys.argv) |