没有section header的情况下,如何确定dynamic符号表个数

key

没有section header的情况下,如何确定符号表个数

如何通过gnu_hash_table获取chains个数

如何通过gnu_hash_table获取符号表个数

前言

背景: 最近我在写一个elf的文件解析的GUI工具, 其中考虑到如下情况

  • 如果elf有节表,我们可以通过.dynamic节找到动态符号表有多少个符号
  • 如果elf没节表, 因为节表不是elf运行的必选项, 该如何找到符号表的数量???. 因为通过sym_tabel对应的table大小,我要去提取相关信息

elf运行, 以我的观点来看, 它不需要知道符号表的个数

因为它的动态加载机制是一种哈希查找, 直接通过index索引值遍历获取,

在获取符号索引值期间, 符号表的个数不是他们查找的限制范围之一. 就像是 sym_idx=chains[xx]

xx不是通过for i in rang(len(sym_table))的形式获取的

接下来,是我通过阅读安卓源码, 初步的分析, elf动态加载机制是怎么获取符号的?

类似于 elf导入了printf, 我怎么拿到printf这个符号表对应的表项

源码网站 https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:

dlsym

首先搜索函数, 在 //external/musl/src/ldso/dlsym.c定位到如下

#include <dlfcn.h>
#include "dynlink.h"

void *dlsym(void *restrict so_handle, const char *restrict func_name)
{
	return __dlsym(so_handle, func_name, 0);
}

然后搜索 __dlsym 进入如下

//external/musl/ldso/dynlink.c
hidden void *__dlsym(
	void *restrict so_handle, 
	const char *restrict func_name, 
	void *restrict ra)
{
	void *res;
	pthread_rwlock_rdlock(&lock);
	res = do_dlsym(so_handle, func_name, ra/*0*/);//ra=0
	pthread_rwlock_unlock(&lock);
	return res;
}

函数继续进入了do_dlsym, 继续跟进

//external/musl/ldso/dynlink.c
static void *do_dlsym(struct dso *so_handle, const char *func_name, void *ra)
{
	int use_deps = 0;
	if (so_handle == head || so_handle == RTLD_DEFAULT) 
    {
		so_handle = head;
	} 
    else if (so_handle == RTLD_NEXT) 
    {
		so_handle = addr2dso((size_t)ra);
		if (!so_handle) 
            so_handle=head;
		so_handle = so_handle->next;
	} 
    else if (__dl_invalid_handle(so_handle)) 
    {
		return 0;
	} 
    else
	{
		use_deps = 1;
	}
    //---------------------------------------------------------
	struct symdef def = find_sym2(so_handle, func_name, 0, use_deps);//分析一下代码,好像是在这里获取了sym_table的index
	if (!def.sym) 
    {
		error("Symbol not found: %func_name", func_name);
		return 0;
	}
	if ((def.sym->st_info&0xf) == STT_TLS)
		return __tls_get_addr((tls_mod_off_t []){def.dso->tls_id, def.sym->st_value-DTP_OFFSET});
	if (DL_FDPIC && (def.sym->st_info&0xf) == STT_FUNC)
		return def.dso->funcdescs + (def.sym - def.dso->syms);
	return laddr(def.dso, def.sym->st_value);
}

分析一下代码,好像是在find_sym2(so_handle, func_name, 0, use_deps);获取了sym_table的index, 跟进函数

static inline struct symdef find_sym2(struct dso *so_handle, const char *func_name, int need_def, int use_deps)
{
	uint32_t h = 0, hash1 = gnu_hash(func_name), hash1_div = hash1 / (8*sizeof(size_t)), *gnu_hash_table;
	size_t hash1_mod_mask = 1ul << (hash1 % (8*sizeof(size_t)));
	struct symdef def = {0};
	struct dso **deps = use_deps ? so_handle->deps : 0;

	for (; so_handle; so_handle=use_deps ? *deps++ : so_handle->syms_next) 
	{
		Sym *sym;
		if ((gnu_hash_table = so_handle->ghashtab)) //首选GNU_HASH_TABLE, 没有就HASH_TABLE
		{
			sym = gnu_lookup_filtered(
				hash1, 
				gnu_hash_table, 
				so_handle, 
				func_name, 
				hash1_div, 
				hash1_mod_mask
				);
		} 
		else 
		{
			if (!h) //一开始为0
			{
				h = sysv_hash(func_name);/*一种hash算法*/
				//然后更新h
			}
			sym = sysv_lookup(func_name, h, so_handle);
		}
		if (!sym) 
			continue;
		if (!sym->st_shndx)
			if (need_def || (sym->st_info&0xf) == STT_TLS|| ARCH_SYM_REJECT_UND(sym))
				continue;
		if (!sym->st_value)
			if ((sym->st_info&0xf) != STT_TLS)
				continue;
		if (!(1<<(sym->st_info&0xf) & OK_TYPES)) 
			continue;
		if (!(1<<(sym->st_info>>4) & OK_BINDS)) 
			continue;
		def.sym = sym;
		def.so_handle = so_handle;
		break;
	}
	return def;
}
static uint32_t gnu_hash(const char *s0)
{
	const unsigned char *s = (void *)s0;
	uint_fast32_t h = 5381;
	for (; *s; s++)
		h += h*32 + *s;
	return h;
}

