frida的基本操作

前置条件

macOS上安装python,下载地址:https://www.python.org/ftp/python/3.10.3/python-3.10.3-macos11.pkg

执行对应的文件:
在这里插入图片描述

1. 先安装 virtualenv

sudo pip3 install virtualenv
#有的时候可能需要 sudo 权限

2. 切换为env环境

source ~/Desktop/frida/bin/activate

3.安装frida环境
pip install frida==12.8.0
pip install frida-tools==5.3.0

安装成功标识:
(frida) ➜ Desktop frida --version
12.8.0

frida是什么?

frida是平台原生app的Greasemonkey,说的专业一点,就是一种动态插桩工具,可以插入一些代码到原生app的内存空间去,(动态地监视和修改其行为),这些原生平台可以是Win、Mac、Linux、Android或者iOS。

1、在Mac上安装Frida

Mac系统的frida版本要与Android中的版本号保持一致

升级Mac系统中的frida为最新版本的命令:

sudo pip install --upgrade frida

或者安装指定版本的frida,这里安装frida的12.1.2版本:

pip install frida==12.1.2

2、为Android手机安装Frida

我的root设备是Android4.4.4系统

下载frida-server到电脑中,地址:frida-server

选择合适的版本,我这里下载的是frida-server-12.1.2-android-arm.xz

下载到电脑要解压这个.xz文件,Mac系统如果不能识别这个文件,可到App Store安装一个叫"The Unarchiver"的工具。

解压后修改文件名为"frida-server",并复制文件到手机对应的目录下:

adb push frida-server /data/local/tmp/

继续在终端输入命令,为frida-server添加执行权限,并启动手机中的frida-server:

$ adb shell
shell@hammerhead:/ $ su 
root@hammerhead:/ # cd /data/local/tmp/
root@hammerhead:/data/local/tmp # chmod 777 frida-server
root@hammerhead:/data/local/tmp # ./frida-server &

手机会重启,重启之后,mac新开一个终端输入命令检查手机上的frida是否运行成功:

frida-ps -U

成功则会打印手机中运行的进程:

PID  Name
-----  ----------------------------------------------
25851  adb
25767  adbd
31402  android.process.acore
31675  android.process.media
  187  bridgemgrd
32228  com.android.defcontainer
...
...
3,一些frida的基本操作:
打印java堆栈:
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
hook java方法:

https://www.52pojie.cn/forum.php?mod=viewthread&tid=931872

获取在前台运行的APP
import frida
import sys
rdev = frida.get_remote_device()
front_app = rdev.get_frontmost_application()
print (front_app)
遍历所有进程
import frida
import sys
rdev = frida.get_remote_device()
processes = rdev.enumerate_processes()
for processe in processes:
print (processe)
枚举进程中加载指定模块中的导出函数
import frida
import sys
rdev = frida.get_remote_device()
session = rdev.attach("com.mfw.roadbook") # 也可以使用attach(pid)的方式
modules = session.enumerate_modules()
for module in modules:
# print (module)
if module.name=="libmfw.so":
export_funcs = module.enumerate_exports()
for export_func in export_funcs:
print ("\t%s\t%s"%(export_func.name,hex(export_func.relative_address)))
枚举遍历所有当前已经加载的类
Java.perform(function(){
    Java.enumerateLoadedClasses({
        onMatch: function(className) {
            send(className);},
        onComplete:function(){
            send("done");
        }
    });

如何在APK内实现注入frida的操作
https://github.com/iGio90/FridaAndroidInjector

操作命令:

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

frida -U -f com.kuaishou.android.common.kwguard //直接注入APP 在APP里面操作

操作案列:

https://juejin.im/post/5b1cc2b85188257d8c7d726c
Frida工具化
https://www.anquanke.com/post/id/197657#h3-1
内存堆搜索与执行,提取内存信息。执行一些hook函数
https://jianshu.com/p/bab4f4714d98
扫描实例类Java.choose 然后实例化一个对象,并调用里面的方法
    //在堆上查找实例化的对象,示例代码如下!
    Java.choose("b3nac.injuredandroid", {
        onMatch: function (instance) {
            console.log("Found instance: " + instance);
            console.log("Result of secret func: " + instance.decrypt());
        },
        onComplete: function () { }
    });
Frida如何打印参数具体内容?

如果是自定义的实体类参数:比如okhttp的Request

var Request = Java.use('okhttp3.Request');
var Java_Request = Java.cast(arguments[j], Request);
console.log(Java_Request.url());


var Map = Java.use('java.util.HashMap');
var args_map = Java.cast(arguments[j], Map);
console.log(args_map.toString());

将参数强制转换成类对象,并打印里面的值

或者使用javascript里面的value
 var1.data.value
jni各种类型参数打印

https://langgithub.github.io/2019/08/01/frida%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/#okhttp%E4%B8%80%E8%88%AChook%E6%96%B9%E5%BC%8F

function abc(){
    var base_address=Module.findBaseAddress('libcms.so')
    if (base_address!=null){
        
        console.log("param:ok");
        var str;
        Java.perform(function () {
            str = Java.use("java.lang.String");
        });
        Interceptor.attach(base_address.add(0x16e19), {
            onEnter: function (args) {
                
                // console.log("param1>>>>>>>" +  args[0].readCString());
                // console.log("param1>>>>>>>" +  Memory.readUtf16String(args[0]));
                // readAnsiString
                console.log("hook success");
                var s3 = Java.cast(args[3], str);
                var s5 = Java.cast(args[5], str);
                console.log("param2>>>>>>>" +  args[2].toInt32());
                console.log("param3>>>>>>>" +  s3);
                const length = Java.vm.getEnv().getArrayLength(args[4]);
                var array=[];
                for(var i=0;i<length;i++){
                    var obj=Java.vm.getEnv().getObjectArrayElement (args[4],i)
                    var result=Java.vm.getEnv().stringFromJni(obj)
                    array.push(result);
                }
                console.log("param4>>>>>>>" + JSON.stringify(array));
                console.log("param5>>>>>>>" +  s5);
            },
            onLeave: function (retval) {
            }
        });
    }
}
abc();

修改常见的参数
Java.perform(function() {
	var TelephonyManager = Java.use("android.telephony.TelephonyManager");

    //IMEI hook
    TelephonyManager.getDeviceId.overload().implementation = function () {
               console.log("[*]Called - getDeviceId()");
               var temp = this.getDeviceId();
               console.log("real IMEI: "+temp);
               return "867979021642856";
    };
    // muti IMEI
    TelephonyManager.getDeviceId.overload('int').implementation = function (p) {
               console.log("[*]Called - getDeviceId(int) param is"+p);
               var temp = this.getDeviceId(p);
               console.log("real IMEI "+p+": "+temp);
               return "867979021642856";
    };


    //IMSI hook
	TelephonyManager.getSimSerialNumber.overload().implementation = function () {
               console.log("[*]Called - getSimSerialNumber(String)");
               var temp = this.getSimSerialNumber();
               console.log("real IMSI: "+temp);
               return "123456789";
    };
    //


    //ANDOID_ID hook
    var Secure = Java.use("android.provider.Settings$Secure");
    Secure.getString.implementation = function (p1,p2) {
    	if(p2.indexOf("android_id")<0) return this.getString(p1,p2);
    	console.log("[*]Called - get android_ID, param is:"+p2);
    	var temp = this.getString(p1,p2);
    	console.log("real Android_ID: "+temp);
    	return "844de23bfcf93801";

    }


    //android的hidden API,需要通过反射调用
    var SP = Java.use("android.os.SystemProperties");
    SP.get.overload('java.lang.String').implementation = function (p1) {
    	var tmp = this.get(p1);
    	console.log("[*]"+p1+" : "+tmp);

    	return tmp;
    }
    SP.get.overload('java.lang.String', 'java.lang.String').implementation = function (p1,p2) {
    	
    	
    	var tmp = this.get(p1,p2)
    	console.log("[*]"+p1+","+p2+" : "+tmp);
    	return tmp;
    } 
    // hook MAC
    var wifi = Java.use("android.net.wifi.WifiInfo");
    wifi.getMacAddress.implementation = function () {
    	var tmp = this.getMacAddress();
    	console.log("[*]real MAC: "+tmp);
    	return tmp;
    }
	
})
frida trace跟踪流程

frida-trace -U -i open XXXX会在本地生成一个open.js 修改里面的内容即可。

/*
 * Auto-generated by Frida. Please modify to match the signature of open.
 * This stub is currently auto-generated from manpages when available.
 *
 * For full API reference, see: https://frida.re/docs/javascript-api/
 */

{
  /**
   * Called synchronously when about to call open.
   *
   * @this {object} - Object allowing you to store state for use in onLeave.
   * @param {function} log - Call this function with a string to be presented to the user.
   * @param {array} args - Function arguments represented as an array of NativePointer objects.
   * For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8.
   * It is also possible to modify arguments by assigning a NativePointer object to an element of this array.
   * @param {object} state - Object allowing you to keep state across function calls.
   * Only one JavaScript function will execute at a time, so do not worry about race-conditions.
   * However, do not use this to store function arguments across onEnter/onLeave, but instead
   * use "this" which is an object for keeping state local to an invocation.
   */
  onEnter: function (log, args, state) {
    if((args[0].readUtf8String()).indexOf("")<0){
      log(args[0].readUtf8String() );
    }
  },

  /**
   * Called synchronously when about to return from open.
   *
   * See onEnter for details.
   *
   * @this {object} - Object allowing you to access state stored in onEnter.
   * @param {function} log - Call this function with a string to be presented to the user.
   * @param {NativePointer} retval - Return value represented as a NativePointer object.
   * @param {object} state - Object allowing you to keep state across function calls.
   */
  onLeave: function (log, retval, state) {
  }
}

Android 加固应用Hook方式-Frida

Java.perform(function () {
    var application = Java.use('android.app.Application');

    application.attach.overload('android.content.Context').implementation = function(context){

        var result = this.attach(context);
        var classloader = context.getClassLoader();
        Java.classFactory.loader = classloader;

        var yeyoulogin = Java.classFactory.use('com.zcm.主窗口');
        console.log("yeyoulogin:"+ yeyoulogin);


        yeyoulogin.按钮_用户登录$被单击.implementation = function(arg){
            console.log("retval:"+ this.返回值);
        }

    }
});

列出加载的类:

Java.enumerateLoadedClasses(
  {
  "onMatch": function(className){ 
        console.log(className) 
    },
  "onComplete":function(){}
  }
)
Hook 动态加载类
获取构造函数的参数
Java.perform(function(){
        //创建一个DexClassLoader的wapper
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        //hook 它的构造函数$init,我们将它的四个参数打印出来看看。
        dexclassLoader.$init.implementation = function(dexPath,optimizedDirectory,librarySearchPath,parent){
            console.log("dexPath:"+dexPath);
            console.log("optimizedDirectory:"+optimizedDirectory);
            console.log("librarySearchPath:"+librarySearchPath);
            console.log("parent:"+parent);
            //不破换它原本的逻辑,我们调用它原本的构造函数。
          this.$init(dexPath,optimizedDirectory,librarySearchPath,parent);
        }
        console.log("down!");

});
获取动态加载的类
Java.perform(function(){
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        var hookClass = undefined;
        var ClassUse = Java.use("java.lang.Class");

        dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
           //定义一个String变量,指定我们需要的类
            var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
            //直接调用第二个重载方法,跟原本的逻辑相同。
            var result = this.loadClass(name,false);
            //如果loadClass的name参数和我们想要hook的类名相同
            if(name === hookname){
                //则拿到它的值
                hookClass = result;
                //打印hookClass变量的值
                console.log(hookClass);
                send(hookClass);
                return result;
            }
            return result;
        }
});
通过Java.cast处理泛型方法(JAVA中Class<?>表示泛型),在调用动态加载方法
Java.perform(function(){
        var hookClass = undefined;
        var ClassUse = Java.use("java.lang.Class");
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        var constructorclass = Java.use("java.lang.reflect.Constructor");
        var objectclass= Java.use("java.lang.Object");
        dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
            var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
            var result = this.loadClass(name,false);

            if(name == hookname){
                var hookClass = result;
                console.log("------------------------------CAST--------------------------------")
                //类型转换
                var hookClassCast = Java.cast(hookClass,ClassUse);
                //调用getMethods()获取类下的所有方法
                var methods = hookClassCast.getMethods();
                console.log(methods);
                console.log("-----------------------------NOT CAST-----------------------------")
                //未进行类型转换,看看能否调用getMethods()方法
                var methodtest = hookClass.getMethods();
                console.log(methodtest);
                console.log("---------------------OVER------------------------")
                return result;

            }
            return result;
        }


});
利用getDeclaredConstructor()获取具有指定参数列表构造函数的Constructor 并实例化
Java.perform(function(){
        var hookClass = undefined;
        var ClassUse = Java.use("java.lang.Class");
        var objectclass= Java.use("java.lang.Object");
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");
        var Integerclass = Java.use("java.lang.Integer");
        //实例化MainActivity对象
        var mainAc = orininclass.$new();


        dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
            var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
            var result = this.loadClass(name,false);

            if(name == hookname){
                var hookClass = result;
                var hookClassCast = Java.cast(hookClass,ClassUse);
                console.log("-----------------------------BEGIN-------------------------------------");
                //获取构造器
                var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);
                var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);
                console.log("Constructor:"+Constructor);
                console.log("orinin:"+mainAc);
                //实例化,newInstance的参数也是Ljava.lang.Object;
                var instance = Constructor.newInstance([mainAc]);
                console.log("patchAc:"+instance);
                send(instance);
console.log("--------------------------------------------------------------------");
                return result;

            }
            return result;
        }
});
利用getDeclaredMethods(),获取本类中的所有方法
Java.perform(function(){
        var hookClass = undefined;
        var ClassUse = Java.use("java.lang.Class");
        var objectclass= Java.use("java.lang.Object");
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");
        var Integerclass = Java.use("java.lang.Integer");
        //实例化MainActivity对象
        var mainAc = orininclass.$new();


        dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
            var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
            var result = this.loadClass(name,false);

            if(name == hookname){
                var hookClass = result;
                var hookClassCast = Java.cast(hookClass,ClassUse);
                console.log("-----------------------------BEGIN-------------------------------------");
                //获取构造器
                var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);
                var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);
                console.log("Constructor:"+Constructor);
                console.log("orinin:"+mainAc);
                //实例化,newInstance的参数也是Ljava.lang.Object;
                var instance = Constructor.newInstance([mainAc]);
                console.log("MainActivityPatchInstance:"+instance);
                send(instance);
                console.log("----------------------------Methods---------------------------------");
                var func = hookClassCast.getDeclaredMethods();
                console.log(func);
                console.log("--------------------------Need Method---------------------------------");
                console.log(func[0]);
                var f = func[0];
                console.log("---------------------------- OVER---------------------------------");
                return result;

            }
            return result;
        }
});
调用Method.invoke()去执行方法(invoke方法的第一个参数是执行这个方法的对象实例,第二个参数是带入的实际值数组,返回值是Object,也既是该方法执行后的返回值)
f.invoke(instance,Array);
read-std-string
/*
 * Note: Only compatible with libc++, though libstdc++'s std::string is a lot simpler.
 */

