feat(launcher): clear the ASAR fuse from a debugger instead of a proxy DLL

Launch Wand with DEBUG_PROCESS and patch the integrity fuse byte in every
process Electron spawns, then detach once the startup burst settles. Electron
respawns children from its own on-disk exe, so patching only the main process
left renderers crashing with -36861.

Remove the version.dll proxy project and its CMake build step; the launcher no
longer ships a native helper. Update the README to describe the debugger-based
mechanism and drop CMake from the build requirements.

Time the detach with Stopwatch instead of Environment.TickCount, which wraps.
Check the PatchFuse result and surface a failure to the startup log.
Scan for the fuse sentinel byte by byte rather than assuming 8-byte alignment.
Close the image handle the kernel hands over with each process event.
Name the DEBUG_EVENT and fuse wire offsets.
Re-quote forwarded argv so Squirrel paths containing spaces survive.
Keep an unobserved task exception from terminating the process.
This commit is contained in:
kitbyte
2026-08-29 16:56:25 +03:00
parent 6716da5c80
commit 572b61ac25
10 changed files with 426 additions and 565 deletions
+4 -9
View File
@@ -15,7 +15,7 @@ There are no official videos showing how to install or use this tool. Scammers a
## 👾 What does it access?
The .NET patcher modifies files in the selected local Wand installation and does not contact an update or telemetry service. The bundled `version.dll` proxy is loaded by Wand and changes Electron's ASAR-integrity fuse byte inside Wand's own process; it does not inject into another process. Wand itself remains an online application, build tools restore declared dependencies, and the optional Remote Web Panel deliberately starts a LAN HTTP/WebSocket server and uses Wand API/CDN data. Review the source and build the executable from your own fork; unsigned patching tools can trigger generic antivirus heuristics.
The .NET patcher modifies files in the selected local Wand installation and does not contact an update or telemetry service. Wand itself remains an online application, build tools restore declared dependencies, and the optional Remote Web Panel deliberately starts a LAN HTTP/WebSocket server and uses Wand API/CDN data. Review the source and build the executable from your own fork; unsigned patching tools can trigger generic antivirus heuristics.
## 💫 What features are improved?
@@ -102,19 +102,17 @@ Building from source on Windows requires a local development environment.
### Requirements
- `CMake`
- `Node.js` and `pnpm`
- `Visual Studio 2022` or `Build Tools for Visual Studio 2022` with `MSBuild`
- Visual Studio `Desktop development with C++` workload
- .NET Framework 4.8 desktop build tools / targeting pack
### Build steps
1. Clone this repository.
2. Install the requirements above and make sure `cmake`, `pnpm`, and `MSBuild` are available.
2. Install the requirements above and make sure `pnpm` and `MSBuild` are available.
3. Run `build.cmd` from Command Prompt or PowerShell.
The build script installs the web panel dependencies, builds the frontend, compiles the native helper with CMake, restores NuGet packages, and builds the WPF solution.
The build script installs the web panel dependencies, type-checks and lints the panel, builds the frontend and bridge, restores NuGet packages, and builds the WPF solution.
---
@@ -141,12 +139,11 @@ The build script installs the web panel dependencies, builds the frontend, compi
![2](./assets/screenshots/app2.png)
</div>
---
## 📜 License
This project is licensed under the Apache-2.0 - see the [LICENSE](LICENSE.md) file for details.
---
## ❤️ Support
If you find this project useful, you can support its development using any of the options below 🙌
@@ -163,5 +160,3 @@ If you find this project useful, you can support its development using any of th
> This project is a third-party enhancement tool intended solely for educational, research, and local interoperability purposes. It does not distribute any proprietary code or bypass server-side validations. All modifications are performed locally to customize the user's interface.
---
[![Star History Chart](https://api.star-history.com/svg?repos=k1tbyte/Wand-Enhancer&type=Date)](https://www.star-history.com/#k1tbyte/Wand-Enhancer&Date)
+4 -26
View File
@@ -1,6 +1,5 @@
using System;
using System;
using System.Reflection;
using WandEnhancer.Models;
namespace WandEnhancer
{
@@ -8,36 +7,15 @@ namespace WandEnhancer
{
public const string RepoName = "Wand-Enhancer";
public const string Owner = "k1tbyte";
/*public const string PatchRegistryName = "patchRegistry.json";*/
public static readonly string RepositoryUrl = $"https://github.com/{Owner}/{RepoName}";
public static readonly Version Version;
public static readonly string[] WeModBrandNames = { "Wand", "WeMod" };
public const string AppSettingsFileName = "appsettings.json";
public const string ProxyDllResouceName = "proxydll";
public const string AutoPatchConfigFileName = "enhancer.json";
// cmp dword ptr [rdx], 0
// jnz loc_XXXXXXXX
// mov rsi, rdx
/*public static Signature ExePatchSignature = new Signature(
"83 3A 00 0F ?? ?? 01 00 00 48 89 D6 48 B8",
4,
new byte[]{ 0x84, 0x17 },
new byte[]{ 0x85, 0x22 }
);*/
/*// ...
// test eax, eax (0x85 for r/m16/32/64)
// jnz short loc_1403A4DD2 (Integrity check failed)
// call near ptr funk_1445527E0
// ...
private const string PatchSignature = "E8 ?? ?? ?? ?? ?? C0 75 ?? F6 C3 01 74 ?? 48 89 F9 E8 ?? ?? ?? ??";
private static readonly byte[] PatchBytes = { 0x31 };
private const int PatchOffset = 0x5;*/
static Constants()
static Constants()
{
Version = Assembly.GetExecutingAssembly().GetName().Version;
}
}
}
}
+291
View File
@@ -0,0 +1,291 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace WandEnhancer.Core
{
/// <summary>
/// Launches Electron under a startup-only debugger and clears the ASAR integrity
/// fuse in every process it spawns (main, renderer, gpu, utility). Electron respawns
/// children from its own on-disk exe where the fuse is still enabled, so patching only
/// the main process leaves renderers crashing with -36861. The debugger stops each child
/// at creation, so there is no race, and memory patching is immune to Chromium's sandbox
/// DLL-signature mitigations. We detach once the window is up - long before any game
/// launch - so game anti-debug/DRM is never exposed to a debugger.
/// </summary>
internal static class FuseLauncher
{
private const int FuseAsarIntegrity = 4;
private const byte FuseStateRemoved = (byte)'r';
private const int SentinelLength = 32;
private const int ScanChunkSize = 0x100000;
// Electron's fuse wire follows the sentinel: [version][fuseCount][state per fuse].
private const int FuseWireVersionOffset = 0;
private const int FuseWireCountOffset = 1;
private const int FuseWireStatesOffset = 2;
private const byte FuseWireSupportedVersion = 1;
private const int FuseWireMinCount = 5;
// Longest tail read past a sentinel hit: version + count + the fuse we edit.
private const int FuseWireTailBytes = FuseWireStatesOffset + FuseAsarIntegrity + 1;
// x64 DEBUG_EVENT: dwDebugEventCode, dwProcessId, dwThreadId, 4 bytes padding,
// then the union. CREATE_PROCESS_DEBUG_INFO starts with hFile, hProcess, hThread,
// lpBaseOfImage; EXCEPTION_DEBUG_INFO starts with the exception code.
private const int DebugEventSize = 192;
private const int OffsetDebugEventCode = 0;
private const int OffsetProcessId = 4;
private const int OffsetThreadId = 8;
private const int OffsetUnion = 16;
private const int OffsetExceptionCode = OffsetUnion;
private const int OffsetCreateProcessFile = OffsetUnion;
private const int OffsetCreateProcessHandle = OffsetUnion + 8;
private const int OffsetCreateProcessImageBase = OffsetUnion + 24;
// Detach after the startup process burst settles (all children spawned and patched),
// capped hard so we never linger into gameplay.
private const long MinDebugMs = 3000;
private const long QuietMs = 1500;
private const long MaxDebugMs = 9000;
private static readonly byte[] Sentinel =
Encoding.ASCII.GetBytes("dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX");
public static bool Launch(string exePath, string args, Action<string> log = null)
{
var si = new STARTUPINFO { cb = Marshal.SizeOf<STARTUPINFO>() };
var cmdLine = new StringBuilder(
string.IsNullOrEmpty(args) ? $"\"{exePath}\"" : $"\"{exePath}\" {args}");
if (!CreateProcessW(null, cmdLine, IntPtr.Zero, IntPtr.Zero,
false, DEBUG_PROCESS, IntPtr.Zero,
Path.GetDirectoryName(exePath), ref si, out var pi))
{
log?.Invoke($"Could not start Wand under the fuse patcher (win32 error {Marshal.GetLastWin32Error()}).");
return false;
}
// Debugged processes must survive after we detach and exit.
DebugSetProcessKillOnExit(false);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
DrivePatchingDebugLoop(pi.dwProcessId, log);
return true;
}
private static void DrivePatchingDebugLoop(int mainPid, Action<string> log)
{
var pids = new List<int>();
var brokeIn = new HashSet<int>();
var evt = new byte[DebugEventSize];
// Stopwatch, not TickCount: TickCount is a 32-bit millisecond counter that wraps
// every ~25 days, and a negative elapsed would keep the debugger attached forever.
var clock = Stopwatch.StartNew();
long lastCreate = 0;
while (true)
{
long now = clock.ElapsedMilliseconds;
if (!WaitForDebugEvent(evt, 200))
{
if (ShouldDetach(now, now - lastCreate)) break;
continue;
}
int code = BitConverter.ToInt32(evt, OffsetDebugEventCode);
int pid = BitConverter.ToInt32(evt, OffsetProcessId);
int tid = BitConverter.ToInt32(evt, OffsetThreadId);
uint status = DBG_CONTINUE;
switch (code)
{
case CREATE_PROCESS_DEBUG_EVENT:
var hFile = (IntPtr)BitConverter.ToInt64(evt, OffsetCreateProcessFile);
var hProc = (IntPtr)BitConverter.ToInt64(evt, OffsetCreateProcessHandle);
var baseImg = (IntPtr)BitConverter.ToInt64(evt, OffsetCreateProcessImageBase);
if (!pids.Contains(pid)) pids.Add(pid);
if (!PatchFuse(hProc, baseImg))
log?.Invoke($"Fuse not cleared in pid {pid}; renderers may fail with -36861.");
// The debugger owns the image handle the kernel hands over with this event.
if (hFile != IntPtr.Zero) CloseHandle(hFile);
lastCreate = now;
break;
case EXCEPTION_DEBUG_EVENT:
int exCode = BitConverter.ToInt32(evt, OffsetExceptionCode);
// Pass the one-shot startup breakpoint, let the app own the rest.
status = (exCode == EXCEPTION_BREAKPOINT && brokeIn.Add(pid))
? DBG_CONTINUE
: DBG_EXCEPTION_NOT_HANDLED;
break;
case EXIT_PROCESS_DEBUG_EVENT:
pids.Remove(pid);
if (pid == mainPid)
{
ContinueDebugEvent(pid, tid, status);
return;
}
break;
}
ContinueDebugEvent(pid, tid, status);
now = clock.ElapsedMilliseconds;
if (ShouldDetach(now, now - lastCreate))
break;
}
foreach (var pid in pids)
DebugActiveProcessStop(pid);
}
private static bool ShouldDetach(long elapsed, long sinceLastCreate)
{
if (elapsed > MaxDebugMs) return true;
return elapsed > MinDebugMs && sinceLastCreate > QuietMs;
}
private static bool PatchFuse(IntPtr hProcess, IntPtr imageBase)
{
if (imageBase == IntPtr.Zero) return false;
int sizeOfImage = ReadSizeOfImage(hProcess, imageBase);
if (sizeOfImage == 0) return false;
const int overlap = 64;
var buffer = new byte[ScanChunkSize + overlap];
for (long offset = 0; offset < sizeOfImage; offset += ScanChunkSize)
{
int toRead = (int)Math.Min(ScanChunkSize + overlap, sizeOfImage - offset);
if (toRead < SentinelLength + FuseWireTailBytes) break;
var addr = new IntPtr(imageBase.ToInt64() + offset);
if (!ReadProcessMemory(hProcess, addr, buffer, toRead, out int bytesRead))
continue;
if (bytesRead < SentinelLength + FuseWireTailBytes) continue;
int limit = bytesRead - SentinelLength - FuseWireTailBytes;
// Byte-by-byte: the linker is free to place the sentinel at any alignment,
// and a miss means every renderer dies with -36861.
for (int i = 0; i <= limit; i++)
{
if (buffer[i] != Sentinel[0] || !MatchesSentinel(buffer, i)) continue;
int wireOffset = i + SentinelLength;
if (buffer[wireOffset + FuseWireVersionOffset] != FuseWireSupportedVersion ||
buffer[wireOffset + FuseWireCountOffset] < FuseWireMinCount) continue;
int fusePos = wireOffset + FuseWireStatesOffset + FuseAsarIntegrity;
if (buffer[fusePos] == FuseStateRemoved) return true;
var target = new IntPtr(imageBase.ToInt64() + offset + fusePos);
VirtualProtectEx(hProcess, target, (UIntPtr)1, PAGE_READWRITE, out uint oldProt);
bool ok = WriteProcessMemory(hProcess, target, new[] { FuseStateRemoved }, 1, out _);
VirtualProtectEx(hProcess, target, (UIntPtr)1, oldProt, out _);
return ok;
}
}
return false;
}
private static bool MatchesSentinel(byte[] buffer, int offset)
{
for (int j = 1; j < SentinelLength; j++)
if (buffer[offset + j] != Sentinel[j]) return false;
return true;
}
private static int ReadSizeOfImage(IntPtr hProcess, IntPtr imageBase)
{
var dosHeader = new byte[64];
if (!ReadProcessMemory(hProcess, imageBase, dosHeader, 64, out _))
return 0;
int peOffset = BitConverter.ToInt32(dosHeader, 0x3C);
var buf = new byte[4];
// SizeOfImage sits at optional-header offset 56 (PE signature + COFF header = 24).
var addr = new IntPtr(imageBase.ToInt64() + peOffset + 80);
if (!ReadProcessMemory(hProcess, addr, buf, 4, out _))
return 0;
return BitConverter.ToInt32(buf, 0);
}
#region P/Invoke
private const uint DEBUG_PROCESS = 0x1;
private const uint PAGE_READWRITE = 0x04;
private const uint DBG_CONTINUE = 0x00010002;
private const uint DBG_EXCEPTION_NOT_HANDLED = 0x80010001;
private const int EXCEPTION_DEBUG_EVENT = 1;
private const int CREATE_PROCESS_DEBUG_EVENT = 3;
private const int EXIT_PROCESS_DEBUG_EVENT = 5;
private const int EXCEPTION_BREAKPOINT = unchecked((int)0x80000003);
[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFO
{
public int cb;
public IntPtr lpReserved, lpDesktop, lpTitle;
public int dwX, dwY, dwXSize, dwYSize;
public int dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
public short wShowWindow, cbReserved2;
public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess, hThread;
public int dwProcessId, dwThreadId;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CreateProcessW(
string lpApplicationName, StringBuilder lpCommandLine,
IntPtr lpProcessAttributes, IntPtr lpThreadAttributes,
bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment,
string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(
IntPtr hProcess, IntPtr lpBaseAddress,
byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(
IntPtr hProcess, IntPtr lpBaseAddress,
byte[] lpBuffer, int dwSize, out int lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualProtectEx(
IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize,
uint flNewProtect, out uint lpflOldProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WaitForDebugEvent(byte[] lpDebugEvent, int dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ContinueDebugEvent(int dwProcessId, int dwThreadId, uint dwContinueStatus);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool DebugActiveProcessStop(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool DebugSetProcessKillOnExit(bool KillOnExit);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
#endregion
}
}
+127 -13
View File
@@ -1,45 +1,159 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
using WandEnhancer.Core;
using WandEnhancer.Models;
using WandEnhancer.Utils;
using WandEnhancer.View.MainWindow;
namespace WandEnhancer
{
public static class Program
{
/// <summary>Log lines from a failed startup auto-patch, replayed by the UI when it opens.</summary>
public static readonly List<KeyValuePair<string, ELogType>> StartupLog =
new List<KeyValuePair<string, ELogType>>();
[STAThread]
public static void Main(string[] args)
{
if (TryLaunchMode(args))
return;
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
List<LogEntry> logEntries = new List<LogEntry>();
if (args.Length > 0)
{
// TODO: Command line arguments handling
}
var application = new App();
application.InitializeComponent();
application.MainWindow = new MainWindow();
foreach (var logEntry in logEntries)
{
MainWindow.Instance.ViewModel.LogList.Add(logEntry);
}
application.Run();
}
private static bool TryLaunchMode(string[] args)
{
string myExe = Assembly.GetExecutingAssembly().Location;
string myName = Path.GetFileNameWithoutExtension(myExe);
if (!Constants.WeModBrandNames.Any(
n => n.Equals(myName, StringComparison.OrdinalIgnoreCase)))
return false;
string myDir = Path.GetDirectoryName(myExe);
if (args.Length > 0 &&
args[0].StartsWith("--squirrel", StringComparison.OrdinalIgnoreCase))
{
string updateExe = Path.Combine(myDir, "Update.exe");
if (File.Exists(updateExe))
Process.Start(updateExe, QuoteArguments(args));
return true;
}
var config = WeModInstalls.FindLatestWeMod(myDir);
if (config == null)
return false;
// A fresh Wand version drops our patches; re-apply the saved selection automatically.
// On failure fall through to the UI so the user sees which patch broke.
if (!Enhancer.IsPatched(config.RootDirectory) && !TryAutoPatch(config, myDir))
return false;
string forwardedArgs = args.Length > 0 ? QuoteArguments(args) : null;
FuseLauncher.Launch(config.ExecutablePath, forwardedArgs,
message => RecordStartupLog(message, ELogType.Warn));
return true;
}
/// <summary>
/// Re-quotes argv for a command line. Squirrel hands us paths with spaces
/// (`--squirrel-install "C:\Users\Some Name\..."`); re-joining on spaces splits them.
/// </summary>
private static string QuoteArguments(IEnumerable<string> args)
{
return string.Join(" ", args.Select(QuoteArgument));
}
private static string QuoteArgument(string value)
{
if (!string.IsNullOrEmpty(value) && value.IndexOfAny(new[] { ' ', '\t', '"' }) < 0)
{
return value;
}
// Backslashes are literal unless they run into the closing quote, where they double.
var quoted = new System.Text.StringBuilder("\"");
int backslashes = 0;
foreach (char current in value ?? string.Empty)
{
if (current == '\\')
{
backslashes++;
continue;
}
if (current == '"')
{
quoted.Append('\\', backslashes * 2 + 1).Append('"');
}
else
{
quoted.Append('\\', backslashes).Append(current);
}
backslashes = 0;
}
return quoted.Append('\\', backslashes * 2).Append('"').ToString();
}
private static bool TryAutoPatch(WeModConfig config, string launcherDir)
{
var patchConfig = Enhancer.LoadAutoPatchConfig(launcherDir);
if (patchConfig == null)
return true; // nothing saved to replay; launch as-is
try
{
new Enhancer(config, RecordStartupLog, patchConfig).Patch();
return true;
}
catch (Exception e)
{
// Localization resources are not loaded yet in launcher mode (no Application),
// so these two replay into the UI log in English by design.
RecordStartupLog($"Auto-patch failed: {e.Message}", ELogType.Error);
RecordStartupLog("The new Wand version may need updated patches. Restore the backup and patch again.", ELogType.Warn);
return false;
}
}
private static void RecordStartupLog(string message, ELogType type)
{
StartupLog.Add(new KeyValuePair<string, ELogType>(message, type));
}
// Fires on the finalizer thread for a task nobody awaited. Non-fatal since .NET 4.5:
// record it and mark it observed rather than killing a patch mid-run.
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
MessageBox.Show(e.Exception.ToString());
Environment.Exit(1);
e.SetObserved();
RecordStartupLog($"Background task failed: {e.Exception.GetBaseException().Message}", ELogType.Error);
}
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
MessageBox.Show(e.ExceptionObject.ToString());
var error = e.ExceptionObject as Exception;
MessageBox.Show(
error?.Message ?? e.ExceptionObject?.ToString() ?? "Unknown error",
Constants.RepoName,
MessageBoxButtons.OK,
MessageBoxIcon.Error);
Environment.Exit(1);
}
}
-61
View File
@@ -1,61 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace WandEnhancer.Utils.Win32
{
public class Shortcut
{
public class ShortcutParams
{
public string FileName { get; set; }
public string TargetPath { get; set; }
public string Arguments { get; set; }
public string WorkingDirectory { get; set; }
public string Description { get; set; }
public string Hotkey { get; set; }
public string IconPath { get; set; }
};
private static readonly Type m_type = Type.GetTypeFromProgID("WScript.Shell");
private static readonly object m_shell = Activator.CreateInstance(m_type);
[ComImport, TypeLibType(0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")]
private interface IWshShortcut
{
[DispId(0)]
string FullName { [return: MarshalAs(UnmanagedType.BStr)][DispId(0)] get; }
[DispId(0x3e8)]
string Arguments { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] set; }
[DispId(0x3e9)]
string Description { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] set; }
[DispId(0x3ea)]
string Hotkey { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] set; }
[DispId(0x3eb)]
string IconLocation { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] set; }
[DispId(0x3ec)]
string RelativePath { [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ec)] set; }
[DispId(0x3ed)]
string TargetPath { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] set; }
[DispId(0x3ee)]
int WindowStyle { [DispId(0x3ee)] get; [param: In][DispId(0x3ee)] set; }
[DispId(0x3ef)]
string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] set; }
[TypeLibFunc((short)0x40), DispId(0x7d0)]
void Load([In, MarshalAs(UnmanagedType.BStr)] string PathLink);
[DispId(0x7d1)]
void Save();
}
public static void CreateShortcut(string fileName, string targetPath, string arguments, string workingDirectory, string description, string iconPath)
{
IWshShortcut shortcut = (IWshShortcut)m_type.InvokeMember("CreateShortcut", System.Reflection.BindingFlags.InvokeMethod, null, m_shell, new object[] { fileName });
shortcut.Description = description;
shortcut.TargetPath = targetPath;
shortcut.WorkingDirectory = workingDirectory;
shortcut.Arguments = arguments;
if (!string.IsNullOrEmpty(iconPath))
shortcut.IconLocation = iconPath;
shortcut.Save();
}
}
}
-78
View File
@@ -1,78 +0,0 @@
# Build directories
/build/
/build-debug/
/build-release/
/out/
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
CTestTestfile.cmake
Makefile
install_manifest.txt
# Compiled binaries
*.o
*.obj
*.lo
*.la
*.a
*.so
*.so.*
*.dylib
*.dll
*.exe
*.out
*.app
# Debug files
*.pch
*.pdb
*.mod
*.map
# Generated configuration headers
config.h
config.hpp
# Logs
*.log
# IDE files
# VS Code
.vscode/
*.code-workspace
# CLion
.idea/
# Visual Studio
*.user
*.suo
*.vcxproj.user
*.vcxproj.*
*.sln
# Xcode
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
*.xcworkspace/
xcuserdata/
# OS junk
# macOS
.DS_Store
# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# Backup files
*~
*.swp
*.tmp
-21
View File
@@ -1,21 +0,0 @@
cmake_minimum_required(VERSION 3.16)
cmake_policy(SET CMP0091 NEW)
project(asar_fuses_bypass C)
set(CMAKE_C_STANDARD 11)
#[[
add_executable(asar_fuses_bypass main.c)
]]
set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(CMAKE_STATIC_LIBRARY_PREFIX "")
add_library(version SHARED library.c library.def fuses.c)
if(MSVC)
set_property(TARGET version PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_link_options(version PRIVATE -static -static-libgcc -static-libstdc++)
endif()
-190
View File
@@ -1,190 +0,0 @@
//
// Created by kitbyte on 30.11.2025.
//
#include <Windows.h>
#include <stdio.h>
#include <time.h>
#define ENABLE_LOGGING 0
#ifndef _DEBUG
#undef ENABLE_LOGGING
#define ENABLE_LOGGING 0
#endif
#define FUSE_SENTINEL_LENGTH 32
#define FUSE_VERSION_SUPPORTED 1
#define FUSE_MIN_WIRE_LENGTH 5
#define ALIGN8(ptr, mod) ((((ULONG_PTR)(ptr) + 7) & ~7) + ((mod) * 8))
#if defined(_WIN64)
#define SENTINEL_PART1 0x6E64474B70374C64ULL
#define SENTINEL_PART2 0x6262503639377A4EULL
#define SENTINEL_PART3 0x58486D4B4E57516AULL
#define SENTINEL_PART4 0x5873743942615A42ULL
#else
static const DWORD SENTINEL_PARTS[8] = {
0x70374C64, 0x6E64474B,
0x39377A4E, 0x62625036,
0x4E57516A, 0x58486D4B,
0x42615A42, 0x58737439
};
#endif
typedef enum {
FUSE_RUN_AS_NODE = 0,
FUSE_COOKIE_ENCRYPTION = 1,
FUSE_NODE_OPTIONS = 2,
FUSE_NODE_CLI_INSPECT = 3,
FUSE_ASAR_INTEGRITY_VALIDATION = 4,
FUSE_ONLY_LOAD_APP_FROM_ASAR = 5,
FUSE_LOAD_BROWSER_V8_SNAPSHOT = 6,
FUSE_GRANT_FILE_PROTOCOL = 7
} ElectronFuseIndex;
typedef enum {
FUSE_STATE_DISABLED = '0',
FUSE_STATE_ENABLED = '1',
FUSE_STATE_REMOVED = 'r'
} FuseState;
typedef struct {
char sentinel[FUSE_SENTINEL_LENGTH];
unsigned char version;
unsigned char wire_length;
unsigned char fuses[];
} FuseWire;
#if ENABLE_LOGGING
static FILE* g_logFile = NULL;
static void log_init(void) {
char path[MAX_PATH];
GetModuleFileNameA(NULL, path, MAX_PATH);
char* dot = strrchr(path, '.');
if (dot) strcpy(dot, ".log");
else strcat(path, ". log");
g_logFile = fopen(path, "a");
if (g_logFile) {
time_t now = time(NULL);
fprintf(g_logFile, "\n=== Session: %s", ctime(&now));
fflush(g_logFile);
}
}
static void log_close(void) {
if (g_logFile) {
fclose(g_logFile);
g_logFile = NULL;
}
}
static void log_msg(const char* fmt, .. .) {
if (!g_logFile) return;
va_list args;
va_start(args, fmt);
vfprintf(g_logFile, fmt, args);
va_end(args);
fflush(g_logFile);
}
#else
#define log_init() ((void)0)
#define log_close() ((void)0)
#define log_msg(...) ((void)0)
#endif
static FuseWire* find_fuse_wire(int offset) {
char* base = (char*)GetModuleHandleA(NULL);
if (!base) return NULL;
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)base;
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew);
if (nt->Signature != IMAGE_NT_SIGNATURE) return NULL;
DWORD size = nt->OptionalHeader.SizeOfImage;
char* start = (char*)ALIGN8(base, 1) + offset;
char* end = (char*)ALIGN8(base + size - FUSE_SENTINEL_LENGTH, -1) - offset;
#if defined(_WIN64)
for (DWORD64* p = (DWORD64*)start; p < (DWORD64*)end; p++) {
if (p[0] == SENTINEL_PART1 && p[1] == SENTINEL_PART2 &&
p[2] == SENTINEL_PART3 && p[3] == SENTINEL_PART4) {
log_msg("[+] Sentinel at: %p\n", p);
return (FuseWire*)p;
}
}
#else
for (DWORD* p = (DWORD*)start; p < (DWORD*)end; p += 2) {
if (p[0] == SENTINEL_PARTS[0] && p[1] == SENTINEL_PARTS[1] &&
p[2] == SENTINEL_PARTS[2] && p[3] == SENTINEL_PARTS[3] &&
p[4] == SENTINEL_PARTS[4] && p[5] == SENTINEL_PARTS[5] &&
p[6] == SENTINEL_PARTS[6] && p[7] == SENTINEL_PARTS[7]) {
log_msg("[+] Sentinel at: %p\n", p);
return (FuseWire*)p;
}
}
#endif
return NULL;
}
static BOOL patch_fuse(unsigned char* fuse) {
DWORD prot;
if (!VirtualProtect(fuse, 1, PAGE_READWRITE, &prot)) {
log_msg("[-] VirtualProtect failed: %lu\n", GetLastError());
return FALSE;
}
*fuse = FUSE_STATE_REMOVED;
VirtualProtect(fuse, 1, prot, &prot);
return TRUE;
}
BOOL disable_asar_integrity(void) {
log_init();
FuseWire* wire = find_fuse_wire(0);
if (! wire) wire = find_fuse_wire(4);
if (! wire) {
log_msg("[-] Fuse wire not found\n");
log_close();
return FALSE;
}
log_msg("[+] Wire at %p, ver=%d, len=%d\n", wire, wire->version, wire->wire_length);
if (wire->version != FUSE_VERSION_SUPPORTED) {
log_msg("[-] Unsupported version: %d\n", wire->version);
log_close();
return FALSE;
}
if (wire->wire_length < FUSE_MIN_WIRE_LENGTH) {
log_msg("[*] Wire too short, skip\n");
log_close();
return TRUE;
}
unsigned char* target = &wire->fuses[FUSE_ASAR_INTEGRITY_VALIDATION];
if (*target == FUSE_STATE_REMOVED) {
log_msg("[*] Already patched\n");
log_close();
return TRUE;
}
log_msg("[*] Patching fuse[%d]: 0x%02X -> 0x%02X\n",
FUSE_ASAR_INTEGRITY_VALIDATION, *target, FUSE_STATE_REMOVED);
BOOL result = patch_fuse(target);
log_msg(result ? "[+] Success\n" : "[-] Failed\n");
log_close();
return result;
}
-147
View File
@@ -1,147 +0,0 @@
//
// Created by kitbyte on 30.11.2025.
//
#include <Windows.h>
#include <winver.h>
extern BOOL disable_asar_integrity(void);
static HMODULE g_originalVersionDll;
#define FOR_EACH_VERSION_FORWARDER(X) \
X(GetFileVersionInfoA, BOOL, FALSE, \
(LPCSTR filename, DWORD handle, DWORD length, LPVOID data), \
(filename, handle, length, data)) \
X(GetFileVersionInfoExA, BOOL, FALSE, \
(DWORD flags, LPCSTR filename, DWORD handle, DWORD length, LPVOID data), \
(flags, filename, handle, length, data)) \
X(GetFileVersionInfoExW, BOOL, FALSE, \
(DWORD flags, LPCWSTR filename, DWORD handle, DWORD length, LPVOID data), \
(flags, filename, handle, length, data)) \
X(GetFileVersionInfoSizeA, DWORD, 0, \
(LPCSTR filename, LPDWORD handle), \
(filename, handle)) \
X(GetFileVersionInfoSizeExA, DWORD, 0, \
(DWORD flags, LPCSTR filename, LPDWORD handle), \
(flags, filename, handle)) \
X(GetFileVersionInfoSizeExW, DWORD, 0, \
(DWORD flags, LPCWSTR filename, LPDWORD handle), \
(flags, filename, handle)) \
X(GetFileVersionInfoSizeW, DWORD, 0, \
(LPCWSTR filename, LPDWORD handle), \
(filename, handle)) \
X(GetFileVersionInfoW, BOOL, FALSE, \
(LPCWSTR filename, DWORD handle, DWORD length, LPVOID data), \
(filename, handle, length, data)) \
X(VerFindFileA, DWORD, 0, \
(DWORD flags, LPCSTR fileName, LPCSTR winDir, LPCSTR appDir, LPSTR curDir, PUINT curDirLen, LPSTR destDir, PUINT destDirLen), \
(flags, fileName, winDir, appDir, curDir, curDirLen, destDir, destDirLen)) \
X(VerFindFileW, DWORD, 0, \
(DWORD flags, LPCWSTR fileName, LPCWSTR winDir, LPCWSTR appDir, LPWSTR curDir, PUINT curDirLen, LPWSTR destDir, PUINT destDirLen), \
(flags, fileName, winDir, appDir, curDir, curDirLen, destDir, destDirLen)) \
X(VerInstallFileA, DWORD, 0, \
(DWORD flags, LPCSTR srcFileName, LPCSTR destFileName, LPCSTR srcDir, LPCSTR destDir, LPCSTR curDir, LPSTR tempFile, PUINT tempFileLen), \
(flags, srcFileName, destFileName, srcDir, destDir, curDir, tempFile, tempFileLen)) \
X(VerInstallFileW, DWORD, 0, \
(DWORD flags, LPCWSTR srcFileName, LPCWSTR destFileName, LPCWSTR srcDir, LPCWSTR destDir, LPCWSTR curDir, LPWSTR tempFile, PUINT tempFileLen), \
(flags, srcFileName, destFileName, srcDir, destDir, curDir, tempFile, tempFileLen)) \
X(VerLanguageNameA, DWORD, 0, \
(DWORD language, LPSTR buffer, DWORD bufferLength), \
(language, buffer, bufferLength)) \
X(VerLanguageNameW, DWORD, 0, \
(DWORD language, LPWSTR buffer, DWORD bufferLength), \
(language, buffer, bufferLength)) \
X(VerQueryValueA, BOOL, FALSE, \
(LPCVOID block, LPCSTR subBlock, LPVOID* buffer, PUINT bufferLength), \
(block, subBlock, buffer, bufferLength)) \
X(VerQueryValueW, BOOL, FALSE, \
(LPCVOID block, LPCWSTR subBlock, LPVOID* buffer, PUINT bufferLength), \
(block, subBlock, buffer, bufferLength))
#if defined(_MSC_VER) && !defined(_WIN64)
#define DECLARE_FORWARDER(name, return_type, default_value, params, args) \
static FARPROC s_##name; \
__declspec(naked) return_type WINAPI name params \
{ \
__asm \
{ \
jmp dword ptr [s_##name] \
} \
}
#define LOAD_FORWARDER(name, return_type, default_value, params, args) \
s_##name = GetProcAddress(g_originalVersionDll, #name);
#else
#define DECLARE_FORWARDER(name, return_type, default_value, params, args) \
typedef return_type (WINAPI *name##_fn) params; \
static name##_fn s_##name; \
return_type WINAPI name params \
{ \
if (s_##name == NULL) \
{ \
SetLastError(ERROR_PROC_NOT_FOUND); \
return default_value; \
} \
return s_##name args; \
}
#define LOAD_FORWARDER(name, return_type, default_value, params, args) \
s_##name = (name##_fn)GetProcAddress(g_originalVersionDll, #name);
#endif
FOR_EACH_VERSION_FORWARDER(DECLARE_FORWARDER)
BOOL WINAPI GetFileVersionInfoByHandle(void)
{
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
return FALSE;
}
static BOOL SourceInit(void)
{
WCHAR source[MAX_PATH];
UINT sourceLength = GetSystemDirectoryW(source, MAX_PATH);
if (sourceLength == 0 || sourceLength >= MAX_PATH)
{
return FALSE;
}
if (wcscat_s(source, MAX_PATH, L"\\version.dll") != 0)
{
return FALSE;
}
g_originalVersionDll = LoadLibraryW(source);
if (!g_originalVersionDll)
{
return FALSE;
}
FOR_EACH_VERSION_FORWARDER(LOAD_FORWARDER);
return TRUE;
}
BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
(void)lpvReserved;
if (fdwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hinstDLL);
if (!SourceInit())
{
return FALSE;
}
disable_asar_integrity();
}
return TRUE;
}
-20
View File
@@ -1,20 +0,0 @@
LIBRARY "VERSION"
EXPORTS
GetFileVersionInfoA
GetFileVersionInfoByHandle
GetFileVersionInfoExA
GetFileVersionInfoExW
GetFileVersionInfoSizeA
GetFileVersionInfoSizeExA
GetFileVersionInfoSizeExW
GetFileVersionInfoSizeW
GetFileVersionInfoW
VerFindFileA
VerFindFileW
VerInstallFileA
VerInstallFileW
VerLanguageNameA
VerLanguageNameW
VerQueryValueA
VerQueryValueW