[ << ALL_FEED ]

Curing a problem

More in General

Curing a problem 🔧

Recently, researchers from ARMO presented a paper and PoC for the Curing malware, which uses the io_uring interface to bypass monitoring of file and network operations carried out via system calls.

🧐 To start, let’s talk about the io_uring interface itself. It appeared in kernel version 5.1 for performing asynchronous I/O operations. Before it, there was the Linux AIO interface, which also offered asynchronous I/O but had a number of drawbacks. The io_uring interface was developed with this in mind and offers a reduction in the overhead associated with performing the corresponding system calls by bypassing them, and also implements the data exchange itself inside the kernel, reducing context switching between the kernel and user space. For I/O, two ring buffers are used: the Submission Queue (SQ) and the Completion Queue (CQ), which are used to queue operations and retrieve results, respectively.

🪞 For an example, we’ll use the aforementioned PoC Curing. It reads the /etc/shadow file and sends it to a server, all by means of io_uring. Therefore, there is no point in configuring auditd or another auditing tool that uses system calls for monitoring, since after the interface is configured, these operations will be carried out bypassing syscalls. In this regard, to demonstrate auditing, we will use Tetragon — a ready-made eBPF-based engine with the ability to flexibly configure monitoring policies via the TracingPolicy mechanism. Using the kprobes attachment mechanism, we can hook into any kernel function for auditing. There are other interfaces that can be used to audit file access, such as fanotify, but Tetragon is more versatile.

It’s worth noting here that tools that track system calls are not entirely useless in this case: they can still intercept calls to the io_uring_setup and io_uring_enter functions used to create and interact with an instance of the io_uring interface. In this case, we will see that an application is performing actions with this interface, but we cannot obtain the details of the operation or its parameters. This is already something we can work with and flag such activity as suspicious, since applications typically do not rely on io_uring, and legitimate users of this interface can be whitelisted without much trouble. Hence, the very fact of accessing the interface will be suspicious behavior.

🫥 But in our case, we need continuous monitoring of files or sockets. Therefore, let’s create a simple policy for monitoring files via the LSM hook security_file_permission (screenshot 1, copy the code). The function itself takes two arguments: the path to the file and the requested permissions as a bitmask, so in the policy, using the Equal operator, we search for the file by its full path, and in the mask, using the Mask operator, we filter by the number 4, which means read access. And as a result, we get an event (screenshot 2) when Curing tries to read the file.

Similarly, we can track the connection to the server: in the policy (screenshot 3, copy the code) we use the kernel function tcp_connect to get the sock argument in it, where all the network information will be. Here we have no input data for filtering, and we will see all TCP connections, since the server port can be reassigned, but here the more important thing for us is the very fact that we can intercept the event (screenshot 4) — as we can see, Curing connects to its standard port 8888.

Even though io_uring does provide a way to bypass system calls, by using the right tools, it can be intercepted without much trouble.

#tip #news
@ptescalator

More from global_author

More from global_author

More in General