我在雀语上面也发了一份, 有TOC看起来比较方便 我的雀语

JNI 导入

jni.h 下载地址

IDA中加入头文件解析

  1. File - loadfile - parse C header 选择 jni.h 文件
  2. 鼠标指向函数 - 右键 - Convert to struct *
  3. 选择 _JNIEnv

Hook 方式

So 的 Hook 分为 两种类型及: PLT/GOT 与 InLine , 《Android中Native Hook的两种方式的区别(PLT hook与Inline hook)》 个人而言比较喜欢 InLine Hook

PLT/GOT Hook

通俗的说就是利用 连接器符号 进行 Hook, 并不需要地址

InLine Hook

通俗的说就是通过 内存地址 进行 hook
function inline_hook() {
    var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
    if (libnative_lib_addr) {
        console.log("libnative_lib_addr:", libnative_lib_addr);
        var addr_101F4 = libnative_lib_addr.add(0x102BC);
        console.log("addr_101F4:", addr_101F4);

        Java.perform(function () {
            Interceptor.attach(addr_101F4, {
                onEnter: function (args) {
                    console.log("addr_101F4 OnEnter :", this.context.PC, this.context.x1, this.context.x5, this.context.x10);
                },
                onLeave: function (retval) {
                     console.log("retval is :", retval) 
                }
            }
            )
        })
    }
}

引入指定 So

参考本文下面的 Art Trace

var module_libext = null;
if (Process.arch === "arm64") {
    module_libext = Module.load("/data/app/libext64.so");
} else if (Process.arch === "arm") {
    module_libext = Module.load("/data/app/libext.so");
}
if (module_libext != null) {
    var addr_PrettyMethod = module_libext.findExportByName("PrettyMethod");
    var PrettyMethod = new NativeFunction(addr_PrettyMethod, "void", ["pointer", "pointer", "pointer", "int"]);

    if (ArtMethod_Invoke) {
        var foo_ArtMethod_PrettyMethod = new NativeFunction(ArtMethod_PrettyMethod, "pointer", ["pointer", "int"]);
        console.log(foo_ArtMethod_PrettyMethod);
        Interceptor.attach(ArtMethod_Invoke, {
            onEnter: function (args) {
                try {
                    var result = Memory.alloc(0x100);
                    PrettyMethod(ArtMethod_PrettyMethod, args[0], result, 0x100);
                    console.log(result.readCString());

                } catch (error) {
                    console.log(error);
                }

            }, onLeave: function (retval) {

            }
        });
    }
}

数据读写

读数据

显示参数或者函数内容
  • readCString 读字符串的时候用
  • hexdump 读取hex信息
  • readByteArray 读指定长度内存 (要写内存的话只能用他的兄弟 writeByteArray 了)
  • env 主动调用函数读取
// so 地址 : soAddr

// 方法1:(字符串)根据地址直接读
soAddr.add(0x2c00).readCString();

// 方法2 hexdump(根据地址显示dump内容)
hexdump(soAddr.add(0x2c00));

// 方法3 功能同上,但指定长度
// 读16个字节 将内存数据,输出可见数据 (必须用console.log才能输出)
soAddr.add(0x2c00).readByteArray(16);

// 使用和 navcat 中相同的方法转换成 cstring, 如果是 jstring 需要先转换为 cstring 才能阅读
Interceptor.attach(nativePointer, {
    onEnter: function(args){
        var env = Java.vm.getEnv();
        var jstring = env.getStringUtfChars(args[2], null).readCString();
    },
    onLeave: function(retval){}
});

写数据

用来修改一些函数的返回值等等

官方API位置

// 指定地址写入数据
soAddr.add(0x2C00).writeByteArray(stringToBytes("love you"));


// 替换返回数据,具体运用的 env 函数要看 ndk 是怎么写的。
Interceptor.attach(nativePointer, {
    onEnter: function(args){
    },
    onLeave: function(retval){
        var env = Java.vm.getEnv();
        var newRetval = env.newStringUtf("newRetval"); // 根据ndk中对应的env方法生成新的值
        retval.replace(ptr(newRetval))  // 这是官方api, 需要replace 一个指针进去(ptr方法)
    }
});

修改参数、返回值

在分析协议的时候较少情况回去修改参数或者返回值

修改 JNIEnv

这里是一个替换 NewStringUTF 的例子
function hook_replace() {
    var addr_NewStringUTF = null;

    // 找到 NewStringUTF 地址
    //console.log( JSON.stringify(Process.enumerateModules()));
    var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i].name;
        if ((symbol.indexOf("CheckJNI") == -1) && (symbol.indexOf("JNI") >= 0)) {
            if (symbol.indexOf("NewStringUTF") >= 0) {
                console.log(symbols[i].name);
                console.log(symbols[i].address);
                addr_NewStringUTF = symbols[i].address;
            }
        }
    }
    console.log("addr_NewStringUTF:", addr_NewStringUTF);  

    // 替换
    var NewStringUTF = new NativeFunction(addr_NewStringUTF, 'pointer', ['pointer', 'pointer']); // 该函数有2个返回值 
    Java.perform(function () {
        Interceptor.replace(addr_NewStringUTF,
            new NativeCallback(
                function (parg0, parg1) {  // 该函数有2个参数
                    console.log("original args:", parg0, Memory.readCString(parg1));
                    var newParg = Memory.allocUtf8String("stringFromFridaNativeHookReplace")
                    var NS = NewStringUTF(parg0, newParg);
                    return NS;
                },
                "pointer",
                ["pointer", "pointer"]))
    })
}

function main() {
    hook_replace();
}
setImmediate(main)

修改普通函数

  • 参数修改:参数 = ptr(1000);
  • 返回值修改:返回值.replace(10000);
Java.perform(function(){
        // 参数1:so文件全名
        // 参数2:导出函数名
        // 返回值:地址指针
        var nativePointer = Module.findExportByName("libhello.so", "Java_com_xiaojianbang_app_NativeHelper_add");
        send("native指针地址: " + nativePointer);

        // hook 这个地址
        // hook 函数不需要写参数类型、参数个数
        Interceptor.attach(nativePointer, {
                // 进入函数前Hook
                onEnter: function(args){
                    send(args[0]);
                    send(args[1]);
                    send(args[2].toInt32());
                    send(args[3].toInt32());
                    send(args[4].toInt32());
                    args[4] = ptr(1000);   //new NativePointer
                    send(args[4].toInt32());
                },
                // 完成函数hook, retval是返回值
                onLeave: function(retval){
                    send(retval.toInt32()); // 转到10 进制显示内容
                    retval.replace(10000);
                    send(retval.toInt32());
                }
            });
    });

例:干掉检测线程

这是一个示例代码: hook 掉检测线程,防止被检测到 frida
function hook_libc_replace() {
    var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
    console.log("libnative_lib_addr is :", libnative_lib_addr);

    if (libnative_lib_addr) {
        var detect_frida_loop = Module.findExportByName("libnative-lib.so", "_Z17detect_frida_loopPv");
        console.log("_detect_frida_loop ", detect_frida_loop);  // 先找到创建检测frida的线程位置
    }

    var pthread_create_addr = null;
    var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i].name;

        if (symbol.indexOf("pthread_create") >= 0) {
            //console.log(symbols[i].name);
            //console.log(symbols[i].address);
            pthread_create_addr = symbols[i].address;
        }

    }
    console.log("pthread_create_addr,", pthread_create_addr);

    // 把主动调用干掉
    var pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]);

    Java.perform(function () {
        Interceptor.replace(pthread_create_addr, new NativeCallback(function (parg0, parg1, parg2, parg3) {
            console.log("parg0,parg1,parg2,parg3:", parg0, parg1, parg2, parg3);
            var PC = null;
            if (String(parg2).endsWith("db0")) {
                console.log("anti-debug sequence");
            } else {
                console.log("ordinary sequence")
                var PC = pthread_create(parg0, parg1, parg2, parg3);
            }
            return PC;
        }, "int", ["pointer", "pointer", "pointer", "pointer"]))
    })

}

function main() {
    hook_libc_replace();
}
setImmediate(main)

主动调用

调用JNIEnv

通过 Java.vm.getEnv() 可以获取到 env 对象, 从而进行 env 操作非常方便。
// 使用和 navcat 中相同的方法转换成 cstring, 如果是 jstring 需要先转换为 cstring 才能阅读
Interceptor.attach(nativePointer, {
    onEnter: function(args){
        var env = Java.vm.getEnv();
        var jstring = env.getStringUtfChars(args[2], null).readCString();
    },
    onLeave: function(retval){}
});

调用静态函数

// 获取函数地址过程省略 addr_hard

// 第一个int 是返回值类型, 第二三个int 是函数的参数
var funcAddr = new NativeFunction(addr_hard, "int", ["int", "int"]) 
send(funcAddr(1,1)); // 调用函数

Hook JNIEnv

想要 java 与 c 代码链接,那么就必须调用 JNIEnv 来进行交互,那么我们就可以通过hook JNIEnv 来实现部分自己想要的结果, 也就是 hook 标准函数

手动hook - 指定偏移

