From 264965b9323f90f1a834fb2d80c362aa91fbb5e5 Mon Sep 17 00:00:00 2001 From: norly Date: Fri, 24 May 2013 07:23:41 +0100 Subject: [PATCH] Object file injection, first part --- include/libelfu/modelops.h | 19 ++- include/options.h | 1 + src/main.c | 26 +++- src/model/clone.c | 35 +++++ src/model/count.c | 20 +++ src/model/insert.c | 75 ++++++++++- src/model/reladd.c | 233 +++++++++++++++++++++++++++++++++ src/model/section-by-type.c | 22 ++++ src/model/section-in-segment.c | 56 ++++++++ src/options.c | 7 + 10 files changed, 488 insertions(+), 6 deletions(-) create mode 100644 src/model/clone.c create mode 100644 src/model/reladd.c create mode 100644 src/model/section-by-type.c create mode 100644 src/model/section-in-segment.c diff --git a/include/libelfu/modelops.h b/include/libelfu/modelops.h index 3ce9a90..1cfe0b6 100644 --- a/include/libelfu/modelops.h +++ b/include/libelfu/modelops.h @@ -7,20 +7,35 @@ #include +size_t elfu_mScnIndex(ElfuElf *me, ElfuScn *ms); size_t elfu_mCountScns(ElfuElf *me); size_t elfu_mCountPhdrs(ElfuElf *me); char* elfu_mScnName(ElfuElf *me, ElfuScn *ms); +ElfuScn* elfu_mScnFirstInSegment(ElfuElf *me, ElfuPhdr *mp); +ElfuScn* elfu_mScnLastInSegment(ElfuElf *me, ElfuPhdr *mp); + +ElfuScn* elfu_mScnByType(ElfuElf *me, Elf32_Word type); + int elfu_mCheck(ElfuElf *me); +ElfuScn* elfu_mCloneScn(ElfuScn *ms); + + ElfuElf* elfu_mFromElf(Elf *e); void elfu_mToElf(ElfuElf *me, Elf *e); void elfu_mExpandNobits(ElfuElf *me, GElf_Off off); -GElf_Xword elfu_mInsertBefore(ElfuElf *me, GElf_Off off, GElf_Xword size); -GElf_Xword elfu_mInsertAfter(ElfuElf *me, GElf_Off off, GElf_Xword size); +GElf_Xword elfu_mInsertSpaceBefore(ElfuElf *me, GElf_Off off, GElf_Xword size); +GElf_Xword elfu_mInsertSpaceAfter(ElfuElf *me, GElf_Off off, GElf_Xword size); + +void elfu_mInsertScnInChainBefore(ElfuElf *me, ElfuScn *oldscn, ElfuScn *newscn); +void elfu_mInsertScnInChainAfter(ElfuElf *me, ElfuScn *oldscn, ElfuScn *newscn); + + +void elfu_mReladd(ElfuElf *me, ElfuElf *mrel); #endif diff --git a/include/options.h b/include/options.h index e634e0b..2210c85 100644 --- a/include/options.h +++ b/include/options.h @@ -13,6 +13,7 @@ typedef struct { unsigned insertAfterOffs; unsigned insertAfterSz; unsigned expandNobitsOffs; + char *fnReladd; } CLIOpts; diff --git a/src/main.c b/src/main.c index a3bf271..2c6268c 100644 --- a/src/main.c +++ b/src/main.c @@ -76,11 +76,33 @@ int main(int argc, char **argv) } if (opts.insertBeforeSz) { - elfu_mInsertBefore(me, opts.insertBeforeOffs, opts.insertBeforeSz); + elfu_mInsertSpaceBefore(me, opts.insertBeforeOffs, opts.insertBeforeSz); } if (opts.insertAfterSz) { - elfu_mInsertAfter(me, opts.insertAfterOffs, opts.insertAfterSz); + elfu_mInsertSpaceAfter(me, opts.insertAfterOffs, opts.insertAfterSz); + } + + if (opts.fnReladd) { + ELFHandles hRel = { 0 }; + ElfuElf *mrel = NULL; + + openElf(&hRel, opts.fnReladd, ELF_C_READ); + if (!hRel.e) { + printf("--reladd: Failed to open file for --reladd, skipping operation.\n"); + } else { + mrel = elfu_mFromElf(hRel.e); + closeElf(&hRel); + if (!me) { + printf("--reladd: Failed to load model for --reladd, skipping operation.\n"); + } else { + printf("--reladd: Model successfully loaded.\n"); + elfu_mCheck(mrel); + printf("--reladd: Input model checked.\n"); + elfu_mReladd(me, mrel); + } + } + } elfu_mCheck(me); diff --git a/src/model/clone.c b/src/model/clone.c new file mode 100644 index 0000000..3dbbc54 --- /dev/null +++ b/src/model/clone.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +ElfuScn* elfu_mCloneScn(ElfuScn *ms) +{ + ElfuScn *newscn; + + assert(ms); + + newscn = malloc(sizeof(ElfuScn)); + if (!newscn) { + ELFU_WARN("elfu_nCloneScn: Could not allocate memory for new ElfuScn.\n"); + return NULL; + } + + + + newscn->shdr = ms->shdr; + newscn->data = ms->data; + if (ms->data.d_buf) { + void *newbuf = malloc(ms->data.d_size); + if (!newbuf) { + ELFU_WARN("elfu_nCloneScn: Could not allocate memory for new data buffer.\n"); + free(newscn); + return NULL; + } + + memcpy(newbuf, ms->data.d_buf, ms->data.d_size); + newscn->data.d_buf = newbuf; + } + + return newscn; +} diff --git a/src/model/count.c b/src/model/count.c index b7c8013..671767e 100644 --- a/src/model/count.c +++ b/src/model/count.c @@ -3,6 +3,26 @@ #include +size_t elfu_mScnIndex(ElfuElf *me, ElfuScn *ms) +{ + ElfuScn *ms2; + size_t i = 1; + + assert(me); + assert(ms); + + CIRCLEQ_FOREACH(ms2, &me->scnList, elem) { + if (ms2 == ms) { + return i; + } + + i++; + } + + /* Section is not in ELF model. This means the calling code is broken. */ + assert(0); +} + /* NULL section is not counted! */ size_t elfu_mCountScns(ElfuElf *me) diff --git a/src/model/insert.c b/src/model/insert.c index 492f06a..312be26 100644 --- a/src/model/insert.c +++ b/src/model/insert.c @@ -20,7 +20,7 @@ * the same address in memory, and everything BEFORE it is shifted to * lower addresses, making space for the new data in-between. */ -GElf_Xword elfu_mInsertBefore(ElfuElf *me, GElf_Off off, GElf_Xword size) +GElf_Xword elfu_mInsertSpaceBefore(ElfuElf *me, GElf_Off off, GElf_Xword size) { ElfuScn *ms; ElfuPhdr *mp; @@ -126,7 +126,7 @@ GElf_Xword elfu_mInsertBefore(ElfuElf *me, GElf_Off off, GElf_Xword size) * use mExpandNobits() first and then inject at the end of * the expansion site. */ -GElf_Xword elfu_mInsertAfter(ElfuElf *me, GElf_Off off, GElf_Xword size) +GElf_Xword elfu_mInsertSpaceAfter(ElfuElf *me, GElf_Off off, GElf_Xword size) { ElfuScn *ms; ElfuPhdr *mp; @@ -211,3 +211,74 @@ GElf_Xword elfu_mInsertAfter(ElfuElf *me, GElf_Off off, GElf_Xword size) return size; } + + + + + +/* Update cross-references */ +static void shiftSections(ElfuElf *me, ElfuScn *first) +{ + ElfuScn *ms = first; + size_t firstIndex = elfu_mScnIndex(me, first); + + do { + if (ms == me->shstrtab) { + me->ehdr.e_shstrndx++; + } + + ms = CIRCLEQ_LOOP_NEXT(&me->scnList, ms, elem); + } while (ms != CIRCLEQ_FIRST(&me->scnList)); + + CIRCLEQ_FOREACH(ms, &me->scnList, elem) { + switch (ms->shdr.sh_type) { + case SHT_REL: + case SHT_RELA: + if (ms->shdr.sh_info >= firstIndex) { + ms->shdr.sh_info++; + } + case SHT_DYNAMIC: + case SHT_HASH: + case SHT_SYMTAB: + case SHT_DYNSYM: + case SHT_GNU_versym: + case SHT_GNU_verdef: + case SHT_GNU_verneed: + if (ms->shdr.sh_link >= firstIndex) { + ms->shdr.sh_link++; + } + } + } +} + + +/* + * Insert a section into an ELF model, /before/ a given other section + */ +void elfu_mInsertScnInChainBefore(ElfuElf *me, ElfuScn *oldscn, ElfuScn *newscn) +{ + assert(me); + assert(oldscn); + assert(newscn); + + shiftSections(me, oldscn); + + CIRCLEQ_INSERT_BEFORE(&me->scnList, oldscn, newscn, elem); +} + + +/* + * Insert a section into an ELF model, /after/ a given other section + */ +void elfu_mInsertScnInChainAfter(ElfuElf *me, ElfuScn *oldscn, ElfuScn *newscn) +{ + assert(me); + assert(oldscn); + assert(newscn); + + if (oldscn != CIRCLEQ_LAST(&me->scnList)) { + shiftSections(me, CIRCLEQ_NEXT(oldscn, elem)); + } + + CIRCLEQ_INSERT_AFTER(&me->scnList, oldscn, newscn, elem); +} diff --git a/src/model/reladd.c b/src/model/reladd.c new file mode 100644 index 0000000..782d245 --- /dev/null +++ b/src/model/reladd.c @@ -0,0 +1,233 @@ +#include +#include +#include +#include +#include + +typedef enum Destsegment { + DS_UNKNOWN, + DS_TEXT, + DS_DATA, +} Destsegment; + + +static Destsegment destsegment(ElfuScn *ms) +{ + if (!(ms->shdr.sh_flags & SHF_ALLOC)) { + return DS_UNKNOWN; + } + + if (!(ms->shdr.sh_flags & SHF_WRITE) + && (ms->shdr.sh_flags & SHF_EXECINSTR)) { + return DS_TEXT; + } else if ((ms->shdr.sh_flags & SHF_WRITE) + && !(ms->shdr.sh_flags & SHF_EXECINSTR)) { + return DS_DATA; + } + + return DS_UNKNOWN; +} + + + +static ElfuScn* insertSection(ElfuElf *me, ElfuElf *mrel, ElfuScn *ms) +{ + ElfuPhdr *mp; + ElfuPhdr *first = NULL; + ElfuPhdr *last = NULL; + ElfuScn *newscn = NULL; + ElfuPhdr *injAnchor; + int searchForCode = 0; + + switch (destsegment(ms)) { + case DS_TEXT: + searchForCode = 1; + case DS_DATA: + newscn = elfu_mCloneScn(ms); + if (!newscn) { + return NULL; + } + + /* Find first and last LOAD PHDRs. */ + CIRCLEQ_FOREACH(mp, &me->phdrList, elem) { + if (mp->phdr.p_type != PT_LOAD) { + continue; + } + + if (!first || mp->phdr.p_vaddr < first->phdr.p_vaddr) { + first = mp; + } + if (!last || mp->phdr.p_vaddr > last->phdr.p_vaddr) { + /* No need to check p_memsz as segments may not overlap in memory. */ + last = mp; + } + } + + if (searchForCode) { + if ((first->phdr.p_flags & PF_X) && !(first->phdr.p_flags & PF_W)) { + injAnchor = first; + } else if ((last->phdr.p_flags & PF_X) && !(last->phdr.p_flags & PF_W)) { + injAnchor = last; + } else { + injAnchor = NULL; + } + } else { + if ((first->phdr.p_flags & PF_W) && !(first->phdr.p_flags & PF_X)) { + injAnchor = first; + } else if ((last->phdr.p_flags & PF_W) && !(last->phdr.p_flags & PF_X)) { + injAnchor = last; + } else { + injAnchor = NULL; + } + } + + if (!injAnchor) { + ELFU_WARN("insertSection: Could not find injection anchor.\n" + " It has to be the first or last segment in the memory image.\n"); + } else { + GElf_Off injOffset; + + /* If the anchor is first or last, insert before or after */ + if (injAnchor == first) { + /* Find first section and inject before it */ + ElfuScn *firstScn = elfu_mScnFirstInSegment(me, injAnchor); + if (!firstScn) { + ELFU_WARN("insertSection: mScnFirstInSegment failed.\n"); + + // TODO: Error handling + } else { + injOffset = firstScn->shdr.sh_offset; + + /* Make space */ + elfu_mInsertSpaceBefore(me, injOffset, ms->shdr.sh_size); + + /* Update memory offset */ + newscn->shdr.sh_addr = injAnchor->phdr.p_vaddr; + + /* Insert into chain of sections */ + elfu_mInsertScnInChainBefore(me, firstScn, newscn); + } + } else { + /* Find last section and inject after it */ + ElfuScn *lastScn = elfu_mScnLastInSegment(me, injAnchor); + if (!lastScn) { + ELFU_WARN("insertSection: mScnLastInSegment failed.\n"); + + // TODO: Error handling + } else { + injOffset = lastScn->shdr.sh_offset + elfu_gScnSizeFile(&lastScn->shdr); + + /* Expand NOBITS sections at injection site, if any. */ + elfu_mExpandNobits(me, injOffset); + + /* Recalculate injOffset in case we expanded a NOBITS section */ + lastScn = elfu_mScnLastInSegment(me, injAnchor); + injOffset = lastScn->shdr.sh_offset + elfu_gScnSizeFile(&lastScn->shdr); + + /* Make space */ + elfu_mInsertSpaceAfter(me, injOffset, ms->shdr.sh_size); + + /* Update memory offset */ + newscn->shdr.sh_addr = injAnchor->phdr.p_vaddr + (injOffset - injAnchor->phdr.p_offset); + + /* Insert into chain of sections */ + elfu_mInsertScnInChainAfter(me, lastScn, newscn); + } + } + + /* Update file offset in new section BEFORE we do anything else */ + newscn->shdr.sh_offset = injOffset; + + /* Inject name */ + // TODO + newscn->shdr.sh_name = 0; + + // TODO: Relocate + + return newscn; + } + break; + + case DS_UNKNOWN: + ELFU_WARN("insertSection: Don't know where to insert ' %s with flags %jd (type %d).\n", + elfu_mScnName(mrel, ms), + ms->shdr.sh_flags, + ms->shdr.sh_type); + default: + ELFU_WARN("insertSection: Skipping section %s with flags %jd (type %d).\n", + elfu_mScnName(mrel, ms), + ms->shdr.sh_flags, + ms->shdr.sh_type); + return NULL; + } + + if (newscn) { + // TODO: Destroy newscn + } + return NULL; +} + + + +void elfu_mReladd(ElfuElf *me, ElfuElf *mrel) +{ + ElfuScn *ms; + + assert(me); + assert(mrel); + + + /* For each section in object file, guess how to insert it */ + CIRCLEQ_FOREACH(ms, &mrel->scnList, elem) { + ElfuScn *newscn; + + switch(ms->shdr.sh_type) { + case SHT_NULL: /* 0 */ + continue; + + case SHT_PROGBITS: /* 1 */ + /* Find a place where it belongs and shove it in. */ + newscn = insertSection(me, mrel, ms); + if (!newscn) { + ELFU_WARN("mReladd: Could not insert section %s (type %d), skipping.\n", + elfu_mScnName(mrel, ms), + ms->shdr.sh_type); + } + break; + + case SHT_SYMTAB: /* 2 */ + case SHT_DYNSYM: /* 11 */ + /* Merge with the existing table. Take care of string tables also. */ + + case SHT_STRTAB: /* 3 */ + /* May have to be merged with the existing string table for + * the symbol table. */ + + case SHT_RELA: /* 4 */ + case SHT_REL: /* 9 */ + /* Possibly append this in memory to the section model + * that it describes. */ + + case SHT_NOBITS: /* 8 */ + /* Expand this to SHT_PROGBITS, then insert as such. */ + + case SHT_HASH: /* 5 */ + case SHT_DYNAMIC: /* 6 */ + case SHT_SHLIB: /* 10 */ + case SHT_SYMTAB_SHNDX: /* 18 */ + + /* Don't care about the next ones yet. I've never seen + * them and they can be implemented when necessary. */ + case SHT_NOTE: /* 7 */ + case SHT_INIT_ARRAY: /* 14 */ + case SHT_FINI_ARRAY: /* 15 */ + case SHT_PREINIT_ARRAY: /* 16 */ + case SHT_GROUP: /* 17 */ + case SHT_NUM: /* 19 */ + default: + ELFU_WARN("mReladd: Skipping section %s (type %d).\n", + elfu_mScnName(mrel, ms), + ms->shdr.sh_type); + } + } +} diff --git a/src/model/section-by-type.c b/src/model/section-by-type.c new file mode 100644 index 0000000..e36865a --- /dev/null +++ b/src/model/section-by-type.c @@ -0,0 +1,22 @@ +#include +#include +#include + + +/* + * Returns the first section with the given type. + */ +ElfuScn* elfu_mScnByType(ElfuElf *me, Elf32_Word type) +{ + ElfuScn *ms; + + assert(me); + + CIRCLEQ_FOREACH(ms, &me->scnList, elem) { + if (ms->shdr.sh_type == type) { + return ms; + } + } + + return NULL; +} diff --git a/src/model/section-in-segment.c b/src/model/section-in-segment.c new file mode 100644 index 0000000..ad7f037 --- /dev/null +++ b/src/model/section-in-segment.c @@ -0,0 +1,56 @@ +#include +#include +#include + + +/* + * Returns the first section beginning in the segment. + * + * Since sections have to be in the file in mapping order to load them + * as a continuous segment, we only have to search by start offset. + */ +ElfuScn* elfu_mScnFirstInSegment(ElfuElf *me, ElfuPhdr *mp) +{ + ElfuScn *ms; + + assert(me); + assert(mp); + + CIRCLEQ_FOREACH(ms, &me->scnList, elem) { + if ((ms->shdr.sh_offset >= mp->phdr.p_offset) + && (ms->shdr.sh_offset < mp->phdr.p_offset + mp->phdr.p_filesz)) { + return ms; + } + } + + return NULL; +} + + + +/* + * Returns the last section ending in the segment. + * + * Since sections have to be in the file in mapping order to load them + * as a continuous segment, we only have to search by end offset. + */ +ElfuScn* elfu_mScnLastInSegment(ElfuElf *me, ElfuPhdr *mp) +{ + ElfuScn *last = NULL; + ElfuScn *ms; + + assert(me); + assert(mp); + + CIRCLEQ_FOREACH(ms, &me->scnList, elem) { + /* Get section size on disk - for NOBITS sections that is 0 bytes. */ + size_t size = elfu_gScnSizeFile(&ms->shdr); + + if (((ms->shdr.sh_offset + size >= mp->phdr.p_offset) + && (ms->shdr.sh_offset + size <= mp->phdr.p_offset + mp->phdr.p_filesz))) { + last = ms; + } + } + + return last; +} diff --git a/src/options.c b/src/options.c index 1a191a5..928d080 100644 --- a/src/options.c +++ b/src/options.c @@ -28,6 +28,9 @@ static void printUsage(char *progname) " mapping everything before it to lower mem addresses.\n" " --insert-after off,sz Insert spacing at given offset,\n" " mapping everything after it to higher mem addresses.\n" + "\n" + "High-level insertion:\n" + " --reladd obj.o Automatically insert object file contents\n" "\n"); } @@ -49,6 +52,7 @@ void parseOptions(CLIOpts *opts, int argc, char **argv) {"insert-before", 1, 0, 10004}, {"insert-after", 1, 0, 10005}, {"expand-nobits", 1, 0, 10006}, + {"reladd", 1, 0, 10007}, {NULL, 0, NULL, 0} }; @@ -96,6 +100,9 @@ void parseOptions(CLIOpts *opts, int argc, char **argv) goto USAGE; } break; + case 10007: + opts->fnReladd = optarg; + break; case '?': default: goto USAGE; -- 2.30.2