首页
论坛
课程
招聘
[原创]内核HOOK的安全问题(全程现场回放)
2009-9-12 11:22 8159

[原创]内核HOOK的安全问题(全程现场回放)

2009-9-12 11:22
8159
呃,大家做好心理准备,字数不少, 是我的整个思考过程,全程记录,当然发上来后要做一些小地方的修改,比如:表情!!!呵呵

写的不好,因为思路很乱,基础知识不好,所以导致很多误区, 文笔不好,只能做为一个实录,千万别当文学作品~~

欢迎新手参观,更欢迎大牛们批评指教! 新手参观的时候要注意了,我里面有很多东西是错误的,你看到的时候最好与自己掌握的东西比较一下,别被我误导, 大牛们也注意了, 能看完您就看完,不能看完呢,也许多看两句话,然后留下两句话,因为你那一两句话对你们没什么,对我们这种菜鸟来说,堪比天籁

内容我回帖在二楼吧,比我快的兄弟别抢沙发啊!

2021 KCTF 秋季赛 防守篇-征题倒计时(11月14日截止)!

收藏
点赞0
打赏
分享
最新回复 (12)
雪    币: 70
活跃值: 活跃值 (15)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
这真不行 活跃值 2009-9-12 11:22
2
0
关于内核HOOK的安全问题

现象:  驱动实现HOOK多个内核函数,虚拟机运行无问题出现 本地机运行多数情况正常,但偶尔在卸载驱动时会出现BSOD,代码:IRQL_NOT_LESS_OR_EQUAL 使用GOOLE与BAIDU了一番,这个错误应该属于内存访问失败吧!

     思考几次BSOD时的本机情况,一般为开启了多个软件时导致,重新检查驱动代码,醒悟为在UNHOOK时,没有做任何检测,直接将所有HOOK的函数还原然后卸载驱动,并且UNHOOK代码的IRQL级别高,不会被系统打断 因此,假设系统中某进程正在调用被HOOK的函数并正好运行至我们的代理函数时被其它进程抢先,并一直未得到继续运行,而此时我们运行UNHOOK,将代理函数自内存中销毁,然后等待的进程得到处理器后,继续执行指令时,原内存地址中的指令已不存在,因而就会发生BSOD了!

既然原因大概知道了,那就想办法解决吧!

思考1:
     首先想到的解决方案是: 创建全局变量做计数器使用 在代理函数入口使计数器+1,返回时-1.  在UNHOOK时判断计数器是否为0 不为0则等待计数器归0后再进行UNHOOK
     方案有了,那就继续思考可行性:
    (1)使用计数器进行函数调用的统计,如何保证计数器进行加减操作时不被打断?
嗯,记得之前看书的时候,提到过这种情况下可以使用自旋锁,来达到锁定操作不被打断的目的. 但如果是一个使用的比较频繁的函数,使用自旋锁会不会使性能降低?  另外如果被一个低优先级的进程抢先了资源后,紧接被高于它优先级的进程抢先,?这种情况是不是就叫所谓的死锁?因为CPU始终运行在等待状态上,而资源得不到释放? 那如何防止这种情况的发生呢?
不行,对自旋锁的概念太模糊,看书找资料!
下面引用WINDOWS驱动开发里的介绍

IRQL概念仅能解决单CPU上的同步问题,在多处理器平台上,它不能保证你的代码不被运行在其它处理器上的代码所干扰。一个称为自旋锁(spin lock)的原始对象可以解决这个问题。为了获得一个自旋锁,在某CPU上运行的代码需先执行一个原子操作,该操作测试并设置(test-and-set)某个内存变量,由于它是原子操作,所以在该操作完成之前其它CPU不可能访问这个内存变量。如果测试结果表明锁已经空闲,则程序获得这个自旋锁并继续执行。如果测试结果表明锁仍被占用,程序将在一个小的循环内重复这个“测试并设置(test-and-set)”操作,即开始“自旋”。最后,锁的所有者通过重置该变量释放这个自旋锁,于是,某个等待的test-and-set操作向其调用者报告锁已释放。
关于自旋锁有两个明显的事实。第一,如果一个已经拥有某个自旋锁的CPU想第二次获得这个自旋锁,则该CPU将死锁(deadlock)。自旋锁没有与其关联的“使用计数器”或“所有者标识”;锁或者被占用或者空闲。如果你在锁被占用时获取它,你将等待到该锁被释放。如果碰巧你的CPU已经拥有了该锁,那么用于释放锁的代码将得不到运行,因为你使CPU永远处于“测试并设置”某个内存变量的自旋状态。
关于自旋锁的另一个事实是,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。所以,为了避免影响性能,你应该在拥有自旋锁时做尽量少的操作,因为此时某个CPU可能正在等待这个自旋锁。
关于自旋锁还存在着一个不太明显但很重要的事实:你仅能在低于或等于DISPATCH_LEVEL级上请求自旋锁,在你拥有自旋锁期间,内核将把你的代码提升到DISPATCH_LEVEL级上运行。在内部,内核能在高于DISPATCH_LEVEL的级上获取自旋锁,但你和我都做不到这一点。


    哦,SHIT! 再看书上关于自旋锁的介绍后才发现自己真够屎的!!!明明人家说的清清楚楚明明白白的,自旋锁,就是用来防止操作不被打断(注意上面标红的地方,其实自己是知道的,但是根本没理解这是啥意思,,,),那又如何存在低优先级线程获取资源后被高优先级抢先的情况呢?~ 不过问题又产生了,,既然不会被打断,又如何会出现死锁呢?同时,证明了我的猜想,使用自旋锁肯定会影响性能.  为了巩固学到的东西,再次找资料,看看什么情况下会发生死锁!

  
