Defeating Kernel Native API Hookers by Direct KiServiceTable Restoration

http://blog.naver.com/lsc3762/90014446734

Defeating Kernel Native API Hookers by Direct KiServiceTable Restoration

유저모드 API 호출
- 유저모드 Win32 어플리케이션은 여러 dll이 익스포트하는 api를 호출해서 시스템 서비스를 요청한다.
- 예를들어 오픈된 파일이나 파이프, 디바이스에 쓸려면 kernel32.dll이 익스포트하는 WriteFile api를 이용한다.
- WriteFile api는 다시 ntdll.dll이 익스포트하는 NtWriteFile/ZwWriteFile native api를 호출한다.
- ntdll.dll에서 NtWriteFile과 ZwWriteFile은 같은 코드를 포인트한다.
- 실제 작업은 커널모드에서 이뤄지므로 NtWriteFile/ZwWriteFile은 INT 0x2E 소프트웨어 인터럽트를 이용하여 커널코드로 이동하는
최소한의 코드만 갖는다.
- win2k에서 NtWriteFile/ZwWriteFile의 디스어셈블은 다음과 같다.
MOV EAX, 0EDh
LEA EDX, DWORD PTR SS:[ESP+4]
INT 2E
RETN 24
- 0xED는 커널 함수의 위치를 갖는 KiServiceTable의 인덱스에 이용되는 시스템 서비스 넘버이다.
- WinXP에서 디스어셈블은 다음과 같다.
MOV EAX, 112h
MOV ED, 7FFE0300h
CALL DWORD PTR DS:[EDX]
RETN 24
- 7FFE0300h은 다음 함수에 대한 포인터이다.
MOV EDX, ESP
SYSENTER
[열기]

[ System Service Dispatcher ]
- INT 2E에 대한 ISR(Interrupt Service Routine)은 KiSystemService이다.
- KiSystemService는 파라미터를 검사한후 SSDT(System Service Dispatch Table, KiServiceTable)의 엔트리를 확인해서 해당 시스템
서비스를 호출한다.
--------------------------------
0x00 NtAcceptConnectPort
--------------------------------
0x01 NtAccessCheck
--------------------------------
0x02 NtAccessCheckAndAuditAlarm
--------------------------------
.............................
--------------------------------
0xED NtWriteFile
--------------------------------
...........................
--------------------------------

[ KeServiceDescriptorTable ]
- KiServiceTable은 커널에서 익스포트되있지 않다.
- 그러나 주소는 KeServiceDescriptorTable로 알수 있다.(KeServiceDescriptorTable.ServiceDescriptor[0].KiServiceTable)
typedef struct ServiceDescriptorTable {
SDE ServiceDescriptor[4];
} SDT;

typedef struct ServiceDescriptorEntry {
PDWORD KiServiceTable;
PDWORD CounterTableBase;
DWORD ServiceLimit; // KiServiceTable의 엔트리 갯수
PBYTE ArgumentTable;
} SDE;
- ServiceDescriptor[1], [2], [3]은 사용되지 않는다.

[ Kernel Native API Hookers ]
- 커널에 로드되는 디바이스 드라이버는 KiServiceTable의 엔트리를 수정할수 있다.
- 자신의 디바이스 드라이버내의 후킹함수를 포인트하도록 수정한다.
- 보통, 원래 native api를 호출할수 있도록 원래 엔트리를 기억해둔다.

NTSYSAPI NTSTATUS NTAPI NtXXXHook(...)
{
ManipulateInputParameters(...);
NtXXXOriginal(...);
ManipulateReturnBuffers(...);
return;
}

- KiServiceTable 엔트리 수정은 보통 DriverEntry()에서 한다.

#define SYSTEMSERVICE(_api) KeServiceDescriptorTable.ServceDescriptor[0].ServiceTable[*(DWORD *)((unsigned char *)_api+1)]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
system service number
// 원래 함수 포인터를 기억
NtWriteFileOrig=(NTWRITEFILE)(SYSTEMSERVICE(ZwWriteFile));