function readStdString (str) {
  const isTiny = (str.readU8() & 1) === 0;
  if (isTiny) {
    return str.add(1).readUtf8String();
  }

  return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}
whereisnative
Java.perform(function() {

    var SystemDef = Java.use('java.lang.System');

    var RuntimeDef = Java.use('java.lang.Runtime');

    var exceptionClass = Java.use('java.lang.Exception');

    var SystemLoad_1 = SystemDef.load.overload('java.lang.String');

    var SystemLoad_2 = SystemDef.loadLibrary.overload('java.lang.String');

    var RuntimeLoad_1 = RuntimeDef.load.overload('java.lang.String');

    var RuntimeLoad_2 = RuntimeDef.loadLibrary.overload('java.lang.String');

    var ThreadDef = Java.use('java.lang.Thread');

    var ThreadObj = ThreadDef.$new();

    SystemLoad_1.implementation = function(library) {
        send("Loading dynamic library => " + library);
        stackTrace();
        return SystemLoad_1.call(this, library);
    }

    SystemLoad_2.implementation = function(library) {
        send("Loading dynamic library => " + library);
        stackTrace();
        SystemLoad_2.call(this, library);
        return;
    }

    RuntimeLoad_1.implementation = function(library) {
        send("Loading dynamic library => " + library);
        stackTrace();
        RuntimeLoad_1.call(this, library);
        return;
    }

    RuntimeLoad_2.implementation = function(library) {
        send("Loading dynamic library => " + library);
        stackTrace();
        RuntimeLoad_2.call(this, library);
        return;
    }

    function stackTrace() {
        var stack = ThreadObj.currentThread().getStackTrace();
        for (var i = 0; i < stack.length; i++) {
            send(i + " => " + stack[i].toString());
        }
        send("--------------------------------------------------------------------------");
    }

});
Non-ASCII
 int ֏(int x) {
        return x + 100;
    }
甚至有一些不可视, 所以可以先编码打印出来, 再用编码后的字符串去 hook.<\br>

Java.perform(
        function x() {

            var targetClass = "com.example.hooktest.MainActivity";

            var hookCls = Java.use(targetClass);
            var methods = hookCls.class.getDeclaredMethods();

            for (var i in methods) {
                console.log(methods[i].toString());
                console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")));
            }

            hookCls[decodeURIComponent("%D6%8F")]
                .implementation = function (x) {
                    console.log("original call: fun(" + x + ")");
                    var result = this[decodeURIComponent("%D6%8F")](900);
                    return result;
                }
        }
    )
Hook 数据库
var SQLiteDatabase = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
    var Set = Java.use("java.util.Set");
    var ContentValues = Java.use("android.content.ContentValues");
    SQLiteDatabase.insert.implementation = function (arg1,arg2,arg3) {

        this.insert.call(this, arg1, arg2, arg3);
        console.log("[insert] -> arg1:" + arg1 + "\t arg2:" + arg2);
        var values = Java.cast(arg3, ContentValues);
        var sets = Java.cast(values.keySet(), Set);
        
        var arr = sets.toArray().toString().split(",");
        for (var i = 0; i < arr.length; i++){
            console.log("[insert] -> key:" + arr[i] + "\t value:" + values.get(arr[i]));
        }
    };

