0x00  前言

这篇文章其实是我之前学习elf文件关于符号表的学习笔记,网上也有很多关于符号表的文章,怎么说呢,感觉像是在翻译elf文件格式的文档一样,千篇一律,因此把自己的学习笔记分享出来。dlsym()的源码是分析的android4.4的源码,android自己实现的bonic C库。

0x01  基本流程

android中关于elf文件,关于so文件信息的结构体:

structsoinfo {public:charname[SOINFO_NAME_LEN];const Elf32_Phdr*phdr;

size_t phnum;

Elf32_Addr entry;

Elf32_Addrbase;

unsigned size;

uint32_t unused1;//DO NOT USE, maintained for compatibility.

Elf32_Dyn*dynamic;

uint32_t unused2;//DO NOT USE, maintained for compatibility

uint32_t unused3; //DO NOT USE, maintained for compatibility

soinfo*next;

unsigned flags;const char*strtab;

Elf32_Sym*symtab;

size_t nbucket;

size_t nchain;

unsigned*bucket;

unsigned*chain;

unsigned*plt_got;

Elf32_Rel*plt_rel;

size_t plt_rel_count;

Elf32_Rel*rel;

size_t rel_count;

linker_function_t*preinit_array;

size_t preinit_array_count;

linker_function_t*init_array;

size_t init_array_count;

linker_function_t*fini_array;

size_t fini_array_count;

linker_function_t init_func;

linker_function_t fini_func;#if defined(ANDROID_ARM_LINKER)

//ARM EABI section used for stack unwinding.

unsigned*ARM_exidx;

size_t ARM_exidx_count;#elif defined(ANDROID_MIPS_LINKER)unsigned mips_symtabno;

unsigned mips_local_gotno;

unsigned mips_gotsym;#endifsize_t ref_count;

link_map_t link_map;boolconstructors_called;//When you read a virtual address from the ELF file, add this//value to get the corresponding address in the process' address space.

Elf32_Addr load_bias;boolhas_text_relocations;boolhas_DT_SYMBOLIC;voidCallConstructors();voidCallDestructors();voidCallPreInitConstructors();private:void CallArray(const char* array_name, linker_function_t* functions, size_t count, boolreverse);void CallFunction(const char*function_name, linker_function_t function);

};

soinfo

然后就是我们关心的dlsym()函数,

void*dlsym(void*handle,constchar*symbol)

dlsym()的实现对于handle是分三种情况

(1)  handle = RTLD_DEFAULT;

(2)  handle = RTLD_NEXT;

(3) 其他,也就是我们平常调用dlopen()的返回值。

0x02  RTLD_DEFAFULT

soinfo* found =NULL;

Elf32_Sym* sym =NULL;if (handle ==RTLD_DEFAULT) {

sym= dlsym_linear_lookup(symbol, &found, NULL);

}///bonic/linker/Linker.cpp

Elf32_Sym* dlsym_linear_lookup(const char* name, soinfo** found, soinfo*start) {

unsigned elf_hash= elfhash(name); //计算符号名称的hash值

if (start ==NULL) {//static soinfo* solist = &libdl_info; libdl_info是soinfo类型的全局变量包含libdl.so的信息

start =solist;

}

接下来就开始循环调用soinfo_elf_lookup() 遍历soinfo的链表

Elf32_Sym* s =NULL;for (soinfo* si = start; (s == NULL) && (si != NULL); si = si->next) {

s=soinfo_elf_lookup(si, elf_hash, name);if (s !=NULL) {*found =si;break;

}

}

soinfo_elf_lookup()函数的源码:

///bonic/linker/Linker.cpp//soinfo_elf_lookup()在指定模块查找指定的符号项

static Elf32_Sym* soinfo_elf_lookup(soinfo* si, unsigned hash, const char*name) {

Elf32_Sym* symtab = si->symtab; //这里实际上是.dynsym 不要被名字误导

const char* strtab = si->strtab; //这里实际上是.dynstr

TRACE_TYPE(LOOKUP, "SEARCH %s in %s@0x%08x %08x %d",

name, si->name, si->base, hash, hash % si->nbucket);for (unsigned n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]) {

Elf32_Sym* s = symtab +n;if (strcmp(strtab + s->st_name, name)) continue;/*only concern ourselves with global and weak symbol definitions*/

switch(ELF32_ST_BIND(s->st_info)){caseSTB_GLOBAL:caseSTB_WEAK:if (s->st_shndx ==SHN_UNDEF) {continue;

}

TRACE_TYPE(LOOKUP,"FOUND %s in %s (%08x) %d",

name, si->name, s->st_value, s->st_size);returns;

}

}returnNULL;

}if (s !=NULL) {

TRACE_TYPE(LOOKUP,"%s s->st_value = 0x%08x, found->base = 0x%08x",

name, s->st_value, (*found)->base);

}returns;

}

