Repository: AdamOron/PatchGuardBypass Branch: master Commit: 125aaf881e50 Files: 19 Total size: 46.3 KB Directory structure: gitextract_yx0qudfh/ ├── .gitignore ├── PatchGuardBypass/ │ ├── PatchGuardBypass.inf │ ├── PatchGuardBypass.vcxproj │ ├── PatchGuardBypass.vcxproj.filters │ └── src/ │ ├── core/ │ │ ├── PatchGuard.h │ │ ├── features/ │ │ │ ├── Disable.cpp │ │ │ ├── Evade.cpp │ │ │ └── Verify.cpp │ │ ├── flows/ │ │ │ ├── Flows.cpp │ │ │ └── Flows.h │ │ ├── symbols/ │ │ │ ├── Globals.cpp │ │ │ ├── Globals.h │ │ │ ├── Offsets.cpp │ │ │ └── Offsets.h │ │ └── timers/ │ │ ├── Timer.cpp │ │ └── Timer.h │ └── main.cpp ├── PatchGuardBypass.sln └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## 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 ================================================ FILE: PatchGuardBypass/PatchGuardBypass.inf ================================================ ; ; PatchGuardBypass.inf ; [Version] Signature="$WINDOWS NT$" Class=Sample ; TODO: edit Class ClassGuid={78A1C341-4539-11d3-B88D-00C04FAD5171} ; TODO: edit ClassGuid Provider=%ManufacturerName% CatalogFile=PatchGuardBypass.cat DriverVer= ; TODO: set DriverVer in stampinf property pages PnpLockDown=1 [DestinationDirs] DefaultDestDir = 12 PatchGuardBypass_Device_CoInstaller_CopyFiles = 11 ; ================= Class section ===================== [ClassInstall32] Addreg=SampleClassReg [SampleClassReg] HKR,,,0,%ClassName% HKR,,Icon,,-5 [SourceDisksNames] 1 = %DiskName%,,,"" [SourceDisksFiles] PatchGuardBypass.sys = 1,, WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll=1 ; make sure the number matches with SourceDisksNames ;***************************************** ; Install Section ;***************************************** [Manufacturer] %ManufacturerName%=Standard,NT$ARCH$ [Standard.NT$ARCH$] %PatchGuardBypass.DeviceDesc%=PatchGuardBypass_Device, Root\PatchGuardBypass ; TODO: edit hw-id [PatchGuardBypass_Device.NT] CopyFiles=Drivers_Dir [Drivers_Dir] PatchGuardBypass.sys ;-------------- Service installation [PatchGuardBypass_Device.NT.Services] AddService = PatchGuardBypass,%SPSVCINST_ASSOCSERVICE%, PatchGuardBypass_Service_Inst ; -------------- PatchGuardBypass driver install sections [PatchGuardBypass_Service_Inst] DisplayName = %PatchGuardBypass.SVCDESC% ServiceType = 1 ; SERVICE_KERNEL_DRIVER StartType = 3 ; SERVICE_DEMAND_START ErrorControl = 1 ; SERVICE_ERROR_NORMAL ServiceBinary = %12%\PatchGuardBypass.sys ; ;--- PatchGuardBypass_Device Coinstaller installation ------ ; [PatchGuardBypass_Device.NT.CoInstallers] AddReg=PatchGuardBypass_Device_CoInstaller_AddReg CopyFiles=PatchGuardBypass_Device_CoInstaller_CopyFiles [PatchGuardBypass_Device_CoInstaller_AddReg] HKR,,CoInstallers32,0x00010000, "WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,WdfCoInstaller" [PatchGuardBypass_Device_CoInstaller_CopyFiles] WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll [PatchGuardBypass_Device.NT.Wdf] KmdfService = PatchGuardBypass, PatchGuardBypass_wdfsect [PatchGuardBypass_wdfsect] KmdfLibraryVersion = $KMDFVERSION$ [Strings] SPSVCINST_ASSOCSERVICE= 0x00000002 ManufacturerName="" ;TODO: Replace with your manufacturer name ClassName="Samples" ; TODO: edit ClassName DiskName = "PatchGuardBypass Installation Disk" PatchGuardBypass.DeviceDesc = "PatchGuardBypass Device" PatchGuardBypass.SVCDESC = "PatchGuardBypass Service" ================================================ FILE: PatchGuardBypass/PatchGuardBypass.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 Debug ARM Release ARM Debug ARM64 Release ARM64 {57A68F53-C563-405D-B2E1-C66585DA64E4} {1bc93793-694f-48fe-9372-81e2b05556fd} v4.5 12.0 Debug Win32 PatchGuardBypass Windows10 true WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 false WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 true WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 false WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 true WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 false WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 true WindowsKernelModeDriver10.0 Driver KMDF Universal Windows10 false WindowsKernelModeDriver10.0 Driver KMDF Universal DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger true DbgengKernelDebugger true DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger DbgengKernelDebugger ================================================ FILE: PatchGuardBypass/PatchGuardBypass.vcxproj.filters ================================================  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms {8E41214B-6785-4CFE-B992-037D68949A14} inf;inv;inx;mof;mc; Driver Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Header Files Header Files Header Files Header Files Header Files Header Files ================================================ FILE: PatchGuardBypass/src/core/PatchGuard.h ================================================ #pragma once #include namespace PG { namespace Disable { BOOLEAN Execute( VOID ); VOID Unload( VOID ); }; namespace Evade { BOOLEAN Execute( VOID ); VOID Unload( VOID ); }; namespace Verify { BOOLEAN Execute( VOID ); VOID Unload( VOID ); }; }; ================================================ FILE: PatchGuardBypass/src/core/features/Disable.cpp ================================================ #include "../PatchGuard.h" #include "../timers/Timer.h" #include "../flows/Flows.h" #include "../../utils/Log/Log.h" #include /** For each Timer encountered, checks if it's PG related and removes it if so. Compatible with the TIMER_CALLBACK signature, passed to SearchSystemTimers. */ TIMER_SEARCH_STATUS RemoveTimer( PKTIMER Timer, PKDPC DecodedDpc ) { if (Flows::ContextAwareTimer::IsTargetTimer(Timer, DecodedDpc)) { KeCancelTimer(Timer); Log("Removed Context-Aware Timer/DPC: %p/%p\n", Timer, DecodedDpc); } if (Flows::ContextUnawareTimer::IsTargetTimer(Timer, DecodedDpc)) { KeCancelTimer(Timer); Log("Removed Context-Unaware Timer/DPC: %p/%p\n", Timer, DecodedDpc); } return ContinueTimerSearch; } /** Removes the DPC stored in the Prcb structure, i.e. NULLs it. According to HalpMcaQueueDpc, a NULL DPC does not result in an error. */ BOOLEAN RemovePrcbDpc( VOID ) { PKDPC *PrcbDpc = Flows::PrcbDpc::GetTargetDpc(); if (!PrcbDpc) return FALSE; if (!*PrcbDpc) return TRUE; Log("Removed Prcb DPC: %p\n", *PrcbDpc); *PrcbDpc = NULL; return TRUE; } BOOLEAN PG::Disable::Execute( VOID ) { /* Disable all PG related Timers (prevents ContextAware/Unaware flows) */ SearchSystemTimers(&RemoveTimer); /* Remove DPC from Prcb (prevents Prcb DPC flow) */ if (!RemovePrcbDpc()) return FALSE; return TRUE; } VOID PG::Disable::Unload( VOID ) { /* Nothing to do here */ } ================================================ FILE: PatchGuardBypass/src/core/features/Evade.cpp ================================================ #include "../PatchGuard.h" #include "../timers/Timer.h" #include "../flows/Flows.h" #include "../../utils/Log/Log.h" #include /** Macro to convert milliseconds to hecto-nanoseconds, i.e. 100-nanoseconds. This is the term for Windows interrupt time units. */ #define MS_TO_HNS(x) (x * 10000) /* Initial StopTimer expiry cooldown (DueTime parameter) */ #define STOP_INITIAL_COOLOWN 1000 /* StopTimer cooldown after initial expiry (Period parameter) */ #define STOP_COOLOWN 500 /** Global context for the evasion feature. */ typedef struct _EVADE_CONTEXT { /* Array of PG-related KTIMERs. The array can contain up to 10 entries. I'm using an array for this because I'm not 100% confident that only 2 timers can exist at a time. If that's the case, this design is redundant and will change. */ #define MAX_PG_TIMERS 10 PKTIMER Timers[MAX_PG_TIMERS]; UINT32 TimerCount; /* The expiration time of the Timer we're currently avoiding */ ULONGLONG LastAvoidedExpiration; /* Define a Timer for starting the evasion process */ KDPC StartEvasionDpc; KTIMER StartEvasionTimer; /* Define a Timer for stopping the evasion process */ KDPC StopEvasionDpc; KTIMER StopEvasionTimer; } EVADE_CONTEXT, PEVADE_CONTEXT; EVADE_CONTEXT g_EvadeContext = { 0 }; VOID InsertTimerToContext( PKTIMER Timer ) { if (g_EvadeContext.TimerCount >= MAX_PG_TIMERS) { g_EvadeContext.TimerCount++; return; } g_EvadeContext.Timers[g_EvadeContext.TimerCount++] = Timer; } FORCEINLINE BOOLEAN TimerOverflow( VOID ) { return g_EvadeContext.TimerCount > MAX_PG_TIMERS; } /** For each Timer encountered, checks if it's PG related and saves it if so. Compatible with the TIMER_CALLBACK signature, passed to IterateSystemTimers. */ TIMER_SEARCH_STATUS FindTimer( PKTIMER Timer, PKDPC DecodedDpc ) { if (Flows::ContextAwareTimer::IsTargetTimer(Timer, DecodedDpc)) { InsertTimerToContext(Timer); } if (Flows::ContextUnawareTimer::IsTargetTimer(Timer, DecodedDpc)) { InsertTimerToContext(Timer); } return ContinueTimerSearch; } BOOLEAN UpdateTimers( VOID ) { /* Reset all previously found Timers */ g_EvadeContext.TimerCount = 0; /* Find all PG related Timers */ SearchSystemTimers(&FindTimer); if (TimerOverflow()) return FALSE; return TRUE; } ULONGLONG EarliestTimerExpiration( VOID ) { ULONGLONG earliestTime = MAXLONGLONG; for (UINT32 i = 0; i < g_EvadeContext.TimerCount; i++) { PKTIMER currentTimer = g_EvadeContext.Timers[i]; if (!currentTimer) continue; ULONGLONG currentTime = currentTimer->DueTime.QuadPart; if (currentTimer->Period) { /* BP if Timer is periodical. Need to figure out how to calculate the DueTime */ DbgBreakPoint(); } if (currentTime < earliestTime) earliestTime = currentTime; } return earliestTime; } /* Start hiding a bit earlier than the Timers expire */ #define EVASION_TIMER_UNDERSHOOT 500 BOOLEAN SetStopEvasionTimer( VOID ); VOID StartEvasion( PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2 ) { UNREFERENCED_PARAMETER(Dpc); UNREFERENCED_PARAMETER(DeferredContext); UNREFERENCED_PARAMETER(SystemArgument1); UNREFERENCED_PARAMETER(SystemArgument2); // DisableAllPatches(); SetStopEvasionTimer(); } #define AVOIDING_TIMER ((PVOID) 0x1) #define AVOIDING_DPC ((PVOID) 0x0) BOOLEAN SetStartEvasionTimer( BOOLEAN RequiresUpdate ) { /* If we require an update and the update failed */ if (RequiresUpdate && !UpdateTimers()) return FALSE; ULONGLONG timerExpiration = EarliestTimerExpiration(); ULONG dpcExecution = Flows::PrcbDpc::NextExecutionTime(); if (timerExpiration < dpcExecution) { g_EvadeContext.LastAvoidedExpiration = timerExpiration; /* Indicate to the DPC that we're avoiding a Timer, and not the Prcb DPC */ g_EvadeContext.StartEvasionDpc.SystemArgument1 = AVOIDING_TIMER; } else { g_EvadeContext.LastAvoidedExpiration = dpcExecution; /* Indicate to the DPC that we're avoiding the Prcb DPC, and not a Timer */ g_EvadeContext.StartEvasionDpc.SystemArgument1 = AVOIDING_DPC; } LARGE_INTEGER startEvasionTime; startEvasionTime.QuadPart = g_EvadeContext.LastAvoidedExpiration - EVASION_TIMER_UNDERSHOOT; if (KeSetTimer( &g_EvadeContext.StartEvasionTimer, startEvasionTime, &g_EvadeContext.StartEvasionDpc )) return FALSE; return TRUE; } VOID StopEvasion( VOID ) { // EnableAllPatches(); SetStartEvasionTimer(FALSE); } VOID TryStopEvasion( PKDPC Dpc, PVOID DeferredContext, PVOID IsAvoidingTimer, PVOID pvAttemptCount ) { UNREFERENCED_PARAMETER(Dpc); UNREFERENCED_PARAMETER(DeferredContext); /* Convert from PVOID to UINT64 */ UINT64 attemptCount = (UINT64) pvAttemptCount; if (attemptCount >= 5) { /* BP if we've attempted & failed more than 5 times. TODO: Better error handling (log error to user, exit program?) */ DbgBreakPoint(); goto Exit; } /* Increment attempt count. Why am I using global variables smh */ g_EvadeContext.StopEvasionDpc.SystemArgument2 = (PVOID) (attemptCount + 1); /* Timer count before evasion started */ UINT32 prevTimerCount = g_EvadeContext.TimerCount; /* Search again for all PG related Timers */ if (!UpdateTimers()) return; /* If we're avoiding a Timer, ensure it was actually removed (it's no longer the earliest) */ if (IsAvoidingTimer == AVOIDING_TIMER && g_EvadeContext.LastAvoidedExpiration == EarliestTimerExpiration()) { /* TODO: Maybe do other stuff, we can sleep longer or something */ return; } /* Check that a new replacement Timer was inserted, i.e. execution finished */ if (prevTimerCount == g_EvadeContext.TimerCount) /* && CurrentTime < GetEarliestTimer(), make sure next Timer isn't already executing? not sure if necessary */ { StopEvasion(); Exit: /* If we finished avoiding, cancel the Timer */ KeCancelTimer(&g_EvadeContext.StopEvasionTimer); } } BOOLEAN SetStopEvasionTimer( VOID ) { LARGE_INTEGER dueTime; dueTime.QuadPart = /* CurrentTime + */ MS_TO_HNS(STOP_INITIAL_COOLOWN); /* Notify StopEvasionDpc whether we're avoiding a Timer or the Prcb DPC */ g_EvadeContext.StopEvasionDpc.SystemArgument1 = g_EvadeContext.StartEvasionDpc.SystemArgument1; /* Initialize attempt count to zero */ g_EvadeContext.StartEvasionDpc.SystemArgument2 = 0; if (KeSetTimerEx( &g_EvadeContext.StopEvasionTimer, dueTime, STOP_COOLOWN, &g_EvadeContext.StopEvasionDpc )) return FALSE; return TRUE; } BOOLEAN PrepareContext( VOID ) { KeInitializeDpc( &g_EvadeContext.StartEvasionDpc, StartEvasion, NULL ); KeInitializeTimerEx( &g_EvadeContext.StartEvasionTimer, NotificationTimer ); KeInitializeDpc( &g_EvadeContext.StopEvasionDpc, TryStopEvasion, NULL ); KeInitializeTimerEx( &g_EvadeContext.StopEvasionTimer, NotificationTimer ); return TRUE; } BOOLEAN PG::Evade::Execute( VOID ) { if (!PrepareContext()) return FALSE; if (!SetStartEvasionTimer(TRUE)) return FALSE; return TRUE; } VOID PG::Evade::Unload( VOID ) { /* Cancel current Timers */ KeCancelTimer(&g_EvadeContext.StartEvasionTimer); KeCancelTimer(&g_EvadeContext.StopEvasionTimer); } ================================================ FILE: PatchGuardBypass/src/core/features/Verify.cpp ================================================ #include "../PatchGuard.h" #include "../timers/Timer.h" #include "../flows/Flows.h" #include "../../utils/Log/Log.h" #include typedef struct _VERIFY_CONTEXT { BOOLEAN bContextAwareTimer; BOOLEAN bContextUnawareTimer; BOOLEAN bPrcbDpc; } VERIFY_CONTEXT, *PVERIFY_CONTEXT; VERIFY_CONTEXT g_VerifyContext = { FALSE }; /** For each Timer encountered, checks if it's PG related and removes it if so. Compatible with the TIMER_CALLBACK signature, passed to IterateSystemTimers. */ TIMER_SEARCH_STATUS VerifyTimer( PKTIMER Timer, PKDPC DecodedDpc ) { if (Flows::ContextAwareTimer::IsTargetTimer(Timer, DecodedDpc)) { g_VerifyContext.bContextAwareTimer = TRUE; } if (Flows::ContextUnawareTimer::IsTargetTimer(Timer, DecodedDpc)) { g_VerifyContext.bContextUnawareTimer = TRUE; } return ContinueTimerSearch; } BOOLEAN VerifyPrcbDpc( VOID ) { PKDPC *PrcbDpc = Flows::PrcbDpc::GetTargetDpc(); if (!PrcbDpc) return FALSE; if (!*PrcbDpc) return FALSE; return TRUE; } BOOLEAN PG::Verify::Execute( VOID ) { /* Verify all PG related Timers exist */ SearchSystemTimers(&VerifyTimer); /* TODO: Not loving this global context variable */ g_VerifyContext.bPrcbDpc = VerifyPrcbDpc(); return g_VerifyContext.bContextAwareTimer && g_VerifyContext.bContextUnawareTimer && g_VerifyContext.bPrcbDpc; } VOID PG::Verify::Unload( VOID ) { /* Nothing to do here */ } ================================================ FILE: PatchGuardBypass/src/core/flows/Flows.cpp ================================================ #include "Flows.h" #include #include "../symbols/Globals.h" #include "../symbols/Offsets.h" BOOLEAN Flows::ContextAwareTimer::IsTargetTimer( PKTIMER Timer, PKDPC DecodedDpc ) { UNREFERENCED_PARAMETER(Timer); if (!MmIsAddressValid(DecodedDpc)) return FALSE; INT64 SpecialBit = (INT64) DecodedDpc->DeferredContext >> 47; return SpecialBit != 0 && SpecialBit != -1; } BOOLEAN Flows::ContextUnawareTimer::IsTargetTimer( PKTIMER Timer, PKDPC DecodedDpc ) { UNREFERENCED_PARAMETER(Timer); if (!MmIsAddressValid(DecodedDpc)) return FALSE; return DecodedDpc->DeferredRoutine == Globals::Functions::CcBcbProfiler; } ULONG Flows::PrcbDpc::NextExecutionTime( VOID ) { return *reinterpret_cast((PBYTE) *Globals::Variables::HalpClockTimer + Offsets::HalpClockTimer::NextExecutionTime); } PKDPC * Flows::PrcbDpc::GetTargetDpc( VOID ) { PKPRCB Prcb = Globals::Functions::KeGetPrcb(0); if (!Prcb) return NULL; return reinterpret_cast((PBYTE) Prcb + Offsets::Prcb::PatchGuardDpc); } ================================================ FILE: PatchGuardBypass/src/core/flows/Flows.h ================================================ #pragma once #include /* Forward declaration of KTIMER struct */ typedef struct _KTIMER KTIMER, *PKTIMER; /* Forward declaration of KDPC struct */ typedef struct _KDPC KDPC, *PKDPC; /** Contains sub-namespaces defining each execution flow for PatchGuard checks. */ namespace Flows { /** PatchGuard issues a check through a Timer inserted to Prcb index 0. This Timer receives the PatchGuard Context struct as a parameter and uses it. */ namespace ContextAwareTimer { BOOLEAN IsTargetTimer( PKTIMER Timer, PKDPC DecodedDpc ); }; /** PatchGuard issues a check through a Timer inserted to Prcb index 0. This Timer does not receive the PatchGuard Context struct as a parameter. It is more static & less complex than the context aware Timer. */ namespace ContextUnawareTimer { BOOLEAN IsTargetTimer( PKTIMER Timer, PKDPC DecodedDpc ); }; /** PatchGuad issues a check through a DPC saved to Prcb index 0. This DPC is inserted to the HalReserved[7] field from FsRtlMdlReadCompleteDevEx. It is executed every 2 minutes from HalpMcaQueueDpc. */ namespace PrcbDpc { /** @return The next execution time of the DPC stored in the Prcb. This value is returned as an interrupt timestamp. */ ULONG NextExecutionTime( VOID ); /** @return Pointer to the target DPC stored in the Prcb. This returns a pointer to allow easily overwriting the DPC. */ PKDPC * GetTargetDpc( VOID ); }; }; ================================================ FILE: PatchGuardBypass/src/core/symbols/Globals.cpp ================================================ #include "Globals.h" /* Just grouping together all offsets into a single namespace. Not necessary, but more convenient for modifying and hiding the vars from global scope. */ namespace GlobalOffsets { /* Keys used for decoding/encoding DPCs within KTIMERs */ constexpr UINT64 KiWaitAlways = 0xCFC9F8; constexpr UINT64 KiWaitNever = 0xCFC7F8; /* Number of processor on the system. Also indicates number of PRCBs */ constexpr UINT64 KeNumberProcessors_0 = 0xCFC404; constexpr UINT64 HalpClockTimer = 0xC4BFC8; /* Offset of some known routine that we can use to calculate ImageBase */ constexpr UINT64 KeBugCheckEx = 0x3f5210; /* Undocumented function that returns the Prcb at the given index */ constexpr UINT64 KeGetPrcb = 0x32DAD0; constexpr UINT64 CcBcbProfiler = 0x3D7330; }; /** Template function to return a Global variable. @param Type is the type of the variable. @param GlobalOffset is the offset of the variable from the image base. */ template Type GetGlobal(UINT64 ImageBase) { return reinterpret_cast(ImageBase + GlobalOffset); } /** Initializes all Globals. */ BOOLEAN Globals::Initialize( VOID ) { /* Not sure if this is ok or completely mentally ill */ UINT64 ImageBase = (UINT64) &KeBugCheckEx - GlobalOffsets::KeBugCheckEx; Globals::Variables::KiWaitAlways = GetGlobal(ImageBase); Globals::Variables::KiWaitNever = GetGlobal(ImageBase); Globals::Variables::KeNumberProcessors_0 = GetGlobal(ImageBase); Globals::Variables::HalpClockTimer = GetGlobal(ImageBase); Globals::Functions::KeGetPrcb = GetGlobal(ImageBase); Globals::Functions::CcBcbProfiler = GetGlobal(ImageBase); return TRUE; } /* Define all externs */ PINT64 Globals::Variables::KiWaitAlways = NULL; PINT64 Globals::Variables::KiWaitNever = NULL; PULONG Globals::Variables::KeNumberProcessors_0 = NULL; PVOID *Globals::Variables::HalpClockTimer = NULL; PKPRCB (__fastcall *Globals::Functions::KeGetPrcb) (ULONG) = NULL; PVOID Globals::Functions::CcBcbProfiler = NULL; ================================================ FILE: PatchGuardBypass/src/core/symbols/Globals.h ================================================ #pragma once #include /* The Kernel KPRCB structure is undocumented, so we'll treat it as a void-pointer */ typedef PVOID PKPRCB; namespace Globals { namespace Variables { extern PINT64 KiWaitAlways; extern PINT64 KiWaitNever; extern PULONG KeNumberProcessors_0; extern PVOID *HalpClockTimer; }; namespace Functions { /* Function pointer to KeGetPrcb, which is undocumented */ extern PKPRCB (__fastcall *KeGetPrcb) (ULONG PrcbNumber); /* Invoked by the Context-Unaware Timers */ extern PVOID CcBcbProfiler; }; BOOLEAN Initialize( VOID ); }; ================================================ FILE: PatchGuardBypass/src/core/symbols/Offsets.cpp ================================================ #include "Offsets.h" UINT32 Offsets::Prcb::TimerTable = 0x3940; UINT32 Offsets::Prcb::PatchGuardDpc = 0x80; UINT32 Offsets::HalpClockTimer::NextExecutionTime = 0x3C; ================================================ FILE: PatchGuardBypass/src/core/symbols/Offsets.h ================================================ #pragma once #include namespace Offsets { /* Offsets to fields within a Prcb (struct _KPRCB) */ namespace Prcb { /* Offset to TimerTable field */ extern UINT32 TimerTable; extern UINT32 PatchGuardDpc; }; namespace HalpClockTimer { extern UINT32 NextExecutionTime; }; }; ================================================ FILE: PatchGuardBypass/src/core/timers/Timer.cpp ================================================ #include "Timer.h" #include #include #include "../symbols/Offsets.h" #include "../symbols/Globals.h" /** * Kernel structure defining an entry within a KTIMER_TABLE. * Each entry contains a linked-list of KTIMERs. */ typedef struct _KTIMER_TABLE_ENTRY { /* Locked used to synchronize access to the entry */ unsigned __int64 Lock; /* Head of a linked-list of KTIMERs */ LIST_ENTRY Entry; /* Earliest expiration time within this entry's Timers (used in insertion process, iirc) */ ULARGE_INTEGER Time; } KTIMER_TABLE_ENTRY, *PKTIMER_TABLE_ENTRY; /* Size of the TimerExpiry array */ #define TIMER_EXPIRY_SIZE 64 /* Size of the TimerEntries array */ #define TIMER_ENTRIES_SIZE 256 typedef struct _KTIMER_TABLE { /* An array of KTIMER pointers, representing all expired Timers that need processing */ PKTIMER TimerExpiry[TIMER_EXPIRY_SIZE]; /* A 2-dimensional array of KTIMER_TABLE_ENTRIES, containing all user-mode & kernel-mode (I think?) non-expired Timers. TimerEntries[0] is for kernel-mode, TimerEntries[1] for user-mode. */ KTIMER_TABLE_ENTRY TimerEntries[2][TIMER_ENTRIES_SIZE]; /* TimerState structure (size=0x18), we do not care for this */ char TableState[0x18]; } KTIMER_TABLE, *PKTIMER_TABLE; /* Retrieves the TimerTable field of a KPRCB */ PKTIMER_TABLE GetTimerTable( PKPRCB Prcb ) { return (PKTIMER_TABLE) ((PCHAR) Prcb + Offsets::Prcb::TimerTable); } /* Returns the decoded pointer to the DPC stored in a KTIMER structre. These DPC pointers are encoded during insertion of the Timers. The encoding/decoding can be seen in KTIMER-related functions in ntoskrnl.exe. */ #define DECODE_TIMER_DPC(Timer) \ (PKDPC) (*Globals::Variables::KiWaitAlways ^ _byteswap_uint64( \ (UINT64) Timer ^ _rotl64( \ (INT64) Timer->Dpc ^ *Globals::Variables::KiWaitNever, \ (UCHAR) *Globals::Variables::KiWaitNever \ ))) /** Iterates over all entries of a linked-list of KTIMERs. For each KTIMER, the given callback function is invoked. @param TimerListHead is the head entry of the list, from a KTIMER_TABLE_ENTRY structure. @param TimerCallbacks is a fixed array of TIMER_CALLBACK routines, invoked for each KTIMER. @return search instructions for the caller. */ TIMER_SEARCH_STATUS SearchTimerList( PKTIMER_TABLE_ENTRY TimerTableEntry, PTIMER_CALLBACK TimerCallback ) { /* TODO: Acquire & Release SpinLock of TimerTableEntry. */ PLIST_ENTRY pListEntry = TimerTableEntry->Entry.Flink; /* As long as the current entry is valid and we haven't reached the end */ while (pListEntry && pListEntry != &TimerTableEntry->Entry) { /* Get the KTIMER that contains the current list entry */ PKTIMER pTimer = CONTAINING_RECORD( pListEntry, KTIMER, TimerListEntry ); /* Get the decoded DPC */ PKDPC pDpc = DECODE_TIMER_DPC(pTimer); /* Invoke registered callback */ if (TimerCallback(pTimer, pDpc) == StopTimerSearch) return StopTimerSearch; /* Advance to the next Timer entry */ pListEntry = pListEntry->Flink; } return ContinueTimerSearch; } /** Iterates all pending KTIMERs in the given KTIMER_TABLE. @param TimerTable is the KTIMER_TABLE we want to iterate. @param TimerCallbacks is a fixed array of TIMER_CALLBACK routines, invoked for each KTIMER. */ VOID SearchTimerTable( PKTIMER_TABLE TimerTable, PTIMER_CALLBACK TimerCallback ) { /* Get the array of kernel-mode KTIMER_TABLE_ENTRYs */ PKTIMER_TABLE_ENTRY pKernelEntries = TimerTable->TimerEntries[KernelMode]; /* Get the array of user-mode KTIMER_TABLE_ENTRYs */ PKTIMER_TABLE_ENTRY pUserEntries = TimerTable->TimerEntries[UserMode]; /* Iterate over all KTIMER_TABLE_ENTRYs in both arrays */ for (USHORT i = 0; i < TIMER_ENTRIES_SIZE; i++) { /* Iterate linked-list of KTIMERs within each KTIMER_TABLE_ENTRY */ if (SearchTimerList(&pKernelEntries[i], TimerCallback) == StopTimerSearch) break; if (SearchTimerList(&pUserEntries[i], TimerCallback) == StopTimerSearch) break; } } /** Iterates over all Timers in the system, invokes the given callbacks for each Timer. @param TimerCallbacks is the fixed array of callbacks to be invoked. */ BOOLEAN SearchSystemTimers( PTIMER_CALLBACK TimerCallback ) { /* Get the matching KPRCB struct (current code is wrong, just a placeholder) */ PKPRCB pPrcb = Globals::Functions::KeGetPrcb(0); if (!pPrcb) return FALSE; /* Get the KPRCB's TimerTable, then iterate its Timers */ SearchTimerTable(GetTimerTable(pPrcb), TimerCallback); return TRUE; } ================================================ FILE: PatchGuardBypass/src/core/timers/Timer.h ================================================ #pragma once #include /* Forward declaration of KTIMER struct */ typedef struct _KTIMER KTIMER, *PKTIMER; /* Forward declaration of KDPC struct */ typedef struct _KDPC KDPC, *PKDPC; /** Enum returned by TIMER_CALLBACK to indicate how the search should continue. */ typedef enum _TIMER_SEARCH_STATUS { StopTimerSearch, ContinueTimerSearch, } TIMER_SEARCH_STATUS, *PTIMER_SEARCH_STATUS; /** Signature of a callback routine for each KTIMER encountered. @param Timer is the KTIMER encountered. @param DecodedDpc is the decoded DPC used by the Timer. @return TRUE if execution should continue, FASLE if it should stop. */ typedef TIMER_SEARCH_STATUS TIMER_CALLBACK( PKTIMER Timer, PKDPC DecodedDpc ); typedef TIMER_CALLBACK *PTIMER_CALLBACK; /** Iterates over all Timers in the system, invokes the given callbacks for each Timer. */ BOOLEAN SearchSystemTimers( PTIMER_CALLBACK TimerCallback ); ================================================ FILE: PatchGuardBypass/src/main.cpp ================================================ #pragma once #include #include #include "utils/log/Log.h" #include "core/PatchGuard.h" VOID DriverUnload( _In_ PDRIVER_OBJECT pDriverObject ) { UNREFERENCED_PARAMETER(pDriverObject); } #include "core/symbols/Globals.h" EXTERN_C NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { UNREFERENCED_PARAMETER(DriverObject); UNREFERENCED_PARAMETER(RegistryPath); DriverObject->DriverUnload = DriverUnload; Globals::Initialize(); PG::Disable::Execute(); Log("%p\n", Globals::Functions::CcBcbProfiler); return STATUS_SUCCESS; } ================================================ FILE: PatchGuardBypass.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.32802.440 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PatchGuardBypass", "PatchGuardBypass\PatchGuardBypass.vcxproj", "{57A68F53-C563-405D-B2E1-C66585DA64E4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|ARM = Release|ARM Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|ARM.ActiveCfg = Debug|ARM {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|ARM.Build.0 = Debug|ARM {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|ARM.Deploy.0 = Debug|ARM {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|ARM64.ActiveCfg = Debug|ARM64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|ARM64.Build.0 = Debug|ARM64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|ARM64.Deploy.0 = Debug|ARM64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|x64.ActiveCfg = Debug|x64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|x64.Build.0 = Debug|x64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|x64.Deploy.0 = Debug|x64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|x86.ActiveCfg = Debug|Win32 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|x86.Build.0 = Debug|Win32 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Debug|x86.Deploy.0 = Debug|Win32 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|ARM.ActiveCfg = Release|ARM {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|ARM.Build.0 = Release|ARM {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|ARM.Deploy.0 = Release|ARM {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|ARM64.ActiveCfg = Release|ARM64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|ARM64.Build.0 = Release|ARM64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|ARM64.Deploy.0 = Release|ARM64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|x64.ActiveCfg = Release|x64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|x64.Build.0 = Release|x64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|x64.Deploy.0 = Release|x64 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|x86.ActiveCfg = Release|Win32 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|x86.Build.0 = Release|Win32 {57A68F53-C563-405D-B2E1-C66585DA64E4}.Release|x86.Deploy.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8AD89A08-9F84-4021-B1FA-0CA109C2D418} EndGlobalSection EndGlobal ================================================ FILE: README.md ================================================ # **PatchGuardBypass** I've had the delightful opportunity to research PatchGuard for the past couple of weeks, and it was mostly pretty fun. I'll be writing a paper about my experience and my findings, hopefully it could help anyone else who's hesitant to do something like this :) In the meantime, I'll also be writing a dynamic PatchGuard bypass for modern Windows 10 systems. **This is still a bit far from done, so please don't expect anything to work at this stage.** Hopefully when it is finished it'll include 3 main features: ### **Disable** Disables PatchGuard completely and prevents its execution. ### **Evade** Evades PatchGuard detection by reverting patches prior to the PG check times. ### **Verify** Checks if PatchGuard has been disabled on the system. Basically the opposite of Disabling. **DISCLAMER: The feature names are work-in-progress, my sincerest apologies for the poor choice <3**