驱动开发:PE导出函数与RVA转换

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 在笔者上篇文章`《驱动开发:内核扫描SSDT挂钩状态》`中简单介绍了如何扫描被挂钩的SSDT函数,并简单介绍了如何解析导出表,本章将继续延申PE导出表的解析,实现一系列灵活的解析如通过传入函数名解析出函数的RVA偏移,ID索引,Index下标等参数,并将其封装为可直接使用的函数,以在后期需要时可以被直接引用,同样为了节约篇幅本章中的`LoadKernelFile()`内存映射函数如需要使用请去前一篇文章中自行摘取。

在笔者上篇文章《驱动开发:内核扫描SSDT挂钩状态》中简单介绍了如何扫描被挂钩的SSDT函数,并简单介绍了如何解析导出表,本章将继续延申PE导出表的解析,实现一系列灵活的解析如通过传入函数名解析出函数的RVA偏移,ID索引,Index下标等参数,并将其封装为可直接使用的函数,以在后期需要时可以被直接引用,同样为了节约篇幅本章中的LoadKernelFile()内存映射函数如需要使用请去前一篇文章中自行摘取。

首先实现GetRvaFromModuleName()函数,当用户传入参数后自动将函数名解析为对应的RVA偏移或Index下标索引值,该函数接收三个参数传递,分别是wzFileName模块名,FunctionName所在模块内的函数名,Flag标志参数,函数输出ULONG64类型的数据。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

// 从指定模块中得到特定函数的RVA或相对序号相对偏移
ULONG64 GetRvaFromModuleName(WCHAR *wzFileName, UCHAR *FunctionName, INT Flag)
{
   
   
    // 加载内核模块
    PVOID BaseAddress = LoadKernelFile(wzFileName);

    // 取出导出表
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeaders;
    PIMAGE_SECTION_HEADER pSectionHeader;
    ULONGLONG FileOffset;
    PIMAGE_EXPORT_DIRECTORY pExportDirectory;

    // DLL内存数据转成DOS头结构
    pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;

    // 取出PE头结构
    pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);

    // 判断PE头导出表是否为空
    if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
    {
   
   
        return 0;
    }

    // 取出导出表偏移
    FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

    // 取出节头结构
    pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
    PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;

    // 遍历节结构进行地址运算
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }

    // 导出表地址
    pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);

    // 取出导出表函数地址
    PULONG AddressOfFunctions;
    FileOffset = pExportDirectory->AddressOfFunctions;

    // 遍历节结构进行地址运算
    pSectionHeader = pOldSectionHeader;
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }
    AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);

    // 取出导出表函数名字
    PUSHORT AddressOfNameOrdinals;
    FileOffset = pExportDirectory->AddressOfNameOrdinals;

    // 遍历节结构进行地址运算
    pSectionHeader = pOldSectionHeader;
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }
    AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);

    // 取出导出表函数序号
    PULONG AddressOfNames;
    FileOffset = pExportDirectory->AddressOfNames;

    // 遍历节结构进行地址运算
    pSectionHeader = pOldSectionHeader;
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }
    AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);

    // 分析导出表
    ULONG uOffset;
    LPSTR FunName;
    ULONG uAddressOfNames;
    ULONG TargetOff = 0;

    for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
    {
   
   
        uAddressOfNames = *AddressOfNames;
        pSectionHeader = pOldSectionHeader;
        for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
        {
   
   
            if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
            {
   
   
                uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
            }
        }
        FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);

        // 如果找到则返回RVA
        if (!_stricmp((const char *)FunctionName, FunName))
        {
   
   
            // 等于1则返回RVA
            if (Flag == 1)
            {
   
   
                TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
                // DbgPrint("索引 [ %p ] 函数名 [ %s ] 相对RVA [ %p ] \n", *AddressOfNameOrdinals, FunName, TargetOff);
                return TargetOff;
            }
            // 返回索引
            else if (Flag == 0)
            {
   
   
                return *AddressOfNameOrdinals;
            }
        }
    }

    // 结束后释放内存
    ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
    return 0;
}

调用该函数很容易,传入模块路径以及该模块内的函数名,解析出RVA地址或Index下标。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
   
   
    // 函数分别传入 [模块路径,函数名,标志=1] 返回该导出函数的RVA
    ULONG64 get_rva = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 1);
    DbgPrint("NtReadFile RVA = %p \n", get_rva);

    // 函数分别传入 [模块路径,函数名,标志=0] 返回该导出函数的ID下标
    ULONG64 get_id = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 0);
    DbgPrint("NtReadFile ID = %d \n", get_id);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

