added a history tracker to get coverage information about the execution of the binary

This commit is contained in:
me
2023-02-16 17:23:17 -05:00
parent d921cb21f7
commit c19fcf1162
2 changed files with 224 additions and 0 deletions
@@ -0,0 +1,143 @@
from typing import List
from qiling import Qiling
from qiling.core_hooks_types import HookRet
import re
class History:
history_hook_handle: HookRet = None
history: List[int] = []
ql: Qiling
def __init__(self, ql: Qiling) -> None:
self.ql = ql
self.track_block_coverage()
def clear_history(self) -> None:
"""Clears the current state of the history
"""
self.history = []
def clear_hooks(self) -> None:
"""Clears the current history hook from the Qiling instance
Returns:
None
"""
self.ql.hook_del(self.history_hook_handle)
def track_block_coverage(self) -> None:
"""Configures the history plugin to track all of the basic blocks that are executed. Removes any existing hooks
Returns:
None
"""
if self.history_hook_handle:
self.clear_hooks()
def __hook_block(ql, address, size):
self.history.append(address)
self.history_hook_handle = self.ql.hook_block(__hook_block)
def track_instruction_coverage(self) -> None:
"""Configures the history plugin to track all of the instructions that are executed. Removes any existing hooks
Returns:
None
"""
if self.history_hook_handle:
self.clear_hooks()
def __hook_block(ql, address, size):
self.history.append(address)
self.history_hook_handle = self.ql.hook_code(__hook_block)
def get_ins_only_lib(self, libs: List[str]) -> List[int]:
"""Returns a list of addresses that have been executed that are only in mmaps for objects that match the regex of items in the list
Args:
libs (List[str]): A list of regex strings to match against the library names in the memory maps
Returns:
List[int]: A list of addresses that have been executed and in the memory maps that match the regex
Examples:
>>> history.get_ins_only_lib(["libc.so", "libpthread.so"])
"""
executable_maps = self.get_regex_matching_exec_maps(libs)
return [x for x in self.history if any([x >= start and x <= end for start, end, _, _, _ in executable_maps])]
def get_ins_exclude_lib(self, libs: list) -> List:
'''Returns a list of history instructions that are not in the libraries that match the regex in the libs list
Args:
libs (List): A list of regex strings to match against the library names in the memory maps
Returns:
List: A list of addresses that have been executed and are not in the memory maps that match the regex
Examples:
>>> history.get_ins_exclude_lib(["libc.so", "libpthread.so"])
'''
executable_maps = self.get_regex_matching_exec_maps(libs)
return [x for x in self.history if any([x < start or x > end for start, end, _, _, _ in executable_maps])]
def get_mem_map_from_addr(self, ins: int) -> tuple:
'''Returns the memory map that contains the instruction
Args:
ins (int): The instruction address to search for
Returns:
tuple: A tuple that contains the memory map that contains the instruction
this tuple is in the format of (start_addr, end_addr, perms, name, path)
Examples:
>>> history.get_mem_map_from_addr(0x7ffff7dd1b97)
'''
#get the memory map that contains the instruction
mem_map = [x for x in self.ql.mem.get_mapinfo() if x[0] <= ins and x[1] >= ins]
if len(mem_map) == 0:
return None
# i sure hope theres not more than one map that contains the instruction lol
return mem_map[0]
def get_regex_matching_exec_maps(self, libs: List) -> List:
'''Returns a list of tuples for current mmaps whose names match the regex of libs in the list
This is a wrapper around ql.mem.get_mapinfo() and just filters the results by the regex of the library names and also only returns maps that are executable
Args:
libs (List): A list of regex strings to match against the library names in the memory maps
Returns:
List: A list of tuples that match the regex and are executable
Examples:
>>> history.get_regex_matching_exec_maps(["libc.so", "libpthread.so"])
>>> history.get_regex_matching_exec_maps(".*libc.*")
'''
# if libs is a string, convert it to a list
if isinstance(libs, str):
libs = [libs]
# filter the history list by the library name, using a list of regex
regex = [re.compile(lib) for lib in libs]
# filter the list of tuples
# so that we return only the ones where the library name matches the regex
regex_matching_libs = [x for x in self.ql.mem.get_mapinfo() if any([r.match(x[3]) for r in regex])]
# filter viable_libs for items that have the executable bit set
executable_maps = [x for x in regex_matching_libs if 'x' in x[2]]
return executable_maps
+81
View File
@@ -0,0 +1,81 @@
import unittest
from qiling import Qiling
from qiling.const import QL_VERBOSE
from qiling.extensions.coverage.formats.history import History
from typing import List
from IPython import embed
class HistoryTest(unittest.TestCase):
@staticmethod
def sanitize_mmap_path(mmap: List[tuple]) -> List[tuple]:
'''Removes the path from the mmap tuple so that it can be compared to other mmaps
currently because the loader is handling loading ld and the main binary, we get the annotation of the path in element 5 of the tuple (index 4)
this path is going to be dependent on the users filesystem, so it doesnt quite make sense to test for it
'''
if isinstance(mmap, tuple):
mmap = [mmap]
return list(map(lambda x: (x[0], x[1], x[2], x[3], ''), mmap))
def test_get_regex_matching_exec_maps(self):
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.OFF)
history = History(ql)
ql.run()
self.assertEqual([(140736278126592, 140736280121344, 'r-x', '[mmap] libc.so.6', '')], history.get_regex_matching_exec_maps(".*libc.so.*"))
self.assertEqual(
[
(140736278126592, 140736280121344, 'r-x', '[mmap] libc.so.6', ''),
(140737351864320, 140737352024064, 'r-x', 'ld-linux-x86-64.so.2', '')
],
self.sanitize_mmap_path(history.get_regex_matching_exec_maps([".*libc.so.*", "ld.*"]))
)
del ql
def test_get_mem_map_from_addr(self):
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.OFF)
history = History(ql)
ql.run()
self.assertEqual(
self.sanitize_mmap_path(history.get_mem_map_from_addr(0x7ffff7df4830))[0],
(
0x7ffff7dd5000,
0x7ffff7dfc000,
'r-x',
'ld-linux-x86-64.so.2',
''))
def test_get_ins_exclude_lib(self):
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.OFF)
history = History(ql)
ql.run(end=0x55555555465a)
non_libc_blocks = history.get_ins_exclude_lib(".*libc.so.*")
# this test is going to take a while but oh well
# also assumes that the get_mem_map_from_addr function works
for block in non_libc_blocks:
map_for_ins = history.get_mem_map_from_addr(block)
self.assertNotRegex(map_for_ins[3], ".*libc.so.*")
def test_get_ins_only_lib(self):
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.OFF)
history = History(ql)
ql.run(end=0x55555555465a)
non_libc_blocks = history.get_ins_only_lib(".*libc.so.*")
# this test is going to take a while but oh well
# also assumes that the get_mem_map_from_addr function works
for block in non_libc_blocks:
map_for_ins = history.get_mem_map_from_addr(block)
self.assertRegex(map_for_ins[3], ".*libc.so.*")
if __name__ == "__main__":
unittest.main()