diff --git a/blutter.py b/blutter.py index 7c34223..7ef6f23 100644 --- a/blutter.py +++ b/blutter.py @@ -31,12 +31,14 @@ def __init__( rebuild_blutter: bool, create_vs_sln: bool, no_analysis: bool, + ida_fcn: bool, ): self.libapp_path = libapp_path self.dart_info = dart_info self.outdir = outdir self.rebuild_blutter = rebuild_blutter self.create_vs_sln = create_vs_sln + self.ida_fcn = ida_fcn vers = dart_info.version.split(".", 2) if int(vers[0]) == 2 and int(vers[1]) < 15: @@ -51,6 +53,8 @@ def __init__( self.name_suffix += "_no-compressed-ptrs" if no_analysis: self.name_suffix += "_no-analysis" + if ida_fcn: + self.name_suffix += "_ida-fcn" # derive blutter executable filename self.blutter_name = f"blutter_{dart_info.lib_name}{self.name_suffix}" self.blutter_file = os.path.join(BIN_DIR, self.blutter_name) + ( @@ -90,7 +94,7 @@ def extract_libs_from_apk(apk_file: str, out_dir: str): return app_file, flutter_file -def find_compat_macro(dart_version: str, no_analysis: bool): +def find_compat_macro(dart_version: str, no_analysis: bool, ida_fcn: bool): macros = [] include_path = os.path.join(PKG_INC_DIR, f"dartvm{dart_version}") vm_path = os.path.join(include_path, "vm") @@ -139,11 +143,17 @@ def find_compat_macro(dart_version: str, no_analysis: bool): if no_analysis: macros.append("-DNO_CODE_ANALYSIS=1") - if dart_version >= "3.5.0": - # [vm] marking_stack_block_offset() changes in Dart Stable 3.5.0 - # https://github.com/worawit/blutter/issues/96#issue-2470674670 - macros.append("-DOLD_MARKING_STACK_BLOCK=1") - + if ida_fcn: + macros.append("-DIDA_FCN=1") + + d_v = float('.'.join(dart_version.split('.')[:2])) + if d_v >= float(3.5) : + with open(os.path.join(vm_path, "compiler", "runtime_api.h"), "rb") as f: + mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) + if not mm.find(b" old_marking_stack_block_offset") == -1: + # [vm] marking_stack_block_offset() changes since Dart Stable 3.5.0 + # https://github.com/worawit/blutter/issues/96#issue-2470674670 + macros.append("-DOLD_MARKING_STACK_BLOCK=1") return macros @@ -151,7 +161,7 @@ def cmake_blutter(input: BlutterInput): blutter_dir = os.path.join(SCRIPT_DIR, "blutter") builddir = os.path.join(BUILD_DIR, input.blutter_name) - macros = find_compat_macro(input.dart_info.version, input.no_analysis) + macros = find_compat_macro(input.dart_info.version, input.no_analysis, input.ida_fcn) my_env = None if platform.system() == "Darwin": @@ -222,7 +232,7 @@ def build_and_run(input: BlutterInput): # creating Visual Studio solution overrides building if input.create_vs_sln: - macros = find_compat_macro(dart_version, no_analysis) + macros = find_compat_macro(input.dart_info.version, input.no_analysis, input.ida_fcn) blutter_dir = os.path.join(SCRIPT_DIR, "blutter") dbg_output_path = os.path.abspath(os.path.join(input.outdir, "out")) dbg_cmd_args = f"-i {input.libapp_path} -o {dbg_output_path}" @@ -269,11 +279,12 @@ def main_no_flutter( rebuild_blutter: bool, create_vs_sln: bool, no_analysis: bool, + ida_fcn: bool, ): version, os_name, arch = dart_version.split("_") dart_info = DartLibInfo(version, os_name, arch) input = BlutterInput( - libapp_path, dart_info, outdir, rebuild_blutter, create_vs_sln, no_analysis + libapp_path, dart_info, outdir, rebuild_blutter, create_vs_sln, no_analysis, ida_fcn ) build_and_run(input) @@ -285,10 +296,11 @@ def main2( rebuild_blutter: bool, create_vs_sln: bool, no_analysis: bool, + ida_fcn: bool, ): dart_info = get_dart_lib_info(libapp_path, libflutter_path) input = BlutterInput( - libapp_path, dart_info, outdir, rebuild_blutter, create_vs_sln, no_analysis + libapp_path, dart_info, outdir, rebuild_blutter, create_vs_sln, no_analysis, ida_fcn ) build_and_run(input) @@ -299,6 +311,7 @@ def main( rebuild_blutter: bool, create_vs_sln: bool, no_analysis: bool, + ida_fcn: bool, ): if indir.endswith(".apk"): with tempfile.TemporaryDirectory() as tmp_dir: @@ -310,6 +323,7 @@ def main( rebuild_blutter, create_vs_sln, no_analysis, + ida_fcn, ) else: libapp_file, libflutter_file = find_lib_files(indir) @@ -321,6 +335,7 @@ def main( rebuild_blutter, create_vs_sln, no_analysis, + ida_fcn, ) @@ -386,13 +401,25 @@ def check_for_updates_and_pull(): "--dart-version", help='Run without libflutter (indir become libapp.so) by specify dart version such as "3.4.2_android_arm64"', ) + parser.add_argument( + "--nu", + action="store_false", + default=True, + help="Don't check for updates", + ) + parser.add_argument( + "--ida-fcn", + action="store_true", + default=False, + help="Generate IDA function names script, Doesn't Generates Thread and Object Pool structs comments", + ) args = parser.parse_args() - # Check for updates and pull them if necessary - check_for_updates_and_pull() + if args.nu: + check_for_updates_and_pull() if args.dart_version is None: - main(args.indir, args.outdir, args.rebuild, args.vs_sln, args.no_analysis) + main(args.indir, args.outdir, args.rebuild, args.vs_sln, args.no_analysis, args.ida_fcn) else: main_no_flutter( args.indir, @@ -401,4 +428,5 @@ def check_for_updates_and_pull(): args.rebuild, args.vs_sln, args.no_analysis, + args.ida_fcn, ) diff --git a/blutter/CMakeLists.txt b/blutter/CMakeLists.txt index a43fda6..a6b8a63 100644 --- a/blutter/CMakeLists.txt +++ b/blutter/CMakeLists.txt @@ -106,6 +106,9 @@ endif() if (OLD_MARKING_STACK_BLOCK) set(defines ${defines} OLD_MARKING_STACK_BLOCK) endif() +if (IDA_FCN) + set(defines ${defines} IDA_FCN) +endif() target_compile_definitions(${BINNAME} PRIVATE ${defines}) target_compile_options(${BINNAME} PRIVATE ${cc_opts}) diff --git a/blutter/src/DartDumper.cpp b/blutter/src/DartDumper.cpp index 7f4a3a5..d8034d2 100644 --- a/blutter/src/DartDumper.cpp +++ b/blutter/src/DartDumper.cpp @@ -132,51 +132,45 @@ void DartDumper::Dump4Radare2(std::filesystem::path outDir) std::filesystem::create_directory(outDir); std::ofstream of((outDir / "addNames.r2").string()); of << "# create flags for libraries, classes and methods\n"; - - // app base & heap base address values changes on every run i.e, setting flag names for them is of no use - // of << fmt::format("f app.base = {:#x}\n", app.base()); - // of << fmt::format("f app.heap_base = {:#x}\n", app.heap_base()); + of << "e emu.str=true\n"; + // app base & heap base address values changes on every run i.e, setting flag names for them is of no use + // but since right now r2 bases it to address 0 let's leave it as it is + // https://github.com/worawit/blutter/pull/104#discussion_r1769637361 + of << fmt::format("f app.base = {:#x}\n", app.base()); + of << fmt::format("f app.heap_base = {:#x}\n", app.heap_base()); bool show_library = true; bool show_class = true; for (auto lib : app.libs) { std::string lib_prefix = lib->GetName(); - - std::replace(lib_prefix.begin(), lib_prefix.end(), '$', '_'); - std::replace(lib_prefix.begin(), lib_prefix.end(), '&', '_'); - std::replace(lib_prefix.begin(), lib_prefix.end(), '-', '_'); - std::replace(lib_prefix.begin(), lib_prefix.end(), '+', '_'); + filterString(lib_prefix); for (auto cls : lib->classes) { std::string cls_prefix = cls->Name(); - std::replace(cls_prefix.begin(), cls_prefix.end(), '$', '_'); - std::replace(cls_prefix.begin(), cls_prefix.end(), '&', '_'); - std::replace(cls_prefix.begin(), cls_prefix.end(), '-', '_'); - std::replace(cls_prefix.begin(), cls_prefix.end(), '+', '_'); + filterString(cls_prefix); for (auto dartFn : cls->Functions()) { const auto ep = dartFn->Address(); - auto name = getFunctionName4Ida(*dartFn, cls_prefix); - std::replace(name.begin(), name.end(), '$', '_'); - std::replace(name.begin(), name.end(), '&', '_'); - std::replace(name.begin(), name.end(), '-', '_'); - std::replace(name.begin(), name.end(), '+', '_'); - std::replace(name.begin(), name.end(), '?', '_'); + std::string name = getFunctionName4Ida(*dartFn, cls_prefix); + filterString(name); if (show_library) { - of << fmt::format("CC Library({:#x}) = {} @ {}\n", lib->id, lib_prefix, ep); - of << fmt::format("f lib.{}={:#x} # {:#x}\n", lib_prefix, ep, lib->id); + of << fmt::format("'@{:#x}'CC Library({:#x}) = {}\n", ep, lib->id, lib->GetName()); + of << fmt::format("'@{:#x}'f lib.{}\n", ep, lib_prefix); show_library = false; } if (show_class) { - of << fmt::format("CC Class({:#x}) = {} @ {}\n", cls->Id(), cls_prefix, ep); - of << fmt::format("f class.{}.{}={:#x} # {:#x}\n", lib_prefix, cls_prefix, ep, cls->Id()); + of << fmt::format("'@{:#x}'CC Class({:#x}) = {}\n", ep, cls->Id(), cls->Name()); + of << fmt::format("'@{:#x}'f class.{}.{}\n", ep, lib_prefix, cls_prefix); show_class = false; } - of << fmt::format("f method.{}.{}.{}_{:x}={:#x}\n", lib_prefix, cls_prefix, name.c_str(), ep, ep); + of << fmt::format("'@{:#x}'f method.{}.{}.{}\n", ep, lib_prefix, cls_prefix, name); + of << fmt::format("'@{:#x}'ic+{}.{}\n", ep, cls_prefix, name); if (dartFn->HasMorphicCode()) { - of << fmt::format("f method.{}.{}.{}.miss={:#x}\n", lib_prefix, cls_prefix, name.c_str(), - dartFn->PayloadAddress()); - of << fmt::format("f method.{}.{}.{}.check={:#x}\n", lib_prefix, cls_prefix, name.c_str(), - dartFn->MonomorphicAddress()); + of << fmt::format("'@{:#x}'f method.{}.{}.{}.miss\n", + dartFn->PayloadAddress(), + lib_prefix, cls_prefix, name); + of << fmt::format("'@{:#x}'f method.{}.{}.{}.check\n", + dartFn->MonomorphicAddress(), + lib_prefix, cls_prefix, name); } } show_class = true; @@ -187,28 +181,19 @@ void DartDumper::Dump4Radare2(std::filesystem::path outDir) auto stub = item.second; const auto ep = stub->Address(); std::string name = stub->FullName(); - std::replace(name.begin(), name.end(), '<', '_'); - std::replace(name.begin(), name.end(), '>', '_'); - std::replace(name.begin(), name.end(), ',', '_'); - std::replace(name.begin(), name.end(), ' ', '_'); - std::replace(name.begin(), name.end(), '$', '_'); - std::replace(name.begin(), name.end(), '&', '_'); - std::replace(name.begin(), name.end(), '-', '_'); - std::replace(name.begin(), name.end(), '+', '_'); - std::replace(name.begin(), name.end(), '?', '_'); - std::replace(name.begin(), name.end(), '(', '_'); // https://github.com/AbhiTheModder/blutter-termux/issues/6 - std::replace(name.begin(), name.end(), ')', '_'); - of << fmt::format("f method.stub.{}_{:x}={:#x}\n", name.c_str(), ep, ep); + std::string flagName = name; + filterString(flagName); + of << fmt::format("'@{:#x}'f method.stub.{}\n", ep, flagName); } - - of << "f pptr=x27\n"; // TODO: hardcoded value + of << "dr x27=`e anal.gp`\n"; + of << "'f PP=x27\n"; auto comments = DumpStructHeaderFile((outDir / "r2_dart_struct.h").string()); for (const auto& [offset, comment] : comments) { if (comment.find("String:") != -1) { std::string flagFromComment = comment; filterString(flagFromComment); - of << "f pp." << flagFromComment << "=pptr+" << offset << "\n"; - of << "'@0x0+" << offset << "'CC " << comment << "\n"; + of << "f pp." << flagFromComment << "=PP+" << offset << "\n"; + of << "'@PP+" << offset << "'CC " << comment << "\n"; } } } @@ -219,6 +204,7 @@ void DartDumper::Dump4Ida(std::filesystem::path outDir) std::ofstream of((outDir / "addNames.py").string()); of << "import ida_funcs\n"; of << "import idaapi\n\n"; + of << "print(\"[+] Adding Function names...\")\n\n"; for (auto lib : app.libs) { std::string lib_prefix = lib->GetName(); @@ -250,8 +236,9 @@ void DartDumper::Dump4Ida(std::filesystem::path outDir) continue; of << fmt::format("ida_funcs.add_func({:#x}, {:#x})\n", ep, ep + stub->Size()); } + of << "print(\"[+] Done!\")\n"; - +#ifndef IDA_FCN // Note: create struct with a lot of member by ida script is very slow // use header file then adding comment is much faster auto comments = DumpStructHeaderFile((outDir / "ida_dart_struct.h").string()); @@ -271,13 +258,27 @@ def create_Dart_structs(): for (const auto& [offset, comment] : comments) { of << "\tida_struct.set_member_cmt(ida_struct.get_member(struc, " << offset << "), '''" << comment << "''', True)\n"; } +#else + auto comments = DumpStructHeaderFile((outDir / "ida_dart_struct.h").string()); + of << R"CBLOCK( +import os +def create_Dart_structs(): + sid1 = idc.get_struc_id("DartThread") + if sid1 != idc.BADADDR: + return sid1, idc.get_struc_id("DartObjectPool") + hdr_file = os.path.join(os.path.dirname(__file__), 'ida_dart_struct.h') + idaapi.idc_parse_types(hdr_file, idc.PT_FILE) + sid1 = idc.import_type(-1, "DartThread") + sid2 = idc.import_type(-1, "DartObjectPool") +)CBLOCK"; +#endif of << "\treturn sid1, sid2\n"; of << "thrs, pps = create_Dart_structs()\n"; - of << "print('Applying Thread and Object Pool struct')\n"; + of << "print('[+] Applying Thread and Object Pool struct')\n"; applyStruct4Ida(of); - of << "print('Script finished!')\n"; + of << "print('[+] Script finished!')\n"; } std::vector> DartDumper::DumpStructHeaderFile(std::string outFile) diff --git a/dartvm_fetch_build.py b/dartvm_fetch_build.py index 87b5ae5..57d8d03 100644 --- a/dartvm_fetch_build.py +++ b/dartvm_fetch_build.py @@ -32,6 +32,17 @@ def load_source(modname, filename): """ +dart_versions = { + "3.4": ["3.4.0", "3.4.1", "3.4.2", "3.4.3", "3.4.4"], + "3.3": ["3.3.0", "3.3.1", "3.3.2", "3.3.3", "3.3.4"], + "3.5": ["3.5.0", "3.5.1", "3.5.2", "3.5.3"], + "3.0_aa": ["3.0.0", "3.0.1", "3.0.2"], + "3.0_90": ["3.0.3", "3.0.4", "3.0.5", "3.0.6", "3.0.7"], + "3.1": ["3.1.0", "3.1.1", "3.1.2", "3.1.3", "3.1.4", "3.1.5"], + "3.2": ["3.2.0", "3.2.1", "3.2.2", "3.2.3", "3.2.4", "3.2.5", "3.2.6"], +} + + class DartLibInfo: def __init__( self, @@ -41,7 +52,6 @@ def __init__( has_compressed_ptrs: bool = None, snapshot_hash: str = None, ): - self.version = version self.os_name = os_name self.arch = arch self.snapshot_hash = snapshot_hash @@ -51,6 +61,23 @@ def __init__( self.has_compressed_ptrs = os_name != "ios" else: self.has_compressed_ptrs = has_compressed_ptrs + + if os.path.exists(os.path.join(SCRIPT_DIR, "bin")): + file_name_version = [] + suffixes = ["", "_no-analysis", "_ida-fcn", "_no-analysis_ida-fcn", "_no-compressed-ptrs", "_no-compressed-ptrs_no-analysis", "_no-compressed-ptrs_no-analysis_ida-fcn", "_no-compressed-ptrs_ida-fcn"] + for file in os.listdir(os.path.join(SCRIPT_DIR, "bin")): + for suffix in suffixes: + if file.startswith("blutter_dartvm") and file.endswith(f"{os_name}_{arch}{suffix}"): + file_name_version.append(file.split("_")[1].replace('dartvm', '')) + + for key, versions in dart_versions.items(): + if version in versions and any(v in versions for v in file_name_version): + matched_version = next(v for v in file_name_version if v in versions) + self.lib_name = f"dartvm{matched_version}_{os_name}_{arch}" + self.version = matched_version + return + + self.version = version self.lib_name = f"dartvm{version}_{os_name}_{arch}"