윈도우즈 시스템 프로그래밍

윈도우즈 시스템 프로그래밍 8장 - 핸들 테이블과 오브젝트 핸들의 상속

111-000-111 2021. 7. 29. 16:35

 

 

 

윈도우즈 시스템 프로그래밍이라는 책과 해당 책의 저자이신 윤성우님의 강의를 통해 공부한 내용을 정리하는 글입니다.

 

 

 


 

 

 

프로세스의 핸들 테이블

 

⦁ 프로세스로 전달되는 핸들 정보가 어떻게 전달 및 저장되는지 보여준다. 

⦁ 핸들 테이블은 핸들 정보를 저장하고 있는 테이블로서 프로세스별로 독립적이다.

⦁ CreateProcess 함수나 CreateMailslot과 같은 함수 호출을 통해서 리소스 생성을 요구한 결과로 핸들 정보를 얻게 될 경우, 프로세스 자신에게 속해 있는 핸들 테이블에 해당 정보가 등록된다.

 

 

 

 

핸들의 상속

 

⦁ 핸들의 상속을 위한 전달인자

 

CreateProcess의 선언

BOOL CreateProcess (
    LPCTSTR lpapplicationName,
    LPTSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCTSTR lpCurrentDirectory,
    LPSTARTUPINFO lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation
);
// If the function fails, the return value is zero.

 

 

이 함수의 다섯 번째 전달인자 bInheritHandle은 자식 프로세스에게 핸들 테이블을 상속해 줄 것인지 결정하는 요소이다. TRUE를 인자로 전달할 경우 상속되고, FALSE일 경우는 이루어지지 않는다.

 

 

 

 

 

⦁ 상속이 되기 위한 핸들의 조건

핸들의 상속 여부는 리소스가 생성되는 순간에 프로그래머에 의해 결정된다.

 

CreateMailslot의 선언

