Frida学习02-java层hook基础

Frida学习02-java层hook基础

日期:Feb-28-2022

作者:狐狸大剑眼镜

书籍:安卓Frida逆向与抓包实战- 肉丝

学到这发现基础有点薄弱

补充一下基础

先学一学开发,写一个简单的app~

Android5个层级

Android主要分为5个层级,依次是Linux内核层、
HAL硬件抽象层、系统运行库层、应用框架层、应用层。

(1)Linux内核层
Linux和Android的底层本质上是一样的,因为Android使用的是
Linux内核。换而言之,Android从某种程度上说就是一个Linux系
统,因此大部分在Linux中存在的内核漏洞在Android中可能也存在,
只是漏洞利用的方式不同而已;另外,理论上在计算机上能够运行的
Linux命令在手机上也是可以执行的,只是因为缺少部分软件包或者其
他部件的支持而导致一些命令无法在手机上执行。在Android中可以使
用名为termux的软件来解封并执行所有的Linux命令。

(2)系统运行库层
系统运行库层分为平行的两个组成部分。
第一部分是与标准Linux中一样的使用C/C++编写的原生库文件,
包括提供媒体库支持的libopengl.so、提供数据库存储功能的
libsqlite3.so等。

第二部分是Android特有的使用C/C++编写的运行库,类似于
Java中的JVM,被称为Android Runtime。在系统运行过程中,每一
个App进程都有一个自己的Android Runtime实例,用于支持所有
Java相关代码的加载与执行。在Android 5.0之前,Dalvik虚拟机是
Android Runtime,对应的库文件名是libdvm.so;从Android 5.0开
始,Google官方彻底放弃了Dalvik,转而支持了更快的Art运行库,对
应的库文件名为libart.so。需要注意的是,在Android 4.4上,虽然是
Dalvik和Art共存,但是默认选择Dalvik作为运行库。

3)应用框架层
应用框架层相当于Linux层级的so文件,用于给Android的Java层
提供API的支持。不管是加壳的App还是未加壳的App,其框架层的代
码在应用层代码加载之前就存在。

(4)应用层
应用层是App所在的层级,包括系统自带的应用(称为系统应用)
以及后续用户自己所安装的应用(称为普通应用)。

MainActivity是一个简单的Activity活动类


package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

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

}
/*
* onCreate()函数先调用了父类的
onCreate()函数,然后调用setContentView()函数加载了在
src/main/res/layout目录下定义的activity_main.xml文件作为
MainActivity对应的界面(一个Activity就是一个界面)。其中,
R.layout.activity_main是activity_main.xml文件的唯一标志id。
* */

Android四大组件

Android基础中的基础就是Android的四大组件,即
活动(Activity)、服务(Service)、广播接收器(Broadcast
Receiver)以及内容提供者(Content Provider)。

Activity可以理解为界面,一个Activity就是一个界面;Service相
当于Windows上的一个后台进程;Broadcast Receiver用于响应来自
其他应用程序或者系统的广播消息;Content Provider用于进程间的
交互,通常通过请求从一个应用程序向其他应用程序提供数据。

常用linux命令

cat #查看文本内容
echotouch #touch命令可以创建一个空文件;echo命令通过配合“>”或者“>>”对文件进行写操作,其中“>”为覆盖写操作、“>>”为扩展写操作
grep #命令用于在shell中过滤出符合条件的输出。
ps #可输出当前设备正在运行的进程。在Android  8之后,ps命令只能打印出当前进程,需要加上-e参数才能打印出全部的进
程。
netstat #输出App连接的IP、端口、协议等网络相关信息,通常使用的参数组合为-alpe。netstat  -alpe用于查看所有sockets连接的IP和端口以及相应的进程名和pid,配合grep往往有奇效。

实例:netstat -alpe |grep org.sfjboldyvukzzlpp

lsof #命令可以用于查看对应进程打开的文件。

实例:lsof -p 21312 -l|grep db

top #查看当前系统运行负载以及对应进程名和一些其他的信息,和之前讲的htop作用一样,只是相对来说htop更加人性化

常用adb命令

adb shell dumpsys activity top #查看当前处于前台的Activity。与grep命令一同使用,输出很多信息。
adb shell dumpsys package <package-name>  #查看包信息,包括四大组件信息以及MIME等相关信息。
adb shell dbinfo <package-name> #用于查看App使用的数据库信息,包括执行操作的查询语句
等信息都会被打印出来。
adb shell pm命令

功能:pm命令是Android中packageManager的命令行,是用于
管理package的命令,比如通过pm list packages命令可以列出所有安
装的APK包名。
示例:
root@VXIDr0ysue:~/Chap02# adb shell pm list packages
package:com.google.android.carriersetup
package:com.android.cts.priv.ctsshim
package:com.google.android.youtube
...
pm install命令用于安装APK文件,只是这里的APK文件不是在主
机目录下,而是在Android手机目录下。
示例:
bullhead:/sdcard # ls | grep apk
network-debug.apk
root@VXIDr0ysue:~/Chap02# adb shell pm install /sdcard/network-debug.apk
Success
pm命令还有很多其他用法,比如pm uninstall命令,用于卸载
Android上的应用。这里仅以pm list和pm install为例,更多用法留待
读者自行探索。

adb shell am #am命令是一个重要的调试工具,主要用于启动或停止服务、发送广播、启动Activity等。在逆向过程中,往往在需要以Debug模式启动App时会使用这个命令。对应命令格式为adb shell am start-activity -D -N <包名>/<类名>。
adb install <App.apk>
允许安装测试 APK。
adb install -t xxx.apk
adb push和adb pull #这两个命令用于在主机和Android设备之间交换文件,前者用于从主机推送文件到Android设备上,后者用于从Android设备上获取文件到主机中。
adb  logcat # 用于查看Android中日志的输出

java层Hook基础

MainActivity.java

package com.example.myapplication;

import androidx.annotation.LongDef;
import androidx.appcompat.app.AppCompatActivity;

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

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("MainActivity","hollowed!");
        while (true){
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            fun(50,30);
        }

        }
    void fun(int x ,int y){
        Log.d("MainActivity", String.valueOf(x+y));

    }
}

每次间隔1秒打印一次fun(50,30)函数
的运行结果。运行结果最终显示在控制台中

哼<( ̄ c ̄)y▂ξ有钱我一定要先买个好笔记本,它老了,现在我只能先在主机写程序,然后到虚拟机执行frida......,想买蓝天x170或者更沉的,我单纯的想锻炼,还有点散热不足恐惧症。

adb logcat | grep MainActivity #类名

编写frida脚本

目标是编写Hook fun()函数并打印出fun()函数的参数值。

function main() {
    console.log("Script loaded successfully")
    Java.perform(function () {
        console.log("inside java perform function")
        var MainActivity = Java.use("com.example.myapplication.MainActivity")
        console.log("java.use.successfully") //定位类成功
        MainActivity.fun.implementation = function (x, y) {
            console.log("x => ", x, "y =>", y)
            var ret_value = this.fun(x, y);
            return ret_value
        }
    })
}
setImmediate(main)

启动完frida-server之后,通过如下命令使用Frida的CLI模式以attach模式注入App

frida -U -l javahook.js com.example.myapplication

一开始代码写错了frida报错,换代码不用重新运行frida。

也可以传递不同的参数,简单修改程序逻辑即可。将参数改为3和4, 重新查看日志内容,就会发现Frida脚本已经生效。

