Microsoft MVP성태의 닷넷 이야기
.NET Framework: 556. C#으로 다루는 MBR(Master Boot Record) [링크 복사], [링크+제목 복사]
조회: 8773
글쓴 사람
홈페이지
첨부 파일
[mbr_cs.zip]    

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

이 소스 코드에서 디스크 장치 목록을 구하는 것은 다음의 코드입니다.

public List<string> GetDriveList()
{
    List<string> drivelist = new List<string>();

    try
    {
        ManagementObjectSearcher searcher =
            new ManagementObjectSearcher("root\\CIMV2",
            "SELECT * FROM Win32_DiskDrive");

        foreach (ManagementObject queryObj in searcher.Get())
        {
            drivelist.Add(queryObj["DeviceID"].ToString());
        }
    }
    catch (ManagementException)
    {
        return null;
    }
    return drivelist;
}

drivelist에 담겨지는 장치명은 이런 식입니다.

\\.\PHYSICALDRIVE0
\\.\PHYSICALDRIVE1

위의 출력은 디스크가 2개 있는 경우입니다. 그 다음 이 장치명을 이용해 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 파티션으로 되어 있기 때문에 분석이 저렇게 밖에 안된 것입니다.

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




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

[연관 글]





[최초 등록일: ]
[최종 수정일: 3/18/2020 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@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을 나타내주는) 변수를 가져오고 싶은데 어떻게 접근을 해야할까요??
[손님]
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) < 이 함수를 호출한다음 값을 가져오고 싶은데
방법이 있을까요? 객체를 생성해서 접근하려고 했더니 원하는 값이 안나왔습니다..

[손님]
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);
이런식으로 접근을 하려고 했는데 안되네요,,
[손님]
2020-03-19 09시40분
그렇게 사용하면 당연히 test.Type의 값이 0이어서 Unknown이 나옵니다. PartitionEntry는 그냥 구조체일 뿐이지 스스로 MBR의 내용을 읽어와 초기화하는 코드는 없습니다.

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

1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12164정성태2/29/2020310오류 유형: 598. Surface Pro 6 - Windows Hello Face Software Device가 인식이 안 되는 문제
12163정성태2/27/2020349.NET Framework: 899. 익명 함수를 가리키는 delegate 필드에 대한 직렬화 문제
12162정성태2/28/2020426디버깅 기술: 166. C#에서 만든 COM 객체를 C/C++로 P/Invoke Interop 시 메모리 누수(Memory Leak) 발생파일 다운로드2
12161정성태2/26/2020204오류 유형: 597. manifest - The value "x64" of attribute "processorArchitecture" in element "assemblyIdentity" is invalid.
12160정성태2/26/2020284개발 환경 구성: 469. Reg-free COM 개체 사용을 위한 manifest 파일 생성 도구 - COMRegFreeManifest
12159정성태2/26/2020198오류 유형: 596. Visual Studio - The project needs to include ATL support
12158정성태2/26/2020312디버깅 기술: 165. C# - Marshal.GetIUnknownForObject/GetIDispatchForObject 사용 시 메모리 누수(Memory Leak) 발생파일 다운로드1
12157정성태2/27/2020327디버깅 기술: 164. C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법파일 다운로드1
12156정성태2/25/2020237오류 유형: 595. LINK : warning LNK4098: defaultlib 'nafxcw.lib' conflicts with use of other libs; use /NODEFAULTLIB:library
12155정성태2/25/2020264오류 유형: 594. Warning NU1701 - This package may not be fully compatible with your project
12154정성태2/25/2020225오류 유형: 593. warning LNK4070: /OUT:... directive in .EXP differs from output filename
12153정성태2/23/2020334.NET Framework: 898. Trampoline을 이용한 후킹의 한계파일 다운로드1
12152정성태2/23/2020303.NET Framework: 897. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Trampoline 후킹)파일 다운로드1
12151정성태2/23/2020331.NET Framework: 896. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 - 두 번째 이야기 (원본 함수 호출)파일 다운로드1
12150정성태2/23/2020339.NET Framework: 895. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법파일 다운로드1
12149정성태2/20/2020387.NET Framework: 894. eBEST C# XingAPI 래퍼 - 연속 조회 처리 방법 [1]
12148정성태2/23/2020429디버깅 기술: 163. x64 환경에서 구현하는 다양한 Trampoline 기법
12147정성태2/27/2020373디버깅 기술: 162. x86/x64의 기계어 코드 최대 길이
12146정성태2/18/2020421.NET Framework: 893. eBEST C# XingAPI 래퍼 - 로그인 처리파일 다운로드1
12145정성태2/18/2020364.NET Framework: 892. eBEST C# XingAPI 래퍼 - Sqlite 지원 추가파일 다운로드1
12144정성태2/23/2020387.NET Framework: 891. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 두 번째 이야기파일 다운로드1
12143정성태2/13/2020280.NET Framework: 890. 상황별 GetFunctionPointer 반환값 정리 - x64파일 다운로드1
12142정성태2/27/2020362.NET Framework: 889. C# 코드로 접근하는 MethodDesc, MethodTable파일 다운로드1
12141정성태2/10/2020478.NET Framework: 888. C# - ASP.NET Core 웹 응용 프로그램의 출력 가로채기 [1]파일 다운로드1
12140정성태2/10/2020333.NET Framework: 887. C# - ASP.NET 웹 응용 프로그램의 출력 가로채기파일 다운로드1
12139정성태2/9/2020525.NET Framework: 886. C# - Console 응용 프로그램에서 UI 스레드 구현 방법
1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...