C#으로 다루는 MBR(Master Boot Record)
최근에 트위터를 통해 공유된 다음의 자료를 읽게 되었습니다.
Windows MBR 분석 (MBR 분석을 통한 자동화 도구 제작 및 악성코드 분석)
; https://drive.google.com/file/d/0B3t7Uu3cQBgYZG5TS04wVTVOVkE/view
그래서, C#으로 간단하게 MBR 내용을 보여주는 프로그램을 만들어 보았습니다.
먼저 해야 할 일이 하드 디스크를 File System을 통하지 않고 직접 읽을 수 있어야 하는데요. 이는 다음의 코드를 이용하면 쉽게 구현할 수 있습니다.
CCS LABS C#: Low Level Disk Access
; https://code.msdn.microsoft.com/windowsapps/CCS-LABS-C-Low-Level-Disk-91676ca9
이 소스 코드에서 디스크 장치 목록을 구하는 것은 다음의 코드입니다.
using System.Management;
// Install-Package System.Management
internal class Program
{
static void Main(string[] args)
{
foreach (Win32_DiskDrive item in GetDriveList())
{
Console.WriteLine(item.DeviceID);
Console.WriteLine(item.Caption);
Console.WriteLine(item.Description);
Console.WriteLine(item.Index);
Console.WriteLine(item.SerialNumber);
Console.WriteLine(item.PNPDeviceID);
Console.WriteLine();
}
}
public static List<Win32_DiskDrive> GetDriveList()
{
List<Win32_DiskDrive> drives = new List<Win32_DiskDrive>();
try
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_DiskDrive");
foreach (ManagementObject queryObj in searcher.Get())
{
Win32_DiskDrive item = new Win32_DiskDrive();
item.DeviceID = queryObj["DeviceID"].ToString();
item.Caption = queryObj["Caption"].ToString();
item.Description = queryObj["Description"].ToString();
item.Index = (uint)queryObj["Index"];
item.SerialNumber = queryObj["SerialNumber"].ToString().Trim();
item.PNPDeviceID = queryObj["PNPDeviceID"].ToString();
drives.Add(item);
}
}
catch (ManagementException)
{
return null;
}
return drives;
}
}
public class Win32_DiskDrive
{
public string DeviceID;
public string Caption;
public string Description;
public uint Index;
public string SerialNumber;
public string PNPDeviceID;
}
drivelist에 담겨지는 장치명은 이런 식입니다.
\\.\PHYSICALDRIVE2
Samsung SSD 980 PRO 1TB
Disk drive
2
0025_38B8_11C6_1162.
SCSI\DISK&VEN_NVME&PROD_SAMSUNG_SSD_980\5&EBD909B&0&000000
\\.\PHYSICALDRIVE1
Samsung SSD 980 PRO 1TB
Disk drive
1
0025_38B8_11C6_117E.
SCSI\DISK&VEN_NVME&PROD_SAMSUNG_SSD_980\5&20DA63D3&0&000000
\\.\PHYSICALDRIVE0
WDC WD20EZBX-00AYRA0
Disk drive
0
WD-WXT2AC0JRNHX
SCSI\DISK&VEN_WDC&PROD_WD20EZBX-00AYRA0\4&E91BE7&0&040000
위의 출력은 디스크가 3개 있는 경우입니다. 그다음 이 장치명을 이용해 CreateFile을 호출하면 되는데요. 이 역시 다음의 소스 코드를 이용해 간단하게 작성할 수 있습니다.
internal byte[] DumpSector(string drive, double sector, int bytesPerSector)
{
uint GENERIC_READ = 0x80000000;
uint OPEN_EXISTING = 3;
uint FILE_SHARE_READWRITE = 0x01 | 0x02;
SafeFileHandle handleValue = UnsafeNativeMethods.CreateFile(drive, GENERIC_READ, FILE_SHARE_READWRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
if (handleValue.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
double sec = sector * bytesPerSector;
int size = int.Parse(bytesPerSector.ToString());
byte[] buf = new byte[size];
int read = 0;
int moveToHigh;
UnsafeNativeMethods.SetFilePointer(handleValue, int.Parse(sec.ToString()), out moveToHigh, EMoveMethod.Begin);
ReadFile(handleValue, buf, size, out read, IntPtr.Zero);
handleValue.Close();
return buf;
}
위의 메서드를 이용해 MBR이 저장된 0번 섹터의 내용을 읽어들이면 됩니다.
byte[] sector = DumpSector(driveName, 0, bytesPerSector);
이제 sector에 담겨진 내용을 "
Windows MBR 분석 (MBR 분석을 통한 자동화 도구 제작 및 악성코드 분석)" 문서에서 설명한 포맷에 맞게 해석만 하면 됩니다. C#이라면, 우선 다음과 같은 구조체로 출발할 수 있습니다.
[StructLayout(LayoutKind.Sequential, Size = 512, Pack = 1)]
public struct MBR
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 446)]
public byte[] BootCode;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public PartitionEntry [] Partitions;
public ushort Signature;
}
[StructLayout(LayoutKind.Explicit, Size = 16, Pack = 1)]
public unsafe struct PartitionEntry
{
[FieldOffset(0)]
public byte BootFlag;
[FieldOffset(1)]
fixed byte _startingCHSAddress[3];
public unsafe int StartingCHSAddress
{
get
{
fixed (byte* pBytes = _startingCHSAddress)
{
return ToInt32(pBytes);
}
}
}
[FieldOffset(4)]
public byte Type;
[FieldOffset(5)]
fixed byte _endingCHSAddress[3];
public int EndingCHSAddress
{
get
{
fixed (byte *pBytes = _endingCHSAddress)
{
return ToInt32(pBytes);
}
}
}
[FieldOffset(8)]
public uint StartingLBAAddress;
[FieldOffset(12)]
public uint SizeOfSector;
unsafe int ToInt32(byte* pBytes)
{
byte[] intBytes = new byte[4];
intBytes[0] = *pBytes;
intBytes[1] = *(pBytes + 1);
intBytes[2] = *(pBytes + 2);
return BitConverter.ToInt32(intBytes, 0);
}
}
이후 작업은 간단합니다. 디스크로부터 읽어들인 바이트 배열에서 MBR 구조체를 다음과 같이 복원해 주면 됩니다.
internal static bool TryParse(byte[] sector, out MBR mbr)
{
mbr = ByteArrayToStructure<MBR>(sector);
return mbr.Signature == 0xaa55;
}
// which marshalling method is better?
// http://stackoverflow.com/questions/14465722/which-marshalling-method-is-better
static T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return stuff;
}
제 컴퓨터 기준으로 (관리자 권한으로 실행시켜) 출력해 보니 이렇군요. ^^
\\.\PHYSICALDRIVE0
Bootable = True, Type = NTFS , SectorSize = 0x000af000, CHS = 0x002120 ~ 0x2c12be, BeginLBA = 0x00000800
Bootable = False, Type = Extended, SectorSize = 0x0a6d383f, CHS = 0x2c33dd ~ 0xfffffe, BeginLBA = 0x000affc1
Bootable = False, Type = NTFS , SectorSize = 0x1356e800, CHS = 0xfffffe ~ 0xfffffe, BeginLBA = 0x0a783800
(undefined)
\\.\PHYSICALDRIVE1
Bootable = False, Type = GPT(protective), SectorSize = 0xffffffff, CHS = 0x000200 ~ 0xffffff, BeginLBA = 0x00000001
(undefined)
(undefined)
(undefined)
PHYSICALDRIVE1의 출력이 흥미로운데요. 해당 디스크가 MBR 형식이 아닌 GPT 파티션으로 되어 있기 때문에 분석이 저렇게 밖에 안된 것입니다.
(
첨부한 파일은 위의 코드를 모두 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]