32
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

実務であまり役に立たないVBAの内部の話

Last updated at Posted at 2020-05-21

はじめに

この記事では、VBAが、どのようにExcelやWordに格納されているかを考えてみます。

なお、もし、VBAのソースコードをプログラムから取得したりしたいだけならば、この記事は訳に立ちません。そういうことをしたい場合はここでやっている感じでVBProjectプロパティを操作するとできると思います。

xlsm中のVBAはどう格納されているか

まず、拡張子がxlsmというファイルの場合、Excelファイルを7-Zipなどの解凍ソフトで開いてみてください。

image.png

すると、圧縮されているファイルの内容が確認されます。
image.png

そのファイル中の「xl」フォルダに「vbaProject.bin」というバイナリファイルが存在し、それがVBAの中身になります。
image.png

このvbaProject.binというバイナリファイルはCompound File Binary Formatというファイル形式で格納されています。
ルートのストレージオブジェクトの中に子ストレージオブジェクトとストリームオブジェクトのツリー構造で構成されており、その仕様は以下に公開されています。

[MS-CFB]: Compound File Binary File Format
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b

なお、7Zipを使用するとCFBの内容を閲覧可能です。
image.png

7ZIPで確認できるのはストレージまでなのでストリームの内容に関しては後述する方法か、自分でバイナリエディタを開いて解析する必要があります。
VBA固有のストレージとストリームの仕様は以下に定義されています。

[MS-OVBA]: Office VBA File Format Structure
https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ovba/575462ba-bf67-4190-9fac-c275523c75fc

古い形式のXLSの場合

2003以前のExcelでVBAを確認する場合は、xlsファイルがCFBになっているので、7ZIPで直接開くと、_VBA_PROJECT_DIRと言うストレージオブジェクトが確認できます。この中にVBAの情報が格納されています。

image.png

バイナリからVBAのソースコードを取得する

[MS-OVBA]: Office VBA File Format Structureによると、ソースコードはModule Stream中のCompressedSourceCodeとして圧縮されたバイト配列で格納されています。

なお、このModule Streamからソースコードを取得するようなPythonのツールが存在しています。

oletools
https://github.com/decalage2/oletools/tree/master/oletools

oletools中のolevbaコマンドを使用することで、xlsmファイル中のモジュールストリームからVBAのコードが抽出可能です。

C:\dev\python3>olevba -c C:\share\vba\test\test.xlsm
olevba 0.54.2 on Python 3.7.4 - http://decalage.info/python/oletools
===============================================================================
FILE: C:\share\vba\test\test.xlsm
Type: OpenXML
-------------------------------------------------------------------------------
VBA MACRO ThisWorkbook.cls
in file: xl/vbaProject.bin - OLE stream: 'VBA/ThisWorkbook'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Option Explicit

-------------------------------------------------------------------------------
VBA MACRO Sheet1.cls
in file: xl/vbaProject.bin - OLE stream: 'VBA/Sheet1'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Option Explicit

-------------------------------------------------------------------------------
VBA MACRO Module1.bas
in file: xl/vbaProject.bin - OLE stream: 'VBA/Module1'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Option Explicit

Public Sub test()
    Dim x As String
    Dim y As String
    Dim z As String
    x = "TEST"
    y = "abe"
    z = x & " " & z & "neko"
    MsgBox z
End Sub
-------------------------------------------------------------------------------
VBA MACRO Class1.cls
in file: xl/vbaProject.bin - OLE stream: 'VBA/Class1'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Option Explicit

Public a As Long
Public Sub test()
    MsgBox "test"
End Sub

Public Function test2(a, b)
    test2 = a + b
End Function
-------------------------------------------------------------------------------
VBA MACRO UserForm1.frm
in file: xl/vbaProject.bin - OLE stream: 'VBA/UserForm1'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Option Explicit

Private Sub CommandButton1_Click()
    MsgBox "test"
End Sub
-------------------------------------------------------------------------------
VBA FORM STRING IN 'xl/vbaProject.bin' - OLE stream: 'UserForm1/o'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
�Label1
-------------------------------------------------------------------------------
VBA FORM STRING IN 'xl/vbaProject.bin' - OLE stream: 'UserForm1/o'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MS UI Gothic
-------------------------------------------------------------------------------
VBA FORM STRING IN 'xl/vbaProject.bin' - OLE stream: 'UserForm1/o'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
�CommandButton1�
-------------------------------------------------------------------------------
VBA FORM STRING IN 'xl/vbaProject.bin' - OLE stream: 'UserForm1/o'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MS UI Gothic
-------------------------------------------------------------------------------
VBA FORM Variable "b'Label1'" IN 'xl/vbaProject.bin' - OLE stream: 'UserForm1'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
None
-------------------------------------------------------------------------------
VBA FORM Variable "b'CommandButton1'" IN 'xl/vbaProject.bin' - OLE stream: 'UserForm1'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
None

中間コードを確認する

VBAにはソースコードの他に中間コードが存在しています。
ここ以降の内容は現時点でMicrosoftのホームページから確認できる内容でなく、非公式の話になります。

※2020.05.21追記
下記のVBAの中間コードやキャッシュを圧縮したり削除するツールのドキュメント中にMicrosoftのページを参考にしています。ただ、Link切れを起こしているのでアーカイブなどで当時の公式情報を探すことはできるかもしれませんが、の情報がどうなっているかは不明です。
http://cpap.com.br/orlando/VBADecompilerMore.asp

Visual Basic Editorでソースコードを編集すると、その時点でP-Codeという中間コードが作成されて、Module Stream中のPerformanceCacheに記録されます。

P-Codeが一度でも実行されると、さらにトークン化された形式でSRP Streamsに格納されます。

P-Codeを取得する

下記のツールを使用することでVBA中のp-codeを取得することが可能です。

pcodedmp.py - A VBA p-code disassembler
https://github.com/bontchev/pcodedmp

たとえば以下のようなVBAのコードがあるとします。

image.png

これをpcodemp.pyでダンプを行うと以下のようになります。

C:\dev\python3\pcodedmp\pcodedmp>pcodedmp -d C:\share\vba\test\testdata\test_006.xlsm
Processing file: C:\share\vba\test\testdata\test_006.xlsm
===============================================================================
Module streams:
VBA/ThisWorkbook - 1067 bytes
Line #0:
        Option  (Explicit)
Line #1:
VBA/Sheet1 - 1059 bytes
Line #0:
        Option  (Explicit)
Line #1:
VBA/Module1 - 1588 bytes
Line #0:
        Option  (Explicit)
Line #1:
Line #2:
        FuncDefn (Public Sub test())
Line #3:
        Dim
        VarDefn z (As String)
Line #4:
        LitStr 0x000A "abcdあいう"
        St z
Line #5:
        Ld z
        ArgsCall MsgBox 0x0001
Line #6:
        EndSub

もし-bオプションを使用した場合は、詳細なアドレスも確認できます。

略
0583: Line #4:
00000000   B9 00 0A 00 61 62 63 64 82 A0 82 A2 82 A4 27 00    ....abcd......'.
00000010   34 02                                              4.

        00B9 LitStr 0x000A "abcdあいう"
        0027 St z
略

P-Codeからソースコードを取得する

P-Codeからソースコードに変換するPythonのツールも存在します。

pcode2code.py - A VBA p-code decompiler
https://github.com/Big5-sec/pcode2code

下記のpcodedmpの出力結果からソースコードを取得してみます。

code.txt
Processing file: C:\share\vba\test\testdata\test_006.xlsm
===============================================================================
Module streams:
VBA/ThisWorkbook - 1067 bytes
Line #0:
	Option  (Explicit)
Line #1:
VBA/Sheet1 - 1059 bytes
Line #0:
	Option  (Explicit)
Line #1:
VBA/Module1 - 1588 bytes
Line #0:
	Option  (Explicit)
Line #1:
Line #2:
	FuncDefn (Public Sub test())
Line #3:
	Dim 
	VarDefn z (As String)
Line #4:
	LitStr 0x000A "abcdあいう"
	St z 
Line #5:
	Ld z 
	ArgsCall MsgBox 0x0001 
Line #6:
	EndSub 

以下のようにpcode2codeコマンドの-pオプションにp-codeが記述されたテキストを与えることで、そのVisualBasicのソースコードが確認できます。

C:\dev\python3\pcodedmp\pcodedmp>pcode2code -p code.txt
stream : VBA/ThisWorkbook - 1067 bytes
########################################

Option Explicit

stream : VBA/Sheet1 - 1059 bytes
########################################

Option Explicit

stream : VBA/Module1 - 1588 bytes
########################################

Option Explicit

Public Sub test()
  Dim z As String
  z = "abcdあいう"
  MsgBox z
End Sub

まとめ

OfficeのVBAはCompound File Binary File Formatの形式でファイル内に格納されています。
この内容は、Office VBA File Format Structureに記載されています。ただし、ここには、中間コードについての詳細は記載されていません。

ファイルから圧縮されたVBAのソースコードを抜き出すにはolevbaを使用すること抽出できます。
また、中間コードのp-codeを抜き出すにはpcodedmp.py - A VBA p-code disassemblerが使用できます。

p-codeが一度実行されたあとに作成されるキャッシュの内容を解析できる方法は現時点で確認できませんでした。

VBAはp-codeまたはキャッシュされた内容で動作するので、ソースコードで記載した通りに動作しない場合は、ここで詳細した方法でp-codeの内容を確認することで解決する可能性があるかもしれません。(未検証)

参考

Compound File Binary Format
https://en.wikipedia.org/wiki/Compound_File_Binary_Format

[MS-CFB]: Compound File Binary File Format
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b

[MS-OVBA]: Office VBA File Format Structure
https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ovba/575462ba-bf67-4190-9fac-c275523c75fc

olevba
https://github.com/decalage2/oletools/wiki/olevba

pcodedmp.py - A VBA p-code disassembler
https://github.com/bontchev/pcodedmp

pcode2code.py - A VBA p-code decompiler
https://github.com/Big5-sec/pcode2code

32
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?