首页  编辑  

杀掉一个拒绝访问的进程

Tags: /超级猛料/OS.操作系统/Process.进程/   Date Created:
http://www.microsoft.com/china/msdn/technic/faq/win32faq.asp
Jeffrey Richter
:我正在开发一个基于Windows NT的服务程序。我的程序同任何程序一样存在一些小的毛病。当我的服务程序出错以后,我想杀掉这个服务程序将它重新启动。我经常使用任务管理器来杀掉我不再想使用的进程。但是,如果我想杀掉一个服务程序的话,显示在我面前的就是一个权限被拒绝的消息框,有什么办法能够让任务管理器来杀掉一个服务程序吗? Andrea DeMaioPacific Grove, CA
A. 在我回答这个问题以前,让我们先来研究以下为什么任务管理器会显示一个消息框(如图1所示)。当你在任务管理器中选择了一个进程并且点击了终止进程的按纽,任务管理器将会获取所选进程的ID并执行下面的伪代码:
DWORD dwProcessId = (Initialized from the ListView control);
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
if (hProcess == NULL) {
  // Display error message box (like in Figure 1).
} else {
  TerminateProcess(hProcess, 1);
  CloseHandle(hProcess);
}
因为OpenProcess这个函数不能够打开特定进程的句柄,所以消息框弹出。这个操作失败是因为服务程序是运行在本机系统安全帐户下,而你哪怕是以管理员的身份登录,也没有足够的权限来打开这个服务程序的句柄。事实上,在OpenProcess返回空并收到一个ERROR_ACCESS_DENIED (5)这样的错误代码后,任务管理器会立即调用GetLastError。任务管理器将代表这个错误的文本放在消息框中。 
 
