initialcommit

This commit is contained in:
Your Name
2026-06-05 16:58:34 -04:00
commit 1512e6b456
17 changed files with 7237 additions and 0 deletions
+398
View File
@@ -0,0 +1,398 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
+37
View File
@@ -0,0 +1,37 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.6.11819.183 stable
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ADWS-BOF", "ADWS-BOF\ADWS-BOF.vcxproj", "{20D4C7A1-5B0E-489D-8871-DE3C9961663E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
UnitTest|x64 = UnitTest|x64
UnitTest|x86 = UnitTest|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.Debug|x64.ActiveCfg = Debug|x64
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.Debug|x64.Build.0 = Debug|x64
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.Debug|x86.ActiveCfg = Debug|Win32
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.Debug|x86.Build.0 = Debug|Win32
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.Release|x64.ActiveCfg = Release|x64
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.Release|x64.Build.0 = Release|x64
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.Release|x86.ActiveCfg = Release|Win32
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.Release|x86.Build.0 = Release|Win32
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.UnitTest|x64.ActiveCfg = UnitTest|x64
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.UnitTest|x64.Build.0 = UnitTest|x64
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.UnitTest|x86.ActiveCfg = UnitTest|Win32
{20D4C7A1-5B0E-489D-8871-DE3C9961663E}.UnitTest|x86.Build.0 = UnitTest|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {05614438-CD94-4CA7-899A-327C067C1C3B}
EndGlobalSection
EndGlobal
+256
View File
@@ -0,0 +1,256 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="UnitTest|Win32">
<Configuration>UnitTest</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="UnitTest|x64">
<Configuration>UnitTest</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{20D4C7A1-5B0E-489D-8871-DE3C9961663E}</ProjectGuid>
<RootNamespace>BOFTemplate</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>ADWS-BOF</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<LocalDebuggerCommand>Debug\bof.exe</LocalDebuggerCommand>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<LocalDebuggerCommand>x64\Debug\bof.exe</LocalDebuggerCommand>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Makefile</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<NMakeBuildCommandLine>nmake /A all</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>nmake /A all</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
<AdditionalOptions>/std:c++20</AdditionalOptions>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<NMakeBuildCommandLine>nmake /A all</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>nmake /A all</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
<AdditionalOptions>/std:c++20</AdditionalOptions>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<NMakeBuildCommandLine>nmake /A all-debug</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>nmake /A all-debug</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
<AdditionalOptions>/std:c++20</AdditionalOptions>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|Win32'">
<NMakeBuildCommandLine>nmake /A all-debug</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>nmake /A all-debug</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<NMakeBuildCommandLine>nmake /A all-debug</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>nmake /A all-debug</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
<IncludePath>$(MSBuildProjectDirectory)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\build\native\include;$(IncludePath)</IncludePath>
<AdditionalOptions>/std:c++20</AdditionalOptions>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|x64'">
<NMakeBuildCommandLine>nmake /A all-debug</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>nmake /A all-debug</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>nmake clean</NMakeCleanCommandLine>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_GTEST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<AdditionalIncludeDirectories>$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\build\native\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-static\$(PlatformTarget)\Debug\gtest_maind.lib;$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-static\$(PlatformTarget)\Debug\gtestd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='UnitTest|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;_GTEST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\build\native\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-static\$(PlatformTarget)\Debug\gtest_maind.lib;$(SolutionDir)\packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\lib\native\v140\windesktop\msvcstl\static\rt-static\$(PlatformTarget)\Debug\gtestd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="base\mock.cpp" />
<ClCompile Include="base\mock_syscalls.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='UnitTest|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='UnitTest|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="adwsldap.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">C:\Users\User\source\repos\BOF-Template\x64\Debug\bof.exe</ExcludedFromBuild>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="base\helpers.h" />
<ClInclude Include="base\mock.h" />
<ClInclude Include="beacon.h" />
<ClInclude Include="beacon_gate.h" />
<ClInclude Include="sleepmask.h" />
</ItemGroup>
<ItemGroup>
<None Include="Makefile" />
<None Include="packages.config" />
<None Include="utils\boflint.py" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Source Files\base">
<UniqueIdentifier>{b74d73f4-acc9-410d-a627-90481555d13a}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\base">
<UniqueIdentifier>{8fa0224c-f4ab-4998-991b-bf2b56db2848}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="adwsldap.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="base\mock.cpp">
<Filter>Source Files\base</Filter>
</ClCompile>
<ClCompile Include="base\mock_syscalls.cpp">
<Filter>Source Files\base</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="beacon.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="base\helpers.h">
<Filter>Header Files\base</Filter>
</ClInclude>
<ClInclude Include="base\mock.h">
<Filter>Header Files\base</Filter>
</ClInclude>
<ClInclude Include="beacon_gate.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="sleepmask.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Makefile" />
<None Include="packages.config" />
<None Include="utils\boflint.py" />
</ItemGroup>
</Project>
+51
View File
@@ -0,0 +1,51 @@
CFLAGS=/c /GS- /std:c++20 /D_HAS_EXCEPTIONS=0 /GR- /Gs9999999
DEBUGCFLAGS=/Zi /MTd /D_DEBUG /EHsc /std:c++20
PYTHON=python
!IF "$(PROCESSOR_ARCHITECTURE)" != "x86" && "$(PROCESSOR_ARCHITECTURE)" != "AMD64"
!ERROR Only x86 and AMD64 architectures are supported or the PROCESSOR_ARCHITECTURE environment variable is not set.
!ELSEIF "$(PROCESSOR_ARCHITECTURE)" == "AMD64"
OUTDIR=..\x64\Release\
IMDIR=x64\Release\
DOUTDIR=..\x64\Debug\
DIMDIR=x64\Debug\
OUTEXT=.x64.o
!ELSE
OUTDIR=..\Release\
IMDIR=Release\
DIMDIR=Debug\
DOUTDIR=..\Debug\
OUTEXT=.x86.o
!ENDIF
all: *.cpp
@$(MAKE) /A $(patsubst %.c,%.obj, $(patsubst %.cpp, %.obj, $(patsubsti %, $(IMDIR)\%, $**)))
@if not exist "$(OUTDIR)" mkdir "$(OUTDIR)"
copy "$(IMDIR)\*.obj" "$(OUTDIR)"
del /F "$(OUTDIR)\*$(OUTEXT)"
ren "$(OUTDIR)*.obj" "*$(OUTEXT)"
all-debug: *.cpp
@$(MAKE) /A $(patsubst %.c,%.exe, $(patsubst %.cpp, %.exe, $(patsubsti %, $(DOUTDIR)\%, $**)))
.cpp{$(IMDIR)}.obj:
@if not exist "$(IMDIR)" mkdir "$(IMDIR)"
$(CPP) $(CFLAGS) /Fo"$@" $<
@(where $(PYTHON) >nul 2>nul \
&& $(PYTHON) --version >nul 2>nul \
|| (echo [*] Install Python to enable boflint && exit /b 0) \
) && $(PYTHON) utils\boflint.py --logformat vs --loader cs "$@"
.cpp{$(DOUTDIR)}.exe:
@if not exist "$(DIMDIR)" mkdir "$(DIMDIR)"
@if not exist "$(DOUTDIR)" mkdir "$(DOUTDIR)"
$(CPP) $(DEBUGCFLAGS) /Fo$(DIMDIR) /Fd$(DIMDIR) /Fe"$@" $< base/mock.cpp
clean:
@if exist "$(DIMDIR)" rmdir /Q /S "$(DIMDIR)"
@if exist "$(IMDIR)" rmdir /Q /S "$(IMDIR)"
@if exist "$(OUTDIR)" rmdir /Q /S "$(OUTDIR)"
@if exist "$(DOUTDIR)" rmdir /Q /S "$(DOUTDIR)"
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
#ifdef __cplusplus
#ifndef _DEBUG
#define DFR(module, function) \
DECLSPEC_IMPORT decltype(function) module##$##function;
#define DFR_LOCAL(module, function) \
DECLSPEC_IMPORT decltype(function) module##$##function; \
decltype(module##$##function) * function = module##$##function;
#else
#define DFR_LOCAL(module, function)
#define DFR(module, function) \
decltype(function) *module##$##function = function;
#endif // end of _DEBUG
#endif // end of __cplusplus
+724
View File
@@ -0,0 +1,724 @@
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <utility>
#include <cstring>
#include <map>
#include <tuple>
#include <cassert>
#include <windows.h>
extern "C" {
#ifdef _DEBUG
#undef DECLSPEC_IMPORT
#define DECLSPEC_IMPORT
#endif
#include "..\beacon.h"
}
#include "mock.h"
namespace bof {
namespace utils {
template <typename T>
T swapEndianness(T value) {
char *ptr = reinterpret_cast<char *>(&value);
std::reverse(ptr, ptr + sizeof(T));
return value;
}
template <typename T>
std::vector<char> toBytes(T input) {
char *ptr = reinterpret_cast<char *>(&input);
return std::vector<char>(ptr, ptr + sizeof(T));
}
const char* typeToStr(int callbackType) {
switch (callbackType) {
case CALLBACK_OUTPUT: return "CALLBACK_OUTPUT";
case CALLBACK_OUTPUT_OEM: return "CALLBACK_OUTPUT_OEM";
case CALLBACK_ERROR: return "CALLBACK_ERROR";
case CALLBACK_OUTPUT_UTF8: return "CALLBACK_OUTPUT_UTF8";
default: return "CALLBACK_UNKOWN";
}
}
}
namespace mock {
static BEACON_INFO beaconInfo = { 0 };
char *BofData::get() {
return size() > 0 ? reinterpret_cast<char *>(&data[0]) : nullptr;
}
int BofData::size() {
return data.size();
}
void BofData::addData(const char *buf, std::size_t len) {
std::vector<char> bytes;
bytes.assign(buf, buf + len);
insert(static_cast<int>(len));
append(bytes);
}
void BofData::append(const std::vector<char> &data) {
this->data.insert(std::end(this->data), std::begin(data), std::end(data));
}
void BofData::insert(int v) {
append(bof::utils::toBytes(bof::utils::swapEndianness(v)));
}
void BofData::insert(short v) {
append(bof::utils::toBytes(bof::utils::swapEndianness(v)));
}
void BofData::insert(unsigned int v) {
insert(static_cast<int>(v));
}
void BofData::insert(unsigned short v) {
insert(static_cast<short>(v));
}
void BofData::insert(const char *v) {
addData(v, std::strlen(v) + 1);
}
void BofData::insert(const wchar_t *v) {
addData((const char *)v, (std::wcslen(v) + 1) * sizeof(wchar_t));
}
void BofData::insert(const std::vector<char>& data) {
pack<int32_t>(data.size());
append(data);
}
void setBeaconInfo(BEACON_INFO& info) {
std::memcpy(&bof::mock::beaconInfo, &info, sizeof(BEACON_INFO));
}
void setSection(PALLOCATED_MEMORY_SECTION info, PVOID baseAddress, SIZE_T size, DWORD finalProtection, DWORD allocationType, ALLOCATED_MEMORY_LABEL label) {
info->BaseAddress = baseAddress;
info->VirtualSize = size;
info->PreviousProtect = finalProtection;
info->CurrentProtect = finalProtection;
info->MaskSection = TRUE;
info->Label = label;
}
ALLOCATED_MEMORY_REGION allocateBeaconMemory(const bof::profile::Stage& stage, size_t size, DWORD* initialPermission) {
ALLOCATED_MEMORY_REGION info;
std::memset(&info, 0, sizeof(info));
/* Allocate the base memory for Beacon. The following .stage options affect how the memory is allocated:
* - .stage.allocator: Set how Beacon's Reflective Loader allocates memory for the agent. Options are: HeapAlloc, MapViewOfFile, and VirtualAlloc.
* - .stage.module_x64/x86: Ask the ReflectiveLoader to load the specified library and overwrite its space instead of allocating memory with the .stage.allocator.
*/
*initialPermission = stage.useRWX == bof::profile::UseRWX::True ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
if (stage.module.empty()) {
HANDLE hHeap = INVALID_HANDLE_VALUE;
HANDLE hFile = INVALID_HANDLE_VALUE;
switch (stage.allocator) {
case bof::profile::Allocator::HeapAlloc: {
// For heap allocator we don't honor the .stage.userwx flag
*initialPermission = PAGE_EXECUTE_READWRITE;
hHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
assert(hHeap != NULL && "Could not create the heap");
info.AllocationBase = HeapAlloc(hHeap, 0, size);
info.Type = MEM_PRIVATE;
info.CleanupInformation.AllocationMethod = METHOD_HEAPALLOC;
info.CleanupInformation.AdditionalCleanupInformation.HeapAllocInfo.HeapHandle = hHeap;
info.CleanupInformation.AdditionalCleanupInformation.HeapAllocInfo.DestroyHeap = TRUE;
break;
}
case bof::profile::Allocator::MapViewOfFile: {
hFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, size, NULL);
assert(hFile != NULL && "Could not create file mapping");
info.AllocationBase = MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE, 0, 0, 0);
info.Type = MEM_MAPPED;
info.CleanupInformation.AllocationMethod = METHOD_NTMAPVIEW;
CloseHandle(hFile);
DWORD old = 0;
if (!VirtualProtect(info.AllocationBase, size, *initialPermission, &old)) {
assert(false && "Could not set the initial memory permission for the Beacon memory");
}
break;
}
case bof::profile::Allocator::VirtualAlloc: {
info.AllocationBase = VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, *initialPermission);
info.Type = MEM_PRIVATE;
info.CleanupInformation.AllocationMethod = METHOD_VIRTUALALLOC;
break;
}
}
}
else {
// For simplicity we use module base here instead of resolving a function
info.AllocationBase = (PVOID)LoadLibraryExA(stage.module.c_str(), NULL, DONT_RESOLVE_DLL_REFERENCES);
info.Type = MEM_IMAGE;
info.CleanupInformation.AllocationMethod = METHOD_MODULESTOMP;
DWORD old = 0;
if (!VirtualProtect(info.AllocationBase, size, *initialPermission, &old)) {
assert(false && "Could not set the initial memory permission for the Beacon memory");
}
}
assert(info.AllocationBase != NULL && "Could not allocate the base memory for Beacon");
// Set the necessary fields for the Beacon memory
info.Purpose = PURPOSE_BEACON_MEMORY;
info.RegionSize = size;
info.CleanupInformation.Cleanup = TRUE;
return info;
}
BEACON_INFO setupMockBeacon(const bof::profile::Stage& stage) {
const std::vector<std::tuple<ALLOCATED_MEMORY_LABEL, std::string>> sections{
{ LABEL_PEHEADER, "PE" },
{ LABEL_TEXT, ".text" },
{ LABEL_RDATA, ".rdata" },
{ LABEL_DATA, ".data" },
#ifdef _M_X64
{ LABEL_PDATA, ".pdata" },
#endif
{ LABEL_RELOC, ".reloc" },
};
const size_t sectionSize = 0x1000;
BEACON_INFO info = { 0 };
info.version = bof::CsVersion;
// Set the static mask key
for (size_t i = 0; i < MASK_SIZE; ++i) {
info.mask[i] = 0xAB;
}
// Allocate the memory for Beacon
DWORD initialProtection = 0;
ALLOCATED_MEMORY_REGION beaconMemory = allocateBeaconMemory(stage, sections.size() * sectionSize, &initialProtection);
info.allocatedMemory.AllocatedMemoryRegions[0] = beaconMemory;
info.beacon_ptr = stage.obfuscate == bof::profile::Obfuscate::False
? reinterpret_cast<char*>(beaconMemory.AllocationBase)
: reinterpret_cast<char*>(beaconMemory.AllocationBase) - 0x1000;
// Build the section list
PALLOCATED_MEMORY_SECTION nextSection = info.allocatedMemory.AllocatedMemoryRegions[0].Sections;
char* sectionBase = reinterpret_cast<char*>(beaconMemory.AllocationBase);
for (auto [section, name] : sections) {
// Skip the PE header if .stage.obfuscate = true
if (stage.obfuscate == bof::profile::Obfuscate::True && section == LABEL_PEHEADER) {
continue;
}
// Fill the memory
for (size_t i = 0; i < sectionSize; ++i) {
sectionBase[i] = name[i % name.length()];
}
DWORD finalProtection = initialProtection;
// Fix the .code section permission if the allocator is not heap alloc and .stage.userwx is false
if (stage.allocator != bof::profile::Allocator::HeapAlloc || !stage.module.empty()) {
if (section == LABEL_TEXT && stage.useRWX == bof::profile::UseRWX::False) {
finalProtection = PAGE_EXECUTE_READ;
// Fix the permissions
DWORD old = 0;
if (!VirtualProtect(sectionBase, sectionSize, finalProtection, &old)) {
assert(false && "Could not set the final memory protection");
}
}
}
// Set the section memory information
setSection(nextSection, sectionBase, sectionSize, finalProtection, beaconMemory.Type, section);
// Next section
++nextSection;
sectionBase += sectionSize;
}
// Add few mock heap records
const size_t numberOfHeapRecords = 2;
info.heap_records = new HEAP_RECORD[numberOfHeapRecords + 1];
assert(info.heap_records != nullptr && "Could not the allocate heap records array");
for (size_t i = 0; i < numberOfHeapRecords; ++i) {
info.heap_records[i].ptr = new char[512];
info.heap_records[i].size = 512;
assert(info.heap_records[i].ptr != nullptr && "Could not allocate a heap record");
}
info.heap_records[numberOfHeapRecords].ptr = NULL;
info.heap_records[numberOfHeapRecords].size = 0;
return info;
}
void resolveMockUpSleepmaskLocation(BEACON_INFO& info) {
// Get the base address of the debug exe
char* exeBase = (char*)GetModuleHandleA(NULL);
// Find the start of the section headers
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)exeBase;
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)(exeBase + dosHeader->e_lfanew);
PIMAGE_SECTION_HEADER sectionHeader = (PIMAGE_SECTION_HEADER)(reinterpret_cast<char*>(&ntHeader->OptionalHeader) + ntHeader->FileHeader.SizeOfOptionalHeader);
// Find the .text section which will be our "mock" sleepmask code
DWORD numberOfSections = ntHeader->FileHeader.NumberOfSections;
while (numberOfSections--) {
if (strcmp(reinterpret_cast<char*>(sectionHeader->Name), ".text") == 0) {
info.sleep_mask_ptr = exeBase + sectionHeader->VirtualAddress;
info.sleep_mask_text_size = sectionHeader->Misc.VirtualSize;
// In theory, the total size would contains also the data part, but we cannot guarantee the section order.
info.sleep_mask_total_size = sectionHeader->Misc.VirtualSize;
return;
}
++sectionHeader;
}
assert(false && "Could not find the text section of the debug image");
}
FUNCTION_CALL createFunctionCallStructure(PVOID targetFunction, WinApi targetFunctionName, BOOL bMask, int numOfArgs, ...) {
FUNCTION_CALL functionCall = { 0 };
va_list valist = NULL;
/* Set basic info for encapsulated function call */
functionCall.functionPtr = targetFunction;
functionCall.function = targetFunctionName;
functionCall.bMask = bMask;
functionCall.numOfArgs = numOfArgs;
/* Start parsing valist and copy over variadic arguments. */
va_start(valist, numOfArgs);
for (int i = 0; i < numOfArgs; i++)
{
functionCall.args[i] = va_arg(valist, ULONG_PTR);
}
va_end(valist);
return functionCall;
}
}
namespace output {
std::vector<OutputEntry> outputs;
void addEntry(int type, const char* data, int len) {
OutputEntry output = {
type,
std::string(data, data + len)
};
outputs.push_back(output);
}
const std::vector<OutputEntry>& getOutputs() {
return outputs;
}
void reset() {
outputs.clear();
}
void PrintTo(const OutputEntry& o, std::ostream* os) {
*os << "{ callbackType: " << bof::utils::typeToStr(o.callbackType) << ", output: " << o.output << " }";
}
}
namespace valuestore {
std::map<std::string, void*> values;
void reset() {
values.clear();
}
}
namespace bud {
char custom[BEACON_USER_DATA_CUSTOM_SIZE] = { 0 };
void reset() {
std::memset(custom, 0, BEACON_USER_DATA_CUSTOM_SIZE);
}
void set(const char* data) {
if (data) {
std::memcpy(custom, data, BEACON_USER_DATA_CUSTOM_SIZE);
}
}
}
std::vector<bof::output::OutputEntry> runMockedSleepMask(SLEEPMASK_FUNC sleepMaskFunc, PBEACON_INFO beaconInfo, PFUNCTION_CALL functionCall) {
// Reset the global output container
bof::output::reset();
// Execute the entrypoint
sleepMaskFunc(beaconInfo, functionCall);
// Return the stored outputs
return bof::output::getOutputs();
}
std::vector<bof::output::OutputEntry> runMockedSleepMask(SLEEPMASK_FUNC sleepMaskFunc, const bof::profile::Stage& stage, const bof::mock::MockSleepMaskConfig& config) {
BEACON_INFO beaconInfo = bof::mock::setupMockBeacon(stage);
FUNCTION_CALL functionCall = bof::mock::createFunctionCallStructure(Sleep, SLEEP, TRUE, 1, config.sleepTimeMs);
bof::mock::resolveMockUpSleepmaskLocation(beaconInfo);
bof::mock::setBeaconInfo(beaconInfo);
std::vector<bof::output::OutputEntry> output;
do {
output = runMockedSleepMask(sleepMaskFunc, &beaconInfo, &functionCall);
} while (config.runForever);
return output;
}
std::vector<bof::output::OutputEntry> runMockedSleepMask(SLEEPMASK_FUNC sleepMaskFunc, const bof::profile::Stage& stage) {
// Set the default config for the mock sleepmask runner
const bof::mock::MockSleepMaskConfig config = {
.sleepTimeMs = 5000,
.runForever = false,
};
return runMockedSleepMask(sleepMaskFunc, stage, config);
}
std::vector<bof::output::OutputEntry> runMockedSleepMask(SLEEPMASK_FUNC sleepMaskFunc) {
return runMockedSleepMask(sleepMaskFunc, bof::profile::defaultStage);
}
std::vector<bof::output::OutputEntry> runMockedBeaconGate(SLEEPMASK_FUNC sleepMaskFunc, PFUNCTION_CALL functionCall, const bof::profile::Stage& stage) {
BEACON_INFO beaconInfo = bof::mock::setupMockBeacon(stage);
bof::mock::resolveMockUpSleepmaskLocation(beaconInfo);
bof::mock::setBeaconInfo(beaconInfo);
return runMockedSleepMask(sleepMaskFunc, &beaconInfo, functionCall);
}
std::vector<bof::output::OutputEntry> runMockedBeaconGate(SLEEPMASK_FUNC sleepMaskFunc, PFUNCTION_CALL functionCall) {
return runMockedBeaconGate(sleepMaskFunc, functionCall, bof::profile::defaultStage);
}
}
#include "mock_syscalls.cpp"
extern "C"
{
// Print API
void BeaconPrintf(int type, const char *fmt, ...) {
printf("[Output Callback: %s (0x%X)]\n", bof::utils::typeToStr(type), type);
va_list args;
va_start(args, fmt);
int size = vsnprintf(nullptr, 0, fmt, args);
if (size >= 0) {
char* buffer = new char[size + 1];
vsnprintf(buffer, size + 1, fmt, args);
bof::output::addEntry(type, buffer, size);
delete[] buffer;
}
vprintf(fmt, args);
printf("\n");
va_end(args);
}
BOOL BeaconDownload(const char* filename, const char* buffer, unsigned int length) {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
return FALSE;
}
void BeaconOutput(int type, const char *data, int len) {
bof::output::addEntry(type, data, len);
printf("[Output Callback: %s (0x%X)]\n%.*s", bof::utils::typeToStr(type), type, len, data);
}
// Parser API
void BeaconDataParse(datap *parser, char *buffer, int size) {
parser->buffer = buffer;
parser->original = buffer;
parser->size = size;
parser->length = size;
}
char* BeaconDataPtr(datap* parser, int size) {
if (parser->length < size) {
return NULL;
}
char* data = parser->buffer;
parser->buffer += size;
parser->length -= size;
return data;
}
int BeaconDataInt(datap *parser) {
int value = *(int *)(parser->buffer);
parser->buffer += sizeof(int);
parser->length -= sizeof(int);
return bof::utils::swapEndianness(value);
}
short BeaconDataShort(datap *parser) {
short value = *(short *)(parser->buffer);
parser->buffer += sizeof(short);
parser->length -= sizeof(short);
return bof::utils::swapEndianness(value);
}
int BeaconDataLength(datap *parser) {
return parser->length;
}
char *BeaconDataExtract(datap *parser, int *size) {
int size_im = BeaconDataInt(parser);
char *buff = parser->buffer;
parser->buffer += size_im;
if (size)
{
*size = size_im;
}
return buff;
}
// Format API
void BeaconFormatAlloc(formatp *format, int maxsz) {
format->original = new char[maxsz];
format->buffer = format->original;
format->length = maxsz;
format->size = maxsz;
}
void BeaconFormatReset(formatp *format) {
format->buffer = format->original;
format->length = format->size;
}
void BeaconFormatFree(formatp *format) {
delete[] format->original;
}
void BeaconFormatAppend(formatp *format, const char *text, int len) {
memcpy(format->buffer, text, len);
format->buffer += len;
format->length -= len;
}
void BeaconFormatPrintf(formatp *format, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int len = vsprintf_s(format->buffer, format->length, fmt, args);
format->buffer += len;
format->length -= len;
va_end(args);
}
char *BeaconFormatToString(formatp *format, int *size) {
if (size)
{
*size = format->size - format->length;
}
return format->original;
}
void BeaconFormatInt(formatp *format, int value) {
value = bof::utils::swapEndianness(value);
BeaconFormatAppend(format, (char *)&value, 4);
}
// Internal API
BOOL BeaconUseToken(HANDLE token) {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
return TRUE;
}
void BeaconRevertToken() {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
BOOL BeaconIsAdmin() {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
return FALSE;
}
void BeaconGetSpawnTo(BOOL x86, char *buffer, int length) {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
void BeaconInjectProcess(HANDLE hProc, int pid, char *payload,
int p_len, int p_offset, char *arg,
int a_len)
{
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
void BeaconInjectTemporaryProcess(PROCESS_INFORMATION *pInfo,
char *payload, int p_len,
int p_offset, char *arg,
int a_len)
{
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO* si, PROCESS_INFORMATION* pInfo) {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
return false;
}
void BeaconCleanupProcess(PROCESS_INFORMATION *pInfo) {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
BOOL toWideChar(char *src, wchar_t *dst, int max) {
std::string str = src;
std::wstring wstr(str.begin(), str.end());
size_t bytes = min(wstr.length() * sizeof(wchar_t), max);
std::memcpy(dst, wstr.c_str(), bytes);
return TRUE;
}
BOOL BeaconInformation(PBEACON_INFO info) {
std::memcpy(info, &bof::mock::beaconInfo, sizeof(BEACON_INFO));
return TRUE;
}
BOOL BeaconAddValue(const char* key, void* ptr) {
auto item = bof::valuestore::values.find(std::string(key));
if (ptr && item == bof::valuestore::values.end()) {
bof::valuestore::values[std::string(key)] = ptr;
return TRUE;
}
return FALSE;
}
void* BeaconGetValue(const char* key) {
auto item = bof::valuestore::values.find(std::string(key));
if (item != bof::valuestore::values.end()) {
return item->second;
}
return NULL;
}
BOOL BeaconRemoveValue(const char* key) {
auto item = bof::valuestore::values.find(std::string(key));
if (item != bof::valuestore::values.end()) {
bof::valuestore::values.erase(item);
return TRUE;
}
return FALSE;
}
PDATA_STORE_OBJECT BeaconDataStoreGetItem(size_t index) {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
return NULL;
}
void BeaconDataStoreProtectItem(size_t index) {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
void BeaconDataStoreUnprotectItem(size_t index) {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
size_t BeaconDataStoreMaxEntries() {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
return 0;
}
char* BeaconGetCustomUserData() {
return bof::bud::custom;
}
BOOL BeaconGetSyscallInformation(PBEACON_SYSCALLS info, SIZE_T infoSize, BOOL resolveIfNotInitialized) {
if (infoSize != sizeof(BEACON_SYSCALLS)) {
return FALSE;
}
if (info) {
bof::mock::syscall::ResolveSyscalls(&info->syscalls);
bof::mock::syscall::ResolveRtls(&info->rtls);
return TRUE;
}
return FALSE;
}
// Beacon System Call API
LPVOID BeaconVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) {
return VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect);
}
LPVOID BeaconVirtualAllocEx(HANDLE processHandle, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) {
return VirtualAllocEx(processHandle, lpAddress, dwSize, flAllocationType, flProtect);
}
BOOL BeaconVirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) {
return VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect);
}
BOOL BeaconVirtualProtectEx(HANDLE processHandle, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) {
return VirtualProtectEx(processHandle, lpAddress, dwSize, flNewProtect, lpflOldProtect);
}
BOOL BeaconVirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType) {
return VirtualFree(lpAddress, dwSize, dwFreeType);
}
BOOL BeaconGetThreadContext(HANDLE threadHandle, PCONTEXT threadContext) {
return GetThreadContext(threadHandle, threadContext);
}
BOOL BeaconSetThreadContext(HANDLE threadHandle, PCONTEXT threadContext) {
return SetThreadContext(threadHandle, threadContext);
}
DWORD BeaconResumeThread(HANDLE threadHandle) {
return ResumeThread(threadHandle);
}
HANDLE BeaconOpenProcess(DWORD desiredAccess, BOOL inheritHandle, DWORD processId) {
return OpenProcess(desiredAccess, inheritHandle, processId);
}
HANDLE BeaconOpenThread(DWORD desiredAccess, BOOL inheritHandle, DWORD threadId) {
return OpenThread(desiredAccess, inheritHandle, threadId);
}
BOOL BeaconCloseHandle(HANDLE object) {
return CloseHandle(object);
}
BOOL BeaconUnmapViewOfFile(LPCVOID baseAddress) {
return UnmapViewOfFile(baseAddress);
}
SIZE_T BeaconVirtualQuery(LPCVOID address, PMEMORY_BASIC_INFORMATION buffer, SIZE_T length) {
return VirtualQuery(address, buffer, length);
}
BOOL BeaconDuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions) {
return DuplicateHandle(hSourceProcessHandle, hSourceHandle, hTargetProcessHandle, lpTargetHandle, dwDesiredAccess, bInheritHandle, dwOptions);
}
BOOL BeaconReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead) {
return ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead);
}
BOOL BeaconWriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesWritten) {
return WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
}
VOID BeaconDisableBeaconGate() {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
VOID BeaconEnableBeaconGate() {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
VOID BeaconDisableBeaconGateMasking() {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
VOID BeaconEnableBeaconGateMasking() {
std::cerr << "Not implemented: " << __FUNCTION__ << std::endl;
}
}
+299
View File
@@ -0,0 +1,299 @@
#include <vector>
#include <string>
#include "../sleepmask.h"
namespace bof {
const DWORD CsVersion = 0x041200;
namespace profile {
/**
* Enum classes to mimic the stage block in the C2 profile.
*/
enum class Allocator {
VirtualAlloc,
HeapAlloc,
MapViewOfFile
};
enum class Obfuscate {
False,
True
};
enum class UseRWX {
False,
True
};
struct Stage {
Allocator allocator;
Obfuscate obfuscate;
UseRWX useRWX;
std::string module;
};
const Stage defaultStage = {
.allocator = bof::profile::Allocator::VirtualAlloc,
.obfuscate = bof::profile::Obfuscate::False,
.useRWX = bof::profile::UseRWX::True,
.module = "",
};
}
namespace mock {
/**
* Data container class used for packing BOF arguments.
*/
class BofData {
public:
/**
* Pack a variadic number of arguments.
* Equivalent to the bof_pack function.
*
* For example, bof_pack("isz", 1, 2, "hello")
* -> pack<int, short, const char*>(1, 2, "hello")
*
* @param ... arguments
*/
template <typename... T>
void pack(T &&...v)
{
((insert(std::forward<T>(v))), ...);
}
/**
* Add binary data to the argument buffer.
* Equivalent to bof_pack("b", $data).
*
* @param buf A char pointer to the data
* @param len A length to the data
*/
void addData(const char *buf, std::size_t len);
/**
* << operator to allow an alternative way to build the argument buffer.
*
* For example: args << 123 << 32;
*
* @param container A BofData object
* @param arg An argument
*/
template <typename T>
friend BofData &operator<<(BofData &container, T arg)
{
container.pack(arg);
return container;
}
/**
* Return a raw argument buffer.
*
* @return A char pointer of raw argument buffer
*/
char* get();
/**
* Get the size of the argument buffer.
*
* @return A size of the argument buffer
*/
int size();
private:
void append(const std::vector<char> &data);
void insert(int v);
void insert(short v);
void insert(unsigned int v);
void insert(unsigned short v);
void insert(const char *v);
void insert(const wchar_t *v);
void insert(const std::vector<char>& data);
std::vector<char> data;
};
/**
* This structure holds the information about how the mock runner should
* execute the sleepmask.
*/
typedef struct {
DWORD sleepTimeMs;
bool runForever;
} MockSleepMaskConfig;
/**
* Setup memory for a mock Beacon
*
* The memory layout is created by mimicking Beacon's default reflective loader,
* and the related C2 options
*
* @param stage The applicable stage{} options
* @return the mock Beacon memory structure
*/
BEACON_INFO setupMockBeacon(const bof::profile::Stage& stage);
/**
* Setup a FUNCTION_CALL structure
*
* @param targetFunction The WINAPI function pointer
* @param targetFunctionName Human readable WinApi enum
* @param bMask Mask beacon
* @param numOfArgs number of arguments for the WINAPI function
* @param ... the arguments for the WINAPI function
*/
FUNCTION_CALL createFunctionCallStructure(PVOID targetFunction, WinApi targetFunctionName, BOOL bMask, int numOfArgs, ...);
/**
* Change the mock BEACON_INFO strucutre
*
* @param info The new beacon inforamation
*/
void setBeaconInfo(BEACON_INFO& info);
}
namespace output {
/**
* Data structure to store a output from BOF
*/
struct OutputEntry {
/**
* The callback type. E.g. CALLBACK_OUTPUT
*/
int callbackType;
/**
* The output data
*/
std::string output;
/**
* Equivalence overloading.
*
* param other Another OutputEntry object
*/
bool operator==(const OutputEntry& other) const {
return callbackType == other.callbackType && output == other.output;
}
};
/**
* Returns the list of BOF outputs
*
* @return A vector of OutputEntry objects
*/
const std::vector<OutputEntry>& getOutputs();
/**
* Clear the currently stored BOF outputs
*/
void reset();
/**
* Pretty print an OutputEntry object.
* Required by the GoogleTest.
*
* @param o An OutputEntry object
* @param os An output stream
*/
void PrintTo(const OutputEntry& o, std::ostream* os);
}
namespace valuestore {
/**
* Clear items in BOF Key/Value store
*/
void reset();
}
namespace bud {
/**
* Clear the custom data buffer in Beacon User Data
*/
void reset();
/**
* Set the custom data buffer in Beacon User Data
*
* @param data A pointer to custom data buffer
*/
void set(const char* data);
}
/**
* Execute a BOF with arguments
*
* @param entry BOF's entry point
* @param ... arguments
* @return A vector of OutputEntry objects
*/
template <typename... T>
std::vector<bof::output::OutputEntry> runMocked(void (*entry)(char*, int), T &&...v) {
// Setup mock Beacon memory
BEACON_INFO beaconInfo = bof::mock::setupMockBeacon(bof::profile::defaultStage);
bof::mock::setBeaconInfo(beaconInfo);
// Reset the global output container
bof::output::reset();
// Pack the arguments
bof::mock::BofData args;
args.pack(std::forward<T>(v)...);
// Execute the entrypoint
entry(args.get(), args.size());
// Return the stored outputs
return bof::output::getOutputs();
}
/**
* Setup a mock-up Beacon and execute the sleepmask function once with the default .stage options and mock-up config.
*
* @param sleepMaskFunc the function pointer for the sleepmask
* @return A vector of OutputEntry objects
*/
std::vector<bof::output::OutputEntry> runMockedSleepMask(SLEEPMASK_FUNC sleepMaskFunc);
/**
* Setup a mock-up Beacon and execute the sleepmask function using a custom stage profile, and the default mock-up config.
*
* @param sleepMaskFunc the function pointer for the sleepmask
* @param stage the stage options
* @return A vector of OutputEntry objects
*/
std::vector<bof::output::OutputEntry> runMockedSleepMask(SLEEPMASK_FUNC sleepMaskFunc, const bof::profile::Stage& stage);
/**
* Setup a mock-up Beacon and execute the sleepmask function using a custom stage profile and mock-up config.
*
* @param sleepMaskFunc the function pointer for the sleepmask
* @param stage the applicable stage{} options
* @param config the mockup config
* @return A vector of OutputEntry objects
*/
std::vector<bof::output::OutputEntry> runMockedSleepMask(SLEEPMASK_FUNC sleepMaskFunc, const bof::profile::Stage& stage, const bof::mock::MockSleepMaskConfig& config);
/**
* Execute the sleepmask.
*
* @param sleepMaskFunc the function pointer for the sleepmask
* @param sleepMaskInfo the pointer to the SLEEPMASK_INFO structure
* @param functionCall the pointer to the FUNCTION_CALL structure
* @return A vector of OutputEntry objects
*/
std::vector<bof::output::OutputEntry> runMockedSleepMask(SLEEPMASK_FUNC sleepMaskFunc, PBEACON_INFO beaconInfo, PFUNCTION_CALL functionCall);
/**
* Setup a mock-up Beacon and execute the sleepmask function as Beacon Gate with the default stage block.
*
* @param sleepMaskFunc the function pointer for the sleepmask
* @param functionCall the pointer to FUNCTION_CALL structure
* @return A vector of OutputEntry objects
*/
std::vector<bof::output::OutputEntry> runMockedBeaconGate(SLEEPMASK_FUNC sleepMaskFunc, PFUNCTION_CALL functionCall);
/**
* Setup a mock-up Beacon and execute the sleepmask function as Beacon Gate with a custom stage profile.
*
* @param sleepMaskFunc the function pointer for the sleepmask
* @param functionCall the pointer to FUNCTION_CALL structure
* @param stage the stage options
* @return A vector of OutputEntry objects
*/
std::vector<bof::output::OutputEntry> runMockedBeaconGate(SLEEPMASK_FUNC sleepMaskFunc, PFUNCTION_CALL functionCall, const bof::profile::Stage& stage);
}
+216
View File
@@ -0,0 +1,216 @@
#include <windows.h>
#include <cassert>
extern "C" {
#ifdef _DEBUG
#undef DECLSPEC_IMPORT
#define DECLSPEC_IMPORT
#endif
#include "..\beacon.h"
}
namespace bof {
namespace mock {
namespace syscall {
/**
* @brief A function to find the first occurrence of a system call instruction in memory.
*
* This function searches for a specific system call instruction pattern within the
* memory block starting at the given address 'addr'. The pattern to search for
* depends on the target architecture:
*
* - On x64, the assembly pattern is: 'syscall; ret;' (0x0f, 0x05, 0xc3).
* - On x86, the assembly pattern is: 'sysenter; ret;' (0x0f, 0x34, 0xc3).
*
* @param addr A pointer to the starting address of the memory block.
* @return A pointer to the first occurrence of the system call instruction pattern
* if found, or NULL if the pattern is not found within the first 32 bytes.
*
*/
PBYTE FindSyscallInstruction(PBYTE addr) {
#if _M_X64
char syscallPattern[] = { '\x0f', '\x05', '\xc3' }; // syscall; ret;
#else
char syscallPattern[] = { '\x0f', '\x34', '\xc3' }; // sysenter; ret;
#endif
for (int offset = 0; offset < 32; ++offset) {
if (!memcmp(syscallPattern, (char*)addr + offset, sizeof(syscallPattern))) {
return addr + offset;
}
}
return NULL;
}
/**
* @brief Find the system call number in the memory block
*
* This function searches for a specific pattern within the memory block starting at the
* given address 'addr' to identify the system call number. The pattern to search for
* depends on the target architecture:
*
* - On x64, the assembly pattern is: 'mov r10, rcx; mov eax, <syscall num>' (0x4c, 0x8b, 0xd1, 0xb8).
* - On x86, the assembly pattern is: 'mov eax, <syscall num>' (0xb8).
*
* @param addr A pointer to the starting address of the memory block.
* @return The system call number found in memory following the pattern, or 0 if the
* pattern is not found within the first 32 bytes.
*
*/
DWORD FindSyscallNumber(PBYTE addr) {
#if _M_X64
char syscallPattern[] = { '\x4c', '\x8b', '\xd1', '\xb8' };
#else
char syscallPattern[] = { '\xb8' };
#endif
for (int offset = 0; offset < 32; ++offset) {
if (!memcmp(syscallPattern, (char*)addr + offset, sizeof(syscallPattern))) {
DWORD* numAddress = (DWORD*)(addr + offset + sizeof(syscallPattern));
return *numAddress;
}
}
return 0;
}
/**
* A function to resolve the a system call number and function address.
*
* @param entry A pointer to a SYSCALL_API_ENTRY structure where resolved information will be stored.
* @param funcHash Hash value representing the target function to resolve.
* @return Returns TRUE if the resolution is successful; otherwise, returns FALSE.
*
*/
BOOL ResolveSyscallEntry(PSYSCALL_API_ENTRY entry, const char* funcName) {
// Resolve the NT function address
static HMODULE ntdll = LoadLibraryA("ntdll");
PVOID fnAddr = GetProcAddress(ntdll, funcName);
if (!fnAddr) {
return FALSE;
}
// Find the syscall number
DWORD sysnum = FindSyscallNumber((PBYTE)fnAddr);
// Find the address of the syscall instruction
PVOID jmpAddr = FindSyscallInstruction((PBYTE)fnAddr);
#ifdef _M_IX86
if (!jmpAddr) {
jmpAddr = (PVOID)__readfsdword(0xc0); // If WoW64, this returns wow64cpu!X86SwitchTo64BitMode
}
#endif
// We did not find the syscall
if (sysnum == 0 || jmpAddr == NULL) {
return FALSE;
}
// Fill the entry
entry->fnAddr = fnAddr;
entry->sysnum = sysnum;
entry->jmpAddr = jmpAddr;
return TRUE;
}
/**
* A helper macro for resolving an SYSCALL_API_ENTRY.
*
* @param field The field in the SYSCALL_API structure in which the resolved entry will be stored.
* @param name The function name used to generate a compile-time hash for entry lookup.
*/
#define RESOLVE_ENTRY(field, name) { \
if(!ResolveSyscallEntry(&field, name)) { assert(false && "Could not resolve the syscall entry"); } \
}
/**
* Resolve system call function addresses and syscall numbers.
*
* @param syscalls A pointer to a SYSCALL_API structure.
* @return TRUE if all system call entries are successfully resolved, FALSE otherwise.
*
*/
void ResolveSyscalls(PSYSCALL_API syscalls) {
RESOLVE_ENTRY(syscalls->ntAllocateVirtualMemory, "NtAllocateVirtualMemory");
RESOLVE_ENTRY(syscalls->ntAllocateVirtualMemory, "NtAllocateVirtualMemory");
RESOLVE_ENTRY(syscalls->ntProtectVirtualMemory, "NtProtectVirtualMemory");
RESOLVE_ENTRY(syscalls->ntFreeVirtualMemory, "NtFreeVirtualMemory");
RESOLVE_ENTRY(syscalls->ntGetContextThread, "NtGetContextThread");
RESOLVE_ENTRY(syscalls->ntSetContextThread, "NtSetContextThread");
RESOLVE_ENTRY(syscalls->ntResumeThread, "NtResumeThread");
RESOLVE_ENTRY(syscalls->ntCreateThreadEx, "NtCreateThreadEx");
RESOLVE_ENTRY(syscalls->ntOpenProcess, "NtOpenProcess");
RESOLVE_ENTRY(syscalls->ntOpenThread, "NtOpenThread");
RESOLVE_ENTRY(syscalls->ntClose, "NtClose");
RESOLVE_ENTRY(syscalls->ntCreateSection, "NtCreateSection");
RESOLVE_ENTRY(syscalls->ntMapViewOfSection, "NtMapViewOfSection");
RESOLVE_ENTRY(syscalls->ntUnmapViewOfSection, "NtUnmapViewOfSection");
RESOLVE_ENTRY(syscalls->ntQueryVirtualMemory, "NtQueryVirtualMemory");
RESOLVE_ENTRY(syscalls->ntDuplicateObject, "NtDuplicateObject");
RESOLVE_ENTRY(syscalls->ntReadVirtualMemory, "NtReadVirtualMemory");
RESOLVE_ENTRY(syscalls->ntWriteVirtualMemory, "NtWriteVirtualMemory");
RESOLVE_ENTRY(syscalls->ntReadFile, "NtReadFile");
RESOLVE_ENTRY(syscalls->ntWriteFile, "NtWriteFile");
RESOLVE_ENTRY(syscalls->ntCreateFile, "NtCreateFile");
/* Added in 411 */
RESOLVE_ENTRY(syscalls->ntQueueApcThread, "NtQueueApcThread");
RESOLVE_ENTRY( syscalls->ntCreateProcess, "NtCreateProcess");
RESOLVE_ENTRY(syscalls->ntOpenProcessToken, "NtOpenProcessToken");
RESOLVE_ENTRY(syscalls->ntQueryDirectoryFile, "NtQueryDirectoryFile");
RESOLVE_ENTRY(syscalls->ntTestAlert, "NtTestAlert");
RESOLVE_ENTRY(syscalls->ntSuspendProcess, "NtSuspendProcess");
RESOLVE_ENTRY(syscalls->ntResumeProcess, "NtResumeProcess");
RESOLVE_ENTRY(syscalls->ntQuerySystemInformation, "NtQuerySystemInformation");
RESOLVE_ENTRY(syscalls->ntSetInformationProcess, "NtSetInformationProcess");
RESOLVE_ENTRY(syscalls->ntSetInformationThread, "NtSetInformationThread");
RESOLVE_ENTRY(syscalls->ntQueryInformationProcess, "NtQueryInformationProcess");
RESOLVE_ENTRY(syscalls->ntQueryInformationThread, "NtQueryInformationThread");
RESOLVE_ENTRY(syscalls->ntOpenSection, "NtOpenSection");
RESOLVE_ENTRY(syscalls->ntAdjustPrivilegesToken, "NtAdjustPrivilegesToken");
RESOLVE_ENTRY(syscalls->ntDeviceIoControlFile, "NtDeviceIoControlFile");
RESOLVE_ENTRY(syscalls->ntWaitForMultipleObjects, "NtWaitForMultipleObjects");
}
/**
* A function to resolve the RTL function address.
*
* @param address A pointer to where the resolved information will be stored.
* @param funcHash Hash value representing the target function to resolve.
*
* @return Returns TRUE if the resolution is successful; otherwise, returns FALSE.
*
*/
BOOL ResolveNtdllFunc(PVOID* address, const char* funcName) {
static const HMODULE ntdll = LoadLibraryA("ntdll");
*address = GetProcAddress(ntdll, funcName);
return *address != NULL;
}
/**
* A helper macro for resolving a RTL function address.
*
* @param field The field in the RTL_API structure in which the resolved function address will be stored.
* @param name The function name used to generate a compile-time hash for entry lookup.
*/
#define RESOLVE_RTL_ENTRY(field, name) { \
if(!ResolveNtdllFunc(&field, name)) { assert(field && "Could not resolve RTL entry"); } \
}
/**
* Resolve RTL function addresses.
*
* @param rtls A pointer to a RTL_API structure.
*
*/
void ResolveRtls(PRTL_API rtls) {
/* Resolve the RTL function addresses */
RESOLVE_RTL_ENTRY(rtls->rtlDosPathNameToNtPathNameUWithStatusAddr, "RtlDosPathNameToNtPathName_U_WithStatus");
RESOLVE_RTL_ENTRY(rtls->rtlFreeHeapAddr, "RtlFreeHeap");
rtls->rtlGetProcessHeapAddr = GetProcessHeap();
assert(rtls->rtlGetProcessHeapAddr && "Could not get the process heap address");
}
}
}
}
+403
View File
@@ -0,0 +1,403 @@
/*
* Beacon Object Files (BOF)
* -------------------------
* A Beacon Object File is a light-weight post exploitation tool that runs
* with Beacon's inline-execute command.
*
* Additional BOF resources are available here:
* - https://github.com/Cobalt-Strike/bof_template
*
* Cobalt Strike 4.x
* ChangeLog:
* 1/25/2022: updated for 4.5
* 7/18/2023: Added BeaconInformation API for 4.9
* 7/31/2023: Added Key/Value store APIs for 4.9
* BeaconAddValue, BeaconGetValue, and BeaconRemoveValue
* 8/31/2023: Added Data store APIs for 4.9
* BeaconDataStoreGetItem, BeaconDataStoreProtectItem,
* BeaconDataStoreUnprotectItem, and BeaconDataStoreMaxEntries
* 9/01/2023: Added BeaconGetCustomUserData API for 4.9
* 3/21/2024: Updated BeaconInformation API for 4.10 to return a BOOL
* Updated the BEACON_INFO data structure to add new parameters
* 4/19/2024: Added BeaconGetSyscallInformation API for 4.10
* 4/25/2024: Added APIs to call Beacon's system call implementation
* 12/18/2024: Updated BeaconGetSyscallInformation API for 4.11 (Breaking changes)
* 2/13/2025: Updated SYSCALL_API structure with more ntAPIs for 4.11
* 3/20/2025: Updated ALLOCATED_MEMORY_SECTION structure with driploader page size for 4.12
* 4/7/2025: Updated ALLOCATED_MEMORY_REGION structure with driploader allocation granularity for 4.12
* 7/16/2025: Updated ALLOCATED_MEMORY_PURPOSE structure with PURPOSE_UDC2_MEMORY for 4.12
*/
#ifndef _BEACON_H_
#define _BEACON_H_
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/* data API */
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} datap;
DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size);
DECLSPEC_IMPORT char * BeaconDataPtr(datap * parser, int size);
DECLSPEC_IMPORT int BeaconDataInt(datap * parser);
DECLSPEC_IMPORT short BeaconDataShort(datap * parser);
DECLSPEC_IMPORT int BeaconDataLength(datap * parser);
DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size);
/* format API */
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} formatp;
DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz);
DECLSPEC_IMPORT void BeaconFormatReset(formatp * format);
DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, const char * text, int len);
DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, const char * fmt, ...);
DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size);
DECLSPEC_IMPORT void BeaconFormatFree(formatp * format);
DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value);
/* Output Functions */
#define CALLBACK_OUTPUT 0x0
#define CALLBACK_OUTPUT_OEM 0x1e
#define CALLBACK_OUTPUT_UTF8 0x20
#define CALLBACK_ERROR 0x0d
#define CALLBACK_CUSTOM 0x1000
#define CALLBACK_CUSTOM_LAST 0x13ff
DECLSPEC_IMPORT void BeaconOutput(int type, const char * data, int len);
DECLSPEC_IMPORT void BeaconPrintf(int type, const char * fmt, ...);
DECLSPEC_IMPORT BOOL BeaconDownload(const char * filename, const char* buffer, unsigned int length);
/* Token Functions */
DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token);
DECLSPEC_IMPORT void BeaconRevertToken();
DECLSPEC_IMPORT BOOL BeaconIsAdmin();
/* Spawn+Inject Functions */
DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length);
DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len);
DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len);
DECLSPEC_IMPORT BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO * si, PROCESS_INFORMATION * pInfo);
DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo);
/* Utility Functions */
DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max);
/* Beacon Information */
/*
* ptr - pointer to the base address of the allocated memory.
* size - the number of bytes allocated for the ptr.
*/
typedef struct {
char * ptr;
size_t size;
} HEAP_RECORD;
#define MASK_SIZE 13
/* Information the user can set in the USER_DATA via a UDRL */
typedef enum {
PURPOSE_EMPTY,
PURPOSE_GENERIC_BUFFER,
PURPOSE_BEACON_MEMORY,
PURPOSE_SLEEPMASK_MEMORY,
PURPOSE_BOF_MEMORY,
PURPOSE_UDC2_MEMORY,
PURPOSE_USER_DEFINED_MEMORY = 1000
} ALLOCATED_MEMORY_PURPOSE;
typedef enum {
LABEL_EMPTY,
LABEL_BUFFER,
LABEL_PEHEADER,
LABEL_TEXT,
LABEL_RDATA,
LABEL_DATA,
LABEL_PDATA,
LABEL_RELOC,
LABEL_USER_DEFINED = 1000
} ALLOCATED_MEMORY_LABEL;
typedef enum {
METHOD_UNKNOWN,
METHOD_VIRTUALALLOC,
METHOD_HEAPALLOC,
METHOD_MODULESTOMP,
METHOD_NTMAPVIEW,
METHOD_USER_DEFINED = 1000,
} ALLOCATED_MEMORY_ALLOCATION_METHOD;
/**
* This structure allows the user to provide additional information
* about the allocated heap for cleanup. It is mandatory to provide
* the HeapHandle but the DestroyHeap Boolean can be used to indicate
* whether the clean up code should destroy the heap or simply free the pages.
* This is useful in situations where a loader allocates memory in the
* processes current heap.
*/
typedef struct _HEAPALLOC_INFO {
PVOID HeapHandle;
BOOL DestroyHeap;
} HEAPALLOC_INFO, *PHEAPALLOC_INFO;
typedef struct _MODULESTOMP_INFO {
HMODULE ModuleHandle;
} MODULESTOMP_INFO, *PMODULESTOMP_INFO;
typedef union _ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION {
HEAPALLOC_INFO HeapAllocInfo;
MODULESTOMP_INFO ModuleStompInfo;
PVOID Custom;
} ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION, *PALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION;
typedef struct _ALLOCATED_MEMORY_CLEANUP_INFORMATION {
BOOL Cleanup;
ALLOCATED_MEMORY_ALLOCATION_METHOD AllocationMethod;
ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION AdditionalCleanupInformation;
} ALLOCATED_MEMORY_CLEANUP_INFORMATION, *PALLOCATED_MEMORY_CLEANUP_INFORMATION;
typedef struct _ALLOCATED_MEMORY_SECTION {
ALLOCATED_MEMORY_LABEL Label; // A label to simplify Sleepmask development
PVOID BaseAddress; // Pointer to virtual address of section
SIZE_T VirtualSize; // Virtual size of the section
DWORD CurrentProtect; // Current memory protection of the section
DWORD PreviousProtect; // The previous memory protection of the section (prior to masking/unmasking)
BOOL MaskSection; // A boolean to indicate whether the section should be masked
DWORD DripLoadPageSize; // The page size used when committing memory during drip-loading
} ALLOCATED_MEMORY_SECTION, *PALLOCATED_MEMORY_SECTION;
typedef struct _ALLOCATED_MEMORY_REGION {
ALLOCATED_MEMORY_PURPOSE Purpose; // A label to indicate the purpose of the allocated memory
PVOID AllocationBase; // The base address of the allocated memory block
SIZE_T RegionSize; // The size of the allocated memory block
DWORD Type; // The type of memory allocated
DWORD DripLoadAllocationGranularity; // The allocation granularity used when reserving memory for drip-loading
ALLOCATED_MEMORY_SECTION Sections[8]; // An array of section information structures
ALLOCATED_MEMORY_CLEANUP_INFORMATION CleanupInformation; // Information required to cleanup the allocation
} ALLOCATED_MEMORY_REGION, *PALLOCATED_MEMORY_REGION;
typedef struct {
ALLOCATED_MEMORY_REGION AllocatedMemoryRegions[6];
} ALLOCATED_MEMORY, *PALLOCATED_MEMORY;
/*
* version - The version of the beacon dll was added for release 4.10
* version format: 0xMMmmPP, where MM = Major, mm = Minor, and PP = Patch
* e.g. 0x040900 -> CS 4.9
* 0x041000 -> CS 4.10
*
* sleep_mask_ptr - pointer to the sleep mask base address
* sleep_mask_text_size - the sleep mask text section size
* sleep_mask_total_size - the sleep mask total memory size
*
* beacon_ptr - pointer to beacon's base address
* The stage.obfuscate flag affects this value when using CS default loader.
* true: beacon_ptr = allocated_buffer - 0x1000 (Not a valid address)
* false: beacon_ptr = allocated_buffer (A valid address)
* For a UDRL the beacon_ptr will be set to the 1st argument to DllMain
* when the 2nd argument is set to DLL_PROCESS_ATTACH.
* heap_records - list of memory addresses on the heap beacon wants to mask.
* The list is terminated by the HEAP_RECORD.ptr set to NULL.
* mask - the mask that beacon randomly generated to apply
*
* Added in version 4.10
* allocatedMemory - An ALLOCATED_MEMORY structure that can be set in the USER_DATA
* via a UDRL.
*/
typedef struct {
unsigned int version;
char * sleep_mask_ptr;
DWORD sleep_mask_text_size;
DWORD sleep_mask_total_size;
char * beacon_ptr;
HEAP_RECORD * heap_records;
char mask[MASK_SIZE];
ALLOCATED_MEMORY allocatedMemory;
} BEACON_INFO, *PBEACON_INFO;
DECLSPEC_IMPORT BOOL BeaconInformation(PBEACON_INFO info);
/* Key/Value store functions
* These functions are used to associate a key to a memory address and save
* that information into beacon. These memory addresses can then be
* retrieved in a subsequent execution of a BOF.
*
* key - the key will be converted to a hash which is used to locate the
* memory address.
*
* ptr - a memory address to save.
*
* Considerations:
* - The contents at the memory address is not masked by beacon.
* - The contents at the memory address is not released by beacon.
*
*/
DECLSPEC_IMPORT BOOL BeaconAddValue(const char * key, void * ptr);
DECLSPEC_IMPORT void * BeaconGetValue(const char * key);
DECLSPEC_IMPORT BOOL BeaconRemoveValue(const char * key);
/* Beacon Data Store functions
* These functions are used to access items in Beacon's Data Store.
* BeaconDataStoreGetItem returns NULL if the index does not exist.
*
* The contents are masked by default, and BOFs must unprotect the entry
* before accessing the data buffer. BOFs must also protect the entry
* after the data is not used anymore.
*
*/
#define DATA_STORE_TYPE_EMPTY 0
#define DATA_STORE_TYPE_GENERAL_FILE 1
typedef struct {
int type;
DWORD64 hash;
BOOL masked;
char* buffer;
size_t length;
} DATA_STORE_OBJECT, *PDATA_STORE_OBJECT;
DECLSPEC_IMPORT PDATA_STORE_OBJECT BeaconDataStoreGetItem(size_t index);
DECLSPEC_IMPORT void BeaconDataStoreProtectItem(size_t index);
DECLSPEC_IMPORT void BeaconDataStoreUnprotectItem(size_t index);
DECLSPEC_IMPORT size_t BeaconDataStoreMaxEntries();
/* Beacon User Data functions */
DECLSPEC_IMPORT char * BeaconGetCustomUserData();
/* Beacon System call */
/* Syscalls API */
typedef struct
{
PVOID fnAddr;
PVOID jmpAddr;
DWORD sysnum;
} SYSCALL_API_ENTRY, *PSYSCALL_API_ENTRY;
typedef struct
{
SYSCALL_API_ENTRY ntAllocateVirtualMemory;
SYSCALL_API_ENTRY ntProtectVirtualMemory;
SYSCALL_API_ENTRY ntFreeVirtualMemory;
SYSCALL_API_ENTRY ntGetContextThread;
SYSCALL_API_ENTRY ntSetContextThread;
SYSCALL_API_ENTRY ntResumeThread;
SYSCALL_API_ENTRY ntCreateThreadEx;
SYSCALL_API_ENTRY ntOpenProcess;
SYSCALL_API_ENTRY ntOpenThread;
SYSCALL_API_ENTRY ntClose;
SYSCALL_API_ENTRY ntCreateSection;
SYSCALL_API_ENTRY ntMapViewOfSection;
SYSCALL_API_ENTRY ntUnmapViewOfSection;
SYSCALL_API_ENTRY ntQueryVirtualMemory;
SYSCALL_API_ENTRY ntDuplicateObject;
SYSCALL_API_ENTRY ntReadVirtualMemory;
SYSCALL_API_ENTRY ntWriteVirtualMemory;
SYSCALL_API_ENTRY ntReadFile;
SYSCALL_API_ENTRY ntWriteFile;
SYSCALL_API_ENTRY ntCreateFile;
SYSCALL_API_ENTRY ntQueueApcThread;
SYSCALL_API_ENTRY ntCreateProcess;
SYSCALL_API_ENTRY ntOpenProcessToken;
SYSCALL_API_ENTRY ntTestAlert;
SYSCALL_API_ENTRY ntSuspendProcess;
SYSCALL_API_ENTRY ntResumeProcess;
SYSCALL_API_ENTRY ntQuerySystemInformation;
SYSCALL_API_ENTRY ntQueryDirectoryFile;
SYSCALL_API_ENTRY ntSetInformationProcess;
SYSCALL_API_ENTRY ntSetInformationThread;
SYSCALL_API_ENTRY ntQueryInformationProcess;
SYSCALL_API_ENTRY ntQueryInformationThread;
SYSCALL_API_ENTRY ntOpenSection;
SYSCALL_API_ENTRY ntAdjustPrivilegesToken;
SYSCALL_API_ENTRY ntDeviceIoControlFile;
SYSCALL_API_ENTRY ntWaitForMultipleObjects;
} SYSCALL_API, *PSYSCALL_API;
/* Additional Run Time Library (RTL) addresses used to support system calls.
* If they are not set then system calls that require them will fall back
* to the Standard Windows API.
*
* Required to support the following system calls:
* ntCreateFile
*/
typedef struct
{
PVOID rtlDosPathNameToNtPathNameUWithStatusAddr;
PVOID rtlFreeHeapAddr;
PVOID rtlGetProcessHeapAddr;
} RTL_API, *PRTL_API;
/* Updated in version 4.11 to use the entire structure instead of pointers to the structure.
* This allows for retrieving a copy of the information which would be under the BOF's
* control instead of a reference pointer which may be obfuscated when beacon is sleeping.
*/
typedef struct
{
SYSCALL_API syscalls;
RTL_API rtls;
} BEACON_SYSCALLS, *PBEACON_SYSCALLS;
/* Updated in version 4.11 to include the size of the info pointer, which equals sizeof(BEACON_SYSCALLS) */
DECLSPEC_IMPORT BOOL BeaconGetSyscallInformation(PBEACON_SYSCALLS info, SIZE_T infoSize, BOOL resolveIfNotInitialized);
/* Beacon System call functions which will use the current system call method */
DECLSPEC_IMPORT LPVOID BeaconVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
DECLSPEC_IMPORT LPVOID BeaconVirtualAllocEx(HANDLE processHandle, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
DECLSPEC_IMPORT BOOL BeaconVirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
DECLSPEC_IMPORT BOOL BeaconVirtualProtectEx(HANDLE processHandle, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
DECLSPEC_IMPORT BOOL BeaconVirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType);
DECLSPEC_IMPORT BOOL BeaconGetThreadContext(HANDLE threadHandle, PCONTEXT threadContext);
DECLSPEC_IMPORT BOOL BeaconSetThreadContext(HANDLE threadHandle, PCONTEXT threadContext);
DECLSPEC_IMPORT DWORD BeaconResumeThread(HANDLE threadHandle);
DECLSPEC_IMPORT HANDLE BeaconOpenProcess(DWORD desiredAccess, BOOL inheritHandle, DWORD processId);
DECLSPEC_IMPORT HANDLE BeaconOpenThread(DWORD desiredAccess, BOOL inheritHandle, DWORD threadId);
DECLSPEC_IMPORT BOOL BeaconCloseHandle(HANDLE object);
DECLSPEC_IMPORT BOOL BeaconUnmapViewOfFile(LPCVOID baseAddress);
DECLSPEC_IMPORT SIZE_T BeaconVirtualQuery(LPCVOID address, PMEMORY_BASIC_INFORMATION buffer, SIZE_T length);
DECLSPEC_IMPORT BOOL BeaconDuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions);
DECLSPEC_IMPORT BOOL BeaconReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead);
DECLSPEC_IMPORT BOOL BeaconWriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten);
/* Beacon Gate APIs */
DECLSPEC_IMPORT VOID BeaconDisableBeaconGate();
DECLSPEC_IMPORT VOID BeaconEnableBeaconGate();
DECLSPEC_IMPORT VOID BeaconDisableBeaconGateMasking();
DECLSPEC_IMPORT VOID BeaconEnableBeaconGateMasking();
/* Beacon User Data
*
* version format: 0xMMmmPP, where MM = Major, mm = Minor, and PP = Patch
* e.g. 0x040900 -> CS 4.9
* 0x041000 -> CS 4.10
*/
#define DLL_BEACON_USER_DATA 0x0d
#define BEACON_USER_DATA_CUSTOM_SIZE 32
typedef struct
{
unsigned int version;
PSYSCALL_API syscalls;
char custom[BEACON_USER_DATA_CUSTOM_SIZE];
PRTL_API rtls;
PALLOCATED_MEMORY allocatedMemory;
} USER_DATA, * PUSER_DATA;
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // _BEACON_H_
+73
View File
@@ -0,0 +1,73 @@
#ifndef _BEACON_GATE_H
#define _BEACON_GATE_H
#include <windows.h>
/* Beacon gate defines. */
#define MAX_BEACON_GATE_ARGUMENTS 10
#define beaconGate(i) ((BEACON_GATE_##i)gateFunction->functionPtr)
#define arg(i) (ULONG_PTR)gateFunction->args[i]
/* Enum to specify what WinAPI is being called. */
typedef enum _WinApi {
INTERNETOPENA,
INTERNETCONNECTA,
VIRTUALALLOC,
VIRTUALALLOCEX,
VIRTUALPROTECT,
VIRTUALPROTECTEX,
VIRTUALFREE,
GETTHREADCONTEXT,
SETTHREADCONTEXT,
RESUMETHREAD,
CREATETHREAD,
CREATEREMOTETHREAD,
OPENPROCESS,
OPENTHREAD,
CLOSEHANDLE,
CREATEFILEMAPPING,
MAPVIEWOFFILE,
UNMAPVIEWOFFILE,
VIRTUALQUERY,
DUPLICATEHANDLE,
READPROCESSMEMORY,
WRITEPROCESSMEMORY,
EXITTHREAD,
VIRTUALFREEEX,
VIRTUALQUERYEX,
WAITFORSINGLEOBJECT,
SLEEP
} WinApi;
/**
* FUNCTION_CALL struct which encapsulates atomic function call.
*
* functionPtr - target function to call
* function - Enum representing target WinApi
* numOfArgs - number of arguments
* args - array of ULONG_PTRs containing the passed arguments (e.g. rcx, rdx, ...)
* bMask - BOOL indicating whether Beacon should be masked during the call
* ULONG_PTR - retValue of the atomic function call
*/
typedef struct {
PVOID functionPtr;
WinApi function;
int numOfArgs;
ULONG_PTR args[MAX_BEACON_GATE_ARGUMENTS];
BOOL bMask;
ULONG_PTR retValue;
} FUNCTION_CALL, * PFUNCTION_CALL;
/* Currently support max 10 arguments. */
/* NB For x86 we only support std call convention as this is what Windows uses for most Win32 APIs. */
typedef ULONG_PTR(__stdcall* BEACON_GATE_00)(VOID);
typedef ULONG_PTR(__stdcall* BEACON_GATE_01)(ULONG_PTR);
typedef ULONG_PTR(__stdcall* BEACON_GATE_02)(ULONG_PTR, ULONG_PTR);
typedef ULONG_PTR(__stdcall* BEACON_GATE_03)(ULONG_PTR, ULONG_PTR, ULONG_PTR);
typedef ULONG_PTR(__stdcall* BEACON_GATE_04)(ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR);
typedef ULONG_PTR(__stdcall* BEACON_GATE_05)(ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR);
typedef ULONG_PTR(__stdcall* BEACON_GATE_06)(ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR);
typedef ULONG_PTR(__stdcall* BEACON_GATE_07)(ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR);
typedef ULONG_PTR(__stdcall* BEACON_GATE_08)(ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR);
typedef ULONG_PTR(__stdcall* BEACON_GATE_09)(ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR);
typedef ULONG_PTR(__stdcall* BEACON_GATE_10)(ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR, ULONG_PTR);
#endif // _BEACON_GATE_H
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static" version="1.8.1.7" targetFramework="native" />
</packages>
+9
View File
@@ -0,0 +1,9 @@
#ifndef _SLEEPMASK_H_
#define _SLEEPMASK_H_
#include <windows.h>
#include "beacon.h"
#include "beacon_gate.h"
typedef void(* SLEEPMASK_FUNC)(PBEACON_INFO, PFUNCTION_CALL);
#endif // _SLEEPMASK_H_
+799
View File
@@ -0,0 +1,799 @@
#!/usr/bin/env python3
import sys
import os
import struct
from enum import Enum, auto, IntFlag
import argparse
from pathlib import Path
###############################################################################
# Constants and Struct Formats
###############################################################################
COFF_HEADER_FORMAT = "<HHLLLHH" # 20 bytes
COFF_HEADER_SIZE = struct.calcsize(COFF_HEADER_FORMAT)
SECTION_HEADER_FORMAT = "<8sLLLLLLHHL" # 40 bytes
SECTION_HEADER_SIZE = struct.calcsize(SECTION_HEADER_FORMAT)
SYMBOL_FORMAT = "<8sLHHBB" # 18 bytes
SYMBOL_SIZE = struct.calcsize(SYMBOL_FORMAT)
RELOCATION_FORMAT = "<LLH" # 10 bytes
RELOCATION_SIZE = struct.calcsize(RELOCATION_FORMAT)
# Machine constants
MACHINE_X86 = 0x14C # IMAGE_FILE_MACHINE_I386
MACHINE_AMD64 = 0x8664 # IMAGE_FILE_MACHINE_AMD64
# Define relocation types
class ImageRelocationType(Enum):
IMAGE_REL_I386_ABSOLUTE = 0x0000
IMAGE_REL_I386_DIR16 = 0x0001
IMAGE_REL_I386_REL16 = 0x0002
IMAGE_REL_I386_DIR32 = 0x0006
IMAGE_REL_I386_DIR32NB = 0x0007
IMAGE_REL_I386_SEG12 = 0x0009
IMAGE_REL_I386_SECTION = 0x000A
IMAGE_REL_I386_SECREL = 0x000B
IMAGE_REL_I386_TOKEN = 0x000C
IMAGE_REL_I386_SECREL7 = 0x000D
IMAGE_REL_I386_REL32 = 0x0014
IMAGE_REL_AMD64_ABSOLUTE = 0x0000
IMAGE_REL_AMD64_ADDR64 = 0x0001
IMAGE_REL_AMD64_ADDR32 = 0x0002
IMAGE_REL_AMD64_ADDR32NB = 0x0003
IMAGE_REL_AMD64_REL32 = 0x0004
IMAGE_REL_AMD64_REL32_1 = 0x0005
IMAGE_REL_AMD64_REL32_2 = 0x0006
IMAGE_REL_AMD64_REL32_3 = 0x0007
IMAGE_REL_AMD64_REL32_4 = 0x0008
IMAGE_REL_AMD64_REL32_5 = 0x0009
IMAGE_REL_AMD64_SECTION = 0x000A
IMAGE_REL_AMD64_SECREL = 0x000B
IMAGE_REL_AMD64_SECREL7 = 0x000C
IMAGE_REL_AMD64_TOKEN = 0x000D
IMAGE_REL_AMD64_SREL32 = 0x000E
IMAGE_REL_AMD64_PAIR = 0x000F
IMAGE_REL_AMD64_SSPAN32 = 0x0010
IMAGE_REL_AMD64_EHANDLER = 0x0011
IMAGE_REL_AMD64_IMPORT_BR = 0x0012
IMAGE_REL_AMD64_IMPORT_CALL = 0x0013
IMAGE_REL_AMD64_CFG_BR = 0x0014
IMAGE_REL_AMD64_CFG_BR_REX = 0x0015
IMAGE_REL_AMD64_CFG_CALL = 0x0016
IMAGE_REL_AMD64_INDIR_BR = 0x0017
IMAGE_REL_AMD64_INDIR_BR_REX = 0x0018
IMAGE_REL_AMD64_INDIR_CALL = 0x0019
IMAGE_REL_AMD64_INDIR_BR_SWITCHTABLE_FIRST = 0x0020
IMAGE_REL_AMD64_INDIR_BR_SWITCHTABLE_LAST = 0x002F
list_of_implant_functions_cs = [
"BeaconDataParse",
"BeaconDataPtr",
"BeaconDataInt",
"BeaconDataShort",
"BeaconDataLength",
"BeaconDataExtract",
"BeaconFormatAlloc",
"BeaconFormatReset",
"BeaconFormatAppend",
"BeaconFormatPrintf",
"BeaconFormatToString",
"BeaconFormatFree",
"BeaconFormatInt",
"BeaconOutput",
"BeaconPrintf",
"BeaconUseToken",
"BeaconRevertToken",
"BeaconIsAdmin",
"BeaconGetSpawnTo",
"BeaconInjectProcess",
"BeaconInjectTemporaryProcess",
"BeaconSpawnTemporaryProcess",
"BeaconCleanupProcess",
"toWideChar",
"BeaconInformation",
"BeaconAddValue",
"BeaconGetValue",
"BeaconRemoveValue",
"BeaconDataStoreGetItem",
"BeaconDataStoreProtectItem",
"BeaconDataStoreUnprotectItem",
"BeaconDataStoreMaxEntries",
"BeaconGetCustomUserData",
"BeaconGetSyscallInformation",
"BeaconVirtualAlloc",
"BeaconVirtualAllocEx",
"BeaconVirtualProtect",
"BeaconVirtualProtectEx",
"BeaconVirtualFree",
"BeaconGetThreadContext",
"BeaconSetThreadContext",
"BeaconResumeThread",
"BeaconOpenProcess",
"BeaconOpenThread",
"BeaconCloseHandle",
"BeaconUnmapViewOfFile",
"BeaconVirtualQuery",
"BeaconDuplicateHandle",
"BeaconReadProcessMemory",
"BeaconWriteProcessMemory",
"BeaconDisableBeaconGate",
"BeaconEnableBeaconGate",
"GetProcAddress",
"GetModuleHandleA",
"GetModuleHandleW",
"LoadLibraryA",
"LoadLibraryW",
"FreeLibrary"
]
list_of_implant_functions_oc2 = [
"BeaconDataParse",
"BeaconDataInt",
"BeaconDataShort",
"BeaconDataLength",
"BeaconDataExtract",
"BeaconFormatAlloc",
"BeaconFormatFree",
"BeaconFormatReset",
"BeaconFormatAppend",
"BeaconFormatPrintf",
"BeaconFormatToString",
"BeaconFormatInt",
"BeaconPrintf",
"BeaconOutput",
"BeaconUseToken",
"BeaconRevertToken",
"BeaconIsAdmin",
"BeaconThrow",
"BeaconInjectProcess",
"GetProcAddress",
"GetModuleHandleA",
"GetModuleHandleW",
"LoadLibraryA",
"LoadLibraryW",
"FreeLibrary"
]
list_of_implant_functions_ci = [
"BeaconDataParse",
"BeaconDataPtr",
"BeaconDataInt",
"BeaconDataShort",
"BeaconDataLength",
"BeaconDataExtract",
"BeaconFormatAlloc",
"BeaconFormatReset",
"BeaconFormatAppend",
"BeaconFormatPrintf",
"BeaconFormatToString",
"BeaconFormatFree",
"BeaconFormatInt",
"BeaconOutput",
"BeaconPrintf",
"GetProcAddress",
"GetModuleHandleA",
"LoadLibraryA",
"FreeLibrary"
]
###############################################################################
# Classes Representing COFF Structures
###############################################################################
class COFFFileHeader:
"""
Represents the 20-byte COFF File Header.
"""
__slots__ = (
"machine", "number_of_sections", "time_date_stamp",
"pointer_to_symbol_table", "number_of_symbols",
"size_of_optional_header", "characteristics",
)
def __init__(self, machine, number_of_sections, time_date_stamp,
pointer_to_symbol_table, number_of_symbols,
size_of_optional_header, characteristics):
self.machine = machine
self.number_of_sections = number_of_sections
self.time_date_stamp = time_date_stamp
self.pointer_to_symbol_table = pointer_to_symbol_table
self.number_of_symbols = number_of_symbols
self.size_of_optional_header = size_of_optional_header
self.characteristics = characteristics
@classmethod
def from_file(cls, f):
data = f.read(COFF_HEADER_SIZE)
if len(data) < COFF_HEADER_SIZE:
raise ValueError("File too small to contain a valid COFF header.")
(
machine,
number_of_sections,
time_date_stamp,
pointer_to_symbol_table,
number_of_symbols,
size_of_opt_header,
characteristics
) = struct.unpack(COFF_HEADER_FORMAT, data)
return cls(
machine,
number_of_sections,
time_date_stamp,
pointer_to_symbol_table,
number_of_symbols,
size_of_opt_header,
characteristics
)
class COFFSectionHeader:
"""
Represents a 40-byte COFF Section Header.
"""
__slots__ = (
"name", "virtual_size", "virtual_address", "size_of_raw_data",
"pointer_to_raw_data", "pointer_to_relocations", "pointer_to_line_numbers",
"number_of_relocations", "number_of_line_numbers", "characteristics",
)
def __init__(self, name, virtual_size, virtual_address, size_of_raw_data,
pointer_to_raw_data, pointer_to_relocations, pointer_to_line_numbers,
number_of_relocations, number_of_line_numbers, characteristics):
self.name = name
self.virtual_size = virtual_size
self.virtual_address = virtual_address
self.size_of_raw_data = size_of_raw_data
self.pointer_to_raw_data = pointer_to_raw_data
self.pointer_to_relocations = pointer_to_relocations
self.pointer_to_line_numbers = pointer_to_line_numbers
self.number_of_relocations = number_of_relocations
self.number_of_line_numbers = number_of_line_numbers
self.characteristics = characteristics
@classmethod
def from_file(cls, f):
data = f.read(SECTION_HEADER_SIZE)
if len(data) < SECTION_HEADER_SIZE:
raise ValueError("File ended while reading section header.")
(
name_bytes,
virtual_size,
virtual_address,
size_of_raw_data,
ptr_raw_data,
ptr_relocs,
ptr_linenums,
num_relocs,
num_linenums,
characteristics
) = struct.unpack(SECTION_HEADER_FORMAT, data)
# decode section name (null-terminated within 8 bytes)
name = name_bytes.split(b'\x00', 1)[0].decode(errors='ignore')
return cls(
name,
virtual_size,
virtual_address,
size_of_raw_data,
ptr_raw_data,
ptr_relocs,
ptr_linenums,
num_relocs,
num_linenums,
characteristics
)
class COFFSymbol:
"""
Represents an 18-byte COFF Symbol Table entry.
"""
__slots__ = (
"name_raw", "value", "section_number", "type_", "storage_class",
"number_of_aux_symbols"
)
def __init__(self, name_raw, value, section_number, type_, storage_class, number_of_aux_symbols):
self.name_raw = name_raw
self.value = value
self.section_number = section_number
self.type_ = type_
self.storage_class = storage_class
self.number_of_aux_symbols = number_of_aux_symbols
@classmethod
def from_file(cls, f):
data = f.read(SYMBOL_SIZE)
if len(data) < SYMBOL_SIZE:
raise ValueError("Unexpected end of file while reading symbol.")
(name_bytes, value, section_number, type_, storage_class, num_aux) = \
struct.unpack(SYMBOL_FORMAT, data)
return cls(
name_raw=name_bytes,
value=value,
section_number=section_number,
type_=type_,
storage_class=storage_class,
number_of_aux_symbols=num_aux
)
class COFFRelocation:
"""
Represents a 10-byte COFF Relocation entry.
"""
__slots__ = ("virtual_address", "symbol_table_index", "type_")
def __init__(self, virtual_address, symbol_table_index, type_):
self.virtual_address = virtual_address
self.symbol_table_index = symbol_table_index
self.type_ = type_
@classmethod
def from_file(cls, f):
data = f.read(RELOCATION_SIZE)
if len(data) < RELOCATION_SIZE:
raise ValueError("Unexpected end of file while reading relocation.")
virt_addr, sym_idx, typ = struct.unpack(RELOCATION_FORMAT, data)
return cls(virt_addr, sym_idx, typ)
###############################################################################
# Main COFF Object Container
###############################################################################
class COFFObject:
"""
Collects a parsed COFF object file:
- file_header (COFFFileHeader)
- section_headers (list[COFFSectionHeader])
- symbols (list[COFFSymbol])
- relocations (dict[int -> list[COFFRelocation]]) indexed by section index
- raw string table
Provides a .lint() method to do basic checks.
"""
def __init__(self):
self.file_header = None
self.section_headers = []
self.symbols = []
self.relocations = {} # section_index -> list of relocations
self.string_table = b''
@classmethod
def from_file(cls, path):
obj = cls()
with open(path, "rb") as f:
# 1) Parse COFF header
obj.file_header = COFFFileHeader.from_file(f)
# 2) Skip optional header if present
sz_opt = obj.file_header.size_of_optional_header
if sz_opt > 0:
f.seek(sz_opt, os.SEEK_CUR)
# 3) Parse section headers
for _ in range(obj.file_header.number_of_sections):
sec = COFFSectionHeader.from_file(f)
obj.section_headers.append(sec)
# 4) Parse symbols and string table
obj._parse_symbols_and_strings(f)
# 5) Read relocations from each section in a second pass
with open(path, "rb") as f:
for i, sec in enumerate(obj.section_headers):
rels = obj._read_relocations_for_section(f, sec)
obj.relocations[i] = rels
return obj
def _parse_symbols_and_strings(self, f):
"""
Read the symbol table (18 bytes each, plus any auxiliary records),
then read the string table if present.
"""
hdr = self.file_header
if hdr.pointer_to_symbol_table == 0 or hdr.number_of_symbols == 0:
return # no symbols
# Seek to the start of the symbol table
f.seek(hdr.pointer_to_symbol_table, os.SEEK_SET)
symbol_count = hdr.number_of_symbols
i = 0
while i < symbol_count:
sym = COFFSymbol.from_file(f)
self.symbols.append(sym)
# If the symbol has auxiliary entries, skip them
if sym.number_of_aux_symbols > 0:
skip_count = sym.number_of_aux_symbols * SYMBOL_SIZE
f.seek(skip_count, os.SEEK_CUR)
i += sym.number_of_aux_symbols
self.symbols.extend([None] * sym.number_of_aux_symbols)
i += 1
# Next 4 bytes should be the string table size
size_data = f.read(4)
if len(size_data) == 4:
string_table_size = struct.unpack("<L", size_data)[0]
if string_table_size > 4:
remaining = string_table_size - 4
self.string_table = f.read(remaining)
else:
self.string_table = b''
def _read_relocations_for_section(self, f, section):
"""
Reads relocations for a given section.
"""
relocs = []
if section.pointer_to_relocations == 0 or section.number_of_relocations == 0:
return relocs
f.seek(section.pointer_to_relocations, os.SEEK_SET)
for _ in range(section.number_of_relocations):
rel = COFFRelocation.from_file(f)
relocs.append(rel)
return relocs
def get_symbol_name(self, sym: COFFSymbol) -> str:
"""
Extracts the symbol name from the raw bytes.
If the first 4 bytes are zero, the last 4 are an offset into the string table.
Otherwise, it is an inline name (null-terminated).
"""
raw = sym.name_raw
# check if first four bytes are zero
if raw[:4] == b'\x00\x00\x00\x00':
offset = struct.unpack("<L", raw[4:8])[0]
# typical approach: offset - 4 from the start of self.string_table
real_offset = offset - 4
if real_offset < 0 or real_offset >= len(self.string_table):
return "<invalid-offset>"
# read until null
sub = self.string_table[real_offset:]
null_pos = sub.find(b'\x00')
return sub[:null_pos].decode(errors='ignore') if null_pos != -1 else sub.decode(errors='ignore')
else:
# inline name
null_pos = raw.find(b'\x00')
return raw[:null_pos].decode(errors='ignore') if null_pos != -1 else raw.decode(errors='ignore')
def is_64bit(self) -> bool:
return self.file_header.machine == MACHINE_AMD64
def lint(self):
"""Perform basic lint checks and print results."""
hdr = self.file_header
machine = hdr.machine
# Print header info
log_message(LogLevel.INFO, "=== COFF File Header ===")
log_message(LogLevel.INFO, f" Machine: 0x{machine:04X}")
log_message(LogLevel.INFO, f" NumberOfSections: {hdr.number_of_sections}")
log_message(LogLevel.INFO, f" TimeDateStamp: 0x{hdr.time_date_stamp:08X}")
log_message(LogLevel.INFO, f" PointerToSymbolTable:0x{hdr.pointer_to_symbol_table:08X}")
log_message(LogLevel.INFO, f" NumberOfSymbols: {hdr.number_of_symbols}")
log_message(LogLevel.INFO, f" SizeOfOptionalHeader:{hdr.size_of_optional_header}")
log_message(LogLevel.INFO, f" Characteristics: 0x{hdr.characteristics:04X}")
if machine == MACHINE_X86:
log_message(LogLevel.INFO, " -> x86 (32-bit) COFF object.n")
elif machine == MACHINE_AMD64:
log_message(LogLevel.INFO, " -> AMD64 (x64) COFF object.\n")
else:
log_message(LogLevel.ERROR, " -> Unknown or non-standard machine type.\n")
sys.exit(1)
# Check sections
log_message(LogLevel.INFO, "=== Sections ===")
for i, sec in enumerate(self.section_headers):
log_message(LogLevel.INFO, f" Section[{i}]: {sec.name}")
log_message(LogLevel.INFO, f" PointerToRelocations: 0x{sec.pointer_to_relocations:08X}")
log_message(LogLevel.INFO, f" NumberOfRelocations: {sec.number_of_relocations}")
log_message(LogLevel.INFO, f" Characteristics: 0x{sec.characteristics:08X}")
log_message(LogLevel.INFO, f" VirtualSize: 0x{sec.virtual_size:08X}")
log_message(LogLevel.INFO, f" SizeOfRawData: 0x{sec.size_of_raw_data:08X}")
if sec.name == ".bss" and (sec.size_of_raw_data > 0 or sec.virtual_size > 0) and loader_type not in (LoaderType.CS, LoaderType.OC2, LoaderType.CI):
log_message(LogLevel.WARN, f"Section '{sec.name}' is present! Not all loaders support uninitialized data in a BOF.")
elif sec.name == ".rdata" and sec.size_of_raw_data > 0 and loader_type not in (LoaderType.CS, LoaderType.OC2, LoaderType.CI):
log_message(LogLevel.WARN, f"Section '{sec.name}' is present! Not all loaders support read-only/const data in a BOF.")
#elif sec.name == ".pdata" and sec.size_of_raw_data > 0 and loader_type not in (LoaderType.CS, LoaderType.OC2):
# log_message(LogLevel.WARN, f"Section '{sec.name}' is present! This may indicate that your BOF is using exception handling, which is not supported.")
log_message(LogLevel.INFO, "")
# Symbols
log_message(LogLevel.INFO, "=== Symbols ===")
allowed_entry_names = {
LoaderType.CS: { "go", "sleep_mask", "_go", "_sleep_mask" },
LoaderType.OC2: { "go", "_go" },
LoaderType.CI: {"go", "_go"},
}
allowed_entry_names[LoaderType.ANY] = allowed_entry_names[LoaderType.CS] | allowed_entry_names[LoaderType.OC2] | allowed_entry_names[LoaderType.CI]
found_entry = False
for i, sym in enumerate(self.symbols):
if sym is None:
continue # skip aux symbols
sym_name = self.get_symbol_name(sym)
log_message(LogLevel.INFO, f" [{i}] Name='{sym_name}' Value=0x{sym.value:08X} "
f"Section={sym.section_number} StorageClass={sym.storage_class}")
if sym_name in allowed_entry_names[loader_type] and sym.section_number > 0:
found_entry = True
if "@" in sym_name:
sym_name = sym_name.split("@", 1)[0]
if sym_name.startswith("__imp_"):
sym_without_prefix = sym_name[6:] # remove __imp_
if not self.is_64bit():
if sym_without_prefix[0] == "_":
sym_without_prefix = sym_without_prefix[1:]
if "$" in sym_without_prefix: # DFR
# split in exact two parts, not more
import_lib, import_func = sym_without_prefix.split("$", 1)
if "@" in import_func:
import_func = import_func.split("@", 1)[0]
import_func = import_func.strip("$")
log_message(LogLevel.INFO, f"Imported symbol '{sym_without_prefix}' from library '{import_lib}' and function '{import_func}'")
else:
if loader_type == LoaderType.CS:
if sym_without_prefix not in list_of_implant_functions_cs:
log_message(LogLevel.ERROR, f"Imported symbol '{sym_without_prefix}' is not a recognized implant function.", LogDestination.CS)
elif loader_type == LoaderType.OC2:
if sym_without_prefix not in list_of_implant_functions_oc2:
log_message(LogLevel.ERROR, f"Imported symbol '{sym_without_prefix}' is not a recognized implant function.", LogDestination.OC2)
elif loader_type == LoaderType.CI:
if sym_without_prefix not in list_of_implant_functions_ci:
log_message(LogLevel.ERROR, f"Imported symbol '{sym_without_prefix}' is not a recognized implant function.", LogDestination.CI)
else:
if sym_without_prefix not in (list_of_implant_functions_cs + list_of_implant_functions_oc2 + list_of_implant_functions_ci):
log_message(LogLevel.ERROR, f"Imported symbol '{sym_without_prefix}' is not a recognized implant function.")
else:
if sym.section_number == 0:
unhandled = True
if unhandled and sym_name in ["___chkstk_ms", "__chkstk"]:
if loader_type != LoaderType.OC2:
log_message(LogLevel.ERROR, f"Symbol '{sym_name}' is a stack check function. You may have a stack variable that is too large.")
unhandled = False
if unhandled and sym_name in ["__stack_chk_fail", "__security_init_cookie"] or "security_check_cookie" in sym_name:
log_message(LogLevel.ERROR, f"Symbol '{sym_name}' is a stack check function. Disable stack protections.")
unhandled = False
if unhandled and sym_name in ["__C_specific_handler", "__cxa_begin_catch"] or "except_handler" in sym_name or "CxxFrameHandler" in sym_name:
log_message(LogLevel.WARN, f"Symbol '{sym_name}' is an exception handling function. This is not supported.")
unhandled = False
if unhandled and sym_name in ["memset", "memmove", "memcpy"]:
if loader_type == LoaderType.OC2:
# OC2 shims these functions in the loader
unhandled = False
if unhandled:
log_message(LogLevel.ERROR, f"Symbol '{sym_name}' is an undefined symbol.")
log_message(LogLevel.INFO, "")
if not found_entry:
entry_names = ', '.join(f"'{name}'" for name in allowed_entry_names[loader_type])
log_message(LogLevel.ERROR, f"No {entry_names} entry point found in object file (required for BOF)")
# Relocations
log_message(LogLevel.INFO, "=== Relocations ===")
for i, sec in enumerate(self.section_headers):
rels = self.relocations.get(i, [])
if rels:
log_message(LogLevel.INFO, f" Section[{i}] '{sec.name}' has {len(rels)} relocation(s):")
for r in rels:
# Symbol reference name
sym_name = "<invalid-symbol>"
if r.symbol_table_index < len(self.symbols):
sym_name = self.get_symbol_name(self.symbols[r.symbol_table_index])
try:
r_type = ImageRelocationType(r.type_)
r_type_str = r_type.name
except ValueError:
r_type_str = f"0x{r.type_:04X}"
# Map relocation type to a name
if self.is_64bit():
# Check for allowed relocation types
allowed_reloc_types = {
ImageRelocationType.IMAGE_REL_AMD64_ADDR64,
ImageRelocationType.IMAGE_REL_AMD64_ADDR32NB,
ImageRelocationType.IMAGE_REL_AMD64_REL32,
ImageRelocationType.IMAGE_REL_AMD64_REL32_1,
ImageRelocationType.IMAGE_REL_AMD64_REL32_2,
ImageRelocationType.IMAGE_REL_AMD64_REL32_3,
ImageRelocationType.IMAGE_REL_AMD64_REL32_4,
ImageRelocationType.IMAGE_REL_AMD64_REL32_5
}
if r_type not in allowed_reloc_types:
log_message(LogLevel.WARN, f"Warning: Unexpected relocation type {r_type_str} at VA=0x{r.virtual_address:08X}")
else:
# Check for allowed relocation types
allowed_reloc_types = {
ImageRelocationType.IMAGE_REL_I386_DIR32,
ImageRelocationType.IMAGE_REL_I386_REL32
}
if r_type not in allowed_reloc_types:
log_message(LogLevel.WARN, f"Warning: Unexpected relocation type {r_type_str} at VA=0x{r.virtual_address:08X}")
log_message(LogLevel.INFO, f" VA=0x{r.virtual_address:08X}, "
f"SymIdx={r.symbol_table_index} ({sym_name}), "
f"Type={r_type_str}")
log_message(LogLevel.INFO, "")
###############################################################################
# Command-line Entry Point
###############################################################################
class LogLevel(Enum):
INFO = auto()
WARN = auto()
ERROR = auto()
class LogDestination(IntFlag):
NONE = 0
CS = auto() # Cobalt Strike BOFs
OC2 = auto() # OC2 BOFs
CI = auto() # Core Impact BOFs
ALL = CS | OC2 | CI # All BOF types
class LogFormat(Enum):
DEFAULT = "default" # Default format
VS = "vs" # Visual Studio
class LoaderType(Enum):
ANY = "any" # Any loader
CS = "cs" # Cobalt Strike
OC2 = "oc2" # OC2
CI = "ci" # Core Impact
loader_type = LoaderType.ANY
verbose_logging = False
has_errors = False
enable_colors = True
log_format = LogFormat.DEFAULT
input_file = ""
def log_message(level: LogLevel, message: str, dest: LogDestination = LogDestination.ALL):
if not verbose_logging and level == LogLevel.INFO:
return
# Skip messages not meant for current loader
if dest != LogDestination.ALL:
if loader_type == LoaderType.CS and not (dest & LogDestination.CS):
return
if loader_type == LoaderType.OC2 and not (dest & LogDestination.OC2):
return
if loader_type == LoaderType.CI and not (dest & LogDestination.CI):
return
if level == LogLevel.ERROR:
global has_errors
has_errors = True
format = {
LogFormat.DEFAULT: log_format_default,
LogFormat.VS: log_format_vs,
}
global log_format
print(format[log_format](level, message, dest))
def log_format_default(level: LogLevel, message: str, dest: LogDestination):
# ANSI color codes
COLORS = {
LogLevel.INFO: "", # default
LogLevel.WARN: "\033[93m", # yellow
LogLevel.ERROR: "\033[91m", # red
}
global enable_colors
RESET = "\033[0m" if enable_colors else ""
COLOR = COLORS[level] if enable_colors else ""
prefix = {
LogLevel.INFO: "INFO",
LogLevel.WARN: "WARN",
LogLevel.ERROR: "ERROR"
}
destinations = format_destinations(dest)
if dest == LogDestination.ALL:
return f"{COLOR}[{prefix[level]}] {message}{RESET}"
else:
return f"{COLOR}[{prefix[level]}] {message}{RESET} [{destinations}]"
def log_format_vs(level: LogLevel, message: str, dest: LogDestination):
global input_file
bof_name = Path(input_file).name
prefix = {
LogLevel.INFO: "",
LogLevel.WARN: "warning",
LogLevel.ERROR: "error"
}
destinations = f" [{format_destinations(dest)}]" if dest != LogDestination.ALL else ""
return f"{bof_name}: {prefix[level]} BOFLINT: {message}{destinations}"
def format_destinations(dest: LogDestination):
# append the log destinations to a string, formatted like this [CS|OC2], properly checking it as an intflag, also properly expanding ALL to CS|OC2
return "|".join([d.name for d in LogDestination if d & dest and d != LogDestination.ALL])
def is_valid_bof(filepath: str) -> bool:
"""Quick check if file is a valid BOF by checking machine type."""
try:
with open(filepath, 'rb') as f:
machine_data = f.read(2)
if len(machine_data) < 2:
return False
machine = struct.unpack("<H", machine_data)[0]
return machine in (MACHINE_AMD64, MACHINE_X86)
except:
return False
def main():
parser = argparse.ArgumentParser(description='BOF Linter - Validates COFF object files')
parser.add_argument('file', help='Path to COFF object file')
parser.add_argument('-v', '--verbose', action='store_true', help='Show INFO messages')
parser.add_argument('--loader', choices=['cs', 'oc2', 'ci', 'any'], default='any',
help='BOF loader type (cs=Cobalt Strike, oc2=OC2, ci=Core Impact, any=Any)')
parser.add_argument('--nocolor', action='store_true', help='Disable color output')
parser.add_argument('--logformat', choices=['default', 'vs'], default='default',
help='Output format (default=Normal output, vs=Visual Studio diagnostic)')
args = parser.parse_args()
global verbose_logging, loader_type, enable_colors, log_format, input_file
verbose_logging = args.verbose
loader_type = LoaderType(args.loader)
enable_colors = not args.nocolor
log_format = LogFormat(args.logformat)
input_file = args.file
if not os.path.isfile(args.file):
log_message(LogLevel.ERROR, "File does not exist")
sys.exit(1)
if not is_valid_bof(args.file):
log_message(LogLevel.ERROR, "Not a valid BOF COFF file (or unsupported machine type)")
sys.exit(1)
coff = COFFObject.from_file(args.file)
coff.lint()
if has_errors:
sys.exit(1)
if __name__ == "__main__":
main()
+201
View File
@@ -0,0 +1,201 @@
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 [yyyy] [name of copyright owner]
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.
+262
View File
@@ -0,0 +1,262 @@
# Beacon Object File Visual Studio Template
[This repository](https://github.com/Cobalt-Strike/bof-vs) contains the Beacon Object File Visual Studio (BOF-VS) template project.
You can read more about rationale and design decisions from this blog [post](https://www.cobaltstrike.com/blog/simplifying-bof-development).
## Quick Start Guide
To get started, use the instructions provided below.
### Video Walkthrough
<center><video width="720" heigth="480" crossorigin="anonymous" aria-label="BOF-VS setup" x-webkit-airplay="allow" playsinline="" controls controlslist="nodownload"><source src="https://raw.githubusercontent.com/Cobalt-Strike/bof-vs/main/media/Setup%20BOF-VS.mp4" type="video/mp4"></video></center>
https://github.com/user-attachments/assets/256fec31-bf25-4c10-9a10-0ab50751ca6d
### Prerequisites:
* An x64 Windows 10/11 development machine (without a security solution)
* Visual Studio Community/Pro/Enterprise 2022 (Desktop Development with C++ installed)
* Python 3 for the BOF linter (optional)
### Template Installation
Download the latest [release](https://github.com/Cobalt-Strike/bof-vs/releases/latest/download/bof-vs.zip),
and copy the `bof-vs.zip` archive under the
`%USERPROFILE%\Documents\Visual Studio 2022\Templates\ProjectTemplates` folder.
The template is accessible through Visual Studio's new project dialog,
where you can locate it by searching with the keyword `BOF`. Be certain
to have `All languages` chosen as the language filter.
If Visual Studio does not recognize the template, then reset the project template cache by
deleting the following file: `%localappdata%\Microsoft\VisualStudio\<VS vesrion>\ProjectTemplatesCache_{<GUID>}\cache.bin`
If you need a BOF-VS template for a previous version of Cobalt Strike, you can find it under the [tags](https://github.com/Cobalt-Strike/bof-vs/tags).
### Debug Build
The `Debug` target builds your BOF to an executable, which allows
you to benefit from the convenience of debugging your BOF code directly within
Visual Studio's built-in debugger. This will enable you to work at the source
code level without running the BOF through a Beacon.
### Release Build
The Release target compiles a release object file of your BOF,
which is designed to be used directly with Cobalt Strike.
## Dynamic Function Resolution
The project template includes two macro definitions to facilitate Dynamic
Function Resolution (DFR) declarations. These macros provide a robust mechanism
for efficiently resolving Win32 API functions in BOFs and simplify the
development process significantly.
### DFR Macro
The `DFR` macro can automatically extract the function type and generate
the required function declaration.
```cpp
DFR(KERNEL32, OpenProcess)
```
The above `DFR` macro statement expands to the following declaration.
```cpp
DECLSPEC_IMPORT decltype(OpenProcess) KERNEL32$OpenProcess;
```
A common practice is to map the `KERNEL32$OpenProcess` function to OpenProcess
using the following macro definition. This mapping enables you to call the
OpenProcess function directly, eliminating the need for the `KERNEL32$` prefix.
```cpp
#define OpenProcess KERNEL32$OpenProcess
```
#### Example Usage
```cpp
DFR(KERNEL32, OpenProcess)
#define OpenProcess KERNEL32$OpenProcess
void func1() {
OpenProcess(...);
}
void func2() {
OpenProcess(...);
}
```
### DFR_LOCAL Macro
The `DFR_LOCAL(module, function)` macro allows you to define a local function
pointer variable that directly references the `module$function` function. One
of the main advantages of using this macro compared to the `DFR` macro is
the elimination of the need for the additional `OpenProcess` -> `KERNEL32$OpenProcess`
mapping. This streamlines the code and makes it more concise. However, it's
important to note that the function pointer created with the `DFR_LOCAL` macro
has a limited scope and can only be accessed within the function where it is defined.
Consequently, if you plan to use the required WINAPI functions in multiple
functions throughout your BOF, you will need to define the function pointer
using the DFR_LOCAL macro in each of those functions.
#### Example Usage
```cpp
void func1() {
DFR_LOCAL(KERNEL32, OpenProcess);
OpenProcess(...);
}
void func2() {
DFR_LOCAL(KERNEL32, OpenProcess);
OpenProcess(...);
}
```
## Mocked APIs
The template includes a mocked version of the Beacon API for Debug builds,
enabling BOF debugging without a running Beacon instance. When you select
either the Debug or UnitTest configuration, the mocked API is automatically
included into the project.
### Argument Packer
The BofData class implements an argument packer to replicate the argument
packing behavior of the bof_pack aggressor function. This enables us to
call BOF's entry point with custom arguments without Beacon.
```cpp
bof::mock::BofData data;
// the pack function takes one or more arguments
data.pack<int, short, int, const char*>(6502, 80, 68010, "Hello World");
// alternatively, the << operator can be used to construct the arguments buffer
data << 0xdeadface << L"Hello World";
// raw buffers can be added too
const char buf[] = { 0x41, 0x42, 0x43, 0x44 };
data.addData(buf, sizeof(buf));
go(data.get(), data.size());
```
### Beacon API
The template also provides a mocked implementation of the Data Parser, Output,
and Format APIs. The mocked functions within the Output API print the output
to the standard output, ensuring the results are visible. Moreover, all returned
output is stored for future examination and analysis.
Furthermore, the Internal API functions (such as BeaconUseToken,
BeaconInjectProcess, etc.) are declared. However, it is important to note that
these functions lack the real implementation and only display an error message
on the standard error if called.
## Unit Tests
The project template offers an additional build target called `UnitTest`,
specifically designed to build BOFs with the GoogleTest framework. Furthermore,
the mock library provides a convenient `runMocked` function that handles
the argument packing, execution of the BOF's entry point, and capturing all
generated outputs.
Install the GoogleTest framework:
1. Right-click the project name in Solution Explorer.
2. Select `Manage NuGet Packages`.
3. Ensure that the `Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static` package is installed.
### Example Usage
```cpp
extern "C" {
#include "beacon.h"
void go(char* args, int len) {
datap parser;
BeaconDataParse(&parser, args, len);
int number = BeaconDataInt(&parser);
BeaconPrintf(CALLBACK_OUTPUT, "Hello: %i", number);
}
}
TEST(ExampleBofTest, TestCase1) {
std::vector<bof::output::OutputEntry> actual =
bof::runMocked<int>(go, 6502);
std::vector<bof::output::OutputEntry> expected = {
{CALLBACK_OUTPUT, "Hello: 6502"}
};
ASSERT_EQ(expected, actual);
}
```
## Sleepmask
In addition to supporting standard Beacon Object Files, the template also includes
functionality for developing Sleepmask BOFs. Beacon's Sleepmask can be used to apply
runtime masking to its PE sections and Heap allocations. Therefore, this template
creates a "mock Beacon" as part of the call to runMockedSleepMask() to replicate
the layout of Beacon in memory during debugging. This function also makes it possible
to apply malleable C2 settings to the "mock Beacon".
```c
// Mock up Beacon and run the sleep mask once
bof::runMockedSleepMask(sleep_mask);
// Mock up Beacon with the specific .stage C2 profile
bof::runMockedSleepMask(sleep_mask,
{
.allocator = bof::profile::Allocator::VirtualAlloc,
.obfuscate = bof::profile::Obfuscate::False,
.useRWX = bof::profile::UseRWX::True,
.module = "",
},
{
.sleepTimeMs = 5000,
.runForever = false,
}
);
```
## Multiple BOFs
The template supports multiple BOFs within separate files, enabling each .cpp
file to be compiled into an individual BOF. This approach eliminates the need
for multiple projects and allows grouping similar BOFs under one Visual Studio
project. When it comes to debugging, since each BOF is compiled into its own
debug executable, it is important to adjust the debug command accordingly.
This can be done through the project properties: `Configuration Properties
-> Debugging -> Command`.
## BOF Linter
To help identify potential BOF issues, the template integrates the `boflint`
tool, which runs during the release build to analyze the BOF for common errors
and shows them in the Error List of Visual Studio. This linter detects several
key issues, including:
- **Relocation types**: Ensures only supported relocation types are used.
- **Entry point validation**: Verifies the presence of the mandatory `go` or `sleep_mask` function.
- **Import resolution**: Flags undefined or unresolvable imports.
- **Large stack variables**: Detects large stack variable usage that results in an unresolvable import for stack probing.
- **Exception handling**: Warns against the use of exception handling, which is unsupported in BOFs.
`boflint` is only executed if Python 3 is detected on the system. If Python 3
is not installed, you can get it from:
- [Microsoft Store](https://apps.microsoft.com/detail/9ncvdn91xzqp?hl=en-us&gl=US)
- [Official Python release](https://www.python.org/downloads/windows/)
You can also run the linter manually:
```
python ./utils/boflint.py --loader cs x64/Release/bof.x64.o
```