해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기
예전에 다음과 같은 글을 쓴 적이 있었는데요. ^^
해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법
; https://www.sysnet.pe.kr/2/0/1279
그때 당시에, 좀 더 개선될 수 있는 것으로 PE 파일 포맷 분석하는 방법을 남겨놓았었는데요. 혹시나, ^^ 어떤 분이 그에 관해서 글을 쓰시지 않을까 하는... 기대를 갖고 있었는데, 오늘 기준으로 아무도 안 쓰시네요. ^^; 별로 관심 사항은 아닌가 봅니다.
어쨌든, 이번에는 PE 파일을 직접 살펴보고 Managed/Unmanaged 판별을 해낼 텐데요.
사실, 그다지 어려운 문제는 아닙니다. 게다가 PE 파일 포맷에 대해 잘 설명된 책도 있고요.
Windows 시스템 실행파일의 구조와 원리
; http://www.yes24.com/24/goods/1493278?scode=029
그럼, 우선 IMAGE_DOS_HEADER 먼저 구조체를 채워보는 것부터 시작하겠습니다.
책에 설명된 데로, 해당 구조체는 WinNT.h에 정의되어 있지만 여기서는 C# 에서 구현할 것이기 때문에 적절한 Interop 구조체가 필요합니다. 역시 ^^ pinvoke.net이 그 해답이겠지요.
IMAGE_DOS_HEADER
; http://www.pinvoke.net/default.aspx/Structures/IMAGE_DOS_HEADER.html
위의 정의를 이용하면 간단하게 다음과 같이 IMAGE_DOS_HEADER의 내용을 채울 수 있습니다.
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
// IMAGE_DOS_HEADER를 읽어들이고,
IMAGE_DOS_HEADER dosHeader = new IMAGE_DOS_HEADER();
{
int dosHeaderSize = Marshal.SizeOf(dosHeader);
byte[] dosHeaderBytes = new byte[dosHeaderSize];
fs.Read(dosHeaderBytes, 0, dosHeaderSize);
fixed (byte* p = dosHeaderBytes)
{
IntPtr ptr = new IntPtr(p);
object objDosHeader = Marshal.PtrToStructure(ptr, typeof(IMAGE_DOS_HEADER));
dosHeader = (IMAGE_DOS_HEADER)objDosHeader;
}
if (dosHeader.isValid == false) // _e_magic == "MZ"
{
return false;
}
}
}
(이후의 PE 포맷에 관련된 헤더들도 모두 기본적으로는 위와 같은 구조를 따릅니다.)
IMAGE_DOS_HEADER 이후에는 IMAGE_NT_HEADERS 구조체를 읽어들여야 하는데, 아쉽게도 IMAGE_NT_HEADERS 내부에 포함하고 있는 IMAGE_OPTIONAL_HEADER가 32비트/64비트 모듈에 따라 달라지므로 이 2가지를 한꺼번에 지원하기 위해 이에 대한 판단을 먼저 해야 합니다.
그러려면, 우선 'PE Signature' 바이트를 읽어서 넘어가고,
// IMAGE_NT_HEADERS - signature 를 읽어들이고,
{
fs.Position = dosHeader.e_lfanew;
byte [] ntSignature = new byte[4];
fs.Read(ntSignature, 0, 4);
// PE 헤더임을 확인 (IMAGE_NT_SIGNATURE == 0x00004550)
if (ntSignature[0] != 80 || ntSignature[1] != 69 || ntSignature[2] != 0 || ntSignature[3] != 0)
{
return false;
}
}
다행히 IMAGE_NT_HEADERS 내에 IMAGE_FILE_HEADER 구조체까지 이어서 읽어들이면 32비트/64비트 구별을 할 수 있게 됩니다.
// IMAGE_NT_HEADERS - IMAGE_FILE_HEADER를 읽어들임
IMAGE_FILE_HEADER ntFileHeader = new IMAGE_FILE_HEADER();
bool is64BitHeader = false;
{
int ntFileHeaderSize = Marshal.SizeOf(ntFileHeader);
byte[] ntFileHeaderBytes = new byte[ntFileHeaderSize];
fs.Read(ntFileHeaderBytes, 0, ntFileHeaderSize);
fixed (byte* p = ntFileHeaderBytes)
{
IntPtr ptr = new IntPtr(p);
object objNtFileHeader = Marshal.PtrToStructure(ptr, typeof(IMAGE_FILE_HEADER));
ntFileHeader = (IMAGE_FILE_HEADER)objNtFileHeader;
}
if (ntFileHeader.Machine == IMAGE_FILE_MACHINE_I386)
{
is64BitHeader = false;
}
else if (ntFileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 ||
ntFileHeader.Machine == IMAGE_FILE_MACHINE_IA64)
{
is64BitHeader = true;
}
else
{
return false;
}
}
이걸로 모든 준비는 끝났습니다. .NET DLL/EXE의 경우, IMAGE_OPTIONAL_HEADER 내부의 DataDirectory 구조체 중의 하나인 CLRRuntimeHeader 값을 포함하고 있기 때문에 그 값이 유효한지를 판단해 내면 됩니다. 다음은 32비트인 경우에 사용될 수 있는 코드입니다.
ushort optionalHeaderSize = ntFileHeader.SizeOfOptionalHeader;
if (is64BitHeader == false)
{
IMAGE_OPTIONAL_HEADER32 optionalHeader32 = new IMAGE_OPTIONAL_HEADER32();
byte[] optionalHeaderBytes = new byte[optionalHeaderSize];
fs.Read(optionalHeaderBytes, 0, optionalHeaderSize);
fixed (byte* p = optionalHeaderBytes)
{
IntPtr ptr = new IntPtr(p);
object objOptionalHeader = Marshal.PtrToStructure(ptr, typeof(IMAGE_OPTIONAL_HEADER32));
optionalHeader32 = (IMAGE_OPTIONAL_HEADER32)objOptionalHeader;
if (optionalHeader32.Magic != MagicType.IMAGE_NT_OPTIONAL_HDR32_MAGIC
|| optionalHeader32.NumberOfRvaAndSizes != 16)
{
return false;
}
}
return optionalHeader32.CLRRuntimeHeader.VirtualAddress != 0;
}
else
{
...[생략: 32비트와 유사]...
}
원래 DataDirectory는 16개의 배열로 정의되는 것이 보통인데, 위의 코드에서는 C# Interop 코드에서 명시적으로 15번째 COM descriptor 요소를 CLRRuntimeHeader 속성으로 제공하고 있어 쉽게 접근하고 있습니다. 따라서, 그 값이 0이면 Native DLL이고, 그렇지 않으면 .NET DLL이 됩니다.
첨부된 파일은 지난번 글에 포함된 소스 코드에다 이번 글의 IsManagedFromPE 메서드를 추가 구현한 것을 포함하고 있습니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]