说明
前面基础部分,都是在通过反编译拿到源码的时候进行hook,如果源代码有加固,那么有没有其他办法能从内存中拿到所有的方法然后去hook呢?也是可行的,这就是本次的内容。
本次以Sieve
这款APK为例吧
frida手册:https://frida.re/docs/javascript-api/#java
# 找到对应的进程
frida-ps -D emulator-5554
拿到所有类名
通过翻阅frida手册,可以看到要枚举出所有类,有2种方法Java.enumerateLoadedClasses(callbacks)
和Java.enumerateLoadedClassesSync()
我们用第一种,通过回调函数一个一个的查类;编写js脚本,通过setTimeout
函数可以控制开始运行的时间。
setTimeout(function (){
Java.perform(function (){
Java.enumerateLoadedClasses({
onMatch: function(_className){
// _className为string类型
console.log("[*] found instance of " + _className);
},
onComplete: function(){
console.log("[*] class enuemration complete");
}
});
});
}, 0);
运行
frida -D emulator-5554 -l test.js Sieve
因为上面的类结果实在是太多了,因此优化一下js只显示相关的类,这里我们以sieve
为关键词进行过滤。
该app的包名为
com.mwr.example.sieve
,所以用sieve
为关键词过滤,定位内部的类
setTimeout(function (){
Java.perform(function (){
Java.enumerateLoadedClasses({
onMatch: function(_className){
// _className为string类型
if (_className.includes("sieve")){
console.log("[*] found instance of " + _className);
}
},
onComplete: function(){
console.log("[*] class enuemration complete");
}
});
});
}, 0);
结果如下,明显少了很多
[*] found instance of com.mwr.example.sieve.AuthServiceConnector$MessageHandler
[*] found instance of com.mwr.example.sieve.WelcomeActivity
[*] found instance of com.mwr.example.sieve.PWDBHelper
[*] found instance of com.mwr.example.sieve.CryptoServiceConnector$ResponseListener
[*] found instance of com.mwr.example.sieve.MainLoginActivity$1
[*] found instance of com.mwr.example.sieve.AuthService
[*] found instance of com.mwr.example.sieve.DBContentProvider
[*] found instance of com.mwr.example.sieve.PINActivity
[*] found instance of com.mwr.example.sieve.AuthServiceConnector$ResponseListener
[*] found instance of com.mwr.example.sieve.SettingsActivity
[*] found instance of com.mwr.example.sieve.MainLoginActivity
[*] found instance of com.mwr.example.sieve.NetBackupHandler$ResultListener
[*] found instance of com.mwr.example.sieve.PWList
[*] found instance of com.mwr.example.sieve.ShortLoginActivity
[*] found instance of com.mwr.example.sieve.FileBackupProvider
[*] found instance of com.mwr.example.sieve.AuthServiceConnector
获取类实例和方法
这里我们就hook com.mwr.example.sieve.MainLoginActivity
这个类吧。通过上述方式枚举出的结果是以字符串返回的,那么就需要通过其他手段拿到实例,有2种方法
Java.use(className)
Java.choose(className, callbacks)
区别在于:
Java.use
是实例化一个类,需要指定类的完整名称,一次只能实例化一个对象,可以直接对该对象进行操作。Java.choose
用于寻找已加载的类实例,并执行回调函数,可以找到多个对象,并对它们进行操作。
这里我们通过Java.choose
去定位类实例(Java.use
也是可以的,见下方注释代码),然后通过java反射机制获取方法和字段等。
插曲:
frida Java.choose()
有时候找不到实例,为什么?
- 目标类没有被加载。在调用
Java.choose
之前,确保目标类已经被加载。可以使用Java.enumerateLoadedClasses
列出所有已经被加载的类。- 目标类的实例数量为0。如果目标类的实例数量为0,
Java.choose
将不会触发onMatch
回调函数。可以使用Java.use
来创建一个新的类实例并进行操作。Java.choose
的调用时机不对。在Java.choose
调用之前,可能需要一些初始化操作,例如启动目标应用程序或者等待应用程序加载完毕。确保在执行Java.choose
时,目标应用程序已经可以正常工作。- 目标类的实例被隐藏或者被更改了名称。在一些情况下,开发者可能会对类的实例进行一些保护措施,例如将类名更改为随机字符串或者通过某些手段隐藏类的实例。这些保护措施可能导致
Java.choose
无法找到目标类的实例。- 没有足够的权限。在某些情况下,应用程序可能会对某些类或者方法进行保护,例如通过
ClassLoader
进行保护。如果当前的用户没有足够的权限来访问这些受保护的类或者方法,Java.choose
将无法找到目标类的实例。
编写脚本如下:
setTimeout(function (){
Java.perform(function (){
Java.choose("com.mwr.example.sieve.MainLoginActivity", {
onMatch: function(instance){
console.log("[*] found instance " + instance);
// java反射部分
var clazz = instance.getClass();
var methods = clazz.getDeclaredMethods();
methods.forEach(function(value, index){
console.log(value);
});
},
onComplete: function(){
console.log("[*] instance find complete")
}
})
});
}, 0);
// Java.use 方法
// setTimeout(function (){
// Java.perform(function (){
// var myClass = Java.use("com.mwr.example.sieve.MainLoginActivity");
// var clazz = myClass.class;
// console.log(clazz.getDeclaredMethods());
// });
// }, 0);
获取到的方法如下:
public void com.mwr.example.sieve.MainLoginActivity.checkKeyResult(boolean)
public void com.mwr.example.sieve.MainLoginActivity.checkPinResult(boolean)
public void com.mwr.example.sieve.MainLoginActivity.connected()
public void com.mwr.example.sieve.MainLoginActivity.firstLaunchResult(int)
public void com.mwr.example.sieve.MainLoginActivity.login(android.view.View)
protected void com.mwr.example.sieve.MainLoginActivity.onActivityResult(int,int,android.content.Intent)
public void com.mwr.example.sieve.MainLoginActivity.onBackPressed()
protected void com.mwr.example.sieve.MainLoginActivity.onCreate(android.os.Bundle)
public boolean com.mwr.example.sieve.MainLoginActivity.onCreateOptionsMenu(android.view.Menu)
public boolean com.mwr.example.sieve.MainLoginActivity.onOptionsItemSelected(android.view.MenuItem)
public void com.mwr.example.sieve.MainLoginActivity.onPause()
public void com.mwr.example.sieve.MainLoginActivity.onResume()
protected void com.mwr.example.sieve.MainLoginActivity.onStart()
public void com.mwr.example.sieve.MainLoginActivity.sendFailed()
public void com.mwr.example.sieve.MainLoginActivity.setKeyResult(boolean)
public void com.mwr.example.sieve.MainLoginActivity.setPinResult(boolean)
private void com.mwr.example.sieve.MainLoginActivity.initaliseActivity()
private void com.mwr.example.sieve.MainLoginActivity.loginFailed()
private void com.mwr.example.sieve.MainLoginActivity.loginSuccessful()
private void com.mwr.example.sieve.MainLoginActivity.openSettings()
private void com.mwr.example.sieve.MainLoginActivity.setPin()
private void com.mwr.example.sieve.MainLoginActivity.unbind()
private void com.mwr.example.sieve.MainLoginActivity.welcomeUser()
调用方法
拿到方法后,可以去调用,这里我们调用com.mwr.example.sieve.MainLoginActivity.loginSuccessful()
吧,因为获取到的是实例,直接调用方法即可。
setTimeout(function (){
Java.perform(function (){
Java.choose("com.mwr.example.sieve.MainLoginActivity", {
onMatch: function(instance){
console.log("[*] found instance " + instance);
instance.loginSuccessful();
},
onComplete: function(){
console.log("[*] instance find complete")
}
})
});
}, 0);
调用后就直接登录成功了。
想要hook方法的话类似,通过Java.use
就行。