This repository has been archived on 2024-09-30. You can view files and clone it, but cannot push or open issues/pull-requests.
hook-frida/venv/Lib/site-packages/frida_tools/creator.py

266 lines
6.4 KiB
Python

import argparse
import codecs
import os
import platform
from typing import Dict, List, Tuple
import frida
from frida_tools.application import ConsoleApplication
def main() -> None:
app = CreatorApplication()
app.run()
class CreatorApplication(ConsoleApplication):
def _usage(self) -> str:
return "%(prog)s [options] -t agent|cmodule"
def _add_options(self, parser: argparse.ArgumentParser) -> None:
default_project_name = os.path.basename(os.getcwd())
parser.add_argument(
"-n", "--project-name", help="project name", dest="project_name", default=default_project_name
)
parser.add_argument("-o", "--output-directory", help="output directory", dest="outdir", default=".")
parser.add_argument("-t", "--template", help="template file: cmodule|agent", dest="template", default=None)
def _initialize(self, parser: argparse.ArgumentParser, options: argparse.Namespace, args: List[str]) -> None:
parsed_args = parser.parse_args()
if not parsed_args.template:
parser.error("template must be specified")
impl = getattr(self, "_generate_" + parsed_args.template, None)
if impl is None:
parser.error("unknown template type")
self._generate = impl
self._project_name = options.project_name
self._outdir = options.outdir
def _needs_device(self) -> bool:
return False
def _start(self) -> None:
(assets, message) = self._generate()
outdir = self._outdir
for name, data in assets.items():
asset_path = os.path.join(outdir, name)
asset_dir = os.path.dirname(asset_path)
try:
os.makedirs(asset_dir)
except:
pass
with codecs.open(asset_path, "wb", "utf-8") as f:
f.write(data)
self._print("Created", asset_path)
self._print("\n" + message)
self._exit(0)
def _generate_agent(self) -> Tuple[Dict[str, str], str]:
assets = {}
assets[
"package.json"
] = f"""{{
"name": "{self._project_name}-agent",
"version": "1.0.0",
"description": "Frida agent written in TypeScript",
"private": true,
"main": "agent/index.ts",
"scripts": {{
"prepare": "npm run build",
"build": "frida-compile agent/index.ts -o _agent.js -c",
"watch": "frida-compile agent/index.ts -o _agent.js -w"
}},
"devDependencies": {{
"@types/frida-gum": "^18.3.1",
"@types/node": "^18.14.0",
"frida-compile": "^16.1.8"
}}
}}
"""
assets[
"tsconfig.json"
] = """\
{
"compilerOptions": {
"target": "es2020",
"lib": ["es2020"],
"allowJs": true,
"noEmit": true,
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node16"
}
}
"""
assets[
"agent/index.ts"
] = """\
import { log } from "./logger.js";
const header = Memory.alloc(16);
header
.writeU32(0xdeadbeef).add(4)
.writeU32(0xd00ff00d).add(4)
.writeU64(uint64("0x1122334455667788"));
log(hexdump(header.readByteArray(16) as ArrayBuffer, { ansi: true }));
Process.getModuleByName("libSystem.B.dylib")
.enumerateExports()
.slice(0, 16)
.forEach((exp, index) => {
log(`export ${index}: ${exp.name}`);
});
Interceptor.attach(Module.getExportByName(null, "open"), {
onEnter(args) {
const path = args[0].readUtf8String();
log(`open() path="${path}"`);
}
});
"""
assets[
"agent/logger.ts"
] = """\
export function log(message: string): void {
console.log(message);
}
"""
assets[".gitignore"] = "/node_modules/\n"
message = """\
Run `npm install` to bootstrap, then:
- Keep one terminal running: npm run watch
- Inject agent using the REPL: frida Calculator -l _agent.js
- Edit agent/*.ts - REPL will live-reload on save
Tip: Use an editor like Visual Studio Code for code completion, inline docs,
instant type-checking feedback, refactoring tools, etc.
"""
return (assets, message)
def _generate_cmodule(self) -> Tuple[Dict[str, str], str]:
assets = {}
assets[
"meson.build"
] = f"""\
project('{self._project_name}', 'c',
default_options: 'buildtype=release',
)
shared_module('{self._project_name}', '{self._project_name}.c',
name_prefix: '',
include_directories: include_directories('include'),
)
"""
assets[
self._project_name + ".c"
] = """\
#include <gum/guminterceptor.h>
static void frida_log (const char * format, ...);
extern void _frida_log (const gchar * message);
void
init (void)
{
frida_log ("init()");
}
void
finalize (void)
{
frida_log ("finalize()");
}
void
on_enter (GumInvocationContext * ic)
{
gpointer arg0;
arg0 = gum_invocation_context_get_nth_argument (ic, 0);
frida_log ("on_enter() arg0=%p", arg0);
}
void
on_leave (GumInvocationContext * ic)
{
gpointer retval;
retval = gum_invocation_context_get_return_value (ic);
frida_log ("on_leave() retval=%p", retval);
}
static void
frida_log (const char * format,
...)
{
gchar * message;
va_list args;
va_start (args, format);
message = g_strdup_vprintf (format, args);
va_end (args);
_frida_log (message);
g_free (message);
}
"""
assets[".gitignore"] = "/build/\n"
session = frida.attach(0)
script = session.create_script("rpc.exports.getBuiltins = () => CModule.builtins;")
self._on_script_created(script)
script.load()
builtins = script.exports_sync.get_builtins()
script.unload()
session.detach()
for name, data in builtins["headers"].items():
assets["include/" + name] = data
system = platform.system()
if system == "Windows":
module_extension = "dll"
elif system == "Darwin":
module_extension = "dylib"
else:
module_extension = "so"
cmodule_path = os.path.join(self._outdir, "build", self._project_name + "." + module_extension)
message = f"""\
Run `meson build && ninja -C build` to build, then:
- Inject CModule using the REPL: frida Calculator -C {cmodule_path}
- Edit *.c, and build incrementally through `ninja -C build`
- REPL will live-reload whenever {cmodule_path} changes on disk
"""
return (assets, message)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass