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

//
// Executive Process (EPROCESS)
//
typedef struct _EPROCESS
{
    KPROCESS Pcb;
    EX_PUSH_LOCK ProcessLock;
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER ExitTime;
    EX_RUNDOWN_REF RundownProtect;
    HANDLE UniqueProcessId;
    LIST_ENTRY ActiveProcessLinks;
    ULONG QuotaUsage[3];
    ULONG QuotaPeak[3];
    ULONG CommitCharge;
    ULONG PeakVirtualSize;
    ULONG VirtualSize;
    LIST_ENTRY SessionProcessLinks;
    PVOID DebugPort;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
    union
    {
        PVOID ExceptionPortData;
        ULONG ExceptionPortValue;
        UCHAR ExceptionPortState:3;
    };
#else
    PVOID ExceptionPort;
#endif
    PHANDLE_TABLE ObjectTable;
    EX_FAST_REF Token;
    ULONG WorkingSetPage;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
    EX_PUSH_LOCK AddressCreationLock;
    PETHREAD RotateInProgress;
#else
    FAST_MUTEX AddressCreationLock;

// FIXME: FAST_MUTEX for XP, KGUARDED_MUTEX for 2K3
    KSPIN_LOCK HyperSpaceLock;
#endif
    PETHREAD ForkInProgress;
    ULONG HardwareTrigger;
    MM_AVL_TABLE PhysicalVadroot;
    PVOID CloneRoot;
    ULONG NumberOfPrivatePages;
    ULONG NumberOfLockedPages;
    PVOID *Win32Process;
    struct _EJOB *Job;
    PVOID SectionObject;
    PVOID SectionBaseAddress;
    PEPROCESS_QUOTA_BLOCK QuotaBlock;
    PPAGEFAULT_HISTORY WorkingSetWatch;
    PVOID Win32WindowStation;
    HANDLE InheritedFromUniqueProcessId;
    PVOID LdtInformation;
    PVOID VadFreeHint;
    PVOID VdmObjects;
    PVOID DeviceMap;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
    ULONG AlpcPagedPoolQuotaCache;
    PVOID EtwDataSource;
    PVOID FreeTebHint;
#else
    PVOID Spare0[3];
#endif
    union
    {
        HARDWARE_PTE_X86 PagedirectoryPte;
        ULONGLONG Filler;
    };
    ULONG Session;
    CHAR ImageFileName[16];
    LIST_ENTRY JobLinks;
    PVOID LockedPagesList;
    LIST_ENTRY ThreadListHead;
    PVOID SecurityPort;
    PVOID PaeTop;
    ULONG ActiveThreads;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
    ULONG ImagePathHash;
#else
    ACCESS_MASK GrantedAccess;
#endif
    ULONG DefaultHardErrorProcessing;
    NTSTATUS LastThreadExitStatus;
    struct _PEB* Peb;
    EX_FAST_REF PrefetchTrace;
    LARGE_INTEGER ReadOperationCount;
    LARGE_INTEGER WriteOperationCount;
    LARGE_INTEGER OtherOperationCount;
    LARGE_INTEGER ReadTransferCount;
    LARGE_INTEGER WriteTransferCount;
    LARGE_INTEGER OtherTransferCount;
    ULONG CommitChargeLimit;
    ULONG CommitChargePeak;
    PVOID AweInfo;
    SE_AUDIT_PROCESS_CREATION_INFO SeAuditProcessCreationInfo;
    MMSUPPORT Vm;
    LIST_ENTRY MmProcessLinks;
    ULONG ModifiedPageCount;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
    union
    {
        struct
        {
            ULONG JobNotReallyActive:1;
            ULONG AccountingFolded:1;
            ULONG NewProcessReported:1;
            ULONG ExitProcessReported:1;
            ULONG ReportCommitChanges:1;
            ULONG LastReportMemory:1;
            ULONG ReportPhysicalPageChanges:1;
            ULONG HandleTableRundown:1;
            ULONG NeedsHandleRundown:1;
            ULONG RefTraceEnabled:1;
            ULONG NumaAware:1;
            ULONG ProtectedProcess:1;
            ULONG DefaultPagePriority:3;
            ULONG ProcessDeleteSelf:1;
            ULONG ProcessVerifierTarget:1;
        };
        ULONG Flags2;
    };
#else
    ULONG JobStatus;
#endif
    union
    {
        struct
        {
            ULONG CreateReported:1;
            ULONG NoDebugInherit:1;
            ULONG ProcessExiting:1;
            ULONG ProcessDelete:1;
            ULONG Wow64SplitPages:1;
            ULONG VmDeleted:1;
            ULONG OutswapEnabled:1;
            ULONG Outswapped:1;
            ULONG ForkFailed:1;
            ULONG Wow64VaSpace4Gb:1;
            ULONG AddressSpaceInitialized:2;
            ULONG SetTimerResolution:1;
            ULONG BreakOnTermination:1;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
            ULONG DeprioritizeViews:1;
#else
            ULONG SessionCreationUnderway:1;
#endif
            ULONG WriteWatch:1;
            ULONG ProcessInSession:1;
            ULONG OverrideAddressSpace:1;
            ULONG HasAddressSpace:1;
            ULONG LaunchPrefetched:1;
            ULONG InjectInpageErrors:1;
            ULONG VmTopDown:1;
            ULONG ImageNotifyDone:1;
            ULONG PdeUpdateNeeded:1;
            ULONG VdmAllowed:1;
            ULONG SmapAllowed:1;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
            ULONG ProcessInserted:1;
#else
            ULONG CreateFailed:1;
#endif
            ULONG DefaultIoPriority:3;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
            ULONG SparePsFlags1:2;
#else
            ULONG Spare1:1;
            ULONG Spare2:1;
#endif
        };
        ULONG Flags;
    };
    NTSTATUS ExitStatus;
#if (NTDDI_VERSION >= NTDDI_LONGHORN)
    USHORT Spare7;
#else
    USHORT NextPageColor;
#endif
    union
    {
        struct
        {
            UCHAR SubSystemMinorVersion;
            UCHAR SubSystemMajorVersion;
        };
        USHORT SubSystemVersion;
    };
    UCHAR PriorityClass;
    MM_AVL_TABLE VadRoot;
    ULONG Cookie;
} EPROCESS, *PEPROCESS;


Documented By :

ReactOS


P.S : 이 EPROCESS 구조체는 Windows 버전에 따라서 호환이 될수도 있고, 안될수도 있습니다

Posted by skensita
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
Programming/Kernel / Driver2008. 12. 3. 11:37

제가 ActiveProcessLinks의 오프셋을 어떻게하면 구할수 있을까 생각하다가

아주 좋은 방법이 떠올랐습니다 +_+


