Найти тему
Adr Asd

пишем трейнер на Delphi_11 часть 2

-------------------
Ссылки на части:
_часть 1 _часть 3
_получаем патч-байты для SCUM.v0.9.113.75065
-------------------

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

Мы будем получать адреса для патчей - отсчитывая их от найденной сигнатуры. Сигнатура - это последовательность байт, уникальная или не очень. Повторюсь, что можно использовать адрес начала секции кода, но я решил реализовать поиск сигнатур ( на будущее ). В кратце глянем как это выглядит в отладчике x64dbg. Для этого, в x64dbg нам нужен плагин SigMake .

Запустим блокнот, присоединимся с нему отладчиком и выделим любой небольшой участок кода, у меня так: 1- плагин SigMake, 2 - выделенный кусок кода, 3 окно SigMake - сигнатура и маска для выделенного участка кода

-2

Сохраним сигнатуру и маску в текстовый файл, поставим сюда бряк (breakPoint, точку остановки) для быстрого поиска и деактивируем, добавим комментарий ( например sign x). Нажимаем кнопку Scan и в статус строке видим результат сканирования. В моём случае количество найденных 1, сигнатура уникальна. Количество в прочем не важно, в коде мы будем использовать только первый результат. Потом мы сделаем тоже самое для игры. Итак - сигнатура и маска для тестов есть, можно закрыть отладчик, продолжаем.

Когда я кодил трейнер в первый раз - то чуть ли не каждая функция тестировалась по отдельности и было много вспомогательного кода, включая тестовые программы для выяснения некоторых нюансов работы той же ScanSignature (она следует). Тут этого не будет в виду ещё большего объёма информации, просто будет готовый код с некоторыми комментариями.

Функции которые мы сейчас напишем - будут "цепляться" одна за другую и использовать результаты друг друга, по этому не торопясь, внимательно собираем алгоритм и если что то не понятно - просто изучаем код, добавляем свои комментарии, читаем справки, если это winAPI - читаем MSDN.

Сначала напишем код для кнопки, потом для таймера. Итак, сам адрес сигнатуры будет искать функция ScanSignature , а DataCompare ей ассистировать:

Функция использует тип данных p_arrdwx6 - который и создаём. Сразу под блоком констант нашего юнита объявим несколько пользовательских типов:

const
gameName = 'notepad.exe';
type
arrdwx6 = Array of DWORD64; //для массива с результатами сканирования сигнатур
p_arrdwx6 = ^arrdwx6; //для передачи в функцию ( ScanSignature ) массива для результатов
ArrayOfByte = array of byte; //вспомогательный тип

и функции поиска и сравнения, пишу их внутри блока комментария /// функции ///:

//сравнение по маске
function DataCompare(data: PByte; sign: PByte; mask: PWideChar): boolean;
begin
while mask^ <> #0 do begin
if ((mask^ = 'x') and (data^ <> sign^)) then begin
result := false;
exit;
end;
inc(mask); inc(data); inc(sign);
end;
// посмотреть результат поиска по маске
//Form1.Memo1.Lines.Add(mask^ + ' || ' + char(data^) + ' || ' + char(sign^));

result := true;
end;


//p_resultArray^ - массив заполняется найденными адресами сигнатуры, возвращает количество найденных адресов
function ScanSignature(m_hProc:THandle; base: DWORD64; size: DWORD64; sign: PByte; signLen:Cardinal; mask: PWideChar; var p_resultArray:p_arrdwx6; oneResult:boolean=true): DWORD64;
var
mbi:MEMORY_BASIC_INFORMATION; offset: DWORD64; buffer: PByte;
BytesRead: Size_T; i: DWORDLONG; x2:integer; scanCount:DWORD64;
begin
scanCount:=0;
offset := 0;
while (offset < size) do begin
VirtualQueryEx(m_hProc, Pointer(base + offset), &mbi, sizeof(MEMORY_BASIC_INFORMATION));
GetMem(buffer, mbi.RegionSize);
if (mbi.State <> MEM_FREE) then begin
if not ReadProcessMemory(m_hProc, mbi.BaseAddress, buffer, mbi.RegionSize, BytesRead) then begin
Form1.sStatusBar1.SimpleText:='ReadProcessMemory error';
Form1.Memo1.Lines.Add('ReadProcessMemory error');
end;
for i := 0 to mbi.RegionSize do begin
if (DataCompare(buffer + i, sign, mask)) then
begin
SetLength( p_resultArray^, length(p_resultArray^)+1);
p_resultArray^[high(p_resultArray^)]:=DWORD64(mbi.BaseAddress) + i;
inc(scanCount);
end;
if oneResult and (scanCount >=1 ) then begin
break;
end;
end;
end;
offset := offset + mbi.RegionSize;
FreeMem(buffer);
end;
result:=scanCount;
end;

