Определенно я не являюсь фанатом казуальных игр и "Raging Pigs Free" была скачана именно с целью попробовать изменить ход игрового процесса. Получилось сделать небольшой патч делающий *свинью* бессмертной:
Набор использованных инструментов следующий:
Получим class-dump для i386 вместе с подписями адресов реализации методов классов (флаг -A)
Этап 2. Внедрение
Для того, чтобы иметь возможность подсмотреть за происходящим в приложении во время его работы логично использовать gdb.
Задачей теперь является привести игровой процесс к моменту, когда потенциально будет вызван необходимый нам метод - подставим свинью под птицу. Задача не сложная и прямо в момент, когда поросенок должен был бы умереть приложение застынет и в gdb мы увидим:
Этой командой выведено 10 инструкций начиная с места на которое указывает регистр $pc, т.е. с текущей команды ожидающей выполнения. Что интересного можно тут узнать, к примеру, что за параметр был передан в метод. Содержимое стека будет следующим:
Этап 3. Закрепляемся
На данном этапе уже ясно, как изменить игровой процесс, осталось закрепиться. Чтобы закрепиться нужно, как-то подправить код программы, исходников конечно же нет, править нужно в бинарном виде и нужно научиться компилировать отдельные asm команды в их бинарное представление. На данном этапе полезным окажется "as", моя вспомогательная утилита выглядит следующим образом:
Использование выглядит следующим образом:
При помощи otx получим дизассемблированный и в хорошем виде код функции killPig:
"Exception Type: EXC_CRASH (Code Signature Invalid)" - приложение из AppStore, после codesign, а я так нагло вклиниваюсь, естественно надо все переподписать.
Набор использованных инструментов следующий:
- otx - "object tool extended", использует otool для дизассемблированния Mach-O приложений и серьезно улучшает вывод последнего, добавляя машинные коды для каждой инструкции, более подробную информацию по методам и т.д.
- class-dump - консольное приложение позволяющее вытащить Obj-C runtime информацию из Mach-O файлов, генерирует вывод в виде легкочитаемых объявлений классов Obj-C.
- gdb - GNU debugger
- 0xED - Hex редактор
- as - Mac OS X Mach-O GNU-based assemblers
- codesign - Create and manipulate code signatures
Этап 1. Точка входа
У меня не было заранее определенных целей, КАК повлиять на игру, анализ начался с просмотра содержимого вывода class-dump.
Приложение представляет из себя FAT binary с поддержкой двух архитектур
$ lipo -info Raging\ Pigs\ Freei386 asm на данный момент мне несколько ближе и понятнее, поэтому все дальнейшие манипуляции проводил с i386 вариантом приложения.
Architectures in the fat file: Raging Pigs Free are: x86_64 i386
Получим class-dump для i386 вместе с подписями адресов реализации методов классов (флаг -A)
$ class-dump --arch i386 -A Raging\ Pigs\ FreeПо префиксу множества классов: CCBlendProtocol, CCKeyboardEventDelegate, CCAction и т.д. можно легко догадаться, что приложение построено на Cocos2D движке, во всяком случае на основе одной из его вариаций. Мое внимание привлек класс Pig, а в частности его метод
- (void)killPig:(int)arg1; // IMP=0x00092e6aО назначении можно легко догадаться по названию, неплохое место, для того, чтобы начать попытки.
Этап 2. Внедрение
Для того, чтобы иметь возможность подсмотреть за происходящим в приложении во время его работы логично использовать gdb.
$ gdb -arch i386 Raging\ Pigs\ FreeПервой командой мы запускаем gdb, передаем ему приложение и напутствуем о том, что нас интересует архитектура i386 и именно этот вариант приложения мы хотим отлаживать. Далее выставляем breakpoint по уже известному нам адресу метода killPig: и запускаем приложение из gdb.
(gdb) b *0x00092e6a
(gdb) run
Задачей теперь является привести игровой процесс к моменту, когда потенциально будет вызван необходимый нам метод - подставим свинью под птицу. Задача не сложная и прямо в момент, когда поросенок должен был бы умереть приложение застынет и в gdb мы увидим:
Breakpoint 1, 0x00092e6a in ?? ()В стеке ничего особо читаемого нет:
(gdb) btС помощью gdb можно легко посмотреть код текущего метода:
#0 0x00092e6a in ?? ()
#1 0x00086b5f in ?? ()
#2 0x000869ed in ?? ()
#3 0x00092dcf in ?? ()
#4 0x00085c09 in ?? ()
#5 0x00092ab0 in ?? ()
#6 0x000899a9 in ?? ()
#7 0x0004411b in ?? ()
#8 0x00046623 in ?? ()
#9 0x0005ccdc in ?? ()
#10 0x0005c96a in ?? ()
#11 0x0005cae1 in ?? ()
#12 0x93f06a88 in CVDisplayLink::performIO ()
#13 0x93f056bc in CVDisplayLink::runIOThread ()
#14 0x93f052ea in startIOThread ()
#15 0x9004a259 in _pthread_start ()
#16 0x9004a0de in thread_start ()
(gdb) x/10i $pc
0x92e6a: push %ebp
0x92e6b: mov %esp,%ebp
0x92e6d: push %ebx
0x92e6e: push %edi
0x92e6f: push %esi
0x92e70: sub $0xc,%esp
0x92e73: call 0x92e78
0x92e78: pop %esi
0x92e79: mov 0x10(%ebp),%edi
0x92e7c: mov 0x8(%ebp),%ebx
Этой командой выведено 10 инструкций начиная с места на которое указывает регистр $pc, т.е. с текущей команды ожидающей выполнения. Что интересного можно тут узнать, к примеру, что за параметр был передан в метод. Содержимое стека будет следующим:
- На вершине стека лежит адрес возврата, который оказался там благодаря команде calll - 0x00086b5f - результаты команды bt, с нами согласны
(gdb) x/x $esp
0xb020fb3c: 0x00086b5f
- Дальше, через 4 байта лежит адрес объекта у которого вызывается метод, он является первым параметром метода dyld_stub_objc_msgSend, который используется в Obj-C для посылки сообщений, т.е. для вызова методов.
(gdb) x/x $esp+4
0xb020fb40: 0x00294ac0
- Вторым параметром dyld_stub_objc_msgSend является указатель на строку метода, который будет вызван
(gdb) x/x $esp+8
0xb020fb44: 0x000acc2e
(gdb) x/s 0x000acc2e
0xacc2e: "killPig:"
- Далее передаются все параметры с которыми вызывается метод, в данном случае параметр у нас один, типа int:
(gdb) x/d $esp+12Итого, метод killPig: был вызван с магическим параметром "2", что нам это дает совершенно не ясно, как оказалось это и не важно, следующей моей попыткой было просто сделать "ret" даже не заходя в тело функции...
0xb020fb48: 2
(gdb) retТаким образом код функции не выполнялся, а сразу произошел возврат выше по стеку и далее "c" , продолжилось выполнение приложения, это привело к потрясающим результатам - свин выжил!
Make selected stack frame return now? (y or n)
(gdb) c
Этап 3. Закрепляемся
На данном этапе уже ясно, как изменить игровой процесс, осталось закрепиться. Чтобы закрепиться нужно, как-то подправить код программы, исходников конечно же нет, править нужно в бинарном виде и нужно научиться компилировать отдельные asm команды в их бинарное представление. На данном этапе полезным окажется "as", моя вспомогательная утилита выглядит следующим образом:
echo $1 >> temp.asmПарметр пользователя это asm команда в синтаксисе at&t, следом к ней прикрепляется "nop", чтобы знать, где заканчиваются полезные данные, получившийся бинарный файл читается в HEX с отбрасыванием первых 9 строк, т.к. они являются стандартными и после них идет непосредственно наша команда.
echo "nop" >> temp.asm
as -arch i386 -o temp.bin temp.asm
rm temp.asm
od -N 256 -t x1 temp.bin | tail -n+10
rm temp.bin
Использование выглядит следующим образом:
$ ./opcode "movl %esp,%ebp"Таким образом команда "movl %sp,%ebp" превращается в 89e5
0000220 00 00 00 00 00 00 00 00 89 e5 90 00
0000234
При помощи otx получим дизассемблированный и в хорошем виде код функции killPig:
-(void)[Pig killPig:]Это самое начало. Моей первой и последней попыткой было заменить первый байт кода на "ret", т.е. заставить функцию сразу вернуться без произведения каких-либо действий.
+0 00092e6a 55 pushl %ebp
+1 00092e6b 89e5 movl %esp,%ebp
+3 00092e6d 53 pushl %ebx
+4 00092e6e 57 pushl %edi
+5 00092e6f 56 pushl %esi
+6 00092e70 83ec0c subl $0x0c,%esp
+9 00092e73 e800000000 calll 0x00092e78
+14 00092e78 5e popl %esi
+15 00092e79 8b7d10 movl 0x10(%ebp),%edi
+18 00092e7c 8b5d08 movl 0x08(%ebp),%ebx
+21 00092e7f 89bbd4010000 movl %edi,0x000001d4(%ebx) (int)deathtype
+27 00092e85 c683c801000001 movb $0x01,0x000001c8(%ebx) (BOOL)dying
+34 00092e8c 8b83cc010000 movl 0x000001cc(%ebx),%eax (GameScreenLayer)gameScreenLayer
+40 00092e92 8b8ec8770200 movl 0x000277c8(%esi),%ecx
+46 00092e98 894c2404 movl %ecx,0x04(%esp)
$ ./opcode "ret"Т.е. код команды "ret" также занимает 1 байт и это "c3", на самом деле подсмотреть можно в самом выводе otx:
0000220 00 00 00 00 00 00 00 00 c3 90 00 00
0000234
+185 00092f23 c3 retПатчинг производил с помощью 0xED. Адрес 0x00092e6a не имеет ничего общего с местоположением кода в файле и является runtime информацией. На данный момент самым простым методом определения нужного места в файле явлется поиск. По выводу otx можно видеть, что интересующее нас место выглядит как 5589e553575683ec0ce8000000005e8b7d10, это дает нам уникальный результат HEX поиска по файлу (адрес 1A2E6A).
55 -> c3 # pushl %ebp -> retПопытка игры после такого исправления закончится crash'ем
"Exception Type: EXC_CRASH (Code Signature Invalid)" - приложение из AppStore, после codesign, а я так нагло вклиниваюсь, естественно надо все переподписать.
$ sudo codesign -f -s "My Home Cert" Raging\ Pigs\ FreeТаким образом осуществляется переподписывание кода, новый становится легальным. Последним важным моментом является необходимость отметить приложение для запуска в 32bit режиме.
Результаты проделанной работы видны на видео, свинья стала бессмертной!
Комментариев нет:
Отправка комментария