死锁:假设有一个或多个内核任务和一个或多个资源,每个内核都在等待其中的一个资源,但所有的资源都已经被占用了。这便会发生所有内核任务都在相互等待,但它们永远不会释放已经占有的资源,于是任何内核任务都无法获得所需要的资源,无法继续运行,这便意味着死锁发生了。自死琐是说自己占有了某个资源,然后自己又申请自己已占有的资源,显然不可能再获得该资源,因此就自缚手脚了。


    呃,看见这个想起来,计算机组成原理上有这个介绍的嘛(忘的一干二净了,) 不过死锁很容易理解,两个CPU,三个资源,两个内核任务 其中每个内核任务需要三个资源才可以达到执行条件,CPU1抢了资源1,CPU2抢了资源2,3 两个互相等吧….这就是死锁了,那自锁呢,还是不太明白~~~只能理解为  CPU获取资源后,释放资源前,又一次申请该资源! 呃,不知道对不对大牛们指点一下!

    到这就大概就明白了,使用自旋锁与计数器应该是可以实现我的需求,自死锁是代码编写时没有注意所产生的,这个可以防止发生了,影响性能嘛~~~反正只做个计数器加减操作,不会影响太大,决定就用这种方法了

   (2)但是!突然又冒出个问题:我在UNHOOK时,判断计数器是否为0,是要在提升当前IRQL后判断呢?还是要在提升前判断呢?
     如果是在提升级别前进行判断,那假设我判断计数器为0后,CPU被其它进程抢先,并且该进程调用了被HOOK的函数,然后使计数器+1 此时CPU又被UNHOOK抢过来进行下面的处理,还是得BSOD不是~~~
    如果在提升级别后判断,那其它进程得不到运行,计数器就不会减1了啊, 嗯?多核的还好,可以有另一个CPU来将进程运行完(咋感觉这概念很有问题啊?),可单核的呢?另外多核的还存在一个多核同步问题.
    (3)换个想法,不用计数器,把自旋锁test-and -set放在代理函数开头,然后在代理函数返回前释放锁资源,在UNHOOK的时候同样进行test-and-set,UNHOOK结束后释放锁资源.不行,先不管其它的问题,这样写的话,性能影响实在太大了.等等,,,UNHOOK判断计数器的时候,不提升当前IRQL级别,而是使用自旋锁,UNHOOK结束后释放锁资源 嗯,这样可以解决多核同步问题,单核的嘛?只能是在代理函数中判断当前CPU数量,如果是单核的,那自旋锁的释放就要等到代理函数执行结束后再进行了,这样其实就等于让CPU在调用被HOOK的函数时,始终不被打断,也就是同一时刻只有一个调用到该函数的线程在运行,性能的影响是相当的大啊..
    只能思考到这个地步了,还好现在多数都是多核机,面临的窘状不会太多…只是感觉这方法很笨啊,而且不知道思考的路子对与不对,大牛们要是能坚持看到这,还是烦劳指点小菜一两手吧!

思考2:
      本来想解决方案的时候一下冒出来的两个方案,上面的是第一个,第二个是不使用自旋锁而使用原子操作进行计数器的加减,不过在上面方案的聚体思考过程中,这个方案已经被否决了,因为不使用自旋会出现的问题上面已经考虑过了!(补充:其实这是一个逻辑性错误,我此时所想的原子操作,是指原子的加减,而思考的自旋,是指自旋后可以运行一段相当多的内容, 级别不同而已! 也说不清楚当时是怎么想的,想到的就想上了,反正就是乱七八糟就对了!)

