http://blog.naver.com/dual5651.do?Redirect=Log&logNo=60016179198

Windows Global API Hooking,Part 1[윈도우 API 전역 후킹,파트1]
        |
        Misc
        
        2005/08/15 16:55
        
        
        
        
          
http://blog.naver.com/dual5651/60016179198
        
        
        
        

                        
                        
                                    HookNativeApi.zip

                                                        


        
        

Windows Global API Hooking,Part 1




Sammuel Dual [Dual5651@hotmail.com]
Homepage : http://dualpage.muz.ro

This article assumes you're familiar with WinNT






Level of Difficulty    1  2  3
Download the code for this article: HookNativeApi.zip

이번글에서는 Windows의 API[Application Programming Interface]
Global Hooking[전역적인 후킹]방법에 대해서 다루어 봅니다.






Windows를 포함한 운영체제들은 응용프로그램을 위한 함수 집합을 제공하는데
이를 API라고 하며 좀 더 정확하게 표현하자면 "Windows API"라고 한다.
API 는 특정 시스템(운영체제든 하드웨어든)을 프로그래밍 하기 위한[좀더 쉽게]
함수 집합을 이르는 일반 명사이며 그 중의 하나가 Windows API이다.
Windows API도 여러가지 종류가 있다. 16bit 운영체지인 Windows 3.1에서 제공하는
Win16 API, 32bit 운영체제인 Windows 95/98 과 NT에서 제공하는 Win32 API
그리고 Windows가 내부적으로 사용하는 Native API도 존재합니다.
이번글에서는 Native API를 Hooking하는 방법에 대해서 다룰것입니다.
WinXX API의 Hooking의 경우 Dll Injection,0xCC Injection,Export,Import Table
Modification 기법 등 여러가지 방법에 관한글이 국내에도 많이 번역 또는 직접 쓰여져 있고
많은 분들이 잘 알고 게십니다.
하지만, Native API Hooking의 경우 알고 보면 간단한 기술인데도 불구하고,
DDK가 필요하다는점과, Device Driver Programming이라는 점이 표면상으로
생소하게 느껴질수 있기때문에 WinXX API의 Hooking보다는 덜 알려져 있는듯 합니다.
이런점에서 이번글에서 다루는 내용은 처음엔 조금 어렵게 느껴질지 모르지만,
알고 보면 "정말 이렇게 간단하게 되는구나!" 하는 감탄 하시게 될겁니다.


SDT?



Native API Hooking은 SDT를 수정하는 방법을 통해서 이루어 집니다.
SDT에 대하여 이야기 하기 전에
User Application Programming을 할때 우리는 WinXX API를
많은 DLL들, 예를들면 KERNEL32,GDI32,USER32같은 것들이 Export해주는
API들을 호출하여 씁니다.이런 DLL들이 Export해주는 API들을
Disassemble하여 보면, 예를들어서 KERNEL32가 Export해주는 API인
WriteFile()를 Disassemble하여 보면,내부적으로는 NTDLL.DLL에서
Export해주는 ZwWriteFile()라는 Native API를 사용함을 볼수 있습니다.
또 ZwWriteFile()를 Disassemble하여 보면,


 





Softice를 이용하여 ZwWriteFile을 Disassemble한 모습.



MOV EAX, 0ED
LEA EDX, DWORD PTR SS:[ESP+4]
INT 2E
RETN 24

