很早就注册了看雪号,结果没分享过什么东西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里。
所以这个函数做了这几件事:
有了上面的分析基础,我们这样做就可以让我们自己的so库被zygote进程dlopen了:
到这,已经可以在zygote里加载自己的库了,但还有一个问题:zygote只打开了这个库,并没有调用任何函数,而我们常用的JNI_OnLoad
函数在这是不会被调用的,怎么才能让zygote执行自己的代码呢?各位估计已经知道了,写一个用__attribute__((constructor))
修饰的函数,这个函数会被登记在.init.array里,会在so被加载时调用,我们就完成了注入逻辑;接下来你就可以在zygote里做自己想要做的事了,玩的开心 :)
最后夹带点私货,此方案最先发表在我的博客上,顺便结合SandHook写了个简单的Xposed实现,感兴趣的可以去围观下~
[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!
最后于 17小时前 被kanxue编辑 ,原因: