【windows】注入--BHO机制

什么是注入

定义:注入技术是指将外部代码(通常是DLL)或数据强制加载到目标进程的地址空间并执行的技术手段

简单来说,就是当前进程强制把一段代码 或者 是一些数据 加载到 目标进程 的地址空间 ,以此来控制目标进程的行为,达到当前进程的某种目的的一种技术手段。

那么,从上面定义中我们可以总结出下面几个问题:

  1. 为什么需要控制另一个进程?换句话说,为什么我们需要注入呢?
  2. 如何控制另一个进程执行自己设定好的代码?换句话说,如何注入?

带着上面两个问题,下面对windows 注入进行一个基本的了解和学习。

为什么需要注入?

听起来,注入貌似非常敏感和危险。那为什么windows 允许这一机制存在呢?
允许注入,本质上是因为操作系统需要为软件提供一种 "合法地打破进程隔离墙"​ 的通道。这不是疏忽,而是有意设计。核心原因有三点:

  1. 实现"无缝集成"的用户体验
    用户期望软件之间能深度协作。比如:
    右键菜单快速打开压缩文件工能,翻译软件可以直接翻译当前程序的语言等
    如果没有注入,这些功能要么无法实现,要么体验会变得极其笨拙(比如想要压缩一个文件还要先找到zip 这个程序)
  2. 提供系统级的扩展能力
    思考一下: 你在浏览器里打字、在Word里打字、在游戏里打字,为什么都能呼出同一个搜狗输入法或微软拼音?
    你打开浏览器,准备在搜索框打字。
    系统告诉输入法:"当前焦点是浏览器,它要输入了"。
    输入法框架(一个系统服务)会将输入法的核心组件(一个DLL)加载(注入)到浏览器的进程空间。
    于是,你在浏览器里按下的每一个字母,都会先被浏览器进程内部的这个输入法组件接收到,转换成汉字候选框,再画在浏览器的窗口上。
    你切换到Word,同样的过程再来一遍,输入法组件被注入到Word的进程里。
    如果没有这种"注入"机制,​ 每个软件都必须自己内置一套输入法处理逻辑,或者你只能使用操作系统自带的那一种,第三方输入法(搜狗、QQ拼音等)根本不可能存在。整个中文输入的生态就没了
  3. 满足底层开发和维护的刚性需求
    以我们开发人员最常用的工具调试器为例,为什么调试器(debugger)可以调试我们的程序?为什么打一个断点,进程就"停止"了?很显然,调试器控制了被调试的进程,无论是数据还是行为。
    从操作系统视角看,杀毒软件和安全监控工具,与调试器是同一类东西------它们都是"需要深入监视和控制其他程序行为的系统级程序"。
    一个没有注入机制的系统,杀毒软件将只能扫描静态文件,无法实时监控内存中的病毒行为。
    性能剖析、热修复补丁、无障碍辅助工具等大量底层工具都将失效。

如何注入

  1. 远程线程注入(最直接)

远程线程注入​是最直接、最经典的方法。其思路是"在目标进程的内部,创建一个完全听命于我的新线程"。这好比目标进程是一家公司,你无法从外部强行闯入。于是,你利用系统提供的"管理权",在这家公司内部直接"招聘"一个属于你的新员工(创建远程线程),并给他一份"立即加载并执行指定代码"的入职任务(调用LoadLibrary)。这个方法的优势是直接、通用,但由于其行为特征非常明显(创建远程线程),极易被现代安全软件检测和拦截。

观察下面代码示例

cpp 复制代码
// 1. 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);

// 2. 在目标进程中分配内存
LPVOID pRemoteMemory = VirtualAllocEx(hProcess, NULL, dllPathLength, 
                                       MEM_COMMIT, PAGE_READWRITE);

// 3. 写入DLL路径到目标进程
WriteProcessMemory(hProcess, pRemoteMemory, dllPath, dllPathLength, NULL);

// 4. 获取LoadLibraryA函数地址
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
LPTHREAD_START_ROUTINE loadLibAddr = (LPTHREAD_START_ROUINE)GetProcAddress(hKernel32, "LoadLibraryA");

// 5. 创建远程线程执行LoadLibraryA
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, loadLibAddr, pRemoteMemory, 0, NULL);

// 6. 等待线程执行完毕
WaitForSingleObject(hThread, INFINITE);
  1. DLL劫持(最隐蔽)

DLL劫持​则是一种更隐蔽的"欺骗"方法。**它不主动强行进入,而是"伪装成目标进程正在寻找的伙伴,让它自己开门请我进去"。许多程序启动时会按固定顺序在一些目录中寻找必需的DLL文件。**攻击者可以将恶意DLL命名为与某个合法DLL相同的名称,并将其放置在更优先被搜索的路径下(例如程序同级目录)。当目标进程启动时,它会"误认"这个恶意DLL为所需组件,并主动将其加载到自己的内存空间中。这种方法的成功依赖于程序不严谨的DLL加载逻辑,隐蔽性高,但并非对每个目标程序都有效。

