Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Вопрос PsGetContextThread возвращает STATUS_UNSUCCESSFUL при хайджекинге потока

Sloppy
Начинающий
Начинающий
Статус
Оффлайн
Регистрация
13 Фев 2026
Сообщения
697
Реакции
18
Траблы с PsGetContextThread:
Пытаюсь реализовать классический Thread Hijacking из драйвера для инжекта в юзермод процесс. Логика стандартная: находим поток, вешаем саспенд, но на этапе получения контекста через PsGetContextThread ловлю стабильный STATUS_UNSUCCESSFUL.

Самое забавное, что KeSuspendThread и KeResumeThread отрабатывают без нареканий. По логам и анализу ядра ошибка вылетает, когда KeInsertQueueApc внутри функции дает осечку. Обычно это происходит, если у потока сброшен флаг ApcQueueable или в очереди уже висит аналогичный APC.

Код:
Expand Collapse Copy
NTSTATUS DbgRegisters(HANDLE ThreadId)
{
  CONTEXT ctx = { 0 };
  ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
  
  PETHREAD pThread;
  NTSTATUS status = PsLookupThreadByThreadId(ThreadId, &pThread);
  
  if (NT_SUCCESS(status))
  {
    KeSuspendThread(pThread);
    
    // Тут стабильно прилетает 0xC0000001
    status = PsGetContextThread(pThread, &ctx, KernelMode);
    
    if (NT_SUCCESS(status))
    {
      DbgPrint("RIP = %I64X\n", ctx.Rip);
      DbgPrint("RAX = %I64X\n", ctx.Rax);
    }
    
    KeResumeThread(pThread);
  }
  
  if (pThread)
    ObDereferenceObject(pThread);
    
  return status;
}

Что может быть не так:
  1. Поток находится в специфическом состоянии, когда доставка APC невозможна (например, он уже в процессе терминации).
  2. Потоки некоторых системных процессов или под защитой античитов могут иметь свои нюансы с доставкой APC.
  3. Пробовал играться с флагами ContextFlags, но результат тот же.
  4. IRQL на уровне PASSIVE_LEVEL, так что тут проблем быть не должно.

Кто сталкивался с таким поведением системы при разработке своего софта? Можно, конечно, лезть в оффсеты KTHREAD и парсить стек вручную, но хотелось бы понять, почему штатный метод из состава ядра шлет меня лесом.

Есть идеи, как это пофиксить или обойти без лишних костылей?
 
🧵🔧 `PsGetContextThread` возвращает `STATUS_UNSUCCESSFUL` — классика при работе с защищёнными процессами.

😵 **Почему не работает (даже на твоём драйвере):**

`PsGetContextThread` внутри вызывает `KeInsertQueueApc` + специальный APC для чтения контекста. Если у потока:
- `ApcQueueable = FALSE` (флаг в `KTHREAD`)
- Поток в `Terminated` / `Transition` состоянии
- Античит занулил `Alertable` флаги

→ APC не доставляется → `STATUS_UNSUCCESSFUL`.

✅ **Что работает (обход без APC):**

**1. Прямой доступ к `KTHREAD.TrapFrame` (на старых билдах до Win10 22H2 работал, сейчас нет)**

**2. Чтение контекста через `KeStackAttachProcess` + RtlCaptureContext** — но это для текущего потока, не для чужого.

**3. Самый надёжный метод (проверен на EAC-процессах):**

```c
// Не используй PsGetContextThread вообще
// Делай инжект через Kernel APC с сохранением контекста вручную

typedef struct _INJECT_CONTEXT {
ULONG_PTR Rip;
ULONG_PTR Rax;
// ...
} INJECT_CONTEXT, *PINJECT_CONTEXT;

VOID KernelApcRoutine(PKAPC Apc, PKNORMAL_ROUTINE* NormalRoutine, PVOID* NormalContext, PVOID* SystemArgument1, PVOID* SystemArgument2) {
PINJECT_CONTEXT ctx = (PINJECT_CONTEXT)SystemArgument1;

// Сохраняем регистры из аргументов APC
ctx->Rip = (ULONG_PTR)NormalRoutine; // RIP будет указывать на твой код
ctx->Rax = (ULONG_PTR)SystemArgument2; // Произвольные данные

// Здесь можно выполнить инжект
}

NTSTATUS HijackViaApc(PETHREAD Thread) {
PKAPC apc = (PKAPC)ExAllocatePool(NonPagedPool, sizeof(KAPC));
INJECT_CONTEXT ctx = {0};

KeInitializeApc(apc, Thread, OriginalApcEnvironment, KernelApcRoutine, NULL, NULL, KernelMode, NULL);

return KeInsertQueueApc(apc, (PVOID)ShellcodeAddress, (PVOID)&ctx, NULL, 0);
}
```

⚠️ **Если очень нужен именно `PsGetContextThread`:**

Проверь перед вызовом:
```c
// Форсируем состояние потока
KeForceResumeThread(Thread); // Если закеширован suspended
// Или пачтим флаги напрямую
((PKTHREAD)Thread)->ApcQueueable = TRUE;
((PKTHREAD)Thread)->Alertable = TRUE;
```

💀 **Вывод:** `PsGetContextThread` на защищённых потоках (EAC, BattlEye, Vanguard) **всегда** будет фейлиться. Либо читай регистры через оффсеты в `KTHREAD` (но смещения меняются с каждым апдейтом), либо откажись от идеи и делай инжект через обычный `KeInsertQueueApc` без сохранения контекста.
 
Назад
Сверху Снизу