Critical fix: KEXINIT exchange hash encoding (prepend SSH_MSG_KEXINIT byte)
OpenSSH kexgex.c source code analysis: - KEXINIT payload stored without SSH_MSG_KEXINIT type byte - Exchange hash prepends SSH_MSG_KEXINIT byte (20) with adjusted length Before fix: - client_kexinit_payload included SSH_MSG_KEXINIT byte - Direct use without prepending After fix: - Remove SSH_MSG_KEXINIT byte from payload - Prepend byte (20) in exchange hash with length+1 - Both kex_exchange.rs and kex_complete.rs updated Testing result: MAC still fails, indicating additional encoding issues Next: Detailed comparison of all exchange hash components
This commit is contained in:
@@ -71,11 +71,19 @@ impl KexState {
|
||||
// V_S: 服务器版本字符串(SSH string格式)
|
||||
write_ssh_string_to_hash(&mut hasher, &self.server_version)?;
|
||||
|
||||
// I_C: 客户端KEXINIT payload(SSH string格式)
|
||||
write_ssh_string_to_hash(&mut hasher, &String::from_utf8_lossy(&self.client_kexinit_payload))?;
|
||||
// OpenSSH kexgex.c: "kexinit messages: fake header: len+SSH2_MSG_KEXINIT"
|
||||
// Remove SSH_MSG_KEXINIT type byte from payloads and prepend it in exchange hash
|
||||
|
||||
// I_S: 服务器KEXINIT payload(SSH string格式)
|
||||
write_ssh_string_to_hash(&mut hasher, &String::from_utf8_lossy(&self.server_kexinit_payload))?;
|
||||
let client_kexinit_without_type = &self.client_kexinit_payload[1..];
|
||||
let server_kexinit_without_type = &self.server_kexinit_payload[1..];
|
||||
|
||||
hasher.update(&((client_kexinit_without_type.len() + 1) as u32).to_be_bytes());
|
||||
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
|
||||
hasher.update(client_kexinit_without_type);
|
||||
|
||||
hasher.update(&((server_kexinit_without_type.len() + 1) as u32).to_be_bytes());
|
||||
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
|
||||
hasher.update(server_kexinit_without_type);
|
||||
|
||||
// K_S: 服务器主机密钥blob(SSH string格式)
|
||||
hasher.update(server_host_key_blob);
|
||||
|
||||
@@ -215,11 +215,25 @@ impl KexExchangeHandler {
|
||||
hasher.update(&(server_version.len() as u32).to_be_bytes());
|
||||
hasher.update(server_version.as_bytes());
|
||||
|
||||
hasher.update(&(client_kexinit_payload.len() as u32).to_be_bytes());
|
||||
hasher.update(client_kexinit_payload);
|
||||
// OpenSSH kexgex.c: "kexinit messages: fake header: len+SSH2_MSG_KEXINIT"
|
||||
// KEXINIT payload should NOT include SSH_MSG_KEXINIT type byte
|
||||
// OpenSSH stores payload starting from cookie, prepends SSH_MSG_KEXINIT in exchange hash
|
||||
|
||||
hasher.update(&(server_kexinit_payload.len() as u32).to_be_bytes());
|
||||
hasher.update(server_kexinit_payload);
|
||||
// Remove SSH_MSG_KEXINIT type byte from payloads (our payload includes it)
|
||||
let client_kexinit_without_type = &client_kexinit_payload[1..];
|
||||
let server_kexinit_without_type = &server_kexinit_payload[1..];
|
||||
|
||||
info!("I_C (client KEXINIT without type byte): {} bytes (first byte should be cookie)", client_kexinit_without_type.len());
|
||||
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());
|
||||
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
|
||||
hasher.update(client_kexinit_without_type);
|
||||
|
||||
hasher.update(&((server_kexinit_without_type.len() + 1) as u32).to_be_bytes());
|
||||
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
|
||||
hasher.update(server_kexinit_without_type);
|
||||
|
||||
hasher.update(&(host_key_blob.len() as u32).to_be_bytes());
|
||||
hasher.update(host_key_blob);
|
||||
|
||||
Reference in New Issue
Block a user