首页
论坛
课程
招聘
[原创]一种简单的注入zygote进程的方案
2020-5-8 16:20 7907

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

2020-5-8 16:20
7907

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

1.源码分析

注:本文分析的是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库

2.注入实现

有了上面的分析基础,我们这样做就可以让我们自己的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实现,感兴趣的可以去围观下~


[注意] 欢迎加入看雪团队!base上海,招聘安全工程师、逆向工程师多个坑位等你投递!

最后于 2020-6-5 09:14 被残页编辑 ,原因: 尝试修复代码块高亮
收藏
点赞1
打赏
分享
最新回复 (12)
雪    币: 1268
活跃值: 活跃值 (334)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
残页 活跃值 2020-5-8 16:24
2
0
第一次在看雪发帖,不知道为啥用markdown写好了,预览也没问题,发出来代码就和正文混到一块了,emmm
雪    币: 417
活跃值: 活跃值 (6509)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 活跃值 8 2020-5-8 16:46
3
0
残页 第一次在看雪发帖,不知道为啥用markdown写好了,预览也没问题,发出来代码就和正文混到一块了,emmm
稍等,我们查查原因
雪    币: 34
活跃值: 活跃值 (88)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
winkar 活跃值 2020-5-8 17:46
4
0
一般用替换系统库的方法实现的原因,可能是避免检测
雪    币: 34
活跃值: 活跃值 (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
嗟来嗟来 活跃值 2020-5-8 20:31
5
0
刚准备找这块的资料,楼主就发帖了。缘分啊!
雪    币: 1178
活跃值: 活跃值 (246)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
上海刘一刀 活跃值 2 2020-5-8 22:04
6
0
棒  向大佬学习  之前写了一个注入的  是替换系统文件的   还是要好好看源码才能学习到更多。。
雪    币: 758
活跃值: 活跃值 (78)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
壹久玖 活跃值 2020-5-11 19:36
7
0
很厉害
雪    币: 208
活跃值: 活跃值 (47)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
一只小猴子 活跃值 2020-5-23 11:25
8
0
好帖,顶
雪    币: 2034
活跃值: 活跃值 (521)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hekes 活跃值 2021-1-26 22:48
9
0
mark
雪    币:
活跃值: 活跃值 (71)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
SunnySmart 活跃值 2021-1-27 09:49
10
0
试了这种方式,貌似不能再ctor里面执行代码,执行了就不开机了,主界面都进不去,模拟器上
雪    币: 18
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
万里星河 活跃值 2021-1-29 14:44
11
0
楼主牛逼
雪    币: 21
活跃值: 活跃值 (227)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jfztaq 活跃值 2021-5-7 06:55
12
0
谢谢分享,学习了
雪    币: 116
活跃值: 活跃值 (618)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huaerxiela 活跃值 2021-5-7 15:50
13
0

把frida的libgadget.so放里面了,然后修改了txt,然后就开不了机了。。。

看报错是frida-gum/gum/backend-posix/gummemory-posix.c:76:gum_alloc_n_pages: assertion failed: (result != NULL)


最后于 2021-5-7 15:55 被huaerxiela编辑 ,原因:
游客
登录 | 注册 方可回帖
返回