ディスクの内容を直接読むと、ほかのユーザーのファイルも見れてしまう件 (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.」がそのまま表示されています。
秘密のファイルは暗号化しましょう。