再知道偏移量的情况下,这里 NewStringUTF 的偏移量是 668 0x29c 上面这种方法是再知道偏移量的情况下直接使用的并且 指针32位下4个字节, 64位下8个字节不同平台偏移量也是不同的。不是最推荐的方法
var env = Java.vm.getEnv();  // 获取 JNIEnv 对象
send(env);
var handlePointer = Memory.readPointer(env.handle);  // 读出 【env结构体的指针】
send("env handle 结构体的指针: " + handlePointer);

// hook env 的 NewStringUTF函数
var NewStringUTFPtr = Memory.readPointer(handlePointer.add(0x29C)); // 偏移量是668 十六进制就是29c
send("NewStringUTFPtr addr: " + NewStringUTFPtr);
Interceptor.attach(NewStringUTFPtr, {
    onEnter: function (args) {
        //send(Memory.readUtf8String(args[1]));
        send(Memory.readCString(args[1]));
        var buffer = Memory.readByteArray(args[1],16);
        console.log(hexdump(buffer, {
            offset: 0,
            length: 16,
            header: true,
            ansi: false
        }));

    },
    onLeave: function(retval){
        send("jni返回的:" + retval);
    }
});

手动hook - libart(推荐)

动态获取libart的导出函数, 并过滤出想要 hookjni 函数
var addr_GetStringUTFChars = null;  

//console.log( JSON.stringify(Process.enumerateModules()));
var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
for(var i = 0;i<symbols.length;i++){
    var symbol = symbols[i].name;
    if((symbol.indexOf("CheckJNI")==-1)&&(symbol.indexOf("JNI")>=0)){
        if(symbol.indexOf("GetStringUTFChars")>=0){  // 以GetStringUTFChars为例
            console.log(symbols[i].name);
            console.log(symbols[i].address);
            addr_GetStringUTFChars = symbols[i].address;
        }
    }
}
console.log("addr_GetStringUTFChars:",addr_GetStringUTFChars);
Java.perform(function () {
    Interceptor.attach(addr_GetStringUTFChars, {
        onEnter: function (args) {
            console.log("addr_GetStringUTFChars OnEnter args[0],args[1]", args[0], args[1]);
            //console.log(hexdump(args[0].readPointer()));
            //console.log(Java.vm.tryGetEnv().getStringUtfChars(args[0]).readCString()); 
        }, onLeave: function (retval) {
            console.log("addr_GetStringUTFChars OnLeave", ptr(retval).readCString());
        }
    })
})

JNIEnv Traes

参考 GitHub 功能等同于 jnitrace 更轻量级, 用于自吐 JNIEnv 函数的调用

通用hook代码

