SSDT Hooking Last Update : 2006 년 11 월 10 일 Written by Jerald Lee Contact Me : lucid78@gmail.com 본문서는커널모드후킹기술중의하나인 SSDT 후킹에대해정리한것입니다. 제가알고있는지식이너무짧아가급적이면다음에언제봐도쉽게이해할수있을정도로쉽게쓸려고노력하였습니다. 제가작성하였던기존의 Windows Hook 시리즈가유저모드에서의후킹을다루었던반면본 SSDT는커널모드에서의후킹을사용하므로디바이스드라이버 (WDM) 를다루는부분이포함되어있습니다. 윈도우디바이스드라이버의경우많은부분을상세히설명하지는못했으나코드작성에필요한부분을중점으로설명하였습니다. 다양한분야의방대한자료들중에서필요한것만골라요약하다보니두서없이써내려간부분도많이있습니다. 읽으시는분들의양해를부탁드립니다. 이번에도역시기존에나와있는여러문서들을짜집기하는형태로작성되었으며기술하지못한원문저자들에게매우죄송할따름입니다. 본문서는읽으시는분들이어느정도 Windows API를알고있다는가정하에쓰여졌습니다. 본문서에서자세히다루지못한내용은참고자료를살펴보시기바랍니다. 제시된코드들은 Windows XP Professional Service Pack 2, Windows Server 2003 SP1 DDK 1 에서테 스트되었습니다. 문서의내용중틀린곳이나수정할곳이있으면연락해주시기바랍니다. 1 http://www.microsoft.com/whdc/devtools/ddk/default.mspx 1
목차 WINDOWS SYSTEM의구조...3 NATIVE API...4 SYSTEM SERVICE DESCRIPTOR TABLE... 11 DEVICE DRIVER 기초및활용...15 SSDT HOOK...33 참고자료...35 2
Windows System 의구조 먼저 Windows System 에대해알아봅니다. 이문서를읽으시는많은분들이이미해당내용을잘알 고계실테지만만약을위해간략하게설명해봅니다. Windows는유저모드 (User Mode) 와커널모드 (Kernel Mode) 로구분되어프로세스가실행됩니다. 커널모드에서동작하는프로세스만이컴퓨터에장착된모든메모리와하드웨어에대한직접적인접근이가능하며디바이스드라이버가가장대표적인예라고할수있습니다. 일반적인윈도우프로그램의경우유저모드프로세스가작동하는것이므로직접하드웨어장치나메모리에접근할수는없습니다. 하드웨어장치나메모리에접근하기위해유저모드프로그램은시스템서비스 (API라고이해하시면될듯합니다.) 를호출하게되고운영체제는 trap을발생시켜커널모드로의스위칭을위한스레드를호출하게됩니다. 이후이스레드에서하드웨어장치또는메모리에접근하게됩니다. 이를 context switch( 공룡책에는문맥교환이라고번역되어있습니다.) 라고합니다. 정확한시점은알수없지만 (-_-;) 어느시점이후부터최근에제작되는 OS는대부분마이크로커널의형태를가집니다.( 좀더엄밀하게말해서수정된마이크로커널의형태를가집니다.) 운영체제에서프로세스스케쥴링이나메모리관리등과같은가장핵심적이고필수적인부분만커널에구현이되어있고기타기능은서브시스템에서관리하는형태를띄게됩니다. 아래그림은 Windows Internal에서발췌한간단한형태의 Windows 구조입니다. [Simplified Windows architecture] 3
Native API 위에서도잠시설명했지만유저모드프로세스는하드웨어장치나메모리에접근하기위해커널모드로스위칭을해야합니다. 일반적으로사용하는 Win32 API는이러한기능을자동으로수행하는데사실 Win32 API는운영체제에서제공하는서브시스템중의하나입니다. 위의그림에서살펴볼수있듯이 Win32, OS/2, Wow, POSIX 등은모두서브시스템환경의일부이며 이는운영체제가다른기종에서작성된프로그램들의호환을위해제공하는것들입니다. 각각의서브 4
시스템들은커널모드에서동작하는서비스 ( 시스템서비스라고합니다.) 들을이용하여하드웨어장치나 메모리에접근하게되며이때사용되는커널모드서비스들은 Native API 라고불리는함수들을호출하 여이러한작업들을수행하게됩니다. 조금더구체적으로알아봅시다. Microsoft는보안을이유로유저모드에서동작하는코드가직접커널모드에서동작하는코드를 call 하는것을허용하지않습니다. MFC 개발자들이디렉토리구조를알기위해서자주사용하는 FindFirstFile(), FindNextFile() 함수가있습니다. FindFirstFile() 로디렉토리를찾은후디렉토리내모든구조를 FindNextFile() 이찾게됩니다. 프로그래머는단순히위의두 API를호출함으로써임의의디렉토리구조를알수있지만실제운영체제내부에서는더많은작업들이수행되게됩니다. 즉, 유저모드코드 FindNextFile() 은 NtQueryDirectoryFile() 이라는 Native API를호출하고 NtQueryDirectoryFile() 은동일한이름의커널모드코드의 Wrapper 역할을함으로써디렉토리구조를얻을려는목적을이루게됩니다. Visual Studio를설치하면기본적으로설치되는 Dependency Walker을이용해 ntdll.dll을열어보면동일한이름을가진 (Nt, Zw로구분되는 ) 함수가존재함을볼수있습니다. NtQueryDirectoryFile() API 5
ZwQueryDirerctoryFile() API Native API는운영체제에서사용하기위해만들어진것이며대부분이문서화되어있지않습니다. 하지만수많은해커들의노력에의해많은수의 Native API가 Undocument API 라는이름으로문서화되기에이르렀습니다. Native API들은보통 Nt 또는 Zt 로시작하는이름을가집니다. 유저모드에서동작하는프로그램이 Native API를이용하려면 ntdll.dll 파일을통해야만하며 ntdll.dll 파일은모든시스템서비스들의진입점을포함하고있습니다. MSDN에따르면 Zw로시작하는함수의경우해당함수를호출한프로세스에대한접근권한체크를하지않습니다. 좀더자세한설명은아래의 URL을참고하시기바랍니다. http://msdn.microsoft.com/library/default.asp?url=/library/enus/kernel_r/hh/kernel_r/k111_80b1882a-8617-45d4-a783-dbc3bfc9aad4.xml.asp 이과정을그림으로나타내면아래와같습니다. 6
위의그림과같이 ntdll.dll은커널모드로진입하기위해인터럽트를사용합니다. INT 2E는 XP 이전버전의운영체제에서사용되던방법이며성능상의이유로 XP부터는 SYSENTER을사용합니다. INT 2E는소프트웨어인터럽트, SYSENTER는하드웨어인터럽트라고생각하시면될듯합니다. 이내용은다음절에서다루도록하겠습니다. 그럼실제함수는어떻게동작하도록되어있는지살펴봅시다. 위에서잠깐언급했었던 NtReadFile을예로들어보도록하겠습니다. 이작업은드라이버스튜디오또는 WinDbg가준비되어있다면가능합니다. 본문서에서는드라이버스튜디오를이용하였습니다. 여기서부터는 Devguru의 Attack Native API 의내용을참고하였음을미리밝힙니다. 먼저유저모드에서의함수동작을알기위해 Softice를실행시키기전 NtReadFile 함수가들어있는 ntdll.dll을로드해야합니다.( 앞에서도이야기했지만 ntdll.dll 파일에유저모드에서커널모드로의진입점이존재합니다 ). 이함수는디폴트로로드되지않습니다. 아래그림은 Driver Studio에포함된 Symbol Loader 프로그램을이용해 ntdll.dll 을로드하는화면을캡쳐한것입니다. 7
File LoadExports 를클릭하여 ntdll.dll 을로드합니다. 로드에성공하면위의그림에서빨간색부분처럼 ntdll 부분이생성됩니다. 8
이제 Softice 를실행시킵니다. Softice 가성공적으로실행된후 Ctrl + D 를누르면 SoftIce 화면으로 진입하게됩니다. Softice 로진입한화면 위의화면에서빨간색으로표시된부분이명령창입니다. Vmware 를통한이미지캡쳐라실제명령어 를수행한결과화면은캡쳐할수없어서텍스트형태로결과를보이도록하겠습니다. 9
먼저명령어창에 u ntdll!ntreadfile을입력한결과입니다. : u ntdll!ntreadfile ntdll!ntreadfile mov eax, 000000b7 mov edx, ntdll!kifastsystemcall call edx ret 0024 : u ntdll!kifastsystemcall ntdll!kifastsystemcall mov edx, esp sysenter eax 레지스터에 NtReadFile 의주소인 0xb7 값을넣고 KiFastSystemCall 을호출합니다. KiFastSystemCall 에서는 edx 에 esp 값을넣은후 SysEnter 을호출하게됩니다. 다음으로 ZwReadFile을살펴보겠습니다. 명령창에 u ntdll!zwreadfile을입력하면 NtReadFile 값이나오는것을확인할수있습니다. 즉, Wrapper인 ntdll을통한 ( 유저모드에서의 ) NtReadFile, ZwReadFile은결국 NtReadFile 함수를호출하게됩니다. 유저모드에서는커널모드함수인 ZwReadFile을호출하더라도결국 NtReadFile을호출하게된다는말입니다. 커널모드 (Ntoskrnl.lib) 에서의 NtReadFile, ZwReadFile 은모두정상적으로해당함수를실행합니다. 명령창에서직접 u ntoskrnl!ntreadfile, u ntoskrnl!zwreadfile 을실행해보시기바랍니다. 10
System Service Descriptor Table Windows는수많은테이블들의집합이라고할수있습니다. 앞에서살펴보았듯이인터럽트가발생하면해당인터럽트의종류에따른결과를반환하게됩니다. 인터럽트가발생했을때어떤시스템서비스가호출되어야하는지에대한판단을하기위해윈도우는테이블을참조하게됩니다.( 정확히얘기하면 CPU는해당시스템서비스들이위치하고있는메모리상의주소를알아야할필요가있습니다. 하지만모든주소를내부적으로저장할수없기때문에 CPU는테이블이라는자료구조를사용하는것이며윈도우, 즉운영체제는이를이용하는것입니다. 좀더자세하고정확한내용은 Windows Internals를참고하시기바랍니다.) 이런테이블들 (CPU가참조하는 ) 에는여러종류의테이블이있으며아래와같습니다. GDT : Global Descriptor Table LDT : Local Descriptor Table IDT : Interrupt Descriptor Table 이런테이블들을통칭하여 System Service Descriptor Table이라고부릅니다. 운영체제또한자신만의테이블을만들어참조하는방법을사용하는데운영체제에서구현된중요한 테이블중에아래의테이블이있습니다. SSDT : System Service Dispatch Table 바로이문서의목표인 SSDT 테이블입니다. 이테이블은시스템에서이용가능한모든시스템서비스들의주소를가지고있으며인터럽트발생시운영체제는이테이블을참고하여적절한결과값을돌려주게됩니다. 이테이블을간단히조작 / 변경함으로써시스템의모든서비스를다룰수있기때문에커널루트킷, 안티바이러스프로그램양쪽모두에사용됩니다. 커널루트킷의경우에는프로세스 / 파일 / 디렉토리은닉등을위해사용됩니다. SSDT 를이용한후킹은 GDT, IDT 등과복합형태의후킹코드로서이용되기도합니다. 여기서앞절에서잠시나왔던 INT 2E와 SYSENTER을알아봅시다. 커널모드의 System Call을위해 Intel 기반하에서 Windows는 INT 2E 또는 SYSENTER을이용합니다. Windows 2000과이전버전의 Windows는유저모드에서커널모드로진입하기위해소프트웨어인터럽트인 INT 2E를이용했습니다. 인터럽트가발생하면 0x2E 값은 Interrupt Descriptor Table(IDT) 에서 Processor가참조하는 offset 값이됩니다.( 일반적으로 EAX 레지스터에들어가는값들은대부분 Offset으로쓰입니다.) 이 Offset이가리키는테이블의값은 System Service Dispatcher( 일반적으로 KiSystemService라고합니다.) 의주소입니다. CPU는 Instruction Pointer Register(IP Register) 에이값을 Load하고 System Service Dispatcher가실행되게됩니다. System Service Dispatcher는 System 11
Service Dispatch Table(SSDT) 에기록된 System Service를실행하게되며 ( 실행할 System Service의구분은 EAX 레지스터를참조하며 EDX 레지스터에서시스템서비스에전달된매개변수의목록-메모리주소-을참조합니다. Windows NT의경우 EDX 레지스터가아니라 EBX 레지스터를사용합니다.) 유저모드에서호출된 API가재호출되게됩니다. 즉, 커널모드에서 API가수행되게됩니다. Windows XP에서는 INT 2E 대신 SYSENTER이라고하는 Instruction을사용하게됩니다. INTEL은 SYSENTER Instruction을 CPU Instruction Set에포함시킴으로써성능상의향상을꾀했습니다. 이 Instruction은펜티엄 2 이상의 CPU에서지원하기시작하였으며 Windows 2000에서는 INT 2E와 SYSENTER을같이사용했으나 XP부터는 SYSENTER만을사용하게됩니다. 윈도우는이 Instruction을지원하기위해부팅할때명령과연결된레지스터에서커널의 System Service Dispatcher 루틴의주소를저장합니다. ntdll.dll이 SYSENTER Instruction을발생시키면 Processor는 IA32_SYSENTER_EIP 라는특수 Register 에저장된값, 즉 System Service Dispatcher의주소를체크하여 IP Register에 Load 하고실행시킵니다. INT 2E와마찬가지로실행할 System Service의구분은 EAX 레지스터에저장된값을참조하며전달되는매개변수의정보는 EDX 레지스터를참조합니다. 유저모드로되돌아가기위해서는일반적으로 SYSEXIT Instruction을사용합니다. AMD 의경우 SYSCALL 이라는 SYSENTER 과비슷한 Instruction 을사용하는데이는생략하도록하겠습 니다. 더자세한정보는 Windows Internals 와참고자료 web)5 를살펴보시기바랍니다. 위에서설명한과정을도식하면아래와같습니다. 12
-FindFirstFile() And FindNextFile(), Inside Windows Rootkits 에서발췌 - 본문서에서다루고자하는 SSDT Hooking 의목적은바로아래그림으로써표현될수있습니다. 13
- SSDT Hooking, Inside Windows Rootkits 에서발췌 - 위의그림에서볼수있듯이 Rootkit을중간에삽입시키는것이본문서의목적이라고할수있겠습니다. 자그럼어떻게 Rootkit을삽입시키는가, 아니그전에어떻게 SSDT를후킹하여우리가만든 Rootkit 으로향하게하는가, 그것이해결해야할가장중요한문제이며그답은디바이스드라이버에있습니다. 14
Device Driver 기초및활용 자디바이스드라이버시간입니다. 기존의 SDK 프로그래밍에익숙한사용자라면약간어색할듯하고프로그래밍에자신이없는분이라면매우어려울듯합니다. 몇년전만해도윈도우드라이버프로그래밍은윈도우프로그래밍의꽃이다라고생각되었었습니다.( 저만의견해인가요? -_-;). 자료라고는그저 MSDN 밖에없던시절, 극악무도또는진짜독한사람들만이하는분야라고취급되었었습니다. 그만큼어렵다는이야기입니다. -_-; 그러던것이시간이흘러한글로된윈도우디바이스드라이버책도나올만큼대중화 (?) 되었습니다만여전히그난해함은있습니다. 이번절은정말정말필요한부분만을모아놓았으니심화학습을원하시는분들은드라이버관련전문 서적을읽으시고정말당장써먹을수있는게필요해 ~ 하시는분들은 RootKits 1 를읽으시길바랍니다. 자이제시작해보겠습니다. 먼저 RING에대한설명으로시작해야할것같습니다. 앞절에서유저모드와커널모드에대한설명이있었습니다. 이것은운영체제에의한분류 (?) 법이라고할수있습니다. INTEL CPU에서는 ( 이하 x86) 이를 Ring이라고부르며우리가잘알고있는유저모드는 Ring 3, 커널모드는 Ring 0를나타내게됩니다. x86 에서지원하는 Ring 모델의구조는아래그림과같습니다. 1 Rootkits: Subverting the Windows Kernel 15
Protection Rings 위의그림에서볼수있듯이 x86은총 4개의 Ring을이용하여 Access Control을지원합니다. 실제이 Ring의구분은숫자 0~3으로구분되며물리적으로 Ring 모양과는전혀상관이없습니다. 위의그림에서볼수있듯이네개의 Ring 중에서 Ring 0가가장큰권한을, Ring 3이가장낮은권한을가지게됩니다. 4개의 Ring 중 Windows는 Ring 0 와 Ring 3의두개만을사용하며 Ring 0를커널모드, Ring 3을유저모드라고부릅니다. 즉 Windows의모든커널코드들은 Ring 0의권한을가지고실행되는것입니다. Ring 0와 Ring 3에서실행되는프로그램들의접근제한규칙은아래와같습니다. 특권수준이낮은코드세그먼트가특권수준이높은데이터세그먼트로액세스하는것은불가능 특권수준이낮은코드세그먼트에서특권수준이높은코드세그먼트로의제어이행은특별한방법을사용해서만이가능 특권수준이높은코드세그먼트에서특권수준이낮은코드세그먼트로의제어이행은불가능 - Windows 구조와원리그리고 Codes, 정덕영저- 앞에서도얘기했지만실제 Ring 을구분하는것은 0~3 까지의숫자입니다. 현재프로그램을실행하는특권레벨을결정하는것은코드세그먼트의특권레벨입니다. X86 보호모드에서실행되는프로그램의각코드세그먼트는세그먼트디스크립터라고불리는 8바이트의데이터구조체에의해기술됩니다. 16
세그먼트디스크립터는아래와같은구조를가지고있습니다. - 출처 : University of Nebraska-Lincoin Computer Science & Engineering CSCE 351- 위의그림중에서 DPL(Descriptor Privilege Level) 부분이바로특권레벨을나타내는부분입니다. 운영체제는해당필드를참고하여커널모드, 유저모드를구분하게됩니다. DPL 중현재실행되고있는코드의 DPL 값을 CPL(Current Privilege Level) 이라고부릅니다. 세그먼트디스크립터는메인메모리에저장되며이디스크럽터들을디스크럽터테이블이관리하게됩니다. 메인메모리에는두개의중요한테이블이있는데그것이바로앞절에서살펴본 GDT 와 LDT d입니다. 즉 GDT와 LDT에세그먼트디스크럽터에대한정보가있습니다. GDT, LDT 의두개의테이블이있기때문에어떤테이블에서찾아야할것인지를알필요가있습니다. 이를위해세그먼트셀렉터 (Segment Selector) 라고하는것이존재합니다만이에대한설명은넘어가도록하겠습니다. 실제 GDT는어떤값을가지고있는지 WinDBG에서확인을해보도록합시다. Dump를위해 ProtMode 라는 WinDBG 확장 dll 파일을이용했습니다. 해당소스및파일은참고자료 web)5 에서받을수있습니다. ProtMode.dll 파일을 C: Program Files Debugging Tools for Windows winxp 에복사합니다.(Debugging Tools for Windows 가설치되어있어야합니다.) WinDBG에서먼저 ntsdexts.dll 파일을 load 한후 ProtMode.dll을 load 합니다. 그리고!ProtMode.Descriptor GDT 1 를입력하면 GDT 를확인할수있습니다. 17
-Dump GDT - 위의화면은 GDT 를 WinDBG 에서확인한모습입니다. 커널모드에서작동하는프로그램의경우에는 DPL 부분이 0, 유저모드에서작동하는프로그램의경우에는 DPL 부분이 3 으로표시됩니다. 저의경 우는 VMWARE 를이용한커널디버깅모드로동작중이기때문에 DPL 이 0 으로되어있습니다. Ring에의한메모리접근제한과더불어 x86에서제공하는다른하나의보안장치가있습니다. 몇몇명령어들의경우특권을가지고있는것으로간주되어오직 Ring 0에서만실행됩니다. 이명령들은일반적으로 CPU의행동을수정하거나직접하드웨어에접근하기위해사용됩니다. 그명령어들중일부는아래와같습니다. cli : 현재 CPU에서실행중인인터럽트를중지시킵니다. sti : 현재 CPU에서인터럽트를실행시킵니다. in : 하드웨어포트로부터데이터를읽어들입니다. out : 하드웨어포트로데이터를씁니다. - RootKits - 위와같은명령어들이있다정도만알아두시면될듯합니다. 디바이스드라이버를설명하기위해서필수적인 Ring Model에대한설명이여기까지입니다. 디바이스드라이버란위에서설명한유저모드 커널모드스위칭을하지않고직접적으로하드웨어에접근하는소프트웨어라고말할수있습니다. 즉커널모드에서동작하며하드웨어에직접접근할수있는프로그램입니다. 윈도우는이런디바이스드라이버를설계하기쉽도록하나의모델을제시하고있는데이것이 WDM(Windows Driver Model) 이라고합니다. 다만모든운영체제가 WDM 방식을지원하는것은아니 18
며모든드라이버가 WDM 방식을사용해서개발할수있는것도아닙니다. WDM 의역사, 문제, 특징등과같은자세한내용은디바이스드라이버서적을참고하시기바랍니다. 디바이스드라이버프로그램은실행프로그램이아닙니다. 즉확장자가 exe 나 dll 이아니라 sys 입니다. 개발된디바이스드라이버프로그램을사용하기위해서는해당드라이버를 LOAD 시켜줘야만합니다. 리눅스에서의모듈이라고생각하시면훨씬더이해가쉬울듯합니다. 디바이스드라이버의기본골격은아래의 4가지엔트리로구성됩니다. DriverEntry Routine AddDevice Routine IRP Dispatch Routine DriverUnload Routine - 디바이스드라이버구조와원리그리고제작노하우, 이봉석 - 간단히정말간단히필요한것들만설명하겠습니다. 디바이스드라이버전문서적참고를권고합니 다. :) * 디바이스드라이버코드를예시함에있어서커널모드드라이버형태또는 WDM 형태로구현될수있으며이차이점에대해서는상세히기술하지않았습니다. 표현의차이만있을뿐원리는동일합니다. 디바이스드라이버분야는넓고도방대해서일일이설명하려면무지막지한시간이걸리는관계로 ( 본인도잘모른다는게큰이유입니다 -_-;) 대충대충넘어가겠습니다. DriverEntry Routine 일반 C 프로그램의시작점은 main(), 윈도우응용프로그램은 WinMain(), dll 프로그램은 DllMain() 그리고디바이스드라이버의시작점은 DriverEntry입니다. 즉드라이버가로드된후처음으로실행되는곳이바로이부분입니다. 이부분은처음드라이버가로드될때단한번만수행하는부분이므로주로초기화과정에많이사용됩니다. AddDevice Routine 자신이만든새로운디바이스를추가하고자할때사용되는부분입니다. 이부분에서 Device Object 를생성하게됩니다. IRP Dispatch Routine IRP는디바이스와 IO Manager 사이에서명령을전달하는역할을하는구조체이며그실체는하나의버퍼에불과합니다. 유저모드프로그램에서파일을열고쓰는동작같은것이 IRP를통해서이루어지게됩니다. 19
DriverUnload Routine 이름그대로드라이버를언로드할때수행되는부분이며이것저것수행한것들을깨끗하게정리하는 부분으로사용됩니다. 먼저만인의연인 Hello World 를작성해보도록하겠습니다. 빌드를위해서는소스파일외에도 Makefile 과 Source 파일이필요하게됩니다. 역시구체적인것은전문서적을참고하시고아래의화면대로입력하시기바랍니다. - Sources - - Makefile - - HelloWorld.c - 위의세개파일을작성한다음 DDK 를설치하면생기는 Windows XP Checked Build Environment 를실행한다음해당파일들이있는디렉토리로이동합니다. 그리고아래화면처럼컴파일을하고나 면 HelloWorld.sys 파일이생성됩니다. 20
- build - - build 결과 - 이제작성한프로그램을로드하고메시지를볼수있는프로그램이필요합니다. 드라이버를로드하는 방법은두가지가있는데그방법은역시서적을참고하시길바랍니다. 본문서에서드라이버를로드하는툴은 Rootkit.com 1 의 InstDrv 를, 메시지를보기위해서는 sysinternals 2 의 Debugview 프로그램을사용했습니다. 1 http://www.rootkit.com/ 2 http://www.sysinternals.com/ 21
- 드라이버 Load - Install -> Start -> Stop -> Remove 버튼을차례로누르면 DebugView 화면에 HelloWorld.c 파일에서 작성한 DbgPrint 함수가출력하는메시지를확인할수있습니다. HelloWorld.c 파일을보시면알겠지만위에서구현된것은 DriverEntry 에진입했을때와 Unload 시에 대한것뿐입니다. 이제여기에조금더살을붙여보도록하겠습니다. 22
- Source - Source 파일은 TARGETNAME 과 SOURCES 부분만변경되었습니다. - Minimal.h - Minimal.cpp 는조금더많은부분이추가되었습니다. 하나씩보겠습니다. - Minimal.cpp #1 - CreateDevice 함수가하나더추가된것을볼수있습니다. DriverEntry 부분은 CreateDevice 를호출 하는부분이추가되었습니다. 아래는 CreateDevice 함수입니다. 23
- Minimal.cpp #2- 이부분은 WDM에서는 AddDevice 함수가처리합니다. 디바이스드라이버는, 즉커널내부에서는모두유니코드를사용합니다. 생성할디바이스의이름을유니코드로변환하는부분이 CreateDevice함수의초반부분입니다. 중간쯤에가면 IoCreateDevice 함수를이용해서디바이스를생성하게됩니다. 이 API의자세한설명은전문서적을참고하시기바랍니다. 디바이스생성에성공하면해당디바이스를유저모드어플리케이션에서이용하기위해전역변수를만들어줘야합니다. 이를위해 Symbolic Link를만들게되며이를 IoCreateSymbolicLink API에서담당하게됩니다. 만약 Symbolic Link 생성에실패하게되면 IoDeleteDevice API를이용해생성한디바이스를삭제합니다. 이부분은왜이렇게해야하는지에대한명확한답변을해드리기어려우나아마도일종의안전장치로써사용되는것이아닌가짐작됩니다. Symbolic Link를생성하는방법에있어서 WDM 을이용하게되면 DriverExtension->AddDevice에서처리를하게됩니다만해당내용은생략하도록하겠습니다. CreateDevice 함수가완료되면디바이스드라이버객체가생성되고어플리케이션에서이를이용할수 24
있는 Symbolic Link 까지생성되게됩니다. - Minimal.cpp #3 - 위의화면은 DriverUnload 부분입니다. 드라이버를 Unload 할때 Symbolic Link 를삭제한후디바이스 를삭제합니다. 작성한디바이스드라이버를설치하면위와같은화면을볼수있습니다. 자여기까지오면이제우리는 Windows System 에커널모드에서작동하는디바이스를생성할수있 게된것입니다. 다음예제는디바이스익스텐션 (Device Extension) 구조체가추가된것입니다. 디바이스익스텐션은 nonpaged 풀에존재하며디바이스의상태정보등을기록하는데쓰입니다. 즉, 디바이스전용이므로 디바이스익스텐션의구조체는헤더파일에정의되어있어야합니다. 아래는디바이스익스텐션구조체가추가된헤더파일입니다. 25
- NewMinimal.h - 위의디바이스익스텐션구조체변수중 PDEVICE_OBJECT 부분은반드시있어야하는부분입니다. Linked List 에서 Next 포인터의역할을한다고생각하시면됩니다. 나머지는드라이버개발자가필요 한전용변수들을선언하면됩니다. - NewMinimal.cpp #1- NewMinimal.cpp 의초반부입니다. CreateDevice 함수에전달되는변수가하나늘었습니다. 바로 uldevicenumber 입니다. 하나의드라이버는하나이상의디바이스를다룰수있습니다. 그러므로 uldevicenumber 변수는드라이버에서다룰디바이스를차례로참조할수있는매개체로써의역할을하게됩니다. 26
- NewMinimal.cpp #2- CreateDevice 에서는우선 PDEVICE_EXTENSION형 pdevext 변수가생긴것이눈에들어옵니다. 중간쯤에디바이스익스텐션구조체를초기화합니다. 디바이스의정보중 Device Name, Symbolic Link Name 등을저장하고있습니다. 추가로 status 의값에따른조건문하나를넣어놨습니다. 동일한이름의 Symbolic Link가존재할경우메시지를뿌립니다. NTSTATUS형이반환하는값은여러가지가정의되어있는데자세한것은 C: WINDDK 3790.1830 inc ddk wxp ntstatus.h 파일을참고하시기바랍니다. 27
- NewMinimal.cpp #3- DriverUnload 부분은형태가조금틀려졌습니다. pnextobj 가마지막노드에도달할때까지, 즉생성 된모든디바이스에대하여차례로 Symbolic Link 와 Device 를삭제합니다.(Linked List 라고생각하시 면됩니다.) Symbolic Link Name 은디바이스익스텐션에저장되어있는값을사용하고있습니다. DbgView 화면입니다. 이번에는마지막으로디스패치루틴을넣어보겠습니다. 앞에서이야기했던디바이스드라이버의기본 골격은이디스패치루틴이들어감으로써완성되게됩니다. 28
- LastMinimal.h - 헤더파일입니다. 앞에서지저분한소스를좀고칠겸이쪽으로몇개뺐습니다. 그림에서볼수있듯이디바이스익스텐션구조체를간단하게수정했고 DispatchDummy 라는함수가 추가되었습니다. 29
- LastMinimal.cpp #1- DriverEntry 부분입니다. 기존소스에서따로분리해놓았던 CreateDevice를합쳐놓았습니다. 디바이스익스텐션초기화가간략하게된것을중간쯤에서확인할수있습니다. 마지막부분에 Dispatch 초기화부분이새로이추가된것이보입니다. MajorFunction 테이블의모든디스패치루틴에대해 DispatchDummy 함수를지정하고있습니다. MajorFunction 테이블은특정 I/O 요청에대한디스패치루틴의함수포인터들을저장하고있으며이런 I/O 요청은 IRP_MJ_XXX 형태의심볼로서정의되어있습니다. 실제로는상수값을가지고있는심볼입니다. Ntddk.h 또는 WDM.h 파일을살펴보시면상세히기술되어있습니다. 30
- ntddk.h - IRP 함수코드값중몇가지만나열해보면아래와같습니다. IRP_MJ_CREATE : 핸들을요청합니다. CreateFile에대응됩니다. IRP_MJ_CLENUP : 핸들을닫을때지연된 IRP를취소시킵니다. CloseHandle에대응됩니다. IRP_MJ_CLOSE : 핸들을닫습니다. CloseHandle에대응됩니다. IRP_MJ_READ : 디바이스로부터데이터를받습니다. ReadFile에대응됩니다. IRP_MJ_WRITE : 디바이스로데이터를보냅니다. WriteFile에대응됩니다. IRP_MJ_SHUTDOWN : 시스템이셧다운될때사용됩니다. InitateSystemShutdown에대응됩니다. - 원리와예제로배워보는 Windows 2000 디바이스드라이버 - 31
- LastMinimal.cpp #2- 위의그림에서주석에도달려있지만실제 DispatchDummy 함수는아무일도하지않는함수입니다. 해당함수가호출되면 status를 STATUS_SUCCESS로대입하고 IoCompleteRequest API를호출한뒤 status를 return 합니다. IoCommpleteRequest API는이름에서도알수있듯이해당요청이완료되었음을알리는역할을합니다. 마지막으로 DriverUnload 부분은 Symbolic Link 이름을지정하는부분만추가되었습니다. 이것으로써디바이스드라이버의기본골격은다다루어보았습니다. 디바이스드라이버항목의마지 막예제로써디바이스하나를생성하여읽고쓰는작업을하는드라이버를소개할까했으나실제우 리가할 SSDT Hooking 시에디스패치루틴은사용되지않으므로패 ~ 쓰하도록하겠습니다. 32
SSDT Hook 멀고먼길을돌아드디어 SSDT Hook으로다시진입하게되었습니다. 자이제실제코드를작성하기전에관련내용들을알아보도록하겠습니다. System Service Dispatcher, 즉 KiSystemService는 SSDT(System Service Dispatch Table) 을참고하여적절한 System Service를실행하게됩니다. 하지만이 SSDT, 즉 KiServiceTable은커널에의해익스포트되지않은구조체이므로원칙적으로는내부구조를알수없습니다. 하지만 KeServiceDescriptorTable이라는 ntoskrnl.exe에의해익스포트된구조체 (Service Descriptor Table) 내에 KiServiceTable에대한정보가들어있습니다. KeServiceDescriptorTable 구조체는아래와같습니다. typedef struct ServiceDescriptorTable { SDE ServiceDescriptor[4]; } SDT; SDT 구조체는아래와같습니다. typedef struct ServiceDescriptorEntry { PDWORD KiServiceTable; PDWORD CounterTableBase; DWORD ServiceLimit; PBYTE ArgumentTable; } SDE; KiServiceTable 에는서비스테이블의시작주소가들어있습니다. 이주소로부터 4바이트단위로각 System Service들의주소가들어가있습니다. 세번째필드 ServiceLimit는 SDT에등록되어있는 System Service의개수가들어있습니다. 마지막필드인 ArgumentTable에는각 System Service마다전달되는파라미터가몇바이트인지기록되어있습니다. 이주소로부터 1바이트단위로전달되는파라미터의바이트수가기록되어있습니다. 아래는 KeServiceDescriptorTable 을 WinDBG 에서 Dump 한화면입니다. 위그림에서각줄은 SDE 구조체배열의구성요소들입니다. 그리고빨간색으로표시된부분이바로 KiServiceTable 의주소입니다. WinDBG 로해당주소의메모리를덤프하면아래와같은화면이나옵니다 33
위의빨간색으로표시한부분처럼각부분은 KiServiceTable 에저장된 System Service 들의주소를나 타냅니다. 첫번째주소를살펴보면 NtAcceptConnectPort 라는 API 임을확인할수있습니다. 동일한방법을 이용하여 ServiceLimit 개의 System Service 를확인할수있습니다. 34
참고자료 Documents) 1. Rootkits: Subverting the Windows Kernel, Greg Hoglund 1. Inside Windows Rootkits 2. Windows Driver Model2, Microsoft Press 3. Attack Native API, Devguru 4. Rootkit을이용하는악성코드, 사이버시큐리티 2005년 11월호 5. ROOTKITS, Greg Hoglund, James Butler 6. Defeating Kernel Native API Hookers by Direct Service Dispatch Table Restoration, Chew Keong TAN 7. 원리와예제로배우는 Windows 2000 디바이스드라이버, 인포북 8. Windows Internals, Microsoft Press 9. 64비트윈도우커널분석 AMD64, Micro Software 2005년 10월호 Web) 1. Hooking Windows NT System Services http://www.windowsitlibrary.com/content/356/06/2.html 2. Windows Global API Hooking http://dual.inxzone.net/backup/blog/index.php?pl=125&ct1=4 3. Window 쪼물딱거리기 http://somma.egloos.com/2731001 4. How Do Windows NT System Calls REALLY Work? http://www.codeguru.com/cpp/w-p/system/devicedriverdevelopment/article.php/c8035 5. System Call Optimization with the SYSENTER Instruction http://www.codeguru.com/cpp/w-p/system/devicedriverdevelopment/article.php/c8223 35