需要说明的两点:

1. symtab 和strtab

Elf32_Sym* symtab = si->symtab;

const char* strtab = si->strtab;

这两句代码,可以跟入dlopen()的源码看到soinfo_link_image()函数中对si->symtab和si->strtab的赋值,这里只截取部分代码:

for (Elf32_Dyn* d = si->dynamic; d->d_tag != DT_NULL; ++d) {switch(d->d_tag){caseDT_HASH:

si->nbucket = ((unsigned *) (base + d->d_un.d_ptr))[0];

si->nchain = ((unsigned *) (base + d->d_un.d_ptr))[1];

si->bucket = (unsigned *) (base + d->d_un.d_ptr + 8);

si->chain = (unsigned *) (base + d->d_un.d_ptr + 8 + si->nbucket * 4);break;case DT_STRTAB: //.dynsymsi->strtab = (const char *) (base + d->d_un.d_ptr);break;case DT_SYMTAB: //.dynstrsi->symtab = (Elf32_Sym *) (base + d->d_un.d_ptr);break;

}

}

可以看到strtab和symtab实际是.dynsym 和.dynstr。

可以看到循环条件中的si->dynamic,.dynamic section 保存了动态链接需要的基本信息,比如依赖哪些共享对象,动态链接符号表(.dynsym)的位置,动态链接重定位表(.rel.dyn)的位置,共享对象初始化代码的地址等等。.dynamic section可以看成动态链接下的类似的ELF“文件头”。

typedef struct{

Elf32_Word d_tag;/*entry tag value*/union {

Elf32_Addr d_ptr;

Elf32_Word d_val;

} d_un;

} Elf32_Dyn;

typedefstruct{

Elf64_Xword d_tag;/*entry tag value*/union {

Elf64_Addr d_ptr;

Elf64_Xword d_val;

} d_un;

} Elf64_Dyn;

d_un联合体的取值很据d_tag来定,这里列举常用的一些:

d_tag

d_un

作用

DT_SYMTAB

d_ptr   .dynsym section addr

确定.dynsym section

DT_STRTAB

d_ptr   .dynstr section addr

确定 .dynstr

DT_STRSZ

d_val   .dynstr section size (byte)

DT_REL

d_ptr   .rel.dyn addr

确定.rel.dyn

DT_RELSZ

d_val   .rel.dyn size (byte)

DT_JMPREL

d_ptr   .rel.plt  addr

确定.rel.plt

DT_PLTRELSZ

d_val   .rel.plt  size (byte)

DT_INIT_ARRAY

d_ptr   .init_array  addr

确定 .init_array

DT_INIT_ARRAYSZ

d_val   .init_arrary size (byte)

DT_FINT_ARRAY

d_ptr   .fint_array  addr

确定.finit_array

DT_FINT_ARRAYSZ

d_val   .fint_array size (byte)

2. 迭代的条件

for (unsigned n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n])

迭代的条件根据符号表(这里就是.dynsym)中Elf32_Sym的存储结构来的

