找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: 活动 交友 discuz
查看: 248|回复: 0

android loadLibrary源码分析

[复制链接]

2万

主题

135

回帖

13万

积分

管理员

积分
137739
发表于 2024-7-4 17:37:15 | 显示全部楼层 |阅读模式 IP:山东省青岛市 联通

登录后更精彩...O(∩_∩)O...

您需要 登录 才可以下载或查看,没有账号?立即注册

×
android loadLibrary源码分析


前言
本文介绍android4与android8中调用.init .init_array段中函数的过程,找到关键函数方便我们hook它
Android4
我们先来看Android4.4中loadLibrary的源码
/libcore/luni/src/main/java/java/lang/System.java
[Java] 纯文本查看 复制代码
public static void loadLibrary(String libName) {
    Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}

[Java] 纯文本查看 复制代码
 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


[Java] 纯文本查看 复制代码
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


[Java] 纯文本查看 复制代码
/*
 * 参数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);



[Java] 纯文本查看 复制代码
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


[C] 纯文本查看 复制代码
// 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


[C] 纯文本查看 复制代码
// 实现对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();





[C] 纯文本查看 复制代码
// 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函数中对其赋值
[C] 纯文本查看 复制代码
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是如何调用的吧


[C++] 纯文本查看 复制代码
// 构造函数调用的实现
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中的实现

[C++] 纯文本查看 复制代码
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


[C] 纯文本查看 复制代码
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的调用如下
[C++] 纯文本查看 复制代码
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()变成什么样了。



[C++] 纯文本查看 复制代码
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



[C++] 纯文本查看 复制代码
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



[AppleScript] 纯文本查看 复制代码
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来判断的。他们分别定义如下


[C] 纯文本查看 复制代码
typedef void (*linker_ctor_function_t)(int, char**, char**);
typedef void (*linker_dtor_function_t)();



其实在Androi8中调用.init .init_array走的函数基本都是



[C] 纯文本查看 复制代码
static void call_function(const char* function_name __unused, linker_ctor_function_t function, const char* realpath __unused);




所以我们在Android8中需要hook的就是这个函数。




作者:Sharkchilli
链接:https://www.jianshu.com/p/c230c17c99c5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|时间戳|加密|CTF WiKi|CTF平台汇总|CTF show|ctfhub|棱角安全|rutracker|攻防世界|php手册|peiqi文库|CyberChef|猫捉鱼铃|手机版|小黑屋|cn-sec|IOTsec-Zone|在线工具|分享屋 ( 鲁ICP备2021028754号 )

GMT+8, 2024-9-17 04:04

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表