首页
论坛
专栏
课程

初识Frida--Android逆向之Java层hook (二)

Editor 发布于 看雪学院 2018-06-12 16:51

今天继续一个新的示例,同样采用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,分析如下:


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


运行看看结果:



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



0x02 调用showPremium获取答案


前面2个步骤,可以说是已经完成90%了,接下来只需要在hook一个能够触发showPremium方法的即可。方法就随意了,这里采用hook verifyClick的方式,这样点击app上的VERIFY按钮,触发verifyClick方法去调用showPremium,进而获得最终答案。


hook_Activity.verifyClick.implementation=function(view){

this.showPremium(view);

}


启动脚本,点击app上的VERIFY按钮看看执行结果:



完整python代码:下载


总结


通过上面的例子,可以学习在java层怎么使用frida实现:

任意类方法调用。

任意类方法重实现。
以及学会怎么构造和使用自定义javascript方法。
当然这还仅仅只是一个开始.....


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



本文由看雪论坛 ghostmazeW 原创

转载请注明来自看雪社区



分享到:
最新评论 (0)
登录后即可评论
上一篇:A站受黑客攻击:近千万条用户数据外泄 涉ID及密码等 下一篇:北京检方发布《网络安全刑事司法保护白皮书》