UAC 徹底解説 ! (その中身を完全理解)

環境 :
Windows Vista / Windows 7

こんにちは。

日経ソフトウェアの 9 月号がリリースされましたが、この中で、以前も予告した通り、UAC (ユーザーアカウント制御) を完全解説しました。

日経ソフトウェア 2009 年 09 月号 「Windows 7 プログラミング 第2回 – UAC 徹底解説」

http://itpro.nikkeibp.co.jp/article/MAG/20090721/334111/

UAC については過去に こちら でも解説をおこなっていますが、上記では 「UAC における昇格のメカニズム (= 開発者は、コードでこれを乗り越えられるのか !?) 」、「ファイル / レジストリー仮想化の落とし穴」など、書ききれなかった「完全解説版」的な位置づけとして記載しました。(かつ、退屈しない分量で . . .)

実は上記は、「Windows 7 プログラミングの連載記事で、な~んで UAC なのさ !?」と思われる方も居られるかもしれませんが、次回は、第 1 回で記載した ” 7 らしい特徴” と、この第 2 回の内容を前提に、一般的なプログラミングの流れを書いていきますので、是非ご期待ください。

さて、「退屈せずに読める」ようにしたかったので長いコード説明などは入れなかったのですが、上記で解説している 「昇格まがいな処理」 について、以下に少し補足しておきましょう。

例えば、上記で説明している昇格の仕組みを応用して、下記のようなコードを書いたと仮定しましょう。
下記の処理は、Windows XP や、UAC を「オフ」にした Windows Vista / Windows 7 などでは、ちゃんと動作します。(すみません、XP では試してませんが、その「ハズ」です。) つまり、標準ユーザーでログインしていても、普通に、管理者のプロセスを起動できます !
と、その前に、こうした危険な処理が実行されないよう、ローカルポリシーが設定されているはずですので、動作確認の前に、OS の以下の設定をおこなっておいてください。(UAC が昇格をおこなう際は System が実行しているため、無論こうした設定がなくても動いています。。。)

事前設定

  1. mmc を起動します (コマンドプロンプトで、「mmc」 でも OK)。
  2. [ローカルポリシーオブジェクト] のスナップインを追加します。
  3. [コンピューターの構成] – [Windows の設定] – [セキュリティの設定] – [ローカルポリシー] – [ユーザー権利の割り当て] の [認証後にクライアントを偽装], [プロセスレベルトークンの置き換え], [プロセスのメモリクォータの増加] に、下記のコードを実行するユーザーを追加しておきます。
    なお、Windows 7 の場合は、[トークンオブジェクトの作成] にもこのユーザーを追加しておきましょう。(既定ではここには空になっています。)
  4. ログインをしなおしてください。

コードの実行 

Windows Vista / Windows 7 では、UAC をオフにして下記を実行すると、標準ユーザーであっても管理者ユーザーのプロセス (メモ帳) がスイスイとあがってきます。

(※注意 : 下記のコードでは、読みやすさのためにエラー処理などはすべて省略していますので注意してください . . .)

