Каков наилучший способ остановить людей, взламывающих таблицу рекордов на основе PHP во Flash-игре? - PullRequest
212 голосов
/ 16 сентября 2008

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

Что мне действительно нужно, так это самое сильное шифрование, возможное во Flash / PHP, и способ запретить людям вызывать страницу PHP иначе, чем через мой файл Flash. В прошлом я пробовал несколько простых методов: сделать несколько вызовов для одного счета, завершить последовательность контрольных сумм / фибоначчи и т. Д., А также запутать SWF с помощью Amayeta SWF Encrypt, но в конечном итоге все они были взломаны.

Благодаря ответам StackOverflow я нашел больше информации от Adobe - http://www.adobe.com/devnet/flashplayer/articles/secure_swf_apps_12.html и https://github.com/mikechambers/as3corelib - которую, я думаю, я могу использовать для шифрования. Не уверен, что это поможет мне освоить CheatEngine.

Мне нужно знать лучшие решения для AS2 и AS3, если они разные.

Основными проблемами, по-видимому, являются такие вещи, как заголовки TamperData и LiveHTTP, но я понимаю, что есть и более продвинутые хакерские инструменты - такие как CheatEngine (спасибо Mark Webster)

Ответы [ 18 ]

414 голосов
/ 16 сентября 2008

Это классическая проблема с интернет-играми и конкурсами. Ваш Flash-код работает с пользователями, чтобы определить счет для игры. Но пользователям не доверяют, и код Flash выполняется на компьютере пользователя. Ты SOL. Вы ничего не можете сделать, чтобы не дать злоумышленнику подделать высокие баллы:

  • Проанализировать Flash даже проще, чем вы думаете, поскольку байт-коды хорошо документированы и описывают язык высокого уровня (Actionscript) - когда вы публикуете игру Flash, вы публикуете ваш исходный код, знаете ли вы это или нет.

  • Злоумышленники управляют оперативной памятью интерпретатора Flash, так что любой, кто знает, как использовать программируемый отладчик, может в любое время изменить любую переменную (включая текущий счет) или саму программу.

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

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

hex-encoding( AES(secret-key-stored-only-on-server, timestamp, user-id, random-number))

(Вы также можете использовать cookie сеанса для того же эффекта).

Код игры возвращает этот токен на сервер с сохранением рекордов. Но злоумышленник все равно может просто запустить игру снова, получить токен, а затем сразу же вставить этот токен в переигранное сохранение.

Итак, далее вы отправляете не только токен или куки-файл сеанса, но также и ключ сеанса с высоким шифрованием. Это будет 128-битный ключ AES, сам зашифрованный ключом, жестко запрограммированным во Flash-игре:

hex-encoding( AES(key-hardcoded-in-flash-game, random-128-bit-key))

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

hex-encoding( AES(random-128-bit-key-from-above, high-score, SHA1(high-score)))

PHP-код на сервере проверяет токен, чтобы убедиться, что запрос поступил от действительного игрового экземпляра, затем расшифровывает зашифрованный высокий балл, проверяя, чтобы высокий балл совпадал с SHA1 высокого балла (если вы пропустите этот шаг, расшифровка просто приведет к случайным, вероятно, очень высоким, высоким баллам).

Так что теперь злоумышленник декомпилирует ваш Flash-код и быстро находит код AES, который торчит, как больной большой палец, хотя даже если бы этого не произошло, он был бы обнаружен через 15 минут с помощью поиска в памяти и трассировщика ( «Я знаю, что моя оценка для этой игры составляет 666, поэтому давайте найдем 666 в памяти, а затем перехватим любую операцию, которая касается этого значения - о, смотрите, код шифрования с высокой оценкой!»). С помощью ключа сеанса злоумышленнику даже не нужно запускать код Flash; она берет маркер запуска игры и ключ сеанса и может отправить произвольный рекорд.

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

  • Скремблирование клавиш AES с операциями XOR

  • Замена байтовых массивов клавиш функциями, вычисляющими ключ

  • Разброс фальшивых ключей и высокий рейтинг сообщений по всему двоичному файлу.

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

