转载自:http://www.shily.net/topic/bypassing-uac-since-the-launch-on-vista-win7/#more-136
在开启了UAC的Vista/Win7/windows server 2008 等系统上,如果程序使用manifest进行了提权(requireAdministrator),程序在启动的时候系统会弹出一个全局的对话框,提示用户是否允许启动并并赋予管理员权限,点击确定后才能启动动程序。
但是如果是在程序自启动的情况下,就不一样了,UAC会直接拦截掉软件自启动,当然也不会弹那个全局对话框,难道需要管理员权限的软件就不能自启动了?
答案肯定是否定的,下面具体分析一下。
我们知道,不要管理员权限的程序是可以自启动的,manifest里面是像这样的:
<requestedPrivileges>;
<requestedExecutionLevel level="asInvoker" uiAccess="false" />;
</requestedPrivileges>
然而开机后我们是可以手动右键点击程序“以管理员身份运行”的,麻烦的就是需要手工点击一下,程序猿不就是喜欢自动化嘛,我们可以用程序来实现这个手动点击的过程。
大致过程和方法如下:
程序编译不使用manifest进行提权,然后使用注册表或者把快捷方式复制到启动目录等方法实现自启动,启动后的程序是没有管理员权限的,需要模拟用户以管理员权限启动的过程,可以使用ShellExecuteEx来实现,然后没有管理员权限的进程退出,以管理员权限启动的程序执行程序自身的其他逻辑。
实现代码如下:
const TCHAR *szUAC_EventNamt = _T("Global\\{AFC38980-3658-4694-34DC-6A0437995ACD}");//
//
// 注意:
// *** 在有单实例要求的程序中,此函数必须在检测多进程之前调用,否则无法重启子进程! ***
//
//返回 TRUE 则继续运行
// FALSE 则退出进程
//
BOOL ElevateOnUAC(void)
{
BOOL bRet;
TCHAR szSelfPath[MAX_PATH];
TCHAR *lpszCommandLine;
HANDLE hUAC_Event;
DWORD dwLastErr;
SHELLEXECUTEINFO sei = {0};
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
::SetLastError(ERROR_SUCCESS);
hUAC_Event = ::CreateEvent(NULL, TRUE, FALSE, szUAC_EventNamt);//创建一个事件
dwLastErr = ::GetLastError();
if(hUAC_Event == NULL)//失败
{
_stprintf_s(szSelfPath, _countof(szSelfPath), _T("CreateEvent return NULL ! 真失败...\r\nLastError = %d"), hUAC_Event, dwLastErr);
::MessageBox(NULL, szSelfPath, _T("Bug!"), MB_ICONERROR);
return FALSE;//创建个事件都失败, 退出程序
}
if(ERROR_ALREADY_EXISTS != dwLastErr)//避免多次创建进程, 进入死循环
{
if(!IsUserAnAdmin() && IsEnableUAC())
{
::GetModuleFileName(NULL, szSelfPath, MAX_PATH);
//这里是为了把参数传给被提权的进程
lpszCommandLine = ::GetCommandLine();// may be like ["c:\p f\a.exe" -a b /c] or [""]
if(lpszCommandLine[0] == L'\"')
{
do
{
lpszCommandLine++;
}while(lpszCommandLine[0] != L'\"');
lpszCommandLine++;//pass the last quotes
if(lpszCommandLine[0] == L' ')//如果有参数,那么这里会有个空格
{
lpszCommandLine++;
}
}
else
{
while(lpszCommandLine[0] != L' ')// space
{
lpszCommandLine++;
}
lpszCommandLine++;//pass the last space
}
sei.cbSize = sizeof(SHELLEXECUTEINFO);
sei.nShow = SW_SHOW;
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
sei.lpFile = szSelfPath;
sei.lpVerb = _T("runas");
sei.lpParameters = lpszCommandLine;
sei.lpDirectory = NULL;
bRet = ::ShellExecuteEx(&sei);
if(bRet)
{
//::CloseHandle(sei.hProcess);
}
else
{
//启动失败, 可能是用户点击了取消, 设置事件让本程序退出
::SetEvent(hUAC_Event);
}
bRet = FALSE;//返回FALSE, 退出程序
}
else
bRet = TRUE;//已经获得管理员权限 或者 没开启UAC 继续运行
}
else//已经存在这个事件, 表示是子进程
{
bRet = TRUE;//子进程执行这句
}
//如果是第一个进程, 这里要等待第二个进程执行这段代码后才会退出
//第二进程会直接设置event为有信号
if(bRet)
{
::SetEvent(hUAC_Event);
}
if(sei.hProcess)
{
//等待两个事件中的任意一个, 第二个进程设置事件,或者由于异常退出进程
//如果第二个进程既没有退出,也没设置事件,那就等待超时(30秒)
HANDLE hEvent[2] = {sei.hProcess, hUAC_Event};
::WaitForMultipleObjects(_countof(hEvent), hEvent, FALSE, 1000*30);
::CloseHandle(sei.hProcess);
sei.hProcess = NULL;
}
//关掉句柄
//::WaitForSingleObject(hUAC_Event, INFINITE);
::CloseHandle(hUAC_Event);//获得权限的进程关闭句柄
hUAC_Event = NULL;
return bRet;
}
BOOL IsEnableUAC(void)
{
BOOL bEnableUAC = FALSE;
OSVERSIONINFO ovi = {0};
ovi.dwOSVersionInfoSize = sizeof(ovi);
if (::GetVersionEx(&ovi))
{
// window vista or windows server 2008 or later operating system
if ( ovi.dwMajorVersion > 5 )
{
HKEY hKey = NULL;
DWORD dwType = REG_DWORD;
DWORD dwEnableLUA = 0;
DWORD dwSize = sizeof(DWORD);
LSTATUS lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,
_T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\"),
0, KEY_READ, &hKey);
if( ERROR_SUCCESS == lRet )
{
lRet = ::RegQueryValueEx(hKey, _T("EnableLUA"), NULL, &dwType, (BYTE*)&dwEnableLUA, &dwSize);
::RegCloseKey(hKey);
if( ERROR_SUCCESS == lRet )
{
bEnableUAC = (dwEnableLUA) ? TRUE : FALSE;
}
}
}
}
return bEnableUAC;
}
使用方法比较简单,只需要在程序的入口处调用ElevateOnUAC()函数就OK了,返回假就退出进程,返回真就继续执行。
我在这里用了一个Event来控制了一下,避免重复启动进程的错误,如果要使用这段代码,这个Event名字要自己改一下哦···