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

MasterGH

Ветераны
  • Постов

    2 999
  • Зарегистрирован

  • Победитель дней

    129

Сообщения, опубликованные MasterGH

  1. Ещё хочется добавить по поводу ToolHelp 32 API.

    В данном случае

    FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    мы создаём поиск процессов. А ещё существует поиск модулей. В тех случаях, когда нам нужно исправлять что-то в модулях.

    Также хочу напомнить, что есть класс TSystemInfo32 (вроде правильно написал), который может помочь в некоторых случаях. Например определить адресные границы .exe файла или модуля, найти директорию в которой находится исполняемый файл по дескриптору процесса и другие

    СПРАВОЧНИК ПО API функциями

  2. Я знаю, что это извращение, но есть игры написанные на .Net такие как Eufloria.

    Соответственно для игр на .Net должны быть .Net трейнеры, в том случае если нужно сделать трейнер платформанезависимым.

    Вот пример компиляции Окна приветствия без среды разработки на C- подобном языке.

    using System;
    using System.Windows.Forms;

    class HelloMessage
    {
    public static void Main()
    {
    MessageBox.Show("Hello...");
    }
    }

    Создать bat - ник и запустить

    C:WINDOWSMicrosoft.NETFrameworkv3.5csc.exe /r:System.Windows.Forms.dll HelloMsg.cs

    Размер exe файла 3,50 КБ.Будет работать на всех ОС-мах поддерживающих .NET.

  3. Пока простейший пример (остальные примеры в этой теме)

     

    Скрытый текст
    
    #include "stdafx.h"
    #include <iostream>
    #include <windows.h>
    using namespace std;
    int main(){
    HWND hWnd;    
    DWORD dwID;    
    HANDLE hProcess;    
    hWnd = FindWindow(NULL, "Test");
    GetWindowThreadProcessId(hWnd, &dwID);
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, dwID);
    int value = 1000000;
    WriteProcessMemory(hProcess, (void*)0x0045B5A4, &value, sizeof(&value),NULL);
    return 0;
    }


     

    Некоторые тонкости.

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

    Оптимизация выходного файла в Microsoft Visual C++.

    Параметры линкера для уменьшения размера:
    - объединение секций

    - можно еще снизить выравнивание, например, задать 512 байт или 32 байта

    или

    соответственно.
    - смена точки входа

    или

    для консольного и виндового приложения соответственно. Уменьшает размер, т.к. тогда линкер не пихает в экзешник код стартовой функции mainCRTStartup (WinMainCRTStartup). Только надо помнить, что параметры main (WinMain) в этом случае не будут нести никакого смысла, аргументы командной строки придется получать явно через API GetCommandLine().
    - еще можно убрать нафиг CRT или линковать ее динамически:
     

    С учетом всех рекомендаций:
     

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

    UPD: Можно снизить выравнивание до 16-и байт, притворившись, что мы собираем драйвер:

    это нужно вписать в настройки проекта, в #pragma comment(linker) это не прокатит

    Источник Античат

    /MERGE:.data=.text /MERGE:.rdata=.text
    /ALIGN:32
    /ALIGN:512
    /ENTRY:main
    /ENTRY:WinMain
    /NODEFAULTLIB msvcrt.lib
    #include <windows.h>
    #pragma comment(linker, "/NODEFAULTLIB /MERGE:.data=.text /MERGE:.rdata=.text /ALIGN:512 /ENTRY:WinMain")
    int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int)
    {
    MessageBox(0, "Hello, World!", "Tiny application", MB_ICONINFORMATION);
    return 0;
    }
    00400230 >/$   55                      PUSH EBP
    00400231  |.   8BEC                    MOV EBP,ESP
    00400233  |.   6A 40                   PUSH 40                                      ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
    00400235  |.   68 08024000             PUSH tiny.00400208                           ; |Title = "Tiny application"
    0040023A  |.   68 1C024000             PUSH tiny.0040021C                           ; |Text = "Hello, World!"
    0040023F  |.   6A 00                   PUSH 0                                       ; |hOwner = NULL
    00400241  |.   FF15 00024000           CALL DWORD PTR DS:[<&USER32.MessageBoxA>>    ; \MessageBoxA
    00400247  |.   33C0                    XOR EAX,EAX
    00400249  |.   5D                      POP EBP
    0040024A  \.   C2 1000                 RETN 10
    /ALIGN:16 /DRIVER
    • Плюс 1
  4. Пример использования MHS скрипта при заморозке

    intromhsscripts.gif

    Объявляем пару переменных через extern. Может можно и без extern я ещё не проверял. Далее по смещению от адреса (на котором делаем скрипт) берём другой адрес. Затем значении другого адреса пишем в первоначальные. Всё просто. Не забываем включить галочку.

    ЕСЛИ объявляем адреса переменные через extern, то они будут работать с адресам в процессе.

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

    Особенные благодарности будут перенесены в этот пост. Возможно, в будущем администрация внесёт рейтинг, звания и награду - медали. Возможно, с моей стороны будут созданы закрытые материалы, к которым будут доступны только избранные набравшие определённый рейтинг.

    Особая благодарность выражается людям за их деятельность.

    Xipho

    Xipho, является главным основателем форума и проекта gamehacking[RU]. У него родилась идея создать форум, которую он и воплотил в реальность. Он же создал форум, он же проделывает работы по настройке форума и его улучшению. С его стороны сделан большой труд, а также он продолжает работать над форумом.

    Xipho выражается особенная благодарность за создание спойлера на форуме.

    Выражается особенная благодарность за создание и оформление статей.

    Выражается особенная благодарность за создание статьи "F.E.A.R., обход расположения кода в модулях"

    MasterGH

    MasterGH является сооснователем проекта gamehacking[RU]. Также как и Xipho продумывал логику разделов и подразделов форума. Вносит свои предложения и идеи связанные с форумом.

    gluk

    Выражается благодарность за публикацию статьи "Взлом Doom III" - участие в проекте Gamehacking[RU].

    aliast

    Бета-тест CE RUS, перевод тутора CE, перевод окна настроек - участие в проекте Gamehacking[RU].

    SERGANT

    Идеи и бета-тест CE RUS - участие в проекте Gamehacking[RU].

    alexweiye

    Создание трейнеров - участие в проекте Gamehacking[RU].

    Akama

    Создание трейнеров - участие в проекте Gamehacking[RU].

    L4R

    Поддержка форума "Новости игровой индустрии" - участие в проекте Gamehacking[RU].

  6. хе-хе )

    Выход намечен в числах января до середины февраля это информация с IRC-канала.

    В прошлом году CE новой версии предстала 18 января, но может так случится, что неожиданно CE вдруг выйдет, например, сегодня :ninja:

  7. Игра: Medieval 2: Total War

    post-3-0-38953500-1395065071_thumb.jpg

    Спрос на трейнер: возможно актуален.

    Дата выхода: 14 ноябрь 2006

    Мин. системные требования: 2 GHz, 512 MB, 128 MB video

    Дополнительная информация: стратегия... отличная игрушка, год назад я от неё оторваться не мог играл сутками полторы недели или больше. Играл конечно без читов )

    Статья сделана под CheatEngine 5.5 RUS (!)

    Реализованы чит-коды:

    1. Деньги

     

    Т.к. они ломаются просто и в них нет ничего нового, то ищем адрес денег и пишем скрипт по нему.

    [ENABLE]
    alloc(newmem,2048)
    label(returnhere)
    
    newmem:
    mov [eax+04],#1000000
    mov eax,[eax+04]
    test eax,eax
    jmp returnhere
    
    medieval2.exe+7F7071:>E8FF8D4C2410E8E4FBE7FF05800A0000YYxxxxxxxxB2FF7C108854240BC644
    jmp newmem
    returnhere:
    
    
    [DISABLE]
    medieval2.exe+7F7071:>E8FF8D4C2410E8E4FBE7FF05800A0000YYxxxxxxxxB2FF7C108854240BC644
    mov eax,[eax+04]
    test eax,eax
    dealloc(newmem)
    2. Бесконечный ход (автор MasterGH)
     

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

    post-3-0-12454900-1395064979_thumb.jpg

    Возьмем, к примеру, нашего ассасина. В его структуре существующие значения 34 и 7 могут находиться рядом, но увы отсев группы значений ничем мне не помог. Вполне возможно что я не правильно его настроил... Тем не менее, проще выйти на структуру игрока отсеивая его координаты перемещения, а ещё проще по уменьшающимся шагам.

    Артмани здесь себя показала с хорошей стороны. В настройках ставим не округлять и ищем неизвестное правилом уменьшилось/увеличилось типов: 4 байта с точкой, 4 байта целое, 1 байт. В итоге нашлось пару адресов, заморозив которых мы получаем бесконечное передвижение ассасина.


    +c8: 100.00 //данные героя
    +1ec: 100.00 // копия данных, по которой выделяется область прохождения

    где [_pID] – указатель на адрес структуры героя. Пока _pID нам не нужно знать.

    Теперь перед нами два коэффициента, которые позволят ходить сколько угодно. Но нам надо ещё сделать так, чтобы мы могли ходить и выполнять свои цели со 100%-ной эффективностью.

     
    3. 100% успех победы персонажей
     

    Для этого надо найти смещения адресов в которых расположен опыт.

    Я нашёл адрес ещё одного ассасина и сравнил характеристики обоих героев и по интуиции за минут пять нашёл нужные адреса ))

     

    +80: [[x]+0x38]=0…10 //опыт героя
    +c8: 100.00 //данные героя
    +1ec: 100.00 // копия данных, по которой выделяется область прохождения

     

    post-3-0-03217900-1395064987_thumb.png

    Итак я заморозил опыт моего ассина на максимуме:

    post-3-0-87384200-1395064991_thumb.jpg

    Теперь я попробую напасть на кого-то )

    post-3-0-14780300-1395064997_thumb.jpg

    Хм… странное дело, почему же вероятность успеха увеличилась, но не на 100%.

    Чтобы понять, в чём дело ставлю бряк на чтение на адресе опыта. Выделяю асcасина направляю на жертву.

    Вот где оказались.

    post-3-0-34870200-1395065001_thumb.png

    EAX= 0000000A
    EBX= 11D803C8
    ECX= 11D803F8
    EDX= 00000003
    ESI= 11D803C8
    EDI= 0000001A
    ESP= 0012AE5C
    EBP= 0000002F
    EIP= 005AED2C

    В этом месте происходит сравнения опыта с нулём, меньше нуля, больше нуля, но меньше 10, больше 10. Чтобы не загромождать статью проделанными действиями я буду писать коротко. За помощью я обратился к OllyDbg и IDA. Я вышел по коду вверх и смотрел что происходит с опытом и как он преобразуется в проценты вероятности.

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

    post-3-0-50954700-1395065005_thumb.png

    Здесь было сравнение опыта с разными числами.

    Опыт в начале функции был равен 10, затем получилось 11.

    Т.е. было 10 опыта, затем стало 11.

    Затем мы умножаем 11 на 12 = 132. Откуда эти 12 взялись, будем разбираться дальше или не будем.

    Здесь мне бы пришлось делать очень много скриншотов (штук 15-20), мне это делать в лом, поэтому привожу конечный.

    post-3-0-51242100-1395065009_thumb.png

    Мы находимся всё в той же функции, тут мы видим получение «процентного числа» в игре в регистре сопроцессора. Оно равно 90,75. Это число на протяжении всего кода раза три видоизменялось. В тех местах, где я замечал изменения, я ставил плюсы в комментариях.

    Итак как было у ассина у которого было 10 опыта.

    10+1=11

    11*12=132

    132*0,892=117,85

    117,85*0,7 = 82.5

    82.5 * 1,1 = 90,75

    А теперь у ассасина у которого 3 опыта и который выбирает того же врага

    3+1=4 (3 отличается от предыдущего)

    4*12=48

    48*0,892=42,85

    42,85*0,7 = 30

    30 * 1,0 = 30 (1,0 отличается от предыдущего)

    Теперь нападём этими аcсасинами на другого врага - священника

    Итак, как было у асcасина у которого было 10 опыта.

    10+1=11

    11*12=132

    132*0,25=33 (0,25 отличается)

    33*2 =66

    66*0,7 = 46.2

    42.6 * 1,1 = 50,82

    Значит вероятность завалить священника 51%

    А теперь у асcасина у которого 3 опыта и который выбирает священника.

    3+1=4 (делаем вывод, что опыт влияет на вероятность)

    4*12=48 (делаем вывод, что 12 это константа)

    48*0,25=12 (делаем вывод, что 0,25 привязана к священнику)

    12*2 =24 (делаем вывод, что иногда может быть умножения на 2 в случаях священников)

    24*0,7 = 16,8 (0,7 константа)

    16,8* 1,0 = 16,8 (1,0 отличается от предыдущего)

    post-3-0-80374900-1395065017_thumb.jpg

    Я решил наугад пойти. У ассасина у которого 3 опыта, я решил сделать вероятность замочить священника 100%, оставляя ассину 3 опыта.

    К моему удивлению тот же код (который мы разбирали) стал работать и я вновь подставил 100% в регистр где надо (об этом ниже) и в итоге я замочил священника.

    Далее я решил замочить инквизитора, который часто не даёт никому покоя. Самое удивительное, что инквизиторов почти никогда невозможно убить, а теперь можно убить сразу.

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

    1) Убийство с одной попытки.

    post-3-0-19716800-1395065025_thumb.png

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

     

    [ENABLE]
    alloc(newmem,2048)
    label(_originalcode)
    label(_returnhere)
    
    newmem:
    cmp ebx,[pId]
    jne short _originalcode
    mov esi,#100
    _originalcode:
    mov eax,[esp+1c]
    test eax,eax
    jmp _returnhere
    
    medieval2.exe+1979F9:>8BC18B0DA80262013BC18BF07C028BF1YYxxxxxxxxC0740983E80175FB8944
    jmp newmem
    nop
    _returnhere:
    
    
    [DISABLE]
    medieval2.exe+1979F9:>8BC18B0DA80262013BC18BF07C028BF1YYxxxxxxxx90740983E80175FB8944
    mov eax,[esp+1c]
    test eax,eax
    dealloc(newmem)
     

    2) Беспрепятственное хождение

    Надо просто напросто заморозить значения ниже по смещениям (мы это сделаем в след. скрипте)

    [_pID]:

    +c8: 100.00 //данные героя, область прохождения

    +1ec: 100.00 // копия данных, по которой выделяется область прохождения

    3) Нахождение _pid

    Давайте, определим, наконец, чему равен _pID.

    Для того чтобы трейнер был универсальным, нужно вытащить _pID из инструкции типа А. _pID должен указывать на то, что объекты расы наши.

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

     

    [ENABLE]
    globalalloc(newmem,2048)
    label(_returnhere)
    
    label(pId)
    registersymbol(pId)
    
    newmem:
    mov [pId],esi
    mov [esi+c8],447A0000
    mov [esi+1ec],447A0000
    ucomiss xmm0,[esi+000000c8]
    jmp _returnhere
    pId:
    dd 0
    
    medieval2.exe+184A40:>558BEC83E4F883EC0C0F57C053568BF1YYxxxxxxxx00009FF6C444570F8B98
    jmp newmem
    nop
    nop
    _returnhere:
    
    [DISABLE]
    medieval2.exe+184A40:>558BEC83E4F883EC0C0F57C053568BF1YYxxxxxxxx90909FF6C444570F8B98
    ucomiss xmm0,[esi+000000c8]
    unregistersymbol(pId)
    dealloc(newmem)
     

    Итак, активируем скрипт по получению pid и все чит-коды…

    Результаты:

    Теперь ассасин (и другие из нашей расы) могут ходить на большие расстояния много раз и убивать каждого на своём пути.

    post-3-0-49069000-1395065032_thumb.jpg

    post-3-0-22441800-1395065038_thumb.jpg

    Но есть пока не решённая проблема.

    Теперь разбираемся, с тем как сделать убийство асcасином за ход игры больше одного раза касательно войска. Т.е. когда асcасин убивает игрока, тот умирает, а на его место встаёт другой (из строя армии), которого убить не представляется возможным в течении одного хода игры...

     
    4. Снятие ограничения на ход игры
     

    Удобнее сделать поставленную задачу следующим способом. Взять не асcасинов, а шпионов. Т.к. шпион не убивает, а следит за жертвой. Жертва не умирает и новую искать не нужно.

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

    На рисунке ниже класс-предок я обозначил скорее всего не правильно, но исправлять уже не буду ))

    post-3-0-08893800-1395065449_thumb.gif

    Адреса, которые обозначены восклицательными знаками, могут быть ключевыми. Итак, первое смещение с восклицательным знаком было мной проверено и действительно, когда адрес по смещению +BC заморожен на FFFFFFFF, то ход не пропадает, в противном случае будет 25 в десятичной системе. Запомним.


    +80: [[x]+0x38]=0…10 //опыт героя
    +bc: 0xFFFFFFFF // активность хода героя
    +c8: 100.00 //данные героя
    +1ec: 100.00 // копия данных, по которой выделяется область прохождения
     

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

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

    post-3-0-43232000-1395065045_thumb.png

    Результат

    post-3-0-70020100-1395065366_thumb.png

    Итоговый скрипт на повышение вероятности при саботаже и заказных убийствах для асcасинов (также для других юнитов нашей рассы)

     
    [ENABLE]
    alloc(newmem,2048)
    label(_originalcode)
    
    newmem:
    cmp ebx,[pId]
    jne short _originalcode
    mov esi,#100
    _originalcode:
    mov eax,[esp+1c]
    test eax,eax
    ret
    
    medieval2.exe+1979F9:>8BC18B0DA80262013BC18BF07C028BF1YYxxxxxxxxC0740983E80175FB8944
    call newmem
    nop
    
    medieval2.exe+197BAC:>EB0E83F85F7D048BF0EB05BE5F000000YYxxxxxxxxC0740983E80175FB8944
    call newmem
    nop
    
    [DISABLE]
    medieval2.exe+1979F9:>8BC18B0DA80262013BC18BF07C028BF1YYxxxxxxxx90740983E80175FB8944
    mov eax,[esp+1c]
    test eax,eax
    
    medieval2.exe+197BAC:>EB0E83F85F7D048BF0EB05BE5F000000YYxxxxxxxx90740983E80175FB8944
    mov eax,[esp+1c]
    test eax,eax
    
    dealloc(newmem)

     

  8. image002df.jpg

    post-3-0-46008900-1424505051_thumb.jpg

     

    Спрос на трейнер: не актульно

    Дата выхода: 7 июня 2005

    Минимальные требования: CPU 1 GHz, 256 Mb, 64 Mb Video

    Рекомендуемые требования: CPU 1,5 GHz, 512 Mb, 128 Mb Video

    На данный момент по игре рассмотрены:

    1. Расструктуризация оружия (автор MasterGH)]

    Введение

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

    Я не ставил цель рассмотрение бесконечного оружия для Карла по той простой причине, что это не сложно, но всё-таки напишу немного об этом в конце статьи.

    Обзор

    В GTASA есть два типа отображения снаряжения у главного героя Карла: оружие с обоймой и без обоймы.

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

    Беру к примеру автомат

    post-3-0-66835500-1424505055.jpg

    Его значение патронов «4905-30» (первое число - патроны, второе - патроны в обойме)

    На картинке общее количество патронов и количество патронов в обойме это две взаимосвязанные величины. Перезарядка произойдёт, если в обойме будет 0 патронов. Для начала отсеиваю адрес со значением 30. Какой нашёлся адрес неважно, он не зелёного цвета, а значит динамический и у вас будет отличаться. Ставлю на него бряк на доступ.Вошёл в игру. Прострелял все 30 патронов. Перезарядил. Поменял оружие на другое. Появились инструкции.

    image004oup.jpg

    post-3-0-57306900-1424505059_thumb.jpg

     

    Обзор инструкций таков.

    При входе в игру из меню при уже выбранном автомате.

    00589461 - 8b 88 a8 05 00 00 - mov ecx,[eax+000005a8]

    0058953b - 2b b4 17 a8 05 00 00 - sub esi,[edi+edx+000005a8]

    При выборе автомата вместо другого оружия.

    00589461 - 8b 88 a8 05 00 00 - mov ecx,[eax+000005a8]

    0060b4fa - 89 88 a8 05 00 00 - mov [eax+000005a8],ecx

    0058953b - 2b b4 17 a8 05 00 00 - sub esi,[edi+edx+000005a8]

    При выстреле

    00742429 - 8b 4e 08 - mov ecx,[esi+08]

    007428a8 - 8b 46 08 - mov eax,[esi+08]

    007428b0 - 89 46 08 - mov [esi+08],eax

    007428e9 - 8b 46 08 - mov eax,[esi+08]

    При перезарядке

    0073aeef - 89 46 08 - mov [esi+08],eax

    Сразу внимание привлекает sub esi,[edi+edx+000005a8] поскольку это самая расширенная инструкция.

    image005oq.jpg

    post-3-0-70550700-1424505063_thumb.jpg

    На этом этапе следует представить структуру и соотнести это представление с тремя значениями

    edi=048A9180, edx,=70h и 5a8.

    Я сформировал её интуитивно: от большего смещения к меньшему.

    edi – структура Карла

    |_5a8 – структура оружия

    |_edx – 1 слот

    |_edx – 2 слот

    |_edx – 3 слот

    |_edx – 4 слот

    |_edx – 5 слот

    |_edx – 6 слот

    |_edx – 7 слот

    |_edx – 8 слот

    |_edx – 9 слот

    |_edx – 10 слот

    |_edx – 11 слот

    |_edx – 12 слот

    Примечание.

    Слотов у Карла всего 12. Я просто уже это заранее знаю.

    На этом этапе сначала нужно индетифицировать Карла. Для этого поиском адреса edi=048A9180 мной был отсеян указатель. Возьму указатель 00B6F3B8. Зелёные адреса теоритически никогда не поменяют своего местоположения (пример изображен на рисунке)

    image006jg.jpg

    post-3-0-09771600-1424505068_thumb.jpg

    Обратите внимание, что рисунок я привел в качестве примера. Вместо 059669D8 у меня было 048A9180 (! это связано с тем, что мне пришлось перезапустить игру) Т.е. [00B6F3B8]=048A9180

    Теперь структура имеет статическое начало [[00B6F3B8]+5a8+edx]

    [b6F3B8] – структура Карла

    |_+5a8 – структура оружия

    |_edx – 1 слот

    |_edx – 2 слот

    |_edx – 3 слот

    |_edx – 4 слот

    |_edx – 5 слот

    |_edx – 6 слот

    |_edx – 7 слот

    |_edx – 8 слот

    |_edx – 9 слот

    |_edx – 10 слот

    |_edx – 11 слот

    |_edx – 12 слот

    Запомните [[00B6F3B8]+5a8+edx]. Это начальный путь, чтобы узнать какими должен быть регистр edx. В нашем случае напомню он равен 70h, а какой это слот не понятно. Также не забывайте, что в эту структуру должны ещё входить патроны общее для данного оружия, а не только для обоймы. Вот общими патронными мы и займемся в OllyDbg.

    Давайте-ка я вам напомню.

    post-3-0-59940400-1424505072.jpg

    У нас две ячейки памяти

    1 – «30», мы ставили бряк на это изменение этого значение

    2 – «4905+30=4935», мы не искали этот адрес в надежде на то, что мы его и так узнаем..

    Теперь логически подумаем. После выстрела должно происходить два раза вычитание.

    Бряки при выстреле

    00742429 - 8b 4e 08 - mov ecx,[esi+08] // здесь ничего интересного

    007428a8 - 8b 46 08 - mov eax,[esi+08] // а здесь я сделал скриншот (ниже)

    007428b0 - 89 46 08 - mov [esi+08],eax

    007428e9 - 8b 46 08 - mov eax,[esi+08]

    Вот что бросается в глаза – как раз два вычитания.

    image007eg.jpg

    post-3-0-46139900-1424505077_thumb.jpg

    Поскольку инструкции сработали от одного бряка давайте сразу предположим что:

    [esi+8], тоже самое, что [[00B6F3B8]+5a8+edx]

    [esi+С] тоже самое, что [[00B6F3B8]+5a8+edx+4].

    [esi+8] и [esi+С] заметно фигурируют на скриншоте. Уже можно догадаться что [esi+8] это обойма, а [esi+С] это общее патроны. Если вы ещё не догадались посмотрите внимательнее. А если опять не понятно откройте справочник по инструкциям чтобы иметь представление о каждой инструкции на этом скриншоте.

    Значит, вывод такой общие патроны лежат на 4 байта ниже относительно обоймы.

    [b6F3B8] – структура Карла

    |

    |_+5a8 – структура оружия

    |

    |_edx – 1 номер обоймы

    | |_+4 общее патроны

    |_edx – 2 номер обоймы

    | |_+4 общее патроны

    |_edx – 3 номер обоймы

    | |_+4 общее патроны

    |_edx – 4 номер обоймы

    | |_+4 общее патроны

    |_edx – 5 номер обоймы

    | |_+4 общее патроны

    |_edx – 6 номер обоймы

    | |_+4 общее патроны

    |_edx – 7 номер обоймы

    | |_+4 общее патроны

    |_edx – 8 номер обоймы

    | |_+4 общее патроны

    |_edx – 9 номер обоймы

    | |_+4 общее патроны

    |_edx – 10 номер обоймы

    | |_+4 общее патроны

    |_edx – 11 номер обоймы

    | |_+4 общее патроны

    |_edx – 12 номер обоймы

    |_+4 общее патроны

    Зная edx, я узнаю как записать патроны (или прочитать).

    Например, записать всем патронам по единице

    mov byte ptr [[edi]+edx+5a8],1

    mov byte ptr [[edi]+edx+5aс],1

    Где edi=00B6F3B8, edx - данный вид оружия в данный момент

    Проблема состоит в этом - edx. Как его рассчитать??

    Это самый первый бряк окно которого вы увидели. Слава Богу что этого куска кода достаточно. Попадаются игры где этого далеко не достаточно.

    image005oq.jpg

    post-3-0-97063900-1424505081_thumb.jpg

    Проревисим в уме и развернём инструкцию бряка.

    [edi+edx+5a8]=[[00B6F3B8]+edx+5a8]=[[00B6F3B8]+b[[00B6F3B8]+718h]*1c +5a8]

    [[00B6F3B8]+5a8+edx]=[[00B6F3B8]+ b[[00B6F3B8]+718h]*1c +5a8]

    [[00B6F3B8]+5aс+edx]= [[00B6F3B8]+b[[00B6F3B8]+718h]*1c +5aс]

    И в MHS проверим.

    post-3-0-43587700-1424505086.jpg

     

    4905+30=4935

    image009ao.jpg

    post-3-0-06046500-1424505091_thumb.jpg

    Тогда правильным будет предположение, что edx= b[[00B6F3B8]+718h]*1c

    Также будет правильно (надеюсь уже вы догадались) что выражение b[[00B6F3B8]+718h] будет целым числом от 0 до 12.

    image010jn.jpg

    post-3-0-77649800-1424505095_thumb.jpg

     

    На скриншоте видно, что выбрана 4 «обойма»

    Итак, полная структура

    [b6F3B8] :Игрок Карл

    |_Оружие +5a8

    |_1 слот - в обойме (+1*1с)

    |_патроны (+4)

    |_2 слот - в обойме (+2*1с)

    |_патроны (+4)

    |_3 слот - в обойме ( +3*1с)

    |_патроны (+4)

    |_4 слот - в обойме ( +4*1с)

    |_патроны (+4)

    |_5 слот - в обойме (+5*1с)

    |_патроны (+4)

    |_6 слот - в обойме (+ 6*1с)

    |_патроны (+4)

    |_7 слот - в обойме (+7*1с)

    |_патроны (+4)

    |_8 слот - в обойме (+ 8*1с)

    |_патроны (+4)

    |_9 слот - в обойме (+9*1с)

    |_патроны (+4)

    |_10 слот - в обойме (+10*1с)

    |_патроны (+4)

    |_11 слот - в обойме (+11*1с)

    |_патроны (+4)

    |_12 слот - в обойме (+ 12*1с)

    |_патроны (+4)

    Внизу я написал скрипт как пример записи во всю структуру.

    Если обоймы нет, то в адресе обоймы будет всёравно еденица (об этом должен был раньше сказать)

     

    Получилось. Во все слоты вписывается значения единиц.

    post-3-0-10519200-1424505100.jpgpost-3-0-79498100-1424505104.jpg

     

    На этом расструктуризация закончена.

    Как сделать бесконечное оружие?

    Смотрим в отладчике близлежащие инструкции.

    image013lx.jpg

    post-3-0-60913000-1424505109_thumb.jpg

     

    Простой способ

    Самый короткий способ сделать бесконечное оружие так это вписать в показанный адрес единицу. Есть недостаток – бесконечное оружие будет у всех.

    Сложный способ

    Поставить фильтр на Карла. Если стреляет Карл, то «перепрыгивать» оба «dec»-а. Фильтр надо ставить ещё в другом месте чтения данного адреса (иначе патроны будут уменьшаться при стрельбе с мотоцикла, из машины, велосипеда…)

    Читы такие как бесконечное оружие и отсутствие оружия у врагов Карла я оставлю вам на самостоятельное решение.

    //mov [[00B6F3B8]+n*1c+5a8]],1//mov [[00B6F3B8]+n*1c+5ac]],1//где n =[1..12][ENABLE]alloc(thread,200)registersymbol(tred)label(x1)thread:push eaxpush ecxpush ebxxor ecx,ecxmov eax,[00B6F3B8]add eax,5a8x1:inc ecximul ebx,ecx,1cmov [eax+ebx],1// mov [[00B6F3B8]+n*1c+5a8]],1 - в обоймеmov [eax+ebx+4],1// mov [[00B6F3B8]+n*1c+5ac]],1 - общие патроныcmp ecx,cjne x1pop ebxpop ecxpop eaxcall GetCurrentThreadretcreatethread(thread)[DISABLE]dealloc(thread)
  9. 1. Мощный поиск адресов в памяти

    Привет. В этой теме я опишу как настроить и как пользоваться мощным поиском MHS - Expression Search.

    Поиск Expression Search является поиском в котором можно задавать множество различных правил сканирования и отсеивания адресов с применим логических, математических операторов. Также операторов обращения к адресу и операторов типа данных. Поиск очень мощный =)

    Выражаю благодарности: L. Spiro, WhiteHat, CoMPMStR и в частности Recifense за то ,что они как и всем в частности и мне помогли приобрести необходимые навыки.

    А теперь поехали...

    Настройка

    Если у вас процессор с более чем одним ядром и в поиске вы используете скобки [], то чтобы поиск работал без вылетов в синий экран снимите опции отключающие функции ядра ОС в настройках программы.

    Операторы

    • [*:2btzni4v]Math
      - (minus) 34 - 3
      + (plus) 60 + 4
      * (multiplication) 32 * 23
      / (division) 1024 / 32
      % (modulus) 512 % 8
      [*:2btzni4v]Unary Prefixes
      - (unary negation) -120
      + (unary positive) +56
      ! (unary NOT) !90
      ~ (unary one’s compliment) ~32
      [*:2btzni4v]Bitwise Operators
      & (bitwise AND) 67 & 15
      | (bitwise OR) 1024 | 1
      ^ (bitwise XOR) 37 ^ 1
      << (bitwise shift left) 1 << 3
      >> (bitwise shift right) 16 >> 4
      [*:2btzni4v]Equality Operators
      < (less than) 1 < 0
      > (greater than) 1 > 0
      <= (less than or equal) 67 <= 90
      >= (greater than or equal) 89 >= 50
      == (equals) 45 == 32
      != (not equal to) 45 != 32
      [*:2btzni4v]Logical Operators
      && (AND) 45 && 32
      || (OR) 0 || 23

    Типы

    b[ ] gets a byte value.

    w[ ] gets a word value.

    [ ] (no prefix) gets a dword value.

    q[ ] gets qword value.

    f[ ] gets a float value.

    d[ ] gets a double value.

    Основные приёмы.

    ?? > 0 && ?? <= 100

    (?? >= 'a' && ?? <= 'z') || (?? >= 'A' && ?? <= 'Z')

    Два знака вопроса означает поиск адресов значение которых соответствует заданной форме поиска.

    [??] == 100

    [[[[??]]+7Ch]+190h] == 1.0f

    f[[[[??]]+7Ch]+190h] >= 0.01f && f[[[[??]]+7Ch]+190h] <= 1.0f

    Два знака вопроса находящихся в скобках означает поиск указателя.

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

    Допустим мы с вами знаем цепочку указателей

    [[[RE5DX9.exe+0xE76E1C]+0x24]+0x1364] =[08524686]= 1000,0 здоровья

    Зададим условия поиска:

    Expression : [??+0x24]+0x1364==08524686

    Treat ?? (Current Value) As : Pointer

    Alignment : 4

    Отсеянные зеленные адреса будут представлять бОльшую заинтересованность и проверив один из них мы получим [[static Address]+0x24]+0x1364.

    На этом пока всё...

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

  10. Вероятно, в будущих статьях я неизбежно затрону новые возможности, так что не стоит отчаиваться :)

    По поводу того что CE не ловит процесс игры, то трудно предположить причину. Её нужно локализовать в отладке в Windows7... думаю это будет исправлено Дарк Байтом в новой CE 5.6

    Да, кстати... после выхода CE 5.6 нужно будет и создать CE RUS 5.6. Наверно, я это смогу сделать в течении недели. Пока я занимаюсь созданием/модификацией двига для трейнера малых размеров с автоассемблером. Движок-то не простой ^_^ - Xipho написал :)

  11. Началось закрытое бета-тестирование CE 5.6.

    Была мало описана первая же критическая ошибка на Windows 7 вызвавшая BSOD не понятно при каких действиях.

    На офруме CE всё сильнее и сильнее поднимается ажиотаж на реализ новой версии.

  12. В CE добавлены улучшения и удобства в окне расструктуризации..

    Вот что теперь реализовано по просьбе юзера, который пишет весьма сложные скрипты по CE.

    1) Sometimes when I am analysing a game data, the tool asks for a size to the structure (default 0x1000). I usually define the size based on the offset that has the info I want to analyse (fi, 0x400). During the analysis I give names to the offsets (ex.: CurrHP, MaxHP, etc). Further in the analysis I find out that there is important info at offset 0x1200). That?s when a resize could be helpful. In CE55, I could have to insert 0x200 entries to get to that offset or creat a new structure (in this case, I would have to enter all the offset names I have already defined and redefine again some information type (double, dword, float, etc).

    2) Also during the analysis, I want to check some structures that are pointed in the structure I am current analysing. In CE55, I have to add the pointer to a CE table and from there I can browse the memory region and choose to create a new dissent window for that region. I just wanted to quick up things. An option to browse the memory region from a pointer in the dissect window could be enough.

  13. Создание пустого окна на Дельфи без VCL (8 кб с пакером)

    Главная программа.

    Program Api;
    uses
    Forms,
    WindowTrainer in 'WindowTrainer.pas',
    cnst in 'cnst.pas',
    CEcheats in 'CEcheats.pas';

    begin
    WindowCreateTrainer;
    end.

    Модуль Окна WindowTrainer

    unit WindowTrainer;

    interface
    uses windows, messages;

    procedure WindowCreateTrainer;

    var
    window:TWndClassEx;
    Mwindow: HWND;
    Mmsg: MSG;

    implementation
    uses cnst;

    // Процедура обработки сообщений
    function WindowProc (wnd: HWND; msg: integer; wparam: WPARAM; lparam: LPARAM):LRESULT;STDCALL;
    begin
    case msg of
    WM_Destroy:
    begin
    PostQuitMessage (0);
    Result := 0;
    Exit;
    end;
    else
    Result := DefWindowProc(wnd,msg,wparam,lparam);
    end;
    end;

    procedure WindowCreateTrainer;
    begin
    // Ристрация класса окна
    window.cbSize := sizeof (window);
    window.style := CS_HREDRAW or CS_VREDRAW;
    window.lpfnWndProc := @WindowProc;
    window.cbClsExtra := 0;
    window.cbWndExtra := 0;
    window.hInstance := HInstance;
    window.hIcon := LoadIcon (0,IDI_APPLICATION);
    window.hCursor := LoadCursor (0,IDC_ARROW);
    window.hbrBackground:=Color_BtnFace+12;
    window.lpszMenuName := nil;
    window.lpszClassName := 'frmTrainer';
    RegisterClassEx (window);
    // Создание окна на основе созданного класса
    Mwindow := CreateWindowEx(0,'frmTrainer',trname, WS_OVERLAPPEDWINDOW - WS_MAXIMIZEBOX+WS_EX_TOOLWINDOW-WS_THICKFRAME,100,100,300,300,0,0,Hinstance,nil);
    // Показать созданное окно
    SHOWWINDOW (Mwindow,SW_Show);

    // Цикл обработки сообщений
    while GetMessage (Mmsg,0,0,0) do
    begin
    TranslateMessage (Mmsg);
    DispatchMessage (Mmsg);
    end;

    end;

    Получаем 15 кб на Дельфи 10,а если сжать пакером, то около 9 кб

  14. Сразу перейдите на 21 пост этой темы "Создание трейнера для начинающих на VCL"

    А если вы знаете Дельфи, то перейдём к тонкостям Дельфи.

    Источник: Взято с античата.

    • Передача аргументов.
      Никогда не передавай функции в качестве параметра структуру, лучше передавать указатель на нее
    //Ни в коем случае не делай так.
    Procedure code (Data:TStructure);
    //Правильный вариант
    Procedure code (PData:PStructure); //гда PStructure = ^TStrucrur


    Старайся передавать своим функциям не более трех параметров, т.к. они передаются через регистры (по соглашению fastcall, принятом по умолчанию в Delphi), а все остальные через стек.
    Функции - инварианты
    Довольно распространенная ошибка программистов – присутствие функций - инвариантов в цикле.

    //Неоптимизированный вариант
    While i<= lstrlen(str) do
    Begin
    X:=x+ord(str[i]);
    Inc(i);
    End;


    Очевидно, что длина str не меняется, но компилятор считает, что все, что передается по ссылке сожжет быть изменено, и lstrlen вычисляется много раз. Оптимизированный вариант выглядит так.

    //Так лучше
    N:=lstrlen(str);
    While i<= n do
    Begin
    X:=x+ord(str[i]);
    Inc(i);
    End;


    Экономия памяти
    Когда класс располагается в памяти, то между полями появляются пустые ячейки памяти. Это происходит потому, что Delphi, оптимизируя код, каждое поле располагает от предыдущего со сдвигом в 4 байта.

    // Неоптимизированный вариант
    TMyClass = class
    private
    field1: boolean;//1 байт
    field2: longint; //4 байт
    field3: char; //1 байт
    field4: string; //4 байт
    field5: byte; //1 байт
    field6: integer; //4 байт
    field7: byte; //1 байт
    public
    procedure code;
    end;


    Реально этот экземпляр класса будет занимать 28 байт. Если мы изменим порядок полей, то сможем добиться уменьшения размера до 16 байт. В нашем примере после field1 размером 1 байт идет field2 размером 4 байта, значит, мы теряем 3 байта на выравнивание. Если же размер field2 не превышал 3 байт, то Delphi не стал бы выравнивать, а поместил бы это поле сразу после первого.
    Код:
    // Оптимизированный вариант

    TMyClass = class
    private
    field1: boolean; //1 байт
    field3: char; //1 байт
    field5: byte; //1 байт
    field7: byte; //1 байт
    field2: longint; //4 байт
    field4: string; //4 байт
    field6: integer; //4 байт
    public
    procedure code;
    end;


    [*:29vn0yvr]Компиляция без RTL (Run Time Library)
    Как известно минимальный размер скомпилированной в Delphi программы с настройками по умолчанию равен 13,5 Кб. Виной тому принудительно подключаемая Delphi RTL, в которой реализованы некоторые возможности языка Delphi.
    Для уменьшения размера скомпилированных прог исправим модели System.pas и SysInit.pas, удалив все «лишнее». Затем перекомпилируем их и полученные dcu-файлы поместим в папку с прогой.
    Минимальный System.pas

    unit System;

    interface

    procedure _HandleFinally;

    type
    TGUID = record
    D1: LongWord;
    D2: Word;
    D3: Word;
    D4: array [0..7] of Byte;
    end;

    PInitContext = ^TInitContext;
    TInitContext = record
    OuterContext: PInitContext;
    ExcFrame: Pointer;
    InitTable: pointer;
    InitCount: Integer;
    Module: pointer;
    DLLSaveEBP: Pointer;
    DLLSaveEBX: Pointer;
    DLLSaveESI: Pointer;
    DLLSaveEDI: Pointer;
    ExitProcessTLS: procedure;
    DLLInitState: Byte;
    end;

    implementation

    procedure _HandleFinally;
    asm
    end;

    end.


    Минимальный SysInit.pas

    unit SysInit;

    interface
    procedure _InitExe;
    procedure _halt0;
    procedure _InitLib(Context: PInitContext);

    var
    ModuleIsLib: Boolean;
    TlsIndex: Integer = -1;
    TlsLast: Byte;

    const
    PtrToNil: Pointer = nil;

    implementation

    procedure _InitLib(Context: PInitContext);
    asm
    end;

    procedure _InitExe;
    asm
    end;

    procedure _halt0;
    asm
    end;
    end.


    Компиляция

    Dcc32.exe – Q System.pas SysInit.pas –M –Y –Z -$D- -O


    Экстремально маленький Hello Word! на Delphi.
    Собирать исполняемый файл линкером от Microsoft. К сожалению, майкрософтовкий линкер понимает только COFF и Intel OMF, наотрез отказываясь работать с Borland OMF. Delphi же, начиная с третьей версии, перешла с формата Intel OMF на Borland OMF. Поэтому компилировать придется компилятором от Delphi 3.

    Unit HelloWord;
    Interface
    Procedure Start;
    Implementation
    Function MessageBoxA(hWnd:cardinal; IpText, IpCaption:Pchar; uType:Cardinal): Integer; stdcall; external ‘user32.dll’ name ‘_MessageBoxA@16’;
    Procedure Start;
    Begin
    MessageBoxA(0,’Hello word!’,nil,0);
    End;
    End.


    Ти модуля Unit нужен для того, чтобы компилятор сгенерировал в объектном файле символьные имена объявленных процедур.
    Компилируем:

    Dcc32.exe –JP -$A-,B-,C-,D-,G-,H-,I-,J-,L-,M-,O+,P-,Q-,R-,T-,U-,V-,W+,X+,Y- HelloWord.pas


    Открываем файл HelloWord.obj в HEX-редакторе и смотрим во что превратилась наша точка входа. Допустим Start$wwrv. Теперь выполняем сборку

    Link.exe /ALIGN:32 /FORCE:UNRESOLVED /SUBSYSTEM:WINDOWS /ENTRY:Start$wwrv HelloWord.obj user32.lib /out:Hello.exe


    В результате имеем файл размером 832 байта.

    Статья про маленький размер приложения:

    Выжимаем из Delphi все возможное

    Ты пишешь на Delphi, но тебе не нравиться размер проги в 400 кб? Ничего, можно и до 850 байт сократить...

    Вступление

    В кругах низкоуровневых программистов существует устоявшееся мнение о том, что Delphi полный остой, так как не годится для системного программирования. Обьясняют это обычно тем, что компилятор генерирует медленный и большой код, а средний размер пустой формы с кнопкой — 400 килобайт. Обычно этим аргументация обычно и заканчивается (иногда просто говорят что дельфи дерьмо вообще без всякой аргументации). На форумах существуют «священные» войны между поклонниками С++ и Delphi, где каждый доказывает что его любимый язык лучше. Поклонники С++ обычно кричат про супернавороченный синтаксис и мошьные возможности ООП (при этом утверждая, что в системном программировании все это незаменимо!), а поклонники Delphi опять же кричат про те возможности ООП которых нет в С++ да и еще про то, что на дельфи все пишется куда проще. Мо моему мнению — это просто крики ламеров, так как по их словам можно заключить, что ни та ни другая сторона обычно ни про Delphi ни про C++ ничего толком не знает.

    Эта статья будет посвящена приемам системного программирования на Delphi, тут мы будем делать на дельфи то, что многие считают невозможным. Эта статья для тех, кто пишет программы на Delphi и при этом хочет добиться максимальной эффективности кода, но не боится вложить в это определенный труд. По оптимизации кода в С++ написано весьма немало статей, а про оптимизацию в Delphi хороших статей просто нет. Видно все считают — что никакая оптимизация здесь не нужна. Тем, кого устраивает 400 килобайтный размер пустой формы с кнопкой статью читать я не рекомендую (все равно толку то...), а тем кто упорно отстаивает мнение о ненужности дельфи лучше тоже не читать, чтобы не расстраивать нервы и не развеивать священные заблуждения.

    Немного о генерируемом компилятором коде

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

    procedure DownloadAndExecute (Source: PChar); stdcall;

    const

    DestFile = 'c:trojan.exe';

    begin

    UrlDownloadToFile (nil, Source, DestFile, 0, nil);

    WinExec (DestFile, SW_HIDE);

    end;

    Этот код я вставил в программу, скомпилировал и дизассемблировал в IDA. Вот его откомментированый листинг:

    DownloadAndExecute proc near

    Source = dword ptr 8

    push ebp

    mov ebp, esp

    push 0 ; LPBINDSTATUSCALLBACK

    push 0 ; DWORD

    push offset DestFile ; LPCSTR

    mov eax, [ebp+Source]

    push eax ; LPCSTR

    push 0 ; LPUNKNOWN

    call URLDownloadToFileA

    push 0 ; uCmdShow

    push offset DestFile ; lpCmdLine

    call WinExec

    pop ebp

    retn 4

    DownloadAndExecute endp

    DestFile db 'c:trojan.exe',0

    Ну и где же куча лишнего кода о котором некоторые так любят говорить? Все просто и красиво. Из этого можно сделать вывод, что компилятор гнерирует вполне приличный код, размер которого весьма невелик. То что сгенерировал компилятор весьма похоже на аналогичный код написанный вручную на ассемблере. Тем более, некоторые люди не знающие ассемблера, но пытающиеся что-то на нем писать иногда такое выдают, что любые ошибки компилятора покажутся мелочью . Так почему программы написанные на дельфи такие большие? Откуда все-таки берется лишний код, если компилятор его не генерирует. Сейчас мы разберем этот вопрос подробнее.

    ООП — двигатель прогреса

    ООП — весьма модное в настоящее время направление развития индустрии программирования. Цель ООП — упростить написание программ и сократить сроки их разработки, и несомненно с этой целью ООП прекрасно справляется. Большинство программистов пишущих на С++ или Delphi прикладные приложения, не мыслят даже о самой возможности программирования без ООП. Если ты относишся к их группе, то немедленно бросай читать статью, так как она не для тебя.

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

    Хороший пример применения ООП — это библиотека VCL в дельфи. Этот пример в полной мере демонстрирует все достоинства и недостатки ООП. С одной стороны — черезвычайная простота написания программ, с другой — огромнейшее количество мертвого кода и ужасно низкая его производительность.

    ООП позволяет писать всякую фигню (обычно связанную с базами данных) за короткое время. Главный принцип — быстрее сдал программы, быстрее получил деньги. Естественно, что в таких условиях о всякой эффективности кода просто забывают. А что? Тормозит? Так пусть клиент купить компьютер помощьнее... Но нам естественно такие результаты не нужны.

    Теперь мы нашли источник всех бед, вот она причина большого размера программ написанных на деьфи, это ООП, и в частности VCL. Некоторые прочитав это зададутся вопросом, почему программа написанная на дельфи с применением VCL занимает гораздо больше места, чем программа написаггая на VB, или на VC с применением MFC. Ответ прост — потому, что великая и ужасная фирма Micro$oft приложила к этому свою лапу. MFC и рунтайм библиотеки в VB занимают ничуть не меньше места, просто они скомпилены в DLL и входят в поставку Windows, а значит их код не приходится таскать с собой в программах, а программа на Delphi содержит весь необходимый для своей работы код. В защиту Borland можно сказать то, что такая возможность присутствует и в Delphi. Нужно просто в настройках проекта поставить галочку «Build with runtime packages», тогда размер программы значительно уменьшится, но она потребует наличия соответствующих Runtime библиотек. Так как Borland не выпускает Windows, то естественно эти библиотеки в поставку винды не входят, но в этом надо винить не Борланд, а монопольную политику мелкософта.

    Любители ООП желающие разрабатывать программы в визуальном режиме могут использовать KOL. Это попытка сделать что-то типа VCL, но с учетом недостатков ООП. Средний размер пустой формы с кнопкой на KOL — 35кб, что уже лучше, но к сожалению для серьезных приложений эта библиотека не подходит, так как часто глючит. Да и решение это половинчатое, те кто хочет добиться действительно высокой эффективности коды должны принять другое решение — забыть про ООП и все что с ним связано раз и навсегда. Писать программы придется только на чистом API.

    Из всего вышесказанного можно сделать вывод, что ООП двигатель прогресса, но с точки зрения

    системного программиста ООП двигает прогресс только назад.

    Виновник номер два

    Создадим в Delphi пустой проект, хаведомо не содержащий никакого полезного кода:

    program Sample;

    begin

    end.

    После компиляции в Delphi 7 мы получаем екзешник размером в 13,5 кб. Почему так много? Ведь в программе то ничего нет. Ответ на этот вопрос опять поможет дать IDA. Дизассемблируем полученный экзешник и посмотрим, что собственно он содержит. Точка входа в программу будет выглядеть так:

    public start

    start:

    push ebp

    mov ebp, esp

    add esp, 0FFFFFFF0h

    mov eax, offset ModuleId

    call _InitExe

    ; здесь мог бы быть наш код

    call _HandleFinally

    CODE ends

    Весь лишний код находится функциях _InitExe и _HandleFinally. Связано это с тем, что к каждой Delphi программе неявно подключается код входящий в состав RTL (Run Time Library). RTL нужна для поддержки таких возможностей языка как ООП, работа со строками (string), специфичные для паскаля функции (AssignFile, ReadLn, WriteLn e.t.c.). InitExe выполняет инициализацию всего этого добра, а HandleFinally обеспечивает корректное освобождение ресурсов.

    Сделано это опять же для упрощения жизни программистам, и применение RTL иногда оправдано, так как может не понизить, а повысить эффективность кода. Например в состав RTL входит менеджер кучи, который позволяет быстро выделять и освобождать маленькие блоки памяти. По своей эффективности он в три раза превосходит системный. Работа со строками реализована в RTL тоже довольно неплохо с точки зрения производительности генерируемого кода, но с точки зрения увеличения размера файла, RTL — виновник номер два (после ООП).

    Уменьшаем размер

    Если минимальный размер в 13.5 кб вас не устраивает, то будет убирать Delphi RTL. Весь код RTL находится в двух файлах: System.pas и SysInit.pas. К сожалению, компилятор подключает их к программе в любом случае, поэтому единственное что можно сделать — это удалить из этих модулей весь код, без которого программа может работать, и перекомпилить модули, а полученные DCU файлы положить в папку с программой.

    Файл System.pas содержит основной код RTL и поддержки классов, но все это мы выбросим. Минимальное содержимое этого файла будет выглядеть так:

    unit System;

    interface

    procedure _HandleFinally;

    type

    TGUID = record

    D1: LongWord;

    D2: Word;

    D3: Word;

    D4: array [0..7] of Byte;

    end;

    PInitContext = ^TInitContext;

    TInitContext = record

    OuterContext: PInitContext;

    ExcFrame: Pointer;

    InitTable: pointer;

    InitCount: Integer;

    Module: pointer;

    DLLSaveEBP: Pointer;

    DLLSaveEBX: Pointer;

    DLLSaveESI: Pointer;

    DLLSaveEDI: Pointer;

    ExitProcessTLS: procedure;

    DLLInitState: Byte;

    end;

    implementation

    procedure _HandleFinally;

    asm

    end;

    end.

    Описания структуры TGUID компилятор требует в любом случае, и без ее наличия компилировать модуль отказывается. TInitContext понадобиться, если мы будем компилировать DLL, без описания этой структуры линкер собирать DLL отказывается. HandleFinally — процедура освобождения ресурсов RTL, компилятору она тоже необходима, хотя может быть пустой.

    Теперь урежем файл SysInit.pas, который содержит код инициализации и завершения работы RTL и управляет поддержкой пакетов. Минимальный файл SysInit.pas будет выглядеть так:

    unit SysInit;

    interface

    procedure _InitExe;

    procedure _halt0;

    procedure _InitLib (Context: PInitContext);

    var

    ModuleIsLib: Boolean;

    TlsIndex: Integer = -1;

    TlsLast: Byte;

    const

    PtrToNil: Pointer = nil;

    implementation

    procedure _InitLib (Context: PInitContext);

    asm

    end;

    procedure _InitExe;

    asm

    end;

    procedure _halt0;

    asm

    end;

    end.

    InitExe — процедура инициализации RTL для EXE файлов, InitLib — для DLL, halt0 — завершение работы программы. Все остальное же просто требуется компилятором.

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

    dcc32.exe -Q system.pas sysinit.pas -M -Y -Z -$D- -O

    Терерь мы наконец избавились от RTL, попробуем скомпилировать пустой проект, и получаем экзешник размером в 3,5 кб. Откуда взялся такой размер в пустом проекте? Борландовский линкер создает ы исполнимом файле 6 секций, и при выравнивании секций в файле по 512 байт + размер PE заголовка, мы как раз получаем размер в 3.5 кб.

    Но в добавок к малому размеру мы получаем определенные неудобства, так как заголовочные файлы на WinAPI идущие с Delphi мы использовать не сможем, вместо них придется писать свои. Но это не трудно, так как описания используемых API можно брать с борландовских хедеров и переносить в свои по мере необходимости. С проблемой увеличения рвзмера можно столкнуться тогда, когда в составе проекта есть несколько PAS файлов. Борландовский линкер в такой ситуации может для выравнивания кода вставлять в файл пустые участки. Чтобы этого избежать — нужно всю программу (включая определения API) помещать в один файл. Это весьма неудобно, поэтому лучше воспользоваться директивой препроцессора $INCLUDE и разнести код на несколько inc файлов. Тут может встретиться еще одна проблема — повторные вставки одного и того же кода (когда несколько inc файлов подключают один и тот же inc), компилятор в таких случаях компилировать откажется. Чтобы этого избежать нужно воспользоваться директивами условной компиляции, полсе чего любой inc файл будет иметь вид:

    {$ifndef win32api}

    {$define win32api}

    // здесь идет наш код

    {$endif}

    Таким образом можно писать без RTL достаточно сложные программы и забыть о неудобствах.

    Можно еще меньше!

    Наверняка минимальный размер экзешника в 3.5кб удовлетворит не всех, но если постараться, то можно добиться еще большего уменьшения размера. Для этого нужно отказаться от удобств работы с борландовским линкером и собирать исполнимые файлы линкером от Microsoft. Но к сожадению, здесь нас ждет одна загвоздка. Проблема в том, что основным рабочим форматом мелкософтовского линкера является COFF, но он может понимать и интеловский OMF. Но программисты борланда (видать с целью создать несовместимость с микрософт) в версиях Delphi выше третьей изменили генерируемый формат obj файлов так, что теперь он несовместим с Intel OMF. Тоесть теперь существуют два вида OMF: Intel OMF и Borland OMF. Программы способной конвертировать обьектные файлы из формата Brland OMF в COFF или Intel OMF я не нашел. Поэтому придется использовать компилятор от Delphi 3, который генерирует стандартный Intel OMF обьектный файл. Импорт используемых API нам тоже придется описывать вручную, но его описание несколько отличается. Для начала возьмем библиотеку импорта user32.lib из состава Visual C++ и откроем ее в HEX редакторе. Имена функций библиотеки имеют такой вид: «_MessageBoxA@16», где после @ идет размер передаваемых параметров. Следовательно обьявлять функции мы будем таким образом:

    function MessageBoxA (hWnd: cardinal; lpText, lpCaption: PChar; uType: Cardinal): Integer;

    stdcall; external 'user32.dll' name '_MessageBoxA@16';

    Попробуем теперь написать HelloWorld как можно меньшего размера. Для этого создаем проект такого типа:

    unit HelloWorld;

    interface

    Procedure Start;

    implementation

    function MessageBoxA (hWnd: cardinal; lpText, lpCaption: PChar; uType: Cardinal): Integer;

    stdcall; external 'user32.dll' name '_MessageBoxA@16';

    Procedure Start;

    begin

    MessageBoxA (0, 'Hello world!', nil, 0);

    end;

    end.

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

    dcc32.exe -JP -$A- ,B- ,C- ,D- ,G- ,H- ,I- ,J- ,L- ,M- ,O+,P- ,Q- ,R- ,T- ,U- ,V- ,W+,X+,Y- HelloWorld.pas

    После компиляции получим файл HelloWorld.obj, который откроем в HEX редакторе и посмотрим во что превратилась наша точка входа. У меня получилось Start$qqrv. Это имя нужно указать как точку входа при сборке исполнимого файла. И наконец выполним сборку:

    link.exe /ALIGN:32 /FORCE:UNRESOLVED /SUBSYSTEM:WINDOWS /ENTRY:Start$qqrv HelloWorld.obj user32.lib /out:Hello.exe

    В результате мы получаем работающий HelloWorld размером в 832 байта! Я думаю, что этот размер удовлетворит любого. Попробуем теперь дизассемблировать этот файл в IDA и поискать лишний код:

    ; Attributes: bp-based frame

    ; char Text[]

    Text db 'Hello world!',0

    public start

    start proc near

    push 0 ; uType

    push 0 ; lpCaption

    push offset Text ; lpText

    push 0 ; hWnd

    call MessageBoxA

    retn

    start endp

    Как мы видим — ни одного байта лишнего кода! Я думаю ты покажешь этот пример тем, кто много говорит о большом размере программ написанных на дельфи. По своему опыту знаю, что прикольно бывает наблюдать после этого за их выражением лица Хотя самые упорные продолжают говорить а... э..., все равно дерьмо..., но никто не может сказать ничего по существу. Но только самые продвинутые спорщики приводят последний аргумент — на Delphi нельзя написать драйвер режима ядра для Windows NT. Ничего... сейчас у них не останется аргументов вообще .

    Пишем драйвер на Delphi

    Используя эту методику можно не только писать очень маленькие программы, можно даже сделать то, что раньше считалось невозможным — написать на Delphi драйвер режима ядра. Об этом даже есть статья на RSDN, и всем интересующимся рекомендуу ее прочитать. Здесь же я приведу пример простейшего драйвера и содержимое make.bat для его сборки.

    Файл Driver.pas:

    unit Driver;

    interface

    function DriverEntry (DriverObject, RegistryPath: pointer): integer; stdcall;

    implementation

    function DbgPrint (Str: PChar): cardinal; cdecl; external 'ntoskrnl.exe' name '_DbgPrint';

    function DriverEntry (DriverObject, RegistryPath: pointer): integer;

    begin

    DbgPrint ('Hello World!');

    Result := -1;

    end;

    end.

    Файл make.bat:

    dcc32.exe -JP -$A- ,B- ,C- ,D- ,G- ,H- ,I- ,J- ,L- ,M- ,O+,P- ,Q- ,R- ,T- ,U- ,V- ,W+,X+,Y- Driver.pas

    link.exe /DRIVER /ALIGN:32 /BASE:0?10000 /SUBSYSTEM:NATIVE /FORCE:UNRESOLVED /ENTRY:DriverEntry$qqspvt1 Driver.obj ntoskrnl.lib /out:Driver.sys

    Для компиляции нам понадобиться файл ntoskrnl.lib из DDK. После компиляции мы получим драйвер размером 1 кб, который выводит сообщение Hello World в отладочную консоль и возвращает ошибку, а поэтому в памяти не остается, что не требует определения функции DriverUnload. Для запуска драйвера можно использовать KmdManager от Four-F, посмотреть на результаты его работы можно в софтайсе или DbgView.

    При наличии некоторых знаний можно писать на Delphi вполне полноценные драйвера. Но здесь есть одна больщая проблема — отсутствие DDK. Для написания драйверов нужны заголовояные файлы на API ядра и описания большого количества системных структур. Все это богатство есть как для С (от Microsoft), так и для MASM32 (от Four-F). Есть слух, что DDK для паскаля уже существует, но его автор продает его за деньги и сильно этот факт не офиширует. Но я думаю, что найдутся энтузиасты, которые перепишут DDK на паскаль и выложат для всеобщего использования. Другой проблемой является то, что большинство примеров связанных с системным программированием написаны на си, поэтому на каком бы языке вы не писали свои программы, а си знать придется. Это конечно не означает, что придется изучать С++ в полном его обьеме. Для понимания системных программ хватит базовых знаний синтаксиса си, а все остальное используется в только в прикладных програмах которые нас совершенно не интересуют.

    Переносимость кода

    При программировании на стандартных Delphi компонентах, в добавок к куче недостатков мы получаем еще одно достоинство — некоторую переносимость кода. Если программа использует только возможности языка, но не возможности системы, то она будет легко компилироваться в Kilix и работать в Linux. Вся проблема в том, что без использования возможностей системы мы получим настоящее глюкалово (Шедевр Ламерского Искусства), тяжелую и неэффективную программу. Тем не менее, при написании серьезных программ по вышеописанным методикам все-таки хочется иметь некоторую независимость от системы. Получить такую независимость очень просто, достаточно писать код не использующий ни API функций ни возможностей языка вообще. Такой код в некоторых случаях писать совершенно невозможно (например в играх), но иногда функции системы абсолютно не нужны (например в математических алгоритмах). В любом случае, следует четко разделять машиннозависимую и машиннонезависимую (если такая есть) части кода. При соблюдении вышеописанных правил машиннонезависимая часть будет совместима на уровне исходных текстов с любой системой, для которой есть компилятор паскаля (а он есть даже для PIC контролеров). Независимый от API код можно смело компилировать в DLL и использовать например в драйвере режима ядра. Также такую длл не составит труда использовать и в других ОС, для этого нужно просто посекционно отмапить длл в адресное пространство процесса, настроить релоки и можно смело пользоваться ее функциями. Код на паскале это осуществляющий занимает около 80 строк. Если же DLL все-таки использует некоторые API функции, то их наличие можно проэмулировать, заполнив таблицу импорта DLL адресами заменяющих их функций в своей программе.

    Общие приемы оптимизации

    Старайся везде, где можно использовать указатели. Никогда не передавай данные в функцию таким образом:

    procedure FigZnaet (Data: TStructure);

    Всегда передавай указатели на структуры:

    procedure FigZnaet (pData: PStructure); где PStructure = ^TStructure;

    Такой вызов происходит быстрее и экономит немалое количество кода.

    Старайся не пользоватся типом данных sring, вместо него всегда можно использовать Pchar и обрабатывать строки вручную. Если нужен временный буффер для хранения строки, то его следует обьявить в локальных переменных как array of Char. Старайся передавать в функцию не больее трех параметров. Это обьясняется тем, что первые три параметра согласно методу вызова fastcall (который по умолчанию применяется в Delphi) передаются в регистрах, а все последующие через стек, что замедляет доступ к ним и увеличивает размер кода. Экономь память, если например у тебя есть массив чисел, диапазон которых укладывается в байт, то не нужно обьявлять его как dword. Никогда не стоит писать повторяющийся код. Если какие-либо действия следует делать повторно, то их нужно вынести в функцию, но тем не менее не стоит делать функции содержащие 2 строчки кода, так как вызов такой функции может занимать куда больше места, чем она сама. И помни главное — эффективность кода в первую очередь определяется не компилятором, а примененным алгоритмом, при этом разница в эффективности может составлять сотни раз!

    Заключение

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

    Автор: Ms-Rem (MircroSoft REMover) Ms-Rem@yandex.ru

  15. 1. У меня тоже такое было

    2. Про этот случай я могу только гадать.

    3. Так же как и во втором пункте - без понятия в чём причина.

    ЗЫ. Если создашь чит-код, то можешь написать статью в этой теме.

    ЗЗЫ. Трейнер ругается, т.к. требуется соблюдать правило - один запуск трейнера, на один запуск игры. В будущем, может быть, исправлю эту проблему.

  16. Kvazimado, если у тебя будет время и желание сделай следующее и отпишись о работе трейнера с твоим первоначальным JustCause.exe.

    Сначала запусти игру, затем начни играть (загрузив локацию игры), а только после этого сверни игру и запусти трейнер, подожди на всякий случай 10 секунд, активируй здоровье (crtl+1)...

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

    Проблема

    JustCause.exe (с защитой от копирования) предположительно поочередно распаковывает модифицированные секции кода, перед тем как к ним будет обращение. Я в этом не уверен, но догадываюсь.

    Обдумываю решение проблемы

    Если описание проблемы совпадает с истинной, то я изменю двиг трейнера следующим образом. При первой активации по горячим клавишам любого чит-кода сначала будет активация всех активирующих скриптов. Надо было сразу так делать, что-то я не додумался раньше. dry.gif У меня же активирующие скрипты активируются сразу после нахождения дескриптора процесса игры.

  17. Знаешь, чтобы тебя не мучить вопросами... сброська мне твой JustCause.exe (ссылкой через личку, поскольку тут на форуме нельзя постить nodvd), я его быстренько проверю. Взамен тебе скину мой и он будет точно работать.

  18. Какая ошибка конкретно...

    "...код модифицированный, множество соответствий..."

    или

    "...код модифицированный, соответствий не найдено..." ? Это весьма важно.

    Русскую cheat engine надо вставить в директорию оригинальной английской версии. Английскую версию лучше оставить (например переименовав её).

  19. Kvazimado, слушай... трейнер-то у тебя заработал? .... я же его переделал.

    Как сделать машины не убиваемыми в которых сидит твой герой?

    Это сделать скорее всего не сложно ^_^ Только я так устал что сегодня уже не могу этим заниматся, а сегодня вечером и завтра и послезавтра я занят сутками.

    Теоретически можешь попробовать


    • [*:v35e6sz5]Найди адреса - изменяющиеся значения 4 байта целое, в случае когда ты садишься в машину и вышел из неё. Допустим ты нашёл адрес1
      [*:v35e6sz5]Найди адрес2 "здоровья" машины
      [*:v35e6sz5]Найди связи между адресами
      [*:v35e6sz5]Напиши чит-код

    ps Я с форума ухожу через 20 минут... когда появлюсь не знаю. Либо сегодня, либо завтра, либо вообще во вторник.

  20. Как известно уважаемый DarkByte выкладывает подарки (новые версии CE) под Новогоднюю цифровую ёлку раз в год.

    Сегодня затронули тему о том что же всё-таки будет в новой версии CE 5.6. Цитирую оригинал.

    next ce release (5.6) which will be the last 32-bit version of ce

    it'll have a pointerscanner that's a lot faster than the old one (where a level 5 pointer scan used to take 3 days it now finishes within a minute and more results)

    improved kerneldebugging, global debug is now functioning properly (so the set debug register isn't visible)

    cloaked memory editing to bypass integrity checks

    some new auto assembler improvements like declaring outside of the enable/disable sections, and an array of byte scan command to find those relocated blocks of code

    stacktrace that should make it easier to locate the caller of a function

    runtime launching of dbvm (more stable than booting off cd) which opens up a workable kernel debug and cloaked memory edit for 64-bit intels

    and lots and lots of bugfixes (e.g the floating point panel is fixed, heaplist is now done different, high dpi display fixes, etc...)

    hopefully a release before February

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

  21. Игра: Dark Sector.

    post-3-1295709482,42_thumb.jpg

    Спрос на трейнер: актуален (вероятность найти рабочий трейнер мала)

    Дата выхода: декабрь 2008

    Мин. системные требования: 2,4 ГГц, 1 Гб, 256 Мб видео

    Дополнительная информация: немного фиговое управление.

    На данный момент рассморено:

    1.Здоровье (автор MasterGH)]Инструменты: CheatEngineRus.

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

    Начинаем миссию. Для здоровья нет опознавательных признаков кроме как степень окровавленности экрана. Чем здоровье меньше, тем эффекта больше. Первое что пришло на ум так это отсев 4 байта целое, т.к. это чаще всего встречающийся тип.

    Ищу неизвестное, попадаю под обстрел, нажимаю на паузу, отсеиваю правилом «уменьшилось»… и т.д.

    В итоге я нашёл единственный рабочий динамический адрес.

    24A60398 = 125 (полное здоровье)

    Ставим брейкпоинт на доступ и получаем сразу же инструкции ниже (даже не пришлось заходить в игру).

    Среди них ищем инструкцию типа А. При этом стреляю во врагов и позволяю им ранить моего героя.

    post-3-1295709486,95_thumb.gif

    рис1

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

    Сейчас я пропущу внедрение чит-кода, чтобы вам было проще сориентироваться далее. Я сделал внедрение постоянного здоровья в первую инструкцию на рис1, но как оказалось значение с периодичностью в секунду возвращается то в 125, то становится 10000. Конечно, меня это не устроило – героя запросто могут прибить. Я поставил бряк на запись, чтобы узнать, в чём дело. И узнал что в итоге рядом с рабочим адресом лежит максимальный адрес именно он все портит.

    post-3-1295709490,67_thumb.gif

    Рис.2

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

    Активирующий Скрипт будет таким.

    Примечание (от 13.01.10)

    Конечно, его можно сделать чуть проще, но я уже переделывать не буду.

     
    
    [ENABLE]
    alloc(newmem,2048)
    label(_originalcode)
    label(_returnhere)
    
    label(_buf1)
    registersymbol(_buf1)
    label(_cheat1)
    registersymbol(_cheat1)
    
    label(pId)
    registersymbol(pId)
    
    newmem:
    mov [pId],edx
    _cheat1:
    jmp short _originalcode
    mov [edx+00000468],#100000
    mov [edx+00000464],#100000
    _originalcode:
    cmp dword ptr [edx+00000468],00
    jmp _returnhere
    pId:
    dd 0
    _buf1:
    db 90 90
    
    DS.exe+569FE2:>17000F85DDFEFFFF8BBB9C0000008B17YYxxxxxxxx00000F8EC8FEFFFF8D54
    jmp newmem
    nop
    nop
    _returnhere:
    
    [DISABLE]
    {DS.exe+569FE2:>17000F85DDFEFFFF8BBB9C0000008B17YYxxxxxxxx90900F8EC8FEFFFF8D54
    cmp dword ptr [edx+00000468],00
    unregistersymbol(pId)
    unregistersymbol(_buf1)
    unregistersymbol(_cheat1)
    dealloc(newmem)}

     

    А на здоровье

     
    
    _buf1:
    readmem(_cheat1,2)
    
    _cheat1:
    db 90 90
    
    [DISABLE]
    _cheat1:
    readmem(_buf1,2)

     

    2.Бесконечные патроны + нет перезарядки (автор MasterGH)]Инструменты: CheatEngineRus

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

    Итак, патроны в магазине имеют тип 1 байт, но я искал 4 байта и что странное нашёл )

    Нашлось два адреса экранный и рабочий. На рабочий адрес ставим бряк. Сразу же появились инструкции.

    post-3-1295709497,7_thumb.gif

    Рис1. Поставили бряк на адрес патронов. ECX – адрес структуры.

    Примечание.

    er* - означает что определить тип невозможно из-за технических недостатков CE.

    Почти во всех играх инструкции, работающие с патронами будут типа (B||C) – ничего удивительного.

    post-3-1295709501,98_thumb.gif

    Рис2. Здоровья – 10000, патронов в магазине – 25.

    Давайте поищем в структуре оружия адрес структуры объекта героя через сканер памяти. Нашлось 24 адреса-указателя и, увы, требуемого указателя нет.

    Тогда давайте поищем указатель на структуру оружия в структуре объекта героя непосредственно или многоуровневый указатель. К удивлению нашёлся один динамический адрес [1CFC1BD0]= 0x1CF424D0, который, увы, также не был в структуре героя. Но рано печалится и думать что надо лесть в отладку с головой.

    Примечание.

    Кстати я всё-таки слазил ))) и узнал вот что (см. рис 3 , патронов 25 в обойме, 6 – патроны в обойме пистолета, 0 – патронов у отсутствующего оружия в 3-ем слоте). Мы имеем дело с массивом указателей на определённое оружие. В объекте оружия нет указателя на объект владельца, поэтому фильтр сделать нельзя – надо изворачиваться.

    post-3-1295709509,41_thumb.gif

    Рис3.

    Как я нашёл этот указатель писать не буду, т.к. я вас точно запутаю (пользовался только CE и установкой брекйпоинтов).

    Поставим бряк на [1CFC1BD0]= 0x1CF424D0 и найдем инструкцию типа А.

    А в уме держим указатель b[[1CFBD334]+0x3e8] = патронам в рожке

    post-3-1295709513,58_thumb.gif

    рис4. После определения типов.

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

     
    
    [ENABLE]
    alloc(newmem,2048)
    label(_originalcode)
    label(_returnhere)
    
    newmem:
    push ebx
    mov ebx,[eax]
    mov byte [ebx+3e8],#30
    pop ebx
    _originalcode:
    cmp dword ptr [eax],00
    je ds.exe+54d934
    jmp _returnhere
    
    DS.exe+54D8FB:>518BCEBB01000000FFD28B0085C07439YYxxxxxxxx8B068B90040100008D4C
    jmp newmem
    _returnhere:
    
    [DISABLE]
    DS.exe+54D8FB:>518BCEBB01000000FFD28B0085C07439YYxxxxxxxx8B068B90040100008D4C
    cmp dword ptr [eax],00
    je ds.exe+54d934
    dealloc(newmem)

     

    3.Бесконечные боеприпасы (автор MasterGH)]Инструменты: CheatEngineRus

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

    Находим рабочий адрес патронов в «рюкзаке».

    Ставим на него бряк.

    Находим указатели (используя сканер памяти и указатель на pID из включенного активирующего скрипта)

    post-3-1295709518,56_thumb.gif

    рис 1.

    Ищем адрес ECX= 14A86030 и нашли единственный указатель [01A42974] =14A86030. Ставим на него бряк на доступ и определяем инструкции типа А.

    post-3-1295709522,9_thumb.gif

    рис 2. Выделенная инструкция типа А.

    Вытащим указатель _pres на структуру ресурсов из выделенной инструкции на рис2.

     
    
    [ENABLE]
    alloc(newmem,2048)
    label(_originalcode)
    label(_returnhere)
    
    label(_pres)
    registersymbol(_pres)
    
    newmem:
    mov [_pres],eax
    _originalcode:
    cmp dword ptr [eax],00
    je ds.exe+569dd0 // [i]кстати в этом месте могут быть проблемы из-за отсутствия метки отпечатка байт (если проблема и есть (т.к. я её не проверял), то будет решена после Нового года, когда выйдет версия CE 5.6)[/i]
    jmp _returnhere
    _pres:
    dd 0
    
    DS.exe+569D89:>00000050897C2420FFD28B0085C07447YYxxxxxxxx8B839C0000008B088B11
    jmp newmem
    _returnhere:
    
    [DISABLE]
    DS.exe+569D89:>00000050897C2420FFD28B0085C07447YYxxxxxxxx8B839C0000008B088B11
    cmp dword ptr [eax],00
    je ds.exe+569dd0
    dealloc(newmem)
    unregistersymbol(_pres)

     

     

    Теперь внедряем в инструкцию на рис. 1.

     

    
    [ENABLE]
    alloc(newmem,2048)
    label(_originalcode)
    label(_returnhere)
    
    newmem:
    pushf
    push eax
    mov eax,[_pres]
    or eax,eax
    jz short _originalcode
    cmp ecx,[eax]
    jne short _originalcode
    mov [ecx+ebp*8+00000394],#99999
    _originalcode:
    pop eax
    popf
    mov ebx,[ecx+ebp*8+00000394]
    jmp _returnhere
    
    DS.exe+54AE8C:>0400CCCC51807C240C0053558B6C2410YYxxxxxxxx0000744656578DB19C01
    jmp newmem
    nop
    nop
    _returnhere:
    
    [DISABLE]
    DS.exe+54AE8C:>0400CCCC51807C240C0053558B6C2410YYxxxxxxxx9090744656578DB19C01
    mov ebx,[ecx+ebp*8+00000394]
    dealloc(newmem)
     

    Результат такой, что можно сказать вауууу =)

    post-3-1295709527,8_thumb.gif

    рис.3

    И не рассмотрено:

    1) Оружие не взрывалось от датчиков

    2) Деньги

    3) Сделать идеальным оружие

    4) Обмануть расстояние действия грефы

    5) Грефа электрическая, огненная, любое метание с силой.

    6) Открыть доступ ко всему оружию

    Ваши статьи….

     

  22. Игра: Grand Theft Auto 2

    Спрос на трейнер: не актуален (поэтому не выкладываю таблицу и трейнер)

    Дата выхода:  30 сентябрь 1999

    Мин. системные требования: CPU 233 MHz, 32 Mb, 8 Mb Video

    Дополнительная информация: старая игрушка.

    -Перейти к теме для новичков (тема не создана, т.к. по ней нет материала).

    -Таблица CE (можете сами сделать).

    -Трейнер (можете сами сделать).

    На данный момент по игре расмотрены:

    1. Расструктуризация патронов и чит-код (Автор MasterGH)

     

    Инструменты: MHS, CheatEngine, IDA + hexrays.

    1. Нашёл адрес патронов.

    2. Поставил бряк (я ставлю всегда на доступ, больше информации видно).

    Получил адреса инструций, рассматриваем любую в данном случае например:

    004A4FC7: MOV CX, WORD PTR [ESI]
    Address: 004A4FC7
    EAX (after): 66666667 ESP (after): 0012FDBC
    ECX (after): 00000127 EBP (after): 00000000
    EDX (after): 00001947 ESI (after): 0356EEB8
    EBX (after): 00000000 EDI (after): 00000001

    Ищем адрес содержащий адрес 0356EEB8 (указатель на 0356EEB8)

    Всё что было выше, было старым. Теперь новое.

    Находим статический gta2.exe+1D85E0. Т.е. [gta2.exe+1D85E0]=0356EEB8. На него указатель искать впринципе не нужно, т.к. он находится в статичной памяти и в MHS зеленого цвета.

    3. Идём в дамп памяти и в окно выражений MHS и смотря на память по интуиции определяем вот такие смещения:

    [[5d85a0+x*16]]= значению, где x равно натуральным числам.

    Некоторые натуральные x в выражении будут показывать зачения количества патронов главного героя.

    [[5d85a0+0*16]] = 1 (1) // без понятия что это
    [[[5d85a0+1*16]]] = 6 (6) // без понятия что это
    [[5d85a0+2*16]] = 1 (1) // не помню
    [[5d85a0+3*16]] = 200 (C8) //патроны и далее патроны
    [[5d85a0+4*16]] = 880 (370) // патронов 880, в игре 88
    [[5d85a0+5*16]] = 100 (64) //патроны, патроны..
    [[5d85a0+6*16]] = 0 (0) // патронов нет
    [[5d85a0+7*16]] = 0 (0) // патронов нет...
    [[5d85a0+8*16]] = 0 (0)// и т.п.
    [[5d85a0+9*16]] = 0 (0)
    [[5d85a0+10*16]] = 0 (0)
    [[5d85a0+11*16]] = 0 (0)
    [[5d85a0+12*16]] = 0 (0)
    [[5d85a0+14*16]] = 0 (0)
    [[5d85a0+15*16]] = 0 (0)
    [[5d85a0+16*16]] = 0 (0)
    //где-то здесь заканчивается набор оружия, т.е. оржия не существует
    [[5d85a0+17*16]] = 0 (0)
    [[5d85a0+18*16]] = 0 (0)
    [[5d85a0+19*16]] = 0 (0)
    [[5d85a0+20*16]] = 0 (0)
    [[5d85a0+21*16]] = 0 (0)

    Дальше идут данные другого контекста.

    [[[5d85a0+22*16]]] = 0 (0)
    [[[5d85a0+23*16]]] = 0 (0)
    [[5d85a0+24*16]] = 0 (0)
    [[5d85a0+25*16]] = 0 (0)
    [[5d85a0+26*16]] = 0 (0)
    [[5d85a0+27*16]] = 0 (0)
    [[5d85a0+28*16]] = 360 (168) чьи-то патроны...
    [[5d85a0+29*16]] = 360 (168)
    [[5d85a0+30*16]] = 1080 (438)

    размер таблицы (5dc430-5d85a0)/16 -1 = 1000 (3E8). 1000 линий по 16 байт.

    4. Итогом будет следующая формула указателей на оружие.

    [[5d85a0+x*16]], если x в промежутке от 3 до 21, то это наше оружие. Это можно учесть при внедрении чит-кода с фильтром.

    Дело в том что на этом можено остановится...

    5. Как положено "маньяку" я пошёл дальше.

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

    Я поставил бряк на доступ на адрес [5d85a0+3*16]. (помним что этот адрес указатель на патроны, т.е. [[5d85a0+3*16]]= равно патронам в первом слоте)

    И вышел на

     

    0041CCC4 - MOV EDI, DWORD PTR [ECX+C] // часто обращаемая инструкция.
    Address: 0041CCC4
    EAX (after): 0000001E ESP (after): 0012FD90
    ECX (after): 005D85C4 EBP (after): 005E1038
    EDX (after): 005D86E4 ESI (after): 005DCBC8
    EBX (after): 005DE028 EDI (after): 032FD0F8
    .text:0041CCA0 sub esp, 28h // начало функции, некоторые комманды "пообрубал", т.к. лишнее
    .text:0041CCA3 mov eax, [esp+28h+arg_0]
    .text:0041CCA7 push ebx
    .text:0041CCA8 push esi
    .text:0041CCA9 mov esi, ecx
    .text:0041CCAB lea eax, [eax+eax*2]
    .text:0041CCAE push edi
    .text:0041CCB9 mov ecx, [esi+eax*4+1480h] // esi - начало объекта героя
    .text:0041CCC4 mov edi, [ecx+0Ch] <<<< мы здесь, где ([ecx+0Ch]==[[005D85C4+0Ch]])==[[5d85a0+3*16]]=патроны
    .text:0041CCC7 mov byte ptr [esi+71h], 2
    .text:0041CCCB mov ecx, edi
    .text:0041CCCD mov [esp+34h+var_1C], edi
    .text:0041CCD1 call sub_41CC80
    //далее выборка по case
    ...

    Сама функция на псевдокоде:

    char __thiscall sub_41CCA0(void *this, int a2)
    {
    v4 = this;
    v5 = *((_DWORD *)this + 3 * a2 + 1312);//esi = this - ссылка на объект.
    v3 = *(_DWORD *)(v5 + 12); // mov edi, [ecx+0Ch]
    *((_BYTE *)v4 + 113) = 2;
    v26 = v3;
    LOBYTE(v2) = sub_41CC80(v3);
    //...
    }

    На esi надо найти указатель, а не на ecx(!) Но 005DCBC8 сам по себе статический адрес и искать указатель на него не нужно.

    Исходя из всего этого фурмула указателей оружия меняется таким образом

    [[005DCBC8+1e*4+1480h]+0xc] это адрес патронов первого оружия, если я не ошибаюсь.

    А это тогда будет для общих [[[005DCBC8+1e*4+1480h]+0xc]+x*16]. Где x - натуральное.

    Если мы хотим найти более чёткий указатель, то нам надо развернуть "1e" из выражения выше, т.е.

    [[005DCBC8+[[esp+28h+arg_0]+[esp+28h+arg_0]*2]*4+1480h]+0xc], где esp надо смотреть выше... но мне это делать лень и так уже кучу времени провозился.

    Подсмотреть по какому принципу рапологаются указатели в структуре объекта игрока.

    7. Финал. Ну и наконец чит-код будет таким, если ресурсы наши, то пишем их максимальное значение.

    [ENABLE]
    alloc(newmem,2048)
    label(_returnhere)
    label(_originalcode)
    newmem:
    cmp esi,005DCBC8 // наш игрок?
    jne short _originalcode
    push eax
    mov eax,[ecx+0c]
    mov [eax],#10000 // если наш, то вписать патроны каждому оружию
    pop eax
    _originalcode:
    mov edi,[ecx+0c]
    mov byte ptr [esi+71],02
    jmp _returnhere
    GTA2.EXE+1CCC4:
    jmp newmem
    nop
    nop
    _returnhere:
    [DISABLE]
    GTA2.EXE+1CCC4://14000000008B8C8680140000885 ... 7C2418E8AA
    mov edi,[ecx+0c]
    mov byte ptr [esi+71],02
    unregistersymbol(dnull)
    dealloc(newmem)

     

  23. ООП состоит на основе трёх концепций: инкапсуляция, наследование и полиморфизм. Но модель ООП на этом не заканчивается. В каждом отдельном языке программирования добавлены свои "прибамбасы" относительно наследования и полиморфизма, что формирует модель ООП соответствующую языку программирования. Из всех языков которых я знаю или сталкивался С++ и его продолжения наиболее сильные в ООП.

    О концепциях ООП

    Класс - это тип или описание объекта. На основе этого описания выделятеся память и строится объект. Когда один объект строит другой возникает цепочка указателей. Всегда есть статический адрес начального объекта, по которому можно добраться через цепочку указателей до данных вложенных объектов.

    Также существуют:

    vftable - таблица виртуальных функций

    vbtable (virtual base class table) - таблица базового виртуального класса

    Обе таблицы находятся в статической памяти и могут служить описанием работы с данными и методами как для одного так и нескольких объектов.

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

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

    Сложное наследование некоторых современных стратегиях это головная боль тех кто умеет внедрять чит-коды.

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

    Насчёт "прибамбасов" таких как перегрузка методов, перегрузка операторов, шаблоны(в С++)... и т.д. Об этом я, может быть, напишу, на примере конкретной игры. Например, в СТАЛКЕРЕ управление объектами осуществятся, скорее всего, через описание шаблона и итератора...

    ООП в играх да и в целом позволяет строить модель программы в удобном виде. В книгах пишут, что если код размеров от 10000-100000 строк, то в нём невозможно ориентироваться без применения ООП чего не говорить уже о большем размере кода. Дело в том не понятно к какой абстракции принадлежит та или иная функция, тот или иной метод. Если программа размером например не более 1000 строк, то вполне можно обойтись без ООП - программа будет работать быстрее, если будет правильно написана, но иногда быстрее что-то написать с ОПП - и получится 400 строк вместо 1000.

    В современной игре в которой очень много объектов обязательно применяется ООП. В этом случае в ней может быть множество объектов разных типов, но лишь один метод (типа B или C) будет работать со здоровьем всех игроков или урона зданий, или даже уменьшение и увеличения опыта, или даже чего-нибудь другого. Может быть, даже не уменьшение, а просто копирование уменьшенного параметра.

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

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

    Что касается применения ООП в написании трейнера. Могу сказать, что это дело вкуса. Есть люди которые от и до пишут трейнер на ассемблере приследуя цель вылизанной от и до программы меньшего размера. Только это не драйвер, который требует лучшей производительности, потраченный труд уходит в пустую.

    Как бы там ни было, для мозгов человека удобнее воспринимать наиболее структурированную информацию - где чит-код является классом иди структурой. А ими управляет специальный класс. С этой стороны трейнеры лучше писать с применением ООП, чем на ассемблере или процедурном с WinApi. Однако, если бы трейнер был очень нужной полезной программой-драйвером, то его без сомнений лучше писать на ассемблере, в "тяжелом" случае на С.

    По поводу языка на котором лучше писать. Это дело вкуса, но если интересно моё мнение, то писать нужно на языке С++ с применением ООП. С++ это системный язык. Дельфи, Байсик больше прикладного характера, но системные программы можно также писать и они будут также работать. Использовать ли визуальное оформление, писать ли на VC++? Да, можно, это не критично. Вам не придётся писать дополнительного кода.

    В завершении статьи. Трейнеры для синголовых игр на сегодняшний день ещё не использовали весь возможный потенциал в обмане игр:

    1. Нет универсальности трейнеров для одного вида игры с разными патчами и локализациями.

    2. Думаю, что нет ни одного трейнера с инжектом игрового скрипта или заданием скрипта для игрового скрипт-интерпритатора в играх где он используется - я довольно много дизассемблировал трейнеров и этого не видел (другое дело моды). Предпосылки к этому есть у MHS и CE - и там и там можно создавать удалённый поток и подгружать библиотеки, которые будут своего рода входным вводом задания скриптов для игрового скрит-интерпретатора.

    Если вы с жадностью хотите ещё прочитать информацию, то читайте её по ссылкам: презентация, более подробная версия реверсинга С++

    Прочтение этой статьи позволит ориентироваться в связях структур игровых объектов – позволит осмысленно подходить к поиску многоуровневых указателей, а также внедрять чит-коды с использованием фильтров.

    Часть1. Как выглядит объект в памяти. Первое знакомство

    Класс можно представить так:

    Название класса:

    Начало:

    Описание данных класса;

    Описание методов класса;

    Конструктор;

    Деструктор;

    Конец описания класса.

    Начало и конец в дальнейшим обозначается фигурными скобками {…}

    «Описание данных класса» - это описание переменных определенного типа. Например, переменная "int а" имеет тип int (это целое число 4 байта со знаком (подробнее о типах см. в Интернете)

    Методы класса представляют собой функции, которые возвращают или не возвращают значения. Например,

    void fun(){printf("Hello World!“}. В данном примере присутствие void, говорит о том, что функция не возвращает значения.

    Привязка данных и методов к классу называется инкапсуляцией.

    Существует такое понятие как ограничение видимости при инкапсуляции. Ограничение описывается основными директивами: private, protected, public:

    private – доступ к данным только изнутри объекта,

    protected – доступ к данным изнутри объекта и из производных классов.

    public – доступ к данным из любого места.

    Давайте посмотрим наконец как выглядит класс в С++.


    int c; //по умолчанию доступ будет как private
    public:
    void pc(){printf("Method C %dn",c);}
    };
    class C{

    Этот класс имеет описание закрытой переменной с и открытую функцию pc, которая выводит на «досовский» экран значение переменной с.

    Напомню, что это всего лишь описание будущего объекта. Любой объект инициализируется «конструктором» и разрушается «деструктором». В примере выше конструктор и деструктор не объявлены, но они вызываются по умолчанию.

    Давайте посмотрим наш пример с конструктором, который инициализирует данные с равной единице.


    int c; //тут может быть здоровья игрока к примеру
    public:
    C(int aa=100){c=aa;} // конструктор
    void pc(){printf("Method C %dn",c);}
    };
    class C{

    Конструктор, принимает по умолчанию входной параметр аа равный 1 и присваивает его с.

    Пару слов о пользе конструкторов. Конструктор в играх может создавать объект по входным параметрам. В читинге конструктор может пригодиться при принудительном создании нового объекта или создании объекта с изменёнными свойствами. Ну, например, когда игра загружается вы, скорее всего, можете подменить скин своего персонажа, или изменить его характиристику - что он как бы является торговцем, которого убить невозможно. Ну, ладно... идём дальше, т.к. это отдельная тема.

    Деструктор выглядел бы так:

    ~C(…);

    Более подробно см. в Интернете.

    Подведём итог. Мы знаем, как выглядят и работают класс, конструктор, деструктор, данные, методы.

    Создадим проект в VC++ проект консольного варианта:



    class Player{
    int NameID,Health,Ammo;
    public:
    void set(int a, int b, int c){NameID=a; Health=b; Ammo=c;}
    void getInfoX(){
    printf("NameID= %dn",NameID);
    printf("Health= %dn",Health);
    printf("Ammo= %dn",Ammo);
    }
    };

    void main()
    {
    Player *obj=new(Player);

    obj->set(1,100,20);
    obj->getInfoX();

    delete(obj);
    };
    #include 

    Оператор –> применяется для обращения к функциям внутри объекта, который был создан в выделнной памяти.

    Результат работы программы:

    post-3-1294595717,35_thumb.gif

    Рис.1

    Данные объекта были построены таким образом

    post-3-1294595711,21_thumb.gif

    Рис.2

    А вот что мы увидим в отладчике VC++

    -----

    #include

    class Player{

    int NameID,Health,Ammo;

    public:

    void set(int a, int b, int c){NameID=a;Health=b;Ammo=c;}

    void getInfoX(){

    printf("NameID= %dn",NameID);

    printf("Health= %dn",Health);

    printf("Ammo= %dn",Ammo);

    }

    };

    void main()

    {

    00401000 push esi

    Player *obj=new(Player);

    00401001 push 0Ch//0Сh – кол-во байт выделенной памяти.

    00401003 call operator new (401189h)

    00401008 mov esi,eax

    obj->set(1,100,20);

    0040100A xor eax,eax

    0040100C inc eax

    obj->getInfoX();

    0040100D push eax

    0040100E push offset string "NameID= %dn" (40BB08h)

    00401013 mov dword ptr [esi],eax //esi – адрес объекта

    00401015 mov dword ptr [esi+4],64h

    0040101C mov dword ptr [esi+8],14h

    00401023 call printf (40104Fh)

    00401028 push dword ptr [esi+4]

    0040102B push offset string "Health= %dn" (40BB14h)

    00401030 call printf (40104Fh)

    00401035 push dword ptr [esi+8]

    00401038 push offset string "Ammo= %dn" (40BB20h)

    0040103D call printf (40104Fh)

    delete(obj);

    00401042 push esi

    00401043 call operator delete (401114h)

    00401048 add esp,20h

    };

    0040104B xor eax,eax

    0040104D pop esi

    0040104E ret

    ---

    Тоже самое в IDA

    post-3-1294595722,6_thumb.gif

    рис. 3

    А вот так выглядит в псевдокоде

    post-3-1294595725,98_thumb.gif

    рис. 4

    При обмане игр у нас не будет исходника С++ и нам нужно научится понимать псевдокод.

    Сделаем немного понятнее псевдокод.

    Нажмём shift+F9 и опишем структуру класса Player с 3*4 байт

    post-3-1294595729,35_thumb.gif

    рис. 5

    Затем в псевдокоде нажмём на Y и определим тип структуры:

    post-3-1294595736,06_thumb.gif

    рис.6

    И получим более лучшее представление псевдокода

    post-3-1294595740,62_thumb.gif

    Рис.7

    И ещё давайте посмотрим, что будет если мы не иницилизируем некоторые данные объекта.

    post-3-1294595747,32_thumb.gif

    рис 8

    Результат такой:

    post-3-1294595751,49_thumb.gif

    рис 9

    Переменные x1.x2.x3 не инициализировались и остался мусорный код выделенной памяти. Когда выделяется память, то она не «подчищена», если вы не знали.

    Подведём итог. Познакомились с С++ (если не были знакомы) увидели на простом примере как оформляется структура объекта в С++ и как она выглядит в памяти и в псевдокоде. Увидели как псевдокод облегчает анализ игровых объектов.

    Главное научиться проводить аналогии между псевдокодом и дизассемблерным кодом для того чтобы представить как устроен объект.

    Если у вас возникли трудности в С++ то существует много информации в Интернете, которую вы можете легко найти самостоятельно.

    Часть2. Дальнейшее исследования игрового объекта в памяти

    Наследование.

    В этой части рассмотрим как выглядит структура объекта:

    • При наследовании базового класса
    • При иерархии наследования классов
    • При иерархии наследования класса с инкапсулированным объектом
    2.1 Структура объекта при простом наследовании базового класса.
    post-3-1294595763,84_thumb.gif
    Рис. 1
    Посмотрим дизассемблерный код...
    post-3-1294595767,6_thumb.gif
    Рис. 2
    Псевдокод будет таким
    post-3-1294595772,02_thumb.gif
    Рис. 3
    2.2 Структура объекта при иерархии наследования классов.
    Ниже представлен пример классов «вложеннных» друг в друга по принципу «матрёшки».
    Базовый класс «стремиться» вверх структуры
    post-3-1294595776,53_thumb.gif
    Рис. 4
    Псевдокод для этого кода приведён ниже.
    post-3-1294595781_thumb.jpg
    рис. 5
    По аналогии можно представить как будут располагаться данные при различных наследованиях.
    2.3 Структура объекта при иерархии наследования классов с инкапсулированным объектом.
    Тут всё просто. Структура одного объекта должна содержать указатель на созданный объект. Красная стрелка это указатель на созданный инкапсулированный объект. Получается что один объект, содержит указатель на другой объект.
    post-3-1294595785,27_thumb.jpg
    рис. 6
    Ниже находится псевдокод (к сожалению картинка сдохла на сервере, а у меня нет оригинала, но осталась рука - пока не знаю как она сюда попала :grin: ).
    image007a.jpg
    рис. 7
    Ниже находится дизассемблерный код.
    post-3-1294597053,63_thumb.jpg
    рис. 8
    Подведём итог вы узнали:
    как выглядит структура инкапсулирующая объект,
    как выглядит структура при наследовании и при иерархии наследования.Часть 3. Дальнейшее исследования игрового объекта в памяти
    В этой части мы рассмотрим.

    • Виртуальные функции
    • Виртуальные классы и множественное наследование

    3.1 Виртуальные функции.

    Если вы не сталкивались с полиморфизмом ни разу и вообще с ООП знакомы мало, то понять эту тему будет, скорее всего, сложно. Виртуальные функции это одинаково объявленные функции в иерархии наследования, которые позволяют работать с данными объекта в контексте нужного класса. Объяснил как смог.. =)

    Очень важно понять, как это работает для исследования игровых объектов в дальнейшем, т.к. виртуальные функции попадаются сплошь и рядом.

    Разберём пример «на пальцах», показывающий выгоду виртуальных функции перед похожими, но не виртуальными одинаковыми функциями.

    Программа перемещает графические объекты по нажатым клавишам клавиатуры, но писать мне её некогда, поэтому разберу основное.


    {
    int x,y;
    public:
    Dot() {x=100; y=100;} // просто зададим координаты по умолчанию
    void show(){сделать пиксел белым};
    void hide() {сделать пиксел черным};
    void move (int dx,dy){ hide(); x+=dx; y+=dy; show()};
    };
    class Dot

    Мы создали класс Точки, у которой описаны координаты положения и описаны функции перемещения с угасанием и отображения точки. Ключевое место здесь в том, что в функции перемещения присутствуют локальные вызовы угасания и отображения точки. Именно из-за локальных вызовов появится проблема в производном классе, т.к. в базовом классе будут вызываться свои локальные функции с тем же именем. Выходом будет определение виртуальных функций. Рассмотрим эту проблему.


    {
    int radius;
    public:
    Ring () {radius=200;}; //зададим радиус круга по умолчанию
    void show(){сделать окружность белой};
    void hide(){сделать окружность чёрной};
    };
    class Ring: public Dot

    Видим, что Круг наследует функцию перемещения, в которой происходят локальные вызовы hide и show. Т.е. если мы зададим движение Кругу, то двигаться будет Точка, т.к. вызываются локальные функции в контексте класса Точки.

    Ring *ring=new(Ring);

    ring->mov(50,60); // вызовет перемещение только точки,а не круга

    Чтобы решить эту проблему был введен механизм виртуальных функций. Вызов той или иной функции задается смещением (генерируемым компилятором), по которому в созданной таблице виртуальных методов (vftable) выбирается нужная функция, которая и вызовется.


    {
    int x,y;
    public:
    Dot() {x=100; y=100;} // просто зададим координаты по умлочанию
    virtual void show(){сделать пиксел белым};
    virtual void hide() {сделать пиксел черным};
    void move (int dx,dy){ hide(); x+=dx; y+=dy; show()};
    };

    class Ring: public Dot
    {
    int radius;
    public:
    Ring () {radius=200;}; // также просто зададим радиус круга
    void show(){сделать окружность белой};
    void hide(){сделать окружность чёрной};
    };
    class Dot

    Теперь


    ring->mov(50,60); // вызовет перемещение только круга в заданные координаты 50 и 60
    ring->Dot::mov(50,60); // этот метод в принципе бесполезный, т.к. координаты точки уже были равны 50 и 60
    Ring *ring=new(Ring);

    Вызывать метод перемещения точки было не обязательно, главное то, что круг наследовал координаты x и y и можно и нужно перемещать только круг. Однако, если вы вызовете ring->Dot::mov(100,100), то это распространится и на круге... т.е. и точка и круг взаимосвязаны и буквально слеплены в одно целое как в симбиозе (кто биологию хорошо знает? ^_^ )

    Как мы увидели механизм «виртуальных функций (полиморфизм)» нужен для правильной работы с данными объекта в требуемых контекстах его описывающих классов. Чаще всего виртуальные функции объявляются без входных параметров, таким образом, их применение наиболее удобно и более расположено к полиморфизму.

    При полиморфизме в статичной области кода возникает таблица виртуальных функций vftable. Указатель на таблицу виртуальных функций находится по первому адресу объекта (см. рис. 1)

    post-3-1294595796,26_thumb.png

    рис. 1

    Следует отметить, что vftable характерна только для определённого типа класса. Если вы найдёте в памяти игры адрес таблицы виртуальных функций (vftable) некоторого объекта и затем в сканере памяти найдёте 6 указателей на ту же vftable, то можете считать что вы «вышли» ещё на 6 объектов, которые наследовали vftable класса или некоторой иерархии классов. Соответственно по указателю на vftable в чит-кодах можно делать фильтры по изменению данных игровых объектов построенных по описанию конкретного класса или по описанию классовой иерархии.

    Учитывая всё выше известное, напишем программу с игровыми объектами и проанализируем формирование объектов с vftable.

    Результат программы будет таким.

    post-3-1294595799,9_thumb.png

    рис. 2. (Координат нет, это вы поймете на рис 6 )

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

    Сначала посмотрим расположение данных двух объектов, после того как мы их заполнили. Так мы увидим данные после того как CE сама расструктуризовала их.

    post-3-1294595804,3_thumb.png

    рис. 3

    Как мы видим, структуры объектов заполнены непонятными данными по смещениям 0x18-2С, 4C-0x64, 0x68-0x6C. Адреса по этим смещениям рассмотрим позже. Сейчас важно понять, как устроены таблицы на рисунке по смещениям 0x0, 0x30 и как они работают.

    Они выглядят вот так (так как и расструктуризавала CE)

    post-3-1294595809,18_thumb.png

    рис. 4

    Для удобства переведём эти значения в hex- вид

    post-3-1294595813,26_thumb.png

    рис. 5

    Я пока не знаю, правильно ли я определил, что по смещению 0хС значение 0х48 указывает на конец таблицы виртуальных функций в контексте всей иерархии классов...

    00401017 – это указатель на функцию Player:: GetInfo

    0040102B – это указатель на функцию Building::GetInfo

    Обращение к указателям компилируется в коде (вы это можете посмотреть в дизассемблерном листинге ниже), т.е. в объектах не содержится данных о том к какой именно функции, в каком описании класса в какой момент обращаться.

    Залезем в псевдокод в функцию main, т.к. именно по нему нам надо учиться обманывать игры и смотрим рис 5 и рис.6

    В псевдокоде смещения в структурах объектов десятичные,но там не сложные числа можно перевести в 16-ричную систему в уме для того чтобы соотнести смещения структур на рис. 5

    post-3-1294595818,45_thumb.png

    рис. 6

    GetInfo в контексте класса control не сработала, из-за виртуальных функций, так и должно быть.

    Псевдокод показывает всё что необходимо и это гораздо нагляднее, чем смотреть дизассемблерный код в отладке. Для интересующихся (маньяков) привожу дизассемблерный код. Комментировать не буду, т.к. основное всё разобрал. Скопируйте его в отдельное место и можете тщательно разобраться как что и куда.

    class abstr{

    virtual void GetInfo()=0;

    };

    class control: abstr{

    protected:

    int x,y,z,ID;

    public:

    control(){x=y=z=0;}

    void move(int dx,int dy, int dz) {x+=dx;y+=dy;x+=dz;GetInfo();}

    void GetInfo(){printf("x= %d y= %d z= %dn",x,y,z);}

    00401000 push dword ptr [ecx+0Ch]

    00401003 push dword ptr [ecx+8]

    00401006 push dword ptr [ecx+4]

    00401009 push offset string "x= %d y= %d z= %dn" (40BB0Ch)

    0040100E call printf (4010EAh)

    00401013 add esp,10h

    00401016 ret

    };

    class Player: public control{

    protected:

    int Health;

    public:

    void Set(int id, int health){ID=id; Health=health; GetInfo();}

    void GetInfo(){printf("PlayerID= %d Health= %dn",ID,Health);

    00401017 push dword ptr [ecx+14h]

    0040101A push dword ptr [ecx+10h]

    0040101D push offset string "PlayerID= %d Health= %dn" (40BB20h)

    00401022 call printf (4010EAh)

    00401027 add esp,0Ch

    }

    0040102A ret

    };

    class Building: public Player{

    int SpeedOfC;

    public:

    void Set(int id, int health, int Speed)

    {ID=id; Health=health;SpeedOfC=Speed; GetInfo();}

    void GetInfo(){printf

    ("Building ID= %d Health= %d SpeedOfC= %dn",ID,Health,SpeedOfC);}

    0040102B push dword ptr [ecx+18h]

    0040102E push dword ptr [ecx+14h]

    00401031 push dword ptr [ecx+10h]

    00401034 push offset string "Building ID= %d Health= %d Speed"... (40BB3Ch)

    00401039 call printf (4010EAh)

    0040103E add esp,10h

    00401041 ret

    };

    void main()

    {

    00401042 push ebx

    00401043 push esi

    00401044 push edi

    Player *objP=new(Player);

    00401045 push 18h

    00401047 call operator new (401255h)

    0040104C xor esi,esi

    0040104E pop ecx

    0040104F cmp eax,esi

    00401051 je main+24h (401066h)

    00401053 mov dword ptr [eax+0Ch],esi

    00401056 mov dword ptr [eax+8],esi

    00401059 mov dword ptr [eax+4],esi

    0040105C mov dword ptr [eax],offset Player::`vftable' (40BB6Ch)

    00401062 mov edi,eax

    00401064 jmp main+26h (401068h)

    00401066 xor edi,edi

    Building *objB=new(Building);

    00401068 push 1Ch

    0040106A call operator new (401255h)

    0040106F pop ecx

    00401070 cmp eax,esi

    00401072 je main+43h (401085h)

    00401074 mov dword ptr [eax+0Ch],esi

    00401077 mov dword ptr [eax+8],esi

    0040107A mov dword ptr [eax+4],esi

    0040107D mov dword ptr [eax],offset Building::`vftable' (40BB74h)

    00401083 mov esi,eax

    objP->Set(1,200);

    00401085 mov eax,dword ptr [edi]

    00401087 xor ebx,ebx

    00401089 inc ebx

    0040108A mov ecx,edi

    0040108C mov dword ptr [edi+10h],ebx

    0040108F mov dword ptr [edi+14h],0C8h

    00401096 call dword ptr [eax]

    objB->Set(1,1000,5);

    00401098 mov eax,dword ptr [esi]

    0040109A push 5

    0040109C mov dword ptr [esi+10h],ebx

    0040109F pop ebx

    004010A0 mov ecx,esi

    004010A2 mov dword ptr [esi+14h],3E8h

    004010A9 mov dword ptr [esi+18h],ebx

    004010AC call dword ptr [eax]

    objP->move(5,5,0);

    004010AE mov eax,dword ptr [edi]

    004010B0 add dword ptr [edi+8],ebx

    004010B3 add dword ptr [edi+4],ebx

    004010B6 mov ecx,edi

    004010B8 call dword ptr [eax]

    objB->move(6,6,0);

    004010BA mov eax,dword ptr [esi]

    004010BC add dword ptr [esi+8],6

    004010C0 add dword ptr [esi+4],6

    004010C4 mov ecx,esi

    004010C6 call dword ptr [eax]

    objP->control::GetInfo();

    004010C8 mov ecx,edi

    004010CA call control::GetInfo (401000h)

    objB->control::GetInfo();

    004010CF mov ecx,esi

    004010D1 call control::GetInfo (401000h)

    delete(objP);

    004010D6 push edi

    004010D7 call operator delete (4011AFh)

    delete(objB);

    004010DC push esi

    004010DD call operator delete (4011AFh)

    004010E2 pop ecx

    004010E3 pop ecx

    004010E4 pop edi

    004010E5 pop esi

    };

    004010E6 xor eax,eax

    004010E8 pop ebx

    004010E9 ret

    С виртуальными функциями более менее познакомились. Возможно, в следующих статья разберём ещё более тяжёлые случаи (адские :ninja: )

    Продолжение следует. В следующей частях возможно будет рассмотрено.

    Множественное наследование.

    Виртуальные функции при множественном наследовании.

    Виртуальное наследование классов.

    Виртуальные функции при виртуальном наследовании классов.

    Статичные данные и функции в объектах. Дружественные функции в объектах.

    Массивы объектов.

    Объекты по шаблону. Объекты по стандартному шаблону .

    Сообщения. Методы дизассемблирования и отладки.

    Объекты совместно с lua – интерпритатором.

    Создаём объекты через конструкторы и команды lua.

    Обход антиотладочных ловушек xlive.

    Распаковка игровых ресурсов.

    Обман flash игр.

    Обман .NET игр.

    Обман JAVA игр.

    Обман эмуляторных игр, поиск нулевого адреса.

    • Плюс 2
×
×
  • Создать...

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

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