forked from Guardsquare/flutter-re-demo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
boris
authored and
boris
committed
Jun 2, 2022
0 parents
commit 8ca91e8
Showing
16 changed files
with
398 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
**/.DS_Store | ||
*.py[cod] | ||
**/__pycache__/ | ||
**/.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# flutter-re-demo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
Oops, something went wrong.