好久沒寫東西了,換工作之后忙得一比。你說創業?風太大沒聽清啊看了看以前寫的東西,覺得以前寫得太嚴肅了,從現在開始要輕松一點,要做一名逗逼碼農。
本文不會介紹破解的細節,最終完成破解所編寫的代碼也不會公開。雖然這個游戲非常無恥,但已經上線運營了,我不想被查水表啊。所以,該文僅以這個游戲為例,講一下如何使用IDA靜態分析破解簡單的cocos2dx-lua腳本加密。
對了,這里說的《艦娘Collection》是一個手游,不是那個艦娘,嗯。
為什么我要突然去搞破解呢?事情是這樣的:某天,一個以前的關系很好的美術同事跟我說,他無意中發現一個手游,里面盜用了部分《艦隊Collection》的原畫。然后給我看了提取出來的圖。
顯然開發商就是用艦C的原畫,自己稍微潤了下色就用進去了。其實這種事在國內手游圈應該是屢見不鮮的吧,之前有個叫《艦娘國服》的頁游也干過類似的事,不過他們更直接,功能和界面直接就是原版的了。這個是題外話。
他又說,他解包了apk,發現美術資源可以直接拿出來用,但是有很多后綴名是lua的文件,不知道是干啥的,是不是也是圖片?
我說,這是代碼文件,不是圖片的。
他又說,這些文件用記事本打不開,有沒有辦法看看里面寫了什么?
我說,打不開肯定是加密了,你研究這個干啥,想轉行當碼農啊?別和自己青春過不去。
他繼續說,你給破解了看看唄,反正閑着也是閑着。
“另請高明吧,”我說,“我實在我也不是謙虛,我一個U3D前端碼農,怎么就去搞反向工程了呢?” (其實我也挺好奇堂而皇之偷圖的公司,寫出的代碼是什么水准的,但是懶得去折騰)
他又說:你看,那個游戲里面,他們把你太太畫成這樣了:
……
……
……
太太!!!!!
你怎么變成死魚眼了!!!!!!
而且這個神似曾哥的表情是鬧哪樣!!!!!!!!
I AM ANGRY!
當時我就念了兩首詩:“苟利太太破解以,豈因禍福避趨之”。然后找那位同事要來了APK。
其實一開始的時候我也不敢保證能破解出來,我對反向工程基本上是只懂一點點雞毛蒜皮,IDA也用得不熟。但是裝出來的逼就像潑出去的水啊,強行上吧。想想曾經有人200人口飛龍狗毒爆打120人口都輸了,我作為一個新手怎么就不能破出來呢~
在破解之前還是容我先裝上看看這游戲到底是個什么玩意。裝上之后:
艾瑪這App名和圖標真夠直接的,還有個“二次元”的角標。
點進去,喲呵還有新手引導呢(而且這引導到后面還有bug,在不合時宜的時候彈出來,或者錯位。為了節約篇幅后面不截圖了)
然后就是看電影一樣開打了,全自動,尼瑪四傻打對面雷爺太太和北方,居然還贏了???
接下來是選擇關卡,媽的還有對話啊,居然是用的艦C的語音,更牛逼的是居然用那些語音組成了劇情!
雖然全自動戰斗的設定和原作一致,但是特效要好看很多啊~打起來的時候的動態也比原作多太多了,看着都比較爽。
然后不知不覺就打到第二張圖了……
等等……
我特么不是要破解他們的腳本么,怎么就玩上了還玩得挺嗨?
好吧,關閉游戲,開始破解。先看看apk。直接解壓。在assets/src下面就是所有的lua腳本了(順便打開res下面看了看原畫,簡直……):
這是cocos2dx-lua(也可能是quick-cocos2dx)做的啊,真是令人懷念的東西。雖然我只用過純C++的cocos,lua的還沒碰過。
用記事本打開Lua文件的話,可以看到共同點是使用jts開頭:
按照常理,解密Lua的邏輯就在編譯后的so文件里面了。lib\armeabi下面有兩個so,一個100多K,一個10多M,那么肯定大的就是正主。小的那個百度了一下名字,似乎是某個銀行的SDK?不管了。接下來分析so文件。祭出神器IDA打開SO文件,可以看到里面cocos自己的類都沒有CC的開頭,說明是3.x的:
因為明確了這個游戲是cocos做的,所以看IDA生成的反匯編C代碼有原版對照了。找了下硬盤,以前寫EasyLive時的3.2還在,cocos new一個lua工程輔助分析。依稀記得在C++版的cocos中,如果要讀取加密的pvr.ccz圖片,一般是在AppDelegate::applicationDidLunchFinished中進行的。於是打開cocos工程中的該方法,在這里就是設置XXTEA加密參數的部分:
然后在IDA中搜索這個方法,F5生成C++代碼,對比一下看:
是不是非常相似,而且看到了兩段關鍵的字符串?在cocos工程中,LuaStack::luaLoadBuffer里面做了XXTEA解密的操作,心中想:看來這波要解密出腳本輕而易舉啊!研發團隊根本不敢說什么,復雜的加密算法也沒有……哎呀奶不死的,媽的老子是專業程序員好嗎?專業程序員,這種代碼都看不懂啊?這怎么奶死嘛!有IDA的情況下怎么會解不出你告訴我,直接復制代碼都解出來了。開新項目穩破解,復制代碼也破了,不可能失敗的,不可能的……
於是我把cocos中的xxtea.h、xxtea.c提出來開一個控制台工程,然后照着前面的參數對腳本進行解密試試。結果是爆炸了,根本解不出。很明顯的,代碼中設置的xxteaSign是一個比較長的字符串,而腳本中的簽名只有三個字符(jts)。所以,這個游戲中對腳本的解密不是直接使用xxtea算法的,甚至可能根本就沒有使用xxtea。(小色:IDA騎臉是你自己玩得菜,跟我有什么關系)
因為cocos在讀取lua的時候必然要經過LuaStack::luaLoadBuffer方法。所以在IDA中打開LuaEngine::executeScriptFile,一路跟蹤進去,最后進到了LuaStack::luaLoadBuffer。把邏輯整理一下就是:
整理的時候我不小心掉進坑里掙扎了半天。strcmp和strncmp這兩個函數,字符串相等返回0,不相等返回1。因為我用C#習慣了,所以直接以為相等返回1(true),用XXTEA搞了半天沒搞出來……
分析一下加密后的腳本內容,這里會走使用BlowFish解密數據的流程,並沒有使用XXTEA,所以自己把自己奶死了……接下來得照着IDA代碼的算法寫一個解密的工程。有些變量看着不舒服,可以單擊選中變量,然后按N鍵,就可以重命名變量了。寫好之后,處理src/main.lua文件試試:(這個文件是cocos生成的,直接放出來也無所謂了)
漂亮!一個文件解出了,是不是其他文件也可以用這一套來解密呢?
在cocos工程中查找LuaStack::luaLoadBuffer的所有引用,發現它在cocos2dx_lua_loader中被調用了一次;再查找cocos2dx_lua_loader,在LuaStack::init中有設置該回調的代碼。所以可以肯定,這里就是Lua層調用腳本的入口了。在IDA中查找對應的地方,也是一樣的,說明他們沒有做特殊處理,所有的Lua腳本讀取的時候,都會走LuaStack::luaLoadBuffer進行解密。所以剛才寫的解密工程對所有的lua腳本都可用。通過分析IDA代碼,可以分析出他們的腳本是這么個文件格式:(一個格子表示一個字節)
於是可以寫一個GUI,將所有的腳本解密了。GUI想怎么做都可以,我用的C#調用dll的方式。
看了下代碼,很意外的是對規范比較注重,從游戲體驗上,細節處理比較好,也不像小公司的作品。那么是出自哪個公司之手呢?代碼中看到了“武將”等詞匯,難道是一個換皮游戲?
再吐槽一下C++層的代碼。IDA中可以看到很多加了j_j_前綴的底層方法,比如這個:
點進去:
再點進去:
其實這次破解說不上是用了什么牛逼的技術,完全就是參照着cocos源生項目,然后跟着IDA反匯編出來的代碼分析。大牛們看了勿噴。
不過我覺得我還是捍衛了深海提督的尊嚴!
最后說一下如何避免自己的項目被這種方式攻破。
一般來說可以對so文件加殼,使IDA打開這個文件的時候,就報錯退出;或者修改一下加密類的名字,這樣破解者就找不到你用的什么方式加密了,拿着密鑰也沒用(不過費點心思還是可以把IDA中的代碼整理出來當解密模塊用的);再或者使用更高級的加密方式,但是別人可以用動態調試直接hook luaL_loadbuffer拿到解密后的腳本……
加密方法有一千種,解密方法更有一萬種,如何有效保護自己項目的資源是一個值得研究的問題。
至於對這個游戲的開發商還有沒有什么想說的呢?——“我今天得罪了你們一下!”
如果說還有一句的話,那應該是:——“別抄襲,做個有骨氣的開發商。多花點功夫在原畫上,至少有機會可以像戰艦少女一樣成功,而抄襲,注定一輩子上不了台面。”
很慚愧,就做了一點微小的工作,謝謝大家!