call *sys_call_table(0, %eax, 4) //Calling sys_call_table[eax]
movl %eax,PT_EAX(%esp) //Storing of return value
系统调用例程核心。eax寄存器存放系统调用号,即sys_call_table数组索引值,指向
某个特定系统调用函数。函数返回值将存放到栈中的eax寄存器中,返回给用户空间。尽管
系统调用例程核心很适合劫持,但我们不利用此位置,因为系统调用表指针是当前各种扫描
工具首要的检测点。
cli //Clear Interrupts
movl TI_flags(%ebp), %ecx //Copy process flags in ecx
testw $_TIF_ALLWORK_MASK, %cx //Is needed extra work?
jne syscallexitwork //If so, do extra work
当系统调用函数返回时,系统会屏蔽所有中断,进程测试是否有额外的工作(确信不丢
失需要调度或发信号挂起的中断)。上面的代码片段提供了另一个劫持机会。movl和testw
指令共8字节大小,足以存放JMP指令,而且这两条指令完全独立不在任一标签体内,可复制。
RESTORE_REGS //CPU’s registers restoration
addl $4, %esp //Clearing up system call number from stack
iret //Return from interrupt
调用例程实现体尾部即准备从系统态切回到用户态。用暂存在栈上的值恢复所有CPU寄
存器值,清除系统调用号,触发iret指令。尾部指令也可作为劫持位置,但问题是当进程被
跟踪时,并不会使用这些代码片段。
3.2 改变系统调用例程中的程序控制流
通过3.1 小节分析,我们已经在系统调用例程中确定了两个合适位置可劫持系统控制流
程。在 2.6内核的所有版本都兼容这种方式的更改,具备很好的兼容性和可用性。我们的目
的是修改系统调用函数的返回值,即在原函数调用后,再劫持转入到 hack 函数执行。故,
我们将确定选择第二个位置实现劫持,如图2 所示。否则,若在函数还未调用之前,转入攻
击者设置的hack函数时,执行完hack 函数后,再调用原函数,则无法实现hack函数目的,
如隐藏文件效果等,而且若不再调用原函数,显然也不合乎逻辑,否则上层应用程序明明是
调用 fork 函数创建一个子进程,却没有实现此功能,很容易被用户感觉到存在内核钩子程
序。
我们重写了movl TI_flags(%ebp), %ecx 和testw $_TIF_ALLWORK_MASK, %cx
两条指令,并跳转到trampoline()函数中重新执行,但在重新执行上面两条指令之前,事先
保存了栈顶地址值,以确保能准确访问到系统调用结果,而后调用hack函数,修改原系统调
用的结果信息,如过滤待隐藏的木马文件、恶意进程等。执行完此函数后,再对此函数的栈
痕迹进行清理,最后重新jmp到原位置继续执行。
实验
为了验证前面提出的劫持方法,笔者基于此方法开发了两款rootkits,分别为MoleKit
和 Powerkit。MoleKit能够借助/dev/mem文件有效渗透到系统。Molekit可提供一些基本的
服务,如进程、目录隐藏等。因为这种攻击方式涉及到很多启发式搜索,如在内存中寻找代
码片段特征值等,但这也是一种很好的思路,可以在不借助可加载模块支持前提下,实现系
统渗透;Powerkit则是使用内核模块渗透到内核,其主要好处是可方便访问内核API,并实
现 keylogging或权限提升等。
小结
本文给出了劫持Linux内核的一个新方法,此类攻击在2.6内核的所有版本上均验证可
行,并给出了两款基于此技术的Rootkit,且当前并没有很好的Anti-Rootkit或扫描器等工
具能够检测此类攻击。
|