Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
boris authored and boris committed Jun 2, 2022
0 parents commit 8ca91e8
Show file tree
Hide file tree
Showing 16 changed files with 398 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**/.DS_Store
*.py[cod]
**/__pycache__/
**/.idea/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# flutter-re-demo
70 changes: 70 additions & 0 deletions flure/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from flure.code_info import CodeInfo, ClassInfo, FunctionInfo

LIBRARY_TOKEN = b"Library:'"
CLASS_TOKEN = b" Class: "
FUNCTION_TOKEN = b" Function "
KNOWN_PREFIXES_IGNORED = [b"Random ", b"Map<Object, ", b"Class: ", b"dynamic ", b'', b'}']


class ReFlutterDumpParser(object):
def __init__(self, filename):
self.filename = filename
self.code_info = CodeInfo()
self.parse()

def parse(self):
with open(self.filename, "rb") as fp:
lines = fp.readlines()
cur_line_index = 0
while cur_line_index < len(lines):
if lines[cur_line_index].startswith(LIBRARY_TOKEN):
class_info, next_line_index = self.parse_class(lines, cur_line_index)
self.code_info.add_classes(class_info)
cur_line_index = next_line_index
else:
raise Exception(f"Unknown line while parsing file: {lines[cur_line_index].strip()}")

def parse_class(self, lines, start_index):
class_info = self.parse_class_declaration_line(lines[start_index])
cur_line_index = start_index + 1
while cur_line_index < len(lines):
if lines[cur_line_index].startswith(LIBRARY_TOKEN):
return class_info, cur_line_index
elif lines[cur_line_index].startswith(FUNCTION_TOKEN):
func_info = self.parse_function_lines(lines[cur_line_index:cur_line_index + 5])
class_info.add_function(func_info)
cur_line_index += 5
else:
prefix_found = False
for known_ignored_prefix in KNOWN_PREFIXES_IGNORED:
if lines[cur_line_index].strip().startswith(known_ignored_prefix):
cur_line_index += 1
prefix_found = True
break
if not prefix_found:
raise Exception(f"Unknown line while parsing class: {lines[cur_line_index].strip()}")
return class_info, cur_line_index

@staticmethod
def parse_class_declaration_line(line):
if not line.startswith(LIBRARY_TOKEN):
raise Exception(f"Invalid line while parsing class declaration line: '{line}'")
module_path = line.split(b"'")[1].decode("ascii")
class_full_declaration = line.split(b"'")[2].strip()
if not class_full_declaration.startswith(CLASS_TOKEN):
return ClassInfo(module_path, None, None)
class_name = class_full_declaration[len(CLASS_TOKEN):].split(b" ")[0].decode("ascii")
return ClassInfo(module_path, class_name, class_full_declaration.decode("ascii"))

@staticmethod
def parse_function_lines(func_lines):
if (func_lines[1].strip() != b'') or (func_lines[3].strip() != b'') or (func_lines[4].strip() != b'}'):
raise Exception(f"Invalid lines while parsing function declaration line: '{func_lines}'")
# Function 'get:_instantiator_type_arguments@0150898': getter. (_Closure@0150898) => dynamic
func_info = func_lines[0].strip()[:-1]
func_name = func_info.split(b"'")[1].decode("ascii")
func_signature = func_info.split(b"'")[2][1:].decode("ascii")
# Code Offset: _kDartIsolateSnapshotInstructions + 0x00000000002ebfb0
func_offset = func_lines[2].strip().split(b"+")[1].strip()
func_relative_base = func_lines[2].strip().split(b"+")[0].split(b":")[1].strip().decode("ascii")
return FunctionInfo(func_name, func_signature, int(func_offset, 16), func_relative_base)
66 changes: 66 additions & 0 deletions flure/code_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

class FunctionInfo(object):
def __init__(self, name, signature, offset, relative_base):
self.name = name
self.signature = signature
self.offset = offset
self.relative_base = relative_base

@staticmethod
def load(func_info_dict):
return FunctionInfo(func_info_dict["name"], func_info_dict["signature"],
func_info_dict["offset"], func_info_dict["relative_base"])

def dump(self):
return {
"name": self.name,
"signature": self.signature,
"offset": self.offset,
"relative_base": self.relative_base,
}


class ClassInfo(object):
def __init__(self, module_path, name, full_declaration):
self.module_path = module_path
self.name = name
self.full_declaration = full_declaration
self.functions = []

def add_function(self, func_info: FunctionInfo):
self.functions.append(func_info)

@staticmethod
def load(class_info_dict):
class_info = ClassInfo(class_info_dict["module"], class_info_dict["name"], class_info_dict["full_declaration"])
for func_info_dict in class_info_dict["functions"]:
class_info.add_function(FunctionInfo.load(func_info_dict))
return class_info

def dump(self):
return {
"module": self.module_path,
"name": self.name,
"full_declaration": self.full_declaration,
"functions": [func_info.dump() for func_info in self.functions]
}


class CodeInfo(object):
def __init__(self):
self.classes = []

def add_classes(self, func_info: ClassInfo):
self.classes.append(func_info)

