adb 实现原理
PC adb 建立连接流程
1、PC端 adb-client 和 adb-server 通过 socket 建立 C/S 连接;
2、adb-server 和手机端的 adbd 通过 socke 建立 C/S 连接, 客户端变成了 adb-server
相关项目 shizuku
App通过无线调试连接到 adbd 的流程
1、mdns 扫描可以找到已配对连接的客户端; 先注册监听
2、进行配对码配对连接
3、与 adbd 进行 tcp 连接
4、tcp 传输数据包结构;
1、mdns 扫描可以找到已配对连接的客户端;
package moe.shizuku.manager.adb
//import androidx.lifecycle.MutableLiveData
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.ServerSocket
@RequiresApi(Build.VERSION_CODES.R)
class AdbMdns(
context: Context, private val serviceType: String,
private val port: PortListener
) {
private var registered = false
var running = false
private var serviceName: String? = null
private val listener: DiscoveryListener
private val nsdManager: NsdManager = context.getSystemService(NsdManager::class.java)
// private val nsdManager: NsdManager =
// context.getSystemService("servicediscovery") as NsdManager//10.19
fun start() {
running = true
if (!registered) {
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener)
}
}
fun stop() {
running = false
if (registered) {
nsdManager.stopServiceDiscovery(listener)
}
}
private fun onDiscoveryStart() {
registered = true
}
private fun onDiscoveryStop() {
registered = false
}
private fun onServiceFound(info: NsdServiceInfo) {
nsdManager.resolveService(info, ResolveListener(this))
}
private fun onServiceLost(info: NsdServiceInfo) {
if (info.serviceName == serviceName)
port.postValue(info.host, -1, false)
}
private fun onStartDiscoveryFailed(str: String,i: Int){
Log.e("my_tag", "onStartDiscoveryFailed :$str :$i")
} private fun onResolveFailed(resolvedService: NsdServiceInfo,i: Int){
Log.e("my_tag", "onResolveFailed :$resolvedService :$i")
}
private fun onServiceResolved(resolvedService: NsdServiceInfo) {
Log.e("my_tag", "resolvedService:$resolvedService")
// if (running && NetworkInterface.getNetworkInterfaces()
// .asSequence()
// .any { networkInterface ->
// networkInterface.inetAddresses
// .asSequence()
// .any { resolvedService.host.hostAddress == it.hostAddress }
// }
//// && isPortAvailable(resolvedService.port)
// ) {
// serviceName = resolvedService.serviceName
// port.postValue(
// resolvedService.host,
// resolvedService.port,
// isPortAvailable(resolvedService.port)
// )
// } else {
//todo 11.2 3:17
port.postValue(
resolvedService.host,
resolvedService.port,
isPortAvailable(resolvedService.port)
)
// }
}
fun getAddress(): String {
if (Build.PRODUCT.contains("sdk")
|| Build.HARDWARE.contains("goldfish")
|| Build.HARDWARE.contains("ranchu")
// || Settings.Secure.getString(context.contentResolver, "android_id") == null
) {
return "10.0.2.2"
}
var hostAddress = InetAddress.getLoopbackAddress().hostAddress
if (hostAddress == null || hostAddress.equals("::1"))
hostAddress = "127.0.0.1"
// println("host Addr:" + hostAddress)
return hostAddress
}
private fun isPortAvailable(port: Int) = try {
ServerSocket().use {
// it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), port), 1)
// it.bind(InetSocketAddress("127.0.0.1", port), 1)//shizuku 3月更新
it.bind(InetSocketAddress(getAddress(), port), 1)//10.19
false
}
} catch (e: IOException) {
true
}
internal class DiscoveryListener(private val adbMdns: AdbMdns) : NsdManager.DiscoveryListener {
override fun onDiscoveryStarted(str: String) {
adbMdns.onDiscoveryStart()
}
override fun onStartDiscoveryFailed(str: String, i: Int) {
adbMdns.onStartDiscoveryFailed(str,i)
}
override fun onDiscoveryStopped(str: String) {
adbMdns.onDiscoveryStop()
}
override fun onStopDiscoveryFailed(str: String, i: Int) {}
override fun onServiceFound(nsdServiceInfo: NsdServiceInfo) {
adbMdns.onServiceFound(nsdServiceInfo)
}
override fun onServiceLost(nsdServiceInfo: NsdServiceInfo) {
adbMdns.onServiceLost(nsdServiceInfo)
}
}
internal class ResolveListener(private val adbMdns: AdbMdns) : NsdManager.ResolveListener {
override fun onResolveFailed(nsdServiceInfo: NsdServiceInfo, i: Int) {
adbMdns.onResolveFailed(nsdServiceInfo,i)
}
override fun onServiceResolved(nsdServiceInfo: NsdServiceInfo) {
adbMdns.onServiceResolved(nsdServiceInfo)
}
}
companion object {
const val TLS_CONNECT = "_adb-tls-connect._tcp"
const val TLS_PAIRING = "_adb-tls-pairing._tcp"
const val TLS_ADB = "_adb._tcp"
}
init {
listener = DiscoveryListener(this)
}
}
2、进行配对码配对
package moe.shizuku.manager.adb
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.android.org.conscrypt.Conscrypt
import java.io.Closeable
import java.io.DataInputStream
import java.io.DataOutputStream
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import javax.net.ssl.SSLSocket
private const val TAG = "AdbPairClient"
private const val kCurrentKeyHeaderVersion = 1.toByte()
private const val kMinSupportedKeyHeaderVersion = 1.toByte()
private const val kMaxSupportedKeyHeaderVersion = 1.toByte()
private const val kMaxPeerInfoSize = 8192
private const val kMaxPayloadSize = kMaxPeerInfoSize * 2
private const val kExportedKeyLabel = "adb-label\u0000"
private const val kExportedKeySize = 64
private const val kPairingPacketHeaderSize = 6
private class PeerInfo(
val type: Byte,
data: ByteArray) {
val data = ByteArray(kMaxPeerInfoSize - 1)
init {
data.copyInto(this.data, 0, 0, data.size.coerceAtMost(kMaxPeerInfoSize - 1))
}
enum class Type(val value: Byte) {
ADB_RSA_PUB_KEY(0.toByte()),
ADB_DEVICE_GUID(0.toByte()),
}
fun writeTo(buffer: ByteBuffer) {
buffer.run {
put(type)
put(data)
}
Log.d(TAG, "write PeerInfo ${toStringShort()}")
}
override fun toString(): String {
return "PeerInfo(${toStringShort()})"
}
fun toStringShort(): String {
return "type=$type, data=${data.contentToString()}"
}
companion object {
fun readFrom(buffer: ByteBuffer): PeerInfo {
val type = buffer.get()
val data = ByteArray(kMaxPeerInfoSize - 1)
buffer.get(data)
return PeerInfo(type, data)
}
}
}
private class PairingPacketHeader(
val version: Byte,
val type: Byte,
val payload: Int) {
enum class Type(val value: Byte) {
SPAKE2_MSG(0.toByte()),
PEER_INFO(1.toByte())
}
fun writeTo(buffer: ByteBuffer) {
buffer.run {
put(version)
put(type)
putInt(payload)
}
Log.d(TAG, "write PairingPacketHeader ${toStringShort()}")
}
override fun toString(): String {
return "PairingPacketHeader(${toStringShort()})"
}
fun toStringShort(): String {
return "version=${version.toInt()}, type=${type.toInt()}, payload=$payload"
}
companion object {
fun readFrom(buffer: ByteBuffer): PairingPacketHeader? {
val version = buffer.get()
val type = buffer.get()
val payload = buffer.int
if (version < kMinSupportedKeyHeaderVersion || version > kMaxSupportedKeyHeaderVersion) {
Log.e(TAG, "PairingPacketHeader version mismatch (us=$kCurrentKeyHeaderVersion them=${version})")
return null
}
if (type != Type.SPAKE2_MSG.value && type != Type.PEER_INFO.value) {
Log.e(TAG, "Unknown PairingPacket type=${type}")
return null
}
if (payload <= 0 || payload > kMaxPayloadSize) {
Log.e(TAG, "header payload not within a safe payload size (size=${payload})")
return null
}
val header = PairingPacketHeader(version, type, payload)
Log.d(TAG, "read PairingPacketHeader ${header.toStringShort()}")
return header
}
}
}
private class PairingContext private constructor(private val nativePtr: Long) {
val msg: ByteArray
init {
msg = nativeMsg(nativePtr)
}
fun initCipher(theirMsg: ByteArray) = nativeInitCipher(nativePtr, theirMsg)
fun encrypt(`in`: ByteArray) = nativeEncrypt(nativePtr, `in`)
fun decrypt(`in`: ByteArray) = nativeDecrypt(nativePtr, `in`)
fun destroy() = nativeDestroy(nativePtr)
private external fun nativeMsg(nativePtr: Long): ByteArray
private external fun nativeInitCipher(nativePtr: Long, theirMsg: ByteArray): Boolean
private external fun nativeEncrypt(nativePtr: Long, inbuf: ByteArray): ByteArray?
private external fun nativeDecrypt(nativePtr: Long, inbuf: ByteArray): ByteArray?
private external fun nativeDestroy(nativePtr: Long)
companion object {
fun create(password: ByteArray): PairingContext? {
val nativePtr = nativeConstructor(true, password)
return if (nativePtr != 0L) PairingContext(nativePtr) else null
}
@JvmStatic
private external fun nativeConstructor(isClient: Boolean, password: ByteArray): Long
}
}
@RequiresApi(Build.VERSION_CODES.R)
class AdbPairingClient(private val host: String, private val port: Int, private val pairCode: String, private val key: AdbKey) : Closeable {
private enum class State {
Ready,
ExchangingMsgs,
ExchangingPeerInfo,
Stopped
}
private lateinit var socket: Socket
private lateinit var inputStream: DataInputStream
private lateinit var outputStream: DataOutputStream
private val peerInfo: PeerInfo = PeerInfo(PeerInfo.Type.ADB_RSA_PUB_KEY.value, key.adbPublicKey)
private lateinit var pairingContext: PairingContext
private var state: State = State.Ready
fun start(): Boolean {
setupTlsConnection()
state = State.ExchangingMsgs
if (!doExchangeMsgs()) {
state = State.Stopped
return false
}
state = State.ExchangingPeerInfo
if (!doExchangePeerInfo()) {
state = State.Stopped
return false
}
state = State.Stopped
return true
}
private fun setupTlsConnection() {
socket = Socket(host, port)
socket.tcpNoDelay = true
val sslContext = key.sslContext
val sslSocket = sslContext.socketFactory.createSocket(socket, host, port, true) as SSLSocket
sslSocket.startHandshake()
Log.d(TAG, "Handshake succeeded.")
inputStream = DataInputStream(sslSocket.inputStream)
outputStream = DataOutputStream(sslSocket.outputStream)
val pairCodeBytes = pairCode.toByteArray()
val keyMaterial = Conscrypt.exportKeyingMaterial(sslSocket, kExportedKeyLabel, null, kExportedKeySize)
val passwordBytes = ByteArray(pairCode.length + keyMaterial.size)
pairCodeBytes.copyInto(passwordBytes)
keyMaterial.copyInto(passwordBytes, pairCodeBytes.size)
val pairingContext = PairingContext.create(passwordBytes)
checkNotNull(pairingContext) { "Unable to create PairingContext." }
this.pairingContext = pairingContext
}
private fun createHeader(type: PairingPacketHeader.Type, payloadSize: Int): PairingPacketHeader {
return PairingPacketHeader(kCurrentKeyHeaderVersion, type.value, payloadSize)
}
private fun readHeader(): PairingPacketHeader? {
val bytes = ByteArray(kPairingPacketHeaderSize)
inputStream.readFully(bytes)
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN)
return PairingPacketHeader.readFrom(buffer)
}
private fun writeHeader(header: PairingPacketHeader, payload: ByteArray) {
val buffer = ByteBuffer.allocate(kPairingPacketHeaderSize).order(ByteOrder.BIG_ENDIAN)
header.writeTo(buffer)
outputStream.write(buffer.array())
outputStream.write(payload)
Log.d(TAG, "write payload, size=${payload.size}")
}
private fun doExchangeMsgs(): Boolean {
val msg = pairingContext.msg
val size = msg.size
val ourHeader = createHeader(PairingPacketHeader.Type.SPAKE2_MSG, size)
writeHeader(ourHeader, msg)
val theirHeader = readHeader() ?: return false
if (theirHeader.type != PairingPacketHeader.Type.SPAKE2_MSG.value) return false
val theirMessage = ByteArray(theirHeader.payload)
inputStream.readFully(theirMessage)
if (!pairingContext.initCipher(theirMessage)) return false
return true
}
private fun doExchangePeerInfo(): Boolean {
val buf = ByteBuffer.allocate(kMaxPeerInfoSize).order(ByteOrder.BIG_ENDIAN)
peerInfo.writeTo(buf)
val outbuf = pairingContext.encrypt(buf.array()) ?: return false
val ourHeader = createHeader(PairingPacketHeader.Type.PEER_INFO, outbuf.size)
writeHeader(ourHeader, outbuf)
val theirHeader = readHeader() ?: return false
if (theirHeader.type != PairingPacketHeader.Type.PEER_INFO.value) return false
val theirMessage = ByteArray(theirHeader.payload)
inputStream.readFully(theirMessage)
val decrypted = pairingContext.decrypt(theirMessage) ?: throw AdbInvalidPairingCodeException()
if (decrypted.size != kMaxPeerInfoSize) {
Log.e(TAG, "Got size=${decrypted.size} PeerInfo.size=$kMaxPeerInfoSize")
return false
}
val theirPeerInfo = PeerInfo.readFrom(ByteBuffer.wrap(decrypted))
Log.d(TAG, theirPeerInfo.toString())
return true
}
override fun close() {
try {
inputStream.close()
} catch (e: Throwable) {
}
try {
outputStream.close()
} catch (e: Throwable) {
}
try {
socket.close()
} catch (e: Exception) {
}
if (state != State.Ready) {
pairingContext.destroy()
}
}
companion object {
init {
System.loadLibrary("adb")
}
@JvmStatic
external fun available(): Boolean
}
}
3、进行 adbd 的 tcp 连接
package moe.shizuku.manager.adb
import android.util.Log
import moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_RSAPUBLICKEY
import moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_SIGNATURE
import moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_TOKEN
import moe.shizuku.manager.adb.AdbProtocol.A_AUTH
import moe.shizuku.manager.adb.AdbProtocol.A_CLSE
import moe.shizuku.manager.adb.AdbProtocol.A_CNXN
import moe.shizuku.manager.adb.AdbProtocol.A_MAXDATA
import moe.shizuku.manager.adb.AdbProtocol.A_OKAY
import moe.shizuku.manager.adb.AdbProtocol.A_OPEN
import moe.shizuku.manager.adb.AdbProtocol.A_STLS
import moe.shizuku.manager.adb.AdbProtocol.A_STLS_VERSION
import moe.shizuku.manager.adb.AdbProtocol.A_VERSION
import moe.shizuku.manager.adb.AdbProtocol.A_WRTE
//import moe.shizuku.manager.ktx.logd
import rikka.core.util.BuildUtils
import java.io.Closeable
import java.io.DataInputStream
import java.io.DataOutputStream
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import javax.net.ssl.SSLSocket
private const val TAG = "AdbClient"
class AdbClient(private val host: String, private val port: Int, private val key: AdbKey) :
Closeable {
private lateinit var socket: Socket
private lateinit var plainInputStream: DataInputStream
private lateinit var plainOutputStream: DataOutputStream
private var useTls = false
private lateinit var tlsSocket: SSLSocket
private lateinit var tlsInputStream: DataInputStream
private lateinit var tlsOutputStream: DataOutputStream
private val inputStream get() = if (useTls) tlsInputStream else plainInputStream
private val outputStream get() = if (useTls) tlsOutputStream else plainOutputStream
fun connect() {
socket = Socket(host, port)
socket.tcpNoDelay = true
plainInputStream = DataInputStream(socket.getInputStream())
plainOutputStream = DataOutputStream(socket.getOutputStream())
write(A_CNXN, A_VERSION, A_MAXDATA, "host::")
var message = read()
if (message != null) {
if (message.command == A_STLS) {
if (!BuildUtils.atLeast29) {
error("Connect to adb with TLS is not supported before Android 9")
}
write(A_STLS, A_STLS_VERSION, 0)
val sslContext = key.sslContext
tlsSocket =
sslContext.socketFactory.createSocket(socket, host, port, true) as SSLSocket
tlsSocket.startHandshake()
Log.d(TAG, "Handshake succeeded.")
tlsInputStream = DataInputStream(tlsSocket.inputStream)
tlsOutputStream = DataOutputStream(tlsSocket.outputStream)
useTls = true
message = read()
} else if (message.command == A_AUTH) {
if (message.command != A_AUTH && message.arg0 != ADB_AUTH_TOKEN) error("not A_AUTH ADB_AUTH_TOKEN")
write(A_AUTH, ADB_AUTH_SIGNATURE, 0, key.sign(message.data))
message = read()
if (message != null) {
if (message.command != A_CNXN) {
write(A_AUTH, ADB_AUTH_RSAPUBLICKEY, 0, key.adbPublicKey)
message = read()
}
}
}
if (message != null)
if (message.command != A_CNXN) error("not A_CNXN")
}
}
fun shellCommand(command: String, listener: ((ByteArray) -> Unit)?) {
val localId = 1
write(A_OPEN, localId, 0, command)
var message = read()
if (message != null)
when (message.command) {
A_OKAY -> {
while (true) {
message = read()
if (message != null) {
val remoteId = message.arg0
if (message.command == A_WRTE) {
if (message.data_length > 0) {
listener?.invoke(message.data!!)
}
write(A_OKAY, localId, remoteId)
} else if (message.command == A_CLSE) {
write(A_CLSE, localId, remoteId)
break
} else {
error("not A_WRTE or A_CLSE")
}
}
}
}
A_CLSE -> {
val remoteId = message.arg0
write(A_CLSE, localId, remoteId)
}
else -> {
error("not A_OKAY or A_CLSE")
}
}
}
private fun write(command: Int, arg0: Int, arg1: Int, data: ByteArray? = null) =
write(AdbMessage(command, arg0, arg1, data))
private fun write(command: Int, arg0: Int, arg1: Int, data: String) =
write(AdbMessage(command, arg0, arg1, data))
private fun write(message: AdbMessage) {
outputStream.write(message.toByteArray())
outputStream.flush()
// Log.d(TAG, "write ${message.toStringShort()}")
}
private fun read(): AdbMessage? {
val buffer = ByteBuffer.allocate(AdbMessage.HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN)
inputStream.readFully(buffer.array(), 0, 24)
try {
val command = buffer.int
val arg0 = buffer.int
val arg1 = buffer.int
val dataLength = buffer.int
val checksum = buffer.int
val magic = buffer.int
val data: ByteArray?
if (dataLength >= 0) {
data = ByteArray(dataLength)
inputStream.readFully(data, 0, dataLength)
} else {
data = null
}
val message = AdbMessage(command, arg0, arg1, dataLength, checksum, magic, data)
message.validateOrThrow()
return message
} catch (e: Exception) {
e.stackTrace
buffer.clear()
System.gc()
}
// Log.d(TAG, "read ${message.toStringShort()}")
return null
}
override fun close() {
try {
plainInputStream.close()
} catch (e: Throwable) {
}
try {
plainOutputStream.close()
} catch (e: Throwable) {
}
try {
socket.close()
} catch (e: Exception) {
}
if (useTls) {
try {
tlsInputStream.close()
} catch (e: Throwable) {
}
try {
tlsOutputStream.close()
} catch (e: Throwable) {
}
try {
tlsSocket.close()
} catch (e: Exception) {
}
}
}
}
4、tcp 传输数据包结构;
struct message {
unsigned command; /* command identifier constant (A_CNXN, ...) */
unsigned arg0; /* first argument */
unsigned arg1; /* second argument */
unsigned data_length; /* length of payload (0 is allowed) */
unsigned data_crc32; /* crc32 of data payload */
unsigned magic; /* command ^ 0xffffffff */
};
java message结构类
package moe.shizuku.manager.adb
import moe.shizuku.manager.adb.AdbProtocol.A_AUTH
import moe.shizuku.manager.adb.AdbProtocol.A_CLSE
import moe.shizuku.manager.adb.AdbProtocol.A_CNXN
import moe.shizuku.manager.adb.AdbProtocol.A_OKAY
import moe.shizuku.manager.adb.AdbProtocol.A_OPEN
import moe.shizuku.manager.adb.AdbProtocol.A_STLS
import moe.shizuku.manager.adb.AdbProtocol.A_SYNC
import moe.shizuku.manager.adb.AdbProtocol.A_WRTE
import java.nio.ByteBuffer
import java.nio.ByteOrder
class AdbMessage(
val command: Int,
val arg0: Int,
val arg1: Int,
val data_length: Int,
val data_crc32: Int,
val magic: Int,
val data: ByteArray?
) {
constructor(command: Int, arg0: Int, arg1: Int, data: String) : this(
command,
arg0,
arg1,
"$data\u0000".toByteArray())
constructor(command: Int, arg0: Int, arg1: Int, data: ByteArray?) : this(
command,
arg0,
arg1,
data?.size ?: 0,
crc32(data),
(command.toLong() xor 0xFFFFFFFF).toInt(),
data)
fun validate(): Boolean {
if (command != magic xor -0x1) return false
if (data_length != 0 && crc32(data) != data_crc32) return false
return true
}
fun validateOrThrow() {
if (!validate()) throw IllegalArgumentException("bad message ${this.toStringShort()}")
}
fun toByteArray(): ByteArray {
val length = HEADER_LENGTH + (data?.size ?: 0)
return ByteBuffer.allocate(length).apply {
order(ByteOrder.LITTLE_ENDIAN)
putInt(command)
putInt(arg0)
putInt(arg1)
putInt(data_length)
putInt(data_crc32)
putInt(magic)
if (data != null) {
put(data)
}
}.array()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AdbMessage
if (command != other.command) return false
if (arg0 != other.arg0) return false
if (arg1 != other.arg1) return false
if (data_length != other.data_length) return false
if (data_crc32 != other.data_crc32) return false
if (magic != other.magic) return false
if (data != null) {
if (other.data == null) return false
if (!data.contentEquals(other.data)) return false
} else if (other.data != null) return false
return true
}
override fun hashCode(): Int {
var result = command
result = 31 * result + arg0
result = 31 * result + arg1
result = 31 * result + data_length
result = 31 * result + data_crc32
result = 31 * result + magic
result = 31 * result + (data?.contentHashCode() ?: 0)
return result
}
override fun toString(): String {
return "AdbMessage(${toStringShort()})"
}
fun toStringShort(): String {
val commandString = when (command) {
A_SYNC -> "A_SYNC"
A_CNXN -> "A_CNXN"
A_AUTH -> "A_AUTH"
A_OPEN -> "A_OPEN"
A_OKAY -> "A_OKAY"
A_CLSE -> "A_CLSE"
A_WRTE -> "A_WRTE"
A_STLS -> "A_STLS"
else -> command.toString()
}
return "command=$commandString, arg0=$arg0, arg1=$arg1, data_length=$data_length, data_crc32=$data_crc32, magic=$magic, data=${data?.contentToString()}"
}
companion object {
const val HEADER_LENGTH = 24
private fun crc32(data: ByteArray?): Int {
if (data == null) return 0
var res = 0
for (b in data) {
if (b >= 0)
res += b
else
res += b + 256
}
return res
}
}
}