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

[C++ Lineage 2] Вызов функции из внедренной DLL


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

Всем здравствуйте.

Пишу бота с интерфейсом, застопорился на вызове функций из внедренной в процесс DLL-ки.

Некоторую информацию можно получить удаленно через ReadProcessMemory по адресам и оффсетам, да. Но чтобы получать класс User к примеру, который имеет динамический адрес надо внутри процесса вызвать API функцию.

И вот вопрос: каким способом это сделать из интерфейса?

 

Кусочек кода:

void memRead(HANDLE hProc, DWORD address, LPVOID buffer)
{
	DWORD oldProtect = 0;
	VirtualProtectEx(hProc, (LPVOID)address, sizeof(buffer), PAGE_READWRITE, &oldProtect);
	ReadProcessMemory(hProc, (LPVOID)address, buffer, sizeof(buffer), 0);
	VirtualProtectEx(hProc, (LPVOID)address, sizeof(buffer), oldProtect, &oldProtect);
}


typedef int(__thiscall* f_GetUser)(int this_UNetworkHandler, int id);
f_GetUser call_GetUser = reinterpret_cast<f_GetUser>(0x203D9150);


int GetMyUserClass()
{
	HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessID);

	DWORD UNetworkHandler_this;
	int my_id;

	memRead(hProc, 0x20522A6C, &UNetworkHandler_this);
	memRead(hProc, 0x2077ED7C, &my_id);

	return call_GetUser(UNetworkHandler_this, my_id); //call_GetUser Возвращает адрес класса User
	CloseHandle(hProc);
}

ID и адрес класса UNetworkHandler получаются корректно, но функция не вызывается. Не пинайте меня, я еще многого не знаю..:)

Изнутри процесса все окей, функция работает.

Можно конечно постоянно юзать ее из DLL, но тогда я не знаю как правильно организовать обмен информацией между DLL и exe-шником. Через пайп как-то геморно..

Как вызвать функцию удаленно?

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

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

Ну, собственно, именнованный пайп - самое оно. Или можно сокетом воспользоваться, с ним меньше геморроя.

В общем, мне нужно как-то закостылить организовать (к примеру) вызов функции MoveToLocation, отправляя при этом в DLL координаты куда нужно бежать и BOOL параметр для активации функции.

При этом, DLL должна непрерывно передавать параметры в GUI. Я немного не понимаю как использовать для этого пайп канал.

 

Чтобы отправить команду в DLL из GUI мне приходится отстыковывать один пайп и пристыковывать другой.. Можно общаться одним пайпом как-то не перекрывая записанные данные?

Я не нашел толковых примеров в гугле.. Нигде блин.

Фрагмент кода GUI:

Спойлер

// По нажатию кнопки в GUI происходит инжект DLL в процесс, устанавливается флаг для работы BackgroundWorker и запускается сам воркер
...
...
	private: System::Void btnInject_Click(System::Object^ sender, System::EventArgs^ e)
	{
		if (InjectDLL()) { tolog("DLL successfuly injected"); }
		else { tolog("ERROR: DLL Injection was failed!"); }

		statusBufferWorker = TRUE;
		bufferWorker->RunWorkerAsync();
	}
