LKRG

LKRG is free and Open Source project distributed primarily in source code form. You can download it and prepare custom build by yourself. However, if you would rather use a commercial product tailored for your specific operating system, please consider LKRG Pro, which is distributed primarily in the form of “native” packages for the target operating systems and in general is meant to be easier to install and use while delivering optimal performance. Additionally, you will help in development of the project (economically). LKRG Pro is available <here>.

LKRG - experimental branch

Given that LKRG correctly prevents unauthorized modifications, experimental branch includes three useful user-mode protections (called “Protected Features”) which are not available in the “main” branch:

Protected process – process can’t be intercepted in any form by anyone from usermode (even by superuser – root).

Protected logs – log file can’t be modified / deleted by anyone even by superuser – root, but it's still possible to append new log entries.

“Experimental” branch has completely different communication channel logic (please read this section) comparing to “main” one which uses sysctl interface (please read this section). Additionally, it also includes a self-defense code which is not available in “main” one.

LKRG-experimnetal is being hosted on the bitbucket git repository, which can be cloned to the local directory via following command:

$ git clone https://bitbucket.org/Adam_pi3/lkrg-experimental.git

Protected features

Experimental branch of LKRG includes a specially designed Protected Features as well as different communication channel comparing to the “main” branch.

Protected process

The main idea behind this feature is to be able to harden certain processes even from the highest privileged accounts (like “root” account) and to warrant that some secrets remain inaccessible. Proper implementation of Protected Process (PP) is not trivial from a couple of reasons:

Linux was never designed to have any protection from superuser account (as opposite to SELinux).

Linux exposes various interfaces which might be consumed to interact directly or indirectly with process and memory.

By definition, superuser can apply/change/remove any limitation in the system.

Some processes might require access to other process for proper functionality.

There is couple of “official” ways how to interact with the processes, and all of them needed to be hardened:

Syscalls – e.g. ptrace()

Signals – e.g. [t[g]]kill()

TODO: /proc interface – e.g. /proc/<pid>/mem

Additionally, process can be affected via direct memory access:

Raw memory access – e.g. via /dev/mem device

Kernel memory access – e.g. via /dev/kmem device

LKRG leverages *kprobes interfaces to “lock down” all possible ways of interacting with processes. As an end-result no one from user-space is able to interact with the process protected by LKRG. There is one exception for compatibility reasons:

Protected process might fork itself and for the security reasons if this ever happens, child must be automatically protected as well. Because mother and child often must communicate with each other, every protected process might interact with each other using official API. Exactly the same as non-protected processes in Linux. In practice LKRG creates a “new group” of processes which are isolated from “normal” Linux processes. These “new” processes have higher privileges since they can normally interact with all types of processes (including PP), where normal process has no access to the Protected Processes at all.

LKRG maintains its own red-black tree to track all PP. When process dies it is automatically removed from the list. There are 2 ways how the process might become Protected Process:

Communication channel has an option to provide a PID of already existing process to be dynamically linked as part of PP. As soon as LKRG receives that message, process will be protected.

Every executable file which is part of “Protected File” feature, automatically becomes PP if it is going to be executed. There are 2 main reasons for that:

One of the way to compromise PP feature is to overwrite executable file from which the process was created. Next, time when this file will be executed, it might include attackers’ code. To mitigate this problem, “Protected File” feature can be used.

Some processes might not exist yet, but administrator might still want them to be PP when they will be executed. It is the way to inform LKRG which executable file must be protected from end-to-end.

Protected file

The main idea behind this feature is to be able to harden certain files even from the highest privileged accounts (like “root” account) and to warrant that some secrets remain inaccessible. Proper implementation of Protected File (PF) is not trivial from a couple of reasons:

Linux was never designed to have any protection from superuser account (as opposite to SELinux).

Linux exposes various of interfaces which might be consumed to interact directly or indirectly with files.

By definition, superuser can apply/change/remove any limitation in the system.

Files can be modified / deleted using official API, as well as indirectly changed via raw disk access. Both methods must be stopped:

Official API – LKRG leverages IMMUTABLE bit in “inode” structure in VFS to “lock down” the file. Special efforts are taken to be sure that “inode” and corresponding “dcache” is never dropped or deleted by Linux kernel during entire lifetime. Even if superuser (root) forces kernel to drop all caches and make “clean” file access, LKRG will protect from deleting PF inodes and Linux kernel won’t do it. Additionally, specific file operation function pointer for the protected file (and directory holding this file) are replaced by LKRG functions for security reasons.

Raw disk access – LKRG does not allow any process (excluding protected processes) to have raw access to the memory neither to the disk. This blocks indirect file and process modification. LKRG virtually extends CAP_SYS_RAWIO capability to achieve this solution, since in normal Linux system, this capability does NOT block raw disk access. Additionally, LKRG forces all process to drop CAP_SYS_RAWIO (excluding protected processes). Both features (PP and PF) might create end-to-end trust chain to protect specific files and executable files for being protected from superuser (root) attacks. Protected file feature might be used to “lock down” critical configuration files (e.g. /etc/ld.so.preload, /etc/ssh/sshd_config, so on) as well as executable file. Executable file in the end creates Protected Process which is also protected from superuser (root) attacks. Some critical processes might heavily benefit from that (e.g. ssh-agent), but protection will be fully trusted only if all shared object (external libraries) will be protected as well.

