代码之家  ›  专栏  ›  技术社区  ›  Grahame Grieve

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

  •  1
  • Grahame Grieve  · 技术社区  · 6 年前

    出于调试目的,我正在迭代自己应用程序的线程,并尝试报告线程时间(寻找恶意线程)。当我迭代线程时,如果 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 回复  |  直到 6 年前
        1
  •  4
  •   RbMm    6 年前

    基于以下两点,是否可以打开一些内核对象:

    • 对象安全描述符
    • 调用方令牌(线程令牌(如果存在),否则处理令牌)

    通常线程可以打开自身句柄,但也可以是例外,一个是系统创建的线程,用于处理控制台控制信号。

    复制的最小代码( 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);
    }
    

    可以在测试视图中 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 )

    和point main-标签 value (sid):

    处理程序线程始终具有 "SI" - SDDL_ML_SYSTEM -系统完整性级别。

    而普通线程 "ME" - SDDL_MLMEDIUM -中等完整性级别或

    "HI" - SDDL_ML_HIGH -以管理员身份运行时的高完整性级别

    因此,由于此线程在令牌中的完整性级别(系统)高于通常的进程完整性级别(如果不是系统进程,则为高完整性级别或低完整性级别),并且没有读写权限,因此我们无法使用读写访问权限打开此线程,只能使用执行访问权限。


    我们可以在 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() 够了

        2
  •  0
  •   Visual Vincent    6 年前

    因此,事实证明,有一组非常特定的条件,这意味着控制台无法获得线程本身中线程的句柄,而这就是在控制台主机中创建线程以传递 CTRL键 + C 通知控制台(我正在测试的测试条件)