AppContainer関連API情報まとめ

AppContainer関連のAPIで調べたことについてまとめておく。
これらの情報は公式ではなく、あくまで自分が個人的に調べたものであるので間違っていている可能性があることに注意。
また、実際に動くコードや解説などが目的の人はAppContainerでデスクトップアプリを起動してみたAppContainer関連サンプル紹介を参照してほしい。

公開API

CreateAppContainerProfile

MSDN参照

pszAppContainerNameの名前でAppContainerを作成する。
必要なくなったらDeleteAppContainerProfileで削除すること。
AppContainerRegisterSidと違いsidはシステム側で適切に生成してくれる様子。
すでに存在する場合は失敗する、生成済みかを判別する方法は特にない様子。
pszAppContainerNameはAppContainerRegisterSidでいうmonikerだと思われる。
pCapabilitiesは指定しなくともUpdateProcThreadAttributeで指定してあれば有効になる、というかpCapabilitiesを指定して何が変わるのかわからなかった、通常のモダンアプリなどを起動する際に付与するCapabilitiesを算出するためのものだろうか?
こちらはAppContainerRegisterSidと違ってMSDNにも載っておりsidの適切な生成などがあるので、一般にはこちらを使ってほしいという意図なのだと思われる。

サンプル

  const wchar_t * const name = L"AppConteinerTest";
  PSID sid;
  if (FAILED(::CreateAppContainerProfile(name, name, name, NULL, 0, &sid))) {
    return;
  }

DeleteAppContainerProfile

MSDN参照

指定したAppContainerを削除する。
GetAppContainerFolderPathで取得できるフォルダの中身や、GetAppContainerRegistryLocationで取得できるレジストリも消える。

サンプル

  const wchar_t * const name = L"AppConteinerTest";
  PSID sid;
  if (FAILED(::CreateAppContainerProfile(name, name, name, NULL, 0, &sid))) {
    return;
  }
  ...
  ::DeleteAppContainerProfile(name);

DeriveAppContainerSidFromAppContainerName

MSDN参照

pszAppContainerNameからCreateAppContainerProfile互換のsidを取得する。
AppContainerDeriveSidFromMonikerとほぼ同一の動作をする。
存在しないAppContainerに対しても実行でき、その場合はCreateAppContainerProfileで生成した場合に割り当てられるsidが返る。
取得したsidは必要なくなったらFreeSidで解放すること。

  PSID sid;
  if (FAILED(::DeriveAppContainerSidFromAppContainerName(name, &sid))) {
    return;
  }
  ...
  ::FreeSid(sid);

GetAppContainerFolderPath

MSDN参照

sidからアプリケーションフォルダを取得する。
アプリケーションフォルダ内はAppContainer下でも自由に読み書きでき、DeleteAppContainerProfileを呼び出さなければアプリ終了後にも残る。
逆にDeleteAppContainerProfileを呼ぶと、プロセスが生きていようともその時点でファイルが削除され読み書き権限もなくなる。
取得したpathは必要なくなったらCoTaskMemFreeで解放すること。
ちなみに手元だと
C:\Users\#{ユーザー名}\AppData\Local\Packages\#{AppContainer名}\AC
に保存されていた。

サンプル

  const wchar_t * const name = L"AppConteinerTest";
  PSID sid;
  if (FAILED(::CreateAppContainerProfile(name, name, name, NULL, 0, &sid))) {
    return;
  }
  wchar_t *sidStr;
  if (::ConvertSidToStringSidW(sid, &sidStr) == FALSE) {
    ::FreeSid(sid);
    return;
  }
  wchar_t *appContainerPath;
  if (FAILED(::GetAppContainerFolderPath(sidStr, &appContainerPath))) {
    ::FreeSid(sid);
    ::LocalFree(sidStr);
    return;
  }
  ...
  ::FreeSid(sid);
  ::LocalFree(sidStr);
  ::CoTaskMemFree(appContainerPath);

GetAppContainerRegistryLocation

MSDN参照

AppContainer下でも読み書きできるレジストリキーを取得する。
AppContainer下で実行されているプロセスでしか使えず、それ以外のプロセスで実行しても失敗する。
GetAppContainerFolderPathと同様にDeleteAppContainerProfileを呼び出さなければアプリ終了後にも残る。
逆にDeleteAppContainerProfileを呼ぶと、プロセスが生きていようともその時点でレジストリは削除され読み書き権限もなくなり、APIも失敗するようになる。
MSDNには記載がないが、おそらくRegCloseKeyで閉じる必要がある。
ちなみに、手元の環境だと
HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Storage\#{AppContainer名}.#{乱数と思われるもの}
に保存されていた。

