初識Frida--Android逆向之Java層hook (二)

Editor發表於2018-06-12

今天繼續一個新的示例,同樣採用CTF作為例子,難度稍微加大了一點,如果對Frida基本的使用還不是很瞭解,建議先看看之前的文章初識Frida--Android逆向之Java層hook (一)



文章涉及到的知識點:

怎麼使用javascript例項化類並呼叫類方法

怎麼在"jscode"中增加自定義javascript方法

怎麼較為靈活的hook類方法


apk的安裝與分析


示例下載:whyshouldIpay


下載apk後安裝,一樣還是先來看看是什麼功能,這是一個比較簡單的驗證程式,簡單的使用後,瞭解到PREMIUM CONETNT內容需要輸入License驗證後才能檢視。那估計PREMIUM CONETNT按鈕中的內容應該就是答案了吧。


初識Frida--Android逆向之Java層hook (二)

流程分析


使用jadx將apk反編譯出來,分析,在AndroidManifest.xml中找到了啟動的Activity是LauncherActivity。


初識Frida--Android逆向之Java層hook (二)


找到其中驗證的主要程式碼verifyClick,分析如下:


public void verifyClick(View v) {

//第一個驗證,將輸入的Licese通過網路驗證,但這個肯定是通不過的,這是一個可能需要繞過的點。

try{

InputStreamin=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。


String activatedKey=new String(MainActivity.xor(getMac().getBytes(), response.getBytes()));


來到MainActivity中,檢視該方法,看上去筆算起來還是比較麻煩。


public static byte[] xor(byte[] val, byte[] key) {

byte[] o=new byte[val.length];

for(inti=0; i < val.length; i++) {

o[i]=(byte) (val[i] ^ key[i%key.length]);

}

returno;

}


接下來當程式被啟用成功後,點選PREMIUM CONETNT按鈕,會呼叫MainActivity中的方法,可以看到它將MAC,以及生成的Key傳送到了MainActivity中。


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)。


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));

}


好,現在原始碼分析基本上能夠理清楚了,大概的過程就是這樣。


輸入License,進行驗證


通過網路驗證獲取返回值“LICENSEKEYOK”後,然後呼叫MainActivity.xor在本地preferences檔案中生成祕鑰,啟用成功。

本地獲取MAC地址及祕鑰Key傳入MainActivity得出答案。


hook點分析


接下來重點就是要尋找hook點,經過剛才解題流程的分析,得出hook思路如下:

獲取getMac()函式的返回值,與“LICENSEKEYOK"字串進行xor運算得出祕鑰Key.

hook getKey方法,讓它不從preferences檔案讀取Key,而是我們自己構造。

hookverifyClick,讓它呼叫showPremium方法


JavaScript程式碼構造與執行


0x00 hook getMac()

先來一個簡單的示例,看看getMac()方法返回的的是什麼,採用的方法是hookshowPremium,這樣就能通過點選PREMIUM CONETNT按鈕直接得到getMac()的返回值。


JavaScript程式碼如下:


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程式碼如下:


importfrida,sys

defon_message(message, data):

ifmessage['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()


執行看看結果:


初識Frida--Android逆向之Java層hook (二)


0x01 計算祕鑰Key


接下來開始真正第一步的hook,將mac值與“LICENSEKEYOK"通過MainActivity.xor獲取祕鑰Key。那就直接hookgetKey方法吧,這樣可以自己來構造祕鑰Key。


仔細分析,會發現在這一步中可能會遇到下面的問題:

怎麼呼叫xor方法。


java是強型別語言,javascript是弱型別語言,怎麼將javascript引數進行型別轉換並傳遞到java語言中。


怎麼將javascript引數進行型別轉換並傳遞到java語言中?其實方法很簡單,既然java是強型別語言,那就根據它要求的型別傳遞對應引數即可,看看它引數的型別。


public static byte[] xor(byte[] val, byte[] key) {

byte[] o=new byte[val.length];

for(inti=0; i < val.length; i++) {

o[i]=(byte) (val[i] ^ key[i%key.length]);

}

returno;

}


那麼,在javascript程式碼中,先準備一個將字串型別轉換為byte[]型別的方法stringToBytes,再通過例項化MainActivity類的方式呼叫xor(),然後還需要一個將byte[]迴轉為String的方法,因為祕鑰key是Sting型別的。


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,即可看到執行結果。


初識Frida--Android逆向之Java層hook (二)


0x02 呼叫showPremium獲取答案


前面2個步驟,可以說是已經完成90%了,接下來只需要在hook一個能夠觸發showPremium方法的即可。方法就隨意了,這裡採用hook verifyClick的方式,這樣點選app上的VERIFY按鈕,觸發verifyClick方法去呼叫showPremium,進而獲得最終答案。


hook_Activity.verifyClick.implementation=function(view){

this.showPremium(view);

}


啟動指令碼,點選app上的VERIFY按鈕看看執行結果:


初識Frida--Android逆向之Java層hook (二)


完整python程式碼:下載


總結


通過上面的例子,可以學習在java層怎麼使用frida實現:

任意類方法呼叫。

任意類方法重實現。
以及學會怎麼構造和使用自定義javascript方法。
當然這還僅僅只是一個開始.....


來源:https://bbs.pediy.com/thread-227233.htm



本文由看雪論壇 ghostmazeW 原創

轉載請註明來自看雪社群


相關文章