dlsym 如何查看一个so里面的_Android so 文件进阶二 从dlsym()源码看android 动态链接过程...
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 动态链接过程...相关推荐
- 如何让一个div里面的div垂直居中?
如何让一个div里面的div垂直居中? 如何让上面灰色有文字那个div和背景图标垂直居中,不管屏幕大小有好大,始终在垂直方向上的中间.上面有整个布局和样式表,谢谢高手指点 CSS3时代当然要用CSS3 ...
- 写一个PE的壳_Part 5:PE格式修复+lief源码修改
系列汇总 写一个PE的壳_Part 1:加载PE文件到内存 写一个PE的壳_Part 2:ASLR+修复输入表(IAT)+重定位表支持(.reloc) 写一个PE的壳_Part 3:Section里实 ...
- 小白前端之路:手写一个简单的vue-router这几年,好像过的好快,怀念我的大学生活。 - 连某人 大三实习生,之前写过简单MVVM框架、简单的vuex、但是看了vue-router的源码(看了
这几年,好像过的好快,怀念我的大学生活. 连某人 大三实习生,之前写过简单MVVM框架.简单的vuex.但是看了vue-router的源码(看了大概)之后就没有写,趁着周末不用工作(大三趁着不开学出来 ...
- mysql怎样修改my ini_修改mysql里面的my.ini文件后,如果让这个改动生效?
比如我们修改了my.ini文件的secure_file_priv变量的值如何让其真正生效? 现在先看下我们系统中的secure_file_priv变量的值 进入mysql输入代码如下 show glo ...
- RCAR会议:我的RTFA算法里面的generate_detections.py文件
地址: nk_DeepSortYolo/deep_sort_yolov3-master/tools/generate_detections.py # vim: expandtab:ts=4:sw=4 ...
- 一个web图片热点生成工具(winform开发) 附源码
给图片加热点是web开发中经常用到的一个功能.这方面的工具也不少. 为了更好的满足自己的需求,写了一个winform程序. 可以方便的给图片加热点,更方便灵活! 源码下载 http://downloa ...
- 从源码解析-Android中Zygote进程是如何fork一个APP进程的
zygote进程fork子进程 前言 强烈推荐 进程创建流程 APP启动第三方应用 startActivity startService sendBroadcast ContentResolver.q ...
- 用flask快速打造一个技术导航网站,并自动采集导航!附源码
洞悉安全,风洞网,认准官方网站! 版权声明:本文由风洞网原创文章,未经博主允许不得转载! 首发声明 本文首发于风洞网,跳到原文地址. 1. 静态部署 到 https://github.com/WebS ...
- 手把手教你使用FineUI开发一个b/s结构的取送货管理信息系统(附源码+视频教程(第9节))...
一 本系列随笔概览及产生的背景 近阶段接到一些b/s类型的软件项目,但是团队成员之前大部分没有这方面的开发经验,于是自己选择了一套目前网上比较容易上手的开发框架(FineUI),计划录制一套视频讲座, ...
最新文章
- echarts怎么用在php,在Vue.JS中怎样使用echarts
- STM32 进阶教程 11 - RAM中运行程序
- 精密空调内部培训资料
- 转: ubuntu apt-get 与 aptitude 用法与区别
- Strut2的属性驱动,模型驱动的理解
- pycharm运行程序时看不到任何结果显示
- php 主进程子进程,PHP中的子进程的任何等价物?
- db2存储过程简单写法
- [转]vs2010 crystal report使用
- Ubuntu彻底卸载MySQL
- ActiveSync用蓝牙接口PC与WM6同步
- python package和目录_Python package和folder
- 862772-11-0,c(RGDfC),cyclo(RGDfC),cyclo(RGD-DPhe-C),cyclo (Arg-Gly-Asp-D-Phe-Cys)
- 关于 RestTemplate 中文乱码和List接收
- python画出一个小房子简单代码
- 总是封群怎么解决_我的群被封了怎么办
- 我的世界虚拟人生可以在服务器玩吗,我的世界虚拟人生全方位攻略 虚拟人生玩法介绍...
- EBS GL表结构学习(转)
- Android实战开发——引导页面(ViewPager)篇
- window系统批量自动安装软件-批处理