引言

ADB,全称Android Debug Bridge。是谷歌推出的一款安卓调试桥程序(工具集),用于安卓软件的开发与调试。在Android Studio中运行程序,就是由ADB在背后把编译好的包推到手机上安装并启动的。
当然,除了安装软件包,它所能做的肯定不止于此。

一些相关背景

你可以用它轻易地控制你的安卓设备,它的权限等级为Shell。除了root外也相当高的权限了,你可以用它卸载一些在用户态无权限被卸载的软件。抑或是修改亮度,设置设备管理员等。
除了消费级产品的标准安卓系统,谷歌也推出了“User-Debug”版的系统供开发者使用。你可以在系统设置的“关于”页看到系统版本,如果里面包含User Debug那就是它的本尊了。区别于消费级手机使用的标准安卓系统,它提供了一个非常特别的东西,那就是root权限
先别急着激动,如果你尝试翻来覆去的找,你会发现根本就找不到类似Magisk、SuperSU、KernelSU之类的常见root管理器。那是因为这个版本的安卓系统,采用的root和我们习惯用的管理器大不相同——它仅供ADB使用。连接设备后输入su指令,可以切换为root,然后运行一些权限更高的命令。当然肯定是不如Magisk等三方root管理器好用,首先它只能ADB用,装不了模块。其次即使是给了你root也有限制,谷歌的小巧思这一块。不过它好歹也算是“系统自带root”了,消费级产品的系统根本就没有root这种东西,本质上获取root其实就是“安装一个su进去”。就ADB能获取root本身而言,这也算不上是冷知识了,基本上用ADB的人提权都用过。这两种root操作方式一模一样。
另外你在UserDebug的系统上刷入面具后,它会报错“已经存在了一个su可能会引发冲突”然后让你卸载其他的su程序。这大概率就是系统自带的root在作祟,去相应的目录把名字是“su”的文件删掉即可(我更倾向于重命名,万一后面还能恢复)

底层架构

如果说获取root然后执行命令是ADB的核心用法之一,那么它自身的冷知识你或许没有深入或者了解过。
使用ADB的过程是什么?手机无论是有线还是无线等无论各种方式连上电脑,然后ADB输入相应命令看设备(adb devices),完事。
那你有没有想过,它的过程究竟是什么?
一般可能会以为在电脑的cmd上输入命令,然后adb程序直接推送指令到手机上。
不,大错特错。

很反常识的一点就在于,ADB其实是一种三层结构
首先,第一层是目标控制设备与电脑。然后就是服务器程序,最后才是你输入指令的cmd(客户机进程)。客户机进程就是adb.exe本身。
简单来说就是cmd(客户机进程)->adb服务器(adb server)->目标设备执行(adbd)。遵循谁敲命令谁就是客户机的原则。
那你可能就会问了,啊如果我在cmd里输入指令实际上是发往adb服务器进程的指令,那么为什么没有要求我配置这个服务器?如何做到无感,几乎让人以为到是电脑上的adb程序直接推送指令到手机上的呢?
很简单,adb服务器在不明确指定的情况下,与adb客户机进程都走127.0.0.1与默认端口5037。而且由于客户机和服务器在同一台宿主机上,所以输入指令本地直达通信,用户无感。

应用:远程ADB控制

然后就有了一套把adb服务器发挥到极致的用法。
下面我来讲一下如何把手机的adb打到公网上,让朋友能远程控制手机。典型应用为两个人物理距离太远,同时自己手里没有什么合适的设备而你刚好就有,临时把操作权交给对方的场景。
首先和基本使用方法一样,用各种方式把手机连接到电脑上。直接有线连接 or 无线调试。

无线调试

这里以无线调试作为例子(有线调试太简单了就是手机拿数据线插电脑上然后执行命令的时候授权而已)
首先手机开启无线调试,然后点击“配对码连接”。记好配对的IP地址和端口以及配对码。然后还有连接地址。
命令如下:

1
2
adb pair 配对地址:配对端口
adb connect 连接地址:连接端口

在第一个命令执行后会要求你输入配对码,请在完成前不要关闭手机上的配对码显示的那个窗口。要不然会变(别问我是怎么知道的)。
连接成功后,你就能像有线连接那样控制设备了。你可以先用adb devices快速验证有没有连上。

配对完成后,如果中途手机重启,重新打开无线调试开关后它会自动重新连接就能继续用了,无需重复授权(注意一下这一块,防止自己以为不能控制引发安全问题)

启动服务器

当然,这只是本地运行,如何远程控制呢?
首先重启adb服务器,并重新作为5037端口开放。

1
2
adb kill-server
adb -a -P 5037 start-server

然后你可以运行如下命令判断是否正常监听了0.0.0.0:5037。

1
netstat -an | findstr :5037

出现了如下内容就成功了,如果啥都没输出就代表没跑起来。

1
TCP    0.0.0.0:5037           0.0.0.0:0              LISTENING

