空指针解引用(nullpointer dereference)_空指针解引用分析

一、 概述

1.CVE-2018-8120 是通过一个空指针解引用达到任意读写的漏洞。其实个人感觉单凭空指针 解引用的危害就是 BSOD,但是利用方面还是要配合任意读写漏洞,如果单纯的只是 BSOD 那可能充其量算一个 BUG,但是如果能配合任意读写(全称:Write What Where)那么才算 是漏洞。通过思考得出的结论就是漏洞 = BUG+利用。

二、 初识空指针解引用 

1.空指针解引用的伪代码 (1)解引用,顾名思义,其实就是对一个指针进行访问其中的值,那么空指针解引用就代 表了这个指针是 NULL,所以伪代码如下:

99d65fd17a6b26e3be5f99209096c34f.png

(2)用户层 如果这段代码身处在用户层环境,那么就是访问异常,0xC0000005 错误,这是因为操 作系统的顶层异常替我们处理了,所以只会弹出来错误对话框显示出错误码。

(3)内核层 如果这段代码身处在内核层环境,那么就会直接 BSOD,因为操作系统在派发两次异常 后发现没有人能处理,那么直接蓝屏。这也体现了操作系统对于内核层的容错性很低。

(4)0xC0000005 或 BSOD 原因 这是因为 NULL 处的地址并没有进行挂 PTE,只是单纯了挂了 PDE(如下图),简而言 之就是没有对应的物理页。此处让我想起了操作系统划分地址空间的时候,都是将虚拟地址 当做是空头支票,实际干活的时候还是得靠物理地址。

b87b0799ab57cbd19d70440098130880.png

2.空指针解引用到任意读写

(1)空指针解引用到任意读写的这一步,就是锦上添花扩大战果的一步。如果从空指针解引用这一句代码为分界线,那么此前的很多代码就是造成空指针的原因,此后的代码就是是 否能够任意读写的关键(这里有点不严谨,因为还要看是否有可控的元素)。

de8f4910236b9814ffaab1f53c332bb1.png

三、 通过 HEVD 看空指针解引用

因为不太懂得漏洞什么,所以先挑选一个比较简单的漏洞环境来进行探一探究竟。这 有点初时不识君难免有些羞涩的味道。

1. 源码角度分析

(1) 漏洞的成因 漏洞原因就是没有对申请的结构体 NullPointerDereference 判断是否为 NULL,直接调用 了其中的函数。

4c67af913fa5b490e47ae8355a9d018a.png

(2) 利用分析 

我们已经知道了漏洞的原因是直接对一空指针进行解引用,并且利用了空指针进行了一 个函数的调用。那么我们让此处的空指针变成不是名义上空的指针,并且在该指针调用的函 数偏移处进行放置我们的 shellcode,那么达到漏洞利用的效果。

1. 申请 0 地址页面内存 调用 NTDLL.NtAllocateVirtualMemory 函数进行为地址为 1 的位置申请内存,为什么不直接写 0 呢?这是如果写 0 的话就由系统确定你的内存从哪里开始申请。如果写 别的值,那么这个函数内部会自动向下取整一个页面大小。

4e8889314bdb6d7679cca178a2e9679f.png

这也就说明了我们写 0x1-0xFFF,都会帮我们分配到 0-0xFFF 这个虚拟地址空间的 内存。其次要想这个函数真正内部做了什么东西(以后详细分析,这里简述)。其实这个函数并没有直接帮我们申请到物理页面,我们通过可以分析后可知,这个函数调用完之后,并没有帮我们挂上PTE,那么它到底做了什么呢?其实它只是帮我们申请了虚拟地 址,也在虚拟内存中申请一个 VAD 来保留一段空间。当我们真正使用的时候操作系统 才会帮我们挂上物理页面。

2. 在特定位置放置 shellcode 这一步,其实就是挂上了 PTE,并在调用的函数偏移处写上我们 shellcode 的地址。

2685b8469b03e148c1dfc553434a97a5.png

3. 提权代码 

提权代码,其实就是将 system 系统进程的 token 令牌替换到我们进程中的 token 结构体中,这种就间接的提升了权限。(这里是操作系统的安全机制,目前不是很了解)

ac342d8e80e672539e5f3afc05454e0b.png

现在对空指针解引用有了初步的了解,当时觉得算是理解了,分析了CVE 后,发现自己以为的真的是自己以为的,这大概就是理想与现实吧!接下来看看 CVE 的分析。

