Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
80 changes: 68 additions & 12 deletions app/src/main/cpp/binder_interceptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ class BinderInterceptor : public BBinder {
struct RegistrationEntry {
wp<IBinder> target;
sp<IBinder> callback_interface;
// Transaction codes to intercept. Empty = intercept all (legacy behavior).
std::vector<uint32_t> filtered_codes;
};

// Reader-Writer lock for the registry to allow concurrent reads (lookups)
Expand All @@ -244,10 +246,15 @@ class BinderInterceptor : public BBinder {
public:
BinderInterceptor() = default;

// Checks if a specific Binder instance is currently registered for interception
bool isBinderIntercepted(const wp<BBinder> &target) const {
// Checks if a specific Binder+code combination should be intercepted.
// Returns true if the binder is registered AND the code is in its filter
// (or the filter is empty, meaning intercept everything).
bool shouldIntercept(const wp<BBinder> &target, uint32_t code) const {
std::shared_lock lock(registry_mutex_);
return registry_.find(target) != registry_.end();
auto it = registry_.find(target);
if (it == registry_.end()) return false;
const auto &codes = it->second.filtered_codes;
return codes.empty() || std::find(codes.begin(), codes.end(), code) != codes.end();
}

// Main entry point for processing the "Man-in-the-Middle" logic
Expand Down Expand Up @@ -307,7 +314,11 @@ class BinderStub : public BBinder {
}

if (!found_context) {
LOGW("BinderStub received transaction but no context found for thread");
LOGW("BinderStub received transaction but no context found for thread (code=%u)", code);
#ifndef NDEBUG
std::lock_guard<std::mutex> dbg_lock(g_thread_context_mutex);
LOGW(" Thread context map has %zu entries", g_thread_context_map.size());
#endif
return UNKNOWN_TRANSACTION;
}

Expand Down Expand Up @@ -386,13 +397,16 @@ void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {
// This is safe because we are holding a strong reference.
wp<BBinder> wp_target = target_binder_ptr;

if (g_interceptor_instance->isBinderIntercepted(wp_target)) {
if (g_interceptor_instance->shouldIntercept(wp_target, txn_data->code)) {
info.transaction_code = txn_data->code;
info.target_binder = wp_target; // Assign the valid weak pointer
hijack = true;
}
// Manually release the temporary strong reference we acquired at the start.
target_binder_ptr->decStrong(nullptr);
} else {
LOGD("[Hook] attemptIncStrong failed for target %p (code=%u, uid=%d) — binder may be dying",
reinterpret_cast<void*>(txn_data->target.ptr), txn_data->code, txn_data->sender_euid);
}
}

Expand All @@ -409,7 +423,13 @@ void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {

// Store context for the stub to retrieve later in its onTransact
std::lock_guard<std::mutex> lock(g_thread_context_mutex);
g_thread_context_map[std::this_thread::get_id()].push(std::move(info));
auto &queue = g_thread_context_map[std::this_thread::get_id()];
queue.push(std::move(info));
#ifndef NDEBUG
if (queue.size() > 8) {
LOGW("[Hook] Thread context queue depth=%zu for thread — possible leak", queue.size());
}
#endif
}
}

Expand Down Expand Up @@ -537,12 +557,26 @@ status_t BinderInterceptor::handleRegister(const Parcel &data) {
return BAD_TYPE;
}

// Read optional transaction code filter. If present: int32 count + count * uint32 codes.
// If absent or count <= 0: intercept all transaction codes (legacy behavior).
std::vector<uint32_t> codes;
int32_t code_count = 0;
if (data.dataAvail() >= sizeof(int32_t) && data.readInt32(&code_count) == OK && code_count > 0) {
codes.reserve(code_count);
for (int32_t i = 0; i < code_count; i++) {
uint32_t c = 0;
if (data.readUint32(&c) == OK) codes.push_back(c);
}
LOGI("Interceptor registered for binder %p with %zu filtered codes", target.get(), codes.size());
} else {
LOGI("Interceptor registered for binder %p (all codes)", target.get());
}

wp<IBinder> weak_target = target;

std::unique_lock lock(registry_mutex_);
registry_[weak_target] = {weak_target, callback};
registry_[weak_target] = {weak_target, callback, std::move(codes)};

LOGI("Interceptor registered for binder %p", target.get());
return OK;
}

Expand Down Expand Up @@ -592,8 +626,24 @@ bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder
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);
#ifndef NDEBUG
struct timespec ts_start{};
clock_gettime(CLOCK_MONOTONIC, &ts_start);
#endif

