開(kāi)篇之前,我一直考慮這算不算侵權(quán)?只是興趣,如果有人告知侵權(quán)的話,立馬刪除。
我所說(shuō)的數(shù)據(jù)不是指拿別人app中的圖片資源。而是程序運(yùn)行所需要的數(shù)據(jù)。
App中的數(shù)據(jù)無(wú)非兩種,一種是網(wǎng)絡(luò)數(shù)據(jù),一種是本地?cái)?shù)據(jù)。網(wǎng)絡(luò)請(qǐng)求我們一般用抓包工具(Mac上的Charles 或者Windows上的Fiddler)來(lái)獲取Api,然后再用程序循環(huán)請(qǐng)求,獲取所有的數(shù)據(jù)。而另一種是app存儲(chǔ)到本地的數(shù)據(jù),一種是存儲(chǔ)到文件中的,一種是存儲(chǔ)到數(shù)據(jù)庫(kù)中的。

一次偶然的機(jī)會(huì),發(fā)現(xiàn)快快查漢語(yǔ)詞典這個(gè)app作為漢語(yǔ)詞典來(lái)說(shuō),還算是比較良心的app,免費(fèi),而且界面還是比較干凈的。而且詞庫(kù)比較全。所以我想獲取這個(gè)詞典的所有數(shù)據(jù),作為自己的詞庫(kù)使用。像這種詞典,一般漢字都用拼音散列開(kāi)了。所以我想獲取漢字和拼音的映射。好吧,用了一個(gè)周的時(shí)間,邊學(xué)習(xí),邊反編譯,并且學(xué)習(xí)了Smali語(yǔ)法。最終還是把數(shù)據(jù)解析出來(lái)了。
大體流程:數(shù)據(jù)解密--->數(shù)據(jù)格式化--->格式化后的數(shù)據(jù)

作為一個(gè)詞典來(lái)說(shuō),我感覺(jué)這個(gè)app做的相當(dāng)不錯(cuò)。有很多值得我們學(xué)習(xí)的地方。so,我給反編譯了。
-
首先確定數(shù)據(jù)來(lái)源。
分析一下數(shù)據(jù)到底來(lái)源于網(wǎng)絡(luò)呢,還是本地?cái)?shù)據(jù)存儲(chǔ)呢。這個(gè)很容易判斷,第一次安裝好app之后,斷開(kāi)網(wǎng)絡(luò),如果仍然能查詢,說(shuō)明本地有數(shù)據(jù)緩存,如果不能查詢,說(shuō)明必須要訪問(wèn)網(wǎng)絡(luò)。好,測(cè)試之后我們發(fā)現(xiàn)能查詢,說(shuō)明本地可定有一份可供查詢的數(shù)據(jù)存儲(chǔ)。
有些app會(huì)做雙重?cái)?shù)據(jù)存儲(chǔ)。也就是說(shuō)數(shù)據(jù)源,既有網(wǎng)絡(luò)也有本地,比如有道詞典,會(huì)做一部分本地存儲(chǔ),詳細(xì)信息要從網(wǎng)絡(luò)查詢。所以還要做,有網(wǎng)和無(wú)網(wǎng)情況下查詢?cè)斍槭遣皇且粯?,?lái)判斷本地是否存有所有的數(shù)據(jù)。經(jīng)過(guò)比較發(fā)現(xiàn)本地確實(shí)有完整的數(shù)據(jù)存儲(chǔ)
-
如果數(shù)據(jù)存儲(chǔ)在本地,存在哪了?
猜測(cè): 本地?cái)?shù)據(jù)
理由: 像
字--拼音--解釋這種數(shù)據(jù)結(jié)構(gòu),一般不適合存放到文件中。實(shí)踐:數(shù)據(jù)庫(kù)存放無(wú)非三個(gè)位置,一個(gè)是
/data/data/包名目錄下,一個(gè)是/sdcard/Android/data/包名,最后一個(gè)就是SD卡了,隨便建個(gè)文件夾就可以存儲(chǔ)。參考,Android文件存放位置,那么如何判斷數(shù)據(jù)庫(kù)文件存放在這三個(gè)位置中的那個(gè)呢?我們來(lái)一個(gè)個(gè)排除:
要訪問(wèn)真機(jī)的/data/data目錄必須要root權(quán)限。當(dāng)然我的測(cè)試機(jī)是root了的。發(fā)現(xiàn)里面并沒(méi)有任何有價(jià)值的數(shù)據(jù)庫(kù)。
/sdcard/Android/data/包名這個(gè)目錄是可以訪問(wèn)的,adb shell的方式訪問(wèn)之后也沒(méi)有發(fā)現(xiàn)任何有價(jià)值的數(shù)據(jù)。那就剩下最后一個(gè)sd卡了。這個(gè)比較麻煩,因?yàn)檫@個(gè)路徑不固定,可以任意創(chuàng)建文件夾。如果是測(cè)試機(jī),那么sd卡上沒(méi)有幾個(gè)文件夾比價(jià)好排查。這里可以用一些小技巧。比如我們app的包名為
com.kk.dict所以按照一般程序員命名規(guī)則,這個(gè)文件夾如果真的在SD卡上的話,那么有很大的可能性和kk或者dict有關(guān),哈哈,經(jīng)過(guò)查找,在sd卡上有個(gè)叫kkdict的文件夾,最后發(fā)現(xiàn)所有的離線數(shù)據(jù)庫(kù)都在這里面/sdcard/kkdict/dict/-
其實(shí)上面的三種排查方式比較大眾化,也就是可以用于所有的情況下的排查,但是在分析別人的app的時(shí)候,我們的方法還是比較靈活的,不拘泥于這么幾種,比如我們要分析的這個(gè)快快查詞典。并不需要這么麻煩。這個(gè)app在
我的-->設(shè)置-->功能包下載里面能夠設(shè)置離線的數(shù)據(jù)庫(kù)的存儲(chǔ)路徑。
Paste_Image.png 由上圖可以發(fā)現(xiàn),在這之前的所有分析,都不用了,從離線路徑可以分析出來(lái),這數(shù)據(jù)查詢就保存在本地,而且保存路徑都可以看到了。
結(jié)果: 和我們猜想的一樣。
-
好那接下來(lái)我們就從數(shù)據(jù)庫(kù)中拿數(shù)據(jù)了哦~
可以看到
/sdcard/kkdict/dict/目錄下主要有以下數(shù)據(jù)庫(kù):
快快查詞典包含的主要數(shù)據(jù)庫(kù)從名字結(jié)合app的界面可以大體分析出數(shù)據(jù)庫(kù)中存儲(chǔ)的內(nèi)容。我們來(lái)看詳解這個(gè)庫(kù),應(yīng)該存儲(chǔ)的是每個(gè)字的詳細(xì)解釋。用數(shù)據(jù)庫(kù)查看軟件查看一下我用的
Navicate(在Mac上沒(méi)發(fā)現(xiàn)什么好用的sqlite3可視化查看軟件)。打開(kāi)數(shù)據(jù)庫(kù),找到里面主要的表,這下懵逼了。表里的數(shù)據(jù)是用二進(jìn)制Blob存儲(chǔ)的。
xiangjie.db/xiangjie表下面的主要任務(wù)就是把這個(gè)zhujie的字段表示的數(shù)據(jù)解析出來(lái):how?這種分析主要從哪下手、我一般主要從兩方面下手:直接分析數(shù)據(jù)庫(kù);從界面反編譯代碼后,從代碼查詢。顯然后者難度很大,如果代碼混淆了,讀起來(lái)相當(dāng)困難。我們先來(lái)實(shí)驗(yàn)第一種方式,看是否能夠成功。也就是直接解析
zhujie字段的二進(jìn)制,翻譯成字符串。猜測(cè)這個(gè)二進(jìn)制是什么:有這么幾種可能,
對(duì)象,直接把數(shù)據(jù)所對(duì)應(yīng)的對(duì)象存到數(shù)據(jù)庫(kù),以二進(jìn)制的方式;字符串,把字符串轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)寫(xiě)入數(shù)據(jù)庫(kù);加密,這也是最頭疼的方式,如果真的加密了,必須去源碼中找到加密算法和相應(yīng)的解密密鑰。試驗(yàn):試著猜想一下,這種數(shù)據(jù)庫(kù)中可能存放對(duì)象嗎?我感覺(jué)以我的經(jīng)驗(yàn)來(lái)說(shuō)不大可能,這種存對(duì)象的方式,平臺(tái)適用性太低。如果使用java存的對(duì)象,只能用java讀出來(lái),那么這個(gè)庫(kù)完全無(wú)法在別的平臺(tái)(如:
IOS)上使用。這種不是不行只是不太理想,極少有程序員這么做。當(dāng)時(shí)我猜測(cè)的最大可能是字符串的二進(jìn)制格式,因?yàn)槲乙恢币詾檫@種數(shù)據(jù)沒(méi)必要加密。所以,我就試著驗(yàn)證了一下。如何讀數(shù)據(jù)庫(kù)就不說(shuō)了,參考;從數(shù)據(jù)庫(kù)以二進(jìn)制的方式讀入,然后用String解碼。一運(yùn)行,額。。。亂碼??
測(cè)試代碼及運(yùn)行結(jié)果
從上圖看出,以我多年解決亂碼的經(jīng)驗(yàn)(瞎搞),出現(xiàn)亂碼<font color="#ff0000">大多數(shù)</font>是由于編碼方式不正確引起的。好,編碼不對(duì)那我改還不行嗎?
于是有了下圖的測(cè)試代碼:

額。。。全是亂碼,從查詞的結(jié)果來(lái)看一定包含中文,我知道的能處理中文的常用編碼也就這么三種,你說(shuō)全是亂碼。這。。。
結(jié)果:上面猜想錯(cuò)誤,0.0,只能繼續(xù)猜測(cè)唄。
繼續(xù)猜測(cè): 會(huì)不會(huì)數(shù)據(jù)庫(kù)存放的是Base64的二進(jìn)制方式,作者會(huì)不會(huì)用Base64的方式對(duì)數(shù)據(jù)進(jìn)行了簡(jiǎn)單的加密? 但是仔細(xì)一想,Base64主要作用是把二進(jìn)制轉(zhuǎn)換成字符來(lái)顯示和表達(dá)的。把一個(gè)字符串,轉(zhuǎn)換成二進(jìn)制在轉(zhuǎn)換成Base64再換成二進(jìn)制,再存儲(chǔ)。好像能有這種想法的人有點(diǎn),那啥吧??既然沒(méi)有別的辦法也只能試試嘍。測(cè)試代碼就不給了,就是Base64的基本操作,就是流程繞了點(diǎn),后來(lái)測(cè)試發(fā)現(xiàn)也是亂碼。(:-
繼續(xù)猜想:看了數(shù)據(jù)庫(kù)的這個(gè)字段的數(shù)據(jù)真的加密了。只從數(shù)據(jù)庫(kù)入手好像無(wú)法解決。加密有難有易,但是無(wú)論如何,他既然會(huì)顯示到界面的數(shù)據(jù)是正常的明文,說(shuō)明即使加密了,apk源碼中也有解密代碼??戳爽F(xiàn)在找到這個(gè)解密的代碼是非常關(guān)鍵的。這個(gè)解密的代碼也有難有易,如果做的簡(jiǎn)單點(diǎn)就是一個(gè)java的Util類。如果做的難了,用jni做到so文件中。但愿他在java中吧。這樣反編譯起來(lái)還簡(jiǎn)單些。
測(cè)試:要把a(bǔ)pk中的dex拿出來(lái)反編譯成jar,然后查看這個(gè)jar中的源文件。具體如何反編譯拿到j(luò)ar,不再給出,可以參考這個(gè)。參考 至于查看瀏覽這個(gè)jar包中的java代碼,我主要借助兩個(gè)工具jd-gui-1.3.0.jar和jadx-0.6.0為什么要接著兩個(gè),我能力有限,查看反編譯后的java代碼,如果這個(gè)代碼被混淆了,讀起來(lái)相當(dāng)困難,而這兩個(gè)tool反編譯后的java代碼各有優(yōu)缺點(diǎn)。前者有代碼跟進(jìn)功能,后者的代碼反編譯的可讀性比較好,但是后者代碼綜合性比較強(qiáng),比如能簡(jiǎn)寫(xiě)的他會(huì)簡(jiǎn)寫(xiě)。這樣讀起來(lái)也比價(jià)困難。結(jié)合來(lái)看就輕松多了。
好,我們可以看到最終數(shù)據(jù)會(huì)被現(xiàn)實(shí)到,漢字查詢結(jié)果頁(yè)面。如下:

那么我們找到這個(gè)頁(yè)面,然后就可以進(jìn)行數(shù)據(jù)分析了,接下來(lái)的問(wèn)題,這個(gè)頁(yè)面怎么找??
猜測(cè):這是個(gè)Activity,既然是詳情頁(yè)面,那么根據(jù)中國(guó)程序員的英語(yǔ)水平命名的話,應(yīng)該和Detail 和 Activity有關(guān)。好找一下。

如圖所示,可以看到,這個(gè)jar里面大多數(shù)是沒(méi)有用的,都是些第三方的引入。被打包進(jìn)來(lái)的。真正我們寫(xiě)的代碼在如圖箭頭指向的包中。是主要的Activity。找來(lái)找去,還真找到了一個(gè)叫DetailActivity.class的類。哈哈...
打開(kāi)一看徹底懵逼:全是a啊b啊的。這特么怎么看。沒(méi)辦法,反編譯比人的工程代碼,需要極大的耐心。慢慢來(lái)看,有些小技巧在里面,我們并不需要全看,要想理解大體思路,也需要一定的代碼基礎(chǔ)。至少自己獨(dú)立架構(gòu)過(guò)一個(gè)項(xiàng)目?;蛘邊⑴c過(guò)很多項(xiàng)目。
思路:接下來(lái)要做的就是找到顯示著段文字的View--->找到這個(gè)View的賦值過(guò)程--->找到這個(gè)值得來(lái)源--->這個(gè)來(lái)源中一定包含解密過(guò)程這個(gè)思路想起了簡(jiǎn)單,但是操作起來(lái)想當(dāng)復(fù)制,我們可以看到這個(gè)DetailActivity.class類大約有幾千行代碼啊。如何找?
猜測(cè):據(jù)我所知,在Android常用的顯示文本的控件也就那么三個(gè)TextView,EditText,WebView。到底是哪個(gè),我們要借助于一個(gè)工具叫Android Device Monitor 這是分析比人程序布局的利器啊。如何使用?這個(gè)地方先空一下,改天補(bǔ)上 因?yàn)橛闷饋?lái)不難,但是描述起來(lái)很煩人。來(lái)看分析截圖:

從紅色箭頭標(biāo)出的部分看,6不6,這界面用了什么View,View的層級(jí)關(guān)系,View的Id都能得到。拿到了這些再去DetailActivity.class中分析是不是就簡(jiǎn)單多了??梢源_定這個(gè)就是為WebView賦值而已嘛。找到WebView的賦值的地方,也就找到了數(shù)據(jù)源了。這時(shí)候就興奮的去DetailActivity.class中找WevView了,我去,沒(méi)有???why?
猜測(cè):頁(yè)面用了Fragment,數(shù)據(jù)在Fragment中,界面用了組合View,WebView在組合View中。分析到這里似乎難以用一種固定的方式分析了,好像要憑感覺(jué)?怎么辦,轉(zhuǎn)向layout 的xml文件,找點(diǎn)突破口。這時(shí)候就用到j(luò)adx出廠了,為啥?自己對(duì)比。

從圖片中可以看到,我們很容易拿到了,這個(gè)activity的布局文件。打開(kāi)它。

分析后發(fā)現(xiàn)果然,主要的數(shù)據(jù)都放到了ViewPager中。好啊,接下來(lái)的主要工作就轉(zhuǎn)戰(zhàn)這個(gè)ViewPager,在這個(gè)DetailActivity中可定有這個(gè)Viewpager的引用。繼續(xù)用jadx。搜索這個(gè)ViewPager的id找到,他的引用。如下圖。