...
...
// Воркер коннектится к DLL, и с установленной периодичностью получает стркутуру из DLL в которой записана инфа о персонаже и выводит данные в GUI.
	private: System::Void bufferWorker_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) 
	{
		Sleep(500);
		if (!hPipeData1)
		{
			hPipeData1 = CreateFile("\\\\.\\pipe\\PipeData1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
			if (hPipeData1) tolog("Connection with injected DLL was opened!");
		}
		else tolog("ERROR: Connection with injected DLL is closed!");

		while (statusBufferWorker)
		{
			ReadFile(hPipeData1, &buf, sizeof(buf), &cbRead, NULL);

			//if (buf.messages.msg.length() > 0) { tolog(sts(buf.messages.msg)); }
		
			float cp_percent = buf.me.curCP / (buf.me.maxCP / 100.0);
			float hp_percent = buf.me.curHP / (buf.me.maxHP / 100.0);
			float mp_percent = buf.me.curMP / (buf.me.maxMP / 100.0);

			userCpBar->Size = System::Drawing::Size(cp_percent, 13);
			userHpBar->Size = System::Drawing::Size(hp_percent, 13);
			userMpBar->Size = System::Drawing::Size(mp_percent, 13);
			userCpLabel->Text = its(buf.me.curCP) + "/" + its(buf.me.maxCP);
			userHpLabel->Text = its(buf.me.curHP) + "/" + its(buf.me.maxHP);
			userMpLabel->Text = its(buf.me.curMP) + "/" + its(buf.me.maxMP);

			userIdField->Text = its(buf.me.ptrClass);
			
			Sleep(100);
		}
	
	}
...
...
// При нажатии кнопки для выгрузки DLL из процесса запускается другой воркер, устанавливается флаг для остановки первого воркера, отсоединяется пайп по которому шли данные, открывается новый пайп, посылает команду в DLL на выгрузку, а там DLL отрабатывает все для выгрузки себя из процесса.
	private: System::Void backgroundWorker1_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) 
	{
		statusBufferWorker = FALSE;

		DisconnectNamedPipe(hPipeData1);
		CloseHandle(hPipeData1);
		hPipeData1 = 0;

		BOOL dllStatus = FALSE;
		hPipeDllStatus = CreateNamedPipeA("\\\\.\\pipe\\PipeDllStatus", 
                                          PIPE_ACCESS_DUPLEX,
                                          PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                                          PIPE_UNLIMITED_INSTANCES,
                                          512, 512, 5000, NULL);

		BOOL fConnected = ConnectNamedPipe(hPipeDllStatus, NULL);

		BOOL msg = WriteFile(hPipeDllStatus, &dllStatus, sizeof(dllStatus), &cbWritten, NULL);
		if (msg) tolog("DLL successfuly unloaded!");

		DisconnectNamedPipe(hPipeDllStatus);
		CloseHandle(hPipeDllStatus);
		hPipeDllStatus = 0;
	}

 

 

Фрагмент кода DLL:

Спойлер

...
...
	// Создаем канал для передачи данных. При успехе возвращается HANDLE канала.
	hPipeData1 = CreateNamedPipeA("\\\\.\\pipe\\PipeData1", 
                                  PIPE_ACCESS_DUPLEX,
                                  PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                                  PIPE_UNLIMITED_INSTANCES,
                                  512, 512, 5000, NULL);
	// Переходим в режим ожидания подключения клиента к каналу
	fConnected = ConnectNamedPipe(hPipeData1, NULL);
	// Пишем сообщение в структуру для GUI
	if (fConnected) { strcpy(buf.messages.msg , "FROM DLL: Connect with DLL successful!"); }

	// Действия выполняемые DLL-кой в цикле
	for (;; Sleep(100))
	{	// Проверяем запущен ли процесс GUI, если нет, то выгружаем DLL
		if (!CheckLoadedGUI()) { DisconnectAll(); break; }

      	// Если есть соединение с таким именем ожидающее подключения, цепляем его, чекаем сообщение на выгрузку
		if (WaitNamedPipeA("\\\\.\\pipe\\PipeDllStatus", 5000))
		{
			hPipeDllStatus = CreateFile("\\\\.\\pipe\\PipeDllStatus", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
			
			BOOL dllStatus;

			ReadFile(hPipeDllStatus, &dllStatus, sizeof(dllStatus), &cbRead, NULL);
			if (dllStatus == FALSE)
			{
				DisconnectAll();
				break;
			}
		}
		
      	// Пишем данные о персонаже в структуру и отправляем её
		UpdateMyUserStruct();
		WriteFile(hPipeData1, &buf, sizeof(buf), &cbWritten, NULL);
...
...

 

 

Читал еще про FileMapping, выглядит очень удобно, но пока не понимаю как побайтно организовать несколько переменных, чтобы они корректно считывались?

Как правильно отобразить несколько переменных в память? Не до конца понимаю смысл параметров dwFileOffsetHigh и dwFileOffsetLow.

MapViewOfFileEx(HANDLE hFileMappingObject, DWORD dwDesiredAccess,  DWORD dwFileOffsetHigh,  DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap, LPVOID lpBaseAddress);

 

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

UPD:

Хм, в принципе, как вариант создать пару-тройку MappedFile, записать в них нужные переменные (а лучше структуру) для работы и использовать.. Но это же костыль и нерациональное использование ресурсов?)

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

 

 

но лучше конечно использовать инжектируемую библиотеку в процесс.

По поводу передачи данных - обычный FileMapping

1) Создаешь FileMapping в основном приложении
2) При инжекте, инжектируемая dll открывает этот FileMapping
3) Читаешь/пишешь в цикле, тут главное согласовать структуры данных, например:

typedef struct MyRedneckProtocol {

BYTE ControlCode;
Vector3 Coord1;

Vector3 Coord2;
int resultCode;

}
эта структуру одинакова в dll и в основном приложении.
 

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

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

Еще вариант - собрать длл-ку с одной пошаренной секцией (shared sections). Но это больше геморроя. Имхо, самый наименее геморройный вариант - общение через сокет. Чуть более геморройный - именованный пайп, я помню, у меня тоже с ним были проблемы как-то. Замапленные файлы использовать - то еще "удовольствие", потому что нужно согласовывать их запись/чтение. 

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

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

Еще вариант - собрать длл-ку с одной пошаренной секцией (shared sections). Но это больше геморроя. Имхо, самый наименее геморройный вариант - общение через сокет. Чуть более геморройный - именованный пайп, я помню, у меня тоже с ним были проблемы как-то. Замапленные файлы использовать - то еще "удовольствие", потому что нужно согласовывать их запись/чтение. 

О, а можно немного подробнее про согласование? Я допустим сейчас сделал структуру общую, разметил её одинаково в обоих файлах, замапил. Вроде всё работает неплохо. Нужно не допустить одновременной записи и чтения одной и той же переменной? Я где-то читал про Event'ы, но так и не понял как они работают (пока что). Это из той оперы?

 

upd:

Или Вы имеете в виду очередность записи/чтения чтобы буфер не перекрывался разными переменными?

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

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

 

 

но лучше конечно использовать инжектируемую библиотеку в процесс.

По поводу передачи данных - обычный FileMapping

1) Создаешь FileMapping в основном приложении
2) При инжекте, инжектируемая dll открывает этот FileMapping
3) Читаешь/пишешь в цикле, тут главное согласовать структуры данных, например:

typedef struct MyRedneckProtocol {

BYTE ControlCode;
Vector3 Coord1;

Vector3 Coord2;
int resultCode;

}
эта структуру одинакова в dll и в основном приложении.
 

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

А что обычно используют в таких случаях? В расчет беру частое обращение на запись/чтение. Принципиально ли использовать что-то конкретное? Быстродействие пайпа и маппинга здесь роли не играет как я понимаю из-за маленького объема данных?

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

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

А что обычно используют в таких случаях?

Я например, когда так делала, у меня было общение через ReadProcessMemory / WriteProcessMemory, А сначала, соединение устанавливалось через PostThreadMessage. А ещё я пробовала через системные объекты, которые можно создавать сколько угодно, единственная проблема, у них может быть только 2 состояние, true или false, и если передавать через них, то как в азбуке Морзе, прямо по битам, 101010 ?

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

Я не понимаю, не могу вызвать функции даже изнутри процесса. Клиент почему-то просто вырубается и все. Объявляю вроде правильно, вызываю тоже.

typedef int(__thiscall* f_NextCreature)(int this_UNetworkHandler, float Radius, int PrevID);
f_NextCreature call_GetNextCreature = reinterpret_cast<f_NextCreature>(0x203D7F30);

