vieyahn2017 / iBlog

44 stars 0 forks source link

12.2 Excel的vba解密 #376

Closed vieyahn2017 closed 3 months ago

vieyahn2017 commented 3 years ago

Excel的vba解密

Excel文件可以以zip方式打开,但是其中的vba代码是以加密方式存储的, 具体代码加密存储在 xl/vbaProject.bin

vieyahn2017 commented 3 years ago

首先,可以通过python代码解密读取出vbaProject.bin

#!/usr/bin/python
##############################################################################
# vba_extract.py
#
# vba_extract - A simple utility to extract a vbaProject.bin binary from an
# Excel 2007+ xlsm file for insertion into an XlsxWriter file.
#
# Copyright 2013-2016, John McNamara, jmcnamara@cpan.org
#
import sys
import shutil
from zipfile import ZipFile
from zipfile import BadZipfile

# The VBA project file we want to extract.
vba_filename = 'vbaProject.bin'

#import pdb
#pdb.set_trace()

# Get the xlsm file name from the commandline.
if len(sys.argv) > 1:
    xlsm_file = sys.argv[1]
else:
    xlsm_file = "11.xlsm"

try:
    # Open the Excel xlsm file as a zip file.
    xlsm_zip = ZipFile(xlsm_file, 'r')

    # print(xlsm_zip.namelist())
    # xlsm_zip.extractall()
    # xlsm_zip.extract('xl/vbaProject.bin')

    # Read the xl/vbaProject.bin file.
    vba_data = xlsm_zip.read('xl/' + vba_filename)

    # Write the vba data to a local file.
    vba_file = open(vba_filename, "wb")
    vba_file.write(vba_data)
    vba_file.close()

except IOError:
    # Use exc_info() for Python 2.5+ compatibility.
    e = sys.exc_info()[1]
    print("File error: %s" % str(e))
    exit()

except KeyError:
    # Usually when there isn't a xl/vbaProject.bin member in the file.
    e = sys.exc_info()[1]
    print("File error: %s" % str(e))
    print("File may not be an Excel xlsm macro file: '%s'" % xlsm_file)
    exit()

except BadZipfile:
    # Usually if the file is an xls file and not an xlsm file.
    e = sys.exc_info()[1]
    print("File error: %s: '%s'" % (str(e), xlsm_file))
    print("File may not be an Excel xlsm macro file.")
    exit()

except:
    # Catch any other exceptions.
    e = sys.exc_info()[1]
    print("File error: %s" % str(e))
    exit()

finally:
    xlsm_zip.close()

print("Extracted: %s" % vba_filename)
vieyahn2017 commented 3 years ago

解密vbaProject.bin

代码参考: https://github.com/z3r0zh0u/pyole

vieyahn2017 commented 3 years ago

https://github.com/z3r0zh0u/pyole/blob/master/pyole.py https://github.com/z3r0zh0u/pyole/blob/master/pyvba.py https://github.com/z3r0zh0u/pyole/blob/master/examples/vbainfo.py

vieyahn2017 commented 3 years ago

我更改的 vbainfo2log.py


#!/usr/bin/env python
# _*_ coding:utf-8 _*_

"""
#  python vbainfo2log.py ../vbaProject.bin

pyvba example: parse VBA information

currently support files in following format:
* ole format
* openxml format
* mhtml format
* base64 encoded mhtml format
"""

import os
import re
import sys
import time
import zlib
import shutil
import base64
import zipfile
import hashlib
from pyvba import *

reload(sys)
sys.setdefaultencoding('utf8')

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

