接上文:
- 深入研究解释语言背后隐藏的攻击面,Part 1(上)》
- 深入研究解释语言背后隐藏的攻击面,Part 1(下)》
- 深入研究解释语言背后隐藏的攻击面,Part 2(一)》
- 深入研究解释语言背后隐藏的攻击面,Part 2(二)》
本文将通过外部函数接口进行深入探讨(Foreign Function Interface,FFI)将基于C/C 的库“粘合”在解释语言的过程中,如何产生安全漏洞。
决定最终漏洞使用策略
在触发我们的堆内存覆盖之前,找出如何处理它data_对于数组的最佳定位,我们需要检查堆的状态。到目前为止,我们有两个感兴趣的目标:png_ptr分析器在运行过程中使用的结构和动态链接器数据。
假如我们检查png_ptr我们会发现结构数据所在的堆块是一个大小0x530的main arena分块。
Thread1"node"hitBreakpoint2,0x00007ffff40309b4inpng_read_row()from/home/anticomputer/node_modules/png-img/build/Release/png_img.nodegef?irrdirdi0x2722ef00x2722ef0gef?heapchunk$rdiChunk(addr=0x2722ef0,size=0x530,flags=PREV_INUSE)Chunksize:1328(0x530)Usablesize:1320(0x528)Previouschunksize:25956(0x6564)PREV_INUSEflag:OnIS_MMAPPEDflag:OffNON_MAIN_ARENAflag:Offgef?我们之前已经研究过了png_ptr结构本身以及如何使用它来颠覆它node现在,让我们仔细考察过程_dl_fixup,以及解析器代码中崩溃的具体原因。
当我们触发崩溃时,我们注意到:
0x00007ffff7de2fb2in_dl_fixup(l=0x2722a10,reloc_arg=0x11d)at../elf/dl-runtime.c:6969constchar*strtab=(constvoid*)D_PTR(l,l_info[DT_STRTAB]);gef?p*l$5={l_addr=0x4141414141414141,...l_info={0x4141414141414141...}gef?pl$6=(structlink_map*)0x2722a10gef?这意味着我们已经破坏了解析png-img库函数的linkmap。实际上,linkmap用于保存动态链接器执行过程中分析和重定位所需的所有信息的数据结构。
下面,我们来看看linkmap在堆块和数据结构被破坏之前:
gef?heapchunk0x2722a10Chunk(addr=0x2722a10,size=0x4e0,flags=PREV_INUSE)Chunksize:1248(0x4e0)Usablesize:1240(0x4d8)Previouschunksize:39612548531313(0x240703e24471)PREV_INUSEflag:OnIS_MMAPPEDflag:OffNON_MAIN_ARENAflag:Offgef?p*l$7={l_addr=0x7ffff400f000,l_name=0x2718010"/home/anticomputer/node_modules/png-img/build/Release/png_img.node",l_ld=0x7ffff4271c40,l_next=0x0,l_prev=0x7ffff7ffd9f0l_real=0x2722a10,l_ns=0x0,l_libname=0x2722e88,l_info={0x0,0x7ffff4271c70,0x7ffff4271d50,0x7ffff4271d40,0x0,0x7ffff4271d00,0x7ffff4271d10,0x7ffff4271d80,0x7ffff4271d90,0x7ffff4271da0,0x7ffff4271d20,0x7ffff4271d30,0x7ffff4271c90,0x7ffff4271ca0,0x7ffff4271c80,0x0,0x0,0x0,0x0,0x0,0x7ffff4271d60,0x0,0x0,0x7ffff4271d70,0x0,0x7ffff4271cb0,0x7ffff4271cd0,0x7ffff4271cc0,0x7ffff4271ce0,0x0,0x0,0x0,0x0,0x0,0x7ffff4271dc0,0x7ffff4271db0,0x0,0x0,0x0,0x0,0x7ffff4271de0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7ffff4271dd0,0x0...}gef?当我们检查png_ptr chunk和linkmap chunk注意到它们的内存不仅是相邻的,而且是连续的。png_ptr chunk位于地址0x2722ef0大小为0x4e0的linkmap chunk则位于它之前的地址0x2722a10地方。因为是连续内存,两者之间没有分块。
当我们从攻击者的角度评估堆状态时,我们总是考虑连续内存布局和逻辑内存布局(如链表)。
因为linkmap和png_ptr我们开始影响目标的内存node在这个过程之前在漏洞利用过程中都处于使用状态,所以我们似乎不太可能在这两个块之间移动我们data_ chunk,破坏畅通无阻png_ptr数据。此外,我们似乎可以通过例子PNG影响早期堆积状态的文件大小,但似乎无法获得可靠的结果。
这意味着我们必须对对linkmap破坏,以获得正确的node控制过程。
解析器攻击运行时
作为攻击者,我们经常需要从系统代码中提取意想不到但有用的行为。这里的挑战是:不要被我们不关心的东西所干扰,而是关注在特定的漏洞利用场景中可用的行为。
那么,哪些行为可能对攻击者有用呢?
要回答这个问题,我们必须知道如何使用分析器linkmap简而言之,它将从linkmap抓取加载库的基地址,然后检查各种二进制段,以确定从库的基地址到要分析的函数的正确偏移。一旦计算出偏移,将其添加到库的基地址中,并用分析函数地址更新函数GOT条目,然后跳转到分析函数的起始地址。
作为攻击者,我们从中提取了以下有用的原语动态链接器的运行提供精心设计linkmap,将它们加在一起,然后将执行流重定向到生成的地址处。加法的第一个操作数是直接从linkmap加法的第二个操作数可以通过linkmap提供的指针从二进制段获得。我们注意到,根据包含在解除引用的二进制段中的数据,分析值将重定向之前,分析值将写入内存位置。
事实上,通过破坏动态链接器发动攻击并不是一个新主意,其中,所谓的“ret2dlresolve”攻击是一种流行的方式,它不知道libc在内存本身的位置下,重定向所需的执行libc函数中。在Nergal发布在Phrack上的“The advanced return-into-lib(c) exploits: PaX case study”本文公开讨论了这一概念。
当PLT在目标二进制文件的已知位置,就像非PIE二进制文件的情况相同,ret2dlresolve攻击是一个非常有吸引力的选择,它可以在不知道所需目标库实际加载到内存的具体位置的情况下,将执行重定向到任何库偏移。这是因为分析器代码将为我们完成所有繁重的工作。
滥用运行过程中分析器的主流方法通常假设攻击者能够重定向执行流,并通过PLT为了回到解析器代码中_dl_runtime_resolve提供攻击者控制的参数。因此,这种方法被称为“ret2dlresolve”(即return to dl resolve缩写形式)。他们的想法是,然后可以使用分析器与现有或精心制作linkmap数据与重定位数据的交互可以推导出攻击者控制的偏移,即内存中现有指针值的偏移。例如,他们可以欺骗分析师将攻击者控制的偏移应用于已建立的偏移libc地址上,以便从那里偏移到一个任意的libc例如函数system(3)。不知道libc基地地址不能直接返回libc在这种情况下,上述方法的一个变体是使用解析器逻辑进行分析libc函数。
当然,这种技术还有其他变体,比如提供一个完全精心制作的内存已知位置linkmap,重定位和符号数据是用相对搜索网站伪造的。这里的目标也是滥用运行时的分析器,将已知的内存位置偏移到攻击者想要转移的位置。
然而,在我们的例子中,我们可以提供一个精心制作的linkmap,但在运行过程中,我们无法控制分析器的参数。此外,我们还没有掌握执行控制权,而是旨在“策反”运行时解析器,通过我们精心制作的linkmap绕过数据ASLR实现机制和重定向实施。因为堆的基地址是随机的,我们是通过的PNG因此,我们无法泄露攻击过程的文件linkmap所以我们只能基于非PIE节点二进制文件进行内存布局和内容假设。
让我们来看看如何更好地理解攻击者的目标_dl_fixup工作原理。所有代码的引用都来自这里glibc-2.27。
elf/dl-runtime.c:#ifndefreloc_offset#definereloc_offsetreloc_arg#definereloc_indexreloc_arg/sizeof(PLTREL)#endif/*ThisfunctioniscalledthroughaspecialtrampolinefromthePLTthefirsttimeeachPLTentryiscalled.WemustperformtherelocationspecifiedinthePLTofthegivensharedobject,andreturntheresolvedfunctionaddresstothetrampoline,whichwillrestarttheoriginalcalltothataddress.FuturecallswillbouncedirectlyfromthePLTtothefunction.*/DL_FIXUP_VALUE_TYPEattribute_hidden__attribute((noinline))ARCH_FIXUP_ATTRIBUTE_dl_fixup(#ifdefELF_MACHINE_RUNTIME_FIXUP_ARGSELF_MACHINE_RUNTIME_FIXUP_ARGS,#endifstructlink_map*l,ElfW(Word)reloc_arg){constElfW(Sym)*constsymtab=(constvoid*)D_PTR(l,l_info[DT_SYMTAB]);constchar*strtab=(constvoid*)D_PTR(l,l_info[DT_STRTAB]);[1]constPLTREL*constreloc=(constvoid*)(D_PTR(l,l_info[DT_JMPREL]) reloc_offset);[2]constElfW(Sym)*sym=&symtab[ELFW(R_SYM)(reloc->r_info)];constElfW(Sym)*refsym=sym;[3]void*constrel_addr=(void*)(l->l_addr reloc->r_offset);lookup_tresult;DL_FIXUP_VALUE_TYPEvalue;/*Sanitycheckthatwe'rereallylookingataPLTrelocation.*/assert(ELFW(R_TYPE)(reloc->r_info)==ELF_MACHINE_JMP_SLOT);/*Lookupthetargetsymbol.Ifthenormallookuprulesarenotuseddon'tlookintheglobalscope.*/if(__builtin_expect(ELFW(ST_VISIBILITY)(sym->st_other),0)==0){conststructr_found_version*version=NULL;if(l->l_info[VERSYMIDX(DT_VERSYM)]!=NULL){constElfW(Half)*vernum=(constvoid*)D_PTR(l,l_info[VERSYMIDX(DT_VERSYM)]);ElfW(Half)ndx=vernum[ELFW(R_SYM)(reloc->r_info)]&0x7fff;version=&l->l_versions[ndx];if(version->hash==0)version=NULL;}/*Weneedtokeepthescopearoundsodosomelocking.Thisisnotnecessaryforobjectswhichcannotbeunloadedorwhenwearenotusinganythreads(yet).*/intflags=DL_LOOKUP_ADD_DEPENDENCY;if(!RTLD_SINGLE_THREAD_P){THREAD_GSCOPE_SET_FLAG();flags|=DL_LOOKUP_GSCOPE_LOCK;}#ifdefRTLD_ENABLE_FOREIGN_CALLRTLD_ENABLE_FOREIGN_CALL;#endifresult=_dl_lookup_symbol_x(strtab sym->st_name,l,&sym,l->l_scope,version,ELF_RTYPE_CLASS_PLT,flags,NULL);/*Wearedonewiththeglobalscope.*/if(!RTLD_SINGLE_THREAD_P)THREAD_GSCOPE_RESET_FLAG();#ifdefRTLD_FINALIZE_FOREIGN_CALLRTLD_FINALIZE_FOREIGN_CALL;#endif/*Currentlyresultcontainsthebaseloadaddress(orlinkmap)oftheobjectthatdefinessym.Nowaddinthesymboloffset.*/value=DL_FIXUP_MAKE_VALUE(result,sym?(LOOKUP_VALUE_ADDRESS(result) sym->st_value):0);}else{/*Wealreadyfoundthesymbol.Themodule(andthereforeitsloadaddress)isalsoknown.*/value=DL_FIXUP_MAKE_VALUE(l,l->l_addr sym->st_value);result=l;}/*Andnowperhapstherelocationaddend.*/value=elf_machine_plt_value(l,reloc,value);if(sym!=NULL&&__builtin_expect(ELFW(ST_TYPE)(sym->st_info)==STT_GNU_IFUNC,0))value=elf_ifunc_invoke(DL_FIXUP_VALUE_ADDR(value));/*Finally,fixupthepltitself.*/if(__glibc_unlikely(GLRO(dl_bind_not)))returnvalue;returnelf_machine_fixup_plt(l,result,refsym,sym,reloc,rel_addr,value);}复杂,但我们只需要关注以下几点:_dl_fixup如何通过与我们控制linkmap三个主要指针交互分析和重定位函数地址,所有这些指针都来自linkmap的l_info从数组中提取:
- l_info[DT_SYMTAB],指向符号表.dynamic条目指针。
- l_info[DT_STRTAB],指向字符串表.dynamic条目指针。
- l_info[DT_JMPREL],指向PLT重定位记录数组.dynamic条目指针。
Elf二进制文件中的.dynamic该段用于保存分析器需要获得的各段的信息。.dynstr (STRTAB)、.dynsym (SYMTAB)和.rela.plt (JMPREL)分析和重定位函数都需要段。
动态条目(Dynamic entry)的结构如下所示:
typedefstruct{Elf64_Sxwordd_tag;/*Dynamicentrytype*/union{Elf64_Xwordd_val;/*Integervalue*/Elf64_Addrd_ptr;/*Addressvalue*/}d_un;}Elf64_Dyn;用于访问l_info条目的D_PTR宏定义为:
/*Allreferencestothevalueofl_info[DT_PLTGOT],l_info[DT_STRTAB],l_info[DT_SYMTAB],l_info[DT_RELA],l_info[DT_REL],l_info[DT_JMPREL],andl_info[VERSYMIDX(DT_VERSYM)]havetobeaccessedviatheD_PTRmacro.Themacroisneededsinceformostarchitecturestheentryisalreadyrelocated-butforsomenotandweneedtorelocateataccesstime.*/#ifdefDL_RO_DYN_SECTION#defineD_PTR(map,i)((map)->i->d_un.d_ptr (map)->l_addr)#else#defineD_PTR(map,i)(map)->i->d_un.d_ptr#endif请注意,在大多数情况下,D_PTR只是从.dynamic获取段目d_ptr检索相关段运行时重定位地址的字段。const char *strtab = (const void *) D_PTR (l,l_info[DT_STRTAB]);按提供的方向.dynstr (STRTAB)段的.dynamic在l_info数组的索引DT_STRTAB获取上述条目的d_ptr字段。
在指针方面,这里很头疼,但我们只需要记住一件事:我们没有控制它linkmap中的l_info数组提供直接指向分析器所需各段的指针,而指向(假设).dynamic在偏移量 8处,条目指针应包含指向相关段的指针。
前面,我们介绍了如何精心制作linkmap数据为分析器提供伪造的二进制段。接下来,让我们快速了解一下_dl_fixup实际分析和重定位逻辑。
重定位记录的定义如下:
elf.h:typedefstruct{Elf64_Addrr_offset;/*Address*/Elf64_Xwordr_info;/*Relocationtypeandsymbolindex*/Elf64_Sxwordr_addend;/*Addend*/}Elf64_Rela;这些符号的定义如下:
elf.h:typedefstruct{Elf64_Wordst_name;/*Symbolname(stringtblindex)*/unsignedcharst_info;/*Symboltypeandbinding*/unsignedcharst_other;/*Symbolvisibility*/Elf64_Sectionst_shndx;/*Sectionindex*/Elf64_Addrst_value;/*Symbolvalue*/Elf64_Xwordst_size;/*Symbolsize*/}Elf64_Sym;让我们回顾一下_dl_fixup[1]注意代码,_dl_fixup的reloc_arg读取重定位记录的参数重定位记录表索引。这个重定位记录提供了一个reloc->r_info该字段通过宏分为高32位符号表索引和低32位重定位类型。
在[2]处,_dl_fixup利用reloc->r_info索引从符号表中获取相应的符号条目reloc->r_info处的ELF_MACHINE_JMP_SLOT类型断言和sym->st_other检查符号搜索范围前,实际函数解决以一种非常简单的方式进行分析。首先,通过linkmap中的l->l_addr字段和符号表项sym->st_value加入字段分析函数地址。然后将分析值写入rel_addr中,rel_addr计算在[3]处,即l->l_addr和reloc->r_offset结果相加。
linkmap中的l->l_addr字段是用来存放加载库的基地址,任何解析的偏移值都会被加入其中。
综上所述,sym->st_value l->l_addr是分析函数的地址,l->l_addr reloc->r_offset重定位目标,即重定位目标,即重定位目标,即重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标,重定位目标重定位目标,重定位目标重定位目标重定位目标,重定位目标,重定位目标,重定位目标重定位目标重定位目标,重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标,重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标,重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标,重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标重定位目标GOT用分析函数地址更新条目。
所以,从我们攻击者的角度来看,既然我们控制了l->l_addr,以及指向符号表和重定位记录.dynamic对于段的指针,我们可以重新定向对我们有利的地方。
小结
本文将通过外部函数接口进行深入探讨(Foreign Function Interface,FFI)将基于C/C 的库“粘合”在解释语言的过程中,如何产生安全漏洞。由于篇幅太长,我们将分为多篇文章进行介绍。请期待更多精彩内容!
本文翻译自:https://securitylab.github.com/research/now-you-c-me-part-two