// 후킹함수인 NtWriteFileHook()을 포인트하도록 KiServiceTable 엔트리를 수정
(NTWRITEFILE(SYSTEMSERVICE(ZwWriteFile))=NtWriteFileHook;

[ 프로세스 숨기기 ]
- 유저모드 프로그램은 프로세스 리스트를 얻기위해 ToolHelp.dll이 익스포트하는 api를 이용한다.
- 이 api는 ntdll.dll에서 익스포트하는 Nt/ZwQuerySystemInformation()을 호출한다.
- 유저모드 프로그램에서 첫번째 파라미터를 SystemProcessAndThreadsInformation로 줘서 직접 Nt/ZwQuerySystemInformation()을 호출
할수도 있다.
- 커널 루트킷은 NtQuerySystemInformation()을 후킹해서 첫번째 파라미터가 SystemProcessAndThreadInformation인지 검사한다.
- 그런다음 리턴된 버퍼에서 프로세스를 숨긴후 콜러에게 리턴해준다.
NTSYSAPI NTSTATUS NTAPI NtQuerySystemInformationHook(IN ULONG SystemInformationClass,
IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL)
{
nts=NtQuerySystemInformationOrig(...);
if((nts==STATUS_SUCCESS) && (SystemInformationClass==SystemProcesAndThreadsInformation)) {
ManipulateReturnBuffers(SystemInformation, ...);
}
return nts;
}

- 리턴된 버퍼는 다음과 같은 구조체 배열이다.
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryDelta; // 다음 엔트리 오프셋
ULONG ThreadCount; // 스레드 갯수
ULONG Reserved[6];
LARGE_INTEGER CreateTime; // 프로세스 생성시간
LARGE_INTEGER UserTime; // 유저모드에서 쓰인 시간
LARGE_INTEGER KernelTime; // 커널모드에서 쓰인 시간
UNICODE_STRING ProcessName; // 프로세스 이름
KPRIORITY BasePriority; // base process priority
ULONG ProcessId; // pid
ULONG InheritedFromProcessId; // ppid
.........
SYSTEM_THREAD_INFORMATION Threads[1];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

[ 로드된 드라이버 숨기기 ]
- 보통 자기 자신을 숨기는데 쓰인다.
- 유저모드 프로그램은 첫번째 파라미터로 SystemModuleInformation을 줘서 Nt/ZwQuerySystemInformation()을 호출해서 로드된 드라이버
목록을 구할수 있다.
typedef NTSYSAPI NTSTATUS (*ZWQUERYSYSTEMINFORMATION)(IN ULONG SystemInformationClass, IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL);
- 두번째 파라미터에 아웃풋 버퍼를 줘서 받는다.
- 이 버퍼의 첫번째 DWORD값은 리턴된 배열 엔트리 갯수이다. 그다음 나머지 바이트는 다음과 같은 구조체 배열이다.
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG Reserved[2];
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION;

[ 파일 숨기기 ]
- 루트킷은 NtQueryDirectoryFile()을 후킹한다.

- 어떻게 시스템 서비스 후킹을 탐지할수 있을까?
SSDT는 커널 이미지 바깥쪽을 포인트하는 엔트리를 갖는다.
Nt/ZwQuerySystemInformation()으로 드라이버 목록과 베이스 주소를 알수 있다.
80400000 001A2340 - \WINNT\System32\ntoskrnl.exe
80062000 00010460 - \WINNT\System32\hal.dll
ED410000 00003000 - \WINNT\System32\BOOTVID.DLL
BFFD8000 00028000 - ACPI.sys
ED5C8000 00001000 - \WINNT\System32\DRIVERS\WMILIB.SYS

kd> d KeServiceDescriptorTable
8046dfa0 b8 42 47 80 00 00 00 00-f8 00 00 00 9c 46 47 80 .BG..........FG. ServiceDescriptor[0]
8046dfb0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ ServiceDescriptor[1]
8046dfc0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ ServiceDescriptor[2]
8046dfd0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ ServiceDescriptor[3]

kd> d 804742b8
804742b8 52 dd 49 80 c1 f6 4a 80-3a 04 4b 80 b8 d5 50 80 R.I...J.:.K...P.
804742c8 70 04 4b 80 a2 ce 45 80-be f7 50 80 fe f7 50 80 p.K...E...P...P.
804742d8 38 4a 49 80 f2 a9 50 80-d8 de 4a 80 2d d8 4f 80 8JI...P...J.-.O.
804742e8 49 a6 4a 80 df 4d 49 80-ca b8 44 80 3d 7e 4c 80 I.J..MI...D.=~L.
804742f8 74 ee 5b f7 a9 94 4b 80-e0 db 4f 80 1a 19 40 80 t.[...K...O...@.
80474308 1c 04 4d 80 76 95 41 80-86 6d 4f 80 8e 35 49 80 ..M.v.A..mO..5I.
80474318 a8 f9 44 80 57 07 4b 80-d5 e2 49 80 75 dc 49 80 ..D.W.K...I.u.I.
80474328 a0 92 46 80 84 4b 4f 80-d2 35 49 80 40 92 4c 80 ..F..KO..5I.@.L.

KiserviceTable내의 주소값으로 시스템 서비스를 후킹하는 드라이버를 알수 있다.
80400000 - \WINNT\System32\ntoskrnl.exe
80062000 - \WINNT\System32\hal.dll
....
BF8B3000 - \SystemRoot\System32\Drivers\Cdfs.SYS
BF6F4000 - \SystemRoot\System32\Drivers\Fastfat.SYS
BF74F000 - \SystemRoot\System32\DRIVERS\ipsec.sys
F75BA000 - \SystemRoot\System32\DRIVERS\KProcCheck.sys
F75BE000 * \SystemRoot\System32\DRIVERS\gotr.sys

[ KProcCheck POC ]
C:\>kproccheck -t
KProcCheck Version 0.1 Proof-of-Concept by SIG^2 (www.security.org.sg)

Checks SDT for Hooked Native APIs

ZwAllocateVirtualMemory 10 \SystemRoot\System32\DRIVERS\gotr.sys [F75BEE74]
ZwCreateFile 20 \SystemRoot\System32\DRIVERS\gotr.sys [F75BEA85]
ZwCreateKey 23 \SystemRoot\System32\DRIVERS\gotr.sys [F75BEC5E]
ZwCreateProcess 29 \SystemRoot\System32\DRIVERS\gotr.sys [F75BEDB7]
ZwDeleteFile 34 \SystemRoot\System32\DRIVERS\gotr.sys [F75BE80C]
ZwGetTickCount 4C \SystemRoot\System32\DRIVERS\gotr.sys [F75BEE27]
ZwLoadDriver 55 \SystemRoot\System32\DRIVERS\gotr.sys [F75BEBF2]
ZwQueryDirectoryFile 7D \SystemRoot\System32\DRIVERS\gotr.sys [F75BE6E8]
ZwQuerySystemInformation 97 \SystemRoot\System32\DRIVERS\gotr.sys [F75BE623]
ZwSetInformationFile C2 \SystemRoot\System32\DRIVERS\gotr.sys [F75BE8A8]

Number of Service Table entries hooked = 10

- NtQuerySystemInformation(SystemModuleInformation, ...)이 후킹됐을수 있으므로 다른 방법으로 로드된 드라이버를 구한다.
- 직접 PsLoadedModuleList를 트래버스해서 구한다.
- Win2k 커널에서 PsLoadedModuleList는 다음과 같은 doubly linked-list의 시작이다.
struct {
LIST_ENTRY link; // Flink, Blink
BYTE unknown1[16];
DWORD ImageBase;
DWORD EntryPoint;
DWORD ImageSize;
UNICODE_STRING DrvPath;
UNICODE_STRING DrvName;
....
}

그러나 PsLoadedModuleList는 익스포트되 있지 않다. 그러므로 익스포트된 함수 MmGetSystemRoutineAddress()부터 다음과 같은
인스트럭션이 나올때 까지 스캐닝해서 주소를 찾는다.
8b35b8e14680 mov esi,[nt!PsLoadedModuleList (8046e1b8)]
81feb8e14680 cmp esi,0x8046e1b8

Nt/ZwquerySystemInformation(SystemModuleInformatin, ...)과 위에서 찾은 값을 비교해서 숨겨진 드라이버를 찾을수 있다.
C:\>kproccheck -d
KProcCheck Version 0.1 Proof-of-Concept by SIG^2 (www.security.org.sg)

80400000 - \WINNT\System32\ntoskrnl.exe
80062000 - \WINNT\System32\hal.dll
F7410000 - \WINNT\System32\BOOTVID.DLL
F7000000 - pci.sys
F7010000 - isapnp.sys
F7500000 - intelide.sys
F7280000 - \WINNT\System32\DRIVERS\PCIIDEX.SYS
F7288000 - MountMgr.sys
BFFE3000 - ftdisk.sys
...
BF6F4000 - \SystemRoot\System32\Drivers\Fastfat.SYS
BF74F000 - \SystemRoot\System32\DRIVERS\ipsec.sys
F75BA000 - \SystemRoot\System32\DRIVERS\KProcCheck.sys
F75BE000 * \SystemRoot\System32\DRIVERS\gotr.sys --[Hidden]--

Total number of drivers = 73

[ 후킹된 엔트리 복원 ]
- 어떻게 원래 값을 알수 있을까?
- KiServiceTable의 복사본은 ntoskrnl.exe에서 찾을수 있다.
.data:004742B8 off_0_4742B8 dd offset sub_0_49DD52 ; DATA XREF: sub_0_55A996
.data:004742BC dd offset sub_0_4AF6C1
.data:004742C0 dd offset sub_0_4B043A
.data:004742C4 dd offset sub_0_50D5B8
.data:004742C8 dd offset sub_0_4B0470
.data:004742CC dd offset sub_0_45CEA2
.data:004742D0 dd offset sub_0_50F7BE
.data:004742D4 dd offset sub_0_50F7FE
.data:004742D8 dd offset NtAddAtom
.data:004742DC dd offset sub_0_50A9F2
.data:004742E0 dd offset NtAdjustPrivilegesToken
.data:004742E4 dd offset sub_0_4FD82D
.data:004742E8 dd offset sub_0_4AA649
.data:004742EC dd offset NtAllocateLocallyUniqueId

- 드라이버를 로드하거나 유저모드에서 직접 \device\physicalmemory에 써서 후킹을 복원할수 있다.
- \device\physicalmemory는 커널 메모리를 포함한 피지컬 메모리에 유저모드 프로그램이 읽거나 쓸수 있게 해준다.

[ SDTrestore POC ]
- physical 메모리를 보기위해 \device\physicalmemory를 이용하는건 Mark Russinovich가 Physmem 툴에서 처음 사용되었다.
90210 - \device\physicalmemory에 써서 GDT call gate를 인스톨한다. ActiveprocessLinks에서 자신의 EPROCESS 구조체를 언링킹해서
프로세스를 숨긴다.
crazylord - "playing with windows /dev/(k)mem", \device\physicalmemory에 써서 GDT call gate를 인스톨한후 ActiveProcessLinks를
트래버싱해서 프로세스 목록을 구한다.
다음은 어떻게 어드민 권한을 가진 유저모드 프로그램이 피지컬 메모리에 읽고 쓸수 있는 권한을 갖을수 있는지 설명한다.
1. ntdll.dll에서 익스포트하는 NtOpenSection() native api에 SECTION_MAP_READ | SECTION_MAP_WRITE access flag를 줘서
\device\physicalmemory 핸들을 구한다. 이건 보통 어드미니스트레이터가 \device\physicalmemory에 SECTION_MAP_WRITE 억세스
권한이 없으므로 실패한다.
2. NtOpenSection()에 READ_CONTROL | WRITE_DAC access flag로 피지컬메모리 핸들을 구한다. 이건 피지컬메모리 오브젝트에 새 DACL을
추가해준다.
3. 어드민이 SECTION_MAP_WRITE access권한을 가질수 있도록 피지컬메모리에 DACL을 추가한다.
4. 다시 피지컬메모리 핸들을 구한다.

피지컬메모리에 쓸려면 프로그램은 피지컬메모리 페이지를 자신의 버츄얼 어드레스 공간에 맵핑해야한다.
ntStatus=_NtMapViewOfSection(
hPhyMem, // \device\physicalmemory handle
(HANDLE)-1, // OUT- physical memory가 맵핑될 virtual memory(paging file)
0,
*length,
&viewBase, // IN/OUT- 맵핑될 physical memory address(page aligned)
length, // IN/OUT- 맵핑된 physical memory size
ViewShare,
0,
PAGE_READWRITE
);

KiServiceTable을 복원할려면 ntoskrnl.exe 커널 파일 이미지에서 KiServiceTable의 위치와 physical memory 주소를 알아야 한다.
win2k, winxp, 서비스팩에 따라 다르므로 이 주소들을 하드코딩할수 없다.
NtmapViewOfSection()에서 페이지를 맵핑하는데 virtual address가 아니라 physical memory address를 이용한걸 기억하라.
win2k에서 커널 베이스 주소는 가상메모리의 0x80400000이다.
win2k 커널의 physical memory 주소는 0x400000이다.

- 디스크 이미지에서 KiServiceTable의 주소 구하기
ntoskrnl.exe를 dll로 로드한후 익스포트 테이블에서 KeServiceDescriptorTable의 오프셋을 구한다.
ServiceDescriptorTable을 갖고있는 physical page로 맵핑한다.
PhyaddrserviceDescriptorTable=kernelPhysicalBaseAddr+offset_of_KeServiceDescriptroTable
맵핑된 페이지에서 KiServiceTable 주소를 구한다.
이것은 커널영역에서 KiServiceTable의 virtual address(0x804742b8)일 것이다.
kd> d KeServiceDescriptorTable
8046dfa0 b8 42 47 80 00 00 00 00-f8 00 00 00 9c 46 47 80 .BG..........FG.
8046dfb0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
8046dfc0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
8046dfd0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
8046dfe0 20 f1 df ff 00 00 00 00-00 00 00 00 00 00 00 00 ...............
8046dff0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
8046e000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

커널 파일 이미지의 오프셋으로 변경한다.
serviceTableOffset=serviceTableVirtualAddr-kernelVirtualBaseAddr(0x80400000)
이렇게해서 KiServiceTable의 복사본의 오프셋을 알수 있다.
그러나 이 방법은 실행중인 커널의 KiServiceTable의 오프셋이 디스크 이미지 내의 오프셋과 같아야 한다.
루트킷이나 다른 보안프로그램이 KiServiceTable을 재배치한다면 이 방법은 안된다.
rootkit.com의 90210이 디스크 이미지에서 KiServiceTable 위치를 찾는 보다 나은 방법을 제시했다.
KiServiceTable은 KeServiceDescriptorTable의 복사본 실행이 설정됐을때 KiInitSystem에서 참조된다.
INIT:0055AA65 mov eax, offset dword_0_482258
INIT:0055AA6A mov ds:KeServiceDescriptorTable, offset KiServiceTable
INIT:0055AA74 mov ds:dword_0_48225C, eax

그런데 KiInitSystem은 익스포트되있지 않으므로 다음과 같은 인스트럭션을 ntoskrnl.exe에서 스캔해서 찾아야 한다.
mov KeServiceDescriptorTable, imm32

relocation table이 참조하는 위치만 스캔하므로 이 방법이 보다 효과적이고 믿을만 하다.
이 테이블에서 각 엔트리에 대해 KeServiceDescriptorTable을 참조하는지 검사한다.
참조한다면 인스트럭션이 mov KeServiceDescriptorTable, imm32 인지 검사한다.

- KiServiceTable 패치
디스크상의 KiServiceTable의 복사본의 위치를 찾은후에 실행중인 커널의 KiServiceTable을 갖고있는 피지컬 페이지로 맵핑하고 다른
엔트리를 패치한다.
패치하기 전에 먼저 값을 커널의 virtual address로 바꿔줘야한다.
for(DWORD i=0; i<sdtCount; i++) {
if((kernelServiceTable[i]-kernelVirtualBase+peXH2.imageBase)!=fileServiceTable[i]) {
kernelServiceTable[i]=fileServiceTable[i]-peXH2imageBase+kernelVirtualBase;
printf("[+] Patched SDT entry %.2X to %.8X\n", i, kernelServiceTable[i]);
}
}

Native API Hookers 예제
커널 루트킷- NT Rootkit, HE4Hook
보안 툴- Sebek Win32, DiamondCS Process Guard, Kerio Personal Firewall 4

HE4Hook은 다음과 같이 파일을 숨긴다.
1. 다음과 같은 시스템 서비스를 후킹한다.
ZwCreateFile 20 --[hooked by unknown at 81222476]--
ZwOpenFile 64 --[hooked by unknown at 812224A8]--
ZwQueryDirectoryFile 7D --[hooked by unknown at 812224D2]--
2. 또는 파일 시스템 드라이버의 콜백 테이블을 후킹한다.
1번 방법은 KiServiceTable 엔트리를 복원해서 쉽게 제거할수 있다.

Sebek은 하니팟에서 주로 사용하는 콘솔 로거이다.
cmd.exe의 인풋, 아웃풋을 캡쳐하고 로깅서버에 udp 패킷으로 보낸다.
cmd.exe 세션을 암호화해서 로깅할수 있다.
다음과 같은 시스템 서비스를 후킹한다.
ZwClose 18 SEBEK.sys [F729A092]
ZwCreateFile 20 SEBEK.sys [F729A98C]
ZwCreateKey 23 SEBEK.sys [F729AD10]
ZwEnumerateKey 3C SEBEK.sys [F729AE02]
ZwEnumerateValueKey 3D SEBEK.sys [F729AA50]
ZwOpenFile 64 SEBEK.sys [F729A8E6]
ZwOpenKey 67 SEBEK.sys [F729AD88]
ZwQueryDirectoryFile 7D SEBEK.sys [F729A4CC]
ZwQuerySystemInformation 97 SEBEK.sys [F729A5F0]
ZwReadFile A1 SEBEK.sys [F7299CF0]
ZwRequestWaitReplyPort B0 SEBEK.sys [F7299F14]
ZwSecureConnectPort B8 SEBEK.sys [F7299FE6]
ZwWriteFile ED SEBEK.sys [F7299D48]
sebek.sys를 숨기기 위해(anti-detection), sebek.sys를 로드하는 레지스트리키를 숨기기 위해(anti-detection), cmd.exe를 로그하기 위해
ZwReadFile/ZwWriteFile을 후킹한다.
역쉬 쉽게 무력화할수 있다.

DiamondCS Process Guard
프로세스 종료, 서스펜드, 악의적인 커널 드라이버가 로딩하는걸 보호하는 보안툴이다.
ZwCreateFile 20 \??\C:\WINNT\System32\drivers\procguard.sys [F7392D8A]
ZwCreateKey 23 \??\C:\WINNT\System32\drivers\procguard.sys [F7391F98]
ZwCreateThread 2E \??\C:\WINNT\System32\drivers\procguard.sys [F73924FC]
ZwOpenFile 64 \??\C:\WINNT\System32\drivers\procguard.sys [F7392C62]
ZwOpenKey 67 \??\C:\WINNT\System32\drivers\procguard.sys [F7391F64]
ZwOpenProcess 6A \??\C:\WINNT\System32\drivers\procguard.sys [F739289E]
ZwOpenThread 6F \??\C:\WINNT\System32\drivers\procguard.sys [F73926F8]
ZwRequestWaitReplyPort B0 \??\C:\WINNT\System32\drivers\procguard.sys [F7390AE6]
ZwSetValueKey D7 \??\C:\WINNT\System32\drivers\procguard.sys [F739224E]
ZwWriteVirtualMemory F0 \??\C:\WINNT\System32\drivers\procguard.sys [F7392A40]

Kerio Personal Firewall 4
알려지지않은 또는 새로운 또는 수정된 프로그램이 실행될때마다 유저에게 프로세스를 실행할 것인지 묻는다.
ZwCreateFile 20 \SystemRoot\system32\drivers\fwdrv.sys [BFBD3830]
ZwCreateProcess 29 \SystemRoot\system32\drivers\fwdrv.sys [BFBD3380]
ZwCreateThread 2E \SystemRoot\system32\drivers\fwdrv.sys [BFBD35E0]
ZwResumeThread B5 \SystemRoot\system32\drivers\fwdrv.sys [BFBD3630]

Native API 후킹 보안툴
KiServiceTable을 복원해서 무력화시킬수 있다.
이걸 방지하기 위해 추가적으로 다음과 같은게 필요하다.
- NtLoadDriver를 후킹해서 드라이버가 로딩되는걸 막는다.
- SystemInformationClass=SystemLoadAndCallImage[13]으로 NtSetSystemInformation을 후킹해서 드라이버 로딩을 막는다.
- ZwOpenSection을 후킹해서 \device\physicalmemory로 write access를 막는다.
- 심볼릭 링크를 통해 \device\physicalmemory로 write access를 막는다.

후킹을 하지않는 루트킷
FU 루트킷
- PsLoadedModuleList에서 언링킹을 해서 드라이버를 숨긴다.
- ActiveprocessLinks에서 언링킹을 해서 프로세스를 숨긴다.

Process Hide- phide
- ActiveProcessLinks에서 언링킹을 해서 프로세스를 숨긴다. 드라이버를 인스톨할 필요없이 GDT call gate를 통해 한다.

이런류의 루트킷은 KiServiceTable을 복원하는 방법으로 무력화시킬수 없다.

결론
Sysem Service 후킹에 기반한 커널 루트킷은 KiServiceTable을 복원해서 무력화시킬수 있다.
요즘 루트킷들은 후킹을 사용하지않고 탐지하고 무력화하기가 힘들다.
보안툴은 시스템 서비스 후킹에 의존해선 안된다.

by narumee | 2008/01/16 12:00 | └Windows Internals | 트랙백(10171) | 덧글(0)

< Fast System Call에 의한 커널코드 진입방식 >



< Fast System Call에 의한 커널코드 진입방식 >



1. 인텔 펜티엄 2 이상에서 사용함.

2. SYSENTER, SYSEXIT 2개의 명령어 사용함.

3. SYSENTER는 Ring 3에서 사용되는 명령어로, 마이크로프로세서에 있는 MSRs(MODEL-

SPECIFIC REGISTERS)로부터 진입하고자하는 커널의 CS, EIP의 레지스터 정보와 커널 레벨 진입

후, 사용하게 될 스택 정보 SS, ESP를 가져와서 세팅한 후 커널레벨로 진입이 이루어진다.

http://blog.naver.com/gunner98?Redirect=Log&logNo=110026271729

[열기]


4. SS(Stack Segment)의 경우 SYSENTER_CS_MSR + 8로 지시되고 있으며, 이는 운영체제를 시작

할 때, Fast System Call을 지원하게 하기 위해서는 GDT(Global Descriptor Table)에서 SS(Stack

Segment)의 위치를 CS(Code Segment) 다음에 위치하도록 만들어야 한다는 이야기이다.

5. SYSEXIT 명령는 다시 복귀하게 될 Application의 CS와 SS를 SYSENTER_CS_MSR + 16과

SYSENTER_CS_MSR + 24로 계산하여 찾으므로 GDT에서 커널레벨의 SS 다음에 배치해야 한다.

6. SYSENTER에 의해 SYSENTER_EIP_MSR에 저징되어 있는 주소로 커널레벨 전환이 이루어진 후

커널에서는 자신이 수행하고자 하는 코드를 수행한 후 다시 Application으로 돌아오고자 하는 할 때

사용하는 명령어가 SYSEXIT이다.

7. SYSEXIT는 IRET 과는 달리 우리가 Application 레벨로 복귀하게 될 EIP, ESP 값을 EDX, ECX 레

지스터에 먼제 세팅하여 준 후 호출해 주어야 하며, 이렇게 호출되어진 SYSENTER는

SYSENTER_CS_MSR + 16과 SYSENTER_CS_MSR + 24를 사용하여 복귀하게 된 Application의

CS와 SS를 세팅하고 SYSEXIT를 호출하기 전에 세팅한 EDX와 ECX 레지스터 값을 통하여 EIP,

ESP를 세팅한 후 복귀하게 된다.



by narumee | 2008/01/10 11:19 | └Windows Internals | 트랙백 | 덧글(0)

Windows NT 시스템 콜은 어떻게 동작하는가?

Windows NT 시스템 콜은 어떻게 동작하는가?


Windows NT 시스템 콜을 설명하는 대부분의 문서들은 중요한 부분들을 숨겨놓고 있다. 이로 인해 유저 모드 어플리케이션이 커널 모드로 진입하는 것을 이해하려고 할 때 혼돈이 생긴다. 다음 글은 시스템 서비스를 수행하기 위해 커널 모드로 전환하는 데에 Windows NT가 사용하는 정확한 메커니즘 밝힐 것이다. 이 설명은 보호 모드에서 동작하는 x86 호환 CPU에 대한 것이다. Windows NT가 지원하는 다른 플랫폼에서도 유사한 메커니즘이 사용될 것이다.



[열기]


By John Gulbrandsen 8/19/2004
John.Gulbrandsen@SummitSoftConsulting.com

Translated into Korean by Snoya 10/01/2004
Homepage: system-inside.com
MSN: neo3k@hanmail.net
E-mail: neo3k@hanmail.net

커널 모드란 무엇인가?

대부분의 개발자(심지어 커널 모드 개발자도)들이 생각하는 것과 다르게, x86 CPU에는 "커널 모드"라고 불리는 모드가 없다. 모토롤라 68000과 같은 CPU는 CPU에 내장된 두 가지의 프로세서 모드를 가진다. 즉, 현재 user-mode 에서 실행되는지 supervisor-mode 에서 실행되는지를 나타내는 status 레지스터를 가진다. 인텔 x86 CPU는 그러한 플래그를 가지고 있지 않다. 대신에 수행 중인 프로그램의 권한을 결정하기 위한 현재 실행중인 코드 세그먼트의 특권 레벨이 있다. x86 CPU의 보호 모드에서 동작하는 어플리케이션의 각각의 코드 세그먼트는 세그먼트 디스크립터라고 하는 8바이트 크기의 데이터 구조에 의해 서술된다. 세그먼트 디스크립터는 세그먼트의 시작 주소와 세그먼트의 크기, 그리고 특권 레벨 등과 같은 정보를 가지고 있다. 특권 레벨 3을 가지는 코드 세그먼트에서 실행되는 코드는 유저 모드에서 실행된다고 말하고 특권 레벨 0을 가지는 코드 세그먼트에서 실행되는 코드는 커널 모드에서 실행된다고 말한다. 즉, 커널 모드(특권 레벨 0)과 유저 모드(특권 레벨 3)은 CPU가 아닌 코드의 속성이다. 인텔은 특권 레벨 0을 "링 0", 특권 레벨 3을 "링 3"이라고 부른다. x86 CPU에는 Windows NT는 사용하지 않는 두 개의 특권 레벨이 더 있다(링 1, 링 2). 특권 레벨 1,2가 사용되지 않는 이유는 Windows NT는 여러 다른 하드웨어 플랫폼에서 동작하도록 설계되어 있고 다른 CPU는 인텔 x86 CPU와 같이 4개의 특권 레벨을 가지고 있을지 없을지 모르기 때문이다.

x86 CPU는 낮은 특권 레벨(숫자는 더 높음)에서 실행되는 코드가 높은 특권 레벨(숫자는 더 낮음)에서 실행되는 코드를 호출하는 것음 금지한다. 만약 이것이 시도되면, 일반 보호(General Protection, GP) 예외가 CPU에 의해 자동으로 발생한다. 운영체제의 일반 보호 예외 핸들러가 호출되고 적절한 처리가 이루어질 것이다(사용자에게 경고, 어플리케이션 종료 등). 특권 레벨을 포함한 위에서 논의한 모든 메모리 보호 기능은 x86 CPU의 기능이지 Windows NT의 것이 아니라는 점에 유의하라. CPU의 지원없이는 Windows NT는 위와 같은 메모리 보호를 수행할 수 없다.

세그먼트 디스크립터는 어디에 있는가?

시스템에 존재하는 각각의 코드 세그먼트는 세그먼트 디스크립터에 의해 서술되기 때문에, 그리고 시스템에는 많은 코드 세그먼트들이 존재할 것이기 때문에, 세그먼트 디스크립터는 어딘가에 저장되어 있어야 할 것이고 프로그램이 어느 세그먼트의 코드를 실행하기를 원할 때 그것을 허용하거나 금지하기 위해 CPU는 그것을 읽을 수 있어야 한다. 인텔은 CPU 칩 자체에 그 모든 정보를 저장하는 것이 아닌 메인 메모리에 저장하기로 결정하였다. 메인 메모리에는 두 개의 테이블이 있다; 전역 디스크립터 테이블(Global Descriptor Table, GDT) 그리고 지역 디스크립터 테이블(Local Descriptor Table, LDT). 이들 디스크립터 테이블의 주소와 크기를 저장하기 위한 두 개의 레지스터가 CPU 안에 있다. 이 레지스터는 전역 디스크립터 테이블 레지스터(Global Descriptor Table Register, GDTR)과 지역 디스크립터 테이블 레지스터(Local Descriptor Table Register, LDTR)이다. 이들 디스크립터 테이블을 설치하고 GDTR, LDTR 레지스터에 GDT, LDT 의 주소를 적재하는 것은 운영체제의 책임이다. 이것은 부트 과정에서 매우 이른 시기에, 심지어 CPU가 보호 모드로 전환되기 전에 이루어져야 한다. 왜냐하면 디스크립터 테이블 없이는 보호 모드에서 메모리 세그먼트가 액세스될 수 없기 때문이다. 아래 그림 1은 GDTR, LDTR, GDT, LDT 사이의 관계를 나타낸다.

두 개의 세그먼트 디스크립터 테이블이 있기 때문에 세그먼트 디스크립터를 선택하기위해 인덱스만을 사용하는 것은 충분하지 않다. 둘 중 어느 테이블을 선택할 지를 결정하는 1 비트가 필요하다. 테이블 지시가 비트와 결합된 인덱스를 세그먼트 셀렉터라고 한다. 세그먼트 셀렉터의 형식은 아래와 같다.

위 그림 2와 같이, 세그먼트 셀렉터는 RPL(Requestor Privilege Level)이라고 하는 2 비트 필드를 포함한는데 그것은 이 셀렉터가 가리키는 코드 세그먼트 디스크립터에 접근할 수 있는지를 결정하는데 사용된다. 예를 들어, 특권 레벨 3(유저 모드) 코드가 셀렉터가 가리키는 코드 세그먼트 디스크립터에 의해 서술되는 코드 세그먼트의 코드를 호출하려고 할 때, 셀렉터의 RPL 특권 레벨 0에서 실행되는 코드만을 읽을 수 있다고 지시한다면 일반 보호 예외가 발생한다(?). 이렇게 x86 CPU는 링 3(유저 모드) 코드가 링 0(커널 모드) 코드에 접근하는 것을 막는다. 사실은 이것보다 약간 더 복잡하다. 보다 자세한 것을 알기 원하면 더 읽을거리 목록에 있는 "Protected Mode Software Architecture"의 RPL 부분을 참고하라. 우리의 목적을 위해서는 이정도만 알고 있으면 충분하다.

인터럽트 게이트

유저 모드에서 실행되는 어플리케이션 코드가 커널 모드 코드를 호출할 수 없다면 어떻게 Windows NT의 시스템 콜이 동작하는 것일까? 다시 해답은 CPU의 기능을 이용하는 것이다. 서로 다른 특권 레벨 사이에서의 제어 이행을 위해서 Windows NT는 인터럽트 게이트라고 하는 x86 CPU의 기능을 이용한다. 인터럽트 게이트를 이해하기 위해 우리는 먼저 x86 CPU 보호 모드에서 인터럽트가 어떻게 사용되는지 알아야 한다.

대부분의 다른 CPU처럼, x86 CPU 또한 각각의 인터럽트를 어떻게 처리할 것인지에 대한 정보를 포함하는 인터럽트 벡터 테이블을 가지고 있다. 리얼 모드에서 x86 CPU의 인터럽트 벡터 테이블은 단순히 인터럽트 서비스 루틴(Interrupt Service Routine, ISR)의 주소(4바이트 크기)를 가진다. 그러나 보호 모드에서 인터럽트 벡터 테이블은 8바이트 크기의 인터럽트 게이트 디스크립터를 가진다. 인터럽트 게이브 디스크립터는 어느 코드 세그먼트에 ISR이 존재하는지 그리고 그 코드 세그먼트에서 ISR은 어디에서 시작하는지에 대한 정보를 가지고 있다. 인터럽트 벡터 테이블이 단순한 포인터가 아닌 인터럽트 게이트 디스크립터를 가지는 이유는, 유저 모드 코드는 커널 모드 코드를 직접 호출할 수 없다는 법칙 때문이다. 인터럽트 게이트 디스크립터의 특권 레벨을 검사함으로써 CPU는 호출하는 어플리케이션이 명확한 위치에 있는 보호된 코드를 호출하는 것이 허용된다는 것을 확인한다(이것이 바로 "인터럽트 게이트"라는 이름이 붙은 이유이다. 즉, 그것은 유저 모드 코드가 커널 모드 코드로 제어를 이행할 수 있도록 하는 명확히 정의된 문이다). 

인터럽트 게이트 디스크립터는 인터럽트 서비스 루틴을 가지고 있는 코드 세그먼트를 서술하는 코드 세그먼트 디스크립터를 가리키는 세그먼트 셀렉터를 포함한다. 우리의 Windows NT 시스템 콜의 경우, 세그먼트 셀렉터는 전역 디스크립터 테이블에 있는 코드 세그먼트 디스크립터를 가리킨다. 전역 디스크립터 테이블은 모든 "전역" 세그먼트 디스크립터를 가지고 있다. 즉, 시스템에서 실행중인 어느 특정 프로세서에 국한되는 것이 아니다(GDT는 운영체제의 코드와 데이터 세그먼트를 서술하는 세그먼트 디스크립터를 가진다). 그림 3은 'int 2e' 명령으로 인한 인터럽트 디스크립터 테이블과 전역 디스크립터 테이블 엔트리 그리고 목적지 코드 세그먼트에 있는 인터럽트 서비스 루틴 사이의 관계를 나타낸다.

NT 시스템 콜을 알아보자

어느 정도 배경 지식을 쌓았으므로 이제 Windows NT 시스템 콜이 어떻게 유저 모드에서 커널 모드로 전환하는지 정확하게 알아볼 준비가 되었다. Windows NT 에서의 시스템 콜은 "int 2e" 명령을 실행함으로써 시작된다. 'int' 명령어는 CPU로 하여금 소프트웨어 인터럽트를 발생하도록 한다. 즉, 인터럽트 디스크립터 테이블로 가서 인덱스 2e에 있는 인터럽트 게이트 디스크립터를 읽는다. 인터럽트 게이트 디스크립터는 ISR을 가지고 있는 코드 세그먼트를 가리키는 세그먼트 셀렉터를 포함한다. 또한 그 목표 코드 세그먼트 내의 ISR이 위치하는 오프셋도 포함한다. CPU는 GDT 또는 LDT 에 있는 엔트리를 결정하기 위해 세그먼트 셀렉터를 사용할 것이다. 일단 CPU가 목표 세그먼트 디스크립터에 있는 정보를 알게되면 그 정보를 CPU로 로드한다. 또한 인터럽트 게이트 디스크립터에 있는 오프셋 값을 EIP 레지스터에 로드한다. 이 시점에서 CPU는 커널 모드 코드 세그먼트에 있는 ISR을 실행할 준비가 거의 되었다.

CPU는 자동으로 커널 모드 스택으로 전환한다

CPU가 커널 모드 코드 세그먼트에 있는 ISR을 실행하기 전에, 커널 모드 스택으로 전환되어야 한다. 그 이유는, 커널 모드 코드는 커널 모드에서 실행되기 위한 충분한 공간이 필요하기 때문에 유저 모드 스택을 신용할 수가 없기 때문이다. 예를 들어, 멍청한 유저 모드 코드가 스택 포인터를 유효하지 않은 메모리 주소를 가리키도록 했을 때 'int 2e' 명령을 수행하면 커널 모드 함수가 그 스택 포인터를 사용할 때 시스템 충돌을 일으킬 것이다. 따라서 x86 보호 모드 환경에서 각각의 특권 레벨은 자신의 스택을 가진다. 인터럽트 게이트 디스크립터 등을 통해 더 높은 특권 레벨로의 함수 호출이 이루어질 때, CPU는 자동으로 유저 모드 프로그램의 SS, ESP, EFLAGS, CS, EIP 레지스터를 커널 모드 스택에 저장한다. Windows NT 시스템 서비스 디스패쳐 함수(KiSystemService)의 경우 유저 모드 코드가 'int 2e'를 호출하기 전에 스택에 넣은 인자에 접근할 필요가 있다. 규칙에 의해, 유저 모드 코드는 'int 2e'를 호출하기 전에 유저 모드 스택의 인자들을 가리키는 포인터를 EBX 레지스터에 적재해야 한다. 그러면 KiSystemService는 호출되는 시스템 함수가 필요로하는 만큼의 인자를 유저 모드 스택에 커널 모드 스택으로 복사한다. 그림 4는 이에 대하여 설명하고 있다.

어떤 시스템 호출을 우리는 호출하는가?

모든 Windows NT 시스템 콜은 커널 모드로 전환하기 위해 'int 2e' 소프트웨어 인터럽트를 사용한다. 그러면 유저 모드 코드는 어떻게 커널 모드 코드에게 어떤 시스템 함수가 실행되어야 하는지를 알릴까? 대답은 'int 2e' 가 호출되기 전에 EAX 레지스터에 인덱스가 적재되어야 한다는 것이다. 커널 모드 ISR은 EAX 레지스터를 보고 유저 모드에서 넘어온 모든 인자가 올바르다면 해당 함수를 호출한다. ISR에 의해 커널 모드 함수로 인자가 전달된다.

시스템 콜에서 돌아와서

일단 시스템 콜이 완료되면 IRET 명령에 의해 CPU는 자동으로 프로그램의 원래 레지스터 값을 복원한다. 커널 모드 스택에 저장된 레지스터 값들을 꺼내고 CPU는 'int 2e' 다음의 유저 모드 코드를 계속 실행한다.

실험

인터럽트 디스크립터 테이블 2e 번째 엔트리에 있는 인터럽트 게이트 디스크립터를 조사함으로써 우리는 CPU가 Windows NT 시스템 서비스 디스패쳐 루틴을 이 글에서 설명한 것처럼 발견하는 것을 확인할 수 있다. 이 문서의 샘플 파일에는 GDT, LDT, IDT 내의 디스크립터를 표시하는 WindDbg 확장 DLL을 포함한다.

예제 코드 다운로드: ProtMode.zip

이 확장 DLL은 'protmode.dll' (Protected Mode) 파일이다. kdextx86.dll 이 있는 디렉토리에 이 DLL을 복사한 후 WinDbg에서 ".load protmode.dll" 명령을 내리면 이 확장 DLL이 로드된다(역자 주: 보통, 경로는 다음과 같을 것이다. C:\Program Files\Debugging Tools for Windows\w2kfre(또는 w2kchk, winxp)). 타겟 플랫폼에 연결되었다면 브레이크를 걸어라. 'int 2e'에 대한 IDT 디스크립터를 표시하는 명령은 "!descriptor IDT 2e" 이다. 이 명령은 다음과 같은 정보를 표시한다.

kd>!descriptor IDT 2e
------------------- Interrupt Gate Descriptor --------------------
IDT base = 0x80036400, Index = 0x2e, Descriptor @ 0x80036570
80036570 c0 62 08 00 00 ee 46 80
Segment is present, DPL = 3, System segment, 32-bit descriptor
Target code segment selector = 0x0008 (GDT Index = 1, RPL = 0)
Target code segment offset = 0x804662c0
------------------- Code Segment Descriptor --------------------
GDT base = 0x80036000, Index = 0x01, Descriptor @ 0x80036008
80036008 ff ff 00 00 00 9b cf 00
Segment size is in 4KB pages, 32-bit default operand and data size
Segment is present, DPL = 0, Not system segment, Code segment
Segment is not conforming, Segment is readable, Segment is accessed
Target code segment base address = 0x00000000
Target code segment size = 0x000fffff 

'descriptor' 명령은 다음과 같은 정보를 파해쳐낸다:

  • IDT 인덱스 2e 에 있는 디스크립터의 주소는 0x80036570 이다.
  • 그 주소에 있는 디스크립터의 실제 값은 C0  62 08 00 00 EE 46 80 이다.
  • 그 값은 다음을 의미한다:
    • 인터럽트 게이트 디스크립터의 세그먼트 셀렉터에 의해 서술되는 코드 세그먼트 디스크립터를 포함하는 세그먼트가 존재한다.
    • 최소한 특권 레벨 3에서 실행되는 코드도 이 인터럽트 게이트를 이용할 수 있다.
    • 우리의 시스템 콜(2e)를 위한 인터럽트 핸들러를 가지고 있는 세그먼트는 GDT의 인덱스 1 에 위치한 세그먼트 디스크립터에 의해 서술된다.
    • KiSystemService는 타겟 세그먼트 내의 오프셋 0x804662c0 에서 시작한다.

"!descriptor IDT 2e" 명령은 또한 GDT 인덱스 1 에 있는 타겟 코드 세그먼트 디스크립터를 덤프한다. 아래는 이 GDT 디스크립터에 대한 설명이다:

  • GDT 인덱스 1 에 있는 코드 디스크립터의 주소는 0x80036008 이다.
  • 그주소에 있는 디스크립터의 실제 값은 FF FF 00 00 00 9B CF 00 이다.
  • 그 값은 다음을 의미한다:
    • 단위는 4KB 페이지이다. 이것이 의미하는 것은, 크기 필드 값(0x000fffff)은 실제 크기를 얻기 위해 가상 메모리 페이지 크기(4096 바이트)로 곱해져야 한다는 것이다. 그 결과 4GB 크기가 된다. 이것이 바로 커널 모드 코드가 메모리의 커널 모드 영역뿐만 아니라 유저 모드 영역에도 접근할 수 있는 이유이다(역자 주: GDT 인덱스 3에 있는 유저 모드 세그먼트도 4GB의 크기를 가진다. 그러나 상위 2G의 커널 영역에는 접근할 수 없는데 그러한 보호는 세그멘테이션이 아닌 페이징 단계에서 보호하는 것으로 알고 있다. 따라서 크기가 4GB이기 때문에 전체 메모리 영역에 접근할 수 있다는 말은 문제가 있는 것 같다).
    • 커널 모드 세그먼트이다(DPL=0).
    • 이 세그먼트는 conforming 이 아니다. 더 읽을거리 목록 중 "Protected Mode Software Architecture" 를 참고하라.
    • 이 세그먼트는 읽기 가능하다. 더 읽을거리 목록 중 "Protected Mode Software Architecture" 를 참고하라.
    • 이 세그먼트는 accessed 되었다. 더 읽을거리 목록 중 "Protected Mode Software Architecture" 를 참고하라.

ProtMode.dll WinDbg 확장 DLL을 빌드하기 위해서는, Visual Studio 6.0 으로 프로젝트를 연 후 빌드를 클릭하라. ProtMode.dll 과 같은 확장 DLL을 작성하는 방법은 "Debugging Tools for Windows" 에 들어있는 SDK을 참고하라(역자 주: WinDbg을 설치하면 도움말 파일이 설치되는 데 확장 DLL을 작성하는 데에도 도움이 될 뿐만 아니라 전체적으로 훌륭한 문서라고 생각된다. 기회가 된다면 한글로 번역하고픈 마음이다). 마이크로소프트에서 무료로 다운받을 수 있다.

더 읽을거리

인텔 x86 CPU 보호 모드에 대한 다음 두 훌륭한 자료:

1)      "Intel Architecture Software Developers Manual, Volume 3 - System Programming Guide". Available from Intel's web site in PDF format.

2)      "Protected Mode Software Architecture" by Tom Shanley. Available from Amazon.com (published by Addison Wesley).

