* Proof-of-concept of a tool to patch *existing* ELF executable to use

our own glibc, to change the rpath, and so on.  This enable us to
  use third-party programs in a pure Nix environment, and to "shrink"
  the rpath of Nix-built executables to contain only those directories
  that are actually needed (in order to reduce the number of retained
  dependencies).

  In order to use our own glibc, we have to set the executable's ELF
  interpreter field to our glib'c ld-linux.so instead of the typical
  /lib/ld-linux.so.2.  The name of the interpreter is stored in the
  PT_INTERP segment of the executable.  A complication is that this
  segment is too small to store the path to our ld-linux.so.  We
  cannot just make this segment bigger, since the segment is actually
  mapped into the executable's virtual memory space, so that making it
  bigger will move all following segments in memory.  That would break
  executables, since they are typically not position-independant (they
  expect to be loaded at specific addresses in memory).

  The solution is to add a *new* segment at the end of the
  executable.  (The data containing the original PT_INTERP segment
  becomes "dead" space within the executable.)  This seems to work:
  e.g., I've succesfully patched SuSE's /bin/cat.

  Something similar could be done for the rpath, although that's a bit
  more complicated since the rpath string is stored indirectly (the
  PT_DYNAMIC segment merely contains a pointer to a string in the
  DT_STRTAB section of the executable, which we cannot grow either, so
  we would have to copy it to the end of the file).
This commit is contained in:
Eelco Dolstra
2004-09-23 20:48:21 +00:00
commit f967dff666
2 changed files with 133 additions and 0 deletions

2
Makefile Normal file
View File

@@ -0,0 +1,2 @@
patchelf: patchelf.c
gcc -Wall -o patchelf patchelf.c

131
patchelf.c Normal file
View File

@@ -0,0 +1,131 @@
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <elf.h>
static off_t fileSize, maxSize = 128 * 1024;
static unsigned char * contents = 0;
static void error(char * msg)
{
if (errno) perror(msg); else printf("%s\n", msg);
exit(1);
}
static void growFile(off_t newSize)
{
if (newSize > maxSize) error("maximum file size exceeded");
if (newSize <= fileSize) return;
if (newSize > fileSize)
memset(contents + fileSize, 0, newSize - fileSize);
fileSize = newSize;
}
static void readFile(char * fileName)
{
struct stat st;
if (stat(fileName, &st) != 0) error("stat");
fileSize = st.st_size;
contents = malloc(fileSize + maxSize);
if (!contents) abort();
int fd = open(fileName, O_RDONLY);
if (fd == -1) error("open");
if (read(fd, contents, fileSize) != fileSize) error("read");
close(fd);
}
static void writeFile(char * fileName)
{
int fd = open(fileName, O_CREAT | O_TRUNC | O_WRONLY, 0777);
if (fd == -1) error("open");
if (write(fd, contents, fileSize) != fileSize) error("write");
close(fd);
}
//char newInterpreter[] = "/lib/ld-linux.so.2";
char newInterpreter[] = "/nix/store/42de22963bca8f234ad54b01118215df-glibc-2.3.2/lib/ld-linux.so.2";
static void patchElf(char * fileName)
{
fprintf(stderr, "patching %s\n", fileName);
readFile(fileName);
if (fileSize < sizeof(Elf32_Ehdr)) error("missing ELF header");
Elf32_Ehdr * hdr = (Elf32_Ehdr *) contents;
if (memcmp(hdr->e_ident, ELFMAG, 4) != 0)
error("not an ELF executable");
if (contents[EI_CLASS] != ELFCLASS32 ||
contents[EI_DATA] != ELFDATA2LSB ||
contents[EI_VERSION] != EV_CURRENT)
error("ELF executable is not 32-bit, little-endian, version 1");
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 header");
if (hdr->e_shoff + hdr->e_shnum * hdr->e_shentsize > fileSize)
error("missing program header");
/* Find the PT_INTERP segment. */
Elf32_Phdr * phdr = (Elf32_Phdr *) (contents + hdr->e_phoff);
int i;
for (i = 0; i < hdr->e_phnum; ++i, ++phdr) {
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));
unsigned int segLen = strlen(newInterpreter) + 1;
phdr->p_offset = fileSize;
growFile(phdr->p_offset + segLen);
phdr->p_vaddr = phdr->p_paddr = 0x08048000 + phdr->p_offset;
phdr->p_filesz = phdr->p_memsz = segLen;
strncpy(contents + phdr->p_offset, newInterpreter, segLen);
}
}
writeFile("./new.exe");
}
int main(int argc, char * * argv)
{
if (argc != 2) {
fprintf(stderr, "syntax: %s FILENAME\n", argv[0]);
return 1;
}
patchElf(argv[1]);
return 0;
}