Thread/Windows

[Thread][Windows] DuplicateHandle

Binceline 2013. 10. 19. 01:46


출처 : http://hongten.egloos.com/265139



서로 다른 프로세스 사이에서 커널 오브젝트를 공유하는 마지막 방법으로 DuplicateHandle() 함수를 사용하는 것이다.



BOOL DuplicateHandle( HANDLE hSourceProcessHandle, 

                      HANDLE hSourceHandle, 

                      HANDLE hTargetProcessHandle,

                      PHANDLE phTargetHandle,

                      DWORD dwDesiredAccess,

                      BOOL bInheritHandle, 

                      DWORD dwOptions

                     );



단순히, 이 함수는 하나의 프로세스 핸들 테이블의 핸들을 다른 프로세스의 핸들 테이블로 복사한다. DuplicateHandle() 함수는 여러개의 인수를 가지고 있지만 실제로 아주 간단하다. 이 함수의 가장 일반적인 사용법은 시스템에서 실행중인 3 개의 서로 다른 프로세스를 사용하는 것이다.



DuplicateHandle() 함수의 첫번째와 세번째 인수 ( hSourceProcessHandle 와 hTargetProcessHandle ) 는 커널 오브젝트 핸들이며 이 함수를 호출하는 프로세스와 관련된 핸들이다. 게다가 이 두 개의 인수는 프로세스 타입의 커널 오브젝트로 DuplicateHandle() 함수의 호출이 실패하였을 경우, 다른 타입의 커널 오브젝트가 전달되었는지 확인해 보아야 한다.



두번째 인수인 hSourceHandle 는 다양한 타입의 커널 오브젝트 핸들이 가능하다. 그러나 이 핸들 값은 DuplicateHandle() 함수를 호출하는 프로세스와 관련된 것이 아니다. 그 대신에 hSourceProcessHandle 핸들에 의하여 식별되는 프로세스와 관련되어 있다. 네번째 인수인 phTargetHandle 는 Source 의 핸들 정보를 복사한 Target 핸들 테이블의 인덱스를 받기 위한 HANDLE 변수의 주소로 hTargetProcessHandle 핸들에 의하여 식별되는 프로세스와 관련된 핸들 값을 받게 된다.



DuplicateHandle() 함수의 마지막 3 개의 인수는 access mask 값과 상속을 설정하는 플래그 값, 그리고 옵션을 지정할 수 있도록 허용한다. dwOptions 인수는 0 또는 두 개의 플래그 ( DUPLICATE_SAME_ACCESS 와 DUPLICATE_CLOSE_SOURCE ) 를 조합하여 사용할 수 있다.



DUPLICATE_SAME_ACCESS 를 지정하면 Source 프로세스의 핸들과 동일한 access mask 값을 Target 핸들로 복사하도록 DuplicateHandle() 함수에게 알려준다. 이 플래그를 사용하면 dwDesiredAccess 인수는 무시된다.



DUPLICATE_CLOSE_SOURCE 를 지정하면 Source 프로세스에서 핸들을 닫을 수 있도록 한다. 이것은 다른 프로세스에서 사용되는 커널 오브젝트를 하나의 프로세스에서 관리하기 쉽도록 만든다. 이 플래그를 사용하게 되면 커널 오브젝트의 Usage count 의 영향을 받지 않는다.



DuplicateHandle() 함수가 어떻게 처리되는지 예를 통하여 알아보자. 여기서 프로세스 S 는 Source 프로세스로 현재 커널 오브젝트를 갖고 있다. 그리고 프로세스 T 는 Target 프로세스로 커널 오브젝트 핸들을 얻어야 한다. 프로세스 C 는 DuplicateHandle() 함수 호출을 실행할 Catalyst 프로세스이다.



프로세스 C 의 핸들 테이블 ( 표 4 ) 는 2 개의 핸들 값 ( 1 과 2 ) 를 갖고 있다. 핸들 값 1 과 2 는 각각 프로세스 S 와 프로세스 T 의 프로세스 타입의 커널 오브젝트 핸들 값이다.



