flare-floss/scripts/idaplugin.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

234 lines
7.7 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.
#!/usr/bin/env python3
"""
Run FLOSS to automatically extract obfuscated strings and apply them to the
currently loaded module in IDA Pro.
author: Willi Ballenthin
email: willi.ballenthin@gmail.com
"""
import os
import time
import logging
from typing import List, Union
from pathlib import Path
import idc
import viv_utils
import floss
import floss.main
import floss.utils
import floss.render
import floss.identify
import floss.stackstrings
import floss.tightstrings
import floss.string_decoder
from floss.results import AddressType, StackString, TightString, DecodedString
logger = logging.getLogger("floss.idaplugin")
MIN_LENGTH = 4
def append_comment(ea: int, s: str, repeatable: bool = False) -> None:
"""
add the given string as a (possibly repeating) comment to the given address.
does not add the comment if it already exists.
adds the comment on its own line.
Args:
ea: the address at which to add the comment.
s: the comment text.
repeatable: if True, set a repeatable comment.
"""
# see: http://blogs.norman.com/2011/security-research/improving-ida-analysis-of-x64-exception-handling
if repeatable:
cmt = idc.get_cmt(ea, True)
else:
cmt = idc.get_cmt(ea, False)
if not cmt:
cmt = s # no existing comment
else:
if s in cmt: # ignore duplicates
return
cmt = cmt + "\n" + s
if repeatable:
idc.set_cmt(ea, cmt, True)
else:
idc.set_cmt(ea, cmt, False)
def append_lvar_comment(fva: int, frame_offset: int, s: str, repeatable: bool = False) -> None:
"""
add the given string as a (possibly repeatable) stack variable comment to the given function.
does not add the comment if it already exists.
adds the comment on its own line.
Args:
fva: the address of the function with the stack variable.
frame_offset: the offset into the stack frame at which the variable is found.
s: the comment text.
repeatable: if True, set a repeatable comment.
"""
stack = idc.get_func_attr(fva, idc.FUNCATTR_FRAME)
if not stack:
raise RuntimeError("failed to find stack frame for function: 0x%x" % fva)
lvar_offset = (
idc.get_func_attr(fva, idc.FUNCATTR_FRSIZE) - frame_offset
) # alternative: idc.get_frame_lvar_size(fva) - frame_offset
if not lvar_offset:
raise RuntimeError("failed to compute local variable offset: 0x%x 0x%x %s" % (fva, stack, s))
if lvar_offset <= 0:
raise RuntimeError("failed to compute positive local variable offset: 0x%x 0x%x %s" % (fva, stack, s))
string = idc.get_member_cmt(stack, lvar_offset, repeatable)
if not string:
string = s
else:
if s in string: # ignore duplicates
return
string = string + "\n" + s
if not idc.set_member_cmt(stack, lvar_offset, string, repeatable):
raise RuntimeError("failed to set comment: 0x%08x 0x%08x 0x%08x: %s" % (fva, stack, lvar_offset, s))
def apply_decoded_strings(decoded_strings: List[DecodedString]) -> None:
for ds in decoded_strings:
if not ds.string:
continue
if ds.address_type == AddressType.GLOBAL:
logger.info("decoded string at global address 0x%x: %s", ds.address, ds.string)
append_comment(ds.address, ds.string)
else:
logger.info("decoded string for function call at 0x%x: %s", ds.decoded_at, ds.string)
append_comment(ds.decoded_at, ds.string)
def apply_stack_strings(
stack_strings: List[StackString], tight_strings: List[TightString], lvar_cmt: bool = True, cmt: bool = True
) -> None:
"""
lvar_cmt: apply stack variable comment
cmt: apply regular comment
"""
strings = stack_strings + tight_strings
for s in strings:
if not s.string:
continue
logger.info(
"decoded stack/tight string in function 0x%x (pc: 0x%x): %s", s.function, s.program_counter, s.string
)
if lvar_cmt:
try:
# TODO this often fails due to wrong frame offset
append_lvar_comment(s.function, s.frame_offset, s.string)
except RuntimeError as e:
logger.warning("failed to apply stack/tight string: %s", str(e))
if cmt:
append_comment(s.program_counter, s.string)
def ignore_floss_logs():
logging.getLogger("floss.api_hooks").setLevel(logging.WARNING)
logging.getLogger("floss.function_argument_getter").setLevel(logging.WARNING)
logging.getLogger("viv_utils").setLevel(logging.CRITICAL)
logging.getLogger("viv_utils.emulator_drivers").setLevel(logging.ERROR)
floss.utils.set_vivisect_log_level(logging.CRITICAL)
def main(argv=None):
logging.basicConfig(level=logging.INFO)
logging.getLogger().setLevel(logging.INFO)
ignore_floss_logs()
idb_path = Path(idc.get_idb_path())
fpath = idb_path.with_suffix("")
viv_path = fpath.with_suffix(".viv")
if viv_path.exists():
logger.info("loading vivisect workspace from %r", str(viv_path))
vw = viv_utils.getWorkspace(str(viv_path))
else:
logger.info("loading vivisect workspace from IDB...")
vw = viv_utils.loadWorkspaceFromIdb()
logger.info("loaded vivisect workspace")
selected_functions = set(vw.getFunctions())
time0 = time.time()
logger.info("identifying decoding functions...")
decoding_function_features, library_functions = floss.identify.find_decoding_function_features(
vw, selected_functions, disable_progress=True
)
logger.info("extracting stackstrings...")
selected_functions = floss.identify.get_functions_without_tightloops(decoding_function_features)
stack_strings = floss.stackstrings.extract_stackstrings(
vw, selected_functions, MIN_LENGTH, verbosity=floss.render.Verbosity.VERBOSE, disable_progress=True
)
logger.info("decoded %d stack strings", len(stack_strings))
logger.info("extracting tightstrings...")
tightloop_functions = floss.identify.get_functions_with_tightloops(decoding_function_features)
tight_strings = floss.tightstrings.extract_tightstrings(
vw,
tightloop_functions,
min_length=MIN_LENGTH,
verbosity=floss.render.Verbosity.VERBOSE,
disable_progress=True,
)
logger.info("decoded %d tight strings", len(tight_strings))
apply_stack_strings(stack_strings, tight_strings)
logger.info("decoding strings...")
top_functions = floss.identify.get_top_functions(decoding_function_features, 20)
fvas_to_emulate = floss.identify.get_function_fvas(top_functions)
fvas_tight_functions = floss.identify.get_tight_function_fvas(decoding_function_features)
fvas_to_emulate = floss.identify.append_unique(fvas_to_emulate, fvas_tight_functions)
decoded_strings = floss.string_decoder.decode_strings(
vw,
fvas_to_emulate,
MIN_LENGTH,
verbosity=floss.render.Verbosity.VERBOSE,
disable_progress=True,
)
logger.info("decoded %d strings", len(decoded_strings))
apply_decoded_strings(decoded_strings)
time1 = time.time()
logger.debug("finished execution after %f seconds", (time1 - time0))
return 0
if __name__ == "__main__":
main()