返回

Win10记事本子类化失败?DLL注入及权限解决

windows

解决Windows 10记事本窗口子类化问题

在Windows系统上,使用SetWindowSubclass API对其他进程的窗口进行子类化时可能会遇到困难。这个技术常被用于拦截、修改目标窗口的消息。本文分析了针对Windows 10记事本使用SetWindowSubclass进行子类化操作失败的原因,并提供了可行的解决方案。

问题分析

使用 SetWindowSubclass尝试对Windows 10记事本窗口进行子类化时,如果出现错误代码 0 (ERROR_SUCCESS),这通常不是代码本身的错误,而是由于进程权限或安全机制的限制 。尽管记事本是一个简单的应用程序,Windows仍然会对其执行某些安全限制,防止非特权进程修改其行为。 SetWindowSubclass操作实际上是对目标进程注入DLL的行为,因此可能涉及到权限检查。错误代码0 表示操作在系统层面成功完成,但是子类化效果没有生效。这是因为操作系统并没有成功把用户代码注入到 Notepad 进程里。

解决方法一:使用注入DLL方式进行窗口子类化

为了解决直接 SetWindowSubclass 调用失败的问题,一种可行的方案是使用DLL注入方式,将包含子类化逻辑的DLL文件注入到记事本进程空间中,然后在那边执行窗口子类化的逻辑。

步骤:

  1. 创建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);

  1. 创建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存在且正确。

  1. 运行EXE程序: 运行EXE,即可注入DLL,并调用其中的 SubclassNotepad 函数完成记事本的窗口子类化,并开始拦截 WM_MOUSELEAVE消息。
  2. 其他操作: 如果还需要在DLL内部获取句柄,可采用通过 FindWindow 获取目标窗口句柄的方法,然后再执行子类化,并记得从DllMain保存模块的句柄, g_hInstance = hInst;HMODULE dllHmod = GetModuleHandleW(L"SubclassDll.dll"); 是可以的,,也可以传递目标窗口句柄到dll, 由注入器程序进行 FindWindow 查找窗口并进行子类化。 本代码采用注入程序查找到窗口传递到dll的方式。
  3. 注意事项 : 在代码编译时注意编译器选项 /MT,否则可能会发生 DLL依赖错误,程序运行崩溃。

解决方法二: 以管理员权限运行程序

如果 DLL 注入方式仍然不可行,一种比较简单方法是以管理员权限运行EXE 。 在某些情况下,权限不足是导致子类化失败的常见原因,特别是在操作系统安全级别较高的情况下。这种做法赋予进程更广泛的操作权限,可以使 SetWindowSubclass 调用成功。 建议先测试第一种方案,并选择更优方案, 因为通常在windows 系统下是以普通权限运行程序为常态。

操作步骤:

  1. 右键点击程序的可执行文件,并选择 "以管理员身份运行"。
  2. 重新执行尝试进行子类化的操作。

注意事项 : 虽然这种方法简单快捷,但是并非推荐的最佳方案 ,因为它提高了程序本身的权限,可能会引入其他安全隐患。只适合简单的开发测试。在正式的应用开发中,应当尽可能遵循最小权限原则。

安全建议

  1. 权限管理: 在子类化过程中,请注意控制你的应用程序权限,只申请必要的权限。避免运行需要管理员权限的进程。
  2. 代码安全: 使用经过安全审查的代码,特别是当你使用DLL注入时,确保DLL文件没有恶意代码。
  3. 错误处理: 总是检查每个操作的结果,合理地处理异常。记录日志以进行调试,特别是与操作系统相关的 API 调用。

希望以上的分析和解决方案对你有所帮助。 请根据自己的具体需求选择合适的方法,并在实际使用中充分考虑安全因素。