cheat-engine/DBKKernel/ultimap.c

739 lines
No EOL
18 KiB
C

/*
Ultimap implements the recording of all the branches in the target process
Requires dbvm for process selection
*/
#pragma warning( disable: 4100 4101 4213)
#include <ntifs.h>
#include "ultimap.h"
#include "vmxhelper.h"
#include "DBKFunc.h"
#include <windef.h>
#include "ultimap2\apic.h"
JUMPBACK perfmonJumpBackLocation;
/*
#ifdef AMD64
volatile PAPIC APIC_BASE=0; //(PAPIC)0xfffffffffffe0000;
#else
volatile PAPIC APIC_BASE=0; //(PAPIC)0xfffe0000;
#endif
*/
BOOL SaveToFile; //If set it will save the results to a file instead of sending a message to the usermode app that is watching the data
HANDLE FileHandle;
int MaxDataBlocks=1;
KSEMAPHORE DataBlockSemaphore; //governs how many events can be active at a time
FAST_MUTEX DataBlockMutex; //when a thread passes the semaphore this is used to pick a DataBlock
typedef struct
{
BOOL Available;
PBTS Data;
int DataSize;
int CpuID;
KEVENT DataReady;
} _DataBlock;
_DataBlock *DataBlock;
PVOID *DataReadyPointerList;
int perfmon_interrupt_centry(void);
/* use apic.* now
#define MSR_IA32_APICBASE 0x0000001b
void setup_APIC_BASE(void)
{
PHYSICAL_ADDRESS Physical_APIC_BASE;
DbgPrint("Fetching the APIC base\n");
Physical_APIC_BASE.QuadPart=readMSR(MSR_IA32_APICBASE) & 0xFFFFFFFFFFFFF000ULL;
DbgPrint("Physical_APIC_BASE=%p\n", Physical_APIC_BASE.QuadPart);
APIC_BASE = (PAPIC)MmMapIoSpace(Physical_APIC_BASE, sizeof(APIC), MmNonCached);
DbgPrint("APIC_BASE at %p\n", APIC_BASE);
}
void clean_APIC_BASE(void)
{
if (APIC_BASE)
MmUnmapIoSpace((PVOID)APIC_BASE, sizeof(APIC));
}*/
void ultimap_flushBuffers_all(UINT_PTR param)
{
DbgPrint("Calling perfmon_interrupt_centry() manually\n");
if (DS_AREA[cpunr()]) //don't call if ultimap has been disabled
{
perfmon_interrupt_centry();
enableInterrupts(); //the handler disables it on exit so re-enable it
}
}
void ultimap_flushBuffers(void)
{
//call this when the buffer of the current cpu needs to be flushed and handled
int i;
int count;
DbgPrint("ultimap_flushBuffers\n");
//what it does:
//for each cpu emulate a "buffer filled" event.
//the handler then copies all the current data to a temporary buffer and signals the worker thread to deal with it. If there is no available worker thread it waits
forEachCpuPassive(ultimap_flushBuffers_all,0);
DbgPrint("ultimap_flushBuffers_all has returned\n");
//it returned and all worker thread are currently working on this data (it only returns when it has send a worker to work)
//now wait for all workers to finish
//do this by aquiring all semaphore slots and waiting for them to return again
//forEachCpuPassive(ultimap_flushBuffers_all,0);
//DbgPrint("ultimap_flushBuffers_all has returned a second time\n"); //this means that the previous blocks have been dealt with
//actually... no, this is no guarantee. Now that the buffers are empty handling is so fast that while block 2,3,4,5 and 6 are still being handled block 1 can become available multiple times
}
NTSTATUS ultimap_continue(PULTIMAPDATAEVENT data)
/*
Called from usermode to signal that the data has been handled
*/
{
DbgPrint("ultimap_continue\n");
MmUnmapLockedPages((PVOID)(UINT_PTR)data->Address, (PMDL)(UINT_PTR)data->Mdl);
IoFreeMdl((PMDL)(UINT_PTR)data->Mdl);
ExFreePool((PVOID)(UINT_PTR)data->KernelAddress); //this memory is not needed anymore
if (DataBlock)
DataBlock[data->Block].Available=TRUE;
KeReleaseSemaphore(&DataBlockSemaphore, 1, 1, FALSE); //Let the next block go through
DbgPrint("Released semaphore\n");
return STATUS_SUCCESS;
}
NTSTATUS ultimap_waitForData(ULONG timeout, PULTIMAPDATAEVENT data)
/*
Called from usermode to wait for data
*/
{
NTSTATUS r;
LARGE_INTEGER wait;
PKWAIT_BLOCK waitblock;
if (DataBlock)
{
waitblock=ExAllocatePool(NonPagedPool, MaxDataBlocks*sizeof(KWAIT_BLOCK));
wait.QuadPart=-10000LL * timeout;
//Wait for the events in the list
//If an event is triggered find out which one is triggered, then map that block into the usermode space and return the address and block
//That block will be needed to continue
if (timeout==0xffffffff) //infinite wait
r=KeWaitForMultipleObjects(MaxDataBlocks, DataReadyPointerList, WaitAny, UserRequest, UserMode, TRUE, NULL, waitblock);
else
r=KeWaitForMultipleObjects(MaxDataBlocks, DataReadyPointerList, WaitAny, UserRequest, UserMode, TRUE, &wait, waitblock);
ExFreePool(waitblock);
data->Block=r-STATUS_WAIT_0;
if (data->Block <= MaxDataBlocks)
{
//Map this block to usermode
ExAcquireFastMutex(&DataBlockMutex);
if (DataBlock)
{
data->KernelAddress=(UINT64)DataBlock[data->Block].Data;
data->Mdl=(UINT64)IoAllocateMdl(DataBlock[data->Block].Data, DataBlock[data->Block].DataSize, FALSE, FALSE, NULL);
if (data->Mdl)
{
MmBuildMdlForNonPagedPool((PMDL)(UINT_PTR)data->Mdl);
data->Address=(UINT_PTR)MmMapLockedPagesSpecifyCache((PMDL)(UINT_PTR)data->Mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority);
if (data->Address)
{
data->Size=DataBlock[data->Block].DataSize;
data->CpuID=DataBlock[data->Block].CpuID;
r=STATUS_SUCCESS;
}
else
r=STATUS_UNSUCCESSFUL;
}
else
r=STATUS_UNSUCCESSFUL;
}
else
r=STATUS_UNSUCCESSFUL;
ExReleaseFastMutex(&DataBlockMutex);
return r;
}
else
return STATUS_UNSUCCESSFUL;
}
else
return STATUS_UNSUCCESSFUL;
}
/*
void apic_clearPerfmon()
{
APIC_BASE->LVT_Performance_Monitor.a = APIC_BASE->LVT_Performance_Monitor.a & 0xff;
APIC_BASE->EOI.a = 0;
}
*/
void ultimap_cleanstate()
{
apic_clearPerfmon();
}
int perfmon_interrupt_centry(void)
{
KIRQL old = PASSIVE_LEVEL;
int changedIRQL = 0;
void *temp;
int causedbyme=(DS_AREA[cpunr()]->BTS_IndexBaseAddress>=DS_AREA[cpunr()]->BTS_InterruptThresholdAddress);
UINT_PTR blocksize;
DbgPrint("perfmon_interrupt_centry\n", cpunr());
if (causedbyme)
ultimap_cleanstate();
blocksize=(UINT_PTR)(DS_AREA[cpunr()]->BTS_IndexBaseAddress-DS_AREA[cpunr()]->BTS_BufferBaseAddress);
{
if (KeGetCurrentIrql() < DISPATCH_LEVEL)
{
//When called by the pre-emptive caller
changedIRQL = 1;
old = KeRaiseIrqlToDpcLevel();
}
DbgPrint("Entry cpunr=%d\n", cpunr());
DbgPrint("Entry threadid=%d\n", PsGetCurrentThreadId());
temp=ExAllocatePool(NonPagedPool, blocksize);
if (temp)
{
RtlCopyMemory(temp, (PVOID *)(UINT_PTR)DS_AREA[cpunr()]->BTS_BufferBaseAddress, blocksize);
DbgPrint("temp=%p\n", temp);
DS_AREA[cpunr()]->BTS_IndexBaseAddress=DS_AREA[cpunr()]->BTS_BufferBaseAddress; //don't reset on alloc error
}
else
{
DbgPrint("ExAllocatePool has failed\n");
KeLowerIrql(old);
disableInterrupts();
return causedbyme;
}
if (changedIRQL)
KeLowerIrql(old);
//should be passive mode, taskswitches and cpu switches will happen now (When this returns, I may not be on the same interrupt as I was when I started)
if (SaveToFile)
{
IO_STATUS_BLOCK iosb;
NTSTATUS r;
//Instead of sending the data to a usermode app it was chosen to store the data to a file for later usage
DbgPrint("Writing buffer to disk\n");
r=ZwWriteFile(FileHandle, NULL, NULL, NULL, &iosb, temp, (ULONG)blocksize, NULL, NULL);
DbgPrint("Done Writing. Result=%x\n", r);
}
else
{
DbgPrint("Waiting till there is a block free\n");
//When all workers are busy do not continue
if ((DataBlock) && (KeWaitForSingleObject(&DataBlockSemaphore, Executive, KernelMode, FALSE, NULL) == STATUS_SUCCESS))
{
int currentblock;
int i;
//Enter a critical section and choose a block
DbgPrint("Acquired semaphore. Now picking a usable datablock\n");
ExAcquireFastMutex(&DataBlockMutex);
DbgPrint("Acquired mutex. Looking for a Datablock that can be used\n");
if (DataBlock)
{
currentblock=-1;
for (i=0; i< MaxDataBlocks; i++)
{
if (DataBlock[i].Available) //look for a block that is set as available
{
currentblock=i;
DataBlock[currentblock].Available=FALSE; //not available anymore
break;
}
}
}
else currentblock=-1;
if (currentblock>=0)
{
DbgPrint("Using datablock %d\n", currentblock);
DataBlock[currentblock].Data=temp;
DataBlock[currentblock].DataSize=(int)blocksize;
DataBlock[currentblock].CpuID=cpunr();
DbgPrint("Calling KeSetEvent/KeWaitForSingleObject\n");
KeSetEvent(&DataBlock[currentblock].DataReady, 1, FALSE); //Trigger a worker thread to start working
}
ExReleaseFastMutex(&DataBlockMutex);
//DbgPrint("Released mutex\n");
}
else
{
DbgPrint("if ((DataBlock) && (KeWaitForSingleObject(&DataBlockSemaphore, Executive, KernelMode, FALSE, NULL) == STATUS_SUCCESS)) failed\n");
}
}
//and return to the caller process
disableInterrupts();
return causedbyme;
}
}
#ifdef AMD64
extern void perfmon_interrupt();
#else
_declspec( naked ) void perfmon_interrupt( void )
{
__asm{
cld
push ebp
mov ebp,esp
//store state
pushad
xor eax,eax
mov ax,ds
push eax
mov ax,es
push eax
mov ax,fs
push eax
mov ax,gs
push eax
mov ax,0x23 //0x10 should work too, but even windows itself is using 0x23
mov ds,ax
mov es,ax
mov gs,ax
mov ax,0x30
mov fs,ax
call perfmon_interrupt_centry
cmp eax,1 //set flag
//restore state
pop gs
pop fs
pop es
pop ds
popad
pop ebp
je skip_original_perfmon
jmp far [perfmonJumpBackLocation]
skip_original_perfmon:
// commented out: I don't think a APIC interrupt has an errorcode.... add esp,4 //undo errorcode push
iretd
}
}
#endif
VOID ultimap_pause_dpc(IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgumen1, IN PVOID SystemArgument2)
{
vmx_ultimap_pause();
}
void ultimap_pause(void)
{
forEachCpu(ultimap_pause_dpc, NULL, NULL, NULL, NULL);
}
VOID ultimap_resume_dpc(IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgumen1, IN PVOID SystemArgument2)
{
vmx_ultimap_resume();
}
void ultimap_resume(void)
{
forEachCpu(ultimap_resume_dpc, NULL, NULL, NULL, NULL);
}
VOID ultimap_disable_dpc(IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgumen1, IN PVOID SystemArgument2)
{
int i;
//DbgPrint("ultimap_disable_dpc()\n");
if (vmxusable)
{
i=vmx_ultimap_disable();
if (DS_AREA[cpunr()])
{
ExFreePool(DS_AREA[cpunr()]);
DS_AREA[cpunr()]=NULL;
}
}
}
void ultimap_disable(void)
{
void *clear = NULL;
if (DataBlock)
{
int i;
forEachCpu(ultimap_disable_dpc, NULL, NULL, NULL, NULL);
if (SaveToFile && FileHandle)
{
ZwClose(FileHandle);
FileHandle=NULL;
}
//all logging should have stopped now
//Trigger all events waking up each thread that was waiting for the events
ExAcquireFastMutex(&DataBlockMutex);
for (i=0; i<MaxDataBlocks; i++)
KeSetEvent(&DataBlock[i].DataReady,0, FALSE);
ExFreePool(DataBlock);
DataBlock=NULL;
if (DataReadyPointerList)
{
ExFreePool(DataReadyPointerList);
DataReadyPointerList=NULL;
}
ExReleaseFastMutex(&DataBlockMutex);
HalSetSystemInformation(HalProfileSourceInterruptHandler, sizeof(PVOID*), &clear); //unhook the perfmon interrupt
}
}
VOID ultimap_setup_dpc(IN struct _KDPC *Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2)
/*
initializes ultimap. If the DS_AREA_SIZE is bigger than 0 it will allocate the required region (the usual option, but if not used it could be a LBR only thing)
Call this for each processor
*/
{
struct
{
UINT64 cr3;
UINT64 dbgctl_msr;
int DS_AREA_SIZE;
} *params;
params=DeferredContext;
DS_AREA_SIZE=params->DS_AREA_SIZE;
if (DS_AREA_SIZE == 0)
{
DbgPrint("DS_AREA_SIZE==0\n");
return;
}
DbgPrint("ultimap(%I64x, %I64x, %d)", (UINT64)params->cr3, (UINT64)params->dbgctl_msr, params->DS_AREA_SIZE);
DS_AREA[cpunr()]=NULL;
if (params->DS_AREA_SIZE)
{
DS_AREA[cpunr()]=ExAllocatePool(NonPagedPool, params->DS_AREA_SIZE);
if (DS_AREA[cpunr()] == NULL)
{
DbgPrint("ExAllocatePool failed\n");
return;
}
RtlZeroMemory(DS_AREA[cpunr()], params->DS_AREA_SIZE);
DbgPrint("DS_AREA[%d]=%p", cpunr(), DS_AREA[cpunr()]);
//Initialize the DS_AREA
DS_AREA[cpunr()]->BTS_BufferBaseAddress=(QWORD)(UINT_PTR)DS_AREA[cpunr()]+sizeof(DS_AREA_MANAGEMENT);
DS_AREA[cpunr()]->BTS_BufferBaseAddress+=sizeof(BTS);
DS_AREA[cpunr()]->BTS_BufferBaseAddress-=DS_AREA[cpunr()]->BTS_BufferBaseAddress % sizeof(BTS);
DS_AREA[cpunr()]->BTS_IndexBaseAddress=DS_AREA[cpunr()]->BTS_BufferBaseAddress;
DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress=(QWORD)(UINT_PTR)DS_AREA[cpunr()]+params->DS_AREA_SIZE-sizeof(BTS);
DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress-=DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress % sizeof(BTS);
DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress++;
DS_AREA[cpunr()]->BTS_InterruptThresholdAddress=(DS_AREA[cpunr()]->BTS_AbsoluteMaxAddress-1) - 4*sizeof(BTS);
}
if (params->dbgctl_msr & (1 << 8))
{
//hook the perfmon interrupt. First get the interrupt assigned (usually 0xfe, but let's be sure and read it from the apic)
int perfmonIVT=(APIC_BASE->LVT_Performance_Monitor.a) & 0xff;
DbgPrint("APIC_BASE->LVT_Performance_Monitor.a=%x\n", APIC_BASE->LVT_Performance_Monitor.a);
if (perfmonIVT==0) //if not setup at all then set it up now
perfmonIVT=0xfe;
APIC_BASE->LVT_Performance_Monitor.a=perfmonIVT; //clear mask flag if it was set
DbgPrint("APIC_BASE->LVT_Performance_Monitor.a=%x\n", APIC_BASE->LVT_Performance_Monitor.a);
/*
if (inthook_HookInterrupt((unsigned char)perfmonIVT, getCS(), (ULONG_PTR)perfmon_interrupt, &perfmonJumpBackLocation))
DbgPrint("Interrupt hooked\n");
else
DbgPrint("Failed to hook interrupt\n");
*/
}
//and finally activate the mapping
if (vmxusable)
{
vmx_ultimap((UINT_PTR)params->cr3, params->dbgctl_msr, DS_AREA[cpunr()]);
}
else
{
DbgPrint("vmxusable is false. So no ultimap for you!!!\n");
}
}
void ultimapapc(PKAPC Apc, PKNORMAL_ROUTINE NormalRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
EFLAGS e = getEflags();
DbgPrint("ultimapapc call for cpu %d ( IF=%d IRQL=%d)\n", KeGetCurrentProcessorNumber(), e.IF, KeGetCurrentIrql());
DbgPrint("SystemArgument1=%x\n", *(PULONG)SystemArgument1);
DbgPrint("tid=%x\n", PsGetCurrentThreadId());
DbgPrint("Apc=%p\n", Apc);
}
void ultimapapcnormal(PVOID arg1, PVOID arg2, PVOID arg3)
{
EFLAGS e = getEflags();
DbgPrint("ultimapapcnormal call for cpu %d ( IF=%d IRQL=%d)\n", KeGetCurrentProcessorNumber(), e.IF, KeGetCurrentIrql());
DbgPrint("tid=%x\n", PsGetCurrentThreadId());
ultimap_flushBuffers();
return;
}
KAPC kApc[128];
volatile LONG apcnr = 0;
void perfmon_hook(__in struct _KINTERRUPT *Interrupt, __in PVOID ServiceContext)
{
int i = InterlockedIncrement(&apcnr) % 128;
EFLAGS e = getEflags();
DbgPrint("permon_hook call for cpu %d ( IF=%d IRQL=%d)\n", KeGetCurrentProcessorNumber(), e.IF, KeGetCurrentIrql());
DbgPrint("kApc=%p\n", &kApc);
//switch buffer pointers
//call DPC for ultimap for this cpu
//todo: if this is buggy use a dpc instead to create the apc. (slower)
KeInitializeApc(&kApc[i],
(PKTHREAD)PsGetCurrentThread(),
0,
(PKKERNEL_ROUTINE)ultimapapc,
NULL,
(PKNORMAL_ROUTINE)ultimapapcnormal,
KernelMode,
0
);
KeInsertQueueApc(&kApc[i], NULL, NULL, 0);
DbgPrint("after KeInsertQueueApc");
//perfmon_interrupt_centry();
ultimap_cleanstate();
DbgPrint("permon_return");
}
void *pperfmon_hook = (void*)perfmon_hook;
NTSTATUS ultimap(UINT64 cr3, UINT64 dbgctl_msr, int _DS_AREA_SIZE, BOOL savetofile, WCHAR *filename, int handlerCount)
{
struct
{
UINT64 cr3;
UINT64 dbgctl_msr;
int DS_AREA_SIZE;
} params;
int i;
if (handlerCount>64)
return STATUS_UNSUCCESSFUL;
//create file
SaveToFile=savetofile;
if (SaveToFile)
{
UNICODE_STRING usFile;
OBJECT_ATTRIBUTES oaFile;
IO_STATUS_BLOCK iosb;
NTSTATUS r;
RtlInitUnicodeString(&usFile, filename);
InitializeObjectAttributes(&oaFile,&usFile, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL,NULL);
DbgPrint("Creating file %S", usFile.Buffer);
FileHandle=0;
r=ZwCreateFile(&FileHandle,SYNCHRONIZE|FILE_READ_DATA|FILE_APPEND_DATA | GENERIC_ALL,&oaFile,&iosb,0,FILE_ATTRIBUTE_NORMAL,0,FILE_SUPERSEDE, FILE_SEQUENTIAL_ONLY | FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);
DbgPrint("ZwCreateFile=%x\n", r);
}
MaxDataBlocks=handlerCount;
KeInitializeSemaphore(&DataBlockSemaphore, MaxDataBlocks, MaxDataBlocks);
ExInitializeFastMutex(&DataBlockMutex);
//Datablock inits
DataBlock=ExAllocatePool(NonPagedPool, sizeof(_DataBlock) * MaxDataBlocks);
DataReadyPointerList=ExAllocatePool(NonPagedPool, sizeof(PVOID) * MaxDataBlocks);
RtlZeroMemory(DataBlock, sizeof(_DataBlock) * MaxDataBlocks);
RtlZeroMemory(DataReadyPointerList, sizeof(PVOID) * MaxDataBlocks);
if ((DataBlock) && (DataReadyPointerList))
{
NTSTATUS r;
for (i=0; i< MaxDataBlocks; i++)
{
//DataBlock[i]->
DataBlock[i].Data=NULL;
KeInitializeEvent(&DataBlock[i].DataReady, SynchronizationEvent , FALSE);
DataBlock[i].Available=TRUE;
DataReadyPointerList[i]=&DataBlock[i].DataReady;
}
params.cr3=cr3;
params.dbgctl_msr=dbgctl_msr;
params.DS_AREA_SIZE=_DS_AREA_SIZE;
r=HalSetSystemInformation(HalProfileSourceInterruptHandler, sizeof(PVOID*), &pperfmon_hook); //hook the perfmon interrupt
DbgPrint("HalSetSystemInformation returned %x\n", r);
forEachCpu(ultimap_setup_dpc, &params, NULL, NULL, NULL);
return STATUS_SUCCESS;
}
else
{
DbgPrint("Failure allocating DataBlock and DataReadyPointerList\n");
return STATUS_MEMORY_NOT_ALLOCATED;
}
}