diff --git a/.gitignore b/.gitignore index 92699284..bea01d86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.o +*.pyc .dirstamp .deps /*.pdb diff --git a/contrib/encodelib.py b/contrib/encodelib.py new file mode 100644 index 00000000..196c4677 --- /dev/null +++ b/contrib/encodelib.py @@ -0,0 +1,115 @@ +# Python module to make it easy to manually encode SSH packets, by +# supporting the various uint32, string, mpint primitives. +# +# The idea of this is that you can use it to manually construct key +# exchange sequences of interesting kinds, for testing purposes. + +import struct, random + +def boolean(b): + return "\1" if b else "\0" + +def byte(b): + assert 0 <= b < 0x100 + return chr(b) + +def uint32(u): + assert 0 <= u < 0x100000000 + return struct.pack(">I", u) + +def uint64(u): + assert 0 <= u < 0x10000000000000000 + return struct.pack(">L", u) + +def string(s): + return uint32(len(s)) + s + +def mpint(m): + s = "" + lastbyte = 0 + while m > 0: + lastbyte = m & 0xFF + s = chr(lastbyte) + s + m >>= 8 + if lastbyte & 0x80: + s = "\0" + s + return string(s) + +def name_list(ns): + s = "" + for n in ns: + assert "," not in n + if s != "": + s += "," + s += n + return string(s) + +def ssh_rsa_key_blob(modulus, exponent): + return string(string("ssh-rsa") + mpint(modulus) + mpint(exponent)) + +def ssh_rsa_signature_blob(signature): + return string(string("ssh-rsa") + mpint(signature)) + +def greeting(string): + # Greeting at the start of an SSH connection. + return string + "\r\n" + +# Packet types. +SSH2_MSG_DISCONNECT = 1 +SSH2_MSG_IGNORE = 2 +SSH2_MSG_UNIMPLEMENTED = 3 +SSH2_MSG_DEBUG = 4 +SSH2_MSG_SERVICE_REQUEST = 5 +SSH2_MSG_SERVICE_ACCEPT = 6 +SSH2_MSG_KEXINIT = 20 +SSH2_MSG_NEWKEYS = 21 +SSH2_MSG_KEXDH_INIT = 30 +SSH2_MSG_KEXDH_REPLY = 31 +SSH2_MSG_KEX_DH_GEX_REQUEST = 30 +SSH2_MSG_KEX_DH_GEX_GROUP = 31 +SSH2_MSG_KEX_DH_GEX_INIT = 32 +SSH2_MSG_KEX_DH_GEX_REPLY = 33 +SSH2_MSG_KEXRSA_PUBKEY = 30 +SSH2_MSG_KEXRSA_SECRET = 31 +SSH2_MSG_KEXRSA_DONE = 32 +SSH2_MSG_USERAUTH_REQUEST = 50 +SSH2_MSG_USERAUTH_FAILURE = 51 +SSH2_MSG_USERAUTH_SUCCESS = 52 +SSH2_MSG_USERAUTH_BANNER = 53 +SSH2_MSG_USERAUTH_PK_OK = 60 +SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ = 60 +SSH2_MSG_USERAUTH_INFO_REQUEST = 60 +SSH2_MSG_USERAUTH_INFO_RESPONSE = 61 +SSH2_MSG_GLOBAL_REQUEST = 80 +SSH2_MSG_REQUEST_SUCCESS = 81 +SSH2_MSG_REQUEST_FAILURE = 82 +SSH2_MSG_CHANNEL_OPEN = 90 +SSH2_MSG_CHANNEL_OPEN_CONFIRMATION = 91 +SSH2_MSG_CHANNEL_OPEN_FAILURE = 92 +SSH2_MSG_CHANNEL_WINDOW_ADJUST = 93 +SSH2_MSG_CHANNEL_DATA = 94 +SSH2_MSG_CHANNEL_EXTENDED_DATA = 95 +SSH2_MSG_CHANNEL_EOF = 96 +SSH2_MSG_CHANNEL_CLOSE = 97 +SSH2_MSG_CHANNEL_REQUEST = 98 +SSH2_MSG_CHANNEL_SUCCESS = 99 +SSH2_MSG_CHANNEL_FAILURE = 100 +SSH2_MSG_USERAUTH_GSSAPI_RESPONSE = 60 +SSH2_MSG_USERAUTH_GSSAPI_TOKEN = 61 +SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE = 63 +SSH2_MSG_USERAUTH_GSSAPI_ERROR = 64 +SSH2_MSG_USERAUTH_GSSAPI_ERRTOK = 65 +SSH2_MSG_USERAUTH_GSSAPI_MIC = 66 + +def clearpkt(msgtype, *stuff): + # SSH-2 binary packet, in the cleartext format used for initial + # setup and kex. + s = byte(msgtype) + for thing in stuff: + s += thing + padlen = 0 + while padlen < 4 or len(s) % 8 != 3: + padlen += 1 + s += byte(random.randint(0,255)) + s = byte(padlen) + s + return string(s) diff --git a/contrib/samplekex.py b/contrib/samplekex.py new file mode 100755 index 00000000..47059837 --- /dev/null +++ b/contrib/samplekex.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +# Example Python script to synthesise the server end of an SSH key exchange. + +# This is an output-only script; you run it by means of saying +# something like +# +# samplekex.py | nc -l 2222 | hex dump utility of your choice +# +# and then connecting PuTTY to port 2222. Being output-only, of +# course, it cannot possibly get the key exchange _right_, so PuTTY +# will terminate with an error when the signature in the final message +# doesn't validate. But everything until then should be processed as +# if it was a normal SSH-2 connection, which means you can use this +# script as a starting point for constructing interestingly malformed +# key exchanges to test bug fixes. + +import sys, random +from encodelib import * + +# A random Diffie-Hellman group, taken from an SSH server I made a +# test connection to. +groupgen = 5 +group = 0xf5d3849d2092fd427b4ebd838ea4830397a55f80b644626320dbbe51e8f63ed88148d787c94e7e67e4f393f26c565e1992b0cff8a47a953439462a4d0ffa5763ef60ff908f8ee6c4f6ef9f32b9ba50f01ad56fe7ebe90876a5cf61813a4ad4ba7ec0704303c9bf887d36abbd6c2aa9545fc2263232927e731060f5c701c96dc34016636df438ce30973715f121d767cfb98b5d09ae7b86fa36a051ad3c2941a295a68e2f583a56bc69913ec9d25abef4fdf1e31ede827a02620db058b9f041da051c8c0f13b132c17ceb893fa7c4cd8d8feebd82c5f9120cb221b8e88c5fe4dc17ca020a535484c92c7d4bee69c7703e1fa9a652d444c80065342c6ec0fac23c24de246e3dee72ca8bc8beccdade2b36771efcc350558268f5352ae53f2f71db62249ad9ac4fabdd6dfb099c6cff8c05bdea894390f9860f011cca046dfeb2f6ef81094e7980be526742706d1f3db920db107409291bb4c11f9a7dcbfaf26d808e6f9fe636b26b939de419129e86b1e632c60ec23b65c815723c5d861af068fd0ac8b37f4c06ecbd5cb2ef069ca8daac5cbd67c6182a65fed656d0dfbbb8a430b1dbac7bd6303bec8de078fe69f443a7bc8131a284d25dc2844f096240bfc61b62e91a87802987659b884c094c68741d29aa5ca19b9457e1f9df61c7dbbb13a61a79e4670b086027f20da2af4f5b020725f8828726379f429178926a1f0ea03f + +# An RSA key, generated specially for this script. +rsaexp = 0x10001 +rsamod = 0xB98FE0C0BEE1E05B35FDDF5517B3E29D8A9A6A7834378B6783A19536968968F755E341B5822CAE15B465DECB80EE4116CF8D22DB5A6C85444A68D0D45D9D42008329619BE3CAC3B192EF83DD2B75C4BB6B567E11B841073BACE92108DA7E97E543ED7F032F454F7AC3C6D3F27DB34BC9974A85C7963C546662AE300A61CBABEE274481FD041C41D0145704F5FA9C77A5A442CD7A64827BB0F21FB56FDE388B596A20D7A7D1C5F22DA96C6C2171D90A673DABC66596CD99499D75AD82FEFDBE04DEC2CC7E1414A95388F668591B3F4D58249F80489646ED2C318E77D4F4E37EE8A588E79F2960620E6D28BF53653F1C974C91845F0BABFE5D167E1CA7044EE20D + +# 16 bytes of random data for the start of KEXINIT. +cookie = "".join([chr(random.randint(0,255)) for i in range(16)]) + +sys.stdout.write(greeting("SSH-2.0-Example KEX synthesis")) + +# Expect client to send KEXINIT + +sys.stdout.write( + clearpkt(SSH2_MSG_KEXINIT, + cookie, + name_list(("diffie-hellman-group-exchange-sha256",)), # kex + name_list(("ssh-rsa",)), # host keys + name_list(("aes128-ctr",)), # client->server ciphers + name_list(("aes128-ctr",)), # server->client ciphers + name_list(("hmac-sha2-256",)), # client->server MACs + name_list(("hmac-sha2-256",)), # server->client MACs + name_list(("none",)), # client->server compression + name_list(("none",)), # server->client compression + name_list(()), # client->server languages + name_list(()), # server->client languages + boolean(False), # first kex packet does not follow + uint32(0))) + +# Expect client to send SSH2_MSG_KEX_DH_GEX_REQUEST(0x1000) + +sys.stdout.write( + clearpkt(SSH2_MSG_KEX_DH_GEX_GROUP, + mpint(group), + mpint(groupgen))) + +# Expect client to send SSH2_MSG_KEX_DH_GEX_INIT + +sys.stdout.write( + clearpkt(SSH2_MSG_KEX_DH_GEX_REPLY, + ssh_rsa_key_blob(rsaexp, rsamod), + mpint(random.randint(2, group-2)), + ssh_rsa_signature_blob(random.randint(2, rsamod-2))))