GOT
和PLT
,首先要知道关于PIC
的知识,Position Independent Code(PIC)
是为了是为了重定位动态链接库的symbols
,现代操作系统不允许修改代码段,只能修改数据段,而使用了动态链接库后函数地址只有在执行时才能确定,所以程序内调用的库中的函数地址在编译时不知道,所以,编译时将函数调用返回.data
段,而包含PIC
的程序在运行时需要更改.data
段中的GOT
和PLT
来重定位全局变量。Global Offset Table
,也就是GOT
表为每个全局变量保存了入口地址,在调用全局变量时,会直接调用对应GOT
表条目中保存的地址,而不调用绝对地址。Procedural Linkage Table
,也就是PLT
是过程链接表,为每个全局变量保存了一段代码,第一次调用一个函数会调用形如[email protected]
的函数,这就是跳到了函数对应的PLT
表开头执行,会解析出函数真正的地址填入GOT
表中,以后调用时会从GOT
表中取出函数真正的起始地址执行,下面给一张我自己做的调用流程图。.dynamic section
里包含了和重定位有关的很多信息,完整的.dynamic
段:GOT
表分成两部分.got
和.got.plt
,前一个保存全局变量引用位置,后一个保存函数引用位置,通常说的GOT
指后面一个,下文GOT即代表.got.plt
。GOT
表的起始地址:GOT
表的前三项有特殊含义:.dynamic
段的地址,第二个是link_map
的地址,第三个是_dl_runtime_resolve
函数的地址,第四项开始就是函数的GOT
表了,第一项就是printf
条目:PLTRELSZ
指定了.rel.plt
大小,RELENT
指定每一项大小,PLTREL
指定条目类型为REL
,JMPREL
对应.rel.plt
地址,保存了重定位表,保存的是结构体信息:REL
的数据结构为:r_offset
就是对应函数GOT
表地址,看看.rel.plt
第一项和第二项:printf
与free
对应,0x0804a000
处就是printf
的GOT
表地址。r_info=0x107
可以知道ELF32_R_TYPE(r_info)=7
,对应于R_386_JUMP_SLOT
;其symbol index
则为RLF32_R_SYM(r_info)=1
call 0x8048340 <[email protected]>
时会跳到PLT
段中,第一句会跳到GOT
条目指向的地址:GOT
表中的地址为PLT
表的第二句地址:reloc_offset
,这里是0,再push link_map
,也就是GOT
表的第二项,再调用_dl_runtime_resolve
函数。_dl_runtime_resolve
根据reloc_offset
找到.rel.plt
段中的结构体:r_info
为0x107
。ELF32_R_SYM(r_info)
找到.dynsym
中对应的结构体:.dynsym
有关的信息为:0x80481cc
,每个结构体大小为16bytes
,SYMTAB[1]
为0x80481cc+16
:sym->st_name
=0x29在.dynstr
中,也就是STRTAB
找到函数对应的字符串:GOT
表对应的位置,跳到函数起始地址执行,执行完后,printf
对应的GOT
表处已经填上了函数真正的地址:dl_resolve_data
与dl_resolve_call
,可以生成伪造符号信息与call时的reloc偏移dl_resolve_data
有两个参数,base_addr
它声明了生成的dl_resolve_data
数据的地址,因为我们接下来就要想办法将生成的dl_resolve_data
数据写入这个地址。call_name_str
是你想要调用函数名的字符串。dl_resolve_call
就是生成劫持地址plt[0],及reloc参数,和call参数一个plt_call_gadget
,它的第一个参数是dl_resolve_data
数据的地址,第二个参数是你想调用函数的参数的地址。reloc->r_info
,这会使得ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff
偏大,在有些情况下会导致version = &l->l_versions[ndx]
出现访存错误,下面介绍的pwndbg库解决了这个问题,它伪造的reloc->r_info,使得ndx为0,即vernum[reloc->r_info]为0。dl_resolve_call
函数的参数只有一个,没有调用函数的参数,因为调用函数的参数需要你用寄存器传递。raycp
师傅根据pwntools写的一个库(此处膜拜),它不仅提供了32位完美的dl_resolve
数据伪造,还提供了64位程序ret2dl_resolve
攻击的link_map
伪造。ret2dl_resolve()
.....这里先不管能用就行......dl=pdbg.ret2dl_resolve()
是初始化一个对象,可以去研究一下源码,功能是识别binary的架构。dl.build_normal_resolve( base_addr , call_name_str , resolve_target)
关键是这串代码,第一个参数是一个base地址,build_normal_resolve
方法根据这个地址微调,这是为了构造ndx=0,寻找正确的symbol_index
,最后返回一个被修正后的correct_addr
。它的返回值有三个,后两个都是构造好的数据。resolve_data
数据写入到被修正后的correct_addr
地址中,它返回的resovle_call
是一个plt_call_gadget
但他不同于roputils生成的,因为他没有把参数封装进去,我们需要自己设置call的参数。build_normal_resolve
方法的第三个参数resolve_target
是程序成功执行dl-resolve执行完我们想要call的函数后,这个地址会被写入 被执行函数的真实地址(虽然普通pwn下没什么用...)。dl_runtime_resolve
利用,因为64位程序bss段的位置离符号信息段较远,构造的数据一般都是在bss段,如0x601000-0x602000,导致其相对于.dynsym的地址0x400000-0x401000很大,使得reloc->r_info也很大,最后使得访问ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;时程序访存出错,导致程序崩溃,且不可避免。#define DL_FIXUP_MAKE_VALUE(map, addr) (addr)
__libc_start_main
的偏移,build_link_map
方法的参数意思也很明显了,fake_addr
是我们要基于这个位置生成fake_link_map
数据,reloc_index
是我们之后执行plt_call_gadget
对应的reloc
fake_link_map
数据写入fake_addr
地址处,还将要程序link_map_got表(got+4)中的link_map
地址写成fake_addr
地址,最后跳去plt表的push reloc
指令地址处执行,之前要构造好参数,最后进入dl_runtime_resolve
函数,根据link_map解析到调用函数,getshell.fake_link_map
功能函数抠出来,可单独使用,脚本如下: