diff options
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | patchelf.c | 335 |
2 files changed, 256 insertions, 89 deletions
@@ -6,7 +6,11 @@ patchelf: patchelf.c test: test.c gcc -Wall -o test test.c -runtest: test patchelf - readelf -a test > dump1 - ./patchelf test +TEST = svn + +runtest: $(TEST) patchelf + readelf -a $(TEST) > dump1 +# ./patchelf --interpreter /nix/store/42de22963bca8f234ad54b01118215df-glibc-2.3.2/lib/ld-linux.so.2 \ +# --shrink-rpath $(TEST) + ./patchelf --shrink-rpath $(TEST) readelf -a new.exe > dump2 @@ -8,18 +8,34 @@ #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> +#include <limits.h> #include <elf.h> +static char * fileName = 0; +static char * newInterpreter = 0; +static int doShrinkRPath = 0; + + #define MAX_PHEADERS 128 #define MAX_SHEADERS 128 +#define MAX_NEEDED 1024 static off_t fileSize, maxSize; static unsigned char * contents = 0; +static Elf32_Ehdr * hdr; static Elf32_Phdr phdrs[MAX_PHEADERS]; static Elf32_Shdr shdrs[MAX_SHEADERS]; +static unsigned int nrNeededLibs = 0; +static char * neededLibs[MAX_NEEDED]; +static int neededLibFound[MAX_NEEDED]; +static unsigned int freeOffset; +static unsigned int firstPage; + +static int changed = 0; +static int rewriteHeaders = 0; static void error(char * msg) @@ -75,21 +91,207 @@ static unsigned int roundUp(unsigned int n, unsigned int m) } -//char newInterpreter[] = "/lib/ld-linux.so.2"; -char newInterpreter[] = "/nix/store/42de22963bca8f234ad54b01118215df-glibc-2.3.2/lib/ld-linux.so.2"; -//char newInterpreter[] = "/nix/store/2ccde1632ef69ebdb5f21cd2222d19f2-glibc-2.3.3/lib/ld-linux.so.2"; +static void shiftFile(void) +{ + /* Move the entire contents of the file one page further. */ + unsigned int oldSize = fileSize; + growFile(fileSize + 4096); + memmove(contents + 4096, contents, oldSize); + memset(contents + sizeof(Elf32_Ehdr), 0, 4096 - sizeof(Elf32_Ehdr)); + + /* Update the ELF header. */ + hdr->e_shoff += 4096; + + /* Update the offsets in the section headers. */ + int i; + for (i = 0; i < hdr->e_shnum; ++i) { + shdrs[i].sh_offset += 4096; + } + + /* Update the offsets in the program headers. */ + for (i = 0; i < hdr->e_phnum; ++i) { + phdrs[i].p_offset += 4096; + } + + /* Add a segment that maps the new program/section headers and + PT_INTERP segment into memory. Otherwise glibc will choke. Add + this after the PT_PHDR segment but before all other PT_LOAD + segments. */ + for (i = hdr->e_phnum; i > 1; --i) + phdrs[i] = phdrs[i - 1]; + hdr->e_phnum++; + Elf32_Phdr * phdr = phdrs + 1; + phdr->p_type = PT_LOAD; + phdr->p_offset = 0; + phdr->p_vaddr = phdr->p_paddr = firstPage; + phdr->p_filesz = phdr->p_memsz = 4096; + phdr->p_flags = PF_R; + phdr->p_align = 4096; + + freeOffset = sizeof(Elf32_Ehdr); + + rewriteHeaders = 1; +} + + +static void setInterpreter(void) +{ + /* Find the PT_INTERP segment and replace it by a new one that + contains the new interpreter name. */ + if (newInterpreter && hdr->e_type == ET_EXEC) { + shiftFile(); + int i; + for (i = 0; i < hdr->e_phnum; ++i) { + Elf32_Phdr * phdr = phdrs + i; + if (phdr->p_type == PT_INTERP) { + fprintf(stderr, "changing interpreter from `%s' to `%s'\n", + (char *) (contents + phdr->p_offset), newInterpreter); + unsigned int interpOffset = freeOffset; + unsigned int interpSize = strlen(newInterpreter) + 1; + freeOffset += roundUp(interpSize, 4); + phdr->p_offset = interpOffset; + growFile(phdr->p_offset + interpSize); + phdr->p_vaddr = phdr->p_paddr = firstPage + interpOffset % 4096; + phdr->p_filesz = phdr->p_memsz = interpSize; + strncpy(contents + interpOffset, + newInterpreter, interpSize); + changed = 1; + break; + } + } + } +} -static void patchElf(char * fileName) +static void shrinkRPath(void) { - fprintf(stderr, "patching %s\n", fileName); + /* Shrink the RPATH. */ + if (doShrinkRPath) { + + /* Find the .dynamic section. */ + int i, dynSec = 0; + for (i = 0; i < hdr->e_shnum; ++i) + if (shdrs[i].sh_type == SHT_DYNAMIC) dynSec = i; + + if (!dynSec) { + fprintf(stderr, "no dynamic section, so no RPATH to shrink\n"); + return; + } + + /* Find the DT_STRTAB entry in the dynamic section. */ + Elf32_Dyn * dyn = (Elf32_Dyn *) (contents + shdrs[dynSec].sh_offset); + Elf32_Addr strTabAddr = 0; + for ( ; dyn->d_tag != DT_NULL; dyn++) + if (dyn->d_tag == DT_STRTAB) strTabAddr = dyn->d_un.d_ptr; + + if (!strTabAddr) { + fprintf(stderr, "strange: no string table\n"); + return; + } + + /* Nasty: map the virtual address for the string table back to + a offset in the file. */ + char * strTab = 0; + for (i = 0; i < hdr->e_phnum; ++i) + if (phdrs[i].p_vaddr <= strTabAddr && + strTabAddr < phdrs[i].p_vaddr + phdrs[i].p_filesz) + { + strTab = contents + + strTabAddr - phdrs[i].p_vaddr + phdrs[i].p_offset; + } + + if (!strTab) error("could not reverse map DT_STRTAB"); + + /* Walk through the dynamic section, look for the RPATH + entry. */ + dyn = (Elf32_Dyn *) (contents + shdrs[dynSec].sh_offset); + Elf32_Dyn * rpathEntry = 0; + char * rpath = 0; + for ( ; dyn->d_tag != DT_NULL; dyn++) { + if (dyn->d_tag == DT_RPATH) { + rpathEntry = dyn; + rpath = strTab + dyn->d_un.d_val; + } + else if (dyn->d_tag == DT_NEEDED) { + if (nrNeededLibs == MAX_NEEDED) + error("too many needed libraries"); + neededLibs[nrNeededLibs++] = strTab + dyn->d_un.d_val; + } + } + + if (!rpath) { + fprintf(stderr, "no RPATH to shrink\n"); + return; + } + + /* For each directory in the RPATH, check if it contains any + needed library. */ + for (i = 0; i < nrNeededLibs; ++i) + neededLibFound[i] = 0; + + char * newRPath = malloc(strlen(rpath) + 1); + *newRPath = 0; + + char * pos = rpath; + while (*pos) { + char * end = strchr(pos, ':'); + if (!end) end = strchr(pos, 0); + + /* Get the name of the directory. */ + char dirName[PATH_MAX]; + if (end - pos >= PATH_MAX) error("library name too long"); + strncpy(dirName, pos, end - pos); + dirName[end - pos] = 0; + if (*end == ':') ++end; + pos = end; + if (*dirName != '/') continue; + + /* For each library that we haven't found yet, see if it + exists in this directory. */ + int j; + int libFound = 0; + for (j = 0; j < nrNeededLibs; ++j) + if (!neededLibFound[j]) { + char libName[PATH_MAX]; + snprintf(libName, sizeof(libName), "%s/%s", dirName, neededLibs[j]); + struct stat st; + if (stat(libName, &st) == 0) { + neededLibFound[j] = 1; + libFound = 1; + } + } + + if (!libFound) + fprintf(stderr, "removing directory `%s' from RPATH\n", dirName); + else { + if (*newRPath) strcat(newRPath, ":"); + strcat(newRPath, dirName); + } + + } + + if (strcmp(rpath, newRPath) != -1) { + assert(strlen(newRPath) <= strlen(rpath)); + /* Zero out the previous rpath to prevent retained + dependencies in Nix. */ + memset(rpath, 0, strlen(rpath)); + strcpy(rpath, newRPath); + changed = 1; + } + } +} + + +static void patchElf(void) +{ + fprintf(stderr, "patching ELF file `%s'\n", fileName); readFile(fileName); /* Check the ELF header for basic validity. */ if (fileSize < sizeof(Elf32_Ehdr)) error("missing ELF header"); - Elf32_Ehdr * hdr = (Elf32_Ehdr *) contents; + hdr = (Elf32_Ehdr *) contents; if (memcmp(hdr->e_ident, ELFMAG, 4) != 0) error("not an ELF executable"); @@ -102,9 +304,6 @@ static void patchElf(char * fileName) if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN) error("wrong ELF type"); - fprintf(stderr, "%d ph entries, %d sh entries\n", - hdr->e_phnum, hdr->e_shnum); - if (hdr->e_phoff + hdr->e_phnum * hdr->e_phentsize > fileSize) error("missing program headers"); @@ -128,96 +327,60 @@ static void patchElf(char * fileName) if (end > nextFreePage) nextFreePage = end; } - fprintf(stderr, "next free page is %x\n", nextFreePage); + firstPage = 0x08047000; - unsigned int firstPage = 0x08047000; - - /* Move the entire contents of the file one page further. */ - unsigned int oldSize = fileSize; - growFile(fileSize + 4096); - memmove(contents + 4096, contents, oldSize); - memset(contents + sizeof(Elf32_Ehdr), 0, 4096 - sizeof(Elf32_Ehdr)); + /* Do what we're supposed to do. */ + setInterpreter(); + shrinkRPath(); - /* Update the ELF header. */ - hdr->e_shoff += 4096; + if (rewriteHeaders) { + /* Rewrite the program header table. */ + hdr->e_phoff = freeOffset; + freeOffset += hdr->e_phnum * sizeof(Elf32_Phdr); + assert(phdrs[0].p_type == PT_PHDR); + phdrs[0].p_offset = hdr->e_phoff; + phdrs[0].p_vaddr = phdrs[0].p_paddr = firstPage + hdr->e_phoff % 4096; + phdrs[0].p_filesz = phdrs[0].p_memsz = hdr->e_phnum * sizeof(Elf32_Phdr); + memcpy(contents + hdr->e_phoff, phdrs, hdr->e_phnum * sizeof(Elf32_Phdr)); - /* Update the offsets in the section headers. */ - for (i = 0; i < hdr->e_shnum; ++i) { - shdrs[i].sh_offset += 4096; - } - - /* Update the offsets in the program headers. */ - for (i = 0; i < hdr->e_phnum; ++i) { - phdrs[i].p_offset += 4096; - } + /* Rewrite the section header table. */ + hdr->e_shoff = freeOffset; + freeOffset += hdr->e_shnum * sizeof(Elf32_Shdr); + memcpy(contents + hdr->e_shoff, shdrs, hdr->e_shnum * sizeof(Elf32_Shdr)); - /* Add a segment that maps the new program/section headers and - PT_INTERP segment into memory. Otherwise glibc will choke. Add - this after the PT_PHDR segment but before all other PT_LOAD - segments. */ - for (i = hdr->e_phnum; i > 1; --i) - phdrs[i] = phdrs[i - 1]; - hdr->e_phnum++; - Elf32_Phdr * phdr = phdrs + 1; - phdr->p_type = PT_LOAD; - phdr->p_offset = 0; - phdr->p_vaddr = phdr->p_paddr = firstPage; - phdr->p_filesz = phdr->p_memsz = 4096; - phdr->p_flags = PF_R; - phdr->p_align = 4096; - - unsigned int freeOffset = sizeof(Elf32_Ehdr); - - /* Find the PT_INTERP segment and replace it by a new one that - contains the new interpreter name. */ - unsigned int interpOffset = 0, interpSize = 0, interpAddr = 0; - for (i = 0; i < hdr->e_phnum; ++i) { - Elf32_Phdr * phdr = phdrs + i; - fprintf(stderr, "segment type %d at %x\n", phdr->p_type, phdr->p_offset); - if (phdr->p_type == PT_INTERP) { - fprintf(stderr, "found interpreter (%s)\n", - (char *) (contents + phdr->p_offset)); - interpOffset = freeOffset; - interpSize = strlen(newInterpreter) + 1; - freeOffset += roundUp(interpSize, 4); - interpAddr = firstPage + interpOffset % 4096; - phdr->p_offset = interpOffset; - growFile(phdr->p_offset + interpSize); - phdr->p_vaddr = phdr->p_paddr = interpAddr; - phdr->p_filesz = phdr->p_memsz = interpSize; - strncpy(contents + interpOffset, - newInterpreter, interpSize); - } + if (freeOffset > 4096) error("ran out of space in page 0"); } - - /* Rewrite the program header table. */ - hdr->e_phoff = freeOffset; - freeOffset += hdr->e_phnum * sizeof(Elf32_Phdr); - assert(phdrs[0].p_type == PT_PHDR); - phdrs[0].p_offset = hdr->e_phoff; - phdrs[0].p_vaddr = phdrs[0].p_paddr = firstPage + hdr->e_phoff % 4096; - phdrs[0].p_filesz = phdrs[0].p_memsz = hdr->e_phnum * sizeof(Elf32_Phdr); - memcpy(contents + hdr->e_phoff, phdrs, hdr->e_phnum * sizeof(Elf32_Phdr)); - - /* Rewrite the section header table. */ - hdr->e_shoff = freeOffset; - freeOffset += hdr->e_shnum * sizeof(Elf32_Shdr); - memcpy(contents + hdr->e_shoff, shdrs, hdr->e_shnum * sizeof(Elf32_Shdr)); - - if (freeOffset > 4096) error("ran out of space in page 0"); - - writeFile("./new.exe"); + + if (changed) + writeFile("./new.exe"); + else + fprintf(stderr, "nothing changed in `%s'\n", fileName); } int main(int argc, char * * argv) { - if (argc != 2) { - fprintf(stderr, "syntax: %s FILENAME\n", argv[0]); + if (argc <= 1) { + fprintf(stderr, "syntax: %s [--interpreter FILENAME] [--shrink-rpath] FILENAME\n", argv[0]); return 1; } - patchElf(argv[1]); + int i; + for (i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--interpreter") == 0) { + if (++i == argc) error("missing argument"); + newInterpreter = argv[i]; + } + else if (strcmp(argv[i], "--shrink-rpath") == 0) { + doShrinkRPath = 1; + } + else break; + } + + if (i == argc) error("missing filename"); + fileName = argv[i]; + + patchElf(); return 0; } |