前言
本文介绍android4与android8中调用.init .init_array段中函数的过程,找到关键函数方便我们hook它
Android4
我们先来看Android4.4中loadLibrary的源码
/libcore/luni/src/main/java/java/lang/System.java
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
void loadLibrary(String libraryName, ClassLoader loader) {
//这里基本不会为null
if (loader != null) {
...
}
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : mLibPaths) {
String candidate = directory + filename;
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {
// 调用doLoad函数加载so库文件
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
这里关键就是doLoad函数加载so
private String doLoad(String name, ClassLoader loader) {
String ldLibraryPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
// so库文件的文件路径
ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
}
synchronized (this) {
// 调用native方法nativeLoad加载so库文件
return nativeLoad(name, loader, ldLibraryPath);
}
}
private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);
这里得到so文件路径后调用了native方法nativeLoad去加载so
这个native的源码按照文件路径格式找到是在java_lang_Runtime.cpp中
/dalvik/vm/native/java_lang_Runtime.cpp
/*
* 参数args[0]保存的是一个Java层的String对象,这个String对象描述的就是要加载的so文件,
* 函数Dalvik_java_lang_Runtime_nativeLoad首先是调用函数dvmCreateCstrFromString来将它转换成一个C++层的字符串fileName,
* 然后再调用函数dvmLoadNativeCode来执行加载so文件的操作。
*/
static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
JValue* pResult)
{
StringObject* fileNameObj = (StringObject*) args[0];
Object* classLoader = (Object*) args[1];
StringObject* ldLibraryPathObj = (StringObject*) args[2];
assert(fileNameObj != NULL);
char* fileName = dvmCreateCstrFromString(fileNameObj);
if (ldLibraryPathObj != NULL) {
char* ldLibraryPath = dvmCreateCstrFromString(ldLibraryPathObj);
void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");
if (sym != NULL) {
typedef void (*Fn)(const char*);
Fn android_update_LD_LIBRARY_PATH = reinterpret_cast<Fn>(sym);
(*android_update_LD_LIBRARY_PATH)(ldLibraryPath);
} else {
ALOGE("android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!");
}
free(ldLibraryPath);
}
StringObject* result = NULL;
char* reason = NULL;
// 调用dvmLoadNativeCode函数加载so库文件
bool success = dvmLoadNativeCode(fileName, classLoader, &reason);
if (!success) {
const char* msg = (reason != NULL) ? reason : "unknown failure";
result = dvmCreateStringFromCstr(msg);
dvmReleaseTrackedAlloc((Object*) result, NULL);
}
free(reason);
free(fileName);
RETURN_PTR(result);
}
这里就把路径java字符串转化为C++层的字符串,最后调用dvmLoadNativeCode(fileName, classLoader, &reason);
bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
char** detail)
{
...
Thread* self = dvmThreadSelf();
ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
// 先调用dlopen函数加载so库文件到内存中
handle = dlopen(pathName, RTLD_LAZY);
dvmChangeStatus(self, oldStatus);
...
/* create a new entry */
SharedLib* pNewEntry;
pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
pNewEntry->pathName = strdup(pathName);
pNewEntry->handle = handle;
pNewEntry->classLoader = classLoader;
dvmInitMutex(&pNewEntry->onLoadLock);
pthread_cond_init(&pNewEntry->onLoadCond, NULL);
pNewEntry->onLoadThreadId = self->threadId;
/* try to add it to the list */
SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
if (pNewEntry != pActualEntry) {
ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",
pathName, classLoader);
freeSharedLibEntry(pNewEntry);
return checkOnLoadResult(pActualEntry);
} else {
if (verbose)
ALOGD("Added shared lib %s %p", pathName, classLoader);
bool result = false;
void* vonLoad;
int version;
// 获取前面加载的so库文件中的导出函数JNI_OnLoad的调用地址
vonLoad = dlsym(handle, "JNI_OnLoad");
if (vonLoad == NULL) {
ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader);
result = true;
} else {
/*
* Call JNI_OnLoad. We have to override the current class
* loader, which will always be "null" since the stuff at the
* top of the stack is around Runtime.loadLibrary(). (See
* the comments in the JNI FindClass function.)
*/
// 保存获取到的JNI_OnLoad函数的调用地址
OnLoadFunc func = (OnLoadFunc)vonLoad;
Object* prevOverride = self->classLoaderOverride;
self->classLoaderOverride = classLoader;
oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
if (gDvm.verboseJni) {
//这里可以用户ida的查找
ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
}
// 调用so库文件中的导出函数JNI_OnLoad
version = (*func)(gDvmJni.jniVm, NULL);
dvmChangeStatus(self, oldStatus);
self->classLoaderOverride = prevOverride;
if (version == JNI_ERR) {
*detail = strdup(StringPrintf("JNI_ERR returned from JNI_OnLoad in \"%s\"",
pathName).c_str());
} else if (dvmIsBadJniVersion(version)) {
*detail = strdup(StringPrintf("Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
pathName, version).c_str());
} else {
result = true;
}
if (gDvm.verboseJni) {
ALOGI("[Returned %s from JNI_OnLoad for \"%s\"]",
(result ? "successfully" : "failure"), pathName);
}
}
...
return result;
}
}
在该函数中先调用dlopen函数加载so库文件到内存中,然后调用dlsym函数获取so库文件中JNI_OnLoad函数的导出地址,然后调用JNI_OnLoad函数执行开发者自定义的代码和实现jni函数的注册。
这里我们就找到了JNI_OnLoad函数的执行处
我们在来看看dlopen函数的代码
这个函数在/bionic/linker/dlfcn.cpp
// dlopen函数调用do_dlopen函数实现so库文件的加载
void* dlopen(const char* filename, int flags) {
// 信号互斥量(锁)
ScopedPthreadMutexLocker locker(&gDlMutex);
// 调用do_dlopen()函数实现so库文件的加载
soinfo* result = do_dlopen(filename, flags);
// 判断so库文件是否加载成功
if (result == NULL) {
__bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
return NULL;
}
// 返回加载后so库文件的文件句柄
return result;
}
可以看到他的关键代码是do_dlopen
/bionic/linker/linker.cpp
// 实现对so库文件的加载和执行构造函数
soinfo* do_dlopen(const char* name, int flags) {
// 判断加载so文件的flags是否符合要求
if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {
DL_ERR("invalid flags to dlopen: %x", flags);
return NULL;
}
// 修改内存属性为可读可写
set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
// find_library会判断so是否已经加载,
// 如果没有加载,对so进行加载,完成一些初始化工作
soinfo* si = find_library(name);
// 判断so库问价是否加载成功
if (si != NULL) {
// ++++++ so加载成功,调用构造函数 ++++++++
si->CallConstructors();
// ++++++++++++++++++++++++++++++++++++++++
}
// 设置内存属性为可读
set_soinfo_pool_protection(PROT_READ);
// 返回so内存模块
return si;
}
so加载成功,调用构造函数 si->CallConstructors();
// so库文件加载完毕以后调用构造函数
void soinfo::CallConstructors() {
if (constructors_called) {
return;
}
constructors_called = true;
if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {
// The GNU dynamic linker silently ignores these, but we warn the developer.
PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",
name, preinit_array_count);
}
// 调用DT_NEEDED类型段的构造函数
if (dynamic != NULL) {
for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
if (d->d_tag == DT_NEEDED) {
const char* library_name = strtab + d->d_un.d_val;
TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);
find_loaded_library(library_name)->CallConstructors();
}
}
}
TRACE("\"%s\": calling constructors", name);
// DT_INIT should be called before DT_INIT_ARRAY if both are present.
// 先调用.init段的构造函数
CallFunction("DT_INIT", init_func);
// 再调用.init_array段的构造函数
CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
}
其实这里就看到.init段和.init_array段构造函数的调用点了。
其中init_func和init_array在soinfo_link_image函数中对其赋值
static bool soinfo_link_image(soinfo* si) {
...
case DT_INIT:
si->init_func = reinterpret_cast<linker_function_t>(base + d->d_un.d_ptr);
DEBUG(“%s constructors (DT_INIT) found at %p”, si->name, si->init_func);
break;
case DT_INIT_ARRAY:
si->init_array = reinterpret_cast<linker_function_t*>(base + d->d_un.d_ptr);
DEBUG(“%s constructors (DT_INIT_ARRAY) found at %p”, si->name, si->init_array);
break;
...
}
我们现在来看看init是如何调用的吧
// 构造函数调用的实现
void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {
// 判断构造函数的调用地址是否符合要求
if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {
return;
}
// function_name被调用的函数名称,function为函数的调用地址
// [ Calling %s @ %p for '%s' ] 字符串为在 /system/bin/linker 中查找.init和.init_array段调用函数的关键
TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);
// 调用function函数
function();
TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);
// The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures
// are still writable. This happens with our debug malloc (see http://b/7941716).
set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
其实最核心的一句话就是function();,我们可以通过上面的字符串信息在ida中找到这个位置
再来看看.init_array中的实现
void soinfo::CallArray(const char* array_name UNUSED, linker_function_t* functions, size_t count, bool reverse) {
if (functions == NULL) {
return;
}
TRACE("[ Calling %s (size %d) @ %p for '%s' ]", array_name, count, functions, name);
int begin = reverse ? (count - 1) : 0;
int end = reverse ? -1 : count;
int step = reverse ? -1 : 1;
// 循环遍历调用.init_arrayt段中每个函数
for (int i = begin; i != end; i += step) {
TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
// .init_arrayt段中,每个函数指针的调用和上面的.init段的构造函数的实现是一样的
CallFunction("function", functions[i]);
}
TRACE("[ Done calling %s for '%s' ]", array_name, name);
}
在这里可以看到与CallFunction没有什么区别就是从这个函数数组中循环去执行CallFunction罢了。
经过上面的代码分析可以得出结论执行顺序init>init_array>JNI_OnLoad
Android8
在Android8中private static native String nativeLoad的调用现在在
/art/runtime/openjdkjvm/OpenjdkJvm.cc
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader,
jstring javaLibrarySearchPath) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == NULL) {
return NULL;
}
std::string error_msg;
{
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
javaLibrarySearchPath,
&error_msg);
if (success) {
return nullptr;
}
}
// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
LoadNativeLibrary的调用如下
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
jstring library_path,
std::string* error_msg) {
...
Locks::mutator_lock_->AssertNotHeld(self);
const char* path_str = path.empty() ? nullptr : path.c_str();
bool needs_native_bridge = false;
void* handle = android::OpenNativeLibrary(env,
runtime_->GetTargetSdkVersion(),
path_str,
class_loader,
library_path,
&needs_native_bridge,
error_msg);
VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";
if (handle == nullptr) {
VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg;
return false;
}
if (env->ExceptionCheck() == JNI_TRUE) {
LOG(ERROR) << "Unexpected exception:";
env->ExceptionDescribe();
env->ExceptionClear();
}
// Create a new entry.
// TODO: move the locking (and more of this logic) into Libraries.
bool created_library = false;
{
// Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.
std::unique_ptr<SharedLibrary> new_library(
new SharedLibrary(env,
self,
path,
handle,
needs_native_bridge,
class_loader,
class_loader_allocator));
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
if (library == nullptr) { // We won race to get libraries_lock.
library = new_library.release();
libraries_->Put(path, library);
created_library = true;
}
}
if (!created_library) {
LOG(INFO) << "WOW: we lost a race to add shared library: "
<< "\"" << path << "\" ClassLoader=" << class_loader;
return library->CheckOnLoadResult();
}
VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
bool was_successful = false;
//得到JNI_OnLoad函数地址
void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
if (sym == nullptr) {
VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
was_successful = true;
} else {
ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
self->SetClassLoaderOverride(class_loader);
VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
//定义JNI_OnLoadFn
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
//调用JNI_OnLoad
int version = (*jni_on_load)(this, nullptr);
if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
// Make sure that sigchain owns SIGSEGV.
EnsureFrontOfChain(SIGSEGV);
}
self->SetClassLoaderOverride(old_class_loader.get());
...
VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
<< " from JNI_OnLoad in \"" << path << "\"]";
}
library->SetResult(was_successful);
return was_successful;
}
在android8中dlopen函数变化的非常多,但是总体的逻辑不变,我们来看看soinfo::CallConstructors()变成什么样了。
void soinfo::call_constructors() {
if (constructors_called) {
return;
}
constructors_called = true;
if (!is_main_executable() && preinit_array_ != nullptr) {
// The GNU dynamic linker silently ignores these, but we warn the developer.
PRINT("\"%s\": ignoring DT_PREINIT_ARRAY in shared library!", get_realpath());
}
get_children().for_each([] (soinfo* si) {
si->call_constructors();
});
if (!is_linker()) {
bionic_trace_begin((std::string("calling constructors: ") + get_realpath()).c_str());
}
// DT_INIT should be called before DT_INIT_ARRAY if both are present.
// 先调用.init段的构造函数
call_function("DT_INIT", init_func_, get_realpath());
// 再调用.init_array段的构造函数
call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false, get_realpath());
if (!is_linker()) {
bionic_trace_end();
}
}
可以看到它的命名方式有了一些变化,再来看看call_array
template <typename F>
static void call_array(const char* array_name __unused,
F* functions,
size_t count,
bool reverse,
const char* realpath) {
if (functions == nullptr) {
return;
}
TRACE("[ Calling %s (size %zd) @ %p for '%s' ]", array_name, count, functions, realpath);
int begin = reverse ? (count - 1) : 0;
int end = reverse ? -1 : count;
int step = reverse ? -1 : 1;
for (int i = begin; i != end; i += step) {
TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
call_function("function", functions[i], realpath);
}
TRACE("[ Done calling %s for '%s' ]", array_name, realpath);
}
这个更加没啥变化
最后看看我们最关心的call_function
static void call_function(const char* function_name __unused,
linker_ctor_function_t function,
const char* realpath __unused) {
if (function == nullptr || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {
return;
}
TRACE("[ Calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);
function(g_argc, g_argv, g_envp);
TRACE("[ Done calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);
}
static void call_function(const char* function_name __unused,
linker_dtor_function_t function,
const char* realpath __unused) {
if (function == nullptr || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {
return;
}
TRACE("[ Calling d-tor %s @ %p for '%s' ]", function_name, function, realpath);
function();
TRACE("[ Done calling d-tor %s @ %p for '%s' ]", function_name, function, realpath);
}
可以看到到了Android8之后,call_function有了两个函数,它的主要区别在于第二个参数function的类型,这个是根据call_array的F来判断的。他们分别定义如下
typedef void (*linker_ctor_function_t)(int, char**, char**);
typedef void (*linker_dtor_function_t)();
其实在Androi8中调用.init .init_array走的函数基本都是
static void call_function(const char* function_name __unused, linker_ctor_function_t function, const char* realpath __unused);
所以我们在Android8中需要hook的就是这个函数。
参考
Android 7.0 dlopen 函数分析
在Android so文件的.init、.init_array上和JNI_OnLoad处下断点