好嘛,兄弟,你在Activity中的引用叫this.B啊,讓我這個(gè)好找啊。既然找到了ViewPager,我們想要ViewPager中的每一個(gè)Pager的數(shù)據(jù),怎么找,找啥?找Adapter不論是啥,一定會(huì)有setAdapter這個(gè)方法。繼續(xù)搜索。this.B.setAdapter

一切如上圖。其實(shí)感覺(jué)好的可以看到我們的方框框起來(lái)的就是我們最終要找的。好這個(gè)PagerAdapter的名字竟然叫j。Adapter有兩種方式存在,一種是單獨(dú)的文件,另一種是匿類的方式,這就是我們的開(kāi)發(fā)常識(shí)了。先猜測(cè)就在本文件中吧。搜索Adapter.如下圖:

從上圖中我們可以分析出ViewPager的View集合了,this.a.C認(rèn)真讀下代碼,這個(gè)this指的是j也即是PagerAdapter的實(shí)例對(duì)象。而a,指的是DetailActivity.this,好了,集合對(duì)象找到了就是DetailActivity中的C,搜索this.C,結(jié)果如下圖:

圖片中包含唯一的自定義View就是DetailContentView,打開(kāi)它看看。哈,果然WebView就在這里面。如下圖:

這貨對(duì)外暴露的設(shè)置內(nèi)容的方法為a(string),好。在DetailActivity中可以看到:
this.U = (DetailContentView) inflate.findViewById(R.id.detail_zhujie_id);
this.V = (DetailContentView) inflate.findViewById(R.id.detail_xiangjie);
this.W = (DetailContentView) inflate.findViewById(R.id.detail_guhanyu);
this.X = (DetailContentView) inflate.findViewById(R.id.detail_kangxi);
this.Y = (DetailContentView) inflate.findViewById(R.id.detail_shuowen);
this.Z = (DetailContentView) inflate.findViewById(R.id.detail_wys_id);
我們從這里面隨便拿一個(gè)分析就行。我就選this.W吧。這個(gè)對(duì)應(yīng)詳情頁(yè)的古漢語(yǔ)展開(kāi)的詳情。
那么這個(gè)this.W要想設(shè)置數(shù)據(jù)可定調(diào)用了a方法。搜索this.W 大小寫(xiě)敏感。注意自己過(guò)濾一些無(wú)關(guān)的搜索。結(jié)果可以看到唯一符合條件的是:

可以看到,這個(gè)p.c(this,this.aX.c)+this.aX.c)就是獲取數(shù)據(jù)源的方法。猜測(cè),這個(gè)p.c方法可能是某個(gè)幫助類。對(duì)數(shù)據(jù)進(jìn)行處理,也可能是某個(gè)查詢類根據(jù)傳入的參數(shù)來(lái)查詢,到底是什么呢?我們用jd-gui來(lái)幫助我們跟進(jìn)代碼。來(lái)跟進(jìn)參數(shù)this.aX.c打這個(gè)東西。(P.S.這個(gè)p.c也是相當(dāng)有用的。劇透一下用于字符串的格式化)如下圖:

可以看到 aX就是上圖中a這個(gè)類的對(duì)象,而c就是這個(gè)對(duì)象的成員,在結(jié)合上面的拼音,我們斷定,這個(gè)a類是和數(shù)據(jù)庫(kù)表結(jié)構(gòu)對(duì)應(yīng)的Bean類。進(jìn)而我斷定這個(gè)a的外部類就是數(shù)據(jù)庫(kù)表的查詢類。查詢方法在上面。數(shù)據(jù)庫(kù)表結(jié)構(gòu)如下圖:

