2012-12-17 55 views
12

我想在64位x86環境下在Linux上加載一個用「gcc -m32 test.c -o test.exe」編譯的ELF文件。我正試圖在具有以下核心邏輯(32位ELF)的用戶空間ELF加載器中加載該32位文件(test.exe)。加載用戶空間C中的ELF文件

問題是調用返回的起始地址導致 分段錯誤核心轉儲。這裏是代碼:

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'表明一個內核模塊,而'mmap'&'mprotect'是用戶空間。如果您正在編寫用戶空間應用程序,您是否考慮使用'gcc -Wall -g'編譯並使用'gdb'調試它?並且ELF圖像的起始地址是* not *'main'例程(但在某些'crt0.o'中有一些'_start') –

回答

23

請提供完整的可運行代碼,包括您嘗試加載的ELF。 我已經花時間儘可能地修改代碼了,至少對於這個簡單的代碼來說,它似乎工作正常。

請注意,加載程序也必須編譯爲32位代碼,您無法將32位文件加載到64位進程中。此外,由於您沒有在原始位置加載代碼,因此它必須是可重新定位的。最後,它必須是一個靜態二進制文件,因爲你不加載任何庫。

更新:你的代碼期望的加載代碼的入口點,以符合int (*main)(int, char **)原型這是不是在一般的情況下(邊注:main實際上得到第三個參數,環境,太)。閱讀關於startup state of ELF。如果您手動創建那裏描述的堆棧佈局,則必須跳到入口點,並且這將永遠不會返回。在C程序的情況下,您可以挖掘main的地址,並且與原型相匹配。然而,你正在跳過C庫的初始化(請記住,你的代碼不會加載庫,所以加載的程序必須靜態鏈接),這可能是一個問題。

我已經通過解析libc引用並調用main來擴展帶有所需位的代碼來處理簡單的C程序。

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; 
} 

測試運行:

$ 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

感謝您的幫助。當你嘗試加載一個C程序(甚至是一個打印hello world的簡單程序)時,問題似乎就出現了。我的猜測是,直接調用C _start例程時出現問題,或者加載程序代碼有一個錯誤重定位多個部分(在調用memmove的地方)。 – Smokey

+0

更新了答案。 – Jester

+0

對不起,但我只是想知道你們在哪裏得到libelf庫。謝謝 – Kimutai

2

exec =(unsigned char *)mmap(NULL,size,...

這會嘗試在任意地址加載可執行文件。非PIE可執行文件只能在鏈接的地址加載(通常在Linux/ix86上爲0x08048000)。

問題出現在您嘗試加載C程序(甚至是打印hello world的簡單程序)時。

如果程序動態鏈接,它是什麼,但簡單,和你的裝載機有赫克很多更多的事情要做:加載和搬遷相關共享庫,固定起來GOTTLS等等等

0

使用

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

代替

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