C#(禁止系统运行某个进程的简单方法)

转发自:https://blog.csdn.net/chenyujing1234/article/details/7797269


一、解决方法

1、理论

要对一个任意进程(包括系统安全进程和服务进程)进行指定了写相关的访问权的OpenProcess操作,只要当前进程具有SeDeDebug权限就可以了。

要是一个用户是Administrator或是被给予了相应的权限,就可以具有该权限。可是,就算我们用Administrator帐号对一个系统安全进程执行OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessID)还是会遇到“访问拒绝”的错误。

什么原因呢?原来在默认的情况下进程的一些访问权限是没有被启用(Enabled)的,所以我们要做的首先是启用这些权限。与此相关的一些API函数有OpenProcessToken、LookupPrivilegevalue、AdjustTokenPrivileges

GetCurrentProcess得到得到的称之为"伪句柄",只是一个标识,你可以发现,其实就是返回$FFFFFFFF。
每个进程得句柄都是一样的,只是实用于进程内部得使用.如果你想得到实际得句柄,在进程间进行通讯,必需要进行转化,
调用DuplicateHandle,注意,得实句柄使用完成以后,你必须要调用CloseHandle去关闭.
其实,你应该明白了为何"伪句柄"得存在,就是使用简单,不用关闭,不会造成内存泄漏.。

内核对象的句柄会在新进程中,产生一条记录,并且该内核对象计数增加。根据引用计数,这里会引出该函数的一种巧妙用法,文件锁定或者叫文件占坑,

原理如下:向系统进程中,复制打开的文件句柄,内核对象在所有引用未删除时不会销毁

2、代码实现
[cpp]  view plain  copy
  1. #include <Windows.h>  
  2.   
  3.   
  4. //提权函数    
  5. void RaiseToDebugP()    
  6. {    
  7.     HANDLE hToken;    
  8.     HANDLE hProcess = GetCurrentProcess();    
  9.     if ( OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) )    
  10.     {    
  11.         TOKEN_PRIVILEGES tkp;    
  12.         if ( LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid) )    
  13.         {    
  14.             tkp.PrivilegeCount = 1;    
  15.             tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;    
  16.   
  17.             BOOL bREt = AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0) ;    
  18.         }    
  19.         CloseHandle(hToken);    
  20.     }        
  21. }    
  22. BOOL OccupyFile( LPCTSTR lpFileName )    
  23. {    
  24.     BOOL    bRet;    
  25.     //提升自身权限    
  26.     RaiseToDebugP();    
  27.     //打开一个pid为4的进程,只要是存在的进程,都可以    
  28.     HANDLE hProcess = OpenProcess( PROCESS_DUP_HANDLE, FALSE, 4);    // 4为system进程号    
  29.   
  30.     if ( hProcess == NULL )    
  31.     {              
  32.         return FALSE;    
  33.     }    
  34.   
  35.     HANDLE hFile;    
  36.     HANDLE hTargetHandle;    
  37.     //以独占模式打开目标文件    
  38.     hFile = CreateFile( lpFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);        
  39.   
  40.   
  41.     if ( hFile == INVALID_HANDLE_VALUE )    
  42.     {    
  43.         CloseHandle( hProcess );    
  44.         return FALSE;    
  45.     }    
  46.   
  47.     //把文件句柄复制到pid=4的进程中去,这样,只要pid=4的进程不退出,谁也动不了目标文件    
  48.     bRet = DuplicateHandle( GetCurrentProcess(), hFile, hProcess, &hTargetHandle,     
  49.         0, FALSE, DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE);    
  50.   
  51.     CloseHandle( hProcess );    
  52.   
  53.     return bRet;    
  54. }    
  55.   
  56.   
  57. //入口函数    
  58. int main()    
  59. {    
  60.     OccupyFile("D:\\Program Files\\IDA\\idag.exe");    
  61.   
  62.     return 0;    
  63. }   


 

二、API函数说明

1、 OpenProcessToken

 使用OpenProcessToken()用于得到指定进程的访问令牌,而第三个参数定义设置不正确可能导致该函数调用失败。
以下举例说明:

[cpp]  view plain  copy
  1. HANDLE hProc;  
  2. hProc = GetCurrentProcess();  
  3.   
  4.    
  5.   
  6. // Method1 - Error(998)  
  7. HANDLE *hToken;  
  8. OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES, hToken);  
  9.   
  10.    
  11.   
  12. // Method2 - Success  
  13. HANDLE hToken;  
  14. OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES, &hToken);  

 

以上是获取访问令牌的调用,OpenProcessToken()函数原型如下:
 

