687 lines
20 KiB
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;
|
|
}
|
|
|