从装载机跳转到ELF的入口点

时间:2019-04-07 23:42:24

标签: c libc

借助this问题的帮助,加载程序现在可以将静态编译的hello世界映射到内存中,并跳转到该内存区域中的某个位置。我现在面临的问题是我似乎没有跳到正确的地址,或者我以错误的方式(或错误的功能?)调用了函数。

下面是要尝试的代码;我在glibc中找不到加载器在哪里调用程序的入口点以验证我在做正确的事情。我尝试过:

  • 调用_start(void)
  • 调用_start(int,char **,char **)
  • 使用最后一个参数设置为NULL(找到here)调用__libc_start_main
  • 直接致电main
  • 在中调用某些地址(在rdi中的gdb中找到:rR:400B4D,r8:4018E0,rcx:401840)

入口点是0x400a30,__ libc_start_main下面有一些说明。其中有SIGSEGV,SIGABRT或印刷品:

hello, world! haswell xeon_phi ../csu/libc-start.c FATAL: kernel too old
   __ehdr_start.e_phentsize == sizeof *GL(dl_phdr) unexpected reloc type in static binary  FATAL: cannot determine kernel version
 __libc_start_main /dev/full /dev/null   cannot set %fs base address for thread-local storage :  %s%s%s:%u: %s%sAssertion `%s' failed.
%n        Unexpected error.

和几百条垃圾线。

int main(int argc, char* argv[argc+1]) {
    FILE *fp = fopen(argv[1], "r");
    if (!fp) {
        fprintf(stderr, "cannot open file %s", argv[1]);
        return 1;
    }

    fseek(fp, 0L, SEEK_END);
    size_t sz = ftell(fp) + 1;
    rewind(fp);

    char *region = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
    if (region == MAP_FAILED) {
        fprintf(stderr, "could not mmap");
        return 1;
    }

    Elf64_Ehdr elf64Ehdr;
    memmove(&elf64Ehdr, region, sizeof(elf64Ehdr));

    size_t offset;
    Elf64_Phdr elf64Phdr;
    for (uint16_t i = 0; i != elf64Ehdr.e_phnum; ++i) {
        offset = elf64Ehdr.e_phoff + i * elf64Ehdr.e_phentsize;
        memmove(&elf64Phdr, region + offset, sizeof(elf64Phdr));
        switch (elf64Phdr.p_type) {
            case PT_NULL:
                break;
            case PT_LOAD:
                if (load(&elf64Phdr, region + elf64Phdr.p_offset)) {
                    exit(EXIT_FAILURE);
                }
                break;
            default:
                break;
        }
    }
    printf("jumping to: 0x%x\n", elf64Ehdr.e_entry);

    char *argv1[] = {"", NULL};

    int ret = ((int (*)(int, char **, char **)) elf64Ehdr.e_entry)(1, argv1, argv1);
    return ret;
}

int load(const Elf64_Phdr *phdr, const void *elf_bytes_for_phdr) {
    const size_t pagesize = getpagesize();
    const size_t unaligned_bytes = phdr->p_vaddr % pagesize;

    void *base_addr = phdr->p_vaddr - unaligned_bytes;
    size_t total_bytes = phdr->p_memsz + unaligned_bytes;

    void *region = mmap(base_addr, total_bytes,
            phdr->p_flags | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
    if (region != MAP_FAILED) {
        memset(region, 0, unaligned_bytes);
        return memcpy(region + unaligned_bytes, elf_bytes_for_phdr, phdr->p_filesz) != region + unaligned_bytes && 
        ! mprotect(region, total_bytes, phdr->p_flags);
    }
    return 1;
}

1 个答案:

答案 0 :(得分:3)

  

我现在面临的问题是我似乎没有跳转到正确的地址,或者我以错误的方式调用了函数

您的问题不是以上两个问题(尽管“打错方式”不太远)。

对于静态链接的可执行文件,Elf64_Ehdr.e_entry是要调用的正确地址(它指向_start),而_start不带任何参数。

问题在于_start的工作是

  1. 初始化libc,然后
  2. 找到正确的值argcargvenvp,最后
  3. 呼叫main(argc, argv, envp)

然后的问题是:_start如何完成第2步?

答案:有一个协议,Linux内核实现_start 使用来完成步骤2。

尤其是,内核将argv[0]argv[1],... envp[0]envp[1]等的实际(字符串)值复制到堆栈中,然后指向这些字符串的指针。还有一种叫做辅助向量的东西。

_start 期望在堆栈上查找所有这些信息,并且在找不到该信息时会表现异常。我相信这是您当前问题的根本原因。

这里是article,其中解释了有关Linux内核源代码的预期设置。另一个article

相关问题