ディスクの内容を直接読むと、ほかのユーザーのファイルも見れてしまう件 (Visual C++)

Visual C++ で、ファイルではなく、ディスクそのものを読み込むプログラムを作ってみます。

動作確認環境

  • Windows 11 Home 21H2
  • Visual Studio Community 2019

ディスクを読み込むプログラム

「物理ディスク 0」の先頭 512 バイトを読み込むプログラムを以下に示します。
ポイントは次の通りです。

  • CreateFile() のファイル名として、”\\\\.\\PHYSICALDRIVE0″ を指定する。
  • 読み込む場所とサイズは、セクターのサイズ(通常 512 バイト)の倍数にする。

セクターのサイズは、たとえば fsutil fsinfo sectorInfo c: で確認できます。

// [readdisk.c]

#include <stdio.h>
#include <windows.h>

int main()
{
    //----- ファイルを開く
    HANDLE  hFile;
    hFile = CreateFile(
        "\\\\.\\PHYSICALDRIVE0", // 物理ディスク 0
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        // 管理者として実行すること。
        // 権限が足りないと 5=ERROR_ACCESS_DENIED が返る。
        printf("CreateFile, err=%d\n", GetLastError());
        return 1;
    }

    //----- ファイルポインタを設定する
    DWORD           dwRet;
    LARGE_INTEGER   liPosSeek;

    // 読み込む場所を指定。
    // セクターサイズ(通常 512=0x200 バイト)の倍数を指定すること。
    // 値が不適切だとエラー 87=ERROR_INVALID_PARAMETER が返る。
    liPosSeek.QuadPart = 0;

    dwRet = SetFilePointer(hFile, liPosSeek.LowPart, &liPosSeek.HighPart, FILE_BEGIN);
    if (dwRet == INVALID_SET_FILE_POINTER)
    {
        printf("SetFilePointer, err=%d\n", GetLastError());
        CloseHandle(hFile);
        return 1;
    }

    //----- ディスクを読む
    BOOL    bRet;
    DWORD   dwNumberOfBytesRead;

    // 読み込むサイズを指定。
    // セクターサイズ(通常 512=0x200 バイト)の倍数を指定すること。
    // 値が不適切だとエラー 87=ERROR_INVALID_PARAMETER が返る。
    BYTE    buf[512];

    bRet = ReadFile(hFile, buf, sizeof(buf), &dwNumberOfBytesRead, NULL);
    if (bRet == FALSE)
    {
        printf("ReadFile, err=%d\n", GetLastError());
        CloseHandle(hFile);
        return 1;
    }

    //----- 読み込んだ結果を表示する
    int     x;
    BYTE    c;
    DWORD   dwBufIndex = 0;
    char    pcAscii[16 + 1];
    LARGE_INTEGER   liPosDisp = liPosSeek;

    while (dwBufIndex < dwNumberOfBytesRead)
    {
        printf("%08x`%08x  ", liPosDisp.HighPart, liPosDisp.LowPart);
        liPosDisp.QuadPart += 16;

        for (x = 0; x < 16 && dwBufIndex < dwNumberOfBytesRead; x++)
        {
            c = buf[dwBufIndex++];
            printf("%02x ", c);
            pcAscii[x] = isprint(c) ? c : '.';
        }
        pcAscii[x] = '\0';

        printf("%*s %s\n", (16 - x) * 3, "", pcAscii);
    }

    return 0;
}

コンパイルし、コマンドプロンプトから実行します。

C:\tmp>readdisk.exe
CreateFile, err=5

管理者ユーザーでログインしていますが、 UAC が ON のため、「5 = ERROR_ACCESS_DENIED = アクセス拒否」で失敗しました。

コマンドプロンプトを「管理者として実行」し、リトライします。

C:\tmp>readdisk.exe
00000000`00000000  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000000`00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000000`00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  <中略>
00000000`000001b0  00 00 00 00 00 00 00 00 54 db 09 2c 00 00 00 00  ........T..,....
00000000`000001c0  02 00 ee fe 7f 99 01 00 00 00 ff ff ff ff 00 00  ................
00000000`000001d0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000000`000001e0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000000`000001f0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa  ..............U.

今度は正常に読めました。

アドレス「00000000`000001fe」にある「55 aa」(シグネチャー, マジックナンバー)や、アドレス「00000000`000001c2」にある「ee」(次のセクターに EFI ヘッダが続くことを示す)などから、GPT ディスクの先頭にある保護 MBR のデータが正しく読めたことが分かります。

ボリュームを読み込むプログラム

「物理ディスク」ではなく、「C ドライブ」といったボリュームを読み込んでみます。
変更点はこれだけです。

【変更前】 "\\\\.\\PHYSICALDRIVE0", // 物理ディスク 0

【変更後】 "\\\\.\\C:", // ボリューム C:

実行します。

