首页
论坛
课程
招聘
[原创]利用IoDriverObjectType控制内核驱动加载
2020-11-20 13:57 1128

[原创]利用IoDriverObjectType控制内核驱动加载

2020-11-20 13:57
1128

在edr或者其他类型的安全软件我们通常要监测当前系统的内核驱动的加载,通常使用的方法是PsSetLoadImageNotifyRoutine设置模块加载回调例程来监控ring3模块以及ring0模块的加载,回调函数 eLoadImageNotifyRoutine 的第二个参数判断,如果 PID是0 ,则表示加载驱动,如果PID非零,则表示加载DLL。此方法的优点是:

 

更底层
方法简单通用

 

缺点当然也就是函数太底层,第二就是方法太通用几乎做过进程、线程监控的搞安全内核开发的人基本都晓得,也很容易被发现,而且也会被摘链,而失效。第三到回调函数这步骤的时候有可能内核已经被加载,被加载的内核驱动的入口点已经执行完毕。
本篇文章将会探索一种新方法去监测并且控制内核模块的加载。首先我们要讲解内核加载驱动的过程。
写一个demo的驱动,然后使用VMware双机调试来调试驱动。(VMware双机调试的方法如果不会可以baidu)

 

连接被调试虚拟机后,在windbg里输入sxe ld demo驱动的名字.sys

 

 

然后go,如果加载系统要加载这个驱动windbg会自动停下来。

 

 

然后输入kb

 

 

可以看到内核里加载的时候会开启一个单独的线程去加载驱动

RetAddr : Args to Child : Call Site

00 fffff80004b1748d : fffff8800456b8a0 fffff880031ac0d0 0000000000000001 fffff80004b74dfe : nt!DebugService2+0x5 01 fffff80004b74ecb : fffff880031ac000 fffffa80016de070 fffff8800456b9b8 0000000000000007 : nt!DbgLoadImageSymbols+0x4d
02 fffff80004e47bfd : fffffa8000eeee20 fffff8a00000001c fffff80004d84a30 fffff8800456b888 : nt!DbgLoadImageSymbolsUnicode+0x2b 03 fffff80004e6286b : fffff880031ac000 fffff8800456b8f8 0000000000000000 fffff8800456b8d8 : nt!MiDriverLoadSucceeded+0x2bd
04 fffff80004e64ebd : fffff8800456b9b8 0000000000000000 0000000000000000 0000000000000000 : nt!MmLoadSystemImage+0x80b 05 fffff80004e65875 : 0000000000000001 0000000000000000 0000000000000000 fffffa800231c1e0 : nt!IopLoadDriver+0x44d
06 fffff80004a8b161 : fffff80000000000 ffffffff8000077c fffff80004e65820 fffffa80006db040 : nt!IopLoadUnloadDriver+0x55 07 fffff80004d21166 : 0000000000000000 fffffa80006db040 0000000000000080 fffffa80006b71d0 : nt!ExpWorkerThread+0x111
08 fffff80004a5c486 : fffff80004bf6e80 fffffa80006db040 fffffa80006da680 0000000000000000 : nt!PspSystemThreadStartup+0x5a 09 0000000000000000 : fffff8800456c000 fffff88004566000 fffff8800456ae60 0000000000000000 : nt!KiStartSystemThread+0x16
这是调试的时候被断点断下来的堆栈,我们需要回到加载驱动的地方,所以要打开源代码,在驱动的入口点DriverEntry按F9设置断点。

 

 

然后f5继续执行,之后就会停在驱动的入口点

 

 

紫红色表示已经运行到断点位置。
当我们使用!process 命令时会看到当前上下文是system

 

再次使用kb可以发现现在执行到入口点的栈的上下文是

 

 