举个很简单的例子来理解这个结构:

一个由函数名hash获得的值为X,那么bucket[X%nbucket]将给出一个索引Y,即是符号表项的索引也是chain表的索引,如果根据Y得到符号表项不满足条件,chain[Y]将给出下一个符号表项(同样的哈希变量),可以一直沿着“chain链”直到选择到期望名字的符号表项。

android 源码下的elfhash函数/bonic/linker/Linker.cpp

static unsigned elfhash(const char*_name) {const unsigned char* name = (const unsigned char*) _name;

unsigned h= 0, g;while(*name) {

h= (h << 4) + *name++;

g= h & 0xf0000000;

h^=g;

h^= g >> 24;

}returnh;

}

0x03   RTLD_NEXT

else if (handle ==RTLD_NEXT) {void* ret_addr = __builtin_return_address(0);

soinfo* si =find_containing_library(ret_addr);

sym=NULL;if (si && si->next) {

sym= dlsym_linear_lookup(symbol, &found, si->next);

}

}

__builtin_return_address(0)是什么?

它其实是一个gcc的内置函数,用于帮助获取给定函数的调用地址,此处即是要获取dlsym()函数的调用地址。

__builtin_return_address()接收一个称为level的参数。这个参数定义希望获取返回地址的调用堆栈级别。例如,如果指定level为0,那么就是请求当前函数的返回地址。如果指定level为1,那么就是请求调用了当前函数的函数的返回地址,以此类推。

接下来的find_containing_library()

soinfo* find_containing_library(const void*p) {

Elf32_Addr address= reinterpret_cast(p); //类型转换

for (soinfo* si = solist; si != NULL; si = si->next) {if (address >= si->base && address - si->base < si->size) {returnsi;

}

}returnNULL;

}

//static soinfo* solist = &libdl_info;

这里的solist是不是很熟悉,就是之前在dym_linear_lookup()也使用soinfo结构的链表。

很明显find_containing_library()就是获得调用dlsym()函数的模块信息soinfo。

接下来也同样是调用dlsym_linear_lookup()函数,与RTLD_DEAFULT不同的是start参数的不同,从指定的soinfo开始查询,也就是dlsym()的调用模块。

0x04  dlopen()

else{

found= reinterpret_cast(handle); //类型转换

sym =dlsym_handle_lookup(found, symbol);

}

很明显dlopen()返回值其实就是指定模块对应的信息soinfo结构体的地址。

Elf32_Sym* dlsym_handle_lookup(soinfo* si, const char*name)

{returnsoinfo_elf_lookup(si, elfhash(name), name);

}

在dlsym_handle_lookup()也知道简单的封装,只是调用了一次soinfo_elf_lookup()来查找指定的符号项。而soinfo_elf_lookup()的具体实现之前已经讨论了。

0x05  剩下的部分

if (sym !=NULL) {

unsigned bind= ELF32_ST_BIND(sym->st_info);//是全局符号,并且不是section索引不是SHN_UNDEF(如果是SHN_UNDEF)说明这个//符号的定义并不在本文件中)

if (bind == STB_GLOBAL && sym->st_shndx != 0) {

unsigned ret= sym->st_value + found->load_bias;//load_bias :.so文件加载的虚拟基地址

return (void*) ret;

}

__bionic_format_dlerror("symbol found but not global", symbol);returnNULL;

}else{

__bionic_format_dlerror("undefined symbol", symbol);returnNULL;

}

}

#define SHN_UNDEF 0 /* Undefined section */

#define ELF_ST_BIND(info) ((uint32_t)(info) >> 4)

#define ELF32_ST_BIND(info) ELF_ST_BIND(info)

剩下的代码需要了解Elf32_Sym结构体各个字段的含义

typedef struct{

Elf32_Word st_name;/*Symbol name (.strtab index)*/Elf32_Word st_value;/*value of symbol*/Elf32_Word st_size;/*size of symbol*/Elf_Byte st_info;/*type / binding attrs*/Elf_Byte st_other;/*unused*/Elf32_Half st_shndx;/*section index of symbol*/} Elf32_Sym;

