elfLoader c(#elf執行視圖# #32位#)

loading ELF file in C in user space

I am trying to load an ELF file compiled with “gcc -m32 test.c -o test.exe” on Linux in a 64 bit x86 environment.
我嘗試加載一個ELF文件,編譯的命令爲:“gcc -m32 test.c -o test.exe” 在 x86 平臺,64位的環境

I am trying to load that 32bit file (test.exe) inside a user space ELF loader which has the following core logic (32bit ELF).
窩嘗試加載 test.exe 在用戶空間,其中(which has)代碼邏輯爲:

The problem is that calling into the returned start address results in a segmentation fault core dump.
問題在於,
調用返回的起始地址(calling into the returned start address)
會導致(results)分段錯誤核心轉儲

Here is the code:

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...
...



ELF文件由4部分組成:

  • 分別是ELF頭(ELF header)
  • 程序頭表(Program header table)
  • 節(Section)
  • 節頭表(Section header table) (可擦除, 命令爲:xxxx)

答案:
Please provide full runnable code, including the ELF that you are trying to load. I have taken the time to amend your code as best I could, and it seems to work, at least for this simple code.

Note that the loader must also be compiled as 32 bit code, you can not load a 32 bit file into a 64 bit process. Furthermore since you are not loading the code in the original place, it must be relocatable. Finally, it must be a static binary because you are not loading any libraries.

Update: Your code expects the entry point of the loaded code to conform to the int (*main)(int, char **) prototype which is not the case in general (side note: main actually gets a third argument, the environment, too). Read about the startup state of ELF. If you manually create the stack layout described there, you must jump to the entry point, and that will never return. In case of a C program, you could dig out the address of main and that would match the prototype. However you are then skipping the initalization of the C library (remember, your code doesn’t do library loading, so the loaded program must be statically linked) and that could be a problem.

I have extended the code with the needed bits to handle a simple C program by resolving libc references and invoking 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);
	// e_phoff 表示Program header table(程序頭)
    phdr = (Elf32_Phdr *)(elf_start + hdr->e_phoff);
	// 遍歷程序頭
	// p_filesz:該成員給出段的文件映像中的字節數;它可能是零。 
	// p_memsz:該成員給出段的內存映像中的字節數;它 可能是零。
    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); // munmap()用來取消參數start所指的映射內存起始地址,參數length則是欲取消的內存大小。
                    // 當進程結束或利用exec相關函數來執行其他程序時,映射內存會自動解除,但關閉對應的文件描述符時不會解除映射。
                    return 0;
            }
            if(!phdr[i].p_filesz) {
                    continue;
            }

            // p_filesz can be smaller than p_memsz,
            // the difference is zeroe'd out.
            // p_offset:該字段指明該段中內容在文件中的位置
            start = elf_start + phdr[i].p_offset; // 在文件中的位置
            taddr = phdr[i].p_vaddr + exec; // 
            memmove(taddr,start,phdr[i].p_filesz);
            // 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) {// .dynsym | SHT_DYNSYM | SHF_ALLOC
            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);
            // 查找爲 main 的符號,所在的地址
            break;
        }
    }

    for(i=0; i < hdr->e_shnum; ++i) {
        if (shdr[i].sh_type == SHT_REL) { // .relname | 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 oracle的文檔 sys/elf.h。

.dynsym - 動態鏈接符號表。有關詳細信息,請參見符號表節
符號表節
目標文件的符號表包含定位和重定位程序的符號定義和符號引用所需的信息。符號表索引是此數組的下標。索引 0 指定表中的第一項並用作未定義的符號索引。請參見表 12-21。
https://docs.huihoo.com/solaris/11/simplified-chinese/html/E25910/chapter6-79797.html#scrolltoc

.relname、.relaname - 重定位信息,如重定位節中所述。如果文件具有包括重定位的可裝入段,則此節的屬性將包括 SHF_ALLOC 位。否則,該位會處於禁用狀態。通常,name 由應用重定位的節提供。因此,.text 的重定位節的名稱通常爲 .rel.text 或 .rela.text。
https://docs.huihoo.com/solaris/11/simplified-chinese/html/E25910/chapter6-54839.html#scrolltoc

鏈接程序和庫指南 Oracle Solaris 11 Information Library (簡體中文)

elf.c:

#include <stdio.h>

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

test run:

$ 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



網上資料:(耐瑟)

打造史上最小可執行 ELF 文件(45 字節,可打印字符串)
來自 https://tinylab.gitbooks.io/cbook/zh/chapters/02-chapter8.html

Android so庫文件的區節section修復代碼分析
來自 https://blog.csdn.net/QQ1084283172/article/details/78818917


Geany linux下開發工具可用。(或許應該適應vim



圖解,未完待續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章