图 1: 错误消息框 
微软允许进程通过使用特权的方式越过这个权限检查。在Windows NT中,特权是分配给用户的属性,这个属性允许用户超越对操作系统某个部分的所谓限制。Windows NT 4.0 支持24种特权允许做一些类似备份恢复文件等通常不会被授予权限的操作,还可以改变系统时间,或是在实时优先级上运行一个进程,以及关闭系统的操作。
其中有一种特权叫做SeDebugPrivilege。通常,操作系统不允许用户对进程进行跟踪,是因为系统不想使用户(或黑客)具有改变一个运行中的进程的能力。然而,有一些用户需要这种对进程进行跟踪调试的能力。缺省情况下,Windows NT自动授予管理员跟踪调试的特权。你可以通过运行User Manager管理工具进行验证。选择Policies 下面的User Rights菜单选项,点击 Show Advanced User Rights checkbox,然后选择Debug programs 的权力 (如图2所示)。 
图 2: 用户权限策略
如果你在登录后被授予了跟踪程序的特权,你就可以对应用程序进行跟踪调试。什么意思呢?就是说,想要对一个应用程序进行跟踪调试,你必须能够被允许成功调用OpenProcess这个函数,这样你就能够调用各种与调试器相关的API,如ReadProcessMemory、 WriteProcessMemory、 VirtualQueryEx、 VirtualProtectEx、 VirtualAllocEx、VirtualFreeEx、CreateRemoteThread,等等。所有这些功能都需要一个进程的句柄才能正确的运行。简单地说, 尽管你通常情况下不能够打开一个进程,如果你具有了调试特权,你就能够强迫OpenProcess 打开一个进程。
读了以上这些,你可能仍然很迷惑。你可能在对自己说: "怪了,我就是以管理员的身份登录的,可是当我告诉任务管理器我要终止一个服务进程时,我看到的仍然是一个权限被拒绝的消息框,这是为什么?"拥有了特权就意味着可以超越普通的操作,具有对强大的系统功能的无限制的权限。然而,系统要保护普通的用户,不允许管理员蓄意或无意地使系统崩溃,影响到其他用户。我们可以很容易地想象一个场景,假设一个管理员登录进入系统,然后告诉任务管理器要杀掉WinLogon.exe 或 CSRSS.exe 进程。这些进程是系统正常运转的必要条件,它们必须始终运行在系统中,否则将会造成整个系统的崩溃。正因如此,任务管理器不会允许一个管理员在无意之中终止这些进程,使整个系统瘫痪。
用户与权限之间有三种关系:被拒绝、被授权和能使用。"被拒绝"以为着用户永远不具有对系统的某个组件超越其所限制权限的能力。例如,如你所看到的所有管理员以外的用户都不具有跟踪调试的特权,所以一个普通的用户就永远不能够调试一个进程。对于系统的许多用户来说,绝大多数特权都是被拒绝的。管理员可以使用User Manager工具给用户授予特权。不过要注意:用户必须退出系统(如果当前是登录状态的话),以新分配的特权重新登录,授权才能生效。
"被授权"意味着用户可以利用特权运行程序。但是请记住特权给了用户一个执行通常被限制的强大操作的权限。这本身是一件非常危险的事情,所以不应当轻易获取。所以,就算一个用户可能被授予特权,系统可能还会在很明白地赋予它使用权以前保留这个特权,在这时,用户可能还是行使不了这个权限。
"能使用"意味着用户已经被赋予特权而且特权已经开放。那么现在,当你要执行一个受限制的操作时,系统会进行检查,看看这个用户是被赋予了哪种特权的使用权(不仅仅是授权),确认后,用户就可以超越这个限制。这就是为什么任务管理器不允许管理员终止一个服务程序的原因,尽管管理员已经被授予了调试特权,任务管理器没有将它开放。
当你登录进系统以后, Windows NT创建了一个叫做访问标志的对象。这个对象包括许多信息,包括那些特权被授权或可使用。访问标志与shell进程Explorer.exe相关联。一旦你运行了一个新的进程,系统会将父进程的访问标志复制赋予新的进程。换句话说,就是每个进程都有它自己的特权集。也就是说,如果你为一个进程赋予了一个特权的使用权,这个使用权不会对其他进程开放除非你明确地赋予。所以,如果你对任务管理进程开放了跟踪调试特权的使用权,任务管理器将能够终止服务进程。
图三显示了一个小程序, EnableDebugPrivAndRun,这个程序演示了如何运行一个具有跟踪调试特权使用权的进程。运行任务管理器(它的调试特权开放),编译链接这个程序使用这样的命令行来运行它: C:\>EnableDebugPrivAndRun.exe TaskMgr.exe 当你执行这个命令行以后, EnableDebugPrivAndRun 首先调用 OpenProcessToken。它返回一个与EnableDebugPrivAndRun 进程相连的访问标志的句柄。如果成功得到访问标志的句柄,调用EnablePrivilege-我自己的函数,功能只是把所有必要的赋予或不赋予特权的步骤打包。
图3: EnableDebugPrivAndRun.cpp
/*************************************************************
Module name: EnableDebugPrivAndRun.cpp
Notices: Written 1998 by Jeffrey Richter
Description: Enables the Debug privilege before running an app
**** *********************************************************/
#define STRICT
#include
//////////////////////////////////////////////////////////////
BOOL EnablePrivilege(HANDLE hToken, LPCTSTR szPrivName,
                    BOOL fEnable) {
  TOKEN_PRIVILEGES tp;
  tp.PrivilegeCount = 1;
  LookupPrivilegeValue(NULL, szPrivName, &tp.Privileges[0].Luid);
  tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
  AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
  return((GetLastError() == ERROR_SUCCESS));
}
//////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE hinstExePrev,
                  LPSTR pszCmdLine, int nCmdShow) {
  HANDLE hToken;
  if (OpenProcessToken(GetCurrentProcess(),
                       TOKEN_ADJUST_PRIVILEGES, &hToken)) {
     if (EnablePrivilege(hToken, SE_DEBUG_NAME, TRUE)) {
        if (ShellExecute(NULL, NULL, pszCmdLine, NULL,
           NULL, SW_SHOWNORMAL) < (HINSTANCE) 32) {
           MessageBox(NULL, pszCmdLine,
                      __TEXT("EnableDebugPrivAndRun: Couldn't run"),
                      MB_OK | MB_ICONINFORMATION);
        }
     }
     CloseHandle(hToken);
  }
  return(0);
}
//////////////////////// End Of File /////////////////////////
EnablePrivilege 需要三个参数:包括用户特权信息的访问标志的句柄、标识你想改变的特权的字符串和一个布尔型变量标识你是否要赋予这个特权。带着这三个参数, EnablePrivilege 对TOKEN_PRIVILEGES 结构进行初始化。这个结构实际上是一个可变长度的结构,它允许你同时说明几个特权。由于EnablePrivilege 函数只允许你一次改变一个特权,所以代码就比同时能改变多个特权的代码简单。
LookupPrivilegeValue函数将特权名字转换成一个64位的等价数值叫做LUID(本地独有标识)。对许多开发者来说这很奇怪。通常,开发人员喜欢使用数字而不是字符串;但是使用特权时,通常是以字符串来方式进行的,然后再调用LookupPrivilegeValue 将这个字符串转化为数字。LUID,保证是再本地系统中独一无二的标识。你不能够在机器之间传递一个LUID,甚至,你不能在同一台机器上经过重新启动后还保留一个LUID。换句话说,如果你在两次调用之间重新启动系统,将调试特权字符串传递给LookupPrivilegeValue将会导致返回两个不同的LUID。
一旦你拥有了调试特权的LUID, EnablePrivilege将调用AdjustTokenPrivileges赋予或取消特权。你应当注意到当我调用OpenProcessToken时,我要申请TOKEN_ADJUST_PRIVILEGES权限, 这时成功调用AdjustTokenPrivileges的权限。
在EnablePrivilege 返回以后,我将EnableDebugPrivAndRun的命令行传递给ShellExecute 函数。ShellExecute 将会在内部调用CreateProcess 生成一个新的进程。记住权限标识是有继承性的,所以这个新的进程将会继承EnableDebugPrivAndRun的权限标识,现在这个标识表示调试特权使用权开放。这意味着新进程的调试使用权也是开放的能够成功地调用OpenProcess。
我喜欢运行在我系统上的任务管理器所以我经常通过查看任务栏上始终左边的小图标来查看CPU的性能。这以下是我为了确信任务管理器总是在调试特权开放的情况下运行所做的工作。首先,我建立了我的EnableDebugPrivAndRun程序并将它放在一个目录下面。然后我创建一个快捷方式让任务管理器一开始以窗口最小化的方式运行。我把这个快捷方式的.LNK 文件放在与EnableDebugPrivAndRun 程序所在的同一目录下。然后再创建另一个快捷方式运行EnableDebugPrivAndRun,把任务管理器快捷方式的文件名作为参数传递给它。这个快捷方式的命令行字符串如下所示:
C:\Bin\EnableDebugPrivAndRun.exe "C:\Bin\Task Manager.lnk"
我把第二个快捷方式放在我的开始菜单的Startup文件夹中这样系统就可以在我一登录进去就将它激活。就是这么简单!

