20120529

[Видеоурок] Третья часть про трейнеры на C# - пишем сканер сигнатур!

Подробности - в [уроке], а весь необходимый софт - на [сайте]. Сразу говорю, что это - одна из самых полезных функций любого трейнера.
Придумал, что показать в третьем видео про трейнер на C#, скоро запишу и выложу. На ассемблере нам это тоже пригодится, так что ждите. :)

20120528

[Видеоурок] И что же, всё-таки, делать с указателями?

[Ссылочка].

Смотрим ещё раз, как ищутся указатели, подробно разбираем код из [статьи].

PS: Совсем забыл! Чтобы узнать именно адрес значения (например, чтобы затем записать туда что-нибудь) - просто остановитесь на предпоследнем шаге - не читайте значение адреса, прочитайте последний уровень указателя - результатом будет адрес значения, куда можно писать, используя уже готовые процедуры.

20120525

Про рисование.

Сижу, разбираюсь с графикой. Ну, вы знаете - тема будущих уроков, всякие там wallhack и прочие радости.

Очень сложный материал, особенно для подачи, мне бы самому разобраться как следует. С чего начать - пока думаю, скорее всего, покодим немного самостоятельно - сначала порисуем под Windows при помощи GDI\GDI+ (почитать про это можно [тут]), затем будем осваивать [DirectX] и [OpenGL].

Если очень вкратце, то GDI (и его расширение, GDI+) - это интерфейс (набор функций, но если хочется подробностей - [пожалуйста]) для работы с двухмерной (плоской) графикой в винде. Грубо говоря всё, что вы видите на мониторе, рисуется при помощи функций этого интерфейса - окна, кнопки, буквы и так далее. Если почитать подробности в MSDN, можно найти примеры рисования линий, точек, геометрических фигур, работу с кистями и много чего интересного.

Одна проблема - работает эта штука довольно медленно, что не устраивает производителей игр, для которых придумали DirectX - набор интерфейсов для отрисовки графики, работы со звуком и прочими вещами, связанными с играми и OpenGL - ещё одну (полностью открытую) библиотеку для рисования графики. Фишка этих двоих в том, что они рисуют быстро - на порядки быстрее, чем GDI\GDI+, так что все более-менее требовательные к железу игры используют эти технологии.

Их-то нам и предстоит ковырять (или смотреть, как их ковыряю я, попивая чаёк с бутерами), но для ковыряния нужна какая-никакая подготовка.

Для начала, почитайте вот про [эту] функцию. Теперь смотрим на код:


format PE GUI 4.0 
entry start 
include 'win32ax.inc' 
section '.data' data readable writable
ps PAINTSTRUCT ?
hDC dd ? 
section '.text' code readable executable
  start:
        invoke  GetModuleHandle,0
        invoke  DialogBoxParam,eax,37,HWND_DESKTOP,DialogProc,0
        or      eax,eax
        jz      exit
  exit:
        invoke  ExitProcess,0
proc DialogProc hwnddlg,msg,wparam,lparam
        push    ebx esi edi
        cmp     [msg],WM_INITDIALOG
        je      .wminitdialog
        cmp     [msg],WM_COMMAND
        je      .wmcommand
        cmp     [msg],WM_CLOSE
        je      .wmclose
        cmp     [msg],WM_PAINT
        je      .wmpaint
        cmp     [msg],WM_LBUTTONDOWN
        je      .move
        cmp     [msg],WM_RBUTTONUP
        je      .wmclose
        xor     eax,eax
        jmp     .finish
  .move:
        invoke  SendMessage,[hwnddlg],WM_NCLBUTTONDOWN,2,0
        jmp     .processed
  .wmpaint:
        invoke  BeginPaint,[hwnddlg],ps
        mov     [hDC],eax
        invoke  Rectangle,[hDC],10,10,30,30
        invoke  EndPaint,[hwnddlg],ps
        jmp     .processed
  .wminitdialog:
        jmp     .processed
  .wmcommand:
        jmp     .processed
  .wmclose:
        invoke  EndDialog,[hwnddlg],0
  .processed:
        mov     eax,1
  .finish:
        pop     edi esi ebx
        ret