初略分析一下代码, 发现如下情况

如果存在gnu_hash_table, 就去调用gnu_lookup_filtered()去gnu_hash_table找符号

不然去进入函数sysv_lookup() 去hash_table寻找

如何判断 是否存在gnu_hash_table和hash_table

这个在 DYNAMIC 的段头中, d_tag = GNU_HASH 或者 HASH, 表明了是否有对应的gnu_hash_table和hash_table

gnu_lookup_filtered

先进入gnu_lookup_filtered()函数

传入了一些参数, 关注如下参数

uint32_t hash1 = gnu_hash(func_name);
uint32_t hash1_div = hash1 / (8*sizeof(size_t)), *gnu_hash_table;
size_t hash1_mod_mask = 1ul << (hash1 % (8*sizeof(size_t)));

hash1通过调用函数gnu_hash()获取一个hash值

在x86中,sizeof(size_t)=4, x64中, sizeof(size_t)=8

(8*sizeof(size_t))就是对应长度的bit位个数

hash1_div 是hash1的值,除以bit位长度

hash1_mod_mask 是hash1的值,取模bit位长度, 得到的值作为1的左移长度

以x64为例, hash1的值被分解为

 hash1 / 0x44 = x ... y 
 z= 1<<y

ps: 接了下来, 我们都以x64的情况为例

ps: 接了下来, 我们都以x64的情况为例

ps: 接了下来, 我们都以x64的情况为例

然后我们继续跟进gnu_lookup_filtered()

static Sym *gnu_lookup_filtered(
	uint32_t hash1, 
	uint32_t *gnu_hash_table, 
	struct dso *so_handle, 
	const char *func_name, 
	uint32_t hash1_div, 
	size_t hash1_mode_mask
	)
{
	uint32_t bloomSize = gnu_hash_table[2];
	uint32_t bloomShift = gnu_hash_table[3];
	const size_t *blooms = (const void *)(gnu_hash_table+4);//获取数组blooms
    uint32_t mask2 = bloomSize-1 ;// bloomSize貌似都是2^xx的形式
	size_t bloom = blooms[hash1_div & mask2]; 
    
	if (!(bloom & hash1_mode_mask)) //hash1_mode_mask大概是 0b10000000000这种形式
		return 0;//hash1_mode_mask对应的bit位是0

	bloom = bloom >> (hash1 >> bloomShift) % (8 * sizeof bloom);
	//bloom = bloom >> ((hash1 >> bloomShift) % 64);
	if (!(bloom & 1)) 
		return 0;

	return gnu_lookup(hash1, gnu_hash_table, so_handle, func_name);
}
/*
 hash1 / 0x44 = x ... y 
 z= 1<<y
 */

通过计算, 得到的 blooms[x&mask2] & (1<<y) ,

如果位运算结果是0, 取消查找,直接返回.

接着继续判断, bloom = bloom >> ( (hash1 >> bloomShift) % 0x40)

如果 bloom & 1, 结果是0, 取消查找, 直接返回

在以上2种判断下, 假设都不为0, 那就继续查找, 跟进函数 gnu_lookup()

