Skip to content
Merged
54 changes: 41 additions & 13 deletions blutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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) + (
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -139,19 +143,25 @@ 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


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":
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand All @@ -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:
Expand All @@ -310,6 +323,7 @@ def main(
rebuild_blutter,
create_vs_sln,
no_analysis,
ida_fcn,
)
else:
libapp_file, libflutter_file = find_lib_files(indir)
Expand All @@ -321,6 +335,7 @@ def main(
rebuild_blutter,
create_vs_sln,
no_analysis,
ida_fcn,
)


Expand Down Expand Up @@ -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,
Expand All @@ -401,4 +428,5 @@ def check_for_updates_and_pull():
args.rebuild,
args.vs_sln,
args.no_analysis,
args.ida_fcn,
)
3 changes: 3 additions & 0 deletions blutter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
95 changes: 48 additions & 47 deletions blutter/src/DartDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";
}
}
}
Expand All @@ -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();
Expand Down Expand Up @@ -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());
Expand All @@ -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<std::pair<intptr_t, std::string>> DartDumper::DumpStructHeaderFile(std::string outFile)
Expand Down
29 changes: 28 additions & 1 deletion dartvm_fetch_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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}"


Expand Down