Android逆向分析案例——某點評APP登陸請求數據解密

今天,七夕,單身23載的程序汪,默默地寫着博客~

上一次的逆向分析案例中講了如何去分析某酒店的APP登陸請求,爲了進一步學習如何逆向分析以及學習其他公司的網絡傳輸加解密,本次案例將繼續就登陸請求的數據加密進行分析,實現從網絡抓包的密文到明文的轉換,這次成爲炮灰的是某點評的APP~

首先,分析的步驟還是和上一次分析某酒店的APP那樣:

反編譯-->找關鍵代碼-->分析請求代碼加解密原理-->驗證密文到明文準確性

分析步驟的重點主要是第3步和第4步,而且這兩步往往是相互結合的,就有點像軟件上的單元測試一樣,每分析完一個加解密的地方就編寫相應的測試代碼去驗證是否是分析中得到的加解密原理。

本次用到的工具有:APKTool(反編譯),Sublime Text(smali閱讀器),Xposed(hook方法,驗證是否調用),Cydia Substrate(hook方法),Android Studio(編寫Xposed和Cydia的hook模塊),Eclipse(編寫測試代碼驗證加解密原理),wireshark(抓包),IDA PRO(看so庫),測試機。

說明:1、和上次不同,之後的分析我基本不會再用dex2jar和jd-gui這兩個工具,爲什麼呢?因爲它從smali轉java不可靠,錯的地方很多,只能輔助用,而且現在對smali語法已經比較熟悉,所以一般會直接看smali文件進行分析,嗯,這是進步~

2、爲什麼用了Xposed進行hook了,還要用Cydia來hook?因爲APK反編譯後,若體積較大,往往會分割成幾個smali子文件夾,Xposed只能hook第一個smali文件夾下的smali文件,第二個smali文件夾下的smali文件是無法hook得到的,原因不明;其次,Cydia還能hook本地方法,也就是標記爲native的方法。那爲什麼還用Xposed呢?這個我表示實在慚愧,Cydia最近才用,還不是很熟悉。。。

3、Xposed和Cydia都需要手機root過,而且最好不要同時安裝在同一部手機,因爲有可能會死機,或者發生日誌輸出衝突等情況。       

明確需求:分析登陸請求數據的加密原理。

開始:

----------------------------------------------------------

利用APKTool對目標APK反編譯,並用Sublime Text打開:


   我們可以看到在整個反編譯的工程文件夾下有三個smali的文件夾,其中第二個和第三個smali文件夾下的smali文件是無法用Xposed工具來hook的,所以需要用到Cydia Substrate。

通過關鍵字符串找關鍵代碼,我們可以從登陸請求的網絡抓包觀察,輸入賬號密碼後,在wireshark的過濾器輸入“login”,按登陸的那一瞬間看看wireshark:


我們可以抓到一個TCP包,那麼打開這個TCP流看看:


從抓包的TCP流可以看到有一段明文頭(json格式)和拼接了一大段密文,很好,那段密文就是我們要解密的~

這裏,我們可以推斷,登陸沒有用HTTP協議,所以我們可以推測登陸請求是用Socket類寫的請求,其次,我們可以看到明文中的關鍵字符“u”,“m”,“i”等。

So,我們在打開的sublime text代碼閱讀器上通過“Find in folder”的功能搜索關鍵詞“u”:


有三個文件,先看第一個文件,可以看到m.smali文件裏有“u”的身影,那麼我們打開m.smali文件,並找到“m”的字符的位置:


可以看到“u”,"i"關鍵詞,並且進行json的拼接,而且看到下面的Socket了嗎?基本可以確定這個m類就是一個socket通信類,還有密文發送到網絡的入口OutputStream~

那麼既然知道了入口,我們就要開始逆方向跟蹤數據是怎麼到達這裏的,並找出中間的加密位置,也即是關鍵代碼:


我們編寫Xposed模塊hook該方法的第4個參數,也即是那個數組,並以十六進制的形式打印出來:


然後我們通過打印出來的日誌,複製一小段去wireshark抓包窗口那裏驗證一下:


將數據顯示設置爲原始數據,然後ctrl+f查找複製過來的那一小段日誌,發現抓包的數據裏包含這一小段,說明確確實實是密文的byte[],驗證成功。

既然知道實例m的字段f是數據的密文,那麼我們就沿着該數據去追溯前面的代碼。

我們可以根據smali語法格式,編寫需要找的f字段形式   “Lcom/dianping/nvnetwork/e/q;->f:[B”  來進行“Find in folder”,找到字段 f 被賦值的位置:


可以看到有4處地方,下面的m.smali文件不用看了,因爲都是“iget-object”,就是取值,因爲我們需要知道是誰給f 字段賦值了,所以我們只關注“iput-object”,因此,我們可以直接打開e.smali文件,並找到該字段被賦值的地方:


可以看到,數據byte[ ]是從一個InputStream轉化而成的,那麼這個這個InputStream又是從哪來的呢?從參數寄存器可以看到v2寄存器,那麼從上面找v2寄存器的賦值位置:


該InputStream是有一個u的實例調用h( )方法得到的。那麼疑問就來了,從InputStream到byte[ ]會不會有加密?先說,沒有的,但還是懷疑?很好,hook吧,反正一開始我也是那麼多疑,雖然費時間,但一步一步來,心裏踏實~

但這裏有個關鍵的地方,因爲你要hook的參數是InputStream,也就是意味着你要將InputStream的流read到ByteArrayOutputStream內再轉byte[ ],這時你必須要對進行了此操作的InputStream調用reset方法還原,因爲處理過流的同學都清楚,InputStream只能讀一次,若要重新使用,必須得進行mark,reset等操作,不然你的InputStream被修改了,原APP調用該方法時的參數前就已被Xposed修改,造成登陸失敗,從而抓不了包,導致沒有數據校驗,所以即使你hook到InputStream的內容也沒有抓包數據給你校驗,這是很蛋疼的,反正我之前不知道reset這個方法,在這裏浪費了不少時間,實在慚愧~

下面是示例:


好了,我們可以知道InputStream的內容還是密文,那麼我們繼續跟蹤,我們去看看u類,因爲該類相當於一個Request類,InputStream就是u類的g 字段。

那麼我們hook一下h()函數吧:


可以看到,h( )方法返回的結果先是明文,然後是密文,因爲h( )相當於javabean裏的get( )方法,沒有對InputStream進行處理,所以我們有理由推測,該h( )方法先是提供明文給加密函數,加密完後再調用該方法將InputStream提供給Socket的網絡入口,那麼h( )提供明文這段代碼,會先在哪裏調用呢?

接下來,我要放大招了,但在放大招之前先問:這樣一步一步的找數據是不是有點低效率?沒錯,實在是太繁瑣了,那麼看招~

在hook方法中刻意拋異常,通過異常路徑找數據來源:(因爲h( )方法是沒有參數的,我們刻意製造一個參數傳進去,製造異常,並觀察異常日誌)


其實,使用這種方法是有一定風險的,因爲雖然是可以看到方法一層一層的調用位置,但你不能確定數據是否在這一系列嵌套函數中是否貫穿,這是缺點。

但還是建議使用的,尤其是針對本本案例中存在兩個常見的問題,使用該方法往往能事半功倍~

而且,當你在“Find in folder”中搜索“Lcom/dianping/nvnetwork/u;->h()”時,你會被嚇一跳,因爲很多地方都調用了h( )這個函數,難道你要一個一個smali文件都去找?顯然不能,這樣效率太低了,所以,還是使用該方法吧,哈哈~

問題實例:

第一種:假設  a 類   implements  b類,a類實現了b類的接口方法c( ),那麼在Sublime Text中根據smali格式

                不能搜  Lcom/.../a;->c()    而是  搜  Lcom/.../b;->c(), 即只能搜接口類->方法,但在Xposed中必須hook方法a( )而不是接口類的方法a( )。

第二種:假設  a 類    extends   b類, a類實現了b類的抽象方法c(), 那麼在Sublime Text中根據smali格式

                不能搜  Lcom/.../b;->c(),   而是 搜  Lcom/.../a;->c(), 即只能搜子類->方法,在Xposed中需要hook非抽象方法。

因爲筆者之前不知道這規則,明明數據追溯到那裏,但hook的時候卻發現沒有調用,原因就是那些繼承和接口的原因,所以追溯數據會很繞,阻力很大,那麼此時你就可以使用這種方法,讓異常路徑告訴你是在哪裏被調用了,然後一層一層地追蹤數據即可

從異常路徑中,我們可以先找com.dianping.h.f.a.o文件中的b方法和a方法:

先看看b方法:


很顯然,傳一個u類實例進去,又出來一個u類實例,我們可以猜測,這中間是否有貓膩,那麼事不宜遲,我們hook吧,比較前後兩個u類實例的字段 g(即InputStream)是否是一致的,若不一樣,那麼說明關鍵代碼就在裏面了。這裏hook需要掌握一些反射機制的知識,通過反射找到u類,再從u類找到字段g,Xposed寫法如下(因爲在Cydia中找不到相應的反射或找類的API,所以才一邊用Xposed,一邊用Cydia).


好,模塊寫好的了,安裝到手機上,然後Xposed框架會在通知欄提醒,模塊已更新,請軟重啓。。。

我們來看看日誌:


可以看到,before hook 之前是明文, after hook 之後是密文,那麼我們基本上已經可以確定,這個方法之後的代碼就是我們需要的關鍵代碼,也即是加密代碼。

我們回到smali文件裏,繼續看看這個方法裏面。

我們知道,h( )這個函數相當於get InputStream了,即從u類實例獲取 g 字段,之前說過,h( ) 前後既有密文,又有明文,那麼我們當時推斷 h( )先是把 InputStream get出來後,進行加密,再把密文封裝成 InputStream 的形式 回到 u類實例。

So,我們看看之前利用 異常路徑 所知道的 h( )調用的位置在哪裏。


我們可以在當前的smali文件裏搜索 “ .line 68” 字符串(不需要 “Find in folder”,就在當前smali內搜索):


在上面提到的  傳 u 返 u  的這個方法內,還有一個 InputStream 返 InputStream 的方法,我們可以繼續hook,這裏我就不貼出來了,結果一樣,根據hook到的參數InputStream內容判斷,確實就是 h( )提供的明文, 而返回InputStream的內容則是密文。

然後,我們找到p類,在p 類的 a(InputStream)InputStream 方法裏,它將 InputStream 作爲參數 初始化了 q 類,這是另外一個類,該類是一個處理加密的類:


因爲數據被作爲InputStream傳到q類實例去,那麼我們接着看q類,q.smali的代碼很少,除了一個構造函數外,還有一個 a( ) 方法, 該方法返回一個InputStream,更重要的是瀏覽一下這個a( )方法,我們可以發現了用於加密的東西:


我們知道,因爲java層容易被反編譯,一般加密這種核心代碼,往往會用C語言寫在SO庫裏(即使被反編譯了,也是一堆可讀性很差的彙編語言),防止反編譯,增強其保密性,然後通過本地調用,即native修飾的函數進行加密。

我們去找NativeHelper這個類:


這裏我就說出來吧, ne方法就是請求數據的加密函數, ndug就是服務器返回數據的解密。

到這裏,我們就不能用Xposed進行hook了,因爲這個smali文件是在smali_classes2裏,即第二個smali文件夾,而且這是個native方法,Xposed是無能爲力的。那麼我們用Cydia去hook這個ne函數吧:


接着我們將模塊安裝在裝有Cydia Substrate的手機上,安裝完後Cydia會在通知欄通知已更新模塊,然後軟重啓,跟Xposed一樣~

然後,我們看看日誌:


可以看到,ne這個函數有4個byte參數,均爲數組,其中before hook之前第一個數組是明文,第二個0數組,第三和第四個都是固定字節數組,after hook之後,第二個數組變成了密文,而其他三個數組沒有變化。

因此,我們可以推測,第三第四個數組應該是加密的key。

我們此時在看看NativeHelper這個類的構造體:


由“nh”這個字符串可知,這是一個libnh.so庫,那麼我們去找APKTOOL反編譯出來的那個文件夾下的lib文件夾,看看是否有這個so庫:


爲了查看這個so庫,我們需要利用 IDA PRO 打開,該工具是世界上最強的反編譯工具,網友如此說~那麼我們就用它打開吧:


如果你不是大牛,勸你不要去閱讀裏面的彙編,我這裏用的是IDA PRO 6.6,找到ne函數的代碼,然後按“F5”功能鍵,將彙編轉成C語言,這個轉換並不一定準確,而且可讀性很差,都是一些指針操作,但我們可以從裏面找到一些有用的信息。

沒錯,AES_cbc,這是運用了AES加密的CBC模式,因爲CBC模式需要用到一個key和一個初始化向量IV,所以前面ne方法中的第三第四個固定字節數組也就知道是什麼回事了吧,對,第三個是key,第四個是向量IV,不懂什麼是CBC加密模式的自行百度。

這裏我不得不用紅字吐槽一下,大哥,你煞費苦心地用C寫個加密方法在so庫,在java層你懂得用“ne”這個迷之字符串聲明,在C裏卻用“AES_cbc”這麼顯眼的命名方式作爲方法名,爲什麼不用迷之字符串+註釋的方法呢?你是欺負我不懂什麼是 AES的CBC模式嗎?

好吧,菜鳥的我也沒資格說別人,繼續吧~

既然我們知道是AES加密,那就開始寫測試代碼測試吧,在eclipse上如此寫道:

	public static byte[] AESDecrypt(byte[] content, byte[] keyBytes, byte[] iv){
		
		try{
			SecretKeySpec keySpec=new SecretKeySpec(keyBytes, "AES");
			Cipher cipher=Cipher.getInstance("AES/CBC/NoPadding");
			cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));	
			byte[] result=cipher.doFinal(content);
			return result;
		}catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("exception:"+e.toString());
		} 		
		return null;
	}
