前言
首先抛出一个问题,假设程序A需要动态库中的函数funb
,那你你怎么知道这个函数在哪个动态库中?以及我们加载器怎么知道我们是否启用延迟加载还是立即加载?
为了解决以上问题ELF
推出了一个节名为.Dynamic
,里面包含很多信息比如你需要的动态库,以及是否立即加载以及符号表等。在got表的第一项指像.Dynamic
,并且延迟绑定那个的时候需要传入.Dynamic
对象地址,因为里面包含解析函数地址的上下文。
实战
我们看看下main的例子
//main.c
#include <stdio.h>
static int mystaticVar = 3 ;
int myglobalvar=5;
int myglobalvar2=6;
extern void testfun();
int main(){
testfun();
printf("hello world %d \r\n",mystaticVar);
return 0;
}
void hell(){
testfun();
}
//test.c
__attribute__((visibility("default"))) void testfun(){
}
__attribute__((visibility("hidden"))) void testfun2(){
}
int libGLobal=2;
编译命令
gcc -fPIC -shared -o test.so test.c
gcc -o main.out main.c test.so
首先我们查看 main.out
相关节
[图1-1]
你会看到一个.dynamic
节,这个节是由多个Elf64_Dyn
组成的
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
Elf32_Off d_off;
} d_un;
} Elf32_Dyn;//32位程序
typedef struct {
Elf64_Xword d_tag;
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
其中d_tag
决定这个是什么类别信息,以及该如何解析d_un
内部变量。
其中有一个约定如果d_tag
是偶数那么你使用d_ptr
。奇数不包证一定使用d_val
,但大多数情况下是。
d_ptr
: 表示一个虚拟地址
d_val
:需要根据d_tag
才能决定表示的意思
d_tag
有很多类别具体您可参阅Dynamic Section
我们这里举例其中几个常用的d_tag
枚举 | 数值 | 使用d_un字段 |
---|---|---|
DT_NULL | 0 | 不使用 |
DT_NEEDED | 1 | d_val |
DT_STRTAB | 5 | d_ptr |
DT_FLAGS | 30 | d_val |
… | … | … |
DT_NULL:无意义
DT_NEEDED:所需要的动态库,d_val指向字符表的下标(字符表由DT_STRTAB确定)
DT_STRTAB:字符串表,d_ptr是字符串表地址
DT_FLAGS:一些标志位比如是否立即绑定so符号
你可以通过readelf查看这个结构数据解析结果
[图1-2]
我们手动来解析其中一个DT_NEEDED
(test.so)
首先我们看到DT_STRTAB
指向0x488,也就是我们字符串表所在的文件偏移。通过 [图1-1] 得知是.dynstr
这个节.
我们首先显示16进制下 .dynamic 节区数据,下图高亮就是test.so所对应的Elf64_Dyn
[图1-3]
将上面二进制放入数据结构解析
d_tag
:01000000 00000000
d_un
:01000000 00000000
d_tag为1所以是DT_NEEDED
类型,因此d_un
中有意义的字段是d_val,指向字符表的下标。我们上文说过字符串是.dynstr
这个节.
而下标因为为1
所以指向的字符串是746573 742e736f 00
,对应assic就是test.so
.
符号导出控制
默认情况GCC 会将所有函数导出.dynamic(可使用__attribute__ ((visibility (“hidden”)))隐藏),如果制定了visibility编译参数除外。
据个例子
使用如下参数编译
gcc -fvisibility=hidden
那么所有这个程序里面的函数或则变量都不会在.dynamic中找到,当然你可以 __attribute__ ((visibility ("default")))
显示说明要公开。
我们在Android环境编译下说明
//手动声明隐藏
extern "C" __attribute__ ((visibility ("hidden"))) void myPrivateC(){
printf("hello myPrivateC");
}
//由于默认所有函数导出所以这个函数可以被在mypublic找到
extern "C" void myPublic(){
printf("hello myPublic");
}
//JNIEXPORT 等于__attribute__ ((visibility ("default")))
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativec_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
myPrivateC();
myPublic();
return env->NewStringUTF(hello.c_str());
}
在ghidra很明显看不到myprivate函数.
readelf查找类似结果。
我们看看调用这个函数的地方有什么特殊。
上图的FUN_0011ddb0就是myprivate函数调用,他已经ghidra随即生成一个符号代替(注意这里这个FUN_0011ddb0没有存储在任何符号表中,只是工具提升可读性的)
如果我们用objdump看实际就是一个固定偏移
C++ -fvisibility=hidden -fvisibility-inlines-hidden