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

格雷厄姆·格里夫(Grahame Grieve)

出于调试目的,我迭代了我自己的应用程序的线程,并尝试报告线程时间(寻找恶意线程)。当我迭代线程时,如果,访问将被拒绝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时,该线程的访问被拒绝-为什么该线程无法获取自身的句柄,但可以访问该进程中的所有其他线程?

RbMm

基于2件事,可以打开一些内核对象:

  • 对象安全描述符
  • 调用者令牌(如果存在则为线程令牌,否则为进程令牌)

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

最小复制代码(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_LABELSYSTEM_MANDATORY_LABEL_ACE_TYPE

强制性标签权利:

"NW"- SDDL_NO_WRITE_UPSYSTEM_MANDATORY_LABEL_NO_WRITE_UP

"NR"- SDDL_NO_READ_UPSYSTEM_MANDATORY_LABEL_NO_READ_UP

和主要点-标签(SID):

处理器线程总是有"SI"- SDDL_ML_SYSTEM-系统完整性级别。

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

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

如此-由于此线程具有比令牌中通常的进程完整性级别更高的完整性级别(System)(如果不是系统进程,则具有较高的完整性级别或波纹管,并且没有读取和写入权限)-我们无法使用读取或写入方式打开此线程访问,仅具有执行访问权限。


我们可以进行下一个测试HandlerRoutine-尝试使用打开线程,MAXIMUM_ALLOWED并使用NtQueryObject(use 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使用00020000READ_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()就足够了

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章