如何打入操作系统内核?这是一个很有趣也很硬核的问题。黑客通过应用程序漏洞(如Java、PHP、Apache、IE、Chrome、Adobe、office等)获得执行代码的能力后,由于操作系统的安全设置,很多情况下,他们处于沙盒中或低权限进程。正在运行,许多操作无法执行。为了做更多高权限的事情,黑客通常会使用工具来提升权限。x86arm操作系统的安全防线是建立在内核最高权限的控制之上的。无论是杀毒软件、沙盒还是防火墙,都运行在内核态。要想突破安全包围圈,就必须获得内核级权限的执行能力,才能与这些安全保护抗衡。我们经常听到的Android系统ROOT、iOS系统越狱就是内核攻击的典型应用。攻击者获得内核权限后,可以安装rootkit级别的木马病毒,实现文件隐藏、进程隐藏、通信隐藏等高级木马功能,对系统危害极大。内核0day漏洞存在APT攻击核武器级别,地下网络安全交易市场价值巨大。进入内核的四种方式其实我们的程序无时无刻不在用户空间和内核空间之间来回穿梭,只不过这些进出门是操作系统事先安排好的,进入内核后去哪里执行什么代码,是操作系统说了算,不是我们自己的程序。从用户态空间进入内核有四种方式:中断:中断分为硬中断和软中断两种。硬中断:硬件设备向CPU发起的中断信号。这是系统调用的实现方式,比如windows上的int2e,linux上的int80,不管是硬中断还是软中断,CPU遇到后都会保留当前的执行上下文,进入内核执行中断处理函数。这些函数记录在中断描述符表IDT中,由操作系统在系统初始化时预先安排好。异常:异常是CPU在执行指令过程中出现的问题,比如除法指令的除数为0,访问的内存地址无效等。异常处理逻辑与中断处理类似,也是由记录在IDT中的异常处理函数来执行的,也是操作系统在系统初始化时预先安排好的。系统调用:您应该熟悉系统调用。要实现文件系统访问、网络I/O、进程线程使用、内存分配和释放等,我们需要使用操作系统提供的编程接口来实现它们。这些接口称为:系统调用。前面提到,早期x86架构下的CPU并没有专门的系统调用机制,操作系统都是通过软中断进入内核完成系统调用。后来因为系统调用是一个非常高频的需求,软中断的方式就有些低效了。CPU增加了特殊的系统调用机制,包括一些特殊的寄存器和一些特殊的系统调用指令。比如sysenter(x86)、syscall(x64)、swi(arm)。通过系统调用进入内核后,操作系统已经提前安排好了转向哪里执行,而不是由应用程序来决定。驱动开发:进入内核的最后一种方式就是开发驱动,但是加载驱动本身需要极高的权限,这点就不细说了。以上就是让我们的程序进入内核态运行的正常方式。可见,一旦进入内核态,执行流程就进入了操作系统事先设定好的代码,攻击者无能为力。有正式的方式,当然也有非正式的方式,就是通过各种漏洞攻击系统内核,让我们的程序进入内核态执行,这也是本文的重点。下面列出了一些常见的攻击方法。零地址攻击学过C语言的朋友都知道,零地址,即NULL,在C语言中代表空指针。一些没有经验的程序员在写一些接口函数的时候,往往会忘记检查指针参数是否为NULL,从而导致程序异常崩溃。以32位操作系统为例,一个进程的地址空间为:0x00000000~0xFFFFFFFF。在x86架构上,内存一般以4KB页为单位进行管理。你有没有想过,如果分配了进程地址空间中以零地址(即NULL)开始的第一个4KB页面会发生什么?假设在内核中,有一段代码忘记对空指针进行检查,通过这个指针调用函数。攻击者提前分配零地址页并准备数据,像这样:这会发生什么?后果就是攻击者的代码会在内核态执行!然而,假设不仅仅是假设,它确实发生了,即使是微软这样强大的程序大亨,有时也会忘记检查空指针。典型漏洞案例:CVE-2014-4113Windows发布后使用:UAF除了空指针,还有一把锋利的刀悬在C/C++程序员的头上,这就是悬空指针。悬垂指针是指忘记立即清空已释放的内存/对象指针,稍后再使用这个指针,但此时对应的内存已经被回收,造成不可预知的后果。哎,这个指针真是害人啊!你有没有想过,如果你在对象释放后忘记及时清空指针,后面继续使用这个指针,在这两个动作之间的这段时间里,有人恶意“占用”了原本释放的内存空间,并编排恶意数据代码。会发生什么?这就是著名的UAF发布后攻击!UAF表示释放后使用。我们来看一个简单的例子:两个对象,一真一假,占用同一个内存空间。下面的代码,原来的对象被释放后,忘记清空obj指针,然后分配了一个FakeObject。由于堆分配算法,两个对象大小相同,新对象很有可能会分配给刚刚释放的对象。芯片内存起来。这时通过原来的悬空指针调用函数,实际调用的是新对象的函数,劫持了执行过程。这只是一个简单的例子,现实世界比这复杂得多,但原理是一样的。这种事情一旦发生在系统内核中,后果就很严重了。应用程序可以劫持内核空间的执行进程,执行自己的代码。典型漏洞案例:CVE-2016-0728Linux整数溢出+数组越界在操作系统中,很多函数地址都是以表的形式存储的,比如:系统调用表:SSDT/sys_call_table中断描述符表:IDT如果有是一种方法如果我们可以修改这些表中的函数地址,重写为攻击者的代码地址,是否可以让我们的代码运行在内核态呢?原因是这样的,但是这些表本身是位于内核空间的。别说重写应用程序,就是读起来也费劲。真的没有办法吗?还是有的!如果内核中的某段代码正在向数组中的某个元素写入数据,恰好忘记检查数组的下标是否越界,而恰好这个下标可以被应用控制的话程序,能不能写越界?如果我不小心写入了以前的功能表怎么办?你可能已经意识到这不是假设,而是真实的案例!典型漏洞案例:CVE-2013-2094LinuxLinux内核任意地址写入漏洞。通过精确控制系统调用的参数,可以将IDT中的函数地址改写为恶意代码地址,恶意代码可以在内核态执行!以下是一些内核攻击的真实案例:安全防线高一尺,路在高处。各种攻击也促进了操作系统和CPU的安全能力:英特尔?modeexecuteprevention),设置CR4寄存器的bit20为1,使ARM从armv7开始加入PXN技术,原理同SMEP。Windows8.1禁止使用零页地址内存。Linux2.6.26开始使用vm.mmap_min_addr来限制地址空间的最小值,防止使用零页内存...空指针,悬空指针,数组越界,整数溢出...这些看似不起眼的编程问题,如果发生在操作系统内核,后果将是灾难性的!可见养成良好的编程习惯是多么的重要!连开发操作系统的大程序员都会犯错,更何况是我们呢?二维码关注。转载本文请联系编程技术宇宙公众号。