サンプル

  HKEY key;
  if (FAILED(::GetAppContainerRegistryLocation(KEY_ALL_ACCESS, &key))) {
    return;
  }
  ...
  ::RegCloseKey(key);

GetAppContainerNamedObjectPath

MSDN参照

AppContainer内プロセスで作成した各種ハンドルのリダイレクト先パス名を取得する。
外部のプロセスからこのパス名を付けてハンドルを作成することでAppContainer内部とやり取りすることができる。
手元の環境で試した範囲では「AppContainerNamedObjects\#{AppContainerのSID}」と帰ってくるようだった。
WinObjで具体的にどこにハンドルが属しているかを見ることができるので、興味があるなら見るのもよい。
詳しくはAppContainer関連サンプル紹介を参照。

サンプル

  PSID sid;
  if (FAILED(::DeriveAppContainerSidFromAppContainerName(name, &sid))) {
    return;
  }
  wchar_t buffer[4096];
  ULONG size;
  if (::GetAppContainerNamedObjectPath(NULL, sid, _countof(buffer), buffer, &size) == FALSE) {
    return;
  }
  ...
  ::FreeSid(sid);

非公開API

使用は推奨されないが、一応非公開APIについても調べたので書いておく。

AppContainerRegisterSid

HRESULT WINAPI AppContainerRegisterSid(PSID sid, LPCWSTR moniker, LPCWSTR display_name);

非公開API、GetProcAddressして使う。
sidをAppContainerとして登録する。
必要なくなったらAppContainerUnregisterSidで削除すること。
CreateAppContainerProfileで作成した場合と違いアプリケーションフォルダやレジストリへのアクセス権限は付与されないので注意。
sidやmonikerがすでに存在する場合は失敗する、すでに存在するか調べたい場合はAppContainerLookupMonikerを使用すること。
win8だとkernel32.dllにいるが8.1でkernelbase.dllに移った、おそらくVirtulDllで何か仮想dllに紐づいていてその実態が移動したとかだと思われる。
ぐぐるとわかるのだがchromiumのソースで使われていたことがある、というかほかに情報がない。

サンプル

  const wchar_t * const appContainerSid = L"S-1-15-2-1234567890-1234567890-1234567890-123456789-1234567890-123456789-1234567890";
  const wchar_t * const name = L"AppConteinerTest";
  typedef HRESULT(WINAPI* AppContainerRegisterSidPtr)(PSID sid, LPCWSTR moniker, LPCWSTR display_name);
  AppContainerRegisterSidPtr AppContainerRegisterSid = reinterpret_cast<AppContainerRegisterSidPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerRegisterSid"));
  if (AppContainerRegisterSid == NULL) {
    AppContainerRegisterSid = reinterpret_cast<AppContainerRegisterSidPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerRegisterSid"));
    if (AppContainerRegisterSid == NULL) {
      return false;
    }
  }
  PSID sid;
  if (::ConvertStringSidToSidW(appContainerSid, &sid) == FALSE) {
    return false;
  }
  if (FAILED(AppContainerRegisterSid(sid, name, name))) {
    return false;
  }

AppContainerUnregisterSid

HRESULT WINAPI AppContainerUnregisterSid(PSID sid);

非公開API、GetProcAddressして使う。
sidでAppContainerに登録されている情報を削除する。
AppContainerRegisterSidと同様にwin8だとkernel32.dllにいるが8.1でkernelbase.dllに移った。

サンプル

  typedef HRESULT(WINAPI* AppContainerUnregisterSidPtr)(PSID sid);
  AppContainerUnregisterSidPtr AppContainerUnregisterSid = reinterpret_cast<AppContainerUnregisterSidPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerUnregisterSid"));
  if (AppContainerUnregisterSid == NULL) {
    AppContainerUnregisterSid = reinterpret_cast<AppContainerUnregisterSidPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerUnregisterSid"));
    if (AppContainerUnregisterSid == NULL) {
      return;
    }
  }
  AppContainerUnregisterSid(sid);

AppContainerLookupMoniker

HRESULT WINAPI AppContainerLookupMoniker(PSID sid, LPWSTR* moniker);

非公開API、GetProcAddressして使う。
sidで登録されているAppContainerのmonikerを取得する。
sidに紐づくAppContainerが存在しない場合は失敗する。
monikerにはAPI内で動的に確保されたメモリーが割り当てられるため、必要なくなったらAppContainerFreeMemoryで解放すること。
AppContainerRegisterSidと同様にwin8だとkernel32.dllにいるが8.1でkernelbase.dllに移った。

サンプル

  typedef HRESULT(WINAPI* AppContainerLookupMonikerPtr)(PSID sid, LPWSTR* moniker);
  AppContainerLookupMonikerPtr AppContainerLookupMoniker = reinterpret_cast<AppContainerLookupMonikerPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerLookupMoniker"));
  if (AppContainerLookupMoniker == NULL) {
    AppContainerLookupMoniker = reinterpret_cast<AppContainerLookupMonikerPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerLookupMoniker"));
    if (AppContainerLookupMoniker == NULL) {
      return;
    }
  }
  typedef BOOLEAN(WINAPI* AppContainerFreeMemoryPtr)(void* ptr);
  AppContainerFreeMemoryPtr AppContainerFreeMemory = reinterpret_cast<AppContainerFreeMemoryPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerFreeMemory"));
  if (AppContainerFreeMemory == NULL) {
    AppContainerFreeMemory = reinterpret_cast<AppContainerFreeMemoryPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerFreeMemory"));
    if (AppContainerFreeMemory == NULL) {
      return;
    }
  }
  wchar_t *moniker;
  if (FAILED(AppContainerLookupMoniker(sid, &moniker))) {
    return;
  }
  ...
  AppContainerFreeMemory(moniker);

AppContainerDeriveSidFromMoniker

HRESULT WINAPI AppContainerDeriveSidFromMoniker(LPCWSTR moniker, PSID *sid);

非公開API、GetProcAddressして使う。
monikerからCreateAppContainerProfile互換のsidを取得する。
存在しないAppContainerに対しても実行でき、その場合はCreateAppContainerProfileで生成した場合に割り当てられるsidが返る。
どうやらmonikerをハッシュ関数に食わせるなどで生成しているようで、AppContainerRegisterSidで登録したmonikerを指定してもまるで違うsidが返ってくるので注意。
取得したsidは必要なくなったらAppContainerFreeMemoryで解放すること。
AppContainerRegisterSidと同様にwin8だとkernel32.dllにいるが8.1でkernelbase.dllに移った。

  typedef HRESULT(WINAPI* AppContainerDeriveSidFromMonikerPtr)(LPCWSTR moniker, PSID *sid);
  AppContainerDeriveSidFromMonikerPtr AppContainerDeriveSidFromMoniker = reinterpret_cast<AppContainerDeriveSidFromMonikerPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerDeriveSidFromMoniker"));
  if (AppContainerDeriveSidFromMoniker == NULL) {
    AppContainerDeriveSidFromMoniker = reinterpret_cast<AppContainerDeriveSidFromMonikerPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerDeriveSidFromMoniker"));
    if (AppContainerDeriveSidFromMoniker == NULL) {
      return;
    }
  }
  PSID sid;
  if (FAILED(AppContainerDeriveSidFromMoniker(moniker, &sid))) {
    return;
  }
  ...
  AppContainerFreeMemory(sid);

AppContainerFreeMemory

BOOLEAN WINAPI AppContainerFreeMemory(void* ptr);

非公開API、GetProcAddressして使う。
AppContainer関連APIで確保したメモリーを開放する。
AppContainerRegisterSidと同様にwin8だとkernel32.dllにいるが8.1でkernelbase.dllに移った。

サンプル

  typedef BOOLEAN(WINAPI* AppContainerFreeMemoryPtr)(void* ptr);
  AppContainerFreeMemoryPtr AppContainerFreeMemory = reinterpret_cast<AppContainerFreeMemoryPtr>(::GetProcAddress(::GetModuleHandleW(L"kernelbase.dll"), "AppContainerFreeMemory"));
  if (AppContainerFreeMemory == NULL) {
    AppContainerFreeMemory = reinterpret_cast<AppContainerFreeMemoryPtr>(::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "AppContainerFreeMemory"));
    if (AppContainerFreeMemory == NULL) {
      return;
    }
  }
  AppContainerFreeMemory(ptr);

AppContainerでデスクトップアプリを起動してみた

Windows8以降にAppContainerというsandbox機能が導入されているのをご存じだろうか?
このAppContainerは主にWindowsストアアプリの実行環境として使用されているのだが、最近巷を騒がせているIEの脆弱性もAppContainerを有効にすることで回避することができるらしいと聞く。
ということは、デスクトップアプリであってもAppContainerでセキュリティが向上できるかもしれない。
そこで今回はAppContainer上でデスクトップアプリを実行する方法を解説してみたいと思う。

