Frida Hook大全

Frida脱壳

这里以脱一代整体壳为例.

优点:调试简单,java层的所有功能都能轻易实现.

缺点:native层的部分系统函数是inline函数,没办法hook,也没办法主动调用.

定位脱壳点

我们在Java层主动加载一个类是调用dexClassLoader.loadClass来加载类的.因此可以阅读DexClassLoader类的loadClass源码来寻求答案.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//http://androidxref.com/8.1.0_r33/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
DexClassLoader
->BaseDexClassLoader
->ClassLoader
->loadClass
protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
  {
          // First, check if the class has already been loaded
          Class<?> c = findLoadedClass(name);
          if (c == null) {
              try {
                  if (parent != null) {
                      c = parent.loadClass(name, false);
                  } else {
                      c = findBootstrapClassOrNull(name);
                  }
              } catch (ClassNotFoundException e) {
                  // ClassNotFoundException thrown if class not found
                  // from the non-null parent class loader
              }

              if (c == null) {
                  // If still not found, then invoke findClass in order
                  // to find the class.
                  c = findClass(name);
              }
          }
          return c;
  }

//如果没找到的话,就调用子类的findClass
//http://androidxref.com/8.1.0_r33/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
BaseDexClassLoader.findClass
->pathList.findClass(name, suppressedExceptions)

//http://androidxref.com/8.1.0_r33/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
DexPathList.findClass
->element.findClass
->dexFile.loadClassBinaryName

//http://androidxref.com/8.1.0_r33/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
DexFile.loadClassBinaryName
->defineClass
->defineClassNative

//进入Native层
//http://androidxref.com/8.1.0_r33/xref/art/runtime/native/dalvik_system_DexFile.cc
defineClassNative
->class_linker->DefineClass
->LoadClass
->LoadClassMembers
->LoadMethod(const DexFile& dex_file,const ClassDataItemIterator& it,Handle<mirror::Class> klass,ArtMethod* dst)

//Native层凡出现DexFile的地方均可作为脱壳点

测试Apk

FridaShell.apk

Hook代码

这里通过Hook libart.so中的LoadMethod函数来实现脱壳.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var map = {};
function main() {
    var libart = Module.enumerateSymbols("libart.so");
    var addr = NULL;
    for (var n in libart) {

        if (libart[n].name.indexOf("lassLinker10LoadMethodERKNS") >= 0) {
            addr = libart[n].address;
            break;
        }

    }

    Interceptor.attach(addr, {
        onEnter: function (arg) {
            var begin = ptr(arg[1]).add(Process.pointerSize).readPointer();
            var size = ptr(arg[1]).add(Process.pointerSize * 2).readU32();

            if (map[size] == undefined) {
                map[size] = begin;
                dump(begin, size)
            }
        }

    })

}

function dump(begin, size) {
    var path = "/sdcard/" + size + ".dex";
    var file = new File(path, "w");
    file.write(ptr(begin).readByteArray(size));
    file.flush();
    file.close();
    console.log("dump success " + size + ".dex")
}

setImmediate(main);

追踪静态注册函数地址

测试APK

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//Java层
package com.example.luonative;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.example.luonative.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luonative' library on application startup.
    static {
        System.loadLibrary("luonative");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        //方便IDA附加
        try {
            Thread.sleep(10 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        LuoTst();

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }

    public native String stringFromJNI();

    public native void LuoTst();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//Native层
#include <jni.h>
#include <string>

#include <android/log.h>
#include <dlfcn.h>

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "Luo", __VA_ARGS__);
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "Luo", __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "Luo", __VA_ARGS__);

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_luonative_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject thiz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_luonative_MainActivity_LuoTst(JNIEnv *env, jobject thiz) {
    void *handle = dlopen("libc.so", 0);
    void *addr = dlsym(handle, "strstr");
    LOGD("strstr addr:%p", addr);
}

定位调试点

假设Native层没有实现Java_com_example_luonative_MainActivity_stringFromJNI这个函数,AS会报以下错误.

追踪静态注册函数地址错误提示