<표 4> 프로세스 C 의 핸들 테이블

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)

1 0xF0000000 (Process S's kernel object) 0x???????? 0x00000000

2 0xF0000010 (Process T's kernel object) 0x???????? 0x00000000


<표 5> 는 프로세스 S 의 핸들 테이블이며 하나의 핸들 값 2 를 갖고 있다. 이 핸들은 어떤 타입의 커널 오브젝트도 가능하며 프로세스 타입의 오브젝트 핸들일 필요는 없다.



<표 5> 프로세스 S 의 핸들 테이블

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)

1 0x00000000 (N/A) (N/A)

2 0xF0000020 (any kernel object) 0x???????? 0x00000000


<표 6> 은 프로세스 C 가 DuplicateHandle() 함수를 호출하기 전의 프로세스 T 의 핸들 테이블이다. 이 테이블은 하나의 핸들 값 2 만을 갖고 있으며, 핸들 1 은 사용하지 않고 있다.



<표 6> DuplicateHandle() 함수를 호출하기 전, 프로세스 T 의 핸들 테이블

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)

1 0x00000000 (N/A) (N/A)

2 0xF0000030 (any kernel object) 0x???????? 0x00000000


프로세스 C 가 다음과 같은 코드로 DuplicateHandle() 함수를 호출할 경우, <표 7> 과 같이 프로세스 T 의 핸들 테이블만 바뀌게 된다.



DuplicateHandle(1, 2, 2, &hObj, 0, TRUE, DUPLICATE_SAME_ACCESS);



<표 7> DuplicateHandle() 함수를 호출한 후, 프로세스 T 의 핸들 테이블

Index Pointer to Kernel Object Memory Block Access Mask (DWORD of Flag Bits) Flags (DWORD of Flag Bits)

1 0xF0000020 0x???????? 0x00000001

2 0xF0000030 (any kernel object) 0x???????? 0x00000000


프로세스 S 의 핸들 테이블의 핸들 2 가 프로세스 T 의 핸들 테이블의 핸들 1 로 복사된다. 또한 DuplicateHandle() 함수는 프로세스 C 의 hObj 변수로 프로세스 T 의 핸들 테이블에 새롭게 할당된 핸들 값 1 을 반환한다.



그리고 DuplicateHandle() 함수에 DUPLICATE_SAME_ACCESS 플래그를 전달하였기 때문에 프로세스 S 의 핸들과 동일한 access mask 값을 프로세스 T 의 핸들에 설정되었다. 또한 DUPLICATE_SAME_ACCESS 플래그를 전달하였으므로 dwDesiredAccess 인수는 무시된다. 마지막으로 bInheritHandle 인수에 TRUE 값을 전달하기 때문에 상속 가능하도록 플래그가 설정되었다.



위의 예에서 보았던 것과 같이 하드 코딩되어 있는 값을 DuplicateHandle() 함수의 인수로 전달하면 절대 안된다. 이 값들은 단지 이 함수가 어떻게 동작하는지 알아보기 위하여 예를 들었을 뿐이다. 따라서 실제 응용 프로그램에서는 변수를 이용하여 다양한 핸들 값을 DuplicateHandle() 함수로 전달해야 할 것이다.



상속과 같이 DuplicateHandle() 함수의 이상한 것 중 하나는 새로운 커널 오브젝트 핸들을 사용할 수 있다는 것을 Target 프로세스에게 알려주지 않는다는 것이다. 그러므로 프로세스 C 는 hObj 변수의 핸들 값을 IPC ( Inter Process Communication ) 와 같은 것을 이용하여 프로세스 T 에게 새로운 커널 오브젝트 핸들 사용할 수 있도록 알려주어야 한다. 분명한 것은 이미 프로세스가 실행되고 있으므로 command-line 이나 프로세스 T 의 환경 변수를 사용할 수 없다는 것이다. 따라서 Window message 나 다른 IPC 메카니즘을 사용해야 한다.



여기서는 단지 DuplicateHandle() 함수의 가장 일반적인 사용방법에 대하여 설명하였다. 이 함수는 대단히 유연한 함수지만 3 개의 프로세스가 연관된 경우는 대단히 드물다 ( 프로세스 S 가 사용하고 있는 핸들 값을 프로세스 C 가 알고 있다는 것은 대단히 어려운 일이기 때문이다 ). 일반적으로 DuplicateHandle() 함수는 서로 연관 있는 두 개의 프로세스에 의하여 사용된다. 다른 프로세스가 갖고 있는 커널 오브젝트를 사용해야 하거나 다른 프로세스에서 사용할 수 있도록 커널 오브젝트를 전달해야 하는 상황을 가정할 수 있다. 예를 들어 프로세스 S 가 갖고 있는 커널 오브젝트를 프로세스 T 로 전달한다고 할 경우, 다음과 같이 DuplicateHandle() 함수를 호출한다.



// All of the following code is executed by Process S.

// Create a mutex object accessible by Process S.

HANDLE hObjProcessS = CreateMutex( NULL, FALSE, NULL );

// Open a handle to Process T's kernel object.

HANDLE hProcessT = OpenProcess( PROCESS_ALL_ACCESS, FALSE, dwProcessIdT );

HANDLE hObjProcessT; 

// An uninitialized handle relative to Process T.

// Give Process T access to our mutex object.

DuplicateHandle( GetCurrentProcess(), hObjProcessS, hProcessT, &hObjProcessT, 0, FALSE, DUPLICATE_SAME_ACCESS );

// Use some IPC mechanism to get the handle

// value in hObjProcessS into Process T....

// We no longer need to communicate with Process T.

CloseHandle( hProcessT );...

// When Process S no longer needs to use the mutex, it should close it.

CloseHandle( hObjProcessS );



GetCurrentProcess() 함수는 이 함수를 호출하는 프로세스를 항상 확인할 수 있는 pseudo 핸들을 반환한다 ( 위의 예에서 프로세스 S ). DuplicateHandle() 함수를 호출하여 반환된 hObjProcessT 변수는 프로세스 S 에서 사용되는 hObjProcessS 변수의 핸들과 동일한 오브젝트를 가리키는 프로세스 T 의 핸들 값을 갖고 있다. 따라서 프로세스 S 는 다음과 같은 코드를 절대로 실행할 수 없다.



// Process S should never attempt to close the// duplicated handle.CloseHandle( hObjProcessT );



만약 프로세스 S 가 이 코드를 실행한다면, 성공하거나 실패할 수도 있다. hObjProcessT 변수의 핸들 값과 동일한 커널 오브젝트를 사용할 수 있다면 프로세스 S 는 성공할 것이다. 그러나 이것은 프로세스 S 의 어떤 오브젝트를 더 이상 사용할 수 없도록 닫는 결과를 가져오게 되므로 응용 프로그램이 이상하게 동작하게 되는 원인이 될 수 있다.



DuplicateHandle() 함수의 또 다른 사용법은 읽기와 쓰기가 가능한 file-mapping 오브젝트를 프로세스가 갖고 있다고 가정하고 어떤 함수를 호출할 때, file-mapping 오브젝트를 읽기만 가능하도록 할 수 있다. 이것은 응용 프로그램을 보다 안전하고 튼튼하게 ( robust ) 하기 위하여 DuplicateHandle() 함수를 사용하여 이미 존재하는 오브젝트에 대하여 읽기만 가능한 새로운 핸들을 만든다. 이렇게 만들어진 읽기 전용 핸들을 함수로 전달한다. 이 방법은 함수 내에서 file-mapping 오브젝트에 대하여 절대로 쓰기를 할 수 없다. 다음과 같이 예를 들 수 있다.