def vba_info(filename, is_log=True):

    infos = []
    try:
        vbafile = VBAFile(filename)

        if vbafile.PROJECT != None:
            infos.append('###### VBA Project Properties ######\n')

            infos.append('[Project Property]')
            for key, value in vbafile.PROJECT.Property.iteritems():
                infos.append(key + ' = ' + value)

            infos.append('\n[Host Extenders]')
            for key, value in vbafile.PROJECT.HostExtenders.iteritems():
                infos.append(key + ' = ' + value)

            infos.append('\n[Workspace]')
            for key, value in vbafile.PROJECT.Workspace.iteritems():
                infos.append(key + ' = ' + value)

        infos.append('\n###### VBA Project Records ######\n')

        infos.append('[Information Record]')
        SysKind = vbafile.dir.InformationRecord.SysKindRecord.SysKind
        if SysKind == 0x00:
            infos.append('SysKind: ' + str(hex(SysKind)) + ' (16-bit Windows Platforms)')
        elif SysKind == 0x01:
            infos.append('SysKind: ' + str(hex(SysKind)) + ' (32-bit Windows Platforms)')
        elif SysKind == 0x02:
            infos.append('SysKind: ' + str(hex(SysKind)) + ' (Macintosh Platforms)')
        elif SysKind == 0x03:
            infos.append('SysKind: ' + str(hex(SysKind)) + ' (64-bit Windows Platforms)')
        infos.append('CodePage: ' + str(hex(vbafile.dir.InformationRecord.CodePageRecord.CodePage)))
        infos.append('ProjectName: ' + vbafile.dir.InformationRecord.NameRecord.ProjectName)
        infos.append('DocString: ' + vbafile.dir.InformationRecord.DocStringRecord.DocString)
        infos.append('HelpFilePath1: ' + vbafile.dir.InformationRecord.HelpFilePathRecord.HelpFile1)
        infos.append('HelpFilePath2: ' + vbafile.dir.InformationRecord.HelpFilePathRecord.HelpFile2)
        infos.append('HelpContext: ' + str(hex(vbafile.dir.InformationRecord.HelpContextRecord.HelpContext)))
        infos.append('MajorVersion: ' + str(hex(vbafile.dir.InformationRecord.VersionRecord.MajorVersion)))
        infos.append('MinorVersion: ' + str(hex(vbafile.dir.InformationRecord.VersionRecord.MinorVersion)))
        infos.append('Constants: ' + vbafile.dir.InformationRecord.ConstantsRecord.Constants)

        infos.append('\n[Reference Record]')
        for ReferenceRecord in vbafile.dir.ReferencesRecord.ReferenceArray:

            if ReferenceRecord[0] is not None:
                infos.append('Name: ' + ReferenceRecord[0].Name)

            if isinstance(ReferenceRecord[1], ReferenceControlRecord):
                infos.append('Type: ControlRecord')
            elif isinstance(ReferenceRecord[1], ReferenceRegisteredRecord):
                infos.append('Type: RegisteredRecord')
                infos.append('Libid: ' + ReferenceRecord[1].Libid)
            elif isinstance(ReferenceRecord[1], ReferenceProjectRecord):
                infos.append('Type: ProjectRecord')
                infos.append('LibidAbsolute: ' + ReferenceRecord[1].LibidAbsolute)
                infos.append('LibidRelative: ' + ReferenceRecord[1].LibidRelative)
                infos.append('MajorVersion: ' + str(hex(ReferenceRecord[1].MajorVersion)))
                infos.append('MinorVersion: ' + str(hex(ReferenceRecord[1].MinorVersion)))
            else:
                infos.append('Unknown reference record type.')
            infos.append('-------------------------')

        infos.append('\n[Module Record]')
        infos.append('ModuleCookie: ' + str(hex(vbafile.dir.ModulesRecord.CookieRecord.Cookie)))
        for ModuleRecord in vbafile.dir.ModulesRecord.ModuleArray:
            infos.append('-------------------------')
            infos.append('ModuleName: ' + ModuleRecord.NameRecord.ModuleName)
            infos.append('SizeOfModuleName: ' + str(hex(ModuleRecord.NameRecord.SizeOfModuleName)))
            infos.append('ModuleNameUnicode: ' + str(ModuleRecord.NameUnicodeRecord.ModuleNameUnicode))
            infos.append('SizeOfModuleNameUnicode: ' + str(hex(ModuleRecord.NameUnicodeRecord.SizeOfModuleNameUnicode)))
            infos.append('StreamName: ' + ModuleRecord.StreamNameRecord.StreamName)
            infos.append('DocString: ' + ModuleRecord.DocStringRecord.DocString)
            infos.append('TextOffset: ' + str(hex(ModuleRecord.OffsetRecord.TextOffset)))
            infos.append('HelpContext: ' + str(hex(ModuleRecord.HelpContextRecord.HelpContext)))
            infos.append('Cookie: ' + str(hex(ModuleRecord.CookieRecord.Cookie)))
            infos.append('Type: ' + str(hex(ModuleRecord.TypeRecord.Id)))
            if ModuleRecord.ReadOnlyRecord is not None:
                infos.append('ReadOnly: True')
            if ModuleRecord.PrivateRecord is not None:
                infos.append('Private: True')
            codepage = 'cp' + str(vbafile.dir.InformationRecord.CodePageRecord.CodePage)
            if codepage == 'cp10000':
                modulename = ModuleRecord.NameRecord.ModuleName.decode('mac_roman')
            else:
                modulename = ModuleRecord.NameRecord.ModuleName.decode(codepage)
            moduledata = vbafile.OLE.find_object_by_name(modulename)
            if moduledata is not None:  
                if len(moduledata) > ModuleRecord.OffsetRecord.TextOffset:
                    code = moduledata[ModuleRecord.OffsetRecord.TextOffset:]
                    infos.append('SourceCodeSize:' + str(hex(len(code))))
                    code = vbafile._decompress(code)
                    infos.append('SourceCode:')
                    infos.append(code)
                else:
                    infos.append('No source code available in module: ' + modulename)
            else:
                infos.append('Can not find module: ' + modulename)

    except Exception as e:
        print(e)

    contents = "\n".join(infos)

    if is_log:
        log_filename = filename + ".info.txt"
        print("parse result: " + log_filename)
        log_file = open(log_filename, 'w')
        log_file.write(contents.decode('gbk') )
        log_file.close()
    else:
        print(contents)

    return False

def parse_file(filename):

    if False == os.path.isfile(filename):
        print('Invalid file: ' + filename)
        return

    print('File: ' + os.path.basename(filename))
    ole_file = extract_ole_file(filename)
    if ole_file is not None:
        vba_info(ole_file)
        if ole_file[0x00:0x07] == 'tmpole_':
            print('Extract OLE file: ' + ole_file)

def extract_ole_file(filename):

    data = open(filename, 'rb').read()
    tmp_file = 'tmpole_' + time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))

    if data[0x00:0x08] == '\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1':
        print('Type: OLE')
        return filename

    if data[0x00:0x04] == '\x50\x4b\x03\x04':
        try:
            zf = zipfile.ZipFile(filename, 'r')
            for name in zf.namelist():
                if name[-14:] == 'vbaProject.bin':
                    data = zf.read(name)
                    open(tmp_file, 'wb').write(data)
                    print('Type: OpenXml')
                    return tmp_file
            print(filename + ': No vbaProject.bin found in zip arachive.')
        except Exception as e:
            print(filename + ': ' + str(e))

    if data[0x00:0x08] == 'IE1JTUUt':
        m = re.search('IE1JTU[0-9a-zA-Z/+=\x0d\x0a]{1000,}', data)
        if m is not None:
            b64data = m.group(0)
            data = base64.b64decode(b64data)

    if data.find('MIME-Version') != -1 or \
       data.find('<?mso-application progid="Word.Document"?>') != -1:
        m = re.search('Q[\x0d\x0a]*W[\x0d\x0a]*N[\x0d\x0a]*0[\x0d\x0a]*a[\x0d\x0a]*X[0-9a-zA-Z/+=\x0d\x0a\x20]{1000,}', data)
        if m is not None:
            b64data = m.group(0)
            data = base64.b64decode(b64data)
            try:
                data = zlib.decompress(data[0x32:])
                open(tmp_file, 'wb').write(data)
                print('Type: MHTML')
                return tmp_file
            except Exception as e:
                print(filename + ': ' + str(e))

    return None

if __name__ == '__main__':

    if len(sys.argv) >= 2 and len(sys.argv) <= 3:
        debug = False
        if len(sys.argv) == 3:
            if sys.argv[2] == 'debug':
                debug = True
            else:
                print('Usage: ' + os.path.basename(sys.argv[0]) + ' filename [debug]')
                exit(0)
        init_logging(debug)
        if os.path.isfile(sys.argv[1]):
            parse_file(sys.argv[1])
        else:
            print('Invalid file: ' + sys.argv[1])
    else:
        print('Usage: ' + os.path.basename(sys.argv[0]) + ' filename [debug]')
vieyahn2017 commented 3 years ago

用git来管理Excel的vba更改记录

python vba_extract.py
sleep 1
python vbainfo2log.py vbaProject.bin
sleep 1

git add  vbaProject.bin.info.txt
nnow=$(date  "+%Y-%m-%d %H:%M:%S")
remark=$1
if [ -z "$remark" ]; then
    git commit -m "commit vba [${nnow}]."
else
    git commit -m "commit vba [${nnow}] ${remark}"
fi
vieyahn2017 commented 3 years ago

