elfLoader c(#elf执行视图# #32位#)

2 篇文章 0 订阅

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



图解,未完待续…

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值