plt表、got表与延迟绑定(Lazy Binding)

准备

实验环境

Ubuntu-Desktop 18.04

$ uname -a
Linux ubuntu 5.4.0-48-generic #52~18.04.1-Ubuntu SMP Thu Sep 10 12:50:22 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

so.2.27

源码

源码程序如下,我将其命名为plt.c

#include<stdio.h>
int main()
{
	puts("learning plt and got!\n");
	return 0;
}

编译

编译为32位的可执行程序gcc -Wall -m32 -z lazy plt.c -o plt86 && chmod u+x plt86

编译为64位的可执行程序gcc -Wall -z lazy plt.c -o plt64 && chmod u+x plt64

编译完成之后用checksec工具确认部分开启relro
在这里插入图片描述

注意:

在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处.

GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation。大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读.

RELOR 有两种形式:

  • Partial RELRO:一些段(包括 .dynamic)在初始化后将会被标记为只读,但是我们对got表具有写权限。
  • Full RELRO:除了 Partial RELRO,延迟绑定将被禁止,所有的导入符号将在开始时被解析,.got.plt 段会被完全初始化为目标函数的最终地址,并被标记为只读。另外 link_map 和 _dl_runtime_resolve 的地址也不会被装入。 这里踩坑了,搞了两天才发现

安全编译选项RELRO会影响延迟绑定,编译的时候需要关闭RELRO

gcc -o test test.c						// 默认情况下,可能开启RELRO,也可能不开启,我的Ubuntu18.04默认开启FULL RELRO,所以我踩坑了
gcc -z norelro -o test test.c			// 关闭,即No RELRO
gcc -z lazy -o test test.c				// 部分开启,即Partial RELRO
gcc -z now -o test test.c				// 全部开启,即Full RELRO

前置知识

节区

.plt节

plt(Procedure Linkage Table),.plt节包含了从动态链接器调用调用从共享库中导入函数所必需的代码。

该节中包含代码,节类型被标记为SHT_PROGBITS

.got.plt

也就是我们所说的got表,.got.plt节保留了全局偏移表。.got和.plt一起提供了对导入共享库函数的访问入口,由动态链接器在运行时进行修改。如果攻击者获取到了堆或.bss漏洞的一个指针大小的写原语,就可以对该节进行任意修改。

.got.plt跟程序执行有关,该节类型被标记位SHT_PROGBITS

.rel.*

重定位节,保存了重定位相关的信息,这些信息描述了在链接或运行时如何对ELF文件的

.strtab节

字符串表,其包括.symtab和.debug节区中的符号表以及节头部的节名称。表中的内容会被.symtab的ElfN_Sym(Elf32_Sym或Elf64_Sym)结构中的st_name条目引用。

该节标记为SHT_STRTAB

.symtab

.symtab节包含ELF文件的符号信息(ElfN_Sym),存放程序中定义和引用的函数和全局变量符号,但是不包含局部变量条目。每个可重定位目标文件在.symtab中都有一张符号表(除非人为用strip去掉符号表)

该节被标记为SHT_SYMTAB

/* Symbol table entry.  */

typedef struct
{
  Elf32_Word	st_name;		/* Symbol name (string tbl index) */
  Elf32_Addr	st_value;		/* Symbol value */
  Elf32_Word	st_size;		/* Symbol size */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char	st_other;		/* Symbol visibility */
  Elf32_Section	st_shndx;		/* Section index */
} Elf32_Sym;


typedef struct
{
  Elf64_Word	st_name;		/* Symbol name (string tbl index) */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char st_other;		/* Symbol visibility */
  Elf64_Section	st_shndx;		/* Section index */
  Elf64_Addr	st_value;		/* Symbol value */
  Elf64_Xword	st_size;		/* Symbol size */
} Elf64_Sym;

  • st_name: 保存了指向符号表中字符表(位于.strtab或.dynstr)的偏移地址,偏移地址保存着符号名称(如printf)
  • st_value:保存符号的值(地址或者位置偏移量)
  • st_size:存放了一个符号的大小,如全局函数指针的大小,32位程序中一个指针的大小为4字节
  • st_other:定义了符号的可见性
  • st_shndx:每个符号表条目的定义都与某些节对应,st_shndx变量保存了相关节头表的索引
  • st_info:指定符号类型以及绑定属性

.dynsym

.bss

存放未初始化的全局和静态变量,以及初始化为0的全局和静态变量。.bss在ELF文件在不占据实际大小,仅是一个占位符(占用空间不超过四字节),在ELF运行时,在内存中分配这些变量并初始化为0.

32位程序

调试

gdb> layout asm
gdb> b main
gdb> r
gdb> b *0x56555542

