今天继续一个新的示例,同样采用CTF作为例子,难度稍微加大了一点,如果对Frida基本的使用还不是很了解,建议先看看之前的文章初识Frida--Android逆向之Java层hook (一)
博客同步:访问
文章涉及到的知识点:
示例下载:whyshouldIpay
下载apk后安装,一样还是先来看看是什么功能,这是一个比较简单的验证程序,简单的使用后,了解到PREMIUM CONETNT
内容需要输入License验证后才能查看。那估计PREMIUM CONETNT
按钮中的内容应该就是答案了吧。
使用jadx将apk反编译出来,分析,在AndroidManifest.xml中找到了启动的Activity是LauncherActivity
。
找到其中验证的主要代码verifyClick
,分析如下:
在verifyClick
中可以知道生成激活秘钥的算法是MainActivity.xor
。
来到MainActivity
中,查看该方法,看上去笔算起来还是比较麻烦。
接下来当程序被激活成功后,点击PREMIUM CONETNT
按钮,会调用MainActivity
中的方法,可以看到它将MAC,以及生成的Key发送到了MainActivity
中。
在MainActivity
的onCreate
方法中,看到了最终答案生成的native方法stringFromJNI(key, mac)
。
好,现在源代码分析基本上能够理清楚了,大概的过程就是这样。
接下来重点就是要寻找hook点,经过刚才解题流程的分析,得出hook思路如下:
先来一个简单的示例,看看getMac()方法返回的的是什么,采用的方法是hook showPremium
,这样就能通过点击PREMIUM CONETNT
按钮直接得到getMac()
的返回值。
JavaScript代码如下:
完整python代码如下:
运行看看结果:
接下来开始真正第一步的hook,将mac值与“LICENSEKEYOK"通过MainActivity.xor
获取秘钥Key。那就直接hook getKey
方法吧,这样可以自己来构造秘钥Key。
仔细分析,会发现在这一步中可能会遇到下面的问题:
怎么将javascript参数进行类型转换并传递到java语言中?其实方法很简单,既然java是强类型语言,那就根据它要求的类型传递对应参数即可,看看它参数的类型。
那么,在javascript代码中,先准备一个将字符串类型转换为byte[]类型的方法stringToBytes
,再通过实例化MainActivity
类的方式调用xor()
,然后还需要一个将byte[]回转为String的方法,因为秘钥key是Sting类型的。
接下来,执行看看,能不能获取秘钥Key。
不知道怎么启动模拟器中的frida-server,以及端口转发,可以先看看初识Frida--Android逆向之Java层hook (一)
启动python脚本,在模拟器中直接点击PREMIUM CONTENT,即可看到执行结果。
前面2个步骤,可以说是已经完成90%了,接下来只需要在hook一个能够触发showPremium方法的即可。方法就随意了,这里采用hook verifyClick的方式,这样点击app上的VERIFY
按钮,触发verifyClick方法去调用showPremium,进而获得最终答案。
启动脚本,点击app上的VERIFY
按钮看看执行结果:
完整python代码:下载
通过上面的例子,可以学习在java层怎么使用frida实现:
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();
}
}
String activatedKey = new String(MainActivity.xor(getMac().getBytes(), response.getBytes()));
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2018-6-6 10:54
被ghostmazeW编辑
,原因: 修复图片