任意の IL で動作させる

最近、よく Windows Server 2008 対応の相談を受けます。
RTM を迎えてから、もう半年以上経っているんですよね。(RTM は 2/4付け)
Vista & 2008 対応製品の開発が加速していることを実感しています。
悩ましいのは、GetVersionEx() を使って、バージョン判定をすると OSVERSIONINFOEX 構造体の
dwMajorVersion と dwMinorVersion は

dwMajorVersion : 6
dwMinorVersion : 0

となり 2008 と Vista で同じだし、さらに szCSDVersion は

szCSDVersion : Service Pack 1

と返ってくるのが紛らわしいですよね。
(2008 は、内部的に Vista の SP1 をベースにしているので、仕方がないのですが)
バージョン判定ネタは、新 OS がリリースされる度に付きまといます。

OS のバージョン判定は GetVersionEx() だけでなく、

  • GetSystemMetrics()
  • GetProductInfo()
  • GetNativeSystemInfo()

という API を駆使して行う方がよいみたいです。

まあ、OS バージョン判定は、常に MSDN に記載されているので、独自でロジックを組むより参考にした方が無難です。

さて、話しは変わりまして、Vista & 2008 には、整合性レベルがありますね。
整合性レベルには

  • 高 (System High)
    4000: Local System
    3000: Local Service、Network Service、Elevated tokens
  • 中 (Medium)
    2000: LUA Tokens、Authenticated Users
  • 低 (Low)
    1000: Everyone

の大きく 3 つあることはすでによろしいかと思います。
今更という感もありますが、今回は、これを意図的に操作(プロセス実行)してみようというネタです。
なお、大元のネタは、Kenny Kerr 氏のブログです。

Kenny Kerr 氏のブログは開発者には、ぜひお勧めします。

その1: 高 IL でプロセスを実行する

これには、runas コマンドが活躍します。
runas コマンドは、コマンドプロンプトを開いて「runas」と入力するとヘルプが表示されます。
簡単なヘルパー関数を作成します。

//
// 整合性レベルを高 IL にしてプロセスを起動するヘルパー関数
// GUI プログラムから使うことができます
//
ULONG RunAsHighLevel(HWND hWnd, LPSTR lpszPath)
{
    ShellExecuteInfo sei(m_hWnd, SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI, lpszPath, NULL, SW_SHOWNORMAL);

    // runas コマンドを使用して、ShellExecuteEx を呼び出す
    if (!ShellExecuteEx(&sei))
    {
        return GetLastError();
    }

    return ERROR_SUCCESS;
}

struct ShellExecuteInfo : public SHELLEXECUTEINFO
{
public:
    ShellExecuteInfo()
    {
    }
    ShellExecuteInfo(HWND hWnd, ULONG fMask, LPCSTR lpFile, LPCSTR lpParameters, int nShow)
    {
        ZeroMemory(this, sizeof(ShellExecuteInfo));
        this->cbSize = sizeof(ShellExecuteInfo);
        this->hwnd = hWnd;
        this->fMask = fMask;
        this->lpVerb = TEXT("runas");;
        this->lpFile = lpFile;
        this->lpParameters = lpParameters;
        this->nShow = nShow;
    }
};

ここでのポイントは、ShellExecuteEx() を使っていることです。
簡単にメカニズムを説明します。

ShellExecuteEx() は内部で CreateProcess() を呼び出しているのですが
CreateProcess() が、ERROR_ELEVATION_REQUIRED エラーを返した場合に
AIS (Application Information Service) を呼び出します。

AIS は、ShellExecuteEx() に渡されたウィンドウハンドルを用いて
権限の昇格が必要であるというダイアログ、つまり、権限昇格ダイアログを表示します。

ここでユーザーが権限昇格を行うという指示を行うことで、AIS は CreateProcessAsProcess() を呼び出して
高 IL でプロセスを起動してくれます。 ただ、ShellExecuteEx() は GUI プログラムでしか使えませんので、
サービスプログラムやコンソールプログラムでは、最初から CreateProcessAsUser() を使って意図的に高 IL で起動しましょう。

続いて、もっと万能に使えるものを考えてみます。

その2: 引数で整合性レベルを指定して、プロセスを実行する

引数で整合性レベルを指定できると便利ですよね~。
そんな欲張りな要求を考えてみます。
ここでは、先ほど記述した CreateProcessAsUser() が活躍します。

//
// 整合性レベルを引数にして、任意の整合性レベルでプロセスを起動するヘルパー関数
//
ULONG RunAsIntegrityLevel(DWORD dwIntegrityLevel, LPSTR lpszPath)
{
    DWORD dwErr;
    HANDLE hToken;

    // 現在のプロセストークンを取得する
    BOOL b = OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &hToken);
    if (!b)
    {
        return GetLastError();
    }

    HANDLE hNewToken;
    b = DuplicateTokenEx(
          hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hNewToken
        );
    // if (!b)
    // エラー処理

    WellKnownSid integrityLevelSid(
                    WellKnownSid::MandatoryLabelAuthority,
                    SECURITY_MANDATORY_LOW_RID
                    );

    TOKEN_MANDATORY_LABEL TIL = {0};
    TIL.Label.Attributes = SE_GROUP_INTEGRITY;
    TIL.Label.Sid        = &integrityLevelSid;

    // プロセスのアクセストークンに、整合性レベルを設定します
    b = SetTokenInformation(hNewToken, TokenIntegrityLevel,
            &TIL, sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(&integrityLevelSid));
    // if (!b)
    // エラー処理

    // プロセスのアクセストークンに、UI 特権レベルを設定します
    b = SetTokenInformation(hNewToken, TokenUIAccess,
            &TIL, sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(&integrityLevelSid));
    // if (!b)
    // エラー処理

    ProcessInformation pi;
    StartupInfo si;

    // 整合性の低い新しいプロセスの作成
    b = CreateProcessAsUser(
            hNewToken, NULL, lpszPath, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    if (!b)
    {
        CloseHandle(hToken);
        CloseHandle(hNewToken);
        return  GetLastError();
    }

    CloseHandle(hToken);
    CloseHandle(hNewToken);

    return ERROR_SUCCESS;
}

struct StartupInfo : public STARTUPINFO
{
public:
    StartupInfo()
    {
        ZeroMemory(this, sizeof(StartupInfo));
        this->cb = sizeof(STARTUPINFO);
    }
};

struct ProcessInformation : public PROCESS_INFORMATION
{
public:
    ProcessInformation()
    {
        ZeroMemory(this, sizeof(ProcessInformation));
    }
};

簡単に言うと、CreateProcessAsUser() のトークンを任意の整合性レベルに変更してプロセスを起動するというものです。
なお、WellKnownSid というものは、Kerr 氏が作成した SID を派生させた独自の構造体です。
面倒な構造体も、こんなラッパーを作成しておくとミスが減りますね。

この関数では、integrityLevelSid の引数を変えることで、各整合性レベルで動作させることができます。
WinNT.h には、

#define SECURITY_MANDATORY_LOW_RID     (0x00001000L)
#define SECURITY_MANDATORY_MEDIUM_RID  (0x00002000L)
#define SECURITY_MANDATORY_HIGH_RID    (0x00003000L)

が定義されています。(Vista 対応以降の SDK が必要です)
この関数であれば、どこからでも使えますので、かなり重宝すると思います。
(ただし、中 IL から 高IL の起動はできませんので、ご注意)
どうですか、意外に簡単でしょう?

整合性レベルときちんと向き合えば、意外と"いいやつ"になってくれます。(ほんと?)

では、今回はこの辺で。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。