通过GDB学透PLT与GOT
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过GDB学透PLT与GOT相关的知识,希望对你有一定的参考价值。
前言
本文需要基础的ELF格式文件知识。
我们知道printf
代码的实现位于libc.so
中,而我们知道我们的so是在运行通过加载器进行加载的.
如下代码所示:
//编译指令 gcc -o main2.out main2.c -zlazy
//zlazy是启用延迟绑定。部分发行版本连接器直接程序加载的时候会进行绑定so函数
#include <stdio.h>
void main()
printf("hello %d",23);
ldd命令可以查看程序所需的动态库
在so
编译的时候printf
是不知道函数地址的,因为你不知道so
中被加载到内存哪个地址。
如下图所示:
图[1]
我们看到so被加载不同的地址对于调用函数指令也有所不同。
解决方案1
我们直接在程序被加载时重写改写所有so的函数调用地址。
比如如下汇编在编译时的指令
call xxx1
cal xxx2
call xxx3
加载后修改所有地址
call yyy1
cal yyy2
call yyy3
但是弊端异常明显,假设一个程序需要重定位100万个地址的话,那么程序启动会异常的慢,而且你程序不可能所有指令都会被调用(也许用户刚打开就关闭,你的代码基本没怎么执行却浪费大量的时间在加载时)。
解决方案2
延迟绑定重定位的地址,也就是我们在调用某条指令的时候再去修改他的调用地址。为了实现延迟绑定ELF推出了两个节 .GOT
,PLT
.
首先我們要明白其中的一些格式
.got
其实本质是一张表,每一项都是一个地址,也许是变量的地址或者函数的。某个需要重定位的函数被调用后.got
表里的与之关联的地址会被改成真正函数地址(未被调用之前got的地址指向plt某个函数)。
.plt
存储的是桩代码,负责解析与调用真正的重定位地址函数。
我们用一个例子举例说明:
我们原始调用某个函数的汇编指令:
call xxxx #调用<printf>实际所在地址
启用延迟绑定后这个指令会改为调用plt某处代码
call 1050 #改为调用plt的某处代码
跳转处plt代码如下所示
jmp QWORD PTR [rip+0x2fbd] # rip+0x2fbd这个地址会指向.got某个存储地址
以下为.got表存储的地址信息为1034
;.got或者.got.plt表存储的地址
1034
而1034
这个地址指的又是.plt
某个函数地址
1034 push 0x0
1039 jmp 1020 <.plt>
push 用于传递参数 标识是哪个函数
jmp 跳转另一段plt代码
1020 push QWORD PTR [rip+0x2fe2] # rip+0x2fe2计算为 4008 指向`.got.plt`表中的第二个地址
1026 jmp QWORD PTR [rip+0x2fe3] # [rip+0x2fe3 4010 指向`.got.plt`表中的第三个地址
其中 QWORD PTR [rip+0x2fe2] 传入的是link_map结构,内包含动态必要的信息,可以让我们查找函数
/* Structure describing a loaded shared object. The `l_next' and `l_prev'
members form a chain of all the shared objects loaded at startup.
These data structures exist in space used by the run-time dynamic linker;
modifying them may have disastrous results. */
struct link_map
/* These first few members are part of the protocol with the debugger.
This is the same format used in SVR4. */
ElfW(Addr) l_addr; /* Difference between the address in the ELF
file and the addresses in memory. */
char *l_name; /* Absolute file name object was found in. */
ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */
struct link_map *l_next, *l_prev; /* Chain of loaded objects. */
;
QWORD PTR [rip+0x2fe3]
其实是一个名叫_dl_runtime_resolve
函数,他的作用有两个
- 根据传入的函数id和link_map修改got表地址为函数地址
- 跳转查找的函数地址
为方便理解我们使用gdb调试进行演示:
如下代码:
//编译指令 gcc -o main2.out main2.c -zlazy
//zlazy是启用延迟绑定。部分发行版本连接器直接程序加载的时候会进行绑定so函数
#include <stdio.h>
void main()
printf("hello %d",23);
首先gbd main2.out
进入gdb调试界面
layout asm
打开汇编面板
断点 函数入口b main
执行 r
命令运行程序,然后单步到call 0x401040 <printf@plt>
输入si
步入这条指令
QWORD PTR [rip+0x2fcd]
是got表中某一项的地址,也就是printf的地址,但是由于他没有重定向此时会指向另一个ptl函数地址
以下为此地址存储的数据:
对其反编译这个地址:
这个地址的代码其实是ptl.sec
中的代码,这段代码最后又跳转0x401020
接着我们步入这个_dl_runtime_resolve
函数
你最后会发现最步入到print函数的实现
我们最后在查看下我们前面的got表地址是否变化了
参考
1. GDB shows incorrect jump address at PLT section
got/plt之_dl_runtime_resolve
_dl_runtime_resolve
学习ret2dlresolve
以上是关于通过GDB学透PLT与GOT的主要内容,如果未能解决你的问题,请参考以下文章
C温故补缺(十七):动态链接(ELF,PIC,GOT,PLT)
聊聊Linux动态链接中的PLT和GOT(1)——何谓PLT与GOT