2012-12-17 17 views
12

Sto provando a caricare un file ELF compilato con "gcc -m32 test.c -o test.exe" su Linux in un ambiente x86 a 64 bit. Sto cercando di caricare quel file a 32 bit (test.exe) all'interno di un caricatore ELF di spazio utente che ha la seguente logica di base (ELF a 32 bit).caricamento file ELF in C nello spazio utente

Il problema è che la chiamata nell'indirizzo di inizio restituito produce un dump del core di errore di segmentazione. Ecco il codice:

void *image_load (char *elf_start, unsigned int size) 
{ 
    Elf32_Ehdr  *hdr = NULL; 
    Elf32_Phdr  *phdr = NULL; 
    unsigned char *start = NULL; 
    Elf32_Addr  taddr = 0; 
    Elf32_Addr  offset = 0; 
    int i = 0; 
    unsigned char *exec = NULL; 
    Elf32_Addr  estart = 0; 

    hdr = (Elf32_Ehdr *) elf_start; 

    if(!is_image_valid(hdr)) { 
     printk("image_load:: invalid ELF image\n"); 
     return 0; 
    } 

    exec = (unsigned char *)mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, 
         MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 

    if(!exec) { 
     printk("image_load:: error allocating memory\n"); 
     return 0; 
    } 

    // Start with clean memory. 
    memset(exec,0x0,size); 

    phdr = (Elf32_Phdr *)(elf_start + hdr->e_phoff); 

    for(i=0; i < hdr->e_phnum; ++i) { 
      if(phdr[i].p_type != PT_LOAD) { 
        continue; 
      } 
      if(phdr[i].p_filesz > phdr[i].p_memsz) { 
        printk("image_load:: p_filesz > p_memsz\n"); 
        munmap(exec, size); 
        return 0; 
      } 
      if(!phdr[i].p_filesz) { 
        continue; 
      } 

      // p_filesz can be smaller than p_memsz, 
      // the difference is zeroe'd out. 
      start = (unsigned char *) (elf_start + phdr[i].p_offset); 
      // taddr = phdr[i].p_vaddr + (Elf32_Addr)exec; 
      if(!estart) { 
       estart = phdr[i].p_paddr; 
      } 
      taddr = (Elf32_Addr)exec + offset + (phdr[i].p_paddr - estart); 
      memmove((unsigned char *)taddr, 
        (unsigned char *)start,phdr[i].p_filesz); 
      offset += (phdr[i].p_memsz + (phdr[i].p_paddr - estart)); 

      if(!(phdr[i].p_flags & PF_W)) { 
        // Read-only. 
        mprotect((unsigned char *) taddr, 
           phdr[i].p_memsz, 
           PROT_READ); 
      } 

      if(phdr[i].p_flags & PF_X) { 
        // Executable. 
        mprotect((unsigned char *) taddr, 
          phdr[i].p_memsz, 
          PROT_EXEC); 
      } 
    } 

    return (void *)((hdr->e_entry - estart) + (Elf32_Addr)exec); 

}/* image_load */ 

... 
    int (*main)(int, char **)=image_load(...); 
    main(argc,argv); // Crash... 
... 
+1

'printk' suggerisce un modulo del kernel, mentre' 'mmap' & mprotect' sono user-space. Se stai codificando un'applicazione per lo spazio utente, hai pensato di compilarlo con 'gcc -Wall -g' e debuggarlo con' gdb'? E l'indirizzo iniziale di un'immagine ELF è * non * la routine 'main' (ma alcuni' _start' in alcuni 'crt0.o') –

risposta

23

si prega di fornire il codice eseguibile, compresa l'ELF che si sta tentando di caricare. Mi sono preso il tempo di modificare il codice come meglio potevo, e sembra funzionare, almeno per questo semplice codice.