0x56555539 <main+28>                    lea    -0x1a20(%eax),%edx                                                                                                          
0x5655553f <main+34>                    push   %edx                                                                                                                        
0x56555540 <main+35>                    mov    %eax,%ebx                                                                                                                   
0x56555542 <main+37>                    call   0x565553b0 <puts@plt>  # jump to plt[0]                                                                                                     
0x56555547 <main+42>                    add    $0x10,%esp        

gdb> si   ;这里来到0x565553b0, 为什么是jump [ebx + 0xc]呢,因为got表的前三项被系统占用,一共是3*4=120xc)字节,puts函数为got表中的第一个条目,也就是got[3],其偏移就是0xc,其中ebx存放的是got表的首地址。

0x565553a0                              pushl  0x4(%ebx) <pushl got[1]>        # 0xf7ffd940  address of link_map                                                                                                         
0x565553a6                              jmp    *0x8(%ebx) <jmp got[2]>         # 0xf7feae40  address of dl_runtime_resolve                                                                                                         
0x565553ac                              add    %al,(%eax)                                                                                                                  
0x565553ae                              add    %al,(%eax)                                                                                                                  
0x565553b0 <puts@plt>                   jmp    *0xc(%ebx)                     # 0x56555b6                                                                                                 
0x565553b6 <puts@plt+6>                 push   $0x0      <push got[3]>        # rel_offset                                                                                                            
0x565553bb <puts@plt+11>                jmp    0x565553a0

0xf7feae40 <_dl_runtime_resolve>        push   %eax                                                                                                                        
0xf7feae41 <_dl_runtime_resolve+1>      push   %ecx                                                                                                                        
0xf7feae42 <_dl_runtime_resolve+2>      push   %edx                                                                                                                        
0xf7feae43 <_dl_runtime_resolve+3>      mov    0x10(%esp),%edx                                                                                                             
0xf7feae47 <_dl_runtime_resolve+7>      mov    0xc(%esp),%eax                                                                                                              
0xf7feae4b <_dl_runtime_resolve+11>     call   0xf7fe4f70 <_dl_fixup>   # get address of puts                                                                                                    
0xf7feae50 <_dl_runtime_resolve+16>     pop    %edx                                                                                                                        
0xf7feae51 <_dl_runtime_resolve+17>     mov    (%esp),%ecx                                                                                                                 
0xf7feae54 <_dl_runtime_resolve+20>     mov    %eax,(%esp)      # eax = 0xf7e43c10 address of <_IO_puts>                                                                                                          
0xf7feae57 <_dl_runtime_resolve+23>     mov    0x4(%esp),%eax                                                                                                              
0xf7feae5b <_dl_runtime_resolve+27>     ret    $0xc

-->开始执行0xf7e43c10 <_IO_puts>



(gdb) print puts
$1 = {int (const char *)} 0xf7e43c10 <_IO_puts>

_dl_fixup函数的定义:

/* This function is called through a special trampoline from the PLT the
   first time each PLT entry is called.  We must perform the relocation
   specified in the PLT of the given shared object, and return the resolved
   function address to the trampoline, which will restart the original call
   to that address.  Future calls will bounce directly from the PLT to the
   function.  */

DL_FIXUP_VALUE_TYPE
attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
	   ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
	   struct link_map *l, ElfW(Word) reloc_arg)
{
	const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]);
	const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
	
	const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
	const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
	const ElfW(Sym) *refsym = sym;
	void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
	lookup_t result;
	DL_FIXUP_VALUE_TYPE value;
	
	/* Sanity check that we're really looking at a PLT relocation.  */
	assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
	
	/* Look up the target symbol.  If the normal lookup rules are not
	   used don't look in the global scope.  */
	if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0)
	{
		const struct r_found_version *version = NULL;
	
	 	if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)
		{
			const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);
			ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff;
			version = &l->l_versions[ndx];
			if (version->hash == 0)
				version = NULL;
		}
	
	   /* We need to keep the scope around so do some locking.  This is
	not necessary for objects which cannot be unloaded or when
	we are not using any threads (yet).  */
		int flags = DL_LOOKUP_ADD_DEPENDENCY;
		if (!RTLD_SINGLE_THREAD_P)
		{
			THREAD_GSCOPE_SET_FLAG ();
			flags |= DL_LOOKUP_GSCOPE_LOCK;
		}
	
		#ifdef RTLD_ENABLE_FOREIGN_CALL
			RTLD_ENABLE_FOREIGN_CALL;
		#endif
	
	   result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
		    version, ELF_RTYPE_CLASS_PLT, flags, NULL);
	
	   /* We are done with the global scope.  */
		if (!RTLD_SINGLE_THREAD_P)
			THREAD_GSCOPE_RESET_FLAG ();
	
		#ifdef RTLD_FINALIZE_FOREIGN_CALL
			RTLD_FINALIZE_FOREIGN_CALL;
		#endif
	
		/* Currently result contains the base load address (or link map)
		of the object that defines sym.  Now add in the symbol
		offset.  */
	    value = DL_FIXUP_MAKE_VALUE (result,
		SYMBOL_ADDRESS (result, sym, false));
	}
	else
	{
		/* We already found the symbol.  The module (and therefore its load
		address) is also known.  */
		value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));
		result = l;
	}
	
	/* And now perhaps the relocation addend.  */
	value = elf_machine_plt_value (l, reloc, value);
	
	if (sym != NULL
		&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
	 	value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
	
	/* Finally, fix up the plt itself.  */
	if (__glibc_unlikely (GLRO(dl_bind_not)))
		return value;
	
	return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
}

