【安卓逆向】Frida学习笔记--第一篇

读书笔记:《安卓Frida逆向与抓包实战》

Frida逆向入门之Java层Hook

Java层主动调用

主动调用是强制一个函数去执行,被动调用是APP按照正常逻辑去执行函数。
在Java中,类的函数可分为两种:类函数和实例方法。通俗的讲,就是静态的方法和动态的方法。
在Frida中主动调用的类型会根据方法类型区分开。如果是类函数的主动调用,直接使用Java.use()函数找到类进行调用即可;如果是实例方法的主动调用,则需要在找到对应的实例后对方法进行调用。这里用到了Frida中非常重要的一个API函数Java.choose(),这个函数可以在Java的堆中寻找指定类的实例。

package com.roysue.demo01;

import androidx.appcompat.app.AppCompatActivity;

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


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        while(true){
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }

            fun(50, 30);
            Log.d("godam.string", fun("LoWeRcAsE Me!!!!"));
        }

    }
    void fun(int x, int y){
        Log.d("godam", String.valueOf(x + y));
    }
    String fun(String x){
        return x.toLowerCase();
    }
    
    void secret(){
        Log.d("dadsda", "this is secret func");
    }
    static void staticSecret(){
        Log.d("dasdad", "this is static secret func");
    }
}

然后使用frida触发添加的secret函数

然而我报错了,查了下是因为Android版本
https://github.com/frida/frida/issues/1156
和这里报错一模一样
Frida will not work on Android 10 when using the "Java.choose" function, I was able to run it on android 9 and 8.1 instead, with same apk and frida version of course.

然后我买了nexus手机,一直连不上电脑,发现是线的问题,换了跟线就行了,然后又发现无法连接到虚拟机,发现是USB服务没开,又用services.msc把USB服务打开了,一波三折了属于是。

APP代码

package com.roysue.demo01;

import androidx.appcompat.app.AppCompatActivity;

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


public class MainActivity extends AppCompatActivity {
    
    private String total = "hello";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        while(true){
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }

            fun(50, 30);
            Log.d("godam.string", fun("LoWeRcAsE Me!!!!"));
        }

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

    void secret(){
        total += " secretFunc";
        Log.d("dadsda", "this is secret func");
    }
    static void staticSecret(){
        Log.d("dasdad", "this is static secret func");
    }
}

脚本代码

function CallSecretFunc(){
    Java.perform(function() {
        //active calling of dynamic functions
        Java.choose('com.roysue.demo01.MainActivity',{
            onMatch: function(instance){
                instance.secret()
            },
            onComplete: function(){
            }
        })
    })
}

function getTotalValue(){
    Java.perform(function(){
        var MainActivity = Java.use('com.roysue.demo01.MainActivity')
        Java.choose('com.roysue.demo01.MainActivity',{
            onMatch: function(instance){
                console.log('total value = ', instance.total.value)
            },
            onComplete: function(){
                console.log('search Complete')
            }
        })
    })
}

setImmediate(getTotalValue)

rpc.exports = {
    CallsecretFunc : CallSecretFunc,
    gettotalvalue : getTotalValue
};

然后写成python脚本是

import frida, sys

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

device = frida.get_usb_device()
process = device.attach('com.roysue.demo02')

with open('4.js') as f:
    jscode = f.read()

script = process.create_script(jscode)

script.on('message', on_message)
script.load()

command = ""

while 1 == 1:
    command = input("\nEnter command:\n1:exit\n2:Call secret function\n3:Get Total Value\nchoice:")
    if command == "1":
        break
    elif command == "2":
        script.exports.callsecretfunc()
    elif command == "3":
        script.exports.gettotalvalue()

REPL模式:Python的交互模式

Objection快速逆向入门

Objection的使用

【安卓逆向】Frida学习笔记--第一篇_第1张图片
在Objection REPL界面中,使用空格键可以有提示
使用android hooking list classes可以看内存中所有的类
【安卓逆向】Frida学习笔记--第一篇_第2张图片
使用android hooking search classes查询包含特定关键字的类
【安卓逆向】Frida学习笔记--第一篇_第3张图片
使用android hooking search methods 可以搜索所有包含key关键字的方法
【安卓逆向】Frida学习笔记--第一篇_第4张图片
相当耗时而且报错了
使用android hooking list class_methods查看类的所有方法。
【安卓逆向】Frida学习笔记--第一篇_第5张图片
除了上述Java类的相关内容,Android中四大组件也值得关注,Objection也提供了支持
android hooking list activities
【安卓逆向】Frida学习笔记--第一篇_第6张图片
service,receivers和providers也可以用
【安卓逆向】Frida学习笔记--第一篇_第7张图片
Hook相关命令:android hooking watch class_method