function EnableDebugPrivilege : Boolean ;
  function EnablePrivilege ( hToken : Cardinal ; PrivName : string ; bEnable : Boolean ): Boolean ;
  var
   TP : TOKEN_PRIVILEGES ;
   Dummy : Cardinal ;
  begin
   TP . PrivilegeCount := 1 ;
   LookupPrivilegeValue ( nil , pchar ( PrivName ), TP . Privileges [ 0 ]. Luid );
    if bEnable then
     TP . Privileges [ 0 ]. Attributes := SE_PRIVILEGE_ENABLED
    else TP . Privileges [ 0 ]. Attributes := 0 ;
   AdjustTokenPrivileges ( hToken , False , TP , SizeOf ( TP ), nil , Dummy );
   Result := GetLastError = ERROR_SUCCESS ;
  end ;
var
 hToken : Cardinal ;
begin
 OpenProcessToken ( GetCurrentProcess , TOKEN_ADJUST_PRIVILEGES , hToken );
  if EnablePrivilege ( hToken , 'SeDebugPrivilege' , True ) then ShowMessage ( 'OK' );
 CloseHandle ( hToken );
end ;

function KillTask ( ExeFileName : string ): Integer ;
const
 PROCESS_TERMINATE = $0001 ;
var
 ContinueLoop : BOOL ;
 FSnapshotHandle : THandle ;
 FProcessEntry32 : TProcessEntry32 ;
begin
 Result := 0 ;
 FSnapshotHandle := CreateToolhelp32Snapshot ( TH32CS_SNAPPROCESS , 0 );
 FProcessEntry32 . dwSize := SizeOf ( FProcessEntry32 );
 ContinueLoop := Process32First ( FSnapshotHandle , FProcessEntry32 );
  while Integer ( ContinueLoop ) <> 0 do
  begin
    if (( UpperCase ( ExtractFileName ( FProcessEntry32 . szExeFile )) =
     UpperCase ( ExeFileName )) or ( UpperCase ( FProcessEntry32 . szExeFile ) =
     UpperCase ( ExeFileName ))) then
     Result := Integer ( TerminateProcess (
       OpenProcess ( PROCESS_TERMINATE ,
       BOOL ( 0 ),
       FProcessEntry32 . th32ProcessID ),
        0 ));
   ContinueLoop := Process32Next ( FSnapshotHandle , FProcessEntry32 );
  end ;
 CloseHandle ( FSnapshotHandle );
end ;

procedure TForm1 . Button1Click ( Sender : TObject );
begin
 EnableDebugPrivilege ;
 Caption := SysErrorMessage ( KillTask ( 'lsass.exe' ));
end ;

img_11872.bmp (187.0KB)
img_32118.bmp (356.0KB)