反注入技术:防范非法 Call 调用的探讨

news/2025/1/30 14:45:49/

DLL 注入是一种常见的技术,用于向目标进程注入外部的动态链接库(DLL),以执行某些特定的操作。这种技术在恶意软件、游戏作弊等场景中被广泛使用,因此,研究和实施一些反注入技术对于提高应用程序的安全性是至关重要的。本文将介绍一种基于返回地址检测的反注入技术的实现,以防范非法的 DLL 注入。

1. 引言

在实际应用中,很多恶意注入行为都是通过劫持目标函数的调用来实现的。因此,通过检测调用栈上的返回地址,我们可以辨别调用者的身份,从而判断注入是否合法。

2. 技术细节

2.1 基本原理

通过使用 Detours 库,我们可以实现对目标函数的挂钩(hooking)。挂钩后,所有对目标函数的调用都会被重定向到我们指定的挂钩函数,这是作弊方可能会采用的一种方法。在目标函数返回前,我们可以进行一些检测,例如检查返回地址是否在合法的范围内,从而判断是否存在非法注入。

通过汇编原理我们知道,对于 call 指令其返回地址是下一条指令的地址,这会存储在程序计数寄存器(PC)中。以便于返回时,恢复控制流程。一个合法的程序过程调用,目标函数的返回地址应该位于合法的上级调用者函数的地址空间(栈帧)内,如果发生了外部注入,钩子例程通常会修改目标函数的开头几个字节,使得其导向攻击者所期望的函数入口地址上,并在执行相关处理后重新执行原函数,来达到劫持正常函数的返回值或处理流程的目的。但是,我们在原函数返回前(或者自己再实现一个返回地址导向挂钩)可以进行一些对返回地址的判断,如果程序被挂钩,返回地址在钩子函数的地址空间内,而不存在于正常例程的空间范围内。

利用返回地址检测可以在一定程度上辨别出非法的调用过程,但不是绝对和安全的防御,因为攻击者可以修改寄存器或者返回地址或者判断条件等来达到对安全手段绕过,本文只是作为一种普及讲解。并且为了安全起见,只给出最简单的判断流程,实际使用中需要更为精细化的检测流程。

2.2 实现步骤

以下是实现基于返回地址检测的反注入技术的主要步骤:

  1. 定义目标函数和挂钩函数: 在代码中定义目标函数(例如 TargetFunction)、挂钩函数(例如 HookedFunction)、返回地址计算函数、函数地址模式匹配函数。

  2. 初始化 Detours 库: 使用 Detours 库提供的函数初始化挂钩,通过模拟挂钩非法劫持原函数的执行流程。

    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&OriginalFunction, HookedFunction);
    DetourTransactionCommit();
    
  3. 调用目标函数: 此时,任何对目标函数的调用都会触发挂钩函数。

    int result = TargetFunction(42);
    
  4. 在原函数返回前执行检测流程:计算返回地址和合法的地址空间(mian 函数的入口地址到返回地址之间的空间)来判断是否存在非法调用。

    PVOID thisAddress = _ReturnAddress();
    size_t searchSize = 0x100;
    PVOID EndAddress = nullptr;
    if (!RelGetFunctionRange(thisAddress, &EndAddress, &searchSize))printf("NofoundAddress.\n");// 判断返回地址是否在内部调用方(main)范围内
    if (targetCallPtr <= lowerBounds || targetCallPtr >= uperBounds || classEndPtr != uperBounds)
    {printf("Target Function call is invalid!\n");
    }
    

  5. 卸载挂钩: 在检测完成后,卸载挂钩,使目标函数恢复正常调用。

    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourDetach(&OriginalFunction, HookedFunction);
    DetourTransactionCommit();
    

3. 方法验证

3.1 验证代码

#include <iostream>
#include <Windows.h>
#include <intrin.h>
#include "detours.h"#pragma intrinsic(_ReturnAddress)
#pragma comment(lib, "detours.lib")// 模拟原函数声明
typedef int(WINAPI* OriginalFunctionType)(int);// 导出函数
extern "C" {  // only need to export C interface if// used by C++ source code__declspec(dllexport) int main(); //主函数的声明__declspec(dllexport) int WINAPI TargetFunction(int param);
}// 原始函数指针
PVOID OriginalFunction = nullptr;typedef struct FuncAddressInfo {PVOID CallFunctionAddress;PVOID BaseAddress;BOOL IsValidCall;
}FuncAddressInfo;// 获取函数区间范围(用于非调试模式)
BOOL RelGetFunctionRange(LPVOID functionAddress,LPVOID* searchAddress,size_t* stackSearchSize
)
{// 计算结束位置unsigned char refCode[2] = { 0 };for (size_t i = 0; i < *stackSearchSize; i++){memcpy(&refCode, (unsigned char*)(functionAddress) + i, sizeof(refCode));switch (refCode[0]){case 0xc3:case 0xc2:if (refCode[1] == 0xcc){*stackSearchSize = i + 1;*searchAddress = reinterpret_cast<PVOID>(i + reinterpret_cast<DWORD>(functionAddress));}break;}if (*searchAddress != nullptr) break;}return TRUE;
}// 定义目标函数
__declspec(dllexport) int WINAPI TargetFunction(int param) {printf("TargetFunction called with param: %d\n",param);size_t searchSize = 0x100;PVOID EndAddress = nullptr;PVOID thisAddress = _ReturnAddress();if (!RelGetFunctionRange(thisAddress, &EndAddress, &searchSize))printf("NofoundAddress.\n");printf("lastCallAddress: 0x%01X  AtEndAddress: 0x%01X\n", reinterpret_cast<DWORD>(thisAddress),reinterpret_cast<DWORD>(EndAddress));// 获取 main 函数入口地址PVOID mainStartAddress = &main;// 计算函数出口地址PVOID mainEndAddress = nullptr;searchSize = 0x100;if (!RelGetFunctionRange(mainStartAddress, &mainEndAddress, &searchSize))printf("NofoundAddress.\n");printf("mainStartAddress: 0x%01X  mainEndAddress: 0x%01X\n", reinterpret_cast<DWORD>(mainStartAddress), reinterpret_cast<DWORD>(mainEndAddress));const DWORD lowerBounds = reinterpret_cast<DWORD>(mainStartAddress);const DWORD uperBounds = reinterpret_cast<DWORD>(mainEndAddress);const DWORD targetCallPtr = reinterpret_cast<DWORD>(thisAddress);const DWORD classEndPtr = reinterpret_cast<DWORD>(EndAddress);// 判断返回地址是否在内部调用方(main)范围内if (targetCallPtr <= lowerBounds || targetCallPtr >= uperBounds || classEndPtr != uperBounds){printf("Target Function call is invalid!\n");}else {printf("Target Function call is valid!\n");}return param * 2;
}// 挂钩函数
int WINAPI HookedFunction(int param) {// 进行钩子毒化处理// ... ...// // 调用原始函数int result = ((OriginalFunctionType)OriginalFunction)(param);// 在这里可以添加其他处理逻辑return result;
}__declspec(dllexport) int main() {// 获取要挂钩的目标函数地址OriginalFunction = &TargetFunction;// 初始化 Detours 库DetourTransactionBegin();DetourUpdateThread(GetCurrentThread());DetourAttach(&OriginalFunction, HookedFunction);DetourTransactionCommit();// 此时调用目标函数将触发挂钩int result = TargetFunction(42);// 卸载挂钩DetourTransactionBegin();DetourUpdateThread(GetCurrentThread());DetourDetach(&OriginalFunction, HookedFunction);DetourTransactionCommit();// 此时调用目标函数将正常执行流程result = TargetFunction(42);system("pause");return 0;
}

3.2 测试过程

运行就可以看出,挂钩时候 call 的下一条指令地址不在 mian 的地址范围内,并且回溯上级调用者的返回地址不是 mian 的返回地址。(这里以MSVC release 版本进行模式匹配的,debug 版本会有跳板函数,需要更多的处理才能获得地址,并且返回值的机器码不止 0xc2cc, 0xc3cc,我这里保留了热补丁区域,所以前后都有有 0xcc )

首先找到未挂钩时候的 call 返回地址(0x391269)和上级函数返回地址(0x39127F),位于 main 中:

和检测的结果比对(源代码里面用的是 0xc3cc,应该改成 0xc355 才对,所以检测跑后面去了,但不影响结果):

挂钩时的地址的分析是类似的,但需要挂断点,拦截脱钩过程,或者用 system("pause) 暂停一下。

4. 结论

通过在目标函数的调用路径上添加检测机制,我们可以有效地防范非法的 DLL 注入。这种技术对于保护应用程序免受外部注入的威胁具有一定的效果。然而,需要注意的是,这只是一种基础的防御机制,更高级的恶意注入技术可能需要更复杂的防御手段。

5. 扩展阅读

  • Microsoft Detours 官方文档
  • Capstone 反汇编库
  • Windows API 文档(_ReturnAddress)

更新于:2023.12.24


http://www.ppmy.cn/news/1280690.html

相关文章

大数据技术学习笔记(十一)—— Flume

目录 1 Flume 概述1.1 Flume 定义1.2 Flume 基础架构 2 Flume 安装3 Flume 入门案例3.1 监控端口数据3.2 实时监控单个追加文件3.3 实时监控目录下多个新文件3.4 实时监控目录下的多个追加文件 4 Flume 进阶4.1 Flume 事务4.2 Flume Agent 内部原理4.3 Flume 拓扑结构4.3.1 简单…

Python - 深夜数据结构与算法之 Heap Binary Heap

目录 一.引言 二.堆与二叉堆介绍 1.Heap 堆 2.Binary Heap 二叉堆 3.HeapifyUp 添加节点 4.HeapifyDown 删除节点 5.Heap 时间复杂度 6.Insert & Delete 代码实现 三.经典算法实战 1.Smallest-K [M14] 2.Sliding-Window-Max [239] 3.Ugly-Number [264] 4.Top-…

nodejs+vue+微信小程序+python+PHP的旅游景点推荐系统-计算机毕业设计推荐

本课题的主要内容包括管理员和用户两个部分&#xff0c;管理员负责旅游相关信息的管理&#xff0c;包括景点信息、用户的预订信息以及用户信息的管理&#xff1b;正是采用计算机技术和网络设计的新型系统&#xff0c;可以有效的把旅游信息与网络相结合&#xff0c;为用户提供旅…

Debezium发布历史25

原文地址&#xff1a; https://debezium.io/blog/2017/12/20/debezium-0-7-1-released/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. Debezium 0.7.1 发布 十二月 20, 2017 作者&#xff1a; Jiri Pechanec 发…

浅谈测试自动化selenium之POM模式

基于本人也是一个初学者&#xff0c;在运用POM模式的时候记录一下自己的学习笔记。 如果你是大神&#xff0c;那么可以略过&#xff0c;如果你是初学者&#xff0c;希望对你有帮助。 本文阐述了以下几个问题&#xff1a; 什么叫POM模式 为什么要用POM模式 POM模式的思想 POM模…

ZooKeeper Client API 安装及使用指北

下载 wget https://archive.apache.org/dist/zookeeper/zookeeper-3.5.4-beta/zookeeper-3.5.4-beta.tar.gz解压 tar -zxf zookeeper-3.5.4-beta.tar.gz安装 cd zookeeper-3.5.4-beta/src/c/ ./configure make sudo make install到 make 这一步大概率会出现报错&#xff1a;…

Spark编程语言选择:Scala、Java和Python

在大数据处理和分析领域&#xff0c;Apache Spark已经成为一种非常流行的工具。它提供了丰富的API和强大的性能&#xff0c;同时支持多种编程语言&#xff0c;包括Scala、Java和Python。选择合适的编程语言可以直接影响Spark应用程序的性能、可维护性和开发效率。在本文中&…

hadoop02_HDFS的API操作

HDFS的API操作 1 HDFS 核心类简介 Configuration类&#xff1a;处理HDFS配置的核心类。 FileSystem类&#xff1a;处理HDFS文件相关操作的核心类,包括对文件夹或文件的创建&#xff0c;删除&#xff0c;查看状态&#xff0c;复制&#xff0c;从本地挪动到HDFS文件系统中等。…