{
	"id": "933313df-b2ba-45a0-bb13-8ce0d81433e8",
	"created_at": "2026-04-06T00:20:00.625312Z",
	"updated_at": "2026-04-10T13:11:50.190137Z",
	"deleted_at": null,
	"sha1_hash": "1e4da3967338cbf1921a5ca7713a990c040e7f23",
	"title": "Reptile's Custom Kernel-Module Launcher",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 63007,
	"plain_text": "Reptile's Custom Kernel-Module Launcher\r\nPublished: 2024-11-10 · Archived: 2026-04-05 16:42:43 UTC\r\nIntroduction\r\n“In REPTILE version 2.0, the original developer of REPTILE altered how the Kernel-level component\r\nis loaded, switching from using insmod to a custom launcher. The launcher Mandiant observed\r\nUNC3886 use throughout their operations, based on the custom launcher, was updated with a new\r\nfunction to daemonize a process.” — Mandiant, Cloaked and Covert: Uncovering UNC3886 Espionage\r\nOperations, 2024.\r\nThis analysis will examine how the Reptile rootkit loader bypasses the standard Linux insmod command for\r\nloading Kernel modules and will explore methods for detecting the use of this custom loader.\r\nAnalysis\r\nWithin the Makefile, a random 32-bit hexadecimal value is generated (stored in the RAND2 variable) each time\r\nthe Kernel module is compiled. This value serves as the encryption key for the Reptile Kernel object, making it\r\ndifficult to identify the module through simple hash searches or hex value hunts on the filesystem. The encrypted\r\nKernel module is stored in reptile.ko.inc .\r\nRAND2 = 0x$(shell cat /dev/urandom | head -c 4 | hexdump '-e\"%x\"')\r\n[..]\r\nreptile: $(LOADER)\r\n@ $(ENCRYPT) $(BUILD_DIR)/reptile.ko $(RAND2) \u003e $(BUILD_DIR)/reptile.ko.inc\r\n@ echo \" CC $(BUILD_DIR)/$@\"\r\n@ $(CC) $(INCLUDE) -I$(BUILD_DIR) $\u003c -o $(BUILD_DIR)/$@\r\nThe custom loader is defined in loader.c. Here, the encrypted Kernel object is loaded into a statically allocated\r\narray named reptile_blob :\r\nstatic char reptile_blob[] = {\r\n#include \"reptile.ko.inc\"\r\n};\r\nThe core of the custom launcher involves decrypting reptile_blob , copying it to module_image , and then\r\ncalling init_module to load it:\r\nlen = sizeof(reptile_blob);\r\ndo_decrypt(reptile_blob, len, DECRYPT_KEY);\r\nmodule_image = malloc(len);\r\nhttps://dfir.ch/posts/reptile_launcher/\r\nPage 1 of 3\n\nmemcpy(module_image, reptile_blob, len);\r\ninit_module(module_image, len, \"\");\r\nThe decryption process is handled by the do_decrypt function (source code here) :\r\ndo_decrypt\r\ndo_decrypt  and  do_encode  are the same function, as defined in a macro before.\r\nstatic inline void do_encode(void *ptr, unsigned int len, unsigned int key)\r\n{\r\nwhile (len \u003e sizeof(key)) {\r\n*(unsigned int *)ptr ^= custom_rol32(key ^ len, (len % 13));\r\nlen -= sizeof(key), ptr += sizeof(key);\r\n}\r\n}\r\nThis function decrypts the Kernel module by:\r\n1. Performing a bitwise XOR operation between the data and a rotated value derived from the key and\r\nlen.\r\n2. Applying a custom_rol32 function to further obfuscate the data by rotating bits.\r\ncustom_rol32\r\nstatic inline unsigned int custom_rol32(unsigned int val, int n)\r\n{\r\nreturn ((val \u003c\u003c n) | (val \u003e\u003e (32 - n)));\r\n}\r\nThis function performs a rotate left operation on a 32-bit integer. The val is shifted left by n bits, and the\r\noverflowed bits are “rotated” back to the right end. This is a common bitwise operation used in encryption\r\nalgorithms to add an extra layer of obfuscation.\r\nThe decrypted Kernel module is then loaded using the init_module system call, bypassing the standard insmod\r\ncommand:\r\ninit_module()\r\nThe init_module system call loads a Kernel module into the Linux Kernel at runtime. Essentially, it allows a\r\nprogram to insert a Kernel module programmatically, functioning similarly to the insmod command executed from\r\nthe command line.\r\nThe function takes three key parameters:\r\nmodule_image : This is the compiled Kernel module code that will be loaded into the Kernel.\r\nhttps://dfir.ch/posts/reptile_launcher/\r\nPage 2 of 3\n\nlen : Specifies the size (in bytes) of the module_image, informing the Kernel how much data to read.\r\nparam_values : Allows passing initialization parameters to the module, similar to how you would provide\r\nparameters when using insmod from the command line.\r\nIn the Reptile rootkit, a macro is defined to simplify the process of invoking init_module . By using the macro,\r\nthe code bypasses standard C library functions and directly interfaces with the system call:\r\n#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)\r\nThis direct syscall approach allows the custom loader to insert the Kernel module without relying on higher-level\r\nfunctions, which can help avoid detection mechanisms that monitor typical command-line usage patterns.\r\nDetection\r\nThe custom loader circumvents standard Linux detections that rely on monitoring for insmod commands, such as\r\nthis Elastic Security rule (source):\r\nprocess where host.os.type == \"linux\"\r\nand event.type == \"start\"\r\nand process.name == \"insmod\"\r\nand process.args : \"*.ko\"\r\nHowever, using auditd_manager rules can still enable detection by monitoring the actual syscalls used to load\r\nKernel modules. The detailed process to enable and configure auditd_manager is explained on the Kernel Driver\r\nLoad rule page from Elastic (source).\r\ndriver where host.os.type == \"linux\"\r\nand event.action == \"loaded-kernel-module\"\r\nand auditd.data.syscall in (\"init_module\", \"finit_module\")\r\nWe are not monitoring for a command ( insmod ) but for a syscall ( init_module ), which the custom reptile\r\nloader uses under the hood. This might be enough to catch the loading of a malicious Kernel module on a server.\r\nIf such a module is loaded, it might taint the Kernel and give us also a heads-up that something fishy is going on. I\r\nwrote about Tainted Kernels before.\r\nSource: https://dfir.ch/posts/reptile_launcher/\r\nhttps://dfir.ch/posts/reptile_launcher/\r\nPage 3 of 3",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://dfir.ch/posts/reptile_launcher/"
	],
	"report_names": [
		"reptile_launcher"
	],
	"threat_actors": [
		{
			"id": "9df8987a-27fc-45c5-83b0-20dceb8288af",
			"created_at": "2025-10-29T02:00:51.836932Z",
			"updated_at": "2026-04-10T02:00:05.253487Z",
			"deleted_at": null,
			"main_name": "UNC3886",
			"aliases": [
				"UNC3886"
			],
			"source_name": "MITRE:UNC3886",
			"tools": [
				"MOPSLED",
				"VIRTUALPIE",
				"CASTLETAP",
				"THINCRUST",
				"VIRTUALPITA",
				"RIFLESPINE"
			],
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "a08d93aa-41e4-4eca-a0fd-002d051a2c2d",
			"created_at": "2024-08-28T02:02:09.711951Z",
			"updated_at": "2026-04-10T02:00:04.957678Z",
			"deleted_at": null,
			"main_name": "UNC3886",
			"aliases": [
				"Fire Ant"
			],
			"source_name": "ETDA:UNC3886",
			"tools": [
				"BOLDMOVE",
				"CASTLETAP",
				"LOOKOVER",
				"MOPSLED",
				"RIFLESPINE",
				"TABLEFLIP",
				"THINCRUST",
				"Tiny SHell",
				"VIRTUALGATE",
				"VIRTUALPIE",
				"VIRTUALPITA",
				"VIRTUALSHINE",
				"tsh"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "1c91699d-77d3-4ad7-9857-9f9196ac1e37",
			"created_at": "2023-11-04T02:00:07.663664Z",
			"updated_at": "2026-04-10T02:00:03.385989Z",
			"deleted_at": null,
			"main_name": "UNC3886",
			"aliases": [],
			"source_name": "MISPGALAXY:UNC3886",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434800,
	"ts_updated_at": 1775826710,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/1e4da3967338cbf1921a5ca7713a990c040e7f23.pdf",
		"text": "https://archive.orkl.eu/1e4da3967338cbf1921a5ca7713a990c040e7f23.txt",
		"img": "https://archive.orkl.eu/1e4da3967338cbf1921a5ca7713a990c040e7f23.jpg"
	}
}