Аргументы функции ScanSignature :
m_hProc - хэндл процесса игры, base - адрес секции кода, size - размер секции кода, sign - указатель на цепочку байт сигнатуры, signLen - длинна сигнатуры, mask - строка символов маски, p_resultArray:p_arrdwx6 указатель на массив типа arrdwx6 заполняемый адресами найденных сигнатур, oneResult - только первый результат. Функция возвращает количество найденных сигнатур.

Теперь напишем функцию getAddressSignature - это обёртка для ScanSignature, она будет подготавливать необходимые данные для вызова ScanSignature и заполнять нужные структуры и переменные. И так - в getAddressSignature с помощью getModuleInfo получаем адрес главного модуля ( есть возможность получать и остальные модули подключенные к процессу), с помощью getPeData получаем данные о секциях из PE-заголовка и сохраняем их в структуру sectionCode с пользовательским типом sections, ну и вызываем ScanSignature - возвращая результатом адрес первой найденной сигнатуры.

Сначала объявим тип данных sections = record , в верху - где объявляли предыдущие пользовательские типы, сразу под ними

type
sections = record
baseAddres:DWORD64;
sName:string;
fullAddress:DWORD64;
//готовый адрес
fullSize:DWORD64;
//полный размер секции, с пустыми строками в конце
virtualAddress:DWORD;
virtualSize:DWORD;
sizeOfRawData:DWORD;
pointerToRawData:DWORD;
characteristics:DWORD;
vaddressNextSection:DWORD;
//для получение всего размера (fullSize) секции кода, включая пустые адреса в конце
end;

и объявим в private переменную этого типа sectionCode и ещё две переменные:

sectionCode:sections; //данные секции кода
SignOne: ArrayOfByte; //хранит сигнатуру, массив байт
MaskOne:PWideChar; // хранить символы маски

создадим функцию getArrayBytesFromString ,в коде где нибудь до использования, можно без объявления:

//возвращает массив байт из hex-строки
//может принимать строку сигнатуру содержащую символы \x , или без них
//пример: '\x40\x53\x48\x83\xEC\x40' или без \x - '40534883EC40'
// replaceX = true для замены \x на $
function getArrayBytesFromString(strSign:string; replaceX:boolean=false):ArrayOfByte;
var
strIterator:integer; strLen:integer; resultArray:ArrayOfByte;
strHexchar:string; counChars:integer; testStr:String;
begin
//если нечётное количество символов - вернём пустой массив
if Odd(strSign.length) then begin SetLength(resultArray,0); result:=resultArray; exit; end;
strHexchar:='$'; counChars:=2; testStr:='';
if replaceX then begin
strSign:=StringReplace(strSign,'\x', strHexchar, [rfReplaceAll, rfIgnoreCase]);
//сколько символов для байта обрабатываем (если в строке присутствует \x - то после замены на $ - 3, иначе 2)
counChars:=3;
strHexchar:='';
// убираем символ $ так как он уже добавлен в символы для байтов
end;
setLength(resultArray,0);
strLen:= strSign.Length; strIterator:=0;
repeat
SetLength(resultArray,length(resultArray)+1);
testStr:= strSign.Substring(strIterator,counChars);
resultArray[high(resultArray)]:= byte(StrToInt(strHexchar + testStr));
strIterator:=strIterator+counChars;
until strIterator = strLen;
result:=resultArray;
end;


ну и инициализация
SignOne и MaskOne в TForm1.FormCreate сразу после
else sStatusBar1.SimpleText:='привилегии не получены'; :

//тестовая сигнатура для notepad.exe w10 SignOne:=getArrayBytesFromString('\x40\x53\x48\x83\xEC\x40\x00\x00\x00\x00\x00\x00\x00\x00\x48\x8B\xD9\x4D\x85\xD2',true);
MaskOne:=PWideChar('xxxxxx????????xxxxxx');

- это сигнатура и маска , что мы получили в x64Dbg с помощью плагина SigMake.

Функция getModuleInfo: объявляем её в блоке type в верху

function getModuleInfo(mPID:Cardinal; aboutProcess:boolean=false; moduleName:string=''):TModuleEntry32;

и реализуем в коде

