如今砰嘁,不同系統(tǒng)平臺(tái)都有專屬的商店應(yīng)用恕刘,Android平臺(tái)有Google Play桑嘶,Windows平臺(tái)有Windows Store,iOS與macOS平臺(tái)則有App Store册着。蘋(píng)果公司的成功拴孤,很大程度上得益于該軟件的生態(tài)環(huán)境App Store。
如何讓系統(tǒng)上的軟件開(kāi)發(fā)人員真正地受益甲捏,是操作系統(tǒng)開(kāi)發(fā)商需要關(guān)注的問(wèn)題演熟。只有系統(tǒng)平臺(tái)上的軟件豐富了,才能吸引更多的用戶去使用該操作系統(tǒng)司顿,而只有開(kāi)發(fā)人員在系統(tǒng)上開(kāi)發(fā)的軟件能夠賺到錢(qián)芒粹,他們才有動(dòng)力去為系統(tǒng)開(kāi)發(fā)更多更好的軟件。蘋(píng)果公司的Apple Store就曾經(jīng)創(chuàng)造了無(wú)數(shù)軟件開(kāi)發(fā)人員的成功神話大溜。而這一切背后化漆,蘋(píng)果公司首創(chuàng)的軟件購(gòu)買(mǎi)、免費(fèi)軟件內(nèi)付費(fèi)猎提,都是它成功的關(guān)鍵所在。
App Store的內(nèi)購(gòu)又稱為IAP(In-app Purchase),它是所有蘋(píng)果商店內(nèi)應(yīng)用內(nèi)付費(fèi)軟件使用的基礎(chǔ)設(shè)施锨苏。對(duì)于軟件開(kāi)發(fā)人員疙教,了解其使用方法與運(yùn)行機(jī)制,對(duì)開(kāi)發(fā)高質(zhì)量的商業(yè)軟件是很有幫助的伞租。蘋(píng)果公司沒(méi)有給出IAP的具體技術(shù)細(xì)節(jié)贞谓,但在WWDC大會(huì)與SDK的開(kāi)發(fā)文檔中詳細(xì)講解了如何在軟件中集成它。IAP技術(shù)基于蘋(píng)果SDK中的Store Kit葵诈,它是系統(tǒng)中的一個(gè)框架StoreKit.framework裸弦。開(kāi)發(fā)人員通過(guò)使用StoreKit提供的API來(lái)完成IAP的集成工作。整個(gè)框架的工作方式如圖1所示作喘。
蘋(píng)果的應(yīng)用內(nèi)付費(fèi)支持使用多種類型的程序理疙。
為基本功能的軟件提供付費(fèi)后的功能更強(qiáng)大的專業(yè)版。
雜志類App購(gòu)買(mǎi)成功后泞坦,支持訂閱與下載窖贤。
免費(fèi)游戲提供付費(fèi)后等級(jí)解鎖。
在線游戲通過(guò)付費(fèi)購(gòu)買(mǎi)道具或虛擬財(cái)產(chǎn)贰锁。
測(cè)試應(yīng)用內(nèi)付費(fèi)軟件的最簡(jiǎn)單方法就是下載應(yīng)用內(nèi)付費(fèi)的應(yīng)用赃梧,然后觀察它們與其他應(yīng)用之間的區(qū)別。由于集成了應(yīng)用內(nèi)付費(fèi)功能的App豌熄,只能通過(guò)App Store來(lái)發(fā)布授嘀,因此在測(cè)試時(shí),需要先從App Store中下載App锣险√阒澹可以發(fā)現(xiàn),通過(guò)App Store下載的程序與網(wǎng)絡(luò)發(fā)布的程序最直觀的不同是:在App Store中下載的程序囱持,在app的Contents/_MASReceipt目錄下會(huì)有一個(gè)receipt文件夯接。其實(shí),這是一個(gè)“憑證文件”纷妆,軟件通過(guò)App Store發(fā)布成功后盔几,蘋(píng)果公司會(huì)為它維護(hù)一份憑證(Receipt),憑證信息以文件形式進(jìn)行存儲(chǔ)掩幢,該文件記錄了以下信息逊拍。
Purchase Information。存放的軟件的購(gòu)買(mǎi)信息际邻。包括軟件的Bundle標(biāo)識(shí)符芯丧、版本號(hào)、唯一標(biāo)識(shí)以及這些屬性值的SHA1哈希值世曾。除此之外缨恒,它還包含軟件的信用記錄(Trusted record)與購(gòu)買(mǎi)記錄(Purchase
Record)。這些數(shù)據(jù)使用ASN.1進(jìn)行編碼存放。所有這些信息被稱為Receipt Payload骗露。
Certificates岭佳。存放的Apple Root CA。用于驗(yàn)證Receipt的簽名信息萧锉。
Signature珊随。簽名信息。驗(yàn)證簽名柿隙,可以檢測(cè)當(dāng)前的Receipt是否有效叶洞,或者是否已經(jīng)更新了。蘋(píng)果在開(kāi)發(fā)文檔中指出禀崖,開(kāi)發(fā)人員應(yīng)該在程序啟動(dòng)時(shí)衩辟,檢測(cè)Receipt是否有效。如果無(wú)效帆焕,程序應(yīng)該調(diào)用exit(173)退出惭婿,系統(tǒng)收到173退出碼后,會(huì)自動(dòng)聯(lián)網(wǎng)請(qǐng)求去刷新Receipt叶雹。相應(yīng)的Objective-C代碼如下:
-(void)applicationWillFinishLaunching:(NSNotification *)notification {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
if(![[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]])
{
exit(173);
}
}
接下來(lái)看看如何在程序中集成StoreKit财饥。圖9-23所示是應(yīng)用內(nèi)付費(fèi)的步驟,整個(gè)應(yīng)用內(nèi)付費(fèi)的開(kāi)發(fā)都圍繞它展開(kāi)折晦。
使用iTunes Connect創(chuàng)建并配置好產(chǎn)品信息后钥星,就可以使用StoreKit提供的API與App Store進(jìn)行交互了。從圖中可以看出满着,整個(gè)交互過(guò)程一共發(fā)送了兩次請(qǐng)求:一次是Makes Products Request谦炒,也就是構(gòu)建產(chǎn)品請(qǐng)求。調(diào)用Store Kit向App Store發(fā)送產(chǎn)品請(qǐng)求风喇,方法是構(gòu)建一個(gè)SKProductsRequest對(duì)象的實(shí)例宁改,該對(duì)象的作用是接收來(lái)自App Store返回的本地化產(chǎn)品信息列表。這些本地化的信息中包含了產(chǎn)品的本地化描述以及價(jià)格信息魂莫,用來(lái)展示給用戶还蹲。SKProductsRequest構(gòu)建完成后,再為它設(shè)置一個(gè)代理delegate耙考,用來(lái)處理返回的信息谜喊。最后調(diào)用它的start()方法。從App Store中下載一個(gè)App倦始,查看相應(yīng)的偽代碼斗遏,如下所示:
void -[ituShopMAS requestProductData](void * self, void * _cmd) {
r14 = [SKProductsRequest alloc];
rdx = self->_product;
rdx = [NSSet setWithObjects:rdx];
r14 = [r14 initWithProductIdentifiers:rdx];
[r14 setDelegate:self]; //設(shè)置代理
rdi = r14;
[rdi start]; //調(diào)用start()
return;
}
圖9-23 應(yīng)用內(nèi)付費(fèi)的步驟
SKProductsRequest的setDelegate()設(shè)置了數(shù)據(jù)返回處理的代理。它是一個(gè)SKProducts RequestDelegate協(xié)議鞋邑,用來(lái)處理服務(wù)器返回的SKProductsResponse對(duì)象诵次。該協(xié)議有一個(gè)接口方法-productsRequest:didReceiveResponse:账蓉,用來(lái)處理返回的SKProductsResponse。調(diào)用它的products屬性會(huì)返回一個(gè)SKProduct列表逾一,解析列表中的產(chǎn)品信息剔猿,然后展示給用戶。App Store中一個(gè)App的相應(yīng)偽代碼如下:
void -[ituShopMAS productsRequest:didReceiveResponse:](void * self, void * _cmd, void * arg2, void *
arg3) {
r15 = self;
rax = [arg3 products];
var_48 = rax;
if ([rax count] != 0x0) {
r12 = @selector(productIdentifier);
var_38 = @selector(isEqualToString:);
var_30 = @selector(setHidden:);
var_78 = @selector(localizedDescription);
var_40 = @selector(setStringValue:);
var_80 = @selector(localizedTitle);
var_68 = @selector(stringWithFormat:);
var_88 = @selector(alloc);
var_90 = @selector(init);
var_98 = @selector(setFormatterBehavior:);
var_A0 = @selector(setNumberStyle:);
var_A8 = @selector(priceLocale);
var_B0 = @selector(setLocale:);
var_B8 = @selector(price);
var_C0 = @selector(stringFromNumber:);
var_C8 = @selector(release);
var_D0 = @selector(setEnabled:);
var_E8 = @selector(shopNeedsIcon);
rbx = 0x0;
do {
rax = [var_48 objectAtIndex:rbx];
r13 = rax;
rax = _objc_msgSend(rax, r12, rbx);
rcx = *objc_ivar_offset_ituShopMAS__product;
rdx = *(r15 + rcx);
if (_objc_msgSend(rax, var_38, rdx, rcx) != 0x0) {
rdi = r15->_product;
rdx = @"iboostup.premium";
if (_objc_msgSend(rdi, var_38) == 0x0) {
var_50 = rbx;
r12 = r15->lblDescription;
rax = _objc_msgSend(r13, var_78, rdx);
_objc_msgSend(r12, var_40, rax);
_objc_msgSend(r15->lblDescription, var_30, 0x0);
var_70 = r15->lblProductName;
rcx = _objc_msgSend(r13, var_80, 0x0);
rax = _objc_msgSend(@class(NSString), var_68, @"%@ only", rcx);
_objc_msgSend(var_70, var_40, rax);
_objc_msgSend(r15->lblProductName, var_30, 0x0);
rbx = _objc_msgSend(_objc_msgSend(@class(NSNumberFormatter),
var_88, 0x0), var_90, 0x0);
_objc_msgSend(rbx, var_98, 0x410);
_objc_msgSend(rbx, var_A0, 0x2);
rdx = _objc_msgSend(r13, var_A8, 0x2);
_objc_msgSend(rbx, var_B0, rdx);
rdi = r13;
r13 = rdi;
rdx = _objc_msgSend(rdi, var_B8, rdx);
var_70 = _objc_msgSend(rbx, var_C0, rdx);
_objc_msgSend(rbx, var_C8, rdx);
r12 = r15->lblPrice;
rcx = var_70;
rax = _objc_msgSend(@class(NSString), var_68, @"Price: %@", rcx);
_objc_msgSend(r12, var_40, rax);
rdi = r15->lblPrice;
rbx = var_50;
_objc_msgSend(rdi, var_30, 0x0);
_objc_msgSend(r15->btnPurchase, var_D0, 0x1);
_objc_msgSend(r15->lblRestore, var_30, 0x0);
_objc_msgSend(r15->imgIcon, var_30, 0x0);
}
......
}
rax = _objc_msgSend(r13, r12, rdx, rcx);
rdx = @"iboostup.premium";
if (_objc_msgSend(rax, var_38, rdx, rcx) != 0x0) {
var_50 = rbx;
r12 = r15->lblDescriptionPro;
rax = _objc_msgSend(r13, var_78, rdx);
_objc_msgSend(r12, var_40, rax);
_objc_msgSend(r15->lblDescriptionPro, var_30, 0x0);
var_70 = r15->lblProductNamePro;
rcx = _objc_msgSend(r13, var_80, 0x0);
rax = _objc_msgSend(@class(NSString), var_68, @"%@", rcx);
_objc_msgSend(var_70, var_40, rax);
_objc_msgSend(r15->lblProductNamePro, var_30, 0x0);
rbx = _objc_msgSend(_objc_msgSend(@class(NSNumberFormatter), var_88,
0x0), var_90, 0x0);
......
_objc_msgSend(rdi, var_40, rax);
_objc_msgSend(r15->lblPricePro, var_30, 0x0);
_objc_msgSend(r15->btnPurchasePro, var_D0, 0x1);
_objc_msgSend(r15->lblRestorePro, var_30, 0x0);
_objc_msgSend(r15->imgIconPro, var_30, 0x0);
}
_objc_msgSend(r15->boxWait, var_30, 0x1, rcx);
rbx = rbx + 0x1;
} while (rbx < [var_48 count]);
}
return;
}
解析完產(chǎn)品信息嬉荆,展示給用戶。當(dāng)用戶選擇好產(chǎn)品點(diǎn)擊購(gòu)買(mǎi)時(shí)酷含,就會(huì)發(fā)出第2次請(qǐng)求:Makes Payment Request鄙早,也就是構(gòu)建付款請(qǐng)求。該請(qǐng)求通過(guò)調(diào)用SKPaymentQueue的addPayment()方法椅亚,添加一個(gè)SKPayment對(duì)象限番。例如,某產(chǎn)品點(diǎn)擊購(gòu)買(mǎi)某功能選項(xiàng)的偽代碼如下:
void -[ituShopMAS btnPurchaseClicked:](void * self, void * _cmd, void * arg2) {
[self waitUI];
rdi = [SKMutablePayment alloc];
rdi = [rdi init];
r15 = [rdi autorelease];
rdx = self->_product;
[r15 setProductIdentifier:rdx]; //設(shè)置產(chǎn)品標(biāo)識(shí)
[r15 setQuantity:0x1];
rdi = [SKPaymentQueue defaultQueue];
rdx = r15;
[rdi addPayment:rdx]; //添加支付請(qǐng)求
return;
}
defaultQueue()方法返回一個(gè)單例的SKPaymentQueue實(shí)例呀舔,它是一個(gè)隊(duì)列結(jié)構(gòu)弥虐,由App
Store去處理。操作完成后媚赖,產(chǎn)品支付請(qǐng)求就加入到支付隊(duì)列中了霜瘪。要想處理支付的狀態(tài),例如購(gòu)買(mǎi)成功惧磺、購(gòu)買(mǎi)失敗颖对、購(gòu)買(mǎi)取消等處理的邏輯,就需要為隊(duì)列添加一個(gè)觀察者磨隘。當(dāng)隊(duì)列中交易的狀態(tài)被更新缤底,或者當(dāng)交易從隊(duì)列中刪除的時(shí)候,觀察者應(yīng)該能正確及時(shí)地處理所有的交易信息番捂,并根據(jù)交易的結(jié)果為購(gòu)買(mǎi)成功的用戶提供相應(yīng)的功能个唧。添加觀察者的操作要在addPayment()調(diào)用前完成,通常是在程序的初始化時(shí)完成的设预,代碼如下所示:
void * -[ituShopMAS init](void * self, void * _cmd) {
rbx = self;
rcx = [SKPaymentQueue canMakePayments];
rax = 0x0;
if (rcx != 0x0) {
rbx = [[rbx super] init];
rax = 0x0;
if (rbx != 0x0) {
rbx->_checked = 0x0;
rbx->_failures = 0x0;
if ([NSBundle loadNibNamed:@"ituShopMAS" owner:rbx] != 0x0) {
rdi = rbx->lblCancel;
[rdi setStringValue:@"Cancel"];
[rbx->lblCancel setClickTarget:rbx sel:@selector(lblCancelClicked)];
[rbx->lblRestore setStringValue:@"Restore"];
[rbx->lblRestore setClickTarget:rbx sel:@selector(lblRestoreClicked)];
[rbx->lblRestorePro setStringValue:@"Restore"];
[rbx->lblRestorePro setClickTarget:rbx
sel:@selector(lblRestoreProClicked)];
rax = [SKPaymentQueue defaultQueue]; //獲取單例隊(duì)列實(shí)例
[rax addTransactionObserver:rbx]; //添加觀察者
}
rax = rbx;
}
}
return rax;
}
添加觀察者對(duì)象使用addTransactionObserver()方法徙歼,它傳入的是一個(gè)SKPaymentTransaction Observer協(xié)議對(duì)象,SKPaymentTransactionObserver協(xié)議有一系列方法被SKPaymentQueue調(diào)用絮缅。下面我們分別進(jìn)行介紹鲁沥。
1. 處理交易
處理交易包括-paymentQueue:updatedTransactions:與-paymentQueue:removedTransactions:方法。前者在一個(gè)或多個(gè)交易狀態(tài)更新時(shí)被調(diào)用耕魄,在目標(biāo)程序中画恰,它必須實(shí)現(xiàn);后者則在交易移除時(shí)被調(diào)用吸奴,在目標(biāo)程序中允扇,它的實(shí)現(xiàn)是可選的缠局。
這兩個(gè)方法傳入的參數(shù)都是一個(gè)SKPaymentTransaction類型的數(shù)組,每一個(gè)SKPayment- Transaction代表著一個(gè)支付交易對(duì)象考润,應(yīng)用程序要明確地處理每個(gè)交易對(duì)象的返回結(jié)果狭园,根據(jù)它的transactionState屬性來(lái)判斷交易是否成功。如果transactionState的值是SKPayment- TransactionStatePurchased糊治,則表示交易成功唱矛,此時(shí)程序應(yīng)該向用戶提供收費(fèi)成功后的功能;如果transactionState的值為SKPaymentTransactionStateFailed井辜,則表示交易失敗绎谦,應(yīng)用程序應(yīng)該獲取交易失敗的錯(cuò)誤信息并反饋給用戶。交易的狀態(tài)是一個(gè)枚舉值粥脚,定義如下:
enum {
SKPaymentTransactionStatePurchasing, //正在付款
SKPaymentTransactionStatePurchased, //付款成功
SKPaymentTransactionStateFailed, //付款失敗
SKPaymentTransactionStateRestored, //交易已恢復(fù)
SKPaymentTransactionStateDeferred, //交易已推遲
};
typedef NSInteger SKPaymentTransactionState;
一個(gè)典型的-paymentQueue:updatedTransactions:代碼的邏輯如下所示:
void -[ituShopMAS paymentQueue:updatedTransactions:](void * self, void * _cmd, void * arg2, void * arg3) {
var_F8 = arg3;
r13 = self;
var_30 = *___stack_chk_guard;
intrinsic_movaps(var_40, 0x0, arg2, arg3);
intrinsic_movaps(var_50, 0x0);
var_60 = intrinsic_movaps(var_60, 0x0);
var_70 = intrinsic_movaps(var_70, 0x0);
rbx = [arg3 countByEnumeratingWithState:var_70 objects:var_F0 count:0x10];
if (rbx == 0x0) goto loc_10001eb00; //如果交易的數(shù)量為0窃肠,則直接返回
loc_10001ea53:
r14 = *var_60;
goto loc_10001ea5a;
loc_10001ea5a:
r15 = 0x0;
goto loc_10001ea5d;
loc_10001ea5d:
if (*var_60 != r14) { //枚舉所有的交易
objc_enumerationMutation(var_F8);
}
r12 = *(var_68 + r15 * 0x8);
rax = [r12 transactionState]; //判斷每個(gè)交易的transactionState
if (rax == 0x3) goto loc_10001eaa2; //3表示SKPaymentTransactionStateRestored
loc_10001ea90:
if (rax != 0x2) goto loc_10001eaae; //2表示SKPaymentTransactionStateFailed
loc_10001ea96:
rdi = r13;
rsi = @selector(failedTransaction:); //交易失敗
goto loc_10001eabe;
loc_10001eabe:
_objc_msgSend(rdi, rsi); //為不同的狀態(tài)執(zhí)行不同的選擇器方法
goto loc_10001eac7;
loc_10001eac7:
r15 = r15 + 0x1;
if (r15 < rbx) goto loc_10001ea5d;
loc_10001eacf:
rbx = [var_F8 countByEnumeratingWithState:var_70 objects:var_F0 count:0x10];
if (rbx != 0x0) goto loc_10001ea5a;
loc_10001eb00:
if (*___stack_chk_guard != var_30) {
__stack_chk_fail();
}
return;
loc_10001eaae:
if (rax != 0x1) goto loc_10001eac7; //1表示SKPaymentTransactionStatePurchased
loc_10001eab4:
rdi = r13;
rsi = @selector(completeTransaction:); //交易完成
goto loc_10001eabe;
loc_10001eaa2:
rdi = r13;
rsi = @selector(restoreTransaction:); //交易恢復(fù)
goto loc_10001eabe;
}
2. 處理恢復(fù)交易
恢復(fù)交易有兩個(gè)方法,一個(gè)是交易成功后的處理刷允,另一個(gè)是失敗后的處理冤留。它們分別是-paymentQueueRestoreCompletedTransactionsFinished:與-paymentQueue:restoreCompletedTransactionsFailedWithError:∈髟睿恢復(fù)失敗的原因通常是網(wǎng)絡(luò)或本地的Receipt驗(yàn)證失敗纤怒。一段典型的處理代碼如下:
void -[ituShopMAS paymentQueue:restoreCompletedTransactionsFailedWithError:](void * self, void * _cmd, void * arg2, void * arg3) {
rcx = [arg3 description];
NSLog(@"Restore failed: %@", rcx);
[self->boxWait setHidden:0x0];
[self->lblResult setStringValue:@"Restore failed."];
[self->lblProductName setHidden:0x0];
[self->lblDescription setHidden:0x0];
[self->wvWait setHidden:0x1];
[self->lblPrice setHidden:0x0];
[self->btnPurchase setEnabled:0x1];
[self->lblRestore setHidden:0x0];
rcx = self->_product;
[self sendTxEvent:@"restore-fail" product:rcx];
rax = [NSBundle mainBundle];
rdx = @selector(appStoreReceiptURL);
if (([rax respondsToSelector:rdx] != 0x0) && ([[NSFileManager defaultManager]
fileExistsAtPath:[[[NSBundle mainBundle] appStoreReceiptURL] path], rcx] == 0x0)) {
exit(0xad);
}
return;
}
3. 處理下載動(dòng)作
處理下載動(dòng)作只有一個(gè)方法-paymentQueue:updatedDownloads:,而且它是可選的天通,只有提供付費(fèi)下載與訂閱的程序才需要實(shí)現(xiàn)它肪跋。
了解了API的使用方法,再來(lái)分析如何破解它就沒(méi)那么困難了土砂。首先州既,應(yīng)該考慮的是如何做到通用破解,即破解IAP的機(jī)制后萝映,可以將同類型的產(chǎn)品一次全部破解吴叶!這種想法并不是異想天開(kāi),在macOS 10.9系統(tǒng)以前序臂,就曾經(jīng)出現(xiàn)過(guò)這樣的破解工具與方法蚌卤。例如2012年7月,一位名叫Alexy的俄羅斯黑客公布了一個(gè)針對(duì)macOS系統(tǒng)上的IAP的破解方法奥秆。在本地系統(tǒng)中安裝兩張證書(shū)逊彭,然后使用一款名為Grim Receiper(死神)的工具,就可以一次性破解App Store中大量支持內(nèi)購(gòu)的程序构订。它的原理是將App Store內(nèi)購(gòu)請(qǐng)求的通信地址轉(zhuǎn)向自己搭建的內(nèi)購(gòu)驗(yàn)證服務(wù)器上侮叮,使交易發(fā)生變化時(shí)transactionState的值永遠(yuǎn)是SKPaymentTransactionStatePurchased。這類破解行為對(duì)蘋(píng)果公司與軟件開(kāi)發(fā)人員的打擊是巨大的悼瘾。之后囊榜,蘋(píng)果公司為了阻止這類行為發(fā)生审胸,采取了不少措施,包括聯(lián)系A(chǔ)lexy網(wǎng)站的ISP關(guān)閉Alexy的網(wǎng)站卸勺,聯(lián)系Paypal拒絕為Alexy的公開(kāi)賬號(hào)提供轉(zhuǎn)帳服務(wù)砂沛,修補(bǔ)App Store的程序驗(yàn)證漏洞等。10.9版本后曙求,Grim Receiper變得無(wú)效碍庵,但Alexy似乎并沒(méi)有放棄對(duì)IAP破解的嘗試。其實(shí)這位黑客還開(kāi)發(fā)出了針對(duì)Android與iOS系統(tǒng)內(nèi)購(gòu)的破解工具悟狱。之后怎抛,Alexy使用比特幣來(lái)收取世界各地人員的開(kāi)發(fā)捐助,網(wǎng)站的ISP也改為了一個(gè)地下服務(wù)商芽淡。在macOS系統(tǒng)10.11初期,Alexy甚至成功開(kāi)發(fā)出了內(nèi)購(gòu)破解工具豆赏。不過(guò)蘋(píng)果公司一直沒(méi)放棄對(duì)他的關(guān)注挣菲,很快就為破解的漏洞打上了補(bǔ)丁。目前掷邦,Alexy還在積極地嘗試如何破解最新的IAP機(jī)制白胀。Grim
Receiper運(yùn)行效果如圖9-24所示。
雖然做到通用破解有些難度抚岗,但針對(duì)個(gè)體內(nèi)購(gòu)機(jī)制的App破解或杠,難度可能就沒(méi)這么大了。一個(gè)典型的破解思路是:修改-paymentQueue:updatedTransactions:方法的代碼邏輯宣蔚,將交易transactionState為SKPaymentTransactionStateFailed時(shí)的代碼邏輯改成SKPaymentTransaction- StatePurchased時(shí)的代碼就可以了向抢。而在具體執(zhí)行破解操作時(shí),可以使用爆破的手段在程序中進(jìn)行修改胚委,或者使用Hook技術(shù)挟鸠,對(duì)方法的返回結(jié)果進(jìn)行Hook。
本文摘自《macOS軟件安全與逆向分析》亩冬。