EPROCESS 구조체의 정보를 보면서 하나하나 알아보죠 ㅇ_ㅇ

다음은 각 Windows별로 EPROCESS 구조체 정보를 생략한 것입니다.

(※이 구조체 정보는 타사이트서 퍼왔습니다)


이 방법은 최소한(?) 다음 Windows에서 성공적으로 동작합니다:

Windows 2000 SP4

Windows XP SP2

Windows Server 2003 No SP

Windows Server 2003 SP1

우선 Windows 2000부터 봅시다.

Windows 2000 SP4

   +0x000 Pcb              : _KPROCESS
   +0x06c ExitStatus       : Int4B
   +0x070 LockEvent        : _KEVENT
   +0x080 LockCount        : Uint4B
   +0x088 CreateTime       : _LARGE_INTEGER
   +0x090 ExitTime         : _LARGE_INTEGER
   +0x098 LockOwner        : Ptr32 _KTHREAD
   +0x09c UniqueProcessId  : Ptr32 Void
   +0x0a0 ActiveProcessLinks : _LIST_ENTRY ---->ActiveProcessLinks
   +0x0a8 QuotaPeakPoolUsage : [2] Uint4B
ActiveProcessLinks의 오프셋은 0x0a0.

UniqueProcessId과 QuotaPeakPoolUsage 사이에 위치해 있군요.


다음은 Windows XP SP2......

Windows XP SP2

   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER
   +0x078 ExitTime         : _LARGE_INTEGER
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : Ptr32 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY ---->ActiveProcessLinks
   +0x090 QuotaUsage       : [3] Uint4B
ActiveProcessLinks의 오프셋은 0x088.

UniqueProcessId과 QuotaUsage 사이에 위치해 있군요.


이번에는 Windows Server 2003 No SP

Windows Server 2003 No SP

   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER
   +0x078 ExitTime         : _LARGE_INTEGER
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : Ptr32 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY ---->ActiveProcessLinks
   +0x090 QuotaUsage       : [3] Uint4B
ActiveProcessLinks의 오프셋은 0x088.

UniqueProcessId과 QuotaUsage 사이에 위치해 있군요.


마지막으로, Windows Server 2003 SP1을 알아봅시다.

Windows Server 2003 SP1

   +0x000 Pcb              : _KPROCESS
   +0x078 ProcessLock      : _EX_PUSH_LOCK
   +0x080 CreateTime       : _LARGE_INTEGER
   +0x088 ExitTime         : _LARGE_INTEGER
   +0x090 RundownProtect   : _EX_RUNDOWN_REF
   +0x094 UniqueProcessId  : Ptr32 Void
   +0x098 ActiveProcessLinks : _LIST_ENTRY ---->ActiveProcessLinks
   +0x0a0 QuotaUsage       : [3] Uint4B
ActiveProcessLinks의 오프셋은 0x098.

UniqueProcessId과 QuotaUsage 사이에 위치해 있군요.


그런데, 위에서 자세히 보면 ActiveProcessLinks의 오프셋은, UniqueProcessId+4 라는것을 알수가 있군요 +_+

실제로 구해 볼까요?

Windows 2000 SP4 : 0x09c+0x004=0x0a0

Windows XP SP2 : 0x084+0x004=0x088

Windows Server 2003 No SP : 0x084+0x004=0x088(Windows XP SP2와 동일)

Windows Server 2003 SP1 : 0x094+0x004=0x098


결과적으로,


ActiveProcessLinks Offset = UniqueProcessId Offset + 4

ActiveProcessLinks=(PLIST_ENTRY)((char *)EPROCESS 구조체 포인터+UniqueProcessId Offset+4)


음......

과연 다른 운영체제에서는 잘 동작하는지 궁금하네요 +_+

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:35

전에 구현한 PsGetNextProcess()를 이용해서 PsLookupProcessByProcessId()를 구현해 보았습니다.

원래는 PsGetFirstProcess()를 쓰려 했으나, 그냥 PsInitialSystemProcess를 넣어주면 될것 같아서 그냥

PsInitialSystemProcess를 썼습니다.


원함수의 원형은 이와 같습니다.

NTKERNELAPI

NTSTATUS

NTAPI

PsLookupProcessByProcessId(

       IN HANDLE ProcessId,               //프로세스 아이디

       OUT PEPROCESS *Process       //성공했을 때에는 여기로 EPROCESS 구조체의 포인터가 넘어온다

       );


제가 만든 함수의 코드는 이렇습니다.


NTSTATUS
NTAPI
PsLookupProcessByProcessId(
       IN HANDLE ProcessId,
       OUT PEPROCESS *Process)
{

       /*Get System Process Object*/
       PEPROCESS NextProcess=PsInitialSystemProcess;
       NTSTATUS Status=STATUS_SUCCESS;

       /*Unexpected Situation! PsInitialSystemProcess==NULL????*/
       if(!NextProcess) return STATUS_UNSUCCESSFUL;
       if(!Process) return STATUS_INVALID_PARAMETER;

       /*Loop and find target Process*/
       while(NT_SUCCESS(Status))
       {

              /*Check if Process is target Proces*/
              if(ProcessId==PsGetProcessId(NextProcess))
              {

                     /*Reference the Object*/
                     Status=ObReferenceObjectByPointer(
                            NextProcess,
                            PROCESS_ALL_ACCESS,
                            PsProcessType,
                            KernelMode);

                     /*Is Object Valid?*/
                     if(NT_SUCCESS(Status))
                     {

                            /*Found it! Now, Save the Pointer.....*/
                            *Process=NextProcess;
                     }

                     /*return it*/
                     return Status;
              }

              /*Get Next Process*/
              Status=PsGetNextProcess(&NextProcess);
       }

       /*Process Not Found*/
       return STATUS_NOT_FOUND;
}

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:35

이 함수들은 제가 직접 작성한 것으로, 현재 떠 있는 프로세스를 구할 수 있습니다.


PsGetFirstProcess()

Idle Process를 제외한 가장 첫번째 프로세스의 EPROCESS 구조체의 포인터를 구합니다.

성공시 STATUS_SUCCESS를 리턴,

실패시 STATUS_NOT_FOUND, STATUS_INVALID_PARAMETER를 리턴.

구현은 했지만, 그다지 필요한 것 같지는 않네요(그럼 왜 구현한 거지 ㅡ.ㅡ)


PsGetNextProcess()

이 함수의 원형은

PEPROCESS

PsGetNextProcess(

       IN PEPROCESS Process);

라고 합니다.

(하지만, 제가 임의로 바꾸었습니다.)

인자로 들어온 EPROCESS 구조체의 포인터에서 ActiveProcessLinks를 얻어와 다음 프로세스의 EPROCESS 구조체의 포인터를 구합니다.