status_t pre_cb_status = callback->transact(intercept::kPreTransact, pre_req, &pre_resp);

#ifndef NDEBUG
struct timespec ts_end{};
clock_gettime(CLOCK_MONOTONIC, &ts_end);
double pre_ms = (ts_end.tv_sec - ts_start.tv_sec) * 1000.0 + (ts_end.tv_nsec - ts_start.tv_nsec) / 1e6;
if (pre_ms > 5000.0) {
LOGW("[TX_ID: %" PRIu64 "] Pre-callback took %.0fms (code=%u) — possible hang", tx_id, pre_ms, code);
}
#endif

if (pre_cb_status != OK) {
LOGW("[TX_ID: %" PRIu64 "] Pre-transaction callback failed (status=%d). Forwarding original call.", tx_id, pre_cb_status);
return false; // Callback failed, proceed as if not intercepted
}

Expand Down Expand Up @@ -627,8 +677,10 @@ bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder
if (action == intercept::kActionOverrideData) {
size_t size = pre_resp.readUint64();
final_request.appendFrom(&pre_resp, pre_resp.dataPosition(), size);
} else if (action == intercept::kActionContinue) {
final_request.appendFrom(&request, 0, request.dataSize());
} else {
// Default (kActionContinue): Use original data
LOGW("[TX_ID: %" PRIu64 "] Unknown pre-callback action %d (code=%u). Forwarding original data.", tx_id, action, code);
final_request.appendFrom(&request, 0, request.dataSize());
}

Expand All @@ -647,14 +699,18 @@ bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder
VALIDATE_STATUS(tx_id, post_req.appendFrom(reply, 0, reply_size));
}

if (callback->transact(intercept::kPostTransact, post_req, &post_resp) == OK) {
status_t post_cb_status = callback->transact(intercept::kPostTransact, post_req, &post_resp);
if (post_cb_status == 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));
}
} else {
LOGW("[TX_ID: %" PRIu64 "] Post-transaction callback failed (status=%d, code=%u). Using original reply.",
tx_id, post_cb_status, code);
}

return true; // We handled the flow, even if we just forwarded it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,10 +418,11 @@ object AttestationBuilder {
)
)