st_name:

符号的名字,但它并不是一个字符串,而是字符串表(.strtab或者.dynstr)中的一个索引值,在字符串表中该索引值的位置上存放的字符串就是该符号名字的实际文本。如果此值不为0,则它就代表符号名字在字符串表中的索引值;如果此值为0,则表示此符号没有名字。

st_value:

符号的值;这个字段的值没有固定的类型,它可能代表一个数值,也可能是一个地址,具体要依据上下文来确定;

在可重定位文件中,st_value 包含节索引为 SHN_COMMON 的符号的对齐约束。

在可重定位文件中,st_value 包含所定义符号的节偏移。st_value 表示从 st_shndx 所标识的节的起始位置的偏移。

在可执行文件和共享目标文件中,st_value 包含虚拟地址。为使这些文件的符号更适用于运行时链接程序,节偏移(文件解释)会替换为与节编号无关的虚拟地址(内存解释)。(根据源码,显然在android的bonic中这里的虚拟地址是指相对于加载基地址的偏移)。

例如:一个可执行文件中含有一个函数的引用,而这个函数被定义在一个共享目标文件中,那么在可执行文件中,针对那个共享目标文件的符号表中就应该含有这个函数的符号;符号表的st_shndx字段的值为SHN_UNDEF,这就告诉动态链接器,这个函数的符号定义并不在可执行文件中;如果已经在可执行文件中给这个符号申请了一个函数连接表项,而且符号表项的st_value字段的值不是0,那么st_value字段的值就将是函数连接表项中第一条指令的地址;否则,st_value字段的值就是0;这个函数连接表项的地址被动态链接器用来解析函数地址;

st_size:

符号的大小;各种符号的大小各不相同,比如一个对象的大小就是它实际占用的字节数;如果一个符号的大小为0,或者大小未知,则这个值为0;

0x06  小结

android 下打包进apk的so文件不存在.symtab和.strtab section ,dlsym()函数所做的就是解析so文件的.dynamic 通过符号表来获得函数的地址。而Windows下的应用层用GetProcAddress()获得动态链接库(dll)获得导出函数地址,内核层用MmGetSystemRoutinAddr()获的ntoskrnl.exe的导出表中的地址。Windows下的与elf不同,Windwos是通过PE文件的导出表获得函数地址。这里给出《0day》里面关于自己实现GetProcAddress()功能的汇编代码。

#include #include

using namespacestd;//GetProcAddress() 的汇编层实现//MessageBoxA GetHash--->0x1e380a6a//ExitProcess GetHash--->0x4fd18963//LoadLibraryA GetHash--->0x0c917432

DWORD GetHash(char *pFuncName)

{

DWORD digest= 0;while(*pFuncName)

{

digest= ((digest<<25)|digest>>7);

digest+= *pFuncName;

pFuncName++;

}returndigest;

}//在将hash压入栈中之前,注意先将增量标志位DF清零,//当shellcode 是利用异常处理机制植入的时候,//往往产生标志位的变化,使shellcode中的字符处理方向发生变化而发生错误

voidFunc()

{

_asm{//;find base addr of kernel32.dll

mov ebx , fs:[edx+0x30] //ebx = addr of PEB

mov ecx , [ebx+0x0c] //Ldr//typedef struct _PEB_LDR_DATA32//{//ULONG Length; +0x00//BOOLEAN Initialized; +0x04//HANDLE SsHandle; +0x08//LIST_ENTRY InLoadOrderModuleList; +0x0c//按模块的加载顺序//LIST_ENTRY InMemoryOrderModuleList; +0x14//按模块在内存中的地址顺序//LIST_ENTRY InInitializationOrderModuleList; +0x1c//按初始化顺序//PVOID EntryInProgress; +0x24//} PEB_LDR_DATA32, *PPEB_LDR_DATA32;

mov ecx , [ecx+0x1c] //ecx -->xxx.exe

mov ebp , [ecx+0x08] //ebp = addr of kernel32

}

}intmain()

