2023-08-29 19:36:58 +02:00
|
|
|
#include "thread.h"
|
|
|
|
|
2023-09-27 15:10:12 +02:00
|
|
|
#include <intrin.h>
|
|
|
|
|
2023-08-29 19:36:58 +02:00
|
|
|
#include "callbacks.h"
|
2023-09-02 15:47:15 +02:00
|
|
|
#include "driver.h"
|
2024-08-01 06:21:53 +02:00
|
|
|
#include "pool.h"
|
2024-06-16 11:21:37 +02:00
|
|
|
|
|
|
|
#include "containers/tree.h"
|
2024-05-11 14:54:58 +02:00
|
|
|
#include "crypt.h"
|
2024-08-01 06:21:53 +02:00
|
|
|
#include "imports.h"
|
|
|
|
#include "session.h"
|
2023-08-29 19:36:58 +02:00
|
|
|
|
2024-07-22 12:43:09 +02:00
|
|
|
#include "lib/stdlib.h"
|
|
|
|
|
2023-10-09 09:34:30 +02:00
|
|
|
#ifdef ALLOC_PRAGMA
|
2024-04-13 10:23:14 +02:00
|
|
|
# pragma alloc_text(PAGE, DetectThreadsAttachedToProtectedProcess)
|
2024-05-05 15:59:00 +02:00
|
|
|
# pragma alloc_text(PAGE, DoesThreadHaveValidCidEntry)
|
2023-10-09 09:34:30 +02:00
|
|
|
#endif
|
|
|
|
|
2023-12-30 08:52:44 +01:00
|
|
|
BOOLEAN
|
2024-05-05 15:58:36 +02:00
|
|
|
DoesThreadHaveValidCidEntry(_In_ PETHREAD Thread)
|
2023-08-29 19:36:58 +02:00
|
|
|
{
|
2024-04-13 10:23:14 +02:00
|
|
|
PAGED_CODE();
|
|
|
|
|
2024-08-01 06:21:53 +02:00
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
|
|
HANDLE thread_id = NULL;
|
|
|
|
PETHREAD thread = NULL;
|
2024-04-13 10:23:14 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* PsGetThreadId simply returns ETHREAD->Cid.UniqueThread
|
|
|
|
*/
|
|
|
|
thread_id = ImpPsGetThreadId(Thread);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For each core on the processor, the first x threads equal to x cores
|
|
|
|
* will be assigned a cid equal to its equivalent core. These threads
|
|
|
|
* are generally executing the HLT instruction or some other boring
|
|
|
|
* stuff while the processor is not busy. The reason this is important
|
|
|
|
* is because passing in a handle value of 0 which, even though is a
|
|
|
|
* valid cid, returns a non success status meaning we mark it an invalid
|
|
|
|
* cid entry even though it is. To combat this we simply add a little
|
|
|
|
* check here. The problem is this can be easily bypassed by simply
|
|
|
|
* modifying the ETHREAD->Cid.UniqueThread identifier.. So while it isnt
|
|
|
|
* a perfect detection method for now it's good enough.
|
|
|
|
*/
|
|
|
|
if ((UINT64)thread_id < (UINT64)ImpKeQueryActiveProcessorCount(NULL))
|
2023-12-30 08:52:44 +01:00
|
|
|
return TRUE;
|
2024-04-13 10:23:14 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* PsLookupThreadByThreadId will use a threads id to find its cid entry,
|
|
|
|
* and return the pointer contained in the HANDLE_TABLE entry pointing
|
|
|
|
* to the thread object. Meaning if we pass a valid thread id which we
|
|
|
|
* retrieved above and dont receive a STATUS_SUCCESS the cid entry could
|
|
|
|
* potentially be removed or disrupted..
|
|
|
|
*/
|
|
|
|
status = ImpPsLookupThreadByThreadId(thread_id, &thread);
|
|
|
|
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
DEBUG_WARNING(
|
|
|
|
"Failed to lookup thread by id. PspCidTable entry potentially removed.");
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
2023-08-31 18:42:38 +02:00
|
|
|
}
|
|
|
|
|
2023-12-31 15:06:24 +01:00
|
|
|
/*
|
2024-04-13 06:40:51 +02:00
|
|
|
* I did not reverse this myself and previously had no idea how you would go
|
|
|
|
* about detecting KiAttachProcess so credits to KANKOSHEV for the find:
|
2023-12-31 15:06:24 +01:00
|
|
|
*
|
|
|
|
* https://github.com/KANKOSHEV/Detect-KeAttachProcess/tree/main
|
|
|
|
* https://doxygen.reactos.org/d0/dc9/procobj_8c.html#adec6dc539d4a5c0ee7d0f48e24ef0933
|
|
|
|
*
|
2024-04-13 06:40:51 +02:00
|
|
|
* To expand on his writeup a little, the offset that he provides is equivalent
|
|
|
|
* to PKAPC_STATE->Process. This is where KiAttachProcess writes the process
|
|
|
|
* that thread is attaching to when it's called. The APC_STATE structure holds
|
|
|
|
* relevant information about the thread's APC state and is quite important
|
|
|
|
* during context switch scenarios as it's how the thread determines if it has
|
|
|
|
* any APC's queued.
|
2023-12-31 15:06:24 +01:00
|
|
|
*/
|
2024-01-08 04:57:07 +01:00
|
|
|
STATIC VOID
|
2024-08-01 06:21:53 +02:00
|
|
|
DetectAttachedThreadsProcessCallback(
|
|
|
|
_In_ PTHREAD_LIST_ENTRY ThreadListEntry, _Inout_opt_ PVOID Context)
|
2023-09-02 10:54:04 +02:00
|
|
|
{
|
2024-04-13 10:23:14 +02:00
|
|
|
UNREFERENCED_PARAMETER(Context);
|
2023-09-13 12:06:25 +02:00
|
|
|
|
2024-08-01 06:21:53 +02:00
|
|
|
NTSTATUS status = STATUS_UNSUCCESSFUL;
|
|
|
|
PKAPC_STATE apc_state = NULL;
|
|
|
|
PEPROCESS protected_process = NULL;
|
|
|
|
UINT32 packet_size =
|
2024-05-11 14:54:58 +02:00
|
|
|
CryptRequestRequiredBufferLength(sizeof(ATTACH_PROCESS_REPORT));
|
2023-09-02 10:54:04 +02:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
SessionGetProcess(&protected_process);
|
2023-09-02 15:47:15 +02:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (!protected_process)
|
|
|
|
return;
|
2023-09-02 10:54:04 +02:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
apc_state = (PKAPC_STATE)((UINT64)ThreadListEntry->thread +
|
|
|
|
KTHREAD_APC_STATE_OFFSET);
|
2023-09-02 10:54:04 +02:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
/*
|
|
|
|
* We don't care if a thread owned by our protected process is attached
|
|
|
|
*
|
|
|
|
* todo: this is filterless and will just report anything, need to have
|
|
|
|
* a look into what processes actually attach to real games
|
|
|
|
*/
|
|
|
|
if (!(apc_state->Process == protected_process &&
|
|
|
|
ThreadListEntry->owning_process != protected_process)) {
|
|
|
|
return;
|
|
|
|
}
|
2023-09-02 15:47:15 +02:00
|
|
|
|
2024-08-01 06:21:53 +02:00
|
|
|
DEBUG_WARNING(
|
|
|
|
"Thread is attached to our protected process: %llx",
|
|
|
|
(UINT64)ThreadListEntry->thread);
|
2023-09-02 15:47:15 +02:00
|
|
|
|
2024-05-11 14:54:58 +02:00
|
|
|
PATTACH_PROCESS_REPORT report =
|
|
|
|
ImpExAllocatePool2(POOL_FLAG_NON_PAGED, packet_size, REPORT_POOL_TAG);
|
2023-09-02 15:47:15 +02:00
|
|
|
|
2024-04-13 10:23:14 +02:00
|
|
|
if (!report)
|
|
|
|
return;
|
2023-09-02 10:54:04 +02:00
|
|
|
|
2024-05-11 14:54:58 +02:00
|
|
|
INIT_REPORT_PACKET(report, REPORT_ILLEGAL_ATTACH_PROCESS, 0);
|
2024-05-05 12:42:22 +02:00
|
|
|
|
2024-08-01 06:21:53 +02:00
|
|
|
report->thread_id = ImpPsGetThreadId(ThreadListEntry->thread);
|
2024-04-13 10:23:14 +02:00
|
|
|
report->thread_address = ThreadListEntry->thread;
|
2024-04-13 10:06:59 +02:00
|
|
|
|
2024-05-11 14:54:58 +02:00
|
|
|
status = CryptEncryptBuffer(report, packet_size);
|
|
|
|
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
|
|
DEBUG_ERROR("CryptEncryptBuffer: %lx", status);
|
|
|
|
ImpExFreePoolWithTag(report, REPORT_POOL_TAG);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-11 17:27:18 +02:00
|
|
|
IrpQueueSchedulePacket(report, packet_size);
|
2023-09-02 10:54:04 +02:00
|
|
|
}
|
|
|
|
|
2023-12-13 05:06:27 +01:00
|
|
|
VOID
|
|
|
|
DetectThreadsAttachedToProtectedProcess()
|
2023-09-02 10:54:04 +02:00
|
|
|
{
|
2024-04-13 10:23:14 +02:00
|
|
|
PAGED_CODE();
|
|
|
|
DEBUG_VERBOSE("Detecting threads attached to our process...");
|
2024-06-16 10:04:28 +02:00
|
|
|
RtlRbTreeEnumerate(
|
2024-08-01 06:21:53 +02:00
|
|
|
GetThreadTree(),
|
|
|
|
DetectAttachedThreadsProcessCallback,
|
|
|
|
NULL);
|
2023-09-02 10:54:04 +02:00
|
|
|
}
|