[cpp]  view plain  copy
  1. BOOL OpenProcessToken(     
  2. __in HANDLE ProcessHandle, //要修改访问权限的进程句柄     
  3. __in DWORD DesiredAccess, //指定你要进行的操作类型     
  4. __out PHANDLE TokenHandle //返回的访问令牌指针     
  5. );  

 方法1和方法2都使用HANDLE类型定义,方法1定义指针,方法2定义变量。
方法1调用函数返回失败(通过GetLastError()可知错误代码为998——拒绝访问);
方法2调用函数则能成功获取访问令牌。为什么出现这种情况呢?WinNT.h中相关的定义引起,如下:

 

[cpp]  view plain  copy
  1. #ifdef STRICT  
  2. typedef void *HANDLE;  
  3. #define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name  
  4. #else  
  5. typedef PVOID HANDLE;  
  6. #define DECLARE_HANDLE(name) typedef HANDLE name  
  7. #endif  
  8. typedef HANDLE *PHANDLE;  

 由此可见可以把任意一种类型的指针赋值给PVOID型,因此PVOID*赋给PVOID型是可以的,而把PVOID型赋值给PVOID*型也可以。

2、AdjustTokenPrivileges

AdjustTokenPrivileges的原型如下:

[cpp]  view plain  copy
  1. BOOL AdjustTokenPrivileges  
  2. (     
  3. HANDLE TokenHandle, // handle to token     
  4. BOOL DisableAllPrivileges, // disabling option     
  5. PTOKEN_PRIVILEGES NewState, // privilege information     
  6. DWORD BufferLength, // size of buffer     
  7. PTOKEN_PRIVILEGES PreviousState, // original state buffer     
  8. PDWORD ReturnLength // required buffer size     
  9. );   


第一个参数是访问令牌的句柄;

第二个参数决定是进行权限修改还是丧失(Disable)所有权限

第三个参数指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组,数据组的每个项指明了权限的类型和要进行的操作;

第四个参数是结构PreviousState的长度,如果PreviousState为空,该参数应为NULL;

第五个参数也是一个指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息,可空;

最后一个参数为实际PreviousState结构返回的大小。

在使用这个函数前再看一下TOKEN_PRIVILEGES这个结构,其声明如下:

[cpp]  view plain  copy
  1. typedef struct _TOKEN_PRIVILEGES   
  2. {     
  3. DWORD PrivilegeCount;     
  4. LUID_AND_ATTRIBUTES Privileges[];     
  5. } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;   


PrivilegeCount指的数组元素的个数,接着是一个LUID_AND_ATTRIBUTES类型的数组,再来看一下LUID_AND_ATTRIBUTES这个结构的内容,声明如下:

[cpp]  view plain  copy
  1. typedef struct _LUID_AND_ATTRIBUTES  
  2. {     
  3.     LUID Luid;     
  4.     DWORD Attributes;     
  5. } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES   


第一个参数就是指权限的类型,是一个LUID的值,LUID就是指locally unique identifier,我想GUID大家是比较熟悉的,和GUID的要求保证全局唯一不同,LUID只要保证局部唯一,就是指在系统的每一次运行期间保证是唯一的就可以了。另外和GUID相同的一点,LUID也是一个64位的值,相信大家都看过GUID那一大串的值,我们要怎么样才能知道一个权限对应的LUID值是多少呢?

第二个参数就指明了我们要进行的操作类型,有三个可选项: SE_PRIVILEGE_ENABLED、SE_PRIVILEGE_ENABLED_BY_DEFAULT、SE_PRIVILEGE_USED_FOR_ACCESS。要使能一个权限就指定Attributes为SE_PRIVILEGE_ENABLED。

3、LookupPrivilegevalue

其原形如下:

[cpp]  view plain  copy
  1. BOOL LookupPrivilegevalue(     
  2.     LPCTSTR lpSystemName, // system name     
  3.     LPCTSTR lpName, // privilege name     
  4.     PLUID lpLuid // locally unique identifier     
  5. );   


(1)第一个参数是系统的名称,如果是本地系统只要指明为NULL就可以了;

(2)第二个参数就是指明了权限的名称,如“SeDebugPrivilege”。

在Winnt.h中还定义了一些权限名称的宏,如:

[cpp]  view plain  copy
  1. #define SE_BACKUP_NAME TEXT("SeBackupPrivilege")     
  2. #define SE_RESTORE_NAME TEXT("SeRestorePrivilege")     
  3. #define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege")     
  4. #define SE_DEBUG_NAME TEXT("SeDebugPrivilege")   