思考3:
      这个是在方案1结束时想到的,,因为想到了线程调度的问题,不管是多核还是单核,线程调度时,CPU需要保存当前线程的上下文,所以应该不会存在一个线程被多个CPU分别调度的情况,那方案一的结果就是不言而喻了,所以就只能是把计数器的判断提前,不管是用自旋还是提升IRQL,都必须要在这个操作之前判断当前的计数器情况,那就又回到了老问题,计数器判断通过的瞬间,线程被调度,其它线程又访问了被HOOK的函数并且运行到了自己的代理函数中,继续BSOD(), 到这就把自己的方案一给否决了,然后换思路思考! 想到最近研究的DNF驱动保护,有人分析的结果是:”驱动一运行,就使用ExAllocateXXXX分配一块内存,然后实现HOOK,然后结束自己的线程” 嗯!就是这个了, 我也可以在驱动入口处先申请一块内存,然后把自己的代理函数写入这块内存中  代理函数中的自旋锁与计数器仍然是必须的, 然后在UNHOOK的时候,先进行UNHOOK操作 再进行计数器的判断(不使用任何自旋与提升IRQL),当计数器清0后, 使用ExFreeXXXX来释放分配的内存. 好像这是我能想到的最好的办法了?感觉很简单啊,竟然用了一个上午的时间想出来的这个方案, 不过不知道是否可行?

      从头看到尾的牛人,你是我的偶像,也是我的救星,烦请您抬抬手,动动口,就告诉我一下,这个办法可不可以用?还有没有什么漏掉的地方?又有没有更好的办法?小弟千恩万谢啊~
雪    币: 70
活跃值: 活跃值 (15)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
这真不行 活跃值 2009-9-12 11:35
3
0
吃饭去,回来看看有没有哪位大牛指点一二
然后试试想的的办法可不可行..

吃饭的时候想到个问题,有点迫不及待的想说说的感觉

使用方案3的时候, 如果是HOOK了多个函数,那就需要申请多个自旋锁对象与多个计数器变量(不知道有没有什么内核结构可以实现一个内核对象控制多个锁的作用)
而我们使用自旋锁的目的,却仅仅是为了给计数器进行加减操作, 其实这个时候完全可以使用原子指令来进行计数器的加减,即可以免去申请自旋锁对象,又可以免除使用自旋锁带来的性能下降的弊端,自我感觉良好啊,呵呵!
雪    币: 142
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiejienet 活跃值 2009-9-12 11:37
4
0
先膜拜再看。。。。。
雪    币: 70
活跃值: 活跃值 (15)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
这真不行 活跃值 2009-9-12 12:05
5
0
顺便请教一下
大家在写驱动的时候,是用什么工具写的?
我一直在用EDITPLUS....本来就菜,用这个嘛提示没有,只知道一些常用的函数,英文的文档又实在看不太明白,中文的资料又少的可怜...

有什么集成环境没?类似VS之类的,不要求他达到提示的作用,至少我在写出某个函数来后,直接右键就可以找到函数的声明原型也行啊..
雪    币: 6696
活跃值: 活跃值 (448)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
b23526 活跃值 2009-9-12 12:13
6
0
偶就就用VC写驱动啊,楼主看看驱动编译平台搭建的帖子吧
雪    币: 70
活跃值: 活跃值 (15)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
这真不行 活跃值 2009-9-12 12:20
7
0
看过几个,好像都是VS2003吧,我这只有2005,,,,,
雪    币: 70
活跃值: 活跃值 (15)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
这真不行 活跃值 2009-9-12 13:12
8
0
还有个问题
函数在返回时是否有可能被抢先?
例如: 在我的代理函数返回前,我进行了原子减操作,然后下面就是RET指令   有没有可能在我刚刚进行完减操作之后,执行RET之前被调度? 如果会被调度,并且正好调度到我的UNHOOK进程上, 那此时因为计数器已经清0,所以可以进行内存释放, 而另一线程的上下文中,指令RET的地址仍然是在原来的地址上,该地址被释放即无处可询,还会发生BSOD,,,

不知道这么想的对还是不对...
雪    币: 514
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
xacker 活跃值 1 2009-9-13 15:04
9
0
LZ想象力真丰富.
雪    币: 210
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
clrheart 活跃值 2009-9-15 10:34
10
0
凭空想象是没有用地 你分析下dump再想象一下才合理 蓝屏了 不分析dump 这个真不行
你可以在windbg的help里看看IRQL_NOT_LESS_OR_EQUAL 是啥意思
雪    币: 70
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小爬 活跃值 2009-9-15 15:01
11
0
不错,学习学习。
雪    币: 104
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zjw 活跃值 2009-9-15 22:40
12
0
学习...,争取哪天超越你
雪    币: 284
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jerrynpc 活跃值 2009-9-16 08:19
13
0
YY强身健体,膜拜楼主
游客
登录 | 注册 方可回帖
返回