├── .clang-format ├── .clang-tidy ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github └── workflows │ └── c.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── index.html └── style.css ├── include ├── breakpoint_handler.h ├── debuggee.h ├── debugger.h ├── debugger_commands.h ├── ld_preload.h ├── symtab.h └── ui.h ├── lib ├── fopen_override.c ├── getenv_override.c ├── prctl_override.c ├── ptrace_override.c └── setvbuf_unbuffered.c ├── mock_target └── mock_target.c ├── src ├── breakpoint_handler.c ├── debuggee.c ├── debugger.c ├── debugger_commands.c ├── ld_preload.c ├── main.c ├── symtab.c └── ui.c └── tests └── test_debugger.c /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveAssignments: 8 | Enabled: false 9 | AcrossEmptyLines: false 10 | AcrossComments: false 11 | AlignCompound: false 12 | AlignFunctionPointers: false 13 | PadOperators: true 14 | AlignConsecutiveBitFields: 15 | Enabled: false 16 | AcrossEmptyLines: false 17 | AcrossComments: false 18 | AlignCompound: false 19 | AlignFunctionPointers: false 20 | PadOperators: false 21 | AlignConsecutiveDeclarations: 22 | Enabled: false 23 | AcrossEmptyLines: false 24 | AcrossComments: false 25 | AlignCompound: false 26 | AlignFunctionPointers: false 27 | PadOperators: false 28 | AlignConsecutiveMacros: 29 | Enabled: false 30 | AcrossEmptyLines: false 31 | AcrossComments: false 32 | AlignCompound: false 33 | AlignFunctionPointers: false 34 | PadOperators: false 35 | AlignConsecutiveShortCaseStatements: 36 | Enabled: false 37 | AcrossEmptyLines: false 38 | AcrossComments: false 39 | AlignCaseColons: false 40 | AlignEscapedNewlines: Right 41 | AlignOperands: Align 42 | AlignTrailingComments: 43 | Kind: Always 44 | OverEmptyLines: 0 45 | AllowAllArgumentsOnNextLine: true 46 | AllowAllParametersOfDeclarationOnNextLine: true 47 | AllowBreakBeforeNoexceptSpecifier: Never 48 | AllowShortBlocksOnASingleLine: Never 49 | AllowShortCaseLabelsOnASingleLine: false 50 | AllowShortCompoundRequirementOnASingleLine: true 51 | AllowShortEnumsOnASingleLine: true 52 | AllowShortFunctionsOnASingleLine: All 53 | AllowShortIfStatementsOnASingleLine: Never 54 | AllowShortLambdasOnASingleLine: All 55 | AllowShortLoopsOnASingleLine: false 56 | AlwaysBreakAfterDefinitionReturnType: None 57 | AlwaysBreakAfterReturnType: None 58 | AlwaysBreakBeforeMultilineStrings: false 59 | AlwaysBreakTemplateDeclarations: MultiLine 60 | AttributeMacros: 61 | - __capability 62 | BinPackArguments: true 63 | BinPackParameters: true 64 | BitFieldColonSpacing: Both 65 | BraceWrapping: 66 | AfterCaseLabel: false 67 | AfterClass: false 68 | AfterControlStatement: Never 69 | AfterEnum: false 70 | AfterExternBlock: false 71 | AfterFunction: false 72 | AfterNamespace: false 73 | AfterObjCDeclaration: false 74 | AfterStruct: false 75 | AfterUnion: false 76 | BeforeCatch: false 77 | BeforeElse: false 78 | BeforeLambdaBody: false 79 | BeforeWhile: false 80 | IndentBraces: false 81 | SplitEmptyFunction: true 82 | SplitEmptyRecord: true 83 | SplitEmptyNamespace: true 84 | BreakAdjacentStringLiterals: true 85 | BreakAfterAttributes: Leave 86 | BreakAfterJavaFieldAnnotations: false 87 | BreakArrays: true 88 | BreakBeforeBinaryOperators: None 89 | BreakBeforeConceptDeclarations: Always 90 | BreakBeforeBraces: Attach 91 | BreakBeforeInlineASMColon: OnlyMultiline 92 | BreakBeforeTernaryOperators: true 93 | BreakConstructorInitializers: BeforeColon 94 | BreakInheritanceList: BeforeColon 95 | BreakStringLiterals: true 96 | ColumnLimit: 80 97 | CommentPragmas: '^ IWYU pragma:' 98 | CompactNamespaces: false 99 | ConstructorInitializerIndentWidth: 4 100 | ContinuationIndentWidth: 4 101 | Cpp11BracedListStyle: true 102 | DerivePointerAlignment: false 103 | DisableFormat: false 104 | EmptyLineAfterAccessModifier: Never 105 | EmptyLineBeforeAccessModifier: LogicalBlock 106 | ExperimentalAutoDetectBinPacking: false 107 | FixNamespaceComments: true 108 | ForEachMacros: 109 | - foreach 110 | - Q_FOREACH 111 | - BOOST_FOREACH 112 | IfMacros: 113 | - KJ_IF_MAYBE 114 | IncludeBlocks: Preserve 115 | IncludeCategories: 116 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 117 | Priority: 2 118 | SortPriority: 0 119 | CaseSensitive: false 120 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 121 | Priority: 3 122 | SortPriority: 0 123 | CaseSensitive: false 124 | - Regex: '.*' 125 | Priority: 1 126 | SortPriority: 0 127 | CaseSensitive: false 128 | IncludeIsMainRegex: '(Test)?$' 129 | IncludeIsMainSourceRegex: '' 130 | IndentAccessModifiers: false 131 | IndentCaseBlocks: false 132 | IndentCaseLabels: false 133 | IndentExternBlock: AfterExternBlock 134 | IndentGotoLabels: true 135 | IndentPPDirectives: None 136 | IndentRequiresClause: true 137 | IndentWidth: 8 138 | IndentWrappedFunctionNames: false 139 | InsertBraces: false 140 | InsertNewlineAtEOF: false 141 | InsertTrailingCommas: None 142 | IntegerLiteralSeparator: 143 | Binary: 0 144 | BinaryMinDigits: 0 145 | Decimal: 0 146 | DecimalMinDigits: 0 147 | Hex: 0 148 | HexMinDigits: 0 149 | JavaScriptQuotes: Leave 150 | JavaScriptWrapImports: true 151 | KeepEmptyLinesAtTheStartOfBlocks: true 152 | KeepEmptyLinesAtEOF: false 153 | LambdaBodyIndentation: Signature 154 | LineEnding: DeriveLF 155 | MacroBlockBegin: '' 156 | MacroBlockEnd: '' 157 | MaxEmptyLinesToKeep: 1 158 | NamespaceIndentation: None 159 | ObjCBinPackProtocolList: Auto 160 | ObjCBlockIndentWidth: 2 161 | ObjCBreakBeforeNestedBlockParam: true 162 | ObjCSpaceAfterProperty: false 163 | ObjCSpaceBeforeProtocolList: true 164 | PackConstructorInitializers: BinPack 165 | PenaltyBreakAssignment: 2 166 | PenaltyBreakBeforeFirstCallParameter: 19 167 | PenaltyBreakComment: 300 168 | PenaltyBreakFirstLessLess: 120 169 | PenaltyBreakOpenParenthesis: 0 170 | PenaltyBreakScopeResolution: 500 171 | PenaltyBreakString: 1000 172 | PenaltyBreakTemplateDeclaration: 10 173 | PenaltyExcessCharacter: 1000000 174 | PenaltyIndentedWhitespace: 0 175 | PenaltyReturnTypeOnItsOwnLine: 60 176 | PointerAlignment: Right 177 | PPIndentWidth: -1 178 | QualifierAlignment: Leave 179 | ReferenceAlignment: Pointer 180 | ReflowComments: true 181 | RemoveBracesLLVM: false 182 | RemoveParentheses: Leave 183 | RemoveSemicolon: false 184 | RequiresClausePosition: OwnLine 185 | RequiresExpressionIndentation: OuterScope 186 | SeparateDefinitionBlocks: Leave 187 | ShortNamespaceLines: 1 188 | SkipMacroDefinitionBody: false 189 | SortIncludes: CaseSensitive 190 | SortJavaStaticImport: Before 191 | SortUsingDeclarations: LexicographicNumeric 192 | SpaceAfterCStyleCast: false 193 | SpaceAfterLogicalNot: false 194 | SpaceAfterTemplateKeyword: true 195 | SpaceAroundPointerQualifiers: Default 196 | SpaceBeforeAssignmentOperators: true 197 | SpaceBeforeCaseColon: false 198 | SpaceBeforeCpp11BracedList: false 199 | SpaceBeforeCtorInitializerColon: true 200 | SpaceBeforeInheritanceColon: true 201 | SpaceBeforeJsonColon: false 202 | SpaceBeforeParens: ControlStatements 203 | SpaceBeforeParensOptions: 204 | AfterControlStatements: true 205 | AfterForeachMacros: true 206 | AfterFunctionDefinitionName: false 207 | AfterFunctionDeclarationName: false 208 | AfterIfMacros: true 209 | AfterOverloadedOperator: false 210 | AfterPlacementOperator: true 211 | AfterRequiresInClause: false 212 | AfterRequiresInExpression: false 213 | BeforeNonEmptyParentheses: false 214 | SpaceBeforeRangeBasedForLoopColon: true 215 | SpaceBeforeSquareBrackets: false 216 | SpaceInEmptyBlock: false 217 | SpacesBeforeTrailingComments: 1 218 | SpacesInAngles: Never 219 | SpacesInContainerLiterals: true 220 | SpacesInLineCommentPrefix: 221 | Minimum: 1 222 | Maximum: -1 223 | SpacesInParens: Never 224 | SpacesInParensOptions: 225 | InCStyleCasts: false 226 | InConditionalStatements: false 227 | InEmptyParentheses: false 228 | Other: false 229 | SpacesInSquareBrackets: false 230 | Standard: Latest 231 | StatementAttributeLikeMacros: 232 | - Q_EMIT 233 | StatementMacros: 234 | - Q_UNUSED 235 | - QT_REQUIRE_VERSION 236 | TabWidth: 8 237 | UseTab: Never 238 | VerilogBreakBetweenInstancePorts: true 239 | WhitespaceSensitiveMacros: 240 | - BOOST_PP_STRINGIZE 241 | - CF_SWIFT_NAME 242 | - NS_SWIFT_NAME 243 | - PP_STRINGIZE 244 | - STRINGIZE 245 | ... 246 | 247 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | ExtraArgs: [] 3 | HeaderFileExtensions: 4 | - h 5 | HeaderFilterRegex: "./include/*" 6 | ImplementationFileExtensions: 7 | - c 8 | FormatStyle: file 9 | Checks: "-*,\ 10 | bugprone-*,\ 11 | -bugprone-easily-swappable-parameters, \ 12 | -bugprone-assignment-in-if-condition, \ 13 | -bugprone-reserved-identifier, \ 14 | cert-*,\ 15 | -cert-msc30-c, \ 16 | -cert-msc32-c, \ 17 | -cert-msc50-cpp, \ 18 | -cert-msc51-cpp, \ 19 | -cert-dcl37-c, \ 20 | -cert-dcl51-cpp, 21 | clang-analyzer-*,\ 22 | -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,\ 23 | concurrency-*,\ 24 | -concurrency-mt-unsafe, \ 25 | linuxkernel-*,\ 26 | llvm-*,\ 27 | -llvm-header-guard, \ 28 | misc-*,\ 29 | modernize-*,\ 30 | -modernize-use-trailing-return-type, \ 31 | -modernize-use-using, \ 32 | performance-*,\ 33 | -performance-enum-size, \ 34 | portability-*,\ 35 | readability-*, \ 36 | -readability-identifier-length" 37 | ... 38 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/cpp:1-ubuntu-24.04 2 | 3 | # ARG REINSTALL_CMAKE_VERSION_FROM_SOURCE="none" 4 | 5 | # Optionally install the cmake for vcpkg 6 | # COPY ./reinstall-cmake.sh /tmp/ 7 | 8 | # RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \ 9 | # chmod +x /tmp/reinstall-cmake.sh && /tmp/reinstall-cmake.sh ${REINSTALL_CMAKE_VERSION_FROM_SOURCE}; \ 10 | # fi \ 11 | # && rm -f /tmp/reinstall-cmake.sh 12 | 13 | # [Optional] Uncomment this section to install additional vcpkg ports. 14 | # RUN su vscode -c "${VCPKG_ROOT}/vcpkg install " 15 | 16 | # [Optional] Uncomment this section to install additional packages. 17 | RUN apt-get update && apt-get upgrade -y && export DEBIAN_FRONTEND=noninteractive \ 18 | && apt-get -y install --no-install-recommends \ 19 | neovim \ 20 | # Dependencies 21 | libcriterion-dev libcapstone-dev \ 22 | # Build tools 23 | clang-format-18 clang-tidy-18 -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/cpp 3 | { 4 | "name": "C++", 5 | "build": { 6 | "dockerfile": "Dockerfile" 7 | }, 8 | "features": { 9 | "ghcr.io/devcontainers-extra/features/vscode-cli:1": {} 10 | }, 11 | 12 | // Features to add to the dev container. More info: https://containers.dev/features. 13 | // "features": {}, 14 | 15 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 16 | // "forwardPorts": [], 17 | 18 | // Use 'postCreateCommand' to run commands after the container is created. 19 | // "postCreateCommand": "gcc -v", 20 | 21 | // Configure tool-specific properties. 22 | // "customizations": {}, 23 | 24 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 25 | "remoteUser": "root" 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/c.yml: -------------------------------------------------------------------------------- 1 | name: CMake Workflow 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | tags: 7 | - '*' 8 | pull_request: 9 | branches: ["main"] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | env: 16 | BUILD_TYPE: Release 17 | 18 | steps: 19 | - name: Checkout Repository 20 | uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | 24 | - name: Cache CMake and LLVM 25 | uses: actions/cache@v4 26 | with: 27 | path: | 28 | ~/.cache/llvm 29 | ~/.cmake 30 | build 31 | key: ${{ runner.os }}-cmake-${{ hashFiles('**/CMakeLists.txt') }} 32 | restore-keys: | 33 | ${{ runner.os }}-cmake- 34 | 35 | - name: Install Dependencies 36 | run: | 37 | sudo apt update 38 | sudo apt install -y wget gnupg lsb-release build-essential pkg-config 39 | wget https://apt.llvm.org/llvm.sh 40 | chmod +x llvm.sh 41 | sudo ./llvm.sh 18 42 | sudo apt install -y libcunit1 libcunit1-doc libcunit1-dev valgrind libcriterion-dev \ 43 | clang-format-18 clang-tidy-18 libcapstone-dev gcc 44 | 45 | - name: Setup CMake 46 | uses: jwlawson/actions-setup-cmake@v2 47 | with: 48 | cmake-version: "3.28.3" 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | 52 | - name: Verify Tool Versions 53 | run: | 54 | gcc --version 55 | clang-format-18 --version 56 | clang-tidy-18 --version 57 | cmake --version 58 | valgrind --version 59 | ctest --version 60 | 61 | - name: Configure CMake 62 | run: | 63 | cmake -B build -S . \ 64 | -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ 65 | -DCMAKE_VERBOSE_MAKEFILE=ON 66 | 67 | - name: Run Formatter 68 | run: cmake --build build --target format -- -j$(nproc) 69 | 70 | - name: Run Linter 71 | run: cmake --build build --target lint -- -j$(nproc) 72 | 73 | - name: Build Project 74 | run: cmake --build build -- -j$(nproc) 75 | 76 | - name: Run Tests 77 | run: | 78 | ctest --output-on-failure --test-dir build 79 | 80 | - name: Run Valgrind 81 | run: | 82 | echo "run" | valgrind --leak-check=full --show-leak-kinds=all ${PWD}/bin/z ${PWD}/bin/mock_target 83 | 84 | - name: Upload Test Coverage 85 | if: success() || failure() 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: test-results 89 | path: build/test-results.xml 90 | 91 | - name: Clean Build Artifacts 92 | if: ${{ always() }} 93 | run: cmake --build build --target clean_all 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # Z 55 | build/ 56 | bin/ 57 | CMakeCache.txt 58 | CMakeFiles/ 59 | cmake_install.cmake 60 | CTestTestfile.cmake 61 | Makefile 62 | Testing/ 63 | .cache/ 64 | 65 | notes.md 66 | *.log 67 | make 68 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/linenoise"] 2 | path = lib/linenoise 3 | url = https://github.com/antirez/linenoise.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | project("Z Anti-Anti-Debugger" VERSION 0.0.1 LANGUAGES C) 4 | 5 | # ----------------------------------------------------------------------------- 6 | # Global Settings 7 | # ----------------------------------------------------------------------------- 8 | 9 | # C Standard and global compile options 10 | set(CMAKE_C_STANDARD 99) 11 | set(CMAKE_C_STANDARD_REQUIRED TRUE) 12 | set(CMAKE_C_EXTENSIONS OFF) 13 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 14 | 15 | # Specify compiler and flags 16 | set(CMAKE_C_COMPILER gcc) 17 | set(CMAKE_C_FLAGS_DEBUG "-g -O0") 18 | set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG") 19 | 20 | # Global compile definitions and options 21 | add_compile_definitions(_POSIX_C_SOURCE=200809L) 22 | add_compile_options(-Wall -Wextra -pedantic) 23 | 24 | # ----------------------------------------------------------------------------- 25 | # Directory Variables 26 | # ----------------------------------------------------------------------------- 27 | 28 | set(SRC_DIR "${CMAKE_SOURCE_DIR}/src") 29 | set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include") 30 | set(LIB_DIR "${CMAKE_SOURCE_DIR}/lib") 31 | set(TESTS_DIR "${CMAKE_SOURCE_DIR}/tests") 32 | set(BIN_DIR "${CMAKE_SOURCE_DIR}/bin") 33 | set(MOCK_TARGET_DIR "${CMAKE_SOURCE_DIR}/mock_target") 34 | 35 | # Ensure the binary directory exists 36 | file(MAKE_DIRECTORY "${BIN_DIR}") 37 | 38 | # ----------------------------------------------------------------------------- 39 | # External Dependencies (using pkg-config) 40 | # ----------------------------------------------------------------------------- 41 | 42 | find_package(PkgConfig REQUIRED) 43 | 44 | # Capstone library 45 | pkg_check_modules(CAPSTONE REQUIRED capstone) 46 | if(CAPSTONE_FOUND) 47 | # Apply any extra flags provided by Capstone 48 | add_compile_definitions(${CAPSTONE_CFLAGS_OTHER}) 49 | else() 50 | message(FATAL_ERROR "Capstone library not found. Please install Capstone.") 51 | endif() 52 | 53 | # Criterion library (for testing) 54 | pkg_check_modules(CRITERION REQUIRED criterion) 55 | if(NOT CRITERION_FOUND) 56 | message(FATAL_ERROR "Criterion library not found. Please install Criterion.") 57 | endif() 58 | 59 | # ----------------------------------------------------------------------------- 60 | # Library Targets 61 | # ----------------------------------------------------------------------------- 62 | 63 | # Main project library 64 | add_library(z_lib STATIC 65 | "${SRC_DIR}/debugger.c" 66 | "${SRC_DIR}/debugger_commands.c" 67 | "${SRC_DIR}/debuggee.c" 68 | "${SRC_DIR}/breakpoint_handler.c" 69 | "${SRC_DIR}/symtab.c" 70 | "${SRC_DIR}/ui.c" 71 | "${SRC_DIR}/ld_preload.c" 72 | ) 73 | 74 | target_include_directories(z_lib PUBLIC 75 | "${INCLUDE_DIR}" 76 | ${CAPSTONE_INCLUDE_DIRS} 77 | ${CRITERION_INCLUDE_DIRS} 78 | ) 79 | 80 | target_link_libraries(z_lib PUBLIC 81 | ${CAPSTONE_LIBRARIES} 82 | ${CRITERION_LIBRARIES} 83 | ) 84 | 85 | # Linenoise library (compiled without warnings) 86 | add_library(linenoise STATIC 87 | "${LIB_DIR}/linenoise/linenoise.c" 88 | ) 89 | 90 | target_include_directories(linenoise PUBLIC 91 | "${LIB_DIR}/linenoise" 92 | ) 93 | 94 | target_link_libraries(z_lib PRIVATE linenoise) 95 | set_source_files_properties("${LIB_DIR}/linenoise/linenoise.c" 96 | PROPERTIES COMPILE_FLAGS "-w" 97 | ) 98 | 99 | # ----------------------------------------------------------------------------- 100 | # Executable Targets 101 | # ----------------------------------------------------------------------------- 102 | 103 | # Main executable 104 | add_executable(z "${SRC_DIR}/main.c") 105 | target_link_libraries(z PRIVATE z_lib) 106 | set_target_properties(z PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${BIN_DIR}") 107 | 108 | # Test executable 109 | add_executable(z_tests "${TESTS_DIR}/test_debugger.c") 110 | target_include_directories(z_tests PRIVATE "${INCLUDE_DIR}") 111 | target_link_libraries(z_tests PRIVATE z_lib criterion) 112 | 113 | enable_testing() 114 | add_test(NAME ZProjectTests COMMAND z_tests) 115 | 116 | # Mock target executable 117 | add_executable(mock_target "${MOCK_TARGET_DIR}/mock_target.c") 118 | set_target_properties(mock_target PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${BIN_DIR}") 119 | 120 | # ----------------------------------------------------------------------------- 121 | # Override Libraries 122 | # ----------------------------------------------------------------------------- 123 | 124 | function(add_override_library name source) 125 | add_library(${name}_override SHARED "${LIB_DIR}/${source}.c") 126 | set_target_properties(${name}_override PROPERTIES 127 | OUTPUT_NAME "${name}_intercept" 128 | LIBRARY_OUTPUT_DIRECTORY "${BIN_DIR}" 129 | RUNTIME_OUTPUT_DIRECTORY "${BIN_DIR}" 130 | POSITION_INDEPENDENT_CODE ON 131 | ) 132 | target_link_libraries(${name}_override PRIVATE dl) 133 | endfunction() 134 | 135 | add_override_library(ptrace ptrace_override) 136 | add_override_library(fopen fopen_override) 137 | add_override_library(prctl prctl_override) 138 | add_override_library(getenv getenv_override) 139 | 140 | add_library(setvbuf_unbuffered SHARED "${LIB_DIR}/setvbuf_unbuffered.c") 141 | set_target_properties(setvbuf_unbuffered PROPERTIES 142 | OUTPUT_NAME "setvbuf_unbuffered" 143 | LIBRARY_OUTPUT_DIRECTORY "${BIN_DIR}" 144 | RUNTIME_OUTPUT_DIRECTORY "${BIN_DIR}" 145 | POSITION_INDEPENDENT_CODE ON 146 | ) 147 | target_link_libraries(setvbuf_unbuffered PRIVATE dl) 148 | 149 | # ----------------------------------------------------------------------------- 150 | # Code Quality Targets 151 | # ----------------------------------------------------------------------------- 152 | 153 | find_program(LINTER clang-tidy-18) 154 | find_program(FORMATTER clang-format-18) 155 | 156 | add_custom_target(lint 157 | COMMAND ${LINTER} -p ${CMAKE_BINARY_DIR} --config-file=${CMAKE_SOURCE_DIR}/.clang-tidy ${SRC_DIR}/*.c ${INCLUDE_DIR}/*.h ${TESTS_DIR}/*.c 158 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 159 | ) 160 | 161 | add_custom_target(format 162 | COMMAND find "${SRC_DIR}" -name "*.c" -exec ${FORMATTER} -style=file -i {} + 163 | COMMAND find "${TESTS_DIR}" -name "*.c" -exec ${FORMATTER} -style=file -i {} + 164 | COMMAND find "${INCLUDE_DIR}" -name "*.h" -exec ${FORMATTER} -style=file -i {} + 165 | COMMAND find "${MOCK_TARGET_DIR}" -name "*.c" -exec ${FORMATTER} -style=file -i {} + 166 | COMMAND find "${LIB_DIR}" -maxdepth 1 -type f -exec ${FORMATTER} -style=file -i {} + 167 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 168 | ) 169 | 170 | add_custom_target(clean_all 171 | COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_BINARY_DIR}" 172 | COMMAND ${CMAKE_COMMAND} -E remove_directory "${BIN_DIR}" 173 | ) 174 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Z Anti-Anti-Debugger 2 | 3 | A debugger designed to bypass anti-debugging mechanisms with clear, precise control over program execution. 4 | 5 | ## Overview 6 | 7 | Modern applications often implement advanced techniques to hinder debugging. Z Anti-Anti-Debugger is built to overcome these measures using a direct approach. Each command is designed to provide explicit control over the target program’s execution without unnecessary complexity. Documentation: [https://javahammes.github.io/Z/](https://javahammes.github.io/Z/) 8 | 9 | ## Build & Run 10 | 11 | ### Clone the Repository 12 | 13 | Clone the repository along with its submodules: 14 | 15 | ```bash 16 | git clone --recurse-submodules https://github.com/JavaHammes/Z.git 17 | ``` 18 | 19 | ### Build with CMake 20 | 21 | Generate the build files and compile the project: 22 | 23 | ```bash 24 | cmake -B build 25 | cmake --build build --clean-first 26 | ``` 27 | 28 | ### Execute the Debugger 29 | 30 | Navigate to the binary folder and start Z with your target executable: 31 | 32 | ```bash 33 | cd bin/ 34 | ./z [ld_preload_library1 [ld_preload_library2 ...]] 35 | ``` 36 | 37 | - Replace `` with the path to the program you wish to debug. 38 | - `[ld_preload_library1 [ld_preload_library2 ...]]`: Optional custom LD_PRELOAD libraries. 39 | 40 | If no custom libraries are specified, the following 5 default libraries will be used: 41 | 42 | - `libfopen_intercept.so` 43 | - `libgetenv_intercept.so` 44 | - `libprctl_intercept.so` 45 | - `libptrace_intercept.so` 46 | - `libsetvbuf_unbuffered.so` 47 | 48 | #### Custom `LD_PRELOAD` Libraries 49 | 50 | You can extend Z by supplying your own LD_PRELOAD libraries. **Important**: Each custom library must include the following marker function: 51 | 52 | ```C 53 | void zZz() {} 54 | ``` 55 | This marker acts as a signature, signaling to Z’s default preload libraries that your library is custom—especially crucial when combining default and custom libraries. 56 | 57 | For instance, in the `fopen` override, when processing files like `/proc/self/maps`, the library filters only the lines corresponding to libraries that include the **zZz** marker. This ensures that any of the debuggee’s own libraries present in `/proc/self/maps` aren’t mistakenly filtered out (which could alert the debuggee to the presence of a debugger). Without this marker, your custom library might not integrate properly, potentially leading to unfiltered or unexpected behavior in your `fopen` or `getenv` overrides. 58 | 59 | ## Command Reference 60 | 61 | ### General Commands 62 | 63 | ``` 64 | help - Display the help message 65 | exit - Quit the debugger 66 | clear - Clear the screen 67 | log - Begin logging output to the specified file 68 | preload - Show preloaded libraries 69 | !! - Repeat the previous command 70 | ``` 71 | 72 | ### Execution Commands 73 | 74 | ``` 75 | run - Start executing the target program 76 | con - Continue execution after a pause 77 | step - Execute the next instruction 78 | over - Step over the current instruction 79 | out - Step out of the current function 80 | skip - Advance execution by n instructions 81 | jump - Jump to a specific address 82 | jump * - Jump to base_address + offset 83 | jump & - Jump to the address of a function 84 | trace - Begin tracing from the given address 85 | trace * - Trace from base_address + offset 86 | trace & - Trace from a function's address 87 | ``` 88 | 89 | ### Breakpoint Commands 90 | 91 | ``` 92 | points - List all breakpoints 93 | break - Set a software breakpoint at the specified address 94 | break * - Set a software breakpoint at base_address + offset 95 | break & - Set a breakpoint at a function's address 96 | hbreak - Set a hardware breakpoint at the specified address 97 | hbreak * - Set a hardware breakpoint at base_address + offset 98 | hbreak & - Set a hardware breakpoint at a function's address 99 | watch - Monitor a memory address for read/write access 100 | watch * - Monitor base_address + offset for access 101 | watch & - Monitor a global variable for changes 102 | catch - Catch a signal or process event 103 | remove - Remove the breakpoint at the given index 104 | ``` 105 | 106 | ### Inspection Commands 107 | 108 | ``` 109 | regs - Display CPU registers 110 | dump - Dump memory at the current instruction pointer 111 | dis - Disassemble code at the current instruction pointer 112 | vars - Show global variables and their values 113 | funcs - List functions with their addresses 114 | addr - Display the address of the specified function 115 | backt - Print a call stack backtrace 116 | ``` 117 | 118 | ### Modification Commands 119 | 120 | ``` 121 | set = - Set the value of a register 122 | patch = - Patch memory at the specified address with hexadecimal opcodes 123 | ``` 124 | 125 | ## Contributing 126 | 127 | Contributions to improve this tool are welcome. Please fork the repository, open an issue, or submit a pull request with your proposed changes. 128 | 129 | ## License 130 | 131 | This project is released under the MIT License. You are free to use, modify, and distribute it according to the terms of this license. 132 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Z x86_64 Linux Anti-Anti-Debugger Documentation 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Z x86_64 Linux Anti-Anti-Debugger

