diff --git a/.github/workflows/dockcross.yml b/.github/workflows/dockcross.yml index a314e6b..c31ee1a 100644 --- a/.github/workflows/dockcross.yml +++ b/.github/workflows/dockcross.yml @@ -58,7 +58,7 @@ jobs: - uses: actions/checkout@v4 - name: Install Dependencies - run: brew install libffi readline llvm + run: brew install libffi readline llvm lief - name: Configure CMake run: brew sh -c "mkdir build && cd build && cmake .." diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 0f77add..c84b8fa 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -18,7 +18,7 @@ jobs: - name: Install Dependencies (macOS) if: runner.os == 'macOS' - run: brew install libffi cmake + run: brew install libffi cmake lief - name: Configure CMake (macOS) if: runner.os == 'macOS' diff --git a/CMakeLists.txt b/CMakeLists.txt index ed26856..ad4b479 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(cliffi VERSION 1.0 LANGUAGES C) +project(cliffi VERSION 1.0 LANGUAGES C CXX) enable_testing() @@ -82,6 +82,7 @@ src/tokenize.c src/parse_address.c src/shims.c src/exception_handling.c +src/export_symbols.cpp ) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -89,6 +90,9 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_library(cliffi_common_deps INTERFACE) +# LIEF is optional for local/pkg-config builds and enabled via Conan/CMakeDeps in cross-platform CI. +set(CLIFFI_HAS_LIEF OFF) + # Target executable add_executable(cliffi ${CLIFFI_COMMON_SOURCES}) @@ -98,6 +102,7 @@ find_package(PkgConfig REQUIRED) pkg_search_module(LIBFFI libffi REQUIRED) pkg_search_module(READLINE readline REQUIRED) +pkg_search_module(LIEF_CPP QUIET lief) target_link_libraries(cliffi_common_deps INTERFACE @@ -105,6 +110,12 @@ ${LIBFFI_LIBRARIES} ${READLINE_LIBRARIES} ) +if(LIEF_CPP_FOUND) + target_include_directories(cliffi_common_deps INTERFACE ${LIEF_CPP_INCLUDE_DIRS}) + target_link_libraries(cliffi_common_deps INTERFACE ${LIEF_CPP_LIBRARIES}) + set(CLIFFI_HAS_LIEF ON) +endif() + endif() # Append the project root directory to CMAKE_PREFIX_PATH list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build") @@ -126,6 +137,12 @@ target_link_libraries(cliffi_common_deps INTERFACE readline::readline) message(STATUS "readline include dirs: ${readline_INCLUDE_DIRS}") message("attempting link readline::readline") endif() + +set(lief_DIR ${CMAKE_SOURCE_DIR}/build) +set(ENV{lief_DIR} ${CMAKE_SOURCE_DIR}/build) +find_package(lief REQUIRED) +target_link_libraries(cliffi_common_deps INTERFACE lief::lief) +set(CLIFFI_HAS_LIEF ON) endif() if(NOT ANDROID) # on android this breaks things @@ -142,6 +159,7 @@ target_include_directories(cliffi_common_deps INTERFACE ) target_link_libraries(cliffi PRIVATE cliffi_common_deps) +target_compile_definitions(cliffi PRIVATE $<$:CLIFFI_HAS_LIEF>) # Compile cliffi_testlib.c into a shared library add_library(cliffitest SHARED test/lib/cliffi_testlib.c) @@ -181,6 +199,7 @@ target_link_libraries(cliffi_unit_tests PRIVATE unity # The Unity testing framework library ) target_compile_definitions(cliffi_unit_tests PRIVATE CLIFFI_UNIT_TESTING) # this prevents main() from main.c from being compiled +target_compile_definitions(cliffi_unit_tests PRIVATE $<$:CLIFFI_HAS_LIEF>) add_test(NAME cliffi_unit_tests COMMAND cliffi_unit_tests) @@ -1340,4 +1359,3 @@ add_test(NAME test_nonzero_exit_for_nonexistent_function set_tests_properties(test_nonzero_exit_for_nonexistent_function PROPERTIES WILL_FAIL TRUE) # this test is expected to fail - diff --git a/README.md b/README.md index 8fd36c0..dde44e3 100644 --- a/README.md +++ b/README.md @@ -149,11 +149,12 @@ addr_pointer = -P intpointer In REPL mode, there are `load`, `dump`, and `store` to respectively load a value from a memory address, print a value from a memory address, or store a value to a memory address. -There are also commands to do a `hexdump` from a memory address, and `calculate_offset` to calculate a memory offset (and store the offset to a variable) for a particular library loaded into memory, relative to some reference layout, given a known symbol (ie function) name and reference address. This is helpful for calculating the actual locations of various stripped global and static variables from their addresses in the binary. +There are also commands to do a `hexdump` from a memory address, `exports ` to list exported symbols/functions from a shared library, and `calculate_offset` to calculate a memory offset (and store the offset to a variable) for a particular library loaded into memory, relative to some reference layout, given a known symbol (ie function) name and reference address. This is helpful for calculating the actual locations of various stripped global and static variables from their addresses in the binary. For example if in your decompiler, there is a static struct `{ int; short; char*; }` of interest at `0x10beef`, you can find the address of an exported function doFoo() at `0x10dead`, calculate the offset and store it in a variable, and then reference it. ``` calculate_offset myoffset mysharedlib.so doFoo 0x10dead // calculate the offset +exports mysharedlib.so // list exports/symbols (LIEF-backed) hexdump myoffset+0x10beef 16 // to just see 16 bytes at that address dump S: i h s :S myoffset+0x10beef // to dump the struct presently at that address store myoffset+0x10beef -S: 20 -h 0x6008 hello :S // to store that value into that address diff --git a/cliffi.rb b/cliffi.rb index eaa571c..b8bc042 100644 --- a/cliffi.rb +++ b/cliffi.rb @@ -6,6 +6,7 @@ class Cliffi < Formula license "MIT" depends_on "cmake" => [:build, :test] + depends_on "lief" uses_from_macos "libffi" def install diff --git a/conanfile.txt b/conanfile.txt index f833b5a..21be91f 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,3 +1,4 @@ [requires] libffi/3.4.6 readline/8.2 +lief/0.16.2 diff --git a/src/export_symbols.cpp b/src/export_symbols.cpp new file mode 100644 index 0000000..440430c --- /dev/null +++ b/src/export_symbols.cpp @@ -0,0 +1,38 @@ +#include "export_symbols.h" + +#include + +#if defined(CLIFFI_HAS_LIEF) +#include +#include + +bool list_exported_symbols_with_lief(const char* library_path) { + std::unique_ptr binary = LIEF::Parser::parse(library_path); + if (!binary) { + fprintf(stderr, "Error: Failed to parse '%s' using LIEF\n", library_path); + return false; + } + + size_t exported_count = 0; + for (const LIEF::Symbol& symbol : binary->symbols()) { + if (!symbol.is_exported()) { + continue; + } + printf("%s\n", symbol.name().c_str()); + exported_count++; + } + + printf("Total exported symbols: %zu\n", exported_count); + return true; +} + +#else + +bool list_exported_symbols_with_lief(const char* library_path) { + (void)library_path; + fprintf(stderr, + "Error: This build does not include LIEF support. Rebuild with a LIEF dependency to use 'exports'.\n"); + return false; +} + +#endif diff --git a/src/export_symbols.h b/src/export_symbols.h new file mode 100644 index 0000000..d6a4cc1 --- /dev/null +++ b/src/export_symbols.h @@ -0,0 +1,16 @@ +#ifndef EXPORT_SYMBOLS_H +#define EXPORT_SYMBOLS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool list_exported_symbols_with_lief(const char* library_path); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/main.c b/src/main.c index 833b2b8..cfea1b2 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ #include "return_formatter.h" #include "types_and_utils.h" #include "var_map.h" +#include "export_symbols.h" #include #include @@ -540,6 +541,26 @@ void parseHexdump(char* hexdumpCommand) { +void parseListExports(char* exportsCommand) { + int argc; + char** argv; + tokenize(exportsCommand, &argc, &argv); + if (argc < 1) { + raiseException(1, "Error: Invalid number of arguments for exports\n"); + return; + } + + char* resolvedPath = resolve_library_path(argv[0]); + if (resolvedPath == NULL) { + raiseException(1, "Error: Could not resolve library path for '%s'\n", argv[0]); + return; + } + + if (!list_exported_symbols_with_lief(resolvedPath)) { + raiseException(1, "Error: Failed to list exported symbols for '%s'\n", resolvedPath); + } +} + int parseREPLCommand(char* command){ command = trim_whitespace(command); if (strlen(command) > 0) { @@ -564,6 +585,7 @@ int parseREPLCommand(char* command){ " hexdump
: Print a hexdump of memory\n" "Shared Library Management:\n" " list: List all opened libraries\n" + " exports : List exported symbols/functions for a library (LIEF-backed)\n" " close : Close the specified library\n" " closeall: Close all opened libraries\n" "Shell commands:\n" @@ -575,6 +597,8 @@ int parseREPLCommand(char* command){ print_usage(">"); } else if (strcmp(command, "list") == 0) { listOpenedLibraries(); + } else if (strncmp(command, "exports ", 8) == 0) { + parseListExports(command + 8); } else if (strncmp(command, "close", 5) == 0) { char* libraryName = command + 5; while (*libraryName == ' ') {