5.7 Windows驱动开发:取进程模块函数地址

news2024/11/18 14:36:58

在笔者上一篇文章《内核取应用层模块基地址》中简单为大家介绍了如何通过遍历PLIST_ENTRY32链表的方式获取到32位应用程序中特定模块的基地址,由于是入门系列所以并没有封装实现太过于通用的获取函数,本章将继续延申这个话题,并依次实现通用版GetUserModuleBaseAddress()取远程进程中指定模块的基址和GetModuleExportAddress()取远程进程中特定模块中的函数地址,此类功能也是各类安全工具中常用的代码片段。

首先封装一个lyshark.h头文件,此类头文件中的定义都是微软官方定义好的规范,如果您想获取该结构的详细说明文档请参阅微软官方,此处不做过多的介绍。

#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>

// 导出未导出函数
NTKERNELAPI PPEB NTAPI PsGetProcessPeb(IN PEPROCESS Process);
NTKERNELAPI PVOID NTAPI PsGetProcessWow64Process(IN PEPROCESS Process);

typedef struct _PEB32
{
  UCHAR InheritedAddressSpace;
  UCHAR ReadImageFileExecOptions;
  UCHAR BeingDebugged;
  UCHAR BitField;
  ULONG Mutant;
  ULONG ImageBaseAddress;
  ULONG Ldr;
  ULONG ProcessParameters;
  ULONG SubSystemData;
  ULONG ProcessHeap;
  ULONG FastPebLock;
  ULONG AtlThunkSListPtr;
  ULONG IFEOKey;
  ULONG CrossProcessFlags;
  ULONG UserSharedInfoPtr;
  ULONG SystemReserved;
  ULONG AtlThunkSListPtr32;
  ULONG ApiSetMap;
} PEB32, *PPEB32;

