GCC编译

文章目录

  • 共享库运行时加载
    • Cmake中指定RPATH
  • PIC
    • 编译选项
  • PIE

共享库运行时加载

动态链接有两步:

  1. 链接阶段(Link time):由链接编辑器将动态库的信息编辑到ELF文件系统的对应段
  2. 运行阶段(Run time):由动态链接器(ld.so)在可执行程序装载时根据链接信息重定位找到共享库的函数

运行时搜索库的路径优先级:

  • 该可执行程序的RPATH
  • LD_LIBRARY_PATH
  • 正被加载对象的RUNPATH

默认编译链接共享库时会将链接共享库的路径写入elf文件系统的dynamic section的RUNPATH字段

# -d表示查看elf文件的dynamic section
readelf -d elf文件

可以看到RUNPATH字段包含了共享库的路径信息。

对库加载过程debug详解

LD_DEBUG=libs exe文件

Cmake中指定RPATH

cmake中与rpath相关的几个变量:

  • CMAKE_BUILD_RPATH、CMAKE_INSTALL_RPATH: 用于指定构建和安装时的rpath。
  • CMAKE_SKIP_RPATH、CMAKE_SKIP_BUILD_RPATH、CMAKE_SKIP_INSTALL_RPATH:用于让cmake是否跳过添加rpath。
  • CMAKE_BUILD_RPATH_USE_ORIGIN:设置rpath为相对可执行文件路径。
  • CMAKE_BUILD_WITH_INSTALL_RPATH:构建时是否使用安装的RPATH
  • CMAKE_INSTALL_RPATH_USE_LINK_PATH:安装时候是否加上rpath,默认不开启。
  • CMAKE_INSTALL_REMOVE_ENVIRONMENT_RPATH:

INSTALL选项只对通过target安装的方式有用install(TARGETS ...)

在Cmake中可以通过设置CMAKE_SKIP_BUILD_RPATH布尔变量来控制构建时是否跳过为目标文件自动生成RPATH信息。通常,CMake在目标文件等时使用BUILD_RPATH 目标属性,当安装时,CMake会重新链接目标文件等,使用INSTALL_RPATH。如果你设置了CMAKE_SKIP_INSTALL_RPATH这个变量为真,那么安装时目标文件将不包含RPATH信息。

LD_RUN_PATH:指定链接阶段搜索库的路径
LD_LIBRARY_PATH:指定运行阶段搜索库的路径

详细解释

gcc编译选项总览

linux下有5中程序保护机制

  • CANARY 栈保护
    gcc -fno-stack-protector -o test test.c  //禁用栈保护
    gcc -fstack-protector -o test test.c   //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
    gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码
    
  • NX(no-execute)
    gcc -z execstack -o test test.c		// 禁用NX保护
    gcc -z noexecstack -o test test.c	// 开启NX保护
    
  • PIE(ASLR)
    position-independent executables位置独立可执行区域;
    address space layout randomization内存地址随机化机制
    sudo echo 0 > /proc/sys/kernel/randomize_va_space
    # 0 - 表示关闭进程地址空间随机化。
    # 1 - 表示将mmap的基址,stack和vdso页面随机化。
    # 2 - 表示在1的基础上增加栈(heap)的随机化。
    # 可以防范基于Ret2libc方式的针对DEP的攻击。ASLR和DEP配合使用,能有效阻止攻击者在堆栈上运行恶意代码。
    gcc -pie -o test test.c			// 默认情况下,开启PIE保护
    gcc -no-pie -o test test.c		// 禁用PIE保护
    
  • RELRO(read only relocation)
  • FOURTYFY_SOURCE

PIC

装载时重定位是解决动态共享库中绝对地址引用的方法之一,但这种方法有一个很大的缺陷就是指令部分无法实现多进程之间共享。如果能把指令中那些ELF加载时需修改的部分分离出来,跟数据部分放在一起,这样指令部分可保持不变,而数据部分每个进程拥有一个备份,可解决指令无法多进程之间共享的问题。这种实现方案就是通常所说的地址无关代码(PIC)技术。

PIC实现的基本原理:ELF文件添加一个全局数据和函数的间接索引表格,当全局数据引用或函数调用时,通过此间接索引表实现数据和函数的引用。通过这种方式可实现代码段的真正位置无关,使共享库可很容易映射到进程虚拟空间,而不需要做任何修改。

PIC实现依赖于GOT(Global Offset Table)和PLT(Procedure Linkage Table)。全局变量调用通过GOT表间接引用。函数的调用通过PLT和GOT结合实现间接调用。
GOT(Global Offset Table)是一个变量地址的引用表格,位于数据段中。GOT提供变量的间接引用,当指令使用变量时,不是直接引用变量地址,而是引用一个GOT表索引,而GOT表中存放着变量的真实地址。
GCC编译_第1张图片
PLT(The Procedure Linkage Table)和GOT结合实现函数的间接地址无关调用,ELF通过PLT和GOT可实现函数的“延迟绑定”,延迟绑定就是函数绑定延迟到函数真正使用时才进行绑定(即确定函数地址)。

当函数首次调用时,编译器转化函数func为func@PLTfunc@PLT代表func在PLT表中的索引值n。PLT表由一系列PLT表项组成,除第0个表项外每个表项都包含:

jmp *GOT[n]
push n
jmp PLT[0]

第0个表项只包含一个_dl_runtime_resolve()函数调用,实现func函数真实地址回填GOT[n]表项,在函数首次调用前,GOT[n]存放的数据是push n指令的指针。
GCC编译_第2张图片

