[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()을 호출하였다. (하드디스크의 시리얼 번호를 읽어옴)
모듈 프로그래밍
위 블로그를 차근차근 따라해보았다. (깔끔하게 설명이 잘 되어있어서 그냥 첨부 한다.. ㅎ)
커널 로그를 확인함으로써 나도 실습 성공!
*ref
butter-shower.tistory.com/29
wiki.kldp.org/KoreanDoc/html/EmbeddedKernel-KLDP/device-understanding.html
'Demon 시즌2 > linux kernel exploitation' 카테고리의 다른 글
[linux kernel] (10) - SMEP 우회 (0) | 2021.01.31 |
---|---|
[linux kernel] (9) - KASLR 우회 (0) | 2021.01.29 |
[linux kernel] (7) - 2018 QWB ctf : core (ret2usr) (1) | 2021.01.17 |
[linux kernel] (6) - CSAW 2010 Kernel Exploit (0) | 2021.01.02 |
[linux kernel] (5) - 환경 세팅 (0) | 2020.12.30 |