设置ipv6监听与防火墙放行

ADB不支持ipv6地址监听,你需要做v6到内网v4的流量转发才能通过公网v6地址远程。另外别人远程连接的时候自己不会收到任何权限请求,直接就能用,尤其要注意。谁能连到 5037,谁就能操作已授权的全部设备,无需任何凭据
ipv6不能用的原因在于adb 只肯为 localhost 拉起 server,-L 一旦指向非 localhost 地址,它直接判定为「远程主机」拒绝启动;而 -a 只能绑 0.0.0.0,是纯 v4。

然后呢,我让AI写了个v6到内网v4的转发,这样来自v6网络的访问也能正常使用ADB服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/usr/bin/env python3
"""ADB IPv6 -> IPv4 TCP proxy.
Listens on :::5037 and forwards to 127.0.0.1:5037 (the adb server).
"""
import asyncio
import socket
import sys

LOCAL_ADDR = '::'
LOCAL_PORT = 5037
REMOTE_ADDR = '127.0.0.1'
REMOTE_PORT = 5037


async def pipe(reader, writer):
try:
while True:
data = await reader.read(65536)
if not data:
break
writer.write(data)
await writer.drain()
except (ConnectionResetError, BrokenPipeError, asyncio.CancelledError):
pass
finally:
try:
writer.close()
await writer.wait_closed()
except Exception:
pass


async def handle(client_reader, client_writer):
peer = client_writer.get_extra_info('peer_name')
print(f'[adb v6 proxy] connection from {peer}')
remote_reader = remote_writer = None
try:
remote_reader, remote_writer = await asyncio.open_connection(REMOTE_ADDR, REMOTE_PORT)
await asyncio.gather(
pipe(client_reader, remote_writer),
pipe(remote_reader, client_writer)
)
except Exception as e:
print(f'[adb v6 proxy] error for {peer}: {e}')
finally:
if remote_writer:
try:
remote_writer.close()
await remote_writer.wait_closed()
except Exception:
pass
client_writer.close()
try:
await client_writer.wait_closed()
except Exception:
pass


async def main():
server = await asyncio.start_server(
handle, LOCAL_ADDR, LOCAL_PORT,
family=socket.AF_INET6,
reuse_address=True
)
print(f'[adb v6 proxy] listening on [{LOCAL_ADDR}]:{LOCAL_PORT} -> {REMOTE_ADDR}:{REMOTE_PORT}')
print('[adb v6 proxy] press Ctrl+C to stop')
async with server:
await server.serve_forever()


if __name__ == '__main__':
try:
asyncio.run(main())
except KeyboardInterrupt:
print('\n[adb v6 proxy] stopped')
sys.exit(0)

(服务端运行这个脚本,将会自动转发流量到ADB服务器。当然如果你有ipv4公网就可以不用跑这个,这个是给ipv6用的。如果你的端口不是5037别忘了改脚本里的为你实际的端口)
最后别忘了防火墙的问题,要不然会被拦截:

1
New-NetFirewallRule -Name "adb-v6" -DisplayName "adb-v6" -Direction Inbound -Protocol TCP -LocalPort 5037 -RemoteAddress Any -Action Allow

上面这个策略是允许所有IP通过,任何人只要扫到IP都能访问你的设备,还是不太安全。推荐指定放行来源IP段

1
2
New-NetFirewallRule -Name "adb-v6" -DisplayName "adb-v6" -Direction Inbound `
-Protocol TCP -LocalPort 5037 -RemoteAddress 2409:xxxx::/64 -Action Allow

2409:xxxx::/64 为允许的来源IP(或IP段),改成实际的你授权的那个人的就可以,减少暴露面,防止攻击。
(依然是如果你的端口不是5037记得替换)

远程结束后用完后的清理

恢复防火墙策略

1
netsh advfirewall firewall delete rule name="adb-v6"

停止ADB服务器,所有客户机会被踢下线

1
adb kill-server

如果你用了那个python转发器,直接关掉即可。同时记得在开发者选项里关无线调试和撤销ADB调试授权

客户机指令

1
adb -H 有服务器的远程地址 -P 5037 devices

运行后会列出远程计算机上ADB连接的所有设备,5037是端口号。
其实本质上来说就是指定发送指令到的目标服务器而已,不指定默认就是本机ADB。每次指令输入都要带指定服务器的指令前缀。
比如这些例子:

1
2
3
adb -H example.com -P 5037 shell
adb -H example.com -P 5037 logcat
adb -H example.com -P 5037 install app.apk

如果不想每次都带,可以选择设置环境变量
ADB_SERVER_SOCKET = “tcp:example.com:5037”

完事,这种就属于ADB的进阶使用了。想不到吧,其实ADB也能远程控制。
基本上只要知道ADB的运行原理之后就能做到更精细的和自由的自动化与控制,当然也别忘了注意网络安全。