성공시 STATUS_SUCCESS를 리턴,

실패시 STATUS_UNSUCCESSFUL, STATUS_NOT_FOUND, STATUS_INVALID_PARAMETER를 리턴.


이 함수는 다음 Windows에서 성공적으로 테스트되었습니다:

Windows 2000 Professional SP4


NTSTATUS
NTAPI
PsGetFirstProcess(
        OUT PEPROCESS *FirstProcess)
{
        if(!FirstProcess) return STATUS_INVALID_PARAMETER;
        *FirstProcess=PsInitialSystemProcess;

        /*PsInitialSystemProcess=System Process Object*/
        if(!*FirstProcess) return STATUS_NOT_FOUND;

        /*Success, return it*/
        return STATUS_SUCCESS;
}

솔직히, PsGetFirstProcess()는 위의 코드대로라면 PsInitialSystemProcess로 대체시킬 수 있습니다.

단순히 값을 얻어온 후 검사하고 리턴하는 기능만 하기 때문이죠.


NTSTATUS
NTAPI
PsGetNextProcess(
        IN OUT PEPROCESS *NextProcess)
{
        ULONG ActiveProcessLinksOffset=0, ProcessIdOffset=0;
        BOOLEAN bFound=FALSE;
        PLIST_ENTRY ActiveProcessLinks;
        PEPROCESS Process;
        PEPROCESS CurrentProcess=PsGetCurrentProcess();
        HANDLE CurrentProcessId=PsGetCurrentProcessId();

        /*PsInitialSystemProcess is System Process' Process Object.*/

        HANDLE SystemProcessId=PsGetProcessId(PsInitialSystemProcess);
        if(!NextProcess) return STATUS_INVALID_PARAMETER;

        /*Reference the Object*/

        Status=ObReferenceObjectByPointer(*NextProcess, PROCESS_ALL_ACCESS,
                  PsProcessType, KernelMode);

        /*invalid EPROCESS structure Pointer, return it*/
        if(!NT_SUCCESS(Status)) return Status;
        /*Get ProcessId Offset*/

        for(ProcessIdOffset=0;ProcessIdOffset<PAGE_SIZE*4;ProcessIdOffset++)
        {
                if(*(PHANDLE)((PCHAR)CurrentProcess+ProcessIdOffset)==CurrentProcessId)
                {
                        bFound=TRUE;
                        break;
                }
        }
        if(!bFound)
        {

                /*Cannot find ProcessId Offset, return status*/
                return STATUS_UNSUCCESSFUL;
        }
        /*ActiveProcessLinks Offset=UniqueProcessId Offset+4*/
        ActiveProcessLinksOffset=ProcessIdOffset+4;
        /*Get ActiveProcessLinks*/

        ActiveProcessLinks=(PLIST_ENTRY)((char *)*NextProcess+ActiveProcessLinksOffset);

        /*Move to Next Process*/
        Process=(PEPROCESS)ActiveProcessLinks->Flink;
        Process=(PEPROCESS)((char *)Process-ActiveProcessLinksOffset);
        /*(PEPROCESS)Process!=null*/

        if(Process)
        {
                if(SystemProcessId!=PsGetProcessId(Process))
                {

                        /*Found it! Now, Dereference the Object*/

                        ObDereferenceObject(*NextProcess);

                        /*Save the EPROCESS Pointer*/
                        *NextProcess=Process;

                        /*return success......*/
                        return STATUS_SUCCESS;
                }
        }

        /*End of Process, or Process not found*/
        return STATUS_NOT_FOUND;
}

위의 함수들은 ActiveProcessLinks로 다음 프로세스를 찾아 이동하므로,

ActiveProcessLinks를 Unlink한다면 Unlink된 프로세스의 EPROCESS는 구할 수 없지만,

ZwQuerySystemInformation() 후킹으로 인한 숨겨진 프로세스는 찾아낼 수 있습니다.

다음에는, 이를 이용한 PsLookupProcessByProcessId() 구현에 대해 올려놓겠습니다.

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:34

ZwCreateFile()....여러모로 쓸모있는 함수입니다.

파일 핸들을 얻어올 때, 파일을 생성할 때, 파일을 이동할 때, 파일을 삭제할 때 등등 여러모로 쓰이는 유용한 함수인데.....

(파일 삭제할때도 쓰이는줄은 몰랐었다)


원형을 보면

NTKERNELAPI

NTSTATUS
NTAPI
ZwCreateFile(
      OUT PHANDLE FileHandle,
      IN ACCESS_MASK DesiredAccess,
      IN POBJECT_ATTRIBUTES ObjectAttributes,
      OUT PIO_STATUS_BLOCK IoStatusBlock,
      IN PLARGE_INTEGER AllocationSize OPTIONAL,
      IN ULONG FileAttributes,
      IN ULONG ShareAccess,
      IN ULONG CreateDisposition,
      IN ULONG CreateOptions,
      IN PVOID EaBuffer OPTIONAL,
      IN ULONG EaLength);

......이렇습니다.


그러나!

이렇게 인자가 많은 반면, 실질적으로 이 함수가 하는 일은 단순히 NewAPI인 IoCreateFile()을 호출하는 역할만 한다는거.....!

(원래 이 일은 ZwCreateFile가 아닌 NtCreateFile이 한다)


직접 구현해 보면 이렇습니다.

NTSTATUS
NTAPI
ZwCreateFile(
    OUT PHANDLE FileHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes,
    OUT PIO_STATUS_BLOCK IoStatusBlock,
    IN PLARGE_INTEGER AllocationSize OPTIONAL,
    IN ULONG FileAttributes,
    IN ULONG ShareAccess,
    IN ULONG CreateDisposition,
    IN ULONG CreateOptions,
    IN PVOID EaBuffer OPTIONAL,
    IN ULONG EaLength)
{
      return IoCreateFile(
            FileHandle,
            DesiredAccess,
            ObjectAttributes,
            IoStatusBlock,
            AllocationSize,
            FileAttributes,
            ShareAccess,
            CreateDisposition,
            CreateOptions,
            EaBuffer,
            EaLength,
            CreateFileTypeNone,
            NULL,
            0);

}

Documented By :

ReactOS


실제로, 제가 ZwCreateFile()을 후킹해서 위와 같은 함수주소로 바꿔치기하여 성공적으로 작동하는지 시도해 본 결과,

성공적으로 잘 되었습니다.

후킹할 때, IoCreateFile()이 후킹되지 않았다면, ZwCreateFile()의 원래 함수를 호출하는 대신, IoCreateFile()을 호출하면 되겠군요!

복구시에는 저장했던 함수의 주소로 다시 바꿔치면......

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:33

