flare-floss/floss/stackstrings.py
Ana Maria Martinez Gomez ab07022ff3
[copyright + license] Fix headers
Replace the header from source code files using the following script:
```Python
for dir_path, dir_names, file_names in os.walk("flare-floss"):
    for file_name in file_names:
        try:
            file_path = f"{dir_path}/{file_name}"
            f = open(file_path, "rb+")
            content = f.read()
            m = re.search(OLD_HEADER, content)
            if not m:
                continue
            print(f"{file_path}: {m.group('year')}")
            content = content.replace(m.group(0), NEW_HEADER % m.group("year"))
            f.seek(0)
            f.write(content)
        except:
            continue
```

Some files had the copyright headers inside a `"""` comment and needed
manual changes before applying the script.

The old header had the confusing sentence `All rights reserved`, which
does not make sense for an open source license. Replace the header by
the default Google header that corrects this issue and keep floss
consistent with other Google projects.
2025-01-14 17:52:22 +01:00

207 lines
7.4 KiB
Python

# Copyright 2017 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Set, List, Optional
from dataclasses import dataclass
import tqdm
import viv_utils
import envi.archs.i386
import envi.archs.amd64
import viv_utils.emulator_drivers
import floss.utils
import floss.strings
from floss.utils import getPointerSize, extract_strings
from floss.render import Verbosity
from floss.results import StackString
logger = floss.logging_.getLogger(__name__)
MAX_STACK_SIZE = 0x10000
MIN_NUMBER_OF_MOVS = 5
@dataclass(frozen=True)
class CallContext:
"""
Context for stackstring extraction.
Attributes:
pc: the current program counter
sp: the current stack counter
init_sp: the initial stack counter at start of function
stack_memory: the active stack frame contents
pre_ctx_strings: strings identified before this context
"""
pc: int
sp: int
init_sp: int
stack_memory: bytes
pre_ctx_strings: Optional[Set[str]]
class StackstringContextMonitor(viv_utils.emulator_drivers.Monitor):
"""
Observes emulation and extracts the active stack frame contents:
- at each function call in a function, and
- based on heuristics looking for mov instructions to a hardcoded buffer.
"""
def __init__(self, init_sp, bb_ends):
super().__init__()
self.ctxs: List[CallContext] = []
self._init_sp = init_sp
# index of VAs of the last instruction of all basic blocks
self._bb_ends = bb_ends
# count of stack mov instructions in current basic block.
# not guaranteed to grow greater than MIN_NUMBER_OF_MOVS.
self._mov_count = 0
def apicall(self, emu, api, argv):
self.update_contexts(emu, emu.getProgramCounter())
# TODO remove va arg? see below
def update_contexts(self, emu, va) -> None:
try:
self.ctxs.append(self.get_call_context(emu, va))
except ValueError as e:
logger.debug("%s", e)
# TODO get va here from emu?
def get_call_context(self, emu, va, pre_ctx_strings: Optional[Set[str]] = None) -> CallContext:
"""
Returns a context with the bytes on the stack between the base pointer
(specifically, stack pointer at function entry), and stack pointer.
"""
stack_top = emu.getStackCounter()
stack_bottom = self._init_sp
stack_size = stack_bottom - stack_top
if stack_size > MAX_STACK_SIZE:
raise ValueError("stack size too big: 0x%x" % stack_size)
stack_buf = emu.readMemory(stack_top, stack_size)
# would probably be an optimization here to strip garbage bytes, however, then we cannot easily track
# the correct frame offset
ctx = CallContext(va, stack_top, stack_bottom, stack_buf, pre_ctx_strings)
return ctx
# overrides emulator_drivers.Monitor
def posthook(self, emu, op, endpc):
self.check_mov_heuristics(emu, op, endpc)
def check_mov_heuristics(self, emu, op, endpc):
"""
Extract contexts at end of a basic block (bb) if bb contains enough movs to a harcoded buffer.
"""
# TODO check number of written bytes via writelog?
# count movs, shortcut if this basic block has enough writes to trigger context extraction already
if self._mov_count < MIN_NUMBER_OF_MOVS and self.is_stack_mov(op):
self._mov_count += 1
if endpc in self._bb_ends:
if self._mov_count >= MIN_NUMBER_OF_MOVS:
self.update_contexts(emu, op.va)
# reset counter at end of basic block
self._mov_count = 0
def is_stack_mov(self, op):
if not op.mnem.startswith("mov"):
return False
opnds = op.getOperands()
if not opnds:
# no operands, e.g. movsb, movsd
# fail safe and count these regardless of where data is moved to.
return True
return isinstance(opnds[0], envi.archs.i386.disasm.i386SibOper) or isinstance(
opnds[0], envi.archs.i386.disasm.i386RegMemOper
)
def extract_call_contexts(vw, fva, bb_ends):
emu = floss.utils.make_emulator(vw)
monitor = StackstringContextMonitor(emu.getStackCounter(), bb_ends)
driver = viv_utils.emulator_drivers.FullCoverageEmulatorDriver(emu, repmax=256)
# note: we don't use ApiMonitor with our custom API hooks here
driver.add_monitor(monitor)
try:
driver.run(fva)
except Exception as e:
logger.debug("error during emulation of function: %s", str(e))
return monitor.ctxs
def get_basic_block_ends(vw):
"""
Return the set of VAs that are the last instructions of basic blocks.
"""
index = set([])
for funcva in vw.getFunctions():
f = viv_utils.Function(vw, funcva)
for bb in f.basic_blocks:
if len(bb.instructions) == 0:
continue
index.add(bb.instructions[-1].va)
return index
def extract_stackstrings(
vw, selected_functions, min_length, verbosity=Verbosity.DEFAULT, disable_progress=False
) -> List[StackString]:
"""
Extracts the stackstrings from functions in the given workspace.
:param vw: The vivisect workspace from which to extract stackstrings.
:param selected_functions: list of selected functions
:param min_length: minimum string length
:param verbosity: verbosity level
:param disable_progress: do NOT show progress bar
"""
logger.info("extracting stackstrings from %d functions", len(selected_functions))
stack_strings = list()
bb_ends = get_basic_block_ends(vw)
pb = floss.utils.get_progress_bar(
selected_functions, disable_progress, desc="extracting stackstrings", unit=" functions"
)
with tqdm.contrib.logging.logging_redirect_tqdm(), floss.utils.redirecting_print_to_tqdm():
for fva in pb:
seen: Set[str] = floss.utils.get_referenced_strings(vw, fva)
logger.debug("extracting stackstrings from function 0x%x", fva)
ctxs = extract_call_contexts(vw, fva, bb_ends)
for n, ctx in enumerate(ctxs, 1):
logger.trace(
"extracting stackstrings at checkpoint: 0x%x stacksize: 0x%x", ctx.pc, ctx.init_sp - ctx.sp
)
for s in extract_strings(ctx.stack_memory, min_length, seen):
frame_offset = (ctx.init_sp - ctx.sp) - s.offset - getPointerSize(vw)
ss = StackString(
function=fva,
string=s.string,
encoding=s.encoding,
program_counter=ctx.pc,
stack_pointer=ctx.sp,
original_stack_pointer=ctx.init_sp,
offset=s.offset,
frame_offset=frame_offset,
)
floss.results.log_result(ss, verbosity)
seen.add(s.string)
stack_strings.append(ss)
return stack_strings