在运行时读取加载的共享对象的ELF头

时间:2017-06-29 23:40:49

标签: linux process mapping shared-libraries elf

我写了一些代码来搜索共享库的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;
}

2 个答案:

答案 0 :(得分:2)

  

在加载库

期间更改ELF标头的原因

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

<强>更新

在此代码中:

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

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

您需要使用mmap中的e_shoff自己从磁盘(或ehdr)读取内存。

答案 1 :(得分:0)

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