multithreading - 访问自己的线程信息(delphi)

标签 multithreading delphi winapi

出于调试目的,我正在迭代自己应用程序的线程,并尝试报告线程时间(寻找恶意线程)。当我迭代线程时,如果使用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件事,可以打开一些内核对象:

  • 对象安全描述符
  • 调用者 token (如果存在则为线程 token ,否则为处理 token )

  • 通常线程可以打开自己的句柄,但也可以是异常(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/

    相关文章:

    c++ - `unique_ptr` 上的原子操作

    java - 如何在 Android 中运行后台任务

    delphi - 如何等待 TTimer 完成?

    Delphi:如何将接口(interface)实现委托(delegate)给子对象?

    winapi - Win32 No-MFC 中的消息映射

    .net - 如何减少 WPF 应用程序的内存使用量

    java - 为什么有两个运行线程而不是一个?

    forms - 如何使表单像屏幕一样 float 在工作区中(移动、调整大小、最大化、最小化)?

    c++ - 如何阻止 LWA_COLORKEY 绘制灰色轮廓?

    winapi - 所有调整大小操作的 Windows 消息