大纲
前言
正文
一、安装方法
1.LocalIAPStore
2.连接in-appstore.com
二、原理分析
1.IAPCracker
2.IAPFree
3.in-appstore.com
三、防御建议
1.apple官方
2.Beeblex
后记
前言
-------------------------------------------
很有人气的iOS 5.1不越狱免费内购方法提出者俄罗斯黑客Alexey V. Borodin(网名ZonD80)于2012年10月22日在网站中申明由于iOS 6更换了服务器并采用了EV证书,该方法不再生效,并由于他要转战android平台,所以不再继续提供对iOS免费内购的支持。
在之前的文章中,我提及过这种免费内购方法,但有部分情况(如推出的LocalIAPStore插件)没有覆盖到,所以打算补充一下,写得更为完整准确点。
下面介绍的方法是在2012年9月20日发布的(号称在 iOS 6 Golden Master版本上测试成功),我试验了一下,对于IAP内购没有第三方服务端验证收据的应用完全有效(对这类应用只需用到LocalIAPStore插件);对运行在iOS 5.1.1并没有采用由第三方服务器与app store服务器直接验证收据措施的应用也是有效的(对这类应用需要连接in-appstore.com服务器)。但对于iOS6正式版本,我没有验证。因为我的测试机只有一台,由于iOS6对某些测试工具的不支持所以没有升级到iOS6.期待网友们能帮忙验证一下。我推测是不生效的,因为黑客在2012年10月22日申明停止该服务的部分原因是iOS 6正式版本采用了EV证书,该方法不再生效。
第一步: 如果有安装IAPFree与IAPCracker,请先卸载
第二步:安装LocalIAPStore
添加源后,在iOS设备的/etc/apt/sources.list.d/cydia.list 文件中(该文件对应的是cydia中所有的软件源地址),会增加一行文字,如下所示
(2)如果没有安装cydia
i.使用wifi ssh连接 iOS设备请安装SSH服务端openssh;PC上,如果是windows,可以使用SSH客户端Puttty或SecureCRT) ssh root@iOS设备IP 默认密码是alpine II.没有wifi的,使用USB ssh连接 插上USB数据线后,windows上使用itunnel_mux.exe itunnel_mux.exe --lport 1234 ssh [email protected]
apt-get update (更新软件源)
iOS平台上安装的第三方插件,一般采用MobileSubstrate开源框架开发,原理是利用ojective-c reflective lauguage 特性进行dylib注入(类似windows的dll注入),使用动态链接指令DYLD_INSERT_LIBRARIES在运行时调用class_replaceMethod替换系统函数。MobileSubstrate为这一hook封装了更便利得方法。
连接方法如下所示(这就是很著名的不越狱俄罗斯内购方法):
点击安装,会提示该证书为经过验证
忽略之,点击安装,回到浏览器,点击second
94.228.221.10(黑客说为了减少每月的维护费用,停用这个) 91.224.160.136(目前只有这个可用)
(4)进入游戏,打开购买链接,在如下弹出框中选择like,然后随便输入非真实的apple id,就可以购买成功了。
-------------------------------------------------方法介绍结束
对于IAPCracker,能够生效的IAP流程是客户端收到来自App store server的SKPaymentTransaction后,直接根据SKPaymentTransaction的transactionState来判断是否下发购买内容。(这也是IAP built-in内置模式,非常不安全)
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased: if([self putStringToItunes:transaction.transactionReceipt]){ //不少开发不做收据校验,就在这里直接给用户添加钱,装备之类的 } break; ......代码省略 } } }
对于这种情况,可以在客户端将transactionState修改为SKPaymentTransactionStatePurchased来欺骗应用程序下发购买内容。IAPCracker就是采用的这种办法。
第一步:从SKPaymentTransaction的transactionReceipt属性种提取收据数据,然后使用base64编码该数据
第二步:创建一个json对象,key名receipt-data,key值为第一步中编码后的收据数据
{ "receipt-data" : "(receipt bytes here)" }
第三步:将该json对象通过HTTP POST的方法发送给app store server,地址为https://buy.itunes.apple.com/verifyReceipt
第四步:app store server会返回一个JSON对象,该对象包括两个key,一个为status表示收据是否合法,0表示合法;一个为receipt表示收据数据。
{ "status" : 0, "receipt" : { (receipt here) } }
receipt数据包括了一些购买信息,用JSON格式表示,具体的key名如下表所示
key | 描述 |
quantity | 物品购买数量。 |
product_id | 购买物品的product标示符。 |
transaction_id |
购买物品的transaction标示符,为transaction的transactionIdentifier 属性
|
purchase_date |
transaction发生时间,为transaction的transactionDate 属性
|
original_transaction_id | 最初的transaction标示符 |
original_purchase_date | 最初的购买时间 |
app_item_id | 用于标识transaction中app的唯一性字符。在sandbox创建的receipt没有该字段 |
version_external_identifier | 用于标识app修订版本的唯一性数字。在sandbox创建的receipt没有该字段 |
bid | app的bundle identifier |
bvrs | app版本号 |
error
描述transaction处理中发生的错误,类型为NSError
payment
transaction的支付内容,类型为SKPayment
transactionState
tranaction当前的状态,类型为SKPaymentTransactionState
transactionIdentifier
成功支付transaction的唯一性标示符,类型为NSString
transactionReceipt
用于纪录成功支付transaction相关信息的签名收据,类型为NSData
transactionDate
transaction加入支付队列的时间,类型为NSDate
downloads
交易中的可下载内容(即购买内容),类型为NSArray
originalTransaction
用于app store transaction恢复类型为SKPaymentTransaction
productIdentifier
购买物品的product标示符,属性为NSString
quantity
property 购买物品的数量,属性为NSInteger
-------------以下是SKPaymentTransactionState对象的属性值-----------------
enum { SKPaymentTransactionStatePurchasing, SKPaymentTransactionStatePurchased, SKPaymentTransactionStateFailed, SKPaymentTransactionStateRestored }; typedef NSInteger SKPaymentTransactionState;
-------------------------------------------------------
下图是来自app store server的合法receipt示例
再看看IAPFree伪造的receipt
IAPFree之所以生效,主要是由于收据校验的流程于步骤发生了两处错误。
下图说明了该服务是完全模拟了app store server购买算法
if (!-e $request_filename) { rewrite ^/(.*)$ /iapcracker.php?URL=$1 last; break; }
itcert.pem
1.确保app用于连接app store server的SSL证书采用的是EV SSL证书 2.确保transaction的transaction ID是唯一的 3.确保Receipt的签名合法 4.确保Receipt中购买信息的合法性
// Check the validity of the receipt. If it checks out then also ensure the transaction is something // we haven't seen before and then decode and save the purchaseInfo from the receipt for later receipt validation. - (BOOL)isTransactionAndItsReceiptValid:(SKPaymentTransaction *)transaction { if (!(transaction && transaction.transactionReceipt && [transaction.transactionReceipt length] > 0)) { // Transaction is not valid. return NO; } // Pull the purchase-info out of the transaction receipt, decode it, and save it for later so // it can be cross checked with the verifyReceipt. NSDictionary *receiptDict = [self dictionaryFromPlistData:transaction.transactionReceipt]; NSString *transactionPurchaseInfo = [receiptDict objectForKey:@"purchase-info"]; NSString *decodedPurchaseInfo = [self decodeBase64:transactionPurchaseInfo length:nil]; receiptDict NSDictionary *purchaseInfoDict = [self dictionaryFromPlistData:[decodedPurchaseInfo dataUsingEncoding:NSUTF8StringEncoding]]; NSString *transactionId = [purchaseInfoDict objectForKey:@"transaction-id"]; NSString *purchaseDateString = [purchaseInfoDict objectForKey:@"purchase-date"]; NSString *signature = [objectForKey:@"signature"]; // Convert the string into a date NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init]; [dateFormat setDateFormat:@"yyyy-MM-dd HH:mm:ss z"]; NSDate *purchaseDate = [dateFormat dateFromString:[purchaseDateString stringByReplacingOccurrencesOfString:@"Etc/" withString:@""]]; if (![self isTransactionIdUnique:transactionId]) { // We've seen this transaction before. // Had [transactionsReceiptStorageDictionary objectForKey:transactionId] // Got purchaseInfoDict return NO; } // Check the authenticity of the receipt response/signature etc. BOOL result = checkReceiptSecurity(transactionPurchaseInfo, signature, (__bridge CFDateRef)(purchaseDate)); if (!result) { return NO; } // Ensure the transaction itself is legit if (![self doTransactionDetailsMatchPurchaseInfo:transaction withPurchaseInfo:purchaseInfoDict]) { return NO; } // Make a note of the fact that we've seen the transaction id already [self saveTransactionId:transactionId]; // Save the transaction receipt's purchaseInfo in the transactionsReceiptStorageDictionary. [transactionsReceiptStorageDictionary setObject:purchaseInfoDict forKey:transactionId]; return YES; }
2.Beeblex
网上也有第三方服务来提供receipt验证,例如Beeblex ,Beeblex提供IAP收据验证服务,用来防御收据伪造和中间人欺骗共计。他们使用了更有效的加密算法和有时间限制的令牌,使伪造收据变得十分困难。Beeblex非常容易使用,只需要调用他们提供的SDK就行了。
后记
-------------------------------------------
周末两天都泡在in-appstore.com和ZonD80博客里,相当喜欢这个人,能发现有趣的事情,能共享有趣的事情,就是有趣的人生。
附上ZonD80博客中的一首歌,,轻摇滚风格的。
http://www.in-appstore.com/
http://www.chto.su/
http://habrahabr.ru/post/149207/
http://developer.apple.com/library/ios/#documentation/StoreKit/Reference/SKPaymentTransaction_Class/Reference/Reference.html
https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/VerifyingStoreReceipts/VerifyingStoreReceipts.html#//apple_ref/doc/uid/TP40008267-CH104-SW1
http://developer.apple.com/library/ios/#releasenotes/StoreKit/IAP_ReceiptValidation/_index.html#//apple_ref/doc/uid/TP40012484-CH0-SW4
https://www.beeblex.com/public/overview
http://www.himigame.com/iphone-cocos2d/673.html