(3)第三个参数就是返回LUID的指针;

4、DuplicateHandle

The DuplicateHandle  function creates a duplicate handle. The returned duplicate is in the caller's process space.(从当前进程复制句柄到其他进程空间)

[cpp]  view plain  copy
  1. BOOL DuplicateHandle(  
  2. HANDLE hSourceProcessHandle, // handle to source process  
  3. HANDLE hSourceHandle, // handle to duplicate  
  4. HANDLE hTargetProcessHandle, // handle to target process  
  5. LPHANDLE lpTargetHandle, // duplicate handle  
  6. DWORD dwDesiredAccess, // requested access  
  7. BOOL bInheritHandle, // handle inheritance option  
  8. DWORD dwOptions // optional actions  
  9. );  


 

(1)在系统中,对象分两类:

核心对象和用户对象

如进程对象,线程对象,文件映射 对象等就是核心对象;

而向窗口,菜单等都是用户对象.。
(2)两者是有差别的

(2、1)用于标示用户对象的句柄是系统唯一的,也就是说,一个进程 完全可以对另外一个进程中的用户对象进行操作,比如两个进程间通信的方法之一, 就是发送消息.正是由于窗口是用户对象,所以句柄是系统唯一,通过FindWindow, 得到另外一个进程的窗口句柄,然后用SendMessage(),让hWnd的窗口过程来处理消 息,实现了进程间的通信.

因此,对于用户对象,你根本不用DuplicateHandle(),直接 把句柄拿来用就行了;
(2、2)而核心对象则不一样。核心对象是为了加强系统的稳定性,因此,核心对象句柄是 进程相关的,在每一个进程中都有一个核心对象表,每一个对象的索引(不完全是)作为内核对象的句柄,从而实现进程相关.同一个对象在不同的进程中可能有不 同的索引,即句柄.

对核心对象进行操作时,系统还要进行安全检验,看一下你是否有权来操作这个对象.因此你不能同用户对象一样,直接把句柄拿过来用.比方 说,你想操作另一个进程中的文件映射对象,这个文件映射对象句柄在那个进程中假设是0x000001,但在你的进程中,很有可能0x00000001时表 示另一个核心对象,此时的操作就永远不会成功,甚至会产生灾难性的后果.此时,就有必要用DuplicateHandle().

二、效果


一、解决方法

1、理论

要对一个任意进程(包括系统安全进程和服务进程)进行指定了写相关的访问权的OpenProcess操作,只要当前进程具有SeDeDebug权限就可以了。

要是一个用户是Administrator或是被给予了相应的权限,就可以具有该权限。可是,就算我们用Administrator帐号对一个系统安全进程执行OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessID)还是会遇到“访问拒绝”的错误。

什么原因呢?原来在默认的情况下进程的一些访问权限是没有被启用(Enabled)的,所以我们要做的首先是启用这些权限。与此相关的一些API函数有OpenProcessToken、LookupPrivilegevalue、AdjustTokenPrivileges

GetCurrentProcess得到得到的称之为"伪句柄",只是一个标识,你可以发现,其实就是返回$FFFFFFFF。
每个进程得句柄都是一样的,只是实用于进程内部得使用.如果你想得到实际得句柄,在进程间进行通讯,必需要进行转化,
调用DuplicateHandle,注意,得实句柄使用完成以后,你必须要调用CloseHandle去关闭.
其实,你应该明白了为何"伪句柄"得存在,就是使用简单,不用关闭,不会造成内存泄漏.。

内核对象的句柄会在新进程中,产生一条记录,并且该内核对象计数增加。根据引用计数,这里会引出该函数的一种巧妙用法,文件锁定或者叫文件占坑,

原理如下:向系统进程中,复制打开的文件句柄,内核对象在所有引用未删除时不会销毁

