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