Перейти к содержанию

Чтение регистров средствами с++


Рекомендуемые сообщения

Приветствую.

У меня к вам такой вопрос: предположим, есть некая инструкция в игре, к примеру, mov [rax+18], rbx и в регистре rax хранится значение, которое мне необходимо получить в своей программе, каким путём это можно сделать? Пытался найти примеры, но во всех используются ассемблерные вставки, которые х64 проект vc++ не поддерживает.

Ссылка на комментарий
Поделиться на другие сайты

Предположим, что в этой же игре ты делаешь Code Cave, в котором, предположим, получаешь нужный тебе регистр в нужный момент времени. И даже предположим, что в этом CodeCave ты можешь сохранить содержимое нужного регистра в выделенной памяти, которую затем прочитать из C++. Более подробно рассказать не могу, так как задача обрисована весьма и весьма неясно.

Ссылка на комментарий
Поделиться на другие сайты

В x64 ассемблерных вставок нет, но можно прикрутить файл(ml64 /c твойасмфайл после компиляции передать *.obj в линкер) с процедурой которая будет получать нужный тебе регистр. А потом использовать 

extern "C" __int64 имяпроцедуры();//(Имя процедуры из асмфайла)

или 

 

Встроенные объекты компилятора и __getReg

 

 

 

Изменено пользователем partoftheworlD
  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

мммм, интересно, т.е. я могу написать так:

Скрытый текст

in_obj file:

void PlayerStruct(int64 &Player)
{
	_asm{
      ...
      mov Player,eax
      ...
	}
}


in_64x Project:

extern "C" _void PlayerStruct(int64);

int64 Player;
PlayerStruct(Player);

 

К сожалению пока нету под рукой VS проверить) винда ставится, пишу с планшета.

Ссылка на комментарий
Поделиться на другие сайты

44 минуты назад, elvis66666 сказал:

да, я тоже компилятор от интела использую под VSx64. Просто интересный вариант выше.

нее интел не годится. слишком большой % мусора. а тот вариант выше, там ведь надо кодить в .asm а потом сгенерить .obj ?

Ссылка на комментарий
Поделиться на другие сайты

2 часа назад, gmz сказал:

нее интел не годится. слишком большой % мусора. а тот вариант выше, там ведь надо кодить в .asm а потом сгенерить .obj ?

да) я особо не заметил мусора от интела, да и проект большим уж точно не будет, чтобы заметить хоть какие-то просадки.

За-то сидишь и пишешь себе инлайн асмом в VS и не надо ничего руками прописывать, преобразовывать =)

Ссылка на комментарий
Поделиться на другие сайты

Угу, с этим разобрался - как подключить и работать с отдельным асм файлом в студии (кстати, сделал это сразу в ней, т.е. просто создал асм файл и указал в его свойствах, что этот файл типа "масм", вручную компилировать и подключать в линкере не потребовалось).

 

Но основной вопрос всё ещё в силе: как получить данные из регистра при срабатывании определённой инструкции. Можете по шагам описать, что для этого необходимо? Или лучше, возможно у кого завалялся где-нибудь листинг, в котором выполняется такая задача, чтобы по нему можно было разобраться.

Ссылка на комментарий
Поделиться на другие сайты

2 часа назад, Mephisto сказал:

как получить данные из регистра при срабатывании определённой инструкции

Я думаю в твоем случае как и в Mid Hook function, тебе нужен трамплин который будет делать прыжок на твою процедуру(asm которая будет передавать в буфер адрес регистра, типа mov Buffer, rcx) и возвращать обратно в код игры. Пример могу показать, только он для x32, но думаю логика будет понятна. Вместо вставки будет процедура только.

 

Скрытый текст

__declspec(naked) void EndlessAmmo()
{
	__asm 
	{

	mov AmmoRegister , esi
	jmp [AmmoJMPBack]

	}

}

