Uncategorized

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 , 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);}

 

 

Categories: Uncategorized

Leave a Reply