static Sym *gnu_lookup(
	uint32_t hash1, 
	uint32_t *gnu_hash_table, 
	struct dso *so_handle, 
	const char *func_name
	)
{
	uint32_t nbuckets = gnu_hash_table[0];
	uint32_t symndx = gnu_hash_table[1];
	uint32_t bloomSize = gnu_hash_table[2];
	
	uint32_t *buckets = gnu_hash_table + 4 + bloomSize * (sizeof(size_t)/4);
	uint32_t *chanis = buckets + nbuckets;

	/*
	(sizeof(size_t)/4) = 1 in x86
	(sizeof(size_t)/4) =2 in x64
	*/
	uint32_t i = buckets[hash1 % nbuckets];//拿的是一个迭代的初始值

	if (!i) //拿到0就算了
		return 0;

	uint32_t *hashval = chanis + (i - symndx);//每一个i都是在以symndx为基础??

	for (hash1 |= 1; ; i++) 
	{
		uint32_t chain = *hashval++;
		if (
			(hash1 == (chain|1)) //这里可以看到并没有使用chain|=1
			&& (!so_handle->versym || so_handle->versym[i] >= 0)
		    && !strcmp(func_name, so_handle->strings + so_handle->syms[i].st_name))
		{
			return so_handle->syms+i;
		}
			
		if (chain & 1) //最低位是1 也g...
		{
			break;
		}
	}
	return 0;
}

首先通过 hash1, 获取一个i值, i = buckets[hash1 % nbuckets];

这个i值将会被作为一个sym_table的起始索引值,

同时i - symndx也会作为一个hash桶(hash数组)的起始索引值

如果i = 0, 说明是一个空符号? 或者不存在该符号? 然后直接返回0

通过 i - symndx获取一个起始的hashval数组 , uint32_t *hashval = chanis + (i - symndx)

接着就是顺序性的判断

如果得打到 chain | 1 和hash1相等, 并且对应的sym_table[i].st_name 和func_name也是相等的,大概就说明找到符号了

如果没找到, 同时chain&1 结果是0, 那就继续查找, 不然没找到, 退出

这大概就是一个符号通过gnu_hash的方式查找…

那么问题来了? sym_table的大小是多少?

多加分析可以发现, 假设在没有 func_name的情况下, 直接来到函数gnu_lookup()

我们理想情况下, 会有一个i值 i = buckets[hash1 % nbuckets];

这个i会作为sym_table的索引值,

是不是可以认为buckets中, 涵盖所有的i 值呢?

实际查看某个kali_x64_libc.so.6文件,

发现buckets的长度和len(sym_table)的数量相差很大

比如buckets有0x3ff给成员, 但sym_table有0xC08个成员

为什么会这样???

既然buckets没有那么多索引值, 如何索引到对应的符号???

因为后面还会有判断的推进, 比如chain&1, hashval++ , i++

	for (hash1 |= 1; ; i++) 
	{
		uint32_t chain = *hashval++;
		if (
			(hash1 == (chain|1)) //这里可以看到并没有使用chain|=1
			&& (!so_handle->versym || so_handle->versym[i] >= 0)
		    && !strcmp(func_name, so_handle->strings + so_handle->syms[i].st_name))
		{
			return so_handle->syms+i;
		}
			
		if (chain & 1) //最低位是1 也g...
		{
			break;
		}
	}

在kali_x64_libc.so.6, 同时也发现, 最大的buckets[]值 + 1 ,就是sym_table的长度

所以可以 初略简单的认为 i_max = bucket_max + 1 = len(sym_table)

当然更加准确的理解,应该是 (i_max + 1) <= len(sym_table)

chains的数量是多少???

chains_len = len(sym_table) - symndx

sysv_lookup

假如没有DT_GNU_HASH对应的d_tag, 我们就要去hash_table查找函数

我们进入函数 sysv_lookup()

#include <dlfcn.h>
#include "dynlink.h"
static Sym *sysv_lookup(const char *func_name, uint32_t hash, struct dso *so_handle)
{
	size_t i;
	Sym *syms = so_handle->syms;
	//Elf_Symndx *hashtab = so_handle->hashtab;
    uint32_t* hashtab = so_handle->hashtab;
    uint32_t nbucket = hashtab[0];
    uint32_t* buckets = hashtab + 2 ;
    uint32_t* chains = buckets + nbucket;
    
    char *strings = so_handle->strings;
    i=buckets[hash%nbucket];
    //i = 0 直接over
	for (; i ; i=chains[i]) 
    {
		if ((!so_handle->versym || so_handle->versym[i] >= 0)
		    && (!strcmp(func_name, strings+syms[i].st_name)))
        {
            return syms+i;
        }
	}
	return 0;
}
static uint32_t sysv_hash(const char *s0) //0结尾的字符串
{
    const unsigned char *s = (void *)s0;
    uint_fast32_t h = 0;
    while (*s) 
    {
        h = 16*h + *s++;
        h ^= h>>24 & 0xf0;
    }
    return h & 0xfffffff;
}

首先通过hash 获取一个i值 i=buckets[hash%nbucket];

这个 i 作为 sym_tables的起始索引值

然后遍历, i的数值更新是 i=chains[i] , 而不是i++

我们可不可以认为, buckets涵盖了所有sym_table的索引值

通过kali_x64_libc.so.6发现, buckets的数量是远远小于sym_table的

那么我们可不可以认为, chains中含有所有的sym_table的index

相比较于gnu_lookup的查找, sysv_lookup的查找比较单一,

i的获取直接在chains[]中拿, 拿不到就g掉

所以chains中应该含有所有的sym_table索引值

可以认为chains[]的数量就是符号表的个数

小结

在hash_table中, hash_table.nchain 就是符号表个数

在gnu_hash_table中, max(gnu_hash_table.buckets[]) + 1<= len(sym_table)

没有hash_table, 可以不妥,简单的认为 sym_table的个数 = max(gnu_hash_table.buckets[]) +1

后续

在 GNU_HASH table 中,我粗略的认为 buckets中, 最大的bucket值可以作为 dynsym_table的符号个数

在有限的知识内,这当然是不妥的, 于是在之后还是碰到了这个bug, 获取的max_bucket + 1 != len( dynsym_table)

那该怎么办?? 于是更新了方案

假如通过 max_bucket 拿到的chain值不是一个有效的符号, 也就说

chani & 1 !=0 成立

那么我们就需要继续遍历, 往 max_bucket 之后遍历, 每遍历一次, 我们的自定义获取的 len_sym就会 ++

一份局部的python代码如下

    while mm_GnuHashTable.chains[-1].value & 1 == 0:
        chani = read_file_from_struct(file, mm_Elf32_Word)
        mm_GnuHashTable.chains.append(chani.item)
        sym_table_cnt += 1

完整的python 参考代码如下

def parse_dynamic_GNU_HASH(
        file: _BufferedIOBase,
        dyn_items_organize: Dict[str, Union[Dynamic32, Dynamic64]],
        elf_classe: str,
) -> Tuple[GnuHashTable, Any]:
    dict_keys = dyn_items_organize.keys()
    if 'DT_GNU_HASH' not in dict_keys:
        return (None, 0)

    mm_gnu_hash = dyn_items_organize['DT_GNU_HASH']

    file.seek(mm_gnu_hash.d_value.value)
    mm_GnuHashTable: GnuHashTable = read_file_from_struct(file, GnuHashTable)
    mm_GnuHashTable.blooms = []

    bloom_type = mm_Elf32_Word

    if elf_classe == '64':
        bloom_type = mm_Elf64_Xword
    for i in range(mm_GnuHashTable.bloomSize.value):
        bloom = read_file_from_struct(file, bloom_type)
        mm_GnuHashTable.blooms.append(bloom.item)

    mm_GnuHashTable.buckets = []
    sym_table_idx_max = 0
    for i in range(mm_GnuHashTable.nbucket.value):
        bucket = read_file_from_struct(file, mm_Elf32_Word)
        mm_GnuHashTable.buckets.append(bucket.item)
        if sym_table_idx_max < bucket.item.value:
            sym_table_idx_max = bucket.item.value

            
    if sym_table_idx_max < mm_GnuHashTable.symndx.value:  #可以寻到的最大值都小于symdnx, 那就没意思了
        sym_table_cnt = mm_GnuHashTable.symndx.value #数量
    else:
        sym_table_cnt = sym_table_idx_max + 1 #符号的数量


    # sym_table_cnt是符号的数量,不是chains的数量
    # sym_table_cnt = len(sym_table) = len(chains) + symndx

    nchain = sym_table_cnt - mm_GnuHashTable.symndx.value
    mm_GnuHashTable.chains = []
    for i in range(nchain):
        chani = read_file_from_struct(file, mm_Elf32_Word)
        mm_GnuHashTable.chains.append(chani.item)
        #print(chani.value & 1)

    # 此刻大概是读取到末尾了,但不一定是末尾
    # 尝试再读取
    while mm_GnuHashTable.chains[-1].value & 1 == 0:
        chani = read_file_from_struct(file, mm_Elf32_Word)
        mm_GnuHashTable.chains.append(chani.item)
        sym_table_cnt += 1

    return (
        mm_GnuHashTable,
        sym_table_cnt
    )

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值