本記事で紹介するコードやコンパイル済みのexeなどを用意しておいたので、実際に動かしてみたい方はどうぞ。
ただし、後始末やここでは解説しない機能の使用なども含んでいるため、本質的ではないコードが多いことをあらかじめ断っておく。
http://wordpress.click3.org/garakuta/StartAppContainer.zip

割り当てるCapabilityを取得する

AppContainer上で動作するアプリは既存のIntegrity Levelによる制限とは違い、Capabilityとして宣言しておくことで追加の権限を得ることができる。
そこでまずは動作させたいアプリに付与する権限を取得するところから始めよう。
なお、具体的にどのような権限があるかは解説しないので、詳しくはMSDNを参照してほしい。

実際にCapabilityを取得するコードは以下のようになる。

bool SetCapability(const WELL_KNOWN_SID_TYPE type, std::vector<SID_AND_ATTRIBUTES> &list, std::vector<SHARED_SID> &sidList) {
  SHARED_SID capabilitySid(new unsigned char[SECURITY_MAX_SID_SIZE]);
  DWORD sidListSize = SECURITY_MAX_SID_SIZE;
  if (::CreateWellKnownSid(type, NULL, capabilitySid.get(), &sidListSize) == FALSE) {
    return false;
  }
  if (::IsWellKnownSid(capabilitySid.get(), type) == FALSE) {
    return false;
  }
  SID_AND_ATTRIBUTES attr;
  attr.Sid = capabilitySid.get();
  attr.Attributes = SE_GROUP_ENABLED;
  list.push_back(attr);
  sidList.push_back(capabilitySid);
  return true;
}

前後にごちゃごちゃ長いものがついているが、本質的にはCreateWellKnownSidでSIDを取得し、SID_AND_ATTRIBUTES構造体に詰めて返している。
IsWellKnownSidはSIDがtypeと一致するかを調べるもので、間違いなく成功するので意味はないassertの一種だと思ってほしい。
その他はメモリーの開放といった後始末になる。

まずCreateWellKnownSidはシステム定義のSIDを取得するAPIでCapabilityのSIDもこれで取得できる。
取得するSIDの種類を表すWELL_KNOWN_SID_TYPEのうちWinCapabilityから始まるものがそれだ。

次にSID_AND_ATTRIBUTESだが、これは何のためのSIDなのかやどういう意図で使用するかなどを表す。
今回はCapabilityを追加したいのでAttributesにSE_GROUP_ENABLEDを設定したものを使う。

AppContainerのプロファイルを作成する

次はAppContainerのプロファイルを作成しよう。
プロファイルも識別子としてはSIDを用いるので、今回取得するのもSIDとなる。

  PSID sidImpl;
  DeleteAppContainer deleteAppContainer(AppContainerName);
  if (FAILED(::CreateAppContainerProfile(AppContainerName, AppContainerName, AppContainerName, (capabilities.empty() ? NULL : &capabilities.front()), capabilities.size(), &sidImpl))) {
    return false;
  }
  const SHARED_SID sid = ToSharedSID2(sidImpl);

CreateAppContainerProfileがプロファイルを作成するAPIで、その前後は後始末に関係する。
CreateAppContainerProfileのプロトタイプは以下のようになる。

HRESULT WINAPI CreateAppContainerProfile(
  _In_   PCWSTR pszAppContainerName,
  _In_   PCWSTR pszDisplayName,
  _In_   PCWSTR pszDescription,
  _In_   PSID_AND_ATTRIBUTES pCapabilities,
  _In_   DWORD dwCapabilityCount,
  _Out_  PSID *ppSidAppContainerSid
);

第一引数はプロファイルの識別子で、AppContainer固有のアプリケーションフォルダのパスなどで使用される。
第二第三は表示用なので割愛。
第四第五引数はCapabilityを指定するもののようだが、ここでは指定しなくとも問題なく権限は付加される。
おそらくはモダンアプリなどexplorerから起動出来るAppContainerのアプリの場合、ここで設定したCapabilityを用いて起動するのだと思われる。
コード中で指定してあるのはあくまで念のためだ。
第六引数は生成したプロファイルのSIDを受けるポインターとなる。

作成したプロファイルでアプリを起動する