int _tmain(int argc, _TCHAR* argv[])
{
  PROCESS_INFORMATION pi = {0}; 
  STARTUPINFO si;

  HANDLE hUser=NULL;
  HANDLE hToken=NULL;

  _tsetlocale(LC_ALL, _T(""));

  ZeroMemory(&si,sizeof(si));
  si.cb=sizeof(si);
  si.lpDesktop = L"winsta0\default";

  // ユーザーID とパスワードを取得
  wchar_t userid[255], password[255];
  wprintf_s(L"何に化けますか?  アカウント:");
  wscanf_s(L"%ls", userid, 254);
  fflush(stdin);
  wprintf_s(L"パスワード:");
  wscanf_s(L"%ls", password, 254);
  fflush(stdin);

  // 別ユーザーでのログイン(トークン取得)
  LogonUser(userid, NULL, password,
    LOGON32_LOGON_INTERACTIVE,
    LOGON32_PROVIDER_DEFAULT, &hUser);

  // このプロセスに、偽装特権を有効化
  HANDLE hProcSelf = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
    GetCurrentProcessId());
  HANDLE hTokenSelf;
  OpenProcessToken(hProcSelf, TOKEN_ADJUST_PRIVILEGES,
    &hTokenSelf);
  LUID luid;
  LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid);
  TOKEN_PRIVILEGES tp;
  tp.PrivilegeCount = 1;
  tp.Privileges[0].Luid = luid;
  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  AdjustTokenPrivileges(hTokenSelf, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);

  // ウィンドウステーションのオープン
  HWINSTA hwinstaSave = NULL;
  HWINSTA hwinsta = NULL;
  HDESK hdesk = NULL;
  PSID pSid = NULL;
  hwinstaSave = GetProcessWindowStation();
  hwinsta = OpenWindowStation(L"winsta0",
    FALSE, READ_CONTROL | WRITE_DAC);
  hdesk = OpenDesktop(
    L"default", // interactive window station 
    0, // no interaction with other desktop processes
    FALSE, // handle is not inheritable
    READ_CONTROL |
    WRITE_DAC | 
    DESKTOP_WRITEOBJECTS | 
    DESKTOP_READOBJECTS);
  SetProcessWindowStation(hwinsta);

  // ウィンドウステーション/ デスクトップへの DACL 設定
  GetLogonSID(hUser, &pSid); // <- 下記参照
  AddAceToWindowStation(hwinsta, pSid); // <- 下記参照
  AddAceToDesktop(hdesk, pSid); // <- 下記参照

  // 偽装実行
  ImpersonateLoggedOnUser(hUser);

  // CreateProcessAsUser !! (メモ帳を起動)
  CreateProcessAsUser(hUser, L"C:\Windows\System32\notepad.exe", L"",
    NULL, NULL,FALSE, NORMAL_PRIORITY_CLASS | 
    CREATE_NEW_CONSOLE /*DETACHED_PROCESS*/, NULL, NULL, &si, &pi);

  // 偽装を戻す
  RevertToSelf();

END:
  if (hwinstaSave)
    SetProcessWindowStation(hwinstaSave);
  if(pi.hProcess)
    CloseHandle(pi.hProcess);
  if(hUser!=NULL)
    CloseHandle(hUser);
  if(hTokenSelf)
    CloseHandle(hTokenSelf);
  if(hwinsta)
    CloseWindowStation(hwinsta);
  if(hdesk)
    CloseDesktop(hdesk);

  return 0;
}

ところが、UAC をオンにすると、記事の中でも記載しているように、「ある箇所 ?」 で立派にエラー (error) となります。(記事を読まれた方はどこかわかりますよね ? . . . 内緒です)
また、上記のコードで、GetLogonSID, AddAceToWindowStation, AddAceToDesktop は、以下の関数になります。(Windows API ではありません。MSDN などにも載っています . . .)

なお、ハッカー養成のために書いているのではありません。記事の中でも記載しているように、今後の Windows プログラミングの 「前提」 となる知識ですので、是非ポイントと考え方をおさえておいてください。

///////////////
// 下記関数は、MSDN から抜粋 (エラー処理などは省略してます)
///////////////

void GetLogonSID(HANDLE hToken, PSID *ppsid)
{
  DWORD dwIndex;
  DWORD dwLength;
  PTOKEN_GROUPS ptg;

   // トークン情報を取得
  // (サイズ取得-> 割り当て-> 情報取得)
  GetTokenInformation(hToken, TokenGroups, NULL, 0, &dwLength);
  ptg = (PTOKEN_GROUPS) HeapAlloc(GetProcessHeap(), 0, dwLength);
  GetTokenInformation(hToken, TokenGroups, (LPVOID) ptg, dwLength, &dwLength);

  // SE_GROUP_LOGON_ID を取得
  for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++)
  {
    if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID)
      ==  SE_GROUP_LOGON_ID)
    {
      dwLength = GetLengthSid(ptg->Groups[dwIndex].Sid);
      *ppsid = (PSID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength);
      CopySid(dwLength, *ppsid, ptg->Groups[dwIndex].Sid);
      break;
    }
  }

END:
  if (ptg != NULL)
    HeapFree(GetProcessHeap(), 0, (LPVOID)ptg);
}
#define GENERIC_ACCESS (GENERIC_READ | GENERIC_WRITE | 
GENERIC_EXECUTE | GENERIC_ALL)