编译并运行程序,分别获取到ntoskrnl.exe模块内NtReadFile函数的RVA,Index索引,调用效果如下;

image.png

第二个函数GetModuleNameFromRVA()则实现传入RVA或者函数Index序号,解析出函数名,具体实现方法与如上函数基本一致,仅仅只是在过滤时做了调整。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

// 根据传入的函数RVA或Index下标,获取该函数的函数名
PCHAR GetModuleNameFromRVA(WCHAR *wzFileName, ULONG64 uRVA, INT Flag)
{
   
   
    // 加载内核模块
    PVOID BaseAddress = LoadKernelFile(wzFileName);

    // 取出导出表
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeaders;
    PIMAGE_SECTION_HEADER pSectionHeader;
    ULONGLONG FileOffset;
    PIMAGE_EXPORT_DIRECTORY pExportDirectory;

    // DLL内存数据转成DOS头结构
    pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;

    // 取出PE头结构
    pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);

    // 判断PE头导出表是否为空
    if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
    {
   
   
        return 0;
    }

    // 取出导出表偏移
    FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

    // 取出节头结构
    pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
    PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;

    // 遍历节结构进行地址运算
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }

    // 导出表地址
    pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);

    // 取出导出表函数地址
    PULONG AddressOfFunctions;
    FileOffset = pExportDirectory->AddressOfFunctions;

    // 遍历节结构进行地址运算
    pSectionHeader = pOldSectionHeader;
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }
    AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);

    // 取出导出表函数名字
    PUSHORT AddressOfNameOrdinals;
    FileOffset = pExportDirectory->AddressOfNameOrdinals;

    // 遍历节结构进行地址运算
    pSectionHeader = pOldSectionHeader;
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }
    AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);

    // 取出导出表函数序号
    PULONG AddressOfNames;
    FileOffset = pExportDirectory->AddressOfNames;

    // 遍历节结构进行地址运算
    pSectionHeader = pOldSectionHeader;
    for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
    {
   
   
        if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
        {
   
   
            FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
        }
    }
    AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);

    // 分析导出表
    ULONG uOffset;
    LPSTR FunName;
    ULONG uAddressOfNames;
    ULONG TargetOff = 0;

    for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
    {
   
   
        uAddressOfNames = *AddressOfNames;
        pSectionHeader = pOldSectionHeader;
        for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
        {
   
   
            if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
            {
   
   
                uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
            }
        }

        FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);
        TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];

        // 等于1则通过RVA返回函数名
        if (Flag == 1)
        {
   
   
            if (uRVA == TargetOff)
            {
   
   
                return FunName;
            }
        }
        // 返回索引
        else if (Flag == 0)
        {
   
   
            if (uRVA == *AddressOfNameOrdinals)
            {
   
   
                return FunName;
            }
        }
    }

    // 结束后释放内存
    ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
    return "None";
}

调用GetModuleNameFromRVA()并传入相应的RVA偏移或Index下标。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
   
   
    DbgPrint("hello lyshark.com \n");

    PCHAR function_name;

    // 传入函数RVA得到函数名
    function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 0x5e5220, 1);
    DbgPrint("根据RVA得到函数名 = %s \n", function_name);

    // 传入函数下标得到函数名
    function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 1472, 0);
    DbgPrint("根据Index得到函数名 = %s \n", function_name);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

编译并运行程序,调用后分别获取到RVA=0x5e5220Index=1472的函数名;

image.png

