Совсем недавно я писал статью Как защитить in-App Purchase от ломалок . Прошло немного времени, а хакеры на месте не сидят. Тот метод защиты оказывается можно обойти, не очень сложно. Под катом метод, который намного надежнее.
Перед чтением этого топика, рекомендую прочитать предыдущий Как защитить in-App Purchase от ломалок , так как это продолжение темы.
В моем приложении после проверки receipt отправляется на мой сервер, и я там его анализирую, и сохраняю в логах. И я заметил, что JSON, который приходит есть стандартный, и какой-то модифицированный.
Стандартный выглядит так:
В результате я заметил 3 типа попыток взлома:
1. Самый частый вариант. Ломалка не подделывает ответ сервера Apple, в результате receipt выглядит так:
Наш предыдущий метод с таким взломом справлялся, так как статус = 21002
2. Совсем недавно появились новые способы взлома. Я не знаю с помощью каких утилит это делается, но они подделывают ответ серверов Apple и на второй запрос.
JSON выглядит тогда так:
Такой взлом отслеживается просто. Можно проверить наличие некоторых переменных, например «product_id», и всё станет на свои места.
3. Но не все так просто. Недавно появился еще один вариант подделаного JSON:
Если опять что-то проверять, тогда надо найти то, чего не сможет получить программа-ломалка из запроса. Просмотрев все данные я понял, что есть достаточно простой способ, который обойти будет очень непросто.
Надо сделать проверку на «item_id» — это id, которое Apple присваевает каждому продукту, в том числе и in-App purchase. Его можно посмотреть в iTunesConnect, нажав на кнопку «Manage In-App Purchases».
Тогда наш код будет выглядеть так:
В чем плюс этого кода: ломалка не знает значения item_id, оно нигде не передается при запросе. Именно поэтому подделать такой receipt будет непросто. Хотя, наверное, возможно.
И просьба, не говорите, что все можно сломать. Цель этой защиты — чтобы не было массовых взломов стандартными средствами.
Перед чтением этого топика, рекомендую прочитать предыдущий Как защитить 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 будет непросто. Хотя, наверное, возможно.
И просьба, не говорите, что все можно сломать. Цель этой защиты — чтобы не было массовых взломов стандартными средствами.