说明在遍历so中的导出函数时没有找到上述函数,我们通常找导出函数地址是调用dlsym这个函数.接下来我们在Android源码中跟踪下dlsym这个函数.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//http://androidxref.com/8.1.0_r33/xref/bionic/libdl/libdl.c
dlsym
->__loader_dlsym
->static const char ANDROID_LIBDL_STRTAB[] =
  // 0000000000111111 11112222222222333 333333344444444 44555555555566666 6666677777777778 8888888889999999999
  // 0123456789012345 67890123456789012 345678901234567 89012345678901234 5678901234567890 1234567890123456789
    "__loader_dlopen\0__loader_dlclose\0__loader_dlsym\0__loader_dlerror\0__loader_dladdr\0__loader_android_up"
->__dlsym

//http://androidxref.com/8.1.0_r33/xref/bionic/linker/dlfcn.cpp#__dlsym
__dlsym
->dlsym_impl
->do_dlsym(void* handle, 
           const char* sym_name,
           const char* sym_ver,
           const void* caller_addr,
           void** symbol)

//http://androidxref.com/8.1.0_r33/xref/bionic/linker/linker.cpp
//最终我们发现是调用了linker.so中的do_dlsym函数

确定Hook点

用IDA附加调试上述apk,在linker64.so中的do_dlsym函数下断,一路单步走,看调用栈.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//调用栈
do_dlsym
dlsym_impl
//http://androidxref.com/8.1.0_r33/xref/art/runtime/java_vm_ext.cc
FindSymbol           //以下函数返回值均为静态注册的函数地址
FindNativeMethod
FindCodeForNativeMethod
artFindNativeMethod
    
//http://androidxref.com/8.1.0_r33/xref/art/runtime/entrypoints/jni/jni_entrypoints.cc
extern "C" const void* artFindNativeMethod() {
  Thread* self = Thread::Current();
#else
extern "C" const void* artFindNativeMethod(Thread* self) {
  DCHECK_EQ(self, Thread::Current());
#endif
  Locks::mutator_lock_->AssertNotHeld(self);  // We come here as Native.
  ScopedObjectAccess soa(self);

  ArtMethod* method = self->GetCurrentMethod(nullptr);
  DCHECK(method != nullptr);

  // Lookup symbol address for method, on failure we'll return null with an exception set,
  // otherwise we return the address of the method we found.
  void* native_code = soa.Vm()->FindCodeForNativeMethod(method);
  if (native_code == nullptr) {
    self->AssertPendingException();
    return nullptr;
  }
  // Register so that future calls don't come here
  return method->RegisterNative(native_code, false);
}
    
//从这里可以看到静态注册的函数也是调用了RegisterNative这个函数

打印静态注册函数地址

这里以Hook FindCodeForNativeMethod这个函数为例,通过参数ArtMethod*可以拿到注册的函数名,通过返回值可以拿到注册的地址.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const STD_STRING_SIZE = 3 * Process.pointerSize;
class StdString {
    constructor() {
        this.handle = Memory.alloc(STD_STRING_SIZE);
    }

    dispose() {
        const [data, isTiny] = this._getData();
        if (!isTiny) {
            Java.api.$delete(data);
        }
    }

    disposeToString() {
        const result = this.toString();
        this.dispose();
        return result;
    }

    toString() {
        const [data] = this._getData();
        return data.readUtf8String();
    }

    _getData() {
        const str = this.handle;
        const isTiny = (str.readU8() & 1) === 0;
        const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();
        return [data, isTiny];
    }
}

function prettyMethod(method_id, withSignature) {
    const result = new StdString();
    Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0);
    return result.disposeToString();
}

function main() {
    var addr = Module.findExportByName("libart.so", "_ZN3art9JavaVMExt23FindCodeForNativeMethodEPNS_9ArtMethodE");
    Interceptor.attach(addr, {
        onEnter: function (arg) {
            var methodname = prettyMethod(arg[1], 0);
            this.name = methodname;
        },
        onLeave: function (res) {
            console.log(this.name + ":" + res);
        }
    })

}

setImmediate(main);

追踪动态注册函数地址

动态注册主要使用RegisterNatives这个函数,查看该部分的Android源码即可.

http://androidxref.com/8.1.0_r33/xref/art/runtime/jni_internal.cc

相关的Frida Hook代码已有大牛写好.