위와 같은
부분이 보이게 됩니다.
첫번쨰 줄의 0x0ED는 ZwWriteFile의 ServiceNumber이며,
세번쨰 줄의 INT 2E(System Call)은 UserMode에서 KernelMode로
전환되는 진입점이라고 보면 됩니다.
(아쉽게도 이것은 Windows 2000까지 해당되는 것이며,
Windows XP System에서는 INT 2E가 아닌 SYSENTER를 사용합니다.
이것은 프로세서 차원에서 제공되는 새로운 명령어로서,
기존의 방식보다 커널모드로의 전환이 훨씬 빠르다고 합니다.
위의 Disassemble과정으로 알수 있었던 Call 과정을 도식화 하면,

WinXXAPI - > Native API - > INT2E(Interruptl)
이 Call과정의 중간 단계에 있는 Native API를 Hooking하는 것입니다.

이제 Call과정에 대해서 어느정도 알게 되었으니 SDT에 대하여 알아보면,
SDT란 Service Descriptor Table의 약자로서 SDE를 묶어놓은 구조체입니다.

typedef struct ServiceDescriptorTable {

SDE ServiceDescriptor[4];

} SDT;

위와 같이 선언되어 있으며, 위에서 쓰인 SDE라는것은,
ServiceDescriptorEntry의 약자로서 SDT의 구성요소로서,
Native API에 대한 자세한 정보가 담겨진 구조체 입니다.

typedef struct ServiceDescriptorEntry {

PDWORD ServiceTable; //<------

PDWORD CounterTableBase;

DWORD ServiceLimit;

PBYTE ArgumentTable;

} SDE;

실제 Hooking에서는 SDE의 ServiceTable(System Service Dispatch Table)을
우리의 Driver내에 존재하는 함수의 주소로 바꾸어 해당API가
호출될떄 우리의 함수가 실행되도록 한후, 우리의 함수 내부에서 원하는 작업을
처리한후 원래해당 API를 호출해줌으로써, 정상적으로 작동하게 하는 구조입니다.
물런 Hooking을 끝내고자 할때는 Driver를 Unload만 해서는 안되며,
SDE의 해당API의 ServiceTable을 원상태로 돌려놓아야 합니다.
돌려놓지 않았을때는 아름다운 Blue Screen을 만나 보실수 있을겁니다. :)


Fucking †he C0de




간단히 Coding 하기 전에 필요한 이론적인 부분에 대해서 알아 보았으니,
이제 실제 Code를 살펴가며 진행하도록 하겠습니다.
Code를 보다가 필요한 부분이 더있다면 추가설명을 하겠습니다.

이번글의 내용을 진행한 컴퓨터의 사양은 밑에와 같습니다.

CPU : Intel(R) Pentium(4) 4 CPU 1500MHz
RAM : 523,248KB
OS : Microsoft Windows 2000 5.00.2195 Service Pack 4
Tools : Microsoft Windows 2000 Driver Development Kit
     Microsoft Visual Studio 6.0

먼저 간단한 Driver의 뼈대를 작성합니다.
Application을 제작할떄 가장 먼저 작성하는 Entry Point가
Console App에서는 main()이고,Win App에서는 WinMain()이며,
우리가 지금부터 작성하고자 하는 Driver에서는 DriverEntry() 입니다.
밑에와 같이 간단한 DriverEntry()를 작성하여 보았습니다.

NTSTATUS DriverEntry (
            IN PDRIVER_OBJECT pDriverObject,
            IN PUNICODE_STRING pRegistryPath    ) {

    NTSTATUS status;
    int i;

    DbgPrint("---Driver Loaded---\n");    //(1)드라이버 로드시 출력

    for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) {
        pDriverObject->MajorFunction[i] = DispatchPassThru; //(2)PassThru
    }

    pDriverObject->DriverUnload = DriverUnload;    //(3)드라이버 언로드시 호출

    status = STATUS_SUCCESS;    //상태값
    return status;        //리턴
}

(1)에서 DbgPrint()함수를 통해서 드라이버가 로드 되었음을 출력합니다.
이 DbgPrint()함수를 통해서 DebugMessage를 출력하는것은sysinternals사의
DebugView를 이용해서 볼수있으며, 또한 Numega사의 DriverStudion를 설치할때
설치되는 프로그램인 Driver Monitor를 쓰는것도 좋습니다.




DebugView의 모습



DebugMessage를 확인함으로써 정상적으로 처리되는지 알아볼수 있음으로
드라이버 계발이 조금더 쉬워지는 방법이 됩니다.

(2)에서는 MajorFunction들을 모두 PassThru루틴으로 채우는것을 볼수 있습니다.
MajorFunction들 예를들면 IRP_MJ_WRITE,IRP_MJ_CREATE 같은 것들을 처리하기 위해
존재하는것인데, 드라이버의 기본 뼈대를 작성하는 단계임으로 PassThru로 모두 채웁니다.
(3)에서는 Unload함수를 채워주는것이 나오는데,Unload로 채워주는 함수인
DriverUnload()가 하는 역활은 Driver가 Unload될떄 처리 할것이 있다면,
그것들을 처리하는 부분입니다.

