Demon 시즌2/linux kernel exploitation

[linux kernel] (8) - Device Driver (디바이스 드라이버)

jir4vvit 2021. 1. 22. 16:19

[linux kernel] (1) - 커널(kernel)이란? 에서 디바이스 드라이버에 대해 간단하게 알아보았었다. 너무 간단하게 알아본 것 같기도 하고... 내용을 더 추가하고 싶은데 이어서 쓰면 글이 너무 길어질 것 같아서 따로 빼서 써보려고 한다..


Device Driver

  • user aplication이 정형화된 인터페이스를 통해 장치에 접근할 수 있도록 해주는 소프트웨어
  • 커널이 컴파일 될 때부터 포함된 디바이스 드라이버/ 별도로 컴파일되어 커널 부팅 후에 로드되는 디바이스 드라이버
  • 모듈의 일종, 디바이스 드라이버를 작성하는 것 = 모듈 프로그래밍, Makefile을 이용해 컴파일함

 

Character Device device를 파일처럼 접근하여 직접 read/write 수행
ex) keyboard. mouse
Block Device hard disk와 같은 file system을 기반으로 block 단위로 데이터를 read/write 수행
ex) hard disk, CD-ROM driver
Network Device network의 물리 계층과 frame 단위의 데이터 송수신
ex) Ethernet device driver

* CTF의 kernel exploit 문제는 대부분 Character Device Driver의 취약점을 찾아 권한 상승을 일으키는 것을 목표로 한다고 한다.

 

file_operations

  • Character Device Driver와 user aplication간의 커뮤니케이션을 위한 인터페이스 
  • linux/fs.h 에서 정의하는 이 구조체는 함수 포인터의 집합
  • 특정 동작 함수를 구현하여 가리켜야 함, 지정하지 않으면 NULL
  • 그러나 file_operations에 존재하는 함수의 개수에 제약이 있으므로 다른 기능 혹은 다른 함수를 원하는 경우엔 ioctl 사용
/*
 * NOTE:
 * read, write, poll, fsync, readv, writev can be called
 *   without the big kernel lock held in all filesystems.
 */
struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};

ioctl

#include <sys/ioctl.h>

int ioctl(int d, int request, ...)

ioctl 함수를 사용할 때 request 숫자를 전달하는데 이것이 ioctl에 의해 불리는 함수의 인덱스가 됨

-> ioctl 함수는 swtich 문과 같은 것을 이용해 request로 전달된 값을 비교해 해당 함수를 다시 호출함

 

 


아래는 하드 디스크의 시리얼 번호를 읽어 내는 기능을 하는 디바이스 드라이버

/* hddinfo.c */
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif

#define __NO_VERSION__
#include <linux/module.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/ide.h>
#include <asm/uaccess.h>

#define HDA			0x0300
#define HARDDISK	HDA

char kernel_version[] = UTS_RELEASE;

static int hddinfo_open(struct inode *node, struct file *f)
{
	return 0;
}

static int hddinfo_release(struct inode *node, struct file *f)
{
	return 0;
}

static ssize_t hddinfo_read(struct file *f, char *buf, size_t nbytes, loff_t *ppos)
{
	return nbytes;
}

static ssize_t read_serial(char *dst)
{
	ide_drive_t *drv;

	drv = get_info_ptr(HARDDISK);
	if (drv)
		copy_to_user(dst, drv->id->serial_no, 20);
	else
	{
		;//PDEBUG("HDDINFO : Cannot get the drive information\n");
		return 0;
	}

	return 20;
}

int hddinfo_ioctl(struct inode *node, struct file *f, unsigned int ioctl_num, unsigned long ioctl_param)
{
	switch (ioctl_num)
	{
		case 0 :
			read_serial((char *)(ioctl_param));
			break;
	}

	return 0;
}

struct file_operations Fops = {
	NULL,
	NULL,
	hddinfo_read,
	NULL,
	NULL,
	NULL,
	hddinfo_ioctl,
	NULL,
	hddinfo_open,
	NULL,
	hddinfo_release
};

int init_module()
{
	if (register_chrdev(212, "hddinfo", &Fops) < 0)
	{
		//PDEBUG("HDDINFO : Unable to register driver.\n");
		return -EIO;
	}

	return 0;
}

void cleanup_module()
{
	if (unregister_chrdev(212, "hddinfo") < 0)
		;//PDEBUG("HDDINFO : Unable to unregister\n");
}

file_operations Fops을 살펴보면 read/ioctl/open/release만을 사용한다.

open과 release는 이 디바이스를 open/close할 때 불린다. (여기서는 단순하게 0을 리턴한다.)

 

read를 사용해 하드 디스크의 시리얼 번호를 읽도록 해도 되지만 여기선 ioctl의 사용을 보기 위해 일부러 read에서 할 일을 ioctl로 봅아서 만들었다고 한다.

 

하드 디스크의 시리얼 번호는 커널 부팅할 때 이미 얻어진 하드디스크에 대한 정보를 갖고 있는 구조체에서 복사한다고 한다.

 

 

copy_to_user(A,B,C) : kernel land에서 user land로 데이터를 복사하는 함수. kernel land address B에서 C바이트 만큼 user land address A로 복사

 

copy_from_user(A,B,C) : user land에서 kernel land로 데이터를 복사하는 함수. user land address B에서 C바이트 만큼 kernel land address A로 복사

 

 

이 모듈의 사용을 아래와 같은 프로그램으로 동작시킨다고 한다.

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <fcntl.h>

int main()
{
	int fd;
	char buf[256];

	fd = open("/dev/hdd_info", O_RDWR);
	if (fd < 0)
	{
		printf("Device open error.\n");
		return -1;
	}

	ioctl(fd, 0, buf);

	printf("buf : %s\n", buf);

	close(fd);

	return 0;
}

일반적으로 디바이스를 사용하기 위해 디바이스 파일을 사용해 디바이스를 open한다. 여기에서 얻어지는 fd를 사용해 읽고 쓰기 동작 등을 한다. 다 사용했으면 close로 사용을 중지해준다.

 

open 후 ioctl의 0번 함수를 호출 해 hddinfo.c의 read_serial()을 호출하였다. (하드디스크의 시리얼 번호를 읽어옴) 

 

 

모듈 프로그래밍

mhko411.tistory.com/255

 

커널 모듈 프로그래밍 예제 (1) - Hello World

개요 앞으로 tc358746의 드라이버를 개발하고 사용하기 위해 커널 모듈 프로그래밍을 공부하기 위해 첫 번째 예제로 커널에 모듈을 삽입하고 삭제하는 예제를 진행했다. insmod를 통해 모듈을 커널

mhko411.tistory.com

위 블로그를 차근차근 따라해보았다. (깔끔하게 설명이 잘 되어있어서 그냥 첨부 한다.. ㅎ)

커널 로그를 확인함으로써 나도 실습 성공! 

 

 

 

*ref

더보기

butter-shower.tistory.com/29

wiki.kldp.org/KoreanDoc/html/EmbeddedKernel-KLDP/device-understanding.html