Frida09 - 调用Java方法ida

76 阅读1分钟

各位读者你们好啊,又是打工的一天!

今天我们主要学习一下 objection 的一些功能实现,更好的熟悉 frida 语法。

列出四大组件

Activity

export const getActivities = (): Promise<string[]> => {
  return wrapJavaPerform(() => {

    const packageManager: PackageManager = Java.use("android.content.pm.PackageManager");
    const GET_ACTIVITIES = packageManager.GET_ACTIVITIES.value;
    const context = getApplicationContext();

    return Array.prototype.concat(context.getPackageManager()
      .getPackageInfo(context.getPackageName(), GET_ACTIVITIES).activities.value.map((activityInfo) => {
        return activityInfo.name.value;
      }),
    );
  });
};

这里面有些是 typescript 的语法,有些是 android 的api,刚开始看起来可能比较蛋疼。

我们先看 android 里面是如何获取一个 app 里面的所有 activity 的:

public void getAllActivity() {
    PackageManager packageManager = getPackageManager();
    PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), PackageManager.GET_ACTIVITIES);
    //所有的Activity 
    ActivityInfo[] activities = packageInfo.activities;
}

对比可以看出,objection 就是直接使用了 Android 提供的 api 来实现获取所有 activity 的。然后用 typescript 的 api 将结果处理了一下。

Service

service 的话要比较麻烦,因为并没有直接提供方法。android apk 在安装的时候,清单文件已经被解析过了,所以肯定是有这些信息,只不过存放的位置比较隐蔽:

export const getServices = (): Promise<string[]> => {
  return wrapJavaPerform(() => {
    const activityThread: ActivityThread = Java.use("android.app.ActivityThread");
    const arrayMap: ArrayMap = Java.use("android.util.ArrayMap");
    const packageManager: PackageManager = Java.use("android.content.pm.PackageManager");

    const GET_SERVICES = packageManager.GET_SERVICES.value;

    const currentApplication = activityThread.currentApplication();
    // not using the helper as we need other variables too
    const context = currentApplication.getApplicationContext();

    let services = [];

    currentApplication.mLoadedApk.value.mServices.value.values().toArray().map((potentialServices) => {
      Java.cast(potentialServices, arrayMap).keySet().toArray().map((service) => {
        services.push(service.$className);
      });
    });

    services = services.concat(context.getPackageManager()
      .getPackageInfo(context.getPackageName(), GET_SERVICES).services.value.map((activityInfo) => {
        return activityInfo.name.value;
      }),
    );

    return services;
  });
};

可以看出,objection 是从 LoadedApk 里面拿了些信息:

150      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
151      private final ArrayMap<ContextArrayMap<ServiceConnectionLoadedApk.ServiceDispatcher>> mServices
152          = new ArrayMap<>();

这个是获取了 Context 对象,这个 context 实际上是一个 service 对象,在创建 service 的时候赋值:

context.setOuterContext(service);

有点不明白,app 里面的 service 应该都在清单里面声明了,应该直接获取就行,为啥要加这一步呢???这个api还包括已经建立连接的外部service???

然后再加上从 getPackageManager 获取的自身的 service 信息即可。

其他的广播,ContentProvider 也是类似的,就不说了,贴一个源码链接:

github.com/sensepost/o…

启动Activity

// starts an Android activity
// This method does not yet allow for 'extra' data to be send along
// with the intent.
export const startActivity = (activityClass: string): Promise<void> => {
  // -- Sample Java
  //
  // Intent intent = new Intent(this, DisplayMessageActivity.class);
  // intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  //
  // startActivity(intent);
  return wrapJavaPerform(() => {
    const context = getApplicationContext();

    // Setup a new Intent
    const androidIntent: Intent = Java.use("android.content.Intent");

    // Get the Activity class's .class
    const newActivity: Java.Wrapper = Java.use(activityClass).class;
    send(`Starting activity ${c.green(activityClass)}...`);

    // Init and launch the intent
    const newIntent: Intent = androidIntent.$new(context, newActivity);
    newIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);

    context.startActivity(newIntent);
    send(c.blackBright(`Activity successfully asked to start.`));
  });
};

就是调用了 Context的 startActivity方法,没有传递参数,如果目标界面有校验,就会失效。

github.com/sensepost/o…

Hook所有重载方法

export const setReturnValue = (fqClazzstringfilterOverloadstring | nullnewRetboolean): Promise<void> => {
  ...

  return wrapJavaPerform(() => {
    ...

    const targetClazzJavaClass = Java.use(clazz);

    targetClazz[method].overloads.forEach((m: any) => {
      // get the argument types for this method
      const calleeArgTypesstring[] = m.argumentTypes.map((arg) => arg.className);

      ...

      // tslint:disable-next-line:only-arrow-functions
      m.implementation = function () {
        let retVal = m.apply(thisarguments);

        ...
        return retVal;
      };

    });

    jobs.add(job);
  });
};

使用 overloads 获取了所有的重载方法。

而且,targetClazz直接是使用了 [] 语法来指定方法名,这样如果有些不可打印的方法名,也可以用这种方式来 hook。

替换方法实现,直接使用 implementation,获取方法参数使用 arguments 即可。

github.com/sensepost/o…

打印堆栈

let t = Java.use("java.lang.Throwable").$new()
let logClazz = Java.use("android.util.Log")
let stackString = logClazz.getStackTraceString(t)
console.log(stackString)

显然,这里其实也是用了 Android 的 Log 类的一个 API:

public static String getStackTraceString(@Nullable Throwable tr) {
    ...
}

总结

如果调用静态方法,可以直接调用:

var a = Java.use(xxx);
a.staticMethod(xxx);

如果是成员方法,先找到对象再调用:

var bclazz = Java.use('这里写类名');
var b = bclazz.$new();
b.memberMethod(xxxx)

字段也是一样。

最后,需要熟悉 Android 的 API。

二手的程序员

欢迎关注二手的程序员,这里主要分享逆向相关的知识。专注于完整系列,让知识不再碎片化。不定时更新,也欢迎关注我的博客:lyldalek.top

**

公众号