【问题标题】:Reading ELF header of loaded shared object during runtime在运行时读取已加载共享对象的 ELF 标头
【发布时间】:2017-06-29 23:40:48
【问题描述】:

我编写了一些代码来搜索共享库的 ELF 标头中的符号。如果我解析存储在磁盘上的共享对象文件,该代码就可以工作。

现在,我想使用此代码来解析已加载共享库的 ELF 标头。例如,将 libdl 库映射到当前进程:

b7735000-b7738000 r-xp 00000000 08:01 315560     /lib/i386-linux-gnu/libdl.so.2
b7738000-b7739000 r--p 00002000 08:01 315560     /lib/i386-linux-gnu/libdl.so.2
b7739000-b773a000 rw-p 00003000 08:01 315560     /lib/i386-linux-gnu/libdl.so.2

地址的(第一个)映射包含 ELF 标头。我试图阅读此标题并在 .dynsym 部分中提取 dlopen 符号。但是,标头与磁盘上的“普通”.so 文件略有不同。例如 .shstrtab 版本的偏移量为 0。因此,无法获取节的名称。

我想问一下为什么在加载库的过程中会更改 ELF 标头以及在哪里可以找到“缺失”部分。加载库后是否甚至可以解析 ELF 标头? 有人知道任何文章解释共享库/它的 ELF 标头映射到进程时的布局吗?

目前我正在使用以下函数来迭代 ELF 标头。如果 libdl_start 指向内存映射的 libdl.so.2 文件,则代码可以正常工作。但是,如果它指向链接器映射的区域,get_dynstr_section 不会找到 dynstr 部分。

int get_libdl_functions()
{
    Elf32_Ehdr *ehdr = libdl_start;
    Elf32_Shdr *shdr, *shdrs_start = (Elf32_Shdr *)(((char *)ehdr) + ehdr->e_shoff);
    Elf32_Sym *symbol, *symbols_start;
    char *strtab = get_dynstr_section();
    int sec_it = 0, sym_it = 0;

    rt_info->dlopen = NULL;
    rt_info->dlsym = NULL;

    if(strtab == NULL)
        return -1;

    for(sec_it = 0; sec_it < ehdr->e_shnum; ++sec_it) {
        // Iterate over all sections to find .dynsym
        shdr = shdrs_start + sec_it;
        if(shdr->sh_type == SHT_DYNSYM)
        {
            // Ok we found the right section
            symbols_start = (Elf32_Sym *)(((char *)ehdr) + shdr->sh_offset);
            for(sym_it = 0; sym_it < shdr->sh_size / sizeof(Elf32_Sym); ++sym_it) {
                symbol = symbols_start + sym_it;
                if(ELF32_ST_TYPE(symbol->st_info) != STT_FUNC)
                    continue;

                if(strncmp(strtab + symbol->st_name, DL_OPEN_NAME, sizeof DL_OPEN_NAME) && !rt_info->dlopen) {
                    //printf("Offset of dlopen: 0x%x\n", symbol->st_value);
                    dlopen = ((char *)ehdr) + symbol->st_value;
                } else if(strncmp(strtab + symbol->st_name, DL_SYM_NAME, sizeof DL_SYM_NAME) && !rt_info->dlsym) {
                    //printf("Offset of dlsym: 0x%x\n", symbol->st_value);
                    dlsym = ((char *)ehdr) + symbol->st_value;
                }

                if(dlopen != 0 && dlsym != 0)
                    return 0;
            }
        }
    }

    return -1;
}

void *get_dynstr_section()
{
    Elf32_Ehdr *ehdr = libdl_start;
    Elf32_Shdr *shdr, *shdrs_start = (Elf32_Shdr *)(((char *)ehdr) + ehdr->e_shoff);
    char *strtab = ((char *)ehdr) + ((shdrs_start + ehdr->e_shstrndx))->sh_offset;
    int sec_it = 0;

    for(sec_it = 0; sec_it < ehdr->e_shnum; ++sec_it) {
        // Iterate over all sections to find .dynstr section
        shdr = shdrs_start + sec_it;
        if(shdr->sh_type == SHT_STRTAB && strncmp(strtab + shdr->sh_name, DYNSTR_NAME, sizeof DYNSTR_NAME))
            return ((char *)ehdr) + shdr->sh_offset;
    }

    return NULL;
}

【问题讨论】:

    标签: linux process mapping shared-libraries elf


    【解决方案1】:

    您不需要再次映射共享库 - 系统已经这样做了 - 但您不能依赖节标题。节头仅用于 ELF 文件的链接视图,通常不分配到程序段中。您将需要从执行视图中查看它。 .dynstr 部分总是加载到内存中。否则动态链接将不起作用。为了得到它,通过程序头找到 PT_DYNAMIC 段。它将具有对应于 .dynsym 和 .dynstr 的元素 DT_SYMTAB 和 DT_STRTAB。您可能还必须使用基地址调整地址值。特别是对于 ASLR,共享对象映射到与链接不同的虚拟地址是很常见的。您可以通过从内存映射中的最低映射段中减去 PT_LOAD 条目中的最低虚拟地址来找到此调整量。或者甚至更好地使用 ld.so 维护的链接图。它包含共享对象的基地址、路径和指向共享对象动态区域的指针。咨询这是如何布置的。如果您正在运行 Linux,您可能对函数 dl_iterate_phdr() 非常感兴趣。查找有关映射到当前过程映像的库的信息非常有用。如果你想检查另一个过程,你必须自己动手。

    【讨论】:

    • 我想咨询“”以了解链接图的布局方式
    【解决方案2】:

    为什么在加载库时会更改 ELF 标头

    不是。您的问题基于错误的假设,但由于您没有显示任何实际代码,因此很难猜测您做错了什么。

    更新:

    在这段代码中:

    *shdrs_start = (Elf32_Shdr *)(((char *)ehdr) + ehdr->e_shoff);
    

    您假设节标题已加载到内存中。但是节头在运行时不需要,如果它们最终加载到内存中,那只是偶然。

    您需要使用从ehdr 获得的e_shoff 自己将它们从磁盘(或mmap 它们)读入内存。

    【讨论】:

    • 我将用于迭代 ELF 标头的代码添加到原始问题中。
    • @PraMiD 据我了解,您的问题是“get_dynstr_section 找不到 dynstr 部分”。但是您没有显示 代码。你希望别人如何帮助你?你应该尝试构造一个stackoverflow.com/help/mcve
    • 问题中包含get_dynstr_section的代码。紧随get_libdl_functions
    • 抱歉,上周没时间回答。 @Employed Russion:感谢您的回答!我同意你的观点,没有理由将 ELF 标头加载到内存中。尽管如此,我观察到 Linux 总是将标头加载到内存中(我能够读取其中的一部分),但它与磁盘上的不同。即,它更短,并且某些部分(例如 shstrtab)“缺失”。这种行为是否有任何原因,还是如您所说的那样只是偶然?
    • @PraMiD ELF header 总是被加载,section 头不是。
    猜你喜欢
    • 2011-10-01
    • 2021-06-04
    • 1970-01-01
    • 2020-10-16
    • 2012-05-08
    • 2012-02-24
    • 2014-11-20
    • 1970-01-01
    • 2018-09-17
    相关资源
    最近更新 更多