一种简单的注入zygote进程的方案
2020-05-09 11:30:52 Author: bbs.pediy.com(查看原文) 阅读量:455 收藏

[原创]一种简单的注入zygote进程的方案

18小时前 409

很早就注册了看雪号,结果没分享过什么东西emm,发现现在大家用的注入方案基本上都是替换系统库,分享个不需要替换系统库的方案吧
先说限制:1.只能在Android 7.0及更高版本中使用;2.部分设备会报UnsatisfiedLinkError,还没找到原因,各位大神可以分析下~

注:本文分析的是Android 7.0的源码
zygote对应的可执行文件其实就是app_process,它的main方法如下:

int main(int argc, char* const argv[])
{
  // 省略无关代码...
  AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
  // 省略无关代码...
  if (zygote) {
    runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
  } else if (className) {
    runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
  } else {
    fprintf(stderr, "Error: no class name or --zygote supplied.\n");
    app_usage();
    LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    return 10;
  }
}

跟踪下去,最终会调用JNI_CreateJavaVM创建虚拟机,这个方法是这样实现的:

// JNI Invocation interface.

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  ScopedTrace trace(__FUNCTION__);
  const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);
  if (IsBadJniVersion(args->version)) {
    LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;
    return JNI_EVERSION;
  }
  RuntimeOptions options;
  for (int i = 0; i < args->nOptions; ++i) {
    JavaVMOption* option = &args->options[i];
    options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));
  }
  bool ignore_unrecognized = args->ignoreUnrecognized;
  if (!Runtime::Create(options, ignore_unrecognized)) {
    return JNI_ERR;
  }

  // Initialize native loader. This step makes sure we have
  // everything set up before we start using JNI.
  android::InitializeNativeLoader();

  Runtime* runtime = Runtime::Current();
  bool started = runtime->Start();
  if (!started) {
    delete Thread::Current()->GetJniEnv();
    delete runtime->GetJavaVM();
    LOG(WARNING) << "CreateJavaVM failed";
    return JNI_ERR;
  }

  *p_env = Thread::Current()->GetJniEnv();
  *p_vm = runtime->GetJavaVM();
  return JNI_OK;
}

注意看android::InitializeNativeLoader(),这个函数直接调用了g_namespaces->Initialize(),而g_namespaces是一个LibraryNamespaces指针,继续看下去,我们发现了宝藏:

void Initialize() {
  std::vector<std::string> sonames;
  const char* android_root_env = getenv("ANDROID_ROOT");
  std::string root_dir = android_root_env != nullptr ? android_root_env : "/system";
  std::string public_native_libraries_system_config =
          root_dir + kPublicNativeLibrariesSystemConfigPathFromRoot;

  LOG_ALWAYS_FATAL_IF(!ReadConfig(public_native_libraries_system_config, &sonames),
                      "Error reading public native library list from \"%s\": %s",
                      public_native_libraries_system_config.c_str(), strerror(errno));

  // 省略无关代码

  // This file is optional, quietly ignore if the file does not exist.
  ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames);

  // android_init_namespaces() expects all the public libraries
  // to be loaded so that they can be found by soname alone.
  //
  // TODO(dimitry): this is a bit misleading since we do not know
  // if the vendor public library is going to be opened from /vendor/lib
  // we might as well end up loading them from /system/lib
  // For now we rely on CTS test to catch things like this but
  // it should probably be addressed in the future.
  for (const auto& soname : sonames) {
    dlopen(soname.c_str(), RTLD_NOW | RTLD_NODELETE);
  }

  public_libraries_ = base::Join(sonames, ':');
}

public_native_libraries_system_config=/system/etc/public.libraries.txt,而ReadConfig方法很简单,读取传进来的文件路径,按行分割,忽略空行和以#开头的行,然后把这行push_back到传进来的vector里。
所以这个函数做了这几件事:

  1. 读取/system/etc/public.libraries.txt和/vendor/etc/public.libraries.txt
  2. 挨个dlopen这两个txt文件里提到的所有so库

有了上面的分析基础,我们这样做就可以让我们自己的so库被zygote进程dlopen了:

  1. 把自己写的so库扔到/system/lib/下面(64位是/system/lib64/)
  2. 把这个so库的文件名追加到/system/etc/public.libraries.txt里

到这,已经可以在zygote里加载自己的库了,但还有一个问题:zygote只打开了这个库,并没有调用任何函数,而我们常用的JNI_OnLoad函数在这是不会被调用的,怎么才能让zygote执行自己的代码呢?各位估计已经知道了,写一个用__attribute__((constructor))修饰的函数,这个函数会被登记在.init.array里,会在so被加载时调用,我们就完成了注入逻辑;接下来你就可以在zygote里做自己想要做的事了,玩的开心 :)

最后夹带点私货,此方案最先发表在我的博客上,顺便结合SandHook写了个简单的Xposed实现,感兴趣的可以去围观下~

[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!

最后于 17小时前 被kanxue编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-259391.htm
如有侵权请联系:admin#unsafe.sh