diff --git a/README.md b/README.md index d18b238..d3e5dc9 100644 --- a/README.md +++ b/README.md @@ -11,28 +11,40 @@

-> 基于FastAPI实现的Frida-Rpc工具 +> 基于`FastAPI`实现的`Frida-RPC`工具,只需开发好相对应app的`Frida-Js`脚本,即可自动生成相应的基于`FastAPI`的`Frida-RPC`工具 ### 🏠 [Homepage](暂无) ### ✨ [Demo](暂无) +Do By You Self! + +## 实现原理 + +`Python`执行`PyexecJs`通过`Js的AST树`结构获取`Frida-Js`脚本中`rpc.exports`的方法以及对应方法的参数个数,根据方法名和参数个数通过`types.FunctionDef`从`Python AST字节码`来动态生成新的`Function对象`,并且结合`pydantic`的`create_model`自动生成的参数模型注册到`FastAPI的路由系统`中,实现`Frida-RPC`的功能。 + ## Install ```sh -暂无 +1. git clone git@github.com:lateautumn4lin/arida.git + +2. conda create -n arida python==3.8 + +3. conda install --yes --file requirements.txt ``` ## Usage ```sh -暂无 +1. uvicorn main:app --reload + +2. watch 127.0.0.1:8000/docs ``` ## Run tests ```sh -暂无 +uvicorn main:app --reload ``` ## 参考资料 diff --git a/config.py b/config.py index 6202a60..a713f2b 100644 --- a/config.py +++ b/config.py @@ -9,10 +9,5 @@ @License : (C)Copyright 2020 @Desc : None ''' - -apps_config = [ - { - "package_name": "com.kuaiduizuoye.scan", - "script_name": "kuaiduizuoye.js" - } -] +from pathlib import Path +PRASE_PATH = Path(__file__).absolute().parent / "parse.js" diff --git a/main.py b/main.py index d7a34cc..3027509 100644 --- a/main.py +++ b/main.py @@ -9,66 +9,32 @@ @License : (C)Copyright 2020 @Desc : None ''' +from pathlib import Path from pydantic import BaseModel, Field, create_model -from typing import NewType -from ast import * -import types from fastapi import APIRouter -import subprocess import time import frida -from frida import ServerNotRunningError +from starlette.applications import Starlette from fastapi import FastAPI from loguru import logger -import execjs +from utils.generate_function import generate_function +from utils import ( + name_transform, + detect_frida_state, + start_frida_server, + get_app_info +) +from config import * -app = FastAPI() -_parse_path = "parse.js" +# 配置相关函数的参数类型列表 +function_params_hints = { + "encryptData": [0, 0, 0, 0, "", 0, 0, 0, 0, 0, 0] +} +# app相关信息 +_frida_js_path = Path(__file__).absolute().parent/"apps/kuaiduizuoye.js" _package_name = "com.kuaiduizuoye.scan" -_frida_js_path = f"apps/kuaiduizuoye.js" - - -def get_app_info(): - with open(_parse_path, encoding="utf-8") as f, open(_frida_js_path, encoding="utf-8") as f1: - ctx = execjs.compile(f.read()) - result = ctx.call( - 'parse', f1.read() - ) - return result - - -def on_message(message, data): - if message['type'] == 'send': - logger.info("[**] {0}".format(message['payload'])) - else: - logger.info(f"log:{message}") - - -def detect_frida_state(): - logger.info("Begin Detect Frida State") - shell = 'adb shell su -c "ps -ef|grep frida"' - p = subprocess.Popen( - shell, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT - ) - stdout, stderr = p.communicate() - stdout = stdout.decode('utf-8') - return ("frida-server" in stdout) - - -def start_frida_server(): - logger.info("Begin Start Frida Server") - shell = 'adb shell su -c "./data/local/tmp/frida-server &"' - subprocess.Popen( - shell, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT - ) - +# frida注入 if not detect_frida_state(): # 启动frida-server,增加延迟防止附加失败 start_frida_server() @@ -77,111 +43,29 @@ session = frida.get_usb_device().attach(_package_name) script = session.create_script( open(_frida_js_path, encoding="utf-8").read() ) -script.on('message', on_message) -logger.info('[*] Start attach') script.load() -Url = create_model("User", **{"origin_url": "asdasd"}) -function_ast = FunctionDef( - lineno=2, - col_offset=0, - name='generate_url', - args=arguments( - args=[ - arg( - lineno=2, - col_offset=17, - arg='item', - annotation=Name(lineno=2, col_offset=22, - id='Url', ctx=Load()), - ), - ], - posonlyargs=[], - vararg=None, - kwonlyargs=[], - kw_defaults=[], - kwarg=None, - defaults=[], - ), - 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=[Name(lineno=3, col_offset=10, - id='Url', ctx=Load())], - keywords=[], - ), - ), - Assign( - lineno=4, - col_offset=4, - targets=[Name(lineno=4, col_offset=4, - id='res', ctx=Store())], - value=Call( - lineno=4, - col_offset=10, - func=Attribute( - lineno=4, - col_offset=10, - value=Attribute( - lineno=4, - col_offset=10, - value=Name(lineno=4, col_offset=10, - id='script', ctx=Load()), - attr='exports', - ctx=Load(), - ), - attr='generate_url', - ctx=Load(), - ), - args=[ - Attribute( - lineno=4, - col_offset=38, - value=Name(lineno=4, col_offset=38, - id='item', ctx=Load()), - attr='origin_url', - ctx=Load(), - ), - ], - keywords=[], - ), - ), - Return( - lineno=5, - col_offset=4, - value=Name(lineno=5, col_offset=11, id='res', ctx=Load()), - ), - ], - decorator_list=[], - returns=None, -) -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] -test = types.FunctionType( - function_code, - { - "script": script, - "Url": Url, - "print": print - } -) -test.__annotations__ = {"item": Url} - -router = APIRouter() -for k, v in get_app_info().items(): - router.add_api_route("/generate_url", test, methods=["POST"]) - break -app.include_router(router) -# @app.post('/generate_url') -# def generate_url(item: c): -# res = script.exports.generate_url(item.origin_url) -# return res +def init_app() -> Starlette: + app = FastAPI() + # 每个app创建特有路由 + router = APIRouter() + for api_name, params in get_app_info(parse_path=PRASE_PATH, frida_js_path=_frida_js_path).items(): + params_dict = dict(zip(params, function_params_hints[api_name])) if ( + api_name in function_params_hints) else dict.fromkeys(params, "bb") + model_name = f"{api_name}Model" + Model = create_model(model_name, **params_dict) + new_api_name = name_transform(api_name) + func = generate_function( + new_api_name, + script, + model_name, + Model + ) + router.add_api_route(f"/{new_api_name}", func, methods=["POST"]) + # 全局添加各app路由类 + app.include_router(router) + return app + + +app = init_app() diff --git a/parse.js b/parse.js index d6016ee..3231029 100644 --- a/parse.js +++ b/parse.js @@ -19,7 +19,11 @@ function parse(code) { // 处理function,获取函数名以及对应参数 FunctionDeclaration(path) { let params = path.node; - functions.set(params.id.name, params.params.length) + 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, { diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d5c66ec --- /dev/null +++ b/requirements.txt @@ -0,0 +1,31 @@ +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: win-64 +autopep8=1.5.4=py_0 +ca-certificates=2020.7.22=0 +certifi=2020.6.20=py38_0 +click=7.1.2=pypi_0 +colorama=0.4.3=pypi_0 +fastapi=0.61.1=pypi_0 +frida=12.11.17=pypi_0 +h11=0.9.0=pypi_0 +loguru=0.5.3=pypi_0 +openssl=1.1.1g=he774522_1 +pip=20.2.2=py38_0 +pycodestyle=2.6.0=py_0 +pydantic=1.6.1=pypi_0 +pyexecjs=1.5.1=pypi_0 +python=3.8.0=hff0d562_2 +setuptools=49.6.0=py38_0 +six=1.15.0=pypi_0 +sqlite=3.33.0=h2a8f88b_0 +starlette=0.13.6=pypi_0 +toml=0.10.1=py_0 +uvicorn=0.11.8=pypi_0 +vc=14.1=h0510ff6_4 +vs2015_runtime=14.16.27012=hf0eaf9b_3 +websockets=8.1=pypi_0 +wheel=0.35.1=py_0 +win32-setctime=1.0.2=pypi_0 +wincertstore=0.2=py38_0 +zlib=1.2.11=h62dcd97_4 diff --git a/source/fastapi_docs.png b/source/fastapi_docs.png new file mode 100644 index 0000000..ec7145d Binary files /dev/null and b/source/fastapi_docs.png differ diff --git a/source/post_body_hints.png b/source/post_body_hints.png new file mode 100644 index 0000000..12c5d77 Binary files /dev/null and b/source/post_body_hints.png differ diff --git a/source/test.png b/source/test.png new file mode 100644 index 0000000..9841899 Binary files /dev/null and b/source/test.png differ diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..7f9777b --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +''' +@File : __init__.py +@Time : 2020/09/24 11:11:38 +@Author : Lateautumn4lin +@Version : 1.0 +@Contact : Lateautumn4lin +@License : (C)Copyright 2020 +@Desc : None +''' +from typing import ( + Dict, + List, + NoReturn +) +from pathlib import PosixPath +import subprocess +from loguru import logger +import execjs + + +def get_app_info(parse_path: PosixPath, frida_js_path: PosixPath) -> Dict[str, List[str]]: + with open(parse_path, encoding="utf-8") as f, open(frida_js_path, encoding="utf-8") as f1: + ctx = execjs.compile(f.read()) + result = ctx.call( + 'parse', f1.read() + ) + return result + + +def name_transform(name: str) -> str: + split_idx = 0 + for idx, s in enumerate(name): + if s.isupper(): + split_idx = idx + return f"{(name[:split_idx]).lower()}_{(name[split_idx:]).lower()}" + + +def detect_frida_state() -> str: + logger.info("Begin Detect Frida State") + shell = 'adb shell su -c "ps -ef|grep frida"' + p = subprocess.Popen( + shell, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + stdout, stderr = p.communicate() + stdout = stdout.decode('utf-8') + return ("frida-server" in stdout) + + +def start_frida_server() -> NoReturn: + logger.info("Begin Start Frida Server") + shell = 'adb shell su -c "./data/local/tmp/frida-server &"' + subprocess.Popen( + shell, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) diff --git a/utils/generate_function.py b/utils/generate_function.py new file mode 100644 index 0000000..0b248af --- /dev/null +++ b/utils/generate_function.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +''' +@File : generate_function.py +@Time : 2020/09/24 10:23:37 +@Author : Lateautumn4lin +@Version : 1.0 +@Contact : Lateautumn4lin +@License : (C)Copyright 2020 +@Desc : None +''' +import types +from ast import * +from typing import Callable +from pydantic import BaseModel +from frida.core import Script + + +def generate_function( + func_name: str, + script: Script, + model_name: str, + model: BaseModel +) -> Callable: + 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, + ) + 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] + function = types.FunctionType( + function_code, + { + "script": script, + model_name: model, + "print": print, + "dict": dict + } + ) + function.__annotations__ = {"item": model} + return function