大纲

前言

正文

一、安装方法

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免费内购的支持。

该iOS免费内购服务曾关闭过多次,有apple屏蔽服务器IP的原因,有服务器压力支撑不住的原因,有没人捐赠停服的原因,有iOS新版本修复漏洞的原因,总之是命运多舛,但都继续下来了,并且ZonD80还将如何搭建免费内购服务器的方法公布在了github上。我觉得,这次也不会例外,服务还会继续开放的,并且很快会扩展到Android平台上的。

在之前的文章中,我提及过这种免费内购方法,但有部分情况(如推出的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

(1)如果有安装cydia,直接打开cydia添加源 http://system.in-appstore.com/repo/
搜索LocalIAPStore,安装

添加源后,在iOS设备的/etc/apt/sources.list.d/cydia.list 文件中(该文件对应的是cydia中所有的软件源地址),会增加一行文字,如下所示

(2)如果没有安装cydia

首先,SSH到iOS设备上
————————-知道如何SSH,不用看这段——————————————————————-
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]
——————————————————————————————————————————–
然后,编辑/etc/apt/sources.list/saurik.list,添加一行deb http://system.in-appstore.com/repo/ ./
vim /etc/apt/sources.list.d/saurik.list (编辑软件源地址列表)

 apt-get update (更新软件源)

 apt-get install anondev.LocalIAPStore (安装插件)
重启SpringBoard或重启设备

LocalAPStore安装后,在/Library/MobileSubstrate/DynamicLibraries目录下会发现2个文件


LocalAPStore.dylib为 Mach-O dynamically linked shared library,可以使用IDA来查看


LocalIAPStore.plist文件为过滤文件,用来限制dylib是否被加载,如下图所示,表示LocalAPStore.dylib只在调用StoreKit功能时加载,即在发生内购操作时加载。

iOS平台上安装的第三方插件,一般采用MobileSubstrate开源框架开发,原理是利用ojective-c reflective lauguage 特性进行dylib注入(类似windows的dll注入),使用动态链接指令DYLD_INSERT_LIBRARIES在运行时调用class_replaceMethod替换系统函数。MobileSubstrate为这一hook封装了更便利得方法。

第三步:有些应用不需要连接到in-appstore.com就可以完成免费内购,有些应用需要连接到in-appstore.com 服务器,大多数需要连接到该服务器。

连接方法如下所示(这就是很著名的不越狱俄罗斯内购方法):

(1)注销已登录的apple ID

(2)在设备上安装证书文件,使用浏览器打开http://system.in-appstore.com/

点击First,安装

 点击安装,会提示该证书为经过验证


 忽略之,点击安装,回到浏览器,点击second


点击安装,会弹出安装该证书会改变iPod上得设置
忽略之,点击现在安装,证书安装成功后,会在设置-通用-描述文件中看到已安装好的证书

(3)修改设备接入wifi的DHCP设置中DNS服务器栏
94.228.221.10(黑客说为了减少每月的维护费用,停用这个)
91.224.160.136(目前只有这个可用)


(4)进入游戏,打开购买链接,在如下弹出框中选择like,然后随便输入非真实的apple id,就可以购买成功了。

备注:如果出现预设的”请确认您的软件内购买。。。” ,说明安装失败,你并没有连接到in-appstore.com服务器上,需要重复第三步。

-------------------------------------------------方法介绍结束

三、原理分析
(1)IAPCracker插件

对于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就是采用的这种办法。

(2)IAP Free插件
经历IAPCracker的洗礼后,IAP流程中开始加入了收据验证。
收据验证的步骤如下:

第一步:从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版本号

   

-------------以下是SKPaymentTransaction对象的属性值-----------

Getting Information About the Transaction

  •   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直接进行验证。
记住一个真理,客户端上一切数据都是可以修改的。
我们可以在客户端上伪造receipt,这正是IAPFree做的事情;
我们可以通过在设备上安装伪造的SSL证书,修改设备的DNS地址,让客户端上的app向伪造的app store server去验证收据,这正是in-appstore.com做的事情;
第二处:验证方法漏洞
根据Receipt的status状态是否为0来判断是否为合法的Receipt,而不去校验receipt中的唯一性标识,例如收据签名(receipt signature)
如果APP没有独立的服务器,或者出于用户付费率考虑(由服务器与app store server进行收据验证显然会增加购买等待时间,中国的wifi又不给力),那就必须严格的验证订单,例如transaction_key,收据签名。但只验证是否合法会有个严重缺陷,如果能伪造一个完全合法的订单,该怎么办?in-appstore.com就可以实现到这种程度。
(3)in-appstore.com
从网站说明来看,是利用iOS5证书漏洞,搭建了一个app store服务器的替代品,可以理解为游戏中的私服
下图说明了利用的什么漏洞,SSL证书硬编码与Receipt可以使用自定义证书来签名。意思是可以使用自己伪造的证书来签名收据。

下图说明了该服务是完全模拟了app store server购买算法

ZonD80在github上开源了用于模拟app store购买算法的源码,有兴趣的可以下载源码,搭建环境,体验一下。
下载源码
环境搭建
1.Unix/Linux系统
2.DNSMasq DNS服务器,
dnsmasq.conf配置如下所示如下图所示

3. Nginx/Apache+PHP
if (!-e $request_filename) { rewrite ^/(.*)$ /iapcracker.php?URL=$1 last; break; }
4.在iOS 5设备安装2个证书,修改设备的DNS IP为你服务器IP
cacert.pem

 itcert.pem

四、防御建议
总而言之,对IAP而言,能绕过这些邪恶工具的唯一可靠办法就是由独立的服务器与app store server验证收据是否合法,不要经过万恶的客户端。如果不得不由客户端与app store server验证收据,那就只能采取下面不怎么可靠的办法。
1.确保app用于连接app store server的SSL证书采用的是EV SSL证书
2.确保transaction的transaction ID是唯一的
3.确保Receipt的签名合法
4.确保Receipt中购买信息的合法性
1,确保连接的是真正的app store server;2、3、4确保Receipt是未篡改的Receipt
1. 苹果官方校验代码

// 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

源链接

Hacking more

...