├── .editorconfig ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── cmake.toml ├── cmkr.cmake ├── loader └── main.cpp ├── ntdll ├── ntdll.h ├── ntdll_x64.lib └── ntdll_x86.lib └── tracer ├── config.cpp ├── config.hpp ├── logger.cpp ├── logger.hpp ├── main.cpp ├── tracer.cpp └── tracer.hpp /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | # C++ 3 | [*.{h,hpp,c,cpp,asm,md,txt}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "printf"] 2 | path = printf 3 | url = https://github.com/mpaland/printf.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is automatically generated from cmake.toml - DO NOT EDIT 2 | # See https://github.com/build-cpp/cmkr for more information 3 | 4 | cmake_minimum_required(VERSION 3.15) 5 | 6 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 7 | message(FATAL_ERROR "In-tree builds are not supported. Run CMake from a separate directory: cmake -B build") 8 | endif() 9 | 10 | set(CMKR_ROOT_PROJECT OFF) 11 | if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 12 | set(CMKR_ROOT_PROJECT ON) 13 | 14 | # Bootstrap cmkr and automatically regenerate CMakeLists.txt 15 | include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT) 16 | if(CMKR_INCLUDE_RESULT) 17 | cmkr() 18 | endif() 19 | 20 | # Enable folder support 21 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 22 | 23 | # Create a configure-time dependency on cmake.toml to improve IDE support 24 | configure_file(cmake.toml cmake.toml COPYONLY) 25 | endif() 26 | 27 | set(ASMJIT_STATIC TRUE) 28 | 29 | cmake_policy(SET CMP0091 NEW) 30 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" CACHE STRING "") 31 | 32 | project(apitracer 33 | LANGUAGES 34 | CXX 35 | ASM_MASM 36 | ) 37 | 38 | include(FetchContent) 39 | 40 | message(STATUS "Fetching minhook...") 41 | FetchContent_Declare(minhook 42 | GIT_REPOSITORY 43 | "https://github.com/TsudaKageyu/minhook.git" 44 | ) 45 | FetchContent_MakeAvailable(minhook) 46 | 47 | message(STATUS "Fetching asmjit...") 48 | FetchContent_Declare(asmjit 49 | GIT_REPOSITORY 50 | "https://github.com/asmjit/asmjit.git" 51 | ) 52 | FetchContent_MakeAvailable(asmjit) 53 | 54 | message(STATUS "Fetching argparse...") 55 | FetchContent_Declare(argparse 56 | GIT_REPOSITORY 57 | "https://github.com/p-ranav/argparse.git" 58 | ) 59 | FetchContent_MakeAvailable(argparse) 60 | 61 | # Target: ntdll 62 | add_library(ntdll INTERFACE) 63 | 64 | target_include_directories(ntdll INTERFACE 65 | "ntdll/" 66 | ) 67 | 68 | target_link_directories(ntdll INTERFACE 69 | "ntdll/" 70 | ) 71 | 72 | if(CMAKE_SIZEOF_VOID_P EQUAL 4) # x32 73 | target_link_libraries(ntdll INTERFACE 74 | ntdll_x86 75 | ) 76 | endif() 77 | 78 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) # x64 79 | target_link_libraries(ntdll INTERFACE 80 | ntdll_x64 81 | ) 82 | endif() 83 | 84 | # Target: tracer 85 | set(tracer_SOURCES 86 | "tracer/config.cpp" 87 | "tracer/logger.cpp" 88 | "tracer/main.cpp" 89 | "tracer/tracer.cpp" 90 | "tracer/config.hpp" 91 | "tracer/logger.hpp" 92 | "tracer/tracer.hpp" 93 | "printf/printf.c" 94 | cmake.toml 95 | ) 96 | 97 | add_library(tracer SHARED) 98 | 99 | target_sources(tracer PRIVATE ${tracer_SOURCES}) 100 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${tracer_SOURCES}) 101 | 102 | target_compile_features(tracer PUBLIC 103 | cxx_std_23 104 | ) 105 | 106 | target_include_directories(tracer PUBLIC 107 | "${CMAKE_CURRENT_SOURCE_DIR}/ntdll/" 108 | "tracer/" 109 | "printf/" 110 | ) 111 | 112 | target_link_libraries(tracer PUBLIC 113 | ntdll 114 | minhook 115 | asmjit 116 | ) 117 | 118 | target_link_options(tracer PUBLIC 119 | "/INCREMENTAL:NO" 120 | ) 121 | 122 | # Target: loader 123 | set(loader_SOURCES 124 | "loader/main.cpp" 125 | cmake.toml 126 | ) 127 | 128 | add_executable(loader) 129 | 130 | target_sources(loader PRIVATE ${loader_SOURCES}) 131 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${loader_SOURCES}) 132 | 133 | target_compile_features(loader PRIVATE 134 | cxx_std_23 135 | ) 136 | 137 | target_include_directories(loader PRIVATE 138 | "${CMAKE_CURRENT_SOURCE_DIR}/ntdll/" 139 | "loader/" 140 | ) 141 | 142 | target_link_libraries(loader PRIVATE 143 | ntdll 144 | argparse 145 | ) 146 | 147 | target_link_options(loader PRIVATE 148 | "/INCREMENTAL:NO" 149 | ) 150 | 151 | get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT) 152 | if(NOT CMKR_VS_STARTUP_PROJECT) 153 | set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT loader) 154 | endif() 155 | 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # api-tracer - tiny (useless) tracer 2 | 3 | api-tracer is a dynamic library that, when loaded into a process, intercepts and logs each exported function from a list of intercepted libraries. It was a fun project to test some functionality when use of DBI/Debugger is not possible. 4 | 5 | ## Usage 6 | 7 | The project comes with the simple loader that creates suspended process and injects `trace.dll`: 8 | ``` 9 | > loader.exe --help 10 | Usage: api-tracer: trace.dll Loader [--help] [--version] --exe VAR [--tracer VAR] args 11 | 12 | Positional arguments: 13 | args Arguments to pass to target executable [nargs: 0 or more] [default: {}] 14 | 15 | Optional arguments: 16 | -h, --help shows help message and exits 17 | -v, --version prints version information and exits 18 | -e, --exe Target executable to trace [default: ""] 19 | -t, --tracer Path to tracer.dll [default: ""] 20 | ``` 21 | 22 | In order to use api-tracer, one needs to place `hooks.txt` file that contains list of dlls seperated by new line in the current directory. To filter api calls from specific modules, place `filters.txt` in the current directory with the list of modules. The output will be logged in `trace.txt` file. 23 | 24 | ## Example setup. 25 | 26 | `filters.txt`: 27 | ``` 28 | my-awesome-program.exe 29 | ``` 30 | 31 | `hooks.txt`: 32 | ``` 33 | kernel32.dll 34 | user32.dll 35 | ``` 36 | -------------------------------------------------------------------------------- /cmake.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "apitracer" 3 | languages = ["CXX", "ASM_MASM"] 4 | 5 | cmake-before = """ 6 | set(ASMJIT_STATIC TRUE) 7 | 8 | cmake_policy(SET CMP0091 NEW) 9 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" CACHE STRING "") 10 | """ 11 | 12 | [target.ntdll] 13 | type = "interface" 14 | include-directories = ["ntdll/"] 15 | link-directories = ["ntdll/"] 16 | x32.link-libraries = ["ntdll_x86"] 17 | x64.link-libraries = ["ntdll_x64"] 18 | 19 | [fetch-content.minhook] 20 | git = "https://github.com/TsudaKageyu/minhook.git" 21 | 22 | [fetch-content.asmjit] 23 | git = "https://github.com/asmjit/asmjit.git" 24 | 25 | [fetch-content.argparse] 26 | git = "https://github.com/p-ranav/argparse.git" 27 | 28 | [target.tracer] 29 | type = "shared" 30 | sources = ["tracer/*.cpp", "tracer/*.hpp", "tracer/*.asm", "printf/*.c"] 31 | compile-features = ["cxx_std_23"] 32 | link-libraries = ["ntdll", "minhook", "asmjit"] 33 | link-options = ["/INCREMENTAL:NO"] 34 | include-directories = ["${CMAKE_CURRENT_SOURCE_DIR}/ntdll/", "tracer/", "printf/"] 35 | 36 | [target.loader] 37 | type = "executable" 38 | sources = ["loader/*.cpp", "loader/*.hpp"] 39 | compile-features = ["cxx_std_23"] 40 | link-libraries = ["ntdll", "argparse"] 41 | link-options = ["/INCREMENTAL:NO"] 42 | include-directories = ["${CMAKE_CURRENT_SOURCE_DIR}/ntdll/", "loader/"] 43 | -------------------------------------------------------------------------------- /cmkr.cmake: -------------------------------------------------------------------------------- 1 | include_guard() 2 | 3 | # Change these defaults to point to your infrastructure if desired 4 | set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE) 5 | set(CMKR_TAG "v0.2.18" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) 6 | set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE) 7 | 8 | # To bootstrap/generate a cmkr project: cmake -P cmkr.cmake 9 | if(CMAKE_SCRIPT_MODE_FILE) 10 | set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/build") 11 | set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}") 12 | file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}") 13 | endif() 14 | 15 | # Set these from the command line to customize for development/debugging purposes 16 | set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable") 17 | set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation") 18 | set(CMKR_BUILD_TYPE "Debug" CACHE STRING "cmkr build configuration") 19 | mark_as_advanced(CMKR_REPO CMKR_TAG CMKR_COMMIT_HASH CMKR_EXECUTABLE CMKR_SKIP_GENERATION CMKR_BUILD_TYPE) 20 | 21 | # Disable cmkr if generation is disabled 22 | if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION) 23 | message(STATUS "[cmkr] Skipping automatic cmkr generation") 24 | unset(CMKR_BUILD_SKIP_GENERATION CACHE) 25 | macro(cmkr) 26 | endmacro() 27 | return() 28 | endif() 29 | 30 | # Disable cmkr if no cmake.toml file is found 31 | if(NOT CMAKE_SCRIPT_MODE_FILE AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 32 | message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") 33 | macro(cmkr) 34 | endmacro() 35 | return() 36 | endif() 37 | 38 | # Convert a Windows native path to CMake path 39 | if(CMKR_EXECUTABLE MATCHES "\\\\") 40 | string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}") 41 | set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE) 42 | unset(CMKR_EXECUTABLE_CMAKE) 43 | endif() 44 | 45 | # Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher) 46 | function(cmkr_exec) 47 | execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT) 48 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 49 | message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})") 50 | endif() 51 | endfunction() 52 | 53 | # Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment) 54 | if(WIN32) 55 | set(CMKR_EXECUTABLE_NAME "cmkr.exe") 56 | else() 57 | set(CMKR_EXECUTABLE_NAME "cmkr") 58 | endif() 59 | 60 | # Use cached cmkr if found 61 | if(DEFINED ENV{CMKR_CACHE}) 62 | set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}") 63 | string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}") 64 | if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$") 65 | set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/") 66 | endif() 67 | # Build in release mode for the cache 68 | set(CMKR_BUILD_TYPE "Release") 69 | else() 70 | set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_") 71 | endif() 72 | set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}") 73 | set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}") 74 | 75 | # Helper function to check if a string starts with a prefix 76 | # Cannot use MATCHES, see: https://github.com/build-cpp/cmkr/issues/61 77 | function(cmkr_startswith str prefix result) 78 | string(LENGTH "${prefix}" prefix_length) 79 | string(LENGTH "${str}" str_length) 80 | if(prefix_length LESS_EQUAL str_length) 81 | string(SUBSTRING "${str}" 0 ${prefix_length} str_prefix) 82 | if(prefix STREQUAL str_prefix) 83 | set("${result}" ON PARENT_SCOPE) 84 | return() 85 | endif() 86 | endif() 87 | set("${result}" OFF PARENT_SCOPE) 88 | endfunction() 89 | 90 | # Handle upgrading logic 91 | if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE) 92 | cmkr_startswith("${CMKR_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/_cmkr" CMKR_STARTSWITH_BUILD) 93 | cmkr_startswith("${CMKR_EXECUTABLE}" "${CMKR_DIRECTORY_PREFIX}" CMKR_STARTSWITH_CACHE) 94 | if(CMKR_STARTSWITH_BUILD) 95 | if(DEFINED ENV{CMKR_CACHE}) 96 | message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'") 97 | if(EXISTS "${CMKR_CACHED_EXECUTABLE}") 98 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 99 | else() 100 | unset(CMKR_EXECUTABLE CACHE) 101 | endif() 102 | else() 103 | message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") 104 | unset(CMKR_EXECUTABLE CACHE) 105 | endif() 106 | elseif(DEFINED ENV{CMKR_CACHE} AND CMKR_STARTSWITH_CACHE) 107 | message(AUTHOR_WARNING "[cmkr] Upgrading cached '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") 108 | unset(CMKR_EXECUTABLE CACHE) 109 | endif() 110 | endif() 111 | 112 | if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}") 113 | message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'") 114 | elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE) 115 | message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found") 116 | elseif(NOT CMKR_EXECUTABLE AND EXISTS "${CMKR_CACHED_EXECUTABLE}") 117 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 118 | message(STATUS "[cmkr] Found cached cmkr: '${CMKR_EXECUTABLE}'") 119 | else() 120 | set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) 121 | message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'") 122 | 123 | message(STATUS "[cmkr] Fetching cmkr...") 124 | if(EXISTS "${CMKR_DIRECTORY}") 125 | cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}") 126 | endif() 127 | find_package(Git QUIET REQUIRED) 128 | cmkr_exec("${GIT_EXECUTABLE}" 129 | clone 130 | --config advice.detachedHead=false 131 | --branch ${CMKR_TAG} 132 | --depth 1 133 | ${CMKR_REPO} 134 | "${CMKR_DIRECTORY}" 135 | ) 136 | if(CMKR_COMMIT_HASH) 137 | execute_process( 138 | COMMAND "${GIT_EXECUTABLE}" checkout -q "${CMKR_COMMIT_HASH}" 139 | RESULT_VARIABLE CMKR_EXEC_RESULT 140 | WORKING_DIRECTORY "${CMKR_DIRECTORY}" 141 | ) 142 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 143 | message(FATAL_ERROR "Tag '${CMKR_TAG}' hash is not '${CMKR_COMMIT_HASH}'") 144 | endif() 145 | endif() 146 | message(STATUS "[cmkr] Building cmkr (using system compiler)...") 147 | cmkr_exec("${CMAKE_COMMAND}" 148 | --no-warn-unused-cli 149 | "${CMKR_DIRECTORY}" 150 | "-B${CMKR_DIRECTORY}/build" 151 | "-DCMAKE_BUILD_TYPE=${CMKR_BUILD_TYPE}" 152 | "-DCMAKE_UNITY_BUILD=ON" 153 | "-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}" 154 | "-DCMKR_GENERATE_DOCUMENTATION=OFF" 155 | ) 156 | cmkr_exec("${CMAKE_COMMAND}" 157 | --build "${CMKR_DIRECTORY}/build" 158 | --config "${CMKR_BUILD_TYPE}" 159 | --parallel 160 | ) 161 | cmkr_exec("${CMAKE_COMMAND}" 162 | --install "${CMKR_DIRECTORY}/build" 163 | --config "${CMKR_BUILD_TYPE}" 164 | --prefix "${CMKR_DIRECTORY}" 165 | --component cmkr 166 | ) 167 | if(NOT EXISTS ${CMKR_EXECUTABLE}) 168 | message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'") 169 | endif() 170 | cmkr_exec("${CMKR_EXECUTABLE}" version) 171 | message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}") 172 | endif() 173 | execute_process(COMMAND "${CMKR_EXECUTABLE}" version 174 | RESULT_VARIABLE CMKR_EXEC_RESULT 175 | ) 176 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 177 | message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding") 178 | endif() 179 | 180 | # Use cmkr.cmake as a script 181 | if(CMAKE_SCRIPT_MODE_FILE) 182 | if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml") 183 | execute_process(COMMAND "${CMKR_EXECUTABLE}" init 184 | RESULT_VARIABLE CMKR_EXEC_RESULT 185 | ) 186 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 187 | message(FATAL_ERROR "[cmkr] Failed to bootstrap cmkr project. Please report an issue: https://github.com/build-cpp/cmkr/issues/new") 188 | else() 189 | message(STATUS "[cmkr] Modify cmake.toml and then configure using: cmake -B build") 190 | endif() 191 | else() 192 | execute_process(COMMAND "${CMKR_EXECUTABLE}" gen 193 | RESULT_VARIABLE CMKR_EXEC_RESULT 194 | ) 195 | if(NOT CMKR_EXEC_RESULT EQUAL 0) 196 | message(FATAL_ERROR "[cmkr] Failed to generate project.") 197 | else() 198 | message(STATUS "[cmkr] Configure using: cmake -B build") 199 | endif() 200 | endif() 201 | endif() 202 | 203 | # This is the macro that contains black magic 204 | macro(cmkr) 205 | # When this macro is called from the generated file, fake some internal CMake variables 206 | get_source_file_property(CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE) 207 | if(CMKR_CURRENT_LIST_FILE) 208 | set(CMAKE_CURRENT_LIST_FILE "${CMKR_CURRENT_LIST_FILE}") 209 | get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) 210 | endif() 211 | 212 | # File-based include guard (include_guard is not documented to work) 213 | get_source_file_property(CMKR_INCLUDE_GUARD "${CMAKE_CURRENT_LIST_FILE}" CMKR_INCLUDE_GUARD) 214 | if(NOT CMKR_INCLUDE_GUARD) 215 | set_source_files_properties("${CMAKE_CURRENT_LIST_FILE}" PROPERTIES CMKR_INCLUDE_GUARD TRUE) 216 | 217 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE) 218 | 219 | # Generate CMakeLists.txt 220 | cmkr_exec("${CMKR_EXECUTABLE}" gen 221 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 222 | ) 223 | 224 | file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST) 225 | 226 | # Delete the temporary file if it was left for some reason 227 | set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt") 228 | if(EXISTS "${CMKR_TEMP_FILE}") 229 | file(REMOVE "${CMKR_TEMP_FILE}") 230 | endif() 231 | 232 | if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST) 233 | # Copy the now-generated CMakeLists.txt to CMakerLists.txt 234 | # This is done because you cannot include() a file you are currently in 235 | configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY) 236 | 237 | # Add the macro required for the hack at the start of the cmkr macro 238 | set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES 239 | CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" 240 | ) 241 | 242 | # 'Execute' the newly-generated CMakeLists.txt 243 | include("${CMKR_TEMP_FILE}") 244 | 245 | # Delete the generated file 246 | file(REMOVE "${CMKR_TEMP_FILE}") 247 | 248 | # Do not execute the rest of the original CMakeLists.txt 249 | return() 250 | endif() 251 | # Resume executing the unmodified CMakeLists.txt 252 | endif() 253 | endmacro() 254 | -------------------------------------------------------------------------------- /loader/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void inject(const std::string& dll, const std::string& exe, const std::vector& args) 6 | { 7 | STARTUPINFO si{ .cb = sizeof(STARTUPINFO) }; 8 | PROCESS_INFORMATION pi{}; 9 | 10 | std::string cmdline; 11 | 12 | for (const auto& arg : args) 13 | { 14 | cmdline += arg + " "; 15 | } 16 | 17 | if (!CreateProcess(exe.c_str(), cmdline.empty() ? nullptr : cmdline.data(), nullptr, nullptr, false, CREATE_SUSPENDED | DETACHED_PROCESS, nullptr, nullptr, &si, &pi)) 18 | { 19 | std::printf("[-] Failed to start process. error: %d", GetLastError()); 20 | std::exit(1); 21 | } 22 | 23 | std::printf("[+] Process created. PID: %u\n", pi.dwProcessId); 24 | 25 | if (auto memory = VirtualAllocEx(pi.hProcess, nullptr, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) 26 | { 27 | std::printf("[+] remote buffer: 0x%p\n", memory); 28 | if (WriteProcessMemory(pi.hProcess, memory, dll.c_str(), dll.size(), nullptr)) 29 | { 30 | if (auto thread = CreateRemoteThread(pi.hProcess, nullptr, 0, reinterpret_cast(LoadLibraryA), memory, 0, nullptr)) 31 | { 32 | WaitForSingleObject(thread, INFINITE); 33 | CloseHandle(thread); 34 | VirtualFreeEx(pi.hProcess, memory, 0x1000, MEM_RELEASE); 35 | } 36 | else 37 | { 38 | std::printf("[-] Failed to create remote thread. error:", GetLastError()); 39 | } 40 | } 41 | else 42 | { 43 | std::printf("[-] Failed to write dll path into remote process. error:", GetLastError()); 44 | } 45 | } 46 | else 47 | { 48 | std::printf("[-] Failed to allocate memory in remote process. error:", GetLastError()); 49 | } 50 | ResumeThread(pi.hThread); 51 | CloseHandle(pi.hProcess); 52 | CloseHandle(pi.hThread); 53 | } 54 | 55 | int main(int argc, char** argv) 56 | { 57 | argparse::ArgumentParser program("api-tracer: trace.dll Loader"); 58 | 59 | program.add_argument("-e", "--exe") 60 | .help("Target executable to trace") 61 | .default_value("") 62 | .required(); 63 | 64 | program.add_argument("-t", "--tracer") 65 | .help("Path to tracer.dll") 66 | .default_value(""); 67 | 68 | program.add_argument("args") 69 | .remaining() 70 | .help("Arguments to pass to target executable") 71 | .default_value>({}); 72 | 73 | try 74 | { 75 | program.parse_args(argc, argv); 76 | } 77 | catch (const std::runtime_error& err) 78 | { 79 | std::cerr << err.what() << std::endl; 80 | std::cerr << program; 81 | std::exit(1); 82 | } 83 | 84 | const auto tracer = program.get("--tracer"); 85 | const auto exe = program.get("--exe"); 86 | const auto args = program.get>("args"); 87 | 88 | std::cout << tracer << std::endl; 89 | std::cout << exe << std::endl; 90 | for (const auto& arg : args) 91 | { 92 | std::cout << arg << std::endl; 93 | } 94 | 95 | inject(tracer.empty() ? "tracer.dll" : tracer, exe, args); 96 | 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /ntdll/ntdll_x64.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archercreat/api-tracer/3948bceecd7d0a9a52a3bfa79ca3572cab2e1fbe/ntdll/ntdll_x64.lib -------------------------------------------------------------------------------- /ntdll/ntdll_x86.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archercreat/api-tracer/3948bceecd7d0a9a52a3bfa79ca3572cab2e1fbe/ntdll/ntdll_x86.lib -------------------------------------------------------------------------------- /tracer/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | 3 | #include 4 | 5 | std::vector read_lines(const char* filepath) 6 | { 7 | std::vector lines; 8 | if (std::ifstream inf(filepath, std::ios::in); inf.is_open()) 9 | { 10 | std::string line; 11 | while(std::getline(inf, line)) 12 | { 13 | lines.push_back(line); 14 | } 15 | } 16 | return lines; 17 | } 18 | 19 | std::vector load_filters() 20 | { 21 | return read_lines(file_filter); 22 | } 23 | 24 | std::vector load_hooks() 25 | { 26 | return read_lines(file_hooks); 27 | } 28 | -------------------------------------------------------------------------------- /tracer/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | static constexpr auto file_filter = "filters.txt"; 7 | static constexpr auto file_hooks = "hooks.txt"; 8 | static constexpr auto file_trace = "trace.txt"; 9 | 10 | /// Load list of modules that will be logged. 11 | /// 12 | std::vector load_filters(); 13 | 14 | /// Load list of dlls that will be hooked. 15 | /// 16 | std::vector load_hooks(); 17 | -------------------------------------------------------------------------------- /tracer/logger.cpp: -------------------------------------------------------------------------------- 1 | #include "logger.hpp" 2 | #include "config.hpp" 3 | #include "printf.h" 4 | 5 | #include 6 | #include 7 | 8 | static asmjit::JitRuntime rt; 9 | static asmjit::CodeHolder code; 10 | 11 | typedef decltype(NtWriteFile) write; 12 | 13 | logger::logger() 14 | { 15 | file = CreateFile(file_trace, FILE_GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); 16 | if (file == INVALID_HANDLE_VALUE) 17 | { 18 | throw -1; 19 | } 20 | 21 | // Get NtWriteFile syscall number. 22 | // mov r10, rcx 23 | // mov eax, ... 24 | // 25 | if (*reinterpret_cast(NtWriteFile) != 0xB8D18B4C) 26 | { 27 | throw -1; 28 | } 29 | uint32_t number = reinterpret_cast(NtWriteFile)[1]; 30 | 31 | code.init(rt.environment(), rt.cpuFeatures()); 32 | 33 | auto jit = asmjit::x86::Assembler(&code); 34 | // Craft NtWriteFile system call stub. 35 | // 36 | jit.mov(asmjit::x86::r10, asmjit::x86::rcx); 37 | jit.mov(asmjit::x86::rax, number); 38 | jit.syscall(); 39 | jit.ret(); 40 | 41 | rt.add(&syscall, &code); 42 | } 43 | 44 | logger::~logger() 45 | { 46 | CloseHandle(file); 47 | } 48 | 49 | void logger::log(const char* format, ...) noexcept 50 | { 51 | char buff[1024]; 52 | va_list args; 53 | va_start(args, format); 54 | 55 | auto len = vsnprintf_(buff, sizeof(buff), format, args); 56 | 57 | va_end(args); 58 | 59 | IO_STATUS_BLOCK isb; 60 | static_cast(syscall)(file, nullptr, nullptr, nullptr, &isb, buff, len, nullptr, nullptr); 61 | } 62 | 63 | extern "C" void _putchar(char) {} 64 | -------------------------------------------------------------------------------- /tracer/logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct logger 4 | { 5 | static logger& get() 6 | { 7 | static logger log; 8 | return log; 9 | } 10 | 11 | logger(logger const&) = delete; 12 | void operator=(logger const&) = delete; 13 | 14 | void log(const char* format, ...) noexcept; 15 | 16 | private: 17 | logger(); 18 | ~logger(); 19 | 20 | void* file; 21 | void* syscall; 22 | }; 23 | -------------------------------------------------------------------------------- /tracer/main.cpp: -------------------------------------------------------------------------------- 1 | #include "tracer.hpp" 2 | #include "logger.hpp" 3 | 4 | #include 5 | 6 | BOOL DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved) 7 | { 8 | switch (reason) 9 | { 10 | case DLL_PROCESS_ATTACH: 11 | { 12 | logger::get().log("api-tracer loading...\n"); 13 | return tracer::initialize(); 14 | } 15 | case DLL_PROCESS_DETACH: 16 | { 17 | logger::get().log("api-tracer unloading...\n"); 18 | tracer::terminate(); 19 | break; 20 | } 21 | default: 22 | break; 23 | } 24 | return TRUE; 25 | } 26 | -------------------------------------------------------------------------------- /tracer/tracer.cpp: -------------------------------------------------------------------------------- 1 | #include "tracer.hpp" 2 | #include "logger.hpp" 3 | #include "config.hpp" 4 | 5 | #include 6 | #include 7 | 8 | typedef void (*Func)(void); 9 | 10 | namespace tracer 11 | { 12 | struct filter_t 13 | { 14 | std::string name; 15 | uintptr_t base; 16 | uintptr_t size; 17 | }; 18 | // Array that holds trampoline pointers. 19 | // 20 | static void* hooks[100000]; 21 | // Array of addresses to filter. 22 | // 23 | static std::vector filters; 24 | // Array of hooked dlls. 25 | // 26 | static std::vector dlls; 27 | // Jit runtime that holds code. 28 | // 29 | static asmjit::JitRuntime rt; 30 | 31 | /// NOTE: Make sure not to call any dlls in this function because that would cause the recursion. 32 | /// The logger instance uses syscalls and custom vsprintf function to avoid that. 33 | /// 34 | void logger_routine(const char* const api, const char* const dll, uintptr_t ret) 35 | { 36 | // If no filters loaded, log everything. 37 | // 38 | if (filters.empty()) 39 | { 40 | logger::get().log("0x%012llx %s:%s\n", ret, dll, api); 41 | } 42 | else 43 | { 44 | for (const auto& filter : filters) 45 | { 46 | if (ret >= filter.base && ret < filter.base + filter.size) 47 | { 48 | logger::get().log("%s+0x%06llx %s:%s\n", filter.name.c_str(), ret - filter.base, dll, api); 49 | break; 50 | } 51 | } 52 | } 53 | } 54 | 55 | bool setup_hooks(uint8_t* base, const char* dll_name) 56 | { 57 | static uint64_t count; 58 | if (count >= 100000) 59 | { 60 | return false; 61 | } 62 | 63 | const auto nt_headers = PIMAGE_NT_HEADERS(base + PIMAGE_DOS_HEADER(base)->e_lfanew); 64 | const auto data_dir = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; 65 | if (!data_dir.VirtualAddress || !data_dir.Size) 66 | return false; 67 | 68 | const auto export_dir = PIMAGE_EXPORT_DIRECTORY(base + data_dir.VirtualAddress); 69 | const auto names = reinterpret_cast(base + export_dir->AddressOfNames); 70 | const auto ordinals = reinterpret_cast(base + export_dir->AddressOfNameOrdinals); 71 | const auto functions = reinterpret_cast(base + export_dir->AddressOfFunctions); 72 | 73 | for (size_t i = 0; i < export_dir->NumberOfNames; i++) 74 | { 75 | asmjit::CodeHolder code; 76 | code.init(rt.environment(), rt.cpuFeatures()); 77 | 78 | const auto api_name = reinterpret_cast(base + names[i]); 79 | // Check for redirected exports. 80 | // 81 | const auto func_rva = functions[ordinals[i]]; 82 | if (func_rva >= data_dir.VirtualAddress && func_rva < data_dir.VirtualAddress + data_dir.Size) 83 | continue; 84 | // Build function. 85 | // 86 | auto jit = asmjit::x86::Assembler(&code); 87 | // Save context. 88 | // 89 | jit.push(asmjit::x86::rax); 90 | jit.push(asmjit::x86::rbx); 91 | jit.push(asmjit::x86::rcx); 92 | jit.push(asmjit::x86::rdx); 93 | jit.push(asmjit::x86::rbp); 94 | jit.push(asmjit::x86::rdi); 95 | jit.push(asmjit::x86::rsi); 96 | jit.push(asmjit::x86::r8); 97 | jit.push(asmjit::x86::r9); 98 | jit.push(asmjit::x86::r10); 99 | jit.push(asmjit::x86::r11); 100 | jit.push(asmjit::x86::r12); 101 | jit.push(asmjit::x86::r13); 102 | jit.push(asmjit::x86::r14); 103 | jit.push(asmjit::x86::r15); 104 | // Get name of the called function and return address. 105 | // 106 | jit.mov(asmjit::x86::rcx, api_name); 107 | jit.mov(asmjit::x86::rdx, dll_name); 108 | jit.mov(asmjit::x86::r8, asmjit::x86::qword_ptr(asmjit::x86::rsp, 8 * 15)); 109 | // Call logger. 110 | // 111 | jit.sub(asmjit::x86::rsp, 0x20); 112 | jit.call(logger_routine); 113 | jit.add(asmjit::x86::rsp, 0x20); 114 | // Restore context and exit. 115 | // 116 | jit.pop(asmjit::x86::r15); 117 | jit.pop(asmjit::x86::r14); 118 | jit.pop(asmjit::x86::r13); 119 | jit.pop(asmjit::x86::r12); 120 | jit.pop(asmjit::x86::r11); 121 | jit.pop(asmjit::x86::r10); 122 | jit.pop(asmjit::x86::r9); 123 | jit.pop(asmjit::x86::r8); 124 | jit.pop(asmjit::x86::rsi); 125 | jit.pop(asmjit::x86::rdi); 126 | jit.pop(asmjit::x86::rbp); 127 | jit.pop(asmjit::x86::rdx); 128 | jit.pop(asmjit::x86::rcx); 129 | jit.pop(asmjit::x86::rbx); 130 | jit.pop(asmjit::x86::rax); 131 | 132 | jit.mov(asmjit::x86::rax, asmjit::x86::qword_ptr(reinterpret_cast(hooks + count))); 133 | jit.jmp(asmjit::x86::rax); 134 | 135 | Func fn; 136 | if (auto err = rt.add(&fn, &code)) 137 | { 138 | return false; 139 | } 140 | 141 | void* trampoline{}; 142 | auto err = MH_CreateHook(static_cast(func_rva + base), fn, &trampoline); 143 | 144 | if (err == MH_OK) 145 | { 146 | hooks[count++] = trampoline; 147 | } 148 | } 149 | return true; 150 | } 151 | 152 | bool initialize() 153 | { 154 | if (MH_Initialize() != MH_OK) 155 | return false; 156 | // Initialize hooks. 157 | // 158 | for (const auto& dll : load_hooks()) 159 | { 160 | // Load dll. 161 | // 162 | if (auto base = LoadLibraryA(dll.c_str())) 163 | { 164 | // Hook every export. 165 | // 166 | if (!setup_hooks(reinterpret_cast(base), dll.c_str())) 167 | { 168 | return false; 169 | } 170 | } 171 | else 172 | { 173 | return false; 174 | } 175 | } 176 | // Initialize filters. 177 | // 178 | for (const auto& dll : load_filters()) 179 | { 180 | const auto base = GetModuleHandle(dll.c_str()); 181 | if (base != nullptr) 182 | { 183 | auto nt = PIMAGE_NT_HEADERS(reinterpret_cast(base) + PIMAGE_DOS_HEADER(base)->e_lfanew); 184 | filters.push_back({ dll, reinterpret_cast(base), nt->OptionalHeader.SizeOfImage }); 185 | } 186 | } 187 | return MH_EnableHook(MH_ALL_HOOKS) == MH_OK; 188 | } 189 | 190 | void terminate() 191 | { 192 | MH_DisableHook(MH_ALL_HOOKS); 193 | MH_Uninitialize(); 194 | } 195 | } // namespace hooks 196 | -------------------------------------------------------------------------------- /tracer/tracer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace tracer 4 | { 5 | bool initialize(); 6 | void terminate(); 7 | } 8 | --------------------------------------------------------------------------------