endp 
section '.idata' import data readable writeable
  library kernel,'KERNEL32.DLL',\
          user,'USER32.DLL',\
          gdi,'GDI32.DLL'
  import kernel,\
         GetModuleHandle,'GetModuleHandleA',\
         ExitProcess,'ExitProcess';,\
  import user,\
         DialogBoxParam,'DialogBoxParamA',\
         SendMessage,'SendMessageA',\
         EndDialog,'EndDialog',\
         BeginPaint,'BeginPaint',\
         EndPaint,'EndPaint'
  import gdi,\
         Rectangle,'Rectangle'

section '.rsrc' resource data readable
  directory RT_DIALOG,dialogs
  resource dialogs,\
           37,LANG_ENGLISH+SUBLANG_DEFAULT,demonstration
  dialog demonstration,'Create message box',70,70,190,175,WS_POPUP
  enddialog

В процедуре работы диалогового окна (DialogProc) у нас появился обработчик нового события - [WM_PAINT]. Это крутое событие вызывается всякий раз, когда окно хочет, чтобы его нарисовали. Тем самым GDI, да-да. Следовательно, если у нас есть обработчик этого события -  обязательно надо там что-нибудь нарисовать (в голову лезут неприличные слова и забор)!

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

Гораздо более интересен нам ответ, который приходит в регистр EAX после выполнения функции. Ответ этот - хэндл на штуковину под названием контекст устройства ([device context]), по сути - структура, описывающая выбранную область рисования, выбранную кисть, её цвет и прочие параметры.

Так, штука важная - сохраняем в отдельной переменной hDC, она нам ещё понадобится. А для чего? Смотрим на следующую строчку и видим вызов функции [Rectangle], которая, как не трудно догадаться из её названия, рисует прямоугольник. Что ей нужно сообщить в аргументах? А всего ничего - контекст устройства (который у нас уже есть) и координаты верхнего правого и нижнего левого угла. Про координаты сделаю небольшую картинку, даже парочку.

Вот как координаты считаются в обычном мире человеков:

Значит, две оси (монитор у нас, как известно, плоский) - абсцисс и ординат, X и Y. Нижний левый угол - ноль. Точка, которую я мастерски нарисовал, имеет координаты (10;10). Всё понятно, правда? На геометрии в школе было то же самое, бррр!

А теперь - как дела обстоят в мире компьютеров:


Всё наоборот, начало координат - верхний левый угол! Предлагаю в верхний правый угол монитора прикрепить бумажку цветную. В общем, привыкнуть придётся. Теперь вспоминаем нашу функцию, рисующую прямоугольник:

invoke  Rectangle,[hDC],10,10,30,30
Координаты верхнего левого угла - (10;10), нижнего правого - (30;30). Вот картинка работающей программы (крестики я нарисовал):

 Значит, два цветных крестика - как раз те самые (10;10) и (30;30), красный и синий, соответственно. Представили, да? Важно тут запомнить, что начало отсчёта (ноль) - верхний левый угол, а не нижний левый, как на геометрии.

После того, как прямоугольник (а в нашем случае - квадрат, но никто не мешает задать другие координаты) нарисовался, мы красноречиво сообщаем, что хватит это терпеть рисование закончено - вызываем [EndPaint], сообщая ей всё тот же хэндл окна и контекст устройства.

Воот, как-то так. На целую лекцию тянет! Уф. В общем, пока я разбираюсь дальше и привожу в порядок кашу в голове (т.к. сам изучаю эту тему практически с нуля), попрактикуйтесь - найдите в MSDN другие функции для рисования, почитайте про них, посмотрите примеры, попробуйте порисовать сами. Удачи, это ведь так интересно!



20120524

Ну очень важно.

Внимание, вопрос!
Про что мне всё-таки записать третью часть видеоурока по трейнерам? Что ещё научимся делать?

Пилим дальше трейнер на C# - учимся работать с указателями.

Ребята, я написал немного кода, но он такой небольшой, что на видеоурок явно не тянет - ну не знаю я, о чём там рассказывать вслух, поэтому напишу здесь.

