在MinGW或VS 2005 SP1环境下创建一个要求UAC权限的程序。
之前在给U盘挂自制操作系统的那篇教程里,我给写了一个pdev程序用来输出windows下,系统各磁盘的主引导记录。
不过那个程序用起来每次都要记得用管理员权限启动程序,这多少增加了不便。(特别是对带UAC功能的win7\vista来说)。
如果能让程序启动时自动要求提权,那当然更符合windows程序的一般习惯。
我查到了一篇这种做法的教程:
http://www.zu14.cn/2010/05/14/delphi-win32-program-on-windows7-vista-uac-administrator-rights/
也就是需要用到以下这个xml文件(保存成uac.manifest文件,不过还可以保存成任意的.manifest文件也行。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
下面具体讲讲在MinGW或者VS2005 SP1的情况下该怎么做。
VS2005下简单,只需要在项目的资源里加这个uac.manifest文件即可。
我在VS2005的“添加资源”一项里没有找到“添加程序清单”,索性我选择了添加xml文件,并保存为uac.manifest,把以上内容复制到文件中,
再编译,就生成了启动时会提示UAC提权的提示框。(这种要求UAC提权的程序不能直接用F5调试运行,在VC里如果要直接看效果可以按Ctrl + F5 直接运行)。
在MinGW下,这个问题要稍复杂那么一点点。
需要在.rc资源文件里添加一个
#include <winuser.h>
1 RT_MANIFEST uac.manifest
这一段。添加#include <winuser.h>文件包含是因为在winuser.h里有对RT_MANIFEST声明,
#define RT_MANIFEST 24
把这段资源脚本文件保存为uac.rc,放到与前面说的uac.manifest相同的目录下,最后需要用MinGW工具中的windres程序将它处理成.res资源文件。
命令如下:
windres --input-format=rc -O coff -i uac.rc -o uac.res
这样就产生了uac.res资源文件,再接下来用gcc将它和程序源文件一起编译就可以了。于是问题解决!
当然如果嫌这样的.rc文件长了,或者不愿意include一个winuser.h文件,可以写成更加简短的版本:
1 24 uac.manifest
效果一样。(在VC中也可以做同样处理,如果你想在资源里添加对程序清单的描述的话。)
写这篇文章参考了以下几个网址,如果需要深入这个问题可以作为参考:
http://www.cnblogs.com/dflying/archive/2007/03/21/683190.html
(Windows Vista for Developers——第四部分:用户帐号控制(User Account Control,UAC))
这篇文章需要详细关注“使用应用程序清单”这一部分。
http://msdn.microsoft.com/en-us/library/aa381043%28v=VS.85%29.aspx
http://msdn.microsoft.com/fr-fr/library/bb773175%28en-us,VS.85%29.aspx
上面2篇MSDN文章中关于RT_MANIFEST的部分值得注意。
总之原理就是,PE文件的资源中有一种类型是RT_MANIFEST,它的内容实际上是一个xml文件,被称为程序清单文件。
换句话说,这个xml格式的.manifest文件文件会被打包进可执行程序文件中。
而程序清单文件中描述了运行程序需要的权限,因此借助这个机制,有UAC管理功能的windows系统会在启动程序时向用户要求提权。
下面把我的pdev程序贴一下,作为一个要求UAC提权的程序的有趣的例子。它可以判断系统中有哪些磁盘,并打印出磁盘的主引导记录。
(注意,这类程序即使在命令行打开,windows仍然会打开一个新的命令行窗口运行程序,并且程序路径是一个完整路径,
因此这时候我们的命令行程序不得不总是在将要推出前用系统的pause命令做暂停,否则用户看到的会是一个一闪而过的窗口。
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void ShowError(){
void *pError;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM|
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,GetLastError(),
MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
(LPTSTR)&pError,0,NULL);
MessageBox(0,(PTSTR)pError,0,0);
LocalFree(pError);
}
void ReadDevice(HANDLE hDeviceHandle,int DriveNumber,FILE *output_file){
BYTE buffer[512];
DWORD nBytes;
int iCount;
if(hDeviceHandle != INVALID_HANDLE_VALUE){
printf("#正在尝试读取物理驱动器%d ...",DriveNumber);
ReadFile(hDeviceHandle,buffer,sizeof buffer,&nBytes,NULL);
}else{
if(GetLastError() == ERROR_FILE_NOT_FOUND && DriveNumber > 0){
printf("#以上%d项就是系统中现有的全部物理驱动器。",DriveNumber);
system("pause");
exit(0);
}
perror("读取设备时发生错误");
puts("请检查您是否正以管理员账户运行此程序,");
printf("或许物理驱动器%d不存在.\n",DriveNumber);
ShowError();
system("pause");
exit(-1);
}
if(nBytes > 0){
printf("\n#读取了%d字节的数据:\n",nBytes);
for(iCount = 0; iCount < sizeof buffer; ++iCount){
if(iCount % 16 == 0){
printf("\n %04X\t|",iCount);
}
printf(" %02X",buffer[iCount]);
}
printf("\n ----\t ");
for(iCount = 0; iCount < 16; ++iCount){
printf(" --");
}
printf("\n\n");
if(output_file != NULL){
fwrite(buffer,sizeof buffer,1,output_file);
printf("已将以上%d字节的数据写入文件。\n",sizeof buffer);
}
}else{
perror("失败。");
ShowError();
system("pause");
exit(-1);
}
}
int main(int argc,char *argv[]){
HANDLE hDeviceHandle = NULL;
int iCount;
FILE *output_file = NULL;
TCHAR DriveNameBuffer[MAX_PATH] = TEXT("");
PTSTR DriveNamePrefix = TEXT("\\\\.\\PHYSICALDRIVE");
if(argc > 1){//带命令行参数,说明用户在命令行里使用本程序
if(argc == 3 && !strcmp("-o",argv[1])){
printf("#以512字节为单位连续输出到文件%s...\n",argv[2]);
output_file = fopen(argv[2],"wb");
if(output_file == NULL){
perror("无法打开指定的文件");
system("pause");
exit(-1);
}
}else{
printf("Usage:\n"
"%s -o [filename ...]"
"\n将系统中所有的物理驱动器的主引导记录依次写入文件。\n",
argv[0]);
printf("\n不合法的命令行参数,将退出程序。");
system("pause");
exit(0);
}
}else{//不带命令行参数,很可能用户是直接双击程序运行的。
output_file = NULL;
}
for(iCount = 0; iCount < 256; ++iCount){
wsprintf(DriveNameBuffer,TEXT("%s%d"),DriveNamePrefix,iCount);
hDeviceHandle = CreateFile(
DriveNameBuffer,
GENERIC_READ,FILE_SHARE_READ,
NULL,OPEN_EXISTING,0,0);
ReadDevice(hDeviceHandle,iCount,output_file);
CloseHandle(hDeviceHandle);
}
return 0;
}
编译之前我们先用之前的uac.manifest文件和uac.rc文件准备好我们的uac.res资源文件:
windres --input-format=rc -O coff -i uac.rc -o uac.res
之后将uac.res文件和程序一起打包:
gcc -o pdev pdev.c uac.res
这下大功告成!