Home Linux Kernel Exploit Development (Part 2/10)
Post
Cancel

Linux Kernel Exploit Development (Part 2/10)

Linux Kernel Exploit Development with VMware - Lab2::Module Debugging


목차

  1. 목차
  2. The Kernel Module
  3. Debugger Attach to Kernel Module
  4. Debug Kernel Module

The Kernel Module

TestVM의 mod_sample 디렉토리에 sample.c 파일이 존재한다.

sample.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
 * Test kernel module
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/device.h>

#define DEVICE_NAME "sample"

static int dev_counter = 0;

static int device_open(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t , loff_t *);
static int device_close(struct inode *, struct file *);

static struct class *class;
static int major_no;

static struct file_operations fops = {
	.open = device_open,
	.release = device_close,
	.read = device_read
};

static int __init load(void) {
	printk(KERN_INFO "Sample module loaded\n");
	major_no = register_chrdev(0, DEVICE_NAME, &fops);

	class = class_create(THIS_MODULE, DEVICE_NAME);
	device_create(class, NULL, MKDEV(major_no, 0), NULL, DEVICE_NAME);


	return 0;
}

static void __exit unload(void) {
	device_destroy(class, MKDEV(major_no, 0));
	class_unregister(class);
	class_destroy(class);
	unregister_chrdev(major_no, DEVICE_NAME);

	printk(KERN_INFO "Sample module unloaded\n");
}

static int device_open(struct inode *inode, struct file *file) {
	if (dev_counter)
		return -EBUSY;

	dev_counter++;

	return 0;
}

static int device_close(struct inode *inode, struct file *f) {
	dev_counter--;

	return 0;
}

static ssize_t device_read(struct file *file, char *buf, size_t size, loff_t *off) {
	char tmp[5] = "test";
	printk(KERN_INFO "%s\n", tmp);

	return 0;
}

module_init(load);
module_exit(unload);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("vnik");
MODULE_DESCRIPTION("Test kernel module.");
MODULE_VERSION("0.1");

위 코드에서 module_init(load);을 통해 module을 등록하고, load함수에서는 device_create() 호출을 통해 디바이스를 등록한다.

해당 디바이스를 열거나, 닫거나, 읽을 때 fops 구조체 안의 필드에 명시된 포인터의 함수가 호출되게 된다.

간단한 명령어를 통해 확인이 가능하다. 먼저 module을 make한 후 insmod 명령을 통해 등록한다.

Makefile

1
2
3
4
5
6
7
8
obj-m += sample.o
ccflags-y += -g

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Compile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
test@ubuntu:~/exercises/mod_sample$ make
make -C /lib/modules/3.5.0-23-generic/build M=/home/test/exercises/mod_sample modules
make[1]: Entering directory `/usr/src/linux-headers-3.5.0-23-generic'
  CC [M]  /home/test/exercises/mod_sample/sample.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/test/exercises/mod_sample/sample.mod.o
  LD [M]  /home/test/exercises/mod_sample/sample.ko
make[1]: Leaving directory `/usr/src/linux-headers-3.5.0-23-generic'
test@ubuntu:~/exercises/mod_sample$ ls
Makefile       Module.symvers  sample.ko     sample.mod.o
modules.order  sample.c        sample.mod.c  sample.o
test@ubuntu:~/exercises/mod_sample$ sudo insmod ./sample.ko
test@ubuntu:~/exercises/mod_sample$ lsmod | grep sample
sample                 12695  0
test@ubuntu:~/exercises/mod_sample$ ls /dev/sample
/dev/sample
test@ubuntu:~/exercises/mod_sample$

이어 /dev/sample 디바이스를 cat 명령어로 읽은 후 dmesg 명령으로 커널 로그를 확인해본다.

1
2
3
4
test@ubuntu:~/exercises/mod_sample$ sudo cat /dev/sample
test@ubuntu:~/exercises/mod_sample$ dmesg | tail -n 1
[ 5217.787281] test
test@ubuntu:~/exercises/mod_sample$

Debugger Attach to Kernel Module

lab1과 비슷하게 먼저 breakpoint를 걸어보자. sample.ko 내부의 함수에 breakpoint를 걸어야 하기 때문에 module의 segmentation 주소를 적어둔다.

TestVM

1
2
3
4
5
test@ubuntu:~/exercises/mod_sample$ sudo cat /sys/module/sample/sections/{.text,.data,.bss}
0xffffffffa018f000
0xffffffffa0191000
0xffffffffa0191338
test@ubuntu:~/exercises/mod_sample$

이후 위 주소를 가지고 MasterVM에서 sample.ko 모듈의 symbol을 추가한다.

MasterVM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[email protected]:~# gdb -q kernels/vmlinux-3.5.0-23-generic
Reading symbols from kernels/vmlinux-3.5.0-23-generic...done.
(gdb) target remote 192.168.94.1:8864
Remote debugging using 192.168.94.1:8864
native_safe_halt ()
    at /build/buildd/linux-lts-quantal-3.5.0/arch/x86/include/asm/irqflags.h:50