Windows에서 프로세스를 다루기 위해서는, 대부분 프로세스 핸들을 얻어 와야 합니다.

핸들을 얻기 위해 사용되는 함수는 OpenProcess()이며, Kernel32.dll에 Export되어 있습니다.

하지만, OpenProcess()는 ntdll.dll의 NtOpenProcess()를 호출하고, NtOpenProcess()는 실질적으로

ntoskrnl.exe의 ZwOpenProcess()를 호출합니다.

이러한 ZwXxx 함수들은 대개 ntdll.dll의 NtXxx 함수들이 결과적으로 호출하게 되는 실질적인 함수인데,

이번에는 ZwOpenProcess()에 대해서만 알아보고, 구현해봅시다.


ZwOpenProcess()는 DDK 헤더 파일에 정의되어 있지 않습니다.(NtOpenProcess()는 정의되어 있는것 같다)

함수의 원형은 다음과 같습니다.

NTKERNELAPI

NTSTATUS

NTAPI

ZwOpenProcess(

      OUT PHANDLE ProcessHandle,                          //성공했을 시 여기로 프로세스 핸들이 넘어온다

      IN ACCESS_MASK DesiredAccess,                    //프로세스를 어떤 권한으로 열 것인가??

      IN POBJECT_ATTRIBUTES ObjectAttributes,         //핸들 상속 권한, 프로세스 이름(ObjectAttributes->ObjectName)

      IN PCLIENT_ID ClientId OPTIONAL);                    //ClientId->UniqueProcess에 프로세스 아이디를 넣으면 된다

이제 원형과 인자들에 대해서 대충 알아보았으니, ZwOpenProcess()가 어떻게 핸들을 얻어오는지 알아봅시다.


ZwOpenProcess()는 일단 SeCreateAccessState()를 호출해서 AccessState를 얻어온 후, 디버그 권한이 있는지를 확인하여 디버그 권한이 있을 경우, AccessState에 추가 권한을 부여합니다.

ObjectAttributes->ObjectName이 NULL 스트링이 아닌 경우, ObOpenObjectByName()을 이용하여 프로세스 핸들을 얻어옵니다.

다른 경우에는(ClientId->UniqueProcess가 유효한 경우) PsLookupProcessByProcessId()를 이용하여 프로세스가 존재하는지를 파악하고, 프로세스 객체를 얻습니다. 그리고 프로세스 객체는 ObOpenObjectByPointer()를 호출하는데에 쓰이게 됩니다.

결과적으로, ObOpenObjectByName()이나 ObOpenObjectByPointer()를 이용하여 얻어온 핸들은 ProcessHandle에 넘겨지게 됩니다.


이제 ZwOpenProcess()가 어떻게 프로세스 핸들을 얻어 오느냐를 알았으니, 간단히 구현해봅시다.

(참고로, 엑세스 권한 확인은 뺐습니다)


NTSTATUS
NTAPI
ZwOpenProcess(
 OUT PHANDLE ProcessHandle,
 IN ACCESS_MASK DesiredAccess,
 IN POBJECT_ATTRIBUTES ObjectAttributes,
 IN PCLIENT_ID ClientId OPTIONAL)
{
 KPROCESSOR_MODE PreviousMode=KeGetPreviousMode();
 NTSTATUS Status=STATUS_SUCCESS;
 PEPROCESS Process;
 if(!ProcessHandle) return STATUS_INVALID_PARAMETER_1;

 /*Unexpected Situation! Has ProcessId And ObjectName*/
 if(ClientId && ObjectAttributes->ObjectName!=NULL)
  return STATUS_INVALID_PARAMETER_MIX;
 if(ClientId)
 {

  /*Is Process Valid?*/
  Status=PsLookupProcessByProcessId(ClientId->UniqueProcess, &Process);
  if(NT_SUCCESS(Status))
  {

   /*OK, Now Get Process Handle By Pointer(EPROCESS *)*/
   Status=ObOpenObjectByPointer(Process, ObjectAttributes->Attributes, NULL, 0, PsProcessType, PreviousMode, ProcessHandle);
  }

  /*Return Status*/
  return Status;
 }
 else if(ObjectAttributes->ObjectName!=NULL)
 {

  /*Get Process Handle By Process Name*/
  Status=ObOpenObjectByName(ObjectAttributes, PsProcessType, PreviousMode, NULL, 0, NULL, ProcessHandle);

  /*Return Status*/
  return Status;
 }

 /*Both Process Id And Object Name is Invalid, Return NtCurrentProcess().*/
 *ProcessHandle=(HANDLE)0xffffffff;

 /*Return Success*/
 return STATUS_SUCCESS;
}


p.s : 테스트는 아직 못해봤습니다

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:33
음 이번에는 Export되지 않은 Symbol 중 PspCidTable의 주소를 구하는 법에 대해 알아봅시다.(약간 노가다적인 방법)
저는 두가지 가정을 하고 시작했습니다.
첫째, PspCidTable의 올바른 주소를 알고 있다.
둘째, PspCidTable은 0x80400000(Kernel Base)~0x81000000 까지 존재한다.

제가 시도해 봤던 방법인데 너무 힘들었습니다;

약간 말이 안되는 방법일 수도 있지만

그런대로 Win2000, WinXP, Win2003 다 잘 구해지더군요

주소도 알맞고......

이제부터 그 방법을 알아보도록 하죠.


우선 밑의 스샷을 봅시다.


사용자 삽입 이미지
 
이 스샷은 제가 파일로 출력한 0x80400000~0x82000000까지의 주소에 들어있는 값들입니다.
원래는 0x81000000까지 하려 했지만 확인을 위해서 0x82000000까지 했습니다.
 
여기서, 구해둔 실제 PspCidTable의 주소를 Ctrl-F키를 눌러서 없을 때까지 찾습니다(주소는 적어둡시다).
(저희집 컴퓨터에서의 PspCidTable의 실제주소는 0x80480388 이었습니다)
여기에서, DUMP : 0x80483088(p=0x80454C60), Offset 86808, Valid 17136 이라고 적혀있는 것은
주소에 들어있는 값은 0x80483088이며, 주소는 0x80454C60이며, 덤프 시작지점+86808=0x80454C50이라는 것입니다.
(Valid는 중요치 않습니다)
 
주소에 들어있는 값이 PspCidTable일 때의 주소를 찾았더니 저희집 컴퓨터에서는 다음과 같이 나왔습니다.
0x80483088(p=0x80454C60)
0x80483088(p=0x80454CF0)
0x80483088(p=0x80454D38)
0x80483088(p=0x80454D54)
0x80483088(p=0x80454D9C)
0x80483088(p=0x8046D4C8)
0x80483088(p=0x804E1D88)
0x80483088(p=0x804E3F5C)
(더 있을 수 있음)
 