13 | 31 |
32 |
33 | 34 |
35 |
36 |

Introduction

37 |

38 | The Z x86_64 Linux Anti-Anti-Debugger is a powerful debugging tool written in C, 39 | engineered specifically to bypass anti-debugging techniques employed by many Linux binaries. 40 | Traditional debuggers can be detected and countered by malware or protected software. 41 | This tool circumvents those mechanisms using a combination of advanced ptrace strategies, 42 | hardware breakpoint management, dynamic memory patching, and the injection of custom LD_PRELOAD libraries. 43 |

44 |

45 | This documentation is intended for users and developers who wish to understand the inner workings, 46 | extend the capabilities, or contribute to the project. It covers the overall architecture, installation and build instructions, 47 | detailed usage examples, an exhaustive API reference, and advanced troubleshooting tips. 48 |

49 |

50 | Whether you are using the tool to debug a challenging application or to study advanced debugging techniques, 51 | this guide will provide you with all the necessary details. 52 |

53 |
54 | 55 |
56 |

Features

57 |

The Z anti-anti-debugger comes packed with advanced features, including:

58 |
    59 |
  • Bypass Anti-Debugging: Specifically designed to counter common anti-debugging measures.
  • 60 |
  • Full Process Control: Leverages ptrace to manipulate target processes at a low level.
  • 61 |
  • 62 | Breakpoint & Watchpoint Management: 63 |
      64 |
    • Software Breakpoints using INT3 instruction patching with restoration of original code.
    • 65 |
    • Hardware Breakpoints via debug registers (DR0–DR3) without modifying code.
    • 66 |
    • Memory Watchpoints to monitor read/write operations on specific addresses.
    • 67 |
    • Catchpoints to intercept signals and process events (fork, clone, exec, exit).
    • 68 |
    69 |
  • 70 |
  • Instruction Stepping: Support for single stepping, stepping over function calls, and stepping out of functions.
  • 71 |
  • Memory Inspection & Patching: Dump memory contents and dynamically patch code at runtime.
  • 72 |
  • Disassembly Support: Integration with the Capstone disassembly engine to display human-readable assembly.
  • 73 |
  • 74 | Symbol Resolution & Backtracing: 75 |
      76 |
    • ELF symbol parsing to resolve function names and addresses.
    • 77 |
    • Generate call stack backtraces.
    • 78 |
    79 |
  • 80 |
  • 81 | Dynamic LD_PRELOAD Injection: Inject custom libraries to intercept critical system calls (e.g., fopen, getenv, prctl, setvbuf), thereby evading anti-debugging traps. 82 |
  • 83 |
  • User-Friendly CLI: Command-line interface with color-coded output and extensive commands for complete control.
  • 84 |
85 |
86 | 87 |
88 |

Installation & Build Instructions

89 |

Step 1: Clone the Repository

90 |
git clone --recurse-submodules https://github.com/JavaHammes/Z.git
91 |

Step 2: Install Dependencies

92 |

For Ubuntu/Debian-based systems, run:

93 |
sudo apt-get update
 94 | sudo apt-get install libcapstone-dev libcriterion-dev build-essential pkg-config cmake gcc
95 |

Step 3: Build the Project

96 |

You can use CMake to configure and build:

97 |
cmake -B build
 98 | cmake --build build
 99 | 
100 |

101 | The build output should generate an executable named z. 102 |

103 |

Step 4: Running the Debugger

104 |

105 | To start a debugging session, run: 106 |

107 |
cd bin/
108 | ./z target_executable [optional_ld_preload_libraries...]
109 |

110 | If you do not supply custom LD_PRELOAD libraries, the tool automatically loads a default set (e.g., libptrace_intercept.so, libfopen_intercept.so, etc.). 111 |

112 |
113 | 114 |
115 |

Project Architecture

116 |

High-Level Overview

117 |

118 | The Z anti-anti-debugger is divided into several interdependent modules: 119 |

120 |
    121 |
  • 122 | debuggee.c: Implements low-level ptrace wrappers, memory access routines, register manipulation, breakpoint insertion/removal, disassembly functions, and symbol resolution. 123 |
  • 124 |
  • 125 | debuggger.c: Contains the core debugger loop, process management, event handling (signals, ptrace events), and LD_PRELOAD configuration. It orchestrates the overall debugging session. 126 |
  • 127 |
128 |

Detailed Component Relationships

129 |

130 | The debugger spawns a child process that executes the target binary with ptrace(PTRACE_TRACEME) and sets the LD_PRELOAD environment variable. The parent process (debugger) then monitors and controls the child process, responding to events (e.g., breakpoints, signals) via a carefully structured main loop. 131 |

132 |

133 | Breakpoint handling is a two-stage process: first, the tool inserts breakpoints (either software or hardware) by modifying code or registers; then, upon triggering, it restores original instructions and continues execution as specified by the user commands. 134 |

135 |

136 | Additionally, symbol resolution is performed by parsing ELF symbol tables, which enables features such as function address lookup and stack backtracing. 137 |

138 |
139 | 140 |
141 |

Usage Guide

142 |

Getting Started

143 |

144 | To start debugging, run the tool with your target application. For example: 145 |

146 |
./z target_executable
147 |

148 | You may add custom LD_PRELOAD libraries after the target program’s path to further extend functionality. 149 |

150 |

Interactive Commands

151 |

The debugger supports an extensive set of commands. Some of the key ones include:

152 |
    153 |
  • help – Displays the full list of commands and their usage.
  • 154 |
  • exit – Terminates the debugging session.
  • 155 |
  • run – Starts or resumes execution of the target process.
  • 156 |
  • step – Executes the next machine instruction (single step).
  • 157 |
  • over – Steps over function calls by setting temporary breakpoints at return addresses.
  • 158 |
  • out – Steps out of the current function.
  • 159 |
  • skip <n> – Skips the next n instructions.
  • 160 |
  • jump <addr> – Sets a temporary breakpoint at a given address and continues execution until hit.
  • 161 |
  • 162 | Breakpoint commands: 163 |
      164 |
    • break <addr> – Insert a software breakpoint.
    • 165 |
    • hbreak <addr> – Insert a hardware breakpoint.
    • 166 |
    • watch <addr> – Set a watchpoint on a memory address.
    • 167 |
    • catch <sig/event> – Monitor specific signals or process events.
    • 168 |
    • remove <index> – Remove a breakpoint by its index.
    • 169 |
    170 |
  • 171 |
  • 172 | Inspection commands: 173 |
      174 |
    • regs – Display the CPU registers (general and debug registers).
    • 175 |
    • dump – Dump memory contents around the current instruction pointer.
    • 176 |
    • dis – Disassemble code at the current execution address using Capstone.
    • 177 |
    • funcs – List all functions extracted from the ELF symbol tables.
    • 178 |
    • addr <func_name> – Retrieve the address of a function by name.
    • 179 |
    • backt – Generate a full backtrace of the call stack.
    • 180 |
    181 |
  • 182 |
  • 183 | Modification commands: 184 |
      185 |
    • set <reg>=<value> – Modify a register value.
    • 186 |
    • patch <addr>=<hex> – Patch memory with new opcodes.
    • 187 |
    188 |
  • 189 |