观察下面代码:

cpp 复制代码
// 劫持逻辑很简单,主要是文件操作
// 1. 将你的malicious.dll重命名为target.exe需要加载的xxx.dll
// 2. 将原版xxx.dll重命名为xxx_orig.dll
// 3. 在你的malicious.dll中,转发调用到原版DLL

// malicious.cpp (你的恶意DLL)
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    if (fdwReason == DLL_PROCESS_ATTACH) {
        // 恶意代码在这里执行
        MessageBoxA(NULL, "成功劫持!", "提示", MB_OK);
    }
    return TRUE;
}

// 导出函数要转发到原版DLL
#pragma comment(linker, "/export:SomeFunction=xxx_orig.SomeFunction")
#pragma comment(linker, "/export:AnotherFunc=xxx_orig.AnotherFunc")
  1. APC注入(最巧妙)

APC注入​是一种更为"巧妙"的方法,它不创建新线程,而是"将任务塞进目标进程现有线程的待办事项队列"。每个线程都有一个异步过程调用队列。攻击者可以向目标进程的某个线程的APC队列中插入一个请求,这个请求通常是指向恶意代码的回调函数。当该线程进入"可警报状态"(例如调用SleepEx等函数)时,它便会自动执行队列中的这个任务。这种方法不需要创建新线程,行为相对隐蔽,但它依赖目标线程进入特定的、可被触发的状态,因此成功率不如前两种方法稳定。

观察下面代码示例

cpp 复制代码
// 1. 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);

// 2. 枚举目标进程的线程
HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
THREADENTRY32 te32 = { sizeof(THREADENTRY32) };
Thread32First(hThreadSnap, &te32);

// 3. 找到目标进程的线程
do {
    if (te32.th32OwnerProcessID == targetPid) {
        HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
        
        // 4. 分配内存并写入shellcode
        LPVOID pRemoteMem = VirtualAllocEx(hProcess, NULL, shellcodeSize, 
                                           MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        WriteProcessMemory(hProcess, pRemoteMem, shellcode, shellcodeSize, NULL);
        
        // 5. 向线程APC队列插入任务
        QueueUserAPC((PAPCFUNC)pRemoteMem, hThread, 0);
        
        CloseHandle(hThread);
    }
} while (Thread32Next(hThreadSnap, &te32));

BHO 注册机制

BHO(Browser Helper Object,浏览器帮助对象) 是微软提供的一种官方扩展机制,允许开发者将自己的 COM 组件注入到 Internet Explorer 和 Windows Explorer(资源管理器) 中。

与前面介绍的几种注入方式不同,BHO 是一种被动式的"合法注入"

  • 不需要主动攻击目标进程
  • 不需要绕过安全检测
  • 由操作系统主动将你的 DLL 加载到 Explorer 进程中

这就像是你在政府部门正式登记注册了一家公司,然后系统会自动把你的代表派驻到相关机构中工作。

4.2 BHO 的工作原理

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        BHO 加载流程                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Explorer.exe 启动                                           │
│         │                                                       │
│         ▼                                                       │
│  2. 系统读取注册表 BHO 键值                                      │
│     HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\             │
│     Explorer\Browser Helper Objects\{Your-CLSID}                │
│         │                                                       │
│         ▼                                                       │
│  3. 通过 CLSID 查找 COM 组件注册信息                             │
│     HKCR\CLSID\{Your-CLSID}\InprocServer32                      │
│         │                                                       │
│         ▼                                                       │
│  4. 系统调用 DllGetClassObject 获取类工厂                        │
│         │                                                       │
│         ▼                                                       │
│  5. 通过类工厂创建 BHO 对象实例                                  │
│         │                                                       │
│         ▼                                                       │
│  6. 调用 IObjectWithSite::SetSite(IWebBrowser2*)                │
│     ┌─────────────────────────────────────────┐                 │
│     │  此时你的代码已经运行在 Explorer 进程中!  │                 │
│     │  可以:                                  │                 │
│     │  • 订阅导航事件                          │                 │
│     │  • 监控文件夹切换                        │                 │
│     │  • 拦截用户操作                          │                 │
│     │  • 修改 Shell 行为                       │                 │
│     └─────────────────────────────────────────┘                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.3 注册表结构详解

BHO 的注册涉及两个关键的注册表位置:

4.3.1 BHO 注册位置(告诉系统"我是一个 BHO")
registry 复制代码
; 64位系统上的64位BHO
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{Your-CLSID-Here}
    (默认)    REG_SZ    (可选的描述)

; 64位系统上的32位BHO(WOW64)
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{Your-CLSID-Here}
4.3.2 COM 组件注册(告诉系统"如何加载我")
registry 复制代码
; COM 类注册
HKEY_CLASSES_ROOT\CLSID\{Your-CLSID-Here}
    (默认)    REG_SZ    YourClassName

; 指定 DLL 路径和线程模型
HKEY_CLASSES_ROOT\CLSID\{Your-CLSID-Here}\InprocServer32
    (默认)           REG_SZ    C:\Path\To\Your.dll
    ThreadingModel   REG_SZ    Apartment

; 类型库(可选)
HKEY_CLASSES_ROOT\CLSID\{Your-CLSID-Here}\TypeLib
    (默认)    REG_SZ    {Your-TypeLib-GUID}

4.4 代码实现

4.4.1 BHO 类定义
cpp 复制代码
// BHO 需要实现两个关键接口
class ATL_NO_VTABLE CMyExplorerBHO :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CMyExplorerBHO, &CLSID_MyExplorerBHO>,
    public IObjectWithSiteImpl<CMyExplorerBHO>,  // 关键接口1:接收Site
    public IDispatchImpl<IMyExplorerBHO>,
    public IDispEventImpl<1, CMyExplorerBHO, &DIID_DWebBrowserEvents2>  // 关键接口2:事件接收
{
public:
    CMyExplorerBHO() : m_dwCookie(0) {}

    DECLARE_REGISTRY_RESOURCEID(IDR_MYEXPLORERBHO)
    DECLARE_NOT_AGGREGATABLE(CMyExplorerBHO)

    BEGIN_COM_MAP(CMyExplorerBHO)
        COM_INTERFACE_ENTRY(IMyExplorerBHO)
        COM_INTERFACE_ENTRY(IDispatch)
        COM_INTERFACE_ENTRY(IObjectWithSite)
    END_COM_MAP()

    // 事件映射
    BEGIN_SINK_MAP(CMyExplorerBHO)
        SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2, OnNavigateComplete2)
        SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, OnBeforeNavigate2)
    END_SINK_MAP()

