• Партнер

  • Выбирать особо было не из чего, т.к. на машине стояла только одна игра Newer Winter Nigths. И решено было писать тренер к ней.

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

    С помощью ArtMoney я нашел тот блок памяти, в котором хранятся очки жизни. Было бы всё просто, если бы не одно НО: при каждой новой загрузке это место меняется, это происходит по тому, что программа выделяет память динамически и раз на раз не приходится. Ну, раз программа находит нужное место то и я его смогу найти.)

    Примечание: ArtMoney найдёт не одно значение, у меня их было 10. Меняйте эти значения и смотрите за остальными, некоторые ячейки тоже поменяют своё значение на введённое вами. Та ячейка, которая изменяет собой больше ячеек и есть искомая.

    Запускаем SoftICE, и ставил брейкпоинт (в дальнейшем БР) на доступ к памяти в ячейку, которую мы нашли. (У меня по адресу 10AEFC70, поэту команда для SoftICE'а выглядит так: bpmd 10AEFC70. Но обратите внимание что под окном кода должно быть написано, что вы в модуле nwmain.text, иначе у вас не чего не выйдет). Жмём F5, когда выскочит
    SoftICE.

    Это должна быть инструкция по адресу 607C87 MOV AX, [ECX+A8]. Т.е. какое-то значение (ECX)+А8 ссылается на жизнь. Следующим шагом нужно отыскать то место где регистр ECX получает своё значение. Чуть ниже этой инструкции стоит команда RET- это выход из функции. Выполняем пошагово (F8) пока не выйдем из функции, и не уведем следующее:

    5D05E1 mov ecx, esi
    5D05E3 call [edx+00000009C]

    Ставим БР по адресу 5D05E1 командой "bpx 5D05E1" или двойным щелчком на строке и нажимаем F5 несколько раз, пока ESI не будет равен найденному значению минус A8 (10AEFC70-A8=10AEFBC8). Двигаем код в окне вверх (CTRL+вверх), ища, где ESI получает своё значение, должны дойти вот досюда:

    005D0071 mov ecx,ebx
    005D0073 call [edx+00030]
    005D0076 mov esi,eax

    Значит, функция возвращает в регистре EAX наше число, но не стоит сразу ковырять внутренности функции, поставив БР по адресу 005D0071, мы увидим, что
    EBX=10AEFBC8. Чуть выше видим:

    005D0049 mov ebx,[esp+00050]

    Снова ставим БР на участок памяти ESP+50 "bpmb esp+20 w"(w означает, что отслеживать только запись по этому адресу). Наберём в командной строке "? ESP+50" чтобы получить точный адрес, затем "d ESp+50" чтобы видеть этот участок. Жмём F5 столько раз, пока на этом участке не появится наше значение в перевёрнутом виде, т.е. C8FBAE10 (руки бы оторвать тому, кто это придумал %).
    Как дальше разворачивались дела я не помню, но вы должны выйти на:

    CALL 005CF000 (например, по адресу 5D8D6A, 5D8866)

    После выполнения этой функции EAX содержит наше число. Значит способ получения этого числа в этой функции. Вот содержимое этой функции
    (примечание: все числа после точки с запетой приведены конкретно для моего примера, у вас они могут быть другие):

    5CF000:
    mov eax,ecx
    mov ecx,[000862360] ; ECX=A6BA590 (7)
    mov edx,[eax][00030]
    mov ecx,[ecx][00004] ; ECX=[A6BA594]=79A6058 (6)
    push edx
    call .0005BB7E0

    5BB7E0:
    mov eax,[esp][00004]
    mov ecx,[ecx][00004] ; ECX=[79A605C]=7A58008 (5)
    push eax
    call .0005C2B10 

    5C2B10:
    push ecx
    mov edx,[esp][00008]
    mov ecx,[ecx][000010074] ; ECX=[7A6807C]=79F1CB8 (4)
    lea eax,[esp][00000]
    push eax
    push edx
    mov d,[esp][00008],000000000
    call .0004259D0

    4259D0:
    mov eax,[esp][00004] ; EAX=20C2
    push esi
    mov esi,eax
    shr esi,01F ; ESI=0
    and esi,001
    mov edx,eax
    and eax,000000FFF ; EAX=0C2
    shl esi,00C ; ESI=0
    add esi,eax ; ESI=0C2
    mov eax,[ecx] ; EAX=[79F1CB8]=7A1EFF8 (3)
    mov eax,[eax][esi]*4 ; EAX=[7A1F300]=7BD1E08 (2)
    and edx,07FFFFFFF
    test eax,eax
    pop esi
    je .000425A04
    cmp [eax],edx
    je .000425A13
    mov eax,[eax][00008]
    test eax,eax
    jne .0004259F9
    mov ecx,[esp][00008]
    mov d,[ecx],000000000
    mov al,001
    retn 00008
    mov edx,[eax][00004] ; EDX=10AEFBC8 (1)

    Цифрами в скобках обозначены ключевые моменты кода. Мы видим, чтобы получить нужный адрес надо прочитать DWORD по адресу EAX+4. В свою очередь этот EAX=EAX+ESI*4, где ESI=0C2. И так далее. Чтобы проверить правильность наших рассуждений пишем в командной строке SoftICE'а "?
    @(10AEFBC8+A8)", программа должна написать текущее количество жизни (первое число в шестнадцатеричное, а вот второе десятичное). Теперь надо заменить число 10AEFBC8 на то,
    что мы получили, т.е. на @(7BD1E08+4). Соединяем все вместе - должно получится "? @(@(7BD1E08+4)+A8)". Аналогичным способом движемся выше до строки обозначенной (7), там адрес задан конкретно. Он будет всегда один и тем же. В итоге у меня получилось так: "? @(@(@(@(@(@(@(@(862360)+4)+4)+10074))+0C2*4)+4)+A8)". Вот в принципе и всё что касается нахождения нашей жизни 😉 Можно уже юзать это совместно с SoftICE'ом.

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

    Чтобы иметь возможность изменять данные в чужом процессе (программе) нужно знать её идентификатор процесса. Его можно получить используя функцию NtQuerySystemInformation(5, адрес_буфера, размер_буфера, 0), из библиотеки ntdll.dll. Чем больше процессов запущено, тем больше
    нужен буфер. У меня он составляет 7 Кб. Буфер состоит из массива структур ProcessInfo. По 0 смещению задан размер структуры DWORD. А со смещением 3C ссылка на имя процесса в формате UniCODE. По смещению 44 находится идентификатор процесса. Затем открываем этот процесс функцией
    OpenProcess(1F0FFF,0,идентификатор_процесса), она возвратит нам хендл на этот процесс. С помощью функции ReadProcessMemory(Handle, адресс_в_процессе_
    откуда_читать, адресс_буфера_ куда_записать, сколько_прочитать,
    0) сначала читаем по адресу 862360, прибавляем к этому значению 4 и снова читаем по полученному адресу, и т.д. Запись в память производится с помощью WriteProcessMemory(Handle, адрес_куда_записывать, адрес_буфера, сколько_записать, 0).

    Процесса заморозки можно добиться, используя вызов по таймеру. Создаём таймер -
    SetTimer(hWnd-идентификатор_окна, уникальный_номер, время_в_милисек, 0 или
    адрес_процедуры_ которая_будет_вызываться). Если последний аргумент равен 0, то процедуре обработки окна будет приходить сообщение WM_TIMER, если таймеров много то отличить их можно по уникальному номеру, который был задан при создании таймера, он находится в переменной wParam. Не забудьте удалить таймер при выходе KillTimer(hWnd,
    handle).

    Ну, вот в принципе и всё, осталось нарисовать морду будущей проги и описать несколько функций. 

    P.S. Функция NtQuerySystemInformation доступна только NT системам в Win9x нужно юзать CreateToolhelp32Snapshot, Process32First, Process32Next чтобы получить идентификатор нужного процесса.

    Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии