没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
H
H
i
i
j
j
a
a
c
c
k
k
i
i
n
n
g
g
L
L
i
i
n
n
u
u
x
x
P
P
a
a
g
g
e
e
F
F
a
a
u
u
l
l
t
t
H
H
a
a
n
n
d
d
l
l
e
e
r
r
E
E
x
x
c
c
e
e
p
p
t
t
i
i
o
o
n
n
T
T
a
a
b
b
l
l
e
e
中
中
文
文
版
版
原文作者:buffer <mailto:buffer@antifork.org>
译者:wzhou <mailto:z-l-dragon@hotmail.com>
内容
1. 介绍
2.
系统调用和访问用户空间
3.
缺页异常
4.
实现
5.
进一步考虑
6. 结论
7.
参考资料
8.
译者后记
介绍
“只是又一篇介绍Linux下的内核模块的文章”...当看到这篇文章的时候,你可能这么想,但
我认为你错了。在过去几年里,我们看到很多利用LKM
1
来隐藏各种各样东西的技术,比如隐藏
进程,网络连接,文件,等等等等。这些技术很容易理解,但真正的问题是它们很容易被检测出
来。如果你替换了系统调用表中的某个地址,或者你覆写了系统调用代码的头 7 个字节(象
Silvio Cesare在【
参考资料 4】中描述的那样),象Kstat【参考资料 5】或Angel【参考资
料 6】这样的工具是很容易鉴别出这些黑客行为的。之后出现了更复杂的技术。比如kad提出了
一项蛮有趣的技术,他建议通过修改中断描述符表
2
(IDT),用新的异常处理器地址取代IDT项
中原来的地址,并由用户空间代码触发该异常(比如“除零错”异常)这种方式来重定向以执行
新的异常处理器
3
,在【参考资料 7】中有介绍。这个主意不错,但有两点不足:
1. 这种黑客技术能被基于对整个 IDT 进行哈希计算的方法检测到,就象最新版的 Angel
(0.9.x 版)做的。这主要是由于处于内核空间的 IDT 表的地址很容易被获得,因为该值
存储在%idtr 寄存器中,而该寄存器可以用汇编指令 sidt 读取并允许存储该值到某个变
量中。
2. 如果用户代码执行了除零操作(它可能发生...)可能出现奇怪的行为。是的,如果我们选
择了合适的(除零异常)处理器(handler),有人可能认为这有点不太正常。如果有什么
比较安全的方法,那是什么呢?
我提出的方法只有一个目的:提供有效的“秘密行动”来对抗各种用于鉴别黑客型 LKM 的工具。
该技术基于在现实中从没有被用到过的一种内核特性(译者:我不知道作者为什么这么说?)。
1
LKM指Loadable Kernel Module,即可载入内核模块
2
中断描述符表是x86 架构的CPU最核心的资源之一,用于存放处理各种中断/异常的处理器函数的地址
3
这里所谓的新的异常处理器及时黑客种的“后门”
实际上,正象我们将要看到的,我们将研究内存管理子系统中的一种通用保护机制。如果用户代
码有严重 bug(译者注:传递非法指针给系统调用),该机制才会被触发,而这是较少出现的。
废话少说,让我们开始吧!
系统调用和访问用户空间
首先,先谈一点理论。我这里引用的是 2.4.20 的 Linux 内核,其实代码同 2.2 内核的几乎是
相同的。我们尤其感兴趣的是当通过系统调用来请求内核特性时,在某些情况下发生的事情。当
用户代码调用系统调用时(通过软件中断 0x80),system_call()异常处理器被执行。让我们
看一下在 arch/i386/kernel/entry.S 文件中的实现代码。
ENTRY(system_call)
pushl %eax # save orig_eax
SAVE_ALL
GET_CURRENT(%ebx)
testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS
jne tracesys
4
cmpl $(NR_syscalls),%eax
jae badsys
call *SYMBOL_NAME(sys_call_table)(,%eax,4)
movl %eax,EAX(%esp) # save the return value
[..]
我们很容易看出,system_call()把所有寄存器值保存在内核堆栈里
5
,然后通过调用
GET_CURRENT(%ebx)获得指向当前运行进程的task_struct结构的指针。接着是检查系统调
用号的正确性和当前进程是否正被追踪。最后通过sys_call_table来调用对应的系统调用。
sys_call_table维护着系统调用的函数地址,以存放在%eax寄存器中的系统调用号为该表的
偏移即可找到对应的函数地址。现在让我们看一下某些特定的系统调用。就我们的目的而言,我
们只要找到接受用户空间指针为参数的系统调用即可。我选择了sys_ioctl,但选择有相似行
为的另外一些系统调用也可以。
asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long
arg)
{
struct file * filp;
unsigned int flag;
int on, error = -EBADF;
[..]
case FIONBIO:
if ((error = get_user(on, (int *)arg)) != 0)
break;
4
系统调用的函数地址表
5
通过SAVE_ALL宏
flag = O_NONBLOCK;
[..]
上面的 get_user()宏常用于从用户空间往内核空间拷贝数据。在这种情况下,我们把注意力
放在设置传递给该系统调用的文件描述符为非阻塞 I/O 的代码上。在用户空间,正确应用该特
性的例子如下:
int on = 1;
ioctl(fd, FIONBIO, &on);
让我们看一下 get_user()的实现,代码在 include/asm/uaccess.h 文件中。
#define __get_user_x(size,ret,x,ptr) \
__asm__ __volatile__("call __get_user_" #size \
:"=a" (ret),"=d" (x) \
:"0" (ptr))
/* Careful: we have to cast the result to the type of the pointer for sign
reasons */
#define get_user(x,ptr) \
({ int __ret_gu,__val_gu; \
switch(sizeof (*(ptr))) { \
case 1: __get_user_x(1,__ret_gu,__val_gu,ptr); break; \
case 2: __get_user_x(2,__ret_gu,__val_gu,ptr); break; \
case 4: __get_user_x(4,__ret_gu,__val_gu,ptr); break; \
default: __get_user_x(X,__ret_gu,__val_gu,ptr); break; \
} \
(x) = (__typeof__(*(ptr)))__val_gu; \
__ret_gu; \
})
我们可以看到,get_user()被用一种非常聪明的方法实现。它基于要被从用户空间拷贝的参数
的大小来调用合适的内部函数。由(sizeof (*(ptr)))的值来决定是调用__get_user_1(),
__get_user_2()或__get_user_4()。
现在让我们看一下其中一个函数,__get_user_4(),代码在 arch/i386/lib/getuser.S
文件中。
addr_limit = 12
[..]
.align 4
.globl __get_user_4
__get_user_4:
addl $3,%eax
movl %esp,%edx
jc bad_get_user
andl $0xffffe000,%edx
cmpl addr_limit(%edx),%eax
jae bad_get_user
3: movl -3(%eax),%edx
xorl %eax,%eax
ret
bad_get_user:
xorl %edx,%edx
movl $-14,%eax
ret
.section __ex_table,"a"
.long 1b,bad_get_user
.long 2b,bad_get_user
.long 3b,bad_get_user
.previous
在.section与.previos之间的最后几行标识了异常处理器表。由于它对于我们目的的重要
current->addr_limit.seg 进行比较(该值存放在距离任务描述符其实
果该参 式可寻址范围内(PAGE_OFFSET 以下) 法地址空间之
缺页异常
“当被寻址的页面不在物理内存中,相应的页表项是空的,或违反了页面保护机制,缺页异常就
性,我们将在后面讨论该表。正象上面看到的,__get_user_4()的实现代码是很直白的
6
。参
数地址在%eax寄存器中。通过给%eax中的值加 3,可能获得最高的用户空间地址,这里是检查
一下该地址是否在用户模式可寻址范围之内(从 0x00000000 到 PAGE_OFFSET – 1,
PAGE_OFFSET通常是 0xC0000000)。
用户空间地址和
把
地址 12 字节的偏移处,而当前进程的任务描述符可以通过对内核堆栈指针的最后 13 位清零来
获得),如果我们发现它大于 PAGE_OFFSET – 1,则跳转到标号为 bad_get_user 处,对%edx
清零,把-EFAULT(-14)存入%eax 中(系统调用的返回值放在%eax 中)。
数的地址在用户模 ,但在进程合
如
外,会发生什么呢?那就是被有人称为 Page Fault(页故障或缺页错)的。
会发生。”【
参考资料 1】
Linux 用 do_page_fault() 函数来处理缺页异常。该处理器的代码在
模式触发的缺页异常的 3 种情况。
一种情况,“内核试图寻址属于进程地址空间的某页,但或者相应的页帧不在物理内存里(按
arch/i386/mm/fault.c 文件中。
们尤其感兴趣的是在内核
我
第
需调页),或者内核试图写只读页(写时复制)。”【
参考资料 1】
6
这当然是在你熟悉汇编语言的情况下。
剩余17页未读,继续阅读
资源评论
- warriorpaw2013-08-30在这谢过LZ了,英文正啃不动,搜索下标题看见你翻译的了,非常感谢
- u0100191582013-10-11这个文章很经典,适合阅读研究
wzhou1974
- 粉丝: 5
- 资源: 33
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- Screenshot_20240427_031602.jpg
- 网页PDF_2024年04月26日 23-46-14_QQ浏览器网页保存_QQ浏览器转格式(6).docx
- 直接插入排序,冒泡排序,直接选择排序.zip
- 在排序2的基础上,再次对快排进行优化,其次增加快排非递归,归并排序,归并排序非递归版.zip
- 实现了7种排序算法.三种复杂度排序.三种nlogn复杂度排序(堆排序,归并排序,快速排序)一种线性复杂度的排序.zip
- 冒泡排序 直接选择排序 直接插入排序 随机快速排序 归并排序 堆排序.zip
- 课设-内部排序算法比较 包括冒泡排序、直接插入排序、简单选择排序、快速排序、希尔排序、归并排序和堆排序.zip
- Python排序算法.zip
- C语言实现直接插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序、归并排序、计数排序,并带图详解.zip
- 常用工具集参考用于图像等数据处理
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功