NDK中so的加载分析
//java层加载
#参数为库文件名,不包含库文件的扩展名,必须是在JVM属性Java.library.path所指向的路径中
System.loadLibrary("libname");
#参数必须为库文件的绝对路径,可以是任意路径;
System.load("lib_path");
libcore/ojluni/src/main/java/java/lang/System.java
public static void load(String filename) {
Runtime.getRuntime().load0(VMStack.getStackClass1(), filename);
}
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
libcore/ojluni/src/main/java/java/lang/Runtime.java
synchronized void load0(Class> fromClass, String filename) {
if (!(new File(filename).isAbsolute())) {
throw new UnsatisfiedLinkError("Expecting an absolute path of the library: " + filename);
}
if (filename == null) {
throw new NullPointerException("filename == null");
}
String error = nativeLoad(filename, fromClass.getClassLoader());
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError("Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +System.mapLibraryName(libraryName) + "\"");
}
String error = nativeLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
String filename = System.mapLibraryName(libraryName);
List candidates = new ArrayList();
String lastError = null;
for (String directory : getLibPaths()) {
String candidate = directory + filename;
candidates.add(candidate)
if (IoUtils.canOpenReadOnly(candidate)) {
String error = nativeLoad(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);
}
libcore/ojluni/src/main/native/Runtime.c
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,jobject javaLoader)
{
return JVM_NativeLoad(env, javaFilename, javaLoader);
}
art/openjdkjvm/OpenjdkJvm.cc
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader) {
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,
&error_msg);
if (success) {
return nullptr;
}
}
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
art/runtime/java_vm_ext.cc
java_vm_ext.cc ---> native_loader.cpp ---> OpenNativeLibrary()
java_vm_ext.cc ---> FindSymbol()
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
std::string* error_msg) {
...
OpenNativeLibrary(){
...
android_dlopen_ext()
...
}
FindSymbol(){
...
FindSymbolWithNativeBridge()/FindSymbolWithoutNativeBridge(){
...
dlsym()
...
}
...
}
...
}
简单归纳一下调用流程:
System.loadLibrary()
Runtime.loadLibrary()
Runtime.loadLibrary0()
Runtime.java ---> nativeload()
Runtime.c ---> Runtime_nativeLoad()
OpenjdkJvm.cc ---> JVM_NativeLoad()
java_vm_ext.cc ---> LoadNativeLibrary()
LoadNativeLibrary() 分为两步
1.dlfcn.cpp ---> dlopen() //加载init()/init_array()
2.libdl.cpp ---> dlsym() //加载jni_onload()
继续往后走进入到init/init_array
extern "C"
void _init(void) { }
-------> 编译生成后在.init段
__attribute__((constructor))
void _init(void) { }
-------》编译生成后在.init_array段
__attribute__((destructor))
void final(void) { }
-------》编译生成后在.final_array段
# RegisterNative效率高于直接调用JNI本地函数(静态注册)
- 1.调用 System. loadlibrarye()方法,将包含本地方法具体实现的C++运行库加载![JB7_XF%YY6S1]I367G3)TST.png](https://upload-images.jianshu.io/upload_images/9548468-3c52ae3c0609d2ae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
到内存中
- 2.Java虚拟机检索加载进来的库函数符号,在其中査找与Java本地方法拥有相同签名的JNI本地函数符号。若找到一致的,则将本地方法映射到具体的JNI本地函数
- 3.Android Framework这类复杂的系统下,拥有大量的包含本地方法的Java类,Java虛机加载相应运行库,再逐一检索,将各个本地方法与相应的函数映射起来,这显然会增加运行时间,降低运行的效率,所以有了RegisterNative()
# 调用顺序
linker
# \bionic\linker\dlfcn.cpp
---> do_dlopen() ---> find_library()
## \bionic\linker\linker.cpp
---> CallConstructors()
# 调用init
---> CallFunction("DT_INIT", init_func)
# 调用init_array
---> CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
CallConstructors
// so库文件加载完毕以后调用构造函数
void soinfo::CallConstructors() {
if (constructors_called) {
return;
}
// We set constructors_called before actually calling the constructors, otherwise it doesn't
// protect against recursive constructor calls. One simple example of constructor recursion
// is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:
// 1. The program depends on libc, so libc's constructor is called here.
// 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.
// 3. dlopen() calls the constructors on the newly created
// soinfo for libc_malloc_debug_leak.so.
// 4. The debug .so depends on libc, so CallConstructors is
// called again with the libc soinfo. If it doesn't trigger the early-
// out above, the libc constructor will be called again (recursively!).
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);
}
CallFunction
// 构造函数调用的实现
void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {
// 判断构造函数的调用地址是否符合要求
if (function == NULL || reinterpret_cast(function) == static_cast(-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
set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
}
CallArray
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);
}
以上为安卓4.4.4.rc的源码
以下为安卓9.0.0_r8的源码
第一个dlfcn.cpp 中的申明就不看了,直接看到linker.cpp,代码量确实比4.4.4多多了,但是逻辑都差不多,可以找到 call_constructors
- 和以前一样 call_array() 就是遍历一个数组重复去调用 call_function()
- 守住 call_function() 就可以守住 init() 和 init_array()
下面归纳一下从源码的角度考虑脱壳方向
5.0以下的脱壳时机
dvmDexFileOpenPartial(addr, len, &pDvmDex)
dexFileParse(const u1* data, size_t length, int flags)
5.0~8.0以下的脱壳时机
DexFile::
OpenMemory
(const byte* base ,size_t size, const std::string& location, uint32_t location_checksum, MemMap* mem_map)
8.0及8.0以上的脱壳时机
DexFile::
OpenCommon
(const uint8_t* base, size_t size, const std::string& location, uint32_t location_checksum, const OatDexFile* oat_dex_file, bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result)
OpenCommon
- 汇编位于libdexfile.so (/system/lib/libdexfile.so)
- 源码位于dex_file_loader.cc (源码)
代表脱壳项目:frida-unpack
说到脱壳除了系统关键函数的拦截,还有另一个思路来自hluwa的FRIDA-DEXDump
思路是基于frida的内存dex035魔数的搜索
除了使用frida的动态注入也可以选择使用ida调试断点关键函数,使用idc脚本dump内存,或者是使用xposed插件来实现脱壳,xposed插件:比如WrBug的 dumpDex 以及集成了 dumpDex 的 易开发 ,或者是 ApkShelling 都可以用来脱壳,以上针对一二代壳,至于三代壳可以考虑使用DexHunter
或者是使用frida_dump
dump_dex使用到的关键点在libart.so中的DefineClass
- 基于内存关键字搜索(dex035)
- 基于openCommen函数断点
- 基于DefineClass函数断点
- Fart 函数方法体抽取
Android ClassLoader
对于ClassLoader的基础理解可以让我在插件化,或者是存在动态加载dex的时候找到dex,frida枚举classloader并尝试加载heap中存在class即可找到对应dex的classloader,自然找到了classloader的pathList,一般情况下加壳的apk使用的ClassLoader也是自定义继承自PathClassLoader,所以在使用frida脚本的时候也需要我们手动的去切换一下ClassLoader,使用默认的PathClassLoader可能会找不到类
BootClassLoader
位于:libcore/ojluni/src/main/java/java/lang/ClassLoader.java
SecureClassLoader
位于:libcore/ojluni/src/main/java/java/security/SecureClassLoader.java
(URLClassLoader extends SercureClassLoader)
URLClassLoader
位于:libcore/ojluni/src/main/java/java/net/URLClassLoader.java
BootstrapClassLoader
位于:external/icu/icu4j/main/classes/core/src/com/ibm/icu/impl/*ClassLoaderUtil.java
BaseDexClassLoader
位于:libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
(和java虚拟机中不同的是BootClassLoader是ClassLoader内部类,由java代码实现而不是c++实现,是Android平台上所有ClassLoader的最终parent,这个内部类是包内可见)
PathClassLoader
位于:libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
DexClassLoader
位于:libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
以下着重分析最常见,常用的 BaseDexClassLoader
BaseDexClassLoader的构造函数包含四个参数,分别为:
- dexPath,指目标类所在的APK或jar文件的路径,类装载器将从该路径中寻找指定的目标类
- File optimizedDirectory,由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex,PathClassLoader默认文件存放的路径/data/dalvik-cache
- libPath,指目标类中所使用的C/C++库存放的路径
- ClassLoader,是指该装载器的父装载器,一般为当前执行类的装载器
源码中可以发现BaseDexClassLoader维护了一个
DexPathList pathList
DexPathList中维护一个dex列表
Element[] dexElements
DexFile用来解析dex
DexFile dexFile;
public Enumeration entries() {
return new DFEnum(this);
}
/*
* Helper class.
*/
private static class DFEnum implements Enumeration {
private int mIndex;
@UnsupportedAppUsage
private String[] mNameList;
DFEnum(DexFile df) {
mIndex = 0;
mNameList = getClassNameList(df.mCookie);
}
public boolean hasMoreElements() {
return (mIndex < mNameList.length);
}
public String nextElement() {
return mNameList[mIndex++];
}
}
从Dex中获取所有类
function HookDexMethod(){
Java.perform(function(){
var DexFileclass = Java.use("dalvik.system.DexFile");
var DexPathListclass = Java.use("dalvik.system.DexPathList");
var BaseDexClassLoaderclass = Java.use("dalvik.system.BaseDexClassLoader");
Java.enumerateClassLoaders({
onMatch: function (loader) {
try{
console.log("\n"+loader)
var BaseDexClassLoader_obj = Java.cast(loader, BaseDexClassLoaderclass);
var pathList = BaseDexClassLoader_obj.pathList.value;
console.warn("\npathList\t---->\t",pathList);
var dexElements = Java.cast(pathList, DexPathListclass).dexElements.value;
console.warn("\ndexElements\t---->\t",dexElements);
dexElements.forEach(function(element,index,arrays){
try {
try{var dexfile = element.dexFile.value;}catch(e){}
var dexfileobj = Java.cast(dexfile, DexFileclass);
const DFEnumName = dexfileobj.entries();
while(DFEnumName.hasMoreElements()){
var className = DFEnumName.nextElement().toString()
if(className.indexOf("lzy")!=-1){
console.log(className)
}
}
}catch(e){
console.log(e)
}
})
}catch(e){
}
},
onComplete:function(){}
})
})