Департамент Образования города Москвы
Индивидуальный проект ученика 10 “Б” класса
По теме: “Изменение структур скомпилированных программ”
Руководитель:
Москва 2021
Оглавление:
1.1…Задачи проекта
1.2…Актуальность проекта
1.3…Введение в тему
1.4…Начало
1.5…Написание программы JMP
1.6…Итоги и результаты
Задачи проекта:
1.Знакомство пользователя с работой программ.
2. Знакомство с ассемблером, базовый реверсинг
3. Написание программы на C++ изменяющей действия другой программы на уровне взаимодействия ассемблерных команд.
Актуальность:
Для большинства человек способы взаимодействия с программой заканчиваются на этапе “превращения” ее из исходного кода в исполняемую интерпретацию энного, проект призван открыть новый мир работы с программным обеспечением без наличия исходного кода и показать способы взаимодействия с ним.
Введение:
Итак, когда мы нажимаем кнопку “скомпилировать” в нашей IDE среде, никакой магии не происходит и программа в подавляющем большинстве собирается на ассемблер – набор препроцессорных команд и инструкций, требуемый для ее конечного исполнения. Ассемблер можно ассоциировать с привычными языками программирования и на основе с этим воспринимать его и работать с ним. Любую программу можно с помощью таких средств как IDA, OLLYDBG, x64dbg и прочих разобрать на эти команды ассемблера и работать с ними. На этом этапе я и вывожу главную цель проекта.
Создание простой игры(программы для примера) и изменение ее условий с помощью сторонней программы.
Начало:
Итак, начнем с программы для изменения, эта будет простая непроходимая игра в стиле змейки. На карте будет всего 2 монеты, а для ее успешного прохождения потребуется 5, и каждый раз она будет выводить ошибку о недостаточном количестве монет, но в случае набора 5 она выведет успешный результат(что невозможно в условиях простой работы)
Скрины и код проверки ниже:
Как вы видите, для того, чтобы программа заработала понадобиться изменить переменную score входящую в функцию проверки моей программы.
Написание программы JMP:
//Ремарка: весь описанный софт собран в x86 компоновке
Для того, чтобы мы могли работать с программой, нужно взаимодействовать с ее главным потоком, записывать обращения в ее адреса и пересылать в собственные. Для этого нужно создать в процессе программы свой собственный поток, сделать это можно через библиотеку динамической компоновки DLL, которая в случае атаки в процесс программы создаст новый поток.
Для этого этого создаем в нашей IDE новые проект типа dll и сразу в главной win api функции, которая принимает аргумент статуса процесса подключения, если ее сообщение равно DLL_PROCESS_ATTACH, значит dll вошла в область работы с программой, тогда мы и создадим новый поток MainThread, в котором и бдуем проводить манипуляции:
Теперь напишем функцию с помощью которой будет проводиться взаимодействие с главным потоком программы, куда мы подключим нашу dll.
Теперь более конкретно разберем задачу нашей программы. Закидываем программу в интерактивный дизассемблер, в моем случае это IDA, и ищем нашу функцию проверки score среди ассемблерных команд, c помощью комментариев от IDA это не составит проблем:
Итак, мы видим 2 сообщения о неуспешном и успешном прохождении игры, а между ними ассемблерную инструкцию cmp, cmp означает сравнение двух регистров, в нашем случае сравнивается некая переменная esi и число 5, нетрудно догадаться что это и есть наша игровая проверка.
Теперь посмотрим на адрес 00424643, там в нашу переменную esi помещается некая другая внешняя переменная dword, получается эта и есть искомая входная переменная score в функцию и наша задача заменить ее на число > или = 5.
Как мы это сделаем? Познакомимся с ассемблерной командой JMP, JMP – это безусловный переход по указанному адресу, по адресу 00424643 мы поставим JMP, а адрес укажем на функцию внутри нашей dll, которую мы подключим в главный поток, внутри dll произойдет присвоению esi числа больше 5 и мы снова вернемся на адрес главного потока. Операцию сделаем на ассемблерном коде, благо c++ позволяет.
Итак, вот функция, который реализует это:
Разберем ее код. Входящие значения – адрес внутри функции на который будет ставиться JMP, ну я его уже писал – 0x424643, второе значение – адрес функции внутри dll на которую будет идти переход, и третье – количество байт.
Конкретно расскажу про третий входной аргумент. Ассемблерная команда считается полной и независимой если ее размер выше или равен 5 байтам, если ставить jmp на меньший размер, то это просто приведет к ошибкам ассемблера. Посмотрим снова на ассемблерный код, байтового размера команды mov нам не хватит, чтобы поставить jmp, поэтому придется захватить еще одну команду – push 0, их общий байтовый размер составит: 0x424649 -0x424643 = 6 байт, этого сполна хватит, даже придется 1 байт нопить.
Итак, 9 строка в коде это и есть проверка условия на необходимое кол-во байт,
В 14 мы устанавливаем на блок памяти флаг PAGE_EXECUTE_READWRITE, чтобы мы могли изменять эти байты, сохраняя предыдущий флаг блоков в curprotection и в 24 строке , после изменения байтов, возвращаем старые флаги на блок памяти.
16 строка – ноппинг байтов, мы изменяем каждый из 6 байтов на 0x90, это на ассемблерном коде означает действие nope, то есть в этом месте никаких действий происходить не будет, это не обязательное действие, но нужное для того, чтобы случайно не изменить байты на то, что ненадо.
18 строка – вычисление конечного адреса на который будет происходить JMP, относительного смещения между адресом того, куда ставиться JMP и адресом функции внутри dll.
Теперь более подробно о ассемблерных действиях, первый байт всегда означает саму команду :jmp, add, push, xor и т.д. А остальные это аргументы, с которыми происходит взаимодействие по команде ассемблера (тело команды).
Поэтому в 20 строке я по ссылке на адрес типа байта изменяю его значение на 0xE9, E9 – означает ассемблерную инструкцию JMP, дальше прибавляю к адресу в 21 строке 1 байт, смещаясь к телу команды и по ссылке адреса на значения DWORD устанавливаю адрес на функцию на которую происходит JMP(та самая внутри нашей dll).
На этом функцию установки JMP завершена, осталось лишь написать нашу новую функцию помещения в регистр esi числа больше 5:
Создадим функцию с препроцессорный регистром __declspec, и включим атрибут naked, что даст возможность написать полную функцию на ассемблере, дальше через __asm переключаемся на ассемблер и записываем в регистр esi условную 6, так же незабываем указать push 0 , так как из этой команды и сформировались 6 байтов необходимые для JMP на адрес.
Следующая команда это JMP[jump2], JMP ,как вы уже знаете, это безусловный прыжок на указанный адрес, а jump2 это адрес команды следующей после тех, которые мы заменили на JMP, то есть мы изменяем esi и возвращаемся на ту команду, которая следовала бы, если бы не ставили JMP на оригинальный адрес.
Теперь в созданном раннее потоке от dll, вызываем функцию jmp:
Adr = адрес куда ставим jmp;
Bytel = длина байтов;
Jump2 = команда следующая после места куда мы ставим jmp;
Незабываем все запустить в бесконечный цикл while, чтобы поток не завершался после установки jmp.
На этом можно сказать, что написание окнчено!
Самое время запустить программу, подключить в нее dll и посмотреть на результат:
Успех!
Итог:
В этом проекте я показал способы работы с ассемблером, на котором работают скомпилированные программы, способы взаимодействия и прочее.
На c++ я создал программу и написал еще одну, благодаря, которой обошел невозможный код проверки и победил. Работайте и подстраивайте программу под свои нужды и теперь уже неважно в каком она ввиде!
Ресурсы:
guidehacking.com
yougame.biz
brokencore.club
youtube.com