위에서 언급된 DispatchPassThru()의 코드는 다음과 같습니다.

NTSTATUS DispatchPassThru(
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp        ) {

    Irp->IoStatus.Status = STATUS_SUCCESS;
    IoCompleteRequest(Irp,IO_NO_INCREMENT); //증가없음
    return Irp->IoStatus.Status;
}

보시다시피 아무것도 수행되지 않는 루틴임을 볼수 있습니다.

DriverUnload()의 코드는 다음과 같습니다.

VOID DriverUnload (
        IN PDRIVER_OBJECT    pDriverObject    ) {

    DbgPrint("---My Driver Unloadded---\n");    //언로드시 출력
    
}

DriverUnload()함수가 지금까지의 코드에서 하는 일이라곤,
성공적으로 언로드 되었는지 확인하는 DebugMessage출력하는 일뿐입니다.
하지만 점점 코드를 더해 나가겠죠 :)
지금까지의 코드만으로도 하나의 드라이버가 작성됩니다.
물런 아무 기능도 없지만 말이에요.

이제 Hooking기능을 집어넣기 위해 해더파일을 만든후 다음을 집어넣습니다.

#pragma once
#include
#include

#define DWORD unsigned long
#define WORD unsigned short
#define BOOL unsigned long

#pragma pack(1)
typedef struct ServiceDescriptorEntry {
    unsigned int *ServiceTableBase;
    unsigned int *ServiceCounterTableBase;
    unsigned int NumberOfServices;
    unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()

typedef struct _SRVTABLE {
    PVOID       *ServiceTable;
    ULONG       LowCall;    
    ULONG       HiCall;
    PVOID        *ArgTable;
} SRVTABLE, *PSRVTABLE;

__declspec(dllimport) ULONG NtBuildNumber;
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

#define SYSTEMSERVICE(_function)
KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]
#define SYSTEMSERVICEIDX(_index)
KeServiceDescriptorTable.ServiceTableBase[_index]

먼저 NTDDK.H라는 해더 파일을 Include함을 볼수 있습니다.
NTDDK.H는 Driver에서 핵심적인 것들이 선언된 해더파일임으로
반드시 Include해주어야 합니다.
SDE(ServiceDescriptorEntry) 구조체를 선언하는것도 볼수 있습니다.
NtBuildNumber를 통해 현재 운영체제의 빌드넘버를 구해올수 있습니다.
후에 Hooking을 할떄에 Exported by Name이 되지 않은 Native API들이 몇개
존재합니다. ZwDebugActiveProcess(),ZwWriteVirtualMemory()등등의 API입니다.
메크로 함수인 SYSTEMSERVICE와 SYSTEMSERVICEIDX는 얻고자 하는 값이 같지만,
쓰이는 상황이 다릅니다. SYSTEMSERVICE같은 경우는 Native API의 이름으로
ServiceTableBase를 구해오고자 할떄 쓰이며,
SYSTEMSERVICEIDX같은 경우는 Service Number로서 ServiceTableBase를 얻어오고자
할떄 쓰입니다. 위에서 말한 Exported by Name이 되지 않은 몇몇의 Native API들은
SYSTEMSERVICEIDX를 어쩔수 없이 써야 하는데 이떄 운영체제의 빌드버젼에 따라서


