1. 程式人生 > >12306 Android客戶端的libcheckcode.so解密及修復

12306 Android客戶端的libcheckcode.so解密及修復

       這篇文章純粹屬於安全分析研究,請勿用於非法用途。如有侵犯到廠家,請告知作者刪除

      12306Android客戶端每個請求包都會帶個baseDTO.check_code(如下圖)作為資料包安全及完整性校驗碼,這個校驗碼由libcheckcode.so生成

    


如果需要模擬購買火車票的過程,就要呼叫這個libcheckcode.so,不過這個SO使用dlopen無法載入。其ELF頭,如下圖所示,紅框內表示ProgramHeader,裡面的內容不符合elf格式規範


   那它是如何載入起來的呢,猜測是藉助了其它SO做了解密,下圖列出lib/armeabi裡的SO

   分析後確定解密的工作是由libDexHelper.so來做的。 libDexHelper.so是最先載入的so,application呼叫的時候就載入了這個SO

  libDexHelper.so自身做了加殼,這個殼不用花心思去脫,等它載入起來後,整個SO DUMP出來,將記憶體檔案對齊修正一下就可以用IDA分析了

   libDexHelper.so帶有JNI_OnLoad,它會呼叫一個JNI_ALIYUN_ONLOAD函式

   從函式名字上看,這個安全方案應該是由阿里雲來做的

   JNI_ALIYUN_ONLOAD內會做如下HOOK動作


   也就是HOOK了dlopen,dlsym,_read,_open,mmap2五個函式,當載入libcheckcode.so的時候,會呼叫這五個函式,呼叫流程如下:

   先呼叫dlopen,這裡網上借個dlopen的呼叫流程圖


這裡的load_library會先呼叫_open開啟檔案,然後呼叫_read,再然後呼叫mmap2,將檔案對映到記憶體


mmap2的hook過濾函式,當發現是libcheckcode.so檔案時,會進行解密。

實際的解密程式碼,F5後,發下圖所示:


   是不是比較亂,它把跳轉和迴圈改成了while switch方式,讓人看得很糾結,所有長一點的函式都是這個樣子。

   等mmap2全部呼叫完了,把libcheckcode.so的記憶體DUMP出來,header如下圖所示


已經變得正常了,這個檔案直接IDA分析是沒有結果的,需要將header裡的檔案偏移改成記憶體偏移,因為mmap已經將檔案內容按記憶體對齊方式來存放了。

    做一下對齊的修復,這個SO就可以用dlopen正常呼叫了。那麼是不是大功告成了呢,我們寫個12306的demo來呼叫這個so.

    按MobileTicket逆向出來的程式碼描一個下面的類:

發現呼叫會CRASH,CRASH的偏移地址是1438a4,用IDA看下這段程式碼,這個地址就是checkcode的首地址

    

    為什麼會這樣?回想一下,dlopen完了,還會呼叫初始化函式init_proc,IDA看下匯出函式,確實存在一個叫.init_proc的函式

   可以確定這裡的init_proc就是殼程式碼,它還會繼續對so進行解密,解密完了,這裡才會有程式碼。

   但是解密完了,這裡執行仍然會出錯。

   什麼原因呢?還有一個dlsym的HOOK函式沒看

  

         這個程式碼看得還是挺糾結的,就從它的return值往前推吧,基本可以確定它會返回wrapHook返回的記憶體地址。那就HOOK wrapHook,看它返回什麼內容。

    把wrapHook返回的記憶體塊DUMP出來,用IDA分析

    圖上已經對這段程式碼做了標註。分析過程比較囉嗦,這裡直接講下結果吧。

  dlsym的HOOK函式 功能:

  如果要獲取的函式名是Java_com_MobileTicket_CheckCodeUtil_checkcode,就呼叫wrapHook函式,返回wrapHook的返回值做為這個函式的地址。

  wrapHook返回的程式碼段對Java_com_MobileTicket_CheckCodeUtil_checkcode函式重新做了一下包裝,先呼叫so_prefix_wrap對Java_com_MobileTicket_CheckCodeUtil_checkcode函式進行解密,然後呼叫真實函式,呼叫完了再用so_postfix_wrap加密回去。

    這裡還有個細節要注意一下,這裡的真實函式地址0x7bad6865是865結尾的,而我們的Java_com_MobileTicket_CheckCodeUtil_checkcode匯出函式是以8a4結尾的,除了表示指令集不一樣以外,同時指向的地址也是不一樣的。

    整理一下這個libcheckcode.so的加密方式,解密出來需要經過三個過程,先是mmap2解密,然後init_proc脫殼解密,最後呼叫checkcode函式的時候,還要執行時解密。

    也就是說殼程式碼執行完後,checkcode還是處於加密狀態,要使得libcheckcode.so能正常執行,init_proc之後還需要一次執行機會

    怎麼提供這個執行機會呢,想到兩個辦法,一個是patch  init_proc函式,使其執行完後再執行一段解密程式碼,還有一個辦法是增加init_array

 

  上圖是dlopen裡的一個程式碼片斷,可以看到,init_func執行完後,還會再執行init_array指向的函式陣列。

  patch程式碼不太好玩,這裡選擇增加init_array的辦法

   從下圖可以看到,這個SO本身存在一個大小4的init_array,可以放一個函式地址

但是指向的地址是0

   只要在164dd8放一個地址就可以了。

   不過還有個問題,init_array指向的函式地址陣列是需要重定向的,還需要在重定向表裡,把這個地址給加上

   檢視重定向表,發現重定向表的後面已經填上了其它結構的資料,並無空間來擴充套件。

那就只有整體搬家了。

    這是dynamicsection解析到的重定位表的偏移0x1dc4和大小0x130

    將這裡的0x1dc4改成其它偏移,就可以對它進行搬家了,大小可根據需要擴大

    要搬到哪裡去呢,armelf檔案的結構比較緊湊,難以在原有檔案上找到空間,只有另外擴充空間了

    從上圖可以看到,第2個programtable只是用來指示dynamic section,第1個program table佔據檔案的後半部分,只要把擴充的內容放到檔案末尾,然後相應增加FileSize和MemSize兩個就可以

    擴充完了,把重定位表搬過去,並增加四位元組大小

    重定位表搬完了,init_array裡的函式地址指向哪裡呢。這個函式用來對checkcode函式進行解密。

    要怎麼去解密呢,逆向演算法成本比較高,就直接把so_prefix_wrap執行後解出來的記憶體直接copy到原位置好了。把要拷貝的源和拷貝函式都放到第1節擴充的空間裡去。

    用C語言寫個拷貝記憶體的程式碼,編譯後,把那段拷貝函式,填到init_array函式指向的地址,再做一些必要的修改,讓它可以正常執行。

    最後一步把第1節的Flag加上可執行屬性,改成跟第0節一樣,都為RWX就行了。

    至此,libcheckcode.so可以單獨執行,不再需要藉助libDexHelper.so的解密。

   (建立了一個Android逆向分析群,歡迎有興趣的同學加入,群號碼:376745720)