aboutsummaryrefslogtreecommitdiff
path: root/unicorn_mode
diff options
context:
space:
mode:
authorvan Hauser <vh@thc.org>2021-03-24 11:25:33 +0100
committerGitHub <noreply@github.com>2021-03-24 11:25:33 +0100
commitf0e08e648609e57732a76e285e57714c6d5fd2cd (patch)
tree9d35021985e2b6ea2b2988f318195d238e6fabc3 /unicorn_mode
parent37829765282421d9e3cb9448bceedcb58256e76a (diff)
parent2dac4e785fa9f27e8c59bb504cfa8942eba938be (diff)
downloadafl++-f0e08e648609e57732a76e285e57714c6d5fd2cd.tar.gz
Merge pull request #842 from AFLplusplus/stable
3.12c release
Diffstat (limited to 'unicorn_mode')
-rw-r--r--unicorn_mode/helper_scripts/ida_context_loader.py197
m---------unicorn_mode/unicornafl0
2 files changed, 197 insertions, 0 deletions
diff --git a/unicorn_mode/helper_scripts/ida_context_loader.py b/unicorn_mode/helper_scripts/ida_context_loader.py
new file mode 100644
index 00000000..31d47a90
--- /dev/null
+++ b/unicorn_mode/helper_scripts/ida_context_loader.py
@@ -0,0 +1,197 @@
+# Copyright (c) 2021 Brandon Miller (zznop)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+"""IDA script for loading state that was dumped from a running process using unicorn AFL's GDB
+plugin (unicorn_dumper_gdb.py). The dumper script can be found in the AFL++ repository at:
+https://github.com/AFLplusplus/AFLplusplus/blob/stable/unicorn_mode/helper_scripts/unicorn_dumper_gdb.py
+"""
+
+import json
+from pathlib import Path, PurePath
+import zlib
+import idaapi
+import ida_bytes
+import ida_kernwin
+import ida_nalt
+import ida_segment
+
+
+class ContextLoaderError(Exception):
+ """Base "catch all" exception for this script
+ """
+
+
+class ArchNotSupportedError(ContextLoaderError):
+ """Exception raised if the input file CPU architecture isn't supported fully
+ """
+
+
+def parse_mapping_index(filepath: str):
+ """Open and unmarshal the _index.json file
+
+ :param filepath: Path to the JSON file
+ :return: Dict representing index file contents
+ """
+
+ if filepath is None:
+ raise ContextLoaderError('_index.json file was not selected')
+
+ try:
+ with open(filepath, 'rb') as _file:
+ return json.load(_file)
+ except Exception as ex:
+ raise ContextLoaderError('Failed to parse json file {}'.format(filepath)) from ex
+
+def get_input_name():
+ """Get the name of the input file
+
+ :retrun: Name of the input file
+ """
+
+ input_filepath = ida_nalt.get_input_file_path()
+ return Path(input_filepath).name
+
+def write_segment_bytes(start: int, filepath: str):
+ """"Read data from context file and write it to the IDA segment
+
+ :param start: Start address
+ :param filepath: Path to context file
+ """
+
+ with open(filepath, 'rb') as _file:
+ data = _file.read()
+
+ decompressed_data = zlib.decompress(data)
+ ida_bytes.put_bytes(start, decompressed_data)
+
+def create_segment(context_dir: str, segment: dict, is_be: bool):
+ """Create segment in IDA and map in the data from the file
+
+ :param context_dir: Parent directory of the context files
+ :param segment: Segment information from _index.json
+ :param is_be: True if processor is big endian, otherwise False
+ """
+
+ input_name = get_input_name()
+ if Path(segment['name']).name != input_name:
+ ida_seg = idaapi.segment_t()
+ ida_seg.start_ea = segment['start']
+ ida_seg.end_ea = segment['end']
+ ida_seg.bitness = 1 if is_be else 0
+ if segment['permissions']['r']:
+ ida_seg.perm |= ida_segment.SEGPERM_READ
+ if segment['permissions']['w']:
+ ida_seg.perm |= ida_segment.SEGPERM_WRITE
+ if segment['permissions']['x']:
+ ida_seg.perm |= ida_segment.SEGPERM_EXEC
+ idaapi.add_segm_ex(ida_seg, Path(segment['name']).name, 'CODE', idaapi.ADDSEG_OR_DIE)
+ else:
+ idaapi.add_segm_ex(ida_seg, Path(segment['name']).name, 'DATA', idaapi.ADDSEG_OR_DIE)
+
+ if segment['content_file']:
+ write_segment_bytes(segment['start'], PurePath(context_dir, segment['content_file']))
+
+def create_segments(index: dict, context_dir: str):
+ """Iterate segments in index JSON, create the segment in IDA, and map in the data from the file
+
+ :param index: _index.json JSON data
+ :param context_dir: Parent directory of the context files
+ """
+
+ info = idaapi.get_inf_structure()
+ is_be = info.is_be()
+ for segment in index['segments']:
+ create_segment(context_dir, segment, is_be)
+
+def rebase_program(index: dict):
+ """Rebase the program to the offset specified in the context _index.json
+
+ :param index: _index.json JSON data
+ """
+
+ input_name = get_input_name()
+ new_base = None
+ for segment in index['segments']:
+ if not segment['name']:
+ continue
+
+ segment_name = Path(segment['name']).name
+ if input_name == segment_name:
+ new_base = segment['start']
+ break
+
+ if not new_base:
+ raise ContextLoaderError('Input file is not in _index.json')
+
+ current_base = idaapi.get_imagebase()
+ ida_segment.rebase_program(new_base-current_base, 8)
+
+def get_pc_by_arch(index: dict) -> int:
+ """Queries the input file CPU architecture and attempts to lookup the address of the program
+ counter in the _index.json by register name
+
+ :param index: _index.json JSON data
+ :return: Program counter value or None
+ """
+
+ progctr = None
+ info = idaapi.get_inf_structure()
+ if info.procname == 'metapc':
+ if info.is_64bit():
+ progctr = index['regs']['rax']
+ elif info.is_32bit():
+ progctr = index['regs']['eax']
+ return progctr
+
+def write_reg_info(index: dict):
+ """Write register info as line comment at instruction pointed to by the program counter and
+ change focus to that location
+
+ :param index: _index.json JSON data
+ """
+
+ cmt = ''
+ for reg, val in index['regs'].items():
+ cmt += f"{reg.ljust(6)} : {hex(val)}\n"
+
+ progctr = get_pc_by_arch(index)
+ if progctr is None:
+ raise ArchNotSupportedError(
+ 'Architecture not fully supported, skipping register status comment')
+ ida_bytes.set_cmt(progctr, cmt, 0)
+ ida_kernwin.jumpto(progctr)
+
+def main(filepath):
+ """Main - parse _index.json input and map context files into the database
+
+ :param filepath: Path to the _index.json file
+ """
+
+ try:
+ index = parse_mapping_index(filepath)
+ context_dir = Path(filepath).parent
+ rebase_program(index)
+ create_segments(index, context_dir)
+ write_reg_info(index)
+ except ContextLoaderError as ex:
+ print(ex)
+
+if __name__ == '__main__':
+ main(ida_kernwin.ask_file(1, '*.json', 'Import file name'))
diff --git a/unicorn_mode/unicornafl b/unicorn_mode/unicornafl
new file mode 160000
+Subproject fb2fc9f25df32f17f6b6b859e4dbd70f9a857e0