warning: Source file is more recent than executable.
50	}
(gdb) add-symbol-file ./modules/sample.ko 0xffffffffa018f000 -s .data 0xffffffffa0191000 -s .bss 0xffffffffa0191338
add symbol table from file "./modules/sample.ko" at
	.text_addr = 0xffffffffa018f000
	.data_addr = 0xffffffffa0191000
	.bss_addr = 0xffffffffa0191338
(y or n) y
Reading symbols from ./modules/sample.ko...done.
(gdb)

이제 sample.ko 모듈 내의 함수 device_read()에 breakpoint를 걸어보자.

1
2
3
4
(gdb) b * device_read
Breakpoint 1 at 0xffffffffa018f050: file /home/vnik/exercises/mod_sample/sample.c, line 62.
(gdb) c
Continuing.

TestVM

1
test@ubuntu:~/exercises$ sudo cat /dev/sample

MasterVM

1
2
3
4
5
6
7
8
(gdb) c
Continuing.

Breakpoint 4, device_read (file=0xffff880078d3c200,
    buf=0xce3000 <error: Cannot access memory at address 0xce3000>, size=32768,
    off=0xffff88007b477f48) at /home/vnik/exercises/mod_sample/sample.c:62
62	/home/vnik/exercises/mod_sample/sample.c: No such file or directory.
(gdb)

BreakPoint에서 정상적으로 interrupt된다.


Debug Kernel Module

이제 device_read() 함수에서 출력하는 문자열 "test""thisisaverylongstring" 이라는 문자열로 변경해보자.

우선 device_read() 함수의 printk() 호출 부분에 BreakPoint를 건다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(gdb) disassemble device_read
Dump of assembler code for function device_read:
   0xffffffffa018f050 <+0>:	push   %rbp
   0xffffffffa018f051 <+1>:	mov    %rsp,%rbp
   0xffffffffa018f054 <+4>:	sub    $0x10,%rsp
   0xffffffffa018f058 <+8>:	nopl   0x0(%rax,%rax,1)
   0xffffffffa018f05d <+13>:	mov    $0xffffffffa0190024,%rdi
   0xffffffffa018f064 <+20>:	mov    %gs:0x28,%rax
   0xffffffffa018f06d <+29>:	mov    %rax,-0x8(%rbp)
   0xffffffffa018f071 <+33>:	xor    %eax,%eax
   0xffffffffa018f073 <+35>:	lea    -0xd(%rbp),%rsi
   0xffffffffa018f077 <+39>:	movl   $0x74736574,-0xd(%rbp)
   0xffffffffa018f07e <+46>:	movb   $0x0,-0x9(%rbp)
   0xffffffffa018f082 <+50>:	callq  0xffffffff81685975 <printk>
   0xffffffffa018f087 <+55>:	xor    %eax,%eax
   0xffffffffa018f089 <+57>:	mov    -0x8(%rbp),%rdx
   0xffffffffa018f08d <+61>:	xor    %gs:0x28,%rdx
   0xffffffffa018f096 <+70>:	jne    0xffffffffa018f09a <device_read+74>
   0xffffffffa018f098 <+72>:	leaveq
   0xffffffffa018f099 <+73>:	retq
   0xffffffffa018f09a <+74>:	nopw   0x0(%rax,%rax,1)
   0xffffffffa018f0a0 <+80>:	callq  0xffffffff81052a90 <__stack_chk_fail>
End of assembler dump.

MasterVM

1
2
3
4
(gdb) b * device_read + 50
Breakpoint 5 at 0xffffffffa018f082: file /home/vnik/exercises/mod_sample/sample.c, line 64.
(gdb) c
Continuing.

TestVM

1
test@ubuntu:~/exercises$ sudo cat /dev/sample

MasterVM

1
2
3
4
5
6
7
(gdb) c
Continuing.

Breakpoint 5, device_read (file=<optimized out>, buf=<optimized out>, size=32768,
    off=0xffff8800776dff48) at /home/vnik/exercises/mod_sample/sample.c:64
64	/home/vnik/exercises/mod_sample/sample.c: No such file or directory.
(gdb)

printk() 함수의 두번째 인자는 x64 호출 규약에 의해 rsi 레지스터에 존재하므로 gdb에서 vmalloc()함수를 통해 받은 임의의 주소에 "thisisaverylongstring" 문자열을 쓴 후, rsi 레지스터를 해당 주소값으로 변경해준다.

MasterVM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(gdb) c
Continuing.

Breakpoint 1, device_read (file=<optimized out>, buf=<optimized out>, size=32768,
    off=0xffff880078f73f48) at /home/vnik/exercises/mod_sample/sample.c:64
64	in /home/vnik/exercises/mod_sample/sample.c
(gdb) p vmalloc(32)
$4 = (void *) 0xffffc90000372000
(gdb) set {char[25]}$4="thisisaverylongstring\n"
(gdb) x/s $4
0xffffc90000372000:	"thisisaverylongstring\n"
(gdb) set $rsi=$4
(gdb) x/s $rsi
0xffffc90000372000:	"thisisaverylongstring\n"
(gdb) c
Continuing.

TestVM

1
2
3
test@ubuntu:~$ dmesg | tail -n 1
[ 3127.299859] thisisaverylongstring
test@ubuntu:~$

성공적으로 "thisisaverylongstring" 문자열이 출력되었다.

다음 Lab으로 넘어가자.

This post is licensed under CC BY 4.0 by the author.