Welcome to MSDN Blogs Sign in | Join | Help

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

環境 :
Windows Vista / Windows 7

こんにちは。

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

日経ソフトウェア 2009 年 09 月号 「Windows 7 プログラミング 第2回 - UAC 徹底解説」
http://itpro.nikkeibp.co.jp/article/MAG/20090721/334111/
(Windows Azure も特集されていますね !)

UAC については過去に Tech Fielders コラム でも解説をおこなっていますが、上記では 「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);
}

 

 

Published Friday, July 24, 2009 2:06 PM by tsmatsuz

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

No Comments

Leave a Comment

(required) 
required 
(required) 

  
Enter Code Here: Required
 
Page view tracker