void InitializeHooks()
{

	DWORD exampleAmmo = FindPattern("server.dll","\xE8\x00\x00\x00\x00\x33\xD2\x66\x89\x50\x02\x89\xBE\x00\x00\x00\x00\x8B\x06\x8B\x90\x00\x00\x00\x00\x8B\xBE\x00\x00\x00\x00","x????xxxxxxxx????xxxx????xx????");
	exampleAmmo += 11; //Смещение от сигнатуры на 11 байт для выхода на нужную инструкцию

	AmmoJMPBack = exampleAmmo + 0x6; // количество байт заменяемых инструкций

	PlaceJMP((BYTE*)exampleAmmo, (DWORD)EndlesseAmmo, 0x6); // трамплин
}

DWORD WINAPI OverwriteValues()
{
	for (;;Sleep(150))
	{
		if (GetAsyncKeyState(VK_MENU))
		{
			DWORD idkptr = AmmoRegister + 0x1414;
			*(int*)idkptr += 1;
		}
	}
	return 0;
}

BOOL WINAPI DllMain(
	HINSTANCE hInstDll,
	DWORD fdwReason,
	LPVOID lpReserved)
{
	switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH:
	InitializeHooks();
	CreateThread(0,0,(LPTHREAD_START_ROUTINE)OverwriteValues,0,0,0);
	break;
	}
	return true;
}

 

 

Изменено пользователем partoftheworlD
Ссылка на комментарий
Поделиться на другие сайты

Я вообще не понимаю, вокруг чего сыр-бор? Я ж в первом же ответе написал алгоритм получения значения регистра из игры. Все предложеннные варианты позволяют прочитать регистр из СВОЕГО приложения (просматривал по диагонали), тогда как топикстартер говорил о стороннем приложении. 

Ссылка на комментарий
Поделиться на другие сайты

Хм, кажется начинает доходить, спасибо.

И вот ещё вопрос возник: я выделил память с помощью VirtualAllocEx, там прописываю код, затем мне необходимо вернуться назад к точке прыжка. Рассчитываю байты для прыжка по формуле "0 - ( Откуда - Куда) - размер моего кода". Но когда эти байты подставляю к прыжку, адрес получается неверным. Т.е. вместо, 13F83104B получается 3F83104B, единичка теряется. А если добавить флаг MEM_TOP_DOWN, то на выходе получится 8003F83104B, т.е. вместо единички будет 800. Похоже это из-за того, что память выделяется не там, где нужно.

 

В свою очередь когда я рассчитываю байты для прыжка от игровой инструкции к выделенной памяти, то к нужному адресу наоборот прибавляется единичка: память выделяется по адресу 07960000, а игра прыгает к 107960000.

 

Как это можно исправить? Полагаю, нужно как-то указать VirtualAllocEx в каком регионе выделять память.

Изменено пользователем Mephisto
Ссылка на комментарий
Поделиться на другие сайты

19 час назад, Mephisto сказал:

Можете по шагам описать, что для этого необходимо? Или лучше, возможно у кого завалялся где-нибудь листинг, в котором выполняется такая задача, чтобы по нему можно было разобраться.

Для начала читаешь о межпроцессорном взаимодействие, затем когда определишься какой метод будешь использовать можешь писать хук. Задача хука - прочитать содержимое регистра и отправить его твоей программе. Задача твоей программы - получить и обработать посылку. Естественно все это должно происходить синхронно.

Ссылка на комментарий
Поделиться на другие сайты

VirtualAllocEx всегда выделит память дальше, чем находится код, с которого прыгаешь. Поэтому для прыжка на выделенную память используешь 

Адрес кейва - Адрес оригинальной инструкции и при записи этого числа не забываешь байты перевернуть (в памяти они хранятся в перевернутом виде). Для прыжка из выделеннной памяти обратно считаешь (Адрес кейва + Размер кейва до инструкции прыжка обратно) - (Адрес оригинальной инструкции + 5 байт).

 

Ссылка на комментарий
Поделиться на другие сайты

Спасибо) Удалось, наконец, сделать что хотел - и заменил часть кода своим, и регистры прочитал в выделенную память.

