Grsecurity/PaX目前应用广泛,特别是具有高安全性的环境,Gnu/Linux发行版里Gentoo提供PaX作为加固选项,最近半年Debian社区发起的对抗大规模监控的加固项目Mempo在内核中也使用了Grsecurity/PaX。
PaX的思路的确非常的震撼,那都是10多年前的设计和实现,在这个一天云计算一天雾计算的年代,虽然关注本质的黑客越来越少,但地下精神并未死去,PaX Team就是一个活生生的例证,相反,不少old school黑客都坚信其实old school的数量并没有减少,至少我个人相信这是真的…Phrack没死,Grsecurity/PaX没死,DNFWAH也没死,希望更多的黑客(在DNFWAH ezine上面)分享自己的hacking之旅。
PaX是针对linux kernel的一个加固版本的补丁,它让linux内核的内存页受限于最小权限原则,是这个星球上有史以来最极端和最优秀的防御系统级别0day的方案,第1版的设计和实现诞生于2000年,那可是一个没有ASLR/RELRO/NX/CANARY/FORITY/PIE都没有的年代
今天意义上的现代mitigation技术不管是linux/windows/macosx都多少抄袭和模仿了PaX的设计和实现
当年Linux内核不收PaX进入upstream是因为很多人觉得PaX不是那么的好维护,之后linux内核推出了LSM( Linux Security Module),LSM利用了一堆CAPABILITY的 机制提供了一些限制用户态程序访问控制的接口,SELinux和Apparmor就是基于LSM开发的,注意LSM并不是一个传统意义上的linux kernel module,至少在2个地方不同于普通module: 1) 必须在bootloader启动内核时启动,不能在内核加载完后启动。 2) 不能同时启动2个LSM的实现。
但PaX Team是一群old school security hackers,他们认为LSM打破了 “security as a whole”的哲学,所以宁愿单独维护一个patch
谈到PaX时都会写Grsecurity/PaX,这是怎么回事呢?PaX从一开始就主要关注如何防御和检测memory corruption,后来Grsecurity社区发现PaX和他们所关注的非常类似,所以就合并了,在很长的一段时间里PaX主要关注memory corruption,而Grsecurity则实现其他的功能包括RBAC,但到最近2个社区的工作开始模糊了:包括USERCOPY, STACKLEAK, RANDSTRUCT, etc..都是整个Grsecurity/PaX共同实现的特性。
1999年7月的plex86社区(old school虚拟化社区之一)打算验证一个概念,当时Pentium(包括P6family)处理器新增加了一个功能,就是CPU把TLB区分为DTLB(数据TLB)和ITLB(指令TLB),TLB主要是PTE( page table entries)的缓存,因此存放着user/kernel spaces的访问权限信息,在正常的情况下,ITLB和DTLB entries从相同的PTE里读出相同的状态,但如果状态有所改变的话也就意味着可以把数据读写和代码执行分开,如果这个POC能成功也就意味着可以对抗缓冲区溢出的最佳方
案,这个成为了今天的NX=>要么可读写要么可执行。
PaX在出现page fault的时候多增加了一些动作包括模拟page table entry里的可访问U标志位和在模拟PTE中检查访问权限。
PaX的第一版的副作用也不小,
1,用户态可执行的栈是不可能的
2,性能损耗在5%–8%
从defensive的平面来看,当时GNU/Linux平台主要依赖PaX的patch来进行加固(包括ASLR),ASLR和NX进入linux内核mainstream是后来的事情,OpenBSD和WindowsXP SP2和OSX 10.5也加入了NX,但都是抄袭PaX的设计(或许也包括实现),
从offensive的平面来看,2003年的背景是stack-based overflow和string format vuln已经泛滥,但ROP还没有大规模的流行,但old school社区对于ROP的研究已经有相当的研究
2003年,PaX team认为会导致漏洞利用的bug给予了攻击者(区分攻击者和黑客是不同的term)在3个不同层面上访问被攻击的进程:
(1) 执行任意代码
(2) 执行现有代码但打破了原有的执行顺序
(3) 原有的执行顺序执行现有代码,但加载任意数据
NOEXEC( Non-executable pages)和MPROTECT(mmap/mprotect)能防御(1),但有一种情况是例外:如果攻击者能创建和写入一个文件然后mmap()到被攻击的进程空间里,这样可以执行任意的代码。
ASLR在一定程度上降低了(1),(2),(3)的风险,但如果内核有信息泄露的bug例外。PaX team在当时就认为把内核当成可信计算( Trusted Computing)的基础是一件可笑的事情,因为内核跟用户空间一样容易遭受各种攻击。所以他们认为”未来”需要做一些事情(注:这些事情今天都已经搞定):
(a) 尝试处理(1)不能处理的那个例外情况
(b) 实现所有可能在内核态自己的防御机制
(c) 为(2)实现确定性( deterministic)防护,可能也为(3)实现类似的机制
(d) 为(2)实现概率行( probalilistic)防护以实现阻止信息泄露
之后这篇文档里详细的罗列了针对(a)(b)(c)(d)需要去实现的加固方案。在下面
其实已经能看出一些后来出现的mitigation技术:Stack Canary, RELRO,
在2003年的晚些时候PaX实现了虚拟内存空间的镜像( vma mirroring)[8],vma mirroring的目的是为了在一组物理页上做特殊文件隐射时有2个不同的线性地址,这2个地址即使在swap-out/swap-in或者COW后依然是不会改变的。这样做的目的为了满足几种场景:
1,把可执行的区域在使用SEGMEXEC隐射进入代码段。在32-bit的linux内核里的4GB地址空间是其中3GB给用户空间,1GB给内核空间,而vma mirroring把用户空间的3GB划分成了2个1.5GB分别是给代码段和数据段,在可执行区域里包含的数据的部分(常量字符串,函数指针表等)都会mirroring到数据段里。
2,实现可执行区域的地址随机化( RANDEXEC)。
3,这个引出了第3种情况,就是SEGMEXEC和RANDEXEC同时激活,个人觉得这个的效果应该和PIE+ASLR的效果类似,不同的不是整个elf binary的代码段随机化,而是在mirroring时对代码段和数据段进行随机化。
当时的PaX的做法大致是这样的,vma mirror是根据已经内存映射mmap()后的地址,用户态通过mmap()是无法直接去做vma mirror请求的,所有的mmap()请求多会经过include/linux/mm.h的do_map(),PaX扩展( SEGMEXEC)也是在这个地方处理,原始内核通过调用do_mmap_pgoff()来调用do_mmap(),PaX在这里为了确保SEGMEXEC能知道来自用户态和内核态的原生文件映射请求所以略过do_mmap_pgoff()而直接调用do_mmap(),而vma mirror请求使用一些特殊参数传递给do_mmap_pgoff():
‘file’ 必须是NULL,因为mirror会引用相同文件的vma作为镜像
‘addr’ 正常使用
‘len’ 必须是0
‘prot’ 正常使用
‘flags’ 正常使用,除了一种情况:指定MAP_MIRROR和只能指定private映射
‘pgoff’ 指定vma的线性起始地址作为镜像
#cp /bin/cat /tmp/
#/tmp/cat /proc/self/maps
[1] 08048000-0804a000 R-Xp 00000000 00:0b 1109 /tmp/cat
[2] 0804a000-0804b000 RW-p 00002000 00:0b 1109 /tmp/cat
[3] 0804b000-0804d000 RW-p 00000000 00:00 0
[4] 20000000-20015000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
[5] 20015000-20016000 RW-p 00014000 03:07 110818 /lib/ld-2.2.5.so
[6] 2001e000-20143000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so
[7] 20143000-20149000 RW-p 00125000 03:07 106687 /lib/libc-2.2.5.so
[8] 20149000-2014d000 RW-p 00000000 00:00 0
[9] 5fffe000-60000000 RW-p fffff000 00:00 0
[10] 68048000-6804a000 R-Xp 00000000 00:0b 1109 /tmp/cat
[11] 80000000-80015000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
[12] 8001e000-80143000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so
这个binary是一个动态连接的可执行程序,所以在执行时会映射其他的库文件。
[1] 这个binary文件/tmp/cat的第1个PT-LOAD段映射为有读和执行的权限,包含
了可执行的代码和只读的初始化后的数据。因为是可执行的所以被[10]镜像。
[2] 第2个PT_LOAD段,映射为读写权限,包含了可写的数据(所有初始化和没有初
始化的)
[3] brk()管理的堆,在运行时会根据malloc()/free()来调整大小
[4][5] 动态连接器
[6][7] C库 ,[4][6]被映射到了[11][12],因为他们是可执行的。
[8] 一个针对C库的初始化数据的匿名映射
[9] 一个匿名映射包含了stack。我们能观察到这个地址在用户空间的数据部分的
结束地址,开启SEGMEXEC后是TASK_SIZE/2。
[10][11][12] 分别映射可执行镜像[1][4][6]。
[1] 08048000-0804a000 R-Xp 00000000 00:0b 1109 /tmp/cat
[2] 0804a000-0804b000 RW-p 00002000 00:0b 1109 /tmp/cat
0804b000-0804d000 RW-p 00000000 00:00 0
[3] 20000000-20002000 ++-p 00000000 00:00 0
[4] 20002000-20003000 RW-p 00002000 00:0b 1109 /tmp/cat
20003000-20018000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
20018000-20019000 RW-p 00014000 03:07 110818 /lib/ld-2.2.5.so
20021000-20146000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so
20146000-2014c000 RW-p 00125000 03:07 106687 /lib/libc-2.2.5.so
2014c000-20150000 RW-p 00000000 00:00 0
[5] 5fffe000-60000000 RW-p 00000000 00:00 0
[6] 80000000-80002000 R-Xp 00000000 00:0b 1109 /tmp/cat
80003000-80018000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
80021000-80146000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so
RANDEXEC有一些改变,
[3]成了第1个可执行的PT_LOAD段的匿名映射,
[4]成为第2个PT_LOAD段的的mirror,[2]和[4]有相同的页偏移值,文档说[1]被[6]给
mirror后是超出了TASK/SIZE/2的范围,但个人觉得这个地方是代码段的区域所以
必然是在1.5G以上(如果数据段在0-1.5G的话),还有就是在RANDUSTACK开启后由
于stack的第1部分不能关闭随机化,所以多比第1个例子多占了1个page,这个怎
么得出的呢?靠我真不知道,可能是fffff000 xor ffffffff = fff来的?
[1] 08048000-0804a000 R--p 00000000 00:0b 1109 /tmp/cat
[2] 0804a000-0804b000 RW-p 00002000 00:0b 1109 /tmp/cat
0804b000-0804d000 RW-p 00000000 00:00 0
[3] 40000000-40002000 R-Xp 00000000 00:0b 1109 /tmp/cat
[4] 40002000-40003000 RW-p 00002000 00:0b 1109 /tmp/cat
40003000-40018000 R-Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
40018000-40019000 RW-p 00014000 03:07 110818 /lib/ld-2.2.5.so
40021000-40146000 R-Xp 00000000 03:07 106687 /lib/libc-2.2.5.so
40146000-4014c000 RW-p 00125000 03:07 106687 /lib/libc-2.2.5.so
4014c000-40150000 RW-p 00000000 00:00 0
bfffe000-c0000000 RW-p fffff000 00:00 0
最后的这种情况是vma mirroring所产生最简单的内存layout,只有binary本生被
镜像了,[1]被[3],[2]被[4]镜像了。注意[1]没有R-X权限了,在PAGEEXEC下只
有R–。
虽然现在的PaX实现肯定不是这个设计的版本,但读读原始的paper会有一些意想
不到的收获,也算技术进化考古的过程了;-)
GRSecurity 为ACL系统提供了内核模块的支持,另外,在用户空间,还需要有gradm这个工具。
ACL是 Access Control List的简写,支持ACL的系统可以对系统及系统文件,系统资源进行细力度的访问控制。
GRSecurity 还没有集成到linux内核中,如想使用需要自己下载相关内核补丁。目前grsecurity的补丁稳定版支持2.6.27.10的内核,测试版支持到了最新的2.6.31.5版内核。
tar -jxf linux-<version>.tar.bz2
patch -p0 <grsecurity-<version>.patch
cd linux-<version>
make menuconfig
在Security options中就可以找到grsecurity的选项。
启动grsecurity gradm -E
停止grsecurity gradm -D
切换到管理员角色 gradm -a admin ,注意,-a 参数只能用于从一个普通角色切换到管理员角色
要实现ACL(访问控制),需要有一个策略文件,grsecurity启动后,会根据该文件中的策略对系统进行访问控制。
该文件位于/etc/grsec/目录下,文件名为policy。
policy中有三个概念:
第一是角色role,如“role admin sA“,“role default ”,”role root uG“,表明有三个角色,分别是admin , default, root,至于角色与角色之间的关系,以及参数的含义,我们会在角色的定义这一节中讲到。
第二是subject,每个subject中首先定义了一个可执行程序(注意,一个subject中只定义一个可执行程序,要对多个可执行程序进行定义的话,就需要多个subject),或者更准确一点,定义了一个运行在系统中的进程。然后后面跟着一系列的object(第三个概念),用来规定当前这个进程的权限。每个角色定义的后面都可以跟一个或多个subject,例如角色default后面,就定义了一个subject,“subject /”,而角色root的后面就定义了多个subject,“subject /”,“subject /usr/X11R6/bin/XFree86”,“subject /usr/bin/ssh” ,“subject /sbin/klogd”。
第三是object,每个subject中都会有若干的object,表示每个进程都有若干个操作对象,这些操作对象一般来说都是一些目录,文件等等,用来规定当前这个subject中的进程对这些文件拥有哪些权限,例如:
subject /usr/bin/ssh /etc/ssh/ssh_config r
表示/usr/bin/ssh这个进程对/etc/ssh/ssh_config这个文件有读权限(r)。更进一步讲,这个subject位于角色root后面,所以这两行策略的含义就是以root角色运行ssh时,ssh进程对/etc/ssh/ssh_config有读权限。该策略只定义在了root角色中,对于其他角色不起作用。
注意:在每一个角色中,都必须有一个subject /,表示一个缺省的ACL,如果没有这个缺省的ACL,grsecurity启动时会报错同时启动失败。当以一个角色登录系统后,如果要执行的可执行程序没有被某个subject定义,那么,该程序就会采用subject /中的缺省定义。例如root角色,没有对cat命令进行定义,所以以root角色执行cat命令时,ACL系统就会参照subject /中的定义来控制cat进程对文件的访问。
grsecurity的角色定义非常简单,只需要在policy中声明一下就可以了,语法为: role
,如例子中的role root uG
。
grsecurity中的角色分为用户角色,组角色,缺省角色(default),还有个管理员角色。定义不同角色需要有不同的参数。
定义用户角色,需要加参数“u”。定义组角色,需要加参数“g”,定义缺省角色可以什么参数都不加,定义管理员角色需要加”A”和“s”。注意,grsecurity的角色与用户是一对一的。假设有tester用户,属于test组,那么tester用户登录后,会先在配置文件中匹配名为tester用户角色,如果没有,就会去匹配叫test的组角色,如果还没有,那么tester用户进入系统后的角色会是default(default角色在配置文件里面定义)。也就是说,tester用户登录后,不是tester用户角色,就是test组角色,要么就是defaul
t缺省角色,不可能进入其他policy中定义的角色。当然,登录之后可以切换角色,这需要配置相应的policy,参考角色切换一节的介绍。
定义角色的参数:
A – This role is an administrative role, thus it has special privilege normal
roles do not have. In particular, this role bypasses the additional ptrace
restrictions
N – Don’t require authentication for this role. To access the role, use
gradm -n
s – This role is a special role, meaning it does not belong to a user or group,
and does not require an enforced secure policy base to be included in the
ruleset.
u – This role is a user role
g – This role is a group role
G – This role can use gradm to authenticate to the kernel A policy for gradm
will automatically be added to the role.
T – Enable TPE for this role
l – Enable learning for this role
P – Use PAM authentication for this role.
关于policy中subject和object的定义,那是相当的复杂,最好的办法就是用gradm的学习模式自动生成策略。
在grsecurity系统中,角色的切换实际上就是用户的切换,通过su命令,由test01用户切换到test02用户,那么你的角色就由test01切换到了test02。当然,有两个前提,第一是你的policy是允许test01通过su命令进入test02的,否则根本
就且换不了用户,更别提角色的变化了,第二是你的policy中定义了test02这个角色,否则,系统将会去匹配test02所在组的组角色,如果组角色也没有定义,那么切换到test02后,你的角色就是default。
由于人工编写policy是非常复杂的,几乎是不可能完成的,所以gradm为我们提供了一个很强大很好用的学习模式,以使系统能够根据用户的日常操作,来学习哪些操作是被允许的,哪些文件是可读的,哪些文件是可写的等等。使用学习模式有一个前提,就
是作为用户,必须非常清楚哪些操作应该被允许,哪些操作不应该被允许。如果一个不应该被允许的操作,在学习模式开启的情况下却被执行了,那么gradm将会记录该操作,并把他列为允许执行之列。
学习模式有两种,分别是Process and role base learning(基于进程和角色的学习)和Full system learning(全系统学习)。下面分别介绍一下:
基于进程和角色的学习
该种学习模式可以指定某个角色或者某个角色中的特定进程进入学习模式
如果我们想让test角色学习,就在test角色定义处添加”l”,如”role test ul”,这样的话,只要你进入这个角色,之后所有你执行的命令都会被学习模式记录下来。
如果只想让test角色中的ls进程学习,就在ls的subject处添加”l”,如”subject /usr/bin/ls l”。
设置完后,执行gradm -D, 确认grsecurity关闭,然后执行 gradm -L /etc/grsec/learning.logs -E
进入学习模式。
我们以ls进程的学习为例,在学习模式下,切换到test角色(su test),然后执行设置了学习模式的命令”ls”,比如,想要”ls”对/etc/可读,那就执行”ls /etc/”,想要ls对/mnt可读,那就执行”ls /mnt”。当你需要做的所有操作都做了至少4遍以后,就可以执行以下命令生成policy。
gradm -D #先要停止grsecurity
gradm -L /etc/grsec/learning.logs -O /etc/grsec/acl #生成acl文件
把acl文件中的内容复制粘贴到policy中对应的subject中。详细操作参看学习模式应用实例。
全系统学习
该学习模式下,系统将记录所有被policy文件deny的操作,并生成相应的policy,以保证这些被deny的操作在新的policy中不会被deny。
这种模式不需要修改配置文件,直接执行如下命令即可:
1)gradm -F -L /etc/grsec/learning.logs
2)执行你需要赋予权限的命令或操作至少4次
3)生成acl文件
gradm -F -L /etc/grsec/learning.logs -O /etc/grsec/acl
4)将acl中的内容复制粘贴到policy中。详细的复制粘贴方法参看学习模式应用实例。
用户切换后,角色也切换,角色的权限是不是继承的?
答:不会继承
2016年4月28日下午三点,PaX/Grsecurity正式公布了针对Linux内核4.5里的新特性:RAP。RAP是一种在Linux内核层面 上的CFI(控制流完整性)的实现,致力于完全消灭代码重用攻击这种漏洞利用的方式,RAP的发布是系统安全领域的又一里程碑,这意味着自2003年PaX team谈“未来”至今,只剩下data-only attack并未完全解决,从技术选型,研究,开发和测试发布,PaX team一共历经5年左右的时间,RAP第一次亮相是去年的H2HC, 这次公开发布的版本虽然只支持x86_64,没有经过连接时优化,编译时的静态分析以及返回地址保护等重要feature,但足以适应于大部分的场景,公 开的版本是基于GPLv2自由软件许可证发布的。另外PaX/Grsecurity的下一个稳定版选为4.4,3.14的稳定版会一直维护到2017年年底