function TForm1.getModuleInfo(mPID:Cardinal; aboutProcess:boolean=false; moduleName:string=''):TModuleEntry32;
var
hSnapShot0: THandle; mInfo: TModuleEntry32;
begin
hSnapShot0 := CreateToolHelp32Snapshot(TH32CS_SNAPMODULE, mPID);
mInfo.dwSize := sizeof(mInfo);
if (Module32First(hSnapShot0, mInfo)) then
begin
if aboutProcess then
begin
CloseHandle(hSnapShot0);
result:= mInfo;
end;

while (Module32Next(hSnapShot0, mInfo)) do
begin
if AnsiCompareText(mInfo.szModule, moduleName) = 0 then
begin
CloseHandle(hSnapShot0);
result:= mInfo;
end;
end;
end;
result:= mInfo;
end;

Аргументы: mPID - пид процесса, aboutProcess - только для главного модуля, moduleName - используется при получении остальных модулей. Возвращает структуру TModuleEntry32. Дословно расписывать не буду - тут используются сплошь winAPI, читаем справки.

//получаем данные PE заголовка
function getPeData(hNdl:THandle; pAddres0: pointer): Boolean;
var
PIMAGE_DOS_HEADER: ^IMAGE_DOS_HEADER;
PIMAGE_NT_HEADERS: PImageNtHeaders ;
PIMAGE_FILE_HEADER:^_IMAGE_FILE_HEADER;
PIMAGE_OPTIONAL_HEADER32: ^TImageOptionalHeader32;
//PE32
PIMAGE_OPTIONAL_HEADER64:^TImageOptionalHeader64;
//PE32+
PIMAGE_SECTION_HEADER: ^TImageSectionHeader;
isX32App:boolean; peHdr: PByte; BytesRead: Size_T;
i,x:integer; Bufr: array [0..7] of WideChar;
testStr:string;
Section: ^IMAGE_SECTION_HEADER;
//Указатель на Заголовок Секций
CountSection: Integer;
//Количество секций

//флаг используется в цикле обхода секций -для получения адреса следующей секции
//таким образом выясняем полный размер секции кода с пустыми строками в конце

sectionCodeFinded:boolean;
begin
isX32App:=false; sectionCodeFinded:=false; testStr:='';

GetMem(peHdr, $1000);
ReadProcessMemory(hNdl, pAddres0, peHdr, $1000, BytesRead);

//получаем структуры PE
PIMAGE_DOS_HEADER:=pointer(peHdr);
PIMAGE_NT_HEADERS:= pointer(DWORD64(pointer(peHdr)) + DWORD64( PIMAGE_DOS_HEADER._lfanew));
PIMAGE_FILE_HEADER:= @(PIMAGE_NT_HEADERS^.FileHeader);

PIMAGE_OPTIONAL_HEADER64:= @(PIMAGE_NT_HEADERS^.OptionalHeader);
PIMAGE_OPTIONAL_HEADER32:= @(PIMAGE_NT_HEADERS^.OptionalHeader);

if PIMAGE_OPTIONAL_HEADER64^.Magic = $010B then isX32App:=true;

PIMAGE_SECTION_HEADER:= pointer(DWORD64(pointer(peHdr)) + DWORD64(PIMAGE_DOS_HEADER._lfanew)
+ sizeof(PIMAGE_NT_HEADERS^.Signature)
+ PIMAGE_NT_HEADERS^.FileHeader.SizeOfOptionalHeader
+ sizeof(_IMAGE_FILE_HEADER));

if isX32App then begin
Form1.sectionCode.baseAddres:= PIMAGE_OPTIONAL_HEADER32^.ImageBase;

end else begin
Form1.sectionCode.baseAddres:= PIMAGE_OPTIONAL_HEADER64^.ImageBase;

end;

CountSection:=PIMAGE_FILE_HEADER^.NumberOfSections;
Section:=pointer(PIMAGE_SECTION_HEADER);

for i := 1 to CountSection do begin

if sectionCodeFinded then begin

Form1.sectionCode.vaddressNextSection:=Section^.VirtualAddress;
Form1.sectionCode.fullAddress:= Form1.sectionCode.baseAddres + Form1.sectionCode.virtualAddress;
Form1.sectionCode.fullSize:= Form1.sectionCode.baseAddres + Form1.sectionCode.vaddressNextSection - Form1.sectionCode.fullAddress -2;

sectionCodeFinded:=false;
//снимаем флаг чтоб vaddressNextSection не переписался последующими адресами секций
end;

for x := 0 to 6 do
begin
Bufr[x]:= WideChar(Chr(Section^.Name[x]));
end;

testStr:= Bufr;

if AnsiCompareText(testStr,'.text') = 0 then begin

sectionCodeFinded:=true;

