Win10记事本子类化失败?DLL注入及权限解决
2024-12-28 22:25:26
解决Windows 10记事本窗口子类化问题
在Windows系统上,使用SetWindowSubclass
API对其他进程的窗口进行子类化时可能会遇到困难。这个技术常被用于拦截、修改目标窗口的消息。本文分析了针对Windows 10记事本使用SetWindowSubclass
进行子类化操作失败的原因,并提供了可行的解决方案。
问题分析
使用 SetWindowSubclass
尝试对Windows 10记事本窗口进行子类化时,如果出现错误代码 0 (ERROR_SUCCESS),这通常不是代码本身的错误,而是由于进程权限或安全机制的限制 。尽管记事本是一个简单的应用程序,Windows仍然会对其执行某些安全限制,防止非特权进程修改其行为。 SetWindowSubclass
操作实际上是对目标进程注入DLL的行为,因此可能涉及到权限检查。错误代码0 表示操作在系统层面成功完成,但是子类化效果没有生效。这是因为操作系统并没有成功把用户代码注入到 Notepad 进程里。
解决方法一:使用注入DLL方式进行窗口子类化
为了解决直接 SetWindowSubclass
调用失败的问题,一种可行的方案是使用DLL注入方式,将包含子类化逻辑的DLL文件注入到记事本进程空间中,然后在那边执行窗口子类化的逻辑。
步骤:
-
创建DLL项目: 新建一个DLL项目,在这个项目中编写子类化回调函数
SubclassProc
,并将其导出,这样可以从外部调用。// SubclassDll.cpp #include "pch.h" #include "SubclassDll.h" #include <iostream> HINSTANCE g_hInstance = nullptr; BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: g_hInstance = hInst; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } LRESULT CALLBACK SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { if (uMsg == WM_MOUSELEAVE) { std::cout << "WM_MOUSELEAVE blocked from dll!" << std::endl; return 0; } return DefSubclassProc(hwnd, uMsg, wParam, lParam); } extern "C" __declspec(dllexport) bool SubclassNotepad(HWND hWnd) { return SetWindowSubclass(hWnd, SubclassProc, 1, 0) ? true : false; }
SubclassDll.h
:
#pragma once
#ifdef SUBCLASSDLL_EXPORTS
#define SUBCLASSDLL_API __declspec(dllexport)
#else
#define SUBCLASSDLL_API __declspec(dllimport)
#endif
SUBCLASSDLL_API bool SubclassNotepad(HWND hWnd);
- 创建EXE程序进行DLL注入: 创建一个exe项目,其作用为找出 notepad 进程窗口,并且将上一步创建的 DLL 文件注入到 notepad 进程, 并调用DLL中的
SubclassNotepad
函数进行窗口子类化操作。
//DllInjection.cpp
#include <iostream>
#include <windows.h>
#include <TlHelp32.h>
#include <string>
DWORD GetProcessId(const std::wstring& processName);
bool InjectDll(DWORD pid,const std::wstring & dllPath)
{
if(pid==0){
std::cout << "Process not found " << std::endl;
return false;
}
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if(hProcess ==nullptr){
std::cout << "failed open process with error " << GetLastError() << std::endl;
return false;
}
LPVOID pRemoteDllPath = VirtualAllocEx(hProcess, NULL, dllPath.length() * 2, MEM_COMMIT, PAGE_READWRITE);
if(pRemoteDllPath == nullptr){
std::cout << "failed alloc memeory with error " << GetLastError() << std::endl;
CloseHandle(hProcess);
return false;
}
BOOL isMemWritten = WriteProcessMemory(hProcess, pRemoteDllPath,dllPath.c_str(),dllPath.length()*2, NULL);
if(!isMemWritten){
std::cout << "failed Write memeory with error " << GetLastError() << std::endl;
VirtualFreeEx(hProcess,pRemoteDllPath,dllPath.length()*2,MEM_DECOMMIT);
CloseHandle(hProcess);
return false;
}
LPVOID pLoadDll = (LPVOID)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW");
if(pLoadDll== nullptr){
std::cout << "failed Get LoadlibraryW with error " << GetLastError() << std::endl;
VirtualFreeEx(hProcess,pRemoteDllPath,dllPath.length()*2,MEM_DECOMMIT);
CloseHandle(hProcess);
return false;
}
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadDll, pRemoteDllPath, 0, NULL);
if(hThread ==nullptr){
std::cout << "failed create remote thread with error " << GetLastError() << std::endl;
VirtualFreeEx(hProcess,pRemoteDllPath,dllPath.length()*2,MEM_DECOMMIT);
CloseHandle(hProcess);
return false;
}
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess,pRemoteDllPath,dllPath.length()*2,MEM_DECOMMIT);
CloseHandle(hThread);
HMODULE dllHmod = nullptr;
if(dllHmod = GetModuleHandleW(dllPath.c_str())){
typedef bool (*SubClassNotepadFunction)(HWND);
SubClassNotepadFunction pfnSubClassNotepad = (SubClassNotepadFunction)GetProcAddress(dllHmod, "SubclassNotepad");
if(!pfnSubClassNotepad){
std::cout << "Can't Get SubclassNotepad func" << GetLastError()<< std::endl;
return false;
}
HWND hwndNotepad = FindWindow(L"Notepad", NULL);
if(!pfnSubClassNotepad(hwndNotepad)){
std::cout << "Call sub class notepad fail " << std::endl;
return false;
}
std::cout << "Succed to hook notepad ! " << std::endl;
return true;
}
return false;
}
DWORD GetProcessId(const std::wstring & processName) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(PROCESSENTRY32W);
HANDLE snapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
if(snapShot== INVALID_HANDLE_VALUE){
std::cout << "Get snap shot fail "<< GetLastError() << std::endl;
return 0;
}
if(!Process32FirstW(snapShot, &entry)){
CloseHandle(snapShot);
return 0;
}
do
{
if (processName==entry.szExeFile) {
CloseHandle(snapShot);
return entry.th32ProcessID;
}
}
while(Process32NextW(snapShot, &entry));
CloseHandle(snapShot);
return 0;
}
int main()
{
// 将 "SubclassDll.dll" 替换为你实际生成的dll路径
if(InjectDll(GetProcessId(L"notepad.exe"), L"Path to \\SubclassDll.dll")){
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
请注意,编译时,需要将 Path to \SubclassDll.dll
替换为你实际生成dll文件的路径, 例如: C:\dev\repos\SubClassDemo\x64\Debug\SubclassDll.dll
。 还要保证 debug 版本的 SubClassDll.dll 和 DllInjection.exe 是在同一平台下(X64),而且保证SubclassDll.dll存在且正确。
- 运行EXE程序: 运行EXE,即可注入DLL,并调用其中的
SubclassNotepad
函数完成记事本的窗口子类化,并开始拦截WM_MOUSELEAVE
消息。 - 其他操作: 如果还需要在DLL内部获取句柄,可采用通过
FindWindow
获取目标窗口句柄的方法,然后再执行子类化,并记得从DllMain保存模块的句柄,g_hInstance = hInst;
,HMODULE dllHmod = GetModuleHandleW(L"SubclassDll.dll");
是可以的,,也可以传递目标窗口句柄到dll, 由注入器程序进行FindWindow
查找窗口并进行子类化。 本代码采用注入程序查找到窗口传递到dll的方式。 - 注意事项 : 在代码编译时注意编译器选项
/MT
,否则可能会发生 DLL依赖错误,程序运行崩溃。
解决方法二: 以管理员权限运行程序
如果 DLL 注入方式仍然不可行,一种比较简单方法是以管理员权限运行EXE 。 在某些情况下,权限不足是导致子类化失败的常见原因,特别是在操作系统安全级别较高的情况下。这种做法赋予进程更广泛的操作权限,可以使 SetWindowSubclass
调用成功。 建议先测试第一种方案,并选择更优方案, 因为通常在windows 系统下是以普通权限运行程序为常态。
操作步骤:
- 右键点击程序的可执行文件,并选择 "以管理员身份运行"。
- 重新执行尝试进行子类化的操作。
注意事项 : 虽然这种方法简单快捷,但是并非推荐的最佳方案 ,因为它提高了程序本身的权限,可能会引入其他安全隐患。只适合简单的开发测试。在正式的应用开发中,应当尽可能遵循最小权限原则。
安全建议
- 权限管理: 在子类化过程中,请注意控制你的应用程序权限,只申请必要的权限。避免运行需要管理员权限的进程。
- 代码安全: 使用经过安全审查的代码,特别是当你使用DLL注入时,确保DLL文件没有恶意代码。
- 错误处理: 总是检查每个操作的结果,合理地处理异常。记录日志以进行调试,特别是与操作系统相关的 API 调用。
希望以上的分析和解决方案对你有所帮助。 请根据自己的具体需求选择合适的方法,并在实际使用中充分考虑安全因素。