There could be 2 ways of doing it:

Individually add to the Protected File list all libraries which are consumed by the processes which we want to protect

Compile executable as a static binary – it won’t use any dynamically linked libraries.

2nd option is used by LKRG client user-mode application. Additionally, LKRG makes it PF by default.

Protected logs

Works exactly the same as PF feature but allows file to be opened only in “append” mode. The main idea behind this feature is to be able to harden certain files even from the highest privileged accounts (like “root” account) and to warrant that some secrets remain inaccessible but at the same time give a possibility to append new information to it. Such a functionality might be desired for certain files (e.g. log files) to provide warranty that information which was already written down won't be modified neither deleted.

Limitation

Proper implementation of the Protected Features requires from LKRG to leverage and virtually extend CAP_SYS_RAWIO capability. Unfortunately, some of the software (e.g. Xorg or doesmu) requires this capability for proper behavior (e.g. accessing /dev/mem device which LKRG must lock down). If Protected Features are being enabled such a software won't properly work.

Mitigation

If you still want to use Protected Features and run the software which requires access to /dev/mem device (uses CAP_SYS_RAWIO capability) you might initialize and run desired software before you load LKRG into the kernel. By doing it, desired software should properly gain necessary resources and correctly use it and LKRG will lock down access to that device after that fact.
Note: If for any reasons such a software tries to regain access to restricted device it won't be allowed anymore and proper functionality will be broken.

LKRG files - experimental branch

Comparing to the “main” branch, more files are generated in “experimental” one. After successful compilation of the project, “output” directory is created with the following files:

p_lkrg.ko

This is the core of the project - same as in “main” branch. Module itself accepts one parameter which determines the default logging level. By default, it is “active” level (levels will be described in details in the next section). Each level of logging is represented by numbers. By providing numbers between 0-4 or 0-6 (if compiled with debugging option) LKRG will immediately use this level even during installation in the kernel memory. It is a very useful option for debugging purpose when LKRG encounters a problem during installation in the system:

Installation

Installation of LKRG is exactly the same as loading normal kernel module. LKRG will fail to install if it detects the following pre-existing files:

”/root /.DELETE_ME_p_lkrg_password.txt” – used for storing the password.

”/root/.p_lkrg-pf” – used as an indicator of initialized protected features.

After successful installation, new randomly generated password (of random length) will be stored in the password file and correct initialization of protected features will create indicator file. This password is used for changing control data in LKRG. During execution of user mode client, it will ask to provide this password. Please be aware that you need to delete the password file as soon as possible after installation and do not allow accessing it from the machine where LKRG is running. The best option is to store it on 3rd part USB device not connected to the machine. Protected features file is automatically protected as Protected File by LKRG. No-one from user-space is capable to delete this file which is a good indicator if LKRG is loaded and avoids “multiple” load of the project:

As soon as system is installed it starts the work. If default logging level is used, LKRG produces one short sentence saying that system is clean unless corruptions are detected. Otherwise LKRG informs where corruption happened, e.g.:

If system is started with more detailed log level, more information will be visible, e.g.:

(this is not the latest version of the project so might not have acquired all information)

If debugging compilation is available, it may produce enormous amount of information regarding the running system (including every function name where LKRG is entering and leaving, etc.). Sometimes even default “active” log level might be too noisy (every time when LKRG checks the system, one-line status information will be produced in case the system is clean). That’s why “none” log level option was introduced. System at that log level is quiet until abnormal situation is detected by LKRG. In that case only alerting logs will be produced.
After LKRG is inserted in the kernel memory it can’t be unloaded. You must reboot machine to remove it unless “unhiding” compilation has been done (by default it should be) and that option will be visible in user-mode client.

client/cli/p_lkrg-client

This is user mode application which consumes (parses and changes) kernel mode configuration module. You can look at it as a “wrapper” which makes communication channel between administrator and LKRG as easy as it could be. First let’s look closer at a kernel configuration module:

This module accepts two parameters:

“pi3_pass” – expects to provide valid string password generated by LKRG during installation in the system. If password matches, module’s .text section will be analysed to find markers and in the end it parses control structure. If everything goes well, the module is loaded in the kernel (even if blocking module functionality is enabled) but parameter is never leaked to the user mode (protected by ACL).

“pi3_path” (optional parameter) – expects path to the file which will be (un)protected as PF. If user wants to change PF, this argument will be used in the communication channel. Parameter is never leaked to the user mode (protected by ACL).

What user-mode client essentially does, is that it tries to parse kernel module (ELF_REL object) to discover .text section (which is next modified) and tries to load it in the kernel by providing password as module parameter. If administrator changes PF, second parameter is filled as well. In theory user could do it by hand but it will require more technical work (like parsing ELF and finding correct offset in the .text section where control structure resides).

User mode client application and kernel control module is compiled at the same time as LKRG. If debugging compilation is done, user-mode application will understand 2 extra logging levels which is not available in normal/production compilation, e.g.:

The following options are available:

timestamp (-t switch) – changes how often kernel timer will be launched (kernel timer periodically calls integrity function). It can’t be less than 5 seconds (to not eat too much system resources) and not more than 1800 seconds (half an hour) – to not be silent for too long

log level (-l switch) – it might be number between 0-4 or 0-6 (if debugging compilation was used). Strong debug provides very useful data to identify where could be a specific problem with LKRG (if it ever appears). Unfortunately, it produces tons of logs per execution and must be used only for debugging purpose, not as normal run.

Protected Process functionality (-P switch) – if user wants to (un)protect any existing process, this switch informs about desired action. IT MUST BE USED WITH ”-p” switch!

0 – unprotect process

1 – protect process

(Un)protect process pid (-p switch) – if user chose to (un)protect process via ”-P” switch, this argument expects PID of the desired process. MUST BE USED WITH ”-P” switch!

Protected File functionality (-S switch) – if user wants to (un)protect any existing file, this switch informs about desired action. IT MUST BE USED WITH ”-s” switch!

0 – unprotect file

1 – protect file

(Un)protect file path (-s switch) – if user chose to (un)protect file via ”-S” switch, this argument expects path to the desired file. MUST BE USED WITH ”-S” switch!

Force (-f switch) – forces LKRG to run integrity function right now.

Configuration module path (-m switch) – full path to the kernel configuration module. If not provided, default one will be used.

Example of usage:

* Yellow box is only visible in debug compilation (echoing password)

This specific example of the communication with LKRG disables blocking modules functionality. At the beginning I tried to insert floppy module, but it was correctly blocked by LKRG. Next I tried to change this procedure (to allow modules to be loaded). I entered the correct password and user-mode client successfully pushed new configuration to the LKRG. Next I tried to load module again and it was successful. This operation could be reversed (if blocking module feature is not enabled and modules can be loaded, we can enable blocking module feature again). Entire operation can be seen in the logs (active log level was used in the example):

First, LKRG detected new module activity and blocked it from loading. Next (after disabling blocking module feature), we can see in logs a floppy module correctly initialized itself and printed some information. LKRG rebuilt database and next time when integration routine was launched, the new module was taken into account (it was already in the database) and module itself was protected.

At the end of running the user mode client application a warning message is always printed. Client detects if administrator entered correct password by analysing if control module was correctly loaded to the system. If password is wrong, LKRG by default should block module loading (blocking module feature is enabled by default) and returning code is analysed by user-mode client. In case blocking module feature is disabled, loading control module will always be successful and there is no way to detect whether password was correct or not.

User-mode client was specially designed to limit the possibility of leaking entered password. Custom password reading function was implemented to read character one-by-one, while buffering and echoing the entered character were disabled (echoed character is replaced by a star).
Entire password is kept for short period of time in memory and zeroed as soon as used in syscall to load the module.

Application loads entire control module to the memory. Any modification is done only in private memory page of the process and none of the modifications is reflected in the real control module file. Client loads modified module to the kernel using memory pointer – because the process is a part of Protected Process nobody can ever interact with this memory.

Protected Process example

After successful installation of LKRG in the system, by default the following files are being marked as protected:

User-mode client application

Control module

LKRG itself

As soon as anyone in user-land executes LKRG client application it becomes a protect process. Even superuser (like root) can’t interact with such processes:

Attaching to the process:

Reading from the process memory:

Sending signals:

Dumping process memory:

Any existing process in the system can be dynamically added or removed from the Protected Process group. As soon as it happens process will be guarded or unguarded. E.g.:

Let’s verify process which we want to protect (e.g. ssh-agent):

Let’s add this process (ssh-agent) to be a part of Protected Process:

Now, verify if protection works (for ssh-agent):

Attacking via RAW memory access:

Let’s now remove this process (ssh-agent) to be a part of Protected Process:

Protected Logs example

TODO

Self-defence

Implementation of Protected Features relies on *kprobes interface. It is known that this interface was not designed for a security purpose and can be easily manipulated by administration of the system. The Following *kprobes configuration files are exposed by “debugfs”:

/sys/kernel/debug/kprobes/blacklist

/sys/kernel/debug/kprobes/enabled

/sys/kernel/debug/kprobes/list

From the LKRG perspective the most critical are “enabled” and “list”:

“enabled” – can globally disable/enable all planted *kprobes

“list” – can leak all addresses hooked by LKRG

LKRG makes special effort to block those interfaces:

Otherwise bypassing LKRG would be trivial! E.g.:

Additionally, LKRG removes any reference to itself from sysfs, debugfs also unlinks itself from module list, deletes any KOBJs related to itself and cleans ddebug_table. All of that is not sufficient, but in the current stage of the project it is not a priority. It will be improved!