private:
    CComPtr<IWebBrowser2> m_spWebBrowser2;  // Explorer 的 IWebBrowser2 接口
    DWORD m_dwCookie;                        // 事件连接 Cookie

public:
    // 核心方法:系统调用此方法将 Explorer 接口传递给我们
    STDMETHOD(SetSite)(IUnknown* pUnkSite);

    // 事件处理
    void STDMETHODCALLTYPE OnNavigateComplete2(IDispatch* pDisp, VARIANT* URL);
    void STDMETHODCALLTYPE OnBeforeNavigate2(IDispatch* pDisp, VARIANT* URL,
        VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData,
        VARIANT* Headers, VARIANT_BOOL* Cancel);
};
4.4.2 SetSite 实现(BHO 的入口点)
cpp 复制代码
STDMETHODIMP CMyExplorerBHO::SetSite(IUnknown* pUnkSite)
{
    // 先断开之前的连接(如果有)
    if (m_spWebBrowser2)
    {
        DispEventUnadvise(m_spWebBrowser2);
        m_spWebBrowser2.Release();
    }

    // 保存新的 Site
    if (pUnkSite != nullptr)
    {
        // 获取 IWebBrowser2 接口 - 这是与 Explorer 交互的核心接口
        HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void**)&m_spWebBrowser2);
        if (SUCCEEDED(hr) && m_spWebBrowser2)
        {
            // 订阅导航事件
            hr = DispEventAdvise(m_spWebBrowser2);
            if (SUCCEEDED(hr))
            {
                // 此时,我们的代码已经成功"注入"到 Explorer 进程中
                // 可以开始监控和响应用户的文件浏览行为了
                OutputDebugString(L"[MyBHO] 成功连接到 Explorer!\n");
            }
        }
    }

    return IObjectWithSiteImpl<CMyExplorerBHO>::SetSite(pUnkSite);
}
4.4.3 事件处理示例
cpp 复制代码
// 导航完成事件 - 用户进入了一个新文件夹
void STDMETHODCALLTYPE CMyExplorerBHO::OnNavigateComplete2(IDispatch* pDisp, VARIANT* URL)
{
    if (URL && URL->vt == VT_BSTR)
    {
        CString strPath(URL->bstrVal);

        // 检查是否是"此电脑"
        if (strPath.Find(L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}") != -1)
        {
            OutputDebugString(L"[MyBHO] 用户打开了「此电脑」!\n");
            // 在这里可以执行自定义逻辑,比如弹出提示、收集信息等
        }

        // 检查是否是特定文件夹
        if (strPath.Find(L"C:\\Windows\\System32") != -1)
        {
            OutputDebugString(L"[MyBHO] 用户正在浏览系统目录!\n");
        }
    }
}

// 导航前事件 - 可以拦截或修改导航行为
void STDMETHODCALLTYPE CMyExplorerBHO::OnBeforeNavigate2(
    IDispatch* pDisp, VARIANT* URL, VARIANT* Flags,
    VARIANT* TargetFrameName, VARIANT* PostData,
    VARIANT* Headers, VARIANT_BOOL* Cancel)
{
    if (URL && URL->vt == VT_BSTR)
    {
        CString strPath(URL->bstrVal);

        // 示例:阻止访问某些路径
        if (strPath.Find(L"Forbidden") != -1)
        {
            *Cancel = VARIANT_TRUE;  // 取消导航
            MessageBox(NULL, L"访问被阻止!", L"提示", MB_OK | MB_ICONWARNING);
        }
    }
}

4.5 注册与卸载

4.5.1 DLL 自注册函数
cpp 复制代码
// DllRegisterServer - 注册 COM 组件和 BHO
STDAPI DllRegisterServer(void)
{
    HRESULT hr = _AtlModule.DllRegisterServer();
    if (FAILED(hr))
        return hr;

    // 注册为 BHO
    HKEY hKey;
    CString strBHOKey = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects\\";
    strBHOKey += L"{Your-CLSID-Here}";

    if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, strBHOKey, 0, NULL,
        REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS)
    {
        RegCloseKey(hKey);
    }

    return S_OK;
}

