본문 바로가기
정상을향해/OS·Kernel Driver·Rootkit

2장. 커널 조작 - 디바이스 드라이버 기본 개념

by 사이테일 2014. 1. 10.

루트킷은 유저 모드(User Mode)와 커널 모드(Kernel Mode) 요소를 모두 쉽게 포함할 수 있다.


유저 모드에서는 네트워킹, 원격 제어와 같은 대부분의 기능을 담당하고,


커널 모드에서는 은닉 기능과 하드웨어 접근에 관련된 기능을 담당한다.


대부분의 루트킷은 커널 레벨에서의 조작을 필요로 한다.




유저모드와 커널모드 요소를 모두 포함하고 있는 루트킷



유저 모드 프로그램은 커널 레벨 드라이버와 다양한 방법으로 서로 통신할 수 있다.


그 중에서 가장 일반적인 방법이 I/O Control(IOCTL) 명령을 이용하는 것이다.


IOCTL 명령은 유저 애플리케이션과 커널 드라이버간의 통신을 위해서 프로그래머가 정의하는 명령이다.


유저 모드와 커널 모드 요소를 모두 포함하는 루트킷을 만들기 위해서는 이후에 설명되는 디바이스 드라이버의 개념들을 모두 이해해야 한다.



IRP


디바이스 드라이버를 이해하려면 I/O Request Packet(IRP)을 이해해야 한다.


윈도우 디바이스 드라이버는 유저 모드 프로그램과 통신하기 위해서 IRP에 대한 처리가 필요하다.


IRP는 데이터 버퍼를 포함하는 데이터 구조체라고 할 수 있다.


유저 모드 프로그램이 파일을 열어서 그 파일에 데이터를 쓰면 커널 레벨에서는 이 작업을 IRP를 이용해 표현한다.


유저 모드 프로그램이 "HELLO DRIVER!"라는 문자열을 파일에 쓰면 커널은 "HELLO DRIVER!"라는 문자열이 포함된 IRP를 생성한다.


이처럼 IRP를 통해서 유저 모드와 커널 모드간의 통신이 이뤄진다.


커널 드라이버는 IRP를 처리하는 함수를 가지고 있어야만 IRP를 처리할 수 있다.


IRP를 처리하기 위한 함수는 언로드 루틴과 마찬가지로 드라이버 오브젝트(Driver Object)에 해당 함수를 등록하면 된다.



#include "ntddk.h"

NTSTATUS OnStubDispatch (IN PDEVICE_OBJECT DeviceObject,
			IN PIRP Irp )
{
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	return STATUS_SUCCESS;
}

//unload
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
	DbgPrint("OnUnload called\n");
}

NTSTATUS DriverEntry ( IN PDRIVER_OBJECT theDriverObject,
			IN PUNICODE_STRING theRegistryPath )
{
	int i;
	theDriverObject->DriverUnload = OnUnload;
	for (i =0; i MajorFunction[i] = OnStubDispatch;
	}
	return STATUS_SUCCESS;
}


드라이버의 Major Function의 주소는 하나의 배열에 저장되고 read, write, ioctl에 해당하는 Major Function의 배열에서의 인덱스 값은 IRP_MJ_READ, IRP_MJ_WRITE, IRP_MJ_DEVICE_CONTROL로 정의된다.


예제에서는 모든 Major Function에 대한 함수가 OnStubDispatch 함수에서는 아무런 작업도 수행하지 않는다.


실전에 사용되는 대부분의 드라이버는 각 Major Function에 대한 함수를 각각 개별적으로 갖는다.


예를 들어서 READ, WRITE 이벤트를 처리한다고 가정해 보자.


이 이벤트들은 유저 모드 프로그램이 드라이버 핸들을 이용해서 ReadFile, WriteFile을 수행했을 때 발생한다.


좀 더 복잡한 드라이버는 유저 모드 프로그램에 의한 파일 닫기나 IOCTL 명령 등을 처리할 것이다.