hook_art.js
// hook_art.js
/*
GetFieldID is at  0xe39b87c5 _ZN3art3JNI10GetFieldIDEP7_JNIEnvP7_jclassPKcS6_
GetMethodID is at  0xe39a1a19 _ZN3art3JNI11GetMethodIDEP7_JNIEnvP7_jclassPKcS6_
NewStringUTF is at  0xe39cff25 _ZN3art3JNI12NewStringUTFEP7_JNIEnvPKc
RegisterNatives is at  0xe39e08fd _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
GetStaticFieldID is at  0xe39c9635 _ZN3art3JNI16GetStaticFieldIDEP7_JNIEnvP7_jclassPKcS6_
GetStaticMethodID is at  0xe39be0ed _ZN3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jclassPKcS6_
GetStringUTFChars is at  0xe39d06e5 _ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringPh
FindClass is at  0xe399ae5d _ZN3art3JNI9FindClassEP7_JNIEnvPKc
*/
function hook_libart() {
    var symbols = Module.enumerateSymbolsSync("libart.so");
    var addrGetStringUTFChars = null;
    var addrNewStringUTF = null;
    var addrFindClass = null;
    var addrGetMethodID = null;
    var addrGetStaticMethodID = null;
    var addrGetFieldID = null;
    var addrGetStaticFieldID = null;
    var addrRegisterNatives = null;
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i];
        if (symbol.name.indexOf("art") >= 0 &&
            symbol.name.indexOf("JNI") >= 0 &&
            symbol.name.indexOf("CheckJNI") < 0
        ) {
            if (symbol.name.indexOf("GetStringUTFChars") >= 0) {
                addrGetStringUTFChars = symbol.address;
                console.log("GetStringUTFChars is at ", symbol.address, symbol.name);
            } else if (symbol.name.indexOf("NewStringUTF") >= 0) {
                addrNewStringUTF = symbol.address;
                console.log("NewStringUTF is at ", symbol.address, symbol.name);
            } else if (symbol.name.indexOf("FindClass") >= 0) {
                addrFindClass = symbol.address;
                console.log("FindClass is at ", symbol.address, symbol.name);
            } else if (symbol.name.indexOf("GetMethodID") >= 0) {
                addrGetMethodID = symbol.address;
                console.log("GetMethodID is at ", symbol.address, symbol.name);
            } else if (symbol.name.indexOf("GetStaticMethodID") >= 0) {
                addrGetStaticMethodID = symbol.address;
                console.log("GetStaticMethodID is at ", symbol.address, symbol.name);
            } else if (symbol.name.indexOf("GetFieldID") >= 0) {
                addrGetFieldID = symbol.address;
                console.log("GetFieldID is at ", symbol.address, symbol.name);
            } else if (symbol.name.indexOf("GetStaticFieldID") >= 0) {
                addrGetStaticFieldID = symbol.address;
                console.log("GetStaticFieldID is at ", symbol.address, symbol.name);
            } else if (symbol.name.indexOf("RegisterNatives") >= 0) {
                addrRegisterNatives = symbol.address;
                console.log("RegisterNatives is at ", symbol.address, symbol.name);
            }
        }
    }
    if (addrGetStringUTFChars != null) {
        Interceptor.attach(addrGetStringUTFChars, {
            onEnter: function(args) {},
            onLeave: function(retval) {
                if (retval != null) {
                    var bytes = Memory.readCString(retval);
                    console.log("[GetStringUTFChars] result:" + bytes);
                }
            }
        });
    }
    if (addrNewStringUTF != null) {
        Interceptor.attach(addrNewStringUTF, {
            onEnter: function(args) {
                if (args[1] != null) {
                    var string = Memory.readCString(args[1]);
                    console.log("[NewStringUTF] bytes:" + string);
                }
            },
            onLeave: function(retval) {}
        });
    }
    if (addrFindClass != null) {
        Interceptor.attach(addrFindClass, {
            onEnter: function(args) {
                if (args[1] != null) {
                    var name = Memory.readCString(args[1]);
                    console.log("[FindClass] name:" + name);
                }
            },
            onLeave: function(retval) {}
        });
    }
    if (addrGetMethodID != null) {
        Interceptor.attach(addrGetMethodID, {
            onEnter: function(args) {
                if (args[2] != null) {
                    var name = Memory.readCString(args[2]);
                    if (args[3] != null) {
                        var sig = Memory.readCString(args[3]);
                        console.log("[GetMethodID] name:" + name + ", sig:" + sig);
                    } else {
                        console.log("[GetMethodID] name:" + name);
                    }
                }
            },
            onLeave: function(retval) {}
        });
    }
    if (addrGetStaticMethodID != null) {
        Interceptor.attach(addrGetStaticMethodID, {
            onEnter: function(args) {
                if (args[2] != null) {
                    var name = Memory.readCString(args[2]);
                    if (args[3] != null) {
                        var sig = Memory.readCString(args[3]);
                        console.log("[GetStaticMethodID] name:" + name + ", sig:" + sig);
                    } else {
                        console.log("[GetStaticMethodID] name:" + name);
                    }
                }
            },
            onLeave: function(retval) {}
        });
    }
    if (addrGetFieldID != null) {
        Interceptor.attach(addrGetFieldID, {
            onEnter: function(args) {
                if (args[2] != null) {
                    var name = Memory.readCString(args[2]);
                    if (args[3] != null) {
                        var sig = Memory.readCString(args[3]);
                        console.log("[GetFieldID] name:" + name + ", sig:" + sig);
                    } else {
                        console.log("[GetFieldID] name:" + name);
                    }
                }
            },
            onLeave: function(retval) {}
        });
    }
    if (addrGetStaticFieldID != null) {
        Interceptor.attach(addrGetStaticFieldID, {
            onEnter: function(args) {
                if (args[2] != null) {
                    var name = Memory.readCString(args[2]);
                    if (args[3] != null) {
                        var sig = Memory.readCString(args[3]);
                        console.log("[GetStaticFieldID] name:" + name + ", sig:" + sig);
                    } else {
                        console.log("[GetStaticFieldID] name:" + name);
                    }
                }
            },
            onLeave: function(retval) {}
        });
    }
    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);
                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, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
                }
            },
            onLeave: function(retval) {}
        });
    }
}
setImmediate(hook_libart);

使用:

frida -U --no-pause -f package_name -l hook_art.js

Env 调用无处遁形

Hook JNI_Onload

参考 GitHub

动态注册的代码