2、代码实现
[cpp]  view plain  copy
  1. #include <Windows.h>  
  2.   
  3.   
  4. //提权函数    
  5. void RaiseToDebugP()    
  6. {    
  7.     HANDLE hToken;    
  8.     HANDLE hProcess = GetCurrentProcess();    
  9.     if ( OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) )    
  10.     {    
  11.         TOKEN_PRIVILEGES tkp;    
  12.         if ( LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid) )    
  13.         {    
  14.             tkp.PrivilegeCount = 1;    
  15.             tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;    
  16.   
  17.             BOOL bREt = AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0) ;    
  18.         }    
  19.         CloseHandle(hToken);    
  20.     }        
  21. }    
  22. BOOL OccupyFile( LPCTSTR lpFileName )    
  23. {    
  24.     BOOL    bRet;    
  25.     //提升自身权限    
  26.     RaiseToDebugP();    
  27.     //打开一个pid为4的进程,只要是存在的进程,都可以    
  28.     HANDLE hProcess = OpenProcess( PROCESS_DUP_HANDLE, FALSE, 4);    // 4为system进程号    
  29.   
  30.     if ( hProcess == NULL )    
  31.     {              
  32.         return FALSE;    
  33.     }    
  34.   
  35.     HANDLE hFile;    
  36.     HANDLE hTargetHandle;    
  37.     //以独占模式打开目标文件    
  38.     hFile = CreateFile( lpFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);        
  39.   
  40.   
  41.     if ( hFile == INVALID_HANDLE_VALUE )    
  42.     {    
  43.         CloseHandle( hProcess );    
  44.         return FALSE;    
  45.     }    
  46.   
  47.     //把文件句柄复制到pid=4的进程中去,这样,只要pid=4的进程不退出,谁也动不了目标文件    
  48.     bRet = DuplicateHandle( GetCurrentProcess(), hFile, hProcess, &hTargetHandle,     
  49.         0, FALSE, DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE);    
  50.   
  51.     CloseHandle( hProcess );    
  52.   
  53.     return bRet;    
  54. }    
  55.   
  56.   
  57. //入口函数    
  58. int main()    
  59. {    
  60.     OccupyFile("D:\\Program Files\\IDA\\idag.exe");    
  61.   
  62.     return 0;    
  63. }   


 

二、API函数说明

1、 OpenProcessToken

 使用OpenProcessToken()用于得到指定进程的访问令牌,而第三个参数定义设置不正确可能导致该函数调用失败。
以下举例说明:

[cpp]  view plain  copy
  1. HANDLE hProc;  
  2. hProc = GetCurrentProcess();  
  3.   
  4.    
  5.   
  6. // Method1 - Error(998)  
  7. HANDLE *hToken;  
  8. OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES, hToken);  
  9.   
  10.    
  11.   
  12. // Method2 - Success  
  13. HANDLE hToken;  
  14. OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES, &hToken);  

 

以上是获取访问令牌的调用,OpenProcessToken()函数原型如下:
 

[cpp]  view plain  copy
  1. BOOL OpenProcessToken(     
  2. __in HANDLE ProcessHandle, //要修改访问权限的进程句柄     
  3. __in DWORD DesiredAccess, //指定你要进行的操作类型     
  4. __out PHANDLE TokenHandle //返回的访问令牌指针     
  5. );  

 方法1和方法2都使用HANDLE类型定义,方法1定义指针,方法2定义变量。
方法1调用函数返回失败(通过GetLastError()可知错误代码为998——拒绝访问);
方法2调用函数则能成功获取访问令牌。为什么出现这种情况呢?WinNT.h中相关的定义引起,如下:

 

[cpp]  view plain  copy
  1. #ifdef STRICT  
  2. typedef void *HANDLE;  
  3. #define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name  
  4. #else  
  5. typedef PVOID HANDLE;  
  6. #define DECLARE_HANDLE(name) typedef HANDLE name  
  7. #endif  
  8. typedef HANDLE *PHANDLE;  

 由此可见可以把任意一种类型的指针赋值给PVOID型,因此PVOID*赋给PVOID型是可以的,而把PVOID型赋值给PVOID*型也可以。

2、AdjustTokenPrivileges

AdjustTokenPrivileges的原型如下:

[cpp]  view plain  copy
  1. BOOL AdjustTokenPrivileges  
  2. (     
  3. HANDLE TokenHandle, // handle to token     
  4. BOOL DisableAllPrivileges, // disabling option     
  5. PTOKEN_PRIVILEGES NewState, // privilege information     
  6. DWORD BufferLength, // size of buffer     
  7. PTOKEN_PRIVILEGES PreviousState, // original state buffer     
  8. PDWORD ReturnLength // required buffer size     
  9. );   


第一个参数是访问令牌的句柄;

第二个参数决定是进行权限修改还是丧失(Disable)所有权限

第三个参数指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组,数据组的每个项指明了权限的类型和要进行的操作;

第四个参数是结构PreviousState的长度,如果PreviousState为空,该参数应为NULL;

第五个参数也是一个指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息,可空;

最后一个参数为实际PreviousState结构返回的大小。

在使用这个函数前再看一下TOKEN_PRIVILEGES这个结构,其声明如下:

[cpp]  view plain  copy
  1. typedef struct _TOKEN_PRIVILEGES   
  2. {     
  3. DWORD PrivilegeCount;     
  4. LUID_AND_ATTRIBUTES Privileges[];     
  5. } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;   


