Microsoft MVP성태의 닷넷 이야기
.NET Framework: 556. C#으로 다루는 MBR(Master Boot Record) [링크 복사], [링크+제목 복사]
조회: 17332
글쓴 사람
정성태 (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분
정성태

... 16  17  [18]  19  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13169정성태11/23/20225593Windows: 212. 윈도우의 Protected Process (Light) 보안 [1]파일 다운로드2
13168정성태11/22/20224874제니퍼 .NET: 31. 제니퍼 닷넷 적용 사례 (9) - DB 서비스에 부하가 걸렸다?!
13167정성태11/21/20224904.NET Framework: 2070. .NET 7 - Console.ReadKey와 리눅스의 터미널 타입
13166정성태11/20/20224633개발 환경 구성: 651. Windows 사용자 경험으로 WSL 환경에 dotnet 런타임/SDK 설치 방법
13165정성태11/18/20224540개발 환경 구성: 650. Azure - "scm" 프로세스와 엮인 서비스 모음
13164정성태11/18/20225458개발 환경 구성: 649. Azure - 비주얼 스튜디오를 이용한 AppService 원격 디버그 방법
13163정성태11/17/20225379개발 환경 구성: 648. 비주얼 스튜디오에서 안드로이드 기기 인식하는 방법
13162정성태11/15/20226440.NET Framework: 2069. .NET 7 - AOT(ahead-of-time) 컴파일
13161정성태11/14/20225676.NET Framework: 2068. C# - PublishSingleFile로 배포한 이미지의 역어셈블 가능 여부 (난독화 필요성) [4]
13160정성태11/11/20225605.NET Framework: 2067. C# - PublishSingleFile 적용 시 native/managed 모듈 통합 옵션
13159정성태11/10/20228782.NET Framework: 2066. C# - PublishSingleFile과 관련된 옵션 [3]
13158정성태11/9/20225110오류 유형: 826. Workload definition 'wasm-tools' in manifest 'microsoft.net.workload.mono.toolchain' [...] conflicts with manifest 'microsoft.net.workload.mono.toolchain.net7'
13157정성태11/8/20225757.NET Framework: 2065. C# - Mutex의 비동기 버전파일 다운로드1
13156정성태11/7/20226668.NET Framework: 2064. C# - Mutex와 Semaphore/SemaphoreSlim 차이점파일 다운로드1
13155정성태11/4/20226186디버깅 기술: 183. TCP 동시 접속 (연결이 아닌) 시도를 1개로 제한한 서버
13154정성태11/3/20225651.NET Framework: 2063. .NET 5+부터 지원되는 GC.GetGCMemoryInfo파일 다운로드1
13153정성태11/2/20226926.NET Framework: 2062. C# - 코드로 재현하는 소켓 상태(SYN_SENT, SYN_RECV)
13152정성태11/1/20225551.NET Framework: 2061. ASP.NET Core - DI로 추가한 클래스의 초기화 방법 [1]
13151정성태10/31/20225674C/C++: 161. Windows 11 환경에서 raw socket 테스트하는 방법파일 다운로드1
13150정성태10/30/20225711C/C++: 160. Visual Studio 2022로 빌드한 C++ 프로그램을 위한 다른 PC에서 실행하는 방법
13149정성태10/27/20225638오류 유형: 825. C# - CLR ETW 이벤트 수신이 GCHeapStats_V1/V2에 대해 안 되는 문제파일 다운로드1
13148정성태10/26/20225628오류 유형: 824. msbuild 에러 - error NETSDK1005: Assets file '...\project.assets.json' doesn't have a target for 'net5.0'. Ensure that restore has run and that you have included 'net5.0' in the TargetFramew
13147정성태10/25/20224756오류 유형: 823. Visual Studio 2022 - Unable to attach to CoreCLR. The debugger's protocol is incompatible with the debuggee.
13146정성태10/24/20225585.NET Framework: 2060. C# - Java의 Xmx와 유사한 힙 메모리 최댓값 제어 옵션 HeapHardLimit
13145정성태10/21/20225845오류 유형: 822. db2 - Password validation for user db2inst1 failed with rc = -2146500508
13144정성태10/20/20225684.NET Framework: 2059. ClrMD를 이용해 윈도우 환경의 메모리 덤프로부터 닷넷 모듈을 추출하는 방법파일 다운로드1
... 16  17  [18]  19  20  21  22  23  24  25  26  27  28  29  30  ...