ELF文件的“导出表”+延迟绑定

上一篇分析ELF格式的文件中忽然发现ELF文件的导入表、重定位没有涉及到;

还发现在ida在显示ELF的导入表时没有显示导入函数所在的库文件

ELF文件的“导出表”+延迟绑定_第1张图片

并没有显示_libc_start_main( )、puts( )函数所在的库文件,感觉ELF文件的重定位好神秘。

 

在linux系统中可以使用ldd [目标文件]命令显示目标文件依赖的共享库文件,用上次文章中的test为例:

ELF文件的“导出表”+延迟绑定_第2张图片

显示依赖3个文件:

linux-gate.so.1文件      可以看到加载地址属于linux内核空间,整个文件系统中也搜索不到这个文件(慢慢搞明白这个文件作用吧)

 

/lib/ld-linux.so.2文件     是一个软连接,指向动态连链接器,动态链接器在操作系统加载完可执行文件后,接过控制权从自身的入口地址(动态链接器也是一个共享库)开始一系列自身的初始化工作,并对可执行文件进行动态链接,完成之后把控制权交给可执行文件的入口地址,ELF程序正式执行。这个动态链接器其实也是就是.interp节区的那个字符串:

 

/lib/i386-linux-gnu/libc.so.6文件     也是一个软连接,连接到同目录下的libc-2.23.so文件,用ida打开可以看到里面导出了_libc_start_main( )、puts( )函数

 

导入函数的位置找到了,那么ELF文件中是如何定义导入表的呢?

ELF文件规定:

.got节区用来保存全局变量的地址值

.got.plt用来保存导入函数的地址值

.rel.dyn存放对全局变量的修正信息

.rel.plt存放对导入函数的修正信息

先看一下.rel.dyn、.rel.plt节区的内容:

ELF文件的“导出表”+延迟绑定_第3张图片

对于R_386_JUMP_SLOT类型,只需要把函数地址直接填入对应内存即可(其他的重定位类型有不同的地址计算方法)

可以看到在内存偏移0x0804a00c存放着puts( )函数地址、偏移0x0804a010存放着_libc_start_main( )函数地址

再看一下ida中显示的内存地址(前12个字节用于存放其他结构),这2个内存地址处确实是存放着对应函数的地址值:

ELF文件的“导出表”+延迟绑定_第4张图片

我们再看一下文件中.got.plt节区的内容:

有没有注意到内容不一样,为什么???

因为ELF文件引用的外部函数使用了延迟绑定,具体代码流程如下:

.text 0x0804832c:	call   0x80482f0 <__libc_start_main@plt>

.plt  0x080482f0:	jmp    DWORD PTR ds:0x804a010
.plt  0x080482f6:	push   0x8						  序号
.plt  0x080482fb: 	jmp    0x80482d0

gdb中无法反汇编的代码
.plt  0x80482d0:	push   DWORD PTR ds:0x804a004	  模块ID
.plt  0x80482d6:        jmp    DWORD PTR ds:0x804a008     _dl_runtime_resolve()函数地址

因为使用了延迟绑定第一次调用_libc_start_main( )函数时,会转到.plt  0x080482f0:    jmp    DWORD PTR ds:0x804a010指令处,由于是第一次调用0x0x804a010内存处应该是文件中的原始值0x080482F6(这样就一致了);

后面4条指令在ida中不显示(这4条指令出的值应该是动态链接器接过控制权时修改的),最后2条在gdb中无法反汇编,不过还好有大神解释:

调用 _dl_runtime_resolve( )函数,结合入栈的模块ID和序号中找到_libc_start_main( )函数地址,调用并把这个地址填入0x804a010内存处(覆盖掉源文件中的初始值0x080482F6,和IDA显示的值就对应了),这样第二次调用_libc_start_main( )函数时,直接就跳到函数处,不用再用_dl_runtime_resolve( )函数寻找了。

贴上gdb调试的代码:

ELF文件的“导出表”+延迟绑定_第5张图片

0x080482d0处内存反汇编失败,只能在od中修改二进制值查看代码了(唉!能用的办法都用上了):

ELF文件的“导出表”+延迟绑定_第6张图片

最后,如果要在linux下显式调用外部函数、变量,需要使用以下4个函数:

void * dlopen( const char * pathname, int mode);
void * dlsym(void * handle,const char * symbol); 
char * dlerror(void);		// 返回错误信息,成功返回NULL
int dlclose (void *handle);

 

 

 

 

你可能感兴趣的:(ELF文件学习)