转发自:https://blog.youkuaiyun.com/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、代码实现
- #include <Windows.h>
- //提权函数
- void RaiseToDebugP()
- {
- HANDLE hToken;
- HANDLE hProcess = GetCurrentProcess();
- if ( OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) )
- {
- TOKEN_PRIVILEGES tkp;
- if ( LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid) )
- {
- tkp.PrivilegeCount = 1;
- tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
- BOOL bREt = AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0) ;
- }
- CloseHandle(hToken);
- }
- }
- BOOL OccupyFile( LPCTSTR lpFileName )
- {
- BOOL bRet;
- //提升自身权限
- RaiseToDebugP();
- //打开一个pid为4的进程,只要是存在的进程,都可以
- HANDLE hProcess = OpenProcess( PROCESS_DUP_HANDLE, FALSE, 4); // 4为system进程号
- if ( hProcess == NULL )
- {
- return FALSE;
- }
- HANDLE hFile;
- HANDLE hTargetHandle;
- //以独占模式打开目标文件
- hFile = CreateFile( lpFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);
- if ( hFile == INVALID_HANDLE_VALUE )
- {
- CloseHandle( hProcess );
- return FALSE;
- }
- //把文件句柄复制到pid=4的进程中去,这样,只要pid=4的进程不退出,谁也动不了目标文件
- bRet = DuplicateHandle( GetCurrentProcess(), hFile, hProcess, &hTargetHandle,
- 0, FALSE, DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE);
- CloseHandle( hProcess );
- return bRet;
- }
- //入口函数
- int main()
- {
- OccupyFile("D:\\Program Files\\IDA\\idag.exe");
- return 0;
- }
二、API函数说明
1、 OpenProcessToken
使用OpenProcessToken()用于得到指定进程的访问令牌,而第三个参数定义设置不正确可能导致该函数调用失败。
以下举例说明:
- HANDLE hProc;
- hProc = GetCurrentProcess();
- // Method1 - Error(998)
- HANDLE *hToken;
- OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES, hToken);
- // Method2 - Success
- HANDLE hToken;
- OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES, &hToken);
以上是获取访问令牌的调用,OpenProcessToken()函数原型如下:
- BOOL OpenProcessToken(
- __in HANDLE ProcessHandle, //要修改访问权限的进程句柄
- __in DWORD DesiredAccess, //指定你要进行的操作类型
- __out PHANDLE TokenHandle //返回的访问令牌指针
- );
方法1和方法2都使用HANDLE类型定义,方法1定义指针,方法2定义变量。
方法1调用函数返回失败(通过GetLastError()可知错误代码为998——拒绝访问);
方法2调用函数则能成功获取访问令牌。为什么出现这种情况呢?WinNT.h中相关的定义引起,如下:
- #ifdef STRICT
- typedef void *HANDLE;
- #define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name
- #else
- typedef PVOID HANDLE;
- #define DECLARE_HANDLE(name) typedef HANDLE name
- #endif
- typedef HANDLE *PHANDLE;
由此可见可以把任意一种类型的指针赋值给PVOID型,因此PVOID*赋给PVOID型是可以的,而把PVOID型赋值给PVOID*型也可以。
2、AdjustTokenPrivileges
AdjustTokenPrivileges的原型如下:
- BOOL AdjustTokenPrivileges
- (
- HANDLE TokenHandle, // handle to token
- BOOL DisableAllPrivileges, // disabling option
- PTOKEN_PRIVILEGES NewState, // privilege information
- DWORD BufferLength, // size of buffer
- PTOKEN_PRIVILEGES PreviousState, // original state buffer
- PDWORD ReturnLength // required buffer size
- );
第一个参数是访问令牌的句柄;
第二个参数决定是进行权限修改还是丧失(Disable)所有权限;
第三个参数指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组,数据组的每个项指明了权限的类型和要进行的操作;
第四个参数是结构PreviousState的长度,如果PreviousState为空,该参数应为NULL;
第五个参数也是一个指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息,可空;
最后一个参数为实际PreviousState结构返回的大小。
在使用这个函数前再看一下TOKEN_PRIVILEGES这个结构,其声明如下:
- typedef struct _TOKEN_PRIVILEGES
- {
- DWORD PrivilegeCount;
- LUID_AND_ATTRIBUTES Privileges[];
- } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
PrivilegeCount指的数组元素的个数,接着是一个LUID_AND_ATTRIBUTES类型的数组,再来看一下LUID_AND_ATTRIBUTES这个结构的内容,声明如下:
- typedef struct _LUID_AND_ATTRIBUTES
- {
- LUID Luid;
- DWORD Attributes;
- } 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
其原形如下:
- BOOL LookupPrivilegevalue(
- LPCTSTR lpSystemName, // system name
- LPCTSTR lpName, // privilege name
- PLUID lpLuid // locally unique identifier
- );
(1)第一个参数是系统的名称,如果是本地系统只要指明为NULL就可以了;
(2)第二个参数就是指明了权限的名称,如“SeDebugPrivilege”。
在Winnt.h中还定义了一些权限名称的宏,如:
- #define SE_BACKUP_NAME TEXT("SeBackupPrivilege")
- #define SE_RESTORE_NAME TEXT("SeRestorePrivilege")
- #define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege")
- #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.(从当前进程复制句柄到其他进程空间)
- BOOL DuplicateHandle(
- HANDLE hSourceProcessHandle, // handle to source process
- HANDLE hSourceHandle, // handle to duplicate
- HANDLE hTargetProcessHandle, // handle to target process
- LPHANDLE lpTargetHandle, // duplicate handle
- DWORD dwDesiredAccess, // requested access
- BOOL bInheritHandle, // handle inheritance option
- DWORD dwOptions // optional actions
- );
(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、代码实现
- #include <Windows.h>
- //提权函数
- void RaiseToDebugP()
- {
- HANDLE hToken;
- HANDLE hProcess = GetCurrentProcess();
- if ( OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) )
- {
- TOKEN_PRIVILEGES tkp;
- if ( LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid) )
- {
- tkp.PrivilegeCount = 1;
- tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
- BOOL bREt = AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0) ;
- }
- CloseHandle(hToken);
- }
- }
- BOOL OccupyFile( LPCTSTR lpFileName )
- {
- BOOL bRet;
- //提升自身权限
- RaiseToDebugP();
- //打开一个pid为4的进程,只要是存在的进程,都可以
- HANDLE hProcess = OpenProcess( PROCESS_DUP_HANDLE, FALSE, 4); // 4为system进程号
- if ( hProcess == NULL )
- {
- return FALSE;
- }
- HANDLE hFile;
- HANDLE hTargetHandle;
- //以独占模式打开目标文件
- hFile = CreateFile( lpFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);
- if ( hFile == INVALID_HANDLE_VALUE )
- {
- CloseHandle( hProcess );
- return FALSE;
- }
- //把文件句柄复制到pid=4的进程中去,这样,只要pid=4的进程不退出,谁也动不了目标文件
- bRet = DuplicateHandle( GetCurrentProcess(), hFile, hProcess, &hTargetHandle,
- 0, FALSE, DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE);
- CloseHandle( hProcess );
- return bRet;
- }
- //入口函数
- int main()
- {
- OccupyFile("D:\\Program Files\\IDA\\idag.exe");
- return 0;
- }
二、API函数说明
1、 OpenProcessToken
使用OpenProcessToken()用于得到指定进程的访问令牌,而第三个参数定义设置不正确可能导致该函数调用失败。
以下举例说明:
- HANDLE hProc;
- hProc = GetCurrentProcess();
- // Method1 - Error(998)
- HANDLE *hToken;
- OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES, hToken);
- // Method2 - Success
- HANDLE hToken;
- OpenProcessToken(hProc, TOKEN_ADJUST_PRIVILEGES, &hToken);
以上是获取访问令牌的调用,OpenProcessToken()函数原型如下:
- BOOL OpenProcessToken(
- __in HANDLE ProcessHandle, //要修改访问权限的进程句柄
- __in DWORD DesiredAccess, //指定你要进行的操作类型
- __out PHANDLE TokenHandle //返回的访问令牌指针
- );
方法1和方法2都使用HANDLE类型定义,方法1定义指针,方法2定义变量。
方法1调用函数返回失败(通过GetLastError()可知错误代码为998——拒绝访问);
方法2调用函数则能成功获取访问令牌。为什么出现这种情况呢?WinNT.h中相关的定义引起,如下:
- #ifdef STRICT
- typedef void *HANDLE;
- #define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name
- #else
- typedef PVOID HANDLE;
- #define DECLARE_HANDLE(name) typedef HANDLE name
- #endif
- typedef HANDLE *PHANDLE;
由此可见可以把任意一种类型的指针赋值给PVOID型,因此PVOID*赋给PVOID型是可以的,而把PVOID型赋值给PVOID*型也可以。
2、AdjustTokenPrivileges
AdjustTokenPrivileges的原型如下:
- BOOL AdjustTokenPrivileges
- (
- HANDLE TokenHandle, // handle to token
- BOOL DisableAllPrivileges, // disabling option
- PTOKEN_PRIVILEGES NewState, // privilege information
- DWORD BufferLength, // size of buffer
- PTOKEN_PRIVILEGES PreviousState, // original state buffer
- PDWORD ReturnLength // required buffer size
- );
第一个参数是访问令牌的句柄;
第二个参数决定是进行权限修改还是丧失(Disable)所有权限;
第三个参数指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组,数据组的每个项指明了权限的类型和要进行的操作;
第四个参数是结构PreviousState的长度,如果PreviousState为空,该参数应为NULL;
第五个参数也是一个指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息,可空;
最后一个参数为实际PreviousState结构返回的大小。
在使用这个函数前再看一下TOKEN_PRIVILEGES这个结构,其声明如下:
- typedef struct _TOKEN_PRIVILEGES
- {
- DWORD PrivilegeCount;
- LUID_AND_ATTRIBUTES Privileges[];
- } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
PrivilegeCount指的数组元素的个数,接着是一个LUID_AND_ATTRIBUTES类型的数组,再来看一下LUID_AND_ATTRIBUTES这个结构的内容,声明如下:
- typedef struct _LUID_AND_ATTRIBUTES
- {
- LUID Luid;
- DWORD Attributes;
- } 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
其原形如下:
- BOOL LookupPrivilegevalue(
- LPCTSTR lpSystemName, // system name
- LPCTSTR lpName, // privilege name
- PLUID lpLuid // locally unique identifier
- );
(1)第一个参数是系统的名称,如果是本地系统只要指明为NULL就可以了;
(2)第二个参数就是指明了权限的名称,如“SeDebugPrivilege”。
在Winnt.h中还定义了一些权限名称的宏,如:
- #define SE_BACKUP_NAME TEXT("SeBackupPrivilege")
- #define SE_RESTORE_NAME TEXT("SeRestorePrivilege")
- #define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege")
- #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.(从当前进程复制句柄到其他进程空间)
- BOOL DuplicateHandle(
- HANDLE hSourceProcessHandle, // handle to source process
- HANDLE hSourceHandle, // handle to duplicate
- HANDLE hTargetProcessHandle, // handle to target process
- LPHANDLE lpTargetHandle, // duplicate handle
- DWORD dwDesiredAccess, // requested access
- BOOL bInheritHandle, // handle inheritance option
- DWORD dwOptions // optional actions
- );
(1)在系统中,对象分两类:
核心对象和用户对象。
如进程对象,线程对象,文件映射 对象等就是核心对象;
而向窗口,菜单等都是用户对象.。
(2)两者是有差别的
(2、1)用于标示用户对象的句柄是系统唯一的,也就是说,一个进程 完全可以对另外一个进程中的用户对象进行操作,比如两个进程间通信的方法之一, 就是发送消息.正是由于窗口是用户对象,所以句柄是系统唯一,通过FindWindow, 得到另外一个进程的窗口句柄,然后用SendMessage(),让hWnd的窗口过程来处理消 息,实现了进程间的通信.
因此,对于用户对象,你根本不用DuplicateHandle(),直接 把句柄拿来用就行了;
(2、2)而核心对象则不一样。核心对象是为了加强系统的稳定性,因此,核心对象句柄是 进程相关的,在每一个进程中都有一个核心对象表,每一个对象的索引(不完全是)作为内核对象的句柄,从而实现进程相关.同一个对象在不同的进程中可能有不 同的索引,即句柄.
对核心对象进行操作时,系统还要进行安全检验,看一下你是否有权来操作这个对象.因此你不能同用户对象一样,直接把句柄拿过来用.比方 说,你想操作另一个进程中的文件映射对象,这个文件映射对象句柄在那个进程中假设是0x000001,但在你的进程中,很有可能0x00000001时表 示另一个核心对象,此时的操作就永远不会成功,甚至会产生灾难性的后果.此时,就有必要用DuplicateHandle().