從上上個(gè)圖中,我們從a類的定義往上看,猜我看到了啥。哈哈,getBlob方法,很多時(shí)候感覺(jué)還是很重要的。一種熟悉的感覺(jué)有木有。一開(kāi)始的時(shí)候我們就是用他讀的數(shù)據(jù)的那個(gè)二進(jìn)制字段。從這行代碼往下讀,看不懂?我也看不懂。但是我讀出來(lái),這個(gè)a類中的c字段就是我們要的東西。繼續(xù)看這個(gè)c是怎么獲取來(lái)的?
locala.c = q.a(paramList,i) //paramlist 就是我們的二進(jìn)制byte數(shù)組,是數(shù)組的長(zhǎng)度,哈哈,這個(gè)方法就是解密方法嘍。
我們要做的就是找到,這個(gè)方法,把他提取出來(lái),就能解密數(shù)據(jù)所有的二進(jìn)制了。這是個(gè)靜態(tài)方法,是比較獨(dú)立的工具類。所以提取沒(méi)有什么難度?但是也遇到很多問(wèn)題,關(guān)鍵是有些加密代碼看不懂。
萬(wàn)里長(zhǎng)征走一半了?No,才剛剛開(kāi)始。還記得我們要干嘛嘛?要拿到解密后數(shù)據(jù)的數(shù)據(jù)。繼續(xù)吧:
用jd-gui跟進(jìn)代碼。q.a(byte[],int)。反編譯查找方法時(shí)候一定注意方法簽名。有可能都是特么的a 但都是重載,別找錯(cuò)了。這個(gè)方法傳遞一個(gè)加密后的二進(jìn)制數(shù)據(jù)和他的長(zhǎng)度進(jìn)去,返回一個(gè)解密后的字符串。
我發(fā)現(xiàn)不能再往下寫(xiě)了,再寫(xiě)人家的加密算法就給拿出來(lái)了。
來(lái)看這個(gè)方法的簽名:

P.S.看反編譯后的代碼要學(xué)會(huì)自動(dòng)過(guò)濾無(wú)關(guān)代碼
這個(gè)方法有用的即使三行。而這三行中唯一不知道的就是d也就是d.getBytes()中的d,繼續(xù)分析這個(gè)類看看這個(gè)d是如何獲取的。a(byte[],int)是個(gè)靜態(tài)方法,所以d不可能在構(gòu)造方法中初始化,一定在這個(gè)方法內(nèi)初始化的,為啥找不到???對(duì)比jadx,效果如下圖:

從上圖可以看到,jadx的反編譯結(jié)果,比jd-gui多了一個(gè)調(diào)用a()a的空方法,這才符合我的猜想嘛。接下來(lái)看看這個(gè)a()的定義,只要能拿到d的值。這個(gè)解密過(guò)程就算完成了。哈哈,這個(gè)不能貼了,這是人家的密鑰生成方式。不過(guò)這個(gè)方法我是真心看不懂,不過(guò)沒(méi)關(guān)系,這個(gè)方法沒(méi)有引用其他的東西,直接拷出來(lái),引入生成一個(gè)d就行了。至此解密完成。如下圖所示:

這不太對(duì)啊。這數(shù)據(jù)格式太丑了,還包含什么亂七八糟的字符?要處理這些就是前面所說(shuō)的字符串格式化。加入css樣式和加入html標(biāo)簽。這個(gè)過(guò)程更是相當(dāng)復(fù)雜。各種猜測(cè)和嘗試,其中還借助了Smali文件,重新打包,打印log等。不過(guò)還好,最終成功了,我給封裝成ExplainUtil,哈哈他的app中也是這么命名的,不過(guò)混淆過(guò)后就成了a,b,c。。。這種亂七八糟的東西了。
這篇文章寫(xiě)了五個(gè)小時(shí)~~~