JNIEXPORT jstring JNICALL stringFromJNI2(JNIEnv *env, jclass clazz) {
    std::string hello = "Hello from C++2";
    return env->NewStringUTF(hello.c_str());
}



jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv * env;
    vm->GetEnv((void**)&env,JNI_VERSION_1_6);
    JNINativeMethod methods[] = {
            {"stringFromJNI2","()Ljava/lang/String;",(void*)stringFromJNI2},
    };
    env->RegisterNatives(env->FindClass("com/example/navso/MainActivity"),methods,1);
    return JNI_VERSION_1_6;
}

通用hook代码

hook_RegisterNatives.js
// hook_RegisterNatives.js
function hook_RegisterNatives() {
    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);
        }
    }
    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, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
                }
            }
        });
    }
}
setImmediate(hook_RegisterNatives);

使用:

frida -U --no-pause -f package_name -l hook_RegisterNatives.js

效果:

注册的参数位置,偏移清晰可见

拓展

当然如果被深度反调试 Frida, 那么久改源码AOSP插桩输出。 参考文章

Hook libc

hook c 中的标准函数: 日路创建线程 pthread_create
var pthread_create_addr = null;
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();  
for(var i = 0;i<symbols.length;i++){  // 遍历
    var symbol = symbols[i].name;

    if(symbol.indexOf("pthread_create")>=0){  // 以pthread_create为例
        //console.log(symbols[i].name);
        //console.log(symbols[i].address);
        pthread_create_addr = symbols[i].address;
    }
}
console.log("pthread_create_addr,",pthread_create_addr);
Interceptor.attach(pthread_create_addr,{
    onEnter:function(args){
        console.log("pthread_create_addr args[0],args[1],args[2],args[3]:",args[0],args[1],args[2],args[3]);

    },onLeave:function(retval){
        console.log("retval is:",retval)
    }
})

Hook dlopen

效果就是 hook java 中的 System.loadLibrary("xxxx"); 和直接hook这个函数的区别就是 dlopen 更底层, 在普通 hook 无法达到预期效果的时候,请用这招。

通常也可以使用 dlopen 或者 mmap 函数,在Native 中引用 so

  • dlopen (低版本用)
  • android_dlopen_ext (高版本用)
// 这里是高低版本都hook
function Test(){
    var dlopen = Module.findExportByName(null, "dlopen");
    console.log(dlopen);
    if(dlopen != null){
        Interceptor.attach(dlopen,{
            onEnter: function(args){
                var soName = args[0].readCString();
                console.log(soName);
                if(soName.indexOf("libxiaojianbang.so") != -1){
                    this.hook = true;
                }
            },
            onLeave: function(retval){
                if(this.hook) { hookTest5() };
            }
        });
    }

    var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
    console.log(android_dlopen_ext);
    if(android_dlopen_ext != null){
        Interceptor.attach(android_dlopen_ext,{
            onEnter: function(args){
                var soName = args[0].readCString();
                console.log(soName);
                if(soName.indexOf("libxiaojianbang.so") != -1){
                    this.hook = true;
                }
            },
            onLeave: function(retval){
                if(this.hook) { hookTest5() };
            }
        });
    }

}

// 功能函数转换
function hexToBytes(str) {
    var pos = 0;
    var len = str.length;
    if (len % 2 != 0) {
        return null;
    }
    len /= 2;
    var hexA = new Array();
    for (var i = 0; i < len; i++) {
        var s = str.substr(pos, 2);
        var v = parseInt(s, 16);
        hexA.push(v);
        pos += 2;
    }
    return hexA;
}
// 功能函数转换
function stringToBytes(str) {  
    var ch, st, re = []; 
    for (var i = 0; i < str.length; i++ ) { 
        ch = str.charCodeAt(i);  
        st = [];                 
        do {  
            st.push( ch & 0xFF );  
            ch = ch >> 8;          
        }    
        while ( ch );  
        re = re.concat( st.reverse() ); 
    }  
    return re;  
}

Hook init_array

Jni_onload 之前还有一个 init_arrayinit , 是其更低一层的函数。

静态分析

  • init 名字不可更改 (可以在IDA中直接搜索 init 直接找到)
  • init_array 名字可以更改 (IDA中按F7, 找到 init_array 并进入)

动态分析

利用 IDA 动态调试获取 init 的函数

InLine Hook 常见实例

例:动态获取So Hook函数

这是 InLine Hook 的方法,通过函数地址hook

注意!! 你用ida的so版本,要和你手机使用的so相同才行, 比如你看的arm平台,但是你手机并没有使用该平台的so,这种情况永远调试错误

import frida, sys

jsCode = """
Java.perform(function(){
        // 获取 So 地址
        var soAddr = Module.findBaseAddress("libhello.so");
        send("So地址:" + soAddr);
        // 函数地址 = so地址.add(偏移地址 + 1)  // 是否+1 取决于cpu平台型号
        var MD5FinalAddr = soAddr.add(0x1768+1)  // 0x 代表 16进制
        send('MD5Final地址:'+MD5FinalAddr)
        
        // hook 这个地址
        // hook 函数不需要写参数类型、参数个数
        Interceptor.attach(MD5FinalAddr, {
            // 进入函数前Hook
            onEnter: function(args){
                send(args[0]);
                send(args[1]);
            },
            // 完成函数hook, retval是返回值
            onLeave: function(retval){
                send(retval); // 16进制 指针
                console.log(hexdump(retval));  // 这是直接返回可视信息
            }
        });
    });
"""


def message(message, data):
    if message["type"] == 'send':
        print(u"[*] {0}".format(message['payload']))
    else:
        print(message)


process = frida.get_remote_device().attach("com.xxx.xxx")
script = process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()

流程:

  1. 动态获取 So 地址
  2. hook函数地址 = so地址.add(偏移地址 + 1)

这些就是偏移地址

例:Hook 遍历导入导出表

有时候,不知道导入导出表名,这个时候需要遍历
import frida, sys

jsCode = """
Java.perform(function(){
        // 导入
        var imports = Module.enumerateImportsSync("libhello.so");
        send(imports); // 打印全部
        for(var i = 0; i < imports.length; i++) {
            // 判断是否是自己需要的
            if(imports[i].name == 'strncat'){
                send(imports[i].name + ": " + imports[i].address);
                break;
            }
        }
        
        // 导出
        var exports = Module.enumerateExportsSync("libhello.so");
        for(var i = 0; i < exports.length; i++) {
            // 判断是否是自己需要的
            if(exports[i].name.indexOf('add') != -1){ 
                send(exports[i].name + ": " + exports[i].address);
                break;
            }
        }
    });
"""

def message(message, data):
    if message["type"] == 'send':
        print(u"[*] {0}".format(message['payload']))
    else:
        print(message)


process = frida.get_remote_device().attach("com.xxx.xxx")
script = process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()

例:Hook 指针函数的返回值(参考形式的返回值)

这是一个hook so 中的MD5 加密结果

import frida, sys

jsCode = """

Java.perform(function(){
        var soAddr = Module.findBaseAddress("libhello.so");
        send('soAddr: ' + soAddr);
        var resultPtr = null;  // 准备一个临时变量储存地址指针 
        var MD5FinalAddr = soAddr.add(0x1768 + 1);
        send('MD5FinalAddr: ' + MD5FinalAddr);
        
        Interceptor.attach(MD5FinalAddr, {
            onEnter: function(args){
                send(args[0]);
                send(args[1]);
                resultPtr = args[1];  // 用临时变量来储存这个指针地址(等待函数执行完之后,才是需要的值)
            },
            onLeave: function(retval){
                send(retval);
                
                // 通过指针地址(resultPtr),获取内存数据
                var buffer = Memory.readByteArray(resultPtr, 16); // 参数1: 地址 ;参数2:读多少位
                
                // 将内存数据,输出可见数据 (必须用console.log才能输出)
                // hexdump 参数1: 内存数; 参数2:参数对象
                console.log(hexdump(buffer, {
                    offset: 0,  // 起始
                    length: 16,  // 结尾
                    header: true,  
                    ansi: false  // false = utf8
                }));
            }
        });
    });
"""

def message(message, data):
    if message["type"] == 'send':
        print(u"[*] {0}".format(message['payload']))
    else:
        print(message)


process = frida.get_remote_device().attach("com.xxx.xxx")
script = process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()

简易版示例

function hookTest5(){
    var soAddr = Module.findBaseAddress("xxx.so");
    console.log(soAddr);
    var sub_930 = soAddr.add(0x930); //函数地址计算 thumb+1 ARM不加
    console.log(sub_930);


     var sub_208C = soAddr.add(0x208C); //函数地址计算 thumb+1 ARM不加
     console.log(sub_208C);
     if(sub_208C != null){
        Interceptor.attach(sub_208C,{
            onEnter: function(args){
                this.args1 = args[1]; // 储存参考形参数
            },
            onLeave: function(retval){
                console.log(hexdump(this.args1));  // 取出
            }
        });
     }
}

上面是一个hook so 中的MD5 加密结果

  1. 指针函数需要一个临时变量来储存指针地址
  2. 当函数执行完成后才能开始获取指针对应的内存数据
  3. 通过指针地址,获取内存数据 readByteArray
  4. 将内存数据,输出可见数据 (必须用console.log才能输出)hexdump 方法

