Hi, everybody. The topic of this issue is sharing open source frameworks, so today I’d like to share with you a tool THAT I’ve been working on recently.
In this paper, starting from the safety guest – a thinking of new media, with identity lateautumn4lin original release, the original link: www.anquanke.com/post/id/218…
This is a frida-RPC tool based on FastAPI implementation -Arida (Github address), I am an independent developer of this tool, want to introduce the concept of this tool and how to use it.
1 Development Idea
Tools often come from everyday work, and when there is a “repeat, repeat, repeat” part of the work, a tool that saves time and improves productivity is born. My daily work involves reverse analysis of APP protocols. Currently, the tool I use is generally Frida. Sometimes, in order to verify analysis results, Hook method will be used to call methods and the interface will be exposed through the built-in RPC method. Therefore, a series of problems prompted me to develop a set of tools to improve my work efficiency.
1.1 Problems encountered in work
1.1.1 Multiple Apps multiple Frida-js files
Those of you who have just started to use Frida frequently in your work will surely find that you need to write different JavaScript files every time you reverse analyze your APP. It takes a long time. How to maintain so many files? How to start corresponding JavaScript files for different apps? How can duplicate code be extracted from each file? These are all questions about frida-JS file management.
1.1.2 Write Js method to construct the corresponding API method
How do we understand this? You know that the Frida JavaScript Function is exposed like this
rpc.exports = {
decryptData: decrypt_data,
generateUrl: generate_url,
encryptData: encrypt_data
}
Copy the code
The Map for RPC. Exports is used to specify the exposed method and the corresponding function, but this only makes use of the JavaScript exports keyword to expose the method so that other scripts can call it. So how do you make an Http interface? You can directly use NodeJs Http frameworks such as Express, but most of the frameworks we use are Python frameworks such as Flask and Django. Anyone who has used frameworks knows that we need to write one method for each API, such as this
@app.route("/test")
def test():
return "hello"
Copy the code
Combined with this approach, this is how we call Frida-RPC
@app.route('/sign')
def sign_test():
sign = request.args['sign']
res = script.exports.sign(sign)
return res
Copy the code
We need to write Python methods for each JavaScript method and call parameters directly. The problem with this is that the more methods we accumulate, the bigger our overall project becomes, but many parts of the code are repetitive simple calls.
1.1.3 Collaboration issues
It’s also a tricky question to ask if you can provide a complete documentation of your API when you’ve done all of the above and deployed your service. Do you still need interfaces to write corresponding documents?
1.2 What pain points should be solved by the tool
In view of the above problems, we need an efficient tool that can help us to block the details in our work, so that we can focus more on reverse analyzing the call flow in the APP. So, we need a tool that does these things:
-
Manage JavaScript files, with APP- file mapping
-
Automatically generate API methods for existing JavaScript methods
-
Automatically generate Open API documentation
1.3 Arida tools
When the idea of a “want to develop a tool” to generate the power to make up, it took about two hours, completed a simple tool, namely the mentioned Arida this tool, the name is derived from the Frida and API, the two words simple joining together of, have the function as mentioned above.
1.3.1 Specific workflow
The workflow is as follows:
There are four main steps:
-
Step 1: Use JavaScript AST tree to obtain the name of function and the number of parameters of corresponding function in the exports Map, so as to facilitate the subsequent construction of Pydantic Model.
-
Step 2: Generate Pydantic dynamic model to facilitate the generation of API Doc for FastAPI.
-
Step 3: Combine the model with the obtained JavaScript method name and the number of parameters to create a new Python method.
-
Step 4: Register the routes corresponding to each APP, and finally register them in the global route.
2 Source code interpretation
I have covered the Arida workflow, but I will focus on the implementation of each part.
2.1 Exporting Frida JavaScript Function Information
The general frida-JS script looks like this
var result = null;
function get_sign(body) {
Java.perform(function () {
try {
var ksecurity = Java.use('com.drcuiyutao.lib.api.APIUtils');
result = ksecurity.updateBodyString(body)
console.log("myfunc result: " + result);
} catch (e) {
console.log(e)
}
});
return result;
}
rpc.exports = {
getSign: get_sign,
}
Copy the code
The information we need is the name of the derived function and the number of parameters of the internal function corresponding to the derived function. We considered using a regular method to do this, but regular methods are cumbersome, so starting from the JavaScript AST tree can better parse the information we need.
The parsed script is as follows:
const babel = require('@babel/core') var exports = new Map(); var functions = new Map(); var result = new Map(); Function parse(code) {let visitor = {// process exports node, Obtain export function corresponding table ExpressionStatement (path) {let params = path. The node. The expression. The right; try { params = params.properties for (let i = 0; i < params.length; Params [I].value. Name, params[I].key.name)}} catch {}}, FunctionDeclaration(path) {let params = path.node; var lst = new Array(); for (let i = 0; i < params.params.length; i++) { lst.push(params.params[i].name) } functions.set(params.id.name, lst) } } babel.transform(code, { plugins: [ { visitor } ] }) exports.forEach(function (value, key, map) { result.set(value, functions.get(key)) }) return Object.fromEntries(result); }Copy the code
Function and exports nodes are resolved, and Map is returned
2.2 FastAPI API interface model is dynamically generated
In the previous step, I got the JavaScript Map data, which looks something like this
{
"getSign":3
}
Copy the code
Next, we need to use this information to dynamically generate the interface model. The reason for generating the interface model is that in the FastAPI framework, the Post interface uses Pydantic BaseModel. The reason for using BaseModel is that on the one hand, we need to generate external interface documents. On the other hand, to do type check for parameters, dynamically generated code is as follows:
from pydantic import create_model
params_dict = {"a":""}
Model = create_model(model_name, **params_dict)
Copy the code
Insert Pydantic create_model, which takes the types of each method parameter. For example, if the type is String, it is “”, and if the type is int, it is 0.
2.3 Dynamically generating Python methods based on Python AST
In the final step, with the model and JavaScript Map data we can dynamically generate Python methods, since the usual API methods are the same, as follows:
def sign_test():
sign = request.args['sign']
res = script.exports.sign(sign)
return res
Copy the code
We need to dynamically generate the above format, which can be done in two ways
- The first: closure methods – functions return functions, such as
def outer():
def inner():
return "hello"
return inner
Copy the code
- Second: use
Python AST
Tree generatedPython bytecode
, the use oftypes.FunctionDef
To generate the following code:
function_ast = FunctionDef(
lineno=2,
col_offset=0,
name=func_name,
args=arguments(
args=[
arg(
lineno=2,
col_offset=17,
arg='item',
annotation=Name(lineno=2, col_offset=23,
id=model_name, ctx=Load()),
),
],
vararg=None,
kwonlyargs=[],
kw_defaults=[],
kwarg=None,
defaults=[],
posonlyargs=[]
),
body=[
# Expr(
# lineno=3,
# col_offset=4,
# value=Call(
# lineno=3,
# col_offset=4,
# func=Name(lineno=3, col_offset=4,
# id='print', ctx=Load()),
# args=[
# Call(
# lineno=3,
# col_offset=10,
# func=Name(lineno=3, col_offset=10,
# id='dict', ctx=Load()),
# args=[Name(lineno=3, col_offset=15,
# id='item', ctx=Load())],
# keywords=[],
# ),
# ],
# keywords=[],
# ),
# ),
Assign(
lineno=3,
col_offset=4,
targets=[Name(lineno=3, col_offset=4,
id='res', ctx=Store())],
value=Call(
lineno=3,
col_offset=10,
func=Attribute(
lineno=3,
col_offset=10,
value=Attribute(
lineno=3,
col_offset=10,
value=Name(lineno=3, col_offset=10,
id='script', ctx=Load()),
attr='exports',
ctx=Load(),
),
attr=func_name,
ctx=Load(),
),
args=[
Starred(
lineno=4,
col_offset=38,
value=Call(
lineno=4,
col_offset=39,
func=Attribute(
lineno=4,
col_offset=39,
value=Call(
lineno=4,
col_offset=39,
func=Name(
lineno=4, col_offset=39, id='dict', ctx=Load()),
args=[
Name(lineno=4, col_offset=44, id='item', ctx=Load())],
keywords=[],
),
attr='values',
ctx=Load(),
),
args=[],
keywords=[],
),
ctx=Load(),
),
],
keywords=[],
),
),
Return(
lineno=4,
col_offset=4,
value=Name(lineno=4, col_offset=11, id='res', ctx=Load()),
),
],
decorator_list=[],
returns=None,
)
Copy the code
Start by dynamically generating a Python AST tree for the corresponding method
module_ast = Module(body=[function_ast], type_ignores=[])
module_code = compile(module_ast, "<>", "exec")
function_code = [
c for c in module_code.co_consts if isinstance(c, types.CodeType)][0]
Copy the code
Generates the bytecode corresponding to the Python AST tree
function = types.FunctionType(
function_code,
{
"script": script,
model_name: model,
"print": print,
"dict": dict
}
)
function.__annotations__ = {"item": model}
Copy the code
To generate new methods using bytecode, the __annotations__ attribute of the original bytecode is lost when the new method is generated, so it needs to be manually added after the new method is generated.
3 Usage Mode
The project already includes two simple examples of how to use Arida. The configuration information is in the config.py file under the Apps directory.
3.1 Two steps to build a new project
How do you build a new project? It only takes two steps, as shown below:
- Step 1: Add configuration information. The file address is
config.py
INJECTION_APPS = [{" name ":" my test 1 ", "path" : "yuxueyuan", "package_name" : "com. Drcuiyutao. Babyhealth"}, {" name ": "My test 2", "path": "kuaiduizuoye", "package_name": "com.kuaiduizuoye.scan"}]Copy the code
INJECTION_APPS: INJECTION_APPS: INJECTION_APPS: INJECTION_APPS: INJECTION_APPS: INJECTION_APPS: INJECTION_APPS
-
Name: This is the name of the group in the FastAPI Doc. It has no real meaning. It can be interpreted as an improvement in the experience of people who read the interface document.
-
Path: Matches the value of this field to the corresponding JavaScript file in the Apps folder.
-
Package_name: indicates the name of the package to be injected
That completes the first step.
- Step 2: Develop the corresponding APP
Frida-Js
The script
3.2 Enterprise-level Multi-App Signature API Exposure
In daily work, we tend to reverse analyze multiple apps at the same time, so it is essential to expose the API interface test of multiple apps at the same time.Arida
Supports simultaneous startup of multiple apps and injection of correspondingJavaScript
Script, just follow the above steps to complete the development of each APP project, the startup will automatically inject the corresponding APP, at the same time, when viewing the document will also be shown in the figure:
Note 4 points
4.1 Parameter Type tags
Since JavaScript cannot specify the parameter type of the method, the JavaScript method read can only be the number of parameters, not the parameter type, so the generated Pydantic model can only be uniform type of characters, if you want to customize the parameter type, This can be configured in function_params_hints in the main.py file:
function_params_hints = {
"encryptData": [0, 0, 0, 0, "", 0, 0, 0, 0, 0, 0]
}
Copy the code
In this way, a suitable parameter model is generated that can be converted to the corresponding types of the parameters in the model when the interface is used by the user.