Получение доступа к собственной информации о потоке (delphi) - PullRequest
0 голосов
/ 30 апреля 2018

В целях отладки я перебираю потоки моего собственного приложения и пытаюсь сообщить о времени потоков (в поисках мошеннических потоков). Когда я перебираю потоки, я получаю отказ в доступе, если 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 ]

0 голосов
/ 30 апреля 2018

- некоторые объекты ядра могут быть открыты на основе 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_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 - метка значение (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 могла проверить идентификатор потока - если он равен GetCurrentThreadId() - просто вернуть GetCurrentThread().

также мы можем позвонить

DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, 0, DUPLICATE_SAME_ACCESS);

это также будет хорошо работать для этой темы. однако обычного использования GetCurrentThread() достаточно

0 голосов
/ 30 апреля 2018

Так что на самом деле оказывается, что существует очень специфический набор условий, который означает, что консоль не может получить дескриптор потока в самом потоке - и именно тогда поток создается в хосте консоли для передачи CTRL + C уведомление консоли (условия тестирования, в которых я тестировал)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...