Вот некоторые вещи, которые действительно могут снизить количество мошеннических действий:

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

  • Отклонить высокие оценки от игровых сессий, которые длятся меньше, чем самые короткие реальные игры, когда-либо играемые (для более изощренного подхода, попробуйте "изолировать" высокие оценки для игровых сессий, которые длятся менее чем на 2 стандартных отклонения ниже средней продолжительности игры) ). Убедитесь, что вы отслеживаете продолжительность игры на сервере.

  • Отклонять или помещать в карантин рекорды по логинам, которые играли в игру только один или два раза, так что злоумышленники должны составлять «бумажный след» разумного вида игры для каждого создаваемого ими логина.

  • Оценка "Heartbeat" во время игры, так что ваш сервер видит рост очков в течение одной игры. Отклонить высокие баллы, которые не соответствуют разумным кривым баллов (например, прыжки от 0 до 999999).

  • Состояние игры «Снимок» во время игры (например, количество боеприпасов, положение на уровне и т. Д.), Которое вы позднее можете согласовать с записанными промежуточными результатами. Вам даже не нужно иметь возможность обнаруживать аномалии в этих данных, чтобы начать с них; вам просто нужно собрать его, а затем вы можете вернуться и проанализировать его, если все выглядит подозрительно.

  • Отключите учетную запись любого пользователя, который не прошел ни одну из ваших проверок безопасности (например, когда-либо отправлял зашифрованный высокий балл, который не прошел проверку).

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

25 голосов
/ 16 сентября 2008

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

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

18 голосов
/ 16 сентября 2008

Простой способ сделать это - предоставить криптографический хеш-код вашего рекорда наряду с самим счетом. Например, при публикации результатов через HTTP GET: http://example.com/highscores.php?score=500&checksum=0a16df3dc0301a36a34f9065c3ff8095

При расчете этой контрольной суммы следует использовать общий секрет; этот секрет никогда не должен передаваться по сети, но должен быть жестко закодирован как в бэкэнде PHP, так и во флеш-интерфейсе. Приведенная выше контрольная сумма была создана путем добавления строки " secret " к значению " 500 " и запуска ее через md5sum.

Хотя эта система не позволяет пользователю публиковать произвольные оценки, она не предотвращает «повторную атаку», когда пользователь повторно публикует ранее рассчитанную комбинацию оценки и хэша. В приведенном выше примере оценка 500 всегда будет приводить к одной и той же хэш-строке. Часть этого риска можно уменьшить, включив дополнительную информацию (например, имя пользователя, метку времени или IP-адрес) в строку, которую необходимо хэшировать. Хотя это не помешает воспроизведению данных, оно гарантирует, что набор данных действителен только для одного пользователя одновременно.

Чтобы предотвратить любые повторные атаки, необходимо создать систему типа «вызов-ответ», такую ​​как:

  1. Флэш-игра («клиент») выполняет HTTP GET http://example.com/highscores.php без параметров. Эта страница возвращает два значения: случайно сгенерированное значение salt и криптографический хэш этого значения соли в сочетании с общим секретом. Это солт-значение должно храниться в локальной базе данных ожидающих запросов, и с ним должна быть связана временная метка, чтобы он мог «истечь», возможно, через одну минуту.
  2. Флэш-игра объединяет значение соли с общим секретом и вычисляет хеш, чтобы убедиться, что он совпадает с хешем, предоставленным сервером. Этот шаг необходим для предотвращения подмены значений соли пользователями, поскольку он проверяет, что значение соли действительно было сгенерировано сервером.
  3. Флэш-игра объединяет значение соли с общим секретным значением, значением высокого балла и любой другой соответствующей информацией (псевдоним, ip, метка времени) и вычисляет хэш. Затем он отправляет эту информацию обратно в бэкэнд PHP через HTTP GET или POST, а также солт-значение, высокий балл и другую информацию.
  4. Сервер объединяет информацию, полученную так же, как и на клиенте, и вычисляет хеш, чтобы убедиться, что он совпадает с хешем, предоставленным клиентом. Затем он также проверяет, что значение соли все еще является действительным, как указано в списке ожидающих запросов. Если оба эти условия выполняются, он записывает рекорд в таблицу рекордов и возвращает клиенту подписанное сообщение об успехе. Он также удаляет значение соли из списка ожидающих запросов.

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

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

11 голосов
/ 17 сентября 2008

Мне нравится то, что сказал tpqf, но вместо того, чтобы отключать учетную запись при обнаружении мошенничества, реализуйте honeypot, чтобы при каждом входе в систему они видели свои взломанные результаты и никогда не подозревали, что их пометили как тролля. Google для "phpBB MOD Troll", и вы увидите оригинальный подход.

4 голосов
/ 18 августа 2011

В принятом ответе tqbf упоминает, что вы можете просто выполнить поиск в памяти для переменной счета («Мой счет - 666, поэтому я ищу число 666 в памяти»).

Есть способ обойти это. У меня есть класс здесь: http://divillysausages.com/blog/safenumber_and_safeint

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

Кроме того, посмотрите видео с парнями из движка PushButton, рассказывающими о различных способах борьбы со взломом: http://zaa.tv/2010/12/the-art-of-hacking-flash-games/. Они были источником вдохновения для класса.

4 голосов
/ 01 августа 2013