四、 CVE-2018-8120 分析 

1. 定位漏洞位置通过对比补丁前后,查看漏洞所在位置。

(可以使用工具BinDiff 或者Diaphora 来对比分析),这里就直接查看漏洞函数 win32k!SetImeInfoEx。打补丁前:

a1502586e6c73a03baaf5bbdfa587947.png

打补丁后:

728ad0627fd435f5558b9802d699a761.png

2. 漏洞的原因分析 接下来从如下几个角度进行审视,进而确定它为漏洞而不是 Bug。

(1) 漏洞位置是否可控?这个自我感觉是很重要的,因为之前有点和 rootkit 混淆,漏洞是利用系统留给用 户的接口来进行攻击,有点像用正当的手法做最不正当的事情,而 rootkit 这种就是大 肆的破坏。通过使用 IDA 对 SetImeInfoEx 函数进行交叉引用后,会发现第一个参数是来自于 NtUserSetImeInfoEx 函数中的 GetProcessWindowStation 函数的返回值,因此这个值是可控的。

df6b63191b6bb017b9db1be413367a97.png

(2) 是否可以扩大影响?对于扩大影响,我们则需要查看 SetImeInfoEx 这个函数的漏洞点以下的部分。通过查看,我们发现了此处有个 qmemcpy 函数,并且目的参数与漏洞点的指针有些间接的关系,如果可以控制这个位置,那么就可以达到任意读写从而使其 bug 变为漏洞。

(3) 为什么会存在这样的问题?站在代码编写的角度来想,编写者可以潜意识的认为这个地址不可能为 NULL,所以没 有进行更深层次的检验。这可能是因为是结构与代码组合时,没有想到过多个 WindowStation 的问题,因为我查看了当前桌面的 WindowStation,发现它并不是为 NULL,但是如果是新建 一个,默认是为 NULL 的。 

3. 漏洞的 POC 编写 

(1) 分析用户层到漏洞点的路径 通过 IDA 一路交叉引用,找到最终的 Nt 开头的函数,它的执行路径如下:Ntdll!NtUserSetImeInfoEx --> Win32k!NtUserSetImeInfoEx --> Win32k!SetImeInfoEx

(2) 代码编写

1. 创建自己的 WindowStation,并且查看(tagWINDOWSTATION*)pwinsta->spklList 是否为 NULL。通过调用 CreateWindowStationA 创建的 WindowStation,pwinsta->spklList 默认是为 NULL。

2. 调用 Ntdll!NtUserSetImeInfoEx,这一步,我们可以模仿 NTDLL 中的残根函数写法,自己调用。完整代码如下:

25e649e674885e2535b1a0fd66cd2a86.png

4. 漏洞的利用编写 POC 的编写是研究如何走到漏洞点,exploit 的编写是从漏洞点如何走下去。

(1) 分析当前可利用的区域

18056a44883806eb077d8e10414b6600.png

通过查看 qmemcpy 第一个参数的来源,我们发现它来自于 v3 的一个指针,由此可知 ,我们可控的区域就是 4 个字节,控制了这 4 个字节,就可以进行任意的读写。

(2) 如何利用这个区域 1 池喷(Pool Spraying)的可行性。查看 qmemcpy 的第一个参数,可以发现该地址是位于内存池的,因此,我们可以通过池喷射的技术来利用漏洞。

c43b969caa800a6d6e49c4643ddf0495.png

可以发现这个地址来自于池里面,所以我们利用池喷进行内存句柄,进而控制住指针。

2. 池喷射

先研究一下池喷射,这个就根据 HEVD 的池溢出漏洞来进行研究。

1) 漏洞点构造

4dd05f410fd49a585d9823a7c171c0d4.png

2) 临界值观察 a. 首先我们将利用的代码中 Size 修改为和缓冲区刚刚好的大小。查看 一下池的布局。这里设置为 0x1F8 个字节。

0ed41057285baf44049e7171ad7efa90.png

bca132c8487facce24c667ece78f459c.png

为什么上面的设定是 0x1F8 个字节呢?而不是别的。这是因为_POOL_HEAD 占 8 个 字节,所以总共加起来刚好 0x200 个字节,够一个 Lookaside 中的大小。 

接下来,查看一下 pool 的布局,通过查看会发现如果用户输入的大小超过 0x1f8, 那么就会覆盖掉下一个池头。

