Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/dockcross.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,17 @@ jobs:
- uses: actions/checkout@v4

- name: Install Dependencies
run: brew install libffi readline llvm
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"
Expand Down
12 changes: 10 additions & 2 deletions .github/workflows/mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,19 @@ jobs:

- name: Install Dependencies (macOS)
if: runner.os == 'macOS'
run: brew install libffi cmake
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
Expand Down
22 changes: 20 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()

Expand Down Expand Up @@ -82,13 +82,17 @@ 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})
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})

Expand All @@ -98,13 +102,20 @@ 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
${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")
Expand All @@ -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
Expand All @@ -142,6 +159,7 @@ target_include_directories(cliffi_common_deps INTERFACE
)

target_link_libraries(cliffi PRIVATE cliffi_common_deps)
target_compile_definitions(cliffi PRIVATE $<$<BOOL:${CLIFFI_HAS_LIEF}>:CLIFFI_HAS_LIEF>)

# Compile cliffi_testlib.c into a shared library
add_library(cliffitest SHARED test/lib/cliffi_testlib.c)
Expand Down Expand Up @@ -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 $<$<BOOL:${CLIFFI_HAS_LIEF}>:CLIFFI_HAS_LIEF>)


add_test(NAME cliffi_unit_tests COMMAND cliffi_unit_tests)
Expand Down Expand Up @@ -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



3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <library>` 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
Expand Down
1 change: 1 addition & 0 deletions conanfile.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[requires]
libffi/3.4.6
readline/8.2
lief/0.16.2
9 changes: 9 additions & 0 deletions profile_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment on lines +88 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop forcing Android builds to use c++_shared

Setting compiler.libcxx to c++_shared makes the Android cliffi/test binaries depend on libc++_shared.so, but the Android test flow only pushes cliffi, libcliffi_test.so, and cliffi_unit_tests to /data/local/tmp (see prepare_and_test_via_adb.sh), so emulator runs can fail at process startup with a missing runtime library. This regression is specific to Android jobs in the dockcross workflow and can break those builds/tests even before any test logic runs.

Useful? React with 👍 / 👎.

fi
fi

# --- Correctly add os.api_level AFTER the 'os=' line ---
if [ "$OS" == "Android" ]; then
TARGET_API_LEVEL="${ANDROID_API}"
Expand Down
37 changes: 37 additions & 0 deletions src/export_symbols.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include "export_symbols.h"

#include <stdio.h>

#if defined(CLIFFI_HAS_LIEF)
#include <LIEF/LIEF.hpp>
#include <memory>
#include <string>

bool list_exported_symbols_with_lief(const char* library_path) {
std::unique_ptr<LIEF::Binary> 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;
const auto exported = binary->exported_functions();
for (const std::string& symbol_name : exported) {
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
16 changes: 16 additions & 0 deletions src/export_symbols.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef EXPORT_SYMBOLS_H
#define EXPORT_SYMBOLS_H

#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif

bool list_exported_symbols_with_lief(const char* library_path);

#ifdef __cplusplus
}
#endif

#endif
24 changes: 24 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "return_formatter.h"
#include "types_and_utils.h"
#include "var_map.h"
#include "export_symbols.h"

#include <stdarg.h>
#include <stdbool.h>
Expand Down Expand Up @@ -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) {
Expand All @@ -564,6 +585,7 @@ int parseREPLCommand(char* command){
" hexdump <address> <size>: Print a hexdump of memory\n"
"Shared Library Management:\n"
" list: List all opened libraries\n"
" exports <library>: List exported symbols/functions for a library (LIEF-backed)\n"
" close <library>: Close the specified library\n"
" closeall: Close all opened libraries\n"
"Shell commands:\n"
Expand All @@ -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 == ' ') {
Expand Down
Loading