@staticmethod
def load(code_info_dict):
code_info = CodeInfo()
for class_info_dict in code_info_dict["classes"]:
code_info.add_classes(ClassInfo.load(class_info_dict))
return code_info

def dump(self):
return {
"classes": [class_info.dump() for class_info in self.classes]
}
Empty file added flure/ida/__init__.py
Empty file.
57 changes: 57 additions & 0 deletions flure/ida/patch_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import idautils
import ida_dirtree
import idc
import ida_name, ida_funcs

from flure.code_info import CodeInfo
from flure.ida.utils import safe_rename

func_dir: ida_dirtree.dirtree_t = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS)

SMALL_FUNC_MAPPING = {
"==": "__equals__",
">>": "__rshift__",
"<<": "__lshift__",
"~/": "__truncdiv__",
"[]=": "__list_set__",
"unary-": "__neg__",
"<=": "__inf_eq__",
">=": "__sup_eq__",
"!=": "__neq__",
"|": "__or__",
"&": "__and__",
"^": "__xor__",
"+": "__add__",
"*": "__mul__",
"-": "__sub__",
"<": "__inf__",
">": "__sup__",
"%": "__mod__",
"/": "__fiv__",
"~": "__bnot__",
}


def create_ida_folders(code_info: CodeInfo):
exported_entries = {}
for entry in idautils.Entries():
exported_entries[entry[3]] = entry[2]
for class_info in code_info.classes:
dir_path = class_info.module_path.replace(":", "/")
func_dir.mkdir(dir_path)
class_name = class_info.name
for func_info in class_info.functions:
func_name = func_info.name
if func_info.relative_base != 0:
func_offset = func_info.offset + exported_entries[func_info.relative_base]
else:
func_offset = func_info.offset
for k, v in SMALL_FUNC_MAPPING.items():
if func_name == k:
func_name = v
func_name = func_name.replace(":", "::")
full_func_name = f"{class_name}::{func_name}" if class_info.name is not None else func_name

ida_funcs.add_func(func_offset, idc.BADADDR)
given_name = safe_rename(func_offset, full_func_name)
func_dir.rename(given_name, f"{dir_path}/{given_name}")
9 changes: 9 additions & 0 deletions flure/ida/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import idc
import ida_name


def safe_rename(offset, wanted_name, option=ida_name.SN_FORCE | ida_name.SN_NOCHECK):
idc.set_name(offset, wanted_name, option)
# Because ida_name.SN_FORCE and ida_name.SN_NOCHECK, the actual name can be different
# from wanted_name, thus we read it from DB after it has been set
return idc.get_name(offset)
Empty file added flure/parser/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions flure/parser/dwarf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from elftools.elf.elffile import ELFFile, SymbolTableSection

from flure.code_info import CodeInfo, ClassInfo, FunctionInfo


class DwarfParser(object):
def __init__(self, filename):
self.filename = filename
self.code_info = CodeInfo()
self.parse()

@staticmethod
def get_file_entries(dwarf_info, cu):
line_program = dwarf_info.line_program_for_CU(cu)
if line_program is None:
print('Warning: DWARF info is missing a line program for this CU')
return []
return line_program.header["file_entry"]

def parse(self):
known_symbol_address = self.parse_dwarf()
self.parse_symbols_table(known_symbol_address)

def parse_dwarf(self):
known_symbol_address = []
with open(self.filename, 'rb') as f:
elffile = ELFFile(f)
if not elffile.has_dwarf_info():
raise Exception(f"File {self.filename} has no DWARF info")
dwarf_info = elffile.get_dwarf_info()
classes_by_name = {}
for CU in dwarf_info.iter_CUs():
file_entries = self.get_file_entries(dwarf_info, CU)
for DIE in CU.iter_DIEs():
if DIE.tag == 'DW_TAG_subprogram':
if 'DW_AT_low_pc' not in DIE.attributes:
continue
low_pc = DIE.attributes['DW_AT_low_pc'].value
if 'DW_AT_abstract_origin' not in DIE.attributes:
raise Exception(f"Unknown DIE: {DIE}")
abstract_origin = DIE.get_DIE_from_attribute('DW_AT_abstract_origin')
full_func_name = abstract_origin.attributes['DW_AT_name'].value.decode("ascii")
decl_file = file_entries[abstract_origin.attributes['DW_AT_decl_file'].value - 1].name.decode("ascii")
if "." in full_func_name:
class_name = full_func_name.split(".")[0]
func_name = ".".join(full_func_name.split(".")[1:])
else:
class_name = ""
func_name = full_func_name
if " " in class_name:
if (class_name.split(" ")[0] != "new") or (len(class_name.split(" ")) != 2):
raise Exception(f"Not-handled space in '{full_func_name}'")
class_name = class_name.split(" ")[1]
if class_name == "":
class_name = "__null__"
full_class_name = ".".join([decl_file, class_name])
if full_class_name not in classes_by_name:
classes_by_name[full_class_name] = ClassInfo(decl_file, class_name, "")
classes_by_name[full_class_name].add_function(FunctionInfo(func_name, "", low_pc, 0))
known_symbol_address.append(low_pc)
for full_class_name, class_info in classes_by_name.items():
self.code_info.add_classes(class_info)
return known_symbol_address