HANDLE CreateMailslot (
    LPCTSTR lpName,
    DWORD nMaxMessageSize,
    DWORD lReadTimeout,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
//If the function fails, the return value is INVALID_HANDLE_VALUE

CreateMailslot의 경우, 마지막 전달인자는 구조체 SECURITY_ATTRIBUTES의 포인터로 정의되어 있다.

즉, SECURITY_ATTRIBUTES 구조체 변수를 적절히 초기화 해서 주소값을 인자로 전달해야 한다.

NULL이 전달될 경우 메일슬롯의 핸들은 자식 프로세스에게 전달되지 않는다.

 

 

SECRUTIY_ATTRIBUTES 구조체의 선언

typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;

첫 번째 멤버는 구조체 변수 크기를 바이트 단위로 설정해 준다.

세 번째 멤버는 상속 여부를 결정지으므로 필요하면 TRUE로 설정해야 한다.

 

 

 

 

 

예제

 

MailSender2_1.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
#include <windows.h>

#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox")

int _tmain(int argc, TCHAR* argv[])
{
	HANDLE hMailSlot;
	TCHAR message[50];
	DWORD bytesWritten;

	SECURITY_ATTRIBUTES sa;
	sa.nLength = sizeof(sa);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = TRUE;

	hMailSlot = CreateFile(SLOT_NAME, GENERIC_WRITE, FILE_SHARE_READ, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hMailSlot == INVALID_HANDLE_VALUE)
	{
		_fputts(_T("Unable to create mailslot!\n"), stdout);
		return 1;
	}

	_tprintf(_T("Inheritable Handle : %d \n"), hMailSlot);

	FILE* file = _tfopen(_T("InheritableHandle.txt"), _T("wt"));
	_ftprintf(file, _T("%d"), hMailSlot);
	fclose(file);

	STARTUPINFO si = { 0, };
	PROCESS_INFORMATION pi;
	si.cb = sizeof(si);

	TCHAR command[] = _T("MailSender2_2.exe");

	CreateProcess(NULL, command, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

	while(1)
	{
		_fputts(_T("MY CMD>"), stdout);
		_fgetts(message, sizeof(message) / sizeof(TCHAR), stdin);
		if (!WriteFile(hMailSlot, message, _tcslen(message) * sizeof(TCHAR), &bytesWritten, NULL))
		{
			_fputts(_T("Unable to write!"), stdout);
			CloseHandle(hMailSlot);
			return 1;
		}

		if (!_tcscmp(message, _T("exit")))
		{
			_fputts(_T("Good Bye!"), stdout);
			break;
		}
	}
	CloseHandle(hMailSlot);
	return 0;
}

 

자식 프로세스의 파일을 열어서 핸들 정보를 얻는 프로그램이다. 파일을 열어서 상속된 핸들값을 확인하고, 이를 이용하여 부모 프로세스와 마찬가지로 메일슬롯에 데이터를 전송하도록 구현했다.

 

 

 

 MailSender2_2.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <tchar.h>
#include <windows.h>

int _tmain(int argc, TCHAR argv[])
{
	HANDLE hMailSlot;
	TCHAR message[50];
	DWORD bytesWritten;

	FILE* file = _tfopen(_T("InheritableHandle.txt"), _T("rt"));
	_ftscanf(file, _T("%d"), &hMailSlot);
	fclose(file);
	_tprintf(_T("Inheritable Handle : %d \n"), hMailSlot);

	while (1)
	{
		_fputts(_T("MY CMD>"), stdout);
		_fgetts(message, sizeof(message) / sizeof(TCHAR), stdin);

		if (!WriteFile(hMailSlot, message, _tcslen(message) * sizeof(TCHAR), &bytesWritten, NULL))
		{
			_fputts(_T("Unable to write!"), stdout);
			_gettchar();
			CloseHandle(hMailSlot);
			return 1;
		}

		if (!_tcscmp(message, _T("exit")))
		{
			_fputts(_T("Good Bye!"), stdout);
			break;
		}
	}

	CloseHandle(hMailSlot);
	return 0;
}

MailSender2_1.cpp의 자식 프로세스에 해당하는 코드이다.

 

 

 

 

Pseudo 핸들과 핸들의 중복(Duplicate)

 

⦁ GetCurrentProcess 함수를 통해 얻은 핸들은 가짜 핸들(Pseudo 핸들)이다. 핸들 테이블에 등록되어있지 않은 핸들이고, 현재 실행중인 프로세스를 참조하기 위한 용도로 정의한 상수가 반환되는 것이다. 그러므로 자식 프로세스로 상속되지 않는다.

⦁ 가짜 핸들은 CloseHandle로 전달되어도 아무 일이 일어나지 않는다.

 

 

⦁ DuplicateHandle 함수

BOOL DuplicateHandle (
    HANDLE hSourceProcessHandle,
    HANDLE hSourceHandle,
    HANDLE hTargetProcessHandle,
    LPHANDLE lpTargetHandle,
    DWORD dwDesireAccess,
    BOOL bInheritHandle,
    DWORD dwOptions
);
// If the function fails, the return value is zero.

 

⦁ hSourceProcessHandle : 복제할 핸들을 소유하는 프로세스를 지정한다.
⦁ HSourceHandle : 복제할 핸들을 지정한다.
⦁ hTargetProcessHandle : 복제된 핸들을 소유할 프로세스를 지정한다.
⦁ lpTargetHandle : 복제된 핸들값을 저장할 변수의 주소를 지정한다.
⦁ dwDesireAccess : 복제된 핸들의 접근 권한 지정.
⦁ bInheritHandle : 복제된 핸들의 상속 여부를 지정한다.
⦁ dwOptions : DUPLICATE_SAME_ACCESS를 전달하면 원본 핸들과 동일한 접근권한 가짐. DUPLICATE_CLOSE_SOURCE가 오면 원본 핸들을 종료시킨다. 비트OR 연산으로 동시 전달 가능.

 

 

DuplicateHandle(
    프로세스A 핸들,
    256,
    프로세스B 핸들,
    &val,
    .....
);

DuplicateHandle 함수가 위와 같이 사용될 경우, 프로세스 A에 존재하는 핸들 256의 정보를 프로세스 B의 핸들 테이블에 등록시키고, 등록된 핸들의 값은 변수 val에 저장하게 된다.

 

 

 

 

부모 프로세스의 핸들을 자식 프로세스에게 전달하기

 

 

 

⦁ DuplicateHandle.cpp

 

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <tchar.h>
#include <windows.h>


int _tmain(int argc, TCHAR* argv[])
{
	HANDLE hProcess;
	TCHAR cmdString[1024];

	DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &hProcess, 0, TRUE, DUPLICATE_SAME_ACCESS);

	_stprintf(cmdString, _T("%s %u"), _T("ChildProcess.exe"), (unsigned)hProcess);

	STARTUPINFO si = { 0, };
	PROCESS_INFORMATION pi = { 0, };
	si.cb = sizeof(si);

	BOOL isSuccess = CreateProcess(NULL, cmdString, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

	if (isSuccess == FALSE)
	{
		_tprintf(_T("CreateProcess failed \n"));
		return -1;
	}

	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	_tprintf(_T("[Parent Process]\n"));
	_tprintf(_T("ooooooooooooooooooooooooooops! \n"));
	return 0;
}

 

위 프로그램이 부모 프로세스가 된다.

 

 

 

⦁ DuplicateHandleChildProcess.cpp

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

int _tmain(int argc, TCHAR* argv[])
{
	HANDLE hParent = (HANDLE)_ttoi(argv[1]);
	DWORD isSuccess = WaitForSingleObject(hParent, INFINITE);

	_tprintf(_T("[Child Process] \n"));

	if (isSuccess == WAIT_FAILED)
	{
		_tprintf(_T("WAIT_FAILED returned!"));
		Sleep(10000);
		return -1;
	}
	else
	{
		_tprintf(_T("General Lee said, \"Dont't inform ") _T("the enemy my death\""));
		Sleep(10000);
		return 0;
	}
}

 

위 프로그램이 자식 프로세스가 된다.

 

 

 

실행결과