https://github.com/lasting-yang/frida_hook_libart/blob/master/hook_RegisterNatives.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function find_RegisterNatives(params) {
    var symbols = Module.enumerateSymbolsSync("libart.so");
    var addrRegisterNatives = null;
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i];
        
        //_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
        if (symbol.name.indexOf("art") >= 0 &&
                symbol.name.indexOf("JNI") >= 0 && 
                symbol.name.indexOf("RegisterNatives") >= 0 && 
                symbol.name.indexOf("CheckJNI") < 0) {
            addrRegisterNatives = symbol.address;
            console.log("RegisterNatives is at ", symbol.address, symbol.name);
            hook_RegisterNatives(addrRegisterNatives)
        }
    }

}

function hook_RegisterNatives(addrRegisterNatives) {

    if (addrRegisterNatives != null) {
        Interceptor.attach(addrRegisterNatives, {
            onEnter: function (args) {
                console.log("[RegisterNatives] method_count:", args[3]);
                var env = args[0];
                var java_class = args[1];
                var class_name = Java.vm.tryGetEnv().getClassName(java_class);
                //console.log(class_name);

                var methods_ptr = ptr(args[2]);

                var method_count = parseInt(args[3]);
                for (var i = 0; i < method_count; i++) {
                    var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
                    var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
                    var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

                    var name = Memory.readCString(name_ptr);
                    var sig = Memory.readCString(sig_ptr);
                    var find_module = Process.findModuleByAddress(fnPtr_ptr);
                    console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr,  " fnOffset:", ptr(fnPtr_ptr).sub(find_module.base), " callee:", DebugSymbol.fromAddress(this.returnAddress));

                }
            }
        });
    }
}

setImmediate(find_RegisterNatives);

Nop掉Native函数

测试Apk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//Java层
package com.example.luonative;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.example.luonative.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luonative' library on application startup.
    static {
        System.loadLibrary("luonative");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }

    public native String stringFromJNI();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//Native层
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_luonative_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject thiz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

Hook代码

本次示例 Nop掉stringFromJNI函数.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("dlopen:", path);

            }
        },
        onLeave: function (retval) {

        }
    })

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("android_dlopen_ext:", path);

                this.path = path;
            }
        },
        onLeave: function (retval) {
            if (this.path.indexOf("libluonative") >= 0) {
                var addr = Module.getExportByName("libluonative.so", "Java_com_example_luonative_MainActivity_stringFromJNI")

                //错误示例 Nop
                // Interceptor.attach(addr, {
                //     onEnter: function (args) {
                //         return
                //     },
                //     onLeave: function (retval) {

                //     }
                // })

                //正确示例 Nop
                Interceptor.replace(addr, new NativeCallback(function () {
                    return 0;
                }, 'int', []));

            }

        }
    });
}

setImmediate(hook_dlopen);

Frida结合自定义Rom

GetDexFile

ArtMethod类中有一个很好用的函数GetDexFile(),但很遗憾它是inline函数,并没有导出.但我们可以编译Android源码,在art_method-inl.h中添加一个导出函数myGetDexFile(),最后在libart.so中可以找到我们导出的函数.

http://androidxref.com/8.1.0_r33/xref/art/runtime/art_method.cc

http://androidxref.com/8.1.0_r33/xref/art/runtime/art_method-inl.h

AndroidStudio新建一个工程,用IDA打开libart.so找到我们导出的函数,将相关代码移植到我们新建的工程中,编译,生成我们自己的so文件(也可以不自己生成so,用Frida直接加载Android源码编译后的libart.so).最后可以用Frida加载我们自己写的so,调用其中的导出函数.

这样的示例,可以参考寒冰大佬的Frida-Fart.

跟踪解释模式下的所有方法

运行在解释模式下的函数,是一定会过解释器的,所以我们要看Android源码中解释器部分的源码.

http://androidxref.com/8.1.0_r33/xref/art/runtime/interpreter/interpreter.cc

在Execute函数中进行插桩,只要拿到ArtMethod*指针,就可以输出函数名.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static inline JValue Execute(
    Thread* self,
    const DexFile::CodeItem* code_item,
    ShadowFrame& shadow_frame,
    JValue result_register,
    bool stay_in_interpreter = false){
    //---------------------------
     ArtMethod *method = shadow_frame.GetMethod();
     LOG(ERROR)<<method->PrettyMethod().c_str();
     if(strstr(method->PrettyMethod().c_str(),"mytrace"))
     return JValue();
    //---------------------------
    }