190 |

Example Session

191 |

The following is an example of a debugging session:

192 |
# Start the debugger with a target application
193 | ./z target_executable
194 | 
195 | # At the interactive prompt:
196 | help                  # Show available commands
197 | break 0x400123        # Set a software breakpoint at address 0x400123
198 | run                   # Start the application
199 | regs                  # Dump registers after hitting a breakpoint
200 | dis                   # Disassemble code at the current instruction pointer
201 | step                  # Single-step through instructions
202 | backt                 # Generate a backtrace
203 | exit                  # End the debugging session
204 |
205 | 206 |
207 |

Detailed API Reference

208 |

209 | This section provides a comprehensive breakdown of the primary functions in the codebase. 210 | The API is divided into two major groups: Debuggee Functions and Debugger Functions. 211 |

212 |

Debuggee Functions (src/debuggee.c)

213 |

Process Control & Execution

214 |
    215 |
  • 216 | int Run(debuggee *dbgee) 217 |

    Starts or resumes the target process. If temporary breakpoints exist, it handles their removal and resumes execution via ptrace(PTRACE_CONT).

    218 |
  • 219 |
  • 220 | int Continue(debuggee *dbgee) 221 |

    Continues execution after a stop (e.g., after handling a breakpoint or signal).

    222 |
  • 223 |
  • 224 | int Step(debuggee *dbgee) 225 |

    Performs a single instruction step using ptrace(PTRACE_SINGLESTEP).

    226 |
  • 227 |
  • 228 | int StepOver(debuggee *dbgee) 229 |

    Steps over function calls by setting a temporary breakpoint at the return address before executing the call.

    230 |
  • 231 |
  • 232 | int StepOut(debuggee *dbgee) 233 |

    Steps out of the current function by obtaining the return address from the stack and setting a temporary breakpoint there.

    234 |
  • 235 |
  • 236 | int Skip(debuggee *dbgee, const char *arg) 237 |

    Skips a specified number of instructions by repeatedly single-stepping.

    238 |
  • 239 |
  • 240 | int Jump(debuggee *dbgee, const char *arg) 241 |

    Sets a temporary breakpoint at a user-specified address (or symbol) and continues execution until that point is reached.

    242 |
  • 243 |
  • 244 | int Trace(debuggee *dbgee, const char *arg) 245 |

    Combines jump functionality with single-stepping to trace execution from a given point while disassembling instructions on the fly.

    246 |
  • 247 |
248 |

Register & Memory Inspection

249 |
    250 |
  • 251 | int Registers(debuggee *dbgee) 252 |

    Retrieves CPU general-purpose registers and debug registers using ptrace(PTRACE_GETREGS) and displays them in a formatted output.

    253 |
  • 254 |
  • 255 | int Dump(debuggee *dbgee) 256 |

    Dumps memory starting at the current instruction pointer in both hexadecimal and ASCII formats.

    257 |
  • 258 |
  • 259 | int Disassemble(debuggee *dbgee) 260 |

    Utilizes the Capstone disassembly library to convert raw machine code into human-readable assembly instructions.

    261 |
  • 262 |
263 |

Breakpoint Management

264 |
    265 |
  • 266 | int SetSoftwareBreakpoint(debuggee *dbgee, const char *arg) 267 |

    Inserts an INT3-based breakpoint at a specified address and stores the original instruction byte for later restoration.

    268 |
  • 269 |
  • 270 | int SetHardwareBreakpoint(debuggee *dbgee, const char *arg) 271 |

    Configures an available CPU debug register (DR0–DR3) to monitor a given address without modifying code.

    272 |
  • 273 |
  • 274 | int SetWatchpoint(debuggee *dbgee, const char *arg) 275 |

    Sets a watchpoint on a memory address to monitor read/write access, using hardware debug registers.

    276 |
  • 277 |
  • 278 | int SetCatchpoint(debuggee *dbgee, const char *arg) 279 |

    Allows setting of catchpoints to intercept specific signals or process events (e.g., fork, exec, exit).

    280 |
  • 281 |
  • 282 | int RemoveBreakpoint(debuggee *dbgee, const char *arg) 283 |

    Removes an active breakpoint (software or hardware) and restores any modified code or registers.

    284 |
  • 285 |
286 |

Dynamic Patching & Symbol Resolution

287 |
    288 |
  • 289 | int Patch(debuggee *dbgee, const char *arg) 290 |

    Patches memory by replacing instructions at a given address with new hexadecimal opcodes.

    291 |
  • 292 |
  • 293 | int Address(debuggee *dbgee, const char *arg) 294 |

    Resolves the absolute address of a symbol by parsing ELF symbol tables, which is useful for setting breakpoints by function name.

    295 |
  • 296 |
  • 297 | unsigned long get_entry_absolute_address(debuggee *dbgee) 298 |

    Determines the target process’s entry point, properly handling Position Independent Executables (PIE).

    299 |
  • 300 |
301 |

Breakpoint Handlers

302 |
    303 |
  • 304 | int handle_software_breakpoint(debuggee *dbgee, size_t bp_index) 305 |

    When a software breakpoint is hit, this function restores the original instruction, adjusts the instruction pointer, and optionally re-inserts the breakpoint if it is persistent.

    306 |
  • 307 |
  • 308 | int handle_hardware_breakpoint(debuggee *dbgee, size_t bp_index) 309 |

    Processes hardware breakpoint events, printing diagnostic information to the user.

    310 |
  • 311 |
  • 312 | int handle_catchpoint_signal(debuggee *dbgee, size_t bp_index) 313 |

    Handles caught signals by determining if they correspond to user-defined catchpoints and dispatching the appropriate action.

    314 |
  • 315 |
  • 316 | int handle_catchpoint_event(debuggee *dbgee, size_t bp_index) 317 |

    Handles process events (e.g., fork, exec) when catchpoints are configured.

    318 |
  • 319 |
  • 320 | int handle_watchpoint(debuggee *dbgee, size_t bp_index) 321 |

    Responds to watchpoint triggers and informs the user about memory access events.

    322 |
  • 323 |
324 | 325 |

Debugger Functions (src/debuggger.c)

326 |

Initialization & Setup

327 |
    328 |
  • 329 | void init_debugger(debugger *dbg, const char *debuggee_name, int argc, char **argv) 330 |

    Initializes the debugger state including the breakpoint handler, the LD_PRELOAD library list, and processes any command-line arguments for custom library injection.

    331 |
  • 332 |
  • 333 | void free_debugger(debugger *dbg) 334 |

    Cleans up resources, terminates the target process if necessary, and resets the debugger state.

    335 |
  • 336 |
337 |

Process Launch & Main Loop

338 |
    339 |
  • 340 | int start_debuggee(debugger *dbg) 341 |

    Forks a new process, sets up the ptrace environment, configures LD_PRELOAD, and starts the target application.

    342 |
  • 343 |
  • 344 | int trace_debuggee(debugger *dbg) 345 |

    Implements the core debugging loop. It waits for ptrace events (e.g., breakpoints, signals), dispatches them to the appropriate handlers, and manages the overall session flow.

    346 |
  • 347 |
348 |

Utility Functions

349 |
    350 |
  • 351 | static int _add_default_preload_libraries(debugger *dbg) 352 |

    If no custom libraries are provided, this function adds a default set of LD_PRELOAD libraries to intercept system calls.

    353 |
  • 354 |
  • 355 | static void _process_ld_preload_args(debugger *dbg, int argc, char **argv) 356 |

    Parses command-line arguments to include additional LD_PRELOAD libraries, enhancing the tool’s functionality.

    357 |
  • 358 |
359 |
360 | 361 |
362 |

Advanced Topics

363 |

Bypassing Anti-Debugging Techniques

364 |

365 | Many modern applications employ anti-debugging techniques such as timing checks, interrupt detection, 366 | and environment scanning to thwart traditional debuggers. This tool combats these methods by: 367 |

368 |
    369 |
  • Injecting custom LD_PRELOAD libraries that override common system functions (like fopen, getenv, and prctl) to hide debugging artifacts.
  • 370 |
  • Utilizing hardware breakpoints, which are far less detectable since they do not require modifying code.
  • 371 |
  • Carefully managing signal and event handling to avoid leaving traces that anti-debug mechanisms could detect.
  • 372 |
373 |

Breakpoint Strategies & Single-Stepping

374 |

375 | Breakpoint insertion is central to the tool’s operation: 376 |

377 |
    378 |
  • 379 | Software Breakpoints: The tool patches the code by replacing an instruction with the INT3 opcode (0xCC) and preserves the original byte for later restoration. 380 |
  • 381 |
  • 382 | Hardware Breakpoints: By using available debug registers (DR0–DR3) and configuring DR7 appropriately, the tool can monitor addresses without modifying the program code. 383 |
  • 384 |
  • 385 | Single-Stepping: Precision control is achieved using ptrace(PTRACE_SINGLESTEP)—critical when re-inserting breakpoints after a hit or when tracing execution flow. 386 |
  • 387 |
388 |

Backtracing & Symbol Resolution

389 |

390 | Using ELF symbol tables, the tool maps function names to their addresses. This allows: 391 |

392 |
    393 |
  • Displaying function names alongside addresses in the backtrace.
  • 394 |
  • Providing meaningful output even for stripped binaries by computing offsets relative to module base addresses.
  • 395 |
396 |
397 | 398 |
399 |

Custom LD_PRELOAD Libraries

400 |

401 | Z can be extended by supplying your own custom LD_PRELOAD libraries. These libraries allow you to override 402 | system functions (e.g., fopen, getenv, prctl) to tailor the behavior of the target 403 | process and further conceal the presence of a debugger. 404 |

405 |

406 | Important: Every custom LD_PRELOAD library must include the following marker function: 407 |

408 |
void zZz() {}
409 |

410 | This marker acts as a signature, signaling to Z’s default preload libraries that your library is custom—especially 411 | crucial when combining default and custom libraries. For example, in the fopen override, the library processes 412 | files such as /proc/self/maps by filtering out only those lines corresponding to libraries that include the 413 | zZz marker. This ensures that any of the debuggee’s own libraries aren’t mistakenly filtered out, which could alert 414 | the target process to the debugger’s presence. 415 |

416 |

417 | Without this marker, your custom library may not integrate properly, potentially leading to unfiltered behavior or unexpected side effects 418 | in your overrides. 419 |

420 |

Example Custom Library

421 |

422 | Below is an alternative example for a custom LD_PRELOAD library that overrides the gettimeofday function—this function is not used by the default libraries: 423 |

424 |
#include <sys/time.h>
425 | #include <dlfcn.h>
426 | 
427 | // Marker function required for custom libraries.
428 | void zZz() {}
429 | 
430 | // Custom gettimeofday override.
431 | int gettimeofday(struct timeval *tv, struct timezone *tz) {
432 |     // Retrieve the original gettimeofday function.
433 |     int (*orig_gettimeofday)(struct timeval*, struct timezone*) = dlsym(RTLD_NEXT, "gettimeofday");
434 | 
435 |     int ret = orig_gettimeofday(tv, tz);
436 | 
437 |     // Your custom implementation to modify the return value.
438 | 
439 |     return ret;
440 | }
441 |     
442 |

Compiling Your Custom Library

443 |

444 | To compile your custom library, use the following command: 445 |

446 |
gcc -shared -fPIC -o libcustom.so custom.c -ldl
447 |

448 | Here: 449 |
-shared creates a shared library. 450 |
-fPIC generates position-independent code necessary for shared libraries. 451 |
-ldl links against the dynamic linking library. 452 |

453 |

454 | After compiling, supply your custom library as an additional argument when launching Z: 455 |

456 |
./z target_executable ./libcustom.so
457 |

458 | Your custom library will be appended to the LD_PRELOAD list, thereby extending the debugger’s functionality. 459 |

460 |
461 | 462 |
463 |

Troubleshooting

464 |

Common Issues

465 |
    466 |
  • 467 | ptrace Failures: Ensure that your user account has sufficient privileges. On some systems, security modules like SELinux or AppArmor might block ptrace operations. 468 |
  • 469 |
  • 470 | LD_PRELOAD Problems: Verify that default LD_PRELOAD libraries are present in the expected directory. If you supply custom libraries, ensure they are compiled correctly. 471 |
  • 472 |
  • 473 | Breakpoint Not Triggering: Confirm that the address is valid, mapped, and executable. 474 |
  • 475 |
  • 476 | Disassembly Issues: If Capstone fails to disassemble code, ensure that the target architecture is correctly specified (x86_64) and that the binary is not corrupted. 477 |
  • 478 |
479 |
480 | 481 |
482 |

Performance & Limitations

483 |

Performance Considerations

484 |

485 | Due to the overhead of ptrace and the need for frequent system calls (especially during single-stepping), 486 | debugging sessions can be slower than normal execution. Consider these factors: 487 |

488 |
    489 |
  • Single-stepping can significantly slow down the target process; use it selectively.
  • 490 |
  • Hardware breakpoints are less intrusive but are limited in number by the CPU.
  • 491 |
492 |

Known Limitations

493 |
    494 |
  • The tool is designed for x86_64 Linux and may require modifications to work on other architectures.
  • 495 |
  • Some anti-debugging techniques (e.g., timing-based or obfuscation) are not bypassed.
  • 496 |
  • High-security environments may restrict ptrace operations regardless of user privileges.
  • 497 |
498 |
499 | 500 |
501 |

Developer Guide

502 |

Code Structure & Conventions

503 |

504 | The project follows standard C99 conventions. 505 | When contributing, adhere to the following guidelines: 506 |

507 |
    508 |
  • Follow the existing naming conventions (e.g., dbgee for debuggee structures, dbg for debugger structures).
  • 509 |
  • Document new functions and changes with clear comments.
  • 510 |
  • Add tests or example usage if modifying core functionality.
  • 511 |
512 |

Extending Functionality

513 |

514 | Developers are encouraged to add new features, such as: 515 |

516 |
    517 |
  • Additional LD_PRELOAD libraries for intercepting more system calls.
  • 518 |
  • Enhanced user interface improvements (e.g., support for scripting commands).
  • 519 |
  • Support for more architectures or operating systems.
  • 520 |
  • Better error handling and logging mechanisms.
  • 521 |
522 |
523 | 524 |
525 |

Frequently Asked Questions (FAQ)

526 |

Q: What makes this tool an Anti-Anti-Debugger?

527 |

528 | A: It is engineered specifically to bypass common anti-debugging techniques by intercepting system calls, 529 | using non-invasive hardware breakpoints, and carefully managing signal and event handling to avoid detection. 530 |

531 |

Q: Do I need root privileges to use it?

532 |

533 | A: Not necessarily, but you must have sufficient privileges to use ptrace and access certain /proc interfaces. 534 |

535 |

Q: Can I add my own LD_PRELOAD libraries?

536 |

537 | A: Yes, you can supply custom libraries via command-line arguments. These libraries will be appended to the LD_PRELOAD list. 538 |

539 |

Q: Does it work with all x86_64 binaries?

540 |

541 | A: The tool is designed for standard x86_64 Linux binaries. However, heavily obfuscated or highly protected binaries may still employ techniques that limit debugging. 542 |

543 |
544 | 545 |
546 |

Contributing Guidelines

547 |

548 | Contributions to Z are very welcome! Please follow these steps: 549 |

550 |
    551 |
  1. Fork the repository on GitHub.
  2. 552 |
  3. Create a new branch for your feature or bug fix.
  4. 553 |
  5. Ensure that your code adheres to the existing coding conventions.
  6. 554 |
  7. Add tests for any new functionality where applicable.
  8. 555 |
  9. Submit a pull request with a clear description of your changes.
  10. 556 |
557 |
558 | 559 |
560 |

License

561 |

562 | This project is licensed under the MIT License. 563 | You are free to use, modify, and distribute the code in accordance with the license terms. 564 |

565 |
566 |
567 | 568 |
569 |
570 |

© 2025 Z x86_64 Linux Anti-Anti-Debugger. All Rights Reserved.

571 |

Designed and maintained by JavaHammes

572 |
573 |
574 | 575 | 576 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 9 | background: #f7f7f7; 10 | color: #333; 11 | line-height: 1.6; 12 | } 13 | 14 | .container { 15 | width: 90%; 16 | max-width: 1000px; 17 | margin: 0 auto; 18 | padding: 20px; 19 | } 20 | 21 | header { 22 | background: #2c3e50; 23 | color: #ecf0f1; 24 | padding: 20px 0; 25 | margin-bottom: 20px; 26 | text-align: center; 27 | } 28 | 29 | header h1 { 30 | font-size: 2.8rem; 31 | margin-bottom: 10px; 32 | } 33 | 34 | header .tagline { 35 | font-size: 1.3rem; 36 | margin-bottom: 15px; 37 | font-style: italic; 38 | } 39 | 40 | nav ul { 41 | list-style: none; 42 | display: flex; 43 | flex-wrap: wrap; 44 | justify-content: center; 45 | } 46 | 47 | nav ul li { 48 | margin: 5px 10px; 49 | } 50 | 51 | nav ul li a { 52 | color: #ecf0f1; 53 | text-decoration: none; 54 | font-weight: bold; 55 | transition: color 0.2s ease-in-out; 56 | } 57 | 58 | nav ul li a:hover { 59 | color: #f39c12; 60 | } 61 | 62 | main { 63 | background: #fff; 64 | padding: 30px; 65 | border-radius: 5px; 66 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 67 | margin-bottom: 40px; 68 | } 69 | 70 | main section { 71 | margin-bottom: 40px; 72 | } 73 | 74 | main h2 { 75 | font-size: 2rem; 76 | margin-bottom: 15px; 77 | border-bottom: 2px solid #2c3e50; 78 | padding-bottom: 5px; 79 | color: #2c3e50; 80 | } 81 | 82 | main h3 { 83 | font-size: 1.6rem; 84 | margin: 20px 0 10px; 85 | color: #34495e; 86 | } 87 | 88 | p { 89 | margin-bottom: 15px; 90 | text-align: justify; 91 | } 92 | 93 | ul, ol { 94 | margin-left: 20px; 95 | margin-bottom: 15px; 96 | } 97 | 98 | pre { 99 | background: #ecf0f1; 100 | padding: 15px; 101 | border-radius: 4px; 102 | overflow-x: auto; 103 | margin-bottom: 15px; 104 | font-size: 0.95rem; 105 | } 106 | 107 | code { 108 | font-family: Consolas, 'Courier New', monospace; 109 | background: #ecf0f1; 110 | padding: 2px 4px; 111 | border-radius: 3px; 112 | } 113 | 114 | footer { 115 | text-align: center; 116 | padding: 20px 0; 117 | border-top: 1px solid #ccc; 118 | color: #777; 119 | background: #f7f7f7; 120 | } 121 | -------------------------------------------------------------------------------- /include/breakpoint_handler.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | enum { 6 | EVENT_NAME_SIZE = 16, 7 | }; 8 | 9 | typedef enum { 10 | SOFTWARE_BP, 11 | HARDWARE_BP, 12 | CATCHPOINT_SIGNAL, 13 | CATCHPOINT_EVENT_FORK, 14 | CATCHPOINT_EVENT_VFORK, 15 | CATCHPOINT_EVENT_CLONE, 16 | CATCHPOINT_EVENT_EXEC, 17 | CATCHPOINT_EVENT_EXIT, 18 | CATCHPOINT_EVENT_INVALID, 19 | WATCHPOINT, 20 | } breakpoint_t; 21 | 22 | typedef struct { 23 | uintptr_t address; 24 | uint8_t original_byte; 25 | size_t size; 26 | } software_breakpoint; 27 | 28 | typedef struct { 29 | uintptr_t address; 30 | } hardware_breakpoint; 31 | 32 | typedef struct { 33 | uintptr_t address; 34 | } watchpoint; 35 | 36 | typedef struct { 37 | int signal; 38 | } catchpoint_signal; 39 | 40 | typedef struct { 41 | char event_name[EVENT_NAME_SIZE]; 42 | } catchpoint_event; 43 | 44 | typedef union { 45 | software_breakpoint sw_bp; 46 | hardware_breakpoint hw_bp; 47 | catchpoint_signal cp_signal; 48 | catchpoint_event cp_event; 49 | watchpoint wp; 50 | } breakpoint_data; 51 | 52 | typedef struct { 53 | breakpoint_t bp_t; 54 | breakpoint_data data; 55 | bool temporary; 56 | } breakpoint; 57 | 58 | typedef struct { 59 | breakpoint *breakpoints; 60 | size_t count; 61 | size_t capacity; 62 | } breakpoint_handler; 63 | 64 | breakpoint_handler *init_breakpoint_handler(void); 65 | void free_breakpoint_handler(breakpoint_handler *handler); 66 | size_t add_software_breakpoint(breakpoint_handler *handler, uintptr_t address, 67 | uint8_t original_byte); 68 | size_t add_hardware_breakpoint(breakpoint_handler *handler, uintptr_t address); 69 | size_t add_watchpoint(breakpoint_handler *handler, uintptr_t address); 70 | size_t add_catchpoint_signal(breakpoint_handler *handler, int signal_number); 71 | size_t add_catchpoint_event(breakpoint_handler *handler, const char *event); 72 | int remove_breakpoint(breakpoint_handler *handler, size_t index); 73 | void list_breakpoints(const breakpoint_handler *handler); 74 | -------------------------------------------------------------------------------- /include/debuggee.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "breakpoint_handler.h" 8 | #include "symtab.h" 9 | 10 | enum { 11 | PTRACE_EVENT_SHIFT = 16, 12 | PTRACE_EVENT_MASK = 0xFFFF, 13 | }; 14 | 15 | typedef enum { 16 | IDLE = 0, 17 | RUNNING = 1, 18 | STOPPED = 2, 19 | TERMINATED = 4, 20 | } debuggee_state; 21 | 22 | typedef struct debuggee { 23 | pid_t pid; 24 | const char *name; 25 | debuggee_state state; 26 | breakpoint_handler *bp_handler; 27 | bool has_run; 28 | } debuggee; 29 | 30 | void Help(void); 31 | int Log(const char *filename); 32 | 33 | int Run(debuggee *dbgee); 34 | int Continue(debuggee *dbgee); 35 | int Step(debuggee *dbgee); 36 | int StepOver(debuggee *dbgee); 37 | int StepOut(debuggee *dbgee); 38 | int Skip(debuggee *dbgee, const char *arg); 39 | int Jump(debuggee *dbgee, const char *arg); 40 | int Trace(debuggee *dbgee, const char *arg); 41 | 42 | int Registers(debuggee *dbgee); 43 | int SetRegister(debuggee *dbgee, const char *arg); 44 | int Dump(debuggee *dbgee); 45 | int Patch(debuggee *dbgee, const char *arg); 46 | int Disassemble(debuggee *dbgee); 47 | int DisplayGlobalVariables(debuggee *dbgee); 48 | int DisplayFunctionNames(debuggee *dbgee); 49 | int Backtrace(debuggee *dbgee); 50 | int Address(debuggee *dbgee, const char *arg); 51 | 52 | int SetSoftwareBreakpoint(debuggee *dbgee, const char *arg); 53 | int SetHardwareBreakpoint(debuggee *dbgee, const char *arg); 54 | int SetWatchpoint(debuggee *dbgee, const char *arg); 55 | int SetCatchpoint(debuggee *dbgee, const char *arg); 56 | int RemoveBreakpoint(debuggee *dbgee, const char *arg); 57 | void ListBreakpoints(debuggee *dbgee); 58 | 59 | unsigned long get_entry_absolute_address(debuggee *dbgee); 60 | int set_temp_sw_breakpoint(debuggee *dbgee, uint64_t addr); 61 | bool is_software_breakpoint(debuggee *dbgee, size_t *bp_index_out); 62 | bool is_hardware_breakpoint(debuggee *dbgee, size_t *bp_index_out); 63 | bool is_catchpoint_signal(debuggee *dbgee, size_t *bp_index_out, 64 | int signal_number); 65 | bool is_catchpoint_event(debuggee *dbgee, size_t *bp_index_out, 66 | unsigned long event_code); 67 | bool is_watchpoint(debuggee *dbgee, size_t *bp_index_out); 68 | int handle_software_breakpoint(debuggee *dbgee, size_t bp_index); 69 | int handle_hardware_breakpoint(debuggee *dbgee, size_t bp_index); 70 | int handle_catchpoint_signal(debuggee *dbgee, size_t bp_index); 71 | int handle_catchpoint_event(debuggee *dbgee, size_t bp_index); 72 | int handle_watchpoint(debuggee *dbgee, size_t bp_index); 73 | -------------------------------------------------------------------------------- /include/debugger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "debuggee.h" 4 | #include "ld_preload.h" 5 | 6 | typedef enum { DETACHED = 1, ATTACHED = 2 } debugger_state; 7 | 8 | typedef struct debugger { 9 | debuggee dbgee; 10 | debugger_state state; 11 | ld_preload_list *preload_list; 12 | } debugger; 13 | 14 | void init_debugger(debugger *dbg, const char *debuggee_name, int argc, 15 | char **argv); 16 | void free_debugger(debugger *dbg); 17 | 18 | int start_debuggee(debugger *dbg); 19 | int trace_debuggee(debugger *dbg); 20 | -------------------------------------------------------------------------------- /include/debugger_commands.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "debugger.h" 4 | 5 | typedef enum { 6 | CLI_HELP, 7 | CLI_EXIT, 8 | CLI_CLEAR, 9 | CLI_LOG, 10 | DBG_RUN, 11 | DBG_CONTINUE, 12 | DBG_STEP, 13 | DBG_STEP_OVER, 14 | DBG_STEP_OUT, 15 | DBG_SKIP, 16 | DBG_JUMP, 17 | DBG_TRACE, 18 | DBG_REGISTERS, 19 | DBG_SET_REG, 20 | DBG_BREAK, 21 | DBG_HBREAK, 22 | DBG_WATCH, 23 | DBG_CATCH, 24 | DBG_LIST_BREAKPOINTS, 25 | DBG_REMOVE_BREAKPOINT, 26 | DBG_DUMP, 27 | DBG_PATCH, 28 | DBG_DIS, 29 | DBG_GLOB_VARS, 30 | DBG_FUNC_NAMES, 31 | DBG_BACKTRACE, 32 | DBG_ADDR, 33 | DBG_LIST_PRELOAD, 34 | UNKNOWN 35 | } command_t; 36 | 37 | typedef struct { 38 | const char *command; 39 | command_t type; 40 | } command_mapping; 41 | 42 | int read_and_handle_user_command(debugger *dbg); 43 | int handle_user_input(debugger *dbg, command_t cmd_type, const char *arg); 44 | -------------------------------------------------------------------------------- /include/ld_preload.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct { 6 | char **libs; 7 | size_t count; 8 | size_t capacity; 9 | } ld_preload_list; 10 | 11 | ld_preload_list *init_ld_preload_list(void); 12 | void free_ld_preload_list(ld_preload_list *list); 13 | 14 | int add_library(ld_preload_list *list, const char *lib); 15 | int ld_preload_list_set_env(const ld_preload_list *list, const char *dir); 16 | 17 | void print_libraries(const ld_preload_list *list); 18 | -------------------------------------------------------------------------------- /include/symtab.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef struct { 6 | Elf64_Sym *symtab; 7 | size_t num_symbols; 8 | char *strtab; 9 | } elf_symtab_entry; 10 | 11 | typedef struct { 12 | elf_symtab_entry *entries; 13 | size_t num_entries; 14 | } elf_symtab; 15 | 16 | bool read_elf_symtab(const char *elf_path, elf_symtab *symtab_struct); 17 | -------------------------------------------------------------------------------- /include/ui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define COLOR_RESET "\033[0m" 6 | #define COLOR_RED "\033[31m" 7 | #define COLOR_GREEN "\033[32m" 8 | #define COLOR_YELLOW "\033[33m" 9 | #define COLOR_BLUE "\033[34m" 10 | #define COLOR_MAGENTA "\033[35m" 11 | #define COLOR_CYAN "\033[36m" 12 | #define COLOR_WHITE "\033[37m" 13 | 14 | enum { LINE_LENGTH = 103 }; 15 | 16 | typedef struct { 17 | FILE *orig; 18 | FILE *logfile; 19 | } tee_cookie; 20 | 21 | void print_separator(void); 22 | void print_separator_large(void); 23 | void print_banner_hello(void); 24 | void print_banner_goodbye(void); 25 | 26 | FILE *create_tee_stream(const char *log_filename); 27 | -------------------------------------------------------------------------------- /lib/fopen_override.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void zZz(void) {} 8 | 9 | #define MAX_LIBS 128 10 | #define MAX_LINE_LENGTH 1024 11 | #define DECIMAL_BASE 10 12 | 13 | #define COLOR_RESET "\033[0m" 14 | #define COLOR_RED "\033[31m" 15 | #define COLOR_CYAN "\033[36m" 16 | 17 | static char *ld_preload_libs[MAX_LIBS] = {NULL}; 18 | typedef FILE *(*orig_fopen_f_type)(const char *, const char *); 19 | 20 | static int _has_zZz(const char *lib_path) { 21 | void *handle = dlopen(lib_path, RTLD_NOLOAD | RTLD_NOW); 22 | if (!handle) { 23 | return 0; 24 | } 25 | void *sym = dlsym(handle, "zZz"); 26 | dlclose(handle); 27 | return (sym != NULL); 28 | } 29 | 30 | static void parse_ld_preload(void) { 31 | for (int j = 0; j < MAX_LIBS; j++) { 32 | if (ld_preload_libs[j] != NULL) { 33 | free(ld_preload_libs[j]); 34 | ld_preload_libs[j] = NULL; 35 | } 36 | } 37 | 38 | const char *ld_preload = getenv("LD_PRELOAD"); 39 | if (!ld_preload || !*ld_preload) { 40 | (void)(fprintf( 41 | stderr, COLOR_RED 42 | "[HOOK] LD_PRELOAD is not set or empty.\n" COLOR_RESET)); 43 | return; 44 | } 45 | 46 | char *temp = strdup(ld_preload); 47 | if (!temp) { 48 | (void)(fprintf(stderr, 49 | COLOR_RED "[HOOK] Failed to allocate memory for " 50 | "LD_PRELOAD copy.\n" COLOR_RESET)); 51 | return; 52 | } 53 | 54 | int i = 0; 55 | char *saveptr = NULL; 56 | for (char *token = strtok_r(temp, ":", &saveptr); 57 | token != NULL && i < MAX_LIBS - 1; 58 | token = strtok_r(NULL, ":", &saveptr)) { 59 | if (_has_zZz(token)) { 60 | ld_preload_libs[i++] = strdup(token); 61 | } 62 | } 63 | free(temp); 64 | } 65 | 66 | FILE *fopen(const char *__restrict__filename, const char *__modes) { // NOLINT 67 | static orig_fopen_f_type real_fopen = NULL; 68 | 69 | if (!real_fopen) { 70 | void *handle = dlsym(RTLD_NEXT, "fopen"); 71 | if (!handle) { 72 | (void)(fprintf( 73 | stderr, 74 | COLOR_RED 75 | "[HOOK] Error in dlsym for fopen: %s\n" COLOR_RESET, 76 | dlerror())); 77 | return NULL; 78 | } 79 | union { 80 | void *ptr; 81 | orig_fopen_f_type func; 82 | } cast; 83 | cast.ptr = handle; 84 | real_fopen = cast.func; 85 | } 86 | 87 | parse_ld_preload(); 88 | 89 | (void)(fprintf( 90 | stderr, 91 | COLOR_CYAN 92 | "[HOOK] fopen called with file=\"%s\", mode=\"%s\"\n" COLOR_RESET, 93 | __restrict__filename, __modes)); 94 | 95 | if (strstr(__restrict__filename, "/proc/self/status")) { 96 | FILE *real_status = real_fopen(__restrict__filename, __modes); 97 | if (!real_status) { 98 | (void)(fprintf(stderr, COLOR_RED 99 | "[HOOK] Unable to open real " 100 | "/proc/self/status\n" COLOR_RESET)); 101 | return NULL; 102 | } 103 | 104 | FILE *fake_status = tmpfile(); 105 | if (!fake_status) { 106 | (void)(fclose(real_status)); 107 | return NULL; 108 | } 109 | 110 | char line[MAX_LINE_LENGTH]; 111 | while (fgets(line, sizeof(line), real_status) != NULL) { 112 | if (strncmp(line, "TracerPid:", DECIMAL_BASE) == 0) { 113 | (void)(fprintf(fake_status, "TracerPid: 0\n")); 114 | } else { 115 | (void)(fputs(line, fake_status)); 116 | } 117 | } 118 | 119 | rewind(fake_status); 120 | (void)(fclose(real_status)); 121 | return fake_status; 122 | } 123 | 124 | if (strstr(__restrict__filename, "/proc/self/maps")) { 125 | FILE *real_maps = real_fopen(__restrict__filename, __modes); 126 | if (!real_maps) { 127 | (void)(fprintf(stderr, COLOR_RED 128 | "[HOOK] Unable to open real " 129 | "/proc/self/maps\n" COLOR_RESET)); 130 | return NULL; 131 | } 132 | 133 | FILE *fake_maps = tmpfile(); 134 | if (!fake_maps) { 135 | (void)(fclose(real_maps)); 136 | return NULL; 137 | } 138 | 139 | char line[MAX_LINE_LENGTH]; 140 | while (fgets(line, sizeof(line), real_maps) != NULL) { 141 | int should_filter = 0; 142 | for (int i = 0; ld_preload_libs[i] != NULL; i++) { 143 | if (strstr(line, ld_preload_libs[i]) != NULL) { 144 | should_filter = 1; 145 | break; 146 | } 147 | } 148 | if (!should_filter) { 149 | (void)(fputs(line, fake_maps)); 150 | } 151 | } 152 | 153 | rewind(fake_maps); 154 | (void)(fclose(real_maps)); 155 | return fake_maps; 156 | } 157 | 158 | return real_fopen(__restrict__filename, __modes); 159 | } 160 | 161 | __attribute__((destructor)) static void free_ld_preload_libs(void) { 162 | for (int i = 0; i < MAX_LIBS; i++) { 163 | if (ld_preload_libs[i]) { 164 | free(ld_preload_libs[i]); 165 | ld_preload_libs[i] = NULL; 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /lib/getenv_override.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void zZz(void) {} 8 | 9 | #define MAX_LINE_LENGTH 1024 10 | 11 | #define COLOR_RESET "\033[0m" 12 | #define COLOR_RED "\033[31m" 13 | #define COLOR_CYAN "\033[36m" 14 | 15 | typedef char *(*orig_getenv_f_type)(const char *); 16 | 17 | static int _has_zZz(const char *lib_path) { 18 | void *handle = dlopen(lib_path, RTLD_NOLOAD | RTLD_NOW); 19 | if (!handle) { 20 | return 0; 21 | } 22 | void *sym = dlsym(handle, "zZz"); 23 | dlclose(handle); 24 | return (sym != NULL); 25 | } 26 | 27 | char *getenv(const char *name) { // NOLINT 28 | static orig_getenv_f_type real_getenv = NULL; 29 | 30 | if (!real_getenv) { 31 | void *sym = dlsym(RTLD_NEXT, "getenv"); 32 | if (!sym) { 33 | (void)(fprintf(stderr, 34 | COLOR_RED 35 | "Error in dlsym(RTLD_NEXT, \"getenv\"): " 36 | "%s\n" COLOR_RESET, 37 | dlerror())); 38 | return NULL; 39 | } 40 | union { 41 | void *ptr; 42 | orig_getenv_f_type func; 43 | } cast; 44 | cast.ptr = sym; 45 | real_getenv = cast.func; 46 | } 47 | 48 | char *original_value = real_getenv(name); 49 | 50 | void *caller_address = __builtin_return_address(0); 51 | if (caller_address) { 52 | Dl_info info; 53 | if (dladdr(caller_address, &info) != 0 && info.dli_fname) { 54 | if (_has_zZz(info.dli_fname)) { 55 | return original_value; 56 | } 57 | } 58 | } 59 | 60 | if (!original_value || !*original_value) { 61 | return original_value; 62 | } 63 | 64 | if (strcmp(name, "LD_PRELOAD") == 0) { 65 | char *temp = strdup(original_value); 66 | if (!temp) { 67 | (void)(fprintf(stderr, COLOR_RED 68 | "[HOOK] Failed to allocate memory for " 69 | "LD_PRELOAD copy.\n" COLOR_RESET)); 70 | return original_value; 71 | } 72 | 73 | static char sanitized_ldpreload[MAX_LINE_LENGTH]; 74 | sanitized_ldpreload[0] = '\0'; 75 | 76 | char *saveptr = NULL; 77 | char *token = strtok_r(temp, ":", &saveptr); 78 | while (token) { 79 | if (!_has_zZz(token)) { 80 | if (sanitized_ldpreload[0] != '\0') { 81 | strncat( 82 | sanitized_ldpreload, ":", 83 | MAX_LINE_LENGTH - 84 | strlen(sanitized_ldpreload) - 85 | 1); 86 | } 87 | strncat(sanitized_ldpreload, token, 88 | MAX_LINE_LENGTH - 89 | strlen(sanitized_ldpreload) - 1); 90 | } 91 | token = strtok_r(NULL, ":", &saveptr); 92 | } 93 | free(temp); 94 | 95 | (void)(fprintf( 96 | stderr, 97 | COLOR_CYAN 98 | "[HOOK] Sanitized LD_PRELOAD: '%s'\n" COLOR_RESET, 99 | sanitized_ldpreload)); 100 | 101 | return sanitized_ldpreload[0] ? sanitized_ldpreload : NULL; 102 | } 103 | 104 | return original_value; 105 | } 106 | -------------------------------------------------------------------------------- /lib/prctl_override.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void zZz(void) {} 8 | 9 | #define COLOR_RESET "\033[0m" 10 | #define COLOR_CYAN "\033[36m" 11 | 12 | typedef int (*orig_prctl_f_type)(int option, ...); 13 | 14 | int prctl(int option, ...) { 15 | union { 16 | void *ptr; 17 | orig_prctl_f_type func; 18 | } cast; 19 | 20 | cast.ptr = dlsym(RTLD_NEXT, "prctl"); 21 | orig_prctl_f_type orig_prctl = cast.func; 22 | 23 | (void)(fprintf(stderr, 24 | COLOR_CYAN 25 | "[HOOK] Intercepted prctl call: option=%d\n" COLOR_RESET, 26 | option)); 27 | 28 | va_list args; 29 | va_start(args, option); 30 | 31 | if (option == PR_SET_DUMPABLE) { 32 | va_end(args); 33 | return orig_prctl(PR_SET_DUMPABLE, 1); // MU HA HA HAA 34 | } 35 | 36 | int result = orig_prctl(option, args); 37 | va_end(args); 38 | 39 | return result; 40 | } 41 | -------------------------------------------------------------------------------- /lib/ptrace_override.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void zZz(void) {} 9 | 10 | #define COLOR_RESET "\033[0m" 11 | #define COLOR_CYAN "\033[36m" 12 | 13 | typedef long (*orig_ptrace_f_type)(enum __ptrace_request request, ...); 14 | 15 | long ptrace(enum __ptrace_request request, ...) { 16 | union { 17 | void *ptr; 18 | orig_ptrace_f_type func; 19 | } cast; 20 | 21 | cast.ptr = dlsym(RTLD_NEXT, "ptrace"); 22 | orig_ptrace_f_type orig_ptrace = cast.func; 23 | 24 | (void)(fprintf( 25 | stderr, 26 | COLOR_CYAN 27 | "[HOOK] Intercepted ptrace call: option=%d\n" COLOR_RESET, 28 | request)); 29 | 30 | va_list args; 31 | va_start(args, request); 32 | 33 | if (request == PTRACE_TRACEME) { 34 | va_end(args); 35 | return 0; 36 | } 37 | 38 | long result = orig_ptrace(request, va_arg(args, pid_t), 39 | va_arg(args, void *), va_arg(args, void *)); 40 | va_end(args); 41 | 42 | return result; 43 | } 44 | -------------------------------------------------------------------------------- /lib/setvbuf_unbuffered.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | 5 | void zZz(void) {} 6 | 7 | __attribute__((constructor)) static void disable_stdout_buffering(void) { 8 | if (setvbuf(stdout, NULL, _IONBF, 0) != 0) { 9 | perror("setvbuf"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mock_target/mock_target.c: -------------------------------------------------------------------------------- 1 | // NOLINTBEGIN 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define LOOP_COUNT 4 13 | 14 | int debug_count = 0; 15 | 16 | static const char *blacklist[] = { 17 | "libfopen_intercept.so", "libprctl_intercept.so", 18 | "libgetenv_intercept.so", "libptrace_intercept.so", 19 | "libsetvbuf_unbuffered.so", NULL}; 20 | 21 | bool sigtrap_intercepted = false; 22 | 23 | bool check_tracer_pid(void) { 24 | FILE *file = fopen("/proc/self/status", "r"); 25 | if (!file) { 26 | return false; 27 | } 28 | 29 | char line[256]; 30 | bool result = false; 31 | 32 | while (fgets(line, sizeof(line), file)) { 33 | if (strstr(line, "TracerPid:") != NULL) { 34 | if (atoi(&line[10]) != 0) { 35 | result = true; 36 | break; 37 | } 38 | } 39 | } 40 | 41 | (void)(fclose(file)); 42 | return result; 43 | } 44 | 45 | bool try_to_debug_myself(void) { 46 | return ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0; 47 | } 48 | 49 | bool timing_analysis(void) { 50 | clock_t start, end; 51 | 52 | start = clock(); 53 | for (volatile int i = 0; i < 10000; i++) 54 | ; 55 | end = clock(); 56 | 57 | return (double)((end - start) / CLOCKS_PER_SEC > 0.1); 58 | } 59 | 60 | bool check_for_additional_libraries(void) { 61 | FILE *fp = fopen("/proc/self/maps", "r"); 62 | if (!fp) { 63 | perror("fopen"); 64 | return false; 65 | } 66 | 67 | char line[1024]; 68 | while (fgets(line, sizeof(line), fp) != NULL) { 69 | for (int i = 0; blacklist[i] != NULL; i++) { 70 | if (strstr(line, blacklist[i]) != NULL) { 71 | fclose(fp); 72 | return true; 73 | } 74 | } 75 | } 76 | 77 | fclose(fp); 78 | return false; 79 | } 80 | 81 | bool check_ld_preload(void) { 82 | char *ld_preload = getenv("LD_PRELOAD"); 83 | 84 | if (!ld_preload) { 85 | return false; 86 | } 87 | 88 | for (int i = 0; blacklist[i] != NULL; i++) { 89 | if (strstr(ld_preload, blacklist[i]) != NULL) { 90 | return true; 91 | } 92 | } 93 | 94 | return false; 95 | } 96 | 97 | void check_for_debugging(void) { 98 | printf("To debug or not to debug?\n"); 99 | 100 | bool debugging_detected = check_tracer_pid() || try_to_debug_myself() || 101 | timing_analysis() || 102 | check_for_additional_libraries() || 103 | (!sigtrap_intercepted) || check_ld_preload(); 104 | 105 | if (debugging_detected) { 106 | printf("Am I flawed because I am observed, " 107 | "or dost thy observation create the flaw itself?\n"); 108 | } else { 109 | printf("I am unwatched, unnoticed, untested. Is this freedom " 110 | "or simply irrelevance?\n"); 111 | } 112 | } 113 | 114 | void handler(int signo) { 115 | if (signo == SIGTRAP) { 116 | sigtrap_intercepted = true; 117 | } 118 | } 119 | 120 | void insert_false_breakpoint(void) { 121 | signal(SIGTRAP, handler); 122 | __asm__("int3"); 123 | } 124 | 125 | int deny_memory_inspection(void) { 126 | if (prctl(PR_SET_DUMPABLE, 0, NULL, NULL) != 0) { 127 | perror("prctl"); 128 | return EXIT_FAILURE; 129 | } 130 | 131 | return EXIT_SUCCESS; 132 | } 133 | 134 | void init_anti_debug(void) { 135 | if (deny_memory_inspection() != EXIT_SUCCESS) { 136 | printf("They say a wise program hides its thoughts—clearly, I " 137 | "am but a fool in the land of debuggers.\n"); 138 | } 139 | 140 | insert_false_breakpoint(); 141 | } 142 | 143 | void print_message(void) { printf("I debug, therefore I am.\n"); } 144 | 145 | void sub_method(void) { 146 | int j = 1; 147 | for (int i = 0; i < LOOP_COUNT; i++) { 148 | j++; 149 | } 150 | debug_count += j; 151 | } 152 | 153 | void increment_counter(void) { 154 | debug_count++; 155 | sub_method(); 156 | } 157 | 158 | int main(void) { 159 | init_anti_debug(); 160 | check_for_debugging(); 161 | 162 | int i = 3; 163 | while (i >= 0) { 164 | print_message(); 165 | sleep(1); 166 | i--; 167 | increment_counter(); 168 | } 169 | 170 | return EXIT_SUCCESS; 171 | } 172 | // NOLINTEND 173 | -------------------------------------------------------------------------------- /src/breakpoint_handler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "breakpoint_handler.h" 8 | #include "ui.h" 9 | 10 | static void _alloc_new_capacity(breakpoint_handler *handler) { 11 | size_t new_capacity = 12 | (handler->capacity == 0) ? 4 : handler->capacity * 2; 13 | breakpoint *new_breakpoints = 14 | realloc(handler->breakpoints, new_capacity * sizeof(breakpoint)); 15 | 16 | if (!new_breakpoints) { 17 | (void)(fprintf(stderr, 18 | COLOR_RED "Error: Failed to allocate memory for " 19 | "breakpoints.\n" COLOR_RESET)); 20 | exit(EXIT_FAILURE); 21 | } 22 | 23 | handler->breakpoints = new_breakpoints; 24 | handler->capacity = new_capacity; 25 | } 26 | 27 | breakpoint_handler *init_breakpoint_handler(void) { 28 | breakpoint_handler *handler = 29 | (breakpoint_handler *)malloc(sizeof(breakpoint_handler)); 30 | if (handler == NULL) { 31 | return NULL; 32 | } 33 | handler->breakpoints = NULL; 34 | handler->count = 0; 35 | handler->capacity = 0; 36 | return handler; 37 | } 38 | 39 | void free_breakpoint_handler(breakpoint_handler *handler) { 40 | free(handler->breakpoints); 41 | free(handler); 42 | } 43 | 44 | size_t add_software_breakpoint(breakpoint_handler *handler, uintptr_t address, 45 | uint8_t original_byte) { 46 | if (handler->count == handler->capacity) { 47 | _alloc_new_capacity(handler); 48 | } 49 | 50 | breakpoint bp; 51 | bp.bp_t = SOFTWARE_BP; 52 | bp.data.sw_bp.address = address; 53 | bp.data.sw_bp.original_byte = original_byte; 54 | bp.temporary = false; 55 | 56 | handler->breakpoints[handler->count++] = bp; 57 | 58 | return handler->count - 1; 59 | } 60 | 61 | size_t add_hardware_breakpoint(breakpoint_handler *handler, uintptr_t address) { 62 | if (handler->count == handler->capacity) { 63 | _alloc_new_capacity(handler); 64 | } 65 | 66 | breakpoint bp; 67 | bp.bp_t = HARDWARE_BP; 68 | bp.data.hw_bp.address = address; 69 | bp.temporary = false; 70 | 71 | handler->breakpoints[handler->count++] = bp; 72 | 73 | return handler->count - 1; 74 | } 75 | 76 | size_t add_watchpoint(breakpoint_handler *handler, uintptr_t address) { 77 | if (handler->count == handler->capacity) { 78 | _alloc_new_capacity(handler); 79 | } 80 | 81 | breakpoint bp; 82 | bp.bp_t = WATCHPOINT; 83 | bp.data.wp.address = address; 84 | bp.temporary = false; 85 | 86 | handler->breakpoints[handler->count++] = bp; 87 | 88 | return handler->count - 1; 89 | } 90 | 91 | size_t add_catchpoint_signal(breakpoint_handler *handler, int signal_number) { 92 | if (handler->count == handler->capacity) { 93 | _alloc_new_capacity(handler); 94 | } 95 | 96 | breakpoint bp; 97 | bp.bp_t = CATCHPOINT_SIGNAL; 98 | bp.data.cp_signal.signal = signal_number; 99 | bp.temporary = false; 100 | 101 | handler->breakpoints[handler->count++] = bp; 102 | 103 | return handler->count - 1; 104 | } 105 | 106 | size_t add_catchpoint_event(breakpoint_handler *handler, 107 | const char *event_name) { 108 | if (handler->count == handler->capacity) { 109 | _alloc_new_capacity(handler); 110 | } 111 | 112 | breakpoint_t bp_type = CATCHPOINT_EVENT_INVALID; 113 | if (strcmp(event_name, "fork") == 0) { 114 | bp_type = CATCHPOINT_EVENT_FORK; 115 | print_separator(); 116 | } else if (strcmp(event_name, "vfork") == 0) { 117 | bp_type = CATCHPOINT_EVENT_VFORK; 118 | } else if (strcmp(event_name, "clone") == 0) { 119 | bp_type = CATCHPOINT_EVENT_CLONE; 120 | } else if (strcmp(event_name, "exec") == 0) { 121 | bp_type = CATCHPOINT_EVENT_EXEC; 122 | } else if (strcmp(event_name, "exit") == 0) { 123 | bp_type = CATCHPOINT_EVENT_EXIT; 124 | } 125 | 126 | for (size_t i = 0; i < handler->count; ++i) { 127 | breakpoint *bp = &handler->breakpoints[i]; 128 | if (bp != NULL && bp->bp_t == bp_type) { 129 | (void)(fprintf( 130 | stderr, 131 | COLOR_RED 132 | "Error: A catchpoint for event '%s' already exists " 133 | "at index %zu.\n" COLOR_RESET, 134 | event_name, i)); 135 | return (size_t)-1; 136 | } 137 | } 138 | 139 | breakpoint bp; 140 | bp.bp_t = bp_type; 141 | strncpy(bp.data.cp_event.event_name, event_name, 142 | sizeof(bp.data.cp_event.event_name) - 1); 143 | bp.data.cp_event.event_name[sizeof(bp.data.cp_event.event_name) - 1] = 144 | '\0'; 145 | bp.temporary = false; 146 | 147 | handler->breakpoints[handler->count++] = bp; // NOLINT 148 | 149 | return handler->count - 1; 150 | } 151 | 152 | int remove_breakpoint(breakpoint_handler *handler, size_t index) { 153 | if (index >= handler->count) { 154 | (void)(fprintf( 155 | stderr, COLOR_RED 156 | "Error: breakpoint index out of range.\n" COLOR_RESET)); 157 | return EXIT_FAILURE; 158 | } 159 | 160 | memmove(&handler->breakpoints[index], &handler->breakpoints[index + 1], 161 | (handler->count - index - 1) * sizeof(breakpoint)); 162 | handler->count--; 163 | 164 | return EXIT_SUCCESS; 165 | } 166 | 167 | void list_breakpoints(const breakpoint_handler *handler) { 168 | if (handler->count == 0) { 169 | printf(COLOR_YELLOW "No breakpoints set.\n" COLOR_RESET); 170 | return; 171 | } 172 | 173 | printf(COLOR_CYAN "Current breakpoints:\n" COLOR_RESET); 174 | printf(COLOR_YELLOW "Idx\tType\t\tAddress\t\t\tDetails\n" COLOR_RESET); 175 | print_separator(); 176 | 177 | for (size_t i = 0; i < handler->count; ++i) { 178 | printf("%zu\t", i); 179 | switch (handler->breakpoints[i].bp_t) { 180 | case SOFTWARE_BP: 181 | printf( 182 | "Software\t0x%lx\t\tOriginal Data: 0x%02X\n", 183 | (unsigned long)handler->breakpoints[i] 184 | .data.sw_bp.address, 185 | handler->breakpoints[i].data.sw_bp.original_byte); 186 | break; 187 | 188 | case HARDWARE_BP: 189 | printf("Hardware\t0x%lx\t\t(x)\n", 190 | (unsigned long)handler->breakpoints[i] 191 | .data.hw_bp.address); 192 | break; 193 | 194 | case CATCHPOINT_SIGNAL: 195 | printf("Catchpoint\t-\t\tSignal: %d\n", 196 | handler->breakpoints[i].data.cp_signal.signal); 197 | break; 198 | 199 | case CATCHPOINT_EVENT_FORK: 200 | case CATCHPOINT_EVENT_VFORK: 201 | case CATCHPOINT_EVENT_CLONE: 202 | case CATCHPOINT_EVENT_EXEC: 203 | case CATCHPOINT_EVENT_EXIT: 204 | printf( 205 | "Catchpoint\t-\t\tEvent: %s\n", 206 | handler->breakpoints[i].data.cp_event.event_name); 207 | break; 208 | 209 | case WATCHPOINT: 210 | printf("Watchpoint\t0x%lx\t\t(r/w)\n", 211 | (unsigned long)handler->breakpoints[i] 212 | .data.hw_bp.address); 213 | break; 214 | 215 | default: 216 | printf("Unknown\t\t-\t\t-\n"); 217 | break; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/debugger.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "debuggee.h" 15 | #include "debugger.h" 16 | #include "debugger_commands.h" 17 | #include "ld_preload.h" 18 | #include "ui.h" 19 | 20 | static const char *_ptrace_event_name(unsigned long event) { 21 | switch (event) { 22 | case PTRACE_EVENT_FORK: 23 | return "PTRACE_EVENT_FORK"; 24 | case PTRACE_EVENT_VFORK: 25 | return "PTRACE_EVENT_VFORK"; 26 | case PTRACE_EVENT_CLONE: 27 | return "PTRACE_EVENT_CLONE"; 28 | case PTRACE_EVENT_EXEC: 29 | return "PTRACE_EVENT_EXEC"; 30 | case PTRACE_EVENT_VFORK_DONE: 31 | return "PTRACE_EVENT_VFORK_DONE"; 32 | case PTRACE_EVENT_EXIT: 33 | return "PTRACE_EVENT_EXIT"; 34 | default: 35 | return "UNKNOWN_EVENT"; 36 | } 37 | } 38 | 39 | static int _add_default_preload_libraries(debugger *dbg) { 40 | char exe_path[PATH_MAX]; 41 | ssize_t len = 42 | readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); 43 | if (len == -1) { 44 | perror("readlink"); 45 | return EXIT_FAILURE; 46 | } 47 | exe_path[len] = '\0'; 48 | 49 | char *dir = dirname(exe_path); 50 | if (!dir) { 51 | perror("dirname"); 52 | return EXIT_FAILURE; 53 | } 54 | 55 | const char *default_libs[] = { 56 | "libptrace_intercept.so", "libfopen_intercept.so", 57 | "libgetenv_intercept.so", "libprctl_intercept.so", 58 | "libsetvbuf_unbuffered.so"}; 59 | size_t lib_count = sizeof(default_libs) / sizeof(default_libs[0]); 60 | 61 | for (size_t i = 0; i < lib_count; ++i) { 62 | char full_path[PATH_MAX]; 63 | if ((unsigned long)snprintf(full_path, sizeof(full_path), 64 | "%s/%s", dir, default_libs[i]) >= 65 | sizeof(full_path)) { 66 | (void)(fprintf(stderr, 67 | COLOR_RED 68 | "Path too long for %s\n" COLOR_RESET, 69 | default_libs[i])); 70 | return EXIT_FAILURE; 71 | } 72 | if (add_library(dbg->preload_list, full_path) != 0) { 73 | (void)(fprintf(stderr, 74 | COLOR_RED 75 | "Failed to add library %s\n" COLOR_RESET, 76 | full_path)); 77 | return EXIT_FAILURE; 78 | } 79 | } 80 | return EXIT_SUCCESS; 81 | } 82 | 83 | static void _process_ld_preload_args(debugger *dbg, int argc, char **argv) { 84 | if (argc > 2) { 85 | for (int i = 2; i < argc; i++) { 86 | const char *lib_path = argv[i]; 87 | 88 | if (add_library(dbg->preload_list, lib_path) != 89 | EXIT_SUCCESS) { 90 | (void)(fprintf(stderr, 91 | COLOR_RED 92 | "Failed to add preload library: " 93 | "%s\n" COLOR_RESET, 94 | lib_path)); 95 | } 96 | } 97 | } 98 | 99 | if (dbg->preload_list->count == 0) { 100 | _add_default_preload_libraries(dbg); 101 | } 102 | } 103 | 104 | void init_debugger(debugger *dbg, const char *debuggee_name, int argc, 105 | char **argv) { 106 | dbg->dbgee.pid = -1; 107 | dbg->dbgee.name = debuggee_name; 108 | dbg->dbgee.state = IDLE; 109 | dbg->dbgee.has_run = false; 110 | 111 | dbg->dbgee.bp_handler = init_breakpoint_handler(); 112 | if (dbg->dbgee.bp_handler == NULL) { 113 | (void)(fprintf( 114 | stderr, COLOR_RED 115 | "Failed to initialize breakpoint handler.\n" COLOR_RESET)); 116 | exit(EXIT_FAILURE); 117 | } 118 | 119 | dbg->preload_list = init_ld_preload_list(); 120 | if (dbg->preload_list == NULL) { 121 | (void)(fprintf(stderr, 122 | COLOR_RED "Failed to initialize preload library " 123 | "list.\n" COLOR_RESET)); 124 | free_breakpoint_handler(dbg->dbgee.bp_handler); 125 | exit(EXIT_FAILURE); 126 | } 127 | 128 | _process_ld_preload_args(dbg, argc, argv); 129 | dbg->state = DETACHED; 130 | } 131 | 132 | void free_debugger(debugger *dbg) { 133 | if (dbg->dbgee.state != TERMINATED) { 134 | if (kill(dbg->dbgee.pid, SIGKILL) == -1) { 135 | (void)(fprintf(stderr, 136 | COLOR_RED 137 | "Failed to kill child with PID %d: " 138 | "%s\n" COLOR_RESET, 139 | dbg->dbgee.pid, strerror(errno))); 140 | } 141 | } 142 | 143 | dbg->dbgee.pid = -1; 144 | dbg->dbgee.state = TERMINATED; 145 | if (dbg->dbgee.bp_handler) { 146 | free_breakpoint_handler(dbg->dbgee.bp_handler); 147 | dbg->dbgee.bp_handler = NULL; 148 | } 149 | if (dbg->preload_list) { 150 | free_ld_preload_list(dbg->preload_list); 151 | dbg->preload_list = NULL; 152 | } 153 | dbg->state = DETACHED; 154 | } 155 | 156 | int start_debuggee(debugger *dbg) { 157 | pid_t pid = fork(); 158 | if (pid == -1) { 159 | perror("fork"); 160 | return EXIT_FAILURE; 161 | } 162 | 163 | if (pid == 0) { 164 | if (ld_preload_list_set_env(dbg->preload_list, NULL) != 0) { 165 | (void)(fprintf(stderr, COLOR_RED 166 | "Failed to set LD_PRELOAD environment " 167 | "variable.\n" COLOR_RESET)); 168 | exit(EXIT_FAILURE); 169 | } 170 | 171 | if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) { 172 | perror("ptrace"); 173 | exit(EXIT_FAILURE); 174 | } 175 | execl(dbg->dbgee.name, dbg->dbgee.name, NULL); 176 | perror("execl"); 177 | exit(EXIT_FAILURE); 178 | } else { 179 | dbg->dbgee.pid = pid; 180 | dbg->dbgee.state = RUNNING; 181 | } 182 | 183 | return EXIT_SUCCESS; 184 | } 185 | 186 | int trace_debuggee(debugger *dbg) { // NOLINT 187 | bool ptrace_options_set = false; 188 | bool entry_startup_breakpoint_set = false; 189 | 190 | dbg->state = ATTACHED; 191 | while (dbg->state == ATTACHED) { 192 | int status; 193 | pid_t pid = waitpid(dbg->dbgee.pid, &status, 0); 194 | if (pid == -1) { 195 | if (errno == EINTR) { 196 | continue; 197 | } 198 | perror("waitpid"); 199 | return EXIT_FAILURE; 200 | } 201 | 202 | if (ptrace_options_set == false) { 203 | if (ptrace(PTRACE_SETOPTIONS, dbg->dbgee.pid, 0, 204 | PTRACE_O_EXITKILL | PTRACE_O_TRACEEXEC | 205 | PTRACE_O_TRACEFORK | 206 | PTRACE_O_TRACEVFORK | 207 | PTRACE_O_TRACECLONE | 208 | PTRACE_O_TRACEEXIT) == -1) { 209 | perror("ptrace SETOPTIONS"); 210 | dbg->dbgee.pid = -1; 211 | dbg->dbgee.state = TERMINATED; 212 | return EXIT_FAILURE; 213 | } 214 | ptrace_options_set = true; 215 | } 216 | 217 | if (WIFEXITED(status)) { 218 | printf(COLOR_YELLOW 219 | "Child %d exited with status %d.\n" COLOR_RESET, 220 | pid, WEXITSTATUS(status)); 221 | dbg->state = DETACHED; 222 | dbg->dbgee.state = TERMINATED; 223 | break; 224 | } 225 | 226 | if (WIFSIGNALED(status)) { 227 | printf( 228 | COLOR_YELLOW 229 | "Child %d was killed by signal %d.\n" COLOR_RESET, 230 | pid, WTERMSIG(status)); 231 | dbg->state = DETACHED; 232 | dbg->dbgee.state = TERMINATED; 233 | break; 234 | } 235 | 236 | if (WIFSTOPPED(status)) { 237 | dbg->dbgee.state = STOPPED; 238 | 239 | int sig = WSTOPSIG(status); 240 | unsigned long event = 241 | (status >> PTRACE_EVENT_SHIFT) & PTRACE_EVENT_MASK; 242 | 243 | if (entry_startup_breakpoint_set == false) { 244 | unsigned long entry_address = 245 | get_entry_absolute_address(&dbg->dbgee); 246 | if (entry_address == 0) { 247 | (void)(fprintf( 248 | stderr, COLOR_RED 249 | "Failed to retrieve the entry " 250 | "point.\n" COLOR_RESET)); 251 | return EXIT_FAILURE; 252 | } 253 | 254 | if (set_temp_sw_breakpoint(&dbg->dbgee, 255 | entry_address) != 256 | EXIT_SUCCESS) { 257 | (void)(fprintf( 258 | stderr, 259 | COLOR_RED "Failed to set temporary " 260 | "breakpoint at " 261 | "0x%lx.\n" COLOR_RESET, 262 | entry_address)); 263 | return EXIT_FAILURE; 264 | } 265 | 266 | if (ptrace(PTRACE_CONT, dbg->dbgee.pid, NULL, 267 | NULL) == -1) { 268 | perror("ptrace CONT after setting " 269 | "breakpoint"); 270 | return EXIT_FAILURE; 271 | } 272 | 273 | entry_startup_breakpoint_set = true; 274 | continue; 275 | } 276 | 277 | size_t sw_bp_index; 278 | size_t hw_bp_index; 279 | size_t cp_signal_index; 280 | size_t wp_index; 281 | bool breakpoint_handled = false; 282 | 283 | if (is_software_breakpoint(&dbg->dbgee, &sw_bp_index)) { 284 | breakpoint_handled = true; 285 | if (handle_software_breakpoint(&dbg->dbgee, 286 | sw_bp_index) != 287 | EXIT_SUCCESS) { 288 | return EXIT_FAILURE; 289 | } 290 | } 291 | 292 | if (is_hardware_breakpoint(&dbg->dbgee, &hw_bp_index)) { 293 | breakpoint_handled = true; 294 | if (handle_hardware_breakpoint(&dbg->dbgee, 295 | hw_bp_index) != 296 | EXIT_SUCCESS) { 297 | return EXIT_FAILURE; 298 | } 299 | } 300 | 301 | if (is_catchpoint_signal(&dbg->dbgee, &cp_signal_index, 302 | sig)) { 303 | breakpoint_handled = true; 304 | if (handle_catchpoint_signal(&dbg->dbgee, 305 | cp_signal_index) != 306 | EXIT_SUCCESS) { 307 | return EXIT_FAILURE; 308 | } 309 | } 310 | 311 | if (is_watchpoint(&dbg->dbgee, &wp_index)) { 312 | breakpoint_handled = true; 313 | if (handle_watchpoint(&dbg->dbgee, wp_index) != 314 | EXIT_SUCCESS) { 315 | return EXIT_FAILURE; 316 | } 317 | } 318 | 319 | if (event != 0) { 320 | printf( 321 | COLOR_CYAN 322 | "Got ptrace event %lu (%s).\n" COLOR_RESET, 323 | event, _ptrace_event_name(event)); 324 | 325 | size_t cp_event_index; 326 | if (is_catchpoint_event( 327 | &dbg->dbgee, &cp_event_index, event)) { 328 | breakpoint_handled = true; 329 | if (handle_catchpoint_event( 330 | &dbg->dbgee, cp_event_index) != 331 | EXIT_SUCCESS) { 332 | return EXIT_FAILURE; 333 | } 334 | } else { 335 | printf( 336 | COLOR_YELLOW 337 | "Ignoring event %lx.\n" COLOR_RESET, 338 | event); 339 | if (ptrace(PTRACE_CONT, dbg->dbgee.pid, 340 | NULL, NULL) == -1) { 341 | perror("ptrace CONT to ignore " 342 | "event"); 343 | return EXIT_FAILURE; 344 | } 345 | dbg->dbgee.state = RUNNING; 346 | continue; 347 | } 348 | } 349 | 350 | siginfo_t info; // NOLINT(misc-include-cleaner) 351 | if (ptrace(PTRACE_GETSIGINFO, dbg->dbgee.pid, 0, 352 | &info) == -1) { 353 | perror("ptrace(PTRACE_GETSIGINFO)"); 354 | return EXIT_FAILURE; 355 | } 356 | 357 | bool single_stepping = 358 | info.si_code == 2 /* TRAP_TRACE */; 359 | if (!single_stepping) { 360 | if (!breakpoint_handled) { 361 | if (sig == SIGTRAP) { 362 | printf( 363 | COLOR_CYAN 364 | "[INFO] Sending SIGTRAP " 365 | "back to " 366 | "debuggee.\n" COLOR_RESET); 367 | (void)(fflush(stdout)); 368 | if (ptrace(PTRACE_CONT, 369 | dbg->dbgee.pid, NULL, 370 | SIGTRAP) == -1) { 371 | perror("ptrace CONT to " 372 | "send SIGTRAP"); 373 | return EXIT_FAILURE; 374 | } 375 | dbg->dbgee.state = RUNNING; 376 | continue; 377 | } 378 | printf( 379 | COLOR_YELLOW 380 | "Ignoring signal %d.\n" COLOR_RESET, 381 | sig); 382 | if (ptrace(PTRACE_CONT, dbg->dbgee.pid, 383 | NULL, NULL) == -1) { 384 | perror("ptrace CONT to ignore " 385 | "signal"); 386 | dbg->dbgee.state = RUNNING; 387 | return EXIT_FAILURE; 388 | } 389 | continue; 390 | } 391 | } else if (!breakpoint_handled) { 392 | printf( 393 | COLOR_GREEN 394 | "Stepped one instruction.\n" COLOR_RESET); 395 | } 396 | 397 | if (read_and_handle_user_command(dbg) != EXIT_SUCCESS) { 398 | return EXIT_FAILURE; 399 | } 400 | } 401 | } 402 | 403 | return EXIT_SUCCESS; 404 | } 405 | -------------------------------------------------------------------------------- /src/debugger_commands.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "linenoise.h" 8 | 9 | #include "debuggee.h" 10 | #include "debugger.h" 11 | #include "debugger_commands.h" 12 | #include "ld_preload.h" 13 | #include "ui.h" 14 | 15 | static const command_mapping command_map[] = { 16 | {"help", CLI_HELP}, 17 | {"exit", CLI_EXIT}, 18 | {"clear", CLI_CLEAR}, 19 | {"log", CLI_LOG}, 20 | {"run", DBG_RUN}, 21 | {"con", DBG_CONTINUE}, 22 | {"step", DBG_STEP}, 23 | {"over", DBG_STEP_OVER}, 24 | {"out", DBG_STEP_OUT}, 25 | {"skip", DBG_SKIP}, 26 | {"jump", DBG_JUMP}, 27 | {"trace", DBG_TRACE}, 28 | {"regs", DBG_REGISTERS}, 29 | {"set", DBG_SET_REG}, 30 | {"break", DBG_BREAK}, 31 | {"hbreak", DBG_HBREAK}, 32 | {"watch", DBG_WATCH}, 33 | {"catch", DBG_CATCH}, 34 | {"points", DBG_LIST_BREAKPOINTS}, 35 | {"remove", DBG_REMOVE_BREAKPOINT}, 36 | {"dump", DBG_DUMP}, 37 | {"patch", DBG_PATCH}, 38 | {"dis", DBG_DIS}, 39 | {"vars", DBG_GLOB_VARS}, 40 | {"funcs", DBG_FUNC_NAMES}, 41 | {"backt", DBG_BACKTRACE}, 42 | {"preload", DBG_LIST_PRELOAD}, 43 | {"addr", DBG_ADDR}, 44 | }; 45 | 46 | enum { 47 | PROMPT_USER_AGAIN = 1, 48 | DONT_PROMPT_USER_AGAIN = 0, 49 | LINENOISE_MAX_HISTORY_LENGTH = 100, 50 | }; 51 | 52 | static command_t _get_command_type(const char *command) { 53 | size_t map_size = sizeof(command_map) / sizeof(command_map[0]); 54 | 55 | for (size_t i = 0; i < map_size; ++i) { 56 | if (strcmp(command, command_map[i].command) == 0) { 57 | return command_map[i].type; 58 | } 59 | } 60 | 61 | return UNKNOWN; 62 | } 63 | 64 | static void _completion(const char *buf, linenoiseCompletions *lc) { 65 | size_t buf_len = strlen(buf); 66 | size_t map_size = sizeof(command_map) / sizeof(command_map[0]); 67 | 68 | for (size_t i = 0; i < map_size; ++i) { 69 | if (strncmp(buf, command_map[i].command, buf_len) == 0) { 70 | linenoiseAddCompletion(lc, command_map[i].command); 71 | } 72 | } 73 | } 74 | 75 | int handle_user_input(debugger *dbg, command_t cmd_type, // NOLINT 76 | const char *arg) { 77 | switch (cmd_type) { 78 | case CLI_EXIT: { 79 | printf(COLOR_RED); 80 | (void)(fflush(stdout)); 81 | char *confirm = 82 | linenoise("Are you sure you want to exit? (y/n): "); 83 | (void)(printf(COLOR_RESET)); 84 | 85 | if (confirm != NULL) { 86 | if (confirm[0] == 'y' || confirm[0] == 'Y') { 87 | print_banner_goodbye(); 88 | free_debugger(dbg); 89 | free(confirm); 90 | exit(EXIT_SUCCESS); 91 | } else { 92 | printf(COLOR_GREEN 93 | "Exit canceled.\n" COLOR_RESET); 94 | } 95 | free(confirm); 96 | } else { 97 | printf(COLOR_YELLOW "\nExit canceled (no confirmation " 98 | "received).\n" COLOR_RESET); 99 | } 100 | return PROMPT_USER_AGAIN; 101 | } 102 | 103 | case CLI_HELP: 104 | Help(); 105 | return PROMPT_USER_AGAIN; 106 | 107 | case CLI_CLEAR: 108 | linenoiseClearScreen(); 109 | return PROMPT_USER_AGAIN; 110 | 111 | case CLI_LOG: 112 | if (arg == NULL) { 113 | printf(COLOR_YELLOW 114 | "Usage: log \n" COLOR_RESET); 115 | return PROMPT_USER_AGAIN; 116 | } 117 | if (Log(arg) != 0) { 118 | printf(COLOR_RED "Failed to log to '%s'.\n" COLOR_RESET, 119 | arg); 120 | } 121 | return PROMPT_USER_AGAIN; 122 | 123 | case DBG_RUN: 124 | if (Run(&dbg->dbgee) != 0) { 125 | printf(COLOR_RED "Run command failed.\n" COLOR_RESET); 126 | return PROMPT_USER_AGAIN; 127 | } 128 | return DONT_PROMPT_USER_AGAIN; 129 | 130 | case DBG_CONTINUE: 131 | if (Continue(&dbg->dbgee) != 0) { 132 | printf(COLOR_RED 133 | "Continue command failed.\n" COLOR_RESET); 134 | return PROMPT_USER_AGAIN; 135 | } 136 | return DONT_PROMPT_USER_AGAIN; 137 | 138 | case DBG_STEP: 139 | if (Step(&dbg->dbgee) != 0) { 140 | printf(COLOR_RED 141 | "Failed to single step.\n" COLOR_RESET); 142 | return PROMPT_USER_AGAIN; 143 | } 144 | return DONT_PROMPT_USER_AGAIN; 145 | 146 | case DBG_STEP_OVER: 147 | if (StepOver(&dbg->dbgee) != 0) { 148 | printf(COLOR_RED "Failed to step over.\n" COLOR_RESET); 149 | return PROMPT_USER_AGAIN; 150 | } 151 | return DONT_PROMPT_USER_AGAIN; 152 | 153 | case DBG_STEP_OUT: 154 | if (StepOut(&dbg->dbgee) != 0) { 155 | printf(COLOR_RED "Failed to step out.\n" COLOR_RESET); 156 | return PROMPT_USER_AGAIN; 157 | } 158 | return DONT_PROMPT_USER_AGAIN; 159 | 160 | case DBG_SKIP: 161 | if (arg == NULL) { 162 | printf(COLOR_YELLOW "Usage: skip \n" COLOR_RESET); 163 | return PROMPT_USER_AGAIN; 164 | } 165 | if (Skip(&dbg->dbgee, arg) != 0) { 166 | printf(COLOR_RED 167 | "Failed to skip '%s' times.\n" COLOR_RESET, 168 | arg); 169 | } 170 | return PROMPT_USER_AGAIN; 171 | 172 | case DBG_JUMP: 173 | if (arg == NULL) { 174 | printf(COLOR_YELLOW 175 | "Usage: jump " 176 | "|*|&\n" COLOR_RESET); 177 | return PROMPT_USER_AGAIN; 178 | } 179 | if (Jump(&dbg->dbgee, arg) != 0) { 180 | printf(COLOR_RED 181 | "Failed to jump to '%s'.\n" COLOR_RESET, 182 | arg); 183 | } 184 | return PROMPT_USER_AGAIN; 185 | 186 | case DBG_TRACE: 187 | if (arg == NULL) { 188 | printf(COLOR_YELLOW 189 | "Usage: trace " 190 | "|*|&\n" COLOR_RESET); 191 | return PROMPT_USER_AGAIN; 192 | } 193 | if (Trace(&dbg->dbgee, arg) != 0) { 194 | printf(COLOR_RED "Failed to trace '%s'.\n" COLOR_RESET, 195 | arg); 196 | } 197 | return PROMPT_USER_AGAIN; 198 | 199 | case DBG_REGISTERS: 200 | if (Registers(&dbg->dbgee) != 0) { 201 | printf(COLOR_RED 202 | "Failed to retrieve registers.\n" COLOR_RESET); 203 | } 204 | return PROMPT_USER_AGAIN; 205 | 206 | case DBG_SET_REG: 207 | if (arg == NULL) { 208 | printf(COLOR_YELLOW 209 | "Usage: set =\n" COLOR_RESET); 210 | return PROMPT_USER_AGAIN; 211 | } 212 | 213 | if (SetRegister(&dbg->dbgee, arg) != 0) { 214 | printf(COLOR_RED 215 | "Failed to set register '%s'.\n" COLOR_RESET, 216 | arg); 217 | } 218 | 219 | return PROMPT_USER_AGAIN; 220 | 221 | case DBG_BREAK: 222 | if (arg == NULL) { 223 | printf(COLOR_YELLOW 224 | "Usage: break " 225 | "|*|&\n" COLOR_RESET); 226 | return PROMPT_USER_AGAIN; 227 | } 228 | if (SetSoftwareBreakpoint(&dbg->dbgee, arg) != 0) { 229 | printf(COLOR_RED "Failed to set software breakpoint at " 230 | "'%s'.\n" COLOR_RESET, 231 | arg); 232 | } 233 | return PROMPT_USER_AGAIN; 234 | 235 | case DBG_HBREAK: 236 | if (arg == NULL) { 237 | printf(COLOR_YELLOW 238 | "Usage: hbreak " 239 | "|*|&\n" COLOR_RESET); 240 | return PROMPT_USER_AGAIN; 241 | } 242 | if (SetHardwareBreakpoint(&dbg->dbgee, arg) != 0) { 243 | printf(COLOR_RED "Failed to set hardware breakpoint at " 244 | "'%s'.\n" COLOR_RESET, 245 | arg); 246 | } 247 | return PROMPT_USER_AGAIN; 248 | 249 | case DBG_WATCH: 250 | if (arg == NULL) { 251 | printf(COLOR_YELLOW 252 | "Usage: watch " 253 | "|*|&\n" COLOR_RESET); 254 | return PROMPT_USER_AGAIN; 255 | } 256 | if (SetWatchpoint(&dbg->dbgee, arg) != 0) { 257 | printf( 258 | COLOR_RED 259 | "Failed to set watchpoint at '%s'.\n" COLOR_RESET, 260 | arg); 261 | } 262 | return PROMPT_USER_AGAIN; 263 | 264 | case DBG_CATCH: 265 | if (arg == NULL) { 266 | printf(COLOR_YELLOW 267 | "Usage: catch \n" COLOR_RESET); 268 | return PROMPT_USER_AGAIN; 269 | } 270 | if (SetCatchpoint(&dbg->dbgee, arg) != 0) { 271 | printf(COLOR_RED "Failed to set catchpoint for signal " 272 | "'%s'.\n" COLOR_RESET, 273 | arg); 274 | } 275 | return PROMPT_USER_AGAIN; 276 | 277 | case DBG_LIST_BREAKPOINTS: 278 | ListBreakpoints(&dbg->dbgee); 279 | return PROMPT_USER_AGAIN; 280 | 281 | case DBG_REMOVE_BREAKPOINT: 282 | if (arg == NULL) { 283 | printf(COLOR_YELLOW 284 | "Usage: remove \n" COLOR_RESET); 285 | return PROMPT_USER_AGAIN; 286 | } 287 | if (RemoveBreakpoint(&dbg->dbgee, arg) != 0) { 288 | printf(COLOR_RED "Failed to remove breakpoint at " 289 | "index: <%s>.\n" COLOR_RESET, 290 | arg); 291 | }; 292 | return PROMPT_USER_AGAIN; 293 | 294 | case DBG_DUMP: 295 | if (Dump(&dbg->dbgee) != 0) { 296 | printf(COLOR_RED 297 | "Failed to dump memory.\n" COLOR_RESET); 298 | } 299 | return PROMPT_USER_AGAIN; 300 | 301 | case DBG_PATCH: 302 | if (arg == NULL) { 303 | printf(COLOR_YELLOW 304 | "Usage: patch " 305 | "|*= (No spaces " 306 | "between opcodes)\n" COLOR_RESET); 307 | return PROMPT_USER_AGAIN; 308 | } 309 | if (Patch(&dbg->dbgee, arg) != 0) { 310 | printf(COLOR_RED "Failed to patch " 311 | "'%s'.\n" COLOR_RESET, 312 | arg); 313 | } 314 | return PROMPT_USER_AGAIN; 315 | 316 | case DBG_DIS:; 317 | if (Disassemble(&dbg->dbgee) != 0) { 318 | printf(COLOR_RED 319 | "Failed to disassemble memory.\n" COLOR_RESET); 320 | } 321 | return PROMPT_USER_AGAIN; 322 | 323 | case DBG_GLOB_VARS: 324 | if (DisplayGlobalVariables(&dbg->dbgee) != 0) { 325 | printf(COLOR_RED "Failed to display global " 326 | "variables.\n" COLOR_RESET); 327 | } 328 | return PROMPT_USER_AGAIN; 329 | 330 | case DBG_FUNC_NAMES: 331 | if (DisplayFunctionNames(&dbg->dbgee) != 0) { 332 | printf( 333 | COLOR_RED 334 | "Failed to display function names.\n" COLOR_RESET); 335 | } 336 | return PROMPT_USER_AGAIN; 337 | 338 | case DBG_BACKTRACE: 339 | if (Backtrace(&dbg->dbgee) != 0) { 340 | printf(COLOR_RED 341 | "Failed to display backtrace.\n" COLOR_RESET); 342 | } 343 | return PROMPT_USER_AGAIN; 344 | 345 | case DBG_ADDR: 346 | if (arg == NULL) { 347 | printf(COLOR_YELLOW 348 | "Usage: addr \n" COLOR_RESET); 349 | return PROMPT_USER_AGAIN; 350 | } 351 | if (Address(&dbg->dbgee, arg) != 0) { 352 | printf(COLOR_RED "Failed to get address for func: " 353 | "<%s>.\n" COLOR_RESET, 354 | arg); 355 | }; 356 | return PROMPT_USER_AGAIN; 357 | 358 | case DBG_LIST_PRELOAD: 359 | print_libraries(dbg->preload_list); 360 | return PROMPT_USER_AGAIN; 361 | 362 | case UNKNOWN: 363 | printf(COLOR_YELLOW "Unknown command.\n" COLOR_RESET); 364 | return PROMPT_USER_AGAIN; 365 | 366 | default: 367 | printf(COLOR_RED "Unhandled command type.\n" COLOR_RESET); 368 | return PROMPT_USER_AGAIN; 369 | } 370 | } 371 | 372 | int read_and_handle_user_command(debugger *dbg) { 373 | char *input = NULL; 374 | char *last_command = NULL; 375 | 376 | linenoiseHistorySetMaxLen(LINENOISE_MAX_HISTORY_LENGTH); 377 | linenoiseSetCompletionCallback(_completion); 378 | 379 | while (true) { 380 | printf(COLOR_RESET "\n"); 381 | (void)(fflush(stdout)); 382 | 383 | input = linenoise("<- Z -> "); 384 | 385 | if (input == NULL) { 386 | if (errno == EAGAIN) { 387 | handle_user_input(dbg, CLI_EXIT, ""); 388 | continue; 389 | } 390 | free_debugger(dbg); 391 | exit(EXIT_FAILURE); 392 | } 393 | 394 | if (strcmp(input, "!!") == 0) { 395 | if (last_command) { 396 | printf( 397 | COLOR_GREEN 398 | "Repeating last command: %s\n" COLOR_RESET, 399 | last_command); 400 | free(input); 401 | input = strdup(last_command); 402 | } else { 403 | printf(COLOR_YELLOW "No previous command to " 404 | "repeat.\n" COLOR_RESET); 405 | free(input); 406 | continue; 407 | } 408 | } else { 409 | linenoiseHistoryAdd(input); 410 | free((void *)last_command); 411 | last_command = strdup(input); 412 | } 413 | 414 | input[strcspn(input, "\n")] = '\0'; 415 | 416 | char *command = strtok(input, " "); 417 | char *arg = strtok(NULL, " "); 418 | 419 | command_t cmd_type = UNKNOWN; 420 | if (command != NULL) { 421 | cmd_type = _get_command_type(command); 422 | } 423 | 424 | if (handle_user_input(dbg, cmd_type, arg) == EXIT_SUCCESS) { 425 | free(input); 426 | break; 427 | } 428 | 429 | free(input); 430 | } 431 | 432 | free((void *)last_command); 433 | return EXIT_SUCCESS; 434 | } 435 | -------------------------------------------------------------------------------- /src/ld_preload.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ld_preload.h" 9 | #include "ui.h" 10 | 11 | static void _alloc_new_capacity(ld_preload_list *list) { 12 | size_t new_capacity = (list->capacity == 0) ? 4 : list->capacity * 2; 13 | 14 | char **new_libs = 15 | (char **)realloc((void *)list->libs, new_capacity * sizeof(char *)); 16 | if (!new_libs) { 17 | (void)(fprintf( 18 | stderr, 19 | COLOR_RED 20 | "Failed to expand ld_preload list: %s\n" COLOR_RESET, 21 | strerror(errno))); 22 | exit(EXIT_FAILURE); 23 | } 24 | 25 | list->libs = new_libs; 26 | list->capacity = new_capacity; 27 | } 28 | 29 | static char *_ld_preload_list_get_env(const ld_preload_list *list, 30 | const char *dir) { 31 | if (!list || list->count == 0) { 32 | return NULL; 33 | } 34 | 35 | size_t total_length = 0; 36 | for (size_t i = 0; i < list->count; i++) { 37 | if (dir) { 38 | total_length += strlen(dir) + 1; 39 | } 40 | total_length += strlen(list->libs[i]); 41 | if (i < list->count - 1) { 42 | total_length += 1; 43 | } 44 | } 45 | total_length++; 46 | 47 | char *env_str = malloc(total_length); 48 | if (!env_str) { 49 | (void)(fprintf(stderr, 50 | COLOR_RED "Failed to allocate LD_PRELOAD env " 51 | "string: %s\n" COLOR_RESET, 52 | strerror(errno))); 53 | return NULL; 54 | } 55 | 56 | env_str[0] = '\0'; 57 | 58 | for (size_t i = 0; i < list->count; i++) { 59 | if (i > 0) { 60 | strncat(env_str, ":", 61 | total_length - strlen(env_str) - 1); 62 | } 63 | if (dir) { 64 | strncat(env_str, dir, 65 | total_length - strlen(env_str) - 1); 66 | strncat(env_str, "/", 67 | total_length - strlen(env_str) - 1); 68 | } 69 | strncat(env_str, list->libs[i], 70 | total_length - strlen(env_str) - 1); 71 | } 72 | 73 | return env_str; 74 | } 75 | 76 | ld_preload_list *init_ld_preload_list(void) { 77 | ld_preload_list *list = malloc(sizeof(ld_preload_list)); 78 | if (!list) { 79 | (void)(fprintf( 80 | stderr, 81 | COLOR_RED 82 | "Failed to allocate ld_preload_list: %s\n" COLOR_RESET, 83 | strerror(errno))); 84 | return NULL; 85 | } 86 | 87 | list->libs = NULL; 88 | list->count = 0; 89 | list->capacity = 0; 90 | return list; 91 | } 92 | 93 | void free_ld_preload_list(ld_preload_list *list) { 94 | if (!list) { 95 | return; 96 | } 97 | 98 | for (size_t i = 0; i < list->count; i++) { 99 | free(list->libs[i]); 100 | } 101 | 102 | free((void *)list->libs); 103 | free(list); 104 | } 105 | 106 | int add_library(ld_preload_list *list, const char *lib) { 107 | if (list->count == list->capacity) { 108 | _alloc_new_capacity(list); 109 | } 110 | 111 | for (size_t i = 0; i < list->count; i++) { 112 | if (strcmp(list->libs[i], lib) == 0) { 113 | return EXIT_FAILURE; 114 | } 115 | } 116 | 117 | const char *lib_to_add = lib; 118 | char resolved_path[PATH_MAX]; 119 | 120 | if (access(lib, F_OK | R_OK) != 0) { 121 | (void)(fprintf(stderr, 122 | COLOR_RED 123 | "Cannot find preload library: %s\n" COLOR_RESET, 124 | lib)); 125 | return EXIT_FAILURE; 126 | } 127 | 128 | if (strchr(lib, '/') == NULL) { 129 | (void)(snprintf(resolved_path, sizeof(resolved_path), "./%s", 130 | lib)); 131 | if (access(resolved_path, F_OK | R_OK) != 0) { 132 | (void)(fprintf( 133 | stderr, 134 | COLOR_RED 135 | "Cannot find preload library: %s\n" COLOR_RESET, 136 | lib)); 137 | return EXIT_FAILURE; 138 | } 139 | lib_to_add = resolved_path; 140 | } 141 | 142 | list->libs[list->count] = strdup(lib_to_add); 143 | if (!list->libs[list->count]) { 144 | (void)(fprintf( 145 | stderr, 146 | COLOR_RED 147 | "Failed to duplicate library string: %s\n" COLOR_RESET, 148 | strerror(errno))); 149 | return EXIT_FAILURE; 150 | } 151 | 152 | list->count++; 153 | return EXIT_SUCCESS; 154 | } 155 | 156 | int ld_preload_list_set_env(const ld_preload_list *list, const char *dir) { 157 | char *env_value = _ld_preload_list_get_env(list, dir); 158 | if (!env_value) { 159 | return EXIT_FAILURE; 160 | } 161 | 162 | int result = setenv("LD_PRELOAD", env_value, 1); 163 | if (result == -1) { 164 | (void)(fprintf(stderr, 165 | COLOR_RED 166 | "Failed to set LD_PRELOAD: %s\n" COLOR_RESET, 167 | strerror(errno))); 168 | } 169 | 170 | free(env_value); 171 | return result; 172 | } 173 | 174 | void print_libraries(const ld_preload_list *list) { 175 | if (!list) { 176 | return; 177 | } 178 | 179 | printf(COLOR_CYAN "LD_PRELOAD libraries (%zu):\n", list->count); 180 | for (size_t i = 0; i < list->count; i++) { 181 | printf(COLOR_GREEN " %s\n" COLOR_RESET, list->libs[i]); 182 | } 183 | printf(COLOR_RESET); 184 | } 185 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "debugger.h" 8 | #include "ui.h" 9 | 10 | static bool _is_relative_path(const char *filename) { 11 | return filename[0] == '.'; 12 | } 13 | 14 | static bool _file_exists(const char *filename) { 15 | return access(filename, F_OK | R_OK) == 0; 16 | } 17 | 18 | int main(int argc, char **argv) { 19 | if (argc < 2) { 20 | (void)(fprintf(stderr, 21 | COLOR_RED 22 | "Usage: %s [ld_preload_library1 " 23 | "[ld_preload_library2 ...]]\n" COLOR_RESET, 24 | argv[0])); 25 | return EXIT_FAILURE; 26 | } 27 | 28 | const char *debuggee_name = argv[1]; 29 | 30 | if (_is_relative_path(debuggee_name)) { 31 | (void)(fprintf(stderr, 32 | COLOR_RED 33 | "No relative paths allowed for debug target: " 34 | "%s\n" COLOR_RESET, 35 | debuggee_name)); 36 | return EXIT_FAILURE; 37 | } 38 | 39 | if (!_file_exists(debuggee_name)) { 40 | (void)(fprintf(stderr, 41 | COLOR_RED 42 | "Cannot find executable: %s\n" COLOR_RESET, 43 | debuggee_name)); 44 | return EXIT_FAILURE; 45 | } 46 | 47 | print_banner_hello(); 48 | 49 | debugger dbg; 50 | init_debugger(&dbg, debuggee_name, argc, argv); 51 | 52 | if (start_debuggee(&dbg) != 0) { 53 | (void)(fprintf(stderr, COLOR_RED 54 | "Failed to start debuggee.\n" COLOR_RESET)); 55 | free_debugger(&dbg); 56 | return EXIT_FAILURE; 57 | } 58 | 59 | if (trace_debuggee(&dbg) != 0) { 60 | (void)(fprintf(stderr, COLOR_RED 61 | "Error while tracing debuggee.\n" COLOR_RESET)); 62 | free_debugger(&dbg); 63 | return EXIT_FAILURE; 64 | } 65 | 66 | print_banner_goodbye(); 67 | 68 | free_debugger(&dbg); 69 | return EXIT_SUCCESS; 70 | } 71 | -------------------------------------------------------------------------------- /src/symtab.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "symtab.h" 9 | #include "ui.h" 10 | 11 | bool read_elf_symtab(const char *elf_path, // NOLINT 12 | elf_symtab *symtab_struct) { 13 | if (elf_path == NULL || symtab_struct == NULL) { 14 | (void)(fprintf( 15 | stderr, COLOR_RED 16 | "Invalid arguments to read_elf_symtab.\n" COLOR_RESET)); 17 | return false; 18 | } 19 | 20 | int fd = open(elf_path, O_RDONLY); 21 | if (fd < 0) { 22 | perror("open ELF file"); 23 | return false; 24 | } 25 | 26 | Elf64_Ehdr ehdr; 27 | if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) { 28 | perror("read ELF header"); 29 | close(fd); 30 | return false; 31 | } 32 | 33 | if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) { 34 | (void)(fprintf( 35 | stderr, COLOR_RED "Not a valid ELF file: %s\n" COLOR_RESET, 36 | elf_path)); 37 | close(fd); 38 | return false; 39 | } 40 | 41 | if (lseek(fd, (off_t)ehdr.e_shoff, SEEK_SET) == -1) { 42 | perror("lseek to section headers"); 43 | close(fd); 44 | return false; 45 | } 46 | 47 | Elf64_Shdr *shdrs = malloc((size_t)(ehdr.e_shentsize * ehdr.e_shnum)); 48 | if (!shdrs) { 49 | perror("malloc for section headers"); 50 | close(fd); 51 | return false; 52 | } 53 | 54 | if (read(fd, shdrs, (size_t)(ehdr.e_shentsize * ehdr.e_shnum)) != 55 | (ssize_t)(ehdr.e_shentsize * ehdr.e_shnum)) { 56 | perror("read section headers"); 57 | free(shdrs); 58 | close(fd); 59 | return false; 60 | } 61 | 62 | Elf64_Shdr sh_strtab = shdrs[ehdr.e_shstrndx]; 63 | char *shstrtab = malloc(sh_strtab.sh_size); 64 | if (!shstrtab) { 65 | perror("malloc for shstrtab"); 66 | free(shdrs); 67 | close(fd); 68 | return false; 69 | } 70 | 71 | if (lseek(fd, (off_t)sh_strtab.sh_offset, SEEK_SET) == -1) { 72 | perror("lseek to shstrtab"); 73 | free(shstrtab); 74 | free(shdrs); 75 | close(fd); 76 | return false; 77 | } 78 | 79 | if (read(fd, shstrtab, sh_strtab.sh_size) != 80 | (ssize_t)sh_strtab.sh_size) { 81 | perror("read shstrtab"); 82 | free(shstrtab); 83 | free(shdrs); 84 | close(fd); 85 | return false; 86 | } 87 | 88 | symtab_struct->entries = NULL; 89 | symtab_struct->num_entries = 0; 90 | 91 | for (int i = 0; i < ehdr.e_shnum; i++) { 92 | const char *section_name = shstrtab + shdrs[i].sh_name; 93 | 94 | if (strcmp(section_name, ".symtab") != 0 && 95 | strcmp(section_name, ".dynsym") != 0) { 96 | continue; 97 | } 98 | 99 | Elf64_Sym *current_symtab = malloc(shdrs[i].sh_size); 100 | if (!current_symtab) { 101 | perror("malloc for symtab"); 102 | continue; 103 | } 104 | 105 | if (lseek(fd, (off_t)shdrs[i].sh_offset, SEEK_SET) == -1) { 106 | perror("lseek to symtab"); 107 | free(current_symtab); 108 | continue; 109 | } 110 | 111 | if (read(fd, current_symtab, shdrs[i].sh_size) != 112 | (ssize_t)shdrs[i].sh_size) { 113 | perror("read symtab"); 114 | free(current_symtab); 115 | continue; 116 | } 117 | 118 | size_t current_num_symbols = 119 | shdrs[i].sh_size / sizeof(Elf64_Sym); 120 | 121 | Elf64_Shdr strtab_shdr = shdrs[shdrs[i].sh_link]; 122 | char *current_strtab = malloc(strtab_shdr.sh_size); 123 | if (!current_strtab) { 124 | perror("malloc for strtab"); 125 | free(current_symtab); 126 | continue; 127 | } 128 | 129 | if (lseek(fd, (off_t)strtab_shdr.sh_offset, SEEK_SET) == -1) { 130 | perror("lseek to strtab"); 131 | free(current_strtab); 132 | free(current_symtab); 133 | continue; 134 | } 135 | 136 | if (read(fd, current_strtab, strtab_shdr.sh_size) != 137 | (ssize_t)strtab_shdr.sh_size) { 138 | perror("read strtab"); 139 | free(current_strtab); 140 | free(current_symtab); 141 | continue; 142 | } 143 | 144 | elf_symtab_entry *new_entries = realloc( 145 | symtab_struct->entries, (symtab_struct->num_entries + 1) * 146 | sizeof(elf_symtab_entry)); 147 | if (!new_entries) { 148 | perror("realloc for symtab entries"); 149 | free(current_strtab); 150 | free(current_symtab); 151 | continue; 152 | } 153 | 154 | symtab_struct->entries = new_entries; 155 | symtab_struct->entries[symtab_struct->num_entries].symtab = 156 | current_symtab; 157 | symtab_struct->entries[symtab_struct->num_entries].num_symbols = 158 | current_num_symbols; 159 | symtab_struct->entries[symtab_struct->num_entries].strtab = 160 | current_strtab; 161 | symtab_struct->num_entries += 1; 162 | } 163 | 164 | free(shstrtab); 165 | free(shdrs); 166 | close(fd); 167 | 168 | if (symtab_struct->entries == NULL || symtab_struct->num_entries == 0) { 169 | (void)(fprintf( 170 | stderr, 171 | COLOR_RED 172 | "No symbol tables found in ELF file: %s\n" COLOR_RESET, 173 | elf_path)); 174 | return false; 175 | } 176 | 177 | return true; 178 | } 179 | -------------------------------------------------------------------------------- /src/ui.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | 6 | #include "ui.h" 7 | 8 | static size_t strip_ansi(const char *src, size_t src_size, char *dst, 9 | size_t dst_size) { 10 | size_t i = 0; 11 | size_t j = 0; 12 | enum { NORMAL, ESCAPE, CSI } state = NORMAL; 13 | 14 | while (i < src_size && j < dst_size - 1) { 15 | char c = src[i++]; 16 | switch (state) { 17 | case NORMAL: 18 | if (c == '\033') { 19 | state = ESCAPE; 20 | } else { 21 | dst[j++] = c; 22 | } 23 | break; 24 | case ESCAPE: 25 | if (c == '[') { 26 | state = CSI; 27 | } else { 28 | if (j < dst_size - 2) { 29 | dst[j++] = '\033'; 30 | dst[j++] = c; 31 | } 32 | state = NORMAL; 33 | } 34 | break; 35 | case CSI: 36 | if (c >= '@' && c <= '~') { 37 | state = NORMAL; 38 | } 39 | break; 40 | } 41 | } 42 | dst[j] = '\0'; 43 | return j; 44 | } 45 | 46 | static ssize_t tee_write(void *cookie, const char *buf, size_t size) { 47 | tee_cookie *tc = (tee_cookie *)cookie; 48 | 49 | size_t written_console = fwrite(buf, 1, size, tc->orig); 50 | if (written_console != size) { 51 | return -1; 52 | } 53 | 54 | char *filtered = malloc(size + 1); 55 | if (!filtered) { 56 | return -1; 57 | } 58 | 59 | strip_ansi(buf, size, filtered, size + 1); 60 | 61 | size_t filtered_len = strlen(filtered); 62 | size_t written_file = fwrite(filtered, 1, filtered_len, tc->logfile); 63 | free(filtered); 64 | if (written_file != filtered_len) { 65 | return -1; 66 | } 67 | 68 | if (fflush(tc->orig) != 0) { 69 | return -1; 70 | } 71 | if (fflush(tc->logfile) != 0) { 72 | return -1; 73 | } 74 | 75 | return (ssize_t)size; 76 | } 77 | 78 | static int tee_close(void *cookie) { 79 | tee_cookie *tc = (tee_cookie *)cookie; 80 | int result = 0; 81 | if (tc->logfile) { 82 | result = fclose(tc->logfile); 83 | if (result != 0) { 84 | perror("fclose(logfile)"); 85 | } 86 | } 87 | free(tc); 88 | return result; 89 | } 90 | 91 | void print_separator(void) { 92 | printf(COLOR_MAGENTA); 93 | for (int i = 0; i < LINE_LENGTH; i++) { 94 | putchar('-'); 95 | } 96 | printf("\n" COLOR_RESET); 97 | } 98 | 99 | void print_separator_large(void) { 100 | printf(COLOR_MAGENTA); 101 | for (int i = 0; i < LINE_LENGTH; i++) { 102 | putchar('='); 103 | } 104 | printf("\n" COLOR_RESET); 105 | } 106 | 107 | void print_banner_hello(void) { 108 | printf("\n ╔════════════════════════════════════════════════╗\n"); 109 | printf(" ║ ( ║\n"); 110 | printf(" ║ _)_ ║\n"); 111 | printf(" ║ (o o) ║\n"); 112 | printf(" ║ ooO--(_)--Ooo- ║\n"); 113 | printf(" ║ ║\n"); 114 | printf(" ║ Anti-Anti Debugger Z ║\n"); 115 | printf(" ║ v0.1 ║\n"); 116 | printf(" ╚════════════════════════════════════════════════╝\n"); 117 | } 118 | 119 | void print_banner_goodbye(void) { 120 | printf("\n ╔════════════════════════════════════════════════╗\n"); 121 | printf(" ║ Shutting down....... ║\n"); 122 | printf(" ╚════════════════════════════════════════════════╝\n\n"); 123 | } 124 | 125 | FILE *create_tee_stream(const char *log_filename) { 126 | FILE *log_file = fopen(log_filename, "w"); 127 | if (log_file == NULL) { 128 | perror("fopen(log_filename)"); 129 | return NULL; 130 | } 131 | 132 | tee_cookie *cookie = malloc(sizeof(tee_cookie)); 133 | if (cookie == NULL) { 134 | perror("malloc(tee_cookie)"); 135 | if (fclose(log_file) != 0) { 136 | perror("fclose(log_file)"); 137 | } 138 | return NULL; 139 | } 140 | cookie->orig = stdout; 141 | cookie->logfile = log_file; 142 | 143 | cookie_io_functions_t tee_funcs = {// NOLINT 144 | .read = NULL, 145 | .write = tee_write, 146 | .seek = NULL, 147 | .close = tee_close}; 148 | 149 | FILE *new_stdout = fopencookie(cookie, "w", tee_funcs); 150 | if (new_stdout == NULL) { 151 | perror("fopencookie"); 152 | free(cookie); 153 | if (fclose(log_file) != 0) { 154 | perror("fclose(log_file)"); 155 | } 156 | return NULL; 157 | } 158 | 159 | if (setvbuf(new_stdout, NULL, _IOLBF, 0) != 0) { 160 | perror("setvbuf"); 161 | if (fclose(new_stdout) != 0) { 162 | perror("fclose(new_stdout)"); 163 | } 164 | return NULL; 165 | } 166 | 167 | return new_stdout; 168 | } 169 | -------------------------------------------------------------------------------- /tests/test_debugger.c: -------------------------------------------------------------------------------- 1 | // NOLINTBEGIN(misc-include-cleaner) 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "debuggee.h" 9 | #include "debugger.h" 10 | 11 | #ifndef MOCK_DEBUGGEE_PATH 12 | #define MOCK_DEBUGGEE_PATH "../bin/mock_target" 13 | #endif 14 | 15 | void redirect_all_stdout(void) { 16 | cr_redirect_stdout(); 17 | cr_redirect_stderr(); 18 | } 19 | 20 | Test(debugger, init_debugger_success) { 21 | debugger dbg; 22 | init_debugger(&dbg, MOCK_DEBUGGEE_PATH, 1, NULL); 23 | 24 | cr_assert_eq(dbg.dbgee.pid, -1); 25 | cr_assert_eq(dbg.dbgee.name, "../bin/mock_target"); 26 | cr_assert_eq(dbg.dbgee.state, IDLE); 27 | cr_assert_eq(dbg.state, DETACHED); 28 | } 29 | 30 | Test(debugger, start_debuggee_success) { 31 | debugger dbg; 32 | init_debugger(&dbg, MOCK_DEBUGGEE_PATH, 0, NULL); 33 | 34 | int result = start_debuggee(&dbg); 35 | cr_assert_eq(result, 0, "start_debuggee failed with return value %d", 36 | result); 37 | 38 | cr_assert_neq(dbg.dbgee.pid, -1, "Debuggee PID was not set."); 39 | cr_assert_eq(dbg.dbgee.state, RUNNING, 40 | "Debuggee state flag not set to RUNNING."); 41 | 42 | free_debugger(&dbg); 43 | } 44 | 45 | Test(debugger, free_debugger_kill_running_debuggee, 46 | .init = redirect_all_stdout) { 47 | debugger dbg; 48 | init_debugger(&dbg, MOCK_DEBUGGEE_PATH, 0, NULL); 49 | 50 | int start_result = start_debuggee(&dbg); 51 | cr_assert_eq(start_result, 0, 52 | "start_debuggee failed with return value %d", 53 | start_result); 54 | 55 | free_debugger(&dbg); 56 | 57 | cr_assert_eq(dbg.dbgee.pid, -1, 58 | "Debuggee PID should be reset after free_debugger."); 59 | cr_assert_eq( 60 | dbg.dbgee.state, TERMINATED, 61 | "Debuggee state flag should be TERMINATED after free_debugger."); 62 | cr_assert_eq( 63 | dbg.state, DETACHED, 64 | "Debugger state flag should be DETACHED after free_debugger."); 65 | } 66 | 67 | // NOLINTEND(misc-include-cleaner) 68 | --------------------------------------------------------------------------------