PIE && ALSR


0. Introduction

这是PIC与PIE在wiki上给出的解释,同我们熟知的PLT与GOT一样,PIE与PIC也是linux下的一对姊妹花

In computing, position-independent code (PIC) or position-independent executable (PIE) is a body of machine code that, being placed somewhere in the primary memory, executes properly regardless of its absolute address.

PIC指的是位置无关代码,PIE是地址无关可执行程序

现代的GNU/Linux以及ELF系统可以随机化shared library的加载地址,这种技术叫做: ASLR. libc肯定是PIC,也就是说可以被加载 在任意地址,而且可以在各个进程之间共享已经加载入RAM的代码段.加载地址的随机化使依赖固定地址的攻击(BOF)变得难以进行.

虽然ASLR可以随机化shared library的加载地址,但是ELF可执行文件在由linker处理的时候被分配了一个固定的入口地址(在i386下是一个略大于0x08048000的值,在x86_64是一个略大于0x4000000的值),这样就提供了Attackdr 一个地址范围,根据应用程序的偏移就可以计算出应用程序加载内存中的实际地址
如果把分配入口的权限的给了kernel,那么kernel就可以给main executable 一个随机的入口地址,而linker需要生成Position Independent Executable或PIE,这样很大程度上可以解决一大类的安全问题.

基于这样的考虑,就出现了PIE保护技术,让应用程序的地址也同libc一样随机化,进一步提高Attacker的攻击难度

1. Linux’s PIE&&ASLR

PIE

gcc关于PIC与PIE有4个参数

如果指定了这些参数那么生成PIC模式的object,如果没有指定那么就生成 load-time relocation模式的object.

上面这4个参数分为两类,-fpic/-fPIC和-fpie/-fPIE,对于i386来说这每一类中的两个参数 完全等价,而且这两类参数都是指导编译器生成PIC模式的汇编代码(也就是会有@PLT/ @GOTOFF这样的汇编伪指令).

当指定了-fpie/-fPIE时gcc也会置位-fpic/-fPIC.
简单理解PIE是PIC的超集,当我们GCC编译时带上-fpie参数时,才能够重新定位main
相应的参数在gcc的处理大致代码如下:

对于这几个参数的具体函数,说法不一:

-fpic与-fpie的差别很细微,当使用-fpie时编译器知道当前的编译会生成一个PIC模式的 main executable(也就是有main入口的可执行文件),这样对于内部定义的global符号,就不 要考虑全局符号介入(global symbol interpose)的问题,对于这样的globals直接产生 PC-relative方式的代码而不需要通过GOT/PLT.
–Ian Lance Taylor

我们需要明白程序是在解决动态链接的时候有两种解决的方式:
1.Load-time relocation 装载时重定位,在windows中被称为基地址重置Rebasing,如果我们在gcc的编译的选项中使用-shared就会生成装载时重定位的程序,这种方法有很多的限制,现在应用的已经不多
2.Position Independent Code) 地址无关代码,这是现在使用比较多的方式
这两种方式生成的方式最大的不同是-pie生成的时可以执行的代码
举例:

并且pie的代码相比与shared的代码多了一些区段:

对比上面的输出可以看出,-pie的代码输出多了一个很重要的section: .interp.kernel就 是根据这个section来找到dynamic linker的,因为pie和shared都是需要重定位的代码,pie没有这个section就找不到dynamic linker
shared没有该区段,所以不可执行。

下面我们来用示例来区分load-time relocation与PIC
这两种方式实现的的PIE的具体不同:

Load-Time-Relocation-PIE

生成load-time relocation-PIE,readelf查看区段:

我们看见对库函数printf的重定位方式R_386_PC32,简单理解就是整个程序在load后整个地址就已经重定位了,不需要plt,got表的方式就是lazy binding的等等复杂过程,它的缺陷时程序在启动时就要进行整个地址空间的重定位,我们多次运行,main函数的load地址在不同的变换,算是完成了PIE的功能

我们调试中也能够发现,函数是已经binding好了真实地址:

PIC-PIE

生成pic-pie gcc -m32 -fpic -pie -o pie test.c
PIC是通过GOT/PLT/dynamic linker来实现的,可以参考Position Independent Code (PIC) in shared libraries或者程序员的自我修养-链接,装载与库7.3中的地址无关代码

简单说来,PIC的实现思想就是在一下四类情况下实现地址与加载的位置无关,不管将程序加载在哪个位置,它都能够正常运行
这四类情况分别是:模块内部调用或跳转,模块内部的数据访问,模块间数据访问,模块间条用与跳转
以32bit为例,使用__x86.get_pc_thunk.bx来获取到当前的地址,然后根据地址之间的offset在模块内部进行跳转或者数据的访问

如上图所示在需要访问模块内部的bss段上的一个字符串,首先通过__x86.get_pc_thunk.bx获取到程序段当前的地址,然后加上偏移后从正确的内存取出字符串,这样的访问方式就能够无视程序加载的地址

在模块间的相互访问仍旧时通过Got表进行的,Got表在lazy binding后存放的是函数的真实地址,在得到后可以跳转到相应的地址,并且保持代码的地址无关

