在运行时读取已加载共享 object 的 ELF header

Reading ELF header of loaded shared object during runtime

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

现在,我想使用这段代码来解析加载的共享库的 ELF header。例如,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 header。我试图阅读此 header 并在 .dynsym 部分中提取 dlopen 符号。但是,header 与磁盘上的 'plain'.so 文件略有不同。例如.shstrtab版本的offset为0,因此无法获取section的名称

我想问一下为什么 ELF header 在加载库的过程中发生了变化,我在哪里可以找到 'missing' 部分。加载库后甚至可以解析 ELF header 吗? 有人知道任何解释共享 library/its ELF header 映射到进程时的布局的文章吗?

目前我正在使用以下函数迭代 ELF header。如果 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;
}

why the ELF header is changed during loading of the library

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

更新:

在此代码中:

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

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

您需要使用从 ehdr.

获得的 e_shoff 自己将它们从磁盘(或 mmap 它们)读入内存

您不需要再次映射共享库 - 系统已经完成了 - 但您不能依赖 headers 部分。 headers 部分仅用于 ELF 文件的 linking 视图,通常不分配到程序段中。您需要从执行视图中查看它。 .dynstr 部分始终加载到内存中。否则动态 linking 将无法工作。要找到它,请通过程序 headers 找到 PT_DYNAMIC 段。它将包含对应于 .dynsym 和 .dynstr 的元素 DT_SYMTAB 和 DT_STRTAB。您可能还必须使用基地址调整地址值。将共享 objects 映射到与 linked 所在的虚拟地址不同的虚拟地址是非常常见的,尤其是对于 ASLR。您可以通过从内存映射中的最低映射段减去 PT_LOAD 条目中的最低虚拟地址来找到此调整量。或者更好地使用 ld.so 维护的 link 地图。它包含基地址、共享 object 的路径和指向共享 object 的动态区域的指针。咨询这是如何布局的。如果你是 运行 Linux,你可能会对函数 dl_iterate_phdr() 非常感兴趣。它非常适合查找有关映射到当前过程映像的库的信息。如果你想检查另一个过程,你必须自己动手。