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
1 change: 1 addition & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ plugins {
android {
namespace = "me.kavishdevar.librepods"
compileSdk = 36
ndkVersion = "27.1.12297006"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Android NDK 27.1.12297006 release date current version 2025 2026

💡 Result:

  • Android NDK 27.1.12297006 corresponds to NDK r27b, which was announced/released on September 5, 2024 (stable channel in SDK Manager). [1]
  • Current (latest stable) NDK as of Feb 6, 2026: NDK r29, ndkVersion "29.0.14206865" (r29 series is dated October 2025 in the revision history). [2][3]
  • Current LTS line (r27 LTS) latest point release shown on the downloads page: NDK r27d, ndkVersion "27.3.13750724". [2]

Sources:
[1] https://groups.google.com/g/android-ndk-announce/c/rpC6OBfwq7g
[2] https://developer.android.com/ndk/downloads
[3] https://developer.android.com/ndk/downloads/revision_history


Pinning ndkVersion is good practice, but update to a more current release.

NDK 27.1.12297006 (r27b) is from September 2024 and is ~17 months behind the current stable NDK r29 (29.0.14206865, October 2025). Either update to the latest r27 LTS point release (27.3.13750724) for a conservative bump, or upgrade to the latest stable NDK r29 (29.0.14206865) to benefit from recent security and ABI improvements.

🤖 Prompt for AI Agents
In `@android/app/build.gradle.kts` at line 12, Update the pinned NDK version
string assigned to ndkVersion from "27.1.12297006" to a more current release;
change the value to either the conservative LTS bump "27.3.13750724" or the
latest stable "29.0.14206865" (replace the existing ndkVersion = "27.1.12297006"
line accordingly).


defaultConfig {
applicationId = "me.kavishdevar.librepods"
Expand Down
168 changes: 162 additions & 6 deletions android/app/src/main/cpp/l2c_fcr_hook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ static void (*original_l2cu_send_peer_info_req)(tL2C_LCB* p_lcb, uint16_t info_t
// Add original pointer for BTA_DmSetLocalDiRecord
static tBTA_STATUS (*original_BTA_DmSetLocalDiRecord)(tSDP_DI_RECORD* p_device_info, uint32_t* p_handle) = nullptr;

// Store the detected library name for use by offset loaders (forward declaration)
static char g_detected_bt_library[64] = "libbluetooth_jni.so";

// Forward declarations for symbol lookup functions
uintptr_t getModuleBase(const char *module_name);
bool findLibraryPath(const char* module_name, char* path_out, size_t path_size);
uintptr_t findSymbolOffset(const char* symbol_name, const char* module_name);

uint8_t fake_l2c_fcr_chk_chan_modes(void* p_ccb) {
LOGI("l2c_fcr_chk_chan_modes hooked, returning true.");
return 1;
Expand Down Expand Up @@ -212,6 +220,7 @@ uintptr_t loadHookOffset([[maybe_unused]] const char* package_name) {
const char* property_name = "persist.librepods.hook_offset";
char value[PROP_VALUE_MAX] = {0};

// 1. First check system property override (takes precedence)
int len = __system_property_get(property_name, value);
if (len > 0) {
LOGI("Read hook offset from property: %s", value);
Expand All @@ -227,21 +236,31 @@ uintptr_t loadHookOffset([[maybe_unused]] const char* package_name) {
offset = strtoul(parse_start, &endptr, 16);

if (errno == 0 && endptr != parse_start && *endptr == '\0' && offset > 0) {
LOGI("Parsed offset: 0x%x", offset);
LOGI("Parsed offset from property: 0x%lx", (unsigned long)offset);
return offset;
}

LOGE("Failed to parse offset from property value: %s", value);
}

LOGI("Using hardcoded fallback offset");
// 2. Try dynamic symbol lookup
LOGI("Attempting dynamic symbol lookup for l2c_fcr_chk_chan_modes");
uintptr_t sym_offset = findSymbolOffset("l2c_fcr_chk_chan_modes", g_detected_bt_library);
if (sym_offset > 0) {
LOGI("Auto-detected l2c_fcr_chk_chan_modes offset: 0x%lx", (unsigned long)sym_offset);
return sym_offset;
}

// 3. Last resort: hardcoded fallback
LOGI("Using hardcoded fallback offset 0x00a55e30");
return 0x00a55e30;
}

uintptr_t loadL2cuProcessCfgReqOffset() {
const char* property_name = "persist.librepods.cfg_req_offset";
char value[PROP_VALUE_MAX] = {0};

// 1. First check system property override (takes precedence)
int len = __system_property_get(property_name, value);
if (len > 0) {
LOGI("Read l2cu_process_our_cfg_req offset from property: %s", value);
Expand All @@ -257,21 +276,31 @@ uintptr_t loadL2cuProcessCfgReqOffset() {
offset = strtoul(parse_start, &endptr, 16);

if (errno == 0 && endptr != parse_start && *endptr == '\0' && offset > 0) {
LOGI("Parsed l2cu_process_our_cfg_req offset: 0x%x", offset);
LOGI("Parsed l2cu_process_our_cfg_req offset from property: 0x%lx", (unsigned long)offset);
return offset;
}

LOGE("Failed to parse l2cu_process_our_cfg_req offset from property value: %s", value);
}

// 2. Try dynamic symbol lookup
LOGI("Attempting dynamic symbol lookup for l2cu_process_our_cfg_req");
uintptr_t sym_offset = findSymbolOffset("l2cu_process_our_cfg_req", g_detected_bt_library);
if (sym_offset > 0) {
LOGI("Auto-detected l2cu_process_our_cfg_req offset: 0x%lx", (unsigned long)sym_offset);
return sym_offset;
}

// Return 0 if not found - we'll skip this hook
LOGI("l2cu_process_our_cfg_req offset not found - skipping hook");
return 0;
}

uintptr_t loadL2cCsmConfigOffset() {
const char* property_name = "persist.librepods.csm_config_offset";
char value[PROP_VALUE_MAX] = {0};

// 1. First check system property override (takes precedence)
int len = __system_property_get(property_name, value);
if (len > 0) {
LOGI("Read l2c_csm_config offset from property: %s", value);
Expand All @@ -287,21 +316,31 @@ uintptr_t loadL2cCsmConfigOffset() {
offset = strtoul(parse_start, &endptr, 16);

if (errno == 0 && endptr != parse_start && *endptr == '\0' && offset > 0) {
LOGI("Parsed l2c_csm_config offset: 0x%x", offset);
LOGI("Parsed l2c_csm_config offset from property: 0x%lx", (unsigned long)offset);
return offset;
}

LOGE("Failed to parse l2c_csm_config offset from property value: %s", value);
}

// 2. Try dynamic symbol lookup (look for l2c_csm_execute, not l2c_csm_config)
LOGI("Attempting dynamic symbol lookup for l2c_csm_execute");
uintptr_t sym_offset = findSymbolOffset("l2c_csm_execute", g_detected_bt_library);
if (sym_offset > 0) {
LOGI("Auto-detected l2c_csm_execute offset: 0x%lx", (unsigned long)sym_offset);
return sym_offset;
}

// Return 0 if not found - we'll skip this hook
LOGI("l2c_csm_execute offset not found - skipping hook");
return 0;
}

uintptr_t loadL2cuSendPeerInfoReqOffset() {
const char* property_name = "persist.librepods.peer_info_req_offset";
char value[PROP_VALUE_MAX] = {0};

// 1. First check system property override (takes precedence)
int len = __system_property_get(property_name, value);
if (len > 0) {
LOGI("Read l2cu_send_peer_info_req offset from property: %s", value);
Expand All @@ -317,14 +356,23 @@ uintptr_t loadL2cuSendPeerInfoReqOffset() {
offset = strtoul(parse_start, &endptr, 16);

if (errno == 0 && endptr != parse_start && *endptr == '\0' && offset > 0) {
LOGI("Parsed l2cu_send_peer_info_req offset: 0x%x", offset);
LOGI("Parsed l2cu_send_peer_info_req offset from property: 0x%lx", (unsigned long)offset);
return offset;
}

LOGE("Failed to parse l2cu_send_peer_info_req offset from property value: %s", value);
}

// 2. Try dynamic symbol lookup
LOGI("Attempting dynamic symbol lookup for l2cu_send_peer_info_req");
uintptr_t sym_offset = findSymbolOffset("l2cu_send_peer_info_req", g_detected_bt_library);
if (sym_offset > 0) {
LOGI("Auto-detected l2cu_send_peer_info_req offset: 0x%lx", (unsigned long)sym_offset);
return sym_offset;
}

// Return 0 if not found - we'll skip this hook
LOGI("l2cu_send_peer_info_req offset not found - skipping hook");
return 0;
}

Expand Down Expand Up @@ -355,6 +403,109 @@ uintptr_t getModuleBase(const char *module_name) {
return base_addr;
}

// Find full library path from /proc/self/maps
bool findLibraryPath(const char* module_name, char* path_out, size_t path_size) {
FILE* fp = fopen("/proc/self/maps", "r");
if (!fp) {
LOGE("findLibraryPath: Failed to open /proc/self/maps");
return false;
}

char line[1024];
while (fgets(line, sizeof(line), fp)) {
if (strstr(line, module_name)) {
// Find the path part (after the last space before newline)
char* path_start = strrchr(line, ' ');
if (path_start && *(path_start + 1) == '/') {
path_start++; // Skip the space
// Remove trailing newline
char* newline = strchr(path_start, '\n');
if (newline) *newline = '\0';

strncpy(path_out, path_start, path_size - 1);
path_out[path_size - 1] = '\0';
fclose(fp);
LOGI("findLibraryPath: Found %s at %s", module_name, path_out);
return true;
}
}
}

fclose(fp);
LOGI("findLibraryPath: Could not find path for %s", module_name);
return false;
}

// Try to find symbol offset using dynamic symbol lookup
// Returns 0 if symbol not found
uintptr_t findSymbolOffset(const char* symbol_name, const char* module_name) {
LOGI("findSymbolOffset: Looking up symbol '%s' in module '%s'", symbol_name, module_name);

// First, try dlopen(NULL) to search all loaded libraries
void* handle = dlopen(NULL, RTLD_NOW);
if (handle) {
dlerror(); // Clear any existing error
void* sym_addr = dlsym(handle, symbol_name);
const char* error = dlerror();

if (sym_addr && !error) {
uintptr_t base_addr = getModuleBase(module_name);
if (base_addr > 0) {
uintptr_t offset = reinterpret_cast<uintptr_t>(sym_addr) - base_addr;
LOGI("findSymbolOffset: Found '%s' via dlopen(NULL) at %p, base=%p, offset=0x%lx",
symbol_name, sym_addr, (void*)base_addr, (unsigned long)offset);
dlclose(handle);
return offset;
} else {
LOGE("findSymbolOffset: Found symbol but couldn't get module base for %s", module_name);
}
} else {
LOGI("findSymbolOffset: dlsym(NULL, '%s') failed: %s", symbol_name, error ? error : "symbol not found");
}
dlclose(handle);
} else {
LOGE("findSymbolOffset: dlopen(NULL) failed: %s", dlerror());
}
Comment on lines +441 to +468
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

dlopen(NULL) + dlsym may resolve the symbol from a different library, producing an incorrect offset.

dlsym(handle, symbol_name) with a NULL handle searches all loaded shared objects globally. If the symbol happens to exist in a library other than module_name, sym_addr will point into that other library's address space while base_addr is the base of module_name — the subtraction on Line 454 would yield a bogus offset. When later added back to base_addr in findAndHookFunction, this hooks arbitrary memory.

Consider validating that the resolved symbol actually falls within the module's mapped range, or prefer the more targeted second approach (dlopen by path) as the primary lookup.

🛡️ Proposed fix — add a sanity check on the resolved address
         if (sym_addr && !error) {
             uintptr_t base_addr = getModuleBase(module_name);
             if (base_addr > 0) {
                 uintptr_t offset = reinterpret_cast<uintptr_t>(sym_addr) - base_addr;
+                // Sanity check: offset should be reasonable (< 256MB) and sym should be above base
+                if (reinterpret_cast<uintptr_t>(sym_addr) >= base_addr && offset < 0x10000000) {
                 LOGI("findSymbolOffset: Found '%s' via dlopen(NULL) at %p, base=%p, offset=0x%lx",
                      symbol_name, sym_addr, (void*)base_addr, (unsigned long)offset);
                 dlclose(handle);
                 return offset;
+                } else {
+                    LOGI("findSymbolOffset: Symbol '%s' at %p is not within module %s (base=%p), skipping",
+                         symbol_name, sym_addr, module_name, (void*)base_addr);
+                }
             } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uintptr_t findSymbolOffset(const char* symbol_name, const char* module_name) {
LOGI("findSymbolOffset: Looking up symbol '%s' in module '%s'", symbol_name, module_name);
// First, try dlopen(NULL) to search all loaded libraries
void* handle = dlopen(NULL, RTLD_NOW);
if (handle) {
dlerror(); // Clear any existing error
void* sym_addr = dlsym(handle, symbol_name);
const char* error = dlerror();
if (sym_addr && !error) {
uintptr_t base_addr = getModuleBase(module_name);
if (base_addr > 0) {
uintptr_t offset = reinterpret_cast<uintptr_t>(sym_addr) - base_addr;
LOGI("findSymbolOffset: Found '%s' via dlopen(NULL) at %p, base=%p, offset=0x%lx",
symbol_name, sym_addr, (void*)base_addr, (unsigned long)offset);
dlclose(handle);
return offset;
} else {
LOGE("findSymbolOffset: Found symbol but couldn't get module base for %s", module_name);
}
} else {
LOGI("findSymbolOffset: dlsym(NULL, '%s') failed: %s", symbol_name, error ? error : "symbol not found");
}
dlclose(handle);
} else {
LOGE("findSymbolOffset: dlopen(NULL) failed: %s", dlerror());
}
uintptr_t findSymbolOffset(const char* symbol_name, const char* module_name) {
LOGI("findSymbolOffset: Looking up symbol '%s' in module '%s'", symbol_name, module_name);
// First, try dlopen(NULL) to search all loaded libraries
void* handle = dlopen(NULL, RTLD_NOW);
if (handle) {
dlerror(); // Clear any existing error
void* sym_addr = dlsym(handle, symbol_name);
const char* error = dlerror();
if (sym_addr && !error) {
uintptr_t base_addr = getModuleBase(module_name);
if (base_addr > 0) {
uintptr_t offset = reinterpret_cast<uintptr_t>(sym_addr) - base_addr;
// Sanity check: offset should be reasonable (< 256MB) and sym should be above base
if (reinterpret_cast<uintptr_t>(sym_addr) >= base_addr && offset < 0x10000000) {
LOGI("findSymbolOffset: Found '%s' via dlopen(NULL) at %p, base=%p, offset=0x%lx",
symbol_name, sym_addr, (void*)base_addr, (unsigned long)offset);
dlclose(handle);
return offset;
} else {
LOGI("findSymbolOffset: Symbol '%s' at %p is not within module %s (base=%p), skipping",
symbol_name, sym_addr, module_name, (void*)base_addr);
}
} else {
LOGE("findSymbolOffset: Found symbol but couldn't get module base for %s", module_name);
}
} else {
LOGI("findSymbolOffset: dlsym(NULL, '%s') failed: %s", symbol_name, error ? error : "symbol not found");
}
dlclose(handle);
} else {
LOGE("findSymbolOffset: dlopen(NULL) failed: %s", dlerror());
}
🤖 Prompt for AI Agents
In `@android/app/src/main/cpp/l2c_fcr_hook.cpp` around lines 441 - 468, The
dlopen(NULL)/dlsym path in findSymbolOffset can return an address from a
different library, producing an incorrect offset when you subtract
getModuleBase(module_name); update findSymbolOffset to verify that the resolved
sym_addr actually lies within the target module before returning an offset —
either (A) obtain the module base via getModuleBase(module_name) and module size
(or parse /proc/self/maps) and check sym_addr is between base and base+size, or
(B) prefer a targeted lookup by dlopen(module_path) / dlsym on the specific
module (falling back to dlopen(NULL) only if the module-specific lookup fails);
ensure error logs mention when the address is out-of-range and do not return an
offset unless the address validation passes so findAndHookFunction cannot
compute a bogus hook target.


// Second attempt: try to dlopen the library directly by path
char lib_path[512];
if (findLibraryPath(module_name, lib_path, sizeof(lib_path))) {
dlerror(); // Clear any existing error
void* lib_handle = dlopen(lib_path, RTLD_NOW | RTLD_NOLOAD);
if (!lib_handle) {
// Library might not be loaded yet, try loading it
lib_handle = dlopen(lib_path, RTLD_NOW);
}
Comment on lines +474 to +478
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

RTLD_NOW fallback may force-load a library with side effects.

If findLibraryPath found the library in /proc/self/maps, it's already mapped. RTLD_NOLOAD failing here likely indicates the mapping isn't from the dynamic linker (e.g., mapped by the runtime in a non-standard way). Falling through to dlopen(lib_path, RTLD_NOW) in that situation could trigger constructors or re-initialize state in the Bluetooth stack.

Consider logging a warning and skipping to return 0 instead of the fallback load.

🤖 Prompt for AI Agents
In `@android/app/src/main/cpp/l2c_fcr_hook.cpp` around lines 474 - 478, The
fallback dlopen call using RTLD_NOW can force-load and reinitialize the library;
instead, if findLibraryPath located the library in /proc/self/maps but
dlopen(lib_path, RTLD_NOW | RTLD_NOLOAD) returns null, do not call
dlopen(lib_path, RTLD_NOW). Modify the logic around lib_handle so that when
findLibraryPath indicates the library is mapped but RTLD_NOLOAD failed, you log
a warning (use the module's logging facility) and return 0/skip hooking rather
than forcing a load; keep the fallback dlopen only for cases where
findLibraryPath did not report an existing mapping. Ensure you update the block
handling dlopen(lib_path, RTLD_NOW | RTLD_NOLOAD), dlopen(lib_path, RTLD_NOW),
lib_handle and the early return flow accordingly.


if (lib_handle) {
dlerror(); // Clear any existing error
void* sym_addr = dlsym(lib_handle, symbol_name);
const char* error = dlerror();

if (sym_addr && !error) {
uintptr_t base_addr = getModuleBase(module_name);
if (base_addr > 0) {
uintptr_t offset = reinterpret_cast<uintptr_t>(sym_addr) - base_addr;
LOGI("findSymbolOffset: Found '%s' via dlopen('%s') at %p, base=%p, offset=0x%lx",
symbol_name, lib_path, sym_addr, (void*)base_addr, (unsigned long)offset);
dlclose(lib_handle);
return offset;
} else {
LOGE("findSymbolOffset: Found symbol but couldn't get module base");
}
} else {
LOGI("findSymbolOffset: dlsym('%s', '%s') failed: %s", lib_path, symbol_name, error ? error : "symbol not found");
}
dlclose(lib_handle);
} else {
LOGE("findSymbolOffset: dlopen('%s') failed: %s", lib_path, dlerror());
}
}

LOGI("findSymbolOffset: Could not find symbol '%s' via dynamic lookup", symbol_name);
return 0;
}

bool findAndHookFunction(const char *library_name) {
if (!hook_func) {
LOGE("Hook function not initialized");
Expand All @@ -367,7 +518,12 @@ bool findAndHookFunction(const char *library_name) {
return false;
}

// Load all offsets from system properties - no hardcoding
// Store the library name for use by offset loaders
strncpy(g_detected_bt_library, library_name, sizeof(g_detected_bt_library) - 1);
g_detected_bt_library[sizeof(g_detected_bt_library) - 1] = '\0';
LOGI("Using Bluetooth library: %s", g_detected_bt_library);

// Load all offsets - tries dynamic symbol lookup first, then properties, then hardcoded
uintptr_t l2c_fcr_offset = loadHookOffset(nullptr);
uintptr_t l2cu_process_our_cfg_req_offset = loadL2cuProcessCfgReqOffset();
uintptr_t l2c_csm_config_offset = loadL2cCsmConfigOffset();
Expand Down