diff --git a/.clang-format b/.clang-format index e9cc863..65e3bbf 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,4 @@ -# es3n1n's clang-format, last upd 29 jun 2023 11:20:59 +# es3n1n's clang-format -- 29 jun 2023 11:20:59 -- https://pastebin.com/4mtxipKe --- AlignAfterOpenBracket: Align AlignEscapedNewlines: Left @@ -20,7 +20,7 @@ BasedOnStyle: WebKit BinPackArguments: true BinPackParameters: true BitFieldColonSpacing: None -BraceWrapping: +BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: Never diff --git a/.clang-tidy b/.clang-tidy index 3dcb78f..cc82bf8 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,27 +1,58 @@ +# es3n1n's clang-tidy -- 09 oct 2024 19:56:40 -- https://pastebin.com/Zqj501X6 Checks: > bugprone-*, - -bugprone-easily-swappable-parameters, - clang-diagnostic-*, + cert-*, clang-analyzer-*, + clang-diagnostic-*, + concurrency-*, cppcoreguidelines-*, - -cppcoreguidelines-pro-type-union-access, - -cppcoreguidelines-pro-bounds-pointer-arithmetic, - -cppcoreguidelines-non-private-member-variables-in-classes, - mpi-*, - modernize-*, - -modernize-use-trailing-return-type, - -modernize-use-nodiscard, + google-*, + hicpp-*, misc-*, - -misc-non-private-member-variables-in-classes, - -misc-no-recursion, + modernize-*, + mpi-*, performance-*, + portability-*, readability-*, - -readability-function-cognitive-complexity, + -bugprone-easily-swappable-parameters, + -bugprone-exception-escape, + -bugprone-unchecked-optional-access, + -bugprone-macro-parentheses, + -cert-err58-cpp, + -concurrency-mt-unsafe, + -clang-analyzer-optin.core.EnumCastOutOfRange, + -clang-analyzer-security.insecureAPI.rand, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-avoid-do-while, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-init-variables, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-special-member-functions, + -hicpp-avoid-c-arrays, + -hicpp-special-member-functions, + -misc-include-cleaner, + -misc-no-recursion, + -misc-non-private-member-variables-in-classes, + -modernize-avoid-c-arrays, + -modernize-use-trailing-return-type, -readability-convert-member-functions-to-static, -WarningsAsErrors: '' + -readability-function-cognitive-complexity, + -readability-identifier-length, + -readability-magic-numbers, +WarningsAsErrors: '*' HeaderFilterRegex: '' -AnalyzeTemporaryDtors: false FormatStyle: file CheckOptions: - cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: '1' - modernize-use-nullptr.NullMacros: 'NULL' \ No newline at end of file + - key: 'modernize-use-nullptr.NullMacros' + value: '1' + - key: 'misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic' + value: '1' + - key: 'readability-redundant-access-specifiers.CheckFirstDeclaration' + value: '1' diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..f97e60d --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,33 @@ +name: clang-format + +on: + pull_request: + paths: + - '.github/workflows/clang-format.yml' + - 'cmake/**' + - 'src/**' + - 'vendor/**' + - '**/CMakeLists.txt' + push: + branches: + - master + paths: + - '.github/workflows/clang-format.yml' + - 'cmake/**' + - 'src/**' + - 'vendor/**' + - '**/CMakeLists.txt' + +jobs: + test_cxx: + name: Run + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + + - uses: jidicula/clang-format-action@v4.14.0 + with: + clang-format-version: '19' + check-path: 'src/' diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml new file mode 100644 index 0000000..392ded1 --- /dev/null +++ b/.github/workflows/clang-tidy.yml @@ -0,0 +1,66 @@ +name: clang-tidy + +on: + pull_request: + paths: + - '.github/workflows/clang-tidy.yml' + - 'cmake/**' + - 'src/**' + - 'vendor/**' + - '**/CMakeLists.txt' + push: + branches: + - master + paths: + - '.github/workflows/clang-tidy.yml' + - 'cmake/**' + - 'src/**' + - 'vendor/**' + - '**/CMakeLists.txt' + +jobs: + test_cxx: + name: Run + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + + - name: Setup clang and libc++ + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 19 + sudo apt-get install -yq --no-install-recommends libc++-19-dev libc++abi-19-dev clang-tidy-19 + echo "CC=clang-19" >> $GITHUB_ENV + echo "CXX=clang++-19" >> $GITHUB_ENV + + - name: Generate build + run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DOBFUSCATOR_BUILD_TESTS=ON -DCMKR_SKIP_GENERATION=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Adjust compile commands + run: python3 scripts/adjust_compile_commands.py build/compile_commands.json + + - name: Adjust dependencies' .clang-tidy + run: | + echo "Checks: '-*'" > build/.clang-tidy + echo "Checks: '-*'" > vendor/zasm/.clang-tidy + + - name: Run clang tidy + run: | + wget https://raw.githubusercontent.com/llvm/llvm-project/refs/heads/release/19.x/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py + python3 run-clang-tidy.py \ + -clang-tidy-binary clang-tidy-19 \ + -p build \ + -j 12 \ + -extra-arg="-std=c++23" \ + -extra-arg="-stdlib=libc++" \ + -header-filter="src/lib|src/bin|src/tests" \ + -q \ + -allow-no-checks diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..add9bcd --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,80 @@ +name: Tests + +on: + pull_request: + paths: + - '.github/workflows/tests.yml' + - 'cmake/**' + - 'src/**' + - 'vendor/**' + - 'CMakeLists.txt' + push: + branches: + - master + paths: + - '.github/workflows/tests.yml' + - 'cmake/**' + - 'src/**' + - 'vendor/**' + - '**/CMakeLists.txt' + +jobs: + test_cxx: + name: ${{ matrix.os }}, ${{ matrix.compiler }}, ${{ matrix.buildtype }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + compiler: [gcc, clang, msvc] + buildtype: [Debug, Release] + exclude: + # Windows is extremely slow so we will test it only with msvc + - os: windows-latest + compiler: gcc + - os: windows-latest + compiler: clang + # No msvc on ubuntu, duh + - os: ubuntu-latest + compiler: msvc + env: + BUILD_TYPE: ${{ matrix.buildtype }} + steps: + - uses: actions/checkout@v4 + with: + submodules: 'true' + + - name: Setup clang and libc++ + if: contains(matrix.os, 'ubuntu') && matrix.compiler == 'clang' + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 19 + sudo apt-get install -yq --no-install-recommends libc++-19-dev libc++abi-19-dev + echo "CC=clang-19" >> $GITHUB_ENV + echo "CXX=clang++-19" >> $GITHUB_ENV + + - name: Setup gcc and libstdc++ + if: contains(matrix.os, 'ubuntu') && matrix.compiler == 'gcc' + run: | + sudo add-apt-repository ppa:ubuntu-toolchain-r/test + sudo add-apt-repository ppa:apt-fast/stable + sudo apt-get update + sudo apt-get install -yq --no-install-recommends apt-fast + sudo apt-fast install -yq --no-install-recommends gcc-14 g++-14 libstdc++-14-dev + echo "CC=gcc-14" >> $GITHUB_ENV + echo "CXX=g++-14" >> $GITHUB_ENV + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DOBFUSCATOR_BUILD_TESTS=ON -DCMKR_SKIP_GENERATION=ON + + - name: Build + run: cmake --build build --config ${{env.BUILD_TYPE}} --parallel + + - name: Test (Unix) + if: contains(matrix.os, 'ubuntu') + run: ./build/src/obfuscator-tests + + - name: Test (Windows) + if: contains(matrix.os, 'windows') + run: .\build\src\${{ env.BUILD_TYPE }}\obfuscator-tests.exe diff --git a/.gitignore b/.gitignore index d89fb1c..9a2ad01 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ samples/ # infer infer-out/** cmake/infer-build + +# clang tidy +__clang_tidy_build/ diff --git a/.gitmodules b/.gitmodules index 997c73d..1cedb45 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,9 @@ [submodule "vendor/gtest"] path = vendor/gtest url = https://github.com/google/googletest +[submodule "vendor/LLVMDemangle"] + path = vendor/LLVMDemangle + url = https://github.com/es3n1n/LLVMDemangle.git +[submodule "vendor/common"] + path = vendor/common + url = https://github.com/es3n1n/common diff --git a/CMakeLists.txt b/CMakeLists.txt index a235a19..121c0fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,3 +56,21 @@ endif() add_subdirectory(src) set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER}) +function(get_all_targets var) + set(targets) + get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR}) + set(${var} ${targets} PARENT_SCOPE) +endfunction() + +macro(get_all_targets_recursive targets dir) + get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES) + foreach(subdir ${subdirectories}) + get_all_targets_recursive(${targets} ${subdir}) + endforeach() + + get_property(current_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS) + list(APPEND ${targets} ${current_targets}) +endmacro() + +get_all_targets(all_targets) +set_target_properties(${all_targets} PROPERTIES VS_GLOBAL_VcpkgEnabled false) diff --git a/README.md b/README.md index 6891b38..5bea4f7 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,19 @@ Available options: -t [name] -- Start new transform configuration -g [name] -- Start new transform global configuration -v [name] [value] -- Push value + -seed [value] -- Set random seed Examples: obfuscator hehe.exe -f main -t TransformName -v SomeName 1337 obfuscator hehe.exe -f main -t TransformName -v SomeName 1337 -g TransformName -v SomeGlobalName 1337 obfuscator hehe.exe -f main -t TransformName -v SomeName 1337 -v SomeName0 1337 -g TransformName -v SomeGlobalName 1337 obfuscator hehe.exe -map mymap.map -pdb mypdb.pdb -f main -t TransformName -v SomeName 1337 -v SomeName0 1337 -g TransformName -v SomeGlobalName 1337 + obfuscator hehe.exe -map mymap.map -pdb mypdb.pdb -f main -seed 0xcb91ccbef7cbcdc1 +``` + +In case of unexpected exit without any error message or in case you feel lucky, try adjusting the chances. E.g.: +```commandline +obfuscator hehe.exe -pdb hehe.pdb -f main -t ConstantCrypt -v chance 55 ``` ## Writeup diff --git a/cmake.toml b/cmake.toml index 00b2a30..56496c6 100644 --- a/cmake.toml +++ b/cmake.toml @@ -15,3 +15,25 @@ build-tests = "OBFUSCATOR_BUILD_TESTS" [subdir.vendor] [subdir.src] + +# Ugly fix for Zydis collisions :/ +cmake-after = """ +function(get_all_targets var) + set(targets) + get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR}) + set(${var} ${targets} PARENT_SCOPE) +endfunction() + +macro(get_all_targets_recursive targets dir) + get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES) + foreach(subdir ${subdirectories}) + get_all_targets_recursive(${targets} ${subdir}) + endforeach() + + get_property(current_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS) + list(APPEND ${targets} ${current_targets}) +endmacro() + +get_all_targets(all_targets) +set_target_properties(${all_targets} PROPERTIES VS_GLOBAL_VcpkgEnabled false) +""" diff --git a/cmake/cmkr.cmake b/cmake/cmkr.cmake index e17d03a..f235dd3 100644 --- a/cmake/cmkr.cmake +++ b/cmake/cmkr.cmake @@ -2,7 +2,7 @@ include_guard() # Change these defaults to point to your infrastructure if desired set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE) -set(CMKR_TAG "v0.2.24" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) +set(CMKR_TAG "v0.2.26" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE) # To bootstrap/generate a cmkr project: cmake -P cmkr.cmake diff --git a/scripts/adjust_compile_commands.py b/scripts/adjust_compile_commands.py new file mode 100644 index 0000000..c260752 --- /dev/null +++ b/scripts/adjust_compile_commands.py @@ -0,0 +1,19 @@ +import json +from sys import argv + + +if len(argv) <= 1: + exit(1) + + +with open(argv[1], 'r') as f: + data = json.load(f) + +data = [ + item + for item in data + if not item['output'].startswith('_deps/') +] + +with open(argv[1], 'w') as f: + json.dump(data, f) diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100644 index 0000000..4fa41b4 --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +cmake -S . -B __clang_tidy_build -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DCMAKE_EXPORT_COMPILE_COMMANDS=1 +python3 scripts/run_clang_tidy.py -clang-tidy-binary clang-tidy-17 -p __clang_tidy_build -j 25 -extra-arg="-std=c++23" -extra-arg="-stdlib=libc++" -q diff --git a/scripts/opaque_predicates_expr_gen/main.py b/scripts/opaque_predicates_expr_gen/main.py index b654820..48d09f7 100644 --- a/scripts/opaque_predicates_expr_gen/main.py +++ b/scripts/opaque_predicates_expr_gen/main.py @@ -100,10 +100,10 @@ def generate_all(num: int = 3) -> None: x1, x2 = BitVecs('x1 x2', sz) s.add(x1 != x2) - s.add(cur >= 0, cur <= 40960) + # s.add(cur >= 0, cur <= 40960) expr1 = substitute(cur, (startv, x1,)) - expr2 = substitute(cur, (startv, x1,)) + expr2 = substitute(cur, (startv, x2,)) if s.check(expr1 != expr2) != unsat: continue diff --git a/scripts/run_clang_tidy.py b/scripts/run_clang_tidy.py new file mode 100644 index 0000000..a5a4269 --- /dev/null +++ b/scripts/run_clang_tidy.py @@ -0,0 +1,519 @@ +#!/usr/bin/env python3 +# +# ===- run-clang-tidy.py - Parallel clang-tidy runner --------*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===-----------------------------------------------------------------------===# +# FIXME: Integrate with clang-tidy-diff.py + + +""" +Parallel clang-tidy runner +========================== + +Runs clang-tidy over all files in a compilation database. Requires clang-tidy +and clang-apply-replacements in $PATH. + +Example invocations. +- Run clang-tidy on all files in the current working directory with a default + set of checks and show warnings in the cpp files and all project headers. + run-clang-tidy.py $PWD + +- Fix all header guards. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard + +- Fix all header guards included from clang-tidy and header guards + for clang-tidy headers. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \ + -header-filter=extra/clang-tidy + +Compilation database setup: +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +""" + +from __future__ import print_function + +import argparse +import glob +import json +import multiprocessing +import os +import queue +import re +import shutil +import subprocess +import sys +import tempfile +import threading +import traceback + +try: + import yaml +except ImportError: + yaml = None + + +def strtobool(val): + """Convert a string representation of truth to a bool following LLVM's CLI argument parsing.""" + + val = val.lower() + if val in ["", "true", "1"]: + return True + elif val in ["false", "0"]: + return False + + # Return ArgumentTypeError so that argparse does not substitute its own error message + raise argparse.ArgumentTypeError( + "'{}' is invalid value for boolean argument! Try 0 or 1.".format(val) + ) + + +def find_compilation_database(path): + """Adjusts the directory until a compilation database is found.""" + result = os.path.realpath("./") + while not os.path.isfile(os.path.join(result, path)): + parent = os.path.dirname(result) + if result == parent: + print("Error: could not find compilation database.") + sys.exit(1) + result = parent + return result + + +def make_absolute(f, directory): + if os.path.isabs(f): + return f + return os.path.normpath(os.path.join(directory, f)) + + +def get_tidy_invocation( + f, + clang_tidy_binary, + checks, + tmpdir, + build_path, + header_filter, + allow_enabling_alpha_checkers, + extra_arg, + extra_arg_before, + quiet, + config_file_path, + config, + line_filter, + use_color, + plugins, + warnings_as_errors, +): + """Gets a command line for clang-tidy.""" + start = [clang_tidy_binary] + if allow_enabling_alpha_checkers: + start.append("-allow-enabling-analyzer-alpha-checkers") + if header_filter is not None: + start.append("-header-filter=" + header_filter) + if line_filter is not None: + start.append("-line-filter=" + line_filter) + if use_color is not None: + if use_color: + start.append("--use-color") + else: + start.append("--use-color=false") + if checks: + start.append("-checks=" + checks) + if tmpdir is not None: + start.append("-export-fixes") + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, name) = tempfile.mkstemp(suffix=".yaml", dir=tmpdir) + os.close(handle) + start.append(name) + for arg in extra_arg: + start.append("-extra-arg=%s" % arg) + for arg in extra_arg_before: + start.append("-extra-arg-before=%s" % arg) + start.append("-p=" + build_path) + if quiet: + start.append("-quiet") + if config_file_path: + start.append("--config-file=" + config_file_path) + elif config: + start.append("-config=" + config) + for plugin in plugins: + start.append("-load=" + plugin) + if warnings_as_errors: + start.append("--warnings-as-errors=" + warnings_as_errors) + start.append(f) + return start + + +def merge_replacement_files(tmpdir, mergefile): + """Merge all replacement files in a directory into a single file""" + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey = "Diagnostics" + merged = [] + for replacefile in glob.iglob(os.path.join(tmpdir, "*.yaml")): + content = yaml.safe_load(open(replacefile, "r")) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = {"MainSourceFile": "", mergekey: merged} + with open(mergefile, "w") as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, "w").close() + + +def find_binary(arg, name, build_path): + """Get the path for a binary or exit""" + if arg: + if shutil.which(arg): + return arg + else: + raise SystemExit( + "error: passed binary '{}' was not found or is not executable".format( + arg + ) + ) + + built_path = os.path.join(build_path, "bin", name) + binary = shutil.which(name) or shutil.which(built_path) + if binary: + return binary + else: + raise SystemExit( + "error: failed to find {} in $PATH or at {}".format(name, built_path) + ) + + +def apply_fixes(args, clang_apply_replacements_binary, tmpdir): + """Calls clang-apply-fixes on a given directory.""" + invocation = [clang_apply_replacements_binary] + invocation.append("-ignore-insert-conflict") + if args.format: + invocation.append("-format") + if args.style: + invocation.append("-style=" + args.style) + invocation.append(tmpdir) + subprocess.call(invocation) + + +def run_tidy(args, clang_tidy_binary, tmpdir, build_path, queue, lock, failed_files): + """Takes filenames out of queue and runs clang-tidy on them.""" + while True: + name = queue.get() + invocation = get_tidy_invocation( + name, + clang_tidy_binary, + args.checks, + tmpdir, + build_path, + args.header_filter, + args.allow_enabling_alpha_checkers, + args.extra_arg, + args.extra_arg_before, + args.quiet, + args.config_file, + args.config, + args.line_filter, + args.use_color, + args.plugins, + args.warnings_as_errors, + ) + + proc = subprocess.Popen( + invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + output, err = proc.communicate() + + has_failed: bool = False + + if proc.returncode != 0: + if proc.returncode < 0: + msg = "%s: terminated by signal %d\n" % (name, -proc.returncode) + err += msg.encode("utf-8") + has_failed = True + + with lock: + full_output = (err or b'') + b'\n' + output + for line in full_output.decode("utf-8").split("\n"): + if ("warnings generated." in line and (len(line) - len("warnings generated.")) <= 8) or line in ["", " "]: + continue + if "no checks enabled" in line: + has_failed = False + break + + has_failed = True + sys.stderr.write(line + '\n') + + if has_failed: + failed_files.append(name) + + queue.task_done() + + +def main(): + parser = argparse.ArgumentParser( + description="Runs clang-tidy over all files " + "in a compilation database. Requires " + "clang-tidy and clang-apply-replacements in " + "$PATH or in your build directory." + ) + parser.add_argument( + "-allow-enabling-alpha-checkers", + action="store_true", + help="allow alpha checkers from " "clang-analyzer.", + ) + parser.add_argument( + "-clang-tidy-binary", metavar="PATH", help="path to clang-tidy binary" + ) + parser.add_argument( + "-clang-apply-replacements-binary", + metavar="PATH", + help="path to clang-apply-replacements binary", + ) + parser.add_argument( + "-checks", + default=None, + help="checks filter, when not specified, use clang-tidy " "default", + ) + config_group = parser.add_mutually_exclusive_group() + config_group.add_argument( + "-config", + default=None, + help="Specifies a configuration in YAML/JSON format: " + " -config=\"{Checks: '*', " + ' CheckOptions: {x: y}}" ' + "When the value is empty, clang-tidy will " + "attempt to find a file named .clang-tidy for " + "each source file in its parent directories.", + ) + config_group.add_argument( + "-config-file", + default=None, + help="Specify the path of .clang-tidy or custom config " + "file: e.g. -config-file=/some/path/myTidyConfigFile. " + "This option internally works exactly the same way as " + "-config option after reading specified config file. " + "Use either -config-file or -config, not both.", + ) + parser.add_argument( + "-header-filter", + default=None, + help="regular expression matching the names of the " + "headers to output diagnostics from. Diagnostics from " + "the main file of each translation unit are always " + "displayed.", + ) + parser.add_argument( + "-line-filter", + default=None, + help="List of files with line ranges to filter the" "warnings.", + ) + if yaml: + parser.add_argument( + "-export-fixes", + metavar="filename", + dest="export_fixes", + help="Create a yaml file to store suggested fixes in, " + "which can be applied with clang-apply-replacements.", + ) + parser.add_argument( + "-j", + type=int, + default=0, + help="number of tidy instances to be run in parallel.", + ) + parser.add_argument( + "files", nargs="*", default=[".*"], help="files to be processed (regex on path)" + ) + parser.add_argument("-fix", action="store_true", help="apply fix-its") + parser.add_argument( + "-format", action="store_true", help="Reformat code " "after applying fixes" + ) + parser.add_argument( + "-style", + default="file", + help="The style of reformat " "code after applying fixes", + ) + parser.add_argument( + "-use-color", + type=strtobool, + nargs="?", + const=True, + help="Use colors in diagnostics, overriding clang-tidy's" + " default behavior. This option overrides the 'UseColor" + "' option in .clang-tidy file, if any.", + ) + parser.add_argument( + "-p", dest="build_path", help="Path used to read a compile command database." + ) + parser.add_argument( + "-extra-arg", + dest="extra_arg", + action="append", + default=[], + help="Additional argument to append to the compiler " "command line.", + ) + parser.add_argument( + "-extra-arg-before", + dest="extra_arg_before", + action="append", + default=[], + help="Additional argument to prepend to the compiler " "command line.", + ) + parser.add_argument( + "-quiet", action="store_true", help="Run clang-tidy in quiet mode" + ) + parser.add_argument( + "-load", + dest="plugins", + action="append", + default=[], + help="Load the specified plugin in clang-tidy.", + ) + parser.add_argument( + "-warnings-as-errors", + default=None, + help="Upgrades warnings to errors. Same format as " "'-checks'", + ) + args = parser.parse_args() + + db_path = "compile_commands.json" + + if args.build_path is not None: + build_path = args.build_path + else: + # Find our database + build_path = find_compilation_database(db_path) + + clang_tidy_binary = find_binary(args.clang_tidy_binary, "clang-tidy", build_path) + + tmpdir = None + if args.fix: + clang_apply_replacements_binary = find_binary( + args.clang_apply_replacements_binary, "clang-apply-replacements", build_path + ) + tmpdir = tempfile.mkdtemp() + + try: + invocation = get_tidy_invocation( + "", + clang_tidy_binary, + args.checks, + None, + build_path, + args.header_filter, + args.allow_enabling_alpha_checkers, + args.extra_arg, + args.extra_arg_before, + args.quiet, + args.config_file, + args.config, + args.line_filter, + args.use_color, + args.plugins, + args.warnings_as_errors, + ) + invocation.append("-list-checks") + invocation.append("-") + if args.quiet: + # Even with -quiet we still want to check if we can call clang-tidy. + with open(os.devnull, "w") as dev_null: + subprocess.check_call(invocation, stdout=dev_null) + else: + subprocess.check_call(invocation) + except: + print("Unable to run clang-tidy.", file=sys.stderr) + sys.exit(1) + + # Load the database and extract all files. + database = json.load(open(os.path.join(build_path, db_path))) + files = set( + [make_absolute(entry["file"], entry["directory"]) for entry in database] + ) + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + # Build up a big regexy filter from all command line arguments. + file_name_re = re.compile("|".join(args.files)) + + return_code = 0 + try: + # Spin up a bunch of tidy-launching threads. + task_queue = queue.Queue(max_task) + # List of files with a non-zero return code. + failed_files = [] + lock = threading.Lock() + for _ in range(max_task): + t = threading.Thread( + target=run_tidy, + args=( + args, + clang_tidy_binary, + tmpdir, + build_path, + task_queue, + lock, + failed_files, + ), + ) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + is_dep = '_deps' in name + is_vendor_code = 'vendor' in name + if file_name_re.search(name) and not is_dep and not is_vendor_code: + task_queue.put(name) + + # Wait for all threads to be done. + task_queue.join() + if len(failed_files): + return_code = 1 + + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print("\nCtrl-C detected, goodbye.") + if tmpdir: + shutil.rmtree(tmpdir) + os.kill(0, 9) + + if yaml and args.export_fixes: + print("Writing fixes to " + args.export_fixes + " ...") + try: + merge_replacement_files(tmpdir, args.export_fixes) + except: + print("Error exporting fixes.\n", file=sys.stderr) + traceback.print_exc() + return_code = 1 + + if args.fix: + print("Applying fixes ...") + try: + apply_fixes(args, clang_apply_replacements_binary, tmpdir) + except: + print("Error applying fixes.\n", file=sys.stderr) + traceback.print_exc() + return_code = 1 + + if tmpdir: + shutil.rmtree(tmpdir) + sys.exit(return_code) + + +if __name__ == "__main__": + main() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 42d3cd6..9ba10f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,24 @@ if(CMKR_ROOT_PROJECT) configure_file(cmake.toml cmake.toml COPYONLY) endif() +# Target: obfuscator-project +add_library(obfuscator-project INTERFACE) + +target_compile_definitions(obfuscator-project INTERFACE + NOMINMAX +) + +target_compile_features(obfuscator-project INTERFACE + cxx_std_23 +) + +if(MSVC) # msvc + target_compile_options(obfuscator-project INTERFACE + "/wd4661" + "/MP" + ) +endif() + # Target: obfuscator-lib set(obfuscator-lib_SOURCES "lib/analysis/analysis.cpp" @@ -30,6 +48,7 @@ set(obfuscator-lib_SOURCES "lib/obfuscator/transforms/startup.cpp" "lib/pe/pe.cpp" "lib/pe/rebuilder/detail/copy_sections.cpp" + "lib/pe/rebuilder/detail/erase_metadata.cpp" "lib/pe/rebuilder/detail/init_header.cpp" "lib/pe/rebuilder/detail/update_checksum.cpp" "lib/pe/rebuilder/detail/update_relocations.cpp" @@ -59,6 +78,7 @@ set(obfuscator-lib_SOURCES "lib/easm/misc/reg_convert.hpp" "lib/func_parser/common/combiner.hpp" "lib/func_parser/common/common.hpp" + "lib/func_parser/common/demangler.hpp" "lib/func_parser/common/sanitizer.hpp" "lib/func_parser/map/map.hpp" "lib/func_parser/parser.hpp" @@ -90,48 +110,101 @@ set(obfuscator-lib_SOURCES "lib/pe/pe.hpp" "lib/pe/rebuilder/detail/common.hpp" "lib/pe/rebuilder/rebuilder.hpp" - "lib/util/defer.hpp" - "lib/util/files.hpp" "lib/util/format.hpp" "lib/util/iterators.hpp" - "lib/util/logger.hpp" - "lib/util/memory/address.hpp" - "lib/util/memory/casts.hpp" - "lib/util/memory/reader.hpp" "lib/util/passes.hpp" - "lib/util/platform.hpp" - "lib/util/progress.hpp" - "lib/util/random.hpp" "lib/util/sections.hpp" - "lib/util/stopwatch.hpp" - "lib/util/string_parser.hpp" "lib/util/structs.hpp" "lib/util/types.hpp" + cmake.toml ) -add_library(obfuscator-lib INTERFACE) +add_library(obfuscator-lib) -target_sources(obfuscator-lib INTERFACE ${obfuscator-lib_SOURCES}) +target_sources(obfuscator-lib PRIVATE ${obfuscator-lib_SOURCES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${obfuscator-lib_SOURCES}) add_library(obfuscator::lib ALIAS obfuscator-lib) -target_compile_features(obfuscator-lib INTERFACE - cxx_std_23 -) - -target_include_directories(obfuscator-lib INTERFACE +target_include_directories(obfuscator-lib PUBLIC "lib/" ) -target_link_libraries(obfuscator-lib INTERFACE +target_link_libraries(obfuscator-lib PUBLIC + obfuscator-project zasm linux-pe magic_enum + LLVMDemangle + es3n1n::common ) # Target: obfuscator set(obfuscator_SOURCES "bin/entry.cpp" + "lib/analysis/analysis.hpp" + "lib/analysis/bb_decomp/bb_decomp.hpp" + "lib/analysis/common/common.hpp" + "lib/analysis/common/debug.hpp" + "lib/analysis/common/provider.hpp" + "lib/analysis/lru_reg/lru_reg.hpp" + "lib/analysis/observer/observer.hpp" + "lib/analysis/passes/collect_img_references.hpp" + "lib/analysis/passes/collect_lookup_table.hpp" + "lib/analysis/passes/label_references.hpp" + "lib/analysis/passes/lru_reg.hpp" + "lib/analysis/passes/misc/bb_insn_passes.hpp" + "lib/analysis/passes/reloc_marker.hpp" + "lib/analysis/var_alloc/var_alloc.hpp" + "lib/cli/cli.hpp" + "lib/config_parser/config_parser.hpp" + "lib/config_parser/structs.hpp" + "lib/easm/assembler/assembler.hpp" + "lib/easm/cursor/cursor.hpp" + "lib/easm/debug/debug.hpp" + "lib/easm/disassembler/disassembler.hpp" + "lib/easm/easm.hpp" + "lib/easm/misc/misc.hpp" + "lib/easm/misc/reg_convert.hpp" + "lib/func_parser/common/combiner.hpp" + "lib/func_parser/common/common.hpp" + "lib/func_parser/common/demangler.hpp" + "lib/func_parser/common/sanitizer.hpp" + "lib/func_parser/map/map.hpp" + "lib/func_parser/parser.hpp" + "lib/func_parser/pdb/detail/parser_v7.hpp" + "lib/func_parser/pdb/detail/structs.hpp" + "lib/func_parser/pdb/pdb.hpp" + "lib/mathop/mathop.hpp" + "lib/mathop/operations/impl/util.hpp" + "lib/mathop/operations/operation.hpp" + "lib/mathop/operations/operations.hpp" + "lib/obfuscator/config_merger/config_merger.hpp" + "lib/obfuscator/function.hpp" + "lib/obfuscator/obfuscator.hpp" + "lib/obfuscator/transforms/configs.hpp" + "lib/obfuscator/transforms/scheduler.hpp" + "lib/obfuscator/transforms/transform.hpp" + "lib/obfuscator/transforms/transforms/bogus_control_flow.hpp" + "lib/obfuscator/transforms/transforms/constant_crypt.hpp" + "lib/obfuscator/transforms/transforms/decomp_break.hpp" + "lib/obfuscator/transforms/transforms/substitution.hpp" + "lib/obfuscator/transforms/transforms/util/anti_decompilers.hpp" + "lib/obfuscator/transforms/transforms/util/bcf.hpp" + "lib/obfuscator/transforms/transforms/util/opaque_predicates.hpp" + "lib/obfuscator/transforms/types.hpp" + "lib/pe/arch/arch.hpp" + "lib/pe/common/common.hpp" + "lib/pe/common/types.hpp" + "lib/pe/debug/debug.hpp" + "lib/pe/pe.hpp" + "lib/pe/rebuilder/detail/common.hpp" + "lib/pe/rebuilder/rebuilder.hpp" + "lib/util/format.hpp" + "lib/util/iterators.hpp" + "lib/util/passes.hpp" + "lib/util/sections.hpp" + "lib/util/structs.hpp" + "lib/util/types.hpp" cmake.toml ) @@ -140,39 +213,12 @@ add_executable(obfuscator) target_sources(obfuscator PRIVATE ${obfuscator_SOURCES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${obfuscator_SOURCES}) -target_compile_definitions(obfuscator PRIVATE - NOMINMAX -) - -target_compile_features(obfuscator PRIVATE - cxx_std_23 -) - -if(UNIX) # unix - target_compile_options(obfuscator PRIVATE - -stdlib=libc++ - ) -endif() - -if(MSVC) # msvc - target_compile_options(obfuscator PRIVATE - "/wd4661" - "/MP" - ) -endif() - -if(UNIX) # unix - target_link_options(obfuscator PRIVATE - -fuse-ld=lld - "-Wl,-L/usr/local/lib/" - ) -endif() - target_include_directories(obfuscator PRIVATE "bin/" ) target_link_libraries(obfuscator PRIVATE + obfuscator-project obfuscator::lib ) @@ -184,14 +230,73 @@ endif() # Target: obfuscator-tests if(OBFUSCATOR_BUILD_TESTS) # build-tests set(obfuscator-tests_SOURCES - "tests/analysis/bb_decomp/bb_decomp.llvm.cpp" - "tests/analysis/bb_decomp/bb_decomp.msvc.cpp" - "tests/func_parser/map/map.ida.cpp" - "tests/func_parser/map/map.llvm.cpp" - "tests/func_parser/map/map.msvc.cpp" - "tests/func_parser/pdb/pdb.llvm.cpp" - "tests/func_parser/pdb/pdb.msvc.cpp" - "tests/tests_util.hpp" + "tests/func_parser/map/test.map.compilers.cpp" + "tests/func_parser/map/test.map.ida.cpp" + "tests/func_parser/test.pdb.compilers.cpp" + "lib/analysis/analysis.hpp" + "lib/analysis/bb_decomp/bb_decomp.hpp" + "lib/analysis/common/common.hpp" + "lib/analysis/common/debug.hpp" + "lib/analysis/common/provider.hpp" + "lib/analysis/lru_reg/lru_reg.hpp" + "lib/analysis/observer/observer.hpp" + "lib/analysis/passes/collect_img_references.hpp" + "lib/analysis/passes/collect_lookup_table.hpp" + "lib/analysis/passes/label_references.hpp" + "lib/analysis/passes/lru_reg.hpp" + "lib/analysis/passes/misc/bb_insn_passes.hpp" + "lib/analysis/passes/reloc_marker.hpp" + "lib/analysis/var_alloc/var_alloc.hpp" + "lib/cli/cli.hpp" + "lib/config_parser/config_parser.hpp" + "lib/config_parser/structs.hpp" + "lib/easm/assembler/assembler.hpp" + "lib/easm/cursor/cursor.hpp" + "lib/easm/debug/debug.hpp" + "lib/easm/disassembler/disassembler.hpp" + "lib/easm/easm.hpp" + "lib/easm/misc/misc.hpp" + "lib/easm/misc/reg_convert.hpp" + "lib/func_parser/common/combiner.hpp" + "lib/func_parser/common/common.hpp" + "lib/func_parser/common/demangler.hpp" + "lib/func_parser/common/sanitizer.hpp" + "lib/func_parser/map/map.hpp" + "lib/func_parser/parser.hpp" + "lib/func_parser/pdb/detail/parser_v7.hpp" + "lib/func_parser/pdb/detail/structs.hpp" + "lib/func_parser/pdb/pdb.hpp" + "lib/mathop/mathop.hpp" + "lib/mathop/operations/impl/util.hpp" + "lib/mathop/operations/operation.hpp" + "lib/mathop/operations/operations.hpp" + "lib/obfuscator/config_merger/config_merger.hpp" + "lib/obfuscator/function.hpp" + "lib/obfuscator/obfuscator.hpp" + "lib/obfuscator/transforms/configs.hpp" + "lib/obfuscator/transforms/scheduler.hpp" + "lib/obfuscator/transforms/transform.hpp" + "lib/obfuscator/transforms/transforms/bogus_control_flow.hpp" + "lib/obfuscator/transforms/transforms/constant_crypt.hpp" + "lib/obfuscator/transforms/transforms/decomp_break.hpp" + "lib/obfuscator/transforms/transforms/substitution.hpp" + "lib/obfuscator/transforms/transforms/util/anti_decompilers.hpp" + "lib/obfuscator/transforms/transforms/util/bcf.hpp" + "lib/obfuscator/transforms/transforms/util/opaque_predicates.hpp" + "lib/obfuscator/transforms/types.hpp" + "lib/pe/arch/arch.hpp" + "lib/pe/common/common.hpp" + "lib/pe/common/types.hpp" + "lib/pe/debug/debug.hpp" + "lib/pe/pe.hpp" + "lib/pe/rebuilder/detail/common.hpp" + "lib/pe/rebuilder/rebuilder.hpp" + "lib/util/format.hpp" + "lib/util/iterators.hpp" + "lib/util/passes.hpp" + "lib/util/sections.hpp" + "lib/util/structs.hpp" + "lib/util/types.hpp" cmake.toml ) @@ -200,39 +305,12 @@ if(OBFUSCATOR_BUILD_TESTS) # build-tests target_sources(obfuscator-tests PRIVATE ${obfuscator-tests_SOURCES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${obfuscator-tests_SOURCES}) - target_compile_definitions(obfuscator-tests PRIVATE - NOMINMAX - ) - - target_compile_features(obfuscator-tests PRIVATE - cxx_std_23 - ) - - if(UNIX) # unix - target_compile_options(obfuscator-tests PRIVATE - -stdlib=libc++ - ) - endif() - - if(MSVC) # msvc - target_compile_options(obfuscator-tests PRIVATE - "/wd4661" - "/MP" - ) - endif() - - if(UNIX) # unix - target_link_options(obfuscator-tests PRIVATE - -fuse-ld=lld - "-Wl,-L/usr/local/lib/" - ) - endif() - target_include_directories(obfuscator-tests PRIVATE "tests/" ) target_link_libraries(obfuscator-tests PRIVATE + obfuscator-project obfuscator::lib GTest::gtest_main ) diff --git a/src/bin/entry.cpp b/src/bin/entry.cpp index fd5c023..9d217e3 100644 --- a/src/bin/entry.cpp +++ b/src/bin/entry.cpp @@ -3,9 +3,9 @@ #include "obfuscator/transforms/scheduler.hpp" #include "pe/arch/arch.hpp" #include "pe/common/common.hpp" -#include "util/files.hpp" -#include "util/logger.hpp" -#include "util/random.hpp" +#include +#include +#include namespace { template @@ -13,32 +13,23 @@ namespace { pe::Image image(raw_image); obfuscator::Instance inst(&image, config); - inst.setup(); - inst.obfuscate(); - inst.assemble(); - inst.save(); + inst.run(); logger::info("startup: bye-bye"); } - int startup(const int argc, char* argv[]) try { - rnd::detail::seed(); - obfuscator::startup_scheduler(); - - auto config = config_parser::from_argv(argc, argv); - - const auto binary_path = config.obfuscator_config().binary_path; + int startup(config_parser::Config& config) try { + rnd::detail::seed(config.obfuscator_config().seed); + const auto& binary_path = config.obfuscator_config().binary_path; logger::info("main: loading binary from {}", binary_path.string()); - auto file = util::read_file(binary_path); - if (file.empty()) { + auto file = files::read_file(binary_path); + if (!file.has_value() || file->empty()) { throw std::runtime_error("Got empty binary"); } - // NOLINTNEXTLINE - auto* img_x64 = reinterpret_cast(file.data()); - // NOLINTNEXTLINE - auto* img_x86 = reinterpret_cast(file.data()); + auto* img_x64 = reinterpret_cast(file->data()); + auto* img_x86 = reinterpret_cast(img_x64); if (!pe::common::is_valid(img_x64)) { throw std::runtime_error("Invalid pe header"); @@ -57,6 +48,12 @@ namespace { } } // namespace -int main(const int argc, char* argv[]) { - return startup(argc, argv); +int main(const int argc, const char* argv[]) try { + obfuscator::startup_scheduler(); + + auto config = config_parser::from_argv(argc, argv); + return startup(config); +} catch (...) { + logger::critical("Unknown runtime error"); + return 1; } diff --git a/src/cmake.toml b/src/cmake.toml index e95d286..42bd2b7 100644 --- a/src/cmake.toml +++ b/src/cmake.toml @@ -1,51 +1,43 @@ -[template.obfuscator-executable] -type = "executable" +[target.obfuscator-project] +type = "interface" compile-features = ["cxx_std_23"] compile-definitions = ["NOMINMAX"] -unix.compile-options = [ - "-stdlib=libc++", - # "-Wall", "-Wextra", "-Wpedantic", "-Wno-nested-anon-types", "-Wno-gnu-anonymous-struct", "-Wno-unused-function", -] -unix.link-options = ["-fuse-ld=lld", "-Wl,-L/usr/local/lib/"] msvc.compile-options = ["/wd4661", "/MP"] - [target.obfuscator-lib] alias = "obfuscator::lib" -type = "interface" -compile-features = ["cxx_std_23"] +type = "library" include-directories = ["lib/"] sources = ["lib/**.cpp", "lib/**.hpp"] link-libraries = [ + "obfuscator-project", "zasm", "linux-pe", "magic_enum", + "LLVMDemangle", + "es3n1n::common", ] - [target.obfuscator] -type = "obfuscator-executable" -sources = ["bin/**.cpp", "bin/**.hpp"] +type = "executable" +sources = ["bin/**.cpp", "lib/**.hpp"] include-directories = ["bin/"] -link-libraries = ["obfuscator::lib"] - +link-libraries = ["obfuscator-project", "obfuscator::lib"] [target.obfuscator-tests] condition = "build-tests" -type = "obfuscator-executable" -sources = ["tests/**.cpp", "tests/**.hpp"] +type = "executable" +sources = ["tests/**.cpp", "lib/**.hpp"] include-directories = ["tests/"] -link-libraries = ["obfuscator::lib", "GTest::gtest_main"] +link-libraries = ["obfuscator-project", "obfuscator::lib", "GTest::gtest_main"] cmake-after = """ FetchContent_MakeAvailable(resources) target_compile_definitions(obfuscator-tests PRIVATE OBFUSCATOR_RESOURCES_PATH="${resources_SOURCE_DIR}") """ - [target.obfuscator-tests.properties] PROJECT_LABEL = "tests" - [[test]] condition = "build-tests" name = "tests" diff --git a/src/lib/analysis/analysis.hpp b/src/lib/analysis/analysis.hpp index f17fc12..0a82500 100644 --- a/src/lib/analysis/analysis.hpp +++ b/src/lib/analysis/analysis.hpp @@ -2,6 +2,7 @@ #include "analysis/bb_decomp/bb_decomp.hpp" #include "analysis/common/common.hpp" #include "analysis/lru_reg/lru_reg.hpp" +#include "easm/misc/misc.hpp" #include "func_parser/parser.hpp" #include "observer/observer.hpp" #include "util/types.hpp" @@ -116,17 +117,20 @@ namespace analysis { // A lookup table with key set to insn rva and value is the ptr to insn info, // \fixme: @es3n1n: ptr could be invalid at some point // - std::unordered_map instructions_lookup = {}; + std::unordered_map instructions_lookup; // BB Provider // - std::shared_ptr bb_provider = {}; + std::shared_ptr bb_provider; }; template Function analyse(Img* image, const func_parser::function_t& function) { auto result = Function(image, function); logger::debug("analysis: analysed function {}", function); + if (auto size = result.range.size(); size < easm::kMaxEntryInstructionSize) { + throw std::runtime_error(std::format("analysis: Minimal function size is {} bytes, got {}", easm::kMaxEntryInstructionSize, size)); + } return result; } } // namespace analysis diff --git a/src/lib/analysis/bb_decomp/bb_decomp.cpp b/src/lib/analysis/bb_decomp/bb_decomp.cpp index 1df7e19..bd03f6c 100644 --- a/src/lib/analysis/bb_decomp/bb_decomp.cpp +++ b/src/lib/analysis/bb_decomp/bb_decomp.cpp @@ -1,6 +1,6 @@ #include "analysis/bb_decomp/bb_decomp.hpp" #include "analysis/common/debug.hpp" -#include "util/logger.hpp" +#include namespace analysis::bb_decomp { template @@ -14,8 +14,8 @@ namespace analysis::bb_decomp { const auto img_base = image_->raw_image->get_nt_headers()->optional_header.image_base; // Make successor proxy - bb_provider_->set_va_finder([this, img_base](const rva_t va, const bb_t* callee) { - return make_successor(va - img_base, callee); // + bb_provider_->set_va_finder([this, img_base](const rva_t virt_addr, const bb_t* callee) { + return make_successor(virt_addr - img_base, callee); // }); // Make successor proxy @@ -192,11 +192,11 @@ namespace analysis::bb_decomp { /// Sum stats std::size_t weird_nodes = 0; - for (auto node = program_->getHead(); node != nullptr; node = node->getNext()) { - const auto user_data = node->getUserDataU64(); + for (auto* node = program_->getHead(); node != nullptr; node = node->getNext()) { + auto* const pinsn = node->getUserData(); // Weird, but ok - if (user_data == 0) { + if (pinsn == nullptr) { if (node->holds() || node->holds()) { weird_nodes++; } @@ -205,7 +205,6 @@ namespace analysis::bb_decomp { } // This is kinda unsafe but whatever.. - auto* pinsn = memory::cast(user_data); pinsn->node_ref = node; pinsn->ref = node->getIf(); @@ -225,6 +224,7 @@ namespace analysis::bb_decomp { logger::warn("bb_decomp: got {} outdated nodes while updating refs", insns.size()); for (auto& [insn, bb] : insns) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) std::erase_if(bb->instructions, [insn](const std::shared_ptr& item) -> bool { return item.get() == insn; }); } } @@ -235,7 +235,7 @@ namespace analysis::bb_decomp { /// \note @es3n1n: Looks kinda scary, but splitting 2k+ basic blocks took me ~350ms so /// i guess we'll keep it as it is (PR welcome), perhaps an interval/segment tree could be used here logger::debug("analysis: splitting BBs.."); - bool split_something; + bool split_something; // NOLINT(cppcoreguidelines-init-variables) // Splitting while there's something to split // @@ -353,16 +353,16 @@ namespace analysis::bb_decomp { void Instance::insert_jmps() { logger::debug("bb_decomp: veryfing BB intersections.."); /// Lookup for the basic blocks that for some reason aren't jumping to their successor(s) - for (auto& [rva, bb] : basic_blocks_) { + for (auto& bb : std::views::values(basic_blocks_)) { if (bb->instructions.empty()) [[unlikely]] { continue; } - auto& last_insn = bb->instructions.at(bb->size() - 1); - const auto last_mnemonic = last_insn->ref->getMnemonic().value(); + const auto& last_insn = bb->instructions.at(bb->size() - 1); /// Looks legit, i think? - if (last_mnemonic == ZYDIS_MNEMONIC_JMP || last_mnemonic == ZYDIS_MNEMONIC_RET) { + if (const auto last_mnemonic = last_insn->ref->getMnemonic().value(); + last_mnemonic == ZYDIS_MNEMONIC_JMP || last_mnemonic == ZYDIS_MNEMONIC_RET) { continue; } @@ -385,19 +385,25 @@ namespace analysis::bb_decomp { /// If we didn't find it via CF info, then try to get the first successor if (expected_next_bb == nullptr) { + /// Funny case where the compiler just places a call to an exception without any successors or ret instructions + if (bb->successors.empty()) { + continue; + } + assert(bb->successors.size() == 1); expected_next_bb = bb->successors.at(0).get(); } /// Let's see if we end up on a successor after this node zasm::Node* next_node = last_insn->node_ref->getNext(); - while (next_node != nullptr && !next_node->holds()) + while (next_node != nullptr && !next_node->holds()) { next_node = next_node->getNext(); + } /// If there's no next node, then we totally should insert a jmp if (next_node != nullptr) { /// Get the next instruction analysis info - const auto next_insn = next_node->getUserData(); + const auto* const next_insn = next_node->getUserData(); if (next_insn == nullptr || !next_insn->rva.has_value()) [[unlikely]] { continue; } @@ -494,19 +500,19 @@ namespace analysis::bb_decomp { /// Step 0. Clear all predecessors info /// We cannot do this in the Step 1 - for (auto& bb : std::views::values(basic_blocks_)) { + for (const auto& bb : std::views::values(basic_blocks_)) { bb->predecessors.clear(); } /// Step 1. Updating successors - for (auto& [start, bb] : basic_blocks_) { + for (auto& bb : std::views::values(basic_blocks_)) { /// Remove all the "outdated" info bb->successors.clear(); /// Looking for the dead CF changer refs /// (because since we're splitting them, the dst bb could've been already deleted at some point) for (auto it = bb->instructions.rbegin(); it != bb->instructions.rend(); std::advance(it, 1)) { - const auto insn = *it; + const auto& insn = *it; if (insn->cf.empty()) { continue; } diff --git a/src/lib/analysis/bb_decomp/bb_decomp.hpp b/src/lib/analysis/bb_decomp/bb_decomp.hpp index ad3ea09..5e561b0 100644 --- a/src/lib/analysis/bb_decomp/bb_decomp.hpp +++ b/src/lib/analysis/bb_decomp/bb_decomp.hpp @@ -23,8 +23,8 @@ namespace analysis::bb_decomp { Instance(const Instance& instance) : image_(instance.image_), function_start_(instance.function_start_), function_size_(instance.function_size_), - basic_blocks_(instance.basic_blocks_), program_(std::move(instance.program_)), assembler_(std::move(instance.assembler_)), - decoder_(instance.decoder_), jump_tables_(instance.jump_tables_), bb_provider_(instance.bb_provider_) { } + basic_blocks_(instance.basic_blocks_), program_(instance.program_), assembler_(instance.assembler_), decoder_(instance.decoder_), + jump_tables_(instance.jump_tables_), bb_provider_(instance.bb_provider_) { } void collect(); void split(); @@ -142,16 +142,16 @@ namespace analysis::bb_decomp { rva_t function_start_ = nullptr; std::optional function_size_ = std::nullopt; - std::unordered_map> basic_blocks_ = {}; - std::vector> virtual_basic_blocks_ = {}; // = without the rva + std::unordered_map> basic_blocks_; + std::vector> virtual_basic_blocks_; // = without the rva - std::shared_ptr program_ = {}; - std::shared_ptr assembler_ = {}; + std::shared_ptr program_; + std::shared_ptr assembler_; easm::Decoder decoder_; - std::unordered_map jump_tables_ = {}; + std::unordered_map jump_tables_; - std::shared_ptr bb_provider_ = {}; + std::shared_ptr bb_provider_; }; template diff --git a/src/lib/analysis/bb_decomp/jumptables.cpp b/src/lib/analysis/bb_decomp/jumptables.cpp index cb530f1..3a8145b 100644 --- a/src/lib/analysis/bb_decomp/jumptables.cpp +++ b/src/lib/analysis/bb_decomp/jumptables.cpp @@ -1,7 +1,7 @@ #include "analysis/analysis.hpp" #include "analysis/bb_decomp/bb_decomp.hpp" #include "analysis/common/debug.hpp" -#include "util/logger.hpp" +#include /// \todo @es3n1n: Notify the linker somehow that it should erase jumptable pointers too namespace analysis::bb_decomp { @@ -14,12 +14,12 @@ namespace analysis::bb_decomp { const auto& insn = basic_block->instructions.at(i); /// We aren't interested in the successful estimations of jcc/jmps - if (!(insn->flags & UNABLE_TO_ESTIMATE_JCC)) { + if ((insn->flags & UNABLE_TO_ESTIMATE_JCC) == 0) { continue; } /// OK, we just hit a `jmp/jcc reg`, let's confirm that to be sure - const auto jmp_reg = insn->ref->getOperandIf(0); + auto* const jmp_reg = insn->ref->getOperandIf(0); if (insn->ref->getOperandCount() != 1 || jmp_reg == nullptr) { continue; } @@ -87,14 +87,14 @@ namespace analysis::bb_decomp { } /// Get the dst reg operand - const auto dst_op = prev_insn->ref->getOperandIf(0); - const auto src_op = prev_insn->ref->getOperandIf(1); - if (dst_op == nullptr || src_op == nullptr) { + const auto* const dst_op = prev_insn->ref->getOperandIf(0); + if (auto* const src_op = prev_insn->ref->getOperandIf(1); // + dst_op == nullptr || src_op == nullptr) { return; } /// Get the memory operand from index_load - const auto mem_index_op = (**jump_table.index_load_at)->ref->getOperandIf(1); + const auto* const mem_index_op = (**jump_table.index_load_at)->ref->getOperandIf(1); assert(mem_index_op != nullptr); /// If matches, then yeah we found it @@ -107,8 +107,9 @@ namespace analysis::bb_decomp { match_load_index(); match_base_move(); - if (jump_table.index_load_at.has_value() && jump_table.base_move_at.has_value()) + if (jump_table.index_load_at.has_value() && jump_table.base_move_at.has_value()) { break; + } } /// Something's off @@ -143,7 +144,7 @@ namespace analysis::bb_decomp { for (auto& [rva, info] : jump_tables_) { /// Get the table start auto* table = image_->template rva_to_ptr(rva); - if (!table) { + if (table == nullptr) { throw std::runtime_error("analysis: unable to find the jump table, huh?"); } @@ -186,9 +187,11 @@ namespace analysis::bb_decomp { /// then we should erase all the other stuff between it and the jump. /// Then, we could easily compare this reg value and jump to the bbs. + assert(info.index_load_at.has_value()); + /// Obtain the ptr mov operand - const auto pjmp_reg = (**info.index_load_at)->ref->getOperandIf(0); - const auto pmem_op = (**info.index_load_at)->ref->getOperandIf(1); + const auto* const pjmp_reg = (**info.index_load_at)->ref->getOperandIf(0); + const auto* const pmem_op = (**info.index_load_at)->ref->getOperandIf(1); if (pmem_op == nullptr || pjmp_reg == nullptr) [[unlikely]] { throw std::runtime_error("analysis: unable to obtain mem_op/jmp_reg for jumptable expansion"); } @@ -207,12 +210,12 @@ namespace analysis::bb_decomp { mem_op.setBase(zasm::Reg(zasm::Reg::Id::None)); /// Change the cursor pos - const auto insert_to = (**info.index_load_at)->node_ref->getPrev(); + auto* const insert_to = (**info.index_load_at)->node_ref->getPrev(); assembler_->setCursor(insert_to); /// Erase other nodes for (auto it = *info.index_load_at; it != (*info.bb)->instructions.end(); ++it) { - const auto ptr = it->get(); + auto* const ptr = it->get(); if (!ptr->rva.has_value()) { continue; } @@ -237,7 +240,7 @@ namespace analysis::bb_decomp { /// Emit the lea instead assembler_->lea(jmp_reg, mem_op); - auto last_node = assembler_->getCursor(); + auto* last_node = assembler_->getCursor(); (void)push_last_instruction(*info.bb); /// Current bb that it should treat as predecessor @@ -251,7 +254,7 @@ namespace analysis::bb_decomp { std::shared_ptr ptr = nullptr; zasm::Node* first = nullptr; zasm::Node* last = nullptr; - const std::size_t count = 0; + std::size_t count = 0; }; std::vector new_bbs = {}; @@ -267,7 +270,7 @@ namespace analysis::bb_decomp { /// Compare the index value with current index assembler_->setCursor(last_node); assembler_->cmp(jmp_reg, zasm::Imm16(index * std::max(mem_op.getScale(), static_cast(1)))); - auto first_node = assembler_->getCursor(); + auto* first_node = assembler_->getCursor(); /// JZ assembler_->jz(label); diff --git a/src/lib/analysis/common/common.hpp b/src/lib/analysis/common/common.hpp index 1b8634d..1e0831e 100644 --- a/src/lib/analysis/common/common.hpp +++ b/src/lib/analysis/common/common.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -53,8 +54,8 @@ namespace analysis { // Instruction flags repr // enum e_insn_fl : std::uint8_t { - UNABLE_TO_ESTIMATE_JCC = (1 << 0), - TO_BE_REMOVED = (1 << 1) + UNABLE_TO_ESTIMATE_JCC = (1U << 0U), + TO_BE_REMOVED = (1U << 1U) }; // CPU Flags @@ -132,7 +133,7 @@ namespace analysis { // in the ways that are stored in this vector. // If not set the execution is just linear. // - std::vector cf = {}; + std::vector cf; // Reloc info // @@ -295,11 +296,9 @@ namespace analysis { predecessors.emplace_back(value); } - std::shared_ptr push_label(zasm::Node* label_node_ptr, const bb_provider_t* bb_provider) { - assert(bb_provider != nullptr); - + std::shared_ptr push_label(zasm::Node* label_node_ptr, const bb_provider_t* /*bb_provider*/) { // Acquire label ref - const auto ref = label_node_ptr->getIf(); + auto* const ref = label_node_ptr->getIf(); if (ref == nullptr) { return nullptr; } @@ -325,7 +324,7 @@ namespace analysis { assert(bb_provider != nullptr); /// We are storing only instructions - const auto ref = insn_node_ptr->getIf(); + auto* const ref = insn_node_ptr->getIf(); if (ref == nullptr) { if (insn_node_ptr->holds()) { // \todo @es3n1n: issue some sort of warning? push_label(insn_node_ptr, bb_provider); @@ -430,17 +429,17 @@ namespace analysis { /// Push ref if condition met /// - const auto not_met_type = is_conditional ? cf_direction_t::e_type::JCC_CONDITION_MET : cf_direction_t::e_type::JMP; + const auto met_type = is_conditional ? cf_direction_t::e_type::JCC_CONDITION_MET : cf_direction_t::e_type::JMP; if (jcc_branch.has_value()) { - push_cf_changer(not_met_type, bb_provider->find_by_start_va(jcc_branch.value(), this)); + push_cf_changer(met_type, bb_provider->find_by_start_va(jcc_branch.value(), this)); return; } if (jcc_branch_label.has_value()) { auto bb_ref = bb_provider->find_by_label(jcc_branch_label.value(), this); assert(bb_ref.has_value()); // we shouldn't reschedule this one - push_cf_changer(not_met_type, bb_ref.value()); + push_cf_changer(met_type, bb_ref); } }; update_cf(); @@ -455,12 +454,12 @@ namespace analysis { void push_last_N_insns(const zasm::x86::Assembler* assembler, const bb_provider_t* bb_provider, const std::size_t count) { /// Get the last inserted instruction /// - auto node = assembler->getCursor(); + auto* node = assembler->getCursor(); /// Insert in reverse order /// - std::size_t i; - for (i = 0; i < count && node != nullptr; i++, node = node->getPrev()) { + std::size_t i = 0; + for (; i < count && node != nullptr; i++, node = node->getPrev()) { (void)push_insn(node, bb_provider); } @@ -659,11 +658,11 @@ namespace analysis { std::optional jmp_at = std::nullopt; // jmp reg std::optional jump_table_rva = std::nullopt; - std::vector entries = {}; + std::vector entries; }; struct bb_storage_t { - DEFAULT_CTOR_DTOR(bb_storage_t); + DEFAULT_CT_CTOR_DTOR(bb_storage_t); DEFAULT_COPY(bb_storage_t); explicit bb_storage_t(const std::vector>& value): basic_blocks(value) { } @@ -745,6 +744,6 @@ namespace analysis { return basic_blocks; } - std::vector> basic_blocks = {}; + std::vector> basic_blocks; }; } // namespace analysis diff --git a/src/lib/analysis/common/debug.hpp b/src/lib/analysis/common/debug.hpp index 14363f6..88c2a9d 100644 --- a/src/lib/analysis/common/debug.hpp +++ b/src/lib/analysis/common/debug.hpp @@ -1,8 +1,7 @@ #pragma once #include "analysis/common/common.hpp" -#include "util/files.hpp" +#include -#include #include #include @@ -23,25 +22,24 @@ namespace analysis::debug { ss << "start:" << std::hex << basic_block.start_rva.value_or(0).inner() << '\n'; ss << "end:" << std::hex << basic_block.start_rva.value_or(0).inner() << '\n'; - for (auto& instruction : basic_block.instructions) { + for (const auto& instruction : basic_block.instructions) { ss << "instruction:" << std::hex << instruction->rva.value_or(0).inner() << ";" << ZydisMnemonicGetString(static_cast(instruction->ref->getMnemonic().value())) << '\n'; } - for (auto& successor : basic_block.successors) { + for (const auto& successor : basic_block.successors) { ss << "successor:" << std::hex << successor->start_rva.value_or(0).inner() << '\n'; } - for (auto& predecessor : basic_block.predecessors) { + for (const auto& predecessor : basic_block.predecessors) { ss << "predecessor:" << std::hex << predecessor->start_rva.value_or(0).inner() << '\n'; } const auto data = ss.str(); - util::write_file(file, reinterpret_cast(data.data()), data.size()); + files::write_file(file, reinterpret_cast(data.data()), data.size()); } [[maybe_unused]] inline void dump_bb(const bb_t& bb) { - using namespace detail; logger::info("BB: {:#x} :: {:#x}", bb.start_rva.value_or(0), bb.end_rva.value_or(0)); logger::info<1>("Valid: {}", bb.flags.valid); logger::info<1>("Instructions:"); @@ -56,9 +54,9 @@ namespace analysis::debug { for (std::size_t i = 0; i < ops_count; ++i) { const auto operand = instruction->ref->getOperand(i); - const auto operand_name_pair = operand_type_lookup.find(operand.getTypeIndex()); + const auto operand_name_pair = detail::operand_type_lookup.find(operand.getTypeIndex()); const auto operand_name = // - operand_name_pair == std::end(operand_type_lookup) ? unknown_type_name : operand_name_pair->second; + operand_name_pair == std::end(detail::operand_type_lookup) ? detail::unknown_type_name : operand_name_pair->second; logger::info<4>("{}", operand_name); } diff --git a/src/lib/analysis/lru_reg/lru_reg.hpp b/src/lib/analysis/lru_reg/lru_reg.hpp index 0536781..390f996 100644 --- a/src/lib/analysis/lru_reg/lru_reg.hpp +++ b/src/lib/analysis/lru_reg/lru_reg.hpp @@ -1,7 +1,7 @@ #pragma once #include "easm/easm.hpp" #include "pe/pe.hpp" -#include "util/random.hpp" +#include #include #include @@ -10,11 +10,14 @@ namespace analysis { using RegID = zasm::Reg::Id; namespace detail { + /// Protected registers list, should be in x64 inline std::array kProtectedRegisters = {RegID{ZYDIS_REGISTER_RSP}, RegID{ZYDIS_REGISTER_RBP}, RegID{ZYDIS_REGISTER_RIP}}; + /// X86 registers. Please note that these should be in x86. inline std::array kRegistersX86 = { - RegID{ZYDIS_REGISTER_RAX}, RegID{ZYDIS_REGISTER_RBX}, RegID{ZYDIS_REGISTER_RCX}, RegID{ZYDIS_REGISTER_RDX}, - RegID{ZYDIS_REGISTER_RSI}, RegID{ZYDIS_REGISTER_RDI}, RegID{ZYDIS_REGISTER_RBP}, RegID{ZYDIS_REGISTER_RSP}, + RegID{ZYDIS_REGISTER_EAX}, RegID{ZYDIS_REGISTER_EBX}, RegID{ZYDIS_REGISTER_ECX}, RegID{ZYDIS_REGISTER_EDX}, + RegID{ZYDIS_REGISTER_ESI}, RegID{ZYDIS_REGISTER_EDI}, RegID{ZYDIS_REGISTER_EBP}, RegID{ZYDIS_REGISTER_ESP}, }; + /// X64-only registers inline std::array kRegistersX64 = { RegID{ZYDIS_REGISTER_R8}, RegID{ZYDIS_REGISTER_R9}, RegID{ZYDIS_REGISTER_R10}, RegID{ZYDIS_REGISTER_R11}, RegID{ZYDIS_REGISTER_R12}, RegID{ZYDIS_REGISTER_R13}, RegID{ZYDIS_REGISTER_R14}, RegID{ZYDIS_REGISTER_R15}, @@ -30,6 +33,8 @@ namespace analysis { /// \brief Emplace register to the cache /// \param reg_id Register id void push(const RegID reg_id) { + assert(reg_id != zasm::Reg::Id::None && reg_id != zasm::Reg::Id::Invalid); + /// Mark as recently used if (cache_.contains(reg_id)) { items_.remove(reg_id); @@ -82,6 +87,13 @@ namespace analysis { }); } + /// \brief Verify that we already have this register in cache + /// \param reg_id Register id + /// \return true if we have this register in cache + [[nodiscard]] bool contains(const RegID reg_id) const { + return cache_.contains(reg_id); + } + /// \brief Temporary blacklist register /// \param reg_id register void blacklist(const RegID reg_id) { @@ -114,6 +126,7 @@ namespace analysis { /// \param callback callback that should return RegID /// \return filtered RegID that isn't blacklisted [[nodiscard]] RegID filter(const std::function& callback) const { + // NOLINENXTLINE(cppcoreguidelines-init-variables) RegID result; do { @@ -124,11 +137,11 @@ namespace analysis { } /// \brief Temporary blacklisted registers - std::unordered_set blacklisted_ = {}; + std::unordered_set blacklisted_; /// \brief Items storage itself - std::list items_ = {}; + std::list items_; /// \brief Unordered set for a bit faster contains checks - std::unordered_set cache_ = {}; + std::unordered_set cache_; }; /// \brief An lru cache for all types of GP registers @@ -162,6 +175,16 @@ namespace analysis { storage_.push(to_gp_ptr(reg_id)); } + /// \brief Push only already known register (if we have it in our cache) + /// \param reg_id register id + void push_known(const RegID reg_id) { + const auto gp_ptr = to_gp_ptr(reg_id); + if (!storage_.contains(gp_ptr)) { + return; + } + storage_.push(gp_ptr); + } + /// \brief Temporary blacklist register /// \param reg_id register void blacklist(const RegID reg_id) { @@ -259,6 +282,6 @@ namespace analysis { private: /// \brief gp uptr lru container - LRURegContainer storage_ = {}; + LRURegContainer storage_; }; } // namespace analysis \ No newline at end of file diff --git a/src/lib/analysis/observer/observer.hpp b/src/lib/analysis/observer/observer.hpp index 775d5b1..9945b0a 100644 --- a/src/lib/analysis/observer/observer.hpp +++ b/src/lib/analysis/observer/observer.hpp @@ -90,14 +90,16 @@ namespace analysis { if (auto [bb, insn] = *prev_pair; // ((*insn)->is_jump() && !(*insn)->is_conditional_jump()) || // !next_pair.has_value()) { - return insert_to(bb, insn + 1); // +1 because we want to insert it **after** the prev item + insert_to(bb, insn + 1); // +1 because we want to insert it **after** the prev item + return; } } if (next_pair.has_value()) { /// Oh well, just insert it before the `next` insn auto [bb, insn] = *next_pair; - return insert_to(bb, insn); + insert_to(bb, insn); + return; } throw std::runtime_error("observer: unable to process prev/next nodes [inserted]"); @@ -106,12 +108,12 @@ namespace analysis { /// /// This is called after a node has been created. /// - void onNodeCreated(zasm::Node*) override { } + void onNodeCreated(zasm::Node* /*node*/) override { } /// /// This is called before a node is detached. /// - void onNodeDetach(zasm::Node*) override { } + void onNodeDetach(zasm::Node* /*node*/) override { } /// \brief Stop the observer void stop() { @@ -140,8 +142,9 @@ namespace analysis { const auto* insn_ref = node->getIf(); while (node != nullptr && insn_ref == nullptr) { node = next(node); - if (node != nullptr) + if (node != nullptr) { insn_ref = node->getIf(); + } } /// No node @@ -153,7 +156,7 @@ namespace analysis { auto* insn_info = node->getUserData(); if (insn_info == nullptr || insn_info->bb_ref == nullptr) [[unlikely]] { assert(false); // huh? - return std::nullopt; + std::unreachable(); } /// Return info @@ -168,11 +171,11 @@ namespace analysis { /// \brief Zasm program instance that we're gonna analyze each time /// an obfuscation transform creates/destroys something - std::shared_ptr program_ = {}; + std::shared_ptr program_; /// \brief A reference to the basic block storage - std::shared_ptr bb_storage_ = {}; + std::shared_ptr bb_storage_; /// \brief BB Provider reference - std::shared_ptr bb_provider_ = {}; + std::shared_ptr bb_provider_; /// \brief Start/stop bool stopped_ = false; }; diff --git a/src/lib/analysis/passes/collect_img_references.hpp b/src/lib/analysis/passes/collect_img_references.hpp index a18852f..3ee8296 100644 --- a/src/lib/analysis/passes/collect_img_references.hpp +++ b/src/lib/analysis/passes/collect_img_references.hpp @@ -5,7 +5,7 @@ namespace analysis::passes { template struct collect_img_references_t { - DEFAULT_CTOR_DTOR(collect_img_references_t); + DEFAULT_CT_CTOR_DTOR(collect_img_references_t); NON_COPYABLE(collect_img_references_t); static bool apply_insn(Function* function, insn_t& instruction, Img* image) { diff --git a/src/lib/analysis/passes/collect_lookup_table.hpp b/src/lib/analysis/passes/collect_lookup_table.hpp index dbe46b9..2d33865 100644 --- a/src/lib/analysis/passes/collect_lookup_table.hpp +++ b/src/lib/analysis/passes/collect_lookup_table.hpp @@ -5,10 +5,10 @@ namespace analysis::passes { template struct collect_lookup_table_t { - DEFAULT_CTOR_DTOR(collect_lookup_table_t); + DEFAULT_CT_CTOR_DTOR(collect_lookup_table_t); NON_COPYABLE(collect_lookup_table_t); - static bool apply_insn(Function* function, insn_t& instruction, Img*) { + static bool apply_insn(Function* function, insn_t& instruction, Img* /*image*/) { if (!instruction.rva.has_value()) { /// \fixme @es3n1n: this could be pretty bad that we don't push newly /// created insns to the lookup table, although we should be just fine without them diff --git a/src/lib/analysis/passes/label_references.hpp b/src/lib/analysis/passes/label_references.hpp index 5b9fe16..9a4d4d8 100644 --- a/src/lib/analysis/passes/label_references.hpp +++ b/src/lib/analysis/passes/label_references.hpp @@ -6,10 +6,10 @@ namespace analysis::passes { template struct label_references_t { - DEFAULT_CTOR_DTOR(label_references_t); + DEFAULT_CT_CTOR_DTOR(label_references_t); NON_COPYABLE(label_references_t); - static bool apply(Function* function, Img*) { + static bool apply(Function* function, Img* /*image*/) { // Iterating over referenced RVAs within the function // for (const auto& [referenced_insn_rva, insn_ptrs] : function->image_references) { @@ -44,6 +44,7 @@ namespace analysis::passes { throw std::runtime_error("analysis: Unable to find referenced insn"); } function->program.get()->moveBefore(referenced_insn->second->node_ref, *referenced_loc_label_node); + referenced_insn->second->bb_ref->push_label(*referenced_loc_label_node, function->bb_provider.get()); // Iterating over instructions that referenced this RVA // diff --git a/src/lib/analysis/passes/lru_reg.hpp b/src/lib/analysis/passes/lru_reg.hpp index e5c9d03..cf90eb1 100644 --- a/src/lib/analysis/passes/lru_reg.hpp +++ b/src/lib/analysis/passes/lru_reg.hpp @@ -5,13 +5,14 @@ namespace analysis::passes { template struct lru_reg_t { - DEFAULT_CTOR_DTOR(lru_reg_t); + DEFAULT_CT_CTOR_DTOR(lru_reg_t); NON_COPYABLE(lru_reg_t); - static bool apply_insn(Function* function, const insn_t& instruction, Img*) { + static bool apply_insn(Function* function, const insn_t& instruction, Img* /*image*/) { /// Collect all the registers and push them to LRU for (auto& reg : easm::get_all_registers(*instruction.ref)) { - function->lru_reg.push(reg.getId()); + /// \note @es3n1n: We are pushing only known registers to avoid xmm/ymm/zmm stuff, we only need GP32/64 + function->lru_reg.push_known(reg.getId()); } return true; diff --git a/src/lib/analysis/passes/misc/bb_insn_passes.cpp b/src/lib/analysis/passes/misc/bb_insn_passes.cpp index d016706..defeb5f 100644 --- a/src/lib/analysis/passes/misc/bb_insn_passes.cpp +++ b/src/lib/analysis/passes/misc/bb_insn_passes.cpp @@ -7,16 +7,7 @@ #include "analysis/passes/reloc_marker.hpp" namespace analysis::passes { - namespace detail::bb_insn_passes { - template - bool on_bb(Function* /*function*/, bb_t& /*basic_block*/, Img* /*image*/) { - constexpr bool result = false; - - // result |= reloc_marker_t::apply_bb(function, basic_block, image); - - return result; - } - + namespace { template bool on_insn(Function* function, insn_t& instruction, Img* image) { bool result = false; @@ -28,19 +19,16 @@ namespace analysis::passes { return result; } - } // namespace detail::bb_insn_passes + } // namespace template bool bb_insn_passes_t::apply(Function* function, Img* image) { - using namespace detail::bb_insn_passes; bool result = false; // Iterating over BB and invoking callbacks // // function->bb_storage->iter_bbs([&](bb_t& basic_block) -> void { - result |= on_bb(function, basic_block, image); - // Iterating over instructions and invoking callbacks // std::for_each(basic_block.instructions.begin(), basic_block.instructions.end(), [&function, &image, &result](auto& instruction) -> void { // diff --git a/src/lib/analysis/passes/misc/bb_insn_passes.hpp b/src/lib/analysis/passes/misc/bb_insn_passes.hpp index 122a44b..7cbedbd 100644 --- a/src/lib/analysis/passes/misc/bb_insn_passes.hpp +++ b/src/lib/analysis/passes/misc/bb_insn_passes.hpp @@ -11,7 +11,7 @@ namespace analysis::passes { template struct bb_insn_passes_t { - DEFAULT_CTOR_DTOR(bb_insn_passes_t); + DEFAULT_CT_CTOR_DTOR(bb_insn_passes_t); NON_COPYABLE(bb_insn_passes_t); static bool apply(Function* function, Img* image); diff --git a/src/lib/analysis/passes/reloc_marker.hpp b/src/lib/analysis/passes/reloc_marker.hpp index 6ca7db6..3197a80 100644 --- a/src/lib/analysis/passes/reloc_marker.hpp +++ b/src/lib/analysis/passes/reloc_marker.hpp @@ -5,7 +5,7 @@ namespace analysis::passes { template struct reloc_marker_t { - DEFAULT_CTOR_DTOR(reloc_marker_t); + DEFAULT_CT_CTOR_DTOR(reloc_marker_t); NON_COPYABLE(reloc_marker_t); static bool apply_insn(Function* function [[maybe_unused]], insn_t& instruction, Img* image) { @@ -44,7 +44,7 @@ namespace analysis::passes { // At this point, we are 100% sure that imm is set to something, so we can ignore the `imm != 0` check. // If there's an imm with the size of uintptr_t, we should check maybe it's present in the .reloc dir // - if (imm != nullptr && getBitSize(imm->getBitSize()) == (ptr_size * 8) && instruction.length >= ptr_size) { + if (imm != nullptr && getBitSize(imm->getBitSize()) == (ptr_size * CHAR_BIT) && instruction.length >= ptr_size) { // Trying to find relocation from PE header within the instruction // \todo @es3n1n: check segments instead of just bruteforcing // diff --git a/src/lib/analysis/var_alloc/var_alloc.hpp b/src/lib/analysis/var_alloc/var_alloc.hpp index 31b603d..cf3f314 100644 --- a/src/lib/analysis/var_alloc/var_alloc.hpp +++ b/src/lib/analysis/var_alloc/var_alloc.hpp @@ -38,7 +38,7 @@ namespace analysis { template class VarAlloc { public: - DEFAULT_CTOR_DTOR(VarAlloc); + DEFAULT_CT_CTOR_DTOR(VarAlloc); DEFAULT_COPY(VarAlloc); explicit VarAlloc(LRUReg* lru_reg): lru_reg_(lru_reg) { } @@ -164,7 +164,7 @@ namespace analysis { } /// \brief A list of gp64 registers that we are already using - std::list registers_in_use_ = {}; + std::list registers_in_use_; /// \brief How many bytes would we need for storing all allocated vars std::size_t stack_space_used_ = 0; /// \brief LRU registers storage diff --git a/src/lib/cli/cli.hpp b/src/lib/cli/cli.hpp index 4e3701a..319110c 100644 --- a/src/lib/cli/cli.hpp +++ b/src/lib/cli/cli.hpp @@ -1,7 +1,7 @@ #pragma once #include "obfuscator/transforms/configs.hpp" #include "obfuscator/transforms/scheduler.hpp" -#include "util/logger.hpp" +#include namespace cli { namespace detail { @@ -22,8 +22,8 @@ namespace cli { logger::info("Available {} transforms:", platform_name); /// Iterate over the transforms - auto& scheduler = obfuscator::TransformScheduler::get().for_arch(); - for (auto& [tag, transform] : scheduler.transforms) { + for (auto& scheduler = obfuscator::TransformScheduler::get().for_arch(); // + auto& [tag, transform] : scheduler.transforms) { /// Get the shared cfg auto& shared_cfg = obfuscator::TransformSharedConfigStorage::get().get_for(tag); @@ -61,7 +61,7 @@ namespace cli { } } // namespace detail - inline void print_help(char* argv[]) { + inline void print_help(const char* argv[]) { logger::enabled = true; // just to be sure logger::info("github.com/es3n1n/obfuscator - A PoC native code obfuscator"); auto pad = [] { @@ -73,7 +73,7 @@ namespace cli { pad(); logger::info("Available options:"); - for (auto& [args, desc] : detail::kCLIOptionsHelp) { + for (const auto& [args, desc] : detail::kCLIOptionsHelp) { logger::info<1>("{:<12} {:<6} {:<8} -- {}", args[0], args[1], args[2], desc); } pad(); diff --git a/src/lib/config_parser/config_parser.cpp b/src/lib/config_parser/config_parser.cpp index 1916d68..6fc75eb 100644 --- a/src/lib/config_parser/config_parser.cpp +++ b/src/lib/config_parser/config_parser.cpp @@ -3,7 +3,7 @@ #include "obfuscator/transforms/configs.hpp" namespace config_parser { - Config from_argv(std::size_t argc, char* argv[]) { + Config from_argv(std::size_t argc, const char* argv[]) { /// No binary path if (argc < 2) { cli::print_help(argv); @@ -17,7 +17,7 @@ namespace config_parser { /// Allocate result Config result = {}; - auto& [binary_path_value] = result.obfuscator_config(); + auto& [binary_path_value, seed] = result.obfuscator_config(); auto& func_parser_config = result.func_parser_config(); /// Get some stuff for transforms resolving @@ -38,7 +38,7 @@ namespace config_parser { for (std::size_t i = 2; i < argc; ++i) { /// Get current arguments - std::string arg_ = argv[i]; + const std::string arg_ = argv[i]; /// \todo @es3n1n: Create a wrapper for these cringe checks std::optional next_arg_ = i + 1 < argc && argv[i + 1][0] != '-' ? std::make_optional(argv[i + 1]) : std::nullopt; @@ -113,6 +113,26 @@ namespace config_parser { state.current_transform->values[next_arg_.value()] = next_next_arg_.value(); skip(2); } + + if (arg_ == "-seed" && next_arg_.has_value()) { + /// \todo @sovissa: + /// [1] add correct processing of literals to string_parser + /// [2] add support for negative values to arg parser + std::size_t seed_value_base = 10; + + std::string_view next_arg_view{next_arg_.value()}; + if (next_arg_view.starts_with('-')) { + next_arg_view = next_arg_view.substr(1); + } + if (next_arg_view.length() >= 2 && // + (next_arg_view.starts_with("0x") || next_arg_view.starts_with("0X"))) { + seed_value_base = 16; + } + + seed = string_parser::parse_uint64(next_arg_.value(), seed_value_base); + skip(1); + continue; + } } return result; diff --git a/src/lib/config_parser/config_parser.hpp b/src/lib/config_parser/config_parser.hpp index a7de547..b0df5dd 100644 --- a/src/lib/config_parser/config_parser.hpp +++ b/src/lib/config_parser/config_parser.hpp @@ -7,7 +7,7 @@ namespace config_parser { class Config { public: - DEFAULT_CTOR_DTOR(Config); + DEFAULT_CT_CTOR_DTOR(Config); DEFAULT_COPY(Config); [[nodiscard]] function_configuration_t& create_function_config() { @@ -51,11 +51,11 @@ namespace config_parser { } private: - std::vector function_configurations_ = {}; - std::vector global_transform_configurations_ = {}; + std::vector function_configurations_; + std::vector global_transform_configurations_; obfuscator_config_t obfuscator_config_ = {}; func_parser_config_t func_parser_config_ = {}; }; - Config from_argv(std::size_t argc, char* argv[]); + Config from_argv(std::size_t argc, const char* argv[]); } // namespace config_parser diff --git a/src/lib/config_parser/structs.hpp b/src/lib/config_parser/structs.hpp index 9504148..09bf1c9 100644 --- a/src/lib/config_parser/structs.hpp +++ b/src/lib/config_parser/structs.hpp @@ -7,16 +7,17 @@ namespace config_parser { struct transform_configuration_t { std::size_t tag = {}; - std::unordered_map values = {}; + std::unordered_map values; }; struct function_configuration_t { - std::string function_name = {}; - std::vector transform_configurations = {}; + std::string function_name; + std::vector transform_configurations; }; struct obfuscator_config_t { std::filesystem::path binary_path = ""; + std::optional seed = std::nullopt; }; struct func_parser_config_t { diff --git a/src/lib/easm/assembler/assembler.hpp b/src/lib/easm/assembler/assembler.hpp index 5c78b79..4fe1e28 100644 --- a/src/lib/easm/assembler/assembler.hpp +++ b/src/lib/easm/assembler/assembler.hpp @@ -1,5 +1,5 @@ #pragma once -#include "util/memory/address.hpp" +#include #include #include #include diff --git a/src/lib/easm/cursor/cursor.hpp b/src/lib/easm/cursor/cursor.hpp index 4abd82f..3cb6443 100644 --- a/src/lib/easm/cursor/cursor.hpp +++ b/src/lib/easm/cursor/cursor.hpp @@ -38,7 +38,7 @@ namespace easm { return std::nullopt; } - const auto prev_node = node->getPrev(); + auto* const prev_node = node->getPrev(); if (prev_node == nullptr) { return std::nullopt; } @@ -84,7 +84,7 @@ namespace easm { } private: - std::shared_ptr program_ = {}; - std::shared_ptr assembler_ = {}; + std::shared_ptr program_; + std::shared_ptr assembler_; }; } // namespace easm diff --git a/src/lib/easm/debug/debug.hpp b/src/lib/easm/debug/debug.hpp index 2898956..c8fe77d 100644 --- a/src/lib/easm/debug/debug.hpp +++ b/src/lib/easm/debug/debug.hpp @@ -1,6 +1,5 @@ #pragma once #include "analysis/common/debug.hpp" -#include "util/memory/address.hpp" namespace easm { inline void dump_program(const zasm::Program& program) { @@ -29,6 +28,7 @@ namespace easm { for (std::size_t i = 0; i < ops_count; ++i) { const auto operand = node_insn->getOperand(i); + // NOLINTNEXTLINE(google-build-using-namespace) using namespace analysis::debug::detail; const auto operand_name_pair = operand_type_lookup.find(operand.getTypeIndex()); const auto operand_name = // @@ -42,11 +42,13 @@ namespace easm { if (const auto* p_mem = node_insn->getOperandIf(i); p_mem) { const auto reg = static_cast(p_mem->getBase().getId()); - logger::info<3>("expr: [{} + {:#x}]", - reg == 0 ? "none" : - p_mem->getBase().isIP() ? "ip" : - std::to_string(reg), - p_mem->getDisplacement()); + + std::string reg_str = reg == 0 ? "none" : std::to_string(reg); + if (p_mem->getBase().isIP()) { + reg_str = "ip"; + } + + logger::info<3>("expr: [{} + {:#x}]", reg_str, p_mem->getDisplacement()); } } } diff --git a/src/lib/easm/disassembler/disassembler.cpp b/src/lib/easm/disassembler/disassembler.cpp index 147e697..32de823 100644 --- a/src/lib/easm/disassembler/disassembler.cpp +++ b/src/lib/easm/disassembler/disassembler.cpp @@ -51,18 +51,4 @@ namespace easm { return decoder.decode_block(data, size, orig_address); } - - std::expected decode_insn_detail(const std::uint8_t* data, const zasm::MachineMode machine_mode, - const uint64_t orig_address) { - Decoder decoder(machine_mode); - - return decoder.decode_insn_detail(data, orig_address); - } - - std::expected decode_insn(const std::uint8_t* data, const zasm::MachineMode machine_mode, - const uint64_t orig_address) { - Decoder decoder(machine_mode); - - return decoder.decode_insn(data, orig_address); - } } // namespace easm diff --git a/src/lib/easm/disassembler/disassembler.hpp b/src/lib/easm/disassembler/disassembler.hpp index 12738be..73fddbd 100644 --- a/src/lib/easm/disassembler/disassembler.hpp +++ b/src/lib/easm/disassembler/disassembler.hpp @@ -6,7 +6,7 @@ #include namespace easm { - constexpr zasm::MachineMode kDefaultMm = zasm::MachineMode::AMD64; + constexpr auto kDefaultMm = zasm::MachineMode::AMD64; constexpr size_t kDefaultSize = 15; class Decoder { diff --git a/src/lib/easm/misc/misc.hpp b/src/lib/easm/misc/misc.hpp index dea23ac..cb98b62 100644 --- a/src/lib/easm/misc/misc.hpp +++ b/src/lib/easm/misc/misc.hpp @@ -6,6 +6,47 @@ #include namespace easm { + constexpr size_t kMaxEntryInstructionSize = 5; // jump in our case + + template + constexpr zasm::x86::Gp sp_for_arch() { + if constexpr (pe::is_x64_v) { + return zasm::x86::rsp; + } else { + return zasm::x86::esp; + } + } + + constexpr zasm::BitSize sp_size_for_arch(const zasm::MachineMode machine_mode) { + switch (machine_mode) { + case zasm::MachineMode::AMD64: + return zasm::toBitSize(sizeof(std::uint64_t) * CHAR_BIT); + case zasm::MachineMode::I386: + return zasm::toBitSize(sizeof(std::uint32_t) * CHAR_BIT); + default: + assert(false); // unknown arch + std::unreachable(); + } + } + + template + constexpr zasm::BitSize sp_size_for_arch() { + if constexpr (pe::is_x64_v) { + return sp_size_for_arch(zasm::MachineMode::AMD64); + } else { + return sp_size_for_arch(zasm::MachineMode::I386); + } + } + + template + constexpr zasm::Mem ptr(TArgs... args) { + if constexpr (pe::is_x64_v) { + return zasm::x86::qword_ptr(std::forward(args)...); + } else { + return zasm::x86::dword_ptr(std::forward(args)...); + } + } + inline bool is_jcc_or_jmp(const zasm::Instruction& insn) { const auto mnemonic = insn.getMnemonic(); @@ -90,20 +131,53 @@ namespace easm { return follow_jcc_or_jmp(insn_info); } - inline std::optional get_operand_size(const zasm::MachineMode machine_mode, const zasm::Operand& operand) { - if (auto* op_reg = operand.getIf()) { + inline zasm::BitSize get_operand_size(const zasm::MachineMode machine_mode, const zasm::Operand& operand) { + if (const auto* op_reg = operand.getIf()) { return op_reg->getBitSize(machine_mode); } - if (auto* op_mem = operand.getIf()) { + if (const auto* op_mem = operand.getIf()) { return op_mem->getBitSize(machine_mode); } - return std::nullopt; + assert(false); // what's this? zasm::Imm is handled in the different function + std::unreachable(); } - inline std::optional get_operand_size(const zasm::MachineMode machine_mode, const zasm::Instruction* insn, const std::size_t index) { - return get_operand_size(machine_mode, insn->getOperand(index)); + inline zasm::BitSize get_operand_size(const zasm::MachineMode machine_mode, const zasm::Instruction* insn, const std::size_t index) { + const auto& operand = insn->getOperand(index); + + if (const auto* op_imm = operand.getIf()) { + /// Let's see if we know the size of operand + const auto attributes = insn->getAttribs(); + + // NOLINTBEGIN(readability-implicit-bool-conversion) + if (attributes & zasm::x86::Attribs::OperandSize8) { + return zasm::toBitSize(8); + } + + if (attributes & zasm::x86::Attribs::OperandSize16) { + return zasm::toBitSize(16); + } + + if (attributes & zasm::x86::Attribs::OperandSize32) { + return zasm::toBitSize(32); + } + + if (attributes & zasm::x86::Attribs::OperandSize64) { + return zasm::toBitSize(64); + } + // NOLINTEND(readability-implicit-bool-conversion) + + /// Push instruction always pushes the stack width bit size + if (insn->getMnemonic().value() == ZYDIS_MNEMONIC_PUSH) { + return sp_size_for_arch(machine_mode); + } + + return op_imm->getBitSize(); + } + + return get_operand_size(machine_mode, operand); } inline zasm::x86::Gp to_gp(const zasm::Reg reg) { @@ -115,24 +189,23 @@ namespace easm { } inline std::pair to_gp_root_gp(const zasm::MachineMode machine_mode, const zasm::Reg reg) { - return std::make_pair(to_gp(reg), to_root_gp(machine_mode, reg)); + return std::make_pair(to_gp(reg), to_root_gp(machine_mode, reg)); } - inline void assert_operand_used_reg(const zasm::MachineMode machine_mode, const zasm::Instruction* insn, const std::size_t index, - const zasm::Reg reg) { - if (auto* op_reg = insn->getOperandIf(index)) { + inline void assert_operand_used_reg(const zasm::MachineMode machine_mode [[maybe_unused]], const zasm::Instruction* insn, const std::size_t index, + const zasm::Reg reg [[maybe_unused]]) { + if (const auto* op_reg = insn->getOperandIf(index)) { assert(to_root_gp(machine_mode, *op_reg).getId() != reg.getId()); } - if (auto* op_mem = insn->getOperandIf(index); op_mem != nullptr && op_mem->getBase().isValid()) { + if (const auto* op_mem = insn->getOperandIf(index); op_mem != nullptr && op_mem->getBase().isValid()) { assert(to_root_gp(machine_mode, op_mem->getBase()).getId() != reg.getId()); } } - inline void assert_operand_size(const zasm::MachineMode machine_mode, const zasm::Instruction* insn, const std::size_t index, const zasm::Reg reg) { - if (const auto size = get_operand_size(machine_mode, insn, index); size.has_value()) { - assert(size.value() == reg.getBitSize(machine_mode)); - } + inline void assert_operand_size(const zasm::MachineMode machine_mode [[maybe_unused]], const zasm::Instruction* insn [[maybe_unused]], + const std::size_t index [[maybe_unused]], const zasm::Reg reg [[maybe_unused]]) { + assert(get_operand_size(machine_mode, insn, index) == reg.getBitSize(machine_mode)); } inline bool is_sp(const zasm::MachineMode machine_mode, const zasm::Reg reg) { @@ -142,14 +215,14 @@ namespace easm { inline bool affects_sp(const zasm::MachineMode machine_mode, const zasm::Instruction& insn) { for (std::size_t i = 0; i < insn.getOperandCount(); ++i) { - if (auto* op_mem = insn.getOperandIf(i); op_mem != nullptr) { + if (const auto* op_mem = insn.getOperandIf(i); op_mem != nullptr) { if (is_sp(machine_mode, op_mem->getBase())) { return true; } continue; } - if (auto* op_reg = insn.getOperandIf(i); op_reg != nullptr) { + if (const auto* op_reg = insn.getOperandIf(i); op_reg != nullptr) { if (is_sp(machine_mode, *op_reg)) { return true; } @@ -163,34 +236,16 @@ namespace easm { std::vector result = {}; for (std::size_t i = 0; i < insn.getOperandCount(); ++i) { - if (auto* op_mem = insn.getOperandIf(i); op_mem != nullptr && op_mem->getBase().isValid()) { + if (const auto* op_mem = insn.getOperandIf(i); op_mem != nullptr && op_mem->getBase().isValid()) { result.emplace_back(op_mem->getBase()); continue; } - if (auto* op_reg = insn.getOperandIf(i); op_reg != nullptr) { + if (const auto* op_reg = insn.getOperandIf(i); op_reg != nullptr) { result.emplace_back(*op_reg); } } return result; } - - template - constexpr zasm::x86::Gp sp_for_arch() { - if constexpr (pe::is_x64_v) { - return zasm::x86::rsp; - } else { - return zasm::x86::esp; - } - } - - template - constexpr zasm::Mem ptr(TArgs... args) { - if constexpr (pe::is_x64_v) { - return zasm::x86::qword_ptr(std::forward(args)...); - } else { - return zasm::x86::dword_ptr(std::forward(args)...); - } - } } // namespace easm diff --git a/src/lib/easm/misc/reg_convert.hpp b/src/lib/easm/misc/reg_convert.hpp index aaea160..c438031 100644 --- a/src/lib/easm/misc/reg_convert.hpp +++ b/src/lib/easm/misc/reg_convert.hpp @@ -1,52 +1,6 @@ #pragma once #include -/* -64 32lo 16lo 8lo -rax eax ax al -rbx ebx bx bl -rcx ecx cx cl -rdx edx dx dl -rsi esi si sil -rdi edi di dil -rbp ebp bp bpl -rsp esp sp spl -r8 r8d r8w r8b -r9 r9d r9w r9b -r10 r10d r10w r10b -r11 r11d r11w r11b -r12 r12d r12w r12b -r13 r13d r13w r13b -r14 r14d r14w r14b -r15 r15d r15w r15b - -In [1]: lines = '''rax^Ieax^Iax^Ial - ...: rbx^Iebx^Ibx^Ibl - ...: rcx^Iecx^Icx^Icl - ...: rdx^Iedx^Idx^Idl - ...: rsi^Iesi^Isi^Isil - ...: rdi^Iedi^Idi^Idil - ...: rbp^Iebp^Ibp^Ibpl - ...: rsp^Iesp^Isp^Ispl - ...: r8^Ir8d^Ir8w^Ir8b - ...: r9^Ir9d^Ir9w^Ir9b - ...: r10^Ir10d^Ir10w^Ir10b - ...: r11^Ir11d^Ir11w^Ir11b - ...: r12^Ir12d^Ir12w^Ir12b - ...: r13^Ir13d^Ir13w^Ir13b - ...: r14^Ir14d^Ir14w^Ir14b - ...: r15^Ir15d^Ir15w^Ir15b''' - -In [2]: lines = lines.splitlines() - -In [3]: lines = [x.split('\t') for x in lines] - -In [4]: for line in lines: - ...: print(f'CONVERTER(ZYDIS_REGISTER_{line[1].upper()}, ZYDIS_REGISTER_{line[0].upper()});') - ...: - */ - -/// \note @es3n1n: Would be pretty cool to have something like this in zasm namespace easm::reg_convert { namespace detail { inline void throw_exc() { @@ -54,109 +8,27 @@ namespace easm::reg_convert { } } // namespace detail -#define CONVERTER(from, to) \ - case from: \ - return zasm::Reg::Id(to) - inline zasm::Reg::Id gp32_to_gp64(const zasm::Reg::Id reg_id) { - switch (static_cast(reg_id)) { - CONVERTER(ZYDIS_REGISTER_EAX, ZYDIS_REGISTER_RAX); - CONVERTER(ZYDIS_REGISTER_EBX, ZYDIS_REGISTER_RBX); - CONVERTER(ZYDIS_REGISTER_ECX, ZYDIS_REGISTER_RCX); - CONVERTER(ZYDIS_REGISTER_EDX, ZYDIS_REGISTER_RDX); - CONVERTER(ZYDIS_REGISTER_ESI, ZYDIS_REGISTER_RSI); - CONVERTER(ZYDIS_REGISTER_EDI, ZYDIS_REGISTER_RDI); - CONVERTER(ZYDIS_REGISTER_EBP, ZYDIS_REGISTER_RBP); - CONVERTER(ZYDIS_REGISTER_ESP, ZYDIS_REGISTER_RSP); - CONVERTER(ZYDIS_REGISTER_R8D, ZYDIS_REGISTER_R8); - CONVERTER(ZYDIS_REGISTER_R9D, ZYDIS_REGISTER_R9); - CONVERTER(ZYDIS_REGISTER_R10D, ZYDIS_REGISTER_R10); - CONVERTER(ZYDIS_REGISTER_R11D, ZYDIS_REGISTER_R11); - CONVERTER(ZYDIS_REGISTER_R12D, ZYDIS_REGISTER_R12); - CONVERTER(ZYDIS_REGISTER_R13D, ZYDIS_REGISTER_R13); - CONVERTER(ZYDIS_REGISTER_R14D, ZYDIS_REGISTER_R14); - CONVERTER(ZYDIS_REGISTER_R15D, ZYDIS_REGISTER_R15); - default: - break; - } - detail::throw_exc(); - std::unreachable(); + const auto result = zasm::x86::Gp(reg_id).getRoot(zasm::MachineMode::AMD64); + assert(result.isValid()); + return result.getId(); } inline zasm::Reg::Id gp64_to_gp32(const zasm::Reg::Id reg_id) { - switch (static_cast(reg_id)) { - CONVERTER(ZYDIS_REGISTER_RAX, ZYDIS_REGISTER_EAX); - CONVERTER(ZYDIS_REGISTER_RBX, ZYDIS_REGISTER_EBX); - CONVERTER(ZYDIS_REGISTER_RCX, ZYDIS_REGISTER_ECX); - CONVERTER(ZYDIS_REGISTER_RDX, ZYDIS_REGISTER_EDX); - CONVERTER(ZYDIS_REGISTER_RSI, ZYDIS_REGISTER_ESI); - CONVERTER(ZYDIS_REGISTER_RDI, ZYDIS_REGISTER_EDI); - CONVERTER(ZYDIS_REGISTER_RBP, ZYDIS_REGISTER_EBP); - CONVERTER(ZYDIS_REGISTER_RSP, ZYDIS_REGISTER_ESP); - CONVERTER(ZYDIS_REGISTER_R8, ZYDIS_REGISTER_R8D); - CONVERTER(ZYDIS_REGISTER_R9, ZYDIS_REGISTER_R9D); - CONVERTER(ZYDIS_REGISTER_R10, ZYDIS_REGISTER_R10D); - CONVERTER(ZYDIS_REGISTER_R11, ZYDIS_REGISTER_R11D); - CONVERTER(ZYDIS_REGISTER_R12, ZYDIS_REGISTER_R12D); - CONVERTER(ZYDIS_REGISTER_R13, ZYDIS_REGISTER_R13D); - CONVERTER(ZYDIS_REGISTER_R14, ZYDIS_REGISTER_R14D); - CONVERTER(ZYDIS_REGISTER_R15, ZYDIS_REGISTER_R15D); - default: - break; - } - detail::throw_exc(); - std::unreachable(); + const auto result = zasm::x86::Gp(reg_id).r32(); + assert(result.isValid()); + return result.getId(); } inline zasm::Reg::Id gp64_to_gp16(const zasm::Reg::Id reg_id) { - switch (static_cast(reg_id)) { - CONVERTER(ZYDIS_REGISTER_RAX, ZYDIS_REGISTER_AX); - CONVERTER(ZYDIS_REGISTER_RBX, ZYDIS_REGISTER_BX); - CONVERTER(ZYDIS_REGISTER_RCX, ZYDIS_REGISTER_CX); - CONVERTER(ZYDIS_REGISTER_RDX, ZYDIS_REGISTER_DX); - CONVERTER(ZYDIS_REGISTER_RSI, ZYDIS_REGISTER_SI); - CONVERTER(ZYDIS_REGISTER_RDI, ZYDIS_REGISTER_DI); - CONVERTER(ZYDIS_REGISTER_RBP, ZYDIS_REGISTER_BP); - CONVERTER(ZYDIS_REGISTER_RSP, ZYDIS_REGISTER_SP); - CONVERTER(ZYDIS_REGISTER_R8, ZYDIS_REGISTER_R8W); - CONVERTER(ZYDIS_REGISTER_R9, ZYDIS_REGISTER_R9W); - CONVERTER(ZYDIS_REGISTER_R10, ZYDIS_REGISTER_R10W); - CONVERTER(ZYDIS_REGISTER_R11, ZYDIS_REGISTER_R11W); - CONVERTER(ZYDIS_REGISTER_R12, ZYDIS_REGISTER_R12W); - CONVERTER(ZYDIS_REGISTER_R13, ZYDIS_REGISTER_R13W); - CONVERTER(ZYDIS_REGISTER_R14, ZYDIS_REGISTER_R14W); - CONVERTER(ZYDIS_REGISTER_R15, ZYDIS_REGISTER_R15W); - default: - break; - } - detail::throw_exc(); - std::unreachable(); + const auto result = zasm::x86::Gp(reg_id).r16(); + assert(result.isValid()); + return result.getId(); } inline zasm::Reg::Id gp64_to_gp8(const zasm::Reg::Id reg_id) { - switch (static_cast(reg_id)) { - CONVERTER(ZYDIS_REGISTER_RAX, ZYDIS_REGISTER_AL); - CONVERTER(ZYDIS_REGISTER_RBX, ZYDIS_REGISTER_BL); - CONVERTER(ZYDIS_REGISTER_RCX, ZYDIS_REGISTER_CL); - CONVERTER(ZYDIS_REGISTER_RDX, ZYDIS_REGISTER_DL); - CONVERTER(ZYDIS_REGISTER_RSI, ZYDIS_REGISTER_SIL); - CONVERTER(ZYDIS_REGISTER_RDI, ZYDIS_REGISTER_DIL); - CONVERTER(ZYDIS_REGISTER_RBP, ZYDIS_REGISTER_BPL); - CONVERTER(ZYDIS_REGISTER_RSP, ZYDIS_REGISTER_SPL); - CONVERTER(ZYDIS_REGISTER_R8, ZYDIS_REGISTER_R8B); - CONVERTER(ZYDIS_REGISTER_R9, ZYDIS_REGISTER_R9B); - CONVERTER(ZYDIS_REGISTER_R10, ZYDIS_REGISTER_R10B); - CONVERTER(ZYDIS_REGISTER_R11, ZYDIS_REGISTER_R11B); - CONVERTER(ZYDIS_REGISTER_R12, ZYDIS_REGISTER_R12B); - CONVERTER(ZYDIS_REGISTER_R13, ZYDIS_REGISTER_R13B); - CONVERTER(ZYDIS_REGISTER_R14, ZYDIS_REGISTER_R14B); - CONVERTER(ZYDIS_REGISTER_R15, ZYDIS_REGISTER_R15B); - default: - break; - } - detail::throw_exc(); - std::unreachable(); + const auto result = zasm::x86::Gp(reg_id).r16(); // \fixme @es3n1n: We can't just straight use r8. see Vol.2B 4 - 35 => no ah/bh/ch/dh with REX + assert(result.isValid()); + return result.getId(); } - -#undef CONVERTER } // namespace easm::reg_convert diff --git a/src/lib/func_parser/common/common.hpp b/src/lib/func_parser/common/common.hpp index 129860b..b85eb3b 100644 --- a/src/lib/func_parser/common/common.hpp +++ b/src/lib/func_parser/common/common.hpp @@ -10,7 +10,7 @@ namespace func_parser { constexpr function_t() = default; bool valid = false; - std::string name = {}; + std::string name; std::uint64_t rva = 0; std::optional size = std::nullopt; diff --git a/src/lib/func_parser/common/demangler.hpp b/src/lib/func_parser/common/demangler.hpp new file mode 100644 index 0000000..1276794 --- /dev/null +++ b/src/lib/func_parser/common/demangler.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "func_parser/common/common.hpp" + +#include +#include + +namespace func_parser::demangler { + inline void demangle_functions(function_list_t& items) { + // Iterating over the functions and demangling their names + // + std::ranges::for_each(items, [](function_t& item) -> void { + if (auto* demangled = LLVMDemangle(item.name.c_str())) { + item.name = demangled; + // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc,cppcoreguidelines-owning-memory) + std::free(demangled); + } + }); + } +} // namespace func_parser::demangler diff --git a/src/lib/func_parser/common/sanitizer.hpp b/src/lib/func_parser/common/sanitizer.hpp index 7d41a80..324bebd 100644 --- a/src/lib/func_parser/common/sanitizer.hpp +++ b/src/lib/func_parser/common/sanitizer.hpp @@ -1,10 +1,11 @@ #pragma once #include "func_parser/common/common.hpp" #include "pe/pe.hpp" +#include namespace func_parser::sanitizer { template - function_list_t sanitize_function_list(function_list_t items, const Img* image) noexcept { + void sanitize_function_list(function_list_t& items, const Img* image) noexcept { // Obtaining executable sections // const auto exec_sections = image->find_sections_if([](const pe::section_t& sec) -> bool { // @@ -41,7 +42,5 @@ namespace func_parser::sanitizer { // return !in_exec_mem; }); - - return items; } } // namespace func_parser::sanitizer diff --git a/src/lib/func_parser/map/map.cpp b/src/lib/func_parser/map/map.cpp index 9991166..3154d5d 100644 --- a/src/lib/func_parser/map/map.cpp +++ b/src/lib/func_parser/map/map.cpp @@ -1,14 +1,11 @@ +#include #include #include "func_parser/map/map.hpp" -#include "util/logger.hpp" -#include "util/memory/casts.hpp" - -#include +#include namespace func_parser::map { namespace { - // Extracting `0001` segment index, `00000000` segment offset and `main` from // `0001:00000000 main 0000000140001000 f FileName.obj` // @@ -23,15 +20,15 @@ namespace func_parser::map { function_list_t discover_functions(const std::filesystem::path& map_path, const std::vector& sections) { // Reading map file // - const auto map_content = util::read_file(map_path); - if (map_content.empty()) { + const auto map_content = files::read_file(map_path); + if (!map_content.has_value() || map_content->empty()) { throw std::runtime_error("Empty map file"); } // Converting to string stream // std::stringstream str_stream; - str_stream.str(std::string{memory::cast(map_content.data()), map_content.size()}); + str_stream.str(std::string{reinterpret_cast(map_content->data()), map_content->size()}); // Initializing func_parser state // diff --git a/src/lib/func_parser/map/map.hpp b/src/lib/func_parser/map/map.hpp index b3b8b67..ce96089 100644 --- a/src/lib/func_parser/map/map.hpp +++ b/src/lib/func_parser/map/map.hpp @@ -1,7 +1,7 @@ #pragma once #include "func_parser/common/common.hpp" #include "pe/common/types.hpp" -#include "util/files.hpp" +#include namespace func_parser::map { function_list_t discover_functions(const std::filesystem::path& map_path, const std::vector& sections); diff --git a/src/lib/func_parser/parser.cpp b/src/lib/func_parser/parser.cpp index 0e7c288..4e36930 100644 --- a/src/lib/func_parser/parser.cpp +++ b/src/lib/func_parser/parser.cpp @@ -2,7 +2,7 @@ #include "func_parser/common/combiner.hpp" #include "func_parser/map/map.hpp" #include "func_parser/pdb/pdb.hpp" -#include "util/logger.hpp" +#include namespace func_parser { template @@ -14,10 +14,10 @@ namespace func_parser { // Combining and sanitizing results // function_list_ = combiner::combine_function_lists(function_lists_); - progress_->step(); + progress_step(); - function_list_ = sanitizer::sanitize_function_list(function_list_, image_); - progress_->step(); + sanitizer::sanitize_function_list(function_list_, image_); + progress_step(); // If 0 functions found // @@ -31,10 +31,10 @@ namespace func_parser { template void Instance::parse() { parse_pdb(); - progress_->step(); + progress_step(); parse_map(); - progress_->step(); + progress_step(); } template diff --git a/src/lib/func_parser/parser.hpp b/src/lib/func_parser/parser.hpp index 2b8318a..c23fd4d 100644 --- a/src/lib/func_parser/parser.hpp +++ b/src/lib/func_parser/parser.hpp @@ -1,14 +1,15 @@ #pragma once #include "config_parser/config_parser.hpp" #include "func_parser/common/common.hpp" +#include "func_parser/common/demangler.hpp" #include "func_parser/common/sanitizer.hpp" -#include "util/progress.hpp" +#include namespace func_parser { template class Instance { public: - DEFAULT_CTOR_DTOR(Instance); + DEFAULT_CT_CTOR_DTOR(Instance); DEFAULT_COPY(Instance); void setup(Img* image, const config_parser::func_parser_config_t& config, const config_parser::obfuscator_config_t& obfuscator_config) { @@ -39,15 +40,26 @@ namespace func_parser { return false; } - function_lists_.emplace_back(sanitizer::sanitize_function_list(std::move(items), image_)); + sanitizer::sanitize_function_list(items, image_); + demangler::demangle_functions(items); + + function_lists_.emplace_back(items); return true; } + void progress_step() { + if (!progress_.has_value()) { + return; + } + + progress_->step(); + } + Img* image_ = nullptr; - std::vector function_lists_ = {}; - function_list_t function_list_ = {}; // function_lists_ combined and sanitized basically + std::vector function_lists_; + function_list_t function_list_; // function_lists_ combined and sanitized basically config_parser::func_parser_config_t config_ = {}; config_parser::obfuscator_config_t obfuscator_config_ = {}; - std::optional progress_ = std::nullopt; + std::optional progress_ = std::nullopt; }; } // namespace func_parser \ No newline at end of file diff --git a/src/lib/func_parser/pdb/detail/parser_v7.hpp b/src/lib/func_parser/pdb/detail/parser_v7.hpp index 78179f9..30ae879 100644 --- a/src/lib/func_parser/pdb/detail/parser_v7.hpp +++ b/src/lib/func_parser/pdb/detail/parser_v7.hpp @@ -6,8 +6,9 @@ #include #include "func_parser/pdb/detail/structs.hpp" -#include "util/memory/address.hpp" -#include "util/types.hpp" +#include +#include +#include // \todo: @es3n1n: we should probably use OMAP from the pdb for rva conversion @@ -24,7 +25,7 @@ namespace func_parser::pdb::detail { void iter_symbols(const std::function& callback) const; template - void iter_symbols(const std::uint16_t kind, std::function callback) const { + void iter_symbols(const std::uint16_t kind, const std::function& callback) const { iter_symbols([=](const std::uint16_t it_kind, const memory::address raw) -> bool { if (it_kind != kind) { return true; @@ -36,7 +37,7 @@ namespace func_parser::pdb::detail { } template - void iter_symbols(std::function callback, Args... args) const { + void iter_symbols(const std::function& callback, Args... args) const { for (auto kind : types::to_array(std::forward(args)...)) { iter_symbols(kind, callback); } @@ -63,12 +64,12 @@ namespace func_parser::pdb::detail { SuperBlock* header_ = nullptr; // contains virtual addresses, cba storing anything else - std::vector sections_ = {}; + std::vector sections_; // these streams should be in the right order - std::vector> streams_ = {}; + std::vector> streams_; // key is sym kind, values are ptrs to the symbols - std::unordered_map> dbi_symbols_ = {}; + std::unordered_map> dbi_symbols_; }; } // namespace func_parser::pdb::detail diff --git a/src/lib/func_parser/pdb/detail/reader_v7.cpp b/src/lib/func_parser/pdb/detail/reader_v7.cpp index c6c850a..74f72ee 100644 --- a/src/lib/func_parser/pdb/detail/reader_v7.cpp +++ b/src/lib/func_parser/pdb/detail/reader_v7.cpp @@ -1,11 +1,11 @@ #include "func_parser/pdb/detail/parser_v7.hpp" -#include "util/logger.hpp" -#include "util/memory/address.hpp" +#include +#include // \note: @es3n1n: s/o to @namazso for the stream related functions namespace func_parser::pdb::detail { namespace { - std::vector get_stream_directory(const SuperBlock* header, const memory::address& raw) noexcept { + std::vector get_stream_directory(const SuperBlock* header, const memory::address& raw) { const auto size = header->NumDirectoryBytes; const auto block_size = static_cast(header->BlockSize); const auto block_count = (size + block_size - 1) / block_size; @@ -55,15 +55,15 @@ namespace func_parser::pdb::detail { const auto block_size = header_->BlockSize; - const std::size_t streams_count = memory::address{stream_directory.data()}.get().value(); + const std::ptrdiff_t streams_count = memory::address{stream_directory.data()}.get().value(); const auto* streams = memory::address{stream_directory.data()}.offset(sizeof(std::uint32_t)).cast(); - const auto* ids = memory::address{streams}.offset(static_cast(streams_count) * sizeof(std::uint32_t)).cast(); + const auto* ids = memory::address{streams}.offset(streams_count * static_cast(sizeof(std::uint32_t))).cast(); streams_.clear(); streams_.reserve(streams_count); - for (std::size_t i = 0; i < streams_count; ++i) { + for (std::size_t i = 0; i < static_cast(streams_count); ++i) { const auto stream_size = streams[i]; const auto stream_blocks = (stream_size + block_size - 1) / block_size; @@ -123,8 +123,8 @@ namespace func_parser::pdb::detail { auto* sym_stream = sym_stream_raw.data(); const auto* sym_stream_end = sym_stream + sym_stream_raw.size(); - for (; sym_stream != sym_stream_end; sym_stream += (memory::cast(sym_stream)->Size + 2)) { - dbi_symbols_[memory::cast(sym_stream)->Kind].emplace_back(sym_stream); + for (; sym_stream != sym_stream_end; sym_stream += reinterpret_cast(sym_stream)->Size + 2) { + dbi_symbols_[reinterpret_cast(sym_stream)->Kind].emplace_back(sym_stream); } }; @@ -201,7 +201,7 @@ namespace func_parser::pdb::detail { } auto& raw_image_section_stream = streams_.at(optional_debug_header->SectionHeaderStreamIndex); - auto* image_section_stream = memory::cast(raw_image_section_stream.data()); + auto* image_section_stream = reinterpret_cast(raw_image_section_stream.data()); for (std::size_t i = 0; i < (raw_image_section_stream.size() / sizeof(IMAGE_SECTION_HEADER)); ++i) { auto* image_section = &image_section_stream[i]; diff --git a/src/lib/func_parser/pdb/detail/structs.hpp b/src/lib/func_parser/pdb/detail/structs.hpp index 544e114..6508b42 100644 --- a/src/lib/func_parser/pdb/detail/structs.hpp +++ b/src/lib/func_parser/pdb/detail/structs.hpp @@ -16,9 +16,11 @@ namespace func_parser::pdb::detail { DBI_HEADER = 3, }; + /// \todo @es3n1n: Add S_GPROC32_ID, S_LPROC32_ID, S_LPROC32_DPC, S_LPROC32_DPC_ID enum e_symbol_kind : std::uint16_t { S_LPROC32 = 0x110FU, // local fn S_GPROC32 = 0x1110U, // global fn + S_PUB32 = 0x110EU, // public symbol S_END = 0x6, // end of mod symbols }; @@ -130,7 +132,7 @@ namespace func_parser::pdb::detail { uint16_t Segment; }; - struct DBIRecordProc32 { + struct DBIFunction { DBIRecordHeader Header; uint32_t Parent; uint32_t End; @@ -142,11 +144,23 @@ namespace func_parser::pdb::detail { uint32_t Offset; uint16_t Segment; uint8_t Flags; - // NOLINTNEXTLINE - char Name[1]; + char Name[1]; // NOLINT }; - struct DBIRecordLProc32 : DBIRecordProc32 { }; - struct DBIRecordGProc32 : DBIRecordProc32 { }; + enum PublicSymFlags : uint32_t { // NOLINT(performance-enum-size) + None = 0, + Code = 1U << 0U, + Function = 1U << 1U, + Managed = 1U << 2U, + MSIL = 1U << 3U, + }; + + struct DBIPublicSymbol { + DBIRecordHeader Header; + PublicSymFlags Flags; + uint32_t Offset; + uint16_t Segment; + char Name[1]; + }; #pragma pack(pop) } // namespace func_parser::pdb::detail diff --git a/src/lib/func_parser/pdb/pdb.cpp b/src/lib/func_parser/pdb/pdb.cpp index 2eaadd0..9abc442 100644 --- a/src/lib/func_parser/pdb/pdb.cpp +++ b/src/lib/func_parser/pdb/pdb.cpp @@ -1,9 +1,10 @@ #include "func_parser/pdb/pdb.hpp" #include "func_parser/pdb/detail/parser_v7.hpp" -#include "util/logger.hpp" +#include +#include namespace func_parser::pdb { - function_list_t discover_functions(const std::filesystem::path& pdb_path, const std::uint64_t) { + function_list_t discover_functions(const std::filesystem::path& pdb_path, const std::uint64_t base_of_code [[maybe_unused]]) { // Return an empty set if file doesn't exist // if (!exists(pdb_path)) { @@ -12,45 +13,73 @@ namespace func_parser::pdb { // Reading file // - const auto pdb_content = util::read_file(pdb_path); - if (pdb_content.empty()) [[unlikely]] { + const auto pdb_content = files::read_file(pdb_path); + if (!pdb_content.has_value() || pdb_content->empty()) [[unlikely]] { return {}; } // If magic doesn't equal to pdb7 magic then sorry we cannot parse this pdb // \todo: @es3n1n: add pdb2 support // - if (std::memcmp(pdb_content.data(), detail::kMicrosoftPdb7Magic.data(), detail::kMicrosoftPdb7Magic.size()) != 0) { + if (std::memcmp(pdb_content->data(), detail::kMicrosoftPdb7Magic.data(), detail::kMicrosoftPdb7Magic.size()) != 0) { FIXME_NO_ARG(1, "Only PDB7 is supported atm"); return {}; } - // We gamin + // Initialize parser // function_list_t result = {}; + const detail::V7Parser parser(pdb_content->data(), pdb_content->size()); - const detail::V7Parser parser(pdb_content.data(), pdb_content.size()); - parser.iter_symbols( - [&result, parser](detail::DBIRecordProc32* sym) -> void { + /// Iterate procedures + parser.iter_symbols( + [&result, parser](detail::DBIFunction* sym) -> void { // NOLINT(bugprone-exception-escape) auto& item = result.emplace_back(); - item.valid = true; // probably we should check for something first - item.name = sym->Name; + item.valid = true; + item.name = static_cast(sym->Name); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) item.size = std::make_optional(sym->Size); const auto segment = parser.get_section(sym->Segment - 1); if (!segment.has_value()) { item.valid = false; - logger::warn("pdb: Unable to obtain segment base num[{}] func[{}]", sym->Segment, sym->Name); + FIXME(1, "pdb: unable to obtain segment {}", sym->Segment); return; } - item.rva = segment.value() + sym->Offset; + item.rva = *segment + sym->Offset; }, detail::e_symbol_kind::S_LPROC32, // Iterating over local procedures detail::e_symbol_kind::S_GPROC32 // Iterating over global procedures ); + /// Iterate public symbols + parser.iter_symbols( + [&result, parser](detail::DBIPublicSymbol* sym) -> void { + /// We are looking only for functions + if ((sym->Flags & detail::PublicSymFlags::Function) == 0U) { + return; + } + + /// Inserting new function + auto& item = result.emplace_back(); + item.valid = true; + item.name = static_cast(sym->Name); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay) + + /// Looking for segment + const auto segment = parser.get_section(sym->Segment - 1); + if (!segment.has_value()) { + FIXME(1, "pdb: unable to obtain segment {}", sym->Segment); + return; + } + + item.rva = *segment + sym->Offset; + }, + detail::e_symbol_kind::S_PUB32 // Public symbols + ); + + /// \todo: @es3n1n: S_EXPORT + // We are done here // return result; diff --git a/src/lib/func_parser/pdb/pdb.hpp b/src/lib/func_parser/pdb/pdb.hpp index 81d199f..10a2c0d 100644 --- a/src/lib/func_parser/pdb/pdb.hpp +++ b/src/lib/func_parser/pdb/pdb.hpp @@ -1,7 +1,7 @@ #pragma once #include "func_parser/common/common.hpp" #include "pe/pe.hpp" -#include "util/files.hpp" +#include // \todo: @es3n1n: validate that the pdb could be used for provided file // \todo: @es3n1n: add custom pdb server support? diff --git a/src/lib/mathop/mathop.hpp b/src/lib/mathop/mathop.hpp index af51e22..68de09d 100644 --- a/src/lib/mathop/mathop.hpp +++ b/src/lib/mathop/mathop.hpp @@ -1,7 +1,6 @@ #pragma once #include "mathop/operations/operations.hpp" -#include "util/random.hpp" -#include "util/types.hpp" +#include #include @@ -33,7 +32,7 @@ namespace mathop { /// \param assembler zasm assembler /// \param dst_reg destination register void lift_revert(zasm::x86::Assembler* assembler, const zasm::x86::Gp dst_reg) const { - return operation_->lift_revert(assembler, dst_reg, lift_rhs_); + operation_->lift_revert(assembler, dst_reg, lift_rhs_); } /// \brief Get the emulation argument @@ -86,7 +85,7 @@ namespace mathop { /// \brief Expression with operations class Expression { public: - DEFAULT_CTOR_DTOR(Expression); + DEFAULT_CT_CTOR_DTOR(Expression); DEFAULT_COPY(Expression); /// \brief Emplace new operation @@ -103,7 +102,7 @@ namespace mathop { [[nodiscard]] ArgumentImm emulate(const ArgumentImm start_value) const { ArgumentImm result = start_value; - for (auto& operation : operations_) { + for (const auto& operation : operations_) { result = operation.emulate(result); } @@ -146,7 +145,7 @@ namespace mathop { private: /// \brief A list of operations - std::vector operations_ = {}; + std::vector operations_; }; /// \brief Expression generator @@ -175,7 +174,7 @@ namespace mathop { /// Generate the random expressions for (std::size_t i = 0; i < num_operations; ++i) { - auto& operation = rnd::item(operations_); + const auto& operation = rnd::item(operations_); result.emplace_operation(operation.get(), bit_size); } @@ -184,6 +183,6 @@ namespace mathop { private: /// \brief List of supported operations - std::vector> operations_ = {}; + std::vector> operations_; }; } // namespace mathop diff --git a/src/lib/mathop/operations/impl/add.cpp b/src/lib/mathop/operations/impl/add.cpp index db91f83..db5abae 100644 --- a/src/lib/mathop/operations/impl/add.cpp +++ b/src/lib/mathop/operations/impl/add.cpp @@ -8,7 +8,7 @@ namespace mathop::operations { ArgumentImm Add::emulate(ArgumentImm op1, std::optional op2) const { ArgumentImm result; std::visit( - [&](Ty&& op1_value) -> void { // + [&](Ty op1_value) -> void { // using Decay = std::decay_t; result.emplace(op1_value + std::get(*op2)); }, diff --git a/src/lib/mathop/operations/impl/dec.cpp b/src/lib/mathop/operations/impl/dec.cpp index 96ef07c..0e136a8 100644 --- a/src/lib/mathop/operations/impl/dec.cpp +++ b/src/lib/mathop/operations/impl/dec.cpp @@ -1,15 +1,15 @@ #include "mathop/operations/impl/util.hpp" -#include "util/random.hpp" +#include namespace mathop::operations { /// \brief Emulate the math operation under the two operands /// \param op1 lhs /// \param op2 rhs /// \return emulated result - ArgumentImm Dec::emulate(ArgumentImm op1, std::optional op2) const { + ArgumentImm Dec::emulate(ArgumentImm op1, std::optional op2 [[maybe_unused]]) const { ArgumentImm result; std::visit( - [&](Ty&& op1_value) -> void { // + [&](Ty op1_value) -> void { // result.emplace>(op1_value - 1); }, op1); @@ -20,7 +20,7 @@ namespace mathop::operations { /// \param assembler zasm assembler /// \param operand dst operand /// \param argument optional rhs - void Dec::lift_revert(zasm::x86::Assembler* assembler, const zasm::x86::Gp operand, std::optional argument) const { + void Dec::lift_revert(zasm::x86::Assembler* assembler, const zasm::x86::Gp operand, std::optional argument [[maybe_unused]]) const { assembler->inc(operand); } } // namespace mathop::operations \ No newline at end of file diff --git a/src/lib/mathop/operations/impl/inc.cpp b/src/lib/mathop/operations/impl/inc.cpp index b70db54..57a86c6 100644 --- a/src/lib/mathop/operations/impl/inc.cpp +++ b/src/lib/mathop/operations/impl/inc.cpp @@ -1,14 +1,15 @@ #include "mathop/operations/impl/util.hpp" -#include "util/random.hpp" +#include namespace mathop::operations { /// \brief Emulate the math operation under the two operands /// \param op1 lhs + /// \param op2 rhs /// \return emulated result - ArgumentImm Inc::emulate(ArgumentImm op1, std::optional) const { + ArgumentImm Inc::emulate(ArgumentImm op1, std::optional op2 [[maybe_unused]]) const { ArgumentImm result; std::visit( - [&](Ty&& op1_value) -> void { // + [&](Ty op1_value) -> void { // result.emplace>(op1_value + 1); }, op1); @@ -18,7 +19,8 @@ namespace mathop::operations { /// \brief Lift the revert operation for this math operation /// \param assembler zasm assembler /// \param operand dst operand - void Inc::lift_revert(zasm::x86::Assembler* assembler, const zasm::x86::Gp operand, std::optional) const { + /// \param argument optional rhs + void Inc::lift_revert(zasm::x86::Assembler* assembler, const zasm::x86::Gp operand, std::optional argument [[maybe_unused]]) const { assembler->dec(operand); } } // namespace mathop::operations \ No newline at end of file diff --git a/src/lib/mathop/operations/impl/neg.cpp b/src/lib/mathop/operations/impl/neg.cpp index b5aeb2d..c8dfe34 100644 --- a/src/lib/mathop/operations/impl/neg.cpp +++ b/src/lib/mathop/operations/impl/neg.cpp @@ -1,14 +1,15 @@ #include "mathop/operations/impl/util.hpp" -#include "util/random.hpp" +#include namespace mathop::operations { /// \brief Emulate the math operation under the two operands /// \param op1 lhs + /// \param op2 rhs /// \return emulated result - ArgumentImm Neg::emulate(ArgumentImm op1, std::optional) const { + ArgumentImm Neg::emulate(ArgumentImm op1, std::optional op2 [[maybe_unused]]) const { ArgumentImm result; std::visit( - [&](Ty&& op1_value) -> void { // + [&](Ty op1_value) -> void { // result.emplace>(-op1_value); }, op1); @@ -18,7 +19,8 @@ namespace mathop::operations { /// \brief Lift the revert operation for this math operation /// \param assembler zasm assembler /// \param operand dst operand - void Neg::lift_revert(zasm::x86::Assembler* assembler, const zasm::x86::Gp operand, std::optional) const { + /// \param argument optional rhs + void Neg::lift_revert(zasm::x86::Assembler* assembler, const zasm::x86::Gp operand, std::optional argument [[maybe_unused]]) const { assembler->neg(operand); } } // namespace mathop::operations \ No newline at end of file diff --git a/src/lib/mathop/operations/impl/not.cpp b/src/lib/mathop/operations/impl/not.cpp index da50bee..24a8a7e 100644 --- a/src/lib/mathop/operations/impl/not.cpp +++ b/src/lib/mathop/operations/impl/not.cpp @@ -1,15 +1,17 @@ #include "mathop/operations/impl/util.hpp" -#include "util/random.hpp" +#include namespace mathop::operations { /// \brief Emulate the math operation under the two operands /// \param op1 lhs + /// \param op2 rhs /// \return emulated result - ArgumentImm Not::emulate(ArgumentImm op1, std::optional) const { + ArgumentImm Not::emulate(ArgumentImm op1, std::optional op2 [[maybe_unused]]) const { ArgumentImm result; std::visit( - [&](Ty&& op1_value) -> void { // - result.emplace>(~op1_value); + [&](Ty op1_value) -> void { + // \todo @es3n1n: Add unsigned safe casts + result.emplace>(~op1_value); // NOLINT(hicpp-signed-bitwise) }, op1); return result; @@ -18,7 +20,8 @@ namespace mathop::operations { /// \brief Lift the revert operation for this math operation /// \param assembler zasm assembler /// \param operand dst operand - void Not::lift_revert(zasm::x86::Assembler* assembler, const zasm::x86::Gp operand, std::optional) const { + /// \param argument optional rhs + void Not::lift_revert(zasm::x86::Assembler* assembler, const zasm::x86::Gp operand, std::optional argument [[maybe_unused]]) const { assembler->not_(operand); } } // namespace mathop::operations \ No newline at end of file diff --git a/src/lib/mathop/operations/impl/sub.cpp b/src/lib/mathop/operations/impl/sub.cpp index c941379..ce25e1d 100644 --- a/src/lib/mathop/operations/impl/sub.cpp +++ b/src/lib/mathop/operations/impl/sub.cpp @@ -1,5 +1,5 @@ #include "mathop/operations/impl/util.hpp" -#include "util/random.hpp" +#include namespace mathop::operations { /// \brief Emulate the math operation under the two operands @@ -9,7 +9,7 @@ namespace mathop::operations { ArgumentImm Sub::emulate(ArgumentImm op1, std::optional op2) const { ArgumentImm result; std::visit( - [&](Ty&& op1_value) -> void { // + [&](Ty op1_value) -> void { // result.emplace>(op1_value - std::get>(*op2)); }, op1); diff --git a/src/lib/mathop/operations/impl/util.hpp b/src/lib/mathop/operations/impl/util.hpp index 91af0bd..0ab9e6a 100644 --- a/src/lib/mathop/operations/impl/util.hpp +++ b/src/lib/mathop/operations/impl/util.hpp @@ -1,7 +1,6 @@ #pragma once #include "mathop/operations/operations.hpp" -#include "util/random.hpp" - +#include #include namespace mathop::detail { diff --git a/src/lib/mathop/operations/impl/xor.cpp b/src/lib/mathop/operations/impl/xor.cpp index 92ee58c..21fe669 100644 --- a/src/lib/mathop/operations/impl/xor.cpp +++ b/src/lib/mathop/operations/impl/xor.cpp @@ -1,5 +1,5 @@ #include "mathop/operations/impl/util.hpp" -#include "util/random.hpp" +#include namespace mathop::operations { /// \brief Emulate the math operation under the two operands @@ -9,8 +9,9 @@ namespace mathop::operations { ArgumentImm Xor::emulate(ArgumentImm op1, std::optional op2) const { ArgumentImm result; std::visit( - [&](Ty&& op1_value) -> void { // - result.emplace>(op1_value ^ std::get>(*op2)); + [&](Ty op1_value) -> void { // + // \todo @es3n1n: Add unsigned safe casts + result.emplace>(op1_value ^ std::get>(*op2)); // NOLINT(hicpp-signed-bitwise) }, op1); return result; diff --git a/src/lib/mathop/operations/operation.hpp b/src/lib/mathop/operations/operation.hpp index 19a7336..4af9c2f 100644 --- a/src/lib/mathop/operations/operation.hpp +++ b/src/lib/mathop/operations/operation.hpp @@ -3,8 +3,8 @@ #include #include -#include #include +#include #include @@ -65,7 +65,7 @@ namespace mathop { /// \brief Math operation representation class Operation { public: - DEFAULT_CTOR(Operation); + DEFAULT_CT_CTOR(Operation); virtual ~Operation() = default; /// \brief Indicates whether this operation should have a second argument or not diff --git a/src/lib/obfuscator/obfuscator.cpp b/src/lib/obfuscator/obfuscator.cpp index 685fdd3..a674e23 100644 --- a/src/lib/obfuscator/obfuscator.cpp +++ b/src/lib/obfuscator/obfuscator.cpp @@ -4,9 +4,10 @@ #include "obfuscator/config_merger/config_merger.hpp" #include "obfuscator/function.hpp" #include "obfuscator/transforms/scheduler.hpp" -#include "util/logger.hpp" -#include "util/progress.hpp" -#include "util/random.hpp" + +#include +#include +#include namespace obfuscator { constexpr size_t kTextSectionAlignment = 0x10; @@ -23,7 +24,7 @@ namespace obfuscator { // Add functions from config, that we should protecc // - auto analysis_progress = util::Progress("obfuscator: setting up functions", config_.size()); + auto analysis_progress = progress::Progress("obfuscator: setting up functions", config_.size()); for (auto& configuration : config_) { add_function(configuration); analysis_progress.step(); @@ -93,7 +94,7 @@ namespace obfuscator { auto transforms = scheduler.select_transforms(tags); /// Init the progress bar - auto progress = util::Progress(std::format("obfuscator: obfuscating {}", obf_func.parsed_func.name), transforms.size()); + auto progress = progress::Progress(std::format("obfuscator: obfuscating {}", obf_func.parsed_func.name), transforms.size()); /// An util that would check the chances and all this other crap, that would be /// needed for like every possible function/transform @@ -184,7 +185,7 @@ namespace obfuscator { template void Instance::assemble() { /// Estimating section size - auto size_estimation_progress = util::Progress("obfuscator: estimating section size", functions_.size()); + auto size_estimation_progress = progress::Progress("obfuscator: estimating section size", functions_.size()); std::size_t section_size = 0; for (auto& func : functions_) { const auto program_size = easm::estimate_program_size(*func.analysed.program); @@ -199,28 +200,31 @@ namespace obfuscator { memory::address virt_address = new_sec.virtual_address; /// Iterate over the obfuscated functions - auto linking_progress = util::Progress("obfuscator: linking functions", functions_.size()); + auto linking_progress = progress::Progress("obfuscator: linking functions", functions_.size()); for (auto& [func, _] : functions_) { /// \todo @es3n1n: perhaps i should split this monstrosity into a separate functions /// Erase the original function code for (auto& basic_block : *func.bb_storage) { for (auto& insn : basic_block) { + /// Clang-tidy is working a bit weird with smart pointers and `bugprone-unchecked-optional-access` + auto* raw_ptr = insn.get(); + /// No need to erase instructions that doesn't exist - if (!insn->rva.has_value()) { + if (!raw_ptr->rva.has_value() || !raw_ptr->length.has_value()) { continue; } /// Generate random bytes - const auto randomized = rnd::bytes(*insn->length); + const auto randomized = rnd::bytes(*raw_ptr->length); /// Replace instruction with junk - auto* insn_ptr = image_->rva_to_ptr(*insn->rva); + auto* insn_ptr = image_->rva_to_ptr(*raw_ptr->rva); std::memcpy(insn_ptr, randomized.data(), randomized.size()); /// Remove pe relocation, if there's any - if (insn->reloc.type == analysis::insn_reloc_t::e_type::HEADER) { - image_->relocations.erase(*insn->rva + insn->reloc.offset.value_or(0)); + if (raw_ptr->reloc.type == analysis::insn_reloc_t::e_type::HEADER) { + image_->relocations.erase(*raw_ptr->rva + raw_ptr->reloc.offset.value_or(0)); } } } @@ -234,7 +238,7 @@ namespace obfuscator { std::memcpy(func_start_ptr, jmp_data->data(), jmp_data->size()); /// Assemble the obfuscated function - auto assemble_progress = util::Progress(std::format("obfuscator: assembling {}", func.parsed_func.name), 1); + auto assemble_progress = progress::Progress(std::format("obfuscator: assembling {}", func.parsed_func.name), 1); const auto assembled = easm::assemble_program(virt_address + img_base, *func.program); assemble_progress.step(); @@ -248,7 +252,7 @@ namespace obfuscator { /// Save the new relocations for (const zasm::RelocationInfo& relocation : assembled.relocations) { /// Map zasm relocation kind to windows relocation kind - win::reloc_type_id win_reloc_type; + win::reloc_type_id win_reloc_type; // NOLINT(cppcoreguidelines-init-variables) switch (relocation.kind) { default: case zasm::RelocationType::None: @@ -280,7 +284,7 @@ namespace obfuscator { } template - void Instance::save() { + std::filesystem::path Instance::save() { logger::info("obfuscator: saving.."); auto new_img = image_->rebuild_pe_image(); @@ -293,9 +297,18 @@ namespace obfuscator { const auto new_filename = filename_no_ext + ".protected" + file_ext; out_path = out_path.replace_filename(new_filename); - util::write_file(out_path, new_img.data(), new_img.size()); + files::write_file(out_path, new_img.data(), new_img.size()); logger::info("obfuscator: saved output to {}", out_path.string()); + return out_path; + } + + template + std::filesystem::path Instance::run() { + setup(); + obfuscate(); + assemble(); + return save(); } PE_DECL_TEMPLATE_CLASSES(Instance); diff --git a/src/lib/obfuscator/obfuscator.hpp b/src/lib/obfuscator/obfuscator.hpp index 9f98f00..075ae71 100644 --- a/src/lib/obfuscator/obfuscator.hpp +++ b/src/lib/obfuscator/obfuscator.hpp @@ -17,7 +17,10 @@ namespace obfuscator { void add_function(const config_parser::function_configuration_t& configuration); void obfuscate(); void assemble(); - void save(); + std::filesystem::path save(); + + // setup() -> obfuscate() -> assemble() -> save() + std::filesystem::path run(); struct function_t { analysis::Function analysed; @@ -26,8 +29,8 @@ namespace obfuscator { private: Img* image_ = nullptr; - config_parser::Config config_ = {}; - func_parser::Instance func_parser_ = {}; - std::vector functions_ = {}; + config_parser::Config config_; + func_parser::Instance func_parser_; + std::vector functions_; }; } // namespace obfuscator \ No newline at end of file diff --git a/src/lib/obfuscator/transforms/configs.hpp b/src/lib/obfuscator/transforms/configs.hpp index 0b93795..07c1563 100644 --- a/src/lib/obfuscator/transforms/configs.hpp +++ b/src/lib/obfuscator/transforms/configs.hpp @@ -1,12 +1,12 @@ #pragma once -#include "obfuscator/transforms/types.hpp" -#include "util/string_parser.hpp" -#include "util/types.hpp" #include +#include "obfuscator/transforms/types.hpp" +#include + namespace obfuscator { namespace detail { - enum e_shared_config_variable_name_index { + enum e_shared_config_variable_name_index : std::uint8_t { CHANCE = 0, REPEAT_TIMES = 1, }; @@ -60,24 +60,24 @@ namespace obfuscator { /// \param value Var stringified value /// \param override_default Should we override the default value too? /// \return true on success, false on failure - bool try_load(const std::string_view name, const std::string_view value, const bool override_default = false) noexcept { + bool try_load(const std::string_view name, const std::string_view value, const bool override_default = false) { static std::unordered_map> callbacks = {}; /// A little bit of overhead with this once flag, but now the init looks n i c e static std::once_flag fl; std::call_once(fl, []() -> void { callbacks[detail::kSharedConfigsVariableNames[detail::CHANCE]] = [](auto* instance_, const auto value_, const auto override_default_) { - instance_->chance(util::string::parse_uint8(value_), override_default_); + instance_->chance(string_parser::parse_uint8(value_), override_default_); }; callbacks[detail::kSharedConfigsVariableNames[detail::REPEAT_TIMES]] = [](auto* instance_, const auto value_, const auto override_default_) { - instance_->repeat_times(util::string::parse_uint8(value_), override_default_); + instance_->repeat_times(string_parser::parse_uint8(value_), override_default_); }; }); /// Try to find the loader, and load if found - if (const auto it = callbacks.find(name.data()); it != std::end(callbacks)) { + if (const auto it = callbacks.find(std::string{name}); it != std::end(callbacks)) { it->second(this, value, override_default); return true; } @@ -89,7 +89,7 @@ namespace obfuscator { /// \brief Stringify var value by its name /// \param var_name variable name /// \return stringified value - [[nodiscard]] std::string stringify_var(const std::string_view var_name) const noexcept { + [[nodiscard]] std::string stringify_var(const std::string_view var_name) const { if (var_name == detail::kSharedConfigsVariableNames[detail::CHANCE]) { return std::to_string(chance_); } @@ -117,7 +117,7 @@ namespace obfuscator { std::uint8_t repeat_times_ = repeat_times_default_; /// \brief Transform run chance in percents - std::uint8_t chance_default_ = 30; // (from 0 to 100)% + std::uint8_t chance_default_ = 100; // (from 0 to 100)% std::uint8_t chance_ = chance_default_; }; @@ -158,7 +158,7 @@ namespace obfuscator { private: /// \brief Configuration storage - std::unordered_map configurations_ = {}; + std::unordered_map configurations_; }; /// \brief Transform configuration storage @@ -170,10 +170,10 @@ namespace obfuscator { class Var { public: - DEFAULT_CTOR_DTOR(Var); + DEFAULT_CT_CTOR_DTOR(Var); DEFAULT_COPY(Var); - enum Type { + enum Type : std::uint8_t { GLOBAL = 0, // one option for all functions PER_FUNCTION, // sets per function }; @@ -213,7 +213,7 @@ namespace obfuscator { /// \brief Set new value parsed from string /// \param value string that contain the new value void parse_value(const std::string_view value) { - util::string::parse_to_any(value_, value); + string_parser::parse_to_any(value_, value); } /// \brief Verify that this variable has a value set @@ -255,7 +255,7 @@ namespace obfuscator { /// \brief Serialize current value as string /// \return string [[nodiscard]] std::string serialize() const { - return util::string::serialize_any(value_); + return string_parser::serialize_any(value_); } /// \brief Set the variable info @@ -275,11 +275,11 @@ namespace obfuscator { private: /// \brief Value holder - std::any value_ = {}; + std::any value_; /// \brief First value holder - std::any default_value_ = {}; + std::any default_value_; /// \brief Var name - std::string name_ = {}; + std::string name_; /// \brief Short description (1 line max) std::optional short_description_ = std::nullopt; /// \brief Is var required to set by user @@ -356,6 +356,6 @@ namespace obfuscator { private: /// \brief A map that stores all the variables with their indices - std::unordered_map variables_ = {}; + std::unordered_map variables_; }; } // namespace obfuscator diff --git a/src/lib/obfuscator/transforms/scheduler.hpp b/src/lib/obfuscator/transforms/scheduler.hpp index 4011124..f9c18b2 100644 --- a/src/lib/obfuscator/transforms/scheduler.hpp +++ b/src/lib/obfuscator/transforms/scheduler.hpp @@ -11,7 +11,7 @@ namespace obfuscator { template class TransformContainer { public: - DEFAULT_CTOR_DTOR(TransformContainer); + DEFAULT_CT_CTOR_DTOR(TransformContainer); NON_COPYABLE(TransformContainer); using T = Img; using TransformPtr = std::unique_ptr>; @@ -19,7 +19,8 @@ namespace obfuscator { /// \brief Register a transform under its tag /// \tparam Ty Transform type - template