From 3abd9f6a7e8c4325bcacee93711748399877325c Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Wed, 7 Aug 2024 12:40:53 -0700 Subject: [PATCH 1/2] Frida template multi-function listening support & full class/function name display --- blutter/src/DartDumper.cpp | 2 +- blutter/src/DartDumper.h | 1 + blutter/src/FridaWriter.cpp | 15 ++++++ scripts/frida.template.js | 95 +++++++++++++++++++++++++++++++++---- 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/blutter/src/DartDumper.cpp b/blutter/src/DartDumper.cpp index dd06afa..1e3845f 100644 --- a/blutter/src/DartDumper.cpp +++ b/blutter/src/DartDumper.cpp @@ -25,7 +25,7 @@ static std::unordered_map OP_MAP { { "&", "LAnd" }, { "|", "LOr" }, { "^", "xor" }, { "~", "not" }, {">>", "shar"}, {"<<", "shal"}, {">>", "shr"} }; -static std::string getFunctionName4Ida(const DartFunction& dartFn, const std::string& cls_prefix) +std::string DartDumper::getFunctionName4Ida(const DartFunction& dartFn, const std::string& cls_prefix) { auto fnName = dartFn.Name(); if (dartFn.IsClosure() && fnName == "") { diff --git a/blutter/src/DartDumper.h b/blutter/src/DartDumper.h index e1d2107..97ac960 100644 --- a/blutter/src/DartDumper.h +++ b/blutter/src/DartDumper.h @@ -17,6 +17,7 @@ class DartDumper void DumpObjects(const char* filename); std::string ObjectToString(dart::Object& obj, bool simpleForm = false, bool nestedObj = false, int depth = 0); + static std::string getFunctionName4Ida(const DartFunction& dartFn, const std::string& cls_prefix); private: std::string getPoolObjectDescription(intptr_t offset, bool simpleForm = true); diff --git a/blutter/src/FridaWriter.cpp b/blutter/src/FridaWriter.cpp index 788df62..cf182c0 100644 --- a/blutter/src/FridaWriter.cpp +++ b/blutter/src/FridaWriter.cpp @@ -3,6 +3,7 @@ #include #include #include "Util.h" +#include "DartDumper.h" #ifndef FRIDA_TEMPLATE_DIR #define FRIDA_TEMPLATE_DIR "scripts" @@ -149,4 +150,18 @@ void FridaWriter::Create(const char* filename) } } of << "];\n"; + of << "function GetFuncPtrMap(){\nreturn new Map([\n"; + for (auto lib : app.libs) { + std::string lib_prefix = lib->GetName(); + for (auto cls : lib->classes) { + std::string cls_prefix = cls->Name(); + for (auto dartFn : cls->Functions()) { + const auto ep = dartFn->Address(); + auto name = DartDumper::getFunctionName4Ida(*dartFn, cls_prefix); + of << "[" << std::format("{:#x}, \"{}_{}::{}_{:x}\"", ep, lib_prefix, cls_prefix, name.c_str(), ep) << "],\n"; + } + } + } + of << "]);\n}\n"; + } diff --git a/scripts/frida.template.js b/scripts/frida.template.js index 613d122..4c7d601 100644 --- a/scripts/frida.template.js +++ b/scripts/frida.template.js @@ -1,20 +1,95 @@ +function GetOptions(){ + const opts = new BlutterOpts(); + opts.IgnoreByClassFuncRegex(/(anim|battery|anon_|build|widget|Dependencies|Observer|Render)/i); + //opts.SpyByClassFuncRegex(/interestingclass.+::func_prefix.+/,4); + // opts.SpyByFunctionAddy(0x2fd950, "ImportantFunc",10); + return opts; +} const ShowNullField = false; const MaxDepth = 5; +const WriteFuncNameBeforeTrace = true; //useful if it may crash to know what caused it var libapp = null; -function onLibappLoaded() { - xxx("remove this line and correct the hook value"); - const fn_addr = 0xdeadbeef; - Interceptor.attach(libapp.add(fn_addr), { - onEnter: function () { - init(this.context); - let objPtr = getArg(this.context, 0); - const [tptr, cls, values] = getTaggedObjectValue(objPtr); - console.log(`${cls.name}@${tptr.toString().slice(2)} =`, JSON.stringify(values, null, 2)); +class BlutterOpts { + + ignoreRegexes = []; + spyRegexes = []; + SpyFunctions = []; + spyCount = 0; + + //similar to SpyByClassFuncRegex except any regex matches here will explicitly not be spied on. Good to be able to cast a wide net and then exclude things you don't care about or that cause crashes. + IgnoreByClassFuncRegex(regex){ + this.ignoreRegexes.push(new SpyRegex(regex)); + } + // Spys on a function by matching the provided js regex against the ida formatted class/function name that is in the format similar to: my_lib$bean$pathwith_bean_ClassINfoBean::get_someval_311eb0_check + SpyByClassFuncRegex(regex,maxDepth=MaxDepth){ + this.spyRegexes.push(new SpyRegex(regex,maxDepth)); + } + // takes an address in the form of 0x2fd950 for example, you can find a list of functions and their addresses in the generated ida_script/addNames.py file, you can optionally pass a display name to show when entered + SpyByFunctionAddy(address, name=""){ + this.SpyFunctions.push(new SpyFunc(address,name)); + } + SetSpys(){ + if (this.spyRegexes.length > 0){ + const FuncPtrToName = GetFuncPtrMap(); + for( const [fnptr, fnname] of FuncPtrToName.entries()) { + let wasMatch=false; + let maxDepth=-1; + for (const spy of this.spyRegexes) { + if (spy.regex.test(fnname)){ + wasMatch=true; + if (spy.maxDepth > maxDepth) + maxDepth = spy.maxDepth; + } + } + if (wasMatch){ + for (const spy of this.ignoreRegexes) + if (spy.regex.test(fnname)) + wasMatch=false; + } + if (wasMatch) + this.SpyByFunctionAddy(fnptr,fnname,maxDepth); + + } } - }); + for (const spy of this.SpyFunctions) + this._SetSpy(spy); + } + _SetSpy(spy){ + const fullName = spy.name; + const maxDepth = spy.maxDepth; + Interceptor.attach(libapp.add(spy.address), { + onEnter: function () { + if (WriteFuncNameBeforeTrace && fullName) + console.log(`${fullName}..`); + init(this.context); + let objPtr = getArg(this.context, 0); + const [tptr, cls, values] = getTaggedObjectValue(objPtr,maxDepth); + console.log(`${fullName} ${cls.name}@${tptr.toString().slice(2)} =`, JSON.stringify(values, null, 2)); + } + }); + console.log(`Blutter Intercept #${++this.spyCount}: ${fullName} (${spy.address})`); + } +} +class SpyRegex { + constructor(regex, maxDepth = MaxDepth){ + this.maxDepth = maxDepth; + this.regex = regex; + } +} +class SpyFunc { + constructor(address,name="",maxDepth=MaxDepth){ + this.address = address; + this.name = name; + this.maxDepth = maxDepth; + } } +function onLibappLoaded() { + const opts = GetOptions(); + opts.SetSpys(); + +} function tryLoadLibapp() { libapp = Module.findBaseAddress('libapp.so'); if (libapp === null) From a0fc4d485d713055b8bc0036af60de73f61a4ce2 Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Wed, 7 Aug 2024 12:41:14 -0700 Subject: [PATCH 2/2] Readme improvements / more detailed frida guide --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ce61747..5e2c5b4 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,38 @@ # B(l)utter Flutter Mobile Application Reverse Engineering Tool by Compiling Dart AOT Runtime +It provides data on flutter including: +- pseudo assembly code broken out by class/function with named calls +- ida script to add class/function and member field meta data +- frida script to allow live tracing function calls on unmodified binaries + + Currently the application supports only Android libapp.so (arm64 only). Also the application is currently work only against recent Dart versions. For high priority missing features, see [TODO](#todo) + + +- [Environment Setup](#environment-setup) + - [Debian Unstable \(gcc 13\)](#debian-unstable-gcc-13) + - [Windows](#windows) + - [macOS Ventura and Sonoma \(clang 16\)](#macos-ventura-and-sonoma-clang-16) +- [Usage](#usage) +- [Update](#update) +- [Output files](#output-files) +- [Directories](#directories) +- [Frida Trace Script](#frida-trace-script) +- [Troubleshooting](#troubleshooting) +- [Development](#development) + - [Generating Visual Studio Solution](#generating-visual-studio-solution) + - [TODO](#todo) + + + ## Environment Setup -This application uses C++20 Formatting library. It requires very recent C++ compiler such as g++>=13, Clang>=16. +This application uses C++20 Formatting library. It requires very recent C++ compiler such as g++>=13, Clang>=16 or MSVC 17. I recommend using Linux OS (only tested on Deiban sid/trixie) because it is easy to setup. @@ -22,10 +46,11 @@ apt install python3-pyelftools python3-requests git cmake ninja-build \ ### Windows - Install git and python 3 - Install latest Visual Studio with "Desktop development with C++" and "C++ CMake tools" -- Install required libraries (libcapstone and libicu4c) +- Run init script to install required libraries (libcapstone and libicu4c): ``` python scripts\init_env_win.py ``` +- Install python elf tools `pip install pyelftools` - Start "x64 Native Tools Command Prompt" ### macOS Ventura and Sonoma (clang 16) @@ -67,14 +92,48 @@ python3 blutter.py path/to/app/lib/arm64-v8a out_dir --rebuild - **packages** contains the static libraries of Dart Runtime - **scripts** contains python scripts for getting/building Dart +## Frida Trace Script +The Frida trace script (found as `output_dir/blutter_frida.js`) allows you to spy on function calls to flutter functions and gives you information about the args/vals passed to it. It tries to recursively display the entire parameter structure and vals but it makes some educated guesses that can be wrong. This can result in crashes. It will print the function it is about to try and trace when it goes to trace so if it does crash try to exclude that function from being spied. + +To use this you need to have frida working on the device either the frida server or the frida gadget injected into the package. Instructions for that are beyond the scope of this project, essentially normal frida-trace / frida / objection commands should work. + +Then to use this edit the BlutterOpts initialization to include the options you want. There are 3 primary functions to call to add traces (they can be called as many times as you want): + +- `opts.IgnoreByClassFuncRegex(regex : RegExp)` +- `opts.SpyByClassFuncRegex(regex : RegExp, maxDepth : number = MaxDepth)` +- `opts.SpyByFunctionAddy(address : number, displayName : string = "", maxDepth : number = MaxDepth)` + +An optional config would then look like: +```javascript +function GetOptions(){ + opts.IgnoreByClassFuncRegex(/(anim|battery|anon_|build|widget|Dependencies|Observer|Render)/i); + opts.SpyByClassFuncRegex(/interestingclass.+::func_prefix.+/i,4); + opts.SpyByClassFuncRegex(/moreInterestingClassByCrashProne.+::myFunc.+/,0); + opts.SpyByFunctionAddy(0x2fd950, "ImportantFunc",10); + return opts; +} +``` + +NOTE: Any changes to the script are overwritten when you regenerate/update the output + +Finally launch it like any other frida script ie: + + `frida -U -l blutter_frida.py "AppToSpy"` + +To find function name look at the output_dir/ida_script/addNames.py file it lists all the functions at the top. + +## Troubleshooting +If you get errors during compiling / initial run make sure you pay attention to the Environment setup in detail, have the deps installed, and for example on Windows are running the script from the Developer Powershell VS instance otherwise you won't have the required items in your path. + +## Development -## Generating Visual Studio Solution for Development +### Generating Visual Studio Solution I use Visual Studio to delevlop Blutter on Windows. ```--vs-sln``` options can be used to generate a Visual Studio solution. ``` python blutter.py path\to\lib\arm64-v8a build\vs --vs-sln ``` -## TODO +### TODO - More code analysis - Function arguments and return type - Some psuedo code for code pattern