dynamic-dso/dynso.c

687 lines
20 KiB
C

#define _GNU_SOURCE
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <elf.h>
#include <dlfcn.h>
#include <link.h>
#include <math.h>
#include <sys/auxv.h>
#include <sys/stat.h>
#define BREAK() asm volatile("int3":::"memory")
static void print_link_map(struct link_map* glob) {
printf("[*] link map:\n");
for (size_t i = 0; glob; glob = glob->l_next, ++i) {
printf("- [%zu] %p -> '%s'\n", i, (void*)glob->l_addr, glob->l_name);
}
}
struct rtldinfo {
ElfW(Addr) base;
ElfW(Sym)* symtab;
char* strtab;
size_t strsz;
size_t shsz;
ptrdiff_t syment;
bool sym_malloc, str_malloc;
};
struct link_map_priv1 {
// SVR4
ElfW(Addr) l_addr;
char* l_name;
ElfW(Dyn)* l_ld;
struct link_map_priv1* l_next, *l_prev;
// glibc-specific
struct link_map_priv1* l_real;
Lmid_t l_ns;
struct libname_list* l_libname;
ElfW(Dyn)* l_info[];
};
struct libname_list;
struct r_found_version;
struct r_search_path_elem;
struct r_file_id {
dev_t dev;
ino64_t ino;
};
struct r_scope_elem {
struct link_map_priv1** r_list;
unsigned int r_nlist;
};
struct r_search_path_struct {
struct r_search_path_elem** dirs;
int malloced;
};
struct link_map_priv2 {
const ElfW(Phdr)* l_phdr;
ElfW(Addr) l_entry;
ElfW(Half) l_phnum;
ElfW(Half) l_ldnum;
struct r_scope_elem l_searchlist, l_symbolic_searchlist;
struct link_map* l_loader;
struct r_found_version* l_versions;
unsigned int l_nversions;
Elf_Symndx l_nbuckets;
Elf32_Word l_gnu_bitmask_idxbits;
Elf32_Word l_gnu_shift;
const ElfW(Addr)* l_gnu_bitmask;
union {
const Elf32_Word* l_gnu_buckets;
const Elf_Symndx* l_chain;
};
union {
const Elf32_Word* l_gnu_chain_zero;
const Elf_Symndx* l_buckets;
};
unsigned int l_direct_opencount;
enum {
lt_executable,
lt_library,
lt_loaded
} l_type : 2;
unsigned int l_relocated : 1;
unsigned int l_init_called : 1;
unsigned int l_global : 1;
unsigned int l_reserved : 2;
unsigned int l_phdr_allocated : 1;
unsigned int l_soname_added : 1;
unsigned int l_faked : 1;
unsigned int l_need_tls_init : 1;
unsigned int l_auditing : 1;
unsigned int l_audit_any_plt : 1;
unsigned int l_removed : 1;
unsigned int l_contiguous : 1;
unsigned int l_symbolic_in_local_scope : 1;
unsigned int l_free_initfini : 1;
//bool l_nodelete_active, l_nodelete_pending; // FIXME
// ^ these two are added somewhere between glibc 2.30 (the one on my
// system) and now (current master), I should probably add a priv3 thing
// to accomodate for this, but EFFORT
/*#if defined(__i386__) || defined(__x86_64__)
enum {
lc_unknown = 0,
lc_none = 1<<0,
lc_ibt = 1<<1,
lc_shstk = 1<<2,
lc_ibt_and_shstk = lc_ibt | lc_shstk
} l_cet : 3;
#endif*/
};
struct link_map_priv3 {
struct r_search_path_struct l_rpath_dirs;
struct reloc_result {
ElfW(Addr) addr;
struct link_map_priv1* bound;
unsigned int boundndx;
uint32_t enterexit;
unsigned int flags;
unsigned int init;
} *l_reloc_result;
ElfW(Versym)* l_versyms;
const char* l_origin;
ElfW(Addr) l_map_start, l_map_end;
ElfW(Addr) l_text_end;
struct r_scope_elem* l_scope_mem[4];
size_t l_scope_max;
struct r_scope_elem** l_scope;
struct r_scope_elem* l_local_scope[2];
struct r_file_id l_file_id;
struct r_search_path_struct l_runpath_dirs;
struct link_map** l_initfini;
struct link_map_reldeps {
unsigned int act;
struct link_map* list[];
} *l_reldeps;
unsigned int l_reldepsmax;
unsigned int l_used;
ElfW(Word) l_feature_1, l_flags_1, l_flags;
int l_idx;
// more stuff...
};
extern void _start(void) __attribute__((__noreturn__));
static ptrdiff_t get_priv1_priv2_offset(void) {
// the offset between the priv1 and priv2 parts of a link_map is a
// constant but only determinted at runtime, as we don't know with which
// glibc version we're running (nor do we want to hardcode stuff)
// link_map of the main executable always comes first (... I hope)
struct link_map_priv1* exe_map = (struct link_map_priv1*)_r_debug.r_map;
// priv2 contains a field with the entrypoint of an ELF. we know ours because
// we're the executable running this stuff (if not, we could try parsing
// the EHDR of the main executable and calculate the address from there,
// but I'm too lazy for that now)
ElfW(Addr) entry = (ElfW(Addr))_start; // entrypoint is actually _start (from crt0.o), not main!
// time for crimes
const ElfW(Addr)* crimes = (const ElfW(Addr)*)&exe_map->l_info[0];
for (size_t iter = 0;
*crimes != entry && iter < 4096/sizeof(*crimes);
++crimes, ++iter)
;
if (*crimes != entry) {
printf("[!] E: can't get priv2 offset, bailing out...\n");
exit(1);
}
// crimes is now &priv2->l_entry;
ptrdiff_t r = (ptrdiff_t)crimes - (ptrdiff_t)exe_map - offsetof(struct link_map_priv2, l_entry);
printf("[-] offset of priv2 is %zd\n", r);
return r;
}
static struct link_map_priv2* priv1_to_priv2(struct link_map_priv1* lm) {
static ptrdiff_t d = -1;
if (d < 0) d = get_priv1_priv2_offset();
return (struct link_map_priv2*)((ptrdiff_t)lm + d);
}
static ptrdiff_t get_priv2_priv3_offset(void) {
struct link_map_priv1* exe_map = (struct link_map_priv1*)_r_debug.r_map;
struct link_map_priv2* exe2 = priv1_to_priv2(exe_map);
// priv3 has a 'versyms' field, which we can easily fetch from the dyn
// table for comparison. otherwise, same method as above
ElfW(Versym)* versym_good = NULL;
for (size_t i = 0; _DYNAMIC[i].d_tag != DT_NULL && i < 128; ++i) {
if (_DYNAMIC[i].d_tag == DT_VERSYM) {
versym_good = (ElfW(Versym*))_DYNAMIC[i].d_un.d_ptr;
break;
}
}
if (versym_good == NULL) {
printf("[!] E: can't find VERSYM, bailing out...\n");
exit(1);
}
const ElfW(Versym)** crimes = (const ElfW(Versym)**)&exe2->l_direct_opencount;
for (size_t iter = 0;
*crimes != versym_good && iter < 4096/sizeof(*crimes);
++crimes, ++iter)
;
if (*crimes != versym_good) {
printf("[!] E: can't get priv3 offset, bailing out...\n");
exit(1);
}
// crimes is now &priv3->l_versyms;
ptrdiff_t r = (ptrdiff_t)crimes - (ptrdiff_t)exe2 - offsetof(struct link_map_priv3, l_versyms);
printf("[-] offset of priv3 is %zd\n", r);
return r;
}
static struct link_map_priv3* priv2_to_priv3(struct link_map_priv2* l2) {
static ptrdiff_t d = -1;
if (d < 0) d = get_priv2_priv3_offset();
return (struct link_map_priv3*)((ptrdiff_t)l2 + d);
}
inline static struct link_map_priv3* priv1_to_priv3(struct link_map_priv1* lm) {
return priv2_to_priv3(priv1_to_priv2(lm));
}
static struct rtldinfo get_rtld_symtab(void) {
ElfW(Addr) ldso_base = (ElfW(Addr))getauxval(AT_BASE);
printf("[-] getauxval(AT_BASE) = %p\n", (void*)ldso_base);
printf("[-] getauxval(AT_SYSINFO_EHDR) = %p\n", (void*)getauxval(AT_SYSINFO_EHDR));
struct link_map* glob = _r_debug.r_map;
print_link_map(glob);
for (size_t i = 0; glob; glob = glob->l_next, ++i) {
if (glob->l_addr != ldso_base) continue;
printf("[*] [%zu] %p:%s is ld.so!\n", i, (void*)glob->l_addr, glob->l_name);
ElfW(Sym)* symtab = NULL;
char* strtab = NULL;
size_t strsz = 0;
size_t shsz = ~(size_t)0;
ptrdiff_t syment = 0;
bool sym_malloc = false, str_malloc = false;
// gives us .dynsym, not .symtab -> kinda useless
ElfW(Dyn)* dyn = glob->l_ld;
for (size_t j = 0; dyn->d_tag != DT_NULL; ++dyn, ++j) {
if (dyn->d_tag == DT_STRTAB) {
strtab = (char*)(/*glob->l_addr +*/ dyn->d_un.d_ptr);
printf(" - [%zu] strtab\n", j);
}
if (dyn->d_tag == DT_SYMTAB) {
symtab = (ElfW(Sym) *)(/*glob->l_addr +*/ dyn->d_un.d_ptr);
printf(" - [%zu] symtab\n", j);
}
if (dyn->d_tag == DT_STRSZ) {
strsz = dyn->d_un.d_val;
printf(" - [%zu] strsz\n", j);
}
if (dyn->d_tag == DT_SYMENT) {
syment = dyn->d_un.d_val;
printf(" - [%zu] syment\n", j);
}
}
if (!memcmp((const void*)ldso_base, ELFMAG, SELFMAG)) {
printf("[-] ldso is ELF\n");
FILE* fff = fopen(glob->l_name, "rb");
if (!fff) {printf("no file\n");exit(1);}
ElfW(Ehdr)* ldso = (ElfW(Ehdr)*)ldso_base;
if (ldso->e_shoff && ldso->e_shnum && ldso->e_shentsize) {
//printf(" - shoff=0x%zx shnum=0x%zx\n", ldso->e_shoff, ldso->e_shnum);
fseek(fff, ldso->e_shoff, SEEK_SET);
uint8_t shdr_blob[ldso->e_shentsize];
ElfW(Shdr)* shdr = (ElfW(Shdr)*)shdr_blob;
fseek(fff, ldso->e_shoff + ldso->e_shstrndx*ldso->e_shentsize, SEEK_SET);
fread(shdr_blob, 1, sizeof(shdr_blob), fff);
char* shstrtab = (char*)malloc(shdr->sh_size);
fseek(fff, shdr->sh_offset, SEEK_SET);
fread(shstrtab, 1, shdr->sh_size, fff);
for (size_t j = 0; j < ldso->e_shnum; ++j) {
fseek(fff, ldso->e_shoff + j*ldso->e_shentsize, SEEK_SET);
fread(shdr_blob, 1, sizeof(shdr_blob), fff);
//printf(" [-] shdr [%zx] -> %p, type=0x%zx\n", j, shdr, shdr->sh_type);
if (shdr->sh_type == SHT_SYMTAB) {
printf(" [-] using .symtab instead of .dynsym at offset 0x%zx\n", shdr->sh_offset);
//symtab = (ElfW(Sym)*)(ldso_base + shdr->sh_offset); // not mapped in phdrs...
sym_malloc = true;
symtab = (ElfW(Sym)*)malloc(shdr->sh_size);
fseek(fff, shdr->sh_offset, SEEK_SET);
fread(symtab, 1, shdr->sh_size, fff);
shsz = shdr->sh_size;
syment = shdr->sh_entsize; // not *technically* needed, but doing this for completeness' sake
}
if (shdr->sh_type == SHT_STRTAB && !strcmp(&shstrtab[shdr->sh_name], ".strtab")) {
printf(" [-] using .strtab instead of .dynstr at offset 0x%zx\n", shdr->sh_offset);
str_malloc = true;
strtab = (char*)malloc(shdr->sh_size);
fseek(fff, shdr->sh_offset, SEEK_SET);
fread(strtab, 1, shdr->sh_size, fff);
strsz = shdr->sh_size;
}
}
free(shstrtab);
} else printf("wut\n");
fclose(fff);
}
printf(" [-] symtab=%p\tstrtab=%p\tstrsz=0x%zx\tsyment=0x%zx\n",
symtab, strtab, strsz, syment);
printf("[-] resolving hidden needed symbols\n");
struct rtldinfo ret;
ret.base = ldso_base;
ret.symtab = symtab;
ret.strtab = strtab;
ret.strsz = strsz;
ret.shsz = shsz;
ret.syment = syment;
ret.sym_malloc = sym_malloc;
ret.str_malloc = str_malloc;
return ret;
}
printf("wtf\n");
exit(1);
}
// type signature for _dl_new_object
typedef struct link_map_priv1* (*dl_new_object_f)(char* realname, const char* libname,
int type, struct link_map* loader, int mode, Lmid_t nsid);
static dl_new_object_f _dl_new_object;
// type signature for _dl_add_to_namespace_list
typedef void (*dl_add_to_namespace_list_f)(struct link_map_priv1 *new, Lmid_t nsid);
static dl_add_to_namespace_list_f _dl_add_to_namespace_list;
// type signature for _dl_setup_hash
typedef void (*dl_setup_hash_f)(struct link_map_priv1* map);
static dl_setup_hash_f _dl_setup_hash;
typedef void* (*gnu_ifunc_f)(void);
static void* sym2addr(ElfW(Addr) ldso_base, ElfW(Sym)* sym) {
void* r = (void*)(ldso_base + sym->st_value);
if (ELF64_ST_TYPE(sym->st_info) == STT_GNU_IFUNC) { // sigh
printf("[-] W: IFUNC. not tested.\n");
return ((gnu_ifunc_f)(r))();
} else {
return r;
}
}
static void link_private_rtld_syms(void) {
struct rtldinfo rtldinf = get_rtld_symtab();
ElfW(Sym)* sym = rtldinf.symtab;
for (size_t j = 0; j*rtldinf.syment < rtldinf.shsz;
++j, sym = (ElfW(Sym*))((ptrdiff_t)sym + rtldinf.syment)) {
if (ELF64_ST_BIND(sym->st_info) != STB_LOCAL) continue;
if (sym->st_value == 0) continue;
if (sym->st_name >= rtldinf.strsz) continue;
const char* name = &rtldinf.strtab[sym->st_name];
if (!*name) continue;
if (!strcmp(name, "_dl_new_object")) {
printf("- [%zu] -> info=0x%x other=0x%x value=0x%zx\t'%s'\n", j,
sym->st_info, sym->st_other, sym->st_value, name);
_dl_new_object = (dl_new_object_f)sym2addr(rtldinf.base, sym);
}
if (!strcmp(name, "_dl_add_to_namespace_list")) {
printf("- [%zu] -> info=0x%x other=0x%x value=0x%zx\t'%s'\n", j,
sym->st_info, sym->st_other, sym->st_value, name);
_dl_add_to_namespace_list = (dl_add_to_namespace_list_f)sym2addr(rtldinf.base, sym);
}
if (!strcmp(name, "_dl_setup_hash")) {
printf("- [%zu] -> info=0x%x other=0x%x value=0x%zx\t'%s'\n", j,
sym->st_info, sym->st_other, sym->st_value, name);
_dl_setup_hash = (dl_setup_hash_f)sym2addr(rtldinf.base, sym);
}
}
if (rtldinf.sym_malloc) free(rtldinf.symtab);
if (rtldinf.str_malloc) free(rtldinf.strtab);
printf("[*] _dl_new_object=%p\n", _dl_new_object);
printf("[*] _dl_add_to_namespace_list=%p\n", _dl_add_to_namespace_list);
printf("[*] _dl_setup_hash=%p\n", _dl_setup_hash);
}
struct inject_undo_info {
struct r_scope_elem** dest;
struct r_scope_elem* old;
struct r_scope_elem* alloced;
struct link_map_priv1** rlist;
};
static void inject_undo(struct inject_undo_info inf) {
*inf.dest = inf.old;
if (inf.alloced) free(inf.alloced);
free(inf.rlist);
}
static struct inject_undo_info inject_in_scope_of(struct link_map_priv1* victim, struct link_map_priv1* add) {
struct inject_undo_info r;
r.alloced = NULL;
struct link_map_priv2* vic2 = priv1_to_priv2(victim);
struct link_map_priv3* vic3 = priv2_to_priv3(vic2);
struct r_scope_elem* oldscope = vic3->l_scope[0];
struct r_scope_elem* scope;
for (size_t i = 0; i < 3; ++i) {
// scope struct has the size of two entries, might hack it in here :D
if (vic3->l_scope_mem[i] == NULL && vic3->l_scope_mem[i+1] == NULL) {
scope = (struct r_scope_elem*)&vic3->l_scope_mem[i];
break;
}
}
if (scope == NULL) {
// otherwise, let's just allocate one
scope = (struct r_scope_elem*)calloc(1, sizeof *scope);
r.alloced = scope;
}
// sadly this one won't fit anywhere :/
struct link_map_priv1** rlist = (struct link_map_priv1**)calloc(
oldscope->r_nlist+1, sizeof(struct link_map_priv1*));
for (size_t i = 0; i < oldscope->r_nlist; ++i)
rlist[i] = oldscope->r_list[i];
rlist[oldscope->r_nlist] = add;
scope->r_list = rlist;
scope->r_nlist = oldscope->r_nlist + 1;
// now apply the new scope
vic3->l_scope[0] = scope;
r.dest = &vic3->l_scope[0];
r.old = oldscope;
r.rlist = rlist;
return r;
//struct link_map_priv2* add2 = priv1_to_priv2(add );
//printf("max scope=%zu\n", vic2->l_scope_max);
for (size_t i = 0; vic3->l_scope[i] != NULL; ++i) {
/*printf("scope %zu: n=%zu p=%p\n", i, scope->r_nlist, vic2->l_scope[i]);
for (size_t j = 0; j < scope->r_nlist; ++j) {
struct link_map_priv1* ent = scope->r_list[j];
struct link_map_priv2* en2 = priv1_to_priv2(ent);
printf("scope i=%zu ent=%zu: name=%s orig=%s\n", i, j, ent->l_name,
en2->l_origin ? en2->l_origin : "<null>");
}*/
// TODO: check if we're allowed to do this w/o corruping memory?
printf(" [-] injecting in scope %zu (0x%zx) at index %u\n", i, (ptrdiff_t)scope->r_list - (ptrdiff_t)victim, scope->r_nlist);
// 'scope' is often in vic3->l_scope_mem
for (size_t iii=0;iii<4;++iii)
printf("scopemem[%zu] = %p\n", iii, vic3->l_scope_mem[iii]);
// FIXME: apparently not, asan does NOT like this. but we'll do this
// for now anyway. fix later.
scope->r_list[scope->r_nlist] = add;
++scope->r_nlist;
}
/*printf("scope mem:\n");
for (size_t j = 0; j < 4; ++j) {
__auto_type m = vic2->l_scope_mem[j];
printf("mem[%zu] m=%p\n", j, m);//m->r_nlist, m->r_list[0]->l_name);
}*/
/*printf("inject after: i=%zu\n", i);
__auto_type sc = (struct r_scope_elem*)calloc(1, sizeof(struct r_scope_elem));
sc->r_nlist = 1;
sc->r_list = &add;
vic2->l_scope[i+1] = NULL; // end the chain here*/
}
struct fakelib {
struct inject_undo_info undoinf;
struct link_map_priv1* fake1;
struct link_map_priv2* fake2;
struct link_map_priv3* fake3;
};
struct fakesym {
const char* name;
void* value;
};
static void a_function_impl(void) {
printf("hello world!\n");
}
static ElfW(Phdr) my_phdr[] = {
(ElfW(Phdr)){
.p_type = PT_DYNAMIC,
.p_flags = PF_R,
.p_offset = 0,
.p_vaddr = 0x69420,
.p_paddr = 0x69420,
.p_filesz = 0,
.p_memsz = 0,
.p_align = 65536
}
};
static char my_strtab[] =
"\x00" // first entry is always empty
"testsym\x00"
"a_function\x00"
"\x00" // end with another null-terminator
;
#define STR2TABIDX(s) ((size_t)strstr(my_strtab, (s)) - (size_t)my_symtab)
static ElfW(Sym) my_symtab[] = {
(ElfW(Sym)){.st_name=0, .st_info=0, .st_other=0, .st_shndx=0, .st_value=0, .st_size=0},
(ElfW(Sym)){
.st_name = 1,//STR2TABIDX("testsym"),
.st_info = (STB_GLOBAL<<4)|STT_FUNC,
.st_other = STV_DEFAULT,
.st_shndx = 0,
.st_value = 0x1337,
.st_size = 0x69
},
(ElfW(Sym)){
.st_name = 9,//STR2TABIDX("a_function"),
.st_info = (STB_GLOBAL<<4)|STT_FUNC,
.st_other = STV_DEFAULT,
.st_shndx = 0,
.st_value = (size_t)a_function_impl - 0x06900000,
.st_size = 0x420
},
(ElfW(Sym)){.st_name=0, .st_info=0, .st_other=0, .st_shndx=0, .st_value=0, .st_size=0},
};
static Elf_Symndx my_idxtab[] = {
0, // dummy value
1, // testsym
2, // a_function
STN_UNDEF, SHN_UNDEF
};
// adjusted dyn addresses by rtld:
// hash, pltgot, strtab, symtab, rela, rel, jmprel, versym, gnu_hash
// this means rtld will use the values as direct addresses instead of
// calculating their offsets etc.!
static ElfW(Dyn) my_dyn[] = {
(ElfW(Dyn)){.d_tag = DT_SYMTAB, .d_un.d_ptr = (ElfW(Addr))my_symtab },
(ElfW(Dyn)){.d_tag = DT_STRTAB, .d_un.d_ptr = (ElfW(Addr))my_strtab },
(ElfW(Dyn)){.d_tag = DT_NULL , .d_un.d_val = 0}
};
int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
//my_symtab[1].st_name = STR2TABIDX("testsym");
//my_symtab[2].st_name = STR2TABIDX("a_function");
// no worky - symbol is local, not global
//_dl_new_object = (dl_new_object_f)dlsym(RTLD_DEFAULT, "_dl_new_object");
// so let's try this instead!
link_private_rtld_syms();
printf("[*] performing magic...\n");
struct link_map_priv1* lm = _dl_new_object((char*)"lol", "lol2",
1/*lt_library*/, _r_debug.r_map, 0, LM_ID_BASE);
struct link_map_priv2* l2 = priv1_to_priv2(lm);
struct link_map_priv3* l3 = priv2_to_priv3(l2);
lm->l_addr = 0x06900000;
// not needed apparently!
//l2->l_phdr = my_phdr;
//l2->l_phnum = sizeof(my_phdr)/sizeof(my_phdr[0]);
lm->l_ld = my_dyn;
lm->l_info[DT_SYMTAB] = &my_dyn[0];
lm->l_info[DT_STRTAB] = &my_dyn[1];
l2->l_ldnum = 2;
l3->l_origin = "mwahahah";
//printf("symtab addr=%p\n", &lm->l_info[DT_SYMTAB]);
//BREAK();
//printf("[*] lm=%p\n", lm);
// TODO: set up:
// * phdr, phnum
// * LOAD (addr, map_end, text_end)
// * needed at all?
// * DYNAMIC (ld, ldnum) stuff
// * DYNAMIC: symtab, strtab
// * map_start, addr, map_end, text_end
// * is this even needed????
// * ld
// * call elf_get_dynamic_info(lm, temp_dyn_array); // temp_dyn_array: ElfW(Dyn)[DL_RO_DYN_TEMP_CNT]
// * need to do this ourselves...
// * call _dl_setup_hash(lm)
// * nah, not needed
// * relocated = 1, used = 1, local_scope[0]->r_{nlist = 1,list = &lm->l_real}
// * init lm->l_libname->name
_dl_setup_hash(lm);
l2->l_relocated = 1;
l3->l_used = 1;
// limit symbol resolution scope
l2->l_global = 1;
l2->l_faked = 1;
l2->l_type = lt_loaded;
l2->l_nbuckets = 1;
l2->l_buckets = &my_idxtab[1];
l2->l_chain = &my_idxtab[1];
l3->l_initfini = NULL;
_dl_add_to_namespace_list(lm, LM_ID_BASE);
print_link_map(_r_debug.r_map);
printf("[*] injecting into scope of main executable\n");
struct inject_undo_info rrr = inject_in_scope_of((struct link_map_priv1*)_r_debug.r_map, lm);
/*printf("scope_max offset=%zu, should be 904\n", (size_t)&l2->l_scope_max - (size_t)lm);
printf("file_id offset=%zu, should be 936\n", (size_t)&l2->l_file_id - (size_t)lm);
printf("runpath_dirs offset=%zu, should be 952\n", (size_t)&l2->l_runpath_dirs - (size_t)lm);*/
printf("[*] trying dlsym(\"testsym\") now: ");
void* res = dlsym(RTLD_DEFAULT, "testsym");
printf("-> %p\n", res);
printf("[*] trying dlsym(\"a_function\") now: ");
//BREAK();
void (*resf)(void) = dlsym(RTLD_DEFAULT, "a_function");
printf("-> %p\n", resf);
printf("[*] calling a_function():\n");
resf();
//printf("sin(argc) = %f\n", sin(argc));
inject_undo(rrr);
l2->l_removed = 1;
return 0;
}