// DllUnregisterServer - 卸载
STDAPI DllUnregisterServer(void)
{
    // 删除 BHO 注册
    CString strBHOKey = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects\\";
    strBHOKey += L"{Your-CLSID-Here}";
    RegDeleteKey(HKEY_LOCAL_MACHINE, strBHOKey);

    return _AtlModule.DllUnregisterServer();
}
4.5.2 手动注册(使用 regsvr32)
batch 复制代码
:: 注册 BHO
regsvr32 /s "C:\Path\To\YourBHO.dll"

:: 卸载 BHO
regsvr32 /u /s "C:\Path\To\YourBHO.dll"

:: 注册后需要重启 Explorer 才能生效
taskkill /f /im explorer.exe
start explorer.exe

4.6 BHO 的优势

特性 BHO 远程线程注入 DLL劫持
合法性 官方支持的机制 可能被视为恶意行为 通常被视为攻击手段
稳定性 由系统管理生命周期 需自行管理 依赖目标程序
隐蔽性 完全合法,不触发告警 极易被检测 可能被检测
维护成本 低,遵循 COM 规范即可 高,需处理各种边界情况 中等
适用场景 Shell 扩展、安全软件 通用注入 特定目标攻击

4.7 实际应用场景

  1. 安全软件:监控用户的文件浏览行为,实时扫描打开的文件夹
  2. Shell 扩展:在"此电脑"中添加自定义图标或功能入口
  3. 企业管理:监控员工访问的网络路径,审计文件操作
  4. 功能增强:为资源管理器添加标签页、快捷操作等功能

五、总结

本文介绍了 Windows 下的几种主流注入技术:

  1. 远程线程注入:直接但容易被检测
  2. DLL 劫持:隐蔽但依赖目标程序的漏洞
  3. APC 注入:巧妙但成功率不稳定
  4. BHO 注册:官方支持、稳定可靠、最适合 Shell 扩展场景

对于需要与 Windows Explorer 集成的应用(如安全软件、Shell 扩展),BHO 是最推荐的方案。它不仅是微软官方支持的机制,而且具有最高的稳定性和兼容性。

相关推荐
XLYcmy2 小时前
一个用于统计文本文件行数的Python实用工具脚本
开发语言·数据结构·windows·python·开发工具·数据处理·源代码
程序员徐师兄2 小时前
Windows JDK17 下载安装教程,附详细图文
java·windows·jdk17 下载安装·java17 下载安装教程
80530单词突击赢2 小时前
C++STL list实现揭秘
windows
myjie05273 小时前
使用 windows ndk-stack 分析NDK crash
windows
小邓睡不饱耶4 小时前
使用Scala实现手机号码归属地查询系统
开发语言·windows·scala
0白露4 小时前
关闭搜狗输入法右下角广告,可以适用于大多数应用系统通知的广告
windows·bug
欧阳x天5 小时前
STL讲解(七)——list容器的模拟实现
c++·windows·list
嵩山小老虎14 小时前
Windows 10/11 安装 WSL2 并配置 VSCode 开发环境(C 语言 / Linux API 适用)
linux·windows·vscode
AndyHeee18 小时前
【windows使用TensorFlow,GPU无法识别问题汇总,含TensorFlow完整安装过程】
人工智能·windows·tensorflow