Programming/Kernel / Driver2008. 12. 3. 11:37

Driver를 작성하면서, 때때로, 후킹을 한다거나 할 때에도 EPROCESS, ETHREAD 구조체에서 얻고자 하는 값의 오프셋이 필요할 때가 있습니다.

이것은 Windows버전마다 EPROCESS, ETHREAD의 구조가 다를뿐만 아니라, EPROCESS, ETHREAD 구조체가 ntddk 헤더에 정의되어있지 않기 때문이기도 합니다.

때문에 값을 얻어오기 위해 오프셋을 알아야 하는데,

저같은 경우는 후킹을 위해서 EPROCESS 구조체의 ImageFileName, UniqueProcessId 오프셋이 필요하고,

ETHREAD에서는 ClientId 오프셋이 필요합니다.

UniqueProcessId인 경우는 그냥 PsGetProcessId()를 이용하면 쉽게 구할 수 있습니다.

하지만 굳이 UniqueProcessId의 오프셋을 구하고 싶다면, 아래와 같이 하면 됩니다.


LONG

NTAPI

GetProcessIdOffset()

{

        PEPROCESS CurrentProcess=PsGetCurrentProcess();

        HANDLE CurrentProcessId=PsGetCurrentProcessId();

        ULONG Offset=0;

        for(Offset=0;Offset<PAGE_SIZE*4;Offset++)

        {

                if(*(PHANDLE)((char *)CurrentProcess+Offset)==CurrentProcessId))

                {

                         return Offset;

                }

        }

        return -1;

}


그리고 UniqueProcessId값을 굳이 구하고 싶다면, 위에 함수를 연결시켜서 아래와 같이 하면 되겠죠?


HANDLE

NTAPI

PsGetProcessId(

        IN PEPROCESS Process)

{

        LONG Offset=GetProcessIdOffset();

        if(Offset!=-1)

        {

                return Process ? *(PHANDLE)((char *)Process+Offset) : NULL;

        }

        return NULL;

}


그리하여 오프셋을 얻어오면 값을 구할 수 있지만,

하지만 오프셋 역시 Windows 버전마다 틀리므로 PsGetVersion() 또는 RtlGetVersion()을 이용하여 OS Version을 구해와서 OS별로 Switch-Case문으로 값을 넣어줘야 합니다.


하지만, 몇몇 오프셋은 일정한 값으로 구해올 수 있습니다.


그 예가 아래에 있습니다.

LONG GetProcessImageFileNameOffset()

{

       ULONG Offset=0;

       PEPROCESS CurrentProcess=PsGetCurrentProcess();

       for(Offset=0;Offset<PAGE_SIZE*4;Offset++)

       {

               if(!strncmp((char *)((char *)CurrentProcess+Offset), "System", 6))

               {

                      return Offset;

               }

       }

       return -1;

}

이 함수는 프로세스 이미지 파일 이름(ImageFileName)의 오프셋을 구해오는 함수입니다.

하지만, 이 함수의 한계점은 DriverEntry()에서만 호출해야 정상적으로 작동할 수 있다는 것입니다.

그 이유는, DriverEntry()는 "System" 이라는 이름을 가진 프로세스가 호출하기 때문입니다.


이 함수를 어디에서 호출하던간에, 알맞게 동작하도록 바꿔 보았습니다.


__declspec(dllimport) PEPROCESS PsInitialSystemProcess;

LONG GetProcessImageFileNameOffset()

{

       ULONG Offset=0;

       /*We used PsGetCurrentProcess(), but now, use PsInitialSystemProcess.*/

       PEPROCESS SystemProcess=PsInitialSystemProcess;

       for(Offset=0;Offset<PAGE_SIZE*4;Offset++)

       {

               if(!strncmp((char *)((char *)SystemProcess+Offset), "System", 6))

               {

                      return Offset;

               }

       }

       return -1;

}


전에는 PsGetCurrentProcess()를 사용하여 그것을 DriverEntry()에서 호출하였으나,

이제는 System Process의 프로세스 객체 포인터(PEPROCESS)를 Import해서 씁니다.

ntoskrnl.exe에 export 되어 있습니다.


그리고, 제가 PsGetThreadClientId() 를 직접 구현해 보았습니다.

이 함수는 ETHREAD 구조체의 ClientId의 오프셋을 구해온 후, CLIENT_ID 구조체의 포인터를 리턴해주는 기능을 가지고 있습니다.


//이 함수는 Win2000에서 테스트 되었습니다~

PCLIENT_ID

NTAPI

PsGetThreadClientId(

       IN PETHREAD Thread)

{

        HANDLE CurrentProcessId=PsGetCurrentProcessId();

        HANDLE CurrentThreadId=PsGetCurrentThreadId();

        PETHREAD CurrentThread=PsGetCurrentThread();

        ULONG Offset=0;

        PCLIENT_ID ClientId;

        //오프셋을 증가시켜가면서 값을 찾는다

        for(Offset=0;Offset<PAGE_SIZE*4;Offset++)

        {

                 ClientId=(CLIENT_ID *)((char *)CurrentThread+Offset);

                 //ClientId->UniqueProcess : Owner Process Id

                 //ClientId->UniqueThread : Thread Id

                 if(ClientId->UniqueProcess==CurrentProcessId &&

                           ClientId->UniqueThread==CurrentThreadId)

                 {

                          DbgPrint("Found Offset at 0x%X", Offset);

                          //찾았다!

                          if(Thread)

                          {

                                  return (CLIENT_ID *)((char *)Thread+Offset);

                          }

                          break;

                 }

        }

        return NULL;

}


인자값이(PEPROCESS) 올바른가 알아보기

인자값이(PEPROCESS) 올바른가 알아보려면

ObReferenceObjectByPointer()를 이용해서

리턴값을 얻어서

NT_SUCCESS(리턴값)==1이면 올바른 프로세스 객체인거고,

0인 경우에는 리턴값에 따라서 잘못된 값인지, 엑세스가 거부되었는지 등등의 자세한 상태를 알 수 있습니다.

Posted by skensita