Form1.sectionCode.sName:= testStr;
Form1.sectionCode.virtualAddress:=Section^.VirtualAddress;
Form1.sectionCode.virtualSize:=Section^.Misc.VirtualSize;
Form1.sectionCode.sizeOfRawData:=Section^.SizeOfRawData;
Form1.sectionCode.pointerToRawData:=Section^.PointerToRawData;
Form1.sectionCode.characteristics:=Section^.Characteristics;

end;

Section:= Pointer(DWORD64(Section) +
SizeOf(_IMAGE_SECTION_HEADER));
end;

FreeMem(peHdr);
Result := true;
end;

Аргументы: hNdl - хэндл процесса игры, pAddres0 - указатель на базовый адрес модуля, возвращённого getModuleInfo.

И сама getAddressSignature : объявляем её в блоке type юнита
function getAddressSignature(processPid:Cardinal):DWORD64;
и пишем реализацию:

//возвращает адрес искомой сигнатуры (одиночный поиск) , обёртка для ScanSignature
function TForm1.getAddressSignature(processPid:Cardinal):DWORD64;
var
findHandle,hSnapShot:THandle; ProcInfo: TProcessEntry32; module: TModuleEntry32;
resultAddress:DWORD64; scanCountSigns:DWORD64;
scanAdressesResult0:arrdwx6;
p_scanAdressesResult0:p_arrdwx6;
//массив для найденных адресов сигнатур
begin
findHandle:=0; resultAddress:=0; scanCountSigns:=0;

if processPid > 0 then begin
try
//получаем адреса модулей
module := Form1.getModuleInfo(processPid,true);
hSnapShot := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
ProcInfo.dwSize := SizeOf(ProcInfo);

if (Process32First(hSnapshot, ProcInfo)) then begin
while (Process32Next(hSnapShot, ProcInfo)) do begin
if ProcInfo.th32ProcessID = processPid then begin
findHandle:=OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_ALL_ACCESS or PROCESS_VM_OPERATION, False, processPid);
if findHandle > 0 then begin
//указатель на массив для найденных адресов сигнатур
p_scanAdressesResult0:=@scanAdressesResult0;
//заполняет структуру sectionCode данными PE секции .text
getPeData(findHandle, pointer(module.modBaseAddr));
//если пуста структура для данных секции кода ( .text )
if sectionCode.fullAddress <= 0 then begin if findHandle > 0 then closeHandle(findHandle); CloseHandle(hSnapShot); result:=0; exit; end;
//находими адрес сигнатуры для отсчёта смещений
scanCountSigns := ScanSignature(findHandle, DWORD64(sectionCode.fullAddress), sectionCode.fullSize
, Pbyte( SignOne ), length( SignOne ) , MaskOne, p_scanAdressesResult0);

if scanCountSigns > 0 then begin
resultAddress:= scanAdressesResult0[0];
Memo1.Lines.Add('scanCountSigns: '+ inttostr(scanCountSigns));
Memo1.Lines.Add('fullAddress: '+Format('%x',[ sectionCode.fullAddress ]));
Memo1.Lines.Add('fullSize: '+Format('%x',[ sectionCode.fullSize ]));
end;

end;
end;
end;
end;

closeHandle(findHandle); closeHandle(hSnapShot);

except on E:exception do
begin
// Memo1.Lines.Add(DateTimeToStr(Now) + #09 + E.ClassName + #09 + E.MethodName(Exception) + #09 + E.Message);
Form1.sStatusBar1.SimpleText:='ошибка: ' + E.MethodName(Exception) + ' ' + E.Message;
end;
end;
end;

result:=resultAddress;
end;

Скомпилируем, проверим, исправляем ошибки и кодим далее. Добавим в блок private нашего юнита переменную patchAdresOne: DWORD64; -которая будет хранить найденный адрес сигнатуры, инициализируем её нулём в FormCreate - patchAdresOne:=0; , и переходим к процедуре sButton1Click нашей кнопки. Инициализируем нулём patchAdresOne:=0; и сразу после if findProcessGame(gameName) then begin пишем код: patchAdresOne :=getAddressSignature(progID); а последующие строки до sButton1.Caption:='деактивировать'; оборачиваем в IF ,должно получится так: (часть процедуры TForm1.sButton1Click)

if findProcessGame(gameName) then begin //заполняется progID

patchAdresOne :=getAddressSignature(progID);
if patchAdresOne > 0 then begin
SCheckBox1.Enabled:=false;
sLabelFX1.Caption:='pid: ' + inttostr(progID);
sLabelFX2.Caption:='имя: '+ progName;
processFindedFlag:=true;
//установка флага наличия процесса
sStatusBar1.SimpleText:='готов к работе';
//активируем слайдеры
activDeactivSliders('X', clGrayText, True);

