当前位置: 首页 > 科技观察

深入探究解释型语言背后隐藏的攻击面,Part2(3)

时间:2023-03-17 22:20:53 科技观察

接上文:《深入考察解释型语言背后隐藏的攻击面,Part 1(上)》《深入考察解释型语言背后隐藏的攻击面,Part 1(下)》《深入考察解释型语言背后隐藏的攻击面,Part 2(一)》《深入考察解释型语言背后隐藏的攻击面,Part 2(二)》(ForeignFunctionInterface,FFI)在处理过程中将基于C/C++的库“粘合”到解释语言中,安全漏洞是如何产生的。决定最终的利用策略为了在触发我们的堆内存覆盖之前弄清楚如何最好地定位data_数组,我们需要检查堆的状态。到目前为止,我们有两个感兴趣的对象:png_ptr结构和运行时解析器正在使用的动态链接器数据。如果我们查看png_ptr结构数据所在的堆块,我们会发现它是一个大小为0x530的主竞技场块。Thread1"node"hitBreakpoint2,0x00007ffff40309b4inpng_read_row()from/home/anticomputer/node_modules/png-img/build/Release/png_img.nodegef?irrdirdi0x2722ef00x2722ef0gef?heapchunk$rdiChunk(addr=0x2722ef0,size=0x538PR_Size)(0x530)可用大小:1320(0x528)上一个块大小:25956(0x6564)PREV_INUSEflag:OnIS_MMAPPEDflag:OffNON_MAIN_ARENAflag:Offgef?之前,我们研究过png_ptr结构体本身,以及如何利用它来颠覆节点进程。现在,让我们检查_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堆块和数据结构在销毁之前是什么样子的:gef?heapchunk0x2722a10Chunk(addr=0x2722a10,size=0x4e0,flags=PREV_INUSE)Chunksize:1248(0x4e0)Usablesize:1240(0x4d8)Previouschunksize:396132543853(0x240703e24471)PREV_INUSEflag:OnIS_MMAPPEDflag:OffNON_MAIN_ARENAflag:Offgef?p*l$7={l_addr=0x7ffff400f000,l_name=0x2718010"/home/node_modules/png-gase/"/home/anticomputer/node_modules/png-gase/",pg/build/Reled0x7ffff4271c40,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...}全球环境基金?当我们检查png_p查看trchunk和linkmapchunk的地址和大小时,注意它们所在的内存不仅相邻而且连续。png_ptr块位于地址0x2722ef0,而大小为0x4e0的链接映射块位于它之前的地址0x2722a10。由于是连续记忆,两者之间没有隔断。从攻击者的角度评估堆状态时,我们总是同时考虑连续和逻辑内存布局(例如链表)。由于linkmap和png_ptr的内存是在我们开始影响目标节点进程之前分配的,并且在利用过程中都在使用,因此我们似乎不太可能在这两个块之间移动我们的data_chunk以不受阻碍地破坏png_ptr数据。此外,我们似乎能够通过例如影响早期堆状态。PNG文件大小,但这似乎不会产生可靠的结果。这意味着我们将不得不破坏链接映射以获得对节点进程的控制。攻击运行时解析器作为攻击者,我们经常不得不从系统代码中提取意想不到但有用的行为。这里的挑战是:不要被我们不关心的事情分心,而是关注在特定利用场景中可利用的行为。那么,运行时解析器代码的哪些行为可能对攻击者有用?要回答这个问题,我们必须了解运行时解析器如何使用链接映射。简而言之,它从链接映射中获取加载库的基地址,然后检查各种二进制段以确定从库基地址到要解析的函数起始地址的正确偏移量。计算出这个偏移量后,将其添加到库的基地址,用已解析函数地址更新函数的GOT条目,并跳转到已解析函数的起始地址。作为攻击者,我们从中提取了以下有用的原语:将精心制作的链接映射提供给动态链接器的运行时解析器,将它们相加,并将执行流重定向到结果地址。加法的第一个操作数直接从linkmap中获取,而加法的第二个操作数可以通过linkmap中提供的指针从二进制段中获取。我们注意到,在重定向执行之前,已解析的值将被写入内存位置,具体取决于其中一个取消引用的二进制段中包含的数据。事实上,通过破解动态链接器进行攻击并不是一个新想法,其中所谓的“ret2dlresolve”攻击是一种流行的方式,它可以在不知道libc本身在内存中的位置的情况下执行。重定向到所需的libc函数。Nergal的Phrack文章“Theadvancedreturn-into-lib(c)exploits:PaXcasestudy”公开讨论了这个概念。当PLT位于目标二进制文件的已知位置时,就像非PIE二进制文件的情况一样,ret2dlresolve攻击是一个非常有吸引力的选择,可以在不知道所有所需对象的具体位置的情况下将执行重定向到任意库偏移量库实际上被加载到内存中。这是因为解析器代码为我们完成了所有繁重的工作。滥用运行时解析器的主流方法通常假设攻击者已经能够重定向进程的执行流程并通过PLT返回解析器代码,以便为_dl_runtime_resolve提供攻击者控制的参数。因此,此方法称为“ret2dlresolve”(returntodlresolve的缩写)。这个想法是,然后可以利用解析器与现有或精心制作的链接映射数据和重定位数据的交互来推断攻击者控制的内存中现有指针值的偏移量。例如,他们可以诱骗解析器将攻击者控制的偏移量应用于已建立的libc地址,以从那里偏移到任意libc函数,例如system(3)。在libc基地址未知且无法直接返回libc的情况下,上述方法的一种变体使用解析器逻辑来解析libc函数。当然,这种技术还有其他变体,例如在内存中的已知位置提供一个完全制作的链接映射,使用相对寻址来伪造重定位和符号数据。这里的目标再次是滥用运行时解析器,将已知内存位置偏移到攻击者想要转移执行的位置。然而,虽然在我们的案例中我们能够提供精心设计的链接映射,但我们无法控制运行时解析器参数。此外,我们还没有执行控制,而是旨在通过我们精心制作的链接映射数据“煽动”运行时解析器,以绕过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);/*查找目标符号。)(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));/*最后,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条目的结构如下:typedefstruct{Elf64_Sxwordd_tag;/*Dynamicentrytype*/union{Elf64_Xwordd_val;/*Integervalue*/Elf64_Addrd_ptr;/*Addressvalue*/}d_un;}Elf64_Dyn;用于访问l_info条目的D_PTR宏定义为:/*所有对fl_info[DT_PLTGOT]、l_info[DT_STRTAB]、l_info[DT_SYMTAB]、l_info[DT_RELA]、l_info[DT_REL]、l_info[DT_JMPREL]和l_info[VERSYMIDX(DT_VERSYM)]的引用都必须通过D_PTR宏访问。该宏是必需的,因为对于大多数体系结构,条目已被重新定位,但对于某些人来说,我们需要在访问时间重新定位。>d_un.d_ptr#endif请注意,在大多数情况下,D_PTR只是从.dynamic段条目中获取d_ptr字段来检索关联段的运行时重定位地址。例如,constchar*strtab=(constvoid*)D_PTR(l,l_info[DT_STRTAB]);将根据提供的指向.dynamic条目的指针在l_info数组的索引DT_STRTAB处获取上述内容。条目的d_ptr字段。在指针方面,这里有点头疼,但我们只需要记住,我们并不是通过操作链接映射中的l_info数组来直接提供指向解析器所需的各个段的指针,而是指向(假设的)指向.dynamic条目的指针,该条目应包含指向偏移量+8处的关联段的指针。早些时候,我们介绍了如何使用精心制作的链接映射数据将伪造的二进制段提供给解析器。接下来,让我们快速了解一下_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;/*符号值*/Elf64_Xwordst_size;/*符号大小*/}Elf64_Sym;我们再回顾一下_dl_fixup的代码。注意在[1]处,_dl_fixup的reloc_arg参数是relocationrecordtable的索引,读取relocationrecord。这条重定位记录提供了一个reloc->r_info字段,通过宏分为高32位符号表索引和低32位重定位类型。在[2]处,_dl_fixup使用reloc->r_info索引从符号表中获取对应的符号项。在reloc->r_info处的ELF_MACHINE_JMP_SLOT类型断言和sym->st_other处的符号查找范围检查之前,实际的函数解析以非常简单的方式进行。首先,通过在链接图中添加l->l_addr字段和符号表项的sym->st_value字段来解析函数地址。然后将解析出的值写入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部分的指针,我们就可以将执行重定向到我们的优势。总结在本文中,我们将深入探讨在通过外部函数接口(FFI)将基于C/C++的库“粘合”到解释型语言的过程中,安全漏洞是如何产生的。由于文章篇幅,我们将分多篇介绍,更多精彩内容敬请期待!本文翻译自:https://securitylab.github.com/research/now-you-c-me-part-two