Service Number가 다를수 있습니다. 그래서 NtBuildNumber로 switch처리를 해주어야 합니다. :(
지금까지 선언한것들이 Hooking을 하는데 필요한 기본적인 구조체와,메크로함수,
변수들의 선언입니다. 이제 필요한 것들을 선언했으니 Hooking하는 실질적인
Code를 작성하여 보도록 하겠습니다.
Hooking을 하는 방법은 위에서 언급했다싶이 SDE의 ServiceTableBase를 조작하는
것입니다. 이 ServiceTableBase를 조작하는 부분을 처리하는 별도의 루틴을
SetupSTBHook()이라는 이름으로 만들겠습니다.

VOID SetupSTBHook( void ) {

    DbgPrint("---SetupHook---\n");    //STBHook시 출력    
    OldZwWriteFile =
    (ZWWRITEFILE)
    (SYSTEMSERVICE(ZwWriteFile));  //(1)기존의 ZwWriteFile의 주소구함

    DbgPrint("OldZwWriteFile : %Xn",
    OldZwWriteFIle);           //기존 주소 DebugMessage로 출력

    _asm
    {
        CLI              //인터럽트 일시 중지
        MOV    EAX, CR0    
        AND EAX, NOT 10000H
        MOV    CR0, EAX
    }

    (ZWWRITEFILE)
    (SYSTEMSERVICE(ZwWriteFile))   //ServiceTableBase의값을 우리의
    = NewZwWriteFile;          //루틴에 주소로 교체

    _asm
    {
        MOV    EAX, CR0      //인터럽트 일시 중지 해제        
        OR    EAX, 10000H        
        MOV    CR0, EAX            
        STI    
    }
}

코드가 그렇게 어렵지 않음으로 별도의 설명이 길게 필요하진 않을것입니다.
원래 Native API의 주소를 저장시켜둔후(복구 할떄를 위해서)
우리의 루틴에 주소로 바꿔치기 합니다.
이렇게 함으로써 ZwWriteFile()이 호출될떄 실제적으로 호출되는 함수는
우리 Driver내의 NewZwWriteFile()라는 함수가 되는것입니다.
그렇다면 NewZwWrtieFile()이라는 함수는 어떻게 구성되어 있어야 할까요?
기존의 함수의 처리를 완벽하게 해내야 함으로 전달받는 Parameter가 같아야
할것입니다. 그럼으로 Hooking을 하고자 할떄는 Hooking되는 대상 API의
전달되는 Parameter가 뭔지 미리 알아야 된다는 말이 됩니다.
ZwWrtieFile()의 인자전달 형태를 알아낸 분들께 감사드립니다. :)

NTSYSAPI
NTSTATUS
NTAPI
ZwWriteFile(
    IN HANDLE hFile,
    IN HANDLE hEvent OPTIONAL,
    IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
    IN PVOID IoApcContext OPTIONAL,
    OUT PIO_STATUS_BLOCK pIoStatusBlock,
    IN PVOID WriteBuffer,
    IN ULONG WriteBufferLength,
    IN PLARGE_INTEGER FileOffset OPTIONAL,
    IN PULONG LockOperationKey OPTIONAL
);

호출될때 전달되는 인자는 앞에 IN이 붙으며,
API가 처리된후 어떠한 결과를 돌려주는 인자는 OUT이 붙습니다.
NewZwWriteFile()함수 역시 위에와 같은 인자전달 형태를 취하고 있어야 합니다.

NTSTATUS NewZwWriteFile(
    IN HANDLE hFile,
    IN HANDLE hEvent OPTIONAL,
    IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
    IN PVOID IoApcContext OPTIONAL,
    OUT PIO_STATUS_BLOCK pIoStatusBlock,
    IN PVOID WriteBuffer,
    IN ULONG WriteBufferLength,
    IN PLARGE_INTEGER FileOffset OPTIONAL,
    IN PULONG LockOperationKey OPTIONAL
)
{
    NTSTATUS rc;

    DbgPrint("ZwWriteFile is Hookedn"); //훅되고 있음을 알림
    rc = ((ZWWRITEFILE)(OldZwWriteFile)) (
        hFile,               //원래 함수를 호출해서
        hEvent OPTIONAL,         //정상적으로 처리되게함
        IoApcRoutine OPTIONAL,
        IoApcContext OPTIONAL,
        pIoStatusBlock,
        WriteBuffer,
        WriteBufferLength,
        FileOffset OPTIONAL,
        LockOperationKey OPTIONAL );    

    return rc;
}

먼저 DbgPrint()함수를 이용하여 훅되고 있음을 DebugMessage로 출력합니다.
그런후 기존의 ZwWriteFile()주소를 담고 있는 OldZwWriteFile()을
호출 함으로써 기존의 ZwWriteFile() API를 호출합니다.
지금까지 작성한 코드를 컴파일 하려고 해보면 ZWWRITEFILE과
OldZwWriteFile등 Hooking Code를 작성하면서 새로 나온 것들이 선언되어
있지 않다는 에러 메시지를 만나게 됩니다.
다음을 해더 파일에 추가 시키면 문제는 해결됩니다.