float Radius = 100.0;
int pClassUser = call_GetNextCreature(pUNetworkHandler, Radius, PrevID));

И все, при таком вот вызове клиент закрывается. Работает только одна функция GetUser..

Вот код из IDA 7:

.text:203D7F30 ; Exported entry 5758. ?GetNextCreature@UNetworkHandler@@UAEPAUUser@@MH@Z
.text:203D7F30
.text:203D7F30 ; =============== S U B R O U T I N E =======================================
.text:203D7F30
.text:203D7F30
.text:203D7F30 ; struct User *__thiscall UNetworkHandler::GetNextCreature(UNetworkHandler *__hidden this, float, int)
.text:203D7F30                 public ?GetNextCreature@UNetworkHandler@@UAEPAUUser@@MH@Z
.text:203D7F30 ?GetNextCreature@UNetworkHandler@@UAEPAUUser@@MH@Z proc near
.text:203D7F30                                         ; DATA XREF: .text:20522E68↓o
.text:203D7F30
.text:203D7F30 var_8           = dword ptr -8
.text:203D7F30 arg_0           = dword ptr  4
.text:203D7F30 arg_4           = dword ptr  8
.text:203D7F30
.text:203D7F30                 mov     edx, [esp+arg_4]

Чего я не так делаю?

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

20 часов назад, edx сказал:

Нужно не допустить одновременной записи и чтения одной и той же переменной?

Именно так. Открывать файл эксклюзивно при чтении и записи. Можно мьютексом это сделать, мьютексы глобально можно юзать. 

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

  • 2 недели спустя...
В 15.04.2020 в 16:36, edx сказал:

 

Чего я не так делаю?

 

1) найди код в игре который вызывает эту функцию и хорошенько присмотрись
2) через asm вставки(если работаешь на x32) ИЛИ через отдельный *.asm файл(если в x64) повтори один в один вызов этой функции из своей DLL

Параметры функции могут передаваться через стек, а могут через регистры. Возможно ты что-то упускаешь


+ Еще вариант:
Поставь бряк на эту функцию и посмотри как туда передаются параметры с реальными данными когда сама игра вызывает эту функцию

 

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

В 25.04.2020 в 22:22, mrPTyshnik сказал:

 

1) найди код в игре который вызывает эту функцию и хорошенько присмотрись
2) через asm вставки(если работаешь на x32) ИЛИ через отдельный *.asm файл(если в x64) повтори один в один вызов этой функции из своей DLL

Параметры функции могут передаваться через стек, а могут через регистры. Возможно ты что-то упускаешь


+ Еще вариант:
Поставь бряк на эту функцию и посмотри как туда передаются параметры с реальными данными когда сама игра вызывает эту функцию

 

Спасибо, уже разобрался. Похукал немного функции, посмотрел что за параметры идут через стек и т.п.

Теперь вопрос, как скрыть загруженную в процесс dll от сканера памяти) И как сделать беспалевный вызов API клиента.

Есть какой-то способ заставить античит думать что эта DLL принадлежит клиенту и клиент сам использует свой API?

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

В 14.04.2020 в 09:58, edx сказал:

как правильно организовать обмен информацией между DLL и exe-шником. Через пайп как-то геморно..

Как вызвать функцию удаленно?

 

Ребята пишут свою dll с перехватом нативных функций

как мы все знаем u пакет - unreal scripts 

u пакет  содержит нативные функции 

Когда я собирала компилятор эффектов то пришлось заново делать  Core.u + Engine.u

там  встречались следующие классы HUD + CANVAS они содержаться в Engine.u 

canvas отрисовка на экране 

hud имеет отношение к интерфейсу 

можно взять и посмотреть скажем исходный код rguard защиты она старенькая но код там полезен для изучения.

и вывод на экран и многое другое там все присутствует 

Спойлер

 

 

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

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

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

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