I need your help in implementing the “Find out what writes to this address” function among cheat engine functions. I will explain by giving you an example of what I want. 2. Let’s say the address of the AMMO is “0x401500”. And when i fire a gun, the AMMO value is reduced by one. And let’s say the memory address that is performing that reduction is “0x501000.” When the “0x501000” address is implemented in this => dec [0x401500], the purpose is to find the “0x501000” address. value of AMMO 0x401500 => 0xA(10bullets) 0x501500 => dec [0x401500] how can i find 0x501500 address? What should I do? Do I have to implement the hardware breakpoint? Do I have to modify the “DR0~4,DR7” register directly?
As an application, you wouldn’t do that. The Windows debugger engine does have methods available for setting breakpoints using the debug registers, and it is possible for you to have a separate process that acts as a debugger for your application, but using the debug registers directly in an application is not a supported scenario.
**Thanks you for wait to me
i was just try that for Hardware Breakpoint
Following my process →
**
Number 1:
"Find APIs → ‘PsGetContextThread()’ , ‘PsSetContextThread()’ "
CODE:
typedef NTSTATUS(*PSGETCONTEXTTHREAD) ( PETHREAD Thread, PCONTEXT TheardContext, KPROCESSOR_MODE Mode );
typedef NTSTATUS(*PSSETCONTEXTTHREAD) (PETHREAD Thread, PCONTEXT TheardContext, KPROCESSOR_MODE Mode);
NTSTATUS DriverEntry(PDRIVER_OBJECT driverobject, PUNICODE_STRING registrypath) {
UNREFERENCED_PARAMETER(driverobject);
UNREFERENCED_PARAMETER(registrypath);
NTSTATUS status = STATUS_SUCCESS;
PSGETCONTEXTTHREAD PsGetContextThread_ = NULL;
PSSETCONTEXTTHREAD PsSetContextThread_ = NULL;
UNICODE_STRING sample;
RtlInitUnicodeString(&sample, L"PsGetContextThread");
PsGetContextThread_ = (PSGETCONTEXTTHREAD)MmGetSystemRoutineAddress(&sample);
if (PsGetContextThread_ == NULL) {
return STATUS_UNSUCCESSFUL;
}
RtlInitUnicodeString(&sample, L"PsSetContextThread");
PsSetContextThread_ = (PSSETCONTEXTTHREAD)MmGetSystemRoutineAddress(&sample);
if (PsSetContextThread_ == NULL) {
return STATUS_UNSUCCESSFUL;
}
Number 2:
**"Catch the target process "
So, my process target is ac_client.exe (x86) ( Assault_Cube ) game
i got this information from “PSYSTEM_PROCESS_INFORMATION” **
CODE:
NTSTATUS Match_Process(PSGETCONTEXTTHREAD PsGetContextThread_, PSSETCONTEXTTHREAD PsSetContextThread_) {
UNREFERENCED_PARAMETER(PsGetContextThread_);
NTSTATUS status = STATUS_SUCCESS;
PSYSTEM_PROCESS_INFORMATION pCurrent = NULL;
PVOID buffer = NULL;
Get_ZwQuerySystemInformation_MDMC(SystemProcessInformation, &buffer);
pCurrent = (PSYSTEM_PROCESS_INFORMATION)buffer;
while (pCurrent) {
if (pCurrent->ImageName.Buffer != NULL) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, " 찾은 프로세스 -> %wZ @ 길이 -> %d @ 길이 비교결과 %d", pCurrent->ImageName, pCurrent->ImageName.Length, wcsncmp(pCurrent->ImageName.Buffer, L"ac_client", wcslen(L"ac_client")));
UNICODE_STRING Process_UNICODE_STRING = pCurrent->ImageName;
if (Process_UNICODE_STRING.Length >= wcslen(L"ac_client")) { // 비교시, 오버플로우 방지위해 미리 길이 확인
if (wcsncmp(Process_UNICODE_STRING.Buffer, L"ac_client", wcslen(L"ac_client")) == 0) {
**if process name is “ac_client” … , catch it
and find the real handle of ac_client.exe with pid
and then find the ETHREAD from PsLookupThreadByThreadId with tid.**
CODE:
PSYSTEM_THREAD_INFORMATION pThread = pCurrent->Threads;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "3 NumberOfThreads %d ", pCurrent->NumberOfThreads);
for (ULONG i = 0; i < pCurrent->NumberOfThreads;i++) {
pThread[i];
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, " index → %d // TID → %lu ", i ,HandleToULong( pThread[i].ClientId.UniqueThread ));
// TID 로 ETHREAD 얻기
PETHREAD Thread_of_process = NULL;
status = PsLookupThreadByThreadId(pThread[i].ClientId.UniqueThread,&Thread_of_process); // 얻은 TID로 THREAD 구조체 정보 얻기
NOW< i need to the CONTEXT of ac_client.exe
CODE:
PCONTEXT Context_by_Virtual_BaseAddress = NULL;
SIZE_T Size = sizeof(CONTEXT);
ZwAllocateVirtualMemory(Process_REAL_Handle, &Context_by_Virtual_BaseAddress, 0, &Size, MEM_COMMIT, PAGE_READWRITE);
The pointer address of CONTEXT should be Virtual address from target process
so i allocated memory to ac_client.exe ( if use the Usermode )
KAPC_STATE ApcState;
KeStackAttachProcess(USER_MODE__Process_EPROCESS, &ApcState); //연결--------------------------------
CODE:
Context_by_Virtual_BaseAddress->ContextFlags = CONTEXT_DEBUG_REGISTERS; // 직접 프로세스 가상주소에 들어가 값을 수정하는 것이기에 Attach 필수!
status = PsGetContextThread_(Thread_of_process, Context_by_Virtual_BaseAddress, UserMode);
**finally i can get CONTEXT from target process
Now, i need to check the available Dr0 to Dr3.**
CODE:
int available_debug_register0_3 = 0;
for (int ii = 0; ii < 4; ii++) {
if ((Context_by_Virtual_BaseAddress->Dr7 & (1UL << (ii * 2))) == 0) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, " 사용가능한 DR은? → %d ", ii);
available_debug_register0_3 = ii;
break;
}
}
i must now set up Dr7 to use the breakpoint.
To set it up, each bit had to be modified.
CODE:
Context_by_Virtual_BaseAddress->Dr0 = (ULONG_PTR)0x0077B3E0;
Context_by_Virtual_BaseAddress->Dr7 |= (1<<0);
Context_by_Virtual_BaseAddress->Dr7 |= (1 << (16 + 0));
Context_by_Virtual_BaseAddress->Dr7 |= (4 << (18 + 0));
**0x0077B3E0 address is AMMO address.
Debug register information must now be saved. with PsSetContextThread API **
CODE:
status = PsSetContextThread_(Thread_of_process, Context_by_Virtual_BaseAddress, UserMode);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “PsSetContextThread_ - Usermode 결과 → %p”, status);
KeUnstackDetachProcess(&ApcState); // 연결해제--------------------------------------------------------
**Now, When i shoot anything, That’s work it for Hardware breakpoint ( and then, the operating system stopped all.
So i can breakpoint status from Windbg.exe**
(WINDBG.exe)
(LOG)
This exception may be expected and handled.
00000000`004c73f1 8d44241c lea eax,[esp+1Ch]
(ASSEMBLY)
004c73ef ff08 dec dword ptr [rax] // execute ( Write ) ( AMMO = AMMO - 1 )
004c73f1 8d44241c lea eax, [rsp+1Ch]// <-STOPPED here
(Register values since the break.)
eax=0077b3e0 ebx=0003b054 ecx=00000078 edx=0031b000 esi=0076f7d8 edi=0077b2a0
eip=004c73f1 esp=0019fd3c ebp=00000001 iopl=0
**you know, you can find 0x0077b3e0 ! ( AMMO address )
I’m now going to resume the breakpoint point, and write a code that increases the count. Thank you.**
My HardwareBreakPoint code was work to me in kernel
#include <ntifs.h>
#include “process.h”
#pragma warning(disable: 4996)
#pragma warning(disable: 4334)
NTSTATUS Get_ZwQuerySystemInformation_MDMC(SYSTEM_INFORMATION_CLASS classes, PVOID* IN_OUT__BUFFER);
NTSTATUS Get_real_HANDLE_from_pid(HANDLE pid, PHANDLE return_real_handle);
typedef NTSTATUS(*PSGETCONTEXTTHREAD) ( PETHREAD Thread, PCONTEXT TheardContext, KPROCESSOR_MODE Mode );
typedef NTSTATUS(*PSSETCONTEXTTHREAD) (PETHREAD Thread, PCONTEXT TheardContext, KPROCESSOR_MODE Mode);
NTSTATUS Match_Process(PSGETCONTEXTTHREAD PsGetContextThread_, PSSETCONTEXTTHREAD PsSetContextThread_);
NTSTATUS DriverEntry(PDRIVER_OBJECT driverobject, PUNICODE_STRING registrypath) {
UNREFERENCED_PARAMETER(driverobject);
UNREFERENCED_PARAMETER(registrypath);
NTSTATUS status = STATUS_SUCCESS;
PSGETCONTEXTTHREAD PsGetContextThread_ = NULL;
PSSETCONTEXTTHREAD PsSetContextThread_ = NULL;
UNICODE_STRING sample;
RtlInitUnicodeString(&sample, L"PsGetContextThread");
PsGetContextThread_ = (PSGETCONTEXTTHREAD)MmGetSystemRoutineAddress(&sample);
if (PsGetContextThread_ == NULL) {
return STATUS_UNSUCCESSFUL;
}
RtlInitUnicodeString(&sample, L"PsSetContextThread");
PsSetContextThread_ = (PSSETCONTEXTTHREAD)MmGetSystemRoutineAddress(&sample);
if (PsSetContextThread_ == NULL) {
return STATUS_UNSUCCESSFUL;
}
Match_Process(PsGetContextThread_, PsSetContextThread_);
return status;
}
NTSTATUS Match_Process(PSGETCONTEXTTHREAD PsGetContextThread_, PSSETCONTEXTTHREAD PsSetContextThread_) {
UNREFERENCED_PARAMETER(PsGetContextThread_);
NTSTATUS status = STATUS_SUCCESS;
PSYSTEM_PROCESS_INFORMATION pCurrent = NULL;
PVOID buffer = NULL;
Get_ZwQuerySystemInformation_MDMC(SystemProcessInformation, &buffer);
pCurrent = (PSYSTEM_PROCESS_INFORMATION)buffer;
while (pCurrent) {
if (pCurrent->ImageName.Buffer != NULL) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, " 찾은 프로세스 -> %wZ @ 길이 -> %d @ 길이 비교결과 %d", pCurrent->ImageName, pCurrent->ImageName.Length, wcsncmp(pCurrent->ImageName.Buffer, L"ac_client", wcslen(L"ac_client")));
UNICODE_STRING Process_UNICODE_STRING = pCurrent->ImageName;
if (Process_UNICODE_STRING.Length >= wcslen(L"ac_client")) { // 비교시, 오버플로우 방지위해 미리 길이 확인
if (wcsncmp(Process_UNICODE_STRING.Buffer, L"ac_client", wcslen(L"ac_client")) == 0) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "1 PID -> %lu", HandleToULong(pCurrent->UniqueProcessId));
//프로세스 실제 핸들 얻기
HANDLE Process_REAL_Handle = 0;
Get_real_HANDLE_from_pid(pCurrent->UniqueProcessId, &Process_REAL_Handle);
//프로세스 EPROCESS 얻기
PEPROCESS USER_MODE__Process_EPROCESS = NULL;
status = PsLookupProcessByProcessId(pCurrent->UniqueProcessId, &USER_MODE__Process_EPROCESS);
//PSYSTEM_THREAD_INFORMATION 해당 프로세스의 실제 스레드 정보 얻는 구조체 습득
PSYSTEM_THREAD_INFORMATION pThread = pCurrent->Threads;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "3 NumberOfThreads %d ", pCurrent->NumberOfThreads);
for (ULONG i = 0; i < pCurrent->NumberOfThreads;i++) {
pThread[i];
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, " index -> %d // TID -> %lu ", i ,HandleToULong( pThread[i].ClientId.UniqueThread ));
// TID 로 ETHREAD 얻기
PETHREAD Thread_of_process = NULL;
status = PsLookupThreadByThreadId(pThread[i].ClientId.UniqueThread,&Thread_of_process); // 얻은 TID로 THREAD 구조체 정보 얻기
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "4 PsLookupThreadByThreadId -> status %p / Thread_of_process addr -> %p", status, Thread_of_process);
//CONTEXT context_of_thread = { 0, };
//memset(&context_of_thread, 0, sizeof(CONTEXT));
//context_of_thread.ContextFlags = CONTEXT_DEBUG_REGISTERS;
//USERMODE로 컨텍스트 얻으려면 타겟 프로세스의 주소가 필요. ( 동적할당 ) 그리고 Attach 하여 참조 및 사용하도록 한다.
PCONTEXT Context_by_Virtual_BaseAddress = NULL;
SIZE_T Size = sizeof(CONTEXT);
ZwAllocateVirtualMemory(Process_REAL_Handle, &Context_by_Virtual_BaseAddress, 0, &Size, MEM_COMMIT, PAGE_READWRITE);
if (status != STATUS_SUCCESS) {
ZwFreeVirtualMemory(Process_REAL_Handle, &Context_by_Virtual_BaseAddress, &Size, MEM_RELEASE);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "ZwAllocateVirtualMemory 실패 %p", status);
return STATUS_UNSUCCESSFUL;
}
else {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "ZwAllocateVirtualMemory 성공 %p", status);
KAPC_STATE ApcState;
KeStackAttachProcess(USER_MODE__Process_EPROCESS, &ApcState); //연결--------------------------------
Context_by_Virtual_BaseAddress->ContextFlags = CONTEXT_DEBUG_REGISTERS; // 직접 프로세스 가상주소에 들어가 값을 수정하는 것이기에 Attach 필수!
//Context_by_Virtual_BaseAddress->ContextFlags = CONTEXT_ALL;
status = PsGetContextThread_(Thread_of_process, Context_by_Virtual_BaseAddress, UserMode);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "PsGetContextThread_ - Usermode 결과 -> %p", status);
//여기부터 컨텍스트 작업시작
// 총알 주소 0x0077B3E0 - 0x0A207B74
ULONG_PTR dr0 = Context_by_Virtual_BaseAddress->Dr0;
int available_debug_register0_3 = 0;
for (int ii = 0; ii < 4; ii++) {
if ((Context_by_Virtual_BaseAddress->Dr7 & (1UL << (ii * 2))) == 0) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, " 사용가능한 DR은? -> %d ", ii);
available_debug_register0_3 = ii;
break;
}
}
switch (available_debug_register0_3) {
case 0: // if only Dr0
Context_by_Virtual_BaseAddress->Dr0 = (ULONG_PTR)0x0077B3E0;
Context_by_Virtual_BaseAddress->Dr7 |= (1<<0);
Context_by_Virtual_BaseAddress->Dr7 |= (1 << (16 + 0));
Context_by_Virtual_BaseAddress->Dr7 |= (4 << (18 + 0));
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Dr0 %p / Dr1 %p / Dr2 %p / Dr3 %p /\n Dr7 %p", dr0, dr1, dr2, dr3, Context_by_Virtual_BaseAddress->Dr7);
break;
case 1:
Context_by_Virtual_BaseAddress->Dr1;
break;
case 2:
Context_by_Virtual_BaseAddress->Dr2;
break;
case 3:
Context_by_Virtual_BaseAddress->Dr3;
break;
default:
ZwFreeVirtualMemory(Process_REAL_Handle, &Context_by_Virtual_BaseAddress, &Size, MEM_RELEASE);
return STATUS_UNSUCCESSFUL;
}
status = PsSetContextThread_(Thread_of_process, Context_by_Virtual_BaseAddress, UserMode);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "PsSetContextThread_ - Usermode 결과 -> %p", status);
KeUnstackDetachProcess(&ApcState); // 연결해제--------------------------------------------------------
ZwFreeVirtualMemory(Process_REAL_Handle, &Context_by_Virtual_BaseAddress, &Size, MEM_RELEASE);
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "\n");
}
}
}
}
if (pCurrent->NextEntryOffset == 0) { // 다음 주소가 없을 때 탈출
break;
}
else {
pCurrent = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pCurrent) + pCurrent->NextEntryOffset); // 다음 주소지가 있을 때, 다음 주소로 이동
}
}
return status;
}
NTSTATUS Get_ZwQuerySystemInformation_MDMC(SYSTEM_INFORMATION_CLASS classes, PVOID* IN_OUT__BUFFER) { // 프로세스 정보를 얻는 용도
NTSTATUS status = STATUS_SUCCESS;
ULONG bufferSize = 0;
status = ZwQuerySystemInformation(classes, (PVOID)*IN_OUT__BUFFER, bufferSize, &bufferSize);// (1) Try 이때는 버퍼값만 가져온다. ///
if (status == STATUS_INFO_LENGTH_MISMATCH) {
*IN_OUT__BUFFER = (PVOID)ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'MDMC');
if (*IN_OUT__BUFFER == NULL) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "메모리 할당 실패~!\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "메모리 할당 성공!~!\n");
status = ZwQuerySystemInformation(classes, (PVOID)*IN_OUT__BUFFER, bufferSize, &bufferSize);// (2) Try 이때는 할당된 버퍼크기만큼 프로세스 구조체를 가져온다.///
if (!NT_SUCCESS(status)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "프로세스 정보 못가져옴!!!!!! >> %p\n", status);
ExFreePoolWithTag(*IN_OUT__BUFFER, 'MDMC');
return status;
}
}
else {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "첫번째 ZwQuerySysteminformation() 성공!\n");
}
return status;
}
When I Shooting my gun, Stopped the OS and then i can check this situation in windbg
( WINDBG.exe)
This exception may be expected and handled.
00000000`004c73f1 8d44241c lea eax,[esp+1Ch]
004c73ef ff08 dec dword ptr [rax] // EXECUTED
004c73f1 8d44241c lea eax, [rsp+1Ch] //STOPPED here
eax=0077b3e0 ebx=0003b054 ecx=00000078 edx=0031b000 esi=0076f7d8 edi=0077b2a0
eip=004c73f1 esp=0019fd3c ebp=00000001
RAX = 0077B3E0 ? This is AMMO address
It’s work to me!
STRUCTs
typedef enum _KTHREAD_STATE
{
Initialized,
Ready,
Running,
Standby,
Terminated,
Waiting,
Transition,
DeferredReady,
GateWaitObsolete,
WaitingForProcessInSwap,
MaximumThreadState
} KTHREAD_STATE, * PKTHREAD_STATE;
typedef struct _SYSTEM_THREAD_INFORMATION
{
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
ULONG_PTR StartAddress;
CLIENT_ID ClientId;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitches;
KTHREAD_STATE ThreadState;
KWAIT_REASON WaitReason;
} SYSTEM_THREAD_INFORMATION, * PSYSTEM_THREAD_INFORMATION;
typedef struct _SYSTEM_PROCESS_INFORMATION
{
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER WorkingSetPrivateSize; // since VISTA
ULONG HardFaultCount; // since WIN7
ULONG NumberOfThreadsHighWatermark; // since WIN7
ULONGLONG CycleTime; // since WIN7
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
HANDLE InheritedFromUniqueProcessId;
ULONG HandleCount;
ULONG SessionId;
ULONG_PTR UniqueProcessKey; // since VISTA (requires SystemExtendedProcessInformation)
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER ReadOperationCount;
LARGE_INTEGER WriteOperationCount;
LARGE_INTEGER OtherOperationCount;
LARGE_INTEGER ReadTransferCount;
LARGE_INTEGER WriteTransferCount;
LARGE_INTEGER OtherTransferCount;
SYSTEM_THREAD_INFORMATION Threads[1]; // SystemProcessInformation
// SYSTEM_EXTENDED_THREAD_INFORMATION Threads[1]; // SystemExtendedProcessinformation
// SYSTEM_EXTENDED_THREAD_INFORMATION + SYSTEM_PROCESS_INFORMATION_EXTENSION // SystemFullProcessInformation
} SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION;
Your solution is fine for a test system, but if this is production code it is a disaster waiting to happen. The hardware debug registers have no way you can reserve them, so the next clever piece of code can overwrite them, whether it be Windbg or someone else trying to solve a problem like that.