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