android hooking watch class_method java.io.File.$init --dump-args --dump-backtrace --dump-return执行结果
–dump-args :打印函数的参数
–dump-backtrace :调用堆栈
–dump-return:返回值
有些函数的明文和密文非常有可能放在参数和返回值中,而打印调用栈可以让分析者快速进行调用链的溯源。
Registering job说明已经把函数加入到Objection的作用系统中了。使用jobs list就可以查看
【安卓逆向】Frida学习笔记--第一篇_第8张图片
当在手机中设置点击时,就触发了hook,看到Backtrace之后打印的调用栈,可以清楚地看到这个构造函数的调用来源。根据arguments可以看到打开的文件路径,以及文件名。测试结束后可以根据作业的ID删除,取消对函数的Hookjobs kill
【安卓逆向】Frida学习笔记--第一篇_第9张图片
除了直接Hook一个函数之外,Objection也可以通过执行android hooking watch class 对指定classname中所有函数Hook(不包括构造函数的hook)。
【安卓逆向】Frida学习笔记--第一篇_第10张图片
这里的调用顺序是自上而下的
【安卓逆向】Frida学习笔记--第一篇_第11张图片
最后学习主动调用在Objection中的应用。
对实例搜索:android heap search instances ,输出中显示的handle(句柄)十分重要,在之后的主动调用中都是以十六进制的Handle值作为实例的句柄调用和执行函数
调用实例方法:android heap execute
P114页
使用execute执行带参数的函数会报错,需要先执行以下命令
android heap evaluate
然后使用如下内容

console.log('File is canWrite? =>', clazz.canWrite())
clazz.setWritable(false)
console.log('File is canWrite? =>', clazz.canWrite())

Objection设定clazz用于代表handle值对应的实例,同时函数canWrite()用于返回这个实例所打开的文件是否可写,setWritable()用于修改对应文件是否可写的属性。

Frida开发思想

定位:Objection辅助定位

遍历手机上的所有activity列表:android hooking list activitues
启动目标活动:android intent launch_activity com.example.junior.CalculatorActivity
通过静态分析源码查找响应类或函数
可以用android hooking list class_methods com.exqmple.junior.CalculatorActivity验证函数是否存在
之后Hook响应函数:android hooking watch class_method com.example.junior.CalculatorActivity.caculate --dump-args --dump-backtrace --dump-return
验证类是否存在:android hooking list classes,之后可以再~/.objexction目录下的objection.log文件中找相关日志(cat objection.log ! grep com.example.junior.util.Arith)。
之后就可以对函数实现Hook了android hooking watch class_method comexample.junior.util.Arith.sub --dump-args --dump-backtrace --dump-return

利用:Frida脚本修改参数、主动调用

function main(){
    Java.perform(function(){
        var Arith = Java.use('com.example.junior.util.Arith')
        Arith.sub.overload('java.lang.String','java.lang.String').implementation = function(str, str2){
            var result = this.sub(str, str2)
            console.log('str, str2, result =>', str, str2, result)
            //print Java call stack
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()))
            return result
        }
    })
}

setImmediate(main)

这里打印Java调用栈的代码,其实是将Android开发中,获取调用栈的函数Log.getStackTraceString(Throwable e)翻译为JavaScript函数

Frida注入frida -UF -l hook.js,-UF命令表示使用USB方式。
上面的123直接传递是不对的,正确的传入字符串参数的方法应当这样

var JavaString = Java.use('java.lang.String')
var result = this.sub(str, JavaString.$new('123'))

Frida脚本中Java函数的主动调用,如果是静态函数,只需要获取类对象即可直接完成函数的主动调用,如果是实例函数,只需要优先获取到类的实例对象即可完成函数的主动调用。

function callSub(a, b){
    var Arith = Java.use('com.example.junior.Arith')
    var javaString = Java.use('Java.lang.String')
    var result = Arith.sub(JavaString.$new(a), JavaString.$new(b))
    console.log(a, "-", b, "=", result)
}

rpc.exports = {
    sub : CallSub,
};

这样就可以使用Python进行hook,大规模利用时,可以将loader.js脚本中的get_usb_device()函数更名为get_device_manager().add_remote_device(':),即可断开adb的连接,通过网络模式对APP进行注入和hook工作。

你可能感兴趣的:(安卓安全,android,学习,java)