PspCidTable의 주소값이 들어있는 주소는
0x80454C60, 0x80454CF0, 0x80454D38, 0x80454D54, 0x80454D9C, 0x8046D4C8, 0x804E1D88, 0x804E3F5C이군요.
(그 외에도 더 있을지 모릅니다)
 
이제, 다음 스샷을 봅시다.
사용자 삽입 이미지
 
이제, 0x80454C60, 0x80454CF0, 0x80454D38, 0x80454D54, 0x80454D9C, 0x8046D4C8, 0x804E1D88, 0x804E3F5C의 주소들이
어떤 함수에서 쓰이는지를 알아야 하는데, 방법은 간단합니다.
어떤 *.exe;*.sys;*.dll;*.cpl;*.scr 등의 파일이 무엇을 Export하고 있는지, 시작주소는 어떤지, 등등을 보여주는 기능을 하는 유용한 프로그램이 있습니다.
위 스샷에서 보시는 바와 같이 [Dependency Walker]라는 프로그램이 이 기능을 합니다.
(Microsoft Visual Studio에서 설치시 설치할 수 있습니다)
 
Open을 눌러서 ntoskrnl.exe를 로드하면 위와 비슷하게 나타날 것입니다.
이제 여기서, 0x80454C60, 0x80454CF0, 0x80454D38, 0x80454D54, 0x80454D9C, 0x8046D4C8, 0x804E1D88, 0x804E3F5C가 각각 어떤 함수에 포함되는지 찾아봅시다.
일단, ListView의 헤더를 눌러서 Entry Point순으로  정렬합니다.
그리고, 실제 주소값을 계산합니다.
 
※ 위 스샷에서의 실제 주소값=Entry Point+Base
 
그리고 Base는 밑에서 찾아볼 수 있습니다.
ntoskrnl.exe의 Base는......0x00400000이군요.
하지만, Kernel의 Base는 이와 다릅니다.
ZwQuerySystemInformation()을 이용하거나, LoadLibrary()->GetModuleBase()를 이용해 ntoskrnl.exe의 Base를 구할 수 있지만, 여기서는 대부분의 주어진 값을 사용하겠습니다.
일반적으로, Kernel(ntoskrnl.exe)의 Base Address는 0x80400000입니다.
이 값을 이용하여 위 스샷의 PsGetCurrentProcessId()의 주소를 구하면 0x80400000+0x00054AE4=0x80454AE4 입니다.
 
어떤 주소가 어떤 Export된 함수에 속하기(?) 위해서는
어떤 주소를 pAddr이라 하고, 함수의 주소는 pFuncAddr1, 다음 함수의 주소를 pFuncAddr2라 놓으면
(pFuncAddr2는 pFuncAddr1<pFuncAddr2 를 만족하는 Export된 최소 주소값이다)
pFuncAddr1<pAddr<pFuncAddr2 가 성립하면
pAddr는 pFuncAddr1에 속한다고 가정하고(단, pFuncAddr1과 pFuncAddr2에 UnExport된 Symbol은 없다고 가정)
계산해 보면
0x80454C52<0x80454C60<0x80454CE6이므로 아마도 0x80454C60은 PsLookupProcessThreadByCid()에 속할 것입니다
각각 계산한 결과
PsLookupProcessThreadByCid
PsLookupProcessByProcessId
PsLookupThreadByThreadId
PsCreateSystemThread(이건 좀 의심이 가는군요 다음 Entry Point와의 차이가 좀 커서......)
이 4개의 함수에 PspCidTable이 쓰인다는 결론(?)이 나왔습니다
(물론 이 방법이 100% 맞는다고 확신은 못합니다)
 
이제 각각의 함수들을 뒤지면 실제 PspCidTable의 값이 나오겠군요
PspCidTable 말고도 여러 함수들이 중복해서 쓰일 수 있기에, 범위를 0x80400000~0x81000000까지 한 것입니다
PspCidTable의 주소를 알아내는 함수를 DumpPspCidTable이라 하면
제가 만든 함수는 이렇습니다.
 