반드시 읽어보야 할 x86 CPU 프로그래밍에 대한 자료:

1)              Intel Architecture Software Developers Manual, Volume 1 - Basic Architecture.

2)              Intel Architecture Software Developers Manual, Volume 2 - Instruction Set Reference Manual.

      이들 책들은 인텔 웹 사이트에서 PDF 포맷으로 얻을 수 있다 (당신은 또한 볼륨 3을 제외한 두 권의 책을 하드카피로 무료로 얻을 수 있다. 그러나 볼륨 3은 PDF 포맷으로만 유효하다).

저자에 대하여

John Gulbrandsen 는 Summit Soft Consulting 의 설립자이며 회장이다. John 은 임베디드와 Windows 시스템 개발뿐만 아니라 Microprocessor-, digital-, analog- electronics 설계에도 정규 지식을 가지고 있다. John 은 1992년(Windows 3.0)부터 Windows를 프로그램해왔다. 그는 C++, C#, VB를 이용하여 Windows 어플리게이션과 웹 시스템을 작성하고 또한 SoftIce를 이용하여 Windows 커널 모드 디바이스 드라이버를 개발하고 디버깅한다.  

To contact John drop him an email: John.Gulbrandsen@SummitSoftConsulting.com

Summit Soft Consulting에 대하여

Summit Soft Consulting 는 사우스 캘리포니아에 위치한 마이크로소프트 운영체제와 그 핵심 기술을 전문으로 하는 컨설팅 회사이다. 우리는 커널 모드와 NT internals 프로그래밍을 포함한 Windows 시스템 개발을 주로 다른다.

Summit Soft Consulting 방문하기: http://www.summitsoftconsulting.com/

by narumee | 2008/01/09 17:57 | └Windows Internals | 트랙백(3) | 덧글(0)

◀ 이전 페이지          다음 페이지 ▶