Memo1.Lines.Add(' адрес сигнатуры: '+ Format('%x', [ patchAdresOne ]) ) ;
end;
........

и в ветку end else begin //деактивировать трейнер сразу после sLabelFX2.Caption:='имя:'; добавим :

patchAdresOne:=0;
Form1.Memo1.Lines.Add( 'начальный адрес сигнатуры сброшен.' );


//обнулим структуру для данных секции кода ( .text )
sectionCode.baseAddres:=0;
sectionCode.sName:='';
sectionCode.fullAddress:=0;
sectionCode.fullSize:=0;
sectionCode.virtualAddress:=0;
sectionCode.virtualSize:=0;
sectionCode.sizeOfRawData:=0;
sectionCode.pointerToRawData:=0;
sectionCode.characteristics:=0;
sectionCode.vaddressNextSection:=0;


Form1.Memo1.Lines.Add('очищены адреса в arrDataForPatchBytes patchAdres: ' );
Form1.Memo1.Lines.Add('sectionCode.baseAddres: ' + Format('%x',[ sectionCode.baseAddres ]) );
Form1.Memo1.Lines.Add('sectionCode.fullAddress: ' + Format('%x',[ sectionCode.fullAddress ]) );
Form1.Memo1.Lines.Add('sectionCode.fullSize: ' + Format('%x',[ sectionCode.fullSize ]) );

Ну что, самое время получить какие нибудь результаты. Компилируем проект, исправляем косяки и испытываем. Запускаем x64Dbg, наш трейнер, ставим галочку без таймера, запускаем блокнот и активируем. Видим в Memo1 адрес сигнатуры, копируем его, присоединяемся x64Dbg к процессу блокнота. Нажимаем Ctrl+G , вставляем адрес сигнатуры и переходим. Видим - что попали прямиком на наш бряк, который ставили при выборе сигнатуры ранее. Значит поиск работает. Отсоединяемся от процесса и закрываем всё.

Теперь допишем код для таймера. Идём в TForm1.Timer1Timer и после if progID > 0 then begin пишем:

patchAdresOne :=getAddressSignature(progID); // так же заполняет структуру sectionCode с данными секции кода
if patchAdresOne <= 0 then exit;
Memo1.Lines.Add('адрес сигнатуры: ' + Format('%x',[ patchAdresOne ]));

а в блоке end else begin //процесс игры не найдена , где то после Form1.Memo1.Lines.Add(' ***** искомый процесс не найден ***** ' ); пишем:

patchAdresOne:=0;
Form1.Memo1.Lines.Add( 'начальный адрес сигнатуры сброшен.' );

//обнулим структуру для данных секции кода ( .text )
sectionCode.baseAddres:=0;
sectionCode.sName:='';
sectionCode.fullAddress:=0;
sectionCode.fullSize:=0;
sectionCode.virtualAddress:=0;
sectionCode.virtualSize:=0;
sectionCode.sizeOfRawData:=0;
sectionCode.pointerToRawData:=0;
sectionCode.characteristics:=0;
sectionCode.vaddressNextSection:=0;

Form1.Memo1.Lines.Add('очищены адреса в arrDataForPatchBytes patchAdres: ' );
Form1.Memo1.Lines.Add('sectionCode.baseAddres: ' + Format('%x',[ sectionCode.baseAddres ]) );
Form1.Memo1.Lines.Add('sectionCode.fullAddress: ' + Format('%x',[ sectionCode.fullAddress ]) );
Form1.Memo1.Lines.Add('sectionCode.fullSize: ' + Format('%x',[ sectionCode.fullSize ]) );

Ну и инициализируем структуру sectionCode в FormCreate, перед строками

if not sCheckBox1.Checked then begin
Timer1.Enabled := True; //таймер активирован

пишем код:

//инициализируем нулями структуру для данных секции кода ( .text )
sectionCode.baseAddres:=0;
sectionCode.sName:='';
sectionCode.fullAddress:=0;
sectionCode.fullSize:=0;
sectionCode.virtualAddress:=0;
sectionCode.virtualSize:=0;
sectionCode.sizeOfRawData:=0;
sectionCode.pointerToRawData:=0;
sectionCode.characteristics:=0;
sectionCode.vaddressNextSection:=0
;

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

Для второй части достаточно, третья следует, возможно их будет больше.

Полный код Unit1 часть 2.

-3

Всем удачи.