出于调试目的,我正在迭代自己应用程序的线程,并尝试报告线程时间(寻找恶意线程)。当我迭代线程时,如果使用threadId = GetCurrentThreadId
,则会拒绝访问。
这是演示该问题的代码示例(delphi):
program Project9;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows, System.SysUtils, TlHelp32;
type
TOpenThreadFunc = function(DesiredAccess: DWORD; InheritHandle: BOOL; ThreadID: DWORD): THandle; stdcall;
var
OpenThreadFunc: TOpenThreadFunc;
function OpenThread(id : DWORD) : THandle;
const
THREAD_GET_CONTEXT = $0008;
THREAD_QUERY_INFORMATION = $0040;
var
Kernel32Lib, ThreadHandle: THandle;
begin
Result := 0;
if @OpenThreadFunc = nil then
begin
Kernel32Lib := GetModuleHandle(kernel32);
OpenThreadFunc := GetProcAddress(Kernel32Lib, 'OpenThread');
end;
result := OpenThreadFunc(THREAD_QUERY_INFORMATION, False, id);
end;
procedure dumpThreads;
var
SnapProcHandle: THandle;
NextProc : Boolean;
TThreadEntry : TThreadEntry32;
Proceed : Boolean;
pid, tid : Cardinal;
h : THandle;
begin
pid := GetCurrentProcessId;
tid := GetCurrentThreadId;
SnapProcHandle := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); //Takes a snapshot of the all threads
Proceed := (SnapProcHandle <> INVALID_HANDLE_VALUE);
if Proceed then
try
TThreadEntry.dwSize := SizeOf(TThreadEntry);
NextProc := Thread32First(SnapProcHandle, TThreadEntry);//get the first Thread
while NextProc do
begin
if TThreadEntry.th32OwnerProcessID = PID then //Check the owner Pid against the PID requested
begin
write('Thread '+inttostr(TThreadEntry.th32ThreadID));
if (tid = TThreadEntry.th32ThreadID) then
write(' (this thread)');
h := OpenThread(TThreadEntry.th32ThreadID);
if h <> 0 then
try
writeln(': open ok');
finally
CloseHandle(h);
end
else
writeln(': '+SysErrorMessage(GetLastError));
end;
NextProc := Thread32Next(SnapProcHandle, TThreadEntry);//get the Next Thread
end;
finally
CloseHandle(SnapProcHandle);//Close the Handle
end;
end;
function DebugCtrlC(dwCtrlType : DWORD) :BOOL;
begin
writeln('ctrl-c');
dumpThreads;
end;
var
s : String;
begin
SetConsoleCtrlHandler(@DebugCtrlC, true);
try
writeln('enter anything to see threads, ''x'' to exit. or press ctrl-c to see threads');
repeat
readln(s);
if s <> '' then
dumpThreads;
until s = 'x';
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
按下ctrl-c时,该线程的访问被拒绝-为什么该线程无法获取自身的句柄,但可以访问该进程中的所有其他线程?
最佳答案
基于2件事,可以打开一些内核对象:
通常线程可以打开自己的句柄,但也可以是异常(exception),一种是-系统创建的线程,用于处理控制台控制信号。
复制的最低代码(c++):
HANDLE g_hEvent;
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
{
if (CTRL_C_EVENT == dwCtrlType)
{
if (HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION,
FALSE, GetCurrentThreadId()))
{
CloseHandle(hThread);
}
else GetLastError();
SetEvent(g_hEvent);
}
return TRUE;
}
并从控制台应用程序调用
if (g_hEvent = CreateEvent(0, TRUE, FALSE, 0))
{
if (SetConsoleCtrlHandler(HandlerRoutine, TRUE))
{
// send ctrl+c, for not manually do this
if (GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0))
{
WaitForSingleObject(g_hEvent, INFINITE);
}
SetConsoleCtrlHandler(HandlerRoutine, FALSE);
}
CloseHandle(g_hEvent);
}
可以在测试 View 中
OpenThread(THREAD_QUERY_INFORMATION, FALSE, GetCurrentThreadId())
失败,出现错误-ERROR_ACCESS_DENIED
为什么会这样呢?需要寻找线程安全描述符。简单的代码如下所示:
void DumpObjectSD(HANDLE hObject = GetCurrentThread())
{
ULONG cb = 0, rcb = 0x40;
static volatile UCHAR guz;
PVOID stack = alloca(guz);
PSECURITY_DESCRIPTOR psd = 0;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(psd = alloca(rcb - cb), stack);
}
if (GetKernelObjectSecurity(hObject,
OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION,
psd, cb, &rcb))
{
PWSTR sz;
if (ConvertSecurityDescriptorToStringSecurityDescriptor(psd, SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION|LABEL_SECURITY_INFORMATION, &sz, &rcb))
{
DbgPrint("%S\n", sz);
LocalFree(sz);
}
break;
}
} while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);
}
并从控制台处理程序线程和通常的(第一个线程)中进行调用以进行比较。
普通进程线程的SD可能如下所示:
对于未提升的过程:
O:S-1-5-21-*
D:(A;;0x1fffff;;;S-1-5-21-*)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;ME)
或用于提升权限(以管理员身份运行)
O:BA
D:(A;;0x1fffff;;;BA)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;HI)
但是,当这从处理程序线程(由系统自动创建)调用时-我们得到了另一个dacl:
对于未提升的:
O:BA
D:(A;;0x1fffff;;;S-1-5-21-*)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;SI)
对于高架:
O:BA
D:(A;;0x1fffff;;;BA)(A;;0x1fffff;;;SY)(A;;0x121848;;;S-1-5-5-0-LogonSessionId)
S:AI(ML;;NWNR;;;SI)
在
SYSTEM_MANDATORY_LABEL
中与此处不同S:AI(ML;;NWNR;;;SI)
此处的
"ML"
是SDDL_MANDATORY_LABEL
(SYSTEM_MANDATORY_LABEL_ACE_TYPE
)强制性标签权利:
"NW"
-SDDL_NO_WRITE_UP
(SYSTEM_MANDATORY_LABEL_NO_WRITE_UP
)"NR"
-SDDL_NO_READ_UP
(SYSTEM_MANDATORY_LABEL_NO_READ_UP
)并指向main-标签value(sid):
处理程序线程始终具有
"SI"
-SDDL_ML_SYSTEM
-系统完整性级别。而通常的线程具有
"ME"
-SDDL_MLMEDIUM
-中等完整性级别或"HI"
-SDDL_ML_HIGH
-以管理员身份运行时的高完整性级别如此-由于此线程具有比 token 中通常的进程完整性级别更高的完整性级别(System)(如果不是系统进程,则具有较高的完整性级别或波纹管,并且没有读取和写入权限)-我们无法使用读取或写入方式打开此线程访问,仅具有执行访问权限。
我们可以在
HandlerRoutine
中进行下一个测试-尝试使用MAXIMUM_ALLOWED
打开线程,并使用 NtQueryObject
查找授予访问权限(使用 ObjectBasicInformation
) if (HANDLE hThread = OpenThread(MAXIMUM_ALLOWED, FALSE, GetCurrentThreadId()))
{
OBJECT_BASIC_INFORMATION obi;
if (0 <= ZwQueryObject(hThread, ObjectBasicInformation, &obi, sizeof(obi), 0))
{
DbgPrint("[%08x]\n", obi.GrantedAccess);
}
CloseHandle(hThread);
}
我们到达这里:
[00101800]
的意思是:SYNCHRONIZE | THREAD_RESUME | THREAD_QUERY_LIMITED_INFORMATION
我们也可以查询
ObjectTypeInformation
并获取 GENERIC_MAPPING
作为线程对象。 OBJECT_BASIC_INFORMATION obi;
if (0 <= ZwQueryObject(hThread, ObjectBasicInformation, &obi, sizeof(obi), 0))
{
ULONG rcb, cb = (obi.TypeInfoSize + __alignof(OBJECT_TYPE_INFORMATION) - 1) & ~(__alignof(OBJECT_TYPE_INFORMATION) - 1);
POBJECT_TYPE_INFORMATION poti = (POBJECT_TYPE_INFORMATION)alloca(cb);
if (0 <= ZwQueryObject(hThread, ObjectTypeInformation, poti, cb, &rcb))
{
DbgPrint("a=%08x\nr=%08x\nw=%08x\ne=%08x\n",
poti->GenericMapping.GenericAll,
poti->GenericMapping.GenericRead,
poti->GenericMapping.GenericWrite,
poti->GenericMapping.GenericExecute);
}
}
并得到了
a=001fffff
r=00020048
w=00020437
e=00121800
因此,我们通常可以使用
GenericExecute
访问打开此线程,但00020000
(READ_CONTROL
)除外,因为GenericRead和GenericWrite和策略中的此访问权限-无读/写权限。但是对于几乎所有需要句柄(线程或通用)的api,我们都可以使用
GetCurrentThread()
-调用线程的伪句柄。当然,这只能用于当前线程。所以我们可以举个例子FILETIME CreationTime, ExitTime, KernelTime, UserTime;
GetThreadTimes(GetCurrentThread(), &CreationTime, &ExitTime, &KernelTime, &UserTime);
CloseHandle(GetCurrentThread());
也是有效的调用-使用此句柄调用CloseHandle函数无效。 (根本什么都不会)。并且此伪句柄已授予GENERIC_ALL
访问权限。因此,您的
OpenThread
例程可以检查线程ID(如果它等于GetCurrentThreadId()
),只需返回GetCurrentThread()
。我们也可以打电话
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, 0, DUPLICATE_SAME_ACCESS);
这对于该线程也将很好地工作。但是通常使用
GetCurrentThread()
就足够了关于multithreading - 访问自己的线程信息(delphi),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50093099/