【草稿】 .NET で始める Ring0 プログラミング(仮)

…某所へ投稿するための原稿作成中… 

はじめに

.NET Framework や Web アプリケーションの台頭など、プログラミングをする上で「ハードウェア」を意識する機会は以前ほど多くはないと思いますが、CPU やチップセットなどの設定変更などハードウェアに近い部分に興味のある方は少なくないのではないでしょうか。

しかし、ハードウェアへのアクセスは Ring3* で動作する一般的なアプリケーションからは行うことができず、基本的に Ring0* で動作するデバイスドライバを経由して行う必要があるため、決してハードルが低いとは言えませんでした。

*Ring0/Ring3 とは CPU の動作モードのことで、OS やデバイスドライバなどは CPU が持つ全ての命令が使用できる Ring0 で動作し、アプリケーションは使用できる命令が制限された Ring3 で動作します。興味のある方は検索エンジンなどで調べてみてください。

本稿では、Visual C# と拙作 WinRing0 を使ったハードウェアアクセスプログラミングを紹介させていただきます。

WinRing0 について

WinRing0 は私が開発した DLL とデバイスドライバで構成された Windows 用のハードウェアアクセスライブラリです。IO ポートや MSR、PCI バス、物理メモリなどにアクセスするための機能を提供します。機能の詳細は マニュアル をご覧ください。また、本ライブラリは修正 BSD ライセンスで公開しているため、どなたでも自由に利用することが可能です。

対象読者

  • IO ポート、MSR、PCI デバイス、物理メモリへのアクセスに興味のある方
  • .NET からの CPUID 命令実行に興味のある方

必要な環境

  • Visual C# 2005 Express Edition 以降
    ※Visual C# 2008 Express Edition Beta 2 で動作確認済み
  • 実行環境としては
    [x86] Windows Vista/XP/2000/(NT4)/(Me)/(98)
    [x64] Windows Vista**/XP
    **Vista ではデジタル署名の強制を無効にする必要があります。詳細

WinRing0 導入の流れ 

  1. 実行ファイルの出力フォルダに DLL とデバイスドライバをコピーします。
    (WinRing0.dll, WinRing0x64.dll, OpenLibSys.sys, OpenLibSysX64.sys, OpenLibSysNT4.sys, OpenLibSys.vxd) 
  2. OpenLibSys.cs をプロジェクトに加えます。
  3. using OpenLibSys; 文をソースコードに加えます。

WinRing0 の初期化

  • Ols クラスのインスタンスを生成し、GetDllStatus() により、DLL が正しく初期化されたかどうかを確認します。デバイスドライバの読み込みに成功し初期化が完了した場合は、OLS_DLL_NO_ERROR が、管理者権限がない場合やネットワークドライブから実行するなどしてデバイスドライバが読み込めない場合は OLS_DLL_DRIVER_NOT_LOADED が返ります。正常に初期化が完了しなかった場合はアプリケーションを終了します。
  • なお、RunAsRestart() は Vista で導入された UAC 対策用関数で、権限昇格のダイアログを表示するようにしてアプリケーションを再起動します。

Ols ols = new Ols();
switch (ols.GetDllStatus())
{
  case (uint)Ols.OlsDllStatus.OLS_DLL_NO_ERROR:
      break;
  case (uint)Ols.OlsDllStatus.OLS_DLL_DRIVER_NOT_LOADED:
  if (RunAsRestart() == false)
  {
      MessageBox.Show("DLL Status Error!! DRIVER_NOT_LOADED");
  }
      Application.Exit();
      break;
  case (uint)Ols.OlsDllStatus.OLS_DLL_UNSUPPORTED_PLATFORM:
  case (uint)Ols.OlsDllStatus.OLS_DLL_DRIVER_NOT_FOUND:
  case (uint)Ols.OlsDllStatus.OLS_DLL_DRIVER_UNLOADED:
  case (uint)Ols.OlsDllStatus.OLS_DLL_UNKNOWN_ERROR:
      Application.Exit();
      break;
}

1. MSR の読み込み

初期化も完了したので早速 Ring0 でなければ実行できない MSR の読み込みに挑戦してみましょう。今回は、多くの CPU で実装されている MSR インデックス 0x10 の Time Stamp Counter の読み込みを行います。

