From 9bd75d15f7584560d082a71ce9c5516f662b0ae2 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sun, 23 Nov 2025 01:01:56 +0100 Subject: [PATCH] Add binder transaction interception framework This commit introduces a comprehensive framework for intercepting and manipulating binder transactions on Android at the `ioctl` level. It provides a man-in-the-middle layer between the binder driver and user-space `libbinder`, enabling detailed analysis and control over IPC. The core mechanism works by hooking the `ioctl` system call within the context of a target process. It specifically intercepts the `BINDER_WRITE_READ` command's return buffer from the kernel. Key components of the framework: - IOCTL Hook: Intercepts `BR_TRANSACTION` commands delivered by the binder driver to the process. - Transaction Rewriting: If a transaction is intended for a monitored service, its destination is rewritten in-memory to a local `BinderStub`. The original transaction details are saved in a thread-local context. - BinderStub: A fake binder service that receives the hijacked transaction. It retrieves the original context and delegates processing to the `BinderInterceptor`. - BinderInterceptor: The central management class. It maintains a registry of monitored binders and their associated callback interfaces. It orchestrates the pre-transact and post-transact hooks. - Callback Protocol: Defines a clear protocol for a remote tool to: - Register and unregister binders for interception. - Receive pre-transaction notifications and choose to: continue, modify data, skip the transaction, or provide an immediate fake reply. - Receive post-transaction notifications with the final result and modify the reply. --- app/src/main/cpp/binder_interceptor.cpp | 697 +++++++++++++++++++++++- 1 file changed, 695 insertions(+), 2 deletions(-) diff --git a/app/src/main/cpp/binder_interceptor.cpp b/app/src/main/cpp/binder_interceptor.cpp index 7739148..4104a39 100644 --- a/app/src/main/cpp/binder_interceptor.cpp +++ b/app/src/main/cpp/binder_interceptor.cpp @@ -1,6 +1,699 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logging.hpp" #include "lsplt.hpp" +/** + * ========================================================================================= + * BINDER INTERCEPTION LOGIC + * ========================================================================================= + * + * [ Application / libbinder.so ] [ Android System / Service ] + * | ^ + * | (1. Calls ioctl(BINDER_WRITE_READ) to wait for work) | + * v | + * [ Kernel Driver ] <------------------------------------------------------+ + * | + * | (2. Kernel has an incoming transaction for this process, + * | prepares a BR_TRANSACTION command in the read_buffer) + * | + * v + * [ return from ioctl() is HOOKED ] + * | + * +---(3. Hook inspects the read_buffer from the Kernel) + * | + * +--- If a BR_TRANSACTION targets a monitored Binder: + * | (4) Rewrites the transaction's target to our BinderStub + * | + * v + * [ libbinder.so ] + * | + * | (5. libbinder processes the (modified) buffer and + * | dispatches the command to the BinderStub) + * | + * v + * [ BinderStub::onTransact ] + * | + * v + * [ BinderInterceptor ] + * | + * +---(6. Pre-Process / Modify / Log) + * | + * +---(7. Forward to Real Target) ----> [ Real Target BBinder ] + * | + * +---(8. Post-Process Reply) + * | + * v + * [ (9) Return Result to libbinder ] + * + + * --- Explanation of the Flow --- + * + * This diagram illustrates a "man-in-the-middle" attack on the Binder framework, achieved + * by hooking the ioctl system call within the application's process. + * + * 1. Waiting for Work: + * An application's binder thread calls `ioctl()` with the `BINDER_WRITE_READ` command. + * This call typically blocks in the kernel, waiting for incoming transactions or other commands. + * + * 2. Kernel Prepares Command: + * When an external process sends a transaction to a service hosted in this application, + * the kernel driver prepares a `BR_TRANSACTION` command and places it in the `read_buffer` + * associated with the waiting `ioctl` call. + * + * 3. Interception on Return: + * The `ioctl()` call returns to userspace. + * Our hook intercepts this return. It now has access to the `read_buffer` + * populated by the kernel *before* `libbinder` gets to see it. + * + * 4. Hijacking: + * The hook parses the `read_buffer`. If it finds a `BR_TRANSACTION` command destined + * for a service that is registered with our `BinderInterceptor`, it rewrites the transaction data in-place. + * Specifically, it changes the target binder handle to that of our `BinderStub` + * and saves the original transaction details in a thread-local map. + * + * 5. Dispatch to Stub: + * The hook then returns control to the original caller, `libbinder`. + * `libbinder` proceeds to parse the now-modified buffer. + * Seeing a transaction for `BinderStub`, it invokes its `onTransact` method. + * + * 6. Pre-Processing: + * The `BinderStub` retrieves the original, unmodified transaction details from the thread-local map. + * It then passes control to the `BinderInterceptor`, which can log, modify, + * or block the transaction before it reaches its real destination. + * + * 7. Forwarding: + * The `BinderInterceptor` forwards the (potentially modified) transaction to the original, + * intended `BBinder` service. + * + * 8. Post-Processing: + * After the real service processes the transaction and generates a reply, + * the reply is returned to the `BinderInterceptor`, + * which gets a final chance to inspect or modify the result. + * + * 9. Return Result: + * The final result is returned up the call stack to `libbinder`, + * which sends the reply back to the kernel driver to be delivered to the original caller. + * + * + * ========================================================================================= +**/ + +using namespace android; + +// ============================================================================================= +// Constants and Protocols +// ============================================================================================= + +namespace { +namespace intercept { + +// Interceptor protocol codes (User space agreement between App and Interceptor Service) +constexpr uint32_t kRegisterInterceptor = 1; +constexpr uint32_t kUnregisterInterceptor = 2; + +constexpr uint32_t kPreTransact = 1; +constexpr uint32_t kPostTransact = 2; + +constexpr uint32_t kActionSkipTransaction = 1; +constexpr uint32_t kActionContinue = 2; +constexpr uint32_t kActionOverrideReply = 3; +constexpr uint32_t kActionOverrideData = 4; +constexpr uint32_t kActionContinueAndSkipPost = 5; + +constexpr uint32_t kBackdoorCode = 0xdeadbeef; + +// Strings for LibBinder hooks +constexpr std::string_view kBinderLibName = "/libbinder.so"; +constexpr std::string_view kIoctlSymbol = "ioctl"; + +} // namespace intercept + +// ============================================================================================= +// Binder Driver Protocol Definitions (Ref: Android Kernel Header) +// ============================================================================================= + +// Use an X-Macro to define a list of all binder return protocols. This allows us +// to generate a string conversion function without a massive, hard-to-maintain switch statement. +#define BINDER_RETURN_COMMAND_LIST(X) \ + X(BR_ERROR) \ + X(BR_OK) \ + X(BR_TRANSACTION_SEC_CTX) \ + X(BR_TRANSACTION) \ + X(BR_REPLY) \ + X(BR_ACQUIRE_RESULT) \ + X(BR_DEAD_REPLY) \ + X(BR_TRANSACTION_COMPLETE) \ + X(BR_INCREFS) \ + X(BR_ACQUIRE) \ + X(BR_RELEASE) \ + X(BR_DECREFS) \ + X(BR_ATTEMPT_ACQUIRE) \ + X(BR_NOOP) \ + X(BR_SPAWN_LOOPER) \ + X(BR_FINISHED) \ + X(BR_DEAD_BINDER) \ + X(BR_CLEAR_DEATH_NOTIFICATION_DONE) \ + X(BR_FAILED_REPLY) \ + X(BR_FROZEN_REPLY) \ + X(BR_ONEWAY_SPAM_SUSPECT) \ + X(BR_TRANSACTION_PENDING_FROZEN) \ + X(BR_FROZEN_BINDER) \ + X(BR_CLEAR_FREEZE_NOTIFICATION_DONE) + +// Helper macro to generate a 'case CMD: return "CMD";' line. +#define GENERATE_CASE_STRING(CMD) \ + case CMD: \ + return #CMD; + +/** + * @brief Converts a binder driver return command code into its string representation. + * @param cmd The command code (e.g., BR_TRANSACTION). + * @return A string literal of the command name or "UNKNOWN_BR_COMMAND". + */ +const char *getBinderReturnCommandName(uint32_t cmd) { + switch (cmd) { + BINDER_RETURN_COMMAND_LIST(GENERATE_CASE_STRING) + default: + return "UNKNOWN_BR_COMMAND"; + } +} + +} // namespace + +// ============================================================================================= +// Global State & Forward Declarations +// ============================================================================================= + +// Original ioctl function pointer +int (*g_original_ioctl)(int fd, int request, ...) = nullptr; + +// Unique ID generator for transactions +static std::atomic g_transaction_id_counter = 0; + +// Context info to pass from the ioctl hook (processBinderWriteRead) to the BinderStub. +struct ThreadTransactionInfo { + uint64_t transaction_id; + uint32_t transaction_code; + wp target_binder; + + // Default constructor + ThreadTransactionInfo() : transaction_id(0), transaction_code(0) {} + + ThreadTransactionInfo(uint64_t id, uint32_t code, wp target) + : transaction_id(id), transaction_code(code), target_binder(std::move(target)) {} +}; + +// A map keyed by thread ID. When ioctl intercepts a transaction intended for us, +// it pushes the info here. When the runtime calls our Stub, it pops the info. +static std::mutex g_thread_context_mutex; +static std::map> g_thread_context_map; + +// ============================================================================================= +// Class: BinderInterceptor +// Logic: Manages the registry of intercepted Binders and handles the protocol (Pre/Post calls). +// ============================================================================================= + +class BinderInterceptor : public BBinder { + struct RegistrationEntry { + wp target; + sp callback_interface; + }; + + // Reader-Writer lock for the registry to allow concurrent reads (lookups) + mutable std::shared_mutex registry_mutex_; + std::map, RegistrationEntry> registry_; + +public: + BinderInterceptor() = default; + + // Checks if a specific Binder instance is currently registered for interception + bool isBinderIntercepted(const wp &target) const { + std::shared_lock lock(registry_mutex_); + return registry_.find(target) != registry_.end(); + } + + // Main entry point for processing the "Man-in-the-Middle" logic + bool processInterceptedTransaction(uint64_t tx_id, sp target, uint32_t code, const Parcel &data, + Parcel *reply, uint32_t flags, status_t &result); + +protected: + // Handle configuration commands sent to the Interceptor itself + status_t onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) override; + +private: + status_t handleRegister(const Parcel &data); + status_t handleUnregister(const Parcel &data); + + // Helpers to serialize data for the remote callback interface + status_t writeTransactionData(Parcel &out, uint64_t tx_id, sp target, uint32_t code, uint32_t flags, + const Parcel &in_data) const; +}; + +static sp g_interceptor_instance = nullptr; + +// ============================================================================================= +// Class: BinderStub +// Logic: The "Dummy" binder that acts as the destination for intercepted calls. +// It retrieves context from the global map and delegates to BinderInterceptor. +// ============================================================================================= + +class BinderStub : public BBinder { +protected: + status_t onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) override { + if (code != intercept::kBackdoorCode) { + LOGE("BinderStub received an unexpected direct call with code %u! This is a bug or misuse.", code); + return UNKNOWN_TRANSACTION; + } + + ThreadTransactionInfo info; + bool found_context = false; + + // 1. Retrieve the context for this thread (set previously by inspectAndRewriteTransaction) + { + std::lock_guard lock(g_thread_context_mutex); + auto it = g_thread_context_map.find(std::this_thread::get_id()); + if (it != g_thread_context_map.end() && !it->second.empty()) { + info = std::move(it->second.front()); + it->second.pop(); + if (it->second.empty()) { + g_thread_context_map.erase(it); // Cleanup to prevent memory leak + } + found_context = true; + } + } + + if (!found_context) { + LOGW("BinderStub received transaction but no context found for thread"); + return UNKNOWN_TRANSACTION; + } + + // 2. Handle special "Backdoor" to get the Interceptor reference + if (info.transaction_code == intercept::kBackdoorCode && info.target_binder == nullptr && reply) { + LOGD("Backdoor handshake received."); + reply->writeStrongBinder(g_interceptor_instance); + return OK; + } + + // 3. Promote the weak reference to the real target + sp real_target = info.target_binder.promote(); + if (!real_target) { + LOGE("[TX_ID: %" PRIu64 "] Target binder is dead.", info.transaction_id); + return DEAD_OBJECT; + } + + // 4. Delegate to the Interceptor logic + status_t status = OK; + bool interceptorManagedFlow = g_interceptor_instance->processInterceptedTransaction( + info.transaction_id, real_target, info.transaction_code, data, reply, flags, status); + + // 5. If Interceptor logic says "Forward it", we call the original binder + if (!interceptorManagedFlow) { + LOGV("[TX_ID: %" PRIu64 "] Forwarding to original implementation.", info.transaction_id); + status = real_target->transact(info.transaction_code, data, reply, flags); + } + + return status; + } +}; + +static sp g_stub_instance = nullptr; + +// ============================================================================================= +// Hook Logic: IOCTL & Buffer Parsing +// ============================================================================================= + +namespace { + +/** + * @brief Analyses a binder transaction. If the target is monitored, + * hijacks the transaction by rewriting its destination to our BinderStub. + * @param txn_data Pointer to the transaction data within the ioctl buffer. + */ +void inspectAndRewriteTransaction(binder_transaction_data *txn_data) { + if (!txn_data || txn_data->target.ptr == 0) + return; + + bool hijack = false; + ThreadTransactionInfo info; + + // Check 1: Root user backdoor for retrieving the interceptor service binder + if (txn_data->code == intercept::kBackdoorCode && txn_data->sender_euid == 0) { + info.transaction_code = intercept::kBackdoorCode; + info.target_binder = nullptr; + hijack = true; + } + // Check 2: Normal interception based on registry of monitored binders + else { + // Safe casting based on Binder driver ABI + RefBase::weakref_type *weak_ref = reinterpret_cast(txn_data->target.ptr); + + // Try to acquire a temporary strong reference to check the object safely + if (weak_ref && weak_ref->attemptIncStrong(nullptr)) { + // The raw pointer to the binder object itself is stored in the cookie + BBinder *target_binder_ptr = reinterpret_cast(txn_data->cookie); + + // This is safe ONLY because we successfully called attemptIncStrong(). + // The sp<> constructor will not increment the ref count again, it just adopts the one we have. + // When sp_target goes out of scope, it will call decStrong(), releasing our temporary reference. + sp sp_target = sp::fromExisting(target_binder_ptr); + + // Now we can safely use sp_target (which implicitly converts to a wp) for the lookup. + if (g_interceptor_instance->isBinderIntercepted(sp_target)) { + info.transaction_code = txn_data->code; + info.target_binder = sp_target; // Assign the valid weak pointer + hijack = true; + } + // No need to manually call decStrong(); the sp destructor handles it. + } + } + + if (hijack) { + uint64_t tx_id = ++g_transaction_id_counter; + info.transaction_id = tx_id; + + LOGV("[Hook] Hijacking Transaction %" PRIu64 " (Code: %u)", tx_id, txn_data->code); + + // Rewrite the destination to our Stub + txn_data->target.ptr = reinterpret_cast(g_stub_instance->getWeakRefs()); + txn_data->cookie = reinterpret_cast(g_stub_instance.get()); + txn_data->code = intercept::kBackdoorCode; + + // Store context for the stub to retrieve later in its onTransact + std::lock_guard lock(g_thread_context_mutex); + g_thread_context_map[std::this_thread::get_id()].push(std::move(info)); + } +} + +/** + * @brief Parses the read buffer from a BINDER_WRITE_READ ioctl call, which contains + * commands sent from the kernel driver to userspace. + * @param bwr The binder_write_read struct containing buffer pointers and sizes. + */ +void processBinderReadBuffer(const binder_write_read &bwr) { + if (bwr.read_size == 0 || bwr.read_consumed == 0 || bwr.read_buffer == 0) + return; + + uintptr_t ptr = bwr.read_buffer; + uintptr_t end = ptr + bwr.read_consumed; + + LOGV("[Hook] Processing Read Buffer: Size=%llu, Consumed=%llu", bwr.read_size, bwr.read_consumed); + + while (ptr < end) { + // Ensure we can read at least the command header + if (end - ptr < sizeof(uint32_t)) + break; + + uint32_t cmd = *reinterpret_cast(ptr); + ptr += sizeof(uint32_t); + + // Calculate payload size from the ioctl command code + size_t cmd_size = _IOC_SIZE(cmd); + + // Log the command using our generated to-string function + LOGV("[Driver -> User] Command: %s (0x%x), DataSize: %zu", getBinderReturnCommandName(cmd), cmd, cmd_size); + + // Safety check: ensure the command's data does not exceed the buffer + if (ptr + cmd_size > end) { + LOGE("[Hook] Buffer overflow detected while parsing command %s", getBinderReturnCommandName(cmd)); + break; + } + + // We are primarily interested in BR_TRANSACTION commands to intercept + if (cmd == BR_TRANSACTION || cmd == BR_TRANSACTION_SEC_CTX) { + binder_transaction_data *txn = nullptr; + + if (cmd == BR_TRANSACTION_SEC_CTX) { + // The data is wrapped in a secctx struct + auto *wrapper = reinterpret_cast(ptr); + txn = &wrapper->transaction_data; + } else { + txn = reinterpret_cast(ptr); + } + + inspectAndRewriteTransaction(txn); + } + + // Advance pointer to the next command + ptr += cmd_size; + } +} + +} // namespace + +// ============================================================================================= +// The Actual Hook Function +// ============================================================================================= + +int intercepted_ioctl(int fd, int request, ...) { + va_list ap; + va_start(ap, request); + void *arg = va_arg(ap, void *); + va_end(ap); + + // 1. Call original kernel ioctl to let the driver do its work + int result = g_original_ioctl(fd, request, arg); + + // 2. After the call returns, check if it was a BINDER_WRITE_READ and if it succeeded + if (result >= 0 && request == BINDER_WRITE_READ && arg != nullptr) { + const auto *bwr = static_cast(arg); + + // We only care about data read FROM the driver (i.e., incoming commands) + if (bwr->read_consumed > 0) { + processBinderReadBuffer(*bwr); + } + } + + return result; +} + +// ============================================================================================= +// BinderInterceptor Implementation +// ============================================================================================= + +// Placed at the top of the .cpp file, inside the BinderInterceptor implementation section. + +#define VALIDATE_STATUS(tx_id, expr) \ + do { \ + status_t __result = (expr); \ + if (__result != OK) { \ + LOGE("[TX_ID: %" PRIu64 "] Parcel operation failed in %s: '%s' returned %d", (tx_id), __func__, #expr, \ + __result); \ + return __result; \ + } \ + } while (0) + +status_t BinderInterceptor::onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) { + switch (code) { + case intercept::kRegisterInterceptor: + return handleRegister(data); + case intercept::kUnregisterInterceptor: + return handleUnregister(data); + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +status_t BinderInterceptor::handleRegister(const Parcel &data) { + sp target; + sp callback; + + if (data.readStrongBinder(&target) != OK || !target) + return BAD_VALUE; + if (data.readStrongBinder(&callback) != OK || !callback) + return BAD_VALUE; + + // We can only intercept local Binders (BBinder), not remote proxies (BpBinder) + if (target->localBinder() == nullptr) { + LOGE("Cannot intercept remote binder proxies."); + return BAD_TYPE; + } + + wp weak_target = target; + + std::unique_lock lock(registry_mutex_); + registry_[weak_target] = {weak_target, callback}; + + LOGI("Interceptor registered for binder %p", target.get()); + return OK; +} + +status_t BinderInterceptor::handleUnregister(const Parcel &data) { + sp target; + if (data.readStrongBinder(&target) != OK || !target) + return BAD_VALUE; + + wp weak_target = target; + + std::unique_lock lock(registry_mutex_); + if (registry_.erase(weak_target) > 0) { + LOGI("Interceptor unregistered for binder %p", target.get()); + return OK; + } + LOGW("Attempted to unregister a non-existent interceptor for binder %p", target.get()); + return NAME_NOT_FOUND; +} + +status_t BinderInterceptor::writeTransactionData(Parcel &out, uint64_t tx_id, sp target, uint32_t code, + uint32_t flags, const Parcel &in_data) const { + // This is the data contract for communicating with the remote analysis/control tool + VALIDATE_STATUS(tx_id, out.writeInt64(tx_id)); + VALIDATE_STATUS(tx_id, out.writeStrongBinder(target)); + VALIDATE_STATUS(tx_id, out.writeUint32(code)); + VALIDATE_STATUS(tx_id, out.writeUint32(flags)); + VALIDATE_STATUS(tx_id, out.writeInt32(IPCThreadState::self()->getCallingUid())); + VALIDATE_STATUS(tx_id, out.writeInt32(IPCThreadState::self()->getCallingPid())); + VALIDATE_STATUS(tx_id, out.writeUint64(in_data.dataSize())); + VALIDATE_STATUS(tx_id, out.appendFrom(&in_data, 0, in_data.dataSize())); + return OK; +} + +bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp target, uint32_t code, + const Parcel &request, Parcel *reply, uint32_t flags, + status_t &result) { + sp callback; + { + std::shared_lock lock(registry_mutex_); + auto it = registry_.find(target); + if (it == registry_.end()) + return false; // Should not happen given logic in hook, but safe + callback = it->second.callback_interface; + } + + // --- Phase 1: Pre-Transaction Callback --- + Parcel pre_req, pre_resp; + writeTransactionData(pre_req, tx_id, target, code, flags, request); + + if (callback->transact(intercept::kPreTransact, pre_req, &pre_resp) != OK) { + LOGW("[TX_ID: %" PRIu64 "] Pre-transaction callback failed. Forwarding original call.", tx_id); + return false; // Callback failed, proceed as if not intercepted + } + + int32_t action = pre_resp.readInt32(); + + // ACTION: Override Reply immediately and skip the real transaction + if (action == intercept::kActionOverrideReply) { + if (reply) { + result = pre_resp.readInt32(); // Read status code from response + size_t size = pre_resp.readUint64(); + reply->setDataSize(0); + reply->appendFrom(&pre_resp, pre_resp.dataPosition(), size); + } + return true; // Handled + } + + // ACTION: Silently skip/drop the transaction + if (action == intercept::kActionSkipTransaction) { + result = OK; // Return OK to caller, but do nothing + return true; // Handled + } + + // ACTION: Skip the post-transaction hook + if (action == intercept::kActionContinueAndSkipPost) { + result = OK; // Return OK to caller, but do nothing + return false; // Forward it + } + + // ACTION: Modify the transaction's request data before forwarding + Parcel final_request; + if (action == intercept::kActionOverrideData) { + size_t size = pre_resp.readUint64(); + final_request.appendFrom(&pre_resp, pre_resp.dataPosition(), size); + } else { + // Default (kActionContinue): Use original data + final_request.appendFrom(&request, 0, request.dataSize()); + } + + // --- Phase 2: Execute Original Transaction --- + result = target->transact(code, final_request, reply, flags); + + // --- Phase 3: Post-Transaction Callback --- + Parcel post_req, post_resp; + writeTransactionData(post_req, tx_id, target, code, flags, final_request); + + // Append the result of the execution for the callback to see + VALIDATE_STATUS(tx_id, post_req.writeInt32(result)); + size_t reply_size = (reply) ? reply->dataSize() : 0; + VALIDATE_STATUS(tx_id, post_req.writeUint64(reply_size)); + if (reply && reply_size > 0) { + VALIDATE_STATUS(tx_id, post_req.appendFrom(reply, 0, reply_size)); + } + + if (callback->transact(intercept::kPostTransact, post_req, &post_resp) == OK) { + int32_t post_action = post_resp.readInt32(); + if (post_action == intercept::kActionOverrideReply && reply) { + result = post_resp.readInt32(); // Read new status + size_t new_size = post_resp.readUint64(); + reply->setDataSize(0); // Clear original reply + VALIDATE_STATUS(tx_id, reply->appendFrom(&post_resp, post_resp.dataPosition(), new_size)); + } + } + + return true; // We handled the flow, even if we just forwarded it +} + +// ============================================================================================= +// Initialization and Entry Point +// ============================================================================================= + +bool initialize_hooks() { + auto maps = lsplt::MapInfo::Scan(); + + dev_t binder_dev = 0; + ino_t binder_ino = 0; + bool found = false; + + for (const auto &map : maps) { + if (map.path.ends_with(intercept::kBinderLibName)) { + binder_dev = map.dev; + binder_ino = map.inode; + found = true; + LOGD("Found libbinder at: %s", map.path.c_str()); + break; + } + } + + if (!found) { + LOGE("Could not find libbinder.so in memory maps."); + return false; + } + + // Instantiate Singleton components + g_interceptor_instance = sp::make(); + g_stub_instance = sp::make(); + + // Register the ioctl hook with LSPLT + lsplt::RegisterHook(binder_dev, binder_ino, intercept::kIoctlSymbol.data(), + reinterpret_cast(intercepted_ioctl), reinterpret_cast(&g_original_ioctl)); + + if (!lsplt::CommitHook()) { + LOGE("lsplt::CommitHook failed."); + return false; + } + + LOGI("Binder interception initialized successfully."); + return true; +} + extern "C" [[gnu::visibility("default")]] [[gnu::used]] -bool entry(void *library_handle) { - return true; +bool entry(void *handle) { + LOGI("Binder Interceptor library loaded (handle: %p)", handle); + return initialize_hooks(); }