介绍#
ssh 协议是一个远程控制的协议,具备加密性。
实现#
dropbear 是一个轻量级的 ssh 服务端,可以参考它的代码
编码格式#
ssh 对字符串,数字,布尔值等有自己的编码方式
数字#
4 byte 大端
字符串#
4 byte 的数字表示字符串的长度,之后接字符串的内容,不带 \0
布尔值#
1 byte
字符#
1 byte
大整数#
4 byte 大整数的 byte 数
如果大整数为负数,那么此处插入 1 byte 0x00
大整数 []byte
字符串序列 name-list#
把字符串用 , 隔开,然后合成一个大字符串,以上述字符串方法编码
包的组成#
ssh 协议的报文以一个包为单位
4 byte 数字表示接下来的内容大小
1 byte 数字表示 padding 大小
动态长度的正文区
padding 区域
所以正文大小等于 package 大小 -1- padding 大小
常量#
以下常量来自 dropbear 的 ssh.h 文件
/* message numbers */
#define SSH_MSG_DISCONNECT 1
#define SSH_MSG_IGNORE 2
#define SSH_MSG_UNIMPLEMENTED 3
#define SSH_MSG_DEBUG 4
#define SSH_MSG_SERVICE_REQUEST 5
#define SSH_MSG_SERVICE_ACCEPT 6
#define SSH_MSG_EXT_INFO 7
#define SSH_MSG_KEXINIT 20
#define SSH_MSG_NEWKEYS 21
#define SSH_MSG_KEXDH_INIT 30
#define SSH_MSG_KEXDH_REPLY 31
/* userauth message numbers */
#define SSH_MSG_USERAUTH_REQUEST 50
#define SSH_MSG_USERAUTH_FAILURE 51
#define SSH_MSG_USERAUTH_SUCCESS 52
#define SSH_MSG_USERAUTH_BANNER 53
/* packets 60-79 are method-specific, aren't one-one mapping */
#define SSH_MSG_USERAUTH_SPECIFIC_60 60
#define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 60
#define SSH_MSG_USERAUTH_PK_OK 60
/* keyboard interactive auth */
#define SSH_MSG_USERAUTH_INFO_REQUEST 60
#define SSH_MSG_USERAUTH_INFO_RESPONSE 61
/* If adding numbers here, check MAX_UNAUTH_PACKET_TYPE in process-packet.c
* is still valid */
/* connect message numbers */
#define SSH_MSG_GLOBAL_REQUEST 80
#define SSH_MSG_REQUEST_SUCCESS 81
#define SSH_MSG_REQUEST_FAILURE 82
#define SSH_MSG_CHANNEL_OPEN 90
#define SSH_MSG_CHANNEL_OPEN_CONFIRMATION 91
#define SSH_MSG_CHANNEL_OPEN_FAILURE 92
#define SSH_MSG_CHANNEL_WINDOW_ADJUST 93
#define SSH_MSG_CHANNEL_DATA 94
#define SSH_MSG_CHANNEL_EXTENDED_DATA 95
#define SSH_MSG_CHANNEL_EOF 96
#define SSH_MSG_CHANNEL_CLOSE 97
#define SSH_MSG_CHANNEL_REQUEST 98
#define SSH_MSG_CHANNEL_SUCCESS 99
#define SSH_MSG_CHANNEL_FAILURE 100
/* extended data types */
#define SSH_EXTENDED_DATA_STDERR 1
/* disconnect codes */
#define SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1
#define SSH_DISCONNECT_PROTOCOL_ERROR 2
#define SSH_DISCONNECT_KEY_EXCHANGE_FAILED 3
#define SSH_DISCONNECT_RESERVED 4
#define SSH_DISCONNECT_MAC_ERROR 5
#define SSH_DISCONNECT_COMPRESSION_ERROR 6
#define SSH_DISCONNECT_SERVICE_NOT_AVAILABLE 7
#define SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8
#define SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9
#define SSH_DISCONNECT_CONNECTION_LOST 10
#define SSH_DISCONNECT_BY_APPLICATION 11
#define SSH_DISCONNECT_TOO_MANY_CONNECTIONS 12
#define SSH_DISCONNECT_AUTH_CANCELLED_BY_USER 13
#define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14
#define SSH_DISCONNECT_ILLEGAL_USER_NAME 15
/* rfc8308 */
#define SSH_EXT_INFO_S "ext-info-s"
#define SSH_EXT_INFO_C "ext-info-c"
#define SSH_SERVER_SIG_ALGS "server-sig-algs"
/* OpenSSH strict KEX feature */
#define SSH_STRICT_KEX_S "[email protected]"
#define SSH_STRICT_KEX_C "[email protected]"
/* service types */
#define SSH_SERVICE_USERAUTH "ssh-userauth"
#define SSH_SERVICE_USERAUTH_LEN 12
#define SSH_SERVICE_CONNECTION "ssh-connection"
#define SSH_SERVICE_CONNECTION_LEN 14
/* public/signature key types */
#define SSH_SIGNKEY_DSS "ssh-dss"
#define SSH_SIGNKEY_DSS_LEN 7
#define SSH_SIGNKEY_RSA "ssh-rsa"
#define SSH_SIGNKEY_RSA_LEN 7
#define SSH_SIGNKEY_ED25519 "ssh-ed25519"
#define SSH_SIGNKEY_ED25519_LEN 11
/* signature type */
#define SSH_SIGNATURE_RSA_SHA256 "rsa-sha2-256"
/* Agent commands. These aren't part of the spec, and are defined
* only on the openssh implementation. */
#define SSH_AGENT_FAILURE 5
#define SSH_AGENT_SUCCESS 6
#define SSH2_AGENTC_REQUEST_IDENTITIES 11
#define SSH2_AGENT_IDENTITIES_ANSWER 12
#define SSH2_AGENTC_SIGN_REQUEST 13
#define SSH2_AGENT_SIGN_RESPONSE 14
#define SSH2_AGENT_FAILURE 30
/* Flags defined by OpenSSH U2F key/signature format */
#define SSH_SK_USER_PRESENCE_REQD 0x01
#define SSH_SK_USER_VERIFICATION_REQD 0x04
#define SSH_SK_RESIDENT_KEY 0x20报文解析#
首先,客户端向服务端建立连接,服务端会向客户端发送如下数据,不经过上述 ssh 编码
serverBanner\r\n
比如
SSH-2.0-dropbear_2025.89\r\n
此时客户端应该向服务端发送 clientBanner\r\n ,比如
SSH-2.0-OpenSSH_for_Windows_9.5\r\n
之后服务端会发送 SSH_MSG_KEXINIT 消息,报文正文内容是
1 byte SSH_MSG_KEXINIT
16 byte Cookie
name-list 密钥协商算法
name-list 主机公钥算法
name-list 客户端向服务端的加密算法
name-list 服务端向客户端的加密算法
name-list 客户端向服务端的 mac 算法
name-list 服务端向客户端的 mac 算法
name-list 客户端向服务端的压缩算法
name-list 服务端向客户端的压缩算法
name-list 客户端向服务端的语言
name-list 服务端向客户端的语言
1 byte FirstKexPacketFollows
4 byte 保留字段
FirstKexPacketFollows 如果为 true 的话,发送端会向接受端发送 SSH_MSG_KEXINIT 包之后发送下一个包
之后客户端会发送自己的 SSH_MSG_KEXINIT 包
那么使用的对应算法就是客户端的 SSH_MSG_KEXINIT 第一个服务端也同时包含的项
假设选择了 DH 协商算法,那么客户端会发送一个 SSH_MSG_KEXDH_INIT 包,正文内容如下
1 byte SSH_MSG_KEXDH_INIT
大整数 e
e 是自己的 DH 公钥
之后服务端会发送 SSH_MSG_KEXDH_REPLY 包,正文内容如下
1 byte SSH_MSG_KEXDH_REPLY
字符串 公钥 []byte
大整数 服务端 DH 公钥
字符串 签名 []byte
公钥字符序列和签名字符串序列都是如下编码
字符串 名字
字符串 内容
比如公钥算法可能是 ssh-ed25519 ,签名算法可能是 ssh-ed25519
客户端需要计算一个 hash 值,然后判断公钥是否可以验证这个 hash 值,如果可以的话,那么可以证明这个消息是由主机发出的
hash 组成
clientBanner + serverBanner + 客户端 SSH_MSG_KEXINIT 包 + 服务端 SSH_MSG_KEXINIT 包 + 服务端公钥 + 客户端 DH 公钥 + 服务端 DH 公钥 + DH 协商密钥
如果验证成功,那么客户端发送一个 SSH_MSG_NEWKEYS 包,包正文内容只有一个字节的 SSH_MSG_NEWKEYS ,服务端也会发送一个 SSH_MSG_NEWKEYS 包,之后双方使用加密的消息