C:\tmp>readdisk.exe
00000000`00000000  eb 52 90 4e 54 46 53 20 20 20 20 00 02 08 00 00  .R.NTFS    .....
00000000`00000010  00 00 00 00 00 f8 00 00 3f 00 ff 00 00 58 18 00  ........?....X..
00000000`00000020  00 00 00 00 80 00 80 00 ff 2f ce 1b 00 00 00 00  ........./......
  <中略>
00000000`000001b0  20 69 73 20 63 6f 6d 70 72 65 73 73 65 64 00 0d   is compressed..
00000000`000001c0  0a 50 72 65 73 73 20 43 74 72 6c 2b 41 6c 74 2b  .Press Ctrl+Alt+
00000000`000001d0  44 65 6c 20 74 6f 20 72 65 73 74 61 72 74 0d 0a  Del to restart..
00000000`000001e0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000000`000001f0  00 00 00 00 00 00 8a 01 a7 01 bf 01 00 00 55 aa  ..............U.

アドレス「00000000`000001fe」にある「55 aa」や、アドレス「00000000`00000000」にある「eb xx 90」(= jmp 相対アドレス xx / nop) およびそれに続く “NTFS” などから、NTFS ブートセクターが正しく読めたことが分かります。

別ユーザーのファイルも読める

新規に「user1」という名前のアカウントを作り、ログインします。

そして、「user1」ユーザーのドキュメントフォルダ、パス名でいうと「C:\Users\user1\Documents」に、秘密の情報「My secret password is 12345.」を書いた「mysecretfile.txt」というテキストファイルを作ります。

さらに、プロパティの設定で「SYSTEM」や「Administrators」といった自分以外からのアクセスを拒否するよう設定します。

さて、もとのアカウントに戻りましょう。

エクスプローラーで「C:\Users\user1」をダブルクリックしても無反応です。ファイルの存在は確認できません。

管理者モードのコマンドプロンプトからであれば「C:\Users\user1\Documents」の中に入ることができ、ファイルの存在が確認できますが、中身を表示しようとすると「アクセスが拒否されました。」になります。

WSL でも、ファイルの存在は確認できますが、中身は「Permission denied」で表示できません。

プログラムからフルパス名指定で読み込もうとしても(以下のように変更)、

【変更前】 "\\\\.\\C:", // ボリューム

【変更後】 "c:\\Users\\user1\\Documents\\mysecretfile.txt", // 別ユーザーのファイル

「5 = ERROR_ACCESS_DENIED = アクセス拒否」で失敗します。

C:\tmp>readdisk.exe
CreateFile, err=5

しかし、何らかの方法(詳細省略)で当該ファイルの情報がドライブ C のアドレス 0xDD41B7C00 から書き込まれていることが分かれば、CreateFile の第一引数を「”\\.\C:”」に、読み込む場所の指定を「liPosSeek.QuadPart = 0xDD41B7C00;」にすることで、次のようにデータがダンプできてしまいます。

C:\tmp>readdisk.exe
0000000d`d41b7c00  46 49 4c 45 30 00 03 00 a3 c7 2c ed 20 00 00 00  FILE0.....,. ...
0000000d`d41b7c10  3f 00 02 00 38 00 01 00 f8 01 00 00 00 04 00 00  ?...8...........
0000000d`d41b7c20  00 00 00 00 00 00 00 00 05 00 00 00 eb d1 03 00  ................
  <中略>
0000000d`d41b7d60  20 00 00 00 00 00 00 00 10 01 6d 00 79 00 73 00   .........m.y.s.
0000000d`d41b7d70  65 00 63 00 72 00 65 00 74 00 66 00 69 00 6c 00  e.c.r.e.t.f.i.l.
0000000d`d41b7d80  65 00 2e 00 74 00 78 00 74 00 00 00 00 00 00 00  e...t.x.t.......
0000000d`d41b7d90  40 00 00 00 28 00 00 00 00 00 00 00 00 00 04 00  @...(...........
0000000d`d41b7da0  10 00 00 00 18 00 00 00 9c cd 98 8e 14 87 ec 11  ................
0000000d`d41b7db0  b3 2c dc 71 96 0a d7 68 80 00 00 00 38 00 00 00  .,.q...h....8...
0000000d`d41b7dc0  00 00 18 00 00 00 01 00 1d 00 00 00 18 00 00 00  ................
0000000d`d41b7dd0  4d 79 20 73 65 63 72 65 74 20 70 61 73 73 77 6f  My secret passwo
0000000d`d41b7de0  72 64 20 69 73 20 31 32 33 34 35 2e 0a 00 00 00  rd is 12345.....
0000000d`d41b7df0  ff ff ff ff 82 79 47 11 00 00 00 00 00 00 07 00  .....yG.........

後ろのほうにテキストファイルの中身「My secret password is 12345.」がそのまま表示されています。

秘密のファイルは暗号化しましょう。