hook 结果

这就是hook 到的 md5 结果

例:修改指针指向的内存数据

继续上面一则(我们获取到了md5 的值),接下来我们要修改这个值
  • [x] readByteArray() 根据指针读取内存指定数据
  • [x] writeByteArray() 修改数据
# -*- encoding: utf-8 -*-
# Auth: Zok  Email: 362416272@qq.com
# Date: 2020/2/10
import frida, sys

jsCode = """

Java.perform(function(){

        var soAddr = Module.findBaseAddress("libhello.so");
        send('soAddr: ' + soAddr);
        var resultPtr = null;  // 准备一个临时变量储存地址指针 
        var MD5FinalAddr = soAddr.add(0x1768 + 1);
        send('MD5FinalAddr: ' + MD5FinalAddr);
        Interceptor.attach(MD5FinalAddr, {
            onEnter: function(args){
                send(args[0]);
                send(args[1]);
                resultPtr = args[1];  // 用临时变量来储存这个指针地址(等待函数执行完之后,才是需要的值)
            },
            onLeave: function(retval){
                send(retval);
                
                // 通过指针地址(resultPtr),获取内存数据
                var buffer = Memory.readByteArray(resultPtr, 16);
                // 将内存数据,输出可见数据 (必须用console.log才能输出)
                // hexdump 参数1: 内存书; 参数2:参数对象
                console.log(hexdump(buffer, {
                    offset: 0,
                    length: 16,
                    header: true,
                    ansi: false
                }));
    
                // 修改内存数据 参数2需要字节说数据
                Memory.writeByteArray(resultPtr, stringToBytes('1234567890'));
    
                // 重新读出确认
                buffer = Memory.readByteArray(resultPtr, 32);
                console.log(hexdump(buffer, {
                    offset: 0,
                    length: 32,
                    header: true,
                    ansi: false
                }));
            }
        });
                function hexToBytes(str) {
            var pos = 0;
            var len = str.length;
            if (len % 2 != 0) {
                return null;
            }
            len /= 2;
            var hexA = new Array();
            for (var i = 0; i < len; i++) {
                var s = str.substr(pos, 2);
                var v = parseInt(s, 16);
                hexA.push(v);
                pos += 2;
            }
            return hexA;
        }
    
        function stringToBytes(str) {  
            var ch, st, re = []; 
            for (var i = 0; i < str.length; i++ ) { 
                ch = str.charCodeAt(i);  
                st = [];                 
               do {  
                    st.push( ch & 0xFF );  
                    ch = ch >> 8;          
                }    
                while ( ch );  
                re = re.concat( st.reverse() ); 
            }  
            return re;  
        } 
    
    });
"""


def message(message, data):
    if message["type"] == 'send':
        print(u"[*] {0}".format(message['payload']))
    else:
        print(message)


process = frida.get_remote_device().attach("com.xxx.xxx")
script = process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()

修改成功

Traes

JNI

Hook 调用 Jni 函数, hook较全 Jni 的标准函数用它足矣

jnitrace GitHub

pip install jnitrace

使用方法在 github 上还是有详细的说明的, 或者通过 jnitrace --help 查看

例如:

jnitrace -l libnative-lib.so com.example.myapplication

就可以快速 trace 该项目还有很多的功能,看文档来慢慢使用!

Art

hook artmethod 用于打印 so 中调用 java层的函数, 参考 GitHub

js代码不能单独运行!! 需要下载项目中的 lib 文件夹中的 so

function hook_native() {
    var module_libart = Process.findModuleByName("libart.so");
    var symbols = module_libart.enumerateSymbols();
    var ArtMethod_Invoke = null;
    var ArtMethod_PrettyMethod = null;
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i];
        var address = symbol.address;
        var name = symbol.name;
        var indexArtMethod = name.indexOf("ArtMethod");
        var indexInvoke = name.indexOf("Invoke");
        var indexThread = name.indexOf("Thread");
        if (indexArtMethod >= 0
            && indexInvoke >= 0
            && indexThread >= 0
            && indexArtMethod < indexInvoke
            && indexInvoke < indexThread) {
            console.log(name);
            ArtMethod_Invoke = address;
        }

        if (indexArtMethod >= 0 && name.indexOf("PrettyMethod") >= 0 && name.indexOf("Eb") >= 0) {
            console.log(name);
            ArtMethod_PrettyMethod = address;
        }
    }

    var module_libext = null;
    if (Process.arch === "arm64") {
        module_libext = Module.load("/data/app/libext64.so");
    } else if (Process.arch === "arm") {
        module_libext = Module.load("/data/app/libext.so");
    }
    if (module_libext != null) {
        var addr_PrettyMethod = module_libext.findExportByName("PrettyMethod");
        var PrettyMethod = new NativeFunction(addr_PrettyMethod, "void", ["pointer", "pointer", "pointer", "int"]);

        if (ArtMethod_Invoke) {
            var foo_ArtMethod_PrettyMethod = new NativeFunction(ArtMethod_PrettyMethod, "pointer", ["pointer", "int"]);
            console.log(foo_ArtMethod_PrettyMethod);
            Interceptor.attach(ArtMethod_Invoke, {
                onEnter: function (args) {
                    try {
                        var result = Memory.alloc(0x100);
                        PrettyMethod(ArtMethod_PrettyMethod, args[0], result, 0x100);
                        console.log(result.readCString());

                    } catch (error) {
                        console.log(error);
                    }

                }, onLeave: function (retval) {

                }
            });
        }
    }
}

