黑暗模式下,最难忍受启动白屏: chrome,edge,vscode,github desktop,这些浏览器/electron应用……
终极方案 : 启动时注入 dll ,劫持 createWindow, 或将窗口隐藏,或将白色设置为透明色,或显示一个黑色半透明遮罩。
劫持 createWindow,将白色设置为透明色即可。
dllmain.cpp :
void SetWindowTransparentColorKey(HWND hWnd)
{
SetWindowLongPtrW(hWnd, GWL_EXSTYLE, GetWindowLongPtrW(hWnd, GWL_EXSTYLE) &~WS_EX_TRANSPARENT| WS_EX_LAYERED);
auto TPKey = RGB(255, 255, 255);
SetLayeredWindowAttributes(hWnd, TPKey, 255, LWA_COLORKEY);
}
...
MH_CreateHook(&CreateWindowExW, &MyCreateWindowExW, reinterpret_cast<void**>(&fpCreateWindowExW));
MH_EnableHook(&CreateWindowExW);injector.cpp :
// Injector.cpp : This file contains the 'main' function. Program execution begins and ends there.
#include <Windows.h>
//#include <detours.h>
#include <string>
#include <cstdlib> // 包含 system() 函数
int wmain(int argc, wchar_t* argv[]) {
std::string command = "I:\\Softwares\\detours\\withdll.exe -d C:\\base\\hook\\popup-resizer\\x64\\Release\\popup-resizer.dll \"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" ";
int exit_code = std::system(command.data());
return 0;
}效果: https://www.youtube.com/watch?v=JrMxuaE3KfE
同样方法,chrome 效果差一些。
tablacus 默认是延时,显示窗口,我不大喜欢,因为这让我觉得是卡顿。
用以上方法即可,不用劫持,修改源代码即可,效果不错的。
最难忍vscode : 启动慢,白屏久,甚至需要移开视线,看看风景。
但有时避之不及,直接呼我一脸,白屏太难受……
使用以上方法几乎无效。我尝试hook CreateProcess ,劫持所有子进程,也还是无效。
只好用“笨办法”,也就是终极方案:显示半透明浮窗,遮住白屏即可。
半透明浮窗 用 ahk 写,作者是deekseep,豆包太笨了:
#SingleInstance, Force
SetWorkingDir, %A_ScriptDir%
#NoTrayIcon
ScreenWidth := A_ScreenWidth
ScreenHeight := A_ScreenHeight
ScreenHeight -= 235 ; 修改:避免遮挡任务栏。我的任务栏是双层的。
Gui +LastFound +AlwaysOnTop -Caption +ToolWindow
Gui Color, Black
; Gui Show, Maximize
Gui Show, x0 y0 w%ScreenWidth% h%ScreenHeight%, BlackWindow
WinSet, Transparent, 150
Sleep 2000
ExitApp
Esc::ExitApp然后是注入,和edege一样,无需真正的注入器。此例只需启动浮窗后,再启动原应用。
需要字写先导程序,然后将其填入注册表,作为 Code.exe 的调试器。
injector.cpp
// Injector.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <Windows.h>
//#include <detours.h> // 无需真正的注入器
#include <string>
#include <cstdlib> // 包含 system() 函数
int wmain(int argc, wchar_t* argv[]) {
// Check if we have at least the original executable path
//if (argc < 2) {
// // Handle error: no target specified
// return 1;
//}
//MessageBoxA(NULL, "123", "?", MB_OK);
::ShowWindow(::GetConsoleWindow(), SW_HIDE);
// Reconstruct the command line for the original process.
// Start with the path to the target executable (argv[1]).
// A more robust solution would combine all remaining arguments.
wchar_t commandLine[32768];
//if (argc >= 2) {
// wcscpy_s(commandLine, argv[1]);
// for (int i = 2; i < argc; ++i) {
// wcscat_s(commandLine, L" ");
// wcscat_s(commandLine, argv[i]);
// }
//}
//MessageBoxW(NULL, (LPWSTR)commandLine, L"123", MB_OK);
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
//wcscpy_s(commandLine, L"D:\\Code\\WODPlayer\\bin\\WODPlayer.exe");
wcscpy_s(commandLine, L"C:\\vscode\\Code.exe");
//wcscpy_s(commandLine, L"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe");
//wcscat_s(commandLine, L" --disable-gpu");
//wcscat_s(commandLine, L" --enable-features=RemoveRedirectionBitmap --RemoveRedirectionBitmap");
//wcscat_s(commandLine, L" --enable-force-dark");
//wcscat_s(commandLine, L" --force-dark");
// 显示遮罩
std::system("start R:\\test_g.ahk");
DWORD createFlags = CREATE_NEW_CONSOLE | // 可选:创建新控制台
CREATE_BREAKAWAY_FROM_JOB | // 脱离作业对象,绕过IFEO
DEBUG_ONLY_THIS_PROCESS; // 仅调试当前进程,不触发递归 DEBUG_PROCESS
BOOL created = CreateProcessW(
nullptr, // No application name (use command line)
commandLine, // Full command line for the target
nullptr, nullptr, // Process/thread security attributes
FALSE, // Handle inheritance
0, // **** CRITICAL FLAG ****
nullptr, nullptr, // Environment/current directory
&si, &pi
);
if (!created) {
MessageBoxW(NULL, (LPWSTR)commandLine, L"!created", MB_OK);
// Handle error
return 1;
}
//CloseHandle(pi.hProcess);
//WCHAR szMsg[512] = { 0 };
//wsprintfW(szMsg, L"创建窗口 %d:", pi.dwProcessId);
//MessageBoxW(nullptr, szMsg, L"创建窗口 ", MB_OK | MB_ICONINFORMATION);
//Sleep(1000);
// edge 的话这样注入:
//std::string command = "R:\\detours-x64\\withdll.exe -d C:\\base\\hook\\popup-resizer\\Release\\popup-resizer.dll --debug ";
//command += std::to_string(pi.dwProcessId); // 数字
//int exit_code = std::system(command.data());
// Close handles to the target process/thread if you don't need them immediately
//CloseHandle(pi.hThread);
//CloseHandle(pi.hProcess);
//while (1) {
//}
// Your debugger logic continues here...
// Optionally, call DebugSetProcessKillOnExit(FALSE) to let the target run
// if your debugger process terminates.
return 0;
}代码需要整理,主体是deepseek写的,可以重建原始参数。
导入.reg
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\【进程名】.exe]
"Debugger"="c:\\【注入器】.exe 【注入参数】"
注入器可以直接用支持启动注入的注入器,也可以自写先导程序,然后create process,得到pid后再用真实的注入器,注入dll,或显示浮窗遮挡。
推荐
Withdll: A small tool to perform DLL injections
Injector: Command line utility to inject and eject DLLs
注入器的编写不用研究,用现成的开源项目即可,功能已经完备。
需要自己写的是要注入的dll,也就是劫持逻辑,可以用 minhook 编写。
TsudaKageyu/minhook: The Minimalistic x86/x64 API Hooking Library for Windows
可以问豆包等,都能答得上来,不过有点笨,许多错误,下面给出可用代码,不到百行:
dllmain.cpp
#include <Windows.h>
#include "MinHook.h"
#include <iostream>
void UnHook();
HMODULE g_hDll;
// https://blog.csdn.net/weixin_44256803/article/details/102614168
HANDLE free(DWORD dwProcID, LPVOID lpModuleAddress)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcID);
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
LPTHREAD_START_ROUTINE lpStartAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel32, "FreeLibrary");
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, lpStartAddress, lpModuleAddress, 0, NULL);
//WaitForSingleObject(hRemoteThread, 2000);
//CloseHandle(hRemoteThread);
//CloseHandle(hProcess);
return hRemoteThread;
}
// 注意,豆包的代码 需要修改类名为 OldSetWindowTextW,避免重复。
typedef BOOL(WINAPI *OldSetWindowTextW)(HWND, LPCWSTR);
OldSetWindowTextW fpSetWindowTextW = NULL;
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPCWSTR lpString)
{
BOOL ret = fpSetWindowTextW(hWnd, lpString); // 原始逻辑
MessageBox(NULL, L"设置标题", lpString, MB_OK); // 劫持后的逻辑
return ret;
}
bool SetHook()
{
if (MH_Initialize() == MB_OK)
{
MH_CreateHook(&SetWindowTextW, &MySetWindowTextW, reinterpret_cast<void**>(&fpSetWindowTextW));
MH_EnableHook(&SetWindowTextW);
// 安装其他钩子……
return true;
}
return false;
}
void UnHook()
{
//if (MH_DisableHook(&SetWindowTextW) == MB_OK)
//{
// MH_Uninitialize();
//}
MH_DisableHook(&SetWindowTextW);
MH_DisableHook(&GetMonitorInfoW);
MH_DisableHook(&SetWindowPos);
MH_Uninitialize();
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: {
//MessageBox(NULL, L"Ez Injected", L"Success", MB_OK);
g_hDll = hModule;
SetHook();
//if(SetHook())
// MessageBox(NULL, L"DLL Injected", L"Success", MB_OK);
//else
// MessageBox(NULL, L"DLL Injected", L"Error", MB_OK);
} break;
case DLL_PROCESS_DETACH:
//MessageBox(NULL, L"DLL UnInjected", L"Success", MB_OK);
//UnHook();
break;
}
return TRUE;
}编译:将 minhook 关键代码下载下来,和 dllmain.cpp 放到一起,visual studio 建立项目,拖入全部源代码,编译成dll即可。
编译成功后,使用命令行测试,用注入器修改运行中的应用程序,如成功,修改标题时会弹窗。
有了这个基础,就可以随意让豆包劫持其他API。
使用注册表自动注入,如果注入器写的不对,会导致死循环。
需要在 create process 时写好标志,或用 detour create process。
还有一个取巧的方法,我从这文中得到:
利用映像劫持(IFEO)实现特定进程运行参数检查 - 哔哩哔哩
先建立目标exe的副本,然后将 副本exe 放到任务栏(手动改 %appdata%\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar 中的快捷方式 )
,劫持后,在 injector.cpp 中转回本体。