RdmsrEx API は 第1引数に MSR インデックス、第2引数および第3引数に結果を格納するための変数(参照渡し)、第4引数に実行するプロセッサを特定するためのスレッドアフィニティマスク(Thread Affinity Mask)を設定します。

uint index = 0, eax = 0, ebx = 0, ecx = 0, edx = 0;
str += "[MSR]\r\n";
str += "index 63-32 31-0\r\n";
index = 0x00000010; // Time Stamp Counter
ols.RdmsrEx(index, ref eax, ref edx, (UIntPtr)1);
str += index.ToString("X8") + ": " + edx.ToString("X8")
    + " " + eax.ToString("X8") + "\r\n";

2. IO ポートアクセス
次に、ReadIoPortByte, WriteIoPortByte を用いた IO ポートアクセスに挑戦してみます。ここでは例としてビープで 440Hz の音を鳴らしてみることにします。※環境によっては、音が鳴らない場合があります。

uint freq = 1193180000 / 440000; // 440Hz
ols.WriteIoPortByte(0x43, 0xB6);
ols.WriteIoPortByte(0x42, (byte)(freq & 0xFF));
ols.WriteIoPortByte(0x42, (byte)(freq >> 9));
System.Threading.Thread.Sleep(100);
ols.WriteIoPortByte(0x61, (byte)(ols.ReadIoPortByte(0x61) | 0x03));
System.Threading.Thread.Sleep(1000);
ols.WriteIoPortByte(0x61, (byte)(ols.ReadIoPortByte(0x61) & 0xFC));
str += "[IO]\r\nBeep 440Hz\r\n";

3. 物理メモリの読み込み
ReadMemBlock を使用して物理メモリの内容を用意したバッファに読み込みます。ここでは、一例として BIOS 内に含まれる SM BIOS (System Management BIOS) 構造体を探し出し、SM BIOS のバージョン情報を表示します。SM BIOS に関する詳細は、拙作 CrystalDMI および DMTF “System Management BIOS Reference Specification” をご覧ください。

const uint MAP_MEMORY_SIZE = 64 * 1024;
byte[] buf = new byte[MAP_MEMORY_SIZE];
byte[] sm = new byte[4];
sm[0] = 0x5F; // '_'
sm[1] = 0x53; // 'S'
sm[2] = 0x4D; // 'M'
sm[3] = 0x5F; // '_'

str += "[MEMORY]\r\n";

fixed (byte* p = &buf[0], s = &sm[0])
{
    if (ols.ReadMemBlock((UIntPtr)0x000F0000, p, MAP_MEMORY_SIZE, 1) == MAP_MEMORY_SIZE)
    {
        for (uint j = 0; j < MAP_MEMORY_SIZE; j += 16)
        {
            if (memcmp(p + j, s, 4) == 0)
            {
                str += "SM BIOS Version : "
                    + ((byte)(p[6 + j])).ToString("D")
                    + "." + ((byte)(p[7 + j])).ToString("D");
                break;
            }
        }
    }
}
str += "\r\n";

 4. PCI デバイスのリストアップ


uint address, value;
str += "[PCI]\r\n";
// All Device
str += "Bus Dev Fnc VendorDevice\r\n";
for (uint bus = 0; bus < 256; bus++)
{
    for (uint dev = 0; dev < 32; dev++)
    {
        for (uint func = 0; func < 8; func++)
        {
            address = ols.PciBusDevFunc(bus, dev, func);
            value = ols.ReadPciConfigDword(address, 0x00);
            if ((value & 0xFFFF) != 0xFFFF && (value & 0xFFFF) != 0x0000)
            {
                str += ols.PciGetBus(address).ToString("X2") + "h "
                    +  ols.PciGetDev(address).ToString("X2") + "h "
                    +  ols.PciGetFunc(address).ToString("X2") + "h "
                    +  ((uint)(value & 0x0000FFFF)).ToString("X04") + "h "
                    +  ((uint)((value >> 16) & 0x0000FFFF)).ToString("X04") + "h\r\n";
            }
        }
    }
}

続く…

あわせて読みたい

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です