猿人学-APP大赛-第一题-Frida初试、调用Java代码

如何用fiddler抓包参考:https://www.cnblogs.com/JKding233/p/16649489.html

先介绍一些前置知识点和环境配置

Frida 安装和运行

pip3 install frida frida-tools

查看模拟器版本:

adb shell
getprop ro.product.cpu.abi

>>> x86_64

下载对应 frida-server:(小于或等于本地frida版本

image.png

解压到adb文件夹,推到手机里面:

adb push frida-server-15.1.10-android-x86_64 /data/local/tmp/frida-server

启动 frida:

adb shell
su
cd /data/local/tmp
chmod 755 frida-server
./frida-server

配置接口转发,注意每次启动都需要配置:

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

开启另一个cmd,运行:frida-ps -R,连接成功:

image.png

Frida Python 脚本
hook 脚本示例
import frida
import sys

device = frida.get_remote_device()  # adb devices 查看
process = device.attach("猿人学2022")  # 进程名

src = """
function main(){
    console.log("脚本加载成功");
    Java.perform(function() {
        var clazz = Java.use('com.yuanrenxue.match2022.security.Sign');
        clazz.sign.implementation = function() {
            console.log('进入函数内部');
            console.log("参数为:", arguments)
            console.log("结果为:",clazz.sign.apply(this, arguments));
            return clazz.sign.apply(this, arguments);
        }
    });
}
setImmediate(main)
"""


def on_message(message, data):
    if message["type"] == "send":
        print(f"send >>> {message['payload']}")
    else:
        print(message)


script = process.create_script(src)
script.on("message", on_message)
script.load()
sys.stdin.read()

  • 一直没有输出的话,先退出重新启动app,再运行脚本
  • 一直进入不了函数内部的话,可以使用 rpc 的方式
  • 不需要 on_message 也可以打印
rpc 脚本示例
import frida


device = frida.get_remote_device()  # adb devices 查看
process = device.attach("猿人学2022")

rpc_code = """
var result
function main() {
 console.log("Script loded successfully")
 console.log()
    Java.perform(function () {
    	// 主要是修改这里面
        var Sign = Java.use('o00OO.OooO00o')
        console.log("调用函数成功")
        // var sign = Sign.$new()
        // console.log("创建实例成功")
        result = Sign.OooO00o(-274893829204)
        console.log(result)
    })
    return result
}
rpc.exports = {
    main: main
}
"""


def on_message(message, data):
    if message["type"] == "send":
        print(f"send >>> {message['payload']}")
        # bytes2str(message['payload'])
    else:
        print(message)


script = process.create_script(rpc_code)
script.on("message", on_message)
script.load()
result = script.exports.main()
print(result)

  • 看情况是否要创建实例public static 开头的静态函数直接调用就行
题目分析:

登录APP后,显示要强制更新,

http://download.python-spider.com/yuanrenxuem107.apk

那就先把 apk 文件下载到本地,方便后面分析,抓包看一下分析请求

image.png

只有 sign 是变动的,page、t、token 都是一些普通的参数,直接整个文件拖进 jadx 分析,搜索关键词 第一题

很快定位到 com.yuanrenxue.match2022.fragment.challenge.ChallengeOneFragment 这个类,直接搜索 sign

image.png

可以看出,参数主要是 sbstring 再转 bytes 传入的,先用 frida hook 一下sign函数的参数和结果:

image.png

把参数 send 出来,转成字符串,python bytes 列表转字符串方法如下

def bytes2str(ascii_lst):
    string = ''.join(map(chr, ascii_lst))
    print(string)
var clazz = Java.use('com.yuanrenxue.match2022.security.Sign');
clazz.sign.implementation = function() {
    console.log('进入函数内部');
    console.log("参数为:", arguments[0]);
    send(arguments[0]);
    console.log("结果为:",clazz.sign.apply(this, arguments));
    return clazz.sign.apply(this, arguments);
}

on_message 函数改一下:

def on_message(message, data):
    if message["type"] == "send":
        print(f"send >>> {message['payload']}")
        bytes2str(message['payload'])
    else:
        print(f"log >>> {message}")

打印结果:

image.png

可见参数为:page=x 加 时间戳

进入 sign 函数内部,只引入了一个依赖,可以考虑扣 java代码,觉得麻烦的话就直接用rpc,这里两个方案的实现都写一下

image.png

方案一 调用 java 代码

既然要扣代码,就得先解决第三方依赖的问题,看一下调用点:

image.png

传入的是一个长整型数字,结合 String.format() 函数的使用 ,这里很可能是一个固定值,用于字符转换

本来想用 hook,结果死活进不去函数,无奈改用rpc调用的方式,脚本我已经放在上面了,计算结果为:%02x%02x%02x%02x

简单介绍一下,在 java中,String.format("%02X", 11) 就是 11 转 十六进制的意思,不足的部分补0,所以输出:0B

同理 String.format("%03X", 11) 输出 00B

抠出来的java代码如下:

import java.util.ArrayList;

/* compiled from: proguard-dict.txt */
/* loaded from: classes2.dex */
public class Sign {

    /* renamed from: A */
    private static final int f276A = 1732584193;

    /* renamed from: B */
    private static final int f277B = -271733879;

    /* renamed from: C */
    private static final int f278C = -1732584194;

    /* renamed from: D */
    private static final int f279D = 271733878;

    /* renamed from: f */
    private static int m21f(int i, int i2, int i3) {
        return ((~i) & i3) | (i2 & i);
    }

    /* renamed from: ff */
    private static int m20ff(int i, int i2, int i3, int i4, int i5, int i6) {
        return rotateLeft(i + m21f(i2, i3, i4) + i5, i6);
    }

    /* renamed from: g */
    private static int m19g(int i, int i2, int i3) {
        return (i & i3) | (i & i2) | (i2 & i3);
    }

    /* renamed from: gg */
    private static int m18gg(int i, int i2, int i3, int i4, int i5, int i6) {
        return rotateLeft(i + m19g(i2, i3, i4) + i5 + 1518565785, i6);
    }

    /* renamed from: h */
    private static int m17h(int i, int i2, int i3) {
        return (i ^ i2) ^ i3;
    }

    /* renamed from: hh */
    private static int m16hh(int i, int i2, int i3, int i4, int i5, int i6) {
        return rotateLeft(i + m17h(i2, i3, i4) + i5 + 1859775393, i6);
    }

    private ArrayList<Integer> padding(byte[] bArr) {
        long length = bArr.length * 8;
        ArrayList<Integer> arrayList = new ArrayList<>();
        for (byte b : bArr) {
            arrayList.add(Integer.valueOf(b));
        }
        arrayList.add(128);
        while (((arrayList.size() * 8) + 64) % 512 != 0) {
            arrayList.add(0);
        }
        for (int i = 0; i < 8; i++) {
            arrayList.add(Integer.valueOf((int) ((length >>> (i * 8)) & 255)));
        }
        return arrayList;
    }

    private static int rotateLeft(int i, int i2) {
        return (i >>> (32 - i2)) | (i << i2);
    }

    public String sign(byte[] bArr) {
        ArrayList<Integer> padding = padding(bArr);
        int i = f276A;
        int i2 = f277B;
        int i3 = f278C;
        int i4 = f279D;
        for (int i5 = 0; i5 < padding.size() / 64; i5++) {
            int[] iArr = new int[16];
            for (int i6 = 0; i6 < 16; i6++) {
                int i7 = (i5 * 64) + (i6 * 4);
                iArr[i6] = (padding.get(i7 + 3).intValue() << 24) | padding.get(i7).intValue() | (padding.get(i7 + 1).intValue() << 8) | (padding.get(i7 + 2).intValue() << 16);
            }
            int[] iArr2 = {0, 4, 8, 12};
            int i8 = i;
            int i9 = i2;
            int i10 = i3;
            int i11 = i4;
            int i12 = 0;
            while (i12 < 4) {
                int i13 = iArr2[i12];
                i8 = m20ff(i8, i9, i10, i11, iArr[i13], 3);
                int m20ff = m20ff(i11, i8, i9, i10, iArr[i13 + 1], 7);
                i10 = m20ff(i10, m20ff, i8, i9, iArr[i13 + 2], 11);
                i9 = m20ff(i9, i10, m20ff, i8, iArr[i13 + 3], 19);
                i12++;
                i11 = m20ff;
            }
            int[] iArr3 = {0, 1, 2, 3};
            int i14 = i8;
            int i15 = i11;
            for (int i16 = 0; i16 < 4; i16++) {
                int i17 = iArr3[i16];
                i14 = m18gg(i14, i9, i10, i15, iArr[i17], 3);
                i15 = m18gg(i15, i14, i9, i10, iArr[i17 + 4], 5);
                i10 = m18gg(i10, i15, i14, i9, iArr[i17 + 8], 9);
                i9 = m18gg(i9, i10, i15, i14, iArr[i17 + 12], 13);
            }
            int[] iArr4 = {0, 2, 1, 3};
            int i18 = i14;
            int i19 = 0;
            while (i19 < 4) {
                int i20 = iArr4[i19];
                int m16hh = m16hh(i18, i9, i10, i15, iArr[i20], 3);
                i15 = m16hh(i15, m16hh, i9, i10, iArr[i20 + 8], 9);
                i10 = m16hh(i10, i15, m16hh, i9, iArr[i20 + 4], 11);
                i9 = m16hh(i9, i10, i15, m16hh, iArr[i20 + 12], 15);
                i19++;
                i18 = m16hh;
            }
            i += i18;
            i2 += i9;
            i3 += i10;
            i4 += i15;
        }
        return String.format("%02x%02x%02x%02x", Integer.valueOf(i), Integer.valueOf(i2), Integer.valueOf(i3), Integer.valueOf(i4));
    }
}

有一个地方需要注意,int length = bArr.length * 8 替换为 long length = bArr.length * 8

jpype的使用,上面有介绍,可以比对 hook结果进行验证

我这里就直接上爬虫脚本了

# -*- coding: utf-8 -*-
# time: 2022/10/1 13:44
# author: Chen

import requests
import jpype

jvm_path = jpype.getDefaultJVMPath()
jpype.startJVM(jvm_path, "-ea", "-Dfile.encoding=utf-8", convertStrings=True)
JDClass = jpype.JClass("Sign")
javaInstance = JDClass()

# jpype.shutdownJVM()
results = []
for page in range(1, 101):
    token_url = "https://appmatch.yuanrenxue.com/time?token=MuuqC7kHE79hZhN3L3Ip3u2e1jp%252BgLY1V2S1%252FKWTbpYZMqchIHNxll%252BE06%252BEuHQ5"
    resp_json = requests.get(token_url).json()
    ts = resp_json["time"]
    raw_sign = f"page={page}{ts}"
    url = "https://appmatch.yuanrenxue.com/app1"
    data = {
        "page": page,
        "sign": javaInstance.sign(raw_sign.encode()),
        "t": ts,
        "token": "MuuqC7kHE79hZhN3L3Ip3u2e1jp gLY1V2S1/KWTbpYZMqchIHNxll E06 EuHQ5"
    }
    resp_json = requests.post(url, data=data).json()
    num_lst = [int(d['value'].strip()) for d in resp_json["data"]]
    print(f"第 {page} 页:", num_lst)
    results.extend(num_lst)

print("100页面总和为:", sum(results))

运行结果:

image.png

方案二 Frida rpc

参考:https://www.cnblogs.com/JKding233/p/16649489.html 原理就不多介绍了,主要是把sign函数暴露出来

# -*- coding: utf-8 -*-
# time: 2022/10/1 14:29
# author: Chen

import frida
import requests


rpc_code2 = """
var bArr
var result
function main(bArr) {
    console.log("Script loded successfully")
    console.log(bArr)
    Java.perform(function () {
        var Sign = Java.use('com.yuanrenxue.match2022.security.Sign')
        console.log("调用函数成功")
        var instance = Sign.$new()
        console.log("创建实例成功")
        result = instance.sign(bArr)
        console.log(result)
    })
    return result
}
rpc.exports = {
    main: main
}
"""

device = frida.get_remote_device()  # adb devices 查看
process = device.attach("猿人学2022")
script = process.create_script(rpc_code2)
script.load()
results = []
for page in range(1, 101):
    token_url = "https://appmatch.yuanrenxue.com/time?token=MuuqC7kHE79hZhN3L3Ip3u2e1jp%252BgLY1V2S1%252FKWTbpYZMqchIHNxll%252BE06%252BEuHQ5"
    resp_json = requests.get(token_url).json()
    ts = resp_json["time"]
    raw_sign = f"page={page}{ts}"
    bArr = [ord(c) for c in raw_sign]
    url = "https://appmatch.yuanrenxue.com/app1"
    data = {
        "page": page,
        "sign": script.exports.main(bArr),
        "t": ts,
        "token": "MuuqC7kHE79hZhN3L3Ip3u2e1jp gLY1V2S1/KWTbpYZMqchIHNxll E06 EuHQ5"
    }
    resp_json = requests.post(url, data=data).json()
    num_lst = [int(d['value'].strip()) for d in resp_json["data"]]
    print(f"第 {page} 页:", num_lst)
    results.extend(num_lst)

print("100页面总和为:", sum(results))

挑战成功

image.png

整个逆向过程花费的时间挺多的,不过总算搞懂了 frida的使用,以后要逆向其他的app就方便多了 😁✨👌

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Frida是一款强大的动态分析工具,可以在运行中的应用程序中注入脚本,以便监测、修改、甚至是欺骗应用程序的行为。要使用Frida主动调用app内的Java方法,可以按照以下步骤进行: 1. 安装FridaFrida可以在Windows、macOS、Linux等多个平台上使用,你可以根据自己的需求选择相应的安装方式。 2. 下载FridaJava API库:Frida提供了Java API库,可以帮助我们在Frida脚本中直接调用Java方法。 3. 编写Frida脚本:在Frida脚本中,我们可以使用Java API库提供的Java.use()方法来获取需要调用Java类,然后使用call()方法来调用该类中的方法。 4. 注入脚本:使用Frida提供的命令行工具或API,在运行中的应用程序中注入脚本。 以下是一个简单的Frida脚本示例,演示了如何调用Android应用程序中的Java方法: ```javascript Java.perform(function () { // 获取要调用Java类 var targetClass = Java.use("com.example.app.TargetClass"); // 调用Java方法 var result = targetClass.targetMethod("arg1", "arg2"); // 打印返回值 console.log("Result: " + result); }); ``` 在上面的脚本中,我们首先使用Java.use()方法获取了一个名为“TargetClass”的Java类,然后使用该类中的“targetMethod”方法,并传递两个参数“arg1”和“arg2”。最后,我们使用console.log()方法将返回的结果打印到控制台上。 当我们将上述脚本注入到运行中的应用程序中后,就可以看到控制台输出了调用Java方法的结果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值