int WINAPI WinMain( HINSTANCE hinstExe, HINSTANCE, LPSTR szCmdLine, int nCmdShow ){

// Create a file-mapping object; the handle has read/write access. 

HANDLE hFileMapRW = CreateFileMapping( INVALID_HANDLE_VALUE,NULL, PAGE_READWRITE, 0, 10240, NULL );

// Create another handle to the file-mapping object; 

// the handle has read-only access. 

HANDLE hFileMapRO;

DuplicateHandle( GetCurrentProcess(), hFileMapRW, GetCurrentProcess(),&hFileMapRO, FILE_MAP_READ, FALSE, 0 );

// Call the function that should only read from the file mapping. 

ReadFromTheFileMapping( hFileMapRO );

// Close the read-only file-mapping object.

CloseHandle( hFileMapRO ); 

// We can still read/write the file-mapping object using hFileMapRW. ... 

// When the main code doesn’t access the file mapping anymore,

// close it. CloseHandle( hFileMapRW );

}

*DuplicateHandle 을 이용해 후킹할때

후킹할때 TerminateProcess 같은 것을 후킹하면 hProcess 를 가로챌 수 있습니다.

죽일려고하는 프로세스의 핸들이 되겠지요..

이 hProcess(타켓 프로그램의 핸들) 만 갖고 PID 를 알아내는데는 GetProcessId 라는 커널단의

함수를 쓰면될것 같지만 GetProcessId 함수는 같은 프로세스내에서만 핸들로 프로세스 아이디를

얻어내는 함수이므로 TerminateProcess 가 실행되는 죽이는자(Killer) 의 공간에서 타켓 프로세스의

PID 를 얻어낼 수 없습니다. 이 문제를 해결하려면 hProcess(타켓 프로그램의 핸들) 을 복사해야

합니다. DuplicateHandle 함수로 타켓 프로그램의 핸들을 복사하는데 이때 속성을

PROCESS_QUERY_INFORMATION 으로 주고 복사를 합니다. 코드는 다음과 같습니다.


    HANDLE   hDup;

    if(!DuplicateHandle(GetCurrentProcess(), 

                        hProcess, 

                        GetCurrentProcess(), 

                        &hDup,

                        PROCESS_QUERY_INFORMATION, 

                        FALSE, 

                        0))

    {

        return(0xffffffff);

    }

이렇게 핸들을 복사하면 그 다음부터는 매우 쉽습니다. NtQueryInformationProcess 함수가

핸들을 갖고 정보를 뽑아오기 때문이죠.. 다음의 코드를 보십시오. 핸들을 통해서

PROCESS_BASIC_INFORMATION 값을 구조체로 받아옵니다.

구조체를 보시면 아시겠지만 pbi.UniqueProcessId 이렇게 구하면 타켓 핸들의 프로세스 아이디를

얻어낼 수 있구요.. pbi.InheritedFromUniqueProcessId 이렇게하면 타켓핸들의 프로세스 아이디뿐만

아니라 그 부모의 프로세스 아이디까지 얻어낼 수 있습니다.. 도움 되셨길..

예제는 powerhacker 사이트에 올려놨습니다. 제가 삽질했던 부분을 물어보시는거 같아서

삽질 덜 하시라고 올려드립니다.. ^^

DWORD WINAPI PowerHacker_GetProcessId(HANDLE hProcess)

{

    NTSTATUS status;

    PROCESS_BASIC_INFORMATION pbi;

    

    HANDLE   hDup;

    if(!DuplicateHandle(GetCurrentProcess(), 

                        hProcess, 

                        GetCurrentProcess(), 

                        &hDup,

                        PROCESS_QUERY_INFORMATION, 

                        FALSE, 

                        0))

    {

        return(0xffffffff);

    }

    CREATE_DYNFUNC_5(NtQueryInformationProcess,

                     NtQueryInformationProcess,

                     ntdll,

                     NTSTATUS,

                     __stdcall,

                     HANDLE,

                     PROCESSINFOCLASS,

                     PVOID,

                     ULONG,

                     PULONG

                    );

    status = NtQueryInformationProcess(hDup, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);

    CloseHandle(hDup);

    if(status >= 0) return pbi.UniqueProcessId;

    else return(0xffffffff);

}


반응형