ELF格式解读 Dynamic节

前言

首先抛出一个问题,假设程序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_NULL0不使用
DT_NEEDED1d_val
DT_STRTAB5d_ptr
DT_FLAGS30d_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

参考

Dynamic Section

PWN基础15:GOT表 和 PLT表_prettyX的博客-程序员宅基地

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值