4、可能会遇到的错误

错误1:

$frida-ps -Ua 
Failed to enumerate applications: the connection is closed

我遇到的错误1和错误2,都是因为手机的Frida版本和Mac电脑的Frida版本不同导致的,安装相同版本的Frida就可解决。

使用frida强制启动chrome报错

$ frida -U --no-pause -f com.android.chrome
Traceback (most recent call last):
  File "/Users/king/Documents/PythonProject/Tornado/test/venv/bin/frida", line 11, in <module>
    sys.exit(main())
  File "/Users/king/Documents/PythonProject/Tornado/test/venv/lib/python2.7/site-packages/frida_tools/repl.py", line 23, in main
    from prompt_toolkit.shortcuts import create_prompt_application, create_output, create_eventloop
ImportError: cannot import name create_prompt_application

解决:

pip install ‘prompt-toolkit==1.0.15’

错误2:

Failed to spawn: unable to connect to remote frida-server

frida-server未启动

** 更新frida: **
sudo -H pip3 install --upgrade frida
sudo -H pip3 install --upgrade frida-tools

参考:

https://bbs.pediy.com/thread-252520.htm

Frida的操作教程:https://github.com/iddoeldor/frida-snippets#hook-overloads

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Frida是一个强大的动态分析工具,可以用于对应用程序进行hook。Frida提供了多种hook方式,包括在应用程序启动前注入代码、通过USB连接和远程连接等。通过在应用程序启动前注入代码,可以在应用程序启动时即实现hook的效果。可以使用frida.get_usb_device()方法连接待调试的USB设备,并使用frida.get_device()方法指定调试的设备。此外,也可以通过远程连接方式进行hook,使用frida -U -f 包名 -l xxx.js --no-pause指令进行注入。以上是使用Frida进行hook的一些基本操作方式。需要根据具体的场景和需求选择合适的方法和参数进行操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Frida hook零基础教程](https://blog.csdn.net/cyjmosthandsome/article/details/120906998)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [python之frida安卓逆向之hook大法好](https://blog.csdn.net/weixin_51111267/article/details/125109497)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值