Всем привет, тема этого выпуска开源框架的分享
, поэтому сегодня я поделюсь с вами инструментом, с которым я недавно соприкоснулся.
Эта статья была впервые опубликована в "Security Guest - Thoughtful Security New Media" и первоначально была опубликована lateautumn4lin от моего имени. Ссылка на исходный текст:Woohoo.Security Section.com/post/ID/218…
На этот раз представленFastAPI
осуществленныйFrida-RPC
инструмент-Arida
(адрес гитхаба), я независимый разработчик этого инструмента, я хотел бы представить вам этот инструмент开发构想
И его使用方式
.
1 идея развития
Инструменты часто являются производными от повседневной работы.Когда в работе есть связь «повторение, повторение, повторение», рождается инструмент, который может сэкономить время и повысить эффективность работы. моя повседневная работа включает в себя逆向分析APP协议
, инструменты, используемые в настоящее время, как правило,Frida
, иногда для проверки результатов анализаHook
способ вызвать метод и передать自带RPC
Метод выставляет интерфейс, потому что количество APP для ежедневного анализа относительно велико, поэтому я столкнулся с рядом проблем, которые побудили меня разработать для себя набор инструментов для повышения эффективности работы.
1.1 Проблемы, возникающие на работе
1.1.1 Несколько приложений и несколько файлов Frida-J
Просто начал часто использовать его на работеFrida
Студенты, изучающие инструменты, обязательно обнаружат, что им нужно писать новую версию каждый раз, когда они проводят обратный анализ приложения.JavaScript
Файлы, со временем, как сохранить столько файлов? Как запустить соответствующее приложение для разных приложенийJavaScript
документ? Как извлечь повторяющийся код каждого файла? это все оFrida-Js
проблемы с управлением файлами.
1.1.2 Написанный метод Js должен создать соответствующий метод API
Как понять этот вопрос? Все знаютFrida JavaScript Function
Метод воздействия такой
rpc.exports = {
decryptData: decrypt_data,
generateUrl: generate_url,
encryptData: encrypt_data
}
использоватьrpc.exports
соответствующийMap
указать выставленный метод и соответствующую функцию, но в этом случае он используется толькоJavaScript
изexports
Ключевое слово раскрывает метод, чтобы его могли вызывать другие скрипты. Как его можно превратить в Http-интерфейс? можно использовать напрямуюNodeJs
изHttp框架
,НапримерExpress
, но мы обычно используем наиболееPython
,НапримерFlask
,Django
Такая структура, люди, которые использовали структуру, знают, что нам нужно написать метод для каждого API, например этот
@app.route("/test")
def test():
return "hello"
В сочетании с этим методом мы называемFrida-RPC
Как оно есть
@app.route('/sign')
def sign_test():
sign = request.args['sign']
res = script.exports.sign(sign)
return res
Нам нужно нацелиться на каждыйJavaScript
способ написать соответствующийPython
метод и параметры, которые будут вызываться напрямую, проблема в том, что чем больше методов мы накопим, тем больше будет наш общий проект, но многие части кода являются повторяющимся и простым вызывающим кодом.
1.1.3 Вопросы сотрудничества
Это также очень неприятная проблема.После того, как вы сделали все вышеперечисленные операции и развернули сервис, другие люди хотят использовать ваш API.Можете ли вы предоставить полныйAPI文档
? Вам все еще нужен интерфейс для написания соответствующей документации?
1.2 Какие болевые точки должен решить инструмент
В ответ на вышеуказанные проблемы нам нужен эффективный инструмент, который может помочь нам скрыть детали этой работы, чтобы мы могли больше сосредоточиться на обратном анализе процесса вызова в приложении. Поэтому нам нужен инструмент, который может выполнять следующие функции:
-
управлять
JavaScript
файл, сAPP-文件
отношения отображения -
Автоматически ориентироваться на существующие
JavaScript
метод создания соответствующегоAPI
метод -
Создано автоматически
Open API
Документация
1.3 Инструменты Ариды
Когда возникла идея «захотеть разработать инструмент», она взлетела в спешке.На создание простого инструмента, о котором упоминается в этот раз, ушло около двух часов.Arida
Этот инструмент, название происходит отFrida
иAPI
Эти два слова, просто соединенные вместе, выполняют те же функции, что и упомянутые выше.
1.3.1 Конкретный рабочий процесс
Рабочий процесс выглядит следующим образом:
В основном делится на четыре этапа:
-
Шаг 1: Используйте
JavaScript AST树
получитьexports
изMap
Имя функции в и количество параметров соответствующей функции для облегчения последующего построенияPydantic
изModel
. -
Шаг 2: Генерация
Pydantic
Динамические модели облегчаютFastAPI
изAPI Doc
поколение. -
Шаг 3: Объедините
模型
и получилJavaScript的方法名和参数个数
генерировать новыеPython方法
. -
Шаг 4: Зарегистрируйте маршрут, соответствующий каждому приложению, и, наконец, зарегистрируйте его в глобальном маршруте.
2 Интерпретация исходного кода
Я говорил об этом раньшеArida
Весь рабочий процесс, следующее в основном объясняет реализацию каждой части.
2.1 Экспорт информации о функциях скрипта Frida JavaScript
ОбщееFrida-Js
Скрипт такой
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,
}
Информация, которую нам необходимо получить,导出函数名
и соответствующая функция экспорта内部函数的参数个数
, рассматривал использование обычного метода для этого, но обычный метод громоздкий, поэтому изJavaScript AST
Начиная с дерева, мы можем лучше анализировать нужную нам информацию.
Разобранный скрипт выглядит следующим образом:
const babel = require('@babel/core')
var exports = new Map();
var functions = new Map();
var result = new Map();
function parse(code) {
let visitor = {
// 处理exports节点,获取导出函数对应表
ExpressionStatement(path) {
let params = path.node.expression.right;
try {
params = params.properties
for (let i = 0; i < params.length; i++) {
exports.set(params[i].value.name, params[i].key.name)
}
} catch {
}
},
// 处理function,获取函数名以及对应参数
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);
}
В основном анализируетсяfunction
иexports
два узла и, наконец, возвратMap
2.2 Динамическая генерация модели интерфейса FastAPI API
получил предыдущий шагJavaScript
изMap
данные, наверное
{
"getSign":3
}
Затем вам нужно использовать эту информацию для динамического создания модели интерфейса.Причина создания модели интерфейса заключается в том, что вFastAPI
В этих рамкахPost
Интерфейс используетPydantic
изBaseModel
,использоватьBaseModel
Причина еще и в том, что с одной стороны нужно сгенерировать документ внешнего интерфейса, а с другой стороны требуется проверка типа параметров.Динамически генерируемый код выглядит следующим образом:
from pydantic import create_model
params_dict = {"a":""}
Model = create_model(model_name, **params_dict)
ВводитьPydantic
изcreate_model
, параметр является типом каждого параметра метода, например.String
тип напрямую""
,Даint
тип напрямую0
.
2.3 Динамическое создание методов Python на основе Python AST
На последнем шаге с моделью иJavaScript
изMap
Данные, которые мы можем динамически генерироватьPython
метод, потому что общие методы API одинаковы, а именно:
def sign_test():
sign = request.args['sign']
res = script.exports.sign(sign)
return res
Нам нужно динамически генерировать вышеуказанный формат, мы можем принять два решения.
- Первый: метод закрытия - функция возвращает функцию, например
def outer():
def inner():
return "hello"
return inner
- Второе: использовать
Python AST
генерация дереваPython字节码
,использоватьtypes.FunctionDef
Для генерации код выглядит следующим образом:
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,
)
Сначала динамически сгенерируйте соответствующий методPython AST树
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]
генерировать соответствующиеPython AST树
из字节码
function = types.FunctionType(
function_code,
{
"script": script,
model_name: model,
"print": print,
"dict": dict
}
)
function.__annotations__ = {"item": model}
использовать字节码
Создайте новый метод, потому что аннотации исходного байт-кода будут потеряны при создании нового метода, то есть__annotations__
Поэтому это свойство необходимо добавить вручную после создания нового метода.
3 Как использовать
Давайте поговорим оArida
Для конкретного использования проект включает два простых примера, вApps
Ниже каталога информация о конфигурации находится вconfig.py
в файле.
3.1 Создайте новый проект в два этапа
Как создать новый проект? Для этого требуется всего два шага, следуйте шагам, показанным ниже:
- Шаг 1: Добавьте информацию о конфигурации, адрес файла
config.py
INJECTION_APPS = [
{
"name": "我的测试1",
"path": "yuxueyuan",
"package_name": "com.drcuiyutao.babyhealth"
},
{
"name": "我的测试2",
"path": "kuaiduizuoye",
"package_name": "com.kuaiduizuoye.scan"
}
]
Как показано в коде, он должен быть вINJECTION_APPS
Добавьте конкретную информацию о приложении в список, в основном в трех полях:
-
name
: влияет наFastAPI Doc
Имя группы в , не имеет особого практического значения и может быть понято как улучшение опыта людей, читающих интерфейсный документ. -
path
: В соответствии со значением этого поля вApps
Папка соответствует соответствующейJavaScript
документ. -
package_name
: имя пакета, который необходимо внедрить
После его добавления первый шаг завершен.
- Шаг 2: Разработайте соответствующее приложение
Frida-Js
сценарий
3.2 Открытость API подписи нескольких приложений корпоративного уровня
Поскольку в нашей повседневной работе мы часто одновременно проводим обратный анализ нескольких приложений, поэтому также важно проводить тестирование интерфейса API нескольких приложений одновременно.Arida
Поддерживает одновременный запуск нескольких приложений и ввод соответствующихJavaScript
Script, просто выполните описанные выше шаги, чтобы завершить разработку каждого проекта APP, и соответствующее APP будет автоматически внедрено при его запуске.В то же время при просмотре документа он также будет таким, как показано на рисунке:
4 примечания
4.1 Маркеры типа параметра
так какJavaScript
Тип параметра метода не может быть указан, в результате чтениеJavaScript
Метод может иметь только количество параметров и не может получить тип параметров, поэтому сгенерированныйPydantic模型
Когда вы можете только унифицировать тип как тип символа, если вы хотите настроить тип параметра, вы можетеmain.py
в файлеfunction_params_hints
настроить:
function_params_hints = {
"encryptData": [0, 0, 0, 0, "", 0, 0, 0, 0, 0, 0]
}
Таким образом создается подходящая модель параметров, так что, когда пользователь использует интерфейс, модель параметров выполняет преобразование типов в соответствии с типом, соответствующим параметрам в модели.