App Store內(nèi)購(gòu)機(jī)制

如今砰嘁,不同系統(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軟件安全與逆向分析》亩冬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末艘希,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子硅急,更是在濱河造成了極大的恐慌覆享,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件营袜,死亡現(xiàn)場(chǎng)離奇詭異撒顿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)荚板,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)核蘸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)巍糯,“玉大人,你說(shuō)我怎么就攤上這事客扎∷盥停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵徙鱼,是天一觀的道長(zhǎng)宅楞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)袱吆,這世上最難降的妖魔是什么厌衙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮绞绒,結(jié)果婚禮上婶希,老公的妹妹穿的比我還像新娘。我一直安慰自己蓬衡,他們只是感情好喻杈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著狰晚,像睡著了一般筒饰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壁晒,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天瓷们,我揣著相機(jī)與錄音,去河邊找鬼秒咐。 笑死谬晕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的携取。 我是一名探鬼主播固蚤,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼歹茶!你這毒婦竟也來(lái)了夕玩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惊豺,失蹤者是張志新(化名)和其女友劉穎燎孟,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體尸昧,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揩页,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了烹俗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爆侣。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡萍程,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出兔仰,到底是詐尸還是另有隱情茫负,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布乎赴,位于F島的核電站忍法,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏榕吼。R本人自食惡果不足惜饿序,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羹蚣。 院中可真熱鬧原探,春花似錦、人聲如沸顽素。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)戈抄。三九已至,卻和暖如春后专,著一層夾襖步出監(jiān)牢的瞬間划鸽,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工戚哎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裸诽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓型凳,卻偏偏與公主長(zhǎng)得像丈冬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子甘畅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容