目录
相关文章
|
7月前
|
编译器 C++ Windows
9.4 Windows驱动开发:内核PE结构VA与FOA转换
本章将继续探索内核中解析PE文件的相关内容,PE文件中FOA与VA,RVA之间的转换也是很重要的,所谓的FOA是文件中的地址,VA则是内存装入后的虚拟地址,RVA是内存基址与当前地址的相对偏移,本章还是需要用到`《内核解析PE结构导出表》`中所封装的`KernelMapFile()`映射函数,在映射后对其PE格式进行相应的解析,并实现转换函数。
44 0
9.4 Windows驱动开发:内核PE结构VA与FOA转换
|
7月前
|
存储 Windows
9.2 Windows驱动开发:内核解析PE结构导出表
在笔者的上一篇文章`《内核特征码扫描PE代码段》`中`LyShark`带大家通过封装好的`LySharkToolsUtilKernelBase`函数实现了动态获取内核模块基址,并通过`ntimage.h`头文件中提供的系列函数解析了指定内核模块的`PE节表`参数,本章将继续延申这个话题,实现对PE文件导出表的解析任务,导出表无法动态获取,解析导出表则必须读入内核模块到内存才可继续解析,所以我们需要分两步走,首先读入内核磁盘文件到内存,然后再通过`ntimage.h`中的系列函数解析即可。
50 0
9.2 Windows驱动开发:内核解析PE结构导出表
|
7月前
|
C语言 Windows
9.3 Windows驱动开发:内核解析PE结构节表
在笔者上一篇文章`《内核解析PE结构导出表》`介绍了如何解析内存导出表结构,本章将继续延申实现解析PE结构的PE头,PE节表等数据,总体而言内核中解析PE结构与应用层没什么不同,在上一篇文章中`LyShark`封装实现了`KernelMapFile()`内存映射函数,在之后的章节中这个函数会被多次用到,为了减少代码冗余,后期文章只列出重要部分,读者可以自行去前面的文章中寻找特定的片段。
31 0
9.3 Windows驱动开发:内核解析PE结构节表
|
Linux Windows
PE格式:实现ELF结构解析工具
ELF文件格式,是一个开放的可执行文件和链接文件格式,其主要工作在Linux系统上,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,ELF文件格式类似于PE格式,但比起PE结构来ELF结构显得更加的简单,Linux文件结构相比于Windows结构来说简单一些.
210 0
|
安全
驱动开发:取进程模块的函数地址
在笔者上一篇文章`《驱动开发:内核取应用层模块基地址》`中简单为大家介绍了如何通过遍历`PLIST_ENTRY32`链表的方式获取到`32位`应用程序中特定模块的基地址,由于是入门系列所以并没有封装实现太过于通用的获取函数,本章将继续延申这个话题,并依次实现通用版`GetUserModuleBaseAddress()`取远程进程中指定模块的基址和`GetModuleExportAddress()`取远程进程中特定模块中的函数地址,此类功能也是各类安全工具中常用的代码片段。
195 0
|
编译器 C++
驱动开发:内核PE结构VA与FOA转换
本章将继续探索内核中解析PE文件的相关内容,PE文件中FOA与VA,RVA之间的转换也是很重要的,所谓的FOA是文件中的地址,VA则是内存装入后的虚拟地址,RVA是内存基址与当前地址的相对偏移,本章还是需要用到`《驱动开发:内核解析PE结构导出表》`中所封装的`KernelMapFile()`映射函数,在映射后对其PE格式进行相应的解析,并实现转换函数。
210 0
|
存储 Windows
驱动开发:内核解析PE结构导出表
在笔者的上一篇文章`《驱动开发:内核特征码扫描PE代码段》`中`LyShark`带大家通过封装好的`LySharkToolsUtilKernelBase`函数实现了动态获取内核模块基址,并通过`ntimage.h`头文件中提供的系列函数解析了指定内核模块的`PE节表`参数,本章将继续延申这个话题,实现对PE文件导出表的解析任务,导出表无法动态获取,解析导出表则必须读入内核模块到内存才可继续解析,所以我们需要分两步走,首先读入内核磁盘文件到内存,然后再通过`ntimage.h`中的系列函数解析即可。
218 0
|
C语言 Windows
驱动开发:内核解析PE结构节表
在笔者上一篇文章`《驱动开发:内核解析PE结构导出表》`介绍了如何解析内存导出表结构,本章将继续延申实现解析PE结构的PE头,PE节表等数据,总体而言内核中解析PE结构与应用层没什么不同,在上一篇文章中`LyShark`封装实现了`KernelMapFile()`内存映射函数,在之后的章节中这个函数会被多次用到,为了减少代码冗余,后期文章只列出重要部分,读者可以自行去前面的文章中寻找特定的片段。
178 0
|
Windows
驱动开发:内核特征码扫描PE代码段
在笔者上一篇文章`《驱动开发:内核特征码搜索函数封装》`中为了定位特征的方便我们封装实现了一个可以传入数组实现的`SearchSpecialCode`定位函数,该定位函数其实还不能算的上简单,本章`LyShark`将对特征码定位进行简化,让定位变得更简单,并运用定位代码实现扫描内核PE的`.text`代码段,并从代码段中得到某个特征所在内存位置。
292 1
驱动开发:内核特征码扫描PE代码段
VC通过函数名调用DLL的标准范例
VC通过函数名调用DLL的标准范例
57 0