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

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

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

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

  • 标 题:答复
  • 作 者:这真不行
  • 时 间:2009-09-12 11:22

关于内核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来释放分配的内存. 好像这是我能想到的最好的办法了?感觉很简单啊,竟然用了一个上午的时间想出来的这个方案, 不过不知道是否可行? 

      从头看到尾的牛人,你是我的偶像,也是我的救星,烦请您抬抬手,动动口,就告诉我一下,这个办法可不可以用?还有没有什么漏掉的地方?又有没有更好的办法?小弟千恩万谢啊~