The Audit DSOs of the rtld ___ ___ / _ \ / _ \ __ __| (_) || | | | ___ \ \/ / \__. || | | | / __| > < / / | |_| || (__ /_/\_\ /_/ \___/ \___| [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. Intro The article covered explanation of The Audit DSOs of Internal of the rtld and writing a DSO, Lastly The vulnerability. ----[ 2. The Audit DSOs ----[ 2.1 The Audit DSO Internal The audit DSO loaded by the rtld after running a process. In other words, It affected all process In the userland. And The audit DSOs module path passed to an environment of $LD_AUDIT. For Instance export $LD_AUDIT=libpcrprofile.so. First, Just See the process of loading audit DSOs. Auditing DSOs load process: (1) Called the function to open the auditing DSO. (2) Do lookup "la_version" symbol and call it. (3) Do lookup symbols by using auditing Interface name and call it. (4) If an Interface is binded, Set 1 to last bit of the flag of main_map(linkmap object)&dl_rtld_map (linkmap object)->l_audit[cnt].bindflags. (5) Setup RTLD debugger with .debug dynamic section. (6) Called "la_activity" symbol function with the constant of LA_ACT_ADD to print out the message of added the auditing Interface object. * (5), (6) is the rtld debugger with la_activity Interface of auditing DSOs. ----[ 2.1.1 The structs of Audit Lists and Interfaces Audit DSO have two structs to load a security module on the rtld. First struct for the audit lists and the next struct for the audit Interfaces. The audit lists struct maintained the loaded security module like an shared object, the struct defined in the global In the rtld.c rtld source code and the audit Interfaces struct mainta ined virtual function pointers to Interface for each DSOs. The audit_list struct gots loaded audit DSO name as the member variable of *name and *next pointer for the next module, It's a queue by a single linked list. *audit_list .-----------. .----------. .----------. | old_newp | | old_newp | | newp | | - *name | | *name | | *name | | - *next |--->| *next |--->| *next |---+ '-----------' '----------' '----------' | (first) ^ | | | +---------+ (The Last Entry) The *audit_list variable pointers to the entry for the first loaded dso and the last entry always for last loaded module. See the struct! The audit_list struct In elf/rtld.c: ----snip----snip----snip----snip----snip----snip---- /* List of auditing DSOs. */ static struct audit_list { const char *name; struct audit_list *next; } *audit_list; ----snip----snip----snip----snip----snip----snip---- The function of process_dl_audit() In elf/rtld.c added an entry to the queue. Just See the next, The audit Interfaces struct as follows: *audit_ifaces (Interfaces) .---------------. .----------. | old | | new | | - (*activity) | | | | - (*objsearch)| | ... | ... n | - (*objopen) | | | GL(dl_naudit)=n | ... | | | | ... | | | | - *next |-->| *next | '---------------' '----------' The Interfaces are symbols on a shared object and the symbol's function pointer loaded on the struct of *audit_ifaces by the rtld. The struct also same queue as the *audit_list and those Intefaces will called by the rtld to load and operation the Audit DSOs. An *audit_list entry per an *audit_ifaces entry even though those structs are not linked for each. The count of audit Interfaces stored on the rtld's global variable of GL(dl_naudit). The audit Interface lookup'd and called as follows: (1) Lookup la_objopen symbol. (2) Called the symbol via calling the audit_ifaces->objopen() function pointer. And The Auditing Interfaces: - 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 Load an audit DSO The audit DSOs process codes In the dl_main() of rtld's main function. rtld called dlmopen_doit() In rtld.c to load the audit DSOs. And dlmopen_doit() call _dl_open() with the audit DSO path as first argument, added __RTLD_AUDIT flag to second argument. It's loaded like the shared object. The dlmopen_doit() In elf/rtld.c: ----snip----snip----snip----snip----snip----snip----snip----snip---- ... 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); } ----snip----snip----snip----snip----snip----snip----snip----snip---- If audit_list variable is exists, entered the load process. And prepared dlmopen_args struct for the argument and called dlmopen _doit() to load the dso. The code of dl_main In elf/rtld.c: ----snip----snip----snip----snip----snip----snip----snip----snip---- 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); ----snip----snip----snip----snip----snip----snip----snip----snip---- Now, _dl_open() loaded the audit DSO passed from the environment of $LD_AUDIT. The dso's Information loaded on somewhere of the link_map objects. ----[ 2.1.3 Do Lookup The Interfaces After a loaded audit dso, the next is to do Lookup the Interfaces of the module. The lookup_doit() did the lookup a Interface. First The la_version symbol lookup'd from the ELF object the process running In userland by using the lookup function. The Interface to check the Interface version is matched. The lookup_doit() In elf/rtld.c: ----snip----snip----snip----snip----snip----snip----snip----snip---- 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); } ----snip----snip----snip----snip----snip----snip----snip----snip---- Do Lookup la_version symbol, a Interface! The lookup_args struct's ->name member variable gots to lookup Interface name and ->map gots NULL. The _dl_catch_error() function's 4th argument is lookup_doit() and 5th argument is &lookup_args. _dl_catch_error() will called lookup_doit() with an argument as &lookup_args. The lookup_doit() In elf/rtld.c: ----snip----snip----snip----snip----snip----snip----snip----snip---- 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); ----snip----snip----snip----snip----snip----snip----snip----snip---- After lookup'd la_version Interface, store the address of the lookup'd symbol that largs.result to laversion function pointer and call it to check Interface version match. If matched, entered below block to lookup other Interfaces also. The lookup_doit() In elf/rtld.c: ----snip----snip----snip----snip----snip----snip----snip----snip---- unsigned int (*laversion) (unsigned int); unsigned int lav; if (err_str == NULL && (laversion = largs.result) != NULL && (lav = laversion (LAV_CURRENT)) > 0 && lav <= LAV_CURRENT) { ----snip----snip----snip----snip----snip----snip----snip----snip---- The next, do lookup other Interfaces. The *newp union declared In the code with the member of the audit_ifaces struct, callback function pointer of the Interface and In the follow code, 6 Interfaces will be lookup'd In a while loop to process a linked list with the *next pointer of *audit_list. The Interface of la_objsearch searched symbol on ELF object and the la_symbind32 or la_symbind64 Interface binding a symbol from an ELF object. All the audit Interfaces Implemeneted In the source code of a audit dso as the test code of elf/tst-auditmod1.c. See the test code. la_symbind32 / la_symbind64 return the relative addr of the symbol to bind. The codes In elf/tst-auditmod1.c: ----snip----snip----snip----snip----snip----snip----snip----snip---- ... 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= %u\n", 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= %u\n", symname, (long int) sym->st_value, ndx, *flags); return sym->st_value; } ... ----snip----snip----snip----snip----snip----snip----snip----snip---- 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\0" "la_objsearch\0" "la_objopen\0" "la_preinit\0" #if __ELF_NATIVE_CLASS == 32 "la_symbind32\0" #elif __ELF_NATIVE_CLASS == 64 "la_symbind64\0" #else # error "__ELF_NATIVE_CLASS must be defined" #endif #define STRING(s) __STRING (s) "la_" STRING (ARCH_LA_PLTENTER) "\0" "la_" STRING (ARCH_LA_PLTEXIT) "\0" "la_objclose\0"; 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, '\0') + 1; } while (*cp != '\0'); 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); ----snip----snip----snip----snip----snip----snip----snip----snip---- The auditing DSOs marked 1 as In used on ->l_auditing member variable of dlmargs.map It's declared In codes for open the dso. The .map member variable of dlmargs gots the pointer for allocated linkmap object after called dlmopen_doit() for the audit dso. In other words The ->l_auditing member variable auditing dso's linkmap object marked as 1 than the process's link_map object. ----[ 2.1.4 Open The Object Finally, Do Open the object of *afct for the audit_ifaces It will called the dso's la_open function Implemented by the author of the auditing dso as can see In the elf/tst-auditmod1.c. The la_open just print out a message of open this object. The rtld called la_open functions for each audit DSO. ----snip----snip----snip----snip----snip----snip----snip----snip---- /* 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 */ } } } ----snip----snip----snip----snip----snip----snip----snip----snip---- As you may know, Audit DSO can be used for automated analyzing for a monitor In userland, the library layer In the OS architecture. Writing a audit DSO! ----[ 2.2 Writing a audit DSO I demostrated a userland monitor via the Audit DSO. It should be compiled In the rtld source code tree of glibc elf/audit_dso_example.c. audit_dso_example.c: ---- #include #include #include #include #include #include #include #include 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 example\n"); 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: %s\n ", l->l_name); fprintf(fp, "Base Addr of the object: %p\n", l->l_addr); fprintf(fp, "The addr of .dynamic: \n\n ", 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 The vulnerability The taviso's arbitrary audit DSO load bug [1] demostrated the audit DSO's security bug: (1) LD_AUDIT="libpcprofile.so" PCPROFILE_OUTPUT="/etc/cron.d/exploit" The codes of the libpcprofile.so creates a file that $PCPROFILE_ OUTPUT. libpcprofile.so is not a audit dso and cannot be loaded the audit dso loaded as a shared object and the code shared object's executed! It's security bug with a SUID bit. (2) Execute /bin/ping to create a world writable file of /etc/cron.d /exploit. The ping SUID bit! and After the ping process running soon, the rtld loaded the audit dso and created the file. (3) Setup a crontab and wait to escalate the privilege. printf "* * * * * root cp /bin/dash /tmp/exploit; chmod u+s /tmp/exploit\n" > /etc/cron.d/exploit. Arbitrary shared object can be loaded via audit DSO load and the audit dso's code executed when after execute a process In userland If the process gots SUID bit like /bin/ping. What the discussion of the security bug is that the arbitrary shared object load via all process In userland execution time. A shared object also can compiled and loaded with as a user and cannot load it with the privilege. 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. Conclusion Teh article covered The Internal of The Audit DSO and Writing a security module for userland and explained the security bug of arbitrary DSO load. The Audit DSO can be used for a automated monitor In the userland and demostrated it In the audit_dso_example.c. It can be Implemented The monitor In the execution time with the ELF resolve. ----[ 4. References [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 Greets to ... #phrack of efnet ... ... #social of overthewire ... EOF