BSides Canberra 2023 (#2)

This commit is contained in:
John U
2023-09-29 09:43:35 +08:00
committed by GitHub
parent d1af3fcd50
commit 793bce11fd
15 changed files with 2175 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
+3
View File
@@ -0,0 +1,3 @@
[submodule "Get-InjectedThreadEx/zydis"]
path = Get-InjectedThreadEx/zydis
url = https://github.com/zyantific/zydis
+569
View File
@@ -0,0 +1,569 @@
// Get-InjectedThreadEx.cpp : A C++ implementation of Get-InjectedThreadEx.ps1
//
// .SYNOPSIS
//
// Looks for threads that were created as a result of code injection.
//
// .DESCRIPTION
//
// Memory resident malware (fileless malware) often uses a form of memory injection to get code execution.
// Get-InjectedThreadEx looks at each running thread to determine if it is the result of memory injection.
//
// Win32StartAddress
//
// original
// - not MEM_IMAGE
// new
// - not MEM_COMMIT
// - MEM_IMAGE and Win32StartAddress is on a private (modified) page
// - MEM_IMAGE and x64 dll and Win32StartAddress is CFG violation or suppressed export
// - MEM_IMAGE and Win32StartAddress is in a suspicious module
// - MEM_IMAGE and x64 and Win32StartAddress is unexpected prolog
// - MEM_IMAGE and Win32StartAddress is preceded by unexpected bytes
// - MEM_IMAGE and x64 and Win32StartAddress wraps non-MEM_IMAGE start address
//
// KNOWN LIMITATIONS:
// - Only detects suspicious thread creations - not hijacks of existing threads.
// - Some WoW64 support not implemented.
#include "Get-InjectedThreadEx/Get-InjectedThreadEx.h"
BOOL ScanThread(HANDLE hProcess, BOOL bIsDotNet, PSS_THREAD_ENTRY& thread, std::string& symbol, std::vector<std::string>& detections) {
if (thread.Flags & PSS_THREAD_FLAGS_TERMINATED)
return FALSE;
if (!GetNearestSymbol(hProcess, thread.Win32StartAddress, symbol, true))
return FALSE;
if (thread.ContextRecord && thread.ContextRecord->Dr6)
detections.push_back("hw_breakpoint");
MEMORY_BASIC_INFORMATION mbi{};
if (!VirtualQueryEx(hProcess, thread.Win32StartAddress, &mbi, sizeof(mbi)))
return FALSE;
if (MEM_IMAGE != mbi.Type && MEM_COMMIT == mbi.State) {
detections.push_back("PRIVATE");
return TRUE;
}
// Has our MEM_IMAGE Win32StartAddress been (naively) hooked?
// https://blog.redbluepurple.io/offensive-research/bypassing-injection-detection//creating-the-thread
// Note - checking against bytes on disk after the fact won't help with false positives
// as the hook can easily be removed after thread start.
// Detection gap - the hook could easily be deeper, potentially even in a subsequent call. :-(
// Microsoft-Windows-Threat-Intelligence ETW events should detect this more robustly.
PSAPI_WORKING_SET_EX_INFORMATION pwsei{};
pwsei.VirtualAddress = thread.Win32StartAddress;
if (K32QueryWorkingSetEx(hProcess, &pwsei, sizeof(pwsei)) && !pwsei.VirtualAttributes.Shared) {
// I'm slightly worried about security vendor hooks landing on the same page
// as ntdll!TppWorkerThread and causing a false positive flood.
// So ignore ntdll modifications if we've been hooked too.
static bool s_edrFalsePositive = symbol.starts_with("ntdll.dll!") &&
K32QueryWorkingSetEx(GetCurrentProcess(), &pwsei, sizeof(pwsei)) && !pwsei.VirtualAttributes.Shared;
if(!s_edrFalsePositive)
detections.push_back("private_image");
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// Check for suspcious CFG BitMap states in our local pristine copy of the x64 bitmap
// Notes - executable CFG bitmaps are not shared - only library (dll) ones.
// - only 16-bytes aligned addresses, as this is a SetProcessValidCallTargets() requirement.
ULONG cfgBits;
if (InSystemImageRange(thread.Win32StartAddress) && 0 == ((ULONG_PTR)thread.Win32StartAddress & 0xF) &&
GetCfgBitsForAddress(thread.Win32StartAddress, &cfgBits) && 0 == cfgBits)
{
detections.push_back("cfg_invalid");
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// Suspicious start modules
std::wstring mappedPath;
// The file path assocated with Win32StartAddressModule
if (!GetMappedFileNameAsDosPath(hProcess, thread.Win32StartAddress, mappedPath))
return FALSE;
// There are no valid thread entry points (that I know of) in many Win32 modules.
const std::array<std::string, 11> modulesWithoutThreadEntrypoints = {
"kernel32", "kernelbase", "user32", "advapi32",
"psapi", "dbghelp", "imagehlp", "powrprof",
"verifier", "setupapi", "rpcrt4" }; // ...and many more
const auto startModule = std::filesystem::path(mappedPath).stem().string();
for (const auto& module : modulesWithoutThreadEntrypoints)
if (startModule == module) {
(void)GetNearestSymbolWithPdb(hProcess, thread.Win32StartAddress, symbol);
detections.push_back("unexpected(" + startModule + ")");
}
// kernel32!LoadLibrary
// And, even if there are, LoadLibrary is always a suspicious start address.
static auto hKernel32 = GetModuleHandleW(L"kernel32.dll");
static auto pLoadLibraryW = GetProcAddress(hKernel32, "LoadLibraryW");
static auto pLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA");
if (pLoadLibraryA == thread.Win32StartAddress || pLoadLibraryW == thread.Win32StartAddress)
detections.push_back("unexpected(" + symbol + ")");
// ntdll.dll but not a known entrypoint.
// These are the only valid thread entry points in ntdll that I know of.
static const std::array<PVOID, 4> ntdllThreadEntryPoints = {
GetSymbolAddress("ntdll!TppWorkerThread"),
GetSymbolAddress("ntdll!EtwpLogger"),
GetSymbolAddress("ntdll!DbgUiRemoteBreakin"),
GetSymbolAddress("ntdll!RtlpQueryProcessDebugInformationRemote")
};
if (mappedPath.ends_with(L"\\System32\\ntdll.dll")) {
auto bInvalidNtdllEntry = true;
for (const auto& address : ntdllThreadEntryPoints)
bInvalidNtdllEntry &= address == thread.Win32StartAddress;
if (bInvalidNtdllEntry) {
detections.push_back("unexpected(" + symbol + ")");
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// Common setup for the disassembler
constexpr auto MAX_INSN_LENGTH = 11ull; // theoretically 15, but empirically lower
ZydisDecoder decoder;
ZydisDecodedInstruction instruction;
// WoW64 can be inferred from the TEB address.
const bool bIsWow64 = (ULONG_PTR)thread.TebBaseAddress < 0x80000000;
if (bIsWow64)
(void)ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32);
else
(void)ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
//////////////////////////////////////////////////////////////////////////////////////////////////
// Check the bytes immmediately after Win32StartAddress
// They must be a function entrypoint.
// ... but almost anything is a valid entrypoint!
// x64 prologs have more structure (albiet mostly by convention) - so we'll stick those.
// See https://learn.microsoft.com/en-us/cpp/build/prolog-and-epilog
//
// Note - the loader ignores AddressOfEntry in CLR assemblies so we need to ignore them too.
auto bIsDotNetProcessEntrypoint = bIsDotNet && mappedPath.ends_with(L".exe");
if (!bIsWow64 && !bIsDotNetProcessEntrypoint) {
std::string startBytes;
constexpr auto MAX_PROLOG_SIZE = 64;
startBytes.resize(MAX_PROLOG_SIZE);
if (!ReadProcessMemorySafely(hProcess, thread.Win32StartAddress, startBytes, mbi))
return FALSE;
auto i = 0;
bool bValidInstruction = true;
ZyanU64 instructionPointer = (ZyanU64)thread.Win32StartAddress; // track this to calculate relative targets
auto framePointer = ZYDIS_REGISTER_RSP;
ZydisDecoderContext ctx{};
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
ZydisRegisterContext registers{};
std::string originalBytes; // if we follow a jump keep original bytes
const auto IsStackOperation = [&]() -> bool {
return ZYAN_SUCCESS(ZydisDecoderDecodeOperands(&decoder, &ctx, &instruction, operands, ZYDIS_MAX_OPERAND_COUNT)) &&
ZYDIS_OPERAND_TYPE_REGISTER == operands[0].type && ZYDIS_REGISTER_RSP == operands[0].reg.value;
};
const auto IsSaveRegisterOperation = [&]() -> bool {
return ZYAN_SUCCESS(ZydisDecoderDecodeOperands(&decoder, &ctx, &instruction, operands, ZYDIS_MAX_OPERAND_COUNT)) &&
ZYDIS_OPERAND_TYPE_MEMORY == operands[0].type && (ZYDIS_REGISTER_RSP == operands[0].mem.base || framePointer == operands[0].mem.base) &&
ZYDIS_OPERAND_TYPE_REGISTER == operands[1].type;
};
const auto IsFramePointerOperation = [&]() {
const auto bIsFP = ZYAN_SUCCESS(ZydisDecoderDecodeOperands(&decoder, &ctx, &instruction, operands, ZYDIS_MAX_OPERAND_COUNT)) &&
ZYDIS_OPERAND_TYPE_REGISTER == operands[0].type &&
((ZYDIS_OPERAND_TYPE_REGISTER == operands[1].type && (ZYDIS_REGISTER_RSP == operands[1].reg.value || framePointer == operands[1].reg.value)) ||
(ZYDIS_OPERAND_TYPE_MEMORY == operands[1].type && (ZYDIS_REGISTER_RSP == operands[1].mem.base || framePointer == operands[1].mem.base)));
if (bIsFP)
framePointer = operands[0].reg.value;
return bIsFP;
};
const auto IsRegDestination = [&](ZydisRegister reg) -> bool {
return ZYAN_SUCCESS(ZydisDecoderDecodeOperands(&decoder, &ctx, &instruction, operands, ZYDIS_MAX_OPERAND_COUNT)) &&
ZYDIS_OPERAND_TYPE_REGISTER == operands[0].type && reg == operands[0].reg.value;
};
const auto IsRegSource = [&](ZydisRegister reg) -> bool {
return ZYAN_SUCCESS(ZydisDecoderDecodeOperands(&decoder, &ctx, &instruction, operands, ZYDIS_MAX_OPERAND_COUNT)) &&
((ZYDIS_OPERAND_TYPE_REGISTER == operands[1].type && reg == operands[1].reg.value) ||
(ZYDIS_OPERAND_TYPE_MEMORY == operands[1].type && reg == operands[1].mem.base));
};
const auto SaveRegister = [&](ZydisRegister reg) -> void {
ZyanU64 regValue = 0;
registers.values[reg] = 0;
// Note - assumes prior call to IsRegDestination
if (ZYDIS_OPERAND_TYPE_REGISTER == operands[0].type && reg == operands[0].reg.value &&
ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&instruction, &operands[1], instructionPointer, &regValue)))
{
registers.values[reg] = regValue;
}
};
bool bThreadParameterInRax = false;
bool bCallThreadParameter = false;
bool bRaxSetLastInstruction = false;
int nFollowedJumps = 0;
constexpr auto MAX_JUMPS = 3;
const auto FollowJump = [&]() -> bool {
ZyanU64 jmpTarget = 0;
if (nFollowedJumps < MAX_JUMPS &&
ZYAN_SUCCESS(ZydisDecoderDecodeOperands(&decoder, &ctx, &instruction, operands, ZYDIS_MAX_OPERAND_COUNT)) &&
ZYAN_SUCCESS(ZydisCalcAbsoluteAddressEx(&instruction, &operands[0], instructionPointer, &registers, &jmpTarget)) &&
0 != jmpTarget)
{
// If the jump target isn't an immediate then we need to read it from the calculated address.
if ((ZYDIS_OPERAND_TYPE_MEMORY == operands[0].type && ZYDIS_REGISTER_RIP == operands[0].mem.base))
{
// If RCX has been mixed into RAX then we can't follow the jump.
// Such start addresses are useful proxy call functions for adversaries.
if (bRaxSetLastInstruction && bThreadParameterInRax) {
bCallThreadParameter = true;
return FALSE;
}
// This looks like a CFG check.
// mov RAX, indirect-call
// jmp __guard_dispatch_icall_fptr
// The CFG thunk will transfer execution to RAX on success - so just jump to RAX now.
if (bRaxSetLastInstruction && InSystemImageRange((PVOID)jmpTarget))
jmpTarget = registers.values[ZYDIS_REGISTER_RAX];
// Read the jump target
if (!ReadProcessMemorySafely(hProcess, (PVOID)jmpTarget, &jmpTarget))
return FALSE;
}
std::string jmpBytes = std::move(startBytes);
startBytes.resize(MAX_PROLOG_SIZE);
if (ReadProcessMemorySafely(hProcess, (PVOID)jmpTarget, startBytes)) {
jmpBytes.resize(i + instruction.length);
originalBytes += ToHex(jmpBytes) + "|";
// reset loop for new bytes
instructionPointer = jmpTarget;
instruction.length = 0;
i = 0;
return TRUE;
}
startBytes = std::move(originalBytes);
}
return FALSE;
};
bool bPrologStarted = false;
bool bPrologFinished = false;
bool bRaxSet = false;
bool bTestRcx = false;
for (i = 0; bValidInstruction && !bPrologFinished && i <= startBytes.size() - MAX_INSN_LENGTH; i += instruction.length) {
bValidInstruction = ZYAN_SUCCESS(ZydisDecoderDecodeInstruction(&decoder, &ctx, startBytes.data() + i, startBytes.length() - i, &instruction));
bRaxSetLastInstruction = bRaxSet;
bRaxSet = false;
switch (instruction.mnemonic) {
case ZYDIS_MNEMONIC_PUSH:
// push nonvolatile
bPrologStarted = true;
break;
case ZYDIS_MNEMONIC_MOV:
// mov [RSP+n], nonvolatile
if (IsSaveRegisterOperation())
bPrologStarted = true;
// mov frame-pointer, RSP
else if (IsFramePointerOperation())
bPrologStarted = true;
// mov RAX, fixed-allocation-size
// mov RAX, indirect-call-target
else if (IsRegDestination(ZYDIS_REGISTER_RAX)) {
SaveRegister(ZYDIS_REGISTER_RAX);
bRaxSet = true;
bThreadParameterInRax |= IsRegSource(ZYDIS_REGISTER_RCX);
}
else if (IsRegDestination(ZYDIS_REGISTER_EAX))
bRaxSet = true;
// sometimes stub functions reorder parameters or set static values
// mov FastcallParamReg, *
else
bValidInstruction = IsRegDestination(ZYDIS_REGISTER_RCX) || IsRegDestination(ZYDIS_REGISTER_RDX) || IsRegDestination(ZYDIS_REGISTER_R8) || IsRegDestination(ZYDIS_REGISTER_R9);
break;
case ZYDIS_MNEMONIC_CALL:
// call __chkstk() is the only call allowed in a prolog
// It uses a special calling convention.
bValidInstruction = bRaxSetLastInstruction;
break;
case ZYDIS_MNEMONIC_LEA:
// lea frame-pointer, [RSP-n]
if (IsFramePointerOperation())
bValidInstruction = true;
// lea RAX,[RIP+n]
else if (IsRegDestination(ZYDIS_REGISTER_RAX)) {
SaveRegister(ZYDIS_REGISTER_RAX);
bRaxSet = true;
}
// Some "functions" are just stubs around other functions with
// one (or more) fixed parameters.
// lea RCX, [n] - set first parameter.
else
bValidInstruction = IsRegDestination(ZYDIS_REGISTER_RCX);
break;
case ZYDIS_MNEMONIC_SUB: // prolog delimiter
bPrologFinished = IsStackOperation();
break;
case ZYDIS_MNEMONIC_TEST:
// test RCX, RCX - is the first parameter NULL?
// Checking for a non-NULL parameter and bailing early is
// a common optimisation.
bTestRcx = IsRegSource(ZYDIS_REGISTER_RCX) && IsRegDestination(ZYDIS_REGISTER_RCX);
break;
case ZYDIS_MNEMONIC_JZ:
// test RCX, RCX
// jz early-exit - don't follow
bValidInstruction = bTestRcx;
break;
case ZYDIS_MNEMONIC_JNZ:
// test RCX, RCX
// jnz true-entry-point - follow
bValidInstruction = bTestRcx;
if (!FollowJump())
bPrologFinished = bTestRcx;
break;
case ZYDIS_MNEMONIC_JMP:
// Some functions start with a short jmp to provide hotpatch space.
// jmp n - follow
bPrologFinished = FollowJump();
break;
default:
bValidInstruction = false;
}
instructionPointer += instruction.length;
}
startBytes.resize(i);
if (bCallThreadParameter)
detections.push_back(std::string("proxy_call(" + originalBytes + ToHex(startBytes) + ")"));
else if (!bPrologFinished)
detections.push_back(std::string("prolog(" + originalBytes + ToHex(startBytes) + ")"));
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// Check the bytes immmediately before Win32StartAddress
// The byte preceding a function prolog is typically a return, or filler byte.
// False positives can occur if data was included in a code section. This was
// common in older compilers...
std::string tailBytes;
tailBytes.resize(std::min(MAX_INSN_LENGTH, (ULONG_PTR)thread.Win32StartAddress - (ULONG_PTR)mbi.AllocationBase));
if (!ReadProcessMemorySafely(hProcess, (PVOID)((ULONG_PTR)thread.Win32StartAddress - tailBytes.size()), tailBytes))
return FALSE;
// False positives can occur if data was included in a code section. This was common in older compilers...
// ...and also in new compilers that support XFG. In this case, the 8-byte XFG hash is immediately before.
// https://blog.quarkslab.com/how-the-msvc-compiler-generates-xfg-function-prototype-hashes.html
const auto& tailbytesEnd = tailBytes.data() + tailBytes.size();
auto bIsValidTail = tailBytes.size() >= sizeof(UINT64) &&
IsValidXfgHash(*(UINT64*)(tailbytesEnd - sizeof(UINT64)));
// The byte preceding a function prolog is typically a return, or filler byte.
bIsValidTail |= tailBytes.empty() || '\x00' == tailBytes.back(); // NUL filled.
for (auto i = 1; !bIsValidTail && i <= tailBytes.size(); i++) {
if (!ZYAN_SUCCESS(ZydisDecoderDecodeInstruction(&decoder, NULL, tailbytesEnd - i, i, &instruction)) || instruction.length != i)
continue;
switch (instruction.mnemonic) {
// valid basic block end instructions
case ZYDIS_MNEMONIC_CALL:
case ZYDIS_MNEMONIC_JMP:
case ZYDIS_MNEMONIC_RET:
// valid alignment filler instructions
case ZYDIS_MNEMONIC_NOP:
case ZYDIS_MNEMONIC_INT3:
bIsValidTail = true;;
}
}
if (!bIsValidTail)
detections.push_back(std::string("tail(" + ToHex(tailBytes) + ")"));
//////////////////////////////////////////////////////////////////////////////////////////////////
// Check for suspicious call stacks
// [expected] ntdll!RtlUserThreadStart -> kernel32!BaseThreadInitThunk -> Win32StartAddress
// https://www.trustedsec.com/blog/avoiding-get-injectedthread-for-internal-thread-creation/
//
if (bIsWow64)
return TRUE; // TODO(jdu) Implement x86 stack climbing?
// The TIB is the first element of the TEB. Read the TIB to determine the stack limits.
NT_TIB64 tib;
if (!ReadProcessMemory(hProcess, thread.TebBaseAddress, &tib, sizeof(tib), NULL))
return FALSE;
// Determine the consumed stack size (and check for stack pivoting such as ROP).
const auto stackPointer = thread.ContextRecord ? thread.ContextRecord->Rsp : tib.StackLimit;
if (stackPointer > tib.StackBase || stackPointer < tib.StackLimit) {
detections.push_back("stack_pivot");
return TRUE;
}
// Read the (partial) base of stack contents - 1.5 pages seems sufficient given current stack randomisation
PVOID stackBuffer[0x1800 / sizeof(PVOID)];
const auto stackReadLength = std::min(sizeof(stackBuffer), (tib.StackBase - stackPointer) & ~0xF);
if (!ReadProcessMemory(hProcess, (PVOID)(tib.StackBase - stackReadLength), stackBuffer, stackReadLength, NULL))
return FALSE;
// Search the stack bottom up for the (probable) initial return addresses of the first 3+2 frames.
// Note - x64 stack frames are 16-byte aligned.
std::vector<std::string> callStackFrames;
bool bCallStackDetection = false;
const auto stackBufferCount = stackReadLength / sizeof(PVOID);
if (!StackClimb64(hProcess, stackBuffer, stackBufferCount, callStackFrames, &bCallStackDetection))
return FALSE;
// If the thread has been hijacked, then the return address alignment might be off.
// Search the skipped offsets this time.
if(0 == callStackFrames.size() && !StackClimb64(hProcess, stackBuffer, stackBufferCount, callStackFrames, &bCallStackDetection, 1))
return FALSE;
// Not enough stack frames discovered yet - append RIP
if (!bCallStackDetection && thread.ContextRecord && callStackFrames.size() < MIN_FRAMES) {
if (!VirtualQueryEx(hProcess, (PVOID)thread.ContextRecord->Rip, &mbi, sizeof(mbi)))
return FALSE;
if (!IsExecutable(mbi))
LogError("pid:%d, tid:%d RIP:%llx is not executable", thread.ProcessId, thread.ThreadId, thread.ContextRecord->Rip);
if (MEM_IMAGE != mbi.Type) {
callStackFrames.push_back("PRIVATE");
bCallStackDetection = true;
}
}
std::string callStackSummary;
callStackSummary.reserve(callStackFrames.size() * 16);
for (const auto& entry : callStackFrames) {
if (entry == "ntdll.dll!RtlUserThreadStart" || entry == "kernel32.dll!BaseThreadInitThunk")
continue; // skip common frames
if (!callStackSummary.empty())
callStackSummary += "|";
callStackSummary += entry.substr(0, entry.find_first_of('<')); // trim type information
}
if (bCallStackDetection)
if(callStackFrames.size() < 4)
detections.push_back("spoof(" + callStackSummary + ")");
else
detections.push_back("wrapper(" + callStackSummary + ")");
return TRUE;
}
struct FalsePositive {
const std::wstring ProcessName;
const std::string Symbol;
const size_t Count;
};
BOOL IsKnownFalsePositive(const HANDLE hProcess, const PROCESSENTRY32 &processEntry, const PSS_THREAD_ENTRY thread, std::string &symbol, const std::vector<std::string> &detections) {
static const std::array<FalsePositive, 2> falsePositives = { {
{ L"dwm.exe", "dwmcore.dll!CMit::RunInputThreadStatic", 1 },
{ L"vctip.exe", "vctip.exe!CorExeMain", 1 }
} };
(void)GetNearestSymbolWithPdb(hProcess, thread.Win32StartAddress, symbol);
BOOL bIsFalsePositive = FALSE;
for (const auto &fp : falsePositives)
bIsFalsePositive |= processEntry.szExeFile == fp.ProcessName && symbol == fp.Symbol && detections.size() == fp.Count;
return bIsFalsePositive;
}
int main(int, char* []) {
if (!IsElevated())
LogError("WARNING Not running as Administrator");
BOOLEAN _;
if(!NT_SUCCESS(RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &_)))
LogError("WARNING RtlAdjustPrivilege(DEBUG) failed");
const auto tsScanStarted = GetTickCount64();
UINT32 nProcessesTotal = 0;
UINT32 nProcessesScanned = 0;
UINT32 nThreadsScanned = 0;
////////////////////////////////////////////////////////////////////////////////
// Scan each process
HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnapshot == INVALID_HANDLE_VALUE) {
LogError("CreateToolhelp32Snapshot(PROCESS) failed. LastError:%d", GetLastError());
return 0;
}
PROCESSENTRY32 processEntry{};
processEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hProcessSnapshot, &processEntry)) {
LogError("Process32First failed. LastError:%d", GetLastError());
return 0;
}
do {
if (processEntry.th32ProcessID <= 4)
continue; // skip Idle and System
nProcessesTotal++;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processEntry.th32ProcessID);
if (NULL == hProcess)
continue; // skip process - Access is Denied, or process stopped
auto bIsDotNet = IsDotNet(hProcess);
//////////////////////////////////////////////////////////////////////////////////////////
// Scan all threads in the process
HPSS hThreadSnapshot = NULL;
HPSSWALK hWalk = NULL;
const auto captureFlags = PSS_CAPTURE_THREADS | PSS_CAPTURE_THREAD_CONTEXT;
const auto contextFlags = CONTEXT_CONTROL | CONTEXT_DEBUG_REGISTERS;
if (S_OK == PssCaptureSnapshot(hProcess, captureFlags, contextFlags, &hThreadSnapshot) && S_OK == PssWalkMarkerCreate(NULL, &hWalk)) {
nProcessesScanned++;
PSS_THREAD_ENTRY thread;
while (S_OK == PssWalkSnapshot(hThreadSnapshot, PSS_WALK_THREADS, hWalk, &thread, sizeof(thread))) {
std::string symbol;
std::vector<std::string> detections;
(void)ScanThread(hProcess, bIsDotNet, thread, symbol, detections);
nThreadsScanned++;
if (detections.size() > 0 && !IsKnownFalsePositive(hProcess, processEntry, thread, symbol, detections)) {
(void)GetNearestSymbolWithPdb(hProcess, thread.Win32StartAddress, symbol, true);
Log("ProcessName : %S", processEntry.szExeFile);
Log("pid:tid : %d:%d", thread.ProcessId, thread.ThreadId);
Log("Win32StartAddress : %s", symbol.c_str());
Log("Detections :");
for (const auto& detection : detections)
Log(" - %s", detection.c_str());
Log("");
}
}
}
if (NULL != hProcess)
(void)CloseHandle(hProcess);
if (NULL != hThreadSnapshot)
(void)PssFreeSnapshot(GetCurrentProcess(), hThreadSnapshot);
if (NULL != hWalk)
(void)PssWalkMarkerFree(hWalk);
} while (Process32Next(hProcessSnapshot, &processEntry));
if (INVALID_HANDLE_VALUE != hProcessSnapshot)
(void)CloseHandle(hProcessSnapshot);
Log("Scanned %d threads in %d (of %d) processes in %.2f seconds", nThreadsScanned, nProcessesScanned, nProcessesTotal, (GetTickCount64() - tsScanStarted) / 1000.0);
return 0;
}
+49
View File
@@ -0,0 +1,49 @@
#include "Get-InjectedThreadEx.h"
BOOL InSystemImageRange(PVOID Address) {
return Address >= (PVOID)0x7FF800000000 && Address < (PVOID)0x7FFFFFFF0000;
}
constexpr UINT64 XFG_MASK_UNSET = ~0xFFFDBFFF7EDFFB71; // mask of unset bits
constexpr UINT64 XFG_MASK_SET = 0x8000060010500070; // mask of set bits
constexpr UINT64 XFG_MASK_ALL = XFG_MASK_UNSET | XFG_MASK_SET;
BOOL IsValidXfgHash(UINT64 xfgHash) {
return XFG_MASK_SET == (xfgHash & XFG_MASK_ALL);
}
static PULONG_PTR FindCfgBitMap() {
// Find non-exported ntdll!LdrSystemDllInitBlock.CfgBitMap by looking at the first instruction of LdrControlFlowGuardEnforced
// 48833d [80be1400], 00 CMP qword ptr[LdrSystemDllInitBlock.CfgBitMap], 0x0
#pragma warning(suppress: 6387) // ntdll is always loaded
auto pLdrControlFlowGuardEnforced = GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "LdrControlFlowGuardEnforced");
if (NULL == pLdrControlFlowGuardEnforced)
return NULL;
auto ppCfgBitMap = (PULONG_PTR*)((ULONG_PTR)pLdrControlFlowGuardEnforced + 8 + *(DWORD*)((ULONG_PTR)pLdrControlFlowGuardEnforced + 3));
MEMORY_BASIC_INFORMATION mbi;
if (!VirtualQuery(*ppCfgBitMap, &mbi, sizeof(mbi)) || MEM_MAPPED != mbi.Type || *ppCfgBitMap != mbi.AllocationBase) {
LogError("FATAL: ntdll!LdrSystemDllInitBlock.CfgBitMap not found\n");
ExitProcess(1);
}
return *ppCfgBitMap;
}
constexpr auto CFG_INVALID = 0b00; // no address in this range is a valid target
constexpr auto CFG_ALL_VALID = 0b11; // all addresses in this range are valid.
constexpr auto CFG_ALIGNED_VALID = 0b01; // the only valid target is 16-byte aligned
constexpr auto CFG_EXPORT_SUPPRESSED = 0b10; // this range contains an export-suppressed target
BOOL GetCfgBitsForAddress(PVOID address, PULONG pCfgBits) {
static auto pCfgBitMap = FindCfgBitMap();
const PULONG_PTR pLocalEntry = pCfgBitMap + ((ULONG_PTR)address >> 9);
const ULONG cfgOffset = (((ULONG_PTR)(address)) >> 3) & 0x3E;
ULONG_PTR localEntry;
// We use ReadProcessMemory to safely read the volatile CfgBitMap.
if (ReadProcessMemory(GetCurrentProcess(), pLocalEntry, &localEntry, sizeof(localEntry), NULL)) {
*pCfgBits = (localEntry >> cfgOffset) & 0b11;
return TRUE;
}
return FALSE;
}
@@ -0,0 +1,89 @@
#pragma once
#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "DbgHelp.lib")
#pragma comment(lib, "Zydis.lib")
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <dbghelp.h>
//#include <ehdata.h>
#include <processsnapshot.h>
#include <psapi.h>
#include <tlhelp32.h>
#include <array>
#include <cstdio>
#include <filesystem>
#include <iostream>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <vector>
// x86/x86-64 disassembler
// https://github.com/zyantific/zydis - MIT
#define ZYDIS_STATIC_BUILD
#include <Zydis/Zydis.h>
// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
constexpr auto MAX_LONG_PATH = 0x7FFF;
////////////////////////////////////////////////////////////////////////////////////////////////////
// Logging
#define Log(_fmt_, ...) printf(_fmt_ "\n", ##__VA_ARGS__)
#define LogError(_fmt_, ...) fprintf(stderr, "[!] " _fmt_ "\n", ##__VA_ARGS__)
#if _DEBUG
#define LogDebug(_fmt_, ...) printf("[#] " _fmt_ "\n", ##__VA_ARGS__)
#else
#define LogDebug(...)
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
// Native API
#define SE_DEBUG_PRIVILEGE (20L)
typedef LONG NTSTATUS;
#define NT_SUCCESS(Status) ((Status) >= 0)
extern "C" {
NTSTATUS RtlAdjustPrivilege(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// CfgBitMap.cpp
BOOL GetCfgBitsForAddress(PVOID address, PULONG pCfgBits);
BOOL IsValidXfgHash(UINT64 xfgHash);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Memory.cpp
std::string ToHex(std::string bytes);
BOOL IsExecutable(const MEMORY_BASIC_INFORMATION& mbi);
BOOL InSystemImageRange(PVOID Address);
BOOL ReadProcessMemorySafely(HANDLE hProcess, PVOID address, PDWORD64 buffer);
BOOL ReadProcessMemorySafely(HANDLE hProcess, PVOID address, std::string& buffer);
BOOL ReadProcessMemorySafely(HANDLE hProcess, PVOID address, std::string& buffer, const MEMORY_BASIC_INFORMATION& mbi);
BOOL GetMappedFileNameAsDosPath(HANDLE hProcess, PVOID address, std::wstring& buffer);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Process.cpp
BOOL IsElevated();
BOOL IsDotNet(HANDLE hProcess);
////////////////////////////////////////////////////////////////////////////////////////////////////
// StackClimb.cpp
constexpr auto MIN_FRAMES = 5;
BOOL StackClimb64(const HANDLE hProcess, const PVOID stackBuffer[], const size_t stackBufferCount, std::vector<std::string>& callStackFrames, bool* pbDetection, int offset = 0);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Symbol.cpp
BOOL GetNearestSymbol(HANDLE hProcess, PVOID address, std::string& symbol, bool bIncludeDisplacement = false);
BOOL GetNearestSymbolWithPdb(HANDLE hProcess, PVOID address, std::string& symbol, bool bIncludeDisplacement = false);
PVOID GetSymbolAddress(const char symbol[]);
////////////////////////////////////////////////////////////////////////////////////////////////////
// Unwind.cpp
HRESULT CalculateFrameSize(PVOID returnAddress, PDWORD pFrameSize, const std::wstring& filepath, PVOID remoteBase);
BOOL IsValidCallSite(HANDLE hProcess, bool bIsWow64, const MEMORY_BASIC_INFORMATION& mbi, PVOID callsite, bool* bCallFound);
int main(int, char* []);
@@ -0,0 +1,142 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33627.172
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Get-InjectedThreadEx", "Get-InjectedThreadEx.vcxproj", "{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}"
ProjectSection(ProjectDependencies) = postProject
{88A23124-5640-35A0-B890-311D7A67A7D2} = {88A23124-5640-35A0-B890-311D7A67A7D2}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Zydis", "zydis\msvc\zydis\Zydis.vcxproj", "{88A23124-5640-35A0-B890-311D7A67A7D2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug Kernel|x64 = Debug Kernel|x64
Debug Kernel|x86 = Debug Kernel|x86
Debug MD DLL|x64 = Debug MD DLL|x64
Debug MD DLL|x86 = Debug MD DLL|x86
Debug MD|x64 = Debug MD|x64
Debug MD|x86 = Debug MD|x86
Debug MT DLL|x64 = Debug MT DLL|x64
Debug MT DLL|x86 = Debug MT DLL|x86
Debug MT|x64 = Debug MT|x64
Debug MT|x86 = Debug MT|x86
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release Kernel|x64 = Release Kernel|x64
Release Kernel|x86 = Release Kernel|x86
Release MD DLL|x64 = Release MD DLL|x64
Release MD DLL|x86 = Release MD DLL|x86
Release MD|x64 = Release MD|x64
Release MD|x86 = Release MD|x86
Release MT DLL|x64 = Release MT DLL|x64
Release MT DLL|x86 = Release MT DLL|x86
Release MT|x64 = Release MT|x64
Release MT|x86 = Release MT|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug Kernel|x64.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug Kernel|x64.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug Kernel|x86.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug Kernel|x86.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MD DLL|x64.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MD DLL|x64.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MD DLL|x86.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MD DLL|x86.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MD|x64.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MD|x64.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MD|x86.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MD|x86.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MT DLL|x64.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MT DLL|x64.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MT DLL|x86.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MT DLL|x86.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MT|x64.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MT|x64.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MT|x86.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug MT|x86.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug|x64.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug|x64.Build.0 = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Debug|x86.ActiveCfg = Debug|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release Kernel|x64.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release Kernel|x64.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release Kernel|x86.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release Kernel|x86.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MD DLL|x64.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MD DLL|x64.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MD DLL|x86.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MD DLL|x86.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MD|x64.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MD|x64.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MD|x86.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MD|x86.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MT DLL|x64.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MT DLL|x64.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MT DLL|x86.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MT DLL|x86.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MT|x64.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MT|x64.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MT|x86.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release MT|x86.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release|x64.ActiveCfg = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release|x64.Build.0 = Release|x64
{256D0782-6D09-44B2-AD5C-FC8EF085D1D3}.Release|x86.ActiveCfg = Release|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug Kernel|x64.ActiveCfg = Debug Kernel|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug Kernel|x64.Build.0 = Debug Kernel|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug Kernel|x86.ActiveCfg = Debug Kernel|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug Kernel|x86.Build.0 = Debug Kernel|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MD DLL|x64.ActiveCfg = Debug MD DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MD DLL|x64.Build.0 = Debug MD DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MD DLL|x86.ActiveCfg = Debug MD DLL|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MD DLL|x86.Build.0 = Debug MD DLL|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MD|x64.ActiveCfg = Debug MD|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MD|x64.Build.0 = Debug MD|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MD|x86.ActiveCfg = Debug MD|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MD|x86.Build.0 = Debug MD|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MT DLL|x64.ActiveCfg = Debug MT DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MT DLL|x64.Build.0 = Debug MT DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MT DLL|x86.ActiveCfg = Debug MT DLL|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MT DLL|x86.Build.0 = Debug MT DLL|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MT|x64.ActiveCfg = Debug MT|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MT|x64.Build.0 = Debug MT|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MT|x86.ActiveCfg = Debug MT|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug MT|x86.Build.0 = Debug MT|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug|x64.ActiveCfg = Debug MD|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug|x64.Build.0 = Debug MD|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug|x86.ActiveCfg = Debug MD|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Debug|x86.Build.0 = Debug MD|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release Kernel|x64.ActiveCfg = Release Kernel|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release Kernel|x64.Build.0 = Release Kernel|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release Kernel|x86.ActiveCfg = Release Kernel|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release Kernel|x86.Build.0 = Release Kernel|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MD DLL|x64.ActiveCfg = Release MD DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MD DLL|x64.Build.0 = Release MD DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MD DLL|x86.ActiveCfg = Release MD DLL|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MD DLL|x86.Build.0 = Release MD DLL|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MD|x64.ActiveCfg = Release MD|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MD|x64.Build.0 = Release MD|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MD|x86.ActiveCfg = Release MD|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MD|x86.Build.0 = Release MD|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MT DLL|x64.ActiveCfg = Release MT DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MT DLL|x64.Build.0 = Release MT DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MT DLL|x86.ActiveCfg = Release MT DLL|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MT DLL|x86.Build.0 = Release MT DLL|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MT|x64.ActiveCfg = Release MT|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MT|x64.Build.0 = Release MT|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MT|x86.ActiveCfg = Release MT|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release MT|x86.Build.0 = Release MT|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release|x64.ActiveCfg = Release MD DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release|x64.Build.0 = Release MD DLL|x64
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release|x86.ActiveCfg = Release MT DLL|Win32
{88A23124-5640-35A0-B890-311D7A67A7D2}.Release|x86.Build.0 = Release MT DLL|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2409743E-DDE1-4665-8C29-FA5C5C8DEEFE}
EndGlobalSection
EndGlobal
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\Get-InjectedThreadEx.cpp" />
<ClCompile Include="CfgBitMap.cpp" />
<ClCompile Include="Memory.cpp" />
<ClCompile Include="Process.cpp" />
<ClCompile Include="StackClimb.cpp" />
<ClCompile Include="Symbol.cpp" />
<ClCompile Include="Unwind.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Get-InjectedThreadEx.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{256d0782-6d09-44b2-ad5c-fc8ef085d1d3}</ProjectGuid>
<RootNamespace>GetInjectedThreadEx</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</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|x64'">
<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)'=='Debug|x64'">
<IncludePath>$(SolutionDir)\phnt;$(SolutionDir)\zydis\include;$(SolutionDir)\zydis\dependencies\zycore\include;$(IncludePath)</IncludePath>
<LibraryPath>$(SolutionDir)\zydis\msvc\bin\$(Configuration)$(Platform);$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>$(SolutionDir)\phnt;$(SolutionDir)\zydis\include;$(SolutionDir)\zydis\dependencies\zycore\include;$(IncludePath)</IncludePath>
<LibraryPath>$(SolutionDir)\zydis\msvc\bin\$(Configuration)$(Platform);$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
<ControlFlowGuard>Guard</ControlFlowGuard>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</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>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard_C>stdc17</LanguageStandard_C>
<ControlFlowGuard>Guard</ControlFlowGuard>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
@@ -0,0 +1,45 @@
<?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>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\Get-InjectedThreadEx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CfgBitMap.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Symbol.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Unwind.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Memory.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Process.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="StackClimb.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Get-InjectedThreadEx.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
+114
View File
@@ -0,0 +1,114 @@
#include "Get-InjectedThreadEx.h"
BOOL IsExecutable(const MEMORY_BASIC_INFORMATION& mbi) {
constexpr auto PAGE_EXECUTE_ANY = PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
return MEM_COMMIT == mbi.State && 0 != (mbi.Protect & PAGE_EXECUTE_ANY);
}
BOOL ReadProcessMemorySafely(HANDLE hProcess, PVOID address, std::string& buffer, const MEMORY_BASIC_INFORMATION& mbi) {
if (MEM_COMMIT != mbi.State || 0 != (mbi.Protect & PAGE_GUARD))
return FALSE;
// TODO(jdu) NEXT handle reads split over multiple regions
return ReadProcessMemory(hProcess, address, buffer.data(), buffer.size(), NULL);
}
BOOL ReadProcessMemorySafely(HANDLE hProcess, PVOID address, std::string& buffer) {
MEMORY_BASIC_INFORMATION mbi;
if(!VirtualQueryEx(hProcess, address, &mbi, sizeof(mbi)))
return FALSE;
return ReadProcessMemorySafely(hProcess, address, buffer, mbi);
}
BOOL ReadProcessMemorySafely(HANDLE hProcess, PVOID address, PDWORD64 buffer) {
MEMORY_BASIC_INFORMATION mbi;
if (!VirtualQueryEx(hProcess, address, &mbi, sizeof(mbi)))
return FALSE;
auto b = MEM_COMMIT != mbi.State;
auto b2 = 0 != (mbi.Protect & PAGE_GUARD);
auto b3 = (MEM_COMMIT != mbi.State || 0 != (mbi.Protect & PAGE_GUARD));
if (MEM_COMMIT != mbi.State || 0 != (mbi.Protect & PAGE_GUARD))
return FALSE;
return ReadProcessMemory(hProcess, address, buffer, sizeof(DWORD64), NULL);
}
BOOL GetMappedFileNameAsDosPath(HANDLE hProcess, PVOID address, std::wstring& buffer) {
// Cache #1 - the mapping of shared image allocations to DOS paths
const auto bIsSharedDll = InSystemImageRange(address);
const auto allocationBase = (ULONG_PTR)address & ~0xFFFFull;
static std::unordered_map<ULONG_PTR, std::wstring> s_DosPathCache;
if (bIsSharedDll) {
const auto it = s_DosPathCache.find(allocationBase);
if (it != s_DosPathCache.end()) {
buffer = it->second;
return TRUE;
}
}
// Cache #2 - the mapping from Device prefixes to Dos prefixes
static std::unordered_map<std::wstring, std::wstring> s_Device2DosPrefixMap;
if (s_Device2DosPrefixMap.empty()) {
wchar_t drive[3] = L"A:";
for (drive[0] = L'A'; drive[0] <= L'Z'; drive[0]++) {
std::wstring deviceName;
deviceName.resize(MAX_PATH);
if (!QueryDosDeviceW(drive, deviceName.data(), (DWORD)deviceName.size())) {
const auto dwError = GetLastError();
if (ERROR_FILE_NOT_FOUND != dwError)
LogError("QueryDosDeviceW(%S) failed with %d\n", drive, dwError);
continue;
}
deviceName.resize(wcslen(deviceName.c_str()));
s_Device2DosPrefixMap.insert(std::make_pair(deviceName, std::wstring(drive)));
}
}
buffer.clear();
buffer.resize(MAX_PATH);
// Note - K32GetMappedFileName returns the device path such as \Device\Harddisk0\Windows\System32\ntdll.dll
auto nBytesReturned = K32GetMappedFileNameW(hProcess, address, buffer.data(), (DWORD)buffer.size());
if (MAX_PATH == nBytesReturned) {
buffer.resize(MAX_LONG_PATH);
nBytesReturned = K32GetMappedFileNameW(hProcess, address, buffer.data(), (DWORD)buffer.size());
if (MAX_LONG_PATH == nBytesReturned)
LogError("K32GetMappedFileNameW(%p) exceeded maximum long path - %S\n", address, buffer.substr(0, MAX_PATH).c_str());
}
if (0 == nBytesReturned)
return FALSE;
// We have a device path - now convert it to a DOS path
constexpr auto DEVICE_PREFIX = L"\\Device\\";
if (buffer.length() < sizeof(DEVICE_PREFIX) || 0 != _wcsnicmp(buffer.c_str(), DEVICE_PREFIX, sizeof(DEVICE_PREFIX))) {
LogError("K32GetMappedFileNameW(%p) did not return device path - %S\n", address, buffer.substr(0, MAX_PATH).c_str());
return TRUE; // best effort - return what we have
}
// Loopkup DOS prefix of "\device\<name>" component in our cached mapping
const auto pos = buffer.find(L'\\', std::char_traits<WCHAR>::length(DEVICE_PREFIX));
const auto devicePrefixLength = (std::wstring::npos == pos) ? buffer.length() : pos;
const auto device2DosPrefixMapping = s_Device2DosPrefixMap.find(buffer.substr(0, devicePrefixLength));
if (s_Device2DosPrefixMap.end() == device2DosPrefixMapping) {
LogError("Could not resolve device prefix to DOS drive letter - %S\n", buffer.substr(0, MAX_PATH).c_str());
return TRUE; // best effort - return what we have
}
buffer = device2DosPrefixMapping->second + buffer.substr(devicePrefixLength);
buffer.resize(wcslen(buffer.c_str()));
// Update our path cache
if (nBytesReturned && bIsSharedDll)
s_DosPathCache.insert(std::make_pair(allocationBase, buffer));
return TRUE;
}
std::string ToHex(std::string bytes) {
std::stringstream hexBytes;
hexBytes << std::hex << std::setfill('0');
for (size_t i = 0; i < bytes.length(); i++) {
hexBytes << std::setw(2) << (DWORD)(BYTE)bytes[i];
}
return hexBytes.str();
}
+68
View File
@@ -0,0 +1,68 @@
#include "Get-InjectedThreadEx.h"
BOOL IsElevated()
{
BOOL bIsElevated = FALSE;
DWORD dwSize;
HANDLE hToken = NULL;
TOKEN_ELEVATION elevation;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken) &&
GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize) &&
sizeof(elevation) == dwSize)
{
bIsElevated = elevation.TokenIsElevated;
}
if (hToken) {
(void)CloseHandle(hToken);
}
return bIsElevated;
}
BOOL GetProcessPath(HANDLE hProcess, std::wstring& buffer) {
buffer.clear();
buffer.resize(MAX_PATH);
DWORD size = (DWORD)buffer.size();
if (!QueryFullProcessImageNameW(hProcess, 0, buffer.data(), &size)) {
buffer.resize(MAX_LONG_PATH);
size = (DWORD)buffer.size();
if (!QueryFullProcessImageNameW(hProcess, 0, buffer.data(), &size)) {
buffer.clear();
return FALSE;
}
}
buffer.resize(size);
return TRUE;
}
BOOL IsDotNet(HANDLE hProcess) {
std::wstring executable;
if (!GetProcessPath(hProcess, executable))
return FALSE;
// Map the executable's PE headers.
constexpr auto FILE_SHARE_ALL = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
auto hFile = CreateFileW(executable.c_str(), GENERIC_READ, FILE_SHARE_ALL, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
auto hMapping = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
auto pMapping = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0x1000);
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-data-directories-image-only
// The last directory is actually the CLR Runtime Header - not the "COM Descriptor".
// If it exists, then the PE file is a CLR assembly - aka .NET
ULONG size = 0;
BOOL bIsDotNet = NULL != ImageDirectoryEntryToData(pMapping, TRUE, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, &size);
if (pMapping)
(void)UnmapViewOfFile(pMapping);
if (hMapping)
(void)CloseHandle(hMapping);
if (hFile)
(void)CloseHandle(hFile);
return bIsDotNet;
}
+118
View File
@@ -0,0 +1,118 @@
#include "Get-InjectedThreadEx.h"
void SymboliseCallStack(HANDLE hProcess, const std::vector<PVOID> returnAddresses, const std::string last, std::vector<std::string> &callStackFrames) {
for (const auto address : returnAddresses) {
std::string frameSymbol;
if (!GetNearestSymbolWithPdb(hProcess, address, frameSymbol))
frameSymbol = "<ERROR>";
callStackFrames.push_back(frameSymbol);
}
callStackFrames.push_back(last);
}
BOOL StackClimb64(const HANDLE hProcess,
const PVOID stackBuffer[],
const size_t stackBufferCount,
std::vector<std::string>& callStackFrames,
bool* pbDetection,
int offset)
{
std::vector<PVOID> returnAddresses;
// Query SystemInfo for the usermode address bounds
static SYSTEM_INFO s_si{};
if (!s_si.lpMaximumApplicationAddress) {
GetSystemInfo(&s_si);
if (0 == s_si.lpMaximumApplicationAddress) {
LogError("GetSystemInfo() failed");
return FALSE;
}
// Note - My code makes some assumptions about page and allocation sizes.
if (0x1000 != s_si.dwPageSize || 0x10000 != s_si.dwAllocationGranularity) {
LogError("System not supported. PageSize=0x%x AllocationGranularity=0x%x", s_si.dwPageSize, s_si.dwAllocationGranularity);
return FALSE;
}
}
auto lastRspOffset = 0u;
const auto StackBufferLast = &stackBuffer[stackBufferCount - 1];
for (auto i = offset; !*pbDetection && (i < stackBufferCount) && (returnAddresses.size() < MIN_FRAMES); i += 2) {
const PVOID candidateRip = StackBufferLast[-i];
// Skip any invalid usermode addresses
if (candidateRip < s_si.lpMinimumApplicationAddress || candidateRip >= s_si.lpMaximumApplicationAddress)
continue;
MEMORY_BASIC_INFORMATION mbi;
if (!VirtualQueryEx(hProcess, candidateRip, &mbi, sizeof(mbi)))
return FALSE;
// Return address must be executable
if (!IsExecutable(mbi))
continue;
// We can't read this executable page due to the Guard - so just alert.
if (0 != (mbi.State & PAGE_GUARD)) {
SymboliseCallStack(hProcess, returnAddresses, "GUARD", callStackFrames);
*pbDetection = true;
return TRUE;
}
bool bValidCallsite;
if (MEM_IMAGE != mbi.Type) {
if (IsValidCallSite(hProcess, false, mbi, candidateRip, &bValidCallsite) && bValidCallsite) {
// A suspicious frame was found!
SymboliseCallStack(hProcess, returnAddresses, "PRIVATE", callStackFrames);
*pbDetection = true;
return TRUE;
}
continue; // Not a return address - keep searching
}
std::wstring candidateRipMappedPath;
if (!GetMappedFileNameAsDosPath(hProcess, candidateRip, candidateRipMappedPath))
return FALSE;
DWORD frameSize = 0;
if (0 != lastRspOffset) {
auto status = CalculateFrameSize(candidateRip, &frameSize, candidateRipMappedPath, mbi.AllocationBase);
if (S_OK == status && (i - lastRspOffset) != frameSize)
continue; // Invalid frame size - keep searching
if (IS_ERROR(status)) // but not WARNINGs
continue;
// We could not calculate exact frame size - but do have a lowerbound.
// TODO(jdu) We could scan ahead one (or more) frames here to see if we recover?
if (S_OK != status && (i - lastRspOffset) < frameSize)
continue; // Frame too small - keep searching
}
if (!IsValidCallSite(hProcess, false, mbi, candidateRip, &bValidCallsite) && !bValidCallsite)
continue; // Not a return address - keep searching
auto status = CalculateFrameSize(candidateRip, &frameSize, candidateRipMappedPath, mbi.AllocationBase);
// LogDebug(" %03i: %p %s valid=%d frameSize=%d FP=%d", i, candidateRip, frameSymbol.c_str(), bValidCallsite, frameSize, S_OK != status);
returnAddresses.push_back(candidateRip);
lastRspOffset = i;
// Frames must be at least 0x20 bytes due to fastcall parameter shadow space
i += 4;
}
// Just use exports if nothing suspicious was found
// TODO(jdu) We only need to return this at all so that we know if a 2nd pass is required.
// Perhaps move 2nd pass and RIP logic here.
if (callStackFrames.empty()) {
for (const auto address : returnAddresses) {
std::string frameSymbol;
(void)GetNearestSymbol(hProcess, address, frameSymbol);
callStackFrames.push_back(frameSymbol);
}
}
return TRUE;
}
+154
View File
@@ -0,0 +1,154 @@
#include "Get-InjectedThreadEx.h"
typedef struct _SYMBOL_INFO_FULL : SYMBOL_INFO {
CHAR NameBuffer[MAX_SYM_NAME - 1];
} SYMBOL_INFO_FULL;
#define INIT_FAILED ((HANDLE)-3)
constexpr auto SYMOPTS = SYMOPT_UNDNAME | SYMOPT_CASE_INSENSITIVE | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_INCLUDE_32BIT_MODULES;
constexpr auto MicrosoftPublicSymbols = L"srv**https://msdl.microsoft.com/download/symbols";
static HANDLE GetUniqueIdForSymbols() {
// Initialise once - note we're only resolving symbols locally.
static HANDLE s_hUniqueId = NULL; // INVALID_HANDLE_VALUE == GetCurrentProcess()!
if (NULL == s_hUniqueId) {
if (!SymSetOptions(SymGetOptions() | SYMOPTS))
LogError("SymSetOptions() failed with 0x%x", GetLastError());
s_hUniqueId = GetCurrentProcess();
if (!SymInitializeW(s_hUniqueId, MicrosoftPublicSymbols, FALSE)) {
LogError("SymInitialize() failed with 0x%x", GetLastError());
s_hUniqueId = INIT_FAILED;
}
else
{
// Check that we're using public symbols - not just exports
auto hNtdll = GetModuleHandleW(L"ntdll.dll");
std::wstring ntdllPath;
if (!GetMappedFileNameAsDosPath(GetCurrentProcess(), hNtdll, ntdllPath))
LogError("GetMappedFilename(ntdll) failed");
if(!SymLoadModuleExW(s_hUniqueId, NULL, ntdllPath.c_str(), NULL, (DWORD64)hNtdll, 0, NULL, 0))
LogError("SymLoadModule(ntdll) failed");
if (!GetSymbolAddress("ntdll!TppWorkerThread"))
LogError("WARNING Symbols not found - falling back to exports");
auto hKernel32 = GetModuleHandleW(L"kernel32.dll");
std::wstring kernel32Path;
if (!GetMappedFileNameAsDosPath(GetCurrentProcess(), hKernel32, kernel32Path))
LogError("GetMappedFilename(kernel32) failed");
if (!SymLoadModuleExW(s_hUniqueId, NULL, kernel32Path.c_str(), NULL, (DWORD64)hKernel32, 0, NULL, 0))
LogError("SymLoadModule(kernel32) failed");
}
}
return s_hUniqueId;
}
PVOID GetSymbolAddress(const char symbol[]) {
const auto hUniqueId = GetUniqueIdForSymbols();
if (INIT_FAILED == hUniqueId)
return NULL;
SYMBOL_INFO_FULL symbolInfo{};
symbolInfo.SizeOfStruct = sizeof(SYMBOL_INFO);
symbolInfo.MaxNameLen = MAX_SYM_NAME;
if (!SymFromName(hUniqueId, symbol, &symbolInfo))
return NULL;
return (PVOID)symbolInfo.Address;
}
// pretty print <module>!<symbol>[+<displacement>]
void GetPrettySymbol(HANDLE hProcess, PVOID address, std::wstring& modulePath, std::string& symbol, bool bIncludeDisplacement) {
symbol.resize(128);
symbol = std::filesystem::path(modulePath).filename().string();
SYMBOL_INFO_FULL symbolInfo{};
symbolInfo.SizeOfStruct = sizeof(SYMBOL_INFO);
symbolInfo.MaxNameLen = MAX_SYM_NAME;
DWORD64 displacement = 0;
if (SymFromAddr(hProcess, (DWORD64)address, &displacement, &symbolInfo)) {
symbol += "!" + std::string(symbolInfo.Name);
}
else if (bIncludeDisplacement) {
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQueryEx(hProcess, address, &mbi, sizeof(mbi)))
displacement = (DWORD64)address - (DWORD64)mbi.AllocationBase;
}
if (bIncludeDisplacement && displacement) {
char buffer[_MAX_U64TOSTR_BASE16_COUNT];
_ui64toa_s(displacement, buffer, sizeof(buffer), 16);
symbol += "+0x" + std::string(buffer);
}
symbol.resize(strlen(symbol.c_str()));
}
BOOL GetNearestSymbolWithPdb(HANDLE hProcess, PVOID address, std::string& symbol, bool bIncludeDisplacement) {
if (!SymSetOptions(SymGetOptions() | SYMOPTS))
LogError("SymSetOptions() failed with 0x%x", GetLastError());
if (!SymInitializeW(hProcess, MicrosoftPublicSymbols, FALSE)) {
LogError("SymInitialize() failed with 0x%x", GetLastError());
return FALSE;
}
std::wstring modulePath;
if (!GetMappedFileNameAsDosPath(hProcess, address, modulePath)) {
if (symbol.empty()) {
char buffer[_MAX_U64TOSTR_BASE16_COUNT];
_ui64toa_s((DWORD64)address, buffer, sizeof(buffer), 16);
symbol = "0x" + std::string(buffer);
}
(void)SymCleanup(hProcess);
return TRUE;
}
MEMORY_BASIC_INFORMATION mbi;
if (!VirtualQueryEx(hProcess, address, &mbi, sizeof(mbi)) ||
!SymLoadModuleExW(hProcess, NULL, modulePath.c_str(), NULL, (DWORD64)mbi.AllocationBase, 0, NULL, 0))
{
(void)SymCleanup(hProcess);
// fallback to best-effort
const auto hUniqueId = GetUniqueIdForSymbols();
GetPrettySymbol(hUniqueId, address, modulePath, symbol, bIncludeDisplacement);
return FALSE;
}
GetPrettySymbol(hProcess, address, modulePath, symbol, bIncludeDisplacement);
(void)SymCleanup(hProcess);
return TRUE;
}
BOOL GetNearestSymbol(HANDLE hProcess, PVOID address, std::string& symbol, bool bIncludeDisplacement) {
const auto hUniqueId = GetUniqueIdForSymbols();
if (INIT_FAILED == hUniqueId)
return FALSE;
// Maintain a cache of symbols in shared image allocations
static std::unordered_map<PVOID, std::string> s_SymbolCache;
const auto it = s_SymbolCache.find(address);
if (it != s_SymbolCache.end()) {
symbol = it->second;
return TRUE;
}
// module name (from remote process)
std::wstring modulePath;
if (!GetMappedFileNameAsDosPath(hProcess, address, modulePath)) {
char buffer[_MAX_U64TOSTR_BASE16_COUNT];
_ui64toa_s((DWORD64)address, buffer, sizeof(buffer), 16);
symbol = "0x" + std::string(buffer);
return TRUE;
}
GetPrettySymbol(hUniqueId, address, modulePath, symbol, bIncludeDisplacement);
if (InSystemImageRange(address))
s_SymbolCache.insert(std::make_pair(address, symbol));
return TRUE;
}
+296
View File
@@ -0,0 +1,296 @@
// Reference:
// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64
//
// Other useful references:
// https://codemachine.com/articles/x64_deep_dive.html
// http://www.uninformed.org/?v=4&a=1 Improving Automated Analysis of Windows x64 Binaries
// http://www.nynaeve.net/?p=113 Programming against the x64 exception handling support
// https://www.sciencedirect.com/science/article/pii/S1742287618300458 Building stack traces from memory dump of Windows x64
// https://github.com/reactos/reactos/blob/master/sdk/lib/rtl/amd64/unwind.c
// https://auscitte.github.io/posts/Exception-Directory-pefile Boots for Walking Backwards: Teaching pefile How to Understand SEH-Related Data in 64-bit PE Files
// https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs
#include "Get-InjectedThreadEx.h"
typedef enum _UNWIND_OP_CODES {
UWOP_PUSH_NONVOL = 0, /* info == register number */
UWOP_ALLOC_LARGE, /* no info, alloc size in next 2 slots */
UWOP_ALLOC_SMALL, /* info == size of allocation / 8 - 1 */
UWOP_SET_FPREG, /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
UWOP_SAVE_NONVOL, /* info == register number, offset in next slot */
UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
UWOP_EPILOG, /* added in v2. UNDOCUMENTED. */
UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */
UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
UWOP_PUSH_MACHFRAME /* info == 0: no dwError-code, 1: dwError-code */
} UNWIND_CODE_OPS;
// from ehdata.h
#pragma warning (push)
#pragma warning (disable: 4201)
typedef union _UNWIND_CODE {
struct {
unsigned char CodeOffset;
unsigned char UnwindOp : 4;
unsigned char OpInfo : 4;
};
unsigned short FrameOffset;
} UNWIND_CODE, * PUNWIND_CODE;
typedef struct _UNWIND_INFO {
unsigned char Version : 3;
unsigned char Flags : 5;
unsigned char SizeOfProlog;
unsigned char CountOfCodes;
unsigned char FrameRegister : 4;
unsigned char FrameOffset : 4;
UNWIND_CODE UnwindCode[1];
/* UNWIND_CODE MoreUnwindCode[((CountOfCodes+1)&~1)-1];
* union {
* OPTIONAL unsigned long ExceptionHandler;
* OPTIONAL unsigned long FunctionEntry;
* };
* OPTIONAL unsigned long ExceptionData[];
*/
} UNWIND_INFO, * PUNWIND_INFO;
#pragma warning (pop)
constexpr auto W_FPREG = 1; // WARNING - frame pointer in use;
static HRESULT CalculateFrameSize_Internal(const PRUNTIME_FUNCTION pRuntimeFunction, const DWORD64 ImageBase, const DWORD rva, PDWORD pFrameSize)
{
HRESULT status = S_OK;
const auto pUnwindInfo = (PUNWIND_INFO)(pRuntimeFunction->UnwindInfoAddress + ImageBase);
if (!pUnwindInfo || pUnwindInfo->Version > 2) {
LogError("UNWIND_INFO v%d not supported", pUnwindInfo->Version);
return E_NOTIMPL;
}
const auto codeOffset = rva - pRuntimeFunction->BeginAddress;
// Loop over unwind codes and calculate total stack space used by target function.
BYTE i = 0;
while (i < pUnwindInfo->CountOfCodes)
{
// Warning: This implementation is not complete and may not work for some edge cases.
// For example, it does not handle handle RIP pointing into an epilog.
// But that should be rare for our early frames use case.
const auto unwindOperation = pUnwindInfo->UnwindCode[i].UnwindOp;
const auto operationInfo = pUnwindInfo->UnwindCode[i].OpInfo;
const auto bApplyOperation = codeOffset > pUnwindInfo->UnwindCode[i].CodeOffset;
i++;
switch (unwindOperation) {
case UWOP_PUSH_NONVOL:
if (bApplyOperation)
(*pFrameSize)++;
break;
case UWOP_ALLOC_LARGE:
switch (operationInfo) {
case 0:
if (bApplyOperation)
*pFrameSize += pUnwindInfo->UnwindCode[i].FrameOffset;
i++;
break;
case 1:
if (bApplyOperation)
*pFrameSize += *(DWORD*)(&pUnwindInfo->UnwindCode[i]) / sizeof(PVOID);
i += 3;
break;
default:
LogError("UWOP_ALLOC_LARGE operationInfo is invalid: (%d)", operationInfo);
return E_INVALIDARG;
}
break;
case UWOP_ALLOC_SMALL:
if (bApplyOperation)
*pFrameSize += operationInfo + 1;
break;
case UWOP_SET_FPREG:
if (bApplyOperation) {
// Frame pointer in use. Our frame size calculation is inaccurate if alloca() was used.
// https://learn.microsoft.com/en-us/cpp/build/stack-usage
// > If space is dynamically allocated (alloca) in a function, then a
// > nonvolatile register must be used as a frame pointer to mark the
// > base of the fixed part of the stack and that register must be saved
// > and initialized in the prolog.
*pFrameSize = pUnwindInfo->FrameOffset * 2;
status = W_FPREG; // This is a WARNING code - not an ERROR
}
break;
case UWOP_SAVE_NONVOL:
case UWOP_SAVE_XMM128:
case UWOP_EPILOG:
i++;
break;
case UWOP_SAVE_NONVOL_FAR:
case UWOP_SAVE_XMM128_FAR:
i += 2;
break;
case UWOP_PUSH_MACHFRAME:
if (bApplyOperation) {
*pFrameSize += 5 + operationInfo;
}
break;
default:
if (bApplyOperation) {
LogError("UNWIND_INFO operation is not implemented: %d", unwindOperation);
return E_INVALIDARG;
}
}
}
if (0 != (UNW_FLAG_CHAININFO & pUnwindInfo->Flags)) {
const auto pPrimaryUwindInfo = (PRUNTIME_FUNCTION) & (pUnwindInfo->UnwindCode[(pUnwindInfo->CountOfCodes + 1) & ~1]);
return CalculateFrameSize_Internal(pPrimaryUwindInfo, ImageBase, rva, pFrameSize);
}
// Add the size of the return address.
*pFrameSize += 1;
return status;
}
static_assert(!IS_ERROR(W_FPREG));
static_assert(!FAILED(W_FPREG));
// Calculates the total stack space (in PVOIDs) used by the stack frame.
HRESULT CalculateFrameSize(PVOID returnAddress, PDWORD pFrameSize, const std::wstring& filepath, PVOID remoteBase)
{
// Check a cache of UNWIND_INFO lookups first.
static std::unordered_map<PVOID, DWORD> frameSizes;
const auto it = frameSizes.find(returnAddress);
if (it != frameSizes.end()) {
*pFrameSize = it->second;
return S_OK;
}
const DWORD rva = (DWORD)((ULONG_PTR)returnAddress - (ULONG_PTR)remoteBase);
PRUNTIME_FUNCTION pRuntimeFunction = NULL;
DWORD64 localBase;
auto hModule = GetModuleHandleW(filepath.c_str());
if (hModule == remoteBase) {
// Image is already loaded in our process - lock it in memory now.
hModule = LoadLibraryW(filepath.c_str());
if (NULL == hModule) {
LogError("LoadLibrary(%S) failed", filepath.c_str());
return E_UNEXPECTED;
}
pRuntimeFunction = RtlLookupFunctionEntry((DWORD64)returnAddress, &localBase, NULL);
if (NULL == pRuntimeFunction)
return E_FAIL;
}
else {
// Image is not loaded - so load it now.
// We don't want to execute anything malicious in our process - so map it now as a read-only resource.
hModule = LoadLibraryExW(filepath.c_str(), NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE);
if (NULL == hModule) {
LogDebug("LoadLibraryEx(%S, AS_IMAGE_RESOURCE) failed", filepath.c_str());
return E_UNEXPECTED;
}
localBase = (DWORD64)hModule & ~0xFFFF; // The lower bits of resource-only module handles are used as flags.
// Being resource-only means that we can't use the usual APIs.
// Instead, manually walk the UNWIND_INFO in the exception directory of the image.
ULONG indexLo = 0;
ULONG indexHi;
auto pTable = (PRUNTIME_FUNCTION)ImageDirectoryEntryToData((PVOID)localBase, TRUE, IMAGE_DIRECTORY_ENTRY_EXCEPTION, &indexHi);
if (!pTable) {
(void)FreeLibrary(hModule);
const auto error = GetLastError();
if (ERROR_SUCCESS != error)
LogError("ImageDirectoryEntryToData(%S) failed with %d", filepath.c_str(), error);
else
LogDebug("ImageDirectoryEntryToData(%S) - Exception Directory not found");
return E_FAIL;
}
indexHi = indexHi / sizeof(RUNTIME_FUNCTION);
while (indexHi > indexLo) {
const ULONG indexMid = (indexLo + indexHi) / 2;
pRuntimeFunction = &pTable[indexMid];
if (rva < pRuntimeFunction->BeginAddress) {
indexHi = indexMid; // search lower
pRuntimeFunction = NULL;
continue;
}
if (rva >= pRuntimeFunction->EndAddress) {
indexLo = indexMid + 1; // search higher
pRuntimeFunction = NULL;
continue;
}
break; // found
}
}
if (!pRuntimeFunction) {
(void)FreeLibrary(hModule);
return E_FAIL; // leaf functions can't call further functions
}
*pFrameSize = 0;
auto status = CalculateFrameSize_Internal(pRuntimeFunction, localBase, rva, pFrameSize);
if (S_OK == status)
frameSizes.insert(std::make_pair(returnAddress, *pFrameSize));
if (!FreeLibrary(hModule))
LogError("FreeLibrary(%S) failed", filepath.c_str());
return status;
}
BOOL IsValidCallSite(
HANDLE hProcess,
bool bIsWow64,
const MEMORY_BASIC_INFORMATION& mbi,
PVOID callsite,
bool* bCallFound)
{
// Check a cache of valid callsites first
static std::unordered_set<PVOID> validCallSites;
if (validCallSites.contains(callsite)) {
*bCallFound = true;
return TRUE;
}
// Otherwise read the preceding bytes and check for a valid call instruction
std::string precedingBytes{};
precedingBytes.resize(std::min(11ull, (ULONG_PTR)callsite - (ULONG_PTR)mbi.BaseAddress));
if(!ReadProcessMemorySafely(hProcess, (PVOID)((ULONG_PTR)callsite - precedingBytes.size()), precedingBytes, mbi))
return FALSE;
ZydisDecoder decoder;
if (bIsWow64)
(void)ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32);
else
(void)ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
*bCallFound = false;
ZydisDecodedInstruction instruction;
// call instructions are 2-11 bytes, but check some common lengths first
static const std::array<int, 10> callLengthSearchOrder = { 5, 6, 7, 2, 3, 4, 8, 9, 10, 11 };
for (const auto& lengthToCheck : callLengthSearchOrder) {
if (lengthToCheck <= precedingBytes.size() &&
ZYAN_SUCCESS(ZydisDecoderDecodeInstruction(&decoder, nullptr, precedingBytes.data() + precedingBytes.size() - lengthToCheck, lengthToCheck, &instruction)) &&
lengthToCheck == instruction.length &&
ZYDIS_MNEMONIC_CALL == instruction.mnemonic)
{
*bCallFound = true;
// Note - For direct calls, we could (attempt to) improve this check by validating
// that the call target matches the return address.
// Though we would need to handle an Tail Call Optimised (TCO) functions...
//
// In practice this additional validation isn't required for our use case.
// False positivies are rare in early stack frames.
// Cache the result
if (MEM_IMAGE == mbi.Type && InSystemImageRange(callsite))
validCallSites.insert(callsite);
break;
}
}
return TRUE;
}
+23
View File
@@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2014-2021 Florian Bernd
Copyright (c) 2014-2021 Joel Höner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.