將wireshark上面的抓包密文的原始數據(十六進制)複製到eclipse上面,然後調用該函數,即可得到明文字符串。



其實,那段明文中還有一段加密的內容,就是   cx=密文   這一段。

其實要解cx這一段也很簡單,首先,“Find in folder” 功能搜索 “cx” 字符串:


找到“cx”的smali文件有很多個,但我們可以隨便挑一個,不用去hook驗證是否是登陸的“cx”,因爲我們只需要知道其加密原理而已,其它的“cx”估計應該也是用同一套加密方法而已,那麼我們就打開第一個吧,剛好第一個跟login有關。

接着順藤摸瓜,一直跟着String或者byte[ ]這樣的數據去找,最後找到了一個加密類的方法a(String, String):


這個smali是在第二個文件夾裏的,所以要hook的話需要用Cydia,這裏我就不hook了,結果告訴大家,這是一個DES加密,第一個參數是明文,第二個參數是key,返回的結果就是一個   DES+Base64 的字符串, 我們在Eclipse上面編寫一下測試代碼,但我們先看一下剛纔那個cx的密文:


可以看到,cx裏面的密文含有%符號,這並不是Base64的編碼符號啊?沒錯,實際上它還套了一層URLEncode編碼上去,其中的“%2F”=“/”,“%0A”=換行符,“%2B”=“+”等,所以在調用下面的方法DES解密前先URL解碼,再Base64解碼,再DES。即  URL Decode-->Base64 Decode-->DES 的順序。

	public static byte[] DESDecrypt(byte[] content, byte[] keyBytes){		
		try {
			DESKeySpec keySpec=new DESKeySpec(keyBytes);
			SecretKeyFactory keyFactory=SecretKeyFactory.getInstance("DES");
			SecretKey key=keyFactory.generateSecret(keySpec);
			
			Cipher cipher=Cipher.getInstance("DES/CBC/NoPadding");
			cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(keySpec.getKey()));
			byte[] result=cipher.doFinal(content);
			return result;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("exception:"+e.toString());
		}
		return null;
	}
將剛纔AES解密的那一串明文中的 cx = 後面的數據截取出來,經過解碼後,結果:



cx明文json裏的的key-value中的key用了“A+數字”的形式,估計他們那邊還有一張key對應的值表,不管了,反正我們已經大概能知道里面就是一些imei的手機狀態信息。

到此爲止,我們已經完成了登陸請求的數據解密任務。

請求的返回結果也一樣,也是調用了AES_CBC進行解密,再通過GzipInputStream解壓後的到序列化的字節流,這裏就不繼續講了。

-----------------------------------------------------

結束。

紙上得來終覺淺,絕知此事要躬行~

別看我寫得那麼簡單,這裏博客我只是把正確的分析過程寫出來了,但實際上爲了解那麼一個解密內容,走了N多彎路,畢竟本人還是菜鳥的原因吧,不過正是走的彎路多了,你纔能有成長,有收穫,才能在下一次少走彎路~

路漫漫其修遠兮,吾將上下而求索~

最後,再次祝賀七夕快樂——單身23載的程序汪。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章