GOT 和 PLT 表的相关知识及其在RE中的考察方式

什么是GOT表和PLT表

ELF文件中包括很多的节,比如.data 节、.text节等,GOT表和PLT表也是这其中的节,PLT表即是.plt节,GOT表即是.got节(这里需要注意的一点是,ELF将GOT拆分成两个表“.got”和”.got.plt”,前者用来保存全局变量引用的地址,后者用来保存函数引用的地址)。

got(全局偏移表)
got表是Linux平台用来解决对全局数据,外部函数引用的表,当在程序中引用外部的数据,函数时,通过got表来实现对相关数据符号的解析。

plt(过程链接表)
在动态链接过程中, 函数在加载共享库之后,会对got节中的函数地址进行填充,所以,调用的时候利用plt跳转到got表中项指定的地址即可。

(这两段内容摘自网络)

简而言之,ELF文件在编译时无法获取到动态链接库的函数地址,因此在运行时就需要这两个节来获取地址,这两个节的功能类似于exe文件格式中的输入表。

两个表的工作原理

当调用到动态链接库的函数时(比如printf、scanf),程序会首先跳转到plt表中的相应位置,接下来有两个分支:

  1. 当这个函数是第一次使用时,plt表会调用动态链接器将这个函数的真正地址填入到.got.plt表中。
  2. 当这个函数是第二次使用时,plt表会直接跳转到got表中这个函数的真正地址(因为在第一次使用时这个地址已经被填入到了.got.plt表)。

本文对这两个表的介绍比较简单,详细内容可以参考《程序员的自我修养》这本书或者下面这个链接:

https://blog.csdn.net/u011987514/article/details/67716639

在RE中的考察方式

主要就是通过GOT劫持,通过修改GOT表中的地址,使得对某一个动态链接库中的函数hook。

当程序调用到这个函数时,程序会找到这个函数在GOT表中的地址,然后跳转到这个地址。然而这个地址已经改变了,这样程序会跳转到修改之后函数的地址。

下面一段代码是GOT劫持的部分代码(IDA分析之后的结果)

int (**sub_795())(const char *s1, const char *s2)
{
int (**result)(const char *, const char *); // rax

result = &strcmp;
qword_201090 = &strcmp;
off_201028 = sub_6EA;
return result;
}

其中的off_201028即为strcmp函数的地址。
下面是这个程序的.got.plt节

got.plt:0000000000201000
.got.plt:0000000000201000 ; Segment type: Pure data
.got.plt:0000000000201000 ; Segment permissions: Read/Write
.got.plt:0000000000201000 _got_plt segment qword public 'DATA' use64
.got.plt:0000000000201000 assume cs:_got_plt
.got.plt:0000000000201000 ;org 201000h
.got.plt:0000000000201000 dq offset stru_200DF8
.got.plt:0000000000201008 qword_201008 dq 0 ; DATA XREF: sub_590↑r
.got.plt:0000000000201010 qword_201010 dq 0 ; DATA XREF: sub_590+6↑r
.got.plt:0000000000201018 off_201018 dq offset puts ; DATA XREF: _puts↑r
.got.plt:0000000000201020 off_201020 dq offset printf ; DATA XREF: _printf↑r
.got.plt:0000000000201028 off_201028 dq offset strcmp ; DATA XREF: _strcmp↑r
.got.plt:0000000000201028 _got_plt ends
.got.plt:0000000000201028

显然,strcmp会被上面那一段代码hook成sun_6EA。

Linux x86系统下main函数执行前的过程

main

本部分只介绍在RE中的应用,想详细了解此部分知识请参考下面这个链接

https://blog.csdn.net/weixin_46222091/article/details/111636282

可以看到main函数不是最开始执行的函数,这意味着在main函数之前可能存在某些自定义函数对程序进行某些操作,事实上正式如此。

我们应该重点关注这几个函数,因为这些函数是可以添加自定义函数的。

  1. _do_global_ctors_aux
  2. __libc_csu_init
    (本部分介绍的是最常见的,可能仍然有其他函数是可以被自定义的,如fini)
    其中在第一个函数中添加自定义函数是这样的:
    void __attribute__ ((constructor)) a_constructor() {
    printf("%s\n", __FUNCTION__);
    }
    在第二个函数中添加自定义函数是这样的:
    void init(int argc, char **argv, char **envp) {
    printf("%s\n", __FUNCTION__);
    }
    __attribute__((section(".init_array"))) typeof(init) *__init = init;