Files
2022-12-30 04:01:39 +01:00

345 lines
12 KiB
Python

import itertools
from pcodedmp.constants import *
from pcodedmp.utils import *
def translateOpcode(opcode, vbaVer, is64bit):
if vbaVer == 3:
if 0 <= opcode <= 67:
return opcode
elif 68 <= opcode <= 70:
return opcode + 2
elif 71 <= opcode <= 111:
return opcode + 4
elif 112 <= opcode <= 150:
return opcode + 8
elif 151 <= opcode <= 164:
return opcode + 9
elif 165 <= opcode <= 166:
return opcode + 10
elif 167 <= opcode <= 169:
return opcode + 11
elif 170 <= opcode <= 238:
return opcode + 12
else: # opcode == 239
return opcode + 24
elif vbaVer == 5:
if 0 <= opcode <= 68:
return opcode
elif 69 <= opcode <= 71:
return opcode + 1
elif 72 <= opcode <= 112:
return opcode + 3
elif 113 <= opcode <= 151:
return opcode + 7
elif 152 <= opcode <= 165:
return opcode + 8
elif 166 <= opcode <= 167:
return opcode + 9
elif 168 <= opcode <= 170:
return opcode + 10
else: # 171 <= opcode <= 252
return opcode + 11
#elif vbaVer == 6:
#elif vbaVer in [6, 7]:
elif not is64bit:
if 0 <= opcode <= 173:
return opcode
elif 174 <= opcode <= 175:
return opcode + 1
elif 176 <= opcode <= 178:
return opcode + 2
else: # 179 <= opcode <= 260
return opcode + 3
else:
return opcode
def getID(idCode, identifiers, vbaVer, is64bit):
origCode = idCode
idCode >>= 1
try:
if idCode >= 0x100:
idCode -= 0x100
if vbaVer >= 7:
idCode -= 4
if is64bit:
idCode -= 3
if idCode > 0xBE:
idCode -= 1
return identifiers[idCode]
else:
if vbaVer >= 7:
if idCode >= 0xC3:
idCode -= 1
return internalNames[idCode]
except:
return 'id_{:04X}'.format(origCode)
def getName(buffer, identifiers, offset, endian, vbaVer, is64bit):
objectID = getWord(buffer, offset, endian)
objectName = getID(objectID, identifiers, vbaVer, is64bit)
return objectName
def disasmName(word, identifiers, mnemonic, opType, vbaVer, is64bit):
varTypes = ['', '?', '%', '&', '!', '#', '@', '?', '$', '?', '?', '?', '?', '?']
varName = getID(word, identifiers, vbaVer, is64bit)
if opType < len(varTypes):
strType = varTypes[opType]
else:
strType = ''
if opType == 32:
varName = '[' + varName + ']'
if mnemonic == 'OnError':
strType = ''
if opType == 1:
varName = '(Resume Next)'
elif opType == 2:
varName = '(GoTo 0)'
elif mnemonic == 'Resume':
strType = ''
if opType == 1:
varName = '(Next)'
elif opType != 0:
varName = ''
return varName + strType + ' '
def disasmImp(objectTable, identifiers, arg, word, mnemonic, endian, vbaVer, is64bit):
if mnemonic != 'Open':
if arg == 'imp_' and (len(objectTable) >= word + 8):
impName = getName(objectTable, identifiers, word + 6, endian, vbaVer, is64bit)
else:
impName = '{}{:04X} '.format(arg, word)
else:
accessMode = ['Read', 'Write', 'Read Write']
lockMode = ['Read Write', 'Write', 'Read']
mode = word & 0x00FF
access = (word & 0x0F00) >> 8
lock = (word & 0xF000) >> 12
impName = '(For '
if mode & 0x01:
impName += 'Input'
elif mode & 0x02:
impName += 'Output'
elif mode & 0x04:
impName += 'Random'
elif mode & 0x08:
impName += 'Append'
elif mode == 0x20:
impName += 'Binary'
if access and (access <= len(accessMode)):
impName += ' Access ' + accessMode[access - 1]
if lock:
if lock & 0x04:
impName += ' Shared'
elif lock <= len(accessMode):
impName += ' Lock ' + lockMode[lock - 1]
impName += ')'
return impName
def disasmRec(indirectTable, identifiers, dword, endian, vbaVer, is64bit):
objectName = getName(indirectTable, identifiers, dword + 2, endian, vbaVer, is64bit)
options = getWord(indirectTable, dword + 18, endian)
if (options & 1) == 0:
objectName = '(Private) ' + objectName
return objectName
def getTypeName(typeID):
dimTypes = ['', 'Null', 'Integer', 'Long', 'Single', 'Double', 'Currency', 'Date', 'String', 'Object', 'Error', 'Boolean', 'Variant', '', 'Decimal', '', '', 'Byte']
typeFlags = typeID & 0xE0
typeID &= ~0xE0
if typeID < len(dimTypes):
typeName = dimTypes[typeID]
else:
typeName = ''
if typeFlags & 0x80:
typeName += 'Ptr'
return typeName
def disasmType(indirectTable, dword):
dimTypes = ['', 'Null', 'Integer', 'Long', 'Single', 'Double', 'Currency', 'Date', 'String', 'Object', 'Error', 'Boolean', 'Variant', '', 'Decimal', '', '', 'Byte']
typeID = ord(indirectTable[dword + 6])
if typeID < len(dimTypes):
typeName = dimTypes[typeID]
else:
typeName = 'type_{:08X}'.format(dword)
return typeName
def disasmObject(indirectTable, objectTable, identifiers, offset, endian, vbaVer, is64bit):
# TODO - Dim declarations in 64-bit Office documents
if is64bit:
return ''
typeDesc = getDWord(indirectTable, offset, endian)
flags = getWord(indirectTable, typeDesc, endian)
if flags & 0x02:
typeName = disasmType(indirectTable, typeDesc)
else:
word = getWord(indirectTable, typeDesc + 2, endian)
if word == 0:
typeName = ''
else:
offs = (word >> 2) * 10
if offs + 4 > len(objectTable):
return ''
flags = getWord(objectTable, offs, endian)
hlName = getWord(objectTable, offs + 6, endian)
# TODO - The following logic is flawed and doesn't always work. Disabling it for now
#if flags & 0x02:
# theNames = []
# numNames = getWord(objectTable, hlName, endian)
# offs = hlName + 2
# for myName in range(numNames):
# theNames.append(getName(objectTable, identifiers, offs, endian, vbaVer, is64bit))
# offs += 2
# typeName = ' '.join(theNames)
#else:
# typeName = getID(hlName, identifiers, vbaVer, is64bit)
# Using the following line instead:
typeName = getID(hlName, identifiers, vbaVer, is64bit)
return typeName
def disasmVar(indirectTable, objectTable, identifiers, dword, endian, vbaVer, is64bit):
bFlag1 = ord(indirectTable[dword])
bFlag2 = ord(indirectTable[dword + 1])
hasAs = (bFlag1 & 0x20) != 0
hasNew = (bFlag2 & 0x20) != 0
varName = getName(indirectTable, identifiers, dword + 2, endian, vbaVer, is64bit)
if hasNew or hasAs:
varType = ''
if hasNew:
varType += 'New'
if hasAs:
varType += ' '
if hasAs:
if is64bit:
offs = 16
else:
offs = 12
word = getWord(indirectTable, dword + offs + 2, endian)
if word == 0xFFFF:
typeID = ord(indirectTable[dword + offs])
typeName = getTypeName(typeID)
else:
typeName = disasmObject(indirectTable, objectTable, identifiers, dword + offs, endian, vbaVer, is64bit)
if len(typeName) > 0:
varType += 'As ' + typeName
if len(varType) > 0:
varName += ' (' + varType + ')'
return varName
def disasmArg(indirectTable, identifiers, argOffset, endian, vbaVer, is64bit):
flags = getWord(indirectTable, argOffset, endian)
if is64bit:
offs = 4
else:
offs = 0
argName = getName(indirectTable, identifiers, argOffset + 2, endian, vbaVer, is64bit)
argType = getDWord(indirectTable, argOffset + offs + 12, endian)
argOpts = getWord(indirectTable, argOffset + offs + 24, endian)
if argOpts & 0x0004:
argName = 'ByVal ' + argName
if argOpts & 0x0002:
argName = 'ByRef ' + argName
if argOpts & 0x0200:
argName = 'Optional ' + argName
# TODO - ParamArray arguments aren't disassebled properly
#if (flags & 0x0040) == 0:
# argName = 'ParamArray ' + argName + '()'
if flags & 0x0020:
argName += ' As '
argTypeName = ''
if argType & 0xFFFF0000:
argTypeID = argType & 0x000000FF
argTypeName = getTypeName(argTypeID)
# TODO - Custom type arguments aren't disassembled properly
#else:
# argTypeName = getName(indirectTable, identifiers, argType + 6, endian, vbaVer, is64bit)
argName += argTypeName
return argName
def disasmFunc(indirectTable, declarationTable, identifiers, dword, opType, endian, vbaVer, is64bit):
funcDecl = '('
flags = getWord(indirectTable, dword, endian)
subName = getName(indirectTable, identifiers, dword + 2, endian, vbaVer, is64bit)
if vbaVer > 5:
offs2 = 4
else:
offs2 = 0
if is64bit:
offs2 += 16
argOffset = getDWord(indirectTable, dword + offs2 + 36, endian)
retType = getDWord(indirectTable, dword + offs2 + 40, endian)
declOffset = getWord(indirectTable, dword + offs2 + 44, endian)
cOptions = ord(indirectTable[dword + offs2 + 54])
#argCount = ord(indirectTable[dword + offs2 + 55])
newFlags = ord(indirectTable[dword + offs2 + 57])
hasDeclare = False
# TODO - 'Private' and 'Declare' for 64-bit Office
if vbaVer > 5:
if ((newFlags & 0x0002) == 0) and not is64bit:
funcDecl += 'Private '
if newFlags & 0x0004:
funcDecl += 'Friend '
else:
if (flags & 0x0008) == 0:
funcDecl += 'Private '
if opType & 0x04:
funcDecl += 'Public '
if flags & 0x0080:
funcDecl += 'Static '
if ((cOptions & 0x90) == 0) and (declOffset != 0xFFFF) and not is64bit:
hasDeclare = True
funcDecl += 'Declare '
if vbaVer > 5:
if newFlags & 0x20:
funcDecl += 'PtrSafe '
hasAs = (flags & 0x0020) != 0
if flags & 0x1000:
if opType in [2, 6]:
funcDecl += 'Function '
else:
funcDecl += 'Sub '
elif flags & 0x2000:
funcDecl += 'Property Get '
elif flags & 0x4000:
funcDecl += 'Property Let '
elif flags & 0x8000:
funcDecl += 'Property Set '
funcDecl += subName
if hasDeclare:
libName = getName(declarationTable, identifiers, declOffset + 2, endian, vbaVer, is64bit)
funcDecl += ' Lib "' + libName + '" '
argList = []
while (argOffset != 0xFFFFFFFF) and (argOffset != 0) and (argOffset + 26 < len(indirectTable)):
argName = disasmArg(indirectTable, identifiers, argOffset, endian, vbaVer, is64bit)
argList.append(argName)
argOffset = getDWord(indirectTable, argOffset + 20, endian)
funcDecl += '(' + ', '.join(argList) + ')'
if hasAs:
funcDecl += ' As '
typeName = ''
if (retType & 0xFFFF0000) == 0xFFFF0000:
typeID = retType & 0x000000FF
typeName = getTypeName(typeID)
else:
typeName = getName(indirectTable, identifiers, retType + 6, endian, vbaVer, is64bit)
funcDecl += typeName
funcDecl += ')'
return funcDecl
def disasmVarArg(moduleData, identifiers, offset, wLength, mnemonic, endian, vbaVer, is64bit):
substring = moduleData[offset:offset + wLength]
varArgName = '0x{:04X} '.format(wLength)
if mnemonic in ['LitStr', 'QuoteRem', 'Rem', 'Reparse']:
varArgName += '"' + decode(substring) + '"'
elif mnemonic in ['OnGosub', 'OnGoto']:
offset1 = offset
vars = []
for _ in itertools.repeat(None, int(wLength / 2)):
offset1, word = getVar(moduleData, offset1, endian, False)
vars.append(getID(word, identifiers, vbaVer, is64bit))
varArgName += ', '.join(v for v in vars) + ' '
else:
hexdump = ' '.join('{:02X}'.format(ord(c)) for c in substring)
varArgName += hexdump
return varArgName