4.frida实现一次hook

一、问题描述

使用frida工具,hook安卓程序中一个类的函数,虽然听起来简单,但对于对一次接触这个东西的来说,却是很容易被绕晕的。

本次实验的对象是我在网上找的一个非常简单的安卓记事本程序,

可以直接在这里下载:notepad, 提取码: 55dc

下载点击运行即可解压,内有两个文件,一个是源码压缩包,另一个是对应的程序安装包:

image-20231101211734758

二、开始操作

这里默认你已经搭建好了frida环境,你可以直接通过下面的命令将其安装到模拟器上:

adb install app-note.apk

注意最后面是这个安装包的路径,这里假设你的终端路径就在这个文件夹下,那么可以直接运行上面的命令进行安装。

安装完成后,在模拟器中点击程序运行,然后输入命令frida-ps -Ua,找到该记事本的标识符:

image-20231101212244965

然后新建一个test.js文件,存入以下代码:

function main() {
    var MainActivity = Java.use("cn.itcast.notepad.NotepadActivity"); //得到主组件的类
    //获取该类上的startActivityForResult函数
    var startActivityForResult = MainActivity.startActivityForResult.overload('android.content.Intent', 'int');
    //hook该函数
    startActivityForResult.implementation = function (v, c) {
        console.log('test:', v, c); //尝试打印程序传入该函数的两个参数
        startActivityForResult.call(this, v, c); //继续让其调用原本的函数逻辑
        console.log('Done:' + JSON.stringify(this));//打印一下该实例对象
    };
}

if (Java.available) { //如果java虚拟器已经启动
    //则执行perform函数传入的函数对象
    Java.perform(()=>{
        main(); //在该函数中跳转执行main函数
    });
}

保存后,执行命令:frida -U -f cn.itcast.notepad -l test.js

image-20231101213126069

此时,只要你在模拟器中点击这个加号,就会调用上面我们写好的函数,并打印出来对应的日志。

三、代码详解

上面是操作过程,不是很复杂,但麻烦的是中间各种步骤的逻辑,以及为什么要这么做。

首先是拿到apk程序的标识符:cn.itcast.notepad,有了这个我们就可以通过后面的命令:

frida -U -f cn.itcast.notepad -l test.js

将我们写的js代码注入到该程序中去。

  1. -U:连接USB设备
  2. -f:注入代码的目标文件,其后跟着的就是apk的标识符。
  3. -l:加载其后的js脚本文件注入到目标程序中。

然后是我们写的js代码,里面有很多对象,比如这里用到的Java对象,就是frida为我们提供好的。

image-20231101213842022

通过Java.available属性来判断目标程序是否已经加载完成,如果加载完成,那就可以通过Java.perform传入我们希望在目标程序中执行的函数代码。

这里为了便于观察,又在传入的这个函数中调用我们前面写的main函数,main函数中才是我们写代码的地方。

了解了这个,其实你就可以直接将其简写成下面这样了:

image-20231101214150111

然后来到我们的主函数main函数中,首先是第一行代码:

var MainActivity = Java.use("cn.itcast.notepad.NotepadActivity"); //得到主组件的类

这里通过Java.use直接获取目标程序中的主类,这个类可以通过一些工具获得,比如我用到的一个:GDA4.10.exe, 提取码: s1w9

将apk文件拖进去,它就会完成分析:

image-20231101214613590

点击右上角的那个图标,就可以进入入口函数中,往最上面翻,就可以看到这个包的类,将其复制下来进入代码中即可。

得到这个类后,我们还需要hook这个类中具体的函数,更准确的来说,是我们要分析点击记事本中这个按钮时会执行哪些函数,我们就hook哪些函数:

image-20231101214834327

最方便的方式自然是直接分析源码,前面也已经提供了源码的压缩包,通过android studio将其打开,直接查看布局文件,看它的id,为add

image-20231101215018406

然后进入代码文件,就可以看到为它添加了监听事件,并在事件中调用了本类的startActivityForResult函数:

image-20231101215107854

所以我们的目标就是hook该函数,第一步就是先得到该函数:

var startActivityForResult = MainActivity.startActivityForResult.overload('android.content.Intent', 'int');

直接通过.的方式就能拿到,但由于这个函数有一系列重载的函数,所以我们还需要用overload来获取指定类型的重载函数,也就是上面代码中所调用的那个函数。

这个不用记,你不用overload也可以继续往下写,只不过后面会报错,并且报错信息有相关的函数签名,然后直接将合适的复制过来用即可。

image-20231101215530706

这里不调用overload,就会报错让你选择一个相应的重载函数,直接将其复制过来即可。

最后就是hook该函数了:

//hook该函数
startActivityForResult.implementation = function (v, c) {
    console.log('test:', v, c); //尝试打印程序传入该函数的两个参数
    startActivityForResult.call(this, v, c); //继续让其调用原本的函数逻辑
    console.log('Done:' + JSON.stringify(this));//打印一下该实例对象
};

通过函数的implementation实现,让其等于一个你想要hook的函数即可,这个函数的参数,也就是startActivityForResult函数的参数,由于它有两个,所以这里我也写了两个。

然后在这个函数中,我将传入的这两个参数直接打印了一下,并且还重新调用了原函数:startActivityForResult,不影响其正常工作逻辑。

注意:由于startActivityForResult只是一个函数,作为类函数是需要实例对象的,开发时由于是通过对象调用,不需要自己手动填充,这里就不行了,必须要填入调用这个函数的实例对象,也不难,就是this

毕竟我们hook掉的是这个类函数,能调用这个hook函数的必然是这个类的实例化对象,那么this自然也就指向该对象了,所以可以直接用。

四、没有源码时

很多时候我们拿不到源码,可能就不是那么容易找到相应的hook函数,但通过前面提到的按个工具,其实我们依旧可以直接看出一点痕迹。

因为这个应用程序主页就只有一个可以点击的地方,所以当你在这个反编译工具中,双击右上角进入入口出,看到setOnClickListener添加监听对象时,就基本可以推测出这就是我们的目标:

image-20231101220337511

然后此时,你可以直接鼠标双击后面的NotepadActivity对象,它会跳转到对应的代码处:

image-20231101220602615

这里可以看到,这里的点击函数中,就通过传入的this$0来调用这个函数,这个传入的this$0参数不就是外面的那个实例对象吗?

所以这样其实没有源码,也能分析出来。

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux