Как НАДЕЖНО защитить in-App Purchase от ломалок

    Совсем недавно я писал статью Как защитить in-App Purchase от ломалок . Прошло немного времени, а хакеры на месте не сидят. Тот метод защиты оказывается можно обойти, не очень сложно. Под катом метод, который намного надежнее.

    Перед чтением этого топика, рекомендую прочитать предыдущий Как защитить in-App Purchase от ломалок , так как это продолжение темы.

    В моем приложении после проверки receipt отправляется на мой сервер, и я там его анализирую, и сохраняю в логах. И я заметил, что JSON, который приходит есть стандартный, и какой-то модифицированный.
    Стандартный выглядит так:
    {
        "receipt": {
            "original_purchase_date_pst": "2012-06-08 04:13:04 America/Los_Angeles",
            "purchase_date_ms": "1339153984956",
            "original_transaction_id": "430000009214053",
            "original_purchase_date_ms": "1339153984956",
            "app_item_id": "12312312323",
            "transaction_id": "430000009214053",
            "quantity": "1",
            "bvrs": "1.0",
            "version_external_identifier": "7809437",
            "bid": "xx.yyyyyy.zzzzzzz",
            "product_id": "xx.yyyyyy.zzzzzz.uuuuuu",
            "purchase_date": "2012-06-08 11:13:04 Etc/GMT",
            "purchase_date_pst": "2012-06-08 04:13:04 America/Los_Angeles",
            "original_purchase_date": "2012-06-08 11:13:04 Etc/GMT",
            "item_id": "123123123"
        },
        "status": 0
    }
    


    В результате я заметил 3 типа попыток взлома:

    1. Самый частый вариант. Ломалка не подделывает ответ сервера Apple, в результате receipt выглядит так:
    {
        "status": 21002,
        "exception": "java.lang.ClassCastException"
    }
    

    Наш предыдущий метод с таким взломом справлялся, так как статус = 21002

    2. Совсем недавно появились новые способы взлома. Я не знаю с помощью каких утилит это делается, но они подделывают ответ серверов Apple и на второй запрос.
    JSON выглядит тогда так:
    { 
    "status":0
    }
    

    Такой взлом отслеживается просто. Можно проверить наличие некоторых переменных, например «product_id», и всё станет на свои места.

    3. Но не все так просто. Недавно появился еще один вариант подделаного JSON:
    {
        "status": 0,
        "receipt": {
            "product_id": "xx.yyyyyy.zzzzzzzz.uuuuuuu",
            "purchase_date": 1339152660.383128,
            "quantity": 1,
            "transaction_id": "xx.yyyyyy.zzzzzzzz.uuuuuuu"
        }
    }
    

    Если опять что-то проверять, тогда надо найти то, чего не сможет получить программа-ломалка из запроса. Просмотрев все данные я понял, что есть достаточно простой способ, который обойти будет очень непросто.
    Надо сделать проверку на «item_id» — это id, которое Apple присваевает каждому продукту, в том числе и in-App purchase. Его можно посмотреть в iTunesConnect, нажав на кнопку «Manage In-App Purchases».

    Тогда наш код будет выглядеть так:

    kFeature1 = "xx.yyyyyy.zzzzzzzz.uuuuuuu";
    kFeatureItemID1 = "123123123";
    kFeature2 = "xx.yyyyyy.zzzzzzzz.uuuuuuu";
    kFeatureItemID2 = "123123123";
    kFeature3 = "xx.yyyyyy.zzzzzzzz.uuuuuuu";
    kFeatureItemID3 = "123123123";
    
    - (BOOL)verifyReceipt:(NSData*)receiptData {
        NSString *urlsting = @"https://buy.itunes.apple.com/verifyReceipt";
        NSURL *url = [NSURL URLWithString:urlsting];
        NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
        NSString *st =  [receiptData base64EncodedString];
        NSString *json = [NSString stringWithFormat:@"{\"receipt-data\":\"%@\"}", st];
        
        [theRequest setHTTPBody:[json dataUsingEncoding:NSUTF8StringEncoding]];
    	[theRequest setHTTPMethod:@"POST"];		
    	[theRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];    
    	NSString *length = [NSString stringWithFormat:@"%d", [json length]];	
    	[theRequest setValue:length forHTTPHeaderField:@"Content-Length"];	
        NSHTTPURLResponse* urlResponse = nil;
    	NSError *error = [[NSError alloc] init];  
    	NSData *responseData = [NSURLConnection sendSynchronousRequest:theRequest
    												 returningResponse:&urlResponse 
    															 error:&error];  
    	NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
      NSDictionary *dic = [responseString JSONValue];
      NSInteger status = [[dic objectForKey:@"status"] intValue];
      NSDictionary *receiptDic = [dic objectForKey:@"receipt"];
    	BOOL retVal = NO;
    	if (status == 0 && receiptDic) {
        NSString *itemId = [receiptDic objectForKey:@"item_id"];    
        NSString *productId = [receiptDic objectForKey:@"product_id"];
    
        if (productId && ([productId isEqualToString:kFeature1]  || 
                          [productId isEqualToString:kFeature2]  ||
                          [productId isEqualToString:kFeature3]   )) {
            if (itemId && ( [itemId isEqualToString:kFeatureItemID1] ||
                            [itemId isEqualToString:kFeatureItemID2] ||
                            [itemId isEqualToString:kFeatureItemID3] )) {
            retVal = YES;
            }
            
        }
      }
      return retVal;
    }
    

    В чем плюс этого кода: ломалка не знает значения item_id, оно нигде не передается при запросе. Именно поэтому подделать такой receipt будет непросто. Хотя, наверное, возможно.

    И просьба, не говорите, что все можно сломать. Цель этой защиты — чтобы не было массовых взломов стандартными средствами.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 57
    • +6
      Оценивать нужно не как просто или сложно сломать. А сколько будет стоить взлом, какую прибыль принесет, тем кто ломает и какие убытки соотвественно. Анализа кто ломает и зачем отсутствует.
      • 0
        А нужен ли здесь этот анализ?
        Ломают пользователи, которые хотят на халяву получить какой-то функционал приложения — это ответ на кто, и зачем.
        • +3
          Мне сложно поверить, что пользователь ради 2$ будет сидеть с консолью, отлаживать, подделывать какие-то запросы тем более без успеха. Даже если контент стоит 1000$, человек не знакомый с программированием вряд ли будет ломать.
          • +4
            Извините, но вы наверное не в теме :-)
            Никто не сидит в консоли и не подделывает. Есть утилиты, которые ставятся из Сидии, называются iAppFree и iAppCracker (вроде бы так, если я не ошибаюсь) — они это делают на автомате. Они подменяют системную функцию, которая должна отправлять запрос на сервер Apple, своей функцией, которая возвращает сразу, что покупка произошла успешно. По сути не происходит никакого вмешательства в работу нашего приложения, ломается механизм защиты iOS. А этот код для того, чтобы если система взломана, защитить нашу прогу от ложного ответа системы. Вот где-то так. конечно это упрощенно.
            • –5
              Как это глупо и мерзко. Я иногда покупаю в приложениях только для того, чтобы разработчиков поощрить, если продукт бесплатный и действительно хорош.
              • –1
                И что же я делаю неправильно?
            • –1
              вы считаете что злобный хацкер не может выложить простую софтинку для подделки запроса? тогда куча пользователей приложения получат итемы на халяву.
              А если стоит 1000$ то человек наймет кого нибудь за 100$ чтобы ломануть.
              • –1
                а в том и нюанс, что проверяя те данные, которые я предлагаю в данной статье проверять — он не подделает, потому что эти данные так просто не доступны (не имея оригинального ответа от сервера Apple).
                А если говорить о взломе конретного приложения с помощью дебагера — понятно, что данный механизм не будет преградой.
            • +2
              Я потратил в appstore 250 долларов на покупку различных программ. Это за два года. Но перед этим я сломал свой iPad, поставил все сломанные программы и поразился, насколько ужасными могут быть приложения за бешеные деньги. Например, хваленный OmniGraffle за 40 долларов, его уделывает с лихвой iDesk за 5 долларов с таким же функционалом.
              Потом я снова вернул iPad в нормальное состояние, чтобы купить приложения, которые я наметил. А зачем мне покупать? Ради апдейтов. Если приложение не популярно, то в Сидии зачастую старые версии взломанных прог.
              • 0
                Я украл из магазина телевизор, чтобы оценить, а потом вернул и купил.

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

                У разработчиков есть возможность предоставить пользователю пробную версию своего приложения для оценки — в том числе благодаря In-App Purchases, о которых идёт речь в этой статье. Если разработчик не предоставил такой возможности, то как по мне, так уж лучше обойти его стороной, чем воровать «для оценки».
          • +1
            А почему нельзя просто подписать ответ сервера используя асимметричное шифрование и проверять его на клиенте?
            • –1
              Это Вы о чем? Ответ сервера — это ответ Apple. Этот ответ подписан. Ломалка эмулирует этот ответ. Как раз защита от эмуляции — цель этого куска кода.
              • +1
                Если ответ подписан, содержит уникальное значение (transaction_id) и привязан к Вашему продукту (product_id), то на свой сервер отправляете именно этот ответ с подписью, проверяете подпись и никаких проблем нет. Но, судя по всему, ответ не подписан и это уже нужно решать на уровне Apple.
                • +1
                  Я не случайно в начале статьи написал: «читайте предыдущую статью». Проверка подписи моим сервером — это самый простой вариант защиты, но он имеет один минус. Нужно чтобы мой сервер был таким же отказоустойчивым как и сервер Apple. потому, что если он вдруг в офлайне — то подтверждения покупки не будет. Или второй вариант — воротить на стороне клиента кучу дополнительных методов для востановления покупки, сохранение тикета и т.д.
                  Этим методом я немного упрощаю жизнь. Это защита от дурака, но она работает. Чтобы не быть голословным.
                  в предыдущей версии у меня «наломали» на $30k. это исходя из статистики на моих серверах.
                  в текущей версии количество уменьшилось на 90%. Думаю тот кусочек кода того стоит :-)
                  • 0
                    А насколько увеличились продажи после установки проверки? Просто считать что ты спас $30к неверно, это как копирасты считают что каждая скаченная копия это ровно одна потерянная продажа.
                    • +1
                      Увеличились. Не знаю наколько, но разница есть. Но и еще нюанс. Убытки есть еще в том, что те кто воруют пурчейзы используют ваши веб сервисы, на сервера возростают нагрузки, надо ставить что-то мощнее — увеличиваются расходы — а доходы в лучшем случае не меняются. Вот и минус.
                      • 0
                        Ну тут я согласен. Только не надо говорить, что оно freemium. Без оплаты основная функция приложения не работает (во всяком случае в описании не написано, что хотя бы один тест бесплатен, кроме как за отзыв). То есть вы абсолютно бесплатно предлагаете абсолютно бесполезное приложение и в данном контексте, конечно, любой слом это потерянные на инфраструктуре деньги.
                        Как странно изменилась модель freemium, раньше почему-то приложения что-то делали без оплаты, а оплата добавляла функциональность. В данном случае оплата требуется для базовой функции приложения.
                        Disclaimer: Я не использую iPhone и я не использую данную программу. Вся информация о программе получена из официального описания на сайте Эппла.
            • 0
              Спасибо.
              • 0
                А что если тип покупки — Consumable? Я так понимаю их таким способом проверить не получится.
                • 0
                  Это не играет роли. Consumable тоже имеет тикет.
                  и его можно проверить. в предыдущей статье я описывал что именно проверяется.
                • 0
                  Кстати, у вас там ошибка в строчке
                  NSError *error = [[NSError alloc] init];
                  должно быть
                  NSError *error = nil;
                  Или вообще
                  NSData *responseData = [NSURLConnection sendSynchronousRequest:theRequest
                                                                   returningResponse:&urlResponse 
                                                                               error:nil];  
                  

                  Все равно error ни где не проверяется
                  • –6
                    ну как сказать, «должно быть» или «может быть»? :-) У меня так работает.
                    • +1
                      Оно то работает, только память течет, а так все ок.
                      Советую почитать Introduction to Error Handling Programming Guide For Cocoa
                      • 0
                        Да и responseString ни где не релизится, или вы используете ARC? Тогда это стоит тоже упомянуть, т.к. многие копипастят особо не вчитываясь в код.
                        • 0
                          По поводу ARC — да используется.
                        • –8
                          Если честно, я не программист :-) на языке програмиистов — я проджект-менеджер.
                          А вообще психолог, юрист, тренер и т.д.
                          код я понимаю, а сам писать особо и не хочу учиться :-)
                          А здесь просто делюсь идеями и способами реализации. Этот способ я придумал лично.
                          • +4
                            о_О Какие взаимоисключающие параграфы.
                            • –2
                              Нда, наверное минусуют ПРОГРАММИСТЫ :-З
                              Спасибо Вселенной, что я не программер :-)
                              • +2
                                Я думаю, что вас минусуют потому, что вы, с одной стороны, делитесь советами по программированию (спасибо, кстати), а с другой, допускаете в этих советах ошибки, и при этом декларируете, что вы не программист и не хотите учиться программировать.

                                Ну это всё равно как прийти на кулинарный форум, написать рецепт, а в ответ на претензии о том, что от сочетания молока и селёдки живот болеть будет написать, что «я не повар и учиться не хочу».
                                • 0
                                  Да я прекрасно понимаю, за что меня минусуют :-)
                                  Но заметьте, думаю я не ошибусь, если скажу, что минусуют в основном те, кто особо ниче здесь не пишут. Я имею в виду топики, а не комменты.
                                  Ту строчку, о которой зашла речь раньше писал не я, а Программист (он себя таковым считает).
                                  А я в даном случае просто владелец прав на код, который решил этим поделиться :-) Заметьте, просто так. хотя за этот кодя я платил живые деньги.
                                  Если есть ошибка — это хорошо. на ней можно чему-то научиться :-)
                                  Agent_Smith, а Вам спасибо за разъяснения.
                                  З.Ы. несмотря на все это 3 из 6 моих приложений из топов аппстора не выходят. и входят в избранное AppStore Made in Russia.
                                  • 0
                                    Ну я и не спорю, что вы молодец ;) Просто, раз уж пишете за код, так приглашайте как раз программиста на комментарии отвечать, а иначе какой-то странный диалог получается.
                      • +1
                        Программисты создают гениальнейшие идеи защиты, но зачастую прокалываются в самых банальных местах. Недавно попросили сломать игрушку, чтобы было много денег и кредитов (валюты за $). В итоге значения их количества просто были записаны в файлик сохранения...:) (П.С. игра на android)
                        • 0
                          Как как их еще хранить локально?
                          • 0
                            Шифровать.
                            • 0
                              как минимум в бинарном виде, или использовать шифрование.
                              В большинстве же случаев, это обычный plist/xml/json/txt
                              • 0
                                Это по-умолчанию. Я просто подумал, что есть что-то более инновационное.
                          • 0
                            Зачем те два if, если можно было бы сделать NSSet с данными и через containsObject: просто проверить есть/нет.
                            • –1
                              поправьте, и код в студию :-)
                              • 0
                                Лучше уже сделать соответствующий форк MKStoreKit
                            • +1
                              	NSString *itemId = [receiptDic objectForKey:@"item_id"];    
                              	NSString *productId = [receiptDic objectForKey:@"product_id"];
                              	
                              	NSSet* productsIDs = [NSSet setWithObjects:@"id01", @"id02", @"id03", nil];
                              	NSSet* itemsIDs = [NSSet setWithObjects:@"id01", @"id02", @"id03", nil];
                              	
                              	if ([productsIDs containsObject:productId] && [itemsIDs containsObject:itemId]) {
                              		retVal = YES;
                              	}
                              


                              Как-то так, варианты размещения productsIDs и itemsIDs — на свой вкус =) думаю это как-то красивше будет
                              • 0
                                Спасибо! Согласен, что красивше :-) + в Карму :-)
                              • 0
                                Подождите, все проблемы из-за того, что вы не хотите с сервера делать запрос в Apple?
                                Механизм проверки же такой официальный: покупаем, от apple приходит response, мы его отправляем на свой сервер и со своего сервера его опять отправляем в apple и проверяем, корректно или нет. После этого сервер отвечает клиенту — ОК или нет.

                                Я не понимаю, как можно выпустить общую ломалку, которая ломает такую проверку :-/
                                • 0
                                  Насколько я понял, сие чудо появилось из-за боязни автора, что его сервер будет оффлайн и в приложении не будут доступны покупки.
                                  • 0
                                    если оффлайн, то можно и разрешить пиратам на пару часов покупать, много не потеряет…
                                    • 0
                                      на айфонах есть hosts? перекидываем запросы к серверу на локал хост, и сервер перестает отвечать.
                                      • 0
                                        Так мы ж защищаемся от общих решений. Если ломают целенаправленно вас — защититься по-любому не получится идеально.
                                        • 0
                                          С этим полностью согласен :-)
                                • 0
                                  Поставил вопрос на обсуждение.
                                  Спасибо за баг-репорт и ваш вклад в то, чтобы сделать наш продукт лучше и качественней!

                                  :)
                                  • +4
                                    Ждем топик Как ОЧЕНЬ НАДЕЖНО защитить in-App Purchase от ломалок? :)
                                    • 0
                                      Лучше всего защищено то приложение, которое не написано!
                                      • 0
                                        Не делать In-App в своём приложении? =)
                                      • –2
                                        Я не понимаю, как это защитит вашу прогу.
                                        Вы не забывайте, что безопасность всей системы определяется самым уязвимым её компонентом.
                                        В конце концов можно вообще вырезать всё общение с вашим сервером.
                                        Если часть кода хранится и исполняется на сервере, то можно её самому дописать.
                                        И зарелизить на многочисленных сайтах и пиратских сидиа репозитариях.
                                        Защитить приложение от взлома невозможно.
                                        Для этого вам придётся весь код выполнять на сервере, а приложение уже нельзя будет назвать полноценным приложением, а лишь обёрткой для вашего сервиса.

                                        >Цель этой защиты — чтобы не было массовых взломов стандартными средствами.
                                        Сидия не является «стандартным средством», программы из неё — тоже.
                                        И вообще, вам должно быть безразлично, как вашу прогу взломают, ибо:
                                        1 её всё равно взломают
                                        2 дополнительной прибыли вы всё равно не получите
                                        Тот, кто качает проги из пиратских репозитариев и пользуется ломалками всё равно вашу прогу не купит, даже если взломанной версии не будет.

                                        Вам нужно просто сделать вашу прогу погламурней, тогда гламурные любители айфона её гламурно купят за гламурную цену чтобы гламурно хвастаться этим гламурным знакомым и незнакомым, чтобы гламурно быть на гламурном уровне.
                                        Айфон — не для гиков, гики его юзают только чтобы деньги зарабатываать да в скачанные игрушки иногда играть.
                                        • –1
                                          Здесь с Вами не соглашусь.
                                          Я выпускаю приложения, а не игры. некоторые мои приложения уникальны, в смысле аналогов просто нет. И если человеку нужен такой функционал — он готов покупать. но если у него установлена ломалка — он просто взломает.
                                          Я не ставлю цель делать прогу, котору невозможно взломать — все ломается, сам когда-то такое проделавал не раз, играясь с дебагером, но это был скорее спортивный интерес.
                                          Да и по себе сужу — года 1,5-2 назад пользовался джейлом, был у меня установлен Инсталоус. Если что-то надо было, в первую очередь смотрел там, если не было того что надо — покупал. Хотя до этого я покупал приложения с первого дня пользования iPod touch с конца 2008 года.
                                          • –1
                                            А по поводу «гламурности», как мне кажется в этих ваших словах слишком много ехидства. Очень многие пользователи просто пользуются.
                                          • +3
                                            Как-то еще года 3 назад мы решили защитить нашу игру на iOS от взлома. После этого продажи только упали, причем значительно! Это были не инапы, а защита самой программы. Причем защита работала вроде исправно. Не знаю, почему так получилось, думаю, просто люды, которые скачивали на шару — показывали игру другим, кто уже покупал. А те, кто качает на шару — обычно все равно не покупают. Лучше они скачают на шару что-то другое, чем будут покупать именно это, защищенное. Ну не привыкли они покупать и все. И ничто их не заставит это сделать!
                                            • 0
                                              Очень интересная статистика. Можете рассказать больше, пожалуйста? Или даже топик сделать, я б с удовольствием почитал.
                                              • +1
                                                Да я бы рад, но с того времени много воды утекло. Занимались игростроением отдел из 4х человек (два программиста, программист-дизайнер и дизайнер). Мы ничего не смыслили в статистике, да и статистика потеряна уже. Остались только впечатления. Тогда мы пытались, много пытались, анализировать из-за чего продажи растут, из-за чего падают, пытались найти формулу успеха. Когда прикрутили статистику и проанализировали, сколько людей реально купили игру а сколько на шару скачали — ужаснулись. Было что-то типа 30% купили, 70% скачали на шару. Потому и решили защитить продукт. Но как-то толку было мало. Потом уже пришли в голову мысли, что наши игры не венец творения, и люди их скачивают не потому, что хотят именно в них поиграть, а скачивают потому, что хотят просто поиграть во что-нибудь. Потому при сообщении, что игра хакнута, купите пожалуйста — скорее всего они просто скачивали другую игру, благо их миллионы. А так хоть работали как ходячая реклама игры. Кому-то покажут — те захотят себе, купят…

                                                К сожалению больше информации по этому делу у меня нету, потому что к защитам не возвращались с тех пор. Лучшим результатом нашим в AppStore было то, что наша игра Medieval HD стала TOP#1 на неделю в США после выхода. Скорее всего это было потому, что iPhone версия давно была на рынке, и пользователи, кому она нравиться скачали и iPad версию сразу после выхода.

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