Правда по этой формуле " (Адрес кейва + Размер кейва до инструкции прыжка обратно) - (Адрес оригинальной инструкции + 5 байт) " всё равно выходит абракадабра (и после переворота байт тоже). Тут ведь дело в чем - по формуле, которую использовал изначально, получалось верно число, просто оно 8-байтовое, тогда как jmp после себя забирает лишь 4 байта для определения адреса и потому единичку теряет, ведь она не умещается в эти 4 байта. Видимо приставка добавляется в зависимости от точки прыжка. Я так посмотрел, когда СЕ выделяет память под скрипты, он выделяет её близ самой игры, а не отсылает так далеко, как VirtualAllocEx.
Чтобы эта функция выделяла память рядом с игрой, написал вот такое:
 

Скрытый текст

UINT64 Trainer::Alloc(int size)
{
    MEMORY_BASIC_INFORMATION mbi;
    UINT64 FreeMem = 0;
    UINT64 offset = 0;
    while (true)
    {
        VirtualQueryEx(pHandle, (LPCVOID)(AddressBase - offset), &mbi, sizeof(MEMORY_BASIC_INFORMATION));
        if (mbi.State == MEM_FREE && mbi.RegionSize >= size)
        {
            FreeMem = AddressBase - offset;
            break;
        }
        offset += mbi.RegionSize;
    }

    return (UINT64)VirtualAllocEx(pHandle, (LPVOID)FreeMem, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
}

 

Теперь, как мне и нужно, к началу адреса добавляется единичка и адрес для прыжка получается верным.

Ссылка на комментарий
Поделиться на другие сайты

В 09.11.2016в05:31, Xipho сказал:

тогда как топикстартер говорил о стороннем приложении.

предполагалось что автор не страдает херней и кодит длл :D

 

В 09.11.2016в12:37, Mephisto сказал:

В свою очередь когда я рассчитываю байты для прыжка от игровой инструкции к выделенной памяти, то к нужному адресу наоборот прибавляется единичка: память выделяется по адресу 07960000, а игра прыгает к 107960000.

в х64 над другой метод. например я использую:

00007FF7755E005B | FF 15 00 00 00 00           | call qword [00007FF7755E0061]
00007FF7755E0061 | D1 15 00 00 01 00 00 00 <- туда пишу адрес а в самой хук функции добавляю add qword[rsp],8 или можно jmp после call поставить на +8

 

5 часов назад, Xipho сказал:

VirtualAllocEx всегда выделит память дальше, чем находится код, с которого прыгаешь. Поэтому для прыжка на выделенную память используешь 

Адрес кейва - Адрес оригинальной инструкции и при записи этого числа не забываешь байты перевернуть (в памяти они хранятся в перевернутом виде). Для прыжка из выделеннной памяти обратно считаешь (Адрес кейва + Размер кейва до инструкции прыжка обратно) - (Адрес оригинальной инструкции + 5 байт)

дк х64

Ссылка на комментарий
Поделиться на другие сайты

Вот на примере игры сапер Win7 x64, при нажатии на кнопку "О программе" она будет отправлять через пайп содержимое регистра RAX в твою программу Main.cpp (код твоей программы)

Скрытый текст

void Pipe()
{
    LPCSTR  lpszPipeName = "\\\\.\\pipe\\GetReg";
    BOOL   fConnected;
    HANDLE hNamedPipe;
    DWORD  cbRead = 0;
    DWORD64 reg;
    TCHAR strout[MAX_PATH];
  
    DWORD size = sizeof(DWORD64);
    while (1)
    {
        hNamedPipe = CreateNamedPipe(
            lpszPipeName,
            PIPE_ACCESS_DUPLEX,
            PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
            1,
            size, size, NMPWAIT_USE_DEFAULT_WAIT, NULL);
        if (hNamedPipe != INVALID_HANDLE_VALUE)
        {
            fConnected = ConnectNamedPipe(hNamedPipe, NULL);
            if (fConnected)
            {
                while (1)
                {
                    if (ReadFile(hNamedPipe, &reg, size, &cbRead, NULL))
                    {
                        wsprintf(strout, "RAX = %p", reg);
                        MessageBox(0, strout, 0, 0);
                        break;
                    }
                    Sleep(100);
                }
                DisconnectNamedPipe(hNamedPipe);
            }
            CloseHandle(hNamedPipe);
        }
    }
}
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPTSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    CreateThread(0, 0, (LPTHREAD_START_ROUTINE)Pipe, 0, 0, 0);
  
   //...  тут твои действия
  
}

 

 


