
CVE-2026-0073 ist eine RCE mit einem CVSS-Schweregrad von 8,3, und hier erklären wir, wie sie funktioniert.
CVE-2026-0073 ist eine RCE mit einem CVSS-Schweregrad von 8,3, und hier erklären wir, wie sie funktioniert.
Zunächst ist das Einzige, was möglicherweise interessant sein könnte, mehr über den Bug selbst zu erfahren.
Meine erste Quelle ist: https://www.tenable.com/cve/CVE-2026-0073
Diese Quelle sagt uns, dass die Datei auth.cpp eine Funktion namens adbd_tls_verify_cert zeigt, die einen Ablauf hat, der eine nicht authentifizierte RCE ermöglicht.
Das Hauptziel ist es nun, den Quellcode von auth.cpp zu erhalten.
Da ich ihn in keinem offiziellen Repository finden konnte, habe ich dieses Repository von Project-Awaken gefunden unter: https://github.com/Project-Awaken/android_packages_modules_adb/blob/12/daemon/auth.cpp
und da ist sie! unsere geheimnisvolle Funktion:
int adbd_tls_verify_cert(X509_STORE_CTX* ctx, std::string* auth_key) {
if (!auth_required) {
// Any key will do.
LOG(INFO) << __func__ << ": auth not required";
return 1;
}
bool authorized = false;
X509* cert = X509_STORE_CTX_get0_cert(ctx);
if (cert == nullptr) {
LOG(INFO) << "got null x509 certificate";
return 0;
}
bssl::UniquePtr<EVP_PKEY> evp_pkey(X509_get_pubkey(cert));
if (evp_pkey == nullptr) {
LOG(INFO) << "got null evp_pkey from x509 certificate";
return 0;
}
IteratePublicKeys([&](std::string_view public_key) {
// TODO: do we really have to support both ' ' and '\t'?
std::vector<std::string> split = android::base::Split(std::string(public_key), " \t");
uint8_t keybuf[ANDROID_PUBKEY_ENCODED_SIZE + 1];
const std::string& pubkey = split[0];
if (b64_pton(pubkey.c_str(), keybuf, sizeof(keybuf)) != ANDROID_PUBKEY_ENCODED_SIZE) {
LOG(ERROR) << "Invalid base64 key " << pubkey;
return true;
}
RSA* key = nullptr;
if (!android_pubkey_decode(keybuf, ANDROID_PUBKEY_ENCODED_SIZE, &key)) {
LOG(ERROR) << "Failed to parse key " << pubkey;
return true;
}
bool verified = false;
bssl::UniquePtr<EVP_PKEY> known_evp(EVP_PKEY_new());
EVP_PKEY_set1_RSA(known_evp.get(), key);
if (EVP_PKEY_cmp(known_evp.get(), evp_pkey.get())) {
LOG(INFO) << "Matched auth_key=" << public_key;
verified = true;
} else {
LOG(INFO) << "auth_key doesn't match [" << public_key << "]";
}
RSA_free(key);
if (verified) {
*auth_key = public_key;
authorized = true;
return false;
}
return true;
});
return authorized ? 1 : 0;
}
Aber der interessante Teil der Funktion ist der folgende:
bool verified = false;
bssl::UniquePtr<EVP_PKEY> known_evp(EVP_PKEY_new());
EVP_PKEY_set1_RSA(known_evp.get(), key);
if (EVP_PKEY_cmp(known_evp.get(), evp_pkey.get())) {
LOG(INFO) << "Matched auth_key=" << public_key;
verified = true;
} else {
LOG(INFO) << "auth_key doesn't match [" << public_key << "]";
}
Warum? Weil verified als ein Boolean gesetzt wird, der die Verbindung erlaubt oder nicht, und er wird nur mit der "if"-Anweisung geändert. Das Problem? Das EVP_PKEY_cmp(known_evp.get(), evp_pkey.get()) gibt nicht immer 1 oder 0 zurück, sondern auch -1 und -2, weil EVP_PKEY_cmp() 1 zurückgibt, wenn die Schlüssel übereinstimmen, 0, wenn sie nicht übereinstimmen, -1, wenn die Schlüsseltypen unterschiedlich sind, und -2, wenn die Operation nicht unterstützt wird.
Es validiert also die Zertifikate unterschiedlicher Schlüssel oder wenn die Operation nicht unterstützt wird, obwohl der Schlüssel keine Übereinstimmung ist.
Um unsere Sitzung zu validieren, müssen wir eine dieser beiden Optionen erzwingen.
Um zu versuchen, diesen Fehler auszunutzen, müssen wir eine TLS-Sitzung aufbauen.
Das funktioniert nur, wenn sowohl der Angreifer als auch das Zielgerät TLS verwenden können. Die Entscheidung, TLS zu verwenden, wird jedoch nur vom Zielgerät getroffen.
Wenn das Zielgerät TLS erlaubt, kannst du eine TLS-Sitzung aufbauen.
Es passiert alles hier:
Lass uns versuchen, es zum Laufen zu bringen