01 fffff8800456b960 fffff80004e65875 nt!IopLoadDriver+0xa07
02 fffff8800456bc30 fffff80004a8b161 nt!IopLoadUnloadDriver+0x55
03 fffff8800456bc70 fffff80004d21166 nt!ExpWorkerThread+0x111
04 fffff8800456bd00 fffff80004a5c486 nt!PspSystemThreadStartup+0x5a
05 fffff8800456bd40 0000000000000000 nt!KiStartSystemThread+0x16
可以发现在 nt!IopLoadDriver+0xa07的位置是执行入口点
使用U命令,可以查看汇编代码

 

fffff80004e6546e 488bd6 mov rdx,rsi
fffff80004e65471 488bcb mov rcx,rbx
fffff80004e65474 ff5358 call qword ptr [rbx+58h]
fffff80004e65477 4c8b15627bdaff mov r10,qword ptr [nt!PnpEtwHandle (fffff80004c0cfe0)]
fffff80004e6547e 8bf8 mov edi,eax
call qword ptr [rbx+58h]这句代码就是执行被加载驱动的模块入口点函数

 

看汇编代码第一个参数rcx就是rbx,大致我们可以明确的就是rbx就是DriverEntry的DRIVER_OBJECT参数,所以就有了rbx+58h就是DRIVER_OBJECT的DriverInit,为了印证我们的猜测,在IDA下看rbx就是DRIVER_OBJECT的结构体,而这里的call执行的就是DriverInit

 

 

Call v29->DriverInit(v29, v32);

 

有了这样的过程,我们是不是就可以探索新的方法去控制驱动的加载呢?答案是肯定的。查看IDA,可以发现在执行驱动入口点之前这个过程内核会处理很多东西,比如分配内存啊,创建驱动的内核对象啊,驱动的权限的判断,创建内核镜像啊等等操作,凡是可以控制的地方我们都可以研究下,今天我们主要研究是内核Object这个东西,众所周知windows内部管理着很多的Object,windows专门有个内核对象管理器,我们通常说的文件、进程、线程、管道、油槽、内核Image等等都属于Object,windows在使用额时候总是会先CreateObject 然后在插入这个Object到object管理器中,成功了才会继续执行,所以在加载内核模块镜像的时候也一定会创建一个Object,然后在插入这个对象。

 

因为DRIVER_OBEJCT就是一个Object,我们可以通过追踪DRIVER_OBEJCT的生成来劫持控制驱动的加载。

 

查看IDA分析过程,对IopLoadDriver的函数分析可以发现,在调用入口点之上确实有个ObInsertObject的函数,而且该函数插入的就是DERIVER_OBJECT对象

 

v10 = ObInsertObject(v21, 0i64, 1u, 0, 0i64, &Handle);

 

有了这个函数我们就可以控制对象了,怎么控制呢?答案很简单,对象的回调函数。

 

ObInsertObject的函数内部是会经过每种对象类型对象的回调函数设置的。下面分析怎么到达过滤回调callback的。

 

在ObInsertObject内部会先调用ObInsertObjectEx函数

 

在ObInsertObjectEx内部会调用ObpCreateHandle

 

 

此时的第一个参数是0,而在ObpCreateHandle函数内部会调用
v51 = ObpPreInterceptHandleCreate(Objecta, Attributes, &v70, &ThreadCallbackListHead);

 

 

ObpPreInterceptHandleCreate函数就是我之前说的在调用当前对象类型的callback函数。

 

所以调用路径是

 

 

有ObInsertObject就一定会有ObCreateObject这个函数,往上继续翻阅就会看到

 

v10 = ObCreateObject(
KeGetCurrentThread()->PreviousMode,
IoDriverObjectType,
&ObjectAttributes,
0,
0i64,
0x188u,
0,
0,
&v74),
创建的是IoDriverObjectType这种类型的对象,理论上是可以在代码上对IoDriverObjectType注册callback,而且这个对象微软是可以外部直接链接的,不需要使用搜索的方法去寻找这个对象类型,下面就是检验理论猜想。

 

在之前的demo实例在加上一段代码注册回调

 

Globals.ob_operation_registrations.ObjectType = IoDriverObjectType;
Globals.ob_operation_registrations.Operations |= OB_OPERATION_HANDLE_CREATE;
Globals.ob_operation_registrations.Operations |= OB_OPERATION_HANDLE_DUPLICATE;
Globals.ob_operation_registrations.PreOperation = CBTdPreOperationCallback;
Globals.ob_operation_registrations.PostOperation = CBTdPostOperationCallback;
Globals.ob_registration.Version = ObGetFilterVersion();
Globals.ob_registration.OperationRegistrationCount = 1;
//CBObRegistration.Altitude = CBAltitude;
Globals.ob_registration.RegistrationContext = NULL;
Globals.ob_registration.OperationRegistration = &(Globals.ob_operation_registrations);

 

Status = ObRegisterCallbacks (
&(Globals.ob_registration),
&(Globals.registration_handle) // save the registration handle to remove callbacks later
);
if ( NT_SUCCESS(Status))
{
Globals.ob_protect_installed = TRUE;
}
然后继续使用双机调试,在入口点下断点

 

进入断点后,继续单步F10,执行过注册后status返回0表示注册成功(注意这里我是用了一些hack手法所以成功了,方法暂时不公开)

 

显示驱动启动成功

 

在我们设置的回调函数的地方下断点,看看加载驱动的时候是否会进入回调,在虚拟机里安装sysmon这个软件,他是会加载一个文件驱动的。

 

 

成功断下来了

 

查看PreInfo->Object
使用dt nt!_DRIVER_OBJECT PreInfo->Object

 

确实是sysmon的驱动sysmonDrv,他的入口点是sysmonDrv+1e058,看来这个方法确实有效,接下来我们要把driver_init设置为0,尝试修改
Eq xxxxxxx 0 修改成0

 

 

接下来直接f5,成功蓝屏

 

因为我们把入口点设置为0,所以一到执行入口点就蓝屏,说明我们可以从代码上控制驱动加载。Kb后显示堆栈,驱动执行路径

 

 

RetAddr : Args to Child : Call Site

 

00 fffff800042b3477 : fffffa80029c81c0 fffffa80029c81c0 0000000000000000 00000000000007ff : 0x0 01 fffff800042b3875 : 0000000000000001 0000000000000000 0000000000000000 fffffa80029c82f8 : nt!IopLoadDriver+0xa07
02 fffff80003ed9161 : fffff8a000000000 ffffffff80000f54 fffff800042b3820 fffffa8000711680 : nt!IopLoadUnloadDriver+0x55 03 fffff8000416f166 : 0000000000000000 fffffa8000711680 0000000000000080 fffffa80006ed1d0 : nt!ExpWorkerThread+0x111
04 fffff80003eaa486 : fffff80004044e80 fffffa8000711680 fffffa8000711b60 0000000000000000 : nt!PspSystemThreadStartup+0x5a 05 0000000000000000 : fffff8800457a000 fffff88004574000 fffff88004578590 0000000000000000 : nt!KiStartSystemThread+0x16
也就是call 0 ,我们设置的驱动入口下面我就方便修改demo驱动代码去控制驱动的加载了入口点。
在CBTdPreOperationCallback的函数里加一个修改DriverInit的数值,然后赋值为我们自定义的FakeDriverEntry函数

 

 

 

实现FakeDriverEntry函数如下:

 

 

下面再次开启双机调试,同样在虚拟机里执行sysmon –i

 

 

断点断在了回调函数的DRIVER_OBJECT pDriverObj = DRIVER_OBJECT)PreInfo->Object;
查看PDriverObj对象

 

 