$ ls -l total 2186 -rw-r--r-- 1 yWX536122 1049089 165 12月 2 14:46 '~$11.xlsm' -rw-r--r-- 1 yWX536122 1049089 294 12月 2 14:54 gitpush.sh -rw-r--r-- 1 yWX536122 1049089 798063 12月 2 14:47 11.xlsm -rw-r--r-- 1 yWX536122 1049089 60571 4月 6 2016 pyole.py -rw-r--r-- 1 yWX536122 1049089 42148 12月 2 12:44 pyole.pyc -rw-r--r-- 1 yWX536122 1049089 54849 4月 6 2016 pyvba.py -rw-r--r-- 1 yWX536122 1049089 48004 12月 2 12:44 pyvba.pyc -rwxr-xr-x 1 yWX536122 1049089 1937 12月 2 14:33 vba_extract.py -rwxr-xr-x 1 yWX536122 1049089 9240 12月 2 14:44 vbainfo2log.py -rw-r--r-- 1 yWX536122 1049089 901632 12月 2 14:47 vbaProject.bin -rw-r--r-- 1 yWX536122 1049089 301999 12月 2 14:47 vbaProject.bin.info.txt

vieyahn2017 commented 3 years ago

执行python脚本记录如下:

$ python vba_extract.py Extracted: vbaProject.bin

$ python vbainfo2log.py vbaProject.bin File: vbaProject.bin Type: OLE parse result: vbaProject.bin.info.txt

vieyahn2017 commented 3 years ago

-- line --

vieyahn2017 commented 3 years ago

另外搜到的ole解析的

https://github.com/ozch/Malicious-Office-File-Detection-Using-Machine-Learning/blob/217fed8f049a4e570621c96fc8684a0ec62a3acf/olevba3.py

该项目

README.md

ole_vba_extractor

Extract ole and vbs data from multiple file using oledump and olevba tools just past the microsoft files in feed folder and get output in output folder

Required Python 2 first execute following commands in terminal pip install yara-python pip install olefile pip install oletools Procedure Paste all the files in feed folder Run chmod u+x ./script.sh command in terminal Run ./script.sh Known Bugs Doesn't work on files with name contraining spaces

vieyahn2017 commented 3 months ago

vba_info函数有点冗余,修正如下

