延迟绑定技术及其实现


0x00 动态链接的优点

动态链接库有较为明显的优势,主要是能节省内存,加快程序初始载入速度,方便对其进行管理,虽说也有在载入模块时更费时的缺点,但是却也瑕不掩瑜。而动态链接技术又有两种分类:Load-Time Relocation 和PIC动态库,但是前者因为不能够实现共享,所以已经很少提及,反倒是后者成为主流技术。对动态链接的浅显理解,如有不对,烦请指正。

0x01 延迟绑定

延迟绑定属于PIC(位置无关)动态库的技术,是动态链接的一种机制,就是在程序需要该函数时在对其进行绑定调用,否则不需要去理会,这也就是动态链接能够较快载入的原因。而延迟绑定的大致流程就是:


func@plt: #这是一个基本的plt项
jmp *(func@got.plt)
push index
jmp _init

这是一个最基本的plt项,也是延迟绑定的简单实现,它的具体实现还有其他的步骤组成。第一条指令就是跳转到该函数对应的got.plt表中去,如果不是第一次调用该函数,该函数已经被绑定到在got.plt 表中,这样跳过去就可以直接访问函数的真实地址(这里想起来前几天庆大佬给我说过的,这个地址只是一个逻辑地址,并不是物理地址,之前一直理解错了),但是如果是第一次调用,跳转过去的地方存放着它的下一条指令的地址,再跳回来,那么这条指令就失去功能了,也就相当于是直接从上面跳到下面。这第二条指令是push一个参数,其实也就是该函数在GOT表中对应的偏移量,这个参数是给下面的对应的函数使用。下面是第三条指令,又是一个跳转,这是跳到plt0,也就是plt表的公共项,然后再由plt0跳转到这个函数去实现符号重定位以及GOT表表项的修改,这也就是延迟绑定的简单实现流程。

0x02 实例实现延迟绑定

上面大致的说了延迟绑定的实现过程,下面就用实例来实现一下延迟绑定的过程。

以下是用来实现的代码,其实很简单,只要写一段能够有调用到另一个函数的代码就好了,这是我在Bean_lee大佬的文章上盗来的代码(跟写代码很low的自己绝交两分钟):

test.c:


int myglob = 42;
int ml_util_func(int a)
{
return a + 1;
}
int ml_func(int a, int b)
{
int c = b + ml_util_func(a);
myglob += c;
return b + myglob;
}

olddriver:


static int header_handler(struct dl_phdr_info info, size_t size, void data)
{
int j;
printf(“name=%s (%d segments) address=%p\n”,
info->dlpi_name, info->dlpi_phnum, (void)info->dlpi_addr);
for ( j = 0; j < info->dlpi_phnum; j++)
{
printf(“\t\t header %2d: address=%10p\n”, j,
(void
) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr));
printf(“\t\t\t type=%u, flags=0x%X\n”,
info->dlpi_phdr[j].p_type, info->[j].p_flags);
}
printf(“\n”);
return 0;
}

extern int ml_func(int, int);
int main(int argc, const char* argv[])
{
dl_iterate_phdr(header_handler, NULL);
int t = ml_func(argc, argc);
sleep(12);
return t;
}


首先使用下面的第一条命令来将test.c编译成一个位置无关的动态共享库,然后再使用第二条指令将olddriver.c和前面生成的动态共享库进行链接,生成可执行文件olddriver用于下面的调试。

gcc -share -fpic -g test.so test.c
gcc -o olddriver -g olddriver.c ./test.so

现在我们使用gdb+peda对olddriver进行调试:

我们知道olddriver调用了共享库中的ml_func函数,那首先来看看main函数:

从上面的函数中可以看到,main()函数在0x804871f对共享库中的ml_func函数调用,地址是0x80484e0,那么我们看看这个地址具体是什么,反编译如下:

我们可以看到这儿也就是上面说的plt的简单实现,如果不是第一次调用这个函数的话,那么0x804a010地址上的就应该是该函数的真实地址,但是我们可以看到的是这里的地址是0x80484d0的下一条地址,说明这个函数还没有绑定。

然后我们随着ml_func@plt一项一项往下看,第二项是将0x10push进去,这个就是该函数在GOT表中的相对偏移量,再往下第三项就是跳转到plt0去实现对函数的绑定,那我们来看看plt0:

从上面可以看到plt0的第一项也是push进一个参数,这个就是这个函数的相对地址,也就是在GOT表中的地址(注意,不是相对偏移量,是GOT表项对应的地址,这个用作下面判断是那个项需要进行绑定),然后发现它又跳转,挑来跳去,绑定一下真心不容易。既然它跳转了,我们再去看看它跳到那干啥去了,查看发现那啥都没有,这个地方困惑了很久,只知道这个地方是进行绑定的,但是却一直没有找到对应的函数,后来看到一篇文章,说需要先运行起来才能进行工作。

于是我们先在调用这个函数前,也就是0x804871f处下一个断点,运行,然后在plt0的第一条指令处下断点,继续运行:



在运行后我们会发现0x804a008处存放着我们要找的绑定函数的地址0xb7ff24f0,然后我们单步运行进入这个函数,进过多次的调试,我发现这个函数就是_dl_runtime_reslove函数,然后我们再想想运行会发现真正去修改GOT表的fixup函数:

然后在调用完这些函数后,我们会发现got.plt表中该函数对应地址发生了变化,这也就是完成了相应的函数绑定。

我们再回头验证一下,从下面的这张图可以看到如果直接步入该函数,它直接就跳到函数对应的真实地址,发现他就是我们上面得到的0xb7f5556,get it,这样就完成了函数的延迟绑定。

0x03 最后的絮叨

我一开始做到plt0那之前没啥问题,但是做到那,然后在0x804a008找下一个调用地址的时候卡住了,困扰好久,后来看到一篇文章才解决。再后来我在进到_dl_runtime_slove函数的时候,试着去反编译出来,达到下面的效果:

但是并没有实现,于是一直怀疑自己哪错了,后来为了大佬才解决,原来是因为libc版本不一样对应的汇编指令不一样,顿悟,失声。。。。。。。。