android检测代理

应用程序检测到使用代理后就会抓包异常
IS_ICS_OR_LATER

相关文章:
https://www.it610.com/article/1280480273930141696.htm

解决方法:
使用VPN原理的软件进行抓包:
黄鸟 postaman charles

证书校验

单向认证:

特征:抓包可以抓到,但是报错,400、ssl
解决方法:Xposed+JustTrustMe

双向认证(rsa算法):

特征:略
解决方法:证书+密码


Frida入门

小技巧:
这串代码可以在notepad++中直接运行python脚本,x.py指的是要执行的py路径

cmd /k python "c://x.py" & ECHO & PAUSE & EXIT

frida-ps -U 可以查看apk的包名称

更加推荐使用如下语句,可以自动获取手机最上层app的包名:

adb shell dumpsys window | grep mCurrentFocus

首先安装frida模块(python3.9)

PC端:

方案一(推荐安装低版本frida,兼容性好):

pip install frida==14.2.18
pip install frida-tools==9.2.5
pip install objection==1.8.4

如果在这一步一直卡着安装不成功就试试方案二
方案二:
https://pypi.org/project/frida/#files下载frida-12.7.16-py3.7-win-amd64.egg。
并把它放到E:\Program Files\Python37\Lib\site-packages中。
此时执行pip install frida就OK了

测试:
打开powershell,
输入frida-ps -U,此时无报错说明成功

设备端:frida-server 14.2.18

下载地址:https://github.com/frida/frida/releases
判断模拟器手机的架构

大部分为两种情况:
手机端下载 frida-server-版本-android-arm64.xz
模拟器下载 frida-server-版本-android-x86.xz

adb -s 设备名 shell getprop ro.product.cpu.abi //我的是32位x86

在这里插入图片描述
下载对应版本并解压:
在这里插入图片描述

连接模拟器

我这里使用的是逍遥模拟器,21503是逍遥模拟器的默认端口
(夜神:adb connect 127.0.0.1:62001)
其他adb命令补充

adb kill-server
adb start-server
adb devices 查看设备列表
adb install f:/1.apk #表示安装apk文件
adb -s 设备名 shell getprop ro.product.cpu.abi # 查看cpu架构,用于判断适合安装哪个版本的frida

adb connect 127.0.0.1:21503
adb shell
adb push 文件路径 /data/local/tmp
cd /data/local/tmp/
ls
chmod 777 frida-server-15.1.22-android-x86   //赋予frida执行权限
./frida-server-15.1.22-android-x86           //运行frida,此时frida就已经跑起来了

最后转发一下端口:

adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

注意:

如果开了两个模拟器则需要匹配使用哪一个模拟器:
adb devices #列出模拟器列表
adb -s 模拟器名 shell


问题集合

byte类型hook

当遇到byte格式数据(类似于下图),需要hook时
在这里插入图片描述
解决方法

var string=Java.use('java.lang.String');
string.$new(arguments[0]);

例子:
在这里插入图片描述

hexdump输出16进制

参考实战hook陌陌so层【43分43秒】
在这里插入图片描述

模板

Frida模板【普通方法】

补充:
查看软件包名方法(其他方法):

adb shell pm list packages -3
adb shell am monitor   //需要先运行此命令,再运行需要查看包名的APP

更推荐如下方法获取手机最顶层app包名

adb shell dumpsys window | grep mCurrentFocus

import frida,sys
jscode="""
Java.perform(function(){
    var utils = Java.use('包名.类名')
    utils.函数.implementation = function(函数值1,函数值2){  //如果是构造方法这里还能utils.$init.inplementation
        console.log("Hook Start...");
        send("Success!");
        return this.函数(函数值1,函数值2);  //同理,这里就写return this.$init(函数1,函数2)
    }
});
"""

def message(message,data):
    if message["type"] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)
process = frida.get_remote_device().attach('app包名')
script=process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()

Frida模板【构造方法】

构造方法指的是new一个对象

