[cpp] PE 파일에서 매니페스트 추출하기

@codemaru · June 18, 2015 · 5 min read

PE 파일의 매니페스트에는 많은 정보가 담겨 있다. 주요하게는 해당 실행 파일이 어떤 CRT와 바인딩이 되는 것인지, 또 해당 실행 파일을 실행하기 위해서 필요한 권한은 무엇인지 등의 정보가 담겨 있다. 그 외에도 실행 파일을 실행하는데 각종 필요한 정보가 계속 추가되고 있다. 매니페스트 파일을 지원하지 않는 컴파일러를 사용하던 시절에는 아래와 같은 매니페스트 xml 파일을 직접 작성해서 실행 파일의 리소스로 포함시켜줘야 했지만 요즈음 Visual Studio는 매니페스트 파일 생성을 도와 주고 있기 때문에 프로젝트 설정에서 각종 세부 사항을 설정하면 된다.

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level='asInvoker' uiAccess='false' />
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

보안 프로그램을 만드는 경우에는 종종 이러한 매니페스트 파일의 내용의 추출이 필요할 때가 있다. PE 파일에서 직접 매니페스트 XML을 추출하기 위해서는 리소스 섹션을 참고하면 된다. 매니페스트 XML 파일의 PE 파일에 RT_MANIFEST 타입의 1번 리소스로 추가되기 때문이다. 로드된 모듈의 경우에는 FindResource 함수를 사용해서 손쉽게 해당 항목을 찾을 수 있지만 로드되지 않은 모듈의 경우에는 이 작업이 쉽지 않다. 이 경우에는 직접 PE 파일에서 리소스 섹션을 뒤져서 항목을 찾아야 한다. 아래 FindResourceDirectoryEntryWithId 함수는 rd 리소스 블록에 포함된 블록 중에 ID가 넘어온 id 파라미터와 일치하는 블록 정보를 찾아서 반환하는 역할을 한다. 해당 함수를 사용해서 루트 블록에서 RT_MANIFEST를 찾고, 그 다음 해당 블록의 1번 리소스를 찾으면 매니페스트 정보를 추출할 수 있다.

PIMAGE_RESOURCE_DIRECTORY_ENTRY
FindResourceDirectoryEntryWithId(PVOID root, PIMAGE_RESOURCE_DIRECTORY rd, USHORT id)
{
	PIMAGE_RESOURCE_DIRECTORY_ENTRY entry;
	entry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) GetPtr(rd, sizeof(*rd));

	ULONG cnt = rd->NumberOfNamedEntries + rd->NumberOfIdEntries;

	for(ULONG i = 0; i < cnt; ++i)
	{
		if(!entry[i].NameIsString)
		{
			if(id == entry[i].Id)
				return &entry[i]; 
		}
	}

	return NULL;
}

아래 GetManifestStringFromBuffer 함수는 PE 파일을 읽어들인 버퍼에서 매니페스트 문자열을 추출하는 방법을 보여주고 있다. 리소스 섹션을 찾은 다음 해당 섹션에서 RT_MANIFEST 블록을 찾고, 다시 1번 ID 블록을 찾은 다음 정보를 복사한다. 실행 파일에서 매니페스트 문자열을 추출하는 전체 프로젝트 소스 코드는 여기에서 다운로드 받을 수 있다.

BOOL
GetManifestStringFromBuffer(LPCVOID abuffer
								, SIZE_T buffer_size
								, LPSTR manifest
								, SIZE_T manifest_size
								, PSIZE_T required_size)
{
	ULONG sec_cnt = 0;
	PIMAGE_SECTION_HEADER sec = NULL;
	PIMAGE_DATA_DIRECTORY dd = NULL;
	PUCHAR buffer = (PUCHAR) abuffer;

	PIMAGE_NT_HEADERS32 nt32 = GetNTHeaders32(buffer, buffer_size);
	if(nt32)
	{
		sec_cnt = nt32->FileHeader.NumberOfSections;
		sec = GetSectionHeaderFromNT(nt32);
		dd = &nt32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];
	}
	else
	{
		PIMAGE_NT_HEADERS64 nt64 = GetNTHeaders64(buffer, buffer_size);
		if(nt64)
		{
			sec_cnt = nt64->FileHeader.NumberOfSections;
			sec = GetSectionHeaderFromNT(nt64);
			dd = &nt64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];
		}
	}

	if(!sec || !dd)
		return FALSE;

	PVOID rptr = GetPtr(buffer, dd->VirtualAddress);

	for(ULONG i = 0; i < sec_cnt; ++i)
	{
		PVOID sptr = GetPtr(buffer, sec[i].VirtualAddress);
		PVOID eptr = GetPtr(sptr, sec[i].Misc.VirtualSize);

		if(rptr >= sptr && rptr < eptr)
		{
			PIMAGE_RESOURCE_DIRECTORY root;
			
			ULONG diff = GetOffset(sptr, rptr);
			root = (PIMAGE_RESOURCE_DIRECTORY) &buffer[sec[i].PointerToRawData + diff];

			// RT_MANIFEST 리소스 블록을 찾는다.

			PIMAGE_RESOURCE_DIRECTORY_ENTRY entry;
			ULONG_PTR man_id = (ULONG_PTR) RT_MANIFEST;
			entry = FindResourceDirectoryEntryWithId(root, root, man_id);
			if(!entry)
				return FALSE;

			// RT_MANIFEST 리소스 블록 아래 ID가 1인 리소스를 찾는다.

			PIMAGE_RESOURCE_DIRECTORY rd;
			rd = (PIMAGE_RESOURCE_DIRECTORY) GetPtr(root, entry->OffsetToDirectory);
			entry = FindResourceDirectoryEntryWithId(root, rd, 1);
			if(!entry)
				return FALSE;

			rd = (PIMAGE_RESOURCE_DIRECTORY) GetPtr(root, entry->OffsetToDirectory);
			entry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) GetPtr(rd, sizeof(*rd));

			PIMAGE_RESOURCE_DATA_ENTRY data;
			data = (PIMAGE_RESOURCE_DATA_ENTRY) GetPtr(root, entry->OffsetToData);

			PVOID pdata;
			pdata = GetPtr(root, data->OffsetToData - sec[i].VirtualAddress + diff);

			if(required_size)
				*required_size = data->Size + 1;

			if(manifest_size < data->Size + 1)
			{
				SetLastError(ERROR_INSUFFICIENT_BUFFER);
				return FALSE;
			}

			memcpy(manifest, pdata, data->Size);
			manifest[data->Size] = '\0';
			return TRUE;
		}
	}

	return FALSE;
}
@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중