{

_asm{

CLD

push0x1e380a6a //MessageBoxA

push 0x4fd18963 //ExitProcess

push 0x0c917432 //LoadLibraryA

mov esi ,esp

lea edi ,[esi-0x0c]//抬高栈顶

xor ebx ,ebx

sub esp ,0x04

//?????//push a pointer to "user32" onto stack

mov bx , 0x3233 //reset of ebx is null

push ebx

push0x72657375push esp

xor edx , edx//find base addr of kernel32.dll

mov ebx , fs:[edx+0x30] //ebx = address of PEB

mov ecx , [ebx+0x0c] //ecx = pointer to loader data

mov ecx , [ecx+0x1c] //ecx = first entry in initialization order list

mov ecx , [ecx] //ecx = second entry in list (kernel32.dll)

mov ebp , [ecx+0x08] //ebp = base address of kernel32.dll

find_lib_function:

lodsd//load next hash into al and increment esi

cmp eax , 0x1e380a6a //hash of MessageBoxA - trigger//LoadLibrary("user32")

jne find_functions

schg eax , ebp//save current hash

call [edi-0x08] //LoadLibraryA

xchg eax , ebp //restore current hash, and update ebp//with base address of user32.dll

find_function:

pushed//preserve registers

mov eax , [ebp+0x3c] //eax = start of PE header

mov ecx , [ebp+eax+0x78] //ecx = relative offset of export table

add ecx , ebp //ecx = absolute addr of export table

mov ebx , [ecx+0x20] //ebx = relative offset of name table

add ebx , ebp //ebx = absolute addr of name table

xor edi , edi //edi will count throght the function

next_function_loop:

inc edi//increment function counter

mov esi , [ebx+edi*4]

add esi , ebp

cdq

hash_loop:

movsx eax ,byteptr[esi]

cmp al , ah

jz compare_hsah

ror edx ,7add edx , eax

inc esi

jmp hash_loop

compare_hash:

cmp edx , [esp+0x1c]

}return 0;

}

asm GetProcAddr()

dlsym()是通过解析符号来获得函数地址的,可以考虑一下,在写native代码时,函数声明加 extern"C" 和不加extern "C"通过dlsym()获得地址有什么不同?dlsym()什么时候会失败?

Windows下开发dll程序时,加extern"C" 和__delspec(dllexport) 有什么用?如果不加为什么GetProcAddress()会崩溃?

