猿人学APP逆向对抗2020赛题

此app为猿人学2020年的赛题,需要得到0-99所有id值对应的返回结果之和。抓包查看到接口为/api/match/11/query,参数是id值以及sing值,返回数据就是id值对应的data值。

img

反编译apk得到发起请求的函数为query,其调用getSign1以id值为参数生成sign值,onResponse函数会接收到请求的返回

img

主动调用请求函数

如果只是为了解题的话就可以直接通过frida主动调用query请求0-99所有id值对应的data,并hook onResponse函数得到返回结果计算和。对应的frida脚本如下:

function main(){
    Java.perform(function(){
        var ma_instance = 0;
        Java.choose("com.yuanrenxue.onlinejudge2020.MainActivity", {
            onMatch:function(instance){
                ma_instance = instance;
                console.log(ma_instance);
            },onComplete:function(){}
        })

        var sum = 0;
        Java.use("com.yuanrenxue.onlinejudge2020.MainActivity$4").onResponse.overload("okhttp3.Call", "okhttp3.Response").implementation = function(arg1, arg2){
            var response_string = arg2.body().string();
            console.log(response_string);
            var JsonObject = Java.use("org.json.JSONObject").$new(response_string);
            sum = sum + JsonObject.optInt("data");
        }

        for(var i = 0; i < 100; i++){
            var num = Java.use("java.lang.Integer").$new(i);
            ma_instance.query(num);
            Thread.sleep(1);
        }
         
        console.log("sum is :", sum);
    })
}
setImmediate(main)

但是在脚本执行的过程中发现每次请求都要等待很多时间,一开始以为是服务器响应的慢,但是手动点击请求确响应的很快。

img

查看一下getSign1函数做了哪些操作,此函数会调用native函数getSign。查看jni_onload函数得到此函数对应的native为sub_5C8A8

img

分析sub_5C8A8函数发现其会调用myLooper函数,如果返回null就会sleep。mylooper会返回当前java线程的looper,但是frida主动调用应该是native线程通过反射调用的,而此native线程与java虚拟机并没有关联,所以没有与之对应的java线程,调用myLooper会返回null并调用sleep,这也是之前脚本每次请求都需要等待很多时间的原因。

img

修改一下之前的脚本,replace sleep函数并直接返回。

var sleep_addr = Module.findExportByName("libc.so", "sleep");
console.log("sleep_addr is:", sleep_addr);
// 使用Interceptor.replace替换sleep函数的实现
Interceptor.replace(sleep_addr, new NativeCallback(function (seconds) {
    return 1;
}, "int", ["int"]));

再次运行脚本就可以正常获取到sum值为483974

img

分析sign算法

另一种方法就是分析sign生成算法,getSign对应的native为sub_5C8A8。此函数会先调用nextInt(10000)nextInt(1000000)生成两个随机值。

img

然后将两个随机值和id值格式化输出到%d:yuanrenxue2020:%ld:randomClientId%sReplaceWithYourTeamNameIfYouCrackedToHere字符串中,接着在字符串开头再增加一个随机值并对字符串其余元素与0x14进行异或运算。

img

最后调用sub_5E950函数对前面的字符串再次进行编码,此函数是一个魔改的base64(四个一组的字符,第二个和第三个的位置发生交换),码表为9+#FvwxNG78pqrghijCDEWXy4oAd56kQHlmBuOPYz0cstef1IJKLM23ZabnRSTUV

img

frida hook各个函数查看sign生成的整个过程。

img

知道了sign的生成算法就可以直接编写python脚本了

import httpx
import random
from urllib.parse import quote_plus

# 定义base64字符表
BASE64_CHARS = "9+#FvwxNG78pqrghijCDEWXy4oAd56kQHlmBuOPYz0cstef1IJKLM23ZabnRSTUV"
def my_base64_encode(data):
    # 将数据转换为字节串
    data = data.encode()
    # 初始化编码后的字符串
    encoded = ""
    # 对每三个字节进行处理
    for i in range(0, len(data), 3):
        # 获取当前三个字节
        chunk = data[i:i+3]
        # 计算当前三个字节对应的24位二进制数
        bits = 0
        for j in range(len(chunk)):
            bits += chunk[j] << (16 - j * 8)
        # 对每六位二进制数进行编码,得到四个base64字符,并拼接到编码后的字符串中
        for k in range(4):
            if i * 8 + k * 6 < len(data) * 8:
                index = bits >> (18 - k * 6) & 0x3F
                encoded += BASE64_CHARS[index]
            else:
                encoded += "=" # 补齐等号

    # 对每四个字符一组的字符中的第2个与第3个互换
    swapped = ""
    for i in range(0, len(encoded), 4):
        group = encoded[i:i+4]
        if len(group) == 4:
            group = group[0] + group[2] + group[1] + group[3]
        swapped += group

    return swapped


def main():
    sum = 0
    num = 0
    client = httpx.Client(http2=True, verify=False)
    url = "https://match.yuanrenxue.com/api/match/11/query?id="
    while num < 100:
        n1 = random.randint(0, 10000)
        n2 = random.randint(0, 1000000)
        n3 = random.randint(0, 120)
        encrypt_sign = str(n1) + ":yuanrenxue2020:" + str(num) + ":randomClientId" + str(n2) + "ReplaceWithYourTeamNameIfYouCrackedToHere"
        for i in encrypt_sign:
            i = ord(i) ^ 0x14
            encrypt_sign = encrypt_sign + chr(i) 
        encrypt_sign = str(chr(n3)) + encrypt_sign;
        encrypt_sign = my_base64_encode(encrypt_sign)
        response = client.get(url + str(num) + "&sign=" + quote_plus(encrypt_sign))
        sum = sum + response.json()['data']
        num = num + 1
        print(response.text)
    print(sum)

if __name__ == '__main__':
    main()

最后运行的结果也是483974

img

posted @ 2023-03-19 18:03  怎么可以吃突突  阅读(202)  评论(0编辑  收藏  举报