编译上述Android源码,通过以下JavaScript代码即可输出apk中解释模式下的所有方法.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function main() {
        //Execute函数是inline函数,无法直接Hook,但在上面我们插入了strstr这个函数,故我们通过Hook libc.so中的strstr函数来输出函数名
    var strstr = Module.findExportByName("libc.so", "strstr");
    Interceptor.attach(strstr, {
        onEnter: function (arg) {

            if (ptr(arg[1]).readCString().indexOf("mytrace") >= 0) {
                console.log(ptr(arg[0]).readCString());
            }

        }
    })
}

setImmediate(main);

Hook壳

壳一般会替换app的入口Application,我们Hook壳的Application类中的onCreate()函数,等待其执行完成,获取ClassLoader,进而进行Hook.

测试Apk

皮皮虾.apk

Hook代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function getclassload() {
    var activityobj = Java.use("android.app.ActivityThread").currentActivityThread();
    var mInitialApplication = activityobj.mInitialApplication.value;
    var mLoadedApk = mInitialApplication.mLoadedApk.value;
    var mClassLoader = mLoadedApk.mClassLoader.value;
    return mClassLoader;
}

function main() {
    Java.perform(function () {

        var myshellclass = Java.use("com.sup.android.superb.MyWrapperProxyApplication");

        myshellclass.onCreate.implementation = function () {
            var result = this.onCreate();
            var loader = getclassload();
            //切ClassLoader
            // Java.classFactory.loader=loader;
            var myclass = Java.use("com.sup.android.superb.SplashActivity");

            myclass.onCreate.implementation = function (arg) {
                console.log("i am from SplashActivity onCreate");
                return this.onCreate(arg);
            }

            console.log(myclass);
            return result;

        }

    })
}

setImmediate(main())

Hook插件Dex

测试Dex

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package com.example.dexplugin;

import android.util.Log;

public class MyDex {
    public static String LuoTst() {
        Log.d("LuoHun", "com.example.dexplugin.MyDex.LuoTst");
        return "Hello MyDex!";
    }
}

测试Apk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.example.loaddex;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DexClassLoader dexLoader = new DexClassLoader("/data/local/tmp/mydex.dex",
                "/sdcard",
                "sdcard",
                MainActivity.class.getClassLoader());

        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    Class myDexCls = dexLoader.loadClass("com.example.dexplugin.MyDex");
            /*        Method LuoTst = myDexCls.getDeclaredMethod("LuoTst");
                    LuoTst.setAccessible(true);*/

                    Method[] myMethods = myDexCls.getDeclaredMethods();
                    for (Method n : myMethods) {
                        if (n.getName().indexOf("LuoTst") >= 0) {
                            n.setAccessible(true);
                            String str = (String) n.invoke(null);
                            Log.d("loaddex", str);
                        }
                    }

                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        });

    }

}

Hook代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
function func1() {
    //通过分析apk,可知ClassLoader用的的DexClassLoader
    //我们通过Hook DexClassLoader的构造函数,拿ClassLoader
    var dexclasloader = Java.use("dalvik.system.DexClassLoader");
    dexclasloader.$init.implementation = function (a, b, c, d) {
        var res = this.$init(a, b, c, d);
        //切ClassLoader
        Java.classFactory.loader = this;

        var myclass = Java.use("com.example.dexplugin.MyDex");
        myclass.LuoTst.implementation = function () {
            var res = this.LuoTst();
            console.log(res)
            return "LuoTst Hook";
        }
    }
}

function func2() {
    Java.enumerateClassLoaders({
        onMatch: function (loader) {
            try {
                if (loader.findClass("com.example.dexplugin.MyDex")) {
                    //切ClassLoader
                    Java.classFactory.loader = loader;
                }
            } catch (e) {

            }

        },
        onComplete: function() {
            console.log("enum completed!")
        }
    })

    var myclass = Java.use("com.example.dexplugin.MyDex");
    myclass.LuoTst.implementation = function () {
        var res = this.LuoTst();
        console.log(res)
        return "LuoTst Hook";
    }
}

function func3() {
    Java.choose("dalvik.system.DexClassLoader", {
        onMatch: function (loader) {
            try {
                //切ClassLoader
                Java.classFactory.loader = loader;

                var myclass = Java.use("com.example.dexplugin.MyDex");
                myclass.LuoTst.implementation = function () {
                    var res = this.LuoTst();
                    console.log(res)
                    return "LuoTst Hook";
                }
            } catch (e) {

            }

        },
        onComplete: function () {

        }

    })
}