在printf函数的模块间,我们看到依旧在调用__x86.get_pc_thunk.bx来进行模块的内部的数据访问与跳转
上述两种方式都能够生成PIE,地址无关应用程序,对main的整个地址空间随机化

ASLR

单单只有PIE还不能很好地确保随机化的效果,配合上ASLR的效果更加
当kernel加载运行一个executable时会会经过load_elf_binary()这个函数来进行,其中有部分用来就行地址随机化

ALSR,配合PIE就可以实现executable的随机化.
对于漏洞利用,我们一般关注比较多的是stack栈空间的地址随机化,和libc共享库的随机化
我们在本地对ASLR的效果进行测试:

Stack ASLR

可见栈中间的随机化

Libraries ASLR

2. Bypassing PIE

leak Proc_base

Bypass PIE需要考虑的问题与怎样绕过ASLR的基本相似,最直接的想法,leak当前main被load到的地址。再通过:

\[
leak\_addr – file\_offset = proc\_base
\]

得到proc的base,Rebasing整个binary,就能够定位整个应用程序的地址空间
这种Bypass的方法需要泄露一个当前text代码段的地址,这是相对困难的,需要指向.text的指针
一般的方式是有一个函数指针可以泄露,这样将整个proc的基地址拽泄出来,最典型的通过heap排布来泄露地址,如果heap中存在一个函数指针,在运行时将其泄露出来就能够顺利Bypass PIE

Hctf中fheap是一个非常典型的Bypass PIE的例题,我们利用一个Use After Free泄露出heap中一个函数指针的地址,从而绕过PIE

Partial Overwirte

Partial Overwirte 的思想非常简单,我们知道在PIE开启时,对于一个32bit的binary,它的高20bit会被随机化的,而最后的12bit不会被随机化,如果漏洞触发,我们能够恰好覆盖最后的12bit,实际上也能在一定的范围内劫持程序的控制流
若在这个范围内有我们感兴趣的things,也能够无视PIE,完成漏洞利用
Partial Overwite就是基于这样一个朴素的想法,我们看下面的示例:
源代码如下:

程序让我们输入password进行检测,我们想做的就是直接getshell,程序中存在一个明显的BOF漏洞,strcpy没有判断长度,我们可以溢出到返回值

由于程序开启了PIE,我们可以Parital Overwrite,进行部分Hijack control flow,我们需要找到一个靠近返回值的地址,并且覆盖它的最后一个字节,期待它有可能(加上offset的结果就不一定了)跳转到我们期待的地址

call verify后的返回地址本应是0x942,我们将它覆盖成getshell的地址最后一个字节0X59,就有可能成功getshell

Partial Overwrite成功

return2vsyscall

这种技术可以使用在64bit的linux下,也仅仅使用在版本比较新的linux内核中

The “vsyscall” and “vDSO” segments are two mechanisms used to accelerate certain system calls in Linux.

vsyscall是64bit的linux下为了实现syscall的 “快速系统调用指令”实现的一个特殊的内存区段
之所以介绍它是因为vsyscall的这一块,不会被ASLR!!!
它的地址是永久固定的

在0xffffffffff600000~0xffffffffff60100固定地址
它的代码也仅仅只有短短三行:

由于vsyscall不会ASLR,我们在做ROP是返回改地址,通过多次ret对堆栈进行shift,这样一个固定的地址能够帮助我们绕过PIE

3. Exploitations

程序的源代码如下:经过hack-lu-ctf-2015 stackstuff 改编

程序是一个检测密码的功能,登录到127.0.0.1 1514后,程序从它存放的文件将password读入到内存中,并根据你输入的password进行比较,如果成功就能够getshell
显然我们无从得知password,但是只要能恶意篡改程序流程,就使得其成功
开启pie的程序就需要Parital overwrite,但是我们无从泄露地址,于是我们先尝试运行程序到溢出返回处

发现程序最多能够溢出覆盖此时rsp+3个参数的后面16bit,但是前面bit恰好时程序的随机地址
于是我们可以找到一个合适的地址返回,但是需要pop pop ret,于是想到利用vsyscall上的ret进行两步pop

暴力前一个字节,确定最后一个字节为0xdd,成功实现Partial Overwrite与Ret2vsyscall,返回到system(”/bin/sh”)处getshell,当然在本机测试时,最后一个字节时0x50

4. Afterword

PIE++ASLR使得几乎整个binary的内存空间地址都被随机化了
地址无关代码本来被应用于共享库,它将共享库映射到不同的进程内存地址从而实现共享的
最后这样一个技术也被应用在了普通的binary作为缓解漏洞利用的一个重要手段

Bypass PIE本质上和Bypass ASLR没有区别,方式就是leak information来做下一步攻击做准备
当然Parital Overwrite个人认为大部分情况下时一种巧合,它并不能够算作一种可靠的Bypass PIE的技术
最后介绍的ret2vsyscall,是结合linux发展中的一个产物,它的局限性也比较大,主要原因时只有3个instrutions,很多场合下难以利用
当然还有许多其他的方式Bypass PIE,这只是其中几种

5.reference

Bypassing PaX ASLR protection
Bypass ASLR with partial EIP overwrite
about ELF – PIE, PIC and else

发表评论