Сегодня речь пойдет о играх, а точнее, об их взломе.
Может возникнуть вопрос, причем тут игры, ведь блог в основном относится к теме системного программирования / реверсинга / декомпиляции?
Да вот как-то после прочтения заметки в блоге HEX'а про создание игр (http://rebl0g.wordpress.com/2013/12/15/%D0%BC%D0%B5%D1%87%D1%82%D1%8B-%D0%B4%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B0/#more-217) навеяло. Но в данной заметке будет рассмотрено прямо противоположное созданию действие - взлом.
Что в основном ломают в играх? Определенно, игровые ресурсы.
Как ломают тоже в принципе давно известно: ищут / меняют значения памяти в программах типа gamehack.
Но, конечно, gamehack я описывать в своем блоге не собираюсь, вместо этого я опишу альтернативный вариант поиска нужных значений в памяти.
Тут как раз и пересекается тематика блога и игр, ибо речь пойдет про PIN.
Итак, нам нужно найти и изменить определенные игровые ресурсы, скажем, деньги. Для этого нужно как-то найти, где в памяти, а еще лучше, прямо в бинаре лежат нужные нам данные. Зачем нам бинарь, ведь достаточно изменить значения в памяти? К примеру, мы хотим еще и пореверсить код игры, или пропатчить бинарь игры, для этого желательно иметь точку, где происходит работа с игровыми ресурсами.
Как будем искать нужные данные? Подход в чем-то схож с подходом gamehack.
Игровые деньги могут уменьшаться, скажем при покупке чего-либо, или увеличиваться, с течением времени. Логично предположить, что в конечном итоге это приведет нас к двум инструкциям sub / add.
Далее, все что нужно, это написать PIN модуль, который смотрит текущие инструкции на предмет sub /add и выводит все инструкции в лог.
После чего, запускаем игру под PIN до изменения наблюдаемых величин( игровой валюты к примеру ) и после изменения.
Наблюдаемую величину можно захардкодить в PIN модуле, чтобы отсеять лишнюю информацию из лога.
В итоге, получаем нечто вроде:
VOID SubReg( ADDRINT ea, string *disasm, ADDRINT reg1Value, ADDRINT reg2Value )
{
...
if ( reg1Value == VALUE_TO_WATCH )
cout << hex << ea << " " << disasm << dec << " reg1Value = " << reg1Value << " reg2Value = " << reg2Value << endl;
...
}
VOID Instruction( INS ins, VOID *v )
{
ADDRINT ea = INS_Address( ins );
if ( ea < gMainExe.GetModuleStartAddress() || ea > gMainExe.GetModuleEndAddress() )
return;
if (INS_Opcode(ins) == XED_ICLASS_SUB && INS_OperandReg(ins, 0) != REG_ESP && !INS_OperandIsImmediate(ins, 1) && INS_OperandIsReg(ins, 0))
//if (INS_Opcode(ins) == XED_ICLASS_ADD && INS_OperandReg(ins, 0) != REG_ESP && !INS_OperandIsImmediate(ins, 1) && INS_OperandIsReg( ins, 0))
{
if ( INS_OperandIsReg( ins, 1 ) )
{
INS_InsertCall( ins, IPOINT_BEFORE, (AFUNPTR)SubReg,
IARG_ADDRINT, ea,
IARG_PTR, new string( INS_Disassemble(ins) ),
IARG_REG_VALUE, INS_OperandReg( ins, 0 ),
IARG_REG_VALUE, INS_OperandReg( ins, 1 ),
IARG_END );
}
}
}
Далее, так как на моем компе видеокарта встроенная и игр не установлено, пришлось покопаться в древних архивах, было найдено три стареньких игры, получены логи.
Пример лога(игра WarGames):
487696 sub edx, ecx reg1Value = 3f7a reg2Value = 9c4
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
В выделенной строке лог после покупки юнита за 2500(9c4h) игровых денег.
То есть из общего баланса (3f7a) вычитается стоимость юнита. Таким образом, мы вышли на код работы с игровой валютой, дальше можно патчить в памяти через тот же PIN, или на диске или анализировать/реверсить в IDA.
Код в IDA:
.text:00487687 xor ecx, ecx
.text:00487689 mov cx, word_7470B2[edx]
.text:00487690 mov edx, dword_73B65C[eax]
.text:00487696 sub edx, ecx
.text:00487698 mov eax, [ebp+var_8]
.text:0048769B xor ecx, ecx
.text:0048769D mov cl, [eax+5]
Такой подход оправдал себя и на двух других играх, для Orion2 искалась не sub инструкция, а add, так как доход игровой валюты там меняется с течением времени.
После всех этих манипуляций мое любопытство по поводу жизнеспособности метода с PIN было удовлетворено, и игры благополучно были отправлены обратно в архив.
Может возникнуть вопрос, причем тут игры, ведь блог в основном относится к теме системного программирования / реверсинга / декомпиляции?
Да вот как-то после прочтения заметки в блоге HEX'а про создание игр (http://rebl0g.wordpress.com/2013/12/15/%D0%BC%D0%B5%D1%87%D1%82%D1%8B-%D0%B4%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B0/#more-217) навеяло. Но в данной заметке будет рассмотрено прямо противоположное созданию действие - взлом.
Что в основном ломают в играх? Определенно, игровые ресурсы.
Как ломают тоже в принципе давно известно: ищут / меняют значения памяти в программах типа gamehack.
Но, конечно, gamehack я описывать в своем блоге не собираюсь, вместо этого я опишу альтернативный вариант поиска нужных значений в памяти.
Тут как раз и пересекается тематика блога и игр, ибо речь пойдет про PIN.
Итак, нам нужно найти и изменить определенные игровые ресурсы, скажем, деньги. Для этого нужно как-то найти, где в памяти, а еще лучше, прямо в бинаре лежат нужные нам данные. Зачем нам бинарь, ведь достаточно изменить значения в памяти? К примеру, мы хотим еще и пореверсить код игры, или пропатчить бинарь игры, для этого желательно иметь точку, где происходит работа с игровыми ресурсами.
Как будем искать нужные данные? Подход в чем-то схож с подходом gamehack.
Игровые деньги могут уменьшаться, скажем при покупке чего-либо, или увеличиваться, с течением времени. Логично предположить, что в конечном итоге это приведет нас к двум инструкциям sub / add.
Далее, все что нужно, это написать PIN модуль, который смотрит текущие инструкции на предмет sub /add и выводит все инструкции в лог.
После чего, запускаем игру под PIN до изменения наблюдаемых величин( игровой валюты к примеру ) и после изменения.
Наблюдаемую величину можно захардкодить в PIN модуле, чтобы отсеять лишнюю информацию из лога.
В итоге, получаем нечто вроде:
VOID SubReg( ADDRINT ea, string *disasm, ADDRINT reg1Value, ADDRINT reg2Value )
{
...
if ( reg1Value == VALUE_TO_WATCH )
cout << hex << ea << " " << disasm << dec << " reg1Value = " << reg1Value << " reg2Value = " << reg2Value << endl;
...
}
VOID Instruction( INS ins, VOID *v )
{
ADDRINT ea = INS_Address( ins );
if ( ea < gMainExe.GetModuleStartAddress() || ea > gMainExe.GetModuleEndAddress() )
return;
if (INS_Opcode(ins) == XED_ICLASS_SUB && INS_OperandReg(ins, 0) != REG_ESP && !INS_OperandIsImmediate(ins, 1) && INS_OperandIsReg(ins, 0))
//if (INS_Opcode(ins) == XED_ICLASS_ADD && INS_OperandReg(ins, 0) != REG_ESP && !INS_OperandIsImmediate(ins, 1) && INS_OperandIsReg( ins, 0))
{
if ( INS_OperandIsReg( ins, 1 ) )
{
INS_InsertCall( ins, IPOINT_BEFORE, (AFUNPTR)SubReg,
IARG_ADDRINT, ea,
IARG_PTR, new string( INS_Disassemble(ins) ),
IARG_REG_VALUE, INS_OperandReg( ins, 0 ),
IARG_REG_VALUE, INS_OperandReg( ins, 1 ),
IARG_END );
}
}
}
Далее, так как на моем компе видеокарта встроенная и игр не установлено, пришлось покопаться в древних архивах, было найдено три стареньких игры, получены логи.
Пример лога(игра WarGames):
487696 sub edx, ecx reg1Value = 3f7a reg2Value = 9c4
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
В выделенной строке лог после покупки юнита за 2500(9c4h) игровых денег.
То есть из общего баланса (3f7a) вычитается стоимость юнита. Таким образом, мы вышли на код работы с игровой валютой, дальше можно патчить в памяти через тот же PIN, или на диске или анализировать/реверсить в IDA.
Код в IDA:
.text:00487687 xor ecx, ecx
.text:00487689 mov cx, word_7470B2[edx]
.text:00487690 mov edx, dword_73B65C[eax]
.text:00487696 sub edx, ecx
.text:00487698 mov eax, [ebp+var_8]
.text:0048769B xor ecx, ecx
.text:0048769D mov cl, [eax+5]
Такой подход оправдал себя и на двух других играх, для Orion2 искалась не sub инструкция, а add, так как доход игровой валюты там меняется с течением времени.
После всех этих манипуляций мое любопытство по поводу жизнеспособности метода с PIN было удовлетворено, и игры благополучно были отправлены обратно в архив.
Комментариев нет:
Отправить комментарий