function main() {
    Java.perform(function () {
        //方案一,spawn方式
        //func1()
        //方案二,不稳
        //func2()
        //方案三,attach方式
        func3()
    })
}

setImmediate(main())

Frida&JNI

这里主要展示下native层如何调用java层,以及frida Hook libart.so中的导出函数.

测试Apk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//Java层
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.example.luodst.databinding.ActivityMainBinding;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "LuoHun";

    // Used to load the 'luodst' library on application startup.
    static {
        System.loadLibrary("luodst");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }

    public native String stringFromJNI();

    public static void  func1(){
        mystaticstr="myfunc1";
        Log.e(TAG,"i am from my func1");

    }

    public static int  func2(int a){
        mystaticstr="myfunc2";
        Log.e(TAG,"i am from my func2");
        return a;

    }
    public static String func3(String a){
        mystaticstr="func3";
        Log.e(TAG,a);
        return a;

    }

    public static byte[] func4(byte[] a){
        mystaticstr="func4";
        Log.e(TAG, Arrays.toString(a));
        return a;
    }

    static String mystaticstr;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//Native层
#include <jni.h>
#include <string>
#include "android/log.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_luodst_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";

    jclass clsMainActivity = env->FindClass("com/example/luodst/MainActivity");
    jmethodID myfun1 = env->GetStaticMethodID(clsMainActivity, "func1", "()V");
    env->CallStaticVoidMethod(clsMainActivity, myfun1);

    jmethodID myfun2 = env->GetStaticMethodID(clsMainActivity, "func2", "(I)I");
    jint myfun2re = env->CallStaticIntMethod(clsMainActivity, myfun2, 58);
    __android_log_print(6, "LuoHun", "%d", myfun2re);

    jmethodID myfun3 = env->GetStaticMethodID(clsMainActivity, "func3",
                                              "(Ljava/lang/String;)Ljava/lang/String;");
    jstring mystr = env->NewStringUTF("1231321321");
    jobject myfun3re = env->CallStaticObjectMethod(clsMainActivity, myfun3, mystr);
    const char *myreturn = env->GetStringUTFChars(static_cast<jstring>(myfun3re), 0);
    __android_log_print(6, "LuoHun", "%s", myreturn);

    jmethodID myfun4 = env->GetStaticMethodID(clsMainActivity, "func4", "([B)[B");
    jbyte mybytearray[5] = {0x11, 0x12, 0x13, 0x14, 0x15};
    jbyteArray mybytearrt = env->NewByteArray(sizeof(mybytearray) / sizeof(jbyte));
    env->SetByteArrayRegion(mybytearrt, 0, sizeof(mybytearray) / sizeof(jbyte), mybytearray);
    jbyteArray myfun4ret = static_cast<jbyteArray>(env->CallStaticObjectMethod(clsMainActivity,
                                                                               myfun4,
                                                                               mybytearrt));
    jbyte *mylog = env->GetByteArrayElements(myfun4ret, 0);
    for (int i = 0; i < env->GetArrayLength(myfun4ret); i++)
        __android_log_print(6, "LuoHun", "%x", *(mylog + i));

    jfieldID myfiled = env->GetStaticFieldID(clsMainActivity, "mystaticstr", "Ljava/lang/String;");
    jstring mystaticstr = static_cast<jstring>(env->GetStaticObjectField(clsMainActivity, myfiled));
    __android_log_print(6, "LuoHun", "%s", env->GetStringUTFChars(mystaticstr, 0));

    return env->NewStringUTF(hello.c_str());
}

Hook代码