dlsym 如何查看一个so里面的_Android so 文件进阶二 从dlsym()源码看android 动态链接过程...相关推荐

  1. 如何让一个div里面的div垂直居中?

    如何让一个div里面的div垂直居中? 如何让上面灰色有文字那个div和背景图标垂直居中,不管屏幕大小有好大,始终在垂直方向上的中间.上面有整个布局和样式表,谢谢高手指点 CSS3时代当然要用CSS3 ...

  2. 写一个PE的壳_Part 5:PE格式修复+lief源码修改

    系列汇总 写一个PE的壳_Part 1:加载PE文件到内存 写一个PE的壳_Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc) 写一个PE的壳_Part 3:Section里实 ...

  3. 小白前端之路:手写一个简单的vue-router这几年,好像过的好快,怀念我的大学生活。 - 连某人 大三实习生,之前写过简单MVVM框架、简单的vuex、但是看了vue-router的源码(看了

    这几年,好像过的好快,怀念我的大学生活. 连某人 大三实习生,之前写过简单MVVM框架.简单的vuex.但是看了vue-router的源码(看了大概)之后就没有写,趁着周末不用工作(大三趁着不开学出来 ...

  4. mysql怎样修改my ini_修改mysql里面的my.ini文件后,如果让这个改动生效?

    比如我们修改了my.ini文件的secure_file_priv变量的值如何让其真正生效? 现在先看下我们系统中的secure_file_priv变量的值 进入mysql输入代码如下 show glo ...

  5. RCAR会议:我的RTFA算法里面的generate_detections.py文件

    地址: nk_DeepSortYolo/deep_sort_yolov3-master/tools/generate_detections.py # vim: expandtab:ts=4:sw=4 ...

  6. 一个web图片热点生成工具(winform开发) 附源码

    给图片加热点是web开发中经常用到的一个功能.这方面的工具也不少. 为了更好的满足自己的需求,写了一个winform程序. 可以方便的给图片加热点,更方便灵活! 源码下载 http://downloa ...

  7. 从源码解析-Android中Zygote进程是如何fork一个APP进程的

    zygote进程fork子进程 前言 强烈推荐 进程创建流程 APP启动第三方应用 startActivity startService sendBroadcast ContentResolver.q ...

  8. 用flask快速打造一个技术导航网站,并自动采集导航!附源码

    洞悉安全,风洞网,认准官方网站! 版权声明:本文由风洞网原创文章,未经博主允许不得转载! 首发声明 本文首发于风洞网,跳到原文地址. 1. 静态部署 到 https://github.com/WebS ...

  9. 手把手教你使用FineUI开发一个b/s结构的取送货管理信息系统(附源码+视频教程(第9节))...

    一 本系列随笔概览及产生的背景 近阶段接到一些b/s类型的软件项目,但是团队成员之前大部分没有这方面的开发经验,于是自己选择了一套目前网上比较容易上手的开发框架(FineUI),计划录制一套视频讲座, ...

最新文章

  1. 2012 MUTC 7 总结
  2. php设计模式中的类型安全 指--只接受特定的对象 ---以避免发生错误
  3. C++中const用于函数重载
  4. 内核对象用于线程同步
  5. 【LightOJ - 1030】Discovering Gold(概率dp,数学期望,期望的线性性)
  6. python路径及运行方式相关说明
  7. Apache Flink 进阶(六):Flink 作业执行深度解析
  8. 云南满泽生物科技有限公司 满泽玛卡玛咖精片 东革阿里 奶昔
  9. 外螺纹对照表_螺纹对照表
  10. RxBus 使用及原理分析
  11. 上传文件框在firefox浏览器中显示路径不全的解决方法
  12. 计算机在哪里设置定时休眠,win7系统设置定时开关机休眠唤醒的详细办法
  13. java调试步骤_Java程序的开发过程及基本调试方法
  14. 【C语言】(用函数实现)任意给定两个正整数a和n,计算a+aa+aaa+aa...(n个a)的和。(例如输入1,3即为计算1+11+111的值)。
  15. Tcpdf操作html转pdf带页眉和页脚
  16. Cors跨域(一):深入理解跨域请求概念及其根因
  17. 微信公众平台微信支付打通流程
  18. 习题9-2(免费糖果)【深搜dfs】+【记忆化搜索】
  19. excel计算机不准确,Excel排序不准确的解决方法
  20. 论文学习笔记: Learning Multi-Scale Photo Exposure Correction(含pytorch代码复现)

热门文章

  1. t检验.医学统计实例详解-医学统计助手★卡方检验,t检验,F检验,秩和检验,方差分析
  2. SSL基础:4:使用openssl生成ssh的非对称密钥对
  3. cannot do a partial commit during a merge.
  4. mysql远程服务器返回错误404_网站服务器经常性出现404错误了怎么办?
  5. Coreldraw中选择工具技巧十五招
  6. 同一数据库在两台机上不同路径的备份还原
  7. 蒙特卡罗方法下乘同余法生成随机数与蒲丰投针问题的实现
  8. Spring MVC 使用支付宝接口完成在线支付
  9. 机器学习路程——k近邻(KNN)算法(python实现)
  10. android qq账号登陆验证手机号码,qq绑定的手机号换了,登陆需要手机验证,怎么办?...