Hide Process Last Update : 2007 년 6 월 11 일 Written by Jerald Lee Contact Me : lucid78@gmail.com 본문서는 SSDT Hook을이용한프로세스를감추는기술에대해정리한것입니다. 제가알고있는지식이너무짧아가급적이면다음에언제보아도쉽게이해할수있도록쓸려고노력하였습니다. 기존에나와있는여러훌륭한문서들을토대로짜집기의형태로작성되었으며기술하지못한원문저자들에게매우죄송할따름입니다. 본문서는읽으시는분들이어느정도 Windows API를알고있다는가정하에쓰여졌습니다. 제시된코드들은 Windows XP Professional, Service Pack 2, Windows DDK build 6000 에서테스트 되었습니다. 문서의내용중틀린곳이나수정할곳이있으면연락해주시기바랍니다. 1
목차 1. SSDT...4 2. HIDE PROCESS...8 3. SSDT HOOKING...11 4. 참고자료...18 2
그림목차 [ 그림 1. FINDNEXTFILE API]...4 [ 그림 2. SSDT HOOKING]...5 [ 그림 3. KESERVICEDESCRIPTORTABLE]...6 [ 그림 4. SSDT]...6 [ 그림 5. DUMP 8059A746]...6 [ 그림 6. KISERVICETABLE]...7 [ 그림 7. EPROCESS 구조체 ]...8 [ 그림 8. DT _EPROCESS]...9 [ 그림 9. _LIST_ENTRY]...9 [ 그림 10. EPROCESS LINKED LIST]...10 [ 그림 11. LINKED LIST 변조 ]...10 [ 그림 12. KESERVICEDESCRIPTORTABLE 선언 ]...11 [ 그림 13. SSDT 구조체추가 ]...11 [ 그림 14. SYSTEMSERVICE 매크로 ]...11 [ 그림 15. SYSTEMSERVICE 매크로사용 ]...12 [ 그림 16. ZWCREATEFILE]...12 [ 그림 17. KESERVICEDESCRIPTORTABLE 의 BASE 주소 +( 인덱스 *4)]...13 [ 그림 18. NTCREATEFILE API 확인 ]...13 [ 그림 19. NEWZWQUERYSYSTEMINFORMATION API]...14 [ 그림 20. NEWZWQUERYSYSTEMINFORMATION 핵심코드 ]...15 [ 그림 21. _SYSTEM_PEROCESS 구조체 ]...16 [ 그림 22. HIDE PROCESS 성공 ]...17 3
1. SSDT SSDT 는 System Service Dispatch Table 의약자입니다. 이테이블은 Windows 에서이용가능한모든 시스템서비스들의주소를가지고있습니다. 시스템서비스를사용하려는인터럽트가발생하면 Windows 는이테이블을참고하여적절할결과값을돌려주게됩니다. [ 그림 1. FindNextFile API] SSDT Hook 은이테이블에기록된서비스의주소를바꿔치기함으로써이루어지게됩니다. 4
[ 그림 2. SSDT Hooking] SSDT, 내부에서는 KiServiceTable 로불리는이테이블은 Windows Kernel에의해 export되지않은구조체이므로이테이블의구조를원칙적으로는알수없습니다. 하지만 ntoskrnl.exe에의해 export된구조체인 ServiceDescriptorTable 에 KiServiceTable 에관한단서가나와있습니다. typedef struct ServiceDescriptorTable { SDE ServiceDescriptor[4]; } SDT; SDT 구조체는아래와같습니다. typedef struct ServiceDescriptorEntry { PDWORD KiServiceTable; PDWORD CounterTableBase; DWORD ServiceLimit; PBYTE ArgumentTable; } SDE; 위에서보이는 KiServiceTable 은 SSDT 의시작주소를가리키며이시작주소로부터각 4 바이트마다 각 System Service 들이등록되어있습니다. 5
[ 그림 3. KeServiceDescriptorTable] [ 그림 3] 에서 0x805031fc 주소는 ServiceDescriptor[0] 구조체의 KiServiceTable 변수를나타내며바 로 SSDT 의시작주소를의미합니다. [ 그림 4. SSDT] [ 그림 4] 는 [ 그림 3] 에서얻는 SSDT 의주소를 Dump 한화면입니다. 각필드들은 SSDT 에기록된 System Service 들이위치하고있는주소를나타냅니다. 빨간색상자로표시되어있는주소를확인하 면아래와같습니다. [ 그림 5. Dump 8059a746] [ 그림 5] 는 SSDT[0] 에저장된주소를 Dump 한화면이며그주소에는 NtAcceptConnectPort 이라는 API 가있음을확인할수있습니다. KiServiceTable 구조를그림으로표현하면아래와같습니다. 6
[ 그림 6. KiServiceTable] 7
2. Hide Process Windows 는현재시스템에서실행되고있는프로세스들의목록을 Linked List 형태로관리하고있으며 EProcess 라는구조체를사용하여 Process 들을관리합니다. [ 그림 7. EProcess 구조체 ] [ 그림 7] 은 EProcess 구조체를도식화한그림입니다. 실제구조체의내용은 Kernel Debug 에서 dt _EPROCESS 명령을이용하여확인할수있습니다. 8
[ 그림 8. dt _EPROCESS] EPROCESS 의많은구조체들중에빨간박스로표시된 ActiveProcessLinks 라는구조체가있으며 _LIST_ENTRY 라는구조체로되어있습니다. 이 _LIST_ENTRY 구조체의필드를이용하여 Process 들 의 List 를만들어관리합니다. [ 그림 9. _LIST_ENTRY] [ 그림 9] 는 _LIST_ENTRY 구조체를나타내고있으며두개의필드인 Flink 과 Blink 를이용하여 List 를관리합니다. Flink 는 Next Process 를, Blink 는 Previous Process 를가리키게됩니다. 9
[ 그림 10. EProcess Linked List] [ 그림 10] 은 EProcess 구조체들의 Linked List 를그림으로표현한것입니다. 이문서에서프로세스 감추기는바로 Linked List 에서 Flink, Blink 를임의로변경시켜마치프로세스가없는것처럼보이게 하는것이목적입니다. 그림으로표현하면아래와같이될겁니다. [ 그림 11. Linked List 변조 ] 자이제프로세스리스트를얻을때사용하는 NtQuerySystemInformation 을 SSDT Hooking 을이용 해서바꿔치기만하면우리가원하는특정프로세스를감출수있게됩니다. 10
3. SSDT Hooking 먼저디바이스드라이버프로그램의골격을만듭니다. 드라이버가로드된후에가장처음에필요한것은바로 SSDT 에접근하는것입니다. 1장에서살펴보았듯이 Kernel에의해 export된 KeServiceDescriptorTable을통해 SSDT에접근하는것이가능하므로아래와같이선언하면됩니다. [ 그림 12. KeServiceDescriptorTable 선언 ] 그리고 SSDT 구조체의원형을선언해줍니다. [ 그림 13. SSDT 구조체추가 ] 여기까지오게되면 [ 그림 12] 에서선언한 KeServiceDescriptorTable 이라는변수를통해 SSDT 구조 체에접근할수있게됩니다. 임의의 API 에쉽게접근하기위해아래의매크로를선언합니다. [ 그림 14. SYSTEMSERVICE 매크로 ] 11
[ 그림 14] 에선언된두매크로를통해아래와같이 SSDT 내의임의의 API 에접근할수있습니다. [ 그림 15. SYSTEMSERVICE 매크로사용 ] 모든유저모드어플리케이션들은 System Service를받기위해 Native API를호출하며 Native API는 SYSENTER을호출하게되고, 이명령에의해서 KiSystemService 내부함수가호출됩니다. KiSystemService는최종적으로 SSDT를참조하여해당 Native API의 System Service Entry Pointer를알아오게됩니다. Windows Native API들은 ZwXXX, NtXXX의두계열이있습니다. 유저모드에서의 Native API 호출은모두 NtXXX 계열의 API를호출하게됩니다. 커널모드에서의 Native API 호출은약간다른데정리하면아래와같습니다. - 커널모드에서 NtXXX 호출 : 직접 System Service 호출 ( 실제 Service Code) - 커널모드에서 ZwXXX 호출 : SYSENTER를호출 바로위에서기술하였듯이 SYSENTER를호출하면 KiSystemSservice 가 SSDT를참고하여해당 API 의 System Service Entry Point를가져옵니다. SSDT를참고할때 eax 레지스터로인덱스번호를넘기게되는데이인덱스번호는 SSDT 에서의인덱스번호가됩니다. 그러므로해당 Native API의 System Service Entry Point 는 KeServiceDescriptorTable의 Base주소에 ( 인덱스번호 * 4) 를더한값을사용하여접근할수있습니다. (SSDT를수정하기위해 ZwXXX 계열의함수를이용해야하는이유입니다.) [ 그림 16. ZwCreateFile] [ 그림 7] 은 ZwCreateFile 의내용을보여주고있습니다. ZwCreateFile 의시작은 mov eax, 25h 이며 eax 레지스터로인덱스번호인 0x25 를넘겨주고있습니다. 이 0x25 가바로 NtCreateFile 의시작주소 12
를가지고있는 SSDT 에서의인덱스번호가되는것입니다. [ 그림 17. KeServiceDescriptorTable 의 Base 주소 +( 인덱스 *4)] [ 그림 8] 은 [ 그림 7] 에서확인한인덱스번호 *4 바이트만큼이동한주소를 Dump 한화면이며 0x8056f136 번지를반환하고있습니다. [ 그림 18. NtCreateFile API 확인 ] [ 그림 9] 는 [ 그림 8] 에서반환된주소를 Dump 한것이며결국 ZwCreateFile 에서 eax 레지스터에넘 겨준인덱스번호를이용해 NtCreateFile 의주소를얻을수있다는것을보여줍니다. 모든 ZwXXX API들의시작은 [move eax, 인덱스번호 ] 가되므로 SYSCALL_INDEX 매크로에서 ((PUCHAR)_Function+1) 이부분은 _Function이시작하는메모리주소의다음 4바이트, 즉인덱스번호를가리키게됩니다. SYSCALL_INDEX(ZwWriteFile) 은 NtWriteFile의인덱스번호인 0x25를가리키게됩니다. SYSCALL_INDEX 매크로가넘겨주는인덱스번호를받은 SYSTEMSERVICE 매크로는 KeServiceDescriptorTable.ServiceTableBase[ZwFunction의인덱스번호 ] 가되어결국 SDT 에서인덱스번호째에존재하는 NtXXX API의 Enrty Point로접근할수있게됩니다. 이제우리는프로세스를감추기위해 ZwQuerySystemInformation API에접근할수있게되었습니다. 다음으로바꿔치기할새로운함수를작성합니다. 13
[ 그림 19. NewZwQuerySystemInformation API] 새로운 ZwQuerySystemInformation 함수의시작은 [ 그림 19] 와같습니다. 먼저미리저장해놓은원 래의 ZwQuerySystemInformation 함수를호출하여서정상적으로처리가되게합니다. 정상적으로처리가되면이후의코드를진행하게합니다. 14
[ 그림 20. NewZwQuerySystemInformation 핵심코드 ] 코드를살펴보겠습니다. 위의코드는 rootkit 에서발췌한것임을미리밝혀둡니다. 먼저원래의 ZwQuerySystemInformation 호출이성공한후에해당 API로넘어온변수값들중 SystemInformationClass 값을검사하는데이값이 SystemProcessInformation(5) 일경우프로세스와스레드에대한정보를요청하는것을말합니다. SystemProcessInformation 일때변수값으로넘겨지는 PVOID형 SystemInformation에는 _SYSTEM_PROCESS 구조체가넘어오게되는데이구조체의내용은아래와같습니다.( 이부분은 msdn과다른점이있어 rootkit을따랐음을밝힙니다.) 15
[ 그림 21. _SYSTEM_PEROCESS 구조체 ] Return 된 _SYSTEM_PROCESS 구조체의 ProcessName 값을검사해서감추기를원하는프로세스의이름과동일하면 (process id로구분할수도있습니다 ) NextEntryDelta를수정해서 linked list를조작하여프로세스리스트에서해당프로세스가나타나지않도록합니다. (linked list의삽입, 삭제에대한알고리즘은생략하겠습니다.) 자이제드라이버단에서 SSDT를수정하여새로운 ZwQuerySystemInformation API로바꾸기만하면됩니다. 여기에서마지막문제가남아있는데 SSDT같은메모리는메모리보호를위해 Readonly로설정되어있습니다. 이걸 write 가능하게하는방법에는몇가지가있는데일반적인디바이스드라이버에서사용하는 MDL을이용하는방법은 DDK 버전이올라가면서새로운함수로변경되어사용하기가좀까다롭습니다. 그래서여기에서는 somma 님께서포스팅하신 CR0 레지스터를수정하는방법을사용했습니다. 16
이방법은참고자료 6 번을참고하시기바랍니다. 아래는프로그램을실행시켰을때 notepad.exe 를프로세스리스트에서숨기는화면입니다. [ 그림 22. Hide Process 성공 ] [ 그림 22] 에서현재실행중인 notepad.exe 가프로세스리스트에없음을확인할수있습니다. 소스는아래에서받으실수있습니다. http://pds3.egloos.com/pds/200706/11/51/hideprocess.zip 17
4. 참고자료 1. Win2K Kernel Hidden Process, SIG2 G-TEC 2. Inside Windows Rootkits, SIG2 G-TEC 3. 아무도모르는 Process, Devguru 4. http://snoya.ye.ro/driver/inwin2k/ch06b.htm Process Internals 5. Attack Native API, Devguru 6. http://somma.egloos.com/2731001, 드라이버쪼물딱거리기 3탄 18