import frida,sys
jscode="""
Java.perform(function(){
    var utils = Java.use('包名.类名')
    utils.$init.implementation = function(函数值1,函数值2){ 
        console.log("Hook Start...");
        send("Success!");
        return this.$init(函数值1,函数值2);  
    }
});
"""

def message(message,data):
    if message["type"] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)
process = frida.get_remote_device().attach('app包名')
script=process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()

Frida模板【重载方法】

add(1,2)
add(1,2,3)
重载就是函数名字一样,例如:
在这里插入图片描述

import frida,sys
jscode="""
Java.perform(function(){
    var utils = Java.use('包名.类名')
    utils.函数.overload("类型").implementation = function(函数值1,函数值2){ //类型指的是int,java.lang.String等
        console.log("Hook Start...");
        send("Success!");
        return this.函数(函数值1,函数值2);  
    }
});
"""

def message(message,data):
    if message["type"] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)
process = frida.get_remote_device().attach('app包名')
script=process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()

Frida模板【构造对象参数】

需要hook的代码段如下:
在这里插入图片描述

import frida,sys
jscode="""
Java.perform(function(){
    var utils = Java.use('包名.类名')
    utils.函数.overload("com.xiaojianbang.app.Money").implementation = function(obj){ //因为这里的类型为对象,所以写上对象的路径
        console.log("Hook Start...");
        send("Success!");
        send(obj.getInfo())
        var mon = mon.$new(1000,'港币')     //自己构造一个对象
        return mon.getInfo()
    }
});
"""

def message(message,data):
    if message["type"] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)
process = frida.get_remote_device().attach('app包名')
script=process.create_script(jscode)
script.on("message",message)
script.load()
sys.stdin.read()

推荐技术文章!:
https://bbs.pediy.com/thread-272270.htm

进阶补充

安装应用

adb install -r E:/a.apk

知识拓展

启动方式

frida也有其他的启动方式,在我上文中我是直接使用了python来启动frida,实质上frida提供了更多的启动方式:
attack模式,程序运行时进行hook

frida -UF -l frida.js 

spwan模式,重启APP进行hook

frida -U -f com.apple.ExampleCode

objection用法

可以不写代码,轻松hook某个方法的调用栈以及对应参数,可用于hook前的分析:
进入命令行

objection -g 程序包名 explore

监听对应方法的参数、调用栈、返回值

android hooking watch class_method 类名.方法名 --dump-args --dump-backtrace --dump-return

监听对应类下的所有方法,运用场景例如点击一个按钮,知道哪些方法被触发了

android hooking watch class 类名

搜索出完整的包名或类名,然后配合watch进行监听一个类

android hooking search classes/methods 类名/方法名

关闭监听方法调用

jobs list
jobs kill ID