typedef struct _PEB_LDR_DATA
{
  ULONG Length;
  UCHAR Initialized;
  PVOID SsHandle;
  LIST_ENTRY InLoadOrderModuleList;
  LIST_ENTRY InMemoryOrderModuleList;
  LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

typedef struct _PEB
{
  UCHAR InheritedAddressSpace;
  UCHAR ReadImageFileExecOptions;
  UCHAR BeingDebugged;
  UCHAR BitField;
  PVOID Mutant;
  PVOID ImageBaseAddress;
  PPEB_LDR_DATA Ldr;
  PVOID ProcessParameters;
  PVOID SubSystemData;
  PVOID ProcessHeap;
  PVOID FastPebLock;
  PVOID AtlThunkSListPtr;
  PVOID IFEOKey;
  PVOID CrossProcessFlags;
  PVOID KernelCallbackTable;
  ULONG SystemReserved;
  ULONG AtlThunkSListPtr32;
  PVOID ApiSetMap;
} PEB, *PPEB;

typedef struct _PEB_LDR_DATA32
{
  ULONG Length;
  UCHAR Initialized;
  ULONG SsHandle;
  LIST_ENTRY32 InLoadOrderModuleList;
  LIST_ENTRY32 InMemoryOrderModuleList;
  LIST_ENTRY32 InInitializationOrderModuleList;
} PEB_LDR_DATA32, *PPEB_LDR_DATA32;

typedef struct _LDR_DATA_TABLE_ENTRY32
{
  LIST_ENTRY32 InLoadOrderLinks;
  LIST_ENTRY32 InMemoryOrderLinks;
  LIST_ENTRY32 InInitializationOrderLinks;
  ULONG DllBase;
  ULONG EntryPoint;
  ULONG SizeOfImage;
  UNICODE_STRING32 FullDllName;
  UNICODE_STRING32 BaseDllName;
  ULONG Flags;
  USHORT LoadCount;
  USHORT TlsIndex;
  LIST_ENTRY32 HashLinks;
  ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY32, *PLDR_DATA_TABLE_ENTRY32;

typedef struct _LDR_DATA_TABLE_ENTRY
{
  LIST_ENTRY InLoadOrderLinks;
  LIST_ENTRY InMemoryOrderLinks;
  LIST_ENTRY InInitializationOrderLinks;
  PVOID DllBase;
  PVOID EntryPoint;
  ULONG SizeOfImage;
  UNICODE_STRING FullDllName;
  UNICODE_STRING BaseDllName;
  ULONG Flags;
  USHORT LoadCount;
  USHORT TlsIndex;
  LIST_ENTRY HashLinks;
  ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

5.7.1 取进程中模块基址

GetUserModuleBaseAddress 取进程中模块基址,该功能在《内核取应用层模块基地址》中详细介绍过原理,这段代码核心原理如下所示,此处最需要注意的是如果是32位进程则我们需要得到PPEB32 Peb32结构体,该结构体通常可以直接使用PsGetProcessWow64Process()这个内核函数获取到,而如果是64位进程则需要将寻找PEB的函数替换为PsGetProcessPeb(),其他的枚举细节与上一篇文章中的方法一致。

#include <ntifs.h>
#include <windef.h>
#include "lyshark.h"

// 获取特定进程内特定模块的基址
PVOID GetUserModuleBaseAddress(IN PEPROCESS EProcess, IN PUNICODE_STRING ModuleName, IN BOOLEAN IsWow64)
{
    if (EProcess == NULL)
        return NULL;
    __try
    {
        // 设置延迟时间为250毫秒
        LARGE_INTEGER Time = { 0 };
        Time.QuadPart = -250ll * 10 * 1000;

        // 如果是32位则执行如下代码
        if (IsWow64)
        {
            // 得到PEB进程信息
            PPEB32 Peb32 = (PPEB32)PsGetProcessWow64Process(EProcess);
            if (Peb32 == NULL)
            {
                return NULL;
            }

            // 延迟加载等待时间
            for (INT i = 0; !Peb32->Ldr && i < 10; i++)
            {
                KeDelayExecutionThread(KernelMode, TRUE, &Time);
            }

            // 没有PEB加载超时
            if (!Peb32->Ldr)
            {
                return NULL;
            }

            // 搜索模块 InLoadOrderModuleList
            for (PLIST_ENTRY32 ListEntry = (PLIST_ENTRY32)((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList.Flink; ListEntry != &((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList; ListEntry = (PLIST_ENTRY32)ListEntry->Flink)
            {
                UNICODE_STRING UnicodeString;
                PLDR_DATA_TABLE_ENTRY32 LdrDataTableEntry32 = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks);
                RtlUnicodeStringInit(&UnicodeString, (PWCH)LdrDataTableEntry32->BaseDllName.Buffer);

                // 找到了返回模块基址
                if (RtlCompareUnicodeString(&UnicodeString, ModuleName, TRUE) == 0)
                {
                    return (PVOID)LdrDataTableEntry32->DllBase;
                }
            }
        }
        // 如果是64位则执行如下代码
        else
        {
            // 同理,先找64位PEB
            PPEB Peb = PsGetProcessPeb(EProcess);
            if (!Peb)
            {
                return NULL;
            }

            // 延迟加载
            for (INT i = 0; !Peb->Ldr && i < 10; i++)
            {
                KeDelayExecutionThread(KernelMode, TRUE, &Time);
            }

            // 找不到PEB直接返回
            if (!Peb->Ldr)
            {
                return NULL;
            }

            // 遍历链表
            for (PLIST_ENTRY ListEntry = Peb->Ldr->InLoadOrderModuleList.Flink; ListEntry != &Peb->Ldr->InLoadOrderModuleList; ListEntry = ListEntry->Flink)
            {
                // 将特定链表转换为PLDR_DATA_TABLE_ENTRY格式
                PLDR_DATA_TABLE_ENTRY LdrDataTableEntry = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

                // 找到了则返回地址
                if (RtlCompareUnicodeString(&LdrDataTableEntry->BaseDllName, ModuleName, TRUE) == 0)
                {
                    return LdrDataTableEntry->DllBase;
                }
            }
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return NULL;
    }
    return NULL;
}

那么该函数该如何调用传递参数呢,如下代码是DriverEntry入口处的调用方法,首先要想得到特定进程的特定模块地址则第一步就是需要PsLookupProcessByProcessId找到模块的EProcess结构,接着通过PsGetProcessWow64Process得到当前被操作进程是32位还是64位,通过调用KeStackAttachProcess附加到进程内存中,然后调用GetUserModuleBaseAddress并传入需要获取模块的名字得到数据后返回给NtdllAddress变量,最后调用KeUnstackDetachProcess取消附加即可。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    HANDLE ProcessID = (HANDLE)7924;

    PEPROCESS EProcess = NULL;
    NTSTATUS Status = STATUS_SUCCESS;
    KAPC_STATE ApcState;

    DbgPrint("Hello lyshark \n");

    // 根据PID得到进程EProcess结构
    Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
    if (Status != STATUS_SUCCESS)
    {
        DbgPrint("获取EProcessID失败 \n");
        return Status;
    }

    // 判断目标进程是32位还是64位
    BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;

    // 验证地址是否可读
    if (!MmIsAddressValid(EProcess))
    {
        DbgPrint("地址不可读 \n");
        Driver->DriverUnload = UnDriver;
        return STATUS_SUCCESS;
    }

    // 将当前线程连接到目标进程的地址空间(附加进程)
    KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);

    __try
    {
        UNICODE_STRING NtdllUnicodeString = { 0 };
        PVOID NtdllAddress = NULL;

        // 得到进程内ntdll.dll模块基地址
        RtlInitUnicodeString(&NtdllUnicodeString, L"Ntdll.dll");
        NtdllAddress = GetUserModuleBaseAddress(EProcess, &NtdllUnicodeString, IsWow64);
        if (!NtdllAddress)
        {
            DbgPrint("没有找到基址 \n");
            Driver->DriverUnload = UnDriver;
            return STATUS_SUCCESS;
        }

        DbgPrint("[*] 模块ntdll.dll基址: %p \n", NtdllAddress);
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
    }

    // 取消附加
    KeUnstackDetachProcess(&ApcState);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

替换DriverEntry入口函数处的ProcessID并替换为当前需要获取的应用层进程PID,运行驱动程序即可得到该进程内Ntdll.dll的模块基址,输出效果如下;

5.7.2 取模块内函数基址

GetModuleExportAddress 实现获取特定模块中特定函数的基地址,通常我们通过GetUserModuleBaseAddress()可得到进程内特定模块的基址,然后则可继续通过GetModuleExportAddress()获取到该模块内特定导出函数的内存地址,至于获取导出表中特定函数的地址则可通过如下方式循环遍历导出表函数获取。

// 获取特定模块下的导出函数地址
PVOID GetModuleExportAddress(IN PVOID ModuleBase, IN PCCHAR FunctionName, IN PEPROCESS EProcess)
{
    PIMAGE_DOS_HEADER ImageDosHeader = (PIMAGE_DOS_HEADER)ModuleBase;
    PIMAGE_NT_HEADERS32 ImageNtHeaders32 = NULL;
    PIMAGE_NT_HEADERS64 ImageNtHeaders64 = NULL;
    PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;
    ULONG ExportDirectorySize = 0;
    ULONG_PTR FunctionAddress = 0;

    // 为空则返回
    if (ModuleBase == NULL)
    {
        return NULL;
    }

    // 是不是PE文件
    if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        return NULL;
    }

    // 获取NT头
    ImageNtHeaders32 = (PIMAGE_NT_HEADERS32)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);
    ImageNtHeaders64 = (PIMAGE_NT_HEADERS64)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);

    // 是64位则执行
    if (ImageNtHeaders64->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
    {
        ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
        ExportDirectorySize = ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    }
    // 是32位则执行
    else
    {
        ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
        ExportDirectorySize = ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    }

    // 得到导出表地址偏移和名字
    PUSHORT pAddressOfOrds = (PUSHORT)(ImageExportDirectory->AddressOfNameOrdinals + (ULONG_PTR)ModuleBase);
    PULONG  pAddressOfNames = (PULONG)(ImageExportDirectory->AddressOfNames + (ULONG_PTR)ModuleBase);
    PULONG  pAddressOfFuncs = (PULONG)(ImageExportDirectory->AddressOfFunctions + (ULONG_PTR)ModuleBase);

    // 循环搜索导出表
    for (ULONG i = 0; i < ImageExportDirectory->NumberOfFunctions; ++i)
    {
        USHORT OrdIndex = 0xFFFF;
        PCHAR  pName = NULL;

        // 搜索导出表下标索引
        if ((ULONG_PTR)FunctionName <= 0xFFFF)
        {
            OrdIndex = (USHORT)i;
        }
        // 搜索导出表名字
        else if ((ULONG_PTR)FunctionName > 0xFFFF && i < ImageExportDirectory->NumberOfNames)
        {
            pName = (PCHAR)(pAddressOfNames[i] + (ULONG_PTR)ModuleBase);
            OrdIndex = pAddressOfOrds[i];
        }
        else
        {
            return NULL;
        }

        // 找到设置返回值并跳出
        if (((ULONG_PTR)FunctionName <= 0xFFFF && (USHORT)((ULONG_PTR)FunctionName) == OrdIndex + ImageExportDirectory->Base) || ((ULONG_PTR)FunctionName > 0xFFFF && strcmp(pName, FunctionName) == 0))
        {
            FunctionAddress = pAddressOfFuncs[OrdIndex] + (ULONG_PTR)ModuleBase;
            break;
        }
    }
    return (PVOID)FunctionAddress;
}

如何调用此方法,首先将ProcessID设置为需要读取的进程PID,然后将上图中所输出的0x00007FF9553C0000赋值给BaseAddress接着调用GetModuleExportAddress()并传入BaseAddress模块基址,需要读取的LdrLoadDll函数名,以及当前进程的EProcess结构。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    HANDLE ProcessID = (HANDLE)4144;
    PEPROCESS EProcess = NULL;
    NTSTATUS Status = STATUS_SUCCESS;

    // 根据PID得到进程EProcess结构
    Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
    if (Status != STATUS_SUCCESS)
    {
        DbgPrint("获取EProcessID失败 \n");
        return Status;
    }

    PVOID BaseAddress = (PVOID)0x00007FF9553C0000;
    PVOID RefAddress = 0;

    // 传入Ntdll.dll基址 + 函数名 得到该函数地址
    RefAddress = GetModuleExportAddress(BaseAddress, "LdrLoadDll", EProcess);
    DbgPrint("[*] 函数地址: %p \n", RefAddress);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行这段程序,即可输出如下信息,此时也就得到了x64.exe进程内ntdll.dll模块里面的LdrLoadDll函数的内存地址,如下所示;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1252848.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国产航顺HK32F030M: 简易篮球计分器(便携计分器)

【自制】《基于航顺HKF030MF4P6手持比赛计分牌》&#xff08;便携计分器&#xff09; 1. 简介 便携篮球计分器是一种小型化设计的设备&#xff0c;主要用于记录和显示篮球比赛的得分和计时。以下是由Type-C充电电路TP5400/ASM1117电路、HK32F030MF4单片机最小系统、数码管显示…

小程序如何禁止指定用户访问?如何设置指定用户才能访问?

​有些商家为了价格保密或者实行严格的会员制等原因&#xff0c;希望小程序能够限制某些人的访问或者设置指定人员才能访问。这种功能在小程序中&#xff0c;怎么支持这些功能呢&#xff1f;下面具体介绍。 一、禁止指定用户访问 禁止指定用户访问&#xff0c;可以通过小程序…

论文笔记:详解NEUPSL DSI

《Using Domain Knowledge to Guide Dialog Structure Induction via Neural Probabilistic 》 名词解释 Dialog Structure Induction&#xff08;DSI&#xff09;是推断给定目标导向对话的潜在对话结构&#xff08;即一组对话状态及其时间转换&#xff09;的任务。它是现代对…

基于springboot+Web实现社区医院管理服务系统项目【项目源码+论文说明】计算机毕业设计

基于springbootWeb实现社区医院管理服务系统演示 摘要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括社区医院管理服务系统的网络应用&#xff0c;在外国线上管理系统已经是很普遍的方式&#xff0c;不过国内的管理系统可…

面试官:知道JVM中一次完整的 GC 流程吗

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

转行要趁早!盘点网络安全的岗位汇总

前段时间&#xff0c;知名机构麦可思研究院发布了《2023年中国本科生就业报告》&#xff0c;其中详细列出近五年的本科绿牌专业&#xff0c;信息安全位列第一。 对于网络安全的发展与就业前景&#xff0c;知了姐说过很多&#xff0c;作为当下应届生收入较高的专业之一&#xf…

栈和队列的实现(详解+图解!文末附完整代码)

栈 栈的基本概念 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&#xff1a;栈的…

【Mybatis】基础增删改查

一.创建SpringBoot项目 创建新项目需要添加的依赖 当然如果是以前的项目也可以直接在pom.xml文件中添加依赖: MySQL Driver依赖 <dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</…

如何通过 Al 的能力提升编程的效率?

通过人工智能&#xff08;AI&#xff09;的技术&#xff0c;可以提升编程效率和能力。以下是一些建议和方法&#xff1a; 代码自动生成&#xff1a;使用AI技术&#xff0c;可以根据程序员的需求和输入&#xff0c;自动生成代码。这可以提高编程效率&#xff0c;减少编写代码所需…

Fedora 36 ARM 镜像源更换与软件安装

1、什么是Fedora Fedora Linux是较具知名度的Linux发行套件之一&#xff0c;由Fedora专案社群开发、红帽公司赞助&#xff0c;目标是建立一套新颖、多功能并且自由的作业系统。 Fedora是商业化的Red Hat Enterprise Linux发行版的上游原始码。 2、Fedora软件安装 64 位 .deb&a…

Python pandas数据分析

Python pandas数据分析&#xff1a; 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&#xff0c;尤其…

Linux shell编程学习笔记29:shell自带的 脚本调试 选项

Linux shell脚本的调试方法比较多&#xff0c;上次我们探讨和测试了shell内建命令set所提供的一些调试选项&#xff0c;其实 shell 本身也提供了一些调试选项。我们以bash为例来看看。 1 bash 的命令行帮助信息&#xff08;bash --help&#xff09; purleEndurer csdn ~ $ ba…

带你用uniapp从零开发一个仿小米商场_3.animate.css动画库在uniapp中的使用

这篇文章仅做简单介绍animate.css动画库在uniapp中如何使用 animate.css动画库引入 怕有人没看专栏前面的文章,所以这里重新介绍一边animate.css动画库的引入,知道的可以跳过 可以在这里下载 animate.css动画库官网http://www.animate.net.cn/ 下载好animate.css后在项目根…

5.2每日一题(无穷级数敛散性:绝对收敛、比较法/比较法的极限形式、p级数)

一般看到绝对收敛和条件收敛——先看级数绝对值的敛散性

ubuntu修改系统语言

修改ubuntu系统语言 操作指令修改系统设置总结 操作 ubuntu系统自带的英文环境&#xff0c;个人觉得用起来不方便。改掉吧。换成中文 指令修改 参考了一些博客的解决方式 ctrlartT 打开终端。 sudo apt-get install language-pack-zh-hans 输入下载汉化包的指令。 但是&…

SpringMVC系列-7 @CrossOrigin注解与跨域问题

背景 前段时间帮同事分析了一个跨域问题&#xff0c;正好系统分析和整理一下。 1.跨域 理解同源策略是理解跨域的前提。同源策略定义如下&#xff1a; 在同一来源的页面和脚本之间进行数据交互时&#xff0c;浏览器会默认允许操作&#xff0c;而不会造成跨站脚本攻击&#x…

ImportError: No module named python_util.util

ImportError: No module named python_util.util 从师兄那里拷贝了一个python文件&#xff0c;运行报错ImportError: No module named python_util.util&#xff0c;python小白上网搜&#xff0c;并没有搜到有价值的答案。。。经过摸索&#xff0c;最后解决。 其实这就是缺少…

vue3+elementPlus之侧边菜单栏功能

选择默认的颜色&#xff0c;将代码拷贝至<el-aside>模块中 稍微把不需要的修改一下。 <template><div class"common-layout"><el-container><el-header class"homeHeader"><div class"headerTitle">Devops…

抖音权重查询源码H5源码

源码下载&#xff1a;123网盘

Linux的基本指令 ( 一 )

目录 前言 Linux基本指令 快速认识五个指令 ls指令 补充内容 pwd指令 补充内容 cd指令 补充内容 重新认识指令 指令的本质 which指令 alias指令 最后 一个文件的三种时间 tree指令及安装 tree指令 前言 关于Linux操作系统的桌面&#xff0c;在学校教学中我们…