// 创建main()存放Hook脚本
function main() {
    console.log("Script loaded successfully")
    // 调用Frida的API函数Java.perform()将脚本中的内容注入到Java运行库
    Java.perform(
        /*
        这个API的参数是一个匿名函数,函数内容是监控
        和修改Java函数逻辑的主体内容。注意,这里的Java.perform()函数
        非常重要,任何对App中Java层的操作都必须包裹在这个函数中,否则
        Frida运行起来后就会报错, 
        */
        function () {
            console.log("inside java perform function")
            /*
            调用了Frida的API函数Java.use(),这个函数的参数是Hook的函数所在类的类名,参
            数的类型是一个字符串类型这个函数的返回值动态地为
            相应Java类获取一个JavaScript  Wrapper,可以通俗地理解为一个
            JavaScript对象
            */
            var MainActivity = Java.use("com.example.myapplication.MainActivity")
            console.log("java.use.successfully") //定位类成功
            /*在获取到对应的JavaScript对象后,通过“.”符号连接fun这个对
            应的函数名,然后加上implementation关键词表示实现MainActivity
            对象的fun()函数,最后通过“=”这个符号连接一个匿名函数,参数内
            容和原Java的内容一致。不同的是,JavaScript是一个弱类型的语
            言,不需要指明参数类型。此时一个针对MainActivity类的fun()函数
            的Hook框架就完成了
            */
            MainActivity.fun.implementation = function (x, y) {
                /*
                调用console.log()函数把参数内容
                打印出来,通过this.fun()函数再次调用原函数,并把原本的参数传递
                给这个fun()函数。简而言之,就是重新执行原函数的内容,最后将这
                个函数的返回值直接通过return指令返回。
                */
                /*
                在Hook一个函数时,还有一个地方需要注意,那就是最好不要修
                改被Hook的函数的返回值类型,否则可能会引起程序崩溃等问题,比
                如直接通过调用原函数将原函数的返回值返回。
                 */
                console.log("x => ", x, "y =>", y)
                var ret_value = this.fun(3, 4);
                return ret_value
            }
        }
    )
}
/*
setImmediate(Frida的API函数)函数传递的参数是要被执行的
函数,比如传入main参数,表示当Frida注入App后立即执行main()函
数。这个函数和setTimeout()函数类似,都是用于指定要执行的函数,
不同的是setTimeout可以用于指定Frida注入App多长时间后执行函
数,往往用于延时注入。如果传递的第二个参数为0或者压根没有第二
个参数,就和setImmediate()函数的作用一样, */
setImmediate(main)

重新修改MainActivity

package com.example.myapplication;

import androidx.annotation.LongDef;
import androidx.appcompat.app.AppCompatActivity;

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

import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("MainActivity","hollowed!");
        while (true){
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            fun(50,30);
            Log.d("MainActivity.string", fun("LoWeRcAsE Me!!!!!!!!!!!!!"));
        }

        }
    void fun(int x ,int y){
        Log.d("MainActivity", String.valueOf(x+y));

    }
    String fun(String x){
        return x.toLowerCase();
    }
}

fun()方法有了重载,在参数是两个int类型的情况下,返回两个整数之和;当参数为String类型时,返回字符串的小写形式。

哪么这时候frida脚本还可以嘛,试一试

Frida运行后提示“Error: fun(): has more than one
overload”,这是函数的重载导致Frida不知道具体应该Hook哪个函
数而出现的问题。其实Frida已经提供了解决方案(use
.overload()),就是指定函数签名,将报错中
的.overload('java.lang.String')或者.overload('int', 'int')添加到要
Hook的函数名后、关键词implementation之前。当然,相应的参数和
具体函数逻辑也得修改

function main() {
    console.log("Script loaded successfully")
    Java.perform(function () {
        console.log("inside java perform function")
        var MainActivity = Java.use("com.example.myapplication.MainActivity")
        console.log("java.use.successfully") //定位类成功
        // Hook重载函数
        MainActivity.fun.overload('int', 'int').implementation = function (x, y) {
            console.log("x => ", x, "y =>", y)
            var ret_value = this.fun(3, 4);
            return ret_value
        }
    })
}
setImmediate(main)

成功修改App日志

posted @   狐狸大剑眼镜  阅读(815)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示