Я сделал своего рода обходной путь ... У меня было задание, при котором баллы увеличивались (вы всегда получаете +1 балл). Во-первых, я начал считать со случайного числа (скажем, 14), и когда я отображал баллы, просто показывал баллы var минус 14. Это было так, если взломщики ищут, например, 20, они не найдут его (это будет 34 в памяти). Во-вторых, поскольку я знаю, какой должна быть следующая точка ... Я использовал криптографическую библиотеку Adobe, чтобы создать хэш следующей точки , равной . Когда мне нужно увеличить баллы, я проверяю, должен ли хеш инкрементных баллов быть равен хешу. Если взломщик изменил точки в памяти, хэши не равны. Я провожу некоторые проверки на стороне сервера, и когда я получаю разные очки от игры и от PHP, я знаю, что это связано с мошенничеством. Вот фрагмент моего кода (я использую класс Adobe Crypto libraty MD5 и случайную криптографическую соль. CallPhp () - моя проверка на стороне сервера)

private function addPoint(event:Event = null):void{
            trace("expectedHash: " + expectedHash + "  || new hash: " + MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt) );
            if(expectedHash == MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt)){
                SCORES +=POINT;
                callPhp();
                expectedHash = MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt);
            } else {
                //trace("cheat engine usage");
            }
        }

Используя эту технику + обфускация SWF, я смог остановить крекеры. Кроме того, когда я отправляю результаты на сервер, я использую свою маленькую функцию шифрования / дешифрования. Примерно так (код на стороне сервера не включен, но вы можете увидеть алгоритм и написать его на PHP):

package  {

    import bassta.utils.Hash;

    public class ScoresEncoder {

        private static var ranChars:Array;
        private static var charsTable:Hash;

        public function ScoresEncoder() {

        }

        public static function init():void{

            ranChars = String("qwertyuiopasdfghjklzxcvbnm").split("")

            charsTable = new Hash({
                "0": "x",
                "1": "f",
                "2": "q",
                "3": "z",
                "4": "a",
                "5": "o",
                "6": "n",
                "7": "p",
                "8": "w",
                "9": "y"

            });

        }

        public static function encodeScore(_s:Number):String{

            var _fin:String = "";

            var scores:String = addLeadingZeros(_s);
            for(var i:uint = 0; i< scores.length; i++){
                //trace( scores.charAt(i) + " - > " + charsTable[ scores.charAt(i) ] );
                _fin += charsTable[ scores.charAt(i) ];
            }

            return _fin;

        }

        public static function decodeScore(_s:String):String{

            var _fin:String = "";

            var decoded:String = _s;

            for(var i:uint = 0; i< decoded.length; i++){
                //trace( decoded.charAt(i) + " - > "  + charsTable.getKey( decoded.charAt(i) ) );
                _fin += charsTable.getKey( decoded.charAt(i) );
            }

            return _fin;

        }

        public static function encodeScoreRand(_s:Number):String{
            var _fin:String = "";

            _fin += generateRandomChars(10) + encodeScore(_s) + generateRandomChars(3)

            return _fin;
        }

        public static function decodeScoreRand(_s:String):Number{

            var decodedString:String = _s;
            var decoded:Number;

            decodedString = decodedString.substring(10,13);         
            decodedString = decodeScore(decodedString);

            decoded = Number(decodedString);

            return decoded;
        }

        public static function generateRandomChars(_length:Number):String{

            var newRandChars:String = "";

            for(var i:uint = 0; i< _length; i++){
                newRandChars+= ranChars[ Math.ceil( Math.random()*ranChars.length-1 )];
            }

            return newRandChars;
        }

        private static function addLeadingZeros(_s:Number):String{

            var _fin:String;

            if(_s < 10 ){
                 _fin = "00" + _s.toString();
            }

            if(_s >= 10 && _s < 99 ) {
                 _fin = "0" + _s.toString();
            }

            if(_s >= 100 ) {
                _fin = _s.toString();
            }           

            return _fin;
        }


    }//end
}

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

Приветствия, Ico

3 голосов
/ 05 января 2009

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

3 голосов
/ 16 сентября 2008

Шифрование с использованием известного (частного) обратимого ключа было бы самым простым методом. Я не все в AS, поэтому я не уверен, какие существуют поставщики шифрования.

Но вы можете включить такие переменные, как продолжительность игры (снова зашифрованные) и количество кликов.

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

Редактировать: Возможно, стоит поторопиться и в некоторых сессиях PHP. Начните сеанс, когда они нажмут начать игру и (как говорится в комментарии к этому сообщению) записать время. Когда они отправляют счет, вы можете убедиться, что у них есть открытая игра, и они не отправляют счет слишком рано или слишком велико.

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

Ни одна из этих вещей не является непреодолимой, но это поможет иметь некоторую логику не во Flash, где люди смогут ее увидеть.

3 голосов
/ 23 мая 2012

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

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

2 голосов
/ 06 марта 2012

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

...