From 21c58ee37dabc6770e91eca35e0c3a660bde5ff4 Mon Sep 17 00:00:00 2001 From: RandomCrocodile Date: Sat, 4 Jul 2026 14:28:04 +0200 Subject: [PATCH] fix: strengthen anti-debug startup detection and polling (#76) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 hardening for the anti-debug protection. The core complaint — a debugger (e.g. dnSpy F5) can attach and the app runs before the async worker ever checks — is fixed portably: - Safe and Win32 modes now run a BLOCKING debugger check inside Initialize() (injected at the top of the module cctor), so an assembly launched under a debugger fails immediately, before any user code runs, instead of only being caught later on the background thread. - Win32's startup check also calls the native IsDebuggerPresent(). - Reduced the watchdog poll interval from 1000ms to 200ms and removed the 500ms initial sleep, shrinking the detection race window. Deliberately NOT changing the default mode from Safe to Win32 (issue's proposal 1): Win32 P/Invokes ntdll/kernel32 and would crash cross-platform .NET targets on non-Windows, conflicting with the project's all-frameworks goal. The strengthened Safe mode addresses the reported issue portably; OS-aware mode selection is a larger Phase 2 change. Added AntiDebug.Test (safe + win32) as a regression guard that an anti-debug protected assembly still runs normally when not debugged (START/output/exit 42). --- Confuser.Runtime/AntiDebug.Safe.cs | 9 +++++-- Confuser.Runtime/AntiDebug.Win32.cs | 9 +++++-- Confuser2.sln | 15 +++++++++++ Tests/AntiDebug.Test/AntiDebug.Test.csproj | 13 ++++++++++ Tests/AntiDebug.Test/AntiDebugTest.cs | 29 ++++++++++++++++++++++ 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 Tests/AntiDebug.Test/AntiDebug.Test.csproj create mode 100644 Tests/AntiDebug.Test/AntiDebugTest.cs diff --git a/Confuser.Runtime/AntiDebug.Safe.cs b/Confuser.Runtime/AntiDebug.Safe.cs index fca4253e8..8fbb126d5 100644 --- a/Confuser.Runtime/AntiDebug.Safe.cs +++ b/Confuser.Runtime/AntiDebug.Safe.cs @@ -15,6 +15,12 @@ static void Initialize() { "1".Equals(method.Invoke(null, new object[] { x + "_ENABLE_PROFILING" }))) Environment.FailFast(null); + // Blocking startup check: if a debugger is already attached when the module loads + // (e.g. the assembly was launched under a debugger / dnSpy's F5), fail immediately — + // before any user code runs — rather than only detecting it later on the async worker. + if (Debugger.IsAttached || Debugger.IsLogging()) + Environment.FailFast(null); + var thread = new Thread(Worker); thread.IsBackground = true; thread.Start(null); @@ -25,7 +31,6 @@ static void Worker(object thread) { th = new Thread(Worker); th.IsBackground = true; th.Start(Thread.CurrentThread); - Thread.Sleep(500); } while (true) { if (Debugger.IsAttached || Debugger.IsLogging()) @@ -34,7 +39,7 @@ static void Worker(object thread) { if (!th.IsAlive) Environment.FailFast(null); - Thread.Sleep(1000); + Thread.Sleep(200); } } } diff --git a/Confuser.Runtime/AntiDebug.Win32.cs b/Confuser.Runtime/AntiDebug.Win32.cs index 10801467e..ddb245b6d 100644 --- a/Confuser.Runtime/AntiDebug.Win32.cs +++ b/Confuser.Runtime/AntiDebug.Win32.cs @@ -15,6 +15,12 @@ static void Initialize() { if (here != null && here.ProcessName.IndexOf("dnspy", StringComparison.OrdinalIgnoreCase) >= 0) Environment.FailFast(""); + // Blocking startup check: fail before any user code runs if a debugger is already + // attached when the module loads (managed or native), instead of only catching it + // later on the async worker thread where the app has already started. + if (Debugger.IsAttached || Debugger.IsLogging() || IsDebuggerPresent()) + Environment.FailFast(""); + var thread = new Thread(Worker); thread.IsBackground = true; thread.Start(null); @@ -97,7 +103,6 @@ static void Worker(object thread) { th = new Thread(Worker); th.IsBackground = true; th.Start(Thread.CurrentThread); - Thread.Sleep(500); } while (true) { // Managed @@ -129,7 +134,7 @@ static void Worker(object thread) { if (!th.IsAlive) Environment.FailFast(""); - Thread.Sleep(1000); + Thread.Sleep(200); } } } diff --git a/Confuser2.sln b/Confuser2.sln index b200f0781..c1c56f613 100644 --- a/Confuser2.sln +++ b/Confuser2.sln @@ -205,6 +205,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.WPF.Net8", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CrossFramework.Library.Net10", "Tests\CrossFramework.Library.Net10\CrossFramework.Library.Net10.csproj", "{4458415A-0F5E-4136-B723-7A67955D6047}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntiDebug.Test", "Tests\AntiDebug.Test\AntiDebug.Test.csproj", "{47197200-B8CB-400A-B1BD-84975FEC8C28}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1343,6 +1345,18 @@ Global {4458415A-0F5E-4136-B723-7A67955D6047}.Release|x64.Build.0 = Release|Any CPU {4458415A-0F5E-4136-B723-7A67955D6047}.Release|x86.ActiveCfg = Release|Any CPU {4458415A-0F5E-4136-B723-7A67955D6047}.Release|x86.Build.0 = Release|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Debug|x64.ActiveCfg = Debug|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Debug|x64.Build.0 = Debug|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Debug|x86.ActiveCfg = Debug|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Debug|x86.Build.0 = Debug|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Release|Any CPU.Build.0 = Release|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Release|x64.ActiveCfg = Release|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Release|x64.Build.0 = Release|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Release|x86.ActiveCfg = Release|Any CPU + {47197200-B8CB-400A-B1BD-84975FEC8C28}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1434,6 +1448,7 @@ Global {3942E3FD-06BC-470C-A1ED-BB18F2332B94} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB} {30DC9F52-E08A-4EF0-B041-04AED6135C3D} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB} {4458415A-0F5E-4136-B723-7A67955D6047} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB} + {47197200-B8CB-400A-B1BD-84975FEC8C28} = {356BDB31-853E-43BB-8F9A-D8AC08F69EBB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0D937D9E-E04B-4A68-B639-D4260473A388} diff --git a/Tests/AntiDebug.Test/AntiDebug.Test.csproj b/Tests/AntiDebug.Test/AntiDebug.Test.csproj new file mode 100644 index 000000000..48ef32c65 --- /dev/null +++ b/Tests/AntiDebug.Test/AntiDebug.Test.csproj @@ -0,0 +1,13 @@ + + + + net462 + false + + + + + + + + diff --git a/Tests/AntiDebug.Test/AntiDebugTest.cs b/Tests/AntiDebug.Test/AntiDebugTest.cs new file mode 100644 index 000000000..71bd72d59 --- /dev/null +++ b/Tests/AntiDebug.Test/AntiDebugTest.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Confuser.Core; +using Confuser.Core.Project; +using Confuser.UnitTest; +using Xunit; +using Xunit.Abstractions; + +namespace AntiDebug.Test { + public sealed class AntiDebugTest : TestBase { + public AntiDebugTest(ITestOutputHelper outputHelper) : base(outputHelper) { } + + // Regression guard: anti-debug injects a startup check plus a background watchdog into + // the module cctor. This verifies that an assembly protected with anti-debug still runs + // normally when it is NOT being debugged — i.e. the strengthened checks (blocking startup + // check, faster polling) do not false-positive on a plain process launch. The test runner + // launches the subject as an ordinary child process with no debugger attached, so a healthy + // build must produce START / / END and exit code 42. + [Theory] + [InlineData("safe")] + [InlineData("win32")] + [Trait("Category", "Protection")] + [Trait("Protection", "anti debug")] + public Task ProtectAntiDebugAndExecute(string mode) => + Run("AntiTamper.exe", + new[] { "This is a test." }, + new SettingItem("anti debug") { { "mode", mode } }, + "_antidebug_" + mode); + } +}