Character Device Driver: 버퍼 캐시를 사용하지 않고 데이터를 한번에 하나의 문자를 읽고 쓰는 드라이버
Block Device Driver : 버퍼 캐시를 통한 임의 접근과 블록 단위 입출력이 가능한 드라이버
Network Device Driver : 네트워크 스택과 네트워크 하드웨어 사이에 위치해 데이터의 송수신을 담당하는 드라이버
file_operations 구조체는 Charactor Device, Block Device 드라이버와
일반 프로그램 간의 통신을 위해 제공되는 인터페이스 입니다.
read,write,open 등의 함 포인터들을 사용할수 있다는 특징을 가지고 있고
Network Device 드라이버는 위의 구조체를 사용하지 않는 대신,
include/linux/netdevice.h 파일의 net_device 구조체를 사용합니다.
예를들어 디바이스 모듈에 open함수를 제공하고 싶다면 위와같이 하면 됩니다.
file operations 구조체에 작성된 함수 포인터의 인자값을 이용해서 open 함수의 코드를 작성합니다.
작성한 함수명 struct file_operations 형태로 선언된 구조체의 .open 필드에 chardev_open 함수의 주소를 저장합니다.
chardev.c 의 동작을 대략적으로 서술하면
디바이스 모듈이 커널에 등록될때 chardev_init() 함수가 동작합니다.
그리고 이 함수에서는 register_chardev() 함수에 의해 해당 문자 디바이스의 메이저 번호를 등록합니다.
유저 공간에서 디바이스가 open 할때 chardev_open() 함수가 호출됩니다.
그리고 디바이스 모듈이 커널에서 제거될때 chardev_exit() 함수가 동작합니다.
해당 함수는 unregister_chrdev() 함수에 의해 해당 문자 디바이스의 메이저 번호가 제거됩니다.
이제 이 디렉토리에서 코드를 빌드하면
다음과 같이 성공적으로 빌드됩니다.
이제 빌드한 커널 모듈을 다음과 같이 커널에 적재하면 되는데
mkmod 명령어로 적재된 모듈을 디바이스 파일로 생성하고, chmod 명령어로 일반 유저들이 읽고 쓸수 있도록 설정합니다.
그리고 chardev_open() 함수의 동작을 확인하기 위해 echo 명령어를 사용해서 디바이스 파일을 열어서 'A'를 저장합니다.
이렇게 하면 dmesg 명령어로 chardev_open 메세지를 확인할수 있는데,
이것은 echo 명령어에 의해 chardev_open() 함수가 동작되었다는것을 알수 있습니다.
명령어 요약
chardev_init()
- alloc_chrdev_region() 함수를 이용하여 Character Device의 번호를 시스템에 등록합니다.
- major(),mkdev()함수를 이용하여 디바이스에서 사용할 Major, Minor 번호를 취득합니다.
- cdev_init() 함수를 이용하여 chardev_cdev 구조체를 초기화합니다.
- cdev_add() 함수를 이용하여 Character Device를 시스템에 추가합니다.
- class_create() 함수를 이용하여 시스템에 생성할 디바이스의 클래스를 생성합니다.
- device_create() 함수를 이용하여 시스템에 디바이스를 생성합니다.
chardev_exit()
- device_destroy() 함수를 이용하여 device_create() 함수에 의해 생성된 디바이스를 제거합니다.
- class_destroy() 함수를 이용하여 class_create() 함수에 의해 생성된 디바이스 클래스를 소멸시킵니다.
- cdev_del() 함수를 이용하여 cdev_add() 함수에 의해 추가된 Character Device를 제거합니다.
- unregister_chrdev_region() 함수를 이용하여 alloc_chrdev_region() 함수에 의해 등록된 장치 번호를 반환합니다.
chardev_open()
- kmalloc() 함수를 이용하여 Kernel heap 영역에 data 구조체의 크기만큼의 공간을 할당받습니다.
- strlcpy() 함수를 이용하여 str변수에 저장된 값을 p→buffer 영역에 복사합니다.
chardev_release()
- kfree() 함수를 이용하여 할당받은 Heap영역을 해제합니다.
chardev_write()
- copy_from_user() 함수를 이용하여 사용자 공간으로 부터 전달 받은 데이터(buf)를 "p->buffer" 변수에 복사합니다.
chardev_read()
- copy_from_user() 함수를 이용하여 커널 영역에 저장된 "p->buffer" 데이터를 "buf" 변수에 복사합니다.
다음 예제는 struct file_operations 에서 사용할수 있는 .open .release .read .write 를 사용하는 예제입니다.
먼저 맨 위에 작성한 커널 모듈을 빌드한뒤
그 다음에 모듈을 커널에 등록하기 전에, /etc/udev/rules.d/ 경로에 모듈 등로시 자동으로 생성되는 디바이스 파일의 규칙을 저장합니다.
insmod 명령어를 이용하여 모듈을 커널에 등록하면 /dev/ 경로에 자동으로 chardev0, chardev1 두개의 디바이스가 생성됩니다.
- 해당 디바이스는 접근권한은 666이기 때문에 일반 유저들도 해당 디바이스를 사용할 수 있습니다.
그리고 test.c 를 컴파일한뒤 실행해보면 아래와 같이 성공적으로 커널 모듈이 동작한다는것을 확인할수 있습니다.
아래와 같이 동일한 디바이스를 중복으로 열수도 있고 동일한 모듈에 2개의 디바이스를 생성할수도 있습니다.
'잡다한 보안 공부 > Linux kernel exploit' 카테고리의 다른 글
[pwn.college] Kernel Security Challenges (0) | 2023.11.12 |
---|---|
리눅스 커널 익스플로잇 공부를 위한 커널 프로그래밍 공부 (1) (0) | 2023.06.09 |