函数第一次调用:

  1. 执行函数func调用,跳转到PLT[n]
  2. PLT[n]被调用,跳转到*GOT[n]地址,而*GOT[n]的值却是push n指令指针。
  3. 跳转PLT[0]执行函数地址回填处理函数,最后完成函数真实调用。

当函数再次调用时,首先跳转到PLT[n],然后执行jmp *GOT[n]指令,跳转到func函数并实现func调用。
GCC编译_第3张图片
其实动态链接共享库即使使用PIC方式编译,但这并不能改变其动态链接的本质。对于动态链接来说,如果一个共享库未以PIC方式编译,那肯定需要装载时重定位,对于使用PIC的共享库,虽然其代码段不需要重定位,但是数据段依然包含了绝对地址的引用。因此还是需要重定位的,但两者的重定位实现是有区别的:如果一个ELF文件以PIC方式编译,并调用了一个外部函数foo,则foo会出现在.rel.plt中,函数调用通过PLT表实现。而如果不是以PIC方式编译,foo将会出现在.rel.dyn中,函数调用不采用PLT表实现,且两者的重定位的计算方法也不同。

特征点 非PIC动态库 PIC动态库
dynamic段 有代码段重定位标记TEXTREL。 无代码段重定位标记TEXTREL。
重定位表 函数位于.rel.dyn段,重定位方式为R_386_PC32;变量位于.rel.dyn段,重定位方式为R_386_32 函数位于.rel.plt段,重定位方式为R_386_JUMP_SLOT;变量位于.rel.dyn段,重定位方式为R_386_GLOB_DAT
函数调用 不通过GOT表、PLT表调用 通过GOT表、PLT表调用

.dynamic段:动态链接ELF中最重要的结构应该是.dynamic段,这个段里面保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。.dynamic段里面保存的信息有点像ELF文件头,只是ELF文件头中保存的是静态链接时相关的内容,比如静态链接时用到的符号表、重定位表等,这里换成了动态链接下所使用的相应信息了。所以,.dynamic段可以看成是动态链接下ELF文件的“文件头”。PIC选项自然会影响.dynamic段的内容。
静态链接的重定位表中,.rel.text是代码段的重定位表,.rel.data是数据段的重定位表。
动态链接的重定位表中,.rel.dyn是对数据引用的修正,它所修定的位置位于.got以及数据段;而.rel.plt是对函数引用的修正,它所修定的位置位于.got.plt

编译选项

–fPIC(-fpic)
-fPIC-fpic均指示GCC产生地址无关代码,唯一的区别是:-fPIC产生代码稍大,-fpic产生代码相对较小。

PIE

PIE是操作系统和GCC结合提供的一个功能,使用PIE可创建介于共享库和普通可执行程序之间的程序(即地址分配时像共享库一样重新分配)。普通可执行程序,在执行时会加载到(0x08040000)固定地址,而PIE可使程序像共享库一样,加载到任何位置实现位置无关代码(PIC),并连接为一个ELF共享对象。

ASLR(Address space layout randomization)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。

ASLR影响进程空间的shared libraries、mmap基址、stack、heap以及vdso页面线性布局随机化,内核参数kernel.randomize_va_space可控制ASLR随机化行为,目前kernel.randomize_va_space取值有3种,分别是[0, 1 , 2]:

  • 0 - 表示关闭进程地址空间随机化。
  • 1 - 表示将Shared libraries,mmap基址,stack和vdso页面随机化。
  • 2 - 表示在1的基础上增加堆(heap)的随机化。

kernel.randomize_va_space值可通过如下方式设置和查看:

# 查看
cat /proc/sys/kernel/randomize_va_space                               # 设置
echo 2 >/proc/sys/kernel/randomize_va_space                         

如果您想永久性修改randomize_va_space值,可通过设置/etc/sysctl.conf系统配置:

kernel.randomize_va_space = value

然后运行sysctl –p命令实现。

随机化的段 Shared libray Stack Mmap基址 Heap
randomize_va_space = 0 未随机 未随机 未随机 未随机
randomize_va_space = 1 随机 随机 随机 未随机
randomize_va_space = 2 随机 随机 随机 随机

除ASLR外,欲PIE执行还需编译时添加-fPIE-pie选项,其中-fPIE是编译选项,-pie链接选项;-pie链接选项仅可应用于可执行程序的链接中,不可应用于动态库程序的链接中,将-pie链接选项应用于动态库程序的链接中,将导致动态链接符号表中导出符号的隐藏。添加-fPIE -pie的可执行文件将具备下述特征:

  1. 可执行文件DYN 共享库属性,而非EXEC可执行属性;
  2. 若ASLR开启,可执行文件加载时所有VMA随机加载,而非固定地址加载,若ASLR关闭,VMA段按固定地址加载。
  3. PIE和共享库相似,但模块内非static函数调用却有所差异。共享库经PLT表调用本共享库中的非static函数,而PIE则直接调用PIE内的非static函数,不经过PLT调用。

除以上3特征外,需要说明的是,如果ASLR开启,而编译时未添加加-fPIE和-pie选项,VMA段按ASLR随机规则加载。

编译时未添加-fPIE –pie选项,生成的可执行文件为EXEC普通可执行文件,ELF文件各段加载位置为绝对固定地址,添加-fPIE –pie选项生成的执行文件为DYN共享库文件,ELF文件各段段加载位置为相对偏移地址。仅添加-fPIE不添加-pie,可执行程序text段和data段无法ASLR随机化,生成的可执行文件为EXEC普通可执行文件。

你可能感兴趣的:(计算机,c++,c语言)