linux渗透测试从入门到精通(黑客基础知识-linux-plt篇)

本文来自我我的chinaunix:http://blog.chinaunix.net/uid-69947851-id-5825797.html

从《Linux应用程序elf描述》我们讲到了PLT,但并没有深入理解,因此本篇主要介绍PLT是怎么工作的。

那么什么是PLT?PLT是一种Linux实现的延迟加载技术,据我了解windows不带这个技能,windows是程序启动的时候进行符号重定向的。而符号重定向是指可执行程序在编译的时候,不能提前知道对应函数、全局变量符号的具体地址,这个地址通常位于动态库当中。因此,需要在加载动态库之后,才能知道它的具体地址。故而,编译器在编译的时候,通常只留一个占位符给对应地址,这需要在应用程序启动的时候,由动态解释器对这种没有具体地址的符号进行新地址填写和查找的过程 叫做符号重定位。既然有了符号重定向,PLT拿来又有什么用? 当我们在启动一个拥有几千上万动态库的应用程序的时候,如果所有函数符号都要在启动的时候去重定位它的实际地址,那么这需要多长时间呢?因此,Linux为了节约程序的启动耗时,采用了PLT技术,所谓PLT技术就是指所有的函数的地址重定位不是在启动的时候完成, 而是在具体函数调用的时候完成,这样启动时间就大大加快了。

我们以hello world程序为例,如下:

点击(此处)折叠或打开

linux渗透测试从入门到精通(黑客基础知识-linux-plt篇)(1)

执行:gcc -g hello.c -o hello 生成hello可执行文件(可以GDB调试的ELF文件格式).

咋们先看看这个hello程序的代码段部分和.plt段部分,如下:

linux渗透测试从入门到精通(黑客基础知识-linux-plt篇)(2)

如上图,我们注意带颜色部分,通过hello.c,我们可以看出,我们实际是在main函数当中调用了printf函数,然后main函数返回。但是通过汇编代码,我们看到在main函数当中,并没有printf函数,这是为什么呢?因为我们的printf函数只传入了字符串,这个写法默认会被编译器优化,优化成有puts函数代替printf来打印字符串,如果有其他可变参数传入,则会直接用printf,而不会被优化(有兴趣的可以自己试试)。

从上图,我们可以了解到字符串"Hello world"最终是由main函数调用puts函数来打印的。但是实际从汇编来看,main函数并没有直接去call puts函数,而是调用的puts@plt。请注意puts@plt并不是puts函数,这里不要搞混了,它只是一个跳板函数。我们知道puts函数的具体实现是在libc.so当中,因此这里引入的puts@plt函数,实际就是我们的PLT技术(程序刚开始运行的时候,由于启动并没有对puts函数的地址进行重定位,因此不能直接去call puts函数,而必须要经过跳板函数puts@plt去转换一下)。

如上图29行,main函数调用puts@plt函数,而puts@plt函数在第8行实现,它也是一个函数,只是的实体在.plt段当中,它的作用就是帮助我们的hello程序去定位真正的puts函数。下面我们来详细解说这个puts@plt的运行过程:

linux渗透测试从入门到精通(黑客基础知识-linux-plt篇)(3)

如上图,_GLOBAL_OFFSET_TABLE_实际上就是PLT GOT表的基地址即.got.plt段的地址0x601000,我们通过objdump -s hello 可以获取到,下面是.got.plt段的部分数据:

linux渗透测试从入门到精通(黑客基础知识-linux-plt篇)(4)

注意:RIP指针始终指向下一条指令

我们看到puts@plt的第一行汇编,jmpq *0x601018(0x601018 = %rip 0x200c12 = 0x4003f6 0x200c12),而0x601018这个地址是属于.got.plt段的,.got.plt段中红色部分就是0x601018的数据部分,即0x400406(注意大小端),这个地址就是puts@plt的第二行汇编,也就是main函数跳转到puts@plt函数运行,然后puts@plt间接跳转到0x400406也就是pushq $0x0(黄色标记部分),最后通过jmpq 0x4003f0跳转到0x4003f0(黄色标记部分)去运行,并执行0x601010上的数据。我们用gdb实际看看运行效果:

点击(此处)折叠或打开

linux渗透测试从入门到精通(黑客基础知识-linux-plt篇)(5)

从上图可以看到,当以后再次想调用puts函数的时候, 就不需要再次经过这些步骤了,因为地址0x601018不在记录它的下一行汇编代码,而是变成了真正的puts函数地址,一个偷梁换柱就这样完成了。

_dl_runtime_resolve函数这里不做详细说明, 有兴趣的可以去C库查找对应实现,这个函数的功能就是去重定位对应C库中的函数的真实虚拟地址的,最后做一些操作,覆盖对应.got.plt的数据,然后返回。整个执行流程大概如下:

linux渗透测试从入门到精通(黑客基础知识-linux-plt篇)(6)

注:以上调用,中间采用GOT表(一张线性数组)来决定。

linux渗透测试从入门到精通(黑客基础知识-linux-plt篇)(7)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页