Задача у нас такая - есть указатель вида 0x12345+0x1+0x5+0x7, надо суметь его прочитать. Что такое указатель - все помнят? Смотрите:

0x12345 - по этому адресу будет лежать другой адрес (это ведь указатель!). Допустим, 0xA.
Прибавляем к результату первое смещение, получаем 0xA+0x1. Читаем получившийся адрес:

[0xA+0x1] = второй адрес, скажем, 0xB

[0xB+0x2 (второе смещение)] = 0xC.

...
...

[0xD+0x7] = 123 - нужное нам значение, на которое указатель и указывает. Вот код:


public int ReadBytes()
        {          
            if (pID != 0) //Проверяем, что игра найдена
            {
                var dwAddress = 0x55D804; //Базовый адрес указателя
                byte[] buffer = new byte[4]; //Буфер, массив из четырёх байт, куда будут читаться значения
                int bytesread; //Переменная-заглушка, хранящая количество прочитанных байт
                var handle = OpenProcess(0x001F0FFF, false, pID); //Получаем хэндл процесса игры
                ReadProcessMemory(handle, (IntPtr)dwAddress, buffer, 4, out bytesread); //Читаем значение базового адреса
                var result = BitConverter.ToInt32(buffer, 0); //Конвертируем массив байт в циферки
                result += 0x70; //Прибавляем первое смещение
                ReadProcessMemory(handle, (IntPtr)result, buffer, 4, out bytesread); //Читаем получившийся адрес
                var temp = BitConverter.ToSingle(buffer, 0); //Конвертируем результат во float
                CloseHandle(handle); //Закрываем хэндл игры
                return temp; //Возвращаем результат
            }
            return -1; //Если не нашли игру - возвращаем -1.
        }

Тут у нас указатель такой - 0x55D804 + 0x70 = Значение, причём это float. Читаем значение базового адреса, конвертируем из массива байт в цифры, прибавляем смещение, читаем получившийся адрес, конвертируем во float - вуаля! Всё просто. Но код можно улучшить, если у нас много смещений. Смотрите:

//В аргументах присылаем базовый адрес указателя + массив смещений
public int ReadBytes(int baseAddress, byte[] offsets)
        {          
            if (pID != 0 && baseAddress != 0 && offsets.Length != 0) //Проверяем, что всё на месте
            {              
                byte[] buffer = new byte[4]; //Буфер для чтения
                int bytesread; //Заглушка
                var handle = OpenProcess(0x001F0FFF, false, pID); //Получаем хэндл
                ReadProcessMemory(handle, (IntPtr)baseAddress, buffer, 4, out bytesread); //Читаем базовый адрес
                for (var i = 0; i < offsets.Length; i++) //Бежим в цикле, пока не кончатся смещения
                {
                    baseAddress = BitConverter.ToInt32(buffer, 0); //Конвертируем результат в циферки
                    baseAddress += offsets[i]; //Прибавляем смещение
                    ReadProcessMemory(handle, (IntPtr)baseAddress, buffer, 4, out bytesread); //Читаем
                }
//Как только смещения в массиве закончились
CloseHandle(handle); //Закрываем хэндл
                var result = BitConverter.ToSingle(buffer, 0); //Конвертируем результат во float               
                return result; //Возвращаем результат
            }
            return -1; //Иначе - ошибка
        }



В принципе, всё довольно просто, если не понимаете - почитайте что-нибудь про циклы, там всё понятно. Вызывать эту функцию нужно вот так:


var address = 0x55D804; //Базовый адрес
var offsets = new byte[] {0x70}; //Массив со смещениями
var myFloat = ReadBytes(address, offsets); //Читаем!





[Видеоурок] Ломаем Split Second!

[Ссылка] на урок. Заранее извиняюсь за небольшое отставание звука (не критичное) - какие-то проблемы с видеоредактором.

В уроке демонстрируется поиск и взлом неизвестного значения (экранной полоски). С нуля, совсем. Из инструментов - голый [Cheat Engine]. Начинаем с поиска значения, заканчиваем уже готовым скриптом (или трейнером, если вы рукастые), попутно стараюсь комментировать свои действия, т.к. уроки на эту тему уже были.

Следующий урок - не за горами!