U 入口点的函数u 0xfffff880`02ac2058

 

下面单步执行后会修改sysmonDrv驱动的DriverInit入口点
同时在FakeDriverEntry下断点

 


直接f5, 断点就直接断在了我们的Fake函数里

 

使用kb命令查看堆栈

 

 

确实执行了call,继续F5,这时我们观察sysmon命令行返回

 

Sysmon installed.
SysmonDrv installed.
StartService failed for SysmonDrv
Failed to start the driver:
Stopping the service failed:

 

Sysmon的驱动加载失败了,说明成功的控制了驱动的加载,从而证明了这种方案的可行。

1
总结,有些知识是已经众所周知的,但是更多的新的方法是需要我们在这些知识点反复研究反复揣摩,反复猜想,然后加以论证,才能获得意想不到额结果,当然今天这个注册回调不是随便就可以注册的,需要反复逆向去修改标志位实现的hack的方法去实现注册回调,具体读者自行研究,我这里只提供方法可行性。

[培训]12月3日2020京麒网络安全大会《物联网安全攻防实战》训练营,正在火热报名中!地点:北京 · 新云南皇冠假日酒店

收藏
点赞0
打赏
分享
最新回复 (14)
雪    币: 499
活跃值: 活跃值 (464)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
低调putchar 活跃值 2020-11-20 15:32
2
0

刚好!前段时间我就完全逆向了nt!ObRegisterCallbacks, 内核版本也和您的完全一样,具体分析可以参考链接:https://bbs.pediy.com/thread-263543.htm

我这里把判断是否允许插入回调项的关键代码发出来:

if(CallbackRegistaion->OperationRegistration[i].Operations!=0
                &&(CallbackRegistaion->OperationRegistration[i].ObjectType->TypeInfo.Flags&0x40)!=0
                &&(CallbackRegistaion->OperationRegistration[i].PreOperation!=NULL||CallbackRegistaion->OperationRegistration[i].PostOperation!=NULL)){ 
            //...    
}

你所说的突破nt!ObRegisterCallbacks限制注册驱动对象回调, 修改标志应该就是: (*IoDriverObjectType)->TypeInfo.ObjectTypeFlags |=0x40了,


但我还是有个疑问, nt!_DRIVER_OBJECT_TYPE 被改了PG不扫么?怎么过PG?


楼主辛苦了!

先收藏了!我去忙了!空了再来慢慢研究!


最后于 2020-11-23 17:27 被低调putchar编辑 ,原因:
雪    币: 1521
活跃值: 活跃值 (297)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
basketwill 活跃值 1 2020-11-20 16:13
3
0

xxxxxxxx

最后于 2020-11-20 16:14 被basketwill编辑 ,原因:
雪    币: 1521
活跃值: 活跃值 (297)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
basketwill 活跃值 1 2020-11-20 16:14
4
0
低调putchar 刚好!前段时间我就完全逆向了nt!ObRegisterCallbacks, 内核版本也和您的完全一样,具体分析可以参考链接:https://bbs.pediy.com/thread-263543.ht ...
是的我改了标志位  把标志type or 了  0x40
雪    币: 9209
活跃值: 活跃值 (1656)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
hzqst 活跃值 3 2020-11-20 16:30
5
0
低调putchar 刚好!前段时间我就完全逆向了nt!ObRegisterCallbacks, 内核版本也和您的完全一样,具体分析可以参考链接:https://bbs.pediy.com/thread-263543.ht ...
高版本PG肯定扫的,楼主估计拿win7sp0测的,win7sp0还真不扫。
雪    币: 388
活跃值: 活跃值 (138)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
wowocock 活跃值 1 2020-11-20 16:35
6
0
必须把标志type or   0x40,否则只会返回c000000d。不过过不了PG也是枉然。虽然想法不错。
雪    币: 1521
活跃值: 活跃值 (297)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
basketwill 活跃值 1 2020-11-20 16:40
7
0

xxxxxxxxxxxx

最后于 2020-11-20 16:40 被basketwill编辑 ,原因:
雪    币: 1521
活跃值: 活跃值 (297)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
basketwill 活跃值 1 2020-11-20 16:40
8
0
hzqst 高版本PG肯定扫的,楼主估计拿win7sp0测的,win7sp0还真不扫。
嗯 改升级下我的虚拟机了
雪    币: 1521
活跃值: 活跃值 (297)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
basketwill 活跃值 1 2020-11-20 16:41
9
0
wowocock 必须把标志type or 0x40,否则只会返回c000000d。不过过不了PG也是枉然。虽然想法不错。
最近忙啥了你。。。。
雪    币: 499
活跃值: 活跃值 (464)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
低调putchar 活跃值 2020-11-20 16:53
10
0
basketwill 是的我改了标志位 把标志type or 了 0x40
我在我的贴子发现一处错误,已经更正了(就相当平时写程序:测出BUG了,修复即可),,应该是:
 (*IoDriverObjectType)->TypeInfo.ObjectTypeFlags |=0x40

感谢楼主!不然我还没发现!

不过Win7 x64 7600是最老的了,说不定PG还不是很完善?我有时间,会按楼主提供的想法验证下的。
楼主很有思维创造力!赞一个!
雪    币: 499
活跃值: 活跃值 (464)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
低调putchar 活跃值 2020-11-20 16:59
11
0
hzqst 高版本PG肯定扫的,楼主估计拿win7sp0测的,win7sp0还真不扫。
很感兴趣!好的!感谢前辈提示!我空了后就从WIN7 7600开始逐个验证。
目前我还是新人一个,知识面有限,以后积累知识多了就会自然会深入明白其中的原因了。
雪    币: 4295
活跃值: 活跃值 (1359)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
hhkqqs 活跃值 1 2020-11-21 01:46
12
0
wowocock 必须把标志type or   0x40,否则只会返回c000000d。不过过不了PG也是枉然。虽然想法不错。

算了当我没说,刚看到18362下调用Callback前也验证了ObjectTypeFlags,不过PG是不行的

if ( ObjectType->TypeInfo.ObjectTypeFlags & 0x40 && ObjectType->CallbackList.Flink != &ObjectType->CallbackList )
{
    ...
    v101 = ObpCallPreOperationCallbacks(v98, (__int64)&Dst, (__int64)&v182);
    ...
}


最后于 2020-11-21 12:01 被hhkqqs编辑 ,原因:
雪    币: 38
活跃值: 活跃值 (66)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
killleer 活跃值 2020-11-21 18:06
13
0

看错了,编辑掉

最后于 2020-11-21 18:07 被killleer编辑 ,原因:
雪    币: 499
活跃值: 活跃值 (464)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
低调putchar 活跃值 2020-11-21 22:22
14
0
hhkqqs wowocock 必须把标志type or   0x40,否则只会返回c000000d。不过过不了PG也是枉然。虽然想法不错。 算了当我没说, ...

PG校验不包括ObjectType->TypeInfo的旧系统,nt!ObpPreInterceptHandleCreate中调用nt!ObpCallPreOperationCallbacks前不验证:ObjectType->TypeInfo.ObjectTypeFlags
PG校验包括ObjectType->TypeInfo的新系统,nt!ObpPreInterceptHandleCreate在调用nt!ObpCallPreOperationCallbacks就要验证ObjectType->TypeInfo.ObjectTypeFlags。
可以看得出微软已经考虑到恶意代码要: ObjectType->TypeInfo.ObjectTypeFlags|=0x40,再:ObjectType->TypeInfo.ObjectTypeFlags&=~0x40改回去的情况了。

楼主说的那个方法,在WIN7 SP0上可以阻止驱动的加载,除了替换pDriverObject->DriverInit为FakeDriverEntry外, 还可以Patch掉pDriverObject->DriverInit。

我命苦!以前为了生存,一天忙个不停当码农,领导喊你干啥就得干啥,学习时间都被榨干了,身不由己,除了手头的那一堆如山的皮毛含量的工作,哪有那么好的条件静下来深入去研究什么底层技术?
除了编码经验外,深层技术我现在就是相当于是新人,现在正是补课的时候了。怎么过PG,以后积累知识多了后再慢慢去尝试!爱好!
大佬多关照!

最后于 2020-11-21 22:23 被低调putchar编辑 ,原因:
雪    币: 164
活跃值: 活跃值 (241)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 活跃值 1 2020-11-23 23:08
15
0
很早之前在pjf前辈的微博里看到这个招数。
游客
登录 | 注册 方可回帖
返回