利用objection 加载wallbreaker(https://github.com/hluwa/Wallbreaker)插件,实现获取程序内存中的类或者对象:

plugin load FILE_PATH
plugin wallbreaker classsearch CLASSNAME
plugin wallbreaker classdump --fullname com.test.CLASSNAME  //获取具体的class代码

plugin wallbreaker objectsearch OBJECTNAME  // 例如有一个new Student(),需要被创建了才能搜索到,所以这里搜索的是内存中的对象
plugin wallbreaker objectdump 0x00         //search到的地址填在这里

利用objection直接调用某程序类,进入命令行后输入以下命令即可执行到目标类:
例如直接进入第一关,第二关等等。

android intent launch_activity com.example.MainActivity

frida快速定位hook点

adb shell dumpsys activity top|findstr ACTIVITY

firda复杂类型的打印输出

下载相关工具包:
连接待补充:

1、将r0gson.dex放入frida同级目录
2、firda代码定义如下

function main(){
	Java.

firda主动调用某方法

// 实现firda主动对某方法进行调用
function invoke() {
    Java.perform(function () {

        // 第一种主动调用,从内存中找到实例对象,再进行调用
        Java.choose('com.dist.TestClass', {
            onMatch: function (instance) {
                instance['Value'].value = "可以调用一个变量用于修改该值(可以是非静态变量)。第二种方式,直接用jadx复制变量为frida片段。"
                instance['_Value'].value = "如果属性名和方法名相同,需要加下划线在前方"
                instance.runtest();
                console.log("invoke success!")
            }, oncomplete: function () {
                console.log("search completed!")
            }
        })

        // 第二种主动调用 创建一个新的实例对象,再进行调用
        // 这里顺便再扩展如何构建一个复杂的参数,拿runtest(Student stu)举例,即传递的参数不是常规的string类型
        let test_ins = Java.use("com.dist.TestClass").$new()
        let stu_ins = Java.use("com.test.Student").$new()
        let arr_ins = Java.use("java.util.ArrayList").$new()
        let age_ins = Java.use("java.lang.Integer").$new(10)
        arr_ins.add("123")
        stu_ins.setFriends(arr_ins)
        stu_ins.setStuName("st1")
        stu_ins.setAge(age_ins)
        test_ins.runtest(stu_ins);
        console.log(test_ins.getTest())

study

// 实现打印复杂类型
function main() {
    Java.perform(function () {
        Java.openClassFile("/data/local/tmp/r0gson.dex").load();
        const gson = Java.use('com.r0ysue.gson.Gson');

        Java.use("xxx.xxx.xxx")['test'].implementation = function (param) {

            console.log(gson.$new().toJSON(param))
            return this.test(param)
        }
    })
}

// 实现firda主动对某方法进行调用
function invoke() {
    Java.perform(function () {

        // 第一种主动调用,从内存中找到实例对象,再进行调用
        Java.choose('com.dist.TestClass', {
            onMatch: function (instance) {
                instance['Value'].value = "可以调用一个变量用于修改该值(可以是非静态变量)。第二种方式,直接用jadx复制变量为frida片段。"
                instance['_Value'].value = "如果属性名和方法名相同,需要加下划线在前方"
                instance.runtest();
                console.log("invoke success!")
            }, oncomplete: function () {
                console.log("search completed!")
            }
        })

        // 第二种主动调用 创建一个新的实例对象,再进行调用
        // 这里顺便再扩展如何构建一个复杂的参数,拿runtest(Student stu)举例,即传递的参数不是常规的string类型
        let test_ins = Java.use("com.dist.TestClass").$new()
        let stu_ins = Java.use("com.test.Student").$new()
        let arr_ins = Java.use("java.util.ArrayList").$new()
        let age_ins = Java.use("java.lang.Integer").$new(10)
        arr_ins.add("123")
        stu_ins.setFriends(arr_ins)
        stu_ins.setStuName("st1")
        stu_ins.setAge(age_ins)
        test_ins.runtest(stu_ins);
        console.log(test_ins.getTest())


        // 使用工具来构造复杂参数
        Java.openClassFile("/data/local/tmp/r0gson.dex").load();
        let gson_ins = Java.use("com.r0ysue.gson.Gson").$new()
        let json_tmp = {stuName : 'st1', stuAge : 10, friends : [123, 456],map : null}

        let stu_ins2 = gson_ins.fromJson(JSON.stringify(json_tmp),java.use("com.test.Student").class)
        let test_ins2 = Java.use("com.dist.TestClass").$new()
        test_ins2.runtest(stu_ins2)

    })

    // 场景: 目标方法不在当前classloader中,在其他classloader,所以无法直接hook,需要切换到其他classloader才能捕获到函数
    function meiju(){
        Java.perform(function(){

            Java.enumerateClassLoaders({
                onMatch:function(loader){
                    console.log('classLoader' + loader);
                        try{
                            if(loader.findClass("com.DynamicCheck")){
                                console.log("this class is true!"+loader);
                                Java.classFactory.loader=loader;
                            }
                        }catch (error){
                            console.log(error);
                        }

                },oncomplete:function(){
                    console.log("enum success!");
                }
            })
            console.log("此时的classloader已经修改为了新的classloader,所以能够寻找到DynamicCheck")
            let DynamicCheck =  Java.use("com.xxx.DynamicCheck")
            DynamicCheck["check"].implementation = function(){
                let result = this["check"]();
                result = true;
                return result;
            }
        })
    }
}

frida打印其他对象

var Bundle = Java.use('com.target.class');
if (needprintdata !== null) {
    var realFinderObject = Java.cast(needprintdata, Bundle);
    var finderObjectClass = realFinderObject.getClass();
    var fields = finderObjectClass.getDeclaredFields();
    for (var i = 0; i < fields.length; i++) {
        var field = fields[i];
        var fieldName = field.getName();
        var fieldValue = field.get(realFinderObject);
        console.log("FinderObject " + fieldName + ": " + fieldValue);
    }
} else {
    console.log("c object is null");
}

frida打印list

 var list = Java.cast(b, Java.use("java.util.List"));
 var size = list.size();
 console.log(size)
 var arrayList = [];
 for (var i = 0; i < size; i++) {
     arrayList.push(list.get(i).toString());
 }
 // 输出数组
 console.log("List elements: ", arrayList);

frida打印Map

var keySet = map.keySet().toArray();

// 遍历键集合,并打印对应的键值对
for (var i = 0; i < keySet.length; i++) {
    var key = keySet[i].toString();
    var value = map.get(key);
    if (value !== null) {
        value = value.toString();
    } else {
        value = "null";
    }
    console.log("\nmap key: " + key + ", value: " + value);

frida打印byte[]

实现将[0,0,0],这类对象输出成正常的字符串

 var byteArray = Java.array('byte', bArr)
var str = '';
for (var i = 0; i < byteArray.length; i++) {
    // 获取字节值并转换为无符号整数
    var byteValue = byteArray[i] & 0xff;
    // 将无符号整数转换为字符,并添加到字符串中
    str += String.fromCharCode(byteValue);
}
// 输出转换后的字符串
console.log("encrycptDataStr", str);

另外将字符串恢复为byte[]如下:

var byteArrayValues = new Array(data.length).fill(0);
var byteArray2 = Java.array('byte', byteArrayValues);

// 遍历字符串中的每个字符,将其转换为对应的字节,并放入字节数组中
for (var j = 0; j < data.length; j++) {
   byteArray2[j] = data.charCodeAt(j);
}
console.log("修改后转换为byte:", byteArray2)

frida Didn’t find class

https://stackoverflow.com/questions/68606094/how-to-fix-error-java-lang-classnotfoundexception-on-frida

unable to find process with name ‘xxx’

import frida
import sys

def main():
    # 连接设备
    try:
        rdev = frida.get_remote_device()
        print("[*] 成功连接到设备")
    except Exception as e:
        print(f"[!] 连接设备失败:{e}")
        sys.exit(1)

    # 枚举应用程序
    applications = rdev.enumerate_applications()
    print("[*] 枚举的应用程序:")
    for app in applications:
        print(f" - {app.name} ({app.identifier})")

    # 检查目标应用是否运行
    target_app = None
    for app in applications:
        if app.identifier == "com.xxx.xxx":
            target_app = app
            break

    if not target_app:
        print("[!] 目标应用未找到或未运行")
        sys.exit(1)

    print(f"[*] 发现目标应用:{target_app.name} (PID: {target_app.pid})")

    # 附加到应用进程
    try:
        session = rdev.attach(target_app.pid)
        print("[*] 成功附加到应用进程")
    except Exception as e:
        print(f"[!] 附加应用进程失败:{e}")
        sys.exit(1)

    # 加载 Hook 脚本
    try:
        with open("hook.js", "r") as f:
            src = f.read()

        script = session.create_script(src)
        print("[*] 加载 Hook 脚本")

        # 消息处理函数
        def on_message(message, data):
            if message["type"] == "send":
                print(f"[*] {message['payload']}")
            elif message["type"] == "error":
                print(f"[!] Error: {message['stack']}")
            else:
                print(message, data)

        script.on("message", on_message)
        script.load()
    except Exception as e:
        print(f"[!] 加载脚本失败:{e}")
        sys.exit(1)

    # 阻塞等待输入
    print("[*] 按 Ctrl+C 退出")
    sys.stdin.read()

if __name__ == "__main__":
    main()
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