Microsoft MVP성태의 닷넷 이야기
.NET Framework: 556. C#으로 다루는 MBR(Master Boot Record) [링크 복사], [링크+제목 복사],
조회: 25160
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
[mbr_cs.zip]    
(연관된 글이 3개 있습니다.)

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 파티션으로 되어 있기 때문에 분석이 저렇게 밖에 안된 것입니다.

(첨부한 파일은 위의 코드를 모두 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/24/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2016-03-09 01시30분
MBR의 부트 코드 바이트 배열을 x86 mnemonic으로 역어셈블한 코드 내용은 다음의 글에서 확인할 수 있습니다.

머신 바이트 배열로부터 역어셈블해주는 라이브러리 - Udis86 Assembler
; http://www.sysnet.pe.kr/2/0/10916
정성태
2020-03-18 01시30분
[lolzz] 관리자님 안녕하세요 궁금한점이 있습니다.
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;
---------------------
다른 cs파일을 만들어서 patitionEntry에 있는 Type(filesystem을 나타내주는) 변수를 가져오고 싶은데 어떻게 접근을 해야할까요??
[guest]
2020-03-18 01시54분
@lolzz 질문을 이해하지 못하겠습니다. struct의 필드 값을 다른 cs 파일에서 접근하는데, 뭘 더 할 필요가 있나요?
정성태
2020-03-18 02시59분
[lolzz] public override string ToString()
        {
            if (SizeOfSector == 0)
            {
                return "(undefined)";
            }

            return string.Format("Type = {0,-8}, GetPartitionTypeName(Type));
        }

        private string GetPartitionTypeName(byte type)
        {
            switch (type)
            {
                case 0x07:
                    return "NTFS";

                case 0x0c:
                    return "FAT32";

                case 0x0b:
                    return "FAT32";

                case 0x0e:
                    return "FAT";

                case 0x0F:
                    return "Extended";

                case 0xee:
                    return "GPT(protective)";

                default:
                    return string.Format("Uknown(0x{0:x})", type);
            }
        }
    }
}
다른 cs 파일에서 GetPartitionTypeName(Type) < 이 함수를 호출한다음 값을 가져오고 싶은데
방법이 있을까요? 객체를 생성해서 접근하려고 했더니 원하는 값이 안나왔습니다..
[guest]
2020-03-18 04시00분
혹시,

private string GetPartitionTypeName(byte type)

위의 코드에서 private 접근자를 public으로 바꾸면 해결되는 것을 묻는 건가요?
정성태
2020-03-19 09시18분
[lolzz] 아뇨 ㅠㅠ public으로 변환을 해도 unknowon 값을 가져오더라구요
Partition test = new PartitionEntry() // 객체생성
var filetype = test.GetPartitionEntry(test.type);
console.writeLine("type : {0}",filetype);
이런식으로 접근을 하려고 했는데 안되네요,,
[guest]
2020-03-19 09시40분
그렇게 사용하면 당연히 test.Type의 값이 0이어서 Unknown이 나옵니다. PartitionEntry는 그냥 구조체일 뿐이지 스스로 MBR의 내용을 읽어와 초기화하는 코드는 없습니다.

초기화 부분은 MasterBootRecord.cs의 MBR.TryParse 코드에서 시작됩니다.
정성태
2020-03-19 10시04분
[lolzz] 아하,,, 감사합니다 일단 알려주신대로 출력 해보겠습니다.
감사합니다!
[guest]
2023-02-13 09시42분
정성태

... 106  107  108  109  110  111  112  113  114  115  [116]  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11025정성태8/12/201622332개발 환경 구성: 294. .NET Core 프로젝트에서 "Copy to Output Directory" 처리 [1]
11024정성태8/12/201621641오류 유형: 350. "nProtect GameMon" 실행 중에는 Visual Studio 디버깅이 안됩니다! [1]
11023정성태8/10/201623096개발 환경 구성: 293. Azure 구독 후 PaaS 서비스 만들어 보기
11022정성태8/10/201623782개발 환경 구성: 292. Azure Cloud Service 배포시 사용자 정의 작업을 추가하는 방법
11021정성태8/10/201620868오류 유형: 349. System.Runtime.Remoting.RemotingException - Type '..., ..., Version=..., Culture=neutral, PublicKeyToken=null' is not registered for activation [2]
11020정성태8/10/201623584VC++: 98. 원본과 대상 버퍼가 같은 경우 memcpy, wmemcpy 주의점
11019정성태8/10/201640255기타: 60. 도서: 시작하세요! C# 6.0 프로그래밍: 기본 문법부터 실전 예제까지 (2쇄 정오표)
11018정성태8/9/201624733.NET Framework: 600. 단일 메서드 내에서의 할당으로 알아보는 자바와 닷넷의 GC 차이점 [1]
11017정성태8/9/201626785웹: 33. HTTP 쿠키에 한글 값을 설정하는 방법
11016정성태8/7/201623970개발 환경 구성: 291. Windows Server Containers 소개
11015정성태8/7/201622252오류 유형: 348. Windows Server 2016 TP5에서 Windows Containers의 docker run 실행 시 encountered an error during Start failed in Win32
11014정성태8/6/201623047오류 유형: 347. Hyper-V Virtual Machine Management service Account does not have permission to open attachment
11013정성태8/6/201633777개발 환경 구성: 290. Windows 10에서 경험해 보는 Windows Containers와 docker [4]
11012정성태8/6/201623824오류 유형: 346. Windows 10에서 Windows Containers의 docker run 실행 시 encountered an error during CreateContainer failed in Win32 발생
11011정성태8/6/201625440기타: 59. outlook.live.com 메일 서비스의 아웃룩 POP3 설정하는 방법
11010정성태8/6/201622868기타: 58. Outlook에 설정한 SMTP/POP3(예:천리안 메일) 계정 암호를 잊어버린 경우
11009정성태8/3/201628052개발 환경 구성: 289. 2016-08-02부터 시작된 윈도우 10 1주년 업데이트에서 Bash Shell 사용 [8]
11008정성태8/1/201621821오류 유형: 345. 2의 30승 이상의 원소를 갖는 경우 버그가 발생하는 이진 검색(Binary Search) 코드
11007정성태8/1/201623552오류 유형: 344. RDP ActiveX 컨트롤로 특정 PC에 연결할 수 없을 때, 오류 상황을 해결하기 위한 팁파일 다운로드1
11006정성태7/22/201626558개발 환경 구성: 288. SSL 인증서를 Azure Cloud Service에 적용하는 방법
11005정성태7/22/201625201개발 환경 구성: 287. Let's Encrypt 인증서 업데이트 주기: 90일
11004정성태7/22/201620046오류 유형: 343. Invalid service definition or service configuration. Please see the Error List for more details.
11003정성태7/20/201627316VS.NET IDE: 110. Visual Studio 2015에서 .NET Core 응용 프로그램 개발 [1]
11002정성태7/20/201620810개발 환경 구성: 286. Microsoft Azure 서비스의 구독은 반드시 IE로!
11001정성태7/19/201631806.NET Framework: 599. .NET Core/SDK 설치 및 기본 사용법 [6]
11000정성태7/16/201620553오류 유형: 342. Microsoft Visual Studio 2010 Tools for Office Runtime (x86 and x64) 설치 시 오류
... 106  107  108  109  110  111  112  113  114  115  [116]  117  118  119  120  ...