The first question of Android Reverse Ape Science 2022 app competition (step by step verification)

Analysis process

  • 1. Start
  • 2. Capture packets
  • 3. Analysis
  • 4. HOOK
  • Five, verification
  • 6. Reference

1. Start

Because you know in advance that it is a practice app, you don’t need to do shell checking and other operations. Generally, you need to check and unpack it first!
First open the APP, open the first question, you can find that it is the question to calculate the sum of numbers

2. Capture packets

Open HttpCanary (little yellow bird) to capture packets (because the first question does not set any packet capture confrontation, all Charles agents can also be captured). It can be found that the request carries 4 parameters, and the token is our identity information which will not change.
The three parameters of page, t, and sign will change every time. The sign parameter is what we need to reverse.

3. Analysis

Now that we know that we want to reverse sign, then open jadx and drag yuanrenxuem109.apk into it for decompilation and analysis.

Open the search window here, and search for the sign field that we need to reverse


You can see that the bottom two are more in line with our requirements, here directly follow up to see, after following up, you can find that this POST is followed by a /app1

At this time, review the content of our packet capture, and you can see that the URL requested with the sign is app1, then continue to follow up

Directly find out who called the method whose name is confused.

You can see that there are only two calls, and carefully observe that the two are exactly the same, so choose one to analyze.

Here you can clearly see a sign of sign, so let’s continue to follow the method of deep search and follow up.

It can be roughly seen that this is an encryption operation function, and it has also followed to the bottom. We will not study the internal logic of this function here, but first hook this function to see if the returned result is consistent with the packet capture result.

I am using the jadx-gui (1.4.7) version here, and there is an option to copy it as a frida fragment. Here I use it directly for convenience. If you are a novice, it is recommended to tap more.

4. HOOK

First create a test.js file, then build a main function that calls the framework, and then paste the code into it.

function main() {<!-- -->
    Java. perform(function () {<!-- -->
        let Sign = Java.use("com.yuanrenxue.match2022.security.Sign");
        Sign["sign"].implementation = function (bArr) {<!-- -->
            console.log(`Sign.sign is called: bArr=${<!-- -->bArr}`);
            let result = this["sign"](bArr);
            console.log(`Sign.sign result=${<!-- -->result}`);
            return result;
        };
    })

}
setImmediate(main)

Then start frida

>adb shell
>su
>cd data/local/tmp
>./frida-server-16.0.2-android-arm64 -l 0.0.0.0:8881

port forwarding

>adb forward tcp:8881 tcp:8881

Frida calls the hook code

> frida -H 127.0.0.1:8881 -f com.yuanrenxue.match2022 -l test.js

The run in the picture is called by the author wrongly, but it is actually test

It can be seen that the hook is successfully hooked up, and it is consistent with the content of the captured packet, but the bArr passed in is a byte[] type of data, and there is no way to intuitively see what parameters are passed in.
Looking back at the calling site, we can find that it is a byte[] array converted from a String, then we can reverse it to see what is passed in.

Here to print byte[] type data I use the following code

let ByteString = Java.use("com.android.okhttp.okio.ByteString")
ByteString.of(bArr).utf8()

Overall code:

function main() {<!-- -->
    Java. perform(function () {<!-- -->
        let ByteString = Java.use("com.android.okhttp.okio.ByteString")
        let Sign = Java.use("com.yuanrenxue.match2022.security.Sign");
        Sign["sign"].implementation = function (bArr) {<!-- -->

            console.log(`Sign.sign is called: bArr=${<!-- -->ByteString.of(bArr).utf8()}`);
            let result = this["sign"](bArr);
            console.log(`Sign.sign result=${<!-- -->result}`);
            return result;
        }
    })

}
setImmediate(main)

Mention to exit the last frida, you can type exit and you will exit.


Start it again, and you can see that it is back to normal this time. The parameter is page=11684850913, and the sign is d8fde916979aeb58d3398ea0c0e455c7, which is similar to the parameters we analyzed before.

Now that we know the parameters and the encrypted function call method, we can try to actively call this method to pass in the parameters to get the encrypted result.

5. Verify

Here, the rpc mode of frida is used, and python is used to call to get the result. This is the basic fixed way of calling rpc in python:

import frida
import sys

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

with open('./agent.js') as f:
    test_js = f. read()

# start mode 1
manager = frida. get_device_manager()
# process = manager.add_remote_device(host).get_frontmost_application()
#print(process)
process = manager.add_remote_device(host).attach("Ape Science 2022")
script = process.create_script(test_js)
script.on('message', on_message)
script. load()
# This is how to call rpc, the parameter is page=11684850913 seen above
print(script.exports_sync.getsign("page=11684850913"))
sys.stdin.read()

Create the agent.js file, enter the following code to get the sign method, and pass in the parameters we got above to call. (The following is the writing method of frida’s rpc export function)

function getSign(str) {<!-- -->
    var result = 0;
    Java. perform(function () {<!-- -->
        var targetClassName = Java.use("com.yuanrenxue.match2022.security.Sign")
        result = targetClassName. sign(str);
    })
    return result
}

rpc.exports = {<!-- -->
    getsign: getSign
};

You can see an error when you run it directly. The parameter passed in is not of byte type ([B)-if you don’t understand, you can see the Jni method signature
Then we need to pass in the incoming parameter into a byte[] type format like him.

Here I use the library that comes with Android to implement this method of converting strings to bytes.

function stringToBytes(str) {<!-- -->
    var data = Java. use("java. lang. String"). $new(str);
    return data. getBytes();
}

After modifying the code:

function getSign(str) {
    var result = 0;
    Java. perform(function () {
        var targetClassName = Java.use("com.yuanrenxue.match2022.security.Sign")
        var bytes = stringToBytes(str);
        result = targetClassName. sign(bytes);
    })
    return result
}

function stringToBytes(str) {<!-- -->
    var data = Java. use("java. lang. String"). $new(str);
    return data. getBytes();
}


rpc.exports = {
    getsign: getSign
};

Run it again, and you will find an error message: sign: cannot call an instance method without an instance.

Checking the code shows that our Java.use(“com.yuanrenxue.match2022.security.Sign”) needs to be instantiated before calling the sign method. Here is the instantiated code:

function getSign(str) {
    var result = 0;
    Java. perform(function () {
        var targetClassName = Java.use("com.yuanrenxue.match2022.security.Sign").$new();
        var bytes = stringToBytes(str);
        result = targetClassName. sign(bytes);
    })
    return result
}

function stringToBytes(str) {<!-- -->
    var data = Java. use("java. lang. String"). $new(str);
    return data. getBytes();
}

rpc.exports = {
    getsign: getSign
};

This time, the data is displayed normally, and it is the same as the above sign. Here, the rpc call is completed and the result is successfully corresponding.

6. Reference

Analysis of the first question of the 2022 Android reverse confrontation competition of ape anthropology