吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7308|回复: 28
收起左侧

[系统底层] 【原创】TP驱动保护分析系列二 代码定位内核函数

  [复制链接]
lyl610abc 发表于 2021-7-4 22:00
本帖最后由 lyl610abc 于 2021-7-10 19:04 编辑

系列索引

【原创】TP驱动保护分析系列一 定位TenProtect保护

【原创】TP驱动保护分析系列二 代码定位内核函数

【原创】TP驱动保护分析系列三 SSDT定位内核函数


前言

前面在【原创】TP驱动保护分析系列一 定位TenProtect保护中分析并找到了TenProtect在内核层所做的手脚,但只通过windbg等工具找到可不够,接下来继续介绍代码定位内核函数的方法


代码定位内核函数

驱动函数直接定位

在驱动开发引入的<wdm.h>里提供了MmGetSystemRoutineAddress函数,根据函数名可以直接获取到已导出的函数地址

该函数的官方文档直达:MmGetSystemRoutineAddress function (wdm.h) - Windows drivers | Microsoft Docs

这里再简单介绍(翻译理解)一下:

函数原型

PVOID MmGetSystemRoutineAddress(
  PUNICODE_STRING SystemRoutineName
);
函数名 MmGetSystemRoutineAddress
函数功能 驱动程序可以使用这个Routine来确定一个Routine在特定版本的Windows上是否可用。它只能用于内核或HAL导出的例程,不能用于任何驱动程序定义的Routine
参数名 SystemRoutineName
参数名说明 想要获取的系统Routine的名称
参数类型 PUNICODE_STRING
参数类型说明 字符串,定义如下
typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
#ifdef MIDL_PASS
    [size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;
#else // MIDL_PASS
    _Field_size_bytes_part_opt_(MaximumLength, Length) PWCH   Buffer;
#endif // MIDL_PASS
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;

函数的使用

        UNICODE_STRING s;                                //声明字符串
        CHAR* string = L"RoutineName";        //要赋值的字符串 注意前面字符串前要加上L L告示编译器使用两个字节的 unicode 字符集。
        RtlInitUnicodeString(&s, string);                //初始化unicode字符串
        MmGetSystemRoutineAddress(&s);        //使用函数

支持直接定位的函数

前面提到了MmGetSystemRoutineAddress只能用于内核或HAL导出的例程,也就是只能获取到内核中的导出函数

如何知道内核导出了哪些函数?

找到C:\WINDOWS\system32\ntkrnlpa.exe的内核文件

PS:根据操作系统版本的不同,对应的内核文件也可能不同,如在虚拟机XP系统下内核文件为ntkrnlpa.exe,而在实机WIN10下对应的内核文件为ntoskrnl.exe

除此之外,还有两种内核文件:ntkrnlmp.exe 和ntkrpamp.exe

这四种内核文件的区别如下:

ntoskrnl - 单处理器,不支持PAE
ntkrnlpa - 单处理器,支持PAE
ntkrnlmp - 多处理器,不支持PAE
ntkrpamp - 多处理器,支持PAE

PAE全称Physical Address Extension,即物理地址拓展,是x86处理器的一个功能,让中央处理器在32位操作系统下访问超过4GB的物理内存


使用IDA Pro打开对应的内核文件,得到:

image-20210704164241143


点击右上角的Exports,查看导出(不仅包含导出的函数,也包含了导出的变量等):

image-20210704195047603


定位代码样例

下面以NtOpenProcess函数为例,通过MmGetSystemRoutineAddress定位函数地址


代码
#include "ntddk.h"

VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
        DbgPrint("卸载完成!\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
        UNICODE_STRING s;
        CHAR* string = L"NtOpenProcess";
        RtlInitUnicodeString(&s, string);
        PVOID address=MmGetSystemRoutineAddress(&s);
        DbgPrint("address:%p\n", address);

        DriverObject->DriverUnload = DriverUnload;

        return STATUS_SUCCESS;
}

运行结果

image-20210704184338648


验证结果

得到的地址为:805CC486

使用windbg查看对应地址的反汇编:

u 805CC486

image-20210704185306712

可以验证得到的就是NtOpenProcess的地址


特征码定位

当想要定位的内核函数没有导出,但其在可获得地址附近时,则可使用特征码定位法

所谓的特征码定位法,就是选一个地址作为搜索的起始地址,然后开始逐一比对 匹配的特征码(字节码)

PsTerminateProcess

下面以PsTerminateProcess函数为例,介绍特征码定位法


确定起始地址

要使用特征码定位PsTerminateProcess函数,首先要确定一个起始地址开始搜索该函数

于是使用IDA Pro查看PsTerminateProcess附近的函数:

image-20210704194254276


可以发现PsTerminateProcess函数的上面一个函数PsTerminateSystemThread正好是导出函数:

image-20210704194910107

于是便可直接使用PsTerminateSystemThread作为起始地址


确定特征码

确定了起始位置后,便要确定特征码了

所谓特征码:关键就是要突出特征,即最好能够保持unique(独一无二),并且稳定(固定不变)

使用Windbg查看PsTerminateProcess对应的反汇编

u PsTerminateProcess

得到:

image-20210704195553450


即:

nt!PsTerminateProcess:
805d35c2 8bff            mov     edi,edi
805d35c4 55              push    ebp
805d35c5 8bec            mov     ebp,esp
805d35c7 5d              pop     ebp
805d35c8 e9b5feffff      jmp     nt!PspTerminateProcess (805d3482)
805d35cd cc              int     3
805d35ce cc              int     3
805d35cf cc              int     3

这里选取的特征码为:8b ff 55 8b ec 5d e9

稍微说明一下为什么不选取jmp     nt!PspTerminateProcess指令后面的b5feffff

jmp XXXX 对应的 硬编码为 e9 offset,e9代表jmp,后面的offset为偏移地址

实际要跳转的地址(XXXX) =  当前地址 + offset +当前指令长度

代入这里: 要跳转的地址(805d3482) =  805d35c8(当前地址) + fffffeb5(offset 小端存储) + 5(当前指令长度)

即 805d3482 = 805d35c8 + fffffeb5+ 5

也就是 805d3482-805d35c8-5 = fffffeb5

这里的fffffeb5是一个有符号负数,可以直接用系统自带的计算器来验证

(关于有符号数、无符号数可回顾:逆向基础笔记二 数据宽度和逻辑运算)

image-20210704200816068

而偏移量是不固定的,即不满足特征码的稳定性要素,故不将后面字节码作为特征码


代码

确定了起始位置和特征码后,就可以写代码实现特征码定位了,代码如下:

#include "ntddk.h"

/*
        获取指定地址的数值
        base:要获取的地址
        offset:偏移量
        size:获取的大小
*/
PVOID GetAddrValue(PVOID* base, INT offset, INT size) {
        PVOID Addr = *base;
        PVOID templong = 0;
        //复制内存,将指定位置的内存的值读取出来
        RtlCopyMemory(&templong, (PUCHAR)Addr + offset, size);

        return templong;
}

VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
        DbgPrint("卸载完成!\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    //通过MmGetSystemRoutineAddress直接获取PsTerminateSystemThread地址
        UNICODE_STRING s;
        CHAR* string = L"PsTerminateSystemThread";
        RtlInitUnicodeString(&s, string);
        PVOID address = MmGetSystemRoutineAddress(&s);
        DbgPrint("PsTerminateSystemThread Address:%p\n", address);

    //将PsTerminateSystemThread作为起始地址
        ULONG beginAdress =(ULONG) address;
    //搜索范围限制为0x1000
        ULONG endAdress = beginAdress + 0x1000;
    //刚开始匹配地址为0,表示尚未匹配到
        PVOID matchAdress = 0;

    //开始循环比较 搜索特征码
        for (ULONG i=beginAdress;i< endAdress;i+=1)
        {
                PVOID value = GetAddrValue(&i, 0, 4);
                PVOID value2 = GetAddrValue(&i, 0, 3);
                //特征码为8b ff 55 8b ec 5d e9 小端存储
                if (value == 0x8b55ff8b && value2 == 0xe95dec) {
            //匹配特征码后 赋值,终止循环
                        matchAdress = i;
                        break;
                }
        }
        DbgPrint("PsTerminateProcess Address:%p\n", address);

        DriverObject->DriverUnload = DriverUnload;

        return STATUS_SUCCESS;
}

运行结果

image-20210704212022921


验证结果

得到的地址为:805D3594

使用windbg查看对应地址的反汇编:

u 805D3594

image-20210704212808362


可以验证得到的就是PsTerminateProcess的地址


总结

本篇介绍了代码定位内核函数的两种方法:MmGetSystemRoutineAddress直接定位和特征码定位

  • MmGetSystemRoutineAddress只能定位到导出的内核函数,比较局限,但胜在方便稳定
  • 特征码定位要注意特征码选取需要满足unique(独一无二)和稳定(固定不变)

限于篇幅,本章就先到这里,后续会再继续介绍SSDT定位法和符号表PDB解析法

PS:还有一种PE文件导出表扫描法在PE文件笔记十四 导出表中已说明过,就不再赘述了

在介绍完这些定位方法后,就会针对TenProtect实例进行分析定位并绕过,敬请期待( •̀ ω •́ )✧


附件

最后附上本篇中用到的用具和最后编译出的驱动文件

包括:

  1. 驱动加载工具:InstDrv 1.3 汉化版
  2. 调试信息查看工具:DbgView
  3. 特征码定位编译出的驱动文件:GetKernelAddress.sys

PS:特征码定位中用到了MmGetSystemRoutineAddress,故不单独提供驱动函数直接定位的驱动文件


DbgView可直接到微软官方下载:DebugView - Windows Sysinternals | Microsoft Docs

加载工具和驱动文件下载:点我下载


务必注意,驱动运行环境为Windows XP!!!WIN7和WIN7以上版本的操作系统不支持

学习交流为主,请勿用于其他非法途径

驱动加载工具和成品.zip

11.42 KB, 下载次数: 48, 下载积分: 吾爱币 -1 CB

论坛备份,土豪专用

免费评分

参与人数 14吾爱币 +19 热心值 +14 收起 理由
Nattevak + 2 + 1 我很赞同!
舒默哦 + 2 + 1 我很赞同!
Chenda1 + 1 + 1 我很赞同!
debug_cat + 2 + 1 终于更新
E家军.编程.无邪 + 1 + 1 我很赞同!
lvbuqing + 1 + 1 热心回复!
antclt + 1 + 1 谢谢@Thanks!
lu_ + 2 + 1 用心讨论,共获提升!
成熟的美羊羊 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
niucaidi + 1 + 1 盼星星盼月亮盼来了第二课
sam喵喵 + 1 + 1 谢谢@Thanks!
苏紫方璇 + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
侃遍天下无二人 + 1 + 1 我很赞同!
longling + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| lyl610abc 发表于 2021-7-5 20:29
Hmily 发表于 2021-7-5 18:30
@lyl610abc 建议文章添加一个上一课和下一课的链接,方便阅读查看系列文章。

已更新索引
popkun222 发表于 2021-11-2 22:26
选的特征码太依赖版本了。。。不同的MSVC的CL编译器编译出来的OS 的函数开始的指令肯定是不一样了。。思路很好。感谢分享。
longling 发表于 2021-7-4 22:22
侃遍天下无二人 发表于 2021-7-4 22:54
小幸姐修改器里引入的驱动是不是就实现了绕过TenProtect
sxwzxc 发表于 2021-7-5 00:03
强,虽然看不太懂,但我感觉一定很厉害
niucaidi 发表于 2021-7-5 00:33
我已经把整个网页包括上个网页撸下来了,请大家把保护打在公屏上
antclt 发表于 2021-7-5 08:56
强大,先留个印..
n1in 发表于 2021-7-5 09:05
tql,大佬汇编和pe都学完了,不打算学crackme啥的吗?
wan456 发表于 2021-7-5 09:36
跟着大佬一起学代码。对自己码农的不断肯定与提升,对于unique的唯一性是肯定的吗?
yingsen1983 发表于 2021-7-5 10:05

跟着大佬一起学代码。对自己码农的不断肯定与提升
lvbuqing 发表于 2021-7-5 10:05
快更新下一期,make了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-21 23:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表