typedef NTSTATUS (*ZWWRITEFILE)(
    IN HANDLE hFile,
    IN HANDLE hEvent OPTIONAL,
    IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
    IN PVOID IoApcContext OPTIONAL,
    OUT PIO_STATUS_BLOCK pIoStatusBlock,
    IN PVOID WriteBuffer,
    IN ULONG WriteBufferLength,
    IN PLARGE_INTEGER FileOffset OPTIONAL,
    IN PULONG LockOperationKey OPTIONAL
);
ZWWRITEFILE    OldZwWriteFile;

이제 Hook설치 Code는 제작이 되었는데 초반부에서 말했던 것처럼,
Hook을 설치했다면,Unload 루틴에는 Hook 설치를 해제 하는 부분이
존재해야 합니다. Hook설치를 해제 하는 방법은 밑에와 같이 아주 간단합니다.

VOID DriverUnload (
        IN PDRIVER_OBJECT    pDriverObject    ) {

    DbgPrint("---My Driver Unloadded---\n");    //언로드시 출력

    _asm
    {
        CLI
        MOV    EAX, CR0    
        AND EAX, NOT 10000H
        MOV    CR0, EAX
    }
    (ZWWRITEFILE)
    (SYSTEMSERVICE(ZwWriteFile))   //OldZwWriteFile으로 채운다.
    = OldZwWriteFile;
    _asm
    {
        MOV    EAX, CR0        
        OR    EAX, 10000H        
        MOV    CR0, EAX            
        STI    
    }
    
}

OldZwWriteFile에 저장해두었던 원래의 값으로 다시 채워넣기만 하면,
Hook해제는 끝입니다. 정말 쉽죠? :)
아! Hook을 설치하는 SetupSTBHook()를 언제 호출하는지 언급하지 않았군요.
DriverEntry()에서 호출 하여 주면 됩니다.


Filtering




그렇다면 Native API를 Hooking 하는 방법으로는 무조건
Global Hooking[전역후킹]만 할수 있는 것일까요?
조건적으로 원하는 프로세스에서 호출하는것만 처리하게 할수는
없는 것일까요? 물런 처리 할수 있습니다. :)
먼저 GetProcessNameOffset()이라는 루틴을 만듭니다.

VOID GetProcessNameOffset( void ) {
    
   PEPROCESS curproc;
   int i;

    curproc = PsGetCurrentProcess(); //(1)
       for( i = 0; i < 3*PAGE_SIZE; i++ )
    {
       if( !strncmp( "System", (PCHAR) curproc + i, strlen("System") ))
     {
         gProcessNameOffset = i;
     }
     }
}

(1)에서 현재 Process를 구해옵니다.
그런후 PAGESIZE * 3까지 돌면서
Process + 루프돌고 있는 값의
문자열이 "System"일떄까지 루프를 돕니다.
"System"이라는 문자열을 만났다면,
gProcessNameOffset라는 변수에 현재의 i값을 대입합니다.
gProcessNameOffset는 해더 파일에 선언해 주면 됩니다.
GetProcessNameOffset()루틴은 DriverEntry()해서 호출해 주면 됩니다.
이를 호출하는것까지 추가 시킨 DriverEntry()는 밑에와 같습니다.

NTSTATUS DriverEntry (
            IN PDRIVER_OBJECT pDriverObject,
            IN PUNICODE_STRING pRegistryPath    ) {

    NTSTATUS status;
    int i;

    DbgPrint("---Driver Loaded---\n");    //(1)드라이버 로드시 출력

    for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) {
        pDriverObject->MajorFunction[i] = DispatchPassThru; //(2)PassThru
    }

    pDriverObject->DriverUnload = DriverUnload;    //(3)드라이버 언로드시 호출
    GetProcessNameOffset();    //GPNO
    SetupSTBHook();    //훅 설치

    status = STATUS_SUCCESS;    //상태값
    return status;        //리턴
}

Offset를 구해오는 루틴을 작성했으니,
이제 ProcessName을 구해오는 루틴을 작성하도록 하겠습니다.