从dl_fixup的源码可以看出,首先通过link_map获取到symtab(.symtab)和strtab(.strtab)以及reloc

struct link_map
{
	ElfW(Addr) l_addr;                  /* base address shared object is loaded at. */
	char *l_name;                       /*absolute file name object was found in.*/
	ElfW(Addr) *l_ld;                   /*dynamic section of the shared object.*/
	struct link_map *l_next, *l_prev;   /*chain of loaded object*/
	
`



```c
/* Symbol table entry.  */

typedef struct
{
  Elf32_Word	st_name;		/* Symbol name (string tbl index) */
  Elf32_Addr	st_value;		/* Symbol value */
  Elf32_Word	st_size;		/* Symbol size */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char	st_other;		/* Symbol visibility */
  Elf32_Section	st_shndx;		/* Section index */
} Elf32_Sym;

/* Symbol visibility specification encoded in the st_other field.  */
#define STV_DEFAULT	0		/* Default symbol visibility rules */
#define STV_INTERNAL	1		/* Processor specific hidden class */
#define STV_HIDDEN	2		/* Sym unavailable in other modules */
#define STV_PROTECTED	3		/* Not preemptible, not exported */


/* Relocation table entry without addend (in section of type SHT_REL).  */

typedef struct
{
  Elf32_Addr	r_offset;		/* Address */
  Elf32_Word	r_info;			/* Relocation type and symbol index */
} Elf32_Rel;
/* How to extract and insert information held in the r_info field.  */

#define ELF32_R_SYM(val)		((val) >> 8)
#define ELF32_R_TYPE(val)		((val) & 0xff)
#define ELF32_R_INFO(sym, type)		(((sym) << 8) + ((type) & 0xff))


_dl_runtime_resolve(link_map_obj, reloc_index)

在 i386 架构下,除了每个函数占用一个 GOT 表项外,GOT 表项还保留了3个公共表项,也即 got 的前3项,分别保存:

  • got [0]: 本 ELF 动态段 (.dynamic 段)的装载地址
  • got [1]:本 ELF 的 link_map 数据结构描述符地址
  • got [2]:_dl_runtime_resolve 函数的地址

动态链接器在加载完 ELF 之后,都会将这3地址写到 GOT 表的前3项,之后写入导入函数的地址。
在这里插入图片描述
运行dl_fixup之后,将获取到的地址写回.got.plt

总结

第一次调用
在这里插入图片描述
再次调用
在这里插入图片描述

64 位程序

/* Symbol table entry.  */

typedef struct
{
  Elf64_Word	st_name;		/* Symbol name (string tbl index) */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char st_other;		/* Symbol visibility */
  Elf64_Section	st_shndx;		/* Section index */
  Elf64_Addr	st_value;		/* Symbol value */
  Elf64_Xword	st_size;		/* Symbol size */
} Elf64_Sym;
/* I have seen two different definitions of the Elf64_Rel and
   Elf64_Rela structures, so we'll leave them out until Novell (or
   whoever) gets their act together.  */
/* The following, at least, is used on Sparc v9, MIPS, and Alpha.  */

typedef struct
{
  Elf64_Addr	r_offset;		/* Address */
  Elf64_Xword	r_info;			/* Relocation type and symbol index */
} Elf64_Rel;
/* How to extract and insert information held in the r_info field.  */

#define ELF64_R_SYM(i)			((i) >> 32)
#define ELF64_R_TYPE(i)			((i) & 0xffffffff)
#define ELF64_R_INFO(sym,type)		((((Elf64_Xword) (sym)) << 32) + (type))

参考

https://bbs.pediy.com/thread-257545.htm
https://delcoding.github.io/2018/12/dl_runtime_resolve/
https://www.freebuf.com/articles/system/233513.html
https://www.anquanke.com/post/id/183370
ROP之return to dl-resolve
很有价值的参考

https://www.freebuf.com/articles/system/233513.html

【star ctf】
https://zoepla.github.io/2018/04/starCTF-babystack/
https://jontsang.github.io/post/34550.html

【pwn carry】
https://www.jianshu.com/p/85d0f7ae822e

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值