linux执行一条指令取址,Linux下实现劫持系统调用的总结(下)--原理分析

本文欢迎自由转载,但请标明出处和本文链接,并保持本文的完整性。

Dec 2, 2009

二、实现原理分析

实现的方法就是通过中断向量表,找到系统调用的中断向量,再通过系统调用时执行的指令,最终找到系统调用表的地址,

进行系统调用的替换。

(一)中断向量表地址的获取

中断向量表(IDT)的入口地址是通过IDTR寄存器来确定的。IDTR寄存器的内容如下图所示。

uid-14327709-id-3036928.html

这就是上面代码中定义结构体

struct idtr {

unsigned short limit;

unsigned int base;

} __attribute__

((packed))

的原因。

idtr寄存器的内容可以通过汇编指令sidt取出,然后就可以IDT的入口地址idtr.base,即高32bit。

(二)系统调用中断向量地址的获取

下一步就通过IDT找到系统调用中断即(0x80)的地址。下图即一个中断向量的组成。由此可以见,一个中断向量占用8个字节。因此,系统调用中断的地址可以表示为:

idt = idtr.base + 8 * 0x80

uid-14327709-id-3036928.html

(三)系统调用处理例程地址的获取

获取到系统调用中断的地址后,同样需要一个数据结构,将中断向量描述符的相关内容保存。数据结构的定义如下:

struct idt {

unsigned short off1;

unsigned short sel;

unsigned char none, flags;

unsigned short off2;

} __attribute__

((packed));

通过以上数据结构就可以得到系统调用中断发生时的中断处理例程的地址,即函数system_call()函数的地址:

sys_call_off

= idt.off2 << 16 | idt.off1

该函数是用汇编实现的,位于arch/i386/kernel/entry.S,下面截取该函数的部分实现:

# system call handler stub

ENTRY(system_call)

RING0_INT_FRAME# can't unwind into user space anyway

pushl %eax# save orig_eax

CFI_ADJUST_CFA_OFFSET 4

SAVE_ALL

GET_THREAD_INFO(%ebp)

testl $TF_MASK,EFLAGS(%esp)

jz no_singlestep

orl $_TIF_SINGLESTEP,TI_flags(%ebp)

no_singlestep:

# system call tracing in

operation / emulation

/* Note, _TIF_SECCOMP is bit number 8, and

so it needs testw and not testb */

testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)

jnz syscall_trace_entry

cmpl $(nr_syscalls), %eax

jae syscall_badsys

syscall_call:

call

*sys_call_table(,%eax,4)

movl %eax,EAX(%esp)# store the return value

(四)系统调用表地址的获取

从上面代码中,我们可以看到,系统调用表的入口地址就是上面倒数第二行中sys_call_table。那么如果获取到sys_call_table的地址呢。由于这行代码执行了函数调用的指令call,该指令对应的指令码为\xff\x14\x85。因此,我们只要在system_call函数体内搜索的前三个字节为\xff\x14\x85的内存地址,然后将该地址加3,即可获取到sys_call_table的地址。同时,还有一个问题需要确定,就是call *sys_call_table(,%eax,4)这条指令相对于system_call函数体的偏移是多少,这样我们可以确定内存搜索的范围。下面是entry.o函数反汇编的部分代码:

000000d0

:

d0:50push%eax

d1:fccld

d2:06push%es

d3:1epush%ds

d4:50push%eax

d5:55push%ebp

d6:57push%edi

d7:56push%esi

d8:52push%edx

d9:51push%ecx

da:53push%ebx

db:ba 7b 00 00 00mov$0x7b,%edx

e0:8e damovl%edx,%ds

e2:8e c2movl%edx,%es

e4:bd 00 f0 ff

ffmov$0xfffff000,%ebp

e9:21 e5and%esp,%ebp

eb:f7 44 24 30 00 01 00testl$0x100,0x30(%esp)

f2:00

f3:74 04jef9

f5:83 4d 08 10orl$0x10,0x8(%ebp)

000000f9 :

f9:66 f7 45 08

c1 01testw$0x1c1,0x8(%ebp)

ff:0f 85 bf 00 00 00jne1c4

105:3d 3e 01 00 00cmp$0x13e,%eax

10a:0f

83 27 01 00 00jae237

00000110

:

110:ff 14 85 00 00 00 00call*0x0(,%eax,4)

117:89 44 24 18mov%eax,0x18(%esp)

通过以上反汇编代码的倒数第二行可以看到,该行即执行call *sys_call_table(,%eax,4)的代码。那么该执行相对于函数体的偏移为0x110-0xd0 = 0x40。我们实际的代码中使用偏移值100作为搜索的最大范围。

至于反汇编出来为什么只是call *0x0(,%eax,4),个人理解应该是该模块尚未与其他模块进行链接的原因。当生成内核镜像vmlinux之后,反汇编vmlinux,然后找到system_call函数,就可以看到指令call *0x0(,%eax,4)中0x0被替换为有效的地址。本人也已经在2.6.18.3的vmlinux上验证过了,实际的代码如下:

c1003d04

:

c1003d04:ff 14 85 e0 14 1f c1call*0xc11f14e0(,%eax,4)

c1003d0b:89 44 24 18mov%eax,0x18(%esp)

因此可以看出,我当前系统的sys_call_table的地址为0xc11f14e0。

(五)系统调用的替换

一旦我们获取到了系统调用表的地址,需要需要替换那些系统调用,只需要将系统调用表的某个系统调用指向自己实现的系统调用即可。

#define

REPLACE(x) old_##x = my_table[__NR_##x];\

my_table[__NR_##x] = new_##x

REPLACE(open);

另外,需要注意的是,在替换系统调用的时候,要先清CR0的第20位并记录原始值,不然在替换sys_call_table的时候会报错。在替换完毕之后,再将CR0的原始值恢复,代码如下:

orig_cr0 =

clear_and_return_cr0();

setback_cr0(orig_cr0);

以上为Linux劫持系统调用的总结。欢迎多多交流,如果不妥之处,请大家指正。

参考链接:

1.

3.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值