初识Frida--Android逆向之Java层hook (二)
目錄
- 初識Frida--Android逆向之Java層hook (二)
- ?????????apk的安裝與分析
- ?????????????????流程分析
- ?????????????????hook點分析
- ?????????JavaScript代碼構造與執行
- ?????????????????0x00 hook getMac()
- ?????????????????0x01 計算秘鑰Key
- ?????????????????0x02 調用showPremium獲取答案
- ?????????總結
?
?
今天繼續一個新的示例,同樣采用CTF作為例子,難度稍微加大了一點,如果對Frida基本的使用還不是很了解,建議先看看之前的文章初識Frida--Android逆向之Java層hook (一)
博客同步:訪問
?
文章涉及到的知識點:
- 怎么使用javascript實例化類并調用類方法
- 怎么在"jscode"中增加自定義javascript方法
- 怎么較為靈活的hook類方法
apk的安裝與分析
示例下載:whyshouldIpay
?
下載apk后安裝,一樣還是先來看看是什么功能,這是一個比較簡單的驗證程序,簡單的使用后,了解到PREMIUM CONETNT內容需要輸入License驗證后才能查看。那估計PREMIUM CONETNT按鈕中的內容應該就是答案了吧。
流程分析
使用jadx將apk反編譯出來,分析,在AndroidManifest.xml中找到了啟動的Activity是LauncherActivity。
?
?
找到其中驗證的主要代碼verifyClick,分析如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public void verifyClick(View v) { ????//第一個驗證,將輸入的Licese通過網絡驗證,但這個肯定是通不過的,這是一個可能需要繞過的點。 ????????try?{ ????????????InputStream?in?=?new URL("http://broken.license.server.com/query?license="?+?((EditText) findViewById(R.id.text_license)).getText().toString()).openConnection().getInputStream(); ????????????StringBuilder responseBuilder?=?new StringBuilder(); ????????????byte[] b?=?new byte[0]; ????????????while?(in.read(b) >?0) { ????????????????responseBuilder.append(b); ????????????} ????????????String response?=?responseBuilder.toString(); ?????//網絡驗證需要服務器返回?"LICENSEKEYOK",才能進行下一步 ????????????if?(response.equals("LICENSEKEYOK")) { ??????//當網絡驗證成功后,生成激活秘鑰,并寫入到preferences文件中 ????????????????String activatedKey?=?new String(MainActivity.xor(getMac().getBytes(), response.getBytes())); ????????????????Editor editor?=?getApplicationContext().getSharedPreferences("preferences",?0).edit(); ????????????????editor.putString("KEY", activatedKey); ????????????????editor.commit(); ??????//這樣便成功激活 ????????????????new Builder(this).setTitle((CharSequence)?"Activation successful").setMessage((CharSequence)?"Activation successful").setIcon(17301543).show(); ????????????????return; ????????????} ????????????new Builder(this).setTitle((CharSequence)?"Invalid license!").setMessage((CharSequence)?"Invalid license!").setIcon(17301543).show(); ????????} catch (Exception e) { ????????????new Builder(this).setTitle((CharSequence)?"Error occured").setMessage((CharSequence)?"Server unreachable").setNeutralButton((CharSequence)?"OK", null).setIcon(17301543).show(); ????????} ????} |
在verifyClick中可以知道生成激活秘鑰的算法是MainActivity.xor。
| 1 | String activatedKey?=?new String(MainActivity.xor(getMac().getBytes(), response.getBytes())); |
來到MainActivity中,查看該方法,看上去筆算起來還是比較麻煩。
| 1 2 3 4 5 6 7 | public static byte[] xor(byte[] val, byte[] key) { ??????byte[] o?=?new byte[val.length]; ??????for?(int?i?=?0; i < val.length; i++) { ??????????o[i]?=?(byte) (val[i] ^ key[i?%?key.length]); ??????} ??????return?o; ??} |
接下來當程序被激活成功后,點擊PREMIUM CONETNT按鈕,會調用MainActivity中的方法,可以看到它將MAC,以及生成的Key發送到了MainActivity中。
| 1 2 3 4 5 6 | public void showPremium(View view) { ???????Intent i?=?new Intent(this, MainActivity.class); ???????i.putExtra("MAC", getMac()); ???????i.putExtra("KEY", getKey()); ???????startActivity(i); ???} |
在MainActivity的onCreate方法中,看到了最終答案生成的native方法stringFromJNI(key, mac)。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | protected void onCreate(Bundle savedInstanceState) { ?//獲取Intent傳遞過來的值 ????????String key?=?getIntent().getStringExtra("KEY"); ????????String mac?=?getIntent().getStringExtra("MAC"); ????????if?(key?==?"" || mac == "") { ????????????key?=?""; ????????????mac?=?""; ????????} ????????super.onCreate(savedInstanceState); ????????setContentView((int) R.layout.activity_main); ?????//調用native函數,算出答案 ????????((TextView) findViewById(R.id.sample_text)).setText(stringFromJNI(key, mac)); ????} |
好,現在源代碼分析基本上能夠理清楚了,大概的過程就是這樣。
hook點分析
接下來重點就是要尋找hook點,經過剛才解題流程的分析,得出hook思路如下:
JavaScript代碼構造與執行
0x00 hook getMac()
先來一個簡單的示例,看看getMac()方法返回的的是什么,采用的方法是hook?showPremium,這樣就能通過點擊PREMIUM CONETNT按鈕直接得到getMac()的返回值。
?
JavaScript代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | js_code?=?''' ????Java.perform(function(){ ????????var hook_Activity = Java.use('de.fraunhofer.sit.premiumapp.LauncherActivity'); ????????//hook showPremium從而方便直接點擊按鈕得出Mac值 ????????hook_Activity.showPremium.implementation = function(v){ ????????????//因為showPremium,getMac()均在LauncherActivity類中,所有直接通過this就能直接調用getMac()方法 ????????????var Key = this.getKey(); ????????????var Mac = this.getMac(); ????????????send(Key); ????????????send(Mac); ? ????????} ????}); ''' |
完整python代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import?frida,sys ? ? def?on_message(message, data): ????if?message['type']?==?'send': ????????print("[*] {0}".format(message['payload'])) ????else: ????????print(message) ? ? js_code?=?''' ????Java.perform(function(){ ????????var hook_Activity = Java.use('de.fraunhofer.sit.premiumapp.LauncherActivity'); ????????hook_Activity.showPremium.implementation = function(v){ ????????????var Key = this.getKey(); ????????????var Mac = this.getMac(); ????????????send(Key); ????????????send(Mac); ? ????????} ????}); ''' ? ? session?=?frida.get_usb_device().attach("de.fraunhofer.sit.premiumapp") script?=?session.create_script(js_code) script.on('message',on_message) script.load() sys.stdin.read() |
運行看看結果:
0x01 計算秘鑰Key
接下來開始真正第一步的hook,將mac值與“LICENSEKEYOK"通過MainActivity.xor獲取秘鑰Key。那就直接hook?getKey方法吧,這樣可以自己來構造秘鑰Key。
仔細分析,會發現在這一步中可能會遇到下面的問題:
怎么將javascript參數進行類型轉換并傳遞到java語言中?其實方法很簡單,既然java是強類型語言,那就根據它要求的類型傳遞對應參數即可,看看它參數的類型。
| 1 2 3 4 5 6 7 | public static byte[] xor(byte[] val, byte[] key) { ????????byte[] o?=?new byte[val.length]; ????????for?(int?i?=?0; i < val.length; i++) { ????????????o[i]?=?(byte) (val[i] ^ key[i?%?key.length]); ????????} ????????return?o; ????} |
那么,在javascript代碼中,先準備一個將字符串類型轉換為byte[]類型的方法stringToBytes,再通過實例化MainActivity類的方式調用xor(),然后還需要一個將byte[]回轉為String的方法,因為秘鑰key是Sting類型的。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | js_code?=?''' ?????//字符串轉換byte[]的方法 ?????stringToBytes = function(str) {? ????????var ch, st, re = []; ????????for (var i = 0; i < str.length; i++ ) { ????????????ch = str.charCodeAt(i);? ????????????st = [];???????????????? ? ???????????do {? ????????????????st.push( ch & 0xFF );? ????????????????ch = ch >> 8;????????? ????????????}??? ????????????while ( ch );? ????????????re = re.concat( st.reverse() ); ????????}? ????????return re;? ????} ????//將byte[]轉成String的方法 ?????function byteToString(arr) {? ????????if(typeof arr === 'string') {? ????????????return arr;? ????????}? ????????var str = '',? ????????????_arr = arr;? ????????for(var i = 0; i < _arr.length; i++) {? ????????????var one = _arr[i].toString(2),? ????????????????v = one.match(/^1+?(?=0)/);? ????????????if(v && one.length == 8) {? ????????????????var bytesLength = v[0].length;? ????????????????var store = _arr[i].toString(2).slice(7 - bytesLength);? ????????????????for(var st = 1; st < bytesLength; st++) {? ????????????????????store += _arr[st + i].toString(2).slice(2);? ????????????????}? ????????????????str += String.fromCharCode(parseInt(store, 2));? ????????????????i += bytesLength - 1;? ????????????} else {? ????????????????str += String.fromCharCode(_arr[i]);? ????????????}? ????????}? ????????return str;? ????} ????//hook 代碼 ????Java.perform(function(){ ????????var hook_Activity = Java.use('de.fraunhofer.sit.premiumapp.LauncherActivity'); ????????var MainActivity = Java.use('de.fraunhofer.sit.premiumapp.MainActivity') ????????var LicenseStr = "LICENSEKEYOK"; ????????//hook getKey()方法,直接構造密碼,而不從preferences讀取 ????????hook_Activity.getKey.implementation = function(){ ????????????//獲取Mac ????????????var Mac = this.getMac(); ????????????//實例化MainActivity ????????????var instance = MainActivity.$new(); ????????????//類型轉換 ????????????var MacByte =stringToBytes(Mac); ????????????var LicenseByte = stringToBytes(LicenseStr); ? ????????????send("MacByte:"+MacByte) ????????????send("LicenseByte:"+LicenseByte) ????????????//調用實例化對象的xor方法 ????????????xorResult = instance.xor(MacByte,LicenseByte); ????????????send(xorResult); ????????????//類型回轉 ????????????var Key = byteToString(xorResult) ????????????send(Key); ????????????return Key; ????????} ????????hook_Activity.verifyClick.implementation = function(view){ ????????????this.showPremium(view); ????????} ????}); ''' |
接下來,執行看看,能不能獲取秘鑰Key。
不知道怎么啟動模擬器中的frida-server,以及端口轉發,可以先看看初識Frida--Android逆向之Java層hook (一)
啟動python腳本,在模擬器中直接點擊PREMIUM CONTENT,即可看到執行結果。
0x02 調用showPremium獲取答案
前面2個步驟,可以說是已經完成90%了,接下來只需要在hook一個能夠觸發showPremium方法的即可。方法就隨意了,這里采用hook verifyClick的方式,這樣點擊app上的VERIFY按鈕,觸發verifyClick方法去調用showPremium,進而獲得最終答案。
| 1 2 3 | hook_Activity.verifyClick.implementation?=?function(view){ ???????????this.showPremium(view); ???????} |
啟動腳本,點擊app上的VERIFY按鈕看看執行結果:
?
完整python代碼:下載
總結
通過上面的例子,可以學習在java層怎么使用frida實現:
以及學會怎么構造和使用自定義javascript方法。
當然這還僅僅只是一個開始.....
?
https://bbs.pediy.com/thread-227233.htm
總結
以上是生活随笔為你收集整理的初识Frida--Android逆向之Java层hook (二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 初识Frida--Android逆向之J
- 下一篇: 进阶Frida--Android逆向之动