def vba_info(filename, is_log=True):

    infos = []
    try:
        vbafile = VBAFile(filename)

        if vbafile.PROJECT != None:
            infos.append('###### VBA Project Properties ######\n')

            infos.append('[Project Property]')
            for key, value in vbafile.PROJECT.Property.iteritems():
                # 一些数字标识类字段每次都变化,并且意义不大,暂时不显示, 比如CMG GC DPB
                if key in ["Name", "Package", "Module", "ID", "Document", "BaseClass", "Class"]:
                    infos.append(key + ' = ' + value)

            # infos.append('\n[Host Extenders]')
            # for key, value in vbafile.PROJECT.HostExtenders.iteritems():
            #     infos.append(key + ' = ' + value)

            infos.append('\n[Workspace]')
            for key, value in vbafile.PROJECT.Workspace.iteritems():
                # infos.append(key + ' = ' + value)
                infos.append(key)

        infos.append('\n###### VBA Project Records ######\n')

        infos.append('[Information Record]')
        SysKind = vbafile.dir.InformationRecord.SysKindRecord.SysKind
        if SysKind == 0x00:
            infos.append('SysKind: ' + str(hex(SysKind)) + ' (16-bit Windows Platforms)')
        elif SysKind == 0x01:
            infos.append('SysKind: ' + str(hex(SysKind)) + ' (32-bit Windows Platforms)')
        elif SysKind == 0x02:
            infos.append('SysKind: ' + str(hex(SysKind)) + ' (Macintosh Platforms)')
        elif SysKind == 0x03:
            infos.append('SysKind: ' + str(hex(SysKind)) + ' (64-bit Windows Platforms)')
        infos.append('CodePage: ' + str(hex(vbafile.dir.InformationRecord.CodePageRecord.CodePage)))
        infos.append('ProjectName: ' + vbafile.dir.InformationRecord.NameRecord.ProjectName)
        infos.append('DocString: ' + vbafile.dir.InformationRecord.DocStringRecord.DocString)
        infos.append('HelpFilePath1: ' + vbafile.dir.InformationRecord.HelpFilePathRecord.HelpFile1)
        infos.append('HelpFilePath2: ' + vbafile.dir.InformationRecord.HelpFilePathRecord.HelpFile2)
        infos.append('HelpContext: ' + str(hex(vbafile.dir.InformationRecord.HelpContextRecord.HelpContext)))
        # infos.append('MajorVersion: ' + str(hex(vbafile.dir.InformationRecord.VersionRecord.MajorVersion)))
        # infos.append('MinorVersion: ' + str(hex(vbafile.dir.InformationRecord.VersionRecord.MinorVersion)))
        infos.append('Constants: ' + vbafile.dir.InformationRecord.ConstantsRecord.Constants)

        # infos.append('\n[Reference Record]')
        # for ReferenceRecord in vbafile.dir.ReferencesRecord.ReferenceArray:
        #
        #     if ReferenceRecord[0] is not None:
        #         infos.append('Name: ' + ReferenceRecord[0].Name)
        #
        #     if isinstance(ReferenceRecord[1], ReferenceControlRecord):
        #         infos.append('Type: ControlRecord')
        #     elif isinstance(ReferenceRecord[1], ReferenceRegisteredRecord):
        #         infos.append('Type: RegisteredRecord')
        #         infos.append('Libid: ' + ReferenceRecord[1].Libid)
        #     elif isinstance(ReferenceRecord[1], ReferenceProjectRecord):
        #         infos.append('Type: ProjectRecord')
        #         infos.append('LibidAbsolute: ' + ReferenceRecord[1].LibidAbsolute)
        #         infos.append('LibidRelative: ' + ReferenceRecord[1].LibidRelative)
        #         infos.append('MajorVersion: ' + str(hex(ReferenceRecord[1].MajorVersion)))
        #         infos.append('MinorVersion: ' + str(hex(ReferenceRecord[1].MinorVersion)))
        #     else:
        #         infos.append('Unknown reference record type.')
        #     infos.append('----------------------------------')

        infos.append('\n[Module Record]')
        infos.append('ModuleCookie: ' + str(hex(vbafile.dir.ModulesRecord.CookieRecord.Cookie)))
        for ModuleRecord in vbafile.dir.ModulesRecord.ModuleArray:
            infos.append('----------------------------------')
            infos.append('ModuleName: ' + ModuleRecord.NameRecord.ModuleName)
            # infos.append('SizeOfModuleName: ' + str(hex(ModuleRecord.NameRecord.SizeOfModuleName)))
            infos.append('ModuleNameUnicode: ' + str(ModuleRecord.NameUnicodeRecord.ModuleNameUnicode))
            # infos.append('SizeOfModuleNameUnicode: ' + str(hex(ModuleRecord.NameUnicodeRecord.SizeOfModuleNameUnicode)))
            infos.append('StreamName: ' + ModuleRecord.StreamNameRecord.StreamName)
            # infos.append('DocString: ' + ModuleRecord.DocStringRecord.DocString)
            # infos.append('TextOffset: ' + str(hex(ModuleRecord.OffsetRecord.TextOffset)))
            # infos.append('HelpContext: ' + str(hex(ModuleRecord.HelpContextRecord.HelpContext)))
            # infos.append('Cookie: ' + str(hex(ModuleRecord.CookieRecord.Cookie)))
            # infos.append('Type: ' + str(hex(ModuleRecord.TypeRecord.Id)))
            if ModuleRecord.ReadOnlyRecord is not None:
                infos.append('ReadOnly: True')
            if ModuleRecord.PrivateRecord is not None:
                infos.append('Private: True')
            codepage = 'cp' + str(vbafile.dir.InformationRecord.CodePageRecord.CodePage)
            if codepage == 'cp10000':
                modulename = ModuleRecord.NameRecord.ModuleName.decode('mac_roman')
            else:
                modulename = ModuleRecord.NameRecord.ModuleName.decode(codepage)
            moduledata = vbafile.OLE.find_object_by_name(modulename)
            if moduledata is not None:  
                if len(moduledata) > ModuleRecord.OffsetRecord.TextOffset:
                    code = moduledata[ModuleRecord.OffsetRecord.TextOffset:]
                    # infos.append('SourceCodeSize:' + str(hex(len(code))))
                    code = vbafile._decompress(code)
                    infos.append('SourceCode:\n----------------------------------\n')
                    for code_line in code.split("\r\n"):
                        infos.append(code_line)
                else:
                    infos.append('No source code available in module: ' + modulename)
            else:
                infos.append('Can not find module: ' + modulename)

    except Exception as e:
        print(e)

    contents = "\n".join(infos)

    if is_log:
        log_filename = filename + ".info.txt"
        print("parse result: " + log_filename)
        log_file = open(log_filename, 'w')
        log_file.write(contents.decode('gbk') )
        log_file.close()
    else:
        print(contents)

    return False
vieyahn2017 commented 2 months ago

之前的 spreadsheet-unprotect

vieyahn2017 commented 2 months ago

spreadsheet-unprotect Simple GUI tool for removing protection from .xlsx and .xlsm spreadsheets.

Features: Remove workbook structure protection Remove protection from selected sheets Remove VBA project from the spreadsheet (e.g. if it contains macros that protect the workbook again when opened in Excel) The tool only removes write protection from sheets and workbook structure protection. It cannot decrypt an encrypted file.