PVOID
NTAPI
DumpPspCidTable()
{
 ULONG Offset=0, ValidCount=0, ValidAddresses=0, CurrentAddress=0,
  ValidAddressIndex=0, RealPspCidTableAddressFlag=0;
 ULONG *PspCidTableAddresses=ExAllocatePool(NonPagedPool, 0x400*4);
 ULONG *PspAddressValidFlag=ExAllocatePool(NonPagedPool, 0x400*4);
 BOOLEAN NotEnoughResources=FALSE, PspSetAddressFlag=FALSE;
 PVOID RealPspCidTable=NULL;
 if(!PspCidTableAddresses || !PspAddressValidFlag)
 {
  if(PspAddressValidFlag)
  {
   ExFreePool(PspAddressValidFlag);
  }
  if(PspCidTableAddresses)
  {
   ExFreePool(PspCidTableAddresses);
  }
  return NULL;
 }
 CurrentAddress=(ULONG)PsLookupProcessByProcessId;
 DbgPrint("DumpPspCidTable, Find Address at PsLookupProcessByProcessId()");
 DbgPrint("PsLookupProcessByProcessId(0x%p) : Dump Start", CurrentAddress);
 for(Offset=0;Offset<=0x400;Offset++)
 {
  if(MmIsAddressValid((PULONG)CurrentAddress))
  {
   if(*(PULONG)CurrentAddress>(ULONG)0x80400000L &&
    *(PULONG)CurrentAddress<(ULONG)0x81000000L &&
    MmIsAddressValid((PVOID)(*(PULONG)CurrentAddress)))
   {
    DbgPrint("DUMP : Possible Address %p(p=%p), Offset %X", *(PULONG)CurrentAddress, CurrentAddress, Offset);
    for(ValidAddressIndex=0;ValidAddressIndex<ValidAddresses;ValidAddressIndex++)
    {
     if(ValidAddresses==0) break;
     if(PspCidTableAddresses[ValidAddressIndex]==*(PULONG)CurrentAddress)
     {
      PspAddressValidFlag[ValidAddressIndex]++;
      PspSetAddressFlag=TRUE;
      break;
     }
    }
    if(!PspSetAddressFlag || ValidAddresses==0)
    {
     PspCidTableAddresses[ValidAddresses]=*(PULONG)CurrentAddress;
     PspAddressValidFlag[ValidAddresses]=1;
     ValidAddresses++;
    }
    PspSetAddressFlag=FALSE;
    ValidCount++;
   }
  }
  CurrentAddress++;
 }
 ValidCount=0;
 CurrentAddress=(ULONG)PsLookupProcessThreadByCid;
 DbgPrint("DumpPspCidTable, Find Address at PsLookupProcessThreadByCid()");
 DbgPrint("PsLookupProcessThreadByCid(0x%p) : Dump Start", CurrentAddress);
 for(Offset=0;Offset<=0x400;Offset++)
 {
  if(MmIsAddressValid((PULONG)CurrentAddress))
  {
   if(*(PULONG)CurrentAddress>(ULONG)0x80400000L &&
    *(PULONG)CurrentAddress<(ULONG)0x81000000L &&
    MmIsAddressValid((PVOID)(*(PULONG)CurrentAddress)))
   {
    DbgPrint("DUMP : Possible Address %p(p=%p), Offset %X", *(PULONG)CurrentAddress, CurrentAddress, Offset);
    for(ValidAddressIndex=0;ValidAddressIndex<ValidAddresses;ValidAddressIndex++)
    {
     if(PspCidTableAddresses[ValidAddressIndex]==*(PULONG)CurrentAddress)
     {
      PspAddressValidFlag[ValidAddressIndex]++;
      break;
     }
    }
    ValidCount++;
   }
  }
  CurrentAddress++;
 }
 ValidCount=0;
 CurrentAddress=(ULONG)PsLookupThreadByThreadId;
 DbgPrint("DumpPspCidTable, Find Address at PsLookupThreadByThreadId()");
 DbgPrint("PsLookupThreadByThreadId(0x%p) : Dump Start", CurrentAddress);
 for(Offset=0;Offset<=0x400;Offset++)
 {
  if(MmIsAddressValid((PULONG)CurrentAddress))
  {
   if(*(PULONG)CurrentAddress>(ULONG)0x80400000L &&
    *(PULONG)CurrentAddress<(ULONG)0x81000000L &&
    MmIsAddressValid((PVOID)(*(PULONG)CurrentAddress)))
   {
    DbgPrint("DUMP : Possible Address %p(p=%p), Offset %X", *(PULONG)CurrentAddress, CurrentAddress, Offset);
    for(ValidAddressIndex=0;ValidAddressIndex<ValidAddresses;ValidAddressIndex++)
    {
     if(PspCidTableAddresses[ValidAddressIndex]==*(PULONG)CurrentAddress)
     {
      PspAddressValidFlag[ValidAddressIndex]++;
      break;
     }
    }
    ValidCount++;
   }
  }
  CurrentAddress++;
 }
 ValidCount=0;
 CurrentAddress=(ULONG)PsCreateSystemThread;
 DbgPrint("DumpPspCidTable, Find Address at PsCreateSystemThread()");
 DbgPrint("PsCreateSystemThread(0x%p) : Dump Start", CurrentAddress);
 for(Offset=0;Offset<=0x400;Offset++)
 {
  if(MmIsAddressValid((PULONG)CurrentAddress))
  {
   if(*(PULONG)CurrentAddress>(ULONG)0x80400000L &&
    *(PULONG)CurrentAddress<(ULONG)0x81000000L &&
    MmIsAddressValid((PVOID)(*(PULONG)CurrentAddress)))
   {
    DbgPrint("DUMP : Possible Address %p(p=%p), Offset %X", *(PULONG)CurrentAddress, CurrentAddress, Offset);
    for(ValidAddressIndex=0;ValidAddressIndex<ValidAddresses;ValidAddressIndex++)
    {
     if(PspCidTableAddresses[ValidAddressIndex]==*(PULONG)CurrentAddress)
     {
      PspAddressValidFlag[ValidAddressIndex]++;
      break;
     }
    }
    ValidCount++;
   }
  }
  CurrentAddress++;
 }
 for(ValidAddressIndex=0;ValidAddressIndex<ValidAddresses;ValidAddressIndex++)
 {
  if(PspAddressValidFlag[ValidAddressIndex])
  {
   DbgPrint("Possible Address of PspCidTable : 0x%p, Flag=%d",
    PspCidTableAddresses[ValidAddressIndex],
    PspAddressValidFlag[ValidAddressIndex]);
   if(RealPspCidTableAddressFlag<PspAddressValidFlag[ValidAddressIndex])
   {
    RealPspCidTableAddressFlag=PspAddressValidFlag[ValidAddressIndex];
    RealPspCidTable=(PVOID)PspCidTableAddresses[ValidAddressIndex];
   }
  }
 }
 if(!RealPspCidTable)
 {
  //should never get there
  DbgPrint("Could Not Find PspCidTable!");
  ExFreePool(PspCidTableAddresses);
  ExFreePool(PspAddressValidFlag);
  return NULL;
 }
 DbgPrint("Find PspCidTable at Address %p", RealPspCidTable);
 ExFreePool(PspCidTableAddresses);
 ExFreePool(PspAddressValidFlag);
 return RealPspCidTable;
}
먼저, 정해진 함수로부터의 0x400의 길이만큼 루프를 돌면서 PspCidTable로 추정되는 주소들을 찾습니다.
그리고, 블루스크린과 중복으로 추정되는 값들을 제거해 주기 위해서
추정되는 주소가 유효주소인지를 MmIsAddressValid(*(PULONG)CurrentAddress)로 검사합니다.
유효주소이면 주소들을 저장해둔 배열에 주소에 들어있는 값(PspCidTable로 추정되는 주소값)을 넣어줍니다.
이미 배열에 저장되어 있으면 ValidFlag을 1씩 더해줍니다.
이 방법을 반복합니다.
 
마지막으로, 가장 ValidFlag가 높은 PspCidTable 추정주소값을 찾고, RealPspCidTable에 저장합니다.
이렇게 해서 얻어진 RealPspCidTable이 PspCidTable의 값일 가능성이 높습니다.
 
그리고, 확인 결과 PspCidTable의 주소는 PsCreateSystemThread()를 0x400 까지 탐색한 결과,
Win2000에서는 일치하는 주소가 나왔으나 WinXP에서는 나오지 않았습니다.
그래도 PspCidTable의 올바른 주소는 나왔습니다. -_-;;
 
DbgPrint의 흔적을 기록한 것이 밑 스샷에 있습니다.

사용자 삽입 이미지
 
이와 같이 구해졌습니다.
 
※ 이 방법은 다음 Windows에서 성공적으로 테스트 되었습니다:
Windows 2000 Professional SP4
Windows XP Professional
Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:05

특정 프로세스를 보호하는 데에는 여러 가지 방법이 존재합니다.

일반적으로 하는 SDT Hook 이외에 여러 방법들이 존재하는데, 지금부터 그 방법들을 알아봅시다.


1. ZwXxx() Hook

일반적으로 많이 쓰이는 방법이라 생각하는데, 대부분 이러한 함수들을 후킹함으로써 프로세스를 보호합니다.

ZwOpenProcess() : 프로세스 핸들을 얻어오지 못하게 해서 프로세스에 접근하지 못하게 합니다.