function main() {
    hook_native();
}

setImmediate(main);

用法

frida -U --no-pause -f package_name -l hook_artmethod.js
or
frida -U --no-pause -f package_name -l hook_artmethod.js > hook_artmethod.log

Libc

frida-trace 官方工具,(可以hookso和java函数) 当然他的主要作用是 hook native 的标准函数, 因为其他的trace已经很好用了。

不清楚就 frida-trace --help

// hook 所有函数
frida-trace -U -I libnative-lib.so com.xxx.xxx

// hook 指定函数 strcmp 然后 -o 保存
frida-trace -U -i "strcmp" -f com.xxx.xxx -o test.js

Linker

Jni_onload 之前还有 init_arrayinit

没有支持arm64,可以在安装app的时候adb install --abi armeabi-v7a强制让app运行在32位模式

  • 这个脚本整体来说就是hook callfunction,然后打印出init_array里面的函数地址和参数等。
  • 从源码看,关键就是call_array这里调用的call_function,第一个参数代表这是注册的init_array里面的function,第二个参数则是init_array里存储的函数的地址。
function LogPrint(log) {
    var theDate = new Date();
    var hour = theDate.getHours();
    var minute = theDate.getMinutes();
    var second = theDate.getSeconds();
    var mSecond = theDate.getMilliseconds()

    hour < 10 ? hour = "0" + hour : hour;
    minute < 10 ? minute = "0" + minute : minute;
    second < 10 ? second = "0" + second : second;
    mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;

    var time = hour + ":" + minute + ":" + second + ":" + mSecond;
    var threadid = Process.getCurrentThreadId();
    console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);

}

function hooklinker() {
    var linkername = "linker";
    var call_function_addr = null;
    var arch = Process.arch;
    LogPrint("Process run in:" + arch);
    if (arch.endsWith("arm")) {
        linkername = "linker";
    } else {
        linkername = "linker64";
        LogPrint("arm64 is not supported yet!");
    }

    var symbols = Module.enumerateSymbolsSync(linkername);
    for (var i = 0; i < symbols.length; i++) {
        var symbol = symbols[i];
        //LogPrint(linkername + "->" + symbol.name + "---" + symbol.address);
        if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != -1) {
            call_function_addr = symbol.address;
            LogPrint("linker->" + symbol.name + "---" + symbol.address)

        }
    }

    if (call_function_addr != null) {
        var func_call_function = new NativeFunction(call_function_addr, 'void', ['pointer', 'pointer', 'pointer']);
        Interceptor.replace(new NativeFunction(call_function_addr,
            'void', ['pointer', 'pointer', 'pointer']), new NativeCallback(function (arg0, arg1, arg2) {
            var functiontype = null;
            var functionaddr = null;
            var sopath = null;
            if (arg0 != null) {
                functiontype = Memory.readCString(arg0);
            }
            if (arg1 != null) {
                functionaddr = arg1;

            }
            if (arg2 != null) {
                sopath = Memory.readCString(arg2);
            }
            var modulebaseaddr = Module.findBaseAddress(sopath);
            LogPrint("after load:" + sopath + "--start call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
            if (sopath.indexOf('libnative-lib.so') >= 0 && functiontype == "DT_INIT") {
                LogPrint("after load:" + sopath + "--ignore call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);

            } else {
                func_call_function(arg0, arg1, arg2);
                LogPrint("after load:" + sopath + "--end call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);

            }

        }, 'void', ['pointer', 'pointer', 'pointer']));
    }
}

setImmediate(hooklinker)
Last modification:September 2, 2020
如果觉得我的文章对你有用,请随意赞赏