BOOL GetProcessName( PCHAR theName )
{
   PEPROCESS     curproc;
   char        *nameptr;

   if( gProcessNameOffset )  //오프셋구해오기에 성공했다면
   {
     curproc = PsGetCurrentProcess(); //(1)현재 프로세스를 구해온다.
     nameptr  = (PCHAR) curproc + gProcessNameOffset; //현재 프로세스로부터의 거리
     strncpy( theName, nameptr, NT_PROCNAMELEN ); //문자열을 복사해 넣는다.
     theName[NT_PROCNAMELEN] = 0; //문자열의 마지막에 Null문자 삽입
    return TRUE; //성공했다면 TRUE 반환
   }
    return FALSE; //실패시 FALSE 반환
}

위에가 바로 프로세스이름을 구해오는 루틴입니다.
(1)에서 현재 프로세스를 구해온다고 했는데,
여기서 말하는 현재 프로세스는 Hooked 되고 있는
Native API를 호출한 프로세스가 됩니다. :)
이 루틴은 어디서 호출해야 할까요?
당연히 Hooking시에 필터링을 해야함으로
NewZwWrtieFile()에 집어넣어야 합니다.
밑의 코드는 필터링을 적용시킨 NewZwWriteFile() 코드입니다.

NTSTATUS NewZwWriteFile(
    IN HANDLE hFile,
    IN HANDLE hEvent OPTIONAL,
    IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
    IN PVOID IoApcContext OPTIONAL,
    OUT PIO_STATUS_BLOCK pIoStatusBlock,
    IN PVOID WriteBuffer,
    IN ULONG WriteBufferLength,
    IN PLARGE_INTEGER FileOffset OPTIONAL,
    IN PULONG LockOperationKey OPTIONAL
)
{
    NTSTATUS rc;
    CHAR aProcessName[PROCNAMELEN];
    
    GetProcessName( aProcessName );    //호출한 프로세스 이름을 구해옴

    DbgPrint("ZwWriteFile was Hooked\n"); //훅되고 있음을 알림
    rc = ((ZWWRITEFILE)(OldZwWriteFile)) (
        hFile,               //원래 함수를 호출해서
        hEvent OPTIONAL,         //정상적으로 처리되게함
        IoApcRoutine OPTIONAL,
        IoApcContext OPTIONAL,
        pIoStatusBlock,
        WriteBuffer,
        WriteBufferLength,
        FileOffset OPTIONAL,
        LockOperationKey OPTIONAL );    

    if(!strncmp( aProcessName, "NOTEPAD", 7))
    {
        DbgPrint("ZwWriteFile was called by Notepad :)\n");
        /*
            이곳에 처리하고 싶은걸 넣는다.
        */
    }

    return rc;
}

대상 프로세스가 NOTEPAD[메모장] 일때만 DbgPrint()로
DebugMessage를 출력합니다. DbgPrint()가 존재하는 저부분에
별도의 원하는 코드를 넣는다면, Notepad에서 ZwWriteFile()을
호출할떄만 작동하게 됩니다.
PROCNAMELEN이라는 값은 밑의 값인데 해더파일에 선언해주면 됩니다.

#define PROCNAMELEN   20

이로써 이번글의 목표인 원하는 프로세스만을 처리하는
필터처리 방법까지를 모두 전게하게 되었습니다.


 


지금까지 만든 드라이버를 테스트 해보고 싶으시면,


같이 올린 드라이버 로더를 이용해 보시면 됩니다.


로더와 드라이버는 같은 폴더에 있어야 하도록 제작했습니다.


드라이버 로더의 소스 같은 경우는 별도의 설명을 하지 않겠습니다.


소스에 주석이 달려있고 별로 어렵지 않으니 말입니다. :)
Part2에서는 이를 이용한 응용 분야에 대한 설명과
예제코드들을 다루어 보도록 하겠습니다.






For related articles see:
Sysinternals Freeware - Inside the Native API
Windows NT System Calls
Undocumented Windows NT
Programming the Microsoft Windows Driver Model
NDIS
Win2K/XP SDT Restore 0.2 (Proof-Of-Concept)

'KB > Win32/x86' 카테고리의 다른 글

임베디드 동향  (0) 2006.04.11
PE header  (0) 2005.10.28
XML Webservice on chip  (0) 2005.05.03
Win32 Trace API  (0) 2005.05.03
crash finder  (0) 2005.04.14

+ Recent posts