Играя в Unreal Tournament (ut99) я использую тактику "ожидание противника в засаде", таких игроков называют camper.
Я мирно cтою за ящиком и слушаю музыку.
Как только раздаётся сигнал "респавна" (воскрешение игрока).
Я говорю: - Привет!
Во время игры я слушаю музыку. Громкость звуков (в игре) установлены ниже, из-за этого звук respawn'a слышен неотчетливо. Я задумался. Нельзя-ли сделать, что-то в виде информационного датчика?
Зародившееся идея успешно воплотилась. Об этом мой рассказ.
Сформировался такой план.
Запуская UnrealTournament.exe внедрять функцию датчика.
Отслеживаются регистры процессора.
При воскрешении игрока - мигнуть диодом клавиатуры.
В Unreal Tournament, в каталоге System находятся исполняемые и dll-модули.
Я выяснил, модуль Galaxy.dll, воспроизводит звук.
Набросок функции Trigger, на ЯП С с ассемблерной вставкой.
В итоге - функция будет преобразованна в шелл-код, который и будет внедрен.
Как действует Trigger?
Если регистр ESI содержит значение 0x106720F4, значит - нужно проверить регистр EDI, если он содержит одно из значений: 0x412, 0x426, 0x44A, 0x44C, значит сейчас будет сигнал.
void Trigger()
{
_asm cmp esi, 0x106720F4
_asm je NEXT
_asm jmp BACK
NEXT:
_asm cmp edi, 0x412
_asm je FOUND
_asm cmp edi, 0x426
_asm je FOUND
_asm cmp edi, 0x44A
_asm je FOUND
_asm cmp edi, 0x44C
_asm je FOUND
_asm cmp edi, 0x44D
_asm je FOUND
_asm cmp edi, 0x446
_asm je FOUND
_asm cmp edi, 0x44E
_asm je FOUND
_asm cmp edi, 0x44F
_asm je FOUND
_asm cmp edi, 0x448
_asm je FOUND
_asm cmp edi, 0x450
_asm je FOUND
_asm cmp edi, 0xC8F
_asm je FOUND
_asm jmp BACK
FOUND:
//_asm mov [g_found], 1
BACK:
//_asm jmp
}
Как понятно, Trigger работает динамически (в памяти запущенного процесса).
План предварительной подготовки.
Найти в модуле пространство для записи функции Trigger.
С помощью hex-редактора отредактировать Galaxy.dll.
Загрузить в Ida (Интерактивный дизассемблер) - проверить.
Поиск пространства (для записи функции) в модуле Galaxy.dll (для тех кто знаком с ассемблером) не сложная процедура.
В Ida я задал текстовый поиск по-ключевому слову "align" (добавив двойку) (продолжить поиск далее нажимайте комбинацию клавиш Ctrl+T).
Align - директива ассемблера, выравнивает сегменты (вставляет любые символы).
В этом месте и вставим код функции Trigger.
Поиск не заставил долго ждать, в нашем распоряжении (2000h) 8192 байт!
Узнаем у Ida - где, в файле Galaxy.dll - находится адрес (смещение) относительно
физического расположения?
Установить курсор в интересующую позицию.
В пункте меню "Jmp" выбрать "Jmp to file offset...".
Копируем адрес, еще лучше записать в блокнотик.
Открыть файл Galaxy.dll в редакторе (в котором любите работать).
Если вы как и я используете QView - нажмите пару раз клавишу "Enter" - будет вид дизассемблера.
Нажмите клавишу "F2" показывать 32-битный код.
Нажмите "F5" введите адрес: 361BA нажмите клавишу "Enter".
Вы окажетесь в месте где мы запишем код функции Trigger.
Записываем код (в Qview работает клавиша "Tab"):
81FEF4206710 cmp esi, 106720F4
Пока не введен весь код - неизвестен точный адрес (т.е. смещение) на которую нужно делать условный переход - поэтому, временно - вводим nop (ничего не делать) две команды; это как набросок - резервирование.
Здесь располагается переход на выход, пока адрес неизвестен,
временно пропускаем "nop" две команды.81FF12040000 cmp edi, 00000412
"nop" две команды
81FF26040000 cmp edi, 00000426
"nop" две команды
81FF4A040000 cmp edi, 0000044A
"nop" две команды
81FF4C040000 cmp edi, 0000044C
"nop" две команды
81FF4D040000 cmp edi, 0000044D
"nop" две команды
81FF46040000 cmp edi, 00000446
"nop" две команды
81FF4E040000 cmp edi, 0000044E
"nop" две команды
81FF4F040000 cmp edi, 0000044F
"nop" две команды
81FF48040000 cmp edi, 00000448
"nop" две команды
81FF50040000 cmp edi, 00000450
"nop" две команды
81FF8F0C0000 cmp edi, 00000C8F
"nop" две команды
FF15B4BE6710 call dword ptr [1067BEB4]
вызов функции воспроизведения звука, она перенесена из места редактирования
E9A1B5FDFF jmp 000117C8
это безусловный переход, прыгаем в место адрес которого неизменен.
893DE0F06510 mov dword ptr [1065F0E0], edi
это переменная в которую мы заносим данные из регистра "EDI"
EBED jmp 0003621C
переход в пункт #25
Редактируем условные переходы.
Вот что получается:
Теперь идем в место откуда мы переходим в функцию Trigger.
Нажмите в "Qview" клавишу "F5", введите адрес 117C2 и нажмите "Enter".
Запишите:
a) E9F3490200 jmp 000361BA
b) 90 nop
Всё готово. Проверим.
Загрузите Galaxy.dll в Ida (если вы еще не сделали этого).
Если загружен - обновите базу данных (нажмите в меню "Options" - "General" - "Reanalyze program").
Также, ничего не случится, если вы вместо обновления - загрузите модуль "Galaxy.dll" снова.
Идем в функцию Trigger, нажмите в Ida клавишу "G", введите адрес 106361BA.
Cмотрим:
Нажмите на "; CODE XREF: sub_10610C00+BC2" и мы попадем в место откуда вызывается функция, помните - это последнее, что мы редактировали, адрес 106117C2.
Выберите в меню "Debugger" отладчик "Local Win32 Debugger".
Установите точку остановки (BPX) перед переходом (инструкция "jmp").
Нажмите в меню "Debugger" "Start process", установите (чекбокс):
"Don't display this message again" и нажмите "Yes".
Ida попросит указать имя исполняемого приложения, выберите "UnrealTournamet.exe".
Когда программа остановится в точке остановки (BPX).
Пройдите весь путь пошагово (нажимая клавишу F8).
Убедитесь в надежности исправленного участка.
Результат. Программа в сборе.
////////////////////////////////////////////////////////////////////////////////
// WinMain.cpp
//
// По умолчанию в UT99 звук воспроизводит: Galaxy.GalaxyAudioSubsystem.
// Если, что-то будет не так - проверьте файл UnrealTournament.ini,
// установлено-ли: AudioDevice=Galaxy.GalaxyAudioSubsystem
#include <windows.h>
#include <winioctl.h>
char* GAppname="Resp2A Trigger UT99'";
char* GAppname_UT="Unreal Tournament";
void mb(char* s);
void OnKbdLeds();
void ToggleLed(BOOL toggle, int led);
char* appGetOpenFileName();
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
CreateMutex(NULL,0,GAppname);
BOOL AlreadyRunning=(ERROR_ALREADY_EXISTS==GetLastError());
if(AlreadyRunning)
return 1;
// Load config
HKEY key;
RegCreateKey(HKEY_CURRENT_USER,
"Software\\MyCompany\\MyAppname",&key);
DWORD dwLen=MAX_PATH;
static char CommandLine[MAX_PATH];
RegQueryValueEx(key,"CommandLine",NULL,NULL,(BYTE*)CommandLine,&dwLen);
if(!CommandLine[0])
{
lstrcpy(CommandLine,appGetOpenFileName());
if(!CommandLine[0])
{
RegCloseKey(key);
return 0;
}
}
char CurrentDirectory[MAX_PATH];
lstrcpy(CurrentDirectory,CommandLine);
int i;
for(i=lstrlen(CurrentDirectory)-1;i>0;--i)
{
if('\\'==CurrentDirectory[i-1] || '//'==CurrentDirectory[i-1])
break;
}
CurrentDirectory[i]=0;
PROCESS_INFORMATION pi;
STARTUPINFO si;
memset(&si,0,sizeof(STARTUPINFO));
si.cb=sizeof(STARTUPINFO);
si.dwFlags=STARTF_FORCEOFFFEEDBACK;
char cmdLine[512];
wsprintf(cmdLine,"%s",CommandLine);
//wsprintf(cmdLine,"%s 195.98.73.166:6666",CommandLine);
CreateProcess(NULL,
cmdLine,NULL,NULL,0,0,NULL,CurrentDirectory,&si,&pi);
if(!pi.hProcess)
{
CommandLine[0]=0;
RegSetValueEx(key,"CommandLine",NULL,REG_SZ,(BYTE*)(CommandLine),MAX_PATH);
RegCloseKey(key);
char buf[512];
wsprintf(buf,"Failed CreateProcess\n\n\"%s\"",CommandLine);
mb(buf);
}
CloseHandle(pi.hThread);
// Save config
RegSetValueEx(key,"CommandLine",NULL,REG_SZ,(BYTE*)(CommandLine),MAX_PATH);
RegCloseKey(key);
HWND hWnd=FindWindow(NULL,GAppname_UT);
while(!hWnd)
{
Sleep(1000);
hWnd=FindWindow(NULL,GAppname_UT);
}
/*-------------------------------------------------------
The trigger
-------------------------------------------------------*/
DWORD addrTrigger=0x106361BA;
unsigned char codeTrigger[]=
"\x81\xFE\xF4\x20\x67\x10\x74\x02\xEB\x58\x81\xFF\x12\x04\x00\x00"
"\x74\x5B\x81\xFF\x26\x04\x00\x00\x74\x53\x81\xFF\x4A\x04\x00\x00"
"\x74\x4B\x81\xFF\x4C\x04\x00\x00\x74\x43\x81\xFF\x4D\x04\x00\x00"
"\x74\x3B\x81\xFF\x46\x04\x00\x00\x74\x33\x81\xFF\x4E\x04\x00\x00"
"\x74\x2B\x81\xFF\x4F\x04\x00\x00\x74\x23\x81\xFF\x48\x04\x00\x00"
"\x74\x1B\x81\xFF\x50\x04\x00\x00\x74\x13\x81\xFF\x8F\x0C\x00\x00"
"\x74\x0B\xFF\x15\xB4\xBE\x67\x10\xE9\xA1\xB5\xFD\xFF\x89\x3D\xE0"
"\xF0\x65\x10\xEB\xED";
DWORD codeTrigger_Len=117;
/*-------------------------------------------------------
The patch
-------------------------------------------------------*/
DWORD addrPatch=0x106117C2;
// E9F349020090
unsigned char codePatch[]="\xE9\xF3\x49\x02\x00\x90";
DWORD codePatch_Len=6;
DWORD dwMagic=0;
BYTE value[128];
BOOL bRet=FALSE;
int n=14; // Wait init "UT" 14 seconds
while(n)
{
ReadProcessMemory(pi.hProcess,
(LPVOID)addrPatch,&dwMagic,sizeof(dwMagic),NULL);
if(dwMagic)
{
if(0xBEB415FF==dwMagic)
{
memcpy(&value,codeTrigger,codeTrigger_Len);
bRet=WriteProcessMemory(pi.hProcess,
(LPVOID)addrTrigger,&value,codeTrigger_Len,NULL);
if(bRet)
{
memcpy(&value,codePatch,codePatch_Len);
bRet=WriteProcessMemory(pi.hProcess,
(LPVOID)addrPatch,&value,codePatch_Len,NULL);
}
}
break;
} // End "if dwMagic"
--n;
Sleep(1000);
} // End "while"
if(!bRet)
mb("Failed patch");
DWORD addrFound=0x1065F0E0;
DWORD dwFound=0;
hWnd=FindWindow(NULL,GAppname_UT);
while(hWnd)
{
ReadProcessMemory(pi.hProcess,
(LPVOID)addrFound,&dwFound,sizeof(dwFound),NULL);
if(dwFound)
{
switch(dwFound)
{
case 0x412:
dwFound=0xCDC31337;
break;
case 0x426:
dwFound=0xCDC31337;
break;
case 0x44A:
dwFound=0xCDC31337;
break;
case 0x44C:
dwFound=0xCDC31337;
break;
case 0x44D:
dwFound=0xCDC31337;
break;
case 0x446:
dwFound=0xCDC31337;
break;
case 0x44E:
dwFound=0xCDC31337;
break;
case 0x44F:
dwFound=0xCDC31337;
break;
case 0x448:
dwFound=0xCDC31337;
break;
case 0x450:
dwFound=0xCDC31337;
break;
case 0xC8F:
dwFound=0xCDC31337;
//break;
}
if(0xCDC31337==dwFound)
{
OnKbdLeds();
ToggleLed(1,1);
dwFound=0;
bRet=WriteProcessMemory(pi.hProcess,
(LPVOID)addrFound,&dwFound,sizeof(dwFound),NULL);
}
} // End "if dwFound"
Sleep(20); // Give up
hWnd=FindWindow(NULL,GAppname_UT);
} // End "while"
return 0;
}
void mb(char* s)
{
UINT uType=MB_OK | MB_ICONINFORMATION |
MB_SETFOREGROUND | MB_SYSTEMMODAL;
int n=0;
if(strstr(s,"Failed") || strstr(s,"Error"))
++n;
if(n) {
uType &=~MB_ICONINFORMATION;
uType |=MB_ICONWARNING;
} MessageBox(GetActiveWindow(),s,GAppname,uType);
if(n)
ExitProcess(n);
}
/*
void Trigger()
{
_asm cmp esi, 0x106720F4
_asm je NEXT
_asm jmp BACK
NEXT:
_asm cmp edi, 0x412
_asm je FOUND
_asm cmp edi, 0x426
_asm je FOUND
_asm cmp edi, 0x44A
_asm je FOUND
_asm cmp edi, 0x44C
_asm je FOUND
_asm cmp edi, 0x44D
_asm je FOUND
_asm cmp edi, 0x446
_asm je FOUND
_asm cmp edi, 0x44E
_asm je FOUND
_asm cmp edi, 0x44F
_asm je FOUND
_asm cmp edi, 0x448
_asm je FOUND
_asm cmp edi, 0x450
_asm je FOUND
_asm cmp edi, 0xC8F
_asm je FOUND
_asm jmp BACK
FOUND:
//_asm mov [g_found], 1
BACK:
//_asm jmp
}
*/
#define IOCTL_KEYBOARD_SET_INDICATORS CTL_CODE(FILE_DEVICE_KEYBOARD, 2, METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_KEYBOARD_QUERY_INDICATORS CTL_CODE(FILE_DEVICE_KEYBOARD, 0x10, METHOD_BUFFERED,FILE_ANY_ACCESS)
void OnKbdLeds()
{
if(!DefineDosDevice(DDD_RAW_TARGET_PATH,"Kbd000000","\\Device\\KeyboardClass0"))
mb("Failed DefineDosDevice");
HANDLE hDevice=CreateFile("\\\\.\\Kbd000000",GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
if(INVALID_HANDLE_VALUE==hDevice)
mb("Failed open kbd");
unsigned int InBuffer;
DWORD OutBufferSize;
unsigned char p[]={32};
for(int i=0; i<300; ++i)
{
InBuffer=0;
InBuffer |=p[i] << 16;
DeviceIoControl(hDevice,IOCTL_KEYBOARD_SET_INDICATORS,&InBuffer,sizeof(InBuffer),NULL,0,&OutBufferSize,NULL);
Sleep(10);
}
DefineDosDevice(DDD_REMOVE_DEFINITION,"Kbd000000",NULL);
CloseHandle(hDevice);
}
void ToggleLed(BOOL toggle, int led)
{
if(!DefineDosDevice(DDD_RAW_TARGET_PATH,"Kbd000000","\\Device\\KeyboardClass0"))
mb("Failed DefineDosDevice");
HANDLE hDevice=CreateFile("\\\\.\\Kbd000000",GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
if(INVALID_HANDLE_VALUE==hDevice)
mb("Failed open kbd");
DWORD OutBufferSize;
unsigned int InBuffer=0, output=0;
if(!DeviceIoControl(hDevice,IOCTL_KEYBOARD_QUERY_INDICATORS,&InBuffer,sizeof(InBuffer),&output, sizeof(output),&OutBufferSize, NULL))
{
CloseHandle(hDevice);
mb("Failed query kbd");
}
InBuffer=output;
if(toggle)
InBuffer &= ~(led << 16);
else
InBuffer |=led << 16;
DeviceIoControl(hDevice,IOCTL_KEYBOARD_SET_INDICATORS,&InBuffer,sizeof(InBuffer),NULL,0,&OutBufferSize,NULL);
CloseHandle(hDevice);
}
char* appGetOpenFileName()
{
static char fname[MAX_PATH];
OPENFILENAME ofn;
memset(&ofn,0,sizeof(OPENFILENAME));
fname[0]=0;
ofn.lStructSize=sizeof(OPENFILENAME);
ofn.hInstance=GetModuleHandle(NULL);
ofn.lpstrFile=fname;
ofn.lpstrInitialDir="D:\\Games\\ut99\\System";
ofn.nMaxFile=MAX_PATH;
ofn.lpstrFileTitle=NULL;
ofn.nMaxFileTitle=0;
ofn.lpstrTitle="Select UnrealTournament.exe";
ofn.lpstrFilter="Applications (*.exe)\0*.exe\0";
ofn.Flags=OFN_FILEMUSTEXIST |
OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
GetOpenFileName(&ofn);
return fname;
}
////////////////////////////////////////////////////////////////////////////////
// <<eof>> WinMain.cpp
////////////////////////////////////////////////////////////////////////////////
Пытливые умы конечно же спросят: - "как ты вычислил адрес, где происходят звуковые события?".
Об этом я расскажу во второй части.
Комментарии (10)
lymes
10.01.2023 11:10+2ИМХО, проще и чище было бы форкнуть https://github.com/JimmieKJ/unrealTournament, добавить нужный функционал и собрать игру.
kin4stat
10.01.2023 12:02+3Куча непонятных телодвижений и танцев с бубном, ради непонятно чего.
Краткий гайд: берем minhook, берем Си, используем обычный ассемблер встраиваемый в код MSVC. Готово. И никаки танцев с бубном с отладчиками, реверс тулзами, и ручной записи опкодов в память, чтобы просто их транслировать в байты
RetardedWolfy
11.01.2023 14:19Крысерство за ящиком заставило автора двигаться в реальной жизни, лишь бы только дальше продолжать крысить)
gmtd
Не проще было попытаться распознать маркеры звука респауна в аудиопотоке?
baldr
Или поискать звуковой файл в папке с игрой и сделать в нем звук погромче.