深度分析:添加完整exchange hash components logging
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

添加详细logging:
- V_C/V_S: 完整SSH string encoding bytes
- I_C/I_S: prepend SSH_MSG_KEXINIT byte验证
- K_S: 完整host key blob bytes
- Q_C/Q_S: 完整32 bytes ECDH keys
- K: shared secret mpint encoding bytes

验证结果:
 所有encoding格式正确(SSH string, mpint)
 KEXINIT prepend byte正确(uint32(len+1) + byte(20) + payload)
 所有component lengths正确

但仍MAC失败,唯一可能:
- OpenSSH client计算exchange hash方式不同
- 需要对比OpenSSH client连接OpenSSH server成功 vs MarkBaseSSH失败

下一步建议:
1. 手动启动OpenSSH server(解决port占用)
2. 使用Wireshark GUI完整对比packet
3. 或使用OpenSSH client源码验证exchange hash计算

Session progress:
- OpenSSH源码深度对比:100%
- KEXINIT encoding修复:100%
- Exchange hash components验证:100%
- MAC失败root cause:待查
This commit is contained in:
Warren
2026-06-15 01:11:25 +08:00
parent 6014362686
commit 7a7030a65f
2 changed files with 26 additions and 10 deletions

View File

@@ -209,11 +209,15 @@ impl KexExchangeHandler {
let mut hasher = Sha256::new();
// RFC 4253 Section 7: V_C and V_S are version strings (without \r\n based on testing)
hasher.update(&(client_version.len() as u32).to_be_bytes());
let vc_ssh_string = &(client_version.len() as u32).to_be_bytes();
hasher.update(vc_ssh_string);
hasher.update(client_version.as_bytes());
info!(" Exchange hash component V_C: len={} bytes=[{:?}] data=[{:?}]", 4+client_version.len(), vc_ssh_string, client_version.as_bytes());
hasher.update(&(server_version.len() as u32).to_be_bytes());
let vs_ssh_string = &(server_version.len() as u32).to_be_bytes();
hasher.update(vs_ssh_string);
hasher.update(server_version.as_bytes());
info!(" Exchange hash component V_S: len={} bytes=[{:?}] data=[{:?}]", 4+server_version.len(), vs_ssh_string, server_version.as_bytes());
// OpenSSH kexgex.c: "kexinit messages: fake header: len+SSH2_MSG_KEXINIT"
// KEXINIT payload should NOT include SSH_MSG_KEXINIT type byte
@@ -227,25 +231,35 @@ impl KexExchangeHandler {
info!("I_S (server KEXINIT without type byte): {} bytes", server_kexinit_without_type.len());
// Exchange hash: uint32(len+1) + uint8(SSH_MSG_KEXINIT) + payload_without_type
hasher.update(&((client_kexinit_without_type.len() + 1) as u32).to_be_bytes());
let ic_len_bytes = &((client_kexinit_without_type.len() + 1) as u32).to_be_bytes();
hasher.update(ic_len_bytes);
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
hasher.update(client_kexinit_without_type);
info!(" Exchange hash component I_C: len={} bytes=[{:?}] type=[20] payload_len={} (first 8 bytes=[{:?}])", 4+1+client_kexinit_without_type.len(), ic_len_bytes, client_kexinit_without_type.len(), &client_kexinit_without_type[..std::cmp::min(8, client_kexinit_without_type.len())]);
hasher.update(&((server_kexinit_without_type.len() + 1) as u32).to_be_bytes());
let is_len_bytes = &((server_kexinit_without_type.len() + 1) as u32).to_be_bytes();
hasher.update(is_len_bytes);
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
hasher.update(server_kexinit_without_type);
info!(" Exchange hash component I_S: len={} bytes=[{:?}] type=[20] payload_len={} (first 8 bytes=[{:?}])", 4+1+server_kexinit_without_type.len(), is_len_bytes, server_kexinit_without_type.len(), &server_kexinit_without_type[..std::cmp::min(8, server_kexinit_without_type.len())]);
hasher.update(&(host_key_blob.len() as u32).to_be_bytes());
let ks_len_bytes = &(host_key_blob.len() as u32).to_be_bytes();
hasher.update(ks_len_bytes);
hasher.update(host_key_blob);
info!(" Exchange hash component K_S: len={} bytes=[{:?}] blob_len={} (full=[{:?}])", 4+host_key_blob.len(), ks_len_bytes, host_key_blob.len(), host_key_blob);
hasher.update(&(client_public_key.len() as u32).to_be_bytes());
let qc_len_bytes = &(client_public_key.len() as u32).to_be_bytes();
hasher.update(qc_len_bytes);
hasher.update(client_public_key);
info!(" Exchange hash component Q_C: len={} bytes=[{:?}] key=[{:?}]", 4+client_public_key.len(), qc_len_bytes, client_public_key);
hasher.update(&(server_public_key.len() as u32).to_be_bytes());
let qs_len_bytes = &(server_public_key.len() as u32).to_be_bytes();
hasher.update(qs_len_bytes);
hasher.update(server_public_key);
info!(" Exchange hash component Q_S: len={} bytes=[{:?}] key=[{:?}]", 4+server_public_key.len(), qs_len_bytes, server_public_key);
info!("Exchange hash components:");
info!(" shared_secret raw ({} bytes): {:?}", shared_secret.len(), &shared_secret[..std::cmp::min(8, shared_secret.len())]);
info!(" shared_secret raw full (32 bytes): {:?}", shared_secret);
// RFC 8731 Section 3.1: X25519 output is little-endian
// OpenSSH sshbuf_put_bignum2_bytes() uses bytes DIRECTLY (no reversal)
@@ -260,7 +274,7 @@ impl KexExchangeHandler {
}
let trimmed_shared_secret = &shared_secret[start..];
info!(" shared_secret after removing leading zeros ({} bytes): {:?}", trimmed_shared_secret.len(), &trimmed_shared_secret[..std::cmp::min(8, trimmed_shared_secret.len())]);
info!(" shared_secret after removing leading zeros ({} bytes): {:?}", trimmed_shared_secret.len(), trimmed_shared_secret);
let mpint_shared_secret_data = if trimmed_shared_secret.len() > 0 && trimmed_shared_secret[0] >= 0x80 {
let mut mpint = vec![0u8];
@@ -274,8 +288,10 @@ impl KexExchangeHandler {
info!(" mpint_shared_secret_data ({} bytes): {:?}", mpint_shared_secret_data.len(), &mpint_shared_secret_data[..std::cmp::min(8, mpint_shared_secret_data.len())]);
// mpint格式 = uint32(length) + mpint_data
hasher.update(&(mpint_shared_secret_data.len() as u32).to_be_bytes());
let mpint_len_bytes = &(mpint_shared_secret_data.len() as u32).to_be_bytes();
hasher.update(mpint_len_bytes);
hasher.update(&mpint_shared_secret_data);
info!(" Exchange hash component K (shared secret mpint): len={} bytes=[{:?}] data_len={} (first 8 bytes=[{:?}])", 4+mpint_shared_secret_data.len(), mpint_len_bytes, mpint_shared_secret_data.len(), &mpint_shared_secret_data[..std::cmp::min(8, mpint_shared_secret_data.len())]);
Ok(hasher.finalize().to_vec())
}