这里主要展示了如何打印Native层的class,char*以及jbyteArray.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
function main() {
    var libart = Module.enumerateSymbols("libart.so");
    var findclass = null;
    var GetStaticMethodID = null;
    var NewStringUTF = null;
    var GetStringUTFChars = null;
    var GetByteArrayElements = null;
    var GetStaticFieldID = null;
    var Callstaticmethid = null;
    var CallStaticObjectMethod = null;

    for (var i = 0; i < libart.length; i++) {
        //console.log(libart[i].name)
        var symbol = libart[i];
        if (symbol.name.indexOf("art") >= 0 &&
            symbol.name.indexOf("JNI") >= 0 &&
            symbol.name.indexOf("CheckJNI") < 0) {

            if (symbol.name.indexOf("art3JNI9FindClassEP7_JNIEnvP") >= 0) {
                //console.log(libart[i].name, libart[i].address)
                findclass = libart[i].address
            }

            if (symbol.name.indexOf("N3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jc") >= 0) {
                //console.log(libart[i].name, libart[i].address)
                GetStaticMethodID = libart[i].address
            }

            if (symbol.name.indexOf("rt3JNI12NewStringUTFEP") >= 0) {
                //console.log(libart[i].name, libart[i].address)
                NewStringUTF = libart[i].address
            }

            if (symbol.name.indexOf("rt3JNI17GetStringUTFChar") >= 0) {
                //console.log(libart[i].name, libart[i].address)
                GetStringUTFChars = libart[i].address
            }

            if (symbol.name.indexOf("NI20GetByteArrayElementsEP") >= 0) {
                console.log(libart[i].name, libart[i].address)
                GetByteArrayElements = libart[i].address
            }

            if (symbol.name.indexOf("NI16GetStaticFieldIDEP") >= 0) {
                //console.log(libart[i].name, libart[i].address)
                GetStaticFieldID = libart[i].address
            }

        }

    }

    // Interceptor.attach(findclass, {
    //     onEnter: function (arg) {
    //         console.log("FindClass: " + ptr(arg[1]).readCString());
    //     },
    //     onLeave: function (retval) {}
    // })

    Interceptor.attach(GetStaticMethodID, {
        onEnter: function (arg) {
            //打印class
            var myclass = Java.use("java.lang.Class");
            var ret = Java.cast(arg[1], myclass);
            //console.log(arg[1])
            console.log(ret)

            //打印字符串
            console.log("GetStaticMethodID: " + ptr(arg[2]).readCString())
        },
        onLeave: function (retval) {}
    })

    Interceptor.attach(GetByteArrayElements, {
        onEnter: function (args) {
        },
        onLeave: function (retval) {
            console.log("GetByteArrayElements:\n" + hexdump(retval))
        }
    })

}

setImmediate(main);

Frida主动调用

这里主要展示frida如何主动调用Native层函数.

测试Apk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//Java层
package com.example.luonative;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.example.luonative.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luonative' library on application startup.
    static {
        System.loadLibrary("luonative");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'luonative' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//Native层
#include <jni.h>
#include <string>

#include <android/log.h>

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "LuoHun", __VA_ARGS__);
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "LuoHun", __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "LuoHun", __VA_ARGS__);

extern "C" {

int myFunc1(int n) {
    LOGE("myFunc1 %d\n", n);
    return n;
}

const char *myFunc2(const char *szBuffer) {
    LOGE("myFunc2 %s\n", szBuffer);
    return szBuffer;
}

jbyte* myFunc3(jbyte* b) {
    LOGE("myFunc3 %p\n", b);
    return b;
}

}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_luonative_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";

    return env->NewStringUTF(hello.c_str());
}

Hook代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("dlopen:", path);

            }
        },
        onLeave: function (retval) {

        }
    })

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("android_dlopen_ext:", path);

                if (path.indexOf("libluonative.so") >= 0) {
                    this.target = true;
                    console.log("found [android_dlopen_ext:]", path);
                }

            }
        },
        onLeave: function (retval) {
            if (this.target) {
                
                var func1 = Module.findExportByName("libluonative.so", "myFunc1");
                func1 = new NativeFunction(func1, 'int', ['int']);
                console.log(func1(1));

                var func2 = Module.findExportByName("libluonative.so", "myFunc2");
                func2 = new NativeFunction(func2, 'pointer', ['pointer']);
                var mystr = Memory.alloc(100).writeUtf8String("func2 xxxxxxx");
                console.log(func2(mystr).readCString());

                var func3 = Module.findExportByName("libluonative.so", "myFunc3");
                func3 = new NativeFunction(func3, 'pointer', ['pointer']);
                var b = [0x81, 0x99, 0xff, 0x7d];
                mystr = Memory.alloc(100).writeByteArray(b);
                console.log(hexdump(func3(mystr)));
            }

        }
    });
}

setImmediate(hook_dlopen);

相关内容

0%