def parse_symbols_table(self, known_symbol_address):
known_symbol_address = known_symbol_address if known_symbol_address is not None else []
with open(self.filename, 'rb') as f:
elffile = ELFFile(f)
precompiled_code = ClassInfo("precompiled", None, None)
for section in elffile.iter_sections():
if isinstance(section, SymbolTableSection):
for symbol in section.iter_symbols():
symbol_address = symbol.entry['st_value']
if symbol_address not in known_symbol_address:
precompiled_code.add_function(FunctionInfo(symbol.name, "", symbol.entry['st_value'], 0))
self.code_info.add_classes(precompiled_code)
70 changes: 70 additions & 0 deletions flure/parser/reflutter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from flure.code_info import CodeInfo, ClassInfo, FunctionInfo

LIBRARY_TOKEN = b"Library:'"
CLASS_TOKEN = b"Class: "
FUNCTION_TOKEN = b" Function "
KNOWN_PREFIXES_IGNORED = [b"Random ", b"Map<Object, ", b"Class: ", b"dynamic ", b'', b'}']


class ReFlutterDumpParser(object):
def __init__(self, filename):
self.filename = filename
self.code_info = CodeInfo()
self.parse()

def parse(self):
with open(self.filename, "rb") as fp:
lines = fp.readlines()
cur_line_index = 0
while cur_line_index < len(lines):
if lines[cur_line_index].startswith(LIBRARY_TOKEN):
class_info, next_line_index = self.parse_class(lines, cur_line_index)
self.code_info.add_classes(class_info)
cur_line_index = next_line_index
else:
raise Exception(f"Unknown line while parsing file: {lines[cur_line_index].strip()}")

def parse_class(self, lines, start_index):
class_info = self.parse_class_declaration_line(lines[start_index])
cur_line_index = start_index + 1
while cur_line_index < len(lines):
if lines[cur_line_index].startswith(LIBRARY_TOKEN):
return class_info, cur_line_index
elif lines[cur_line_index].startswith(FUNCTION_TOKEN):
func_info = self.parse_function_lines(lines[cur_line_index:cur_line_index + 5])
class_info.add_function(func_info)
cur_line_index += 5
else:
prefix_found = False
for known_ignored_prefix in KNOWN_PREFIXES_IGNORED:
if lines[cur_line_index].strip().startswith(known_ignored_prefix):
cur_line_index += 1
prefix_found = True
break
if not prefix_found:
raise Exception(f"Unknown line while parsing class: {lines[cur_line_index].strip()}")
return class_info, cur_line_index

@staticmethod
def parse_class_declaration_line(line):
if not line.startswith(LIBRARY_TOKEN):
raise Exception(f"Invalid line while parsing class declaration line: '{line}'")
module_path = line.split(b"'")[1].decode("ascii")
class_full_declaration = line.split(b"'")[2].strip()
if not class_full_declaration.startswith(CLASS_TOKEN):
return ClassInfo(module_path, None, None)
class_name = class_full_declaration[len(CLASS_TOKEN):].split(b" ")[0].decode("ascii")
return ClassInfo(module_path, class_name, class_full_declaration[:-1].decode("ascii"))

@staticmethod
def parse_function_lines(func_lines):
if (func_lines[1].strip() != b'') or (func_lines[3].strip() != b'') or (func_lines[4].strip() != b'}'):
raise Exception(f"Invalid lines while parsing function declaration line: '{func_lines}'")
# Function 'get:_instantiator_type_arguments@0150898': getter. (_Closure@0150898) => dynamic
func_info = func_lines[0].strip()[:-1]
func_name = func_info.split(b"'")[1].decode("ascii")
func_signature = func_info.split(b"'")[2][1:].decode("ascii")
# Code Offset: _kDartIsolateSnapshotInstructions + 0x00000000002ebfb0
func_offset = func_lines[2].strip().split(b"+")[1].strip()
func_relative_base = func_lines[2].strip().split(b"+")[0].split(b":")[1].strip().decode("ascii")
return FunctionInfo(func_name, func_signature, int(func_offset, 16), func_relative_base)
27 changes: 27 additions & 0 deletions parse_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import json
import argparse


from flure.parser.dwarf import DwarfParser
from flure.parser.reflutter import ReFlutterDumpParser


if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Parse and reformat snapshot information")
parser.add_argument("input", help="Input file to parse")
parser.add_argument("input_type", choices=["dwarf", "reflutter"], help="Specify which parser should be used")
parser.add_argument("-o", "--output", help="Output file")

args = parser.parse_args()
if args.input_type == "dwarf":
parser = DwarfParser(args.input)
elif args.input_type == "reflutter":
parser = ReFlutterDumpParser(args.input)
else:
raise Exception(f"Unknown input type {args.input_type}")

if args.output is not None:
with open(args.output, 'w') as fp:
json.dump(parser.code_info.dump(), fp, indent=4)
else:
print(json.dumps(parser.code_info.dump(), indent=4))
Loading

0 comments on commit 8ca91e8

Please sign in to comment.