원래 글을 잘라서 쓰는 걸 좋아하지 않는데, 제가 번역이다 뭐다 좀 바빴습니다. 양해해 주세요. ^^; 오늘은 저번에 마무리 짓지 못한 나머지 부분을 써 볼까 합니다.
Kprobes에 세 가지 정도의 kernel probe가 준비되어 있다는 이야기는 했습니다. 이 중 커널로부터 뭔가 쓸만한 정보를 알아내는 데 가장 활용도가 높은 것은 Jprobe입니다. 커널 함수를 Hooking할 수 있을 뿐 아니라, 그 함수에 전달되는 인자들까지 살펴볼 수가 있거든요. Jprobe는 다음과 같이 선언합니다. 구조체죠.
#include <linux/kprobes.h>
...
static struct jprobe my_probe = {
.kp.addr = (kprobe_opcode_t *) udp_sendmsg,
.entry = (kprobe_opcode_t *) inst_udp_sendmsg
};
udp_sendmsg는 커널 함수의 이름입니다. inst_udp_sendmsg는 후킹 함수의 이름입니다. inst_udp_sendmsg는 udp_sendmsg가 받는 인자들을 똑같이 받도록 정의되어야 합니다. 가령 udp_sendmsg는 다음과 같이 선언되어 있습니다.
// linux/udp.h
void udp_sendmsg(struct kiocb* iocb, struct sock* sk, struct msghdr* msg, size_t len);
그러므로 inst_udp_sendmsg는 다음과 같이 정의되어야 합니다.
static void inst_udp_sendmsg(
struct kiocb* iocb,
struct sock* sk,
struct msghdr* msg,
size_t len)
{
...
jprobe_return();
}
이 함수의 제일 마지막에서 jprobe_return()을 호출하고 있다는 것을 유의해 보십시다. 이 함수를 호출하지 않으면, 후킹 함수가 1회 실행된 뒤에는 (그러니까 inst_udp_sendmsg가 실행된 뒤에) 커널이 hang되어 아무 작업도 할 수 없는 상태가 되므로 주의해야 합니다.
또 한 가지 주의할 것이 있군요. Jprobe를 정의할 때 구조체의 .kp.addr에 커널 함수의 이름(다시 말해, 커널 함수의 주소, 즉 포인터)을 저장하고 있음을 보실 수 있는데요. 커널 모듈을 컴파일하다 보면 컴파일러가 "그 함수의 정의를 찾을 수 없다"는 오류 메시지를 출력하는 경우를 간혹 보게 됩니다.
그런 경우에는 함수의 이름 대신 함수의 주소를 직접 넘겨주는 방법을 쓰는 것이 제일 속편합니다. 커널에 정의되어 있는 함수들의 주소는 어디 있냐면 (inline 함수 제외) /boot 아래에 있습니다. 이 아래에 보면 System.map-2.6.18-1.2798.fc6과 같은 파일이 있는데, 이 파일을 열어보면 커널 함수들과, 그 함수들의 주소가 전부 적혀 있습니다.
그러니 사실 jprobe를 정의할 때 이렇게 해도 됩니다.
static struct jprobe my_probe = {
.kp.addr = (kprobe_opcode_t *) 0xc05e7c29, // 정확한 값은 아닙니다. 대충 적었습니다.
.entry = (kprobe_opcode_t *) inst_udp_sendmsg
};
이 방법보다 좀 더 우아한 방법으로는 kallsyms_lookup_name()이라는 다른 커널 함수를 사용하는 방법도 있는데, 간혹가다가 컴파일해 보면 이 함수의 정의를 찾을 수 없다고 불평하는 메시지가 나올 때도 있습니다. -_-; 그러니 그냥 속편하게, System.map 파일을 참고하는 것이 제일 나을 것입니다.
아무튼 이렇게 하면, (1) 커널을 후킹하는 함수를 정의하고 (2) 그 함수가 특정 커널 함수 실행 직전에 실행되도록 하여 (3) 그 함수에 전달되는 인자의 값을 검사할 수 있습니다.
가령 위에서 예로 든 예제 함수의 경우, UDP 패킷의 목적지 주소를 알아내고 싶다면 다음과 같이 하면 됩니다.
static void inst_udp_sendmsg(
struct kiocb* iocb,
struct sock* sk,
struct msghdr* msg,
size_t len)
{
struct inet_sock* up = (struct inet_sock*)sk;
printk("destination address = %u\n", ntohl(up->daddr) );
jprobe_return();
}
자. 그런데 JProbe를 하나 정의한 것만으로는 사실 불충분하구요. 이 프로브를 커널에 명시적으로 심어주는 register_jprobe를 호출해 주어야 비로서 프로브가 커널 함수 호출을 hooking할 수 있게 됩니다. 이 함수 호출 형식은 모든 JProbe 프로브들에 대해서 동일하니까, 다음과 같이 매크로를 정의해두면 좀 편리하게 써먹을 수 있을 것 같습니다.
#define REGISTER_JPROBE(obj) \
do { \
if ( register_jprobe(&obj) < 0 ) { \
printk("registration operation for " #obj " has failed\n"); \
} \
} while ( 0 )
#define UNREGISTER_JPROBE(obj) unregister_jprobe(&obj)
이 매크로들은 이전 글에서 설명했던 init_module() 함수와 cleanup_module() 함수에서 다음과 같이 사용합니다.
int init_module(void)
{
REGISTER_JPROBE(my_probe);
...
return 0;
}
void cleanup_module(void)
{
UNREGISTER_JPROBE(my_probe);
}
자. 이렇게 하면 커널 프로브를 심는 모듈을 정의하고, 사용할 수 있습니다. 생각보다 어렵지 않습니다. KProbe나 Kretprobe도 이와 비슷한 방식으로 사용이 가능합니다. 나중에 그에 관해서는 따로 소개할 기회가 있을것 같군요. 오늘은 이정도로 마무리 하겠습니다.
'Systems > Unix / Linux' 카테고리의 다른 글
| /usr/bin/ld: crt1.o: No such file: No such file or directory (0) | 2008/06/14 |
|---|---|
| Fedora Core 6에 Skype 설치 (0) | 2008/06/05 |
| Kprobes를 사용한 Linux Kernel Hooking (2) (5) | 2008/03/18 |
| Kprobes를 사용한 Linux Kernel Hooking (1) (0) | 2008/03/06 |
| Ubuntu on Windows XP with VMWare Player: 가장 쉬운 방법 (0) | 2008/01/08 |
| 리눅스 서버에 telnet이 잘 안될때는 이렇게 (0) | 2007/11/22 |
댓글을 달아 주세요
안녕하세요 ~!
2008/08/28 13:33 [ ADDR : EDIT/ DEL : REPLY ]kprobe에 관한 흥미있는 글 잘 읽었습니다 :D
궁금한게 있는데요 kprobe로 후킹을 하다보니 후킹이 되는 함수가 있는가 하면
안되는 함수들도 있더라구요 특히 register_jprobe()를 사용할 때 등록이 안되는 경우가 많습니다.
Sysmap으로 확인할 때 코드가 T 인 경우와 t 인경우에 따라서 안되는 경우가 있나요???
예로 sys_read나 do_fork는 잘 후킹이 되는데 put_queue와 같은 함수는 잘 안되네요 .. ^^;
안녕하세요? 코드가 T인 경우와 t인 경우에 차이가 있는지는 여지껏 생각해보지 않은 문제인데... 새로운 숙제를 주시는군요. ^^;; 제가 요즘 이쪽일을 안하고 있어서.. 시간나는대로 한번 살펴보겠습니다. 좋은 질문 감사해요~
2008/08/29 09:41 [ ADDR : EDIT/ DEL ]http://unixhelp.ed.ac.uk/CGI/man-cgi?nm 여기를 찾아보니 T는 text section에 있는 global symbol이고 t는 local symbol이라는군요. 제 생각에 local symbol은 참조가 불가능할 것 같습니다. 그 scope가 해당 symbol이 정의된 파일 안으로 한정될것 같거든요. (C에서 static 키워드가 하는 일을 생각해보시면 쉬울듯) 실제로 lxr.linux.no사이트에서 검색해보니 put_queue는 static으로 정의되어 있네요.
2008/08/29 10:00 [ ADDR : EDIT/ DEL ]앗 .. 이렇게나 빠른 답변을 ^^
2008/08/29 12:01 [ ADDR : EDIT/ DEL : REPLY ]정말 감사합니다 ~! 대소문자가 전역과 지역을 나타내는 거였군요
local symbol에서는 참조가 안된다니 아쉽네요 ㅋ
다른 방법을 강구해봐야 겠군요 ~
커널 2.6.x에서 IDT 후킹을 하고 있는데 이게 잘 안되서 지금 죽을맛이에요 ㅎㅎ
좋은 답변 감사 드립니다 ^^!
별말씀을... 제가 도움이 될것같은 일이 있다면 또 들러주세요. ^^
2008/08/29 13:12 [ ADDR : EDIT/ DEL ]