Dllmain.cpp(код твоей Dll, которая должна быть загружена в игру)

Скрытый текст

#include "stdafx.h"
#include <stdlib.h>

typedef PVOID(*GetContext)();
extern "C" void HookedGetReg();
extern "C" PVOID Pipe(DWORD64 reg);

GetContext tGetReg = nullptr, mGetReg = nullptr;

PVOID Pipe(DWORD64 reg)
{

	LPCSTR  lpszPipeName = "\\\\.\\pipe\\GetReg";
	HANDLE hNamedPipe;
	DWORD  cbWrite;
	DWORD size;

	if (WaitNamedPipe(lpszPipeName, NMPWAIT_USE_DEFAULT_WAIT))
	{

		hNamedPipe = CreateFile(
			lpszPipeName, GENERIC_READ | GENERIC_WRITE,
			0, NULL, OPEN_EXISTING, 0, NULL);
		if (hNamedPipe != INVALID_HANDLE_VALUE)
		{
			
			size = sizeof(DWORD64);

			while (1)
			{
				if (WriteFile(hNamedPipe, &reg, size, &cbWrite, NULL))
					break;
				Sleep(100);
			}

			CloseHandle(hNamedPipe);
		}
	}

	return tGetReg();
}




void SetHook()
{
	
	BYTE opcodejmp[15] = "\xFF\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
	DWORD dwold, len;

	len = 15; // длина инструкций, оригинальные байты которые заменяем прыжком на наш код  (не менее 14)

	mGetReg = (GetContext)((PBYTE)GetModuleHandleA("minesweeper.exe") + 0x351E0); // Инструкция которую хукаем

	*(DWORD64*)&opcodejmp[6] = (DWORD64)((PBYTE)mGetReg + len);
	tGetReg = (GetContext)malloc(14 + len);// выделяем память для трамплина
	VirtualProtect(tGetReg, 14 + len, PAGE_EXECUTE_READWRITE, &dwold);
	memcpy(tGetReg, mGetReg, len);
	memcpy((LPVOID)((PBYTE)tGetReg + len), opcodejmp, 14);

	*(DWORD64*)&opcodejmp[6] = (DWORD64)&HookedGetReg;
	VirtualProtect(mGetReg, 14, PAGE_EXECUTE_READWRITE, &dwold);
	memcpy(mGetReg, (LPCVOID)opcodejmp, 14);
	VirtualProtect(mGetReg, 14, dwold, &dwold);
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		SetHook();
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

 

 

 

Asm файл(так же должен быть включен в проект с Dll !)

Скрытый текст



EXTRN  Pipe : proc

.CODE
HookedGetReg PROC
push rax  
push rcx
push rdx
push rdi
push r8
push r9
sub rsp, 28h
mov RCX,RAX 
call Pipe
add rsp, 28h
pop r9
pop r8
pop rdi  
pop rdx
pop rcx
pop rax
ret
HookedGetReg ENDP

END

 

 

Код для загрузки Dll в стороннее приложение можешь найти где угодно, поэтому не вижу смысла его здесь приводить.

Изменено пользователем Xipho
Куски кода принято оборачивать спойлером, дабы не создавать "простыней".
Ссылка на комментарий
Поделиться на другие сайты

2 часа назад, Dino сказал:

Asm файл(так же должен быть включен в проект с Dll !)

надо сохр RAX RCX RDX R8 R9 R10 R11

если он поставит хук, а там они активно юзаются - call Pipe напихает мусора и будет эпик крешш

 

Ссылка на комментарий
Поделиться на другие сайты

1 час назад, gmz сказал:

надо сохр RAX RCX RDX R8 R9 R10 R11

если он поставит хук, а там они активно юзаются - call Pipe напихает мусора и будет эпик крешш

 

ага ты прав.. А все из за того что я трамплин не из того места вызываю :D

 

Вот исправленный вариант:

 

Dllmain.cpp(код твоей Dll, которая должна быть загружена в игру)

Скрытый текст

#include <windows.h>

typedef PVOID(*GetContext)();

extern "C" void HookedGetReg();
extern "C" void Pipe(DWORD64 reg);
extern "C" DWORD64 tGetReg[10] = { 0x00000000 }; //выделим побольше памяти для трамплина

GetContext mGetReg = nullptr;


void Pipe(DWORD64 reg)
{

	LPCSTR  lpszPipeName = "\\\\.\\pipe\\GetReg";
	HANDLE hNamedPipe;
	DWORD  cbWrite = 0;
	DWORD size;

	if (WaitNamedPipe(lpszPipeName, NMPWAIT_USE_DEFAULT_WAIT))
	{

		hNamedPipe = CreateFile(
			lpszPipeName, GENERIC_READ | GENERIC_WRITE,
			0, NULL, OPEN_EXISTING, 0, NULL);
		if (hNamedPipe != INVALID_HANDLE_VALUE)
		{
			
			size = sizeof(DWORD64);

			while (1)
			{
				if (WriteFile(hNamedPipe, &reg, size, &cbWrite, NULL))
					break;
				Sleep(100);
			}

			CloseHandle(hNamedPipe);
		}
	}
}




void SetHook()
{
	BYTE opcodejmp[15] = "\xFF\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
	DWORD dwold, len;

	len = 15; // длина инструкций, оригинальные байты которые заменяем прыжком на наш код  (не менее 14)

	mGetReg = (GetContext)((PBYTE)GetModuleHandleA("minesweeper.exe") + 0x351E0); // Инструкция которую хукаем

	*(DWORD64*)&opcodejmp[6] = (DWORD64)((PBYTE)mGetReg + len);
	VirtualProtect((LPVOID)(tGetReg), 14 + len, PAGE_EXECUTE_READWRITE, &dwold);
	memcpy((LPVOID)tGetReg, mGetReg, len);
	memcpy((LPVOID)((PBYTE)tGetReg + len), opcodejmp, 14);

	*(DWORD64*)&opcodejmp[6] = (DWORD64)&HookedGetReg;
	VirtualProtect(mGetReg, 14, PAGE_EXECUTE_READWRITE, &dwold);
	memcpy(mGetReg, (LPCVOID)opcodejmp, 14);
	VirtualProtect(mGetReg, 14, dwold, &dwold);
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		CreateThread(0,0,(LPTHREAD_START_ROUTINE)SetHook,0,0,0);
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

 

 

Asm файл(так же должен быть включен в проект с Dll !)

Скрытый текст

EXTRN  Pipe : proc
EXTRN  tGetReg : proc

.CODE
HookedGetReg PROC
push rax  
push rbx
push rcx
push rdx
push rdi
push rsi
push r8
push r9
push r10
push r11
push r12
sub rsp, 30h
mov RCX,RAX 
call Pipe ; Важно! Когда функция сделает что-то вроде <sub rsp, 48h> верхушка стека должна быть кратна 16, в противном случае краш!
add rsp, 30h
pop r12
pop r11
pop r10
pop r9
pop r8
pop rsi
pop rdi  
pop rdx
pop rcx
pop rbx
pop rax
jmp tGetReg ; выполняем оригинальный код

HookedGetReg ENDP

END

 

 

Изменено пользователем Dino
  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

51 минуту назад, Dino сказал:

Asm файл(так же должен быть включен в проект с Dll !)

что то их слишком много, можно обойтись списком выше, но даже так ты rbp забыл :D

 

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.