// Collect unique signature digests from the signing history.
packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature ->
val digest = sha256.digest(signature.toByteArray())
signatureDigests.add(Digest(digest))
val certs =
packageInfo.signingInfo?.signingCertificateHistory
?: packageInfo.signingInfo?.apkContentsSigners
certs?.forEach { signature ->
signatureDigests.add(Digest(sha256.digest(signature.toByteArray())))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,60 +41,84 @@ data class KeyMintAttestation(
val manufacturer: ByteArray?,
val model: ByteArray?,
val secondImei: ByteArray?,
// key_parameter.rs: l=855..1041 (RSA_OAEP_MGF_DIGEST through MAX_BOOT_LEVEL)
val userAuthType: Int? = null,
val userConfirmationRequired: Boolean? = null,
val activeDateTime: Date? = null,
val originationExpireDateTime: Date? = null,
val usageExpireDateTime: Date? = null,
val usageCountLimit: Int? = null,
val callerNonce: Boolean? = null,
val unlockedDeviceRequired: Boolean? = null,
val includeUniqueId: Boolean? = null,
val rollbackResistance: Boolean? = null,
val earlyBootOnly: Boolean? = null,
val allowWhileOnBody: Boolean? = null,
val trustedUserPresenceRequired: Boolean? = null,
val trustedConfirmationRequired: Boolean? = null,
val maxUsesPerBoot: Int? = null,
val maxBootLevel: Int? = null,
val minMacLength: Int? = null,
val rsaOaepMgfDigest: List<Int> = emptyList(),
) {
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
constructor(
params: Array<KeyParameter>
) : this(
// AOSP: [key_param(tag = ALGORITHM, field = Algorithm)]
// AOSP: [key_param(tag = ALGORITHM, field = Algorithm)] (key_parameter.rs: l=837)
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,

// AOSP: [key_param(tag = KEY_SIZE, field = Integer)]
// For EC keys, derive keySize from EC_CURVE when KEY_SIZE is absent.
// https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/ec_key_factory.cpp;l=54
keySize = params.findInteger(Tag.KEY_SIZE) ?: params.deriveKeySizeFromCurve(),

// AOSP: [key_param(tag = EC_CURVE, field = EcCurve)]
// AOSP: [key_param(tag = EC_CURVE, field = EcCurve)] (key_parameter.rs: l=871)
ecCurve = params.findEcCurve(Tag.EC_CURVE),
ecCurveName = params.deriveEcCurveName(),

// AOSP: [key_param(tag = ORIGIN, field = Origin)]
// AOSP: [key_param(tag = ORIGIN, field = Origin)] (key_parameter.rs: l=955)
origin = params.findOrigin(Tag.ORIGIN),

// AOSP: [key_param(tag = NO_AUTH_REQUIRED, field = BoolValue)]
// AOSP: [key_param(tag = NO_AUTH_REQUIRED, field = BoolValue)] (key_parameter.rs: l=917)
noAuthRequired = params.findBoolean(Tag.NO_AUTH_REQUIRED),

// AOSP: [key_param(tag = BLOCK_MODE, field = BlockMode)]
// AOSP: [key_param(tag = BLOCK_MODE, field = BlockMode)] (key_parameter.rs: l=845)
blockMode = params.findAllBlockMode(Tag.BLOCK_MODE),

// AOSP: [key_param(tag = PADDING, field = PaddingMode)]
// AOSP: [key_param(tag = PADDING, field = PaddingMode)] (key_parameter.rs: l=860)
padding = params.findAllPaddingMode(Tag.PADDING),

// AOSP: [key_param(tag = PURPOSE, field = KeyPurpose)]
// AOSP: [key_param(tag = PURPOSE, field = KeyPurpose)] (key_parameter.rs: l=832)
purpose = params.findAllKeyPurpose(Tag.PURPOSE),

// AOSP: [key_param(tag = DIGEST, field = Digest)]
// AOSP: [key_param(tag = DIGEST, field = Digest)] (key_parameter.rs: l=850)
digest = params.findAllDigests(Tag.DIGEST),

// AOSP: [key_param(tag = RSA_PUBLIC_EXPONENT, field = LongInteger)]
// AOSP: [key_param(tag = RSA_PUBLIC_EXPONENT, field = LongInteger)] (key_parameter.rs:
// l=874)
rsaPublicExponent = params.findLongInteger(Tag.RSA_PUBLIC_EXPONENT),

// AOSP: [key_param(tag = CERTIFICATE_SERIAL, field = Blob)]
// AOSP: [key_param(tag = CERTIFICATE_SERIAL, field = Blob)] (key_parameter.rs: l=1028)
certificateSerial = params.findBlob(Tag.CERTIFICATE_SERIAL)?.let { BigInteger(it) },

// AOSP: [key_param(tag = CERTIFICATE_SUBJECT, field = Blob)]
// AOSP: [key_param(tag = CERTIFICATE_SUBJECT, field = Blob)] (key_parameter.rs: l=1032)
certificateSubject =
params.findBlob(Tag.CERTIFICATE_SUBJECT)?.let { X500Name(X500Principal(it).name) },

// AOSP: [key_param(tag = CERTIFICATE_NOT_BEFORE, field = DateTime)]
// AOSP: [key_param(tag = CERTIFICATE_NOT_BEFORE, field = DateTime)] (key_parameter.rs:
// l=1035)
certificateNotBefore = params.findDate(Tag.CERTIFICATE_NOT_BEFORE),

// AOSP: [key_param(tag = CERTIFICATE_NOT_AFTER, field = DateTime)]
// AOSP: [key_param(tag = CERTIFICATE_NOT_AFTER, field = DateTime)] (key_parameter.rs:
// l=1038)
certificateNotAfter = params.findDate(Tag.CERTIFICATE_NOT_AFTER),

// AOSP: [key_param(tag = ATTESTATION_CHALLENGE, field = Blob)]
// AOSP: [key_param(tag = ATTESTATION_CHALLENGE, field = Blob)] (key_parameter.rs: l=970)
attestationChallenge = params.findBlob(Tag.ATTESTATION_CHALLENGE),

// AOSP: [key_param(tag = ATTESTATION_ID_*, field = Blob)]
// AOSP: [key_param(tag = ATTESTATION_ID_*, field = Blob)] (key_parameter.rs: l=976, 991,
// 1000)
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
product = params.findBlob(Tag.ATTESTATION_ID_PRODUCT),
Expand All @@ -104,6 +128,27 @@ data class KeyMintAttestation(
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
// Enforcement tags.
// key_parameter.rs: l=855..1041 (RSA_OAEP_MGF_DIGEST through MAX_BOOT_LEVEL)
userAuthType = params.findInteger(Tag.USER_AUTH_TYPE),
userConfirmationRequired = params.findBoolean(Tag.USER_SECURE_ID),
activeDateTime = params.findDate(Tag.ACTIVE_DATETIME),
originationExpireDateTime = params.findDate(Tag.ORIGINATION_EXPIRE_DATETIME),
usageExpireDateTime = params.findDate(Tag.USAGE_EXPIRE_DATETIME),
usageCountLimit = params.findInteger(Tag.USAGE_COUNT_LIMIT),
callerNonce = params.findBoolean(Tag.CALLER_NONCE),
unlockedDeviceRequired = params.findBoolean(Tag.UNLOCKED_DEVICE_REQUIRED),
includeUniqueId = params.findBoolean(Tag.INCLUDE_UNIQUE_ID),
rollbackResistance = params.findBoolean(Tag.ROLLBACK_RESISTANCE),
earlyBootOnly = params.findBoolean(Tag.EARLY_BOOT_ONLY),
allowWhileOnBody = params.findBoolean(Tag.ALLOW_WHILE_ON_BODY),
trustedUserPresenceRequired = params.findBoolean(Tag.TRUSTED_USER_PRESENCE_REQUIRED),
trustedConfirmationRequired = params.findBoolean(Tag.TRUSTED_CONFIRMATION_REQUIRED),
maxUsesPerBoot = params.findInteger(Tag.MAX_USES_PER_BOOT),
maxBootLevel = params.findInteger(Tag.MAX_BOOT_LEVEL),
minMacLength = params.findInteger(Tag.MIN_MAC_LENGTH),
// key_parameter.rs: l=855
rsaOaepMgfDigest = params.findAllDigests(Tag.RSA_OAEP_MGF_DIGEST),
) {
// Log all parsed parameters for debugging purposes.
params.forEach { KeyMintParameterLogger.logParameter(it) }
Expand Down Expand Up @@ -168,12 +213,17 @@ private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.digest }

/** Derives keySize from EC_CURVE tag when KEY_SIZE is not explicitly provided. */
/**
* Derives keySize from EC_CURVE tag when KEY_SIZE is not explicitly provided.
*
* https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/ec_key_factory.cpp;l=54
*/
private fun Array<KeyParameter>.deriveKeySizeFromCurve(): Int {
val curveId = this.find { it.tag == Tag.EC_CURVE }?.value?.ecCurve ?: return 0
return when (curveId) {
EcCurve.P_224 -> 224
EcCurve.P_256, EcCurve.CURVE_25519 -> 256
EcCurve.P_256,
EcCurve.CURVE_25519 -> 256
EcCurve.P_384 -> 384
EcCurve.P_521 -> 521
else -> 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,18 @@ object ConfigurationManager {
return iPackageManager
}

/** Checks if any package belonging to the UID holds the given permission. */
fun hasPermissionForUid(uid: Int, permission: String): Boolean {
val userId = uid / 100000
return getPackagesForUid(uid).any { pkg ->
try {
getPackageManager()?.checkPermission(permission, pkg, userId) == 0
} catch (_: Exception) {
false
}
}
}

/** Retrieves the package names associated with a UID. */
fun getPackagesForUid(uid: Int): Array<String> {
return uidToPackagesCache.getOrPut(uid) {
Expand Down
Loading
Loading