为项目应用设置No-PIE

Steps for building your application as PIE

In Xcode, select your target in the "Targets" section, then click the "Build Settings" tab to view its settings.

For iOS apps, set iOS Deployment Target to iOS 4.3 or later. For Mac apps, set OS X Deployment Target to OS X 10.7 or later.

Verify that Generate Position-Dependent Code is set at to NO.

Verify that Don't Create Position Independent Executables is set to NO.
复制代码

PIE是什么

PIE(position-independent executable)是一种生成地址无关可执行程序的技术。若是编译器在生成可执行程序的过程当中使用了PIE,那么当可执行程序被加载到内存中时其加载地址存在不可预知性。html

PIE还有个孪生兄弟PIC(position-independent code)。其做用和PIE相同,都是使被编译后的程序可以随机的加载到某个内存地址。区别在于PIC是在生成动态连接库时使用(Linux中的so),PIE是在生成可执行文件时使用。安全

PIE的做用

安全性

PIE能够提升缓冲区溢出攻击的门槛。它属于ASLR(Address space layout randomization)的一部分。ASLR要求执行程序被加载到内存时,它其中的任意部分都是随机的。包括 Stack, Heap ,Libs and mmap, Executable, Linker, VDSO。经过PIE咱们可以实现Executable 内存随机化bash

节约内存使用空间

除了安全性,地址无关代码还有一个重要的做用是提升内存使用效率。app

一个共享库能够同时被多个进程装载,若是不是地址无关代码(代码段中存在绝对地址引用),每一个进程必须结合其自生的内存地址调用动态连接库。致使不得不将共享库总体拷贝到进程中。若是系统中有100个进程调用这个库,就会有100份该库的拷贝在内存中,这会照成极大的空间浪费。dom

相反若是被加载的共享库是地址无关代码,100个进程调用该库,则该库只须要在内存中加载一次。这是由于PIE将共享库中代码段需要变换的内容分离到数据段。使得代码段加载到内存时能作到地址无关。多个进程调用共享库时只须要在本身的进程中加载共享库的数据段,而代码段则能够共享。 函数

image

PIE工做原理简介

咱们先从实际的例子出发,观察PIE和NO-PIE在可执行程序表现形式上的区别。管中窥豹探索地址无关代码的实现原理。ui

例子一

定义以下C代码:spa

#include <stdio.h>

int global;

void main()
{
    printf("global address = %x\n", &global);
}
复制代码

程序中定义了一个全局变量global并打印其地址。咱们先用普通的方式编译程序。.net

gcc -o sample1 sample1.ccode

运行程序能够观察到global加载到内存的地址每次都同样。

$./sample1
global address = 6008a8
$./sample1
global address = 6008a8
$./sample1
global address = 6008a8
复制代码

接着用PIE方式编译 sample1.c

gcc -o sample1_pie sample1.c -fpie -pie

运行程序观察global的输出结果:

./sample1_pie
global address = 1ce72b38
./sample1_pie
global address = 4c0b38
./sample1_pie
global address = 766dcb38
复制代码

每次运行地址都会发生变换,说明PIE使执行程序每次加载到内存的地址都是随机的。

例子二

在代码中声明一个外部变量global。但这个变量的定义并未包含进编译文件中。

#include <stdio.h>

extern int global;

void main()
{
    printf("extern global address = %x\n", &global);
}
复制代码

首先使用普通方式编译 extern_var.c。在编译选项中故意不包含有global定义的源文件。

gcc -o extern_var extern_var.c

发现不能编译经过, gcc提示:

/tmp/ccJYN5Ql.o: In function `main': extern_var.c:(.text+0xa): undefined reference to `global'
collect2: ld returned 1 exit status
复制代码

编译器在连接阶段有一步重要的动做叫符号解析与重定位。连接器会将全部中间文件的数据,代码,符号分别合并到一块儿,并计算出连接后的虚拟基地址。好比 “.text”段从 0x1000开始,”.data”段从0x2000开始。接着连接器会根据基址计算各个符号(global)的相对虚拟地址。

当编译器发如今符号表中找不到global的地址时就会报出 undefined reference to global.说明在静态连接的过程当中编译器必须在编译连接阶段完成对全部符号的连接。

若是使用PIE方式将extern_var.c编译成一个share library会出现什么状况呢?

gcc -o extern_var.so extern_var.c -shared -fPIC

程序可以顺利编译经过生成extern_var.so。但运行时会报错,由于装载时找不到global符号目标地址。这说明-fPIC选项生成了地址无关代码。将静态连接时没有找到的global符号的连接工做推迟到装载阶段。

那么在编译连接阶段,连接器是如何将这个缺失的目标地址在代码段中进行地址引用的呢?

连接器巧妙的用一张中间表GOT(Global Offset Table)来解决被引用符号缺失目标地址的问题。若是在连接阶段(jing tai)发现一个不能肯定目标地址的符号。连接器会将该符号加到GOT表中,并将全部引用该符号的地方用该符号在GOT表中的地址替换。到装载阶段动态连接器会将GOT表中每一个符号对应的实际目标地址填上。

当程序执行到符号对应的代码时,程序会先查GOT表中对应符号的位置,而后根据位置找到符号的实际的目标地址。

地址无关代码的生成方式 所谓地址无关代码要求程序被加载到内存中的任意地址都可以正常执行。因此程序中对变量或函数的引用必须是相对的,不能包含绝对地址。

好比以下伪汇编代码:

PIE方式:代码能够运行在地址100或1000的地方

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP
复制代码

Non-PIE: 代码只能运行在地址100的地方

100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP
复制代码

由于可执行程序的代码段只有读和执行属性没有写属性,而数据段具备读写属性。要实现地址无关代码,就要将代码段中需要改变的绝对值分离到数据段中。在程序加载时能够保持代码段不变,经过改变数据段中的内容,实现地址无关代码。

PIE和Non-PIE程序在内存中映射方式

在Non-PIE时程序每次加载到内存中的位置都是同样的。

image

执行程序会在固定的地址开始加载。系统的动态连接器库ld.so会首先加载,接着ld.so会经过.dynamic段中类型为DT_NEED的字段查找其余须要加载的共享库。并依次将它们加载到内存中。注意:由于是Non-PIE模式,这些动态连接库每次加载的顺序和位置都同样。

而对于经过PIE方式生成的执行程序,由于没有绝对地址引用因此每次加载的地址也不尽相同。

image

不只动态连接库的加载地址不固定,就连执行程序每次加载的地址也不同。这就要求ld.so首先被加载后它不只要负责重定位其余的共享库,同时还要对可执行文件重定位。


Reference

No-PIE 设置

APP漏洞扫描器之未使用地址空间随机化

相关文章
相关标签/搜索