さて、プロファイルも無事生成出来たのでいよいよアプリを実行しよう。
少々長いが、アプリの起動は以下のようになる。

  const unsigned int attrCount = 3;
  SIZE_T size;
  ::InitializeProcThreadAttributeList(NULL, attrCount, 0, &size);
  const SHARED_ATTR_LIST attrList = ToSharedAttributeList(reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(new unsigned char[size]));
  if (::InitializeProcThreadAttributeList(attrList.get(), attrCount, 0, &size) == FALSE) {
    return false;
  }
  SECURITY_CAPABILITIES sc;
  sc.AppContainerSid = sid.get();
  sc.Capabilities = (capabilities.empty() ? NULL : &capabilities.front());
  sc.CapabilityCount = capabilities.size();
  sc.Reserved = 0;
  if (::UpdateProcThreadAttribute(attrList.get(), 0, PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, &sc, sizeof(sc), NULL, NULL) == FALSE) {
    return false;
  }
  STARTUPINFOEXW startupInfoEx = { 0 };
  startupInfoEx.StartupInfo.cb = sizeof(STARTUPINFOEXW); // StartupInfoのサイズではなくstartupInfoExのサイズである点に注意
  startupInfoEx.lpAttributeList = attrList.get();
  PROCESS_INFORMATION pi = { 0 };
  if (::CreateProcessW(path.wstring().c_str(), NULL, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, reinterpret_cast<LPSTARTUPINFO>(&startupInfoEx), &pi) == FALSE) {
    std::wcout << GetLastError() << std::endl;
    return false;
  }
  ::WaitForSingleObject(pi.hProcess, INFINITE);
  ::CloseHandle(pi.hThread);
  ::CloseHandle(pi.hProcess);

InitializeProcThreadAttributeListでLPPROC_THREAD_ATTRIBUTE_LISTを初期化し、UpdateProcThreadAttributeでプロファイルのsidとCapabilityを設定してCreateProcessWでアプリを起動している。
その他諸々はほかと同じく後始末になる。

InitializeProcThreadAttributeListは見たまま単に初期化を行っているだけだ。
設定する属性の数は念のため3にしてあるが、おそらく1でも動作する。
UpdateProcThreadAttributeもみたままにLPPROC_THREAD_ATTRIBUTE_LISTにsidを設定しているだけだ。
Capabilityはここで指定してあればプロファイルに指定がなくともちゃんと割り振られて動作する。
CreateProcessWはEXTENDED_STARTUPINFO_PRESENTを指定してやるとSTARTUPINFOでLPPROC_THREAD_ATTRIBUTE_LISTを受けてくれるようになる。
あとは単にSTARTUPINFOEXWにLPPROC_THREAD_ATTRIBUTE_LISTを設定して起動しているだけだ。

実際に起動してみる

http://wordpress.click3.org/garakuta/StartAppContainer.zipに完成品があるのでDLしてもらって、中のTestAppContainer.exeを普通にダブルクリックで起動してみてほしい。
どうだろうか?おそらくは「このアプリケーションはアプリ コンテナーのコンテキストでのみ実行できます。」と表示されて起動しなかったと思う。
これは/APPCONTAINERオプションを指定してコンパイルされたものでAppContainer以外では動作しない。
そこで次はTestAppContainer2.exeを起動してみてほしい。
こちらは単に/APPCONTAINERオプションを外しただけのものになる。
動作としてはいろいろなフォルダにアクセスして中身を列挙するというものだ。

では完成品であるStartAppContainer.exeにTestAppContainer.exeをドラッグ&ドロップしてみてほしい。
今度はちゃんと起動したと思う。
また動作もTestAppContainer2.exeを起動したときと違い、見えないフォルダがあったり、そもそも別フォルダを指していたりしたと思う。
それがAppContainerによるサンドボックス化された環境ということになる。

また、付与したCapabilityによる差が見たい人はTestWinRT.exeを試してみてほしい。
こちらはWinRTのAPIを用いている以外はTestAppContainer.exeと同様だが、AppContainerとして起動するとミュージックフォルダが取得できるようになっているはずだ。
これはCapabilityでMusicLibraryを付与したためにアクセス可能になったことを表している。
TestAppContainer.exeで見えていないのはCapabilityがあくまでWinRTのAPIにしか影響しないためだ。

その他cmd.exeをAppContainerで起動してみるなどもできるのでいろいろ試してみてほしい。

終わりに

どうだっただろうか。
今回はあくまでAppContainerで起動するところまでを目標としたが、zipの中では紹介していないAPIを使用するコードなども入れてあり、また次の記事では関連APIについてもまとめているので、興味があれば見ていってほしい。

最初にも書いたがIEもAppContainerにより脆弱性が見つかってもできるだけ他所に波及しないようにと作られている。
この記事を読んでそういうことに興味を持ってくれる人が一人でも増えたら幸いだ。

ではまた何か機会があればその時に。