9b1f8c595781d3f36e2a0edf0ba123e6.png

269e886d8287b0c2ee1601d7738bfc64.png

3) 寻找利用点 a. 由上面可知,可以控制溢出到池头,那么必然我们的利用点得利用池 头中的某些成员来进行控制EIP。

f0e42eb96c3e18906fef33708fe410c6.png

通过查看,池头部和池配额结构体,都没有发现可以利用地方,但是考虑到这是非分页池,那么里面就存放了很多内核对象结构体。(这里思考的比较久,已经看很多网上的文章都是一路 dt 下去,找到了内核对象,都没有说这块内存是用来干嘛的。那是 不是如果是非分页池了,就不能这么使用了。)

于是我们可以尝试探索一下,查看里面是不是存放的是内核对象,由于某一个内核 对象都会有一个头部(_OBJECT_HEADER),所以可以试探性的搜寻是否可以找到对应的 BODY。

f6f1b463b421f36423c067bf111a61ff.png

这也就是为什么国外的文章,直接使用 dt _OBJECT_HEADER 这个地址。

78805317ff011eceb47d62bdcd952ed4.png

是不是内核对象,还得在通过其中的 TypeIndex 来进行验证。操作系统将所有的内核对 象定义成了一个全局数组 nt!ObTypeIndexTable,而 TypeIndex 就是数组的索引。这个数组的类型是 OBJECT_TYPE 类型,如果这个索引可以到这个数组中寻找到,那么必然是内核对象。

fa913aef5cc71ce12df53b1a7b2e665d.png

查看对象模板中的 _OBJECT_TYPE_INITIALIZER,会发现很多回调函数,都可以进行利用, 但是最好利用应该就是 CloseProcedure 了,只需要通过调用 closeHandle 函数来触发。

fcb1cc67ac32ea3f27163b4be251a9de.png

4) 内存布局 内存布局的话,可以通过申请大量的内核对象,这里选择的 Event 对象,这个 和这个对象的大小刚好为 0x40 个字节(释放 8 个就是 0x200),申请大量的 Event 对象将 Lookaside 和 ListHeads 占用了,然后通过申请新页来分配内存, 进而释放内存的时候可以进行合并成想要的大小。(因为分配 Pool 内存的时 候循序为,lookaside-->ListHeads -->申请新页)

这个漏洞可以利用的关键点还在于 Win7 系统可以申请 NULL 页面。所以将 TypeIndex 修改为 0(溢出的位置),然后将 NULL 页面 +0x60 偏移处修改为 shellcode 的位置,就可以在 CloseHandle 的时候触发 shellcode 代码。5) 攻击过程综述 a. 进行内存布局,制造出 0x200 的空隙,控制位置

6970bedda80a52ebab19d4bee3928a3c.png

54a211f11918d6a71b7d0bdbdc1a741b.png

b. 申请 0 页面,构造shellcode

4fb7929fd6db05f8c6198a00e9cb80e8.png

c. 触发

f6114e0d9c37037afce0a81f58ea93a4.png

3 研究 Bitmap 布局的可行性。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FindBugs 是一个静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题。有了静态分析工具,就可以在不实际运行程序的情况对软件进行分析。不是通过分析类文件的形式或结构来确定程序的意图,而是通常使用 Visitor 模式(请参阅 参考资料)。图 1 显示了分析一个匿名项目的结果(为防止可怕的犯罪,这里不给出它的名字):   在FindBugs的GUI中,需要先选择待扫描的.class文件(FindBugs其实就是对编译后的class进行扫描,藉以发现一些隐藏的bug。)。如果你拥有这些.class档对应的源文件,可把这些.java文件再选上,这样便可以从稍后得出的报告中快捷的定位到出问题的代码上面。此外,还可以选上工程所使用的library,这样似乎可以帮助FindBugs做一些高阶的检查,藉以发现一些更深层的bug。   选定了以上各项后,便可以开始检测了。检测的过程可能会花好几分钟,具体视工程的规模而定。检测完毕可生成一份详细的报告,藉由这份报告,可以发现许多代码中间潜在的bug。比较典型的,如引用了空指针(null pointer dereference), 特定的资源(db connection)未关闭,等等。如果用人工检查的方式,这些bug可能很难才会被发现,或许永远也无法发现,直到运行时发作…当除掉了这些典型的(classic) bug后,可以确信的是,我们的系统稳定度将会上一个新的台阶。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值