king

深入考察解释型语言背后隐藏的攻击面,Part 2(三)

king 安全防护 2022-12-01 341浏览 0

接上文:

  • 《深入考察解释型语言背后隐藏的攻击面,Part 1(上)》
  • 《深入考察解释型语言背后隐藏的攻击面,Part 1(下)》
  • 《深入考察解释型语言背后隐藏的攻击面,Part 2(一)》
  • 《深入考察解释型语言背后隐藏的攻击面,Part 2(二)》

在本文中,我们将深入地探讨,在通过外部函数接口(Foreign Function Interface,FFI)将基于C/C++的库“粘合”到解释语言的过程中,安全漏洞是如何产生的。

深入考察解释型语言背后隐藏的攻击面,Part 2(三)

决定最终的漏洞利用策略

为了弄清楚如何在触发我们的堆内存覆盖之前对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.node
gef?irrdi
rdi0x2722ef00x2722ef0
gef?heapchunk$rdi
Chunk(addr=0x2722ef0,size=0x530,flags=PREV_INUSE)
Chunksize:1328(0x530)
Usablesize:1320(0x528)
Previouschunksize:25956(0x6564)
PREV_INUSEflag:On
IS_MMAPPEDflag:Off
NON_MAIN_ARENAflag:Off


gef?

此前,我们已经研究了png_ptr结构体本身,以及如何利用它来颠覆node进程,现在,让我们仔细考察_dl_fixup,以及在解析器代码中发生崩溃的具体原因。

当我们触发崩溃时,我们注意到:

0x00007ffff7de2fb2in_dl_fixup(l=0x2722a10,reloc_arg=0x11d)at../elf/dl-runtime.c:69
69constchar*strtab=(constvoid*)D_PTR(l,l_info[DT_STRTAB]);
gef?p*l
$5={
l_addr=0x4141414141414141,
...
l_info={0x4141414141414141
...
}
gef?pl
$6=(structlink_map*)0x2722a10
gef?

这意味着,我们已经破坏了用于解析png-img库函数的linkmap。实际上,linkmap是一个数据结构,用于保存动态链接器执行运行时解析和重定位所需的所有信息。

下面,我们来看一下linkmap堆块和数据结构未被破坏之前的样子:

gef?heapchunk0x2722a10
Chunk(addr=0x2722a10,size=0x4e0,flags=PREV_INUSE)
Chunksize:1248(0x4e0)
Usablesize:1240(0x4d8)
Previouschunksize:39612548531313(0x240703e24471)
PREV_INUSEflag:On
IS_MMAPPEDflag:Off
NON_MAIN_ARENAflag:Off


gef?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=0x7ffff7ffd9f0
l_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进程的控制权。

深入考察解释型语言背后隐藏的攻击面,Part 2(三)

攻击运行时解析器

作为攻击者,我们经常要从系统代码中提炼出非预期的、但有用的行为。这里的挑战是:不要被那些我们不关心的东西所干扰,而要专注于那些在特定的漏洞利用场景中可资利用的行为。

那么,运行时解析器代码的哪些行为可能对攻击者有用呢?

要回答这个问题,我们必须了解运行时解析器是如何使用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


/*ThisfunctioniscalledthroughaspecialtrampolinefromthePLTthe
firsttimeeachPLTentryiscalled.Wemustperformtherelocation
specifiedinthePLTofthegivensharedobject,andreturntheresolved
functionaddresstothetrampoline,whichwillrestarttheoriginalcall
tothataddress.FuturecallswillbouncedirectlyfromthePLTtothe
function.*/


DL_FIXUP_VALUE_TYPE
attribute_hidden__attribute((noinline))ARCH_FIXUP_ATTRIBUTE
_dl_fixup(
#ifdefELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
#endif
structlink_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.Ifthenormallookuprulesarenot
useddon'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.Thisis
notnecessaryforobjectswhichcannotbeunloadedorwhen
wearenotusinganythreads(yet).*/
intflags=DL_LOOKUP_ADD_DEPENDENCY;
if(!RTLD_SINGLE_THREAD_P)
{
THREAD_GSCOPE_SET_FLAG();
flags|=DL_LOOKUP_GSCOPE_LOCK;
}


#ifdefRTLD_ENABLE_FOREIGN_CALL
RTLD_ENABLE_FOREIGN_CALL;
#endif


result=_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_CALL
RTLD_FINALIZE_FOREIGN_CALL;
#endif


/*Currentlyresultcontainsthebaseloadaddress(orlinkmap)
oftheobjectthatdefinessym.Nowaddinthesymbol
offset.*/
value=DL_FIXUP_MAKE_VALUE(result,
sym?(LOOKUP_VALUE_ADDRESS(result)
+sym->st_value):0);
}
else
{
/*Wealreadyfoundthesymbol.Themodule(andthereforeitsload
address)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.Themacroisneededsincefor
mostarchitecturestheentryisalreadyrelocated-butforsomenot
andweneedtorelocateataccesstime.*/
#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处应该包含一个指向相关段的指针。

深入考察解释型语言背后隐藏的攻击面,Part 2(三)

前面,我们介绍了如何通过精心制作的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++的库“粘合”到解释语言的过程中,安全漏洞是如何产生的。由于篇幅过长,我们将分为多篇进行介绍,更多精彩内容敬请期待!

本文翻

继续浏览有关 安全 的文章
发表评论