#define WINSTA_ALL (WINSTA_ENUMDESKTOPS | WINSTA_READATTRIBUTES | 
WINSTA_ACCESSCLIPBOARD | WINSTA_CREATEDESKTOP | 
WINSTA_WRITEATTRIBUTES | WINSTA_ACCESSGLOBALATOMS | 
WINSTA_EXITWINDOWS | WINSTA_ENUMERATE | WINSTA_READSCREEN | 
STANDARD_RIGHTS_REQUIRED)

void AddAceToWindowStation(HWINSTA hwinsta, PSID psid)
{
  SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION;

  // ウィンドウステーションのSecurity Descriptor を取得
  // (サイズ取得-> 割り当て-> 情報取得)
  PSECURITY_DESCRIPTOR psd;
  DWORD dwSdSize;
  GetUserObjectSecurity(hwinsta, &si, NULL, 0, &dwSdSize);
  psd = (PSECURITY_DESCRIPTOR) HeapAlloc(
    GetProcessHeap(),
    HEAP_ZERO_MEMORY,
    dwSdSize);
  GetUserObjectSecurity(hwinsta, &si, psd, dwSdSize, &dwSdSize);

  // 新規にSecurity Descriptor を作成
  PSECURITY_DESCRIPTOR psdNew;
  psdNew = (PSECURITY_DESCRIPTOR)HeapAlloc(
    GetProcessHeap(),
    HEAP_ZERO_MEMORY,
    dwSdSize);
  InitializeSecurityDescriptor(psdNew, SECURITY_DESCRIPTOR_REVISION);

  // 既存のSecurity Descriptor からACL を取得
  PACL pacl;
  BOOL bDaclPresent, bDaclExist;
  GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl, &bDaclExist);

  // 必要なACL サイズ情報の取得
  DWORD dwNewAclSize;
  ACL_SIZE_INFORMATION aclSizeInfo;
  GetAclInformation(pacl, (LPVOID)&aclSizeInfo,
    sizeof(ACL_SIZE_INFORMATION), AclSizeInformation);
  dwNewAclSize = aclSizeInfo.AclBytesInUse +
    (2*sizeof(ACCESS_ALLOWED_ACE)) + (2*GetLengthSid(psid)) -
    (2*sizeof(DWORD));

  // 新規にACL を作成(初期化)
  PACL pNewAcl;
  pNewAcl = (PACL)HeapAlloc(
    GetProcessHeap(),
    HEAP_ZERO_MEMORY,
    dwNewAclSize);
  InitializeAcl(pNewAcl, dwNewAclSize, ACL_REVISION);

  for (unsigned int i=0; i < aclSizeInfo.AceCount; i++)
  {
    // i 番目のACE を取得
    PVOID pTempAce;
    GetAce(pacl, i, &pTempAce);

    // ACL に、既存のACE を設定
    AddAce(pNewAcl, ACL_REVISION, MAXDWORD, pTempAce,
      ((PACE_HEADER)pTempAce)->AceSize);
  }

    // 新しい権限のACE を追加(1 つ目)
  ACCESS_ALLOWED_ACE *pace;
  pace = (ACCESS_ALLOWED_ACE *)HeapAlloc(
    GetProcessHeap(),
    HEAP_ZERO_MEMORY,
    sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psid)
    - sizeof(DWORD));
  pace->Header.AceType = ACCESS_ALLOWED_ACE_TYPE;
  pace->Header.AceFlags = CONTAINER_INHERIT_ACE
    | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE;
  pace->Header.AceSize = sizeof(ACCESS_ALLOWED_ACE)
    + GetLengthSid(psid) - sizeof(DWORD);
  pace->Mask = GENERIC_ACCESS;
  CopySid(GetLengthSid(psid), &pace->SidStart, psid);
  AddAce(pNewAcl, ACL_REVISION, MAXDWORD, (LPVOID)pace,
    pace->Header.AceSize);

  // 新しい権限のACE を追加(2 つ目)
  pace->Header.AceFlags = NO_PROPAGATE_INHERIT_ACE;
  pace->Mask = WINSTA_ALL;
  AddAce(pNewAcl, ACL_REVISION, MAXDWORD, (LPVOID)pace,
            pace->Header.AceSize);

  // ウィンドウステーションにSecurity Descriptor とACL を設定
  SetSecurityDescriptorDacl(psdNew, TRUE, pNewAcl, FALSE);
  SetUserObjectSecurity(hwinsta, &si, psdNew);

