From 8859c43d5054c963c72343f37a717bb858259713 Mon Sep 17 00:00:00 2001 From: Yaakov Saxon Date: Tue, 10 Mar 2026 15:57:04 -0400 Subject: [PATCH 1/3] Fix Conan/CMake LIEF integration and Android libcxx profile --- .github/workflows/dockcross.yml | 2 +- .github/workflows/mac.yml | 2 +- CMakeLists.txt | 22 +++++++++++++++++-- README.md | 3 ++- cliffi.rb | 1 + conanfile.txt | 1 + profile_script.sh | 9 ++++++++ src/export_symbols.cpp | 38 +++++++++++++++++++++++++++++++++ src/export_symbols.h | 16 ++++++++++++++ src/main.c | 24 +++++++++++++++++++++ 10 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 src/export_symbols.cpp create mode 100644 src/export_symbols.h 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..04d250f 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 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/profile_script.sh b/profile_script.sh index 83e9872..66cf109 100755 --- a/profile_script.sh +++ b/profile_script.sh @@ -82,6 +82,15 @@ sed -i "s|^compiler\s*=.*|compiler=$COMPILER|" "$PROFILE_PATH" sed -i "s|^os\s*=.*|os=$OS|" "$PROFILE_PATH" # This ensures the 'os=' line is present and set. echo "Updated arch, compiler, os in profile." +# Conan Android profiles must use libc++ (not libstdc++11) with clang/NDK. +if [ "$OS" == "Android" ]; then + if grep -q '^compiler\.libcxx\s*=' "$PROFILE_PATH"; then + sed -i 's|^compiler\.libcxx\s*=.*|compiler.libcxx=c++_shared|' "$PROFILE_PATH" + else + printf '\ncompiler.libcxx=c++_shared\n' >> "$PROFILE_PATH" + fi +fi + # --- Correctly add os.api_level AFTER the 'os=' line --- if [ "$OS" == "Android" ]; then TARGET_API_LEVEL="${ANDROID_API}" 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 == ' ') { From 411dee34b570a25185cd5d5557e579de0bf6f66b Mon Sep 17 00:00:00 2001 From: Yaakov Saxon Date: Tue, 10 Mar 2026 16:14:21 -0400 Subject: [PATCH 2/3] Fix LIEF 0.16 API usage for export listing --- src/export_symbols.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/export_symbols.cpp b/src/export_symbols.cpp index 440430c..6fa4c56 100644 --- a/src/export_symbols.cpp +++ b/src/export_symbols.cpp @@ -5,6 +5,7 @@ #if defined(CLIFFI_HAS_LIEF) #include #include +#include bool list_exported_symbols_with_lief(const char* library_path) { std::unique_ptr binary = LIEF::Parser::parse(library_path); @@ -14,11 +15,9 @@ bool list_exported_symbols_with_lief(const char* library_path) { } size_t exported_count = 0; - for (const LIEF::Symbol& symbol : binary->symbols()) { - if (!symbol.is_exported()) { - continue; - } - printf("%s\n", symbol.name().c_str()); + const auto exported = binary->exported_functions(); + for (const std::string& symbol_name : exported) { + printf("%s\n", symbol_name.c_str()); exported_count++; } From 868a20647734bbfb65e8147b43ec04f36ce6bdbe Mon Sep 17 00:00:00 2001 From: Yaakov Saxon Date: Wed, 11 Mar 2026 10:59:35 -0400 Subject: [PATCH 3/3] Use Conan for LIEF on macOS CI and remove invalid brew dep --- .github/workflows/dockcross.yml | 11 +++++++++-- .github/workflows/mac.yml | 12 ++++++++++-- cliffi.rb | 1 - 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dockcross.yml b/.github/workflows/dockcross.yml index c31ee1a..9a4dd03 100644 --- a/.github/workflows/dockcross.yml +++ b/.github/workflows/dockcross.yml @@ -58,10 +58,17 @@ jobs: - uses: actions/checkout@v4 - name: Install Dependencies - run: brew install libffi readline llvm lief + run: | + brew install libffi readline llvm + python3 -m pip install --upgrade pip conan + + - name: Resolve Conan dependencies + run: | + conan profile detect --force + conan install . --output-folder=build --build=missing -g CMakeDeps - name: Configure CMake - run: brew sh -c "mkdir build && cd build && cmake .." + run: brew sh -c "mkdir build && cd build && cmake .. -DUSE_FIND_PACKAGE=ON" - name: Build run: brew sh -c "cd build && make -j 4" diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index c84b8fa..3b43954 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -18,11 +18,19 @@ jobs: - name: Install Dependencies (macOS) if: runner.os == 'macOS' - run: brew install libffi cmake lief + run: | + brew install libffi cmake + python3 -m pip install --upgrade pip conan + + - name: Resolve Conan dependencies (macOS) + if: runner.os == 'macOS' + run: | + conan profile detect --force + conan install . --output-folder=build --build=missing -g CMakeDeps - name: Configure CMake (macOS) if: runner.os == 'macOS' - run: cmake -B build -DCMAKE_BUILD_TYPE=Release + run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DUSE_FIND_PACKAGE=ON - name: Build run: cmake --build build --config Release diff --git a/cliffi.rb b/cliffi.rb index b8bc042..eaa571c 100644 --- a/cliffi.rb +++ b/cliffi.rb @@ -6,7 +6,6 @@ class Cliffi < Formula license "MIT" depends_on "cmake" => [:build, :test] - depends_on "lief" uses_from_macos "libffi" def install