ZwTerminateProcess() : 프로세스 핸들을 얻지 못하게 했다 해도, 프로세스 핸들을 얻어오는 또다른 방법은

ObOpenObjectByPointer()/ObOpenObjectByName() 을 호출하는 방법이 있습니다. 따라서 프로세스를 종료하지 못하게 막으려면 이 함수를 후킹해야 합니다.

ZwCreateThread() : 프로세스에 새로운 쓰레드를 만드는 함수인데, 아래와 같은 방법으로 프로세스를 종료할 수 있으므로 이 함수를 후킹해야 프로세스를 종료/접근 에서 막을 수 있습니다.

(간단하게 알기 쉽게 CreateThread()를 이용해서 설명하겠습니다.)

DWORD ProcessTerminateRoutine(PVOID pContext)

{

       return TerminateProcess((HANDLE)0xFFFFFFFF, 0);

}

 void RemoteTerminateProcess(HANDLE hProcess)

{

         HANDLE hThread=CreateThread(hProcess, ..., ProcessTerminateRoutine, ...);

         CloseHandle(hThread);

}

ZwOpenThread() : ZwOpenProcess()를 후킹했다 해도, 쓰레드에 접근 가능하다면 쓰레드 핸들을 얻어 온 후, 쓰레드를 종료해 버리면 프로세스는 종료됩니다. 그리하여 이 함수 역시 후킹할 필요가 있습니다.

ZwTerminateThread() : ZwOpenThread()를 후킹하는 이유와 거의 일치합니다.

ZwQuerySystemInformation() : 이 함수로 시스템의 정보를 알 수 있습니다. 그리고 프로세스/스레드 정보 역시 이 함수를 통해 얻을 수 있습니다(리스트로). 그리하여 이 함수를 후킹하여 SystemInformationBuffer에서 보호하려는 프로세스와의 링크를 끊어 주면 프로세스를 은폐함으로써 프로세스를 보호할 수 있습니다.

이외에도 ZwReadVirtualMemory()/ZwWriteVirtualMemory()/ZwQueryInformationProcess() 등등을 후킹할 필요도 있습니다.


이 방법들의 단점은, 여러 개의 함수를 후킹해야 한다는 점과 SDT Hook일 경우, SDT를 Restore하거나 Relocate되었을 경우, 혹은 이미지 파일에서 주소를 구해올 경우 무력화될수 있다는 점입니다.


2. EPROCESS 구조체의 ActiveProcessLinks 링크를 끊는다.

EPROCESS 구조체에는 ActiveProcessLinks가 있는데, 이것을 Tracing하면 프로세스 리스트를 얻을 수 있습니다.

그러나, 이 Link를 끊는다면, 프로세스는 은폐하여 보이지 않게 되어 프로세스를 보호할 수 있습니다.

이 방법의 단점은, Handle Table을 Tracing 할 경우, 혹은 무작위로 OpenProcess() 시도시, 들킬 수 있다는 것이 단점입니다.


3. PspCidTable의 주소를 구해서 프로세스의 흔적을 지워버린다.

PspCidTable에는 프로세스의 핸들에 대한 정보가 담겨 있습니다.

그런데, PspCidTable에서 프로세스의 핸들을 지워 버린다면, ZwOpenProcess()로 프로세스를 열지라도, 핸들이 지워졌기 때문에 프로세스 핸들을 얻어 오는 데에 실패할 것입니다.

결국, 프로세스는 보호됩니다.(FU_RootKit이 사용했던 방법입니다.)

이 방법의 단점은 PspCidTable의 주소를 직접 구해 와야 한다는 것과, 이 방법으로도 프로세스를 완전히 흔적을 없앨 수 없습니다.


4. ObOpenObjectByName() Hook

프로세스 핸들을 얻어 오는 방법 중 하나는 ObOpenObjectByName()을 호출하여 이름으로 프로세스 핸들을 얻어 오는 것입니다.

이 함수를 후킹한다면, 특정 이름의 Object를 여는 것을 막아서 프로세스를 보호할 수 있을 것입니다.

단점은 핸들이 어떤 Type의 핸들인지를 알 수 있어야 한다는 것(ObjectType이 NULL일 때), ObjectName을 어떻게 구분지을 것인지를 알아야 하고, 좀더 막으려면 PsLookupXxx()등의 함수를 보조적으로 후킹해야 한다는 것이 있습니다.


5. ObOpenObjectByPointer() Hook

이 함수 역시 프로세스 핸들을 얻어 올 수 있습니다. 프로세스 핸들을 얻을 때, Parameter 중 Object에는 EPROCESS의 포인터가 들어가고, ObjectType에는 PsProcessType이 들어갑니다(NULL이 들어가기도 한다). 그리하여 후킹한 후 내부적으로 인자를 검사해서 프로세스를 여는 것일 경우 막으면 프로세스를 보호할 수 있습니다.

단점은 핸들이 어떤 Type의 핸들인지를 알 수 있어야 한다는 것(ObjectType이 NULL일 때)과 좀더 막으려면 PsLookupXxx()등의 함수를 보조적으로 후킹해야 한다는 것이 있습니다.(4번과 비슷)


6. KeAttachProcess()/KeStackAttachProcess() Hook

이 함수는 프로세스에 Attach할 수 있는 함수입니다. Attach하면.....일단 프로세스에 접근 할 수 있을 테니 종료도 가능할 것입니다.

그러므로 이 함수를 후킹할 필요가 있습니다.(GameGuard에서 후킹하는 함수라죠)

단점은 인자에 EPROCESS 구조체의 포인터가 필요하므로 PsLookupXxx()등의 함수도 후킹해야 합니다.


7. PspTerminateProcess() Hook

이 함수는 ntoskrnl.exe가 내부적으로 가지고 있는 하나의 내부함수로서, 프로세스를 종료할 때 쓰이나, ZwTerminateProcess()와의 차이점은 첫번째 인자에 ProcessHandle 대신 Process Object의 포인터(EPROCESS 포인터)가 들어간다는 것입니다.

이 함수는 Unexported된 함수이며, 주소를 직접 구해 오는 수밖에 없습니다. 이 함수를 후킹하면 프로세스를 종료에서 보호할 수 있습니다.

단점은 무엇보다도 PspTerminateProcess()의 주소를 직접 구해와야 하는데에 있을거라 생각됩니다. (필자가 시도중)


8. PspTerminateThreadByPointer() Hook

이 함수는 ntoskrnl::NtTerminateProcess()과 ntoskrnl::NtTerminateThread()가 내부적으로 호출하는 함수이고, PspTerminateProcess() 역시 이 함수를 내부적으로 사용합니다.

PspTerminateProcess()는 PsGetNextProcessThread()를 사용하여 프로세스의 Next Thread Object Pointer를 얻어 온 후(ETHREAD의 포인터), PspTerminateThreadByPointer()를 호출하여 프로세스가 소유하는 쓰레드를 하나하나씩 종료합니다.

이 함수 역시 Unexported된 함수이며, 주소를 직접 구해 와야 합니다. 이 함수는 PspTerminateProcess() 조차도 내부적으로 호출하는 함수이므로 후킹하면 프로세스/쓰레드를 종료에서 보호할 수 있습니다.

단점 역시 7번과 비슷합니다.


이상으로 몇몇 안되지만, 프로세스를 보호하는 여러 방법과 그 단점에 대해 알아보았습니다.

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:02

PspTerminateThreadByPointer() 주소 구하기.


일단, PspTerminateThreadByPointer()를 구하기 위해서는

어느 함수에서 PspTerminateThreadByPointer()를 호출하는지 알 필요가 있습니다.

필자는 WinDbg의 힘을 빌렸고, 아래와 같은 함수들이 대개 PspTerminateThreadByPointer()를 호출한다는 것을 알아 내었습니다.

NtTerminateProcess()

NtTerminateThread()

PsTerminateSystemThread()

PspTerminateProcess()

......

(이 외에도 여럿 있을 수 있음)


이 중, NtXxx()의 함수는 실제 구현코드를 가지고, 코드가 그만큼 길기 때문에 나중에 생각하기로 했습니다.

또한, PspTerminateProcess()는 Unexported 된 함수이므로 생각하지 않기로 했습니다.


위의 목록 중, 가장 간단한 코드를 가지고 있는 함수는 바로 PsTerminateSystemThread() 입니다.

한번 코드를 보도록 하죠.


nt!PsTerminateSystemThread:
805c9754 8bff            mov     edi,edi
805c9756 55              push    ebp
805c9757 8bec            mov     ebp,esp
805c9759 64a124010000    mov     eax,dword ptr fs:[00000124h]
805c975f f6804802000010  test    byte ptr [eax+248h],10h
805c9766 7507            jne     nt!PsTerminateSystemThread+0x1b (805c976f)
805c9768 b80d0000c0      mov     eax,0C000000Dh
805c976d eb09            jmp     nt!PsTerminateSystemThread+0x24 (805c9778)
805c976f ff7508          push    dword ptr [ebp+8]
805c9772 50              push    eax
805c9773 e828fcffff      call    nt!PspTerminateThreadByPointer (805c93a0)
805c9778 5d              pop     ebp
805c9779 c20400          ret     4
805c977c cc              int     3
805c977d cc              int     3
805c977e cc              int     3
805c977f cc              int     3
805c9780 cc              int     3
805c9781 cc              int     3
nt!PsTerminateProcess:
805c9782 8bff            mov     edi,edi
805c9784 55              push    ebp
805c9785 8bec            mov     ebp,esp
805c9787 5d              pop     ebp

......
(아래 생략)


역시....간결하군요,

일단 함수를 호출할 때 쓰이는 call 명령이 있는 부분을 찾아봅시다.

탁 보이는군요.

805c9773 e828fcffff      call    nt!PspTerminateThreadByPointer (805c93a0)

그리고 Code Bytes는 e8 28 fc ff ff 가 됩니다.

e8은 call 명령을 뜻합니다.

"잠깐! 그렇다면 뒤의 이 알수없는 28 fc ff ff 는 데체 뭐야?! a0 93 c5 80이 와야 하는 게 아니었어?!"

.....라고 저는 생각했었습니다(그때는 어셈을 지금보다 더 몰랐으므로).

공식(....이랄것까지야 없지만, 알아둡시다.)에 의하면,

"call 명령의 다음 4바이트+명령어 주소+5=호출하는 함수의 실제 주소" 라고 합니다.

call 명령의 다음 4바이트는 위에서 보면 28 fc ff ff, 즉 4바이트 정수로 변환하면 0xfffffc28이 됩니다.
여기에서 simple한 방정식을 풀어야 원래의 호출 주소를 구할 수 있습니다.

이동할 대상 = d 라고 하고
call 명령의 다음 4바이트 = i + 1
명령어 주소 = i(상수)
f(i + 1) = d - (i + 5) 에서
구할 주소 d = f(i + 1) + (i + 5)
(단, f(x)=*x 로 정의한다.)
한번 맞나 볼까요 ㅇ_ㅇ?


call 명령의 다음 4바이트+명령어 주소+5=PspTerminateThreadByPointer()
0xfffffc28+0x805c9773+5=0x805c93a0=PspTerminateThreadByPointer()
정확히 맞아 떨어졌습니다.


이제, 그에 따라 함수를 구현해 보면 이렇습니다.

PVOID PspDumpPspTerminateThreadByPointer()
{
 PVOID PspTerminateThreadByPointer;
 PVOID InstructionPointer=MmFindAddressIncludeInstructionCodeBytes(
  (PVOID)PsTerminateSystemThread,
  "\xe8\x28\xfc\xff\xff", //call PspTerminateThreadByPointer
  5,
  0x100); //Proc Length = 0x2D!
 DbgPrint("InstructionPointer 0x%p", InstructionPointer);
 //이동할 대상 = dest
 //call 명령의 다음 4바이트 = inst + 1
 //명령어 주소 = inst(상수)
 //f(inst + 1) = dest - (inst + 5)
 //dest = f(inst + 1) + (inst + 5)
 //x = dest
 //단, f(x)=*x이다.
 if(MmIsAddressValid(InstructionPointer))
 {
  //call 명령의 다음 4바이트+명령어 주소+5=PspTerminateThreadByPointer()
  //0xfffffc28+0x805c9773+5=((0x805c9773+5)-0x000003d7)-1=0x805c93a0
  //PspTerminateThreadByPointer=0x805c93a0
  DbgPrint("Call(=0xE8) Next 4 Bytes : 0x%p", *(PULONG)((PUCHAR)InstructionPointer+1));
  DbgPrint("Address Of InstructionPointer+5 0x%p", (ULONG)InstructionPointer+5);
  PspTerminateThreadByPointer=
   (PVOID)(*(PULONG)((PUCHAR)InstructionPointer+1)+(ULONG)InstructionPointer+5);
  if(MmIsAddressValid(PspTerminateThreadByPointer))
  {
   DbgPrint("Found PspTerminateThreadByPointer at Address 0x%p", PspTerminateThreadByPointer);
   return PspTerminateThreadByPointer;
  }
 }
 return NULL;
}

※ 위의 함수는 Windows XP SP2 에서만 테스트 되었으므로, 다른 운영체제에서는 동작하지 않을 수 있다는 단점이 있습니다.

Posted by skensita