PrivilegeCount指的数组元素的个数,接着是一个LUID_AND_ATTRIBUTES类型的数组,再来看一下LUID_AND_ATTRIBUTES这个结构的内容,声明如下:

[cpp]  view plain  copy
  1. typedef struct _LUID_AND_ATTRIBUTES  
  2. {     
  3.     LUID Luid;     
  4.     DWORD Attributes;     
  5. } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES   


第一个参数就是指权限的类型,是一个LUID的值,LUID就是指locally unique identifier,我想GUID大家是比较熟悉的,和GUID的要求保证全局唯一不同,LUID只要保证局部唯一,就是指在系统的每一次运行期间保证是唯一的就可以了。另外和GUID相同的一点,LUID也是一个64位的值,相信大家都看过GUID那一大串的值,我们要怎么样才能知道一个权限对应的LUID值是多少呢?

第二个参数就指明了我们要进行的操作类型,有三个可选项: SE_PRIVILEGE_ENABLED、SE_PRIVILEGE_ENABLED_BY_DEFAULT、SE_PRIVILEGE_USED_FOR_ACCESS。要使能一个权限就指定Attributes为SE_PRIVILEGE_ENABLED。

3、LookupPrivilegevalue

其原形如下:

[cpp]  view plain  copy
  1. BOOL LookupPrivilegevalue(     
  2.     LPCTSTR lpSystemName, // system name     
  3.     LPCTSTR lpName, // privilege name     
  4.     PLUID lpLuid // locally unique identifier     
  5. );   


(1)第一个参数是系统的名称,如果是本地系统只要指明为NULL就可以了;

(2)第二个参数就是指明了权限的名称,如“SeDebugPrivilege”。

在Winnt.h中还定义了一些权限名称的宏,如:

[cpp]  view plain  copy
  1. #define SE_BACKUP_NAME TEXT("SeBackupPrivilege")     
  2. #define SE_RESTORE_NAME TEXT("SeRestorePrivilege")     
  3. #define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege")     
  4. #define SE_DEBUG_NAME TEXT("SeDebugPrivilege")   

(3)第三个参数就是返回LUID的指针;

4、DuplicateHandle

The DuplicateHandle  function creates a duplicate handle. The returned duplicate is in the caller's process space.(从当前进程复制句柄到其他进程空间)

[cpp]  view plain  copy
  1. BOOL DuplicateHandle(  
  2. HANDLE hSourceProcessHandle, // handle to source process  
  3. HANDLE hSourceHandle, // handle to duplicate  
  4. HANDLE hTargetProcessHandle, // handle to target process  
  5. LPHANDLE lpTargetHandle, // duplicate handle  
  6. DWORD dwDesiredAccess, // requested access  
  7. BOOL bInheritHandle, // handle inheritance option  
  8. DWORD dwOptions // optional actions  
  9. );  


 

(1)在系统中,对象分两类:

核心对象和用户对象

如进程对象,线程对象,文件映射 对象等就是核心对象;

而向窗口,菜单等都是用户对象.。
(2)两者是有差别的

(2、1)用于标示用户对象的句柄是系统唯一的,也就是说,一个进程 完全可以对另外一个进程中的用户对象进行操作,比如两个进程间通信的方法之一, 就是发送消息.正是由于窗口是用户对象,所以句柄是系统唯一,通过FindWindow, 得到另外一个进程的窗口句柄,然后用SendMessage(),让hWnd的窗口过程来处理消 息,实现了进程间的通信.

因此,对于用户对象,你根本不用DuplicateHandle(),直接 把句柄拿来用就行了;
(2、2)而核心对象则不一样。核心对象是为了加强系统的稳定性,因此,核心对象句柄是 进程相关的,在每一个进程中都有一个核心对象表,每一个对象的索引(不完全是)作为内核对象的句柄,从而实现进程相关.同一个对象在不同的进程中可能有不 同的索引,即句柄.

对核心对象进行操作时,系统还要进行安全检验,看一下你是否有权来操作这个对象.因此你不能同用户对象一样,直接把句柄拿过来用.比方 说,你想操作另一个进程中的文件映射对象,这个文件映射对象句柄在那个进程中假设是0x000001,但在你的进程中,很有可能0x00000001时表 示另一个核心对象,此时的操作就永远不会成功,甚至会产生灾难性的后果.此时,就有必要用DuplicateHandle().

二、效果

猜你喜欢

转载自blog.csdn.net/xkf1119/article/details/80256077
今日推荐