END:
      if (pace != NULL)
         HeapFree(GetProcessHeap(), 0, (LPVOID)pace);
      if (pNewAcl != NULL)
         HeapFree(GetProcessHeap(), 0, (LPVOID)pNewAcl);
      if (psd != NULL)
         HeapFree(GetProcessHeap(), 0, (LPVOID)psd);
      if (psdNew != NULL)
         HeapFree(GetProcessHeap(), 0, (LPVOID)psdNew);
}
#define DESKTOP_ALL (DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | 
DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD | 
DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | 
DESKTOP_SWITCHDESKTOP | STANDARD_RIGHTS_REQUIRED)

void AddAceToDesktop(HDESK hdesk, PSID psid)
{
  SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION;

  // デスクトップのSecurity Descriptor を取得
  // (サイズ取得-> 割り当て-> 情報取得)
  PSECURITY_DESCRIPTOR psd;
  DWORD dwSdSize;
  GetUserObjectSecurity(hdesk, &si, NULL, 0, &dwSdSize);
  psd = (PSECURITY_DESCRIPTOR) HeapAlloc(
    GetProcessHeap(),
    HEAP_ZERO_MEMORY,
    dwSdSize);
  GetUserObjectSecurity(hdesk, &si, psd, dwSdSize, &dwSdSize);

  // 新規にSecurity Descriptor を作成
  PSECURITY_DESCRIPTOR psdNew;
  psdNew = (PSECURITY_DESCRIPTOR)HeapAlloc(
    GetProcessHeap(),
    HEAP_ZERO_MEMORY,
    dwSdSize);
  InitializeSecurityDescriptor(psdNew, SECURITY_DESCRIPTOR_REVISION);

  // 既存のSecurity Descriptor からACL を取得
  PACL pacl;
  BOOL bDaclPresent, bDaclExist;
  GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl, &bDaclExist);

  // 必要なACL サイズ情報の取得
  DWORD dwNewAclSize;
  ACL_SIZE_INFORMATION aclSizeInfo;
  GetAclInformation(pacl, (LPVOID)&aclSizeInfo,
    sizeof(ACL_SIZE_INFORMATION), AclSizeInformation);
  dwNewAclSize = aclSizeInfo.AclBytesInUse +
    sizeof(ACCESS_ALLOWED_ACE) +
    GetLengthSid(psid) - sizeof(DWORD);

  // 新規にACL を作成(初期化)
  PACL pNewAcl;
  pNewAcl = (PACL)HeapAlloc(
    GetProcessHeap(),
    HEAP_ZERO_MEMORY,
    dwNewAclSize);
  InitializeAcl(pNewAcl, dwNewAclSize, ACL_REVISION);

  for (unsigned int i=0; i < aclSizeInfo.AceCount; i++)
  {
    // i 番目のACE を取得
    PVOID pTempAce;
    GetAce(pacl, i, &pTempAce);

    // ACL に、既存のACE を設定
    AddAce(pNewAcl, ACL_REVISION, MAXDWORD, pTempAce,
      ((PACE_HEADER)pTempAce)->AceSize);
  }

    // 新しい権限のACE を追加
  AddAccessAllowedAce(pNewAcl, ACL_REVISION, DESKTOP_ALL, psid);

  // デスクトップにSecurity Descriptor とACL を設定
  SetSecurityDescriptorDacl(psdNew, TRUE, pNewAcl, FALSE);
  SetUserObjectSecurity(hdesk, &si, psdNew);

END:
  if(pNewAcl)
    HeapFree(GetProcessHeap(), 0, (LPVOID)pNewAcl);
  if(psd)
    HeapFree(GetProcessHeap(), 0, (LPVOID)psd);
  if(psdNew)
    HeapFree(GetProcessHeap(), 0, (LPVOID)psdNew);
}

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s