Si noti che il caricatore deve anche essere compilato come codice a 32 bit, non è possibile caricare un file a 32 bit in un processo a 64 bit. Inoltre, poiché non stai caricando il codice nel posto originale, deve essere rilocabile. Infine, deve essere un binario statico perché non stai caricando alcuna libreria.

Aggiornamento: Il codice prevede che il punto di ingresso del codice caricato di conformarsi al prototipo int (*main)(int, char **) che non è il caso, in generale, (nota a margine: main ottiene in realtà un terzo argomento, l'ambiente, anche). Maggiori informazioni su startup state of ELF. Se crei manualmente il layout dello stack qui descritto, devi saltare al punto di ingresso e questo non tornerà mai più. Nel caso di un programma in C, è possibile estrarre l'indirizzo di main e questo corrisponderebbe al prototipo. Tuttavia, stai saltando l'inizializzazione della libreria C (ricorda che il tuo codice non carica la libreria, quindi il programma caricato deve essere collegato staticamente) e questo potrebbe essere un problema.

Ho esteso il codice con i bit necessari per gestire un semplice programma C risolvendo i riferimenti libc e invocando main.

loader.c:

#include <stdio.h> 
#include <string.h> 
#include <libelf.h> 
#include <sys/mman.h> 
#include <dlfcn.h> 

void printk(const char* msg) 
{ 
    fputs(msg, stderr); 
} 

int is_image_valid(Elf32_Ehdr *hdr) 
{ 
    return 1; 
} 

void *resolve(const char* sym) 
{ 
    static void *handle = NULL; 
    if (handle == NULL) { 
     handle = dlopen("libc.so", RTLD_NOW); 
    } 
    return dlsym(handle, sym); 
} 

void relocate(Elf32_Shdr* shdr, const Elf32_Sym* syms, const char* strings, const char* src, char* dst) 
{ 
    Elf32_Rel* rel = (Elf32_Rel*)(src + shdr->sh_offset); 
    int j; 
    for(j = 0; j < shdr->sh_size/sizeof(Elf32_Rel); j += 1) { 
     const char* sym = strings + syms[ELF32_R_SYM(rel[j].r_info)].st_name; 
     switch(ELF32_R_TYPE(rel[j].r_info)) { 
      case R_386_JMP_SLOT: 
      case R_386_GLOB_DAT: 
       *(Elf32_Word*)(dst + rel[j].r_offset) = (Elf32_Word)resolve(sym); 
       break; 
     } 
    } 
} 

void* find_sym(const char* name, Elf32_Shdr* shdr, const char* strings, const char* src, char* dst) 
{ 
    Elf32_Sym* syms = (Elf32_Sym*)(src + shdr->sh_offset); 
    int i; 
    for(i = 0; i < shdr->sh_size/sizeof(Elf32_Sym); i += 1) { 
     if (strcmp(name, strings + syms[i].st_name) == 0) { 
      return dst + syms[i].st_value; 
     } 
    } 
    return NULL; 
} 

void *image_load (char *elf_start, unsigned int size) 
{ 
    Elf32_Ehdr  *hdr  = NULL; 
    Elf32_Phdr  *phdr = NULL; 
    Elf32_Shdr  *shdr = NULL; 
    Elf32_Sym  *syms = NULL; 
    char   *strings = NULL; 
    char   *start = NULL; 
    char   *taddr = NULL; 
    void   *entry = NULL; 
    int i = 0; 
    char *exec = NULL; 

    hdr = (Elf32_Ehdr *) elf_start; 

    if(!is_image_valid(hdr)) { 
     printk("image_load:: invalid ELF image\n"); 
     return 0; 
    } 

    exec = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, 
         MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 

    if(!exec) { 
     printk("image_load:: error allocating memory\n"); 
     return 0; 
    } 

    // Start with clean memory. 
    memset(exec,0x0,size); 

    phdr = (Elf32_Phdr *)(elf_start + hdr->e_phoff); 

    for(i=0; i < hdr->e_phnum; ++i) { 
      if(phdr[i].p_type != PT_LOAD) { 
        continue; 
      } 
      if(phdr[i].p_filesz > phdr[i].p_memsz) { 
        printk("image_load:: p_filesz > p_memsz\n"); 
        munmap(exec, size); 
        return 0; 
      } 
      if(!phdr[i].p_filesz) { 
        continue; 
      } 

      // p_filesz can be smaller than p_memsz, 
      // the difference is zeroe'd out. 
      start = elf_start + phdr[i].p_offset; 
      taddr = phdr[i].p_vaddr + exec; 
      memmove(taddr,start,phdr[i].p_filesz); 

      if(!(phdr[i].p_flags & PF_W)) { 
        // Read-only. 
        mprotect((unsigned char *) taddr, 
           phdr[i].p_memsz, 
           PROT_READ); 
      } 

      if(phdr[i].p_flags & PF_X) { 
        // Executable. 
        mprotect((unsigned char *) taddr, 
          phdr[i].p_memsz, 
          PROT_EXEC); 
      } 
    } 

    shdr = (Elf32_Shdr *)(elf_start + hdr->e_shoff); 

    for(i=0; i < hdr->e_shnum; ++i) { 
     if (shdr[i].sh_type == SHT_DYNSYM) { 
      syms = (Elf32_Sym*)(elf_start + shdr[i].sh_offset); 
      strings = elf_start + shdr[shdr[i].sh_link].sh_offset; 
      entry = find_sym("main", shdr + i, strings, elf_start, exec); 
      break; 
     } 
    } 

    for(i=0; i < hdr->e_shnum; ++i) { 
     if (shdr[i].sh_type == SHT_REL) { 
      relocate(shdr + i, syms, strings, elf_start, exec); 
     } 
    } 

    return entry; 

}/* image_load */ 

int main(int argc, char** argv, char** envp) 
{ 
    int (*ptr)(int, char **, char**); 
    static char buf[1048576]; 
    FILE* elf = fopen(argv[1], "rb"); 
    fread(buf, sizeof buf, 1, elf); 
    ptr=image_load(buf, sizeof buf); 
    return ptr(argc,argv,envp); 
} 

elf.c:

#include <stdio.h> 

int main() 
{ 
    fprintf(stdout, "Hello world! fprintf=%p, stdout=%p\n", fprintf, stdout); 
    return 0; 
} 

prova di funzionamento:

$ gcc -m32 -g -Wall -ldl -o loader loader.c 
$ gcc -m32 -pie -fPIE -o elf elf.c 
$ ./loader elf 
Hello world! fprintf=0xf7612420, stdout=0xf770e4c0 
+1

Grazie per il tuo aiuto. Il problema sembra essere quando si tenta di caricare un programma in C (anche se semplice che stampi Hello World). La mia ipotesi è che o ci sia un problema nel chiamare direttamente la routine C _start, o che il codice del caricatore abbia un errore nel ricollocare più di una sezione (attorno a dove viene effettuata la chiamata a memmove). – Smokey

+0

Aggiornamento della risposta. – Jester

+0

scusa per questo, ma volevo solo sapere dove si trovano le librerie libelf. Grazie – Kimutai

2

exec = (char *) mmap (NULL, dimensioni, ...

Questo tenta di caricare l'eseguibile all'indirizzo arbitraria. Un eseguibile non-PIE può essere caricato solo all'indirizzo a cui è stato collegato (solitamente 0x08048000 su Linux/ix86).

Il problema sembra essere quando si tenta di caricare un programma C (anche uno semplice che stampa ciao mondo).

Se questo programma è stato collegato dinamicamente, è nulla ma semplice, e la vostra pala ha una diamine di molto più da fare: carico e trasferirsi librerie condivise dipendenti, sistemando GOT e TLS, ecc . eccetera.

0

uso

exec = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, 
        MAP_PRIVATE | MAP_ANONYMOUS, hdr, 0); 

anziché

exec = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, 
        MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 
Problemi correlati