├── README.md ├── centos ├── README.md └── openssh-7.4p1-obfuscate.patch ├── debian ├── 10.0p1.diff ├── 6.7p1-5+deb8u2.diff ├── 7.4p1-10+deb9u7.diff ├── 7.9p1-10+deb10u2.diff ├── 8.3p1.diff ├── 8.4p1.diff ├── 8.9p1.diff ├── 9.0p1.diff ├── 9.2p1.diff ├── 9.6p1.diff ├── 9.7p1.diff ├── 9.8p1.diff └── 9.9p1.diff ├── openbsd └── 8.0.diff ├── portable ├── 10.0.diff ├── 6.1.diff ├── 6.2.diff ├── 6.3_6.4.diff ├── 6.5.diff ├── 6.6.diff ├── 6.7.diff ├── 6.8.diff ├── 6.9.diff ├── 7.0_7.1.diff ├── 7.2.diff ├── 7.3.diff ├── 7.4.diff ├── 7.5.diff ├── 7.6.diff ├── 7.7.diff ├── 7.8.diff ├── 7.9.diff ├── 8.0.diff ├── 8.1.diff ├── 8.2.diff ├── 8.3.diff ├── 8.4.diff ├── 8.5_8.6.diff ├── 8.7.diff ├── 8.8_8.9.diff ├── 9.0.diff ├── 9.1.diff ├── 9.2.diff ├── 9.3.diff ├── 9.4.diff ├── 9.5.diff ├── 9.6.diff ├── 9.7.diff ├── 9.8.diff └── 9.9.diff └── ubuntu ├── 5.9p1-5ubuntu1.7.diff ├── 6.6p1-2ubuntu2.12.patch ├── 6.6p1-2ubuntu2.3.patch ├── 6.7p1-5ubuntu1.3.patch ├── 6.9p1-2.patch ├── 7.1p1-4.patch ├── 7.2p2-4.patch ├── 7.2p2-4ubuntu2.7.patch ├── 7.4p1.patch ├── 7.5p1.patch ├── 7.6p1.patch ├── 7.7p1.patch ├── 7.9p1.diff ├── 8.0p1.patch ├── 8.1p1.patch ├── 8.2p1.patch ├── 8.9p1.patch ├── 9.3p1.patch ├── 9.6p1.patch └── 9.7p1.patch /README.md: -------------------------------------------------------------------------------- 1 | # obfuscated-openssh-patches 2 | ### Patches for adding handshake obfuscation to OpenSSH 3 | 4 | 5 | ### Not fully tested. 6 | 7 | 8 | ### [Usage Info](https://zinglau.com/projects/ObfuscatedOpenSSHPatches.html) 9 | 10 | 11 | ### [Ubuntu PPA](https://launchpad.net/~zinglau/+archive/ubuntu/obfuscated-openssh) 12 | 13 | 14 | ### [Debian Archive](https://deb.zinglau.com/) 15 | 16 | 17 | ### Acknowledgements 18 | - [brl](https://github.com/brl) developed the initial concept and authored the handshake obfuscattion patch 19 | for openssh-5.2 ([repo](https://github.com/brl/obfuscated-openssh)). 20 | - [aligo](https://github.com/aligo) ported the patch to openssh-6.1 ([repo](https://github.com/aligo/obfuscated-openssh)). 21 | 22 | #### [Donate using LiberaPay](https://liberapay.com/ZingLau2015/donate) 23 | [![Donate using LiberaPay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/ZingLau2015/donate) 24 | -------------------------------------------------------------------------------- /centos/README.md: -------------------------------------------------------------------------------- 1 | How to use 2 | ====== 3 | 4 | 1. Clone source repository from CentOS 5 | ```Bash 6 | git clone https://git.centos.org/r/rpms/openssh.git 7 | ``` 8 | 9 | 2. Checkout the corresponding version tag. For full list of tags and branches, check [here](https://git.centos.org/summary/rpms!openssh.git). 10 | ```Bash 11 | cd openssh 12 | # git checkout tags/ -b 13 | git checkout tags/imports/c7/openssh-7.4p1-16.el7 -b c7 14 | ``` 15 | 16 | 3. Download the patch for corresponding version and save to `SOURCES/` 17 | 18 | 4. Modify `SPECS/openssh.spec` accordingly. This may include: 19 | * Changing `openssh_ver` or `openssh_rel` variable to indicate modification 20 | ```diff 21 | %define openssh_ver 7.4p1 22 | -%define openssh_rel 16 23 | +%define openssh_rel 16.obfs 24 | %define pam_ssh_agent_ver 0.10.3 25 | %define pam_ssh_agent_rel 2 26 | ``` 27 | * Add the patch to patch list, and execute it **after all other patches** 28 | ```diff 29 | # Internal debug 30 | Patch0: openssh-5.9p1-wIm.patch 31 | +Patch1: openssh-7.4p1-obfuscate.patch 32 | 33 | #? 34 | Patch100: openssh-7.4p1-coverity.patch 35 | ``` 36 | ```diff 37 | %patch700 -p1 -b .fips 38 | 39 | %patch100 -p1 -b .coverity 40 | +%patch1 -p1 -b .obfuscate 41 | 42 | %if 0 43 | # Nothing here yet 44 | ``` 45 | 46 | 5. Build RPM 47 | ```Bash 48 | # You may need to install build dependencies first 49 | yum-builddep openssh 50 | # Test build (Executes the "%prep" stage. Normally this involves unpacking the sources and applying any patches.) 51 | rpmbuild -bp SPECS/openssh.spec 52 | # Build binary and source packages 53 | rpmbuild -ba SPECS/openssh.spec 54 | ``` 55 | 56 | 6. If all finished without error, you would now have RPM packages in `RPMS/` that can be used. On a system with both `openssh-server` and `openssh-clients` installed, you have to update both packages plus `openssh`, or you would break dependencies. Otherwise, just install the package you need, e.g. `openssh-server` with `openssh` as dependency. 57 | ```Bash 58 | # cd RPMS/ 59 | cd RPMS/x86_64 60 | yum localinstall openssh-7.4p1-16.obfs.el7.x86_64.rpm openssh-clients-7.4p1-16.obfs.el7.x86_64.rpm openssh-server-7.4p1-16.obfs.el7.x86_64.rpm 61 | ``` 62 | 63 | 7. Modify your existing `sshd_config`. (You can use `sshd_config.rpmnew` as a reference) 64 | ```Bash 65 | vi /etc/ssh/sshd_config 66 | ``` 67 | 68 | 8. If you specified a port other than 22 as either the default port (not obfuscated), or the obfuscated port, you would have to update your SELinux config (if SELinux config is enabled) 69 | ```Bash 70 | semanage port -l | grep ssh 71 | # ssh_port_t tcp 22 72 | 73 | # semanage port -a -t ssh_port_t -p tcp 74 | semanage port -a -t ssh_port_t -p tcp 222 75 | ``` 76 | 77 | 9. Reload your `sshd.service`. **Make sure to test the new configuration before disconnecting you old SSH connection**, in case anything goes wrong, you can at least revert changes to `sshd_config` and possibly `yum reinstall openssh-server`. 78 | ```Bash 79 | systemctl reload sshd 80 | ``` 81 | 82 | References 83 | ====== 84 | 85 | https://wiki.centos.org/PackageManagement/Rpm 86 | 87 | https://blog.tinned-software.net/change-ssh-port-in-centos-with-selinux/ 88 | -------------------------------------------------------------------------------- /debian/8.4p1.diff: -------------------------------------------------------------------------------- 1 | Index: openssh-8.4p1/Makefile.in 2 | =================================================================== 3 | --- openssh-8.4p1.orig/Makefile.in 2021-02-14 19:49:28.000000000 +0800 4 | +++ openssh-8.4p1/Makefile.in 2021-02-15 00:13:45.124094521 +0800 5 | @@ -109,7 +109,7 @@ 6 | sntrup4591761.o kexsntrup4591761x25519.o kexgen.o \ 7 | kexgssc.o \ 8 | sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ 9 | - sshbuf-io.o 10 | + sshbuf-io.o obfuscate.o 11 | 12 | SKOBJS= ssh-sk-client.o 13 | 14 | Index: openssh-8.4p1/kex.c 15 | =================================================================== 16 | --- openssh-8.4p1.orig/kex.c 2021-02-14 19:49:28.000000000 +0800 17 | +++ openssh-8.4p1/kex.c 2021-02-15 00:13:45.124094521 +0800 18 | @@ -59,6 +59,7 @@ 19 | #include "monitor.h" 20 | #include "xmalloc.h" 21 | 22 | +#include "obfuscate.h" 23 | #include "ssherr.h" 24 | #include "sshbuf.h" 25 | #include "digest.h" 26 | @@ -517,9 +518,12 @@ 27 | return r; 28 | debug("SSH2_MSG_NEWKEYS sent"); 29 | ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); 30 | - if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) 31 | + if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) { 32 | + sshpkt_disable_obfuscation(ssh); 33 | if ((r = kex_send_ext_info(ssh)) != 0) 34 | return r; 35 | + sshpkt_enable_obfuscation(ssh); 36 | + } 37 | debug("expecting SSH2_MSG_NEWKEYS"); 38 | return 0; 39 | } 40 | @@ -582,6 +586,7 @@ 41 | kex->flags &= ~KEX_INIT_SENT; 42 | free(kex->name); 43 | kex->name = NULL; 44 | + sshpkt_disable_obfuscation(ssh); 45 | return 0; 46 | } 47 | 48 | @@ -1252,14 +1257,42 @@ 49 | goto out; 50 | } 51 | 52 | + /* Make copy and obfuscate the original */ 53 | + if (sshpkt_get_obfuscation(ssh) == 1) { 54 | + if ((cp = sshbuf_dup_string(our_version)) == NULL) { 55 | + error("%s: sshbuf_dup_string failed for obfuscation", __func__); 56 | + r = SSH_ERR_ALLOC_FAIL; 57 | + goto out; 58 | + } 59 | + obfuscate_output(sshbuf_mutable_ptr(our_version), sshbuf_len(our_version)); 60 | + } 61 | + 62 | if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), 63 | sshbuf_mutable_ptr(our_version), 64 | sshbuf_len(our_version)) != sshbuf_len(our_version)) { 65 | oerrno = errno; 66 | debug("%s: write: %.100s", __func__, strerror(errno)); 67 | r = SSH_ERR_SYSTEM_ERROR; 68 | + if (sshpkt_get_obfuscation(ssh) == 1) { 69 | + free(cp); 70 | + } 71 | goto out; 72 | } 73 | + 74 | + /* Restore the original */ 75 | + if (sshpkt_get_obfuscation(ssh) == 1) { 76 | + if ((r = sshbuf_consume(our_version, sshbuf_len(our_version))) != 0) { 77 | + error("%s: sshbuf_consume failed for obfuscation", __func__); 78 | + free(cp); 79 | + goto out; 80 | + } 81 | + if ((r = sshbuf_put(our_version, cp, strlen(cp))) != 0) { 82 | + error("%s: sshbuf_put failed for obfuscation", __func__); 83 | + free(cp); 84 | + goto out; 85 | + } 86 | + } 87 | + 88 | if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ 89 | oerrno = errno; 90 | error("%s: sshbuf_consume_end: %s", __func__, ssh_err(r)); 91 | @@ -1319,6 +1352,8 @@ 92 | r = SSH_ERR_SYSTEM_ERROR; 93 | goto out; 94 | } 95 | + if(sshpkt_get_obfuscation(ssh) == 1) 96 | + obfuscate_input(&c, 1); 97 | if (c == '\r') { 98 | expect_nl = 1; 99 | continue; 100 | Index: openssh-8.4p1/obfuscate.c 101 | =================================================================== 102 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 103 | +++ openssh-8.4p1/obfuscate.c 2021-02-15 00:13:45.124094521 +0800 104 | @@ -0,0 +1,220 @@ 105 | +#include "includes.h" 106 | +#include 107 | +#include 108 | +#include "openbsd-compat/openssl-compat.h" 109 | +#include 110 | +#include 111 | +#include "atomicio.h" 112 | +#include "canohost.h" 113 | +#include "xmalloc.h" 114 | +#include "log.h" 115 | +#include "packet.h" 116 | +#include "obfuscate.h" 117 | + 118 | +static RC4_KEY rc4_input; 119 | +static RC4_KEY rc4_output; 120 | + 121 | +static const char *obfuscate_keyword = NULL; 122 | + 123 | +#define OBFUSCATE_KEY_LENGTH 16 124 | +#define OBFUSCATE_SEED_LENGTH 16 125 | +#define OBFUSCATE_HASH_ITERATIONS 6000 126 | +#define OBFUSCATE_MAX_PADDING 8192 127 | +#define OBFUSCATE_MAGIC_VALUE 0x0BF5CA7E 128 | + 129 | +struct seed_msg { 130 | + u_char seed_buffer[OBFUSCATE_SEED_LENGTH]; 131 | + u_int32_t magic; 132 | + u_int32_t padding_length; 133 | + u_char padding[]; 134 | +}; 135 | + 136 | +static void generate_key_pair(const u_char *, u_char *, u_char *); 137 | +static void generate_key(const u_char *, const u_char *, u_int, u_char *); 138 | +static void set_keys(const u_char *, const u_char *); 139 | +static void initialize(const u_char *, int); 140 | +static void read_forever(int); 141 | + 142 | + 143 | +/* 144 | + * Server calls this 145 | + */ 146 | +void 147 | +obfuscate_receive_seed(struct ssh *ssh, int sock_in) 148 | +{ 149 | + struct seed_msg seed; 150 | + 151 | + u_char padding_drain[OBFUSCATE_MAX_PADDING]; 152 | + u_int len; 153 | + u_int32_t padding_length; 154 | + 155 | + len = atomicio(read, sock_in, &seed, sizeof(struct seed_msg)); 156 | + 157 | + debug2("obfuscate_receive_seed: read %d byte seed message from client", len); 158 | + if(len != sizeof(struct seed_msg)) 159 | + fatal("obfuscate_receive_seed: read failed"); 160 | + 161 | + initialize(seed.seed_buffer, 1); 162 | + obfuscate_input((u_char *)&seed.magic, 8); 163 | + 164 | + if(OBFUSCATE_MAGIC_VALUE != ntohl(seed.magic)) { 165 | + logit("Magic value check failed (%u) on obfuscated handshake " 166 | + "from %.200s port %d", ntohl(seed.magic), 167 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 168 | + read_forever(sock_in); 169 | + } 170 | + padding_length = ntohl(seed.padding_length); 171 | + if(padding_length > OBFUSCATE_MAX_PADDING) { 172 | + logit("Illegal padding length %d for obfuscated handshake " 173 | + "from %.200s port %d", ntohl(seed.padding_length), 174 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 175 | + read_forever(sock_in); 176 | + } 177 | + len = atomicio(read, sock_in, padding_drain, padding_length); 178 | + if(len != padding_length) 179 | + fatal("obfuscate_receive_seed: read failed"); 180 | + debug2("obfuscate_receive_seed: read %d bytes of padding from client.", len); 181 | + obfuscate_input(padding_drain, padding_length); 182 | +} 183 | + 184 | +/* 185 | + * Client calls this 186 | + */ 187 | +void 188 | +obfuscate_send_seed(int sock_out) 189 | +{ 190 | + struct seed_msg *seed; 191 | + int i; 192 | + u_int32_t rnd = 0; 193 | + u_int message_length; 194 | + u_int padding_length; 195 | + 196 | + padding_length = arc4random() % OBFUSCATE_MAX_PADDING; 197 | + message_length = padding_length + sizeof(struct seed_msg); 198 | + seed = xmalloc(message_length); 199 | + 200 | + for(i = 0; i < OBFUSCATE_SEED_LENGTH; i++) { 201 | + if(i % 4 == 0) 202 | + rnd = arc4random(); 203 | + seed->seed_buffer[i] = rnd & 0xff; 204 | + rnd >>= 8; 205 | + } 206 | + seed->magic = htonl(OBFUSCATE_MAGIC_VALUE); 207 | + seed->padding_length = htonl(padding_length); 208 | + for(i = 0; i < (int)padding_length; i++) { 209 | + if(i % 4 == 0) 210 | + rnd = arc4random(); 211 | + seed->padding[i] = rnd & 0xff; 212 | + } 213 | + initialize(seed->seed_buffer, 0); 214 | + obfuscate_output(((u_char *)seed) + OBFUSCATE_SEED_LENGTH, 215 | + message_length - OBFUSCATE_SEED_LENGTH); 216 | + debug2("obfuscate_send_seed: Sending seed message with %d bytes of padding", padding_length); 217 | + atomicio(vwrite, sock_out, seed, message_length); 218 | + free(seed); 219 | + 220 | +} 221 | + 222 | +void 223 | +obfuscate_set_keyword(const char *keyword) 224 | +{ 225 | + debug2("obfuscate_set_keyword: Setting obfuscation keyword to '%s'", keyword); 226 | + obfuscate_keyword = keyword; 227 | +} 228 | + 229 | +void 230 | +obfuscate_input(u_char *buffer, u_int buffer_len) 231 | +{ 232 | + RC4(&rc4_input, buffer_len, buffer, buffer); 233 | +} 234 | + 235 | +void 236 | +obfuscate_output(u_char *buffer, u_int buffer_len) 237 | +{ 238 | + RC4(&rc4_output, buffer_len, buffer, buffer); 239 | +} 240 | + 241 | +static void 242 | +initialize(const u_char *seed, int server) 243 | +{ 244 | + u_char client_to_server_key[OBFUSCATE_KEY_LENGTH]; 245 | + u_char server_to_client_key[OBFUSCATE_KEY_LENGTH]; 246 | + 247 | + generate_key_pair(seed, client_to_server_key, server_to_client_key); 248 | + 249 | + if(server) 250 | + set_keys(client_to_server_key, server_to_client_key); 251 | + else 252 | + set_keys(server_to_client_key, client_to_server_key); 253 | +} 254 | + 255 | +static void 256 | +generate_key_pair(const u_char *seed, u_char *client_to_server_key, u_char *server_to_client_key) 257 | +{ 258 | + generate_key(seed, "client_to_server", strlen("client_to_server"), client_to_server_key); 259 | + generate_key(seed, "server_to_client", strlen("server_to_client"), server_to_client_key); 260 | +} 261 | + 262 | +static void 263 | +generate_key(const u_char *seed, const u_char *iv, u_int iv_len, u_char *key_data) 264 | +{ 265 | + EVP_MD_CTX *ctx; 266 | + u_char md_output[EVP_MAX_MD_SIZE]; 267 | + int md_len; 268 | + int i; 269 | + u_char *buffer; 270 | + u_char *p; 271 | + u_int buffer_length; 272 | + 273 | + if ((ctx = EVP_MD_CTX_new()) == NULL) 274 | + fatal("Cannot create new digest context"); 275 | + 276 | + buffer_length = OBFUSCATE_SEED_LENGTH + iv_len; 277 | + if(obfuscate_keyword) 278 | + buffer_length += strlen(obfuscate_keyword); 279 | + 280 | + p = buffer = xmalloc(buffer_length); 281 | + 282 | + memcpy(p, seed, OBFUSCATE_SEED_LENGTH); 283 | + p += OBFUSCATE_SEED_LENGTH; 284 | + 285 | + if(obfuscate_keyword) { 286 | + memcpy(p, obfuscate_keyword, strlen(obfuscate_keyword)); 287 | + p += strlen(obfuscate_keyword); 288 | + } 289 | + memcpy(p, iv, iv_len); 290 | + 291 | + EVP_DigestInit(ctx, EVP_sha1()); 292 | + EVP_DigestUpdate(ctx, buffer, OBFUSCATE_SEED_LENGTH + iv_len); 293 | + EVP_DigestFinal(ctx, md_output, &md_len); 294 | + 295 | + free(buffer); 296 | + 297 | + for(i = 0; i < OBFUSCATE_HASH_ITERATIONS; i++) { 298 | + EVP_DigestInit(ctx, EVP_sha1()); 299 | + EVP_DigestUpdate(ctx, md_output, md_len); 300 | + EVP_DigestFinal(ctx, md_output, &md_len); 301 | + } 302 | + 303 | + if(md_len < OBFUSCATE_KEY_LENGTH) 304 | + fatal("Cannot derive obfuscation keys from hash length of %d", md_len); 305 | + 306 | + memcpy(key_data, md_output, OBFUSCATE_KEY_LENGTH); 307 | +} 308 | + 309 | +static void 310 | +set_keys(const u_char *input_key, const u_char *output_key) 311 | +{ 312 | + RC4_set_key(&rc4_input, OBFUSCATE_KEY_LENGTH, input_key); 313 | + RC4_set_key(&rc4_output, OBFUSCATE_KEY_LENGTH, output_key); 314 | +} 315 | + 316 | +static void 317 | +read_forever(int sock_in) 318 | +{ 319 | + u_char discard_buffer[1024]; 320 | + 321 | + while(atomicio(read, sock_in, discard_buffer, sizeof(discard_buffer)) > 0) 322 | + ; 323 | + cleanup_exit(255); 324 | +} 325 | Index: openssh-8.4p1/obfuscate.h 326 | =================================================================== 327 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 328 | +++ openssh-8.4p1/obfuscate.h 2021-02-15 00:13:45.124094521 +0800 329 | @@ -0,0 +1,10 @@ 330 | +#ifndef _OBFUSCATE_H 331 | +#define _OBFUSCATE_H 332 | + 333 | +void obfuscate_receive_seed(struct ssh *, int); 334 | +void obfuscate_send_seed(int); 335 | +void obfuscate_set_keyword(const char *); 336 | +void obfuscate_input(u_char *, u_int); 337 | +void obfuscate_output(u_char *, u_int); 338 | + 339 | +#endif 340 | Index: openssh-8.4p1/packet.c 341 | =================================================================== 342 | --- openssh-8.4p1.orig/packet.c 2020-09-27 15:25:01.000000000 +0800 343 | +++ openssh-8.4p1/packet.c 2021-02-15 00:13:45.124094521 +0800 344 | @@ -94,6 +94,7 @@ 345 | #include "channels.h" 346 | #include "ssh.h" 347 | #include "packet.h" 348 | +#include "obfuscate.h" 349 | #include "ssherr.h" 350 | #include "sshbuf.h" 351 | 352 | @@ -177,6 +178,8 @@ 353 | /* Set to true if we are authenticated. */ 354 | int after_authentication; 355 | 356 | + int obfuscation; 357 | + 358 | int keep_alive_timeouts; 359 | 360 | /* The maximum time that we will wait to send or receive a packet */ 361 | @@ -1203,6 +1206,8 @@ 362 | if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) 363 | goto out; 364 | } 365 | + if(state->obfuscation) 366 | + obfuscate_output(cp, sshbuf_len(state->outgoing_packet)); 367 | #ifdef PACKET_DEBUG 368 | fprintf(stderr, "encrypted: "); 369 | sshbuf_dump(state->output, stderr); 370 | @@ -1545,6 +1550,8 @@ 371 | if ((r = sshbuf_reserve(state->incoming_packet, block_size, 372 | &cp)) != 0) 373 | goto out; 374 | + if(state->obfuscation) 375 | + obfuscate_input(sshbuf_mutable_ptr(state->input), block_size); 376 | if ((r = cipher_crypt(state->receive_context, 377 | state->p_send.seqnr, cp, sshbuf_ptr(state->input), 378 | block_size, 0, 0)) != 0) 379 | @@ -1610,6 +1617,8 @@ 380 | goto out; 381 | } 382 | } 383 | + if(state->obfuscation) 384 | + obfuscate_input(sshbuf_mutable_ptr(state->input), need); 385 | if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, 386 | &cp)) != 0) 387 | goto out; 388 | @@ -2745,3 +2754,25 @@ 389 | ssh->state->extra_pad = pad; 390 | return 0; 391 | } 392 | + 393 | +void 394 | +sshpkt_enable_obfuscation(struct ssh *ssh) 395 | +{ 396 | + debug("Obfuscation enabled"); 397 | + ssh->state->obfuscation = 1; 398 | +} 399 | + 400 | +void 401 | +sshpkt_disable_obfuscation(struct ssh *ssh) 402 | +{ 403 | + if(ssh->state->obfuscation) { 404 | + debug("Obfuscation disabled"); 405 | + ssh->state->obfuscation = 0; 406 | + } 407 | +} 408 | + 409 | +int 410 | +sshpkt_get_obfuscation(struct ssh *ssh) 411 | +{ 412 | + return ssh->state->obfuscation; 413 | +} 414 | Index: openssh-8.4p1/packet.h 415 | =================================================================== 416 | --- openssh-8.4p1.orig/packet.h 2020-09-27 15:25:01.000000000 +0800 417 | +++ openssh-8.4p1/packet.h 2021-02-15 00:13:45.124094521 +0800 418 | @@ -180,6 +180,9 @@ 419 | __attribute__((format(printf, 3, 4))) 420 | __attribute__((noreturn)); 421 | int sshpkt_msg_ignore(struct ssh *, u_int); 422 | +void sshpkt_enable_obfuscation(struct ssh *); 423 | +void sshpkt_disable_obfuscation(struct ssh *); 424 | +int sshpkt_get_obfuscation(struct ssh *); 425 | 426 | int sshpkt_put(struct ssh *ssh, const void *v, size_t len); 427 | int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b); 428 | Index: openssh-8.4p1/readconf.c 429 | =================================================================== 430 | --- openssh-8.4p1.orig/readconf.c 2021-02-14 19:49:28.000000000 +0800 431 | +++ openssh-8.4p1/readconf.c 2021-02-15 00:13:45.124094521 +0800 432 | @@ -144,7 +144,7 @@ 433 | oBadOption, 434 | oHost, oMatch, oInclude, 435 | oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, 436 | - oGatewayPorts, oExitOnForwardFailure, 437 | + oGatewayPorts, oExitOnForwardFailure, oObfuscateHandshake, oObfuscateKeyword, 438 | oPasswordAuthentication, 439 | oChallengeResponseAuthentication, oXAuthLocation, 440 | oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward, 441 | @@ -329,6 +329,8 @@ 442 | { "securitykeyprovider", oSecurityKeyProvider }, 443 | { "protocolkeepalives", oProtocolKeepAlives }, 444 | { "setuptimeout", oSetupTimeOut }, 445 | + { "obfuscatehandshake", oObfuscateHandshake }, 446 | + { "obfuscatekeyword", oObfuscateKeyword }, 447 | 448 | { NULL, oBadOption } 449 | }; 450 | @@ -1906,6 +1908,16 @@ 451 | *charptr = xstrdup(arg); 452 | break; 453 | 454 | + case oObfuscateHandshake: 455 | + intptr = &options->obfuscate_handshake; 456 | + goto parse_flag; 457 | + 458 | + case oObfuscateKeyword: 459 | + if (*activep) 460 | + options->obfuscate_handshake = 1; 461 | + charptr = &options->obfuscate_keyword; 462 | + goto parse_string; 463 | + 464 | case oDeprecated: 465 | debug("%s line %d: Deprecated option \"%s\"", 466 | filename, linenum, keyword); 467 | @@ -2105,6 +2117,8 @@ 468 | options->add_keys_to_agent_lifespan = -1; 469 | options->identity_agent = NULL; 470 | options->visual_host_key = -1; 471 | + options->obfuscate_handshake = 0; 472 | + options->obfuscate_keyword = NULL; 473 | options->ip_qos_interactive = -1; 474 | options->ip_qos_bulk = -1; 475 | options->request_tty = -1; 476 | Index: openssh-8.4p1/readconf.h 477 | =================================================================== 478 | --- openssh-8.4p1.orig/readconf.h 2021-02-14 19:49:28.000000000 +0800 479 | +++ openssh-8.4p1/readconf.h 2021-02-15 00:13:45.124094521 +0800 480 | @@ -147,6 +147,8 @@ 481 | int permit_local_command; 482 | char *remote_command; 483 | int visual_host_key; 484 | + int obfuscate_handshake; 485 | + char *obfuscate_keyword; 486 | 487 | int request_tty; 488 | 489 | Index: openssh-8.4p1/scp.c 490 | =================================================================== 491 | --- openssh-8.4p1.orig/scp.c 2021-02-14 19:49:28.000000000 +0800 492 | +++ openssh-8.4p1/scp.c 2021-02-15 00:16:52.039545456 +0800 493 | @@ -440,7 +440,7 @@ 494 | 495 | fflag = Tflag = tflag = 0; 496 | while ((ch = getopt(argc, argv, 497 | - "12346ABCTdfpqrtvF:J:P:S:c:i:l:o:")) != -1) { 498 | + "12346ABCTdfpqrtvF:J:P:S:c:i:l:o:zZ:")) != -1) { 499 | switch (ch) { 500 | /* User-visible flags. */ 501 | case '1': 502 | @@ -453,6 +453,7 @@ 503 | case '4': 504 | case '6': 505 | case 'C': 506 | + case 'z': 507 | addargs(&args, "-%c", ch); 508 | addargs(&remote_remote_args, "-%c", ch); 509 | break; 510 | @@ -464,6 +465,7 @@ 511 | case 'i': 512 | case 'F': 513 | case 'J': 514 | + case 'Z': 515 | addargs(&remote_remote_args, "-%c", ch); 516 | addargs(&remote_remote_args, "%s", optarg); 517 | addargs(&args, "-%c", ch); 518 | Index: openssh-8.4p1/servconf.c 519 | =================================================================== 520 | --- openssh-8.4p1.orig/servconf.c 2021-02-14 19:49:28.000000000 +0800 521 | +++ openssh-8.4p1/servconf.c 2021-02-15 00:13:45.128094509 +0800 522 | @@ -96,6 +96,7 @@ 523 | 524 | /* Standard Options */ 525 | options->num_ports = 0; 526 | + options->num_obfuscated_ports = 0; 527 | options->ports_from_cmdline = 0; 528 | options->queued_listen_addrs = NULL; 529 | options->num_queued_listens = 0; 530 | @@ -181,6 +182,7 @@ 531 | options->permitted_listens = NULL; 532 | options->adm_forced_command = NULL; 533 | options->chroot_directory = NULL; 534 | + options->obfuscate_keyword = NULL; 535 | options->authorized_keys_command = NULL; 536 | options->authorized_keys_command_user = NULL; 537 | options->revoked_keys_file = NULL; 538 | @@ -323,7 +325,7 @@ 539 | #endif /* WITH_XMSS */ 540 | } 541 | /* No certificates by default */ 542 | - if (options->num_ports == 0) 543 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 544 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 545 | if (options->address_family == -1) 546 | options->address_family = AF_UNSPEC; 547 | @@ -518,7 +520,7 @@ 548 | /* Portable-specific options */ 549 | sUsePAM, 550 | /* Standard Options */ 551 | - sPort, sHostKeyFile, sLoginGraceTime, 552 | + sPort, sObfuscatedPort, sObfuscateKeyword, sHostKeyFile, sLoginGraceTime, 553 | sPermitRootLogin, sLogFacility, sLogLevel, 554 | sRhostsRSAAuthentication, sRSAAuthentication, 555 | sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, 556 | @@ -576,6 +578,8 @@ 557 | { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, 558 | /* Standard Options */ 559 | { "port", sPort, SSHCFG_GLOBAL }, 560 | + { "obfuscatedport", sObfuscatedPort, SSHCFG_GLOBAL }, 561 | + { "obfuscatekeyword", sObfuscateKeyword, SSHCFG_GLOBAL }, 562 | { "hostkey", sHostKeyFile, SSHCFG_GLOBAL }, 563 | { "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL }, /* alias */ 564 | { "hostkeyagent", sHostKeyAgent, SSHCFG_GLOBAL }, 565 | @@ -796,6 +800,10 @@ 566 | add_one_listen_addr(options, addr, rdomain, 567 | options->ports[i]); 568 | } 569 | + for (i = 0; i < options->num_obfuscated_ports; i++) { 570 | + add_one_listen_addr(options, addr, rdomain, 571 | + options->obfuscated_ports[i]); 572 | + } 573 | } 574 | } 575 | 576 | @@ -910,7 +918,7 @@ 577 | u_int i; 578 | struct queued_listenaddr *qla; 579 | 580 | - if (options->num_ports == 0) 581 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 582 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 583 | if (options->address_family == -1) 584 | options->address_family = AF_UNSPEC; 585 | @@ -1368,6 +1376,30 @@ 586 | filename, linenum); 587 | break; 588 | 589 | + case sObfuscatedPort: 590 | + if(options->ports_from_cmdline) 591 | + return 0; 592 | + if(options->listen_addrs != NULL) 593 | + fatal("%s line %d: ports must be specified before ListenAddress.", filename, linenum); 594 | + if(options->num_obfuscated_ports >= MAX_PORTS) 595 | + fatal("%s line %d: too many ports.", filename, linenum); 596 | + arg = strdelim(&cp); 597 | + if(!arg || *arg == '\0') 598 | + fatal("%s line %d: missing port number.", filename, linenum); 599 | + options->obfuscated_ports[options->num_obfuscated_ports++] = a2port(arg); 600 | + if(options->obfuscated_ports[options->num_obfuscated_ports - 1] <= 0) 601 | + fatal("%s line %d: badly formatted port number.", filename, linenum); 602 | + break; 603 | + case sObfuscateKeyword: 604 | + charptr = &options->obfuscate_keyword; 605 | + arg = strdelim(&cp); 606 | + if(!arg || *arg == '\0') 607 | + fatal("%s line %d: missing keyword argument.", 608 | + filename, linenum); 609 | + if(*activep && *charptr == NULL) 610 | + *charptr = xstrdup(arg); 611 | + break; 612 | + 613 | case sLoginGraceTime: 614 | intptr = &options->login_grace_time; 615 | parse_time: 616 | Index: openssh-8.4p1/servconf.h 617 | =================================================================== 618 | --- openssh-8.4p1.orig/servconf.h 2021-02-14 19:49:28.000000000 +0800 619 | +++ openssh-8.4p1/servconf.h 2021-02-15 00:13:45.128094509 +0800 620 | @@ -207,6 +207,11 @@ 621 | u_int num_permitted_listens; 622 | 623 | char *chroot_directory; 624 | + 625 | + int obfuscated_ports[MAX_PORTS]; 626 | + u_int num_obfuscated_ports; 627 | + char *obfuscate_keyword; 628 | + 629 | char *revoked_keys_file; 630 | char *trusted_user_ca_keys; 631 | char *authorized_keys_command; 632 | Index: openssh-8.4p1/sftp.c 633 | =================================================================== 634 | --- openssh-8.4p1.orig/sftp.c 2020-09-27 15:25:01.000000000 +0800 635 | +++ openssh-8.4p1/sftp.c 2021-02-15 00:18:28.247249714 +0800 636 | @@ -2408,13 +2408,14 @@ 637 | infile = stdin; 638 | 639 | while ((ch = getopt(argc, argv, 640 | - "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:")) != -1) { 641 | + "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:zZ:")) != -1) { 642 | switch (ch) { 643 | /* Passed through to ssh(1) */ 644 | case 'A': 645 | case '4': 646 | case '6': 647 | case 'C': 648 | + case 'z': 649 | addargs(&args, "-%c", ch); 650 | break; 651 | /* Passed through to ssh(1) with argument */ 652 | @@ -2423,6 +2424,7 @@ 653 | case 'c': 654 | case 'i': 655 | case 'o': 656 | + case 'Z': 657 | addargs(&args, "-%c", ch); 658 | addargs(&args, "%s", optarg); 659 | break; 660 | Index: openssh-8.4p1/ssh.c 661 | =================================================================== 662 | --- openssh-8.4p1.orig/ssh.c 2021-02-14 19:49:28.000000000 +0800 663 | +++ openssh-8.4p1/ssh.c 2021-02-15 00:13:45.128094509 +0800 664 | @@ -203,13 +203,14 @@ 665 | usage(void) 666 | { 667 | fprintf(stderr, 668 | -"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]\n" 669 | +"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYyz] [-B bind_interface]\n" 670 | " [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]\n" 671 | " [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]\n" 672 | " [-i identity_file] [-J [user@]host[:port]] [-L address]\n" 673 | " [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n" 674 | " [-Q query_option] [-R address] [-S ctl_path] [-W host:port]\n" 675 | -" [-w local_tun[:remote_tun]] destination [command]\n" 676 | +" [-w local_tun[:remote_tun]] [-Z obfuscate_keyword]\n" 677 | +" destination [command]\n" 678 | ); 679 | exit(255); 680 | } 681 | @@ -722,7 +723,7 @@ 682 | 683 | again: 684 | while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" 685 | - "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { 686 | + "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYyzZ:")) != -1) { 687 | switch (opt) { 688 | case '1': 689 | fatal("SSH protocol v.1 is no longer supported"); 690 | @@ -1068,6 +1069,13 @@ 691 | case 'F': 692 | config = optarg; 693 | break; 694 | + case 'z': 695 | + options.obfuscate_handshake = 1; 696 | + break; 697 | + case 'Z': 698 | + options.obfuscate_handshake = 1; 699 | + options.obfuscate_keyword = optarg; 700 | + break; 701 | default: 702 | usage(); 703 | } 704 | Index: openssh-8.4p1/sshconnect.c 705 | =================================================================== 706 | --- openssh-8.4p1.orig/sshconnect.c 2021-02-14 19:49:28.000000000 +0800 707 | +++ openssh-8.4p1/sshconnect.c 2021-02-15 00:13:45.128094509 +0800 708 | @@ -64,6 +64,7 @@ 709 | #include "monitor_fdpass.h" 710 | #include "ssh2.h" 711 | #include "version.h" 712 | +#include "obfuscate.h" 713 | #include "authfile.h" 714 | #include "ssherr.h" 715 | #include "authfd.h" 716 | @@ -284,6 +285,12 @@ 717 | if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) 718 | return -1; /* ssh_packet_set_connection logs error */ 719 | 720 | + if(options.obfuscate_handshake) { 721 | + if(options.obfuscate_keyword) 722 | + obfuscate_set_keyword(options.obfuscate_keyword); 723 | + sshpkt_enable_obfuscation(ssh); 724 | + } 725 | + 726 | return 0; 727 | } 728 | 729 | @@ -544,6 +551,11 @@ 730 | /* Set the connection. */ 731 | if (ssh_packet_set_connection(ssh, sock, sock) == NULL) 732 | return -1; /* ssh_packet_set_connection logs error */ 733 | + if(options.obfuscate_handshake) { 734 | + if(options.obfuscate_keyword) 735 | + obfuscate_set_keyword(options.obfuscate_keyword); 736 | + sshpkt_enable_obfuscation(ssh); 737 | + } 738 | 739 | return 0; 740 | } 741 | @@ -1295,6 +1307,9 @@ 742 | host = xstrdup(orighost); 743 | lowercase(host); 744 | 745 | + if(options.obfuscate_handshake) 746 | + obfuscate_send_seed(ssh_packet_get_connection_out(ssh)); 747 | + 748 | /* Exchange protocol version identification strings with the server. */ 749 | if ((r = kex_exchange_identification(ssh, timeout_ms, 1, NULL)) != 0) 750 | sshpkt_fatal(ssh, r, "banner exchange"); 751 | Index: openssh-8.4p1/sshd.c 752 | =================================================================== 753 | --- openssh-8.4p1.orig/sshd.c 2021-02-14 19:49:28.000000000 +0800 754 | +++ openssh-8.4p1/sshd.c 2021-02-15 00:13:45.128094509 +0800 755 | @@ -122,6 +122,7 @@ 756 | #include "ssh-gss.h" 757 | #endif 758 | #include "monitor_wrap.h" 759 | +#include "obfuscate.h" 760 | #include "ssh-sandbox.h" 761 | #include "auth-options.h" 762 | #include "version.h" 763 | @@ -268,6 +269,9 @@ 764 | /* message to be displayed after login */ 765 | struct sshbuf *loginmsg; 766 | 767 | +/* Enable handshake obfuscation */ 768 | +int use_obfuscation = 0; 769 | + 770 | /* Unprivileged user */ 771 | struct passwd *privsep_pw = NULL; 772 | 773 | @@ -1540,7 +1544,7 @@ 774 | struct ssh *ssh = NULL; 775 | extern char *optarg; 776 | extern int optind; 777 | - int r, opt, on = 1, already_daemon, remote_port; 778 | + int r, opt, on = 1, already_daemon, remote_port, local_port; 779 | int sock_in = -1, sock_out = -1, newsock = -1; 780 | const char *remote_ip, *rdomain; 781 | char *fp, *line, *laddr, *logfile = NULL; 782 | @@ -2176,6 +2180,14 @@ 783 | channel_set_af(ssh, options.address_family); 784 | process_permitopen(ssh, &options); 785 | 786 | + local_port = ssh_local_port(ssh); 787 | + for(i = 0; i < options.num_obfuscated_ports; i++) { 788 | + if(options.obfuscated_ports[i] == local_port) { 789 | + use_obfuscation = 1; 790 | + break; 791 | + } 792 | + } 793 | + 794 | /* Set SO_KEEPALIVE if requested. */ 795 | if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && 796 | setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) 797 | @@ -2241,6 +2253,13 @@ 798 | if (!debug_flag) 799 | alarm(options.login_grace_time); 800 | 801 | + if (use_obfuscation) { 802 | + if (options.obfuscate_keyword) 803 | + obfuscate_set_keyword(options.obfuscate_keyword); 804 | + sshpkt_enable_obfuscation(ssh); 805 | + obfuscate_receive_seed(ssh, sock_in); 806 | + } 807 | + 808 | if ((r = kex_exchange_identification(ssh, -1, options.debian_banner, 809 | options.version_addendum)) != 0) 810 | sshpkt_fatal(ssh, r, "banner exchange"); 811 | @@ -2266,8 +2285,12 @@ 812 | auth_debug_reset(); 813 | 814 | if (use_privsep) { 815 | - if (privsep_preauth(ssh) == 1) 816 | + 817 | + if (privsep_preauth(ssh) == 1) { 818 | + if(use_obfuscation) 819 | + sshpkt_disable_obfuscation(ssh); 820 | goto authenticated; 821 | + } 822 | } else if (have_agent) { 823 | if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { 824 | error("Unable to get agent socket: %s", ssh_err(r)); 825 | Index: openssh-8.4p1/sshd_config 826 | =================================================================== 827 | --- openssh-8.4p1.orig/sshd_config 2021-02-14 19:49:28.000000000 +0800 828 | +++ openssh-8.4p1/sshd_config 2021-02-15 00:13:45.128094509 +0800 829 | @@ -13,6 +13,8 @@ 830 | Include /etc/ssh/sshd_config.d/*.conf 831 | 832 | #Port 22 833 | +#ObfuscatedPort 222 834 | +#ObfuscateKeyword key 835 | #AddressFamily any 836 | #ListenAddress 0.0.0.0 837 | #ListenAddress :: 838 | -------------------------------------------------------------------------------- /debian/9.2p1.diff: -------------------------------------------------------------------------------- 1 | Index: openssh-9.2p1/Makefile.in 2 | =================================================================== 3 | --- openssh-9.2p1.orig/Makefile.in 2023-02-24 10:54:30.000000000 +0800 4 | +++ openssh-9.2p1/Makefile.in 2023-02-24 10:55:06.821149662 +0800 5 | @@ -112,7 +112,7 @@ 6 | kexsntrup761x25519.o sntrup761.o kexgen.o \ 7 | kexgssc.o \ 8 | sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ 9 | - sshbuf-io.o 10 | + sshbuf-io.o obfuscate.o 11 | 12 | SKOBJS= ssh-sk-client.o 13 | 14 | Index: openssh-9.2p1/kex.c 15 | =================================================================== 16 | --- openssh-9.2p1.orig/kex.c 2023-02-24 10:54:30.000000000 +0800 17 | +++ openssh-9.2p1/kex.c 2023-02-24 10:55:06.821149662 +0800 18 | @@ -59,6 +59,7 @@ 19 | #include "monitor.h" 20 | #include "xmalloc.h" 21 | 22 | +#include "obfuscate.h" 23 | #include "ssherr.h" 24 | #include "sshbuf.h" 25 | #include "digest.h" 26 | @@ -521,9 +522,12 @@ 27 | return r; 28 | debug("SSH2_MSG_NEWKEYS sent"); 29 | ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); 30 | - if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) 31 | + if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) { 32 | + sshpkt_disable_obfuscation(ssh); 33 | if ((r = kex_send_ext_info(ssh)) != 0) 34 | return r; 35 | + sshpkt_enable_obfuscation(ssh); 36 | + } 37 | debug("expecting SSH2_MSG_NEWKEYS"); 38 | return 0; 39 | } 40 | @@ -601,6 +605,7 @@ 41 | kex->flags &= ~KEX_INIT_SENT; 42 | free(kex->name); 43 | kex->name = NULL; 44 | + sshpkt_disable_obfuscation(ssh); 45 | return 0; 46 | } 47 | 48 | @@ -1299,14 +1304,42 @@ 49 | goto out; 50 | } 51 | 52 | + /* Make copy and obfuscate the original */ 53 | + if (sshpkt_get_obfuscation(ssh) == 1) { 54 | + if ((cp = sshbuf_dup_string(our_version)) == NULL) { 55 | + error("%s: sshbuf_dup_string failed for obfuscation", __func__); 56 | + r = SSH_ERR_ALLOC_FAIL; 57 | + goto out; 58 | + } 59 | + obfuscate_output(sshbuf_mutable_ptr(our_version), sshbuf_len(our_version)); 60 | + } 61 | + 62 | if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), 63 | sshbuf_mutable_ptr(our_version), 64 | sshbuf_len(our_version)) != sshbuf_len(our_version)) { 65 | oerrno = errno; 66 | debug_f("write: %.100s", strerror(errno)); 67 | r = SSH_ERR_SYSTEM_ERROR; 68 | + if (sshpkt_get_obfuscation(ssh) == 1) { 69 | + free(cp); 70 | + } 71 | goto out; 72 | } 73 | + 74 | + /* Restore the original */ 75 | + if (sshpkt_get_obfuscation(ssh) == 1) { 76 | + if ((r = sshbuf_consume(our_version, sshbuf_len(our_version))) != 0) { 77 | + error("%s: sshbuf_consume failed for obfuscation", __func__); 78 | + free(cp); 79 | + goto out; 80 | + } 81 | + if ((r = sshbuf_put(our_version, cp, strlen(cp))) != 0) { 82 | + error("%s: sshbuf_put failed for obfuscation", __func__); 83 | + free(cp); 84 | + goto out; 85 | + } 86 | + } 87 | + 88 | if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ 89 | oerrno = errno; 90 | error_fr(r, "sshbuf_consume_end"); 91 | @@ -1363,6 +1396,8 @@ 92 | r = SSH_ERR_SYSTEM_ERROR; 93 | goto out; 94 | } 95 | + if(sshpkt_get_obfuscation(ssh) == 1) 96 | + obfuscate_input(&c, 1); 97 | if (c == '\r') { 98 | expect_nl = 1; 99 | continue; 100 | Index: openssh-9.2p1/obfuscate.c 101 | =================================================================== 102 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 103 | +++ openssh-9.2p1/obfuscate.c 2023-02-24 10:55:06.821149662 +0800 104 | @@ -0,0 +1,220 @@ 105 | +#include "includes.h" 106 | +#include 107 | +#include 108 | +#include "openbsd-compat/openssl-compat.h" 109 | +#include 110 | +#include 111 | +#include "atomicio.h" 112 | +#include "canohost.h" 113 | +#include "xmalloc.h" 114 | +#include "log.h" 115 | +#include "packet.h" 116 | +#include "obfuscate.h" 117 | + 118 | +static RC4_KEY rc4_input; 119 | +static RC4_KEY rc4_output; 120 | + 121 | +static const char *obfuscate_keyword = NULL; 122 | + 123 | +#define OBFUSCATE_KEY_LENGTH 16 124 | +#define OBFUSCATE_SEED_LENGTH 16 125 | +#define OBFUSCATE_HASH_ITERATIONS 6000 126 | +#define OBFUSCATE_MAX_PADDING 8192 127 | +#define OBFUSCATE_MAGIC_VALUE 0x0BF5CA7E 128 | + 129 | +struct seed_msg { 130 | + u_char seed_buffer[OBFUSCATE_SEED_LENGTH]; 131 | + u_int32_t magic; 132 | + u_int32_t padding_length; 133 | + u_char padding[]; 134 | +}; 135 | + 136 | +static void generate_key_pair(const u_char *, u_char *, u_char *); 137 | +static void generate_key(const u_char *, const u_char *, u_int, u_char *); 138 | +static void set_keys(const u_char *, const u_char *); 139 | +static void initialize(const u_char *, int); 140 | +static void read_forever(int); 141 | + 142 | + 143 | +/* 144 | + * Server calls this 145 | + */ 146 | +void 147 | +obfuscate_receive_seed(struct ssh *ssh, int sock_in) 148 | +{ 149 | + struct seed_msg seed; 150 | + 151 | + u_char padding_drain[OBFUSCATE_MAX_PADDING]; 152 | + u_int len; 153 | + u_int32_t padding_length; 154 | + 155 | + len = atomicio(read, sock_in, &seed, sizeof(struct seed_msg)); 156 | + 157 | + debug2("obfuscate_receive_seed: read %d byte seed message from client", len); 158 | + if(len != sizeof(struct seed_msg)) 159 | + fatal("obfuscate_receive_seed: read failed"); 160 | + 161 | + initialize(seed.seed_buffer, 1); 162 | + obfuscate_input((u_char *)&seed.magic, 8); 163 | + 164 | + if(OBFUSCATE_MAGIC_VALUE != ntohl(seed.magic)) { 165 | + logit("Magic value check failed (%u) on obfuscated handshake " 166 | + "from %.200s port %d", ntohl(seed.magic), 167 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 168 | + read_forever(sock_in); 169 | + } 170 | + padding_length = ntohl(seed.padding_length); 171 | + if(padding_length > OBFUSCATE_MAX_PADDING) { 172 | + logit("Illegal padding length %d for obfuscated handshake " 173 | + "from %.200s port %d", ntohl(seed.padding_length), 174 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 175 | + read_forever(sock_in); 176 | + } 177 | + len = atomicio(read, sock_in, padding_drain, padding_length); 178 | + if(len != padding_length) 179 | + fatal("obfuscate_receive_seed: read failed"); 180 | + debug2("obfuscate_receive_seed: read %d bytes of padding from client.", len); 181 | + obfuscate_input(padding_drain, padding_length); 182 | +} 183 | + 184 | +/* 185 | + * Client calls this 186 | + */ 187 | +void 188 | +obfuscate_send_seed(int sock_out) 189 | +{ 190 | + struct seed_msg *seed; 191 | + int i; 192 | + u_int32_t rnd = 0; 193 | + u_int message_length; 194 | + u_int padding_length; 195 | + 196 | + padding_length = arc4random() % OBFUSCATE_MAX_PADDING; 197 | + message_length = padding_length + sizeof(struct seed_msg); 198 | + seed = xmalloc(message_length); 199 | + 200 | + for(i = 0; i < OBFUSCATE_SEED_LENGTH; i++) { 201 | + if(i % 4 == 0) 202 | + rnd = arc4random(); 203 | + seed->seed_buffer[i] = rnd & 0xff; 204 | + rnd >>= 8; 205 | + } 206 | + seed->magic = htonl(OBFUSCATE_MAGIC_VALUE); 207 | + seed->padding_length = htonl(padding_length); 208 | + for(i = 0; i < (int)padding_length; i++) { 209 | + if(i % 4 == 0) 210 | + rnd = arc4random(); 211 | + seed->padding[i] = rnd & 0xff; 212 | + } 213 | + initialize(seed->seed_buffer, 0); 214 | + obfuscate_output(((u_char *)seed) + OBFUSCATE_SEED_LENGTH, 215 | + message_length - OBFUSCATE_SEED_LENGTH); 216 | + debug2("obfuscate_send_seed: Sending seed message with %d bytes of padding", padding_length); 217 | + atomicio(vwrite, sock_out, seed, message_length); 218 | + free(seed); 219 | + 220 | +} 221 | + 222 | +void 223 | +obfuscate_set_keyword(const char *keyword) 224 | +{ 225 | + debug2("obfuscate_set_keyword: Setting obfuscation keyword to '%s'", keyword); 226 | + obfuscate_keyword = keyword; 227 | +} 228 | + 229 | +void 230 | +obfuscate_input(u_char *buffer, u_int buffer_len) 231 | +{ 232 | + RC4(&rc4_input, buffer_len, buffer, buffer); 233 | +} 234 | + 235 | +void 236 | +obfuscate_output(u_char *buffer, u_int buffer_len) 237 | +{ 238 | + RC4(&rc4_output, buffer_len, buffer, buffer); 239 | +} 240 | + 241 | +static void 242 | +initialize(const u_char *seed, int server) 243 | +{ 244 | + u_char client_to_server_key[OBFUSCATE_KEY_LENGTH]; 245 | + u_char server_to_client_key[OBFUSCATE_KEY_LENGTH]; 246 | + 247 | + generate_key_pair(seed, client_to_server_key, server_to_client_key); 248 | + 249 | + if(server) 250 | + set_keys(client_to_server_key, server_to_client_key); 251 | + else 252 | + set_keys(server_to_client_key, client_to_server_key); 253 | +} 254 | + 255 | +static void 256 | +generate_key_pair(const u_char *seed, u_char *client_to_server_key, u_char *server_to_client_key) 257 | +{ 258 | + generate_key(seed, "client_to_server", strlen("client_to_server"), client_to_server_key); 259 | + generate_key(seed, "server_to_client", strlen("server_to_client"), server_to_client_key); 260 | +} 261 | + 262 | +static void 263 | +generate_key(const u_char *seed, const u_char *iv, u_int iv_len, u_char *key_data) 264 | +{ 265 | + EVP_MD_CTX *ctx; 266 | + u_char md_output[EVP_MAX_MD_SIZE]; 267 | + int md_len; 268 | + int i; 269 | + u_char *buffer; 270 | + u_char *p; 271 | + u_int buffer_length; 272 | + 273 | + if ((ctx = EVP_MD_CTX_new()) == NULL) 274 | + fatal("Cannot create new digest context"); 275 | + 276 | + buffer_length = OBFUSCATE_SEED_LENGTH + iv_len; 277 | + if(obfuscate_keyword) 278 | + buffer_length += strlen(obfuscate_keyword); 279 | + 280 | + p = buffer = xmalloc(buffer_length); 281 | + 282 | + memcpy(p, seed, OBFUSCATE_SEED_LENGTH); 283 | + p += OBFUSCATE_SEED_LENGTH; 284 | + 285 | + if(obfuscate_keyword) { 286 | + memcpy(p, obfuscate_keyword, strlen(obfuscate_keyword)); 287 | + p += strlen(obfuscate_keyword); 288 | + } 289 | + memcpy(p, iv, iv_len); 290 | + 291 | + EVP_DigestInit(ctx, EVP_sha1()); 292 | + EVP_DigestUpdate(ctx, buffer, OBFUSCATE_SEED_LENGTH + iv_len); 293 | + EVP_DigestFinal(ctx, md_output, &md_len); 294 | + 295 | + free(buffer); 296 | + 297 | + for(i = 0; i < OBFUSCATE_HASH_ITERATIONS; i++) { 298 | + EVP_DigestInit(ctx, EVP_sha1()); 299 | + EVP_DigestUpdate(ctx, md_output, md_len); 300 | + EVP_DigestFinal(ctx, md_output, &md_len); 301 | + } 302 | + 303 | + if(md_len < OBFUSCATE_KEY_LENGTH) 304 | + fatal("Cannot derive obfuscation keys from hash length of %d", md_len); 305 | + 306 | + memcpy(key_data, md_output, OBFUSCATE_KEY_LENGTH); 307 | +} 308 | + 309 | +static void 310 | +set_keys(const u_char *input_key, const u_char *output_key) 311 | +{ 312 | + RC4_set_key(&rc4_input, OBFUSCATE_KEY_LENGTH, input_key); 313 | + RC4_set_key(&rc4_output, OBFUSCATE_KEY_LENGTH, output_key); 314 | +} 315 | + 316 | +static void 317 | +read_forever(int sock_in) 318 | +{ 319 | + u_char discard_buffer[1024]; 320 | + 321 | + while(atomicio(read, sock_in, discard_buffer, sizeof(discard_buffer)) > 0) 322 | + ; 323 | + cleanup_exit(255); 324 | +} 325 | Index: openssh-9.2p1/obfuscate.h 326 | =================================================================== 327 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 328 | +++ openssh-9.2p1/obfuscate.h 2023-02-24 10:55:06.821149662 +0800 329 | @@ -0,0 +1,10 @@ 330 | +#ifndef _OBFUSCATE_H 331 | +#define _OBFUSCATE_H 332 | + 333 | +void obfuscate_receive_seed(struct ssh *, int); 334 | +void obfuscate_send_seed(int); 335 | +void obfuscate_set_keyword(const char *); 336 | +void obfuscate_input(u_char *, u_int); 337 | +void obfuscate_output(u_char *, u_int); 338 | + 339 | +#endif 340 | Index: openssh-9.2p1/packet.c 341 | =================================================================== 342 | --- openssh-9.2p1.orig/packet.c 2023-02-02 20:21:54.000000000 +0800 343 | +++ openssh-9.2p1/packet.c 2023-02-24 10:55:06.821149662 +0800 344 | @@ -94,6 +94,7 @@ 345 | #include "channels.h" 346 | #include "ssh.h" 347 | #include "packet.h" 348 | +#include "obfuscate.h" 349 | #include "ssherr.h" 350 | #include "sshbuf.h" 351 | 352 | @@ -177,6 +178,8 @@ 353 | /* Set to true if we are authenticated. */ 354 | int after_authentication; 355 | 356 | + int obfuscation; 357 | + 358 | int keep_alive_timeouts; 359 | 360 | /* The maximum time that we will wait to send or receive a packet */ 361 | @@ -1200,6 +1203,8 @@ 362 | if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) 363 | goto out; 364 | } 365 | + if(state->obfuscation) 366 | + obfuscate_output(cp, sshbuf_len(state->outgoing_packet)); 367 | #ifdef PACKET_DEBUG 368 | fprintf(stderr, "encrypted: "); 369 | sshbuf_dump(state->output, stderr); 370 | @@ -1534,6 +1539,8 @@ 371 | if ((r = sshbuf_reserve(state->incoming_packet, block_size, 372 | &cp)) != 0) 373 | goto out; 374 | + if(state->obfuscation) 375 | + obfuscate_input(sshbuf_mutable_ptr(state->input), block_size); 376 | if ((r = cipher_crypt(state->receive_context, 377 | state->p_send.seqnr, cp, sshbuf_ptr(state->input), 378 | block_size, 0, 0)) != 0) 379 | @@ -1599,6 +1606,8 @@ 380 | goto out; 381 | } 382 | } 383 | + if(state->obfuscation) 384 | + obfuscate_input(sshbuf_mutable_ptr(state->input), need); 385 | if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, 386 | &cp)) != 0) 387 | goto out; 388 | @@ -2717,3 +2726,25 @@ 389 | ssh->state->extra_pad = pad; 390 | return 0; 391 | } 392 | + 393 | +void 394 | +sshpkt_enable_obfuscation(struct ssh *ssh) 395 | +{ 396 | + debug("Obfuscation enabled"); 397 | + ssh->state->obfuscation = 1; 398 | +} 399 | + 400 | +void 401 | +sshpkt_disable_obfuscation(struct ssh *ssh) 402 | +{ 403 | + if(ssh->state->obfuscation) { 404 | + debug("Obfuscation disabled"); 405 | + ssh->state->obfuscation = 0; 406 | + } 407 | +} 408 | + 409 | +int 410 | +sshpkt_get_obfuscation(struct ssh *ssh) 411 | +{ 412 | + return ssh->state->obfuscation; 413 | +} 414 | Index: openssh-9.2p1/packet.h 415 | =================================================================== 416 | --- openssh-9.2p1.orig/packet.h 2023-02-02 20:21:54.000000000 +0800 417 | +++ openssh-9.2p1/packet.h 2023-02-24 10:55:06.821149662 +0800 418 | @@ -182,6 +182,9 @@ 419 | __attribute__((format(printf, 3, 4))) 420 | __attribute__((noreturn)); 421 | int sshpkt_msg_ignore(struct ssh *, u_int); 422 | +void sshpkt_enable_obfuscation(struct ssh *); 423 | +void sshpkt_disable_obfuscation(struct ssh *); 424 | +int sshpkt_get_obfuscation(struct ssh *); 425 | 426 | int sshpkt_put(struct ssh *ssh, const void *v, size_t len); 427 | int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b); 428 | Index: openssh-9.2p1/readconf.c 429 | =================================================================== 430 | --- openssh-9.2p1.orig/readconf.c 2023-02-24 10:54:30.000000000 +0800 431 | +++ openssh-9.2p1/readconf.c 2023-02-24 10:57:04.041464823 +0800 432 | @@ -144,7 +144,7 @@ 433 | oBadOption, 434 | oHost, oMatch, oInclude, 435 | oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, 436 | - oGatewayPorts, oExitOnForwardFailure, 437 | + oGatewayPorts, oExitOnForwardFailure, oObfuscateHandshake, oObfuscateKeyword, 438 | oPasswordAuthentication, 439 | oXAuthLocation, 440 | oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward, 441 | @@ -342,6 +342,8 @@ 442 | { "enableescapecommandline", oEnableEscapeCommandline }, 443 | { "protocolkeepalives", oProtocolKeepAlives }, 444 | { "setuptimeout", oSetupTimeOut }, 445 | + { "obfuscatehandshake", oObfuscateHandshake }, 446 | + { "obfuscatekeyword", oObfuscateKeyword }, 447 | 448 | { NULL, oBadOption } 449 | }; 450 | @@ -2248,6 +2250,16 @@ 451 | intptr = &options->required_rsa_size; 452 | goto parse_int; 453 | 454 | + case oObfuscateHandshake: 455 | + intptr = &options->obfuscate_handshake; 456 | + goto parse_flag; 457 | + 458 | + case oObfuscateKeyword: 459 | + if (*activep) 460 | + options->obfuscate_handshake = 1; 461 | + charptr = &options->obfuscate_keyword; 462 | + goto parse_string; 463 | + 464 | case oDeprecated: 465 | debug("%s line %d: Deprecated option \"%s\"", 466 | filename, linenum, keyword); 467 | @@ -2482,6 +2494,8 @@ 468 | options->add_keys_to_agent_lifespan = -1; 469 | options->identity_agent = NULL; 470 | options->visual_host_key = -1; 471 | + options->obfuscate_handshake = 0; 472 | + options->obfuscate_keyword = NULL; 473 | options->ip_qos_interactive = -1; 474 | options->ip_qos_bulk = -1; 475 | options->request_tty = -1; 476 | Index: openssh-9.2p1/readconf.h 477 | =================================================================== 478 | --- openssh-9.2p1.orig/readconf.h 2023-02-24 10:54:30.000000000 +0800 479 | +++ openssh-9.2p1/readconf.h 2023-02-24 10:55:06.821149662 +0800 480 | @@ -151,6 +151,8 @@ 481 | int permit_local_command; 482 | char *remote_command; 483 | int visual_host_key; 484 | + int obfuscate_handshake; 485 | + char *obfuscate_keyword; 486 | 487 | int request_tty; 488 | int session_type; 489 | Index: openssh-9.2p1/scp.c 490 | =================================================================== 491 | --- openssh-9.2p1.orig/scp.c 2023-02-24 10:54:30.000000000 +0800 492 | +++ openssh-9.2p1/scp.c 2023-02-24 10:55:06.821149662 +0800 493 | @@ -512,7 +512,7 @@ 494 | 495 | fflag = Tflag = tflag = 0; 496 | while ((ch = getopt(argc, argv, 497 | - "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:")) != -1) { 498 | + "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:zZ:")) != -1) { 499 | switch (ch) { 500 | /* User-visible flags. */ 501 | case '1': 502 | @@ -525,6 +525,7 @@ 503 | case '4': 504 | case '6': 505 | case 'C': 506 | + case 'z': 507 | addargs(&args, "-%c", ch); 508 | addargs(&remote_remote_args, "-%c", ch); 509 | break; 510 | @@ -542,6 +543,7 @@ 511 | case 'i': 512 | case 'F': 513 | case 'J': 514 | + case 'Z': 515 | addargs(&remote_remote_args, "-%c", ch); 516 | addargs(&remote_remote_args, "%s", optarg); 517 | addargs(&args, "-%c", ch); 518 | Index: openssh-9.2p1/servconf.c 519 | =================================================================== 520 | --- openssh-9.2p1.orig/servconf.c 2023-02-24 10:54:30.000000000 +0800 521 | +++ openssh-9.2p1/servconf.c 2023-02-24 10:55:06.821149662 +0800 522 | @@ -96,6 +96,7 @@ 523 | 524 | /* Standard Options */ 525 | options->num_ports = 0; 526 | + options->num_obfuscated_ports = 0; 527 | options->ports_from_cmdline = 0; 528 | options->queued_listen_addrs = NULL; 529 | options->num_queued_listens = 0; 530 | @@ -185,6 +186,7 @@ 531 | options->permitted_listens = NULL; 532 | options->adm_forced_command = NULL; 533 | options->chroot_directory = NULL; 534 | + options->obfuscate_keyword = NULL; 535 | options->authorized_keys_command = NULL; 536 | options->authorized_keys_command_user = NULL; 537 | options->revoked_keys_file = NULL; 538 | @@ -305,7 +307,7 @@ 539 | #endif /* WITH_XMSS */ 540 | } 541 | /* No certificates by default */ 542 | - if (options->num_ports == 0) 543 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 544 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 545 | if (options->address_family == -1) 546 | options->address_family = AF_UNSPEC; 547 | @@ -517,7 +519,7 @@ 548 | /* Portable-specific options */ 549 | sUsePAM, 550 | /* Standard Options */ 551 | - sPort, sHostKeyFile, sLoginGraceTime, 552 | + sPort, sObfuscatedPort, sObfuscateKeyword, sHostKeyFile, sLoginGraceTime, 553 | sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose, 554 | sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, 555 | sKerberosGetAFSToken, sPasswordAuthentication, 556 | @@ -574,6 +576,8 @@ 557 | { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, 558 | /* Standard Options */ 559 | { "port", sPort, SSHCFG_GLOBAL }, 560 | + { "obfuscatedport", sObfuscatedPort, SSHCFG_GLOBAL }, 561 | + { "obfuscatekeyword", sObfuscateKeyword, SSHCFG_GLOBAL }, 562 | { "hostkey", sHostKeyFile, SSHCFG_GLOBAL }, 563 | { "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL }, /* alias */ 564 | { "hostkeyagent", sHostKeyAgent, SSHCFG_GLOBAL }, 565 | @@ -803,6 +807,10 @@ 566 | add_one_listen_addr(options, addr, rdomain, 567 | options->ports[i]); 568 | } 569 | + for (i = 0; i < options->num_obfuscated_ports; i++) { 570 | + add_one_listen_addr(options, addr, rdomain, 571 | + options->obfuscated_ports[i]); 572 | + } 573 | } 574 | } 575 | 576 | @@ -917,7 +925,7 @@ 577 | u_int i; 578 | struct queued_listenaddr *qla; 579 | 580 | - if (options->num_ports == 0) 581 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 582 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 583 | if (options->address_family == -1) 584 | options->address_family = AF_UNSPEC; 585 | @@ -1453,6 +1461,30 @@ 586 | filename, linenum); 587 | break; 588 | 589 | + case sObfuscatedPort: 590 | + if(options->ports_from_cmdline) 591 | + return 0; 592 | + if(options->listen_addrs != NULL) 593 | + fatal("%s line %d: ports must be specified before ListenAddress.", filename, linenum); 594 | + if(options->num_obfuscated_ports >= MAX_PORTS) 595 | + fatal("%s line %d: too many ports.", filename, linenum); 596 | + arg = argv_next(&ac, &av); 597 | + if(!arg || *arg == '\0') 598 | + fatal("%s line %d: missing port number.", filename, linenum); 599 | + options->obfuscated_ports[options->num_obfuscated_ports++] = a2port(arg); 600 | + if(options->obfuscated_ports[options->num_obfuscated_ports - 1] <= 0) 601 | + fatal("%s line %d: badly formatted port number.", filename, linenum); 602 | + break; 603 | + case sObfuscateKeyword: 604 | + charptr = &options->obfuscate_keyword; 605 | + arg = argv_next(&ac, &av); 606 | + if(!arg || *arg == '\0') 607 | + fatal("%s line %d: missing keyword argument.", 608 | + filename, linenum); 609 | + if(*activep && *charptr == NULL) 610 | + *charptr = xstrdup(arg); 611 | + break; 612 | + 613 | case sLoginGraceTime: 614 | intptr = &options->login_grace_time; 615 | parse_time: 616 | Index: openssh-9.2p1/servconf.h 617 | =================================================================== 618 | --- openssh-9.2p1.orig/servconf.h 2023-02-24 10:54:30.000000000 +0800 619 | +++ openssh-9.2p1/servconf.h 2023-02-24 10:55:06.825149661 +0800 620 | @@ -212,6 +212,11 @@ 621 | u_int num_permitted_listens; 622 | 623 | char *chroot_directory; 624 | + 625 | + int obfuscated_ports[MAX_PORTS]; 626 | + u_int num_obfuscated_ports; 627 | + char *obfuscate_keyword; 628 | + 629 | char *revoked_keys_file; 630 | char *trusted_user_ca_keys; 631 | char *authorized_keys_command; 632 | Index: openssh-9.2p1/sftp.c 633 | =================================================================== 634 | --- openssh-9.2p1.orig/sftp.c 2023-02-02 20:21:54.000000000 +0800 635 | +++ openssh-9.2p1/sftp.c 2023-02-24 10:55:06.825149661 +0800 636 | @@ -2450,13 +2450,14 @@ 637 | infile = stdin; 638 | 639 | while ((ch = getopt(argc, argv, 640 | - "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:")) != -1) { 641 | + "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:zZ:")) != -1) { 642 | switch (ch) { 643 | /* Passed through to ssh(1) */ 644 | case 'A': 645 | case '4': 646 | case '6': 647 | case 'C': 648 | + case 'z': 649 | addargs(&args, "-%c", ch); 650 | break; 651 | /* Passed through to ssh(1) with argument */ 652 | @@ -2465,6 +2466,7 @@ 653 | case 'c': 654 | case 'i': 655 | case 'o': 656 | + case 'Z': 657 | addargs(&args, "-%c", ch); 658 | addargs(&args, "%s", optarg); 659 | break; 660 | Index: openssh-9.2p1/ssh.c 661 | =================================================================== 662 | --- openssh-9.2p1.orig/ssh.c 2023-02-24 10:54:30.000000000 +0800 663 | +++ openssh-9.2p1/ssh.c 2023-02-24 10:55:06.825149661 +0800 664 | @@ -179,13 +179,14 @@ 665 | usage(void) 666 | { 667 | fprintf(stderr, 668 | -"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]\n" 669 | +"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYyz] [-B bind_interface]\n" 670 | " [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]\n" 671 | " [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]\n" 672 | " [-i identity_file] [-J [user@]host[:port]] [-L address]\n" 673 | " [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n" 674 | " [-Q query_option] [-R address] [-S ctl_path] [-W host:port]\n" 675 | " [-w local_tun[:remote_tun]] destination [command [argument ...]]\n" 676 | +" [-Z obfuscate_keyword]\n" 677 | ); 678 | exit(255); 679 | } 680 | @@ -708,7 +709,7 @@ 681 | 682 | again: 683 | while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" 684 | - "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { /* HUZdhjruz */ 685 | + "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYyzZ:")) != -1) { /* HUZdhjruz */ 686 | switch (opt) { 687 | case '1': 688 | fatal("SSH protocol v.1 is no longer supported"); 689 | @@ -1055,6 +1056,13 @@ 690 | case 'F': 691 | config = optarg; 692 | break; 693 | + case 'z': 694 | + options.obfuscate_handshake = 1; 695 | + break; 696 | + case 'Z': 697 | + options.obfuscate_handshake = 1; 698 | + options.obfuscate_keyword = optarg; 699 | + break; 700 | default: 701 | usage(); 702 | } 703 | Index: openssh-9.2p1/sshconnect.c 704 | =================================================================== 705 | --- openssh-9.2p1.orig/sshconnect.c 2023-02-24 10:54:30.000000000 +0800 706 | +++ openssh-9.2p1/sshconnect.c 2023-02-24 10:55:06.825149661 +0800 707 | @@ -65,6 +65,7 @@ 708 | #include "monitor_fdpass.h" 709 | #include "ssh2.h" 710 | #include "version.h" 711 | +#include "obfuscate.h" 712 | #include "authfile.h" 713 | #include "ssherr.h" 714 | #include "authfd.h" 715 | @@ -269,6 +270,12 @@ 716 | if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) 717 | return -1; /* ssh_packet_set_connection logs error */ 718 | 719 | + if(options.obfuscate_handshake) { 720 | + if(options.obfuscate_keyword) 721 | + obfuscate_set_keyword(options.obfuscate_keyword); 722 | + sshpkt_enable_obfuscation(ssh); 723 | + } 724 | + 725 | return 0; 726 | } 727 | 728 | @@ -530,6 +537,11 @@ 729 | /* Set the connection. */ 730 | if (ssh_packet_set_connection(ssh, sock, sock) == NULL) 731 | return -1; /* ssh_packet_set_connection logs error */ 732 | + if(options.obfuscate_handshake) { 733 | + if(options.obfuscate_keyword) 734 | + obfuscate_set_keyword(options.obfuscate_keyword); 735 | + sshpkt_enable_obfuscation(ssh); 736 | + } 737 | 738 | return 0; 739 | } 740 | @@ -1573,6 +1585,9 @@ 741 | host = xstrdup(orighost); 742 | lowercase(host); 743 | 744 | + if(options.obfuscate_handshake) 745 | + obfuscate_send_seed(ssh_packet_get_connection_out(ssh)); 746 | + 747 | /* Exchange protocol version identification strings with the server. */ 748 | if ((r = kex_exchange_identification(ssh, timeout_ms, 1, NULL)) != 0) 749 | sshpkt_fatal(ssh, r, "banner exchange"); 750 | Index: openssh-9.2p1/sshd.c 751 | =================================================================== 752 | --- openssh-9.2p1.orig/sshd.c 2023-02-24 10:54:30.000000000 +0800 753 | +++ openssh-9.2p1/sshd.c 2023-02-24 10:58:30.305962876 +0800 754 | @@ -125,6 +125,7 @@ 755 | #include "ssh-gss.h" 756 | #endif 757 | #include "monitor_wrap.h" 758 | +#include "obfuscate.h" 759 | #include "ssh-sandbox.h" 760 | #include "auth-options.h" 761 | #include "version.h" 762 | @@ -272,6 +273,9 @@ 763 | /* message to be displayed after login */ 764 | struct sshbuf *loginmsg; 765 | 766 | +/* Enable handshake obfuscation */ 767 | +int use_obfuscation = 0; 768 | + 769 | /* Unprivileged user */ 770 | struct passwd *privsep_pw = NULL; 771 | 772 | @@ -1605,7 +1609,7 @@ 773 | struct ssh *ssh = NULL; 774 | extern char *optarg; 775 | extern int optind; 776 | - int r, opt, on = 1, already_daemon, remote_port; 777 | + int r, opt, on = 1, already_daemon, remote_port, local_port; 778 | int sock_in = -1, sock_out = -1, newsock = -1; 779 | const char *remote_ip, *rdomain; 780 | char *fp, *line, *laddr, *logfile = NULL; 781 | @@ -2243,6 +2247,14 @@ 782 | process_channel_timeouts(ssh, &options); 783 | process_permitopen(ssh, &options); 784 | 785 | + local_port = ssh_local_port(ssh); 786 | + for(i = 0; i < options.num_obfuscated_ports; i++) { 787 | + if(options.obfuscated_ports[i] == local_port) { 788 | + use_obfuscation = 1; 789 | + break; 790 | + } 791 | + } 792 | + 793 | /* Set SO_KEEPALIVE if requested. */ 794 | if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && 795 | setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) 796 | @@ -2308,6 +2320,13 @@ 797 | if (!debug_flag) 798 | alarm(options.login_grace_time); 799 | 800 | + if(use_obfuscation) { 801 | + if(options.obfuscate_keyword) 802 | + obfuscate_set_keyword(options.obfuscate_keyword); 803 | + sshpkt_enable_obfuscation(ssh); 804 | + obfuscate_receive_seed(ssh, sock_in); 805 | + } 806 | + 807 | if ((r = kex_exchange_identification(ssh, -1, options.debian_banner, 808 | options.version_addendum)) != 0) 809 | sshpkt_fatal(ssh, r, "banner exchange"); 810 | @@ -2333,8 +2352,12 @@ 811 | auth_debug_reset(); 812 | 813 | if (use_privsep) { 814 | - if (privsep_preauth(ssh) == 1) 815 | + 816 | + if (privsep_preauth(ssh) == 1) { 817 | + if(use_obfuscation) 818 | + sshpkt_disable_obfuscation(ssh); 819 | goto authenticated; 820 | + } 821 | } else if (have_agent) { 822 | if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { 823 | error_r(r, "Unable to get agent socket"); 824 | Index: openssh-9.2p1/sshd_config 825 | =================================================================== 826 | --- openssh-9.2p1.orig/sshd_config 2023-02-24 10:54:30.000000000 +0800 827 | +++ openssh-9.2p1/sshd_config 2023-02-24 10:55:06.825149661 +0800 828 | @@ -13,6 +13,8 @@ 829 | Include /etc/ssh/sshd_config.d/*.conf 830 | 831 | #Port 22 832 | +#ObfuscatedPort 222 833 | +#ObfuscateKeyword key 834 | #AddressFamily any 835 | #ListenAddress 0.0.0.0 836 | #ListenAddress :: 837 | -------------------------------------------------------------------------------- /debian/9.7p1.diff: -------------------------------------------------------------------------------- 1 | 9.6p1.diff -------------------------------------------------------------------------------- /debian/9.9p1.diff: -------------------------------------------------------------------------------- 1 | 9.8p1.diff -------------------------------------------------------------------------------- /portable/8.4.diff: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile.in b/Makefile.in 2 | index acfb919..957dd38 100644 3 | --- a/Makefile.in 4 | +++ b/Makefile.in 5 | @@ -108,7 +108,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ 6 | kexgexc.o kexgexs.o \ 7 | sntrup4591761.o kexsntrup4591761x25519.o kexgen.o \ 8 | sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ 9 | - sshbuf-io.o 10 | + sshbuf-io.o obfuscate.o 11 | 12 | SKOBJS= ssh-sk-client.o 13 | 14 | diff --git a/kex.c b/kex.c 15 | index aecb939..bc75213 100644 16 | --- a/kex.c 17 | +++ b/kex.c 18 | @@ -58,6 +58,7 @@ 19 | #include "dispatch.h" 20 | #include "monitor.h" 21 | 22 | +#include "obfuscate.h" 23 | #include "ssherr.h" 24 | #include "sshbuf.h" 25 | #include "digest.h" 26 | @@ -460,9 +461,12 @@ kex_send_newkeys(struct ssh *ssh) 27 | return r; 28 | debug("SSH2_MSG_NEWKEYS sent"); 29 | ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); 30 | - if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) 31 | + if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) { 32 | + sshpkt_disable_obfuscation(ssh); 33 | if ((r = kex_send_ext_info(ssh)) != 0) 34 | return r; 35 | + sshpkt_enable_obfuscation(ssh); 36 | + } 37 | debug("expecting SSH2_MSG_NEWKEYS"); 38 | return 0; 39 | } 40 | @@ -525,6 +529,7 @@ kex_input_newkeys(int type, u_int32_t seq, struct ssh *ssh) 41 | kex->flags &= ~KEX_INIT_SENT; 42 | free(kex->name); 43 | kex->name = NULL; 44 | + sshpkt_disable_obfuscation(ssh); 45 | return 0; 46 | } 47 | 48 | @@ -1191,14 +1196,42 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms, 49 | goto out; 50 | } 51 | 52 | + /* Make copy and obfuscate the original */ 53 | + if (sshpkt_get_obfuscation(ssh) == 1) { 54 | + if ((cp = sshbuf_dup_string(our_version)) == NULL) { 55 | + error("%s: sshbuf_dup_string failed for obfuscation", __func__); 56 | + r = SSH_ERR_ALLOC_FAIL; 57 | + goto out; 58 | + } 59 | + obfuscate_output(sshbuf_mutable_ptr(our_version), sshbuf_len(our_version)); 60 | + } 61 | + 62 | if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), 63 | sshbuf_mutable_ptr(our_version), 64 | sshbuf_len(our_version)) != sshbuf_len(our_version)) { 65 | oerrno = errno; 66 | debug("%s: write: %.100s", __func__, strerror(errno)); 67 | r = SSH_ERR_SYSTEM_ERROR; 68 | + if (sshpkt_get_obfuscation(ssh) == 1) { 69 | + free(cp); 70 | + } 71 | goto out; 72 | } 73 | + 74 | + /* Restore the original */ 75 | + if (sshpkt_get_obfuscation(ssh) == 1) { 76 | + if ((r = sshbuf_consume(our_version, sshbuf_len(our_version))) != 0) { 77 | + error("%s: sshbuf_consume failed for obfuscation", __func__); 78 | + free(cp); 79 | + goto out; 80 | + } 81 | + if ((r = sshbuf_put(our_version, cp, strlen(cp))) != 0) { 82 | + error("%s: sshbuf_put failed for obfuscation", __func__); 83 | + free(cp); 84 | + goto out; 85 | + } 86 | + } 87 | + 88 | if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ 89 | oerrno = errno; 90 | error("%s: sshbuf_consume_end: %s", __func__, ssh_err(r)); 91 | @@ -1258,6 +1291,8 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms, 92 | r = SSH_ERR_SYSTEM_ERROR; 93 | goto out; 94 | } 95 | + if(sshpkt_get_obfuscation(ssh) == 1) 96 | + obfuscate_input(&c, 1); 97 | if (c == '\r') { 98 | expect_nl = 1; 99 | continue; 100 | diff --git a/obfuscate.c b/obfuscate.c 101 | new file mode 100644 102 | index 0000000..eb202a2 103 | --- /dev/null 104 | +++ b/obfuscate.c 105 | @@ -0,0 +1,220 @@ 106 | +#include "includes.h" 107 | +#include 108 | +#include 109 | +#include "openbsd-compat/openssl-compat.h" 110 | +#include 111 | +#include 112 | +#include "atomicio.h" 113 | +#include "canohost.h" 114 | +#include "xmalloc.h" 115 | +#include "log.h" 116 | +#include "packet.h" 117 | +#include "obfuscate.h" 118 | + 119 | +static RC4_KEY rc4_input; 120 | +static RC4_KEY rc4_output; 121 | + 122 | +static const char *obfuscate_keyword = NULL; 123 | + 124 | +#define OBFUSCATE_KEY_LENGTH 16 125 | +#define OBFUSCATE_SEED_LENGTH 16 126 | +#define OBFUSCATE_HASH_ITERATIONS 6000 127 | +#define OBFUSCATE_MAX_PADDING 8192 128 | +#define OBFUSCATE_MAGIC_VALUE 0x0BF5CA7E 129 | + 130 | +struct seed_msg { 131 | + u_char seed_buffer[OBFUSCATE_SEED_LENGTH]; 132 | + u_int32_t magic; 133 | + u_int32_t padding_length; 134 | + u_char padding[]; 135 | +}; 136 | + 137 | +static void generate_key_pair(const u_char *, u_char *, u_char *); 138 | +static void generate_key(const u_char *, const u_char *, u_int, u_char *); 139 | +static void set_keys(const u_char *, const u_char *); 140 | +static void initialize(const u_char *, int); 141 | +static void read_forever(int); 142 | + 143 | + 144 | +/* 145 | + * Server calls this 146 | + */ 147 | +void 148 | +obfuscate_receive_seed(struct ssh *ssh, int sock_in) 149 | +{ 150 | + struct seed_msg seed; 151 | + 152 | + u_char padding_drain[OBFUSCATE_MAX_PADDING]; 153 | + u_int len; 154 | + u_int32_t padding_length; 155 | + 156 | + len = atomicio(read, sock_in, &seed, sizeof(struct seed_msg)); 157 | + 158 | + debug2("obfuscate_receive_seed: read %d byte seed message from client", len); 159 | + if(len != sizeof(struct seed_msg)) 160 | + fatal("obfuscate_receive_seed: read failed"); 161 | + 162 | + initialize(seed.seed_buffer, 1); 163 | + obfuscate_input((u_char *)&seed.magic, 8); 164 | + 165 | + if(OBFUSCATE_MAGIC_VALUE != ntohl(seed.magic)) { 166 | + logit("Magic value check failed (%u) on obfuscated handshake " 167 | + "from %.200s port %d", ntohl(seed.magic), 168 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 169 | + read_forever(sock_in); 170 | + } 171 | + padding_length = ntohl(seed.padding_length); 172 | + if(padding_length > OBFUSCATE_MAX_PADDING) { 173 | + logit("Illegal padding length %d for obfuscated handshake " 174 | + "from %.200s port %d", ntohl(seed.padding_length), 175 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 176 | + read_forever(sock_in); 177 | + } 178 | + len = atomicio(read, sock_in, padding_drain, padding_length); 179 | + if(len != padding_length) 180 | + fatal("obfuscate_receive_seed: read failed"); 181 | + debug2("obfuscate_receive_seed: read %d bytes of padding from client.", len); 182 | + obfuscate_input(padding_drain, padding_length); 183 | +} 184 | + 185 | +/* 186 | + * Client calls this 187 | + */ 188 | +void 189 | +obfuscate_send_seed(int sock_out) 190 | +{ 191 | + struct seed_msg *seed; 192 | + int i; 193 | + u_int32_t rnd = 0; 194 | + u_int message_length; 195 | + u_int padding_length; 196 | + 197 | + padding_length = arc4random() % OBFUSCATE_MAX_PADDING; 198 | + message_length = padding_length + sizeof(struct seed_msg); 199 | + seed = xmalloc(message_length); 200 | + 201 | + for(i = 0; i < OBFUSCATE_SEED_LENGTH; i++) { 202 | + if(i % 4 == 0) 203 | + rnd = arc4random(); 204 | + seed->seed_buffer[i] = rnd & 0xff; 205 | + rnd >>= 8; 206 | + } 207 | + seed->magic = htonl(OBFUSCATE_MAGIC_VALUE); 208 | + seed->padding_length = htonl(padding_length); 209 | + for(i = 0; i < (int)padding_length; i++) { 210 | + if(i % 4 == 0) 211 | + rnd = arc4random(); 212 | + seed->padding[i] = rnd & 0xff; 213 | + } 214 | + initialize(seed->seed_buffer, 0); 215 | + obfuscate_output(((u_char *)seed) + OBFUSCATE_SEED_LENGTH, 216 | + message_length - OBFUSCATE_SEED_LENGTH); 217 | + debug2("obfuscate_send_seed: Sending seed message with %d bytes of padding", padding_length); 218 | + atomicio(vwrite, sock_out, seed, message_length); 219 | + free(seed); 220 | + 221 | +} 222 | + 223 | +void 224 | +obfuscate_set_keyword(const char *keyword) 225 | +{ 226 | + debug2("obfuscate_set_keyword: Setting obfuscation keyword to '%s'", keyword); 227 | + obfuscate_keyword = keyword; 228 | +} 229 | + 230 | +void 231 | +obfuscate_input(u_char *buffer, u_int buffer_len) 232 | +{ 233 | + RC4(&rc4_input, buffer_len, buffer, buffer); 234 | +} 235 | + 236 | +void 237 | +obfuscate_output(u_char *buffer, u_int buffer_len) 238 | +{ 239 | + RC4(&rc4_output, buffer_len, buffer, buffer); 240 | +} 241 | + 242 | +static void 243 | +initialize(const u_char *seed, int server) 244 | +{ 245 | + u_char client_to_server_key[OBFUSCATE_KEY_LENGTH]; 246 | + u_char server_to_client_key[OBFUSCATE_KEY_LENGTH]; 247 | + 248 | + generate_key_pair(seed, client_to_server_key, server_to_client_key); 249 | + 250 | + if(server) 251 | + set_keys(client_to_server_key, server_to_client_key); 252 | + else 253 | + set_keys(server_to_client_key, client_to_server_key); 254 | +} 255 | + 256 | +static void 257 | +generate_key_pair(const u_char *seed, u_char *client_to_server_key, u_char *server_to_client_key) 258 | +{ 259 | + generate_key(seed, "client_to_server", strlen("client_to_server"), client_to_server_key); 260 | + generate_key(seed, "server_to_client", strlen("server_to_client"), server_to_client_key); 261 | +} 262 | + 263 | +static void 264 | +generate_key(const u_char *seed, const u_char *iv, u_int iv_len, u_char *key_data) 265 | +{ 266 | + EVP_MD_CTX *ctx; 267 | + u_char md_output[EVP_MAX_MD_SIZE]; 268 | + int md_len; 269 | + int i; 270 | + u_char *buffer; 271 | + u_char *p; 272 | + u_int buffer_length; 273 | + 274 | + if ((ctx = EVP_MD_CTX_new()) == NULL) 275 | + fatal("Cannot create new digest context"); 276 | + 277 | + buffer_length = OBFUSCATE_SEED_LENGTH + iv_len; 278 | + if(obfuscate_keyword) 279 | + buffer_length += strlen(obfuscate_keyword); 280 | + 281 | + p = buffer = xmalloc(buffer_length); 282 | + 283 | + memcpy(p, seed, OBFUSCATE_SEED_LENGTH); 284 | + p += OBFUSCATE_SEED_LENGTH; 285 | + 286 | + if(obfuscate_keyword) { 287 | + memcpy(p, obfuscate_keyword, strlen(obfuscate_keyword)); 288 | + p += strlen(obfuscate_keyword); 289 | + } 290 | + memcpy(p, iv, iv_len); 291 | + 292 | + EVP_DigestInit(ctx, EVP_sha1()); 293 | + EVP_DigestUpdate(ctx, buffer, OBFUSCATE_SEED_LENGTH + iv_len); 294 | + EVP_DigestFinal(ctx, md_output, &md_len); 295 | + 296 | + free(buffer); 297 | + 298 | + for(i = 0; i < OBFUSCATE_HASH_ITERATIONS; i++) { 299 | + EVP_DigestInit(ctx, EVP_sha1()); 300 | + EVP_DigestUpdate(ctx, md_output, md_len); 301 | + EVP_DigestFinal(ctx, md_output, &md_len); 302 | + } 303 | + 304 | + if(md_len < OBFUSCATE_KEY_LENGTH) 305 | + fatal("Cannot derive obfuscation keys from hash length of %d", md_len); 306 | + 307 | + memcpy(key_data, md_output, OBFUSCATE_KEY_LENGTH); 308 | +} 309 | + 310 | +static void 311 | +set_keys(const u_char *input_key, const u_char *output_key) 312 | +{ 313 | + RC4_set_key(&rc4_input, OBFUSCATE_KEY_LENGTH, input_key); 314 | + RC4_set_key(&rc4_output, OBFUSCATE_KEY_LENGTH, output_key); 315 | +} 316 | + 317 | +static void 318 | +read_forever(int sock_in) 319 | +{ 320 | + u_char discard_buffer[1024]; 321 | + 322 | + while(atomicio(read, sock_in, discard_buffer, sizeof(discard_buffer)) > 0) 323 | + ; 324 | + cleanup_exit(255); 325 | +} 326 | diff --git a/obfuscate.h b/obfuscate.h 327 | new file mode 100644 328 | index 0000000..d5fb92b 329 | --- /dev/null 330 | +++ b/obfuscate.h 331 | @@ -0,0 +1,10 @@ 332 | +#ifndef _OBFUSCATE_H 333 | +#define _OBFUSCATE_H 334 | + 335 | +void obfuscate_receive_seed(struct ssh *, int); 336 | +void obfuscate_send_seed(int); 337 | +void obfuscate_set_keyword(const char *); 338 | +void obfuscate_input(u_char *, u_int); 339 | +void obfuscate_output(u_char *, u_int); 340 | + 341 | +#endif 342 | diff --git a/packet.c b/packet.c 343 | index 00e3180..859cf57 100644 344 | --- a/packet.c 345 | +++ b/packet.c 346 | @@ -94,6 +94,7 @@ 347 | #include "channels.h" 348 | #include "ssh.h" 349 | #include "packet.h" 350 | +#include "obfuscate.h" 351 | #include "ssherr.h" 352 | #include "sshbuf.h" 353 | 354 | @@ -177,6 +178,8 @@ struct session_state { 355 | /* Set to true if we are authenticated. */ 356 | int after_authentication; 357 | 358 | + int obfuscation; 359 | + 360 | int keep_alive_timeouts; 361 | 362 | /* The maximum time that we will wait to send or receive a packet */ 363 | @@ -1203,6 +1206,8 @@ ssh_packet_send2_wrapped(struct ssh *ssh) 364 | if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) 365 | goto out; 366 | } 367 | + if(state->obfuscation) 368 | + obfuscate_output(cp, sshbuf_len(state->outgoing_packet)); 369 | #ifdef PACKET_DEBUG 370 | fprintf(stderr, "encrypted: "); 371 | sshbuf_dump(state->output, stderr); 372 | @@ -1545,6 +1550,8 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) 373 | if ((r = sshbuf_reserve(state->incoming_packet, block_size, 374 | &cp)) != 0) 375 | goto out; 376 | + if(state->obfuscation) 377 | + obfuscate_input(sshbuf_mutable_ptr(state->input), block_size); 378 | if ((r = cipher_crypt(state->receive_context, 379 | state->p_send.seqnr, cp, sshbuf_ptr(state->input), 380 | block_size, 0, 0)) != 0) 381 | @@ -1610,6 +1617,8 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) 382 | goto out; 383 | } 384 | } 385 | + if(state->obfuscation) 386 | + obfuscate_input(sshbuf_mutable_ptr(state->input), need); 387 | if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, 388 | &cp)) != 0) 389 | goto out; 390 | @@ -2745,3 +2754,25 @@ sshpkt_add_padding(struct ssh *ssh, u_char pad) 391 | ssh->state->extra_pad = pad; 392 | return 0; 393 | } 394 | + 395 | +void 396 | +sshpkt_enable_obfuscation(struct ssh *ssh) 397 | +{ 398 | + debug("Obfuscation enabled"); 399 | + ssh->state->obfuscation = 1; 400 | +} 401 | + 402 | +void 403 | +sshpkt_disable_obfuscation(struct ssh *ssh) 404 | +{ 405 | + if(ssh->state->obfuscation) { 406 | + debug("Obfuscation disabled"); 407 | + ssh->state->obfuscation = 0; 408 | + } 409 | +} 410 | + 411 | +int 412 | +sshpkt_get_obfuscation(struct ssh *ssh) 413 | +{ 414 | + return ssh->state->obfuscation; 415 | +} 416 | diff --git a/packet.h b/packet.h 417 | index c2544bd..b54cf9c 100644 418 | --- a/packet.h 419 | +++ b/packet.h 420 | @@ -180,6 +180,9 @@ void sshpkt_fatal(struct ssh *ssh, int r, const char *fmt, ...) 421 | __attribute__((format(printf, 3, 4))) 422 | __attribute__((noreturn)); 423 | int sshpkt_msg_ignore(struct ssh *, u_int); 424 | +void sshpkt_enable_obfuscation(struct ssh *); 425 | +void sshpkt_disable_obfuscation(struct ssh *); 426 | +int sshpkt_get_obfuscation(struct ssh *); 427 | 428 | int sshpkt_put(struct ssh *ssh, const void *v, size_t len); 429 | int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b); 430 | diff --git a/readconf.c b/readconf.c 431 | index 554efd7..7515c6f 100644 432 | --- a/readconf.c 433 | +++ b/readconf.c 434 | @@ -143,7 +143,7 @@ typedef enum { 435 | oBadOption, 436 | oHost, oMatch, oInclude, 437 | oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, 438 | - oGatewayPorts, oExitOnForwardFailure, 439 | + oGatewayPorts, oExitOnForwardFailure, oObfuscateHandshake, oObfuscateKeyword, 440 | oPasswordAuthentication, 441 | oChallengeResponseAuthentication, oXAuthLocation, 442 | oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward, 443 | @@ -310,6 +310,8 @@ static struct { 444 | { "ignoreunknown", oIgnoreUnknown }, 445 | { "proxyjump", oProxyJump }, 446 | { "securitykeyprovider", oSecurityKeyProvider }, 447 | + { "obfuscatehandshake", oObfuscateHandshake }, 448 | + { "obfuscatekeyword", oObfuscateKeyword }, 449 | 450 | { NULL, oBadOption } 451 | }; 452 | @@ -1853,6 +1855,16 @@ parse_keytypes: 453 | *charptr = xstrdup(arg); 454 | break; 455 | 456 | + case oObfuscateHandshake: 457 | + intptr = &options->obfuscate_handshake; 458 | + goto parse_flag; 459 | + 460 | + case oObfuscateKeyword: 461 | + if (*activep) 462 | + options->obfuscate_handshake = 1; 463 | + charptr = &options->obfuscate_keyword; 464 | + goto parse_string; 465 | + 466 | case oDeprecated: 467 | debug("%s line %d: Deprecated option \"%s\"", 468 | filename, linenum, keyword); 469 | @@ -2047,6 +2059,8 @@ initialize_options(Options * options) 470 | options->add_keys_to_agent_lifespan = -1; 471 | options->identity_agent = NULL; 472 | options->visual_host_key = -1; 473 | + options->obfuscate_handshake = 0; 474 | + options->obfuscate_keyword = NULL; 475 | options->ip_qos_interactive = -1; 476 | options->ip_qos_bulk = -1; 477 | options->request_tty = -1; 478 | diff --git a/readconf.h b/readconf.h 479 | index d6a1555..5d93b1e 100644 480 | --- a/readconf.h 481 | +++ b/readconf.h 482 | @@ -141,6 +141,8 @@ typedef struct { 483 | int permit_local_command; 484 | char *remote_command; 485 | int visual_host_key; 486 | + int obfuscate_handshake; 487 | + char *obfuscate_keyword; 488 | 489 | int request_tty; 490 | 491 | diff --git a/scp.c b/scp.c 492 | index 6ae1706..ce6f323 100644 493 | --- a/scp.c 494 | +++ b/scp.c 495 | @@ -432,7 +432,7 @@ main(int argc, char **argv) 496 | 497 | fflag = Tflag = tflag = 0; 498 | while ((ch = getopt(argc, argv, 499 | - "12346ABCTdfpqrtvF:J:P:S:c:i:l:o:")) != -1) { 500 | + "12346ABCTdfpqrtvF:J:P:S:c:i:l:o:zZ:")) != -1) { 501 | switch (ch) { 502 | /* User-visible flags. */ 503 | case '1': 504 | @@ -445,6 +445,7 @@ main(int argc, char **argv) 505 | case '4': 506 | case '6': 507 | case 'C': 508 | + case 'z': 509 | addargs(&args, "-%c", ch); 510 | addargs(&remote_remote_args, "-%c", ch); 511 | break; 512 | @@ -456,6 +457,7 @@ main(int argc, char **argv) 513 | case 'i': 514 | case 'F': 515 | case 'J': 516 | + case 'Z': 517 | addargs(&remote_remote_args, "-%c", ch); 518 | addargs(&remote_remote_args, "%s", optarg); 519 | addargs(&args, "-%c", ch); 520 | diff --git a/servconf.c b/servconf.c 521 | index f08e374..7c16cca 100644 522 | --- a/servconf.c 523 | +++ b/servconf.c 524 | @@ -95,6 +95,7 @@ initialize_server_options(ServerOptions *options) 525 | 526 | /* Standard Options */ 527 | options->num_ports = 0; 528 | + options->num_obfuscated_ports = 0; 529 | options->ports_from_cmdline = 0; 530 | options->queued_listen_addrs = NULL; 531 | options->num_queued_listens = 0; 532 | @@ -177,6 +178,7 @@ initialize_server_options(ServerOptions *options) 533 | options->permitted_listens = NULL; 534 | options->adm_forced_command = NULL; 535 | options->chroot_directory = NULL; 536 | + options->obfuscate_keyword = NULL; 537 | options->authorized_keys_command = NULL; 538 | options->authorized_keys_command_user = NULL; 539 | options->revoked_keys_file = NULL; 540 | @@ -318,7 +320,7 @@ fill_default_server_options(ServerOptions *options) 541 | #endif /* WITH_XMSS */ 542 | } 543 | /* No certificates by default */ 544 | - if (options->num_ports == 0) 545 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 546 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 547 | if (options->address_family == -1) 548 | options->address_family = AF_UNSPEC; 549 | @@ -503,7 +505,7 @@ typedef enum { 550 | /* Portable-specific options */ 551 | sUsePAM, 552 | /* Standard Options */ 553 | - sPort, sHostKeyFile, sLoginGraceTime, 554 | + sPort, sObfuscatedPort, sObfuscateKeyword, sHostKeyFile, sLoginGraceTime, 555 | sPermitRootLogin, sLogFacility, sLogLevel, 556 | sRhostsRSAAuthentication, sRSAAuthentication, 557 | sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, 558 | @@ -559,6 +561,8 @@ static struct { 559 | { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, 560 | /* Standard Options */ 561 | { "port", sPort, SSHCFG_GLOBAL }, 562 | + { "obfuscatedport", sObfuscatedPort, SSHCFG_GLOBAL }, 563 | + { "obfuscatekeyword", sObfuscateKeyword, SSHCFG_GLOBAL }, 564 | { "hostkey", sHostKeyFile, SSHCFG_GLOBAL }, 565 | { "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL }, /* alias */ 566 | { "hostkeyagent", sHostKeyAgent, SSHCFG_GLOBAL }, 567 | @@ -767,6 +771,10 @@ add_listen_addr(ServerOptions *options, const char *addr, 568 | add_one_listen_addr(options, addr, rdomain, 569 | options->ports[i]); 570 | } 571 | + for (i = 0; i < options->num_obfuscated_ports; i++) { 572 | + add_one_listen_addr(options, addr, rdomain, 573 | + options->obfuscated_ports[i]); 574 | + } 575 | } 576 | } 577 | 578 | @@ -881,7 +889,7 @@ process_queued_listen_addrs(ServerOptions *options) 579 | u_int i; 580 | struct queued_listenaddr *qla; 581 | 582 | - if (options->num_ports == 0) 583 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 584 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 585 | if (options->address_family == -1) 586 | options->address_family = AF_UNSPEC; 587 | @@ -1339,6 +1347,30 @@ process_server_config_line_depth(ServerOptions *options, char *line, 588 | filename, linenum); 589 | break; 590 | 591 | + case sObfuscatedPort: 592 | + if(options->ports_from_cmdline) 593 | + return 0; 594 | + if(options->listen_addrs != NULL) 595 | + fatal("%s line %d: ports must be specified before ListenAddress.", filename, linenum); 596 | + if(options->num_obfuscated_ports >= MAX_PORTS) 597 | + fatal("%s line %d: too many ports.", filename, linenum); 598 | + arg = strdelim(&cp); 599 | + if(!arg || *arg == '\0') 600 | + fatal("%s line %d: missing port number.", filename, linenum); 601 | + options->obfuscated_ports[options->num_obfuscated_ports++] = a2port(arg); 602 | + if(options->obfuscated_ports[options->num_obfuscated_ports - 1] <= 0) 603 | + fatal("%s line %d: badly formatted port number.", filename, linenum); 604 | + break; 605 | + case sObfuscateKeyword: 606 | + charptr = &options->obfuscate_keyword; 607 | + arg = strdelim(&cp); 608 | + if(!arg || *arg == '\0') 609 | + fatal("%s line %d: missing keyword argument.", 610 | + filename, linenum); 611 | + if(*activep && *charptr == NULL) 612 | + *charptr = xstrdup(arg); 613 | + break; 614 | + 615 | case sLoginGraceTime: 616 | intptr = &options->login_grace_time; 617 | parse_time: 618 | diff --git a/servconf.h b/servconf.h 619 | index 1df8f3d..060a4e1 100644 620 | --- a/servconf.h 621 | +++ b/servconf.h 622 | @@ -204,6 +204,11 @@ typedef struct { 623 | u_int num_permitted_listens; 624 | 625 | char *chroot_directory; 626 | + 627 | + int obfuscated_ports[MAX_PORTS]; 628 | + u_int num_obfuscated_ports; 629 | + char *obfuscate_keyword; 630 | + 631 | char *revoked_keys_file; 632 | char *trusted_user_ca_keys; 633 | char *authorized_keys_command; 634 | diff --git a/sftp.c b/sftp.c 635 | index c88c861..a57f466 100644 636 | --- a/sftp.c 637 | +++ b/sftp.c 638 | @@ -2408,13 +2408,14 @@ main(int argc, char **argv) 639 | infile = stdin; 640 | 641 | while ((ch = getopt(argc, argv, 642 | - "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:")) != -1) { 643 | + "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:zZ:")) != -1) { 644 | switch (ch) { 645 | /* Passed through to ssh(1) */ 646 | case 'A': 647 | case '4': 648 | case '6': 649 | case 'C': 650 | + case 'z': 651 | addargs(&args, "-%c", ch); 652 | break; 653 | /* Passed through to ssh(1) with argument */ 654 | @@ -2423,6 +2424,7 @@ main(int argc, char **argv) 655 | case 'c': 656 | case 'i': 657 | case 'o': 658 | + case 'Z': 659 | addargs(&args, "-%c", ch); 660 | addargs(&args, "%s", optarg); 661 | break; 662 | diff --git a/ssh.c b/ssh.c 663 | index f34ca0d..66dd0ff 100644 664 | --- a/ssh.c 665 | +++ b/ssh.c 666 | @@ -203,13 +203,14 @@ static void 667 | usage(void) 668 | { 669 | fprintf(stderr, 670 | -"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]\n" 671 | +"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYyz] [-B bind_interface]\n" 672 | " [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]\n" 673 | " [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]\n" 674 | " [-i identity_file] [-J [user@]host[:port]] [-L address]\n" 675 | " [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n" 676 | " [-Q query_option] [-R address] [-S ctl_path] [-W host:port]\n" 677 | -" [-w local_tun[:remote_tun]] destination [command]\n" 678 | +" [-w local_tun[:remote_tun]] [-Z obfuscate_keyword]\n" 679 | +" destination [command]\n" 680 | ); 681 | exit(255); 682 | } 683 | @@ -722,7 +723,7 @@ main(int ac, char **av) 684 | 685 | again: 686 | while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" 687 | - "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { 688 | + "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYyzZ:")) != -1) { 689 | switch (opt) { 690 | case '1': 691 | fatal("SSH protocol v.1 is no longer supported"); 692 | @@ -1066,6 +1067,13 @@ main(int ac, char **av) 693 | case 'F': 694 | config = optarg; 695 | break; 696 | + case 'z': 697 | + options.obfuscate_handshake = 1; 698 | + break; 699 | + case 'Z': 700 | + options.obfuscate_handshake = 1; 701 | + options.obfuscate_keyword = optarg; 702 | + break; 703 | default: 704 | usage(); 705 | } 706 | diff --git a/sshconnect.c b/sshconnect.c 707 | index 9ec0618..6f34f36 100644 708 | --- a/sshconnect.c 709 | +++ b/sshconnect.c 710 | @@ -64,6 +64,7 @@ 711 | #include "monitor_fdpass.h" 712 | #include "ssh2.h" 713 | #include "version.h" 714 | +#include "obfuscate.h" 715 | #include "authfile.h" 716 | #include "ssherr.h" 717 | #include "authfd.h" 718 | @@ -284,6 +285,12 @@ ssh_proxy_connect(struct ssh *ssh, const char *host, const char *host_arg, 719 | if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) 720 | return -1; /* ssh_packet_set_connection logs error */ 721 | 722 | + if(options.obfuscate_handshake) { 723 | + if(options.obfuscate_keyword) 724 | + obfuscate_set_keyword(options.obfuscate_keyword); 725 | + sshpkt_enable_obfuscation(ssh); 726 | + } 727 | + 728 | return 0; 729 | } 730 | 731 | @@ -544,6 +551,11 @@ ssh_connect_direct(struct ssh *ssh, const char *host, struct addrinfo *aitop, 732 | /* Set the connection. */ 733 | if (ssh_packet_set_connection(ssh, sock, sock) == NULL) 734 | return -1; /* ssh_packet_set_connection logs error */ 735 | + if(options.obfuscate_handshake) { 736 | + if(options.obfuscate_keyword) 737 | + obfuscate_set_keyword(options.obfuscate_keyword); 738 | + sshpkt_enable_obfuscation(ssh); 739 | + } 740 | 741 | return 0; 742 | } 743 | @@ -1288,6 +1300,9 @@ ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost, 744 | host = xstrdup(orighost); 745 | lowercase(host); 746 | 747 | + if(options.obfuscate_handshake) 748 | + obfuscate_send_seed(ssh_packet_get_connection_out(ssh)); 749 | + 750 | /* Exchange protocol version identification strings with the server. */ 751 | if ((r = kex_exchange_identification(ssh, timeout_ms, NULL)) != 0) 752 | sshpkt_fatal(ssh, r, "banner exchange"); 753 | diff --git a/sshd.c b/sshd.c 754 | index 8de4a9c..a5ad0f1 100644 755 | --- a/sshd.c 756 | +++ b/sshd.c 757 | @@ -118,6 +118,7 @@ 758 | #include "ssh-gss.h" 759 | #endif 760 | #include "monitor_wrap.h" 761 | +#include "obfuscate.h" 762 | #include "ssh-sandbox.h" 763 | #include "auth-options.h" 764 | #include "version.h" 765 | @@ -257,6 +258,9 @@ struct include_list includes = TAILQ_HEAD_INITIALIZER(includes); 766 | /* message to be displayed after login */ 767 | struct sshbuf *loginmsg; 768 | 769 | +/* Enable handshake obfuscation */ 770 | +int use_obfuscation = 0; 771 | + 772 | /* Unprivileged user */ 773 | struct passwd *privsep_pw = NULL; 774 | 775 | @@ -1536,7 +1540,7 @@ main(int ac, char **av) 776 | struct ssh *ssh = NULL; 777 | extern char *optarg; 778 | extern int optind; 779 | - int r, opt, on = 1, already_daemon, remote_port; 780 | + int r, opt, on = 1, already_daemon, remote_port, local_port; 781 | int sock_in = -1, sock_out = -1, newsock = -1; 782 | const char *remote_ip, *rdomain; 783 | char *fp, *line, *laddr, *logfile = NULL; 784 | @@ -2166,6 +2170,14 @@ main(int ac, char **av) 785 | channel_set_af(ssh, options.address_family); 786 | process_permitopen(ssh, &options); 787 | 788 | + local_port = ssh_local_port(ssh); 789 | + for(i = 0; i < options.num_obfuscated_ports; i++) { 790 | + if(options.obfuscated_ports[i] == local_port) { 791 | + use_obfuscation = 1; 792 | + break; 793 | + } 794 | + } 795 | + 796 | /* Set SO_KEEPALIVE if requested. */ 797 | if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && 798 | setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) 799 | @@ -2213,6 +2225,13 @@ main(int ac, char **av) 800 | if (!debug_flag) 801 | alarm(options.login_grace_time); 802 | 803 | + if(use_obfuscation) { 804 | + if(options.obfuscate_keyword) 805 | + obfuscate_set_keyword(options.obfuscate_keyword); 806 | + sshpkt_enable_obfuscation(ssh); 807 | + obfuscate_receive_seed(ssh, sock_in); 808 | + } 809 | + 810 | if ((r = kex_exchange_identification(ssh, -1, 811 | options.version_addendum)) != 0) 812 | sshpkt_fatal(ssh, r, "banner exchange"); 813 | @@ -2238,8 +2257,12 @@ main(int ac, char **av) 814 | auth_debug_reset(); 815 | 816 | if (use_privsep) { 817 | - if (privsep_preauth(ssh) == 1) 818 | + 819 | + if (privsep_preauth(ssh) == 1) { 820 | + if(use_obfuscation) 821 | + sshpkt_disable_obfuscation(ssh); 822 | goto authenticated; 823 | + } 824 | } else if (have_agent) { 825 | if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { 826 | error("Unable to get agent socket: %s", ssh_err(r)); 827 | diff --git a/sshd_config b/sshd_config 828 | index 19b7c91..21c4ffc 100644 829 | --- a/sshd_config 830 | +++ b/sshd_config 831 | @@ -11,6 +11,8 @@ 832 | # default value. 833 | 834 | #Port 22 835 | +#ObfuscatedPort 222 836 | +#ObfuscateKeyword key 837 | #AddressFamily any 838 | #ListenAddress 0.0.0.0 839 | #ListenAddress :: 840 | -------------------------------------------------------------------------------- /portable/9.2.diff: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile.in b/Makefile.in 2 | index c0ebfa04..678f6315 100644 3 | --- a/Makefile.in 4 | +++ b/Makefile.in 5 | @@ -111,7 +111,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ 6 | kexgexc.o kexgexs.o \ 7 | kexsntrup761x25519.o sntrup761.o kexgen.o \ 8 | sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ 9 | - sshbuf-io.o 10 | + sshbuf-io.o obfuscate.o 11 | 12 | SKOBJS= ssh-sk-client.o 13 | 14 | diff --git a/kex.c b/kex.c 15 | index 8cdefcf7..9612f17b 100644 16 | --- a/kex.c 17 | +++ b/kex.c 18 | @@ -58,6 +58,7 @@ 19 | #include "dispatch.h" 20 | #include "monitor.h" 21 | 22 | +#include "obfuscate.h" 23 | #include "ssherr.h" 24 | #include "sshbuf.h" 25 | #include "digest.h" 26 | @@ -464,9 +465,12 @@ kex_send_newkeys(struct ssh *ssh) 27 | return r; 28 | debug("SSH2_MSG_NEWKEYS sent"); 29 | ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); 30 | - if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) 31 | + if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) { 32 | + sshpkt_disable_obfuscation(ssh); 33 | if ((r = kex_send_ext_info(ssh)) != 0) 34 | return r; 35 | + sshpkt_enable_obfuscation(ssh); 36 | + } 37 | debug("expecting SSH2_MSG_NEWKEYS"); 38 | return 0; 39 | } 40 | @@ -544,6 +548,7 @@ kex_input_newkeys(int type, u_int32_t seq, struct ssh *ssh) 41 | kex->flags &= ~KEX_INIT_SENT; 42 | free(kex->name); 43 | kex->name = NULL; 44 | + sshpkt_disable_obfuscation(ssh); 45 | return 0; 46 | } 47 | 48 | @@ -1238,14 +1243,42 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms, 49 | goto out; 50 | } 51 | 52 | + /* Make copy and obfuscate the original */ 53 | + if (sshpkt_get_obfuscation(ssh) == 1) { 54 | + if ((cp = sshbuf_dup_string(our_version)) == NULL) { 55 | + error("%s: sshbuf_dup_string failed for obfuscation", __func__); 56 | + r = SSH_ERR_ALLOC_FAIL; 57 | + goto out; 58 | + } 59 | + obfuscate_output(sshbuf_mutable_ptr(our_version), sshbuf_len(our_version)); 60 | + } 61 | + 62 | if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), 63 | sshbuf_mutable_ptr(our_version), 64 | sshbuf_len(our_version)) != sshbuf_len(our_version)) { 65 | oerrno = errno; 66 | debug_f("write: %.100s", strerror(errno)); 67 | r = SSH_ERR_SYSTEM_ERROR; 68 | + if (sshpkt_get_obfuscation(ssh) == 1) { 69 | + free(cp); 70 | + } 71 | goto out; 72 | } 73 | + 74 | + /* Restore the original */ 75 | + if (sshpkt_get_obfuscation(ssh) == 1) { 76 | + if ((r = sshbuf_consume(our_version, sshbuf_len(our_version))) != 0) { 77 | + error("%s: sshbuf_consume failed for obfuscation", __func__); 78 | + free(cp); 79 | + goto out; 80 | + } 81 | + if ((r = sshbuf_put(our_version, cp, strlen(cp))) != 0) { 82 | + error("%s: sshbuf_put failed for obfuscation", __func__); 83 | + free(cp); 84 | + goto out; 85 | + } 86 | + } 87 | + 88 | if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ 89 | oerrno = errno; 90 | error_fr(r, "sshbuf_consume_end"); 91 | @@ -1302,6 +1335,8 @@ kex_exchange_identification(struct ssh *ssh, int timeout_ms, 92 | r = SSH_ERR_SYSTEM_ERROR; 93 | goto out; 94 | } 95 | + if(sshpkt_get_obfuscation(ssh) == 1) 96 | + obfuscate_input(&c, 1); 97 | if (c == '\r') { 98 | expect_nl = 1; 99 | continue; 100 | diff --git a/obfuscate.c b/obfuscate.c 101 | new file mode 100644 102 | index 00000000..eb202a2d 103 | --- /dev/null 104 | +++ b/obfuscate.c 105 | @@ -0,0 +1,220 @@ 106 | +#include "includes.h" 107 | +#include 108 | +#include 109 | +#include "openbsd-compat/openssl-compat.h" 110 | +#include 111 | +#include 112 | +#include "atomicio.h" 113 | +#include "canohost.h" 114 | +#include "xmalloc.h" 115 | +#include "log.h" 116 | +#include "packet.h" 117 | +#include "obfuscate.h" 118 | + 119 | +static RC4_KEY rc4_input; 120 | +static RC4_KEY rc4_output; 121 | + 122 | +static const char *obfuscate_keyword = NULL; 123 | + 124 | +#define OBFUSCATE_KEY_LENGTH 16 125 | +#define OBFUSCATE_SEED_LENGTH 16 126 | +#define OBFUSCATE_HASH_ITERATIONS 6000 127 | +#define OBFUSCATE_MAX_PADDING 8192 128 | +#define OBFUSCATE_MAGIC_VALUE 0x0BF5CA7E 129 | + 130 | +struct seed_msg { 131 | + u_char seed_buffer[OBFUSCATE_SEED_LENGTH]; 132 | + u_int32_t magic; 133 | + u_int32_t padding_length; 134 | + u_char padding[]; 135 | +}; 136 | + 137 | +static void generate_key_pair(const u_char *, u_char *, u_char *); 138 | +static void generate_key(const u_char *, const u_char *, u_int, u_char *); 139 | +static void set_keys(const u_char *, const u_char *); 140 | +static void initialize(const u_char *, int); 141 | +static void read_forever(int); 142 | + 143 | + 144 | +/* 145 | + * Server calls this 146 | + */ 147 | +void 148 | +obfuscate_receive_seed(struct ssh *ssh, int sock_in) 149 | +{ 150 | + struct seed_msg seed; 151 | + 152 | + u_char padding_drain[OBFUSCATE_MAX_PADDING]; 153 | + u_int len; 154 | + u_int32_t padding_length; 155 | + 156 | + len = atomicio(read, sock_in, &seed, sizeof(struct seed_msg)); 157 | + 158 | + debug2("obfuscate_receive_seed: read %d byte seed message from client", len); 159 | + if(len != sizeof(struct seed_msg)) 160 | + fatal("obfuscate_receive_seed: read failed"); 161 | + 162 | + initialize(seed.seed_buffer, 1); 163 | + obfuscate_input((u_char *)&seed.magic, 8); 164 | + 165 | + if(OBFUSCATE_MAGIC_VALUE != ntohl(seed.magic)) { 166 | + logit("Magic value check failed (%u) on obfuscated handshake " 167 | + "from %.200s port %d", ntohl(seed.magic), 168 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 169 | + read_forever(sock_in); 170 | + } 171 | + padding_length = ntohl(seed.padding_length); 172 | + if(padding_length > OBFUSCATE_MAX_PADDING) { 173 | + logit("Illegal padding length %d for obfuscated handshake " 174 | + "from %.200s port %d", ntohl(seed.padding_length), 175 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 176 | + read_forever(sock_in); 177 | + } 178 | + len = atomicio(read, sock_in, padding_drain, padding_length); 179 | + if(len != padding_length) 180 | + fatal("obfuscate_receive_seed: read failed"); 181 | + debug2("obfuscate_receive_seed: read %d bytes of padding from client.", len); 182 | + obfuscate_input(padding_drain, padding_length); 183 | +} 184 | + 185 | +/* 186 | + * Client calls this 187 | + */ 188 | +void 189 | +obfuscate_send_seed(int sock_out) 190 | +{ 191 | + struct seed_msg *seed; 192 | + int i; 193 | + u_int32_t rnd = 0; 194 | + u_int message_length; 195 | + u_int padding_length; 196 | + 197 | + padding_length = arc4random() % OBFUSCATE_MAX_PADDING; 198 | + message_length = padding_length + sizeof(struct seed_msg); 199 | + seed = xmalloc(message_length); 200 | + 201 | + for(i = 0; i < OBFUSCATE_SEED_LENGTH; i++) { 202 | + if(i % 4 == 0) 203 | + rnd = arc4random(); 204 | + seed->seed_buffer[i] = rnd & 0xff; 205 | + rnd >>= 8; 206 | + } 207 | + seed->magic = htonl(OBFUSCATE_MAGIC_VALUE); 208 | + seed->padding_length = htonl(padding_length); 209 | + for(i = 0; i < (int)padding_length; i++) { 210 | + if(i % 4 == 0) 211 | + rnd = arc4random(); 212 | + seed->padding[i] = rnd & 0xff; 213 | + } 214 | + initialize(seed->seed_buffer, 0); 215 | + obfuscate_output(((u_char *)seed) + OBFUSCATE_SEED_LENGTH, 216 | + message_length - OBFUSCATE_SEED_LENGTH); 217 | + debug2("obfuscate_send_seed: Sending seed message with %d bytes of padding", padding_length); 218 | + atomicio(vwrite, sock_out, seed, message_length); 219 | + free(seed); 220 | + 221 | +} 222 | + 223 | +void 224 | +obfuscate_set_keyword(const char *keyword) 225 | +{ 226 | + debug2("obfuscate_set_keyword: Setting obfuscation keyword to '%s'", keyword); 227 | + obfuscate_keyword = keyword; 228 | +} 229 | + 230 | +void 231 | +obfuscate_input(u_char *buffer, u_int buffer_len) 232 | +{ 233 | + RC4(&rc4_input, buffer_len, buffer, buffer); 234 | +} 235 | + 236 | +void 237 | +obfuscate_output(u_char *buffer, u_int buffer_len) 238 | +{ 239 | + RC4(&rc4_output, buffer_len, buffer, buffer); 240 | +} 241 | + 242 | +static void 243 | +initialize(const u_char *seed, int server) 244 | +{ 245 | + u_char client_to_server_key[OBFUSCATE_KEY_LENGTH]; 246 | + u_char server_to_client_key[OBFUSCATE_KEY_LENGTH]; 247 | + 248 | + generate_key_pair(seed, client_to_server_key, server_to_client_key); 249 | + 250 | + if(server) 251 | + set_keys(client_to_server_key, server_to_client_key); 252 | + else 253 | + set_keys(server_to_client_key, client_to_server_key); 254 | +} 255 | + 256 | +static void 257 | +generate_key_pair(const u_char *seed, u_char *client_to_server_key, u_char *server_to_client_key) 258 | +{ 259 | + generate_key(seed, "client_to_server", strlen("client_to_server"), client_to_server_key); 260 | + generate_key(seed, "server_to_client", strlen("server_to_client"), server_to_client_key); 261 | +} 262 | + 263 | +static void 264 | +generate_key(const u_char *seed, const u_char *iv, u_int iv_len, u_char *key_data) 265 | +{ 266 | + EVP_MD_CTX *ctx; 267 | + u_char md_output[EVP_MAX_MD_SIZE]; 268 | + int md_len; 269 | + int i; 270 | + u_char *buffer; 271 | + u_char *p; 272 | + u_int buffer_length; 273 | + 274 | + if ((ctx = EVP_MD_CTX_new()) == NULL) 275 | + fatal("Cannot create new digest context"); 276 | + 277 | + buffer_length = OBFUSCATE_SEED_LENGTH + iv_len; 278 | + if(obfuscate_keyword) 279 | + buffer_length += strlen(obfuscate_keyword); 280 | + 281 | + p = buffer = xmalloc(buffer_length); 282 | + 283 | + memcpy(p, seed, OBFUSCATE_SEED_LENGTH); 284 | + p += OBFUSCATE_SEED_LENGTH; 285 | + 286 | + if(obfuscate_keyword) { 287 | + memcpy(p, obfuscate_keyword, strlen(obfuscate_keyword)); 288 | + p += strlen(obfuscate_keyword); 289 | + } 290 | + memcpy(p, iv, iv_len); 291 | + 292 | + EVP_DigestInit(ctx, EVP_sha1()); 293 | + EVP_DigestUpdate(ctx, buffer, OBFUSCATE_SEED_LENGTH + iv_len); 294 | + EVP_DigestFinal(ctx, md_output, &md_len); 295 | + 296 | + free(buffer); 297 | + 298 | + for(i = 0; i < OBFUSCATE_HASH_ITERATIONS; i++) { 299 | + EVP_DigestInit(ctx, EVP_sha1()); 300 | + EVP_DigestUpdate(ctx, md_output, md_len); 301 | + EVP_DigestFinal(ctx, md_output, &md_len); 302 | + } 303 | + 304 | + if(md_len < OBFUSCATE_KEY_LENGTH) 305 | + fatal("Cannot derive obfuscation keys from hash length of %d", md_len); 306 | + 307 | + memcpy(key_data, md_output, OBFUSCATE_KEY_LENGTH); 308 | +} 309 | + 310 | +static void 311 | +set_keys(const u_char *input_key, const u_char *output_key) 312 | +{ 313 | + RC4_set_key(&rc4_input, OBFUSCATE_KEY_LENGTH, input_key); 314 | + RC4_set_key(&rc4_output, OBFUSCATE_KEY_LENGTH, output_key); 315 | +} 316 | + 317 | +static void 318 | +read_forever(int sock_in) 319 | +{ 320 | + u_char discard_buffer[1024]; 321 | + 322 | + while(atomicio(read, sock_in, discard_buffer, sizeof(discard_buffer)) > 0) 323 | + ; 324 | + cleanup_exit(255); 325 | +} 326 | diff --git a/obfuscate.h b/obfuscate.h 327 | new file mode 100644 328 | index 00000000..d5fb92b4 329 | --- /dev/null 330 | +++ b/obfuscate.h 331 | @@ -0,0 +1,10 @@ 332 | +#ifndef _OBFUSCATE_H 333 | +#define _OBFUSCATE_H 334 | + 335 | +void obfuscate_receive_seed(struct ssh *, int); 336 | +void obfuscate_send_seed(int); 337 | +void obfuscate_set_keyword(const char *); 338 | +void obfuscate_input(u_char *, u_int); 339 | +void obfuscate_output(u_char *, u_int); 340 | + 341 | +#endif 342 | diff --git a/packet.c b/packet.c 343 | index 3f64d2d3..13699f9d 100644 344 | --- a/packet.c 345 | +++ b/packet.c 346 | @@ -94,6 +94,7 @@ 347 | #include "channels.h" 348 | #include "ssh.h" 349 | #include "packet.h" 350 | +#include "obfuscate.h" 351 | #include "ssherr.h" 352 | #include "sshbuf.h" 353 | 354 | @@ -177,6 +178,8 @@ struct session_state { 355 | /* Set to true if we are authenticated. */ 356 | int after_authentication; 357 | 358 | + int obfuscation; 359 | + 360 | int keep_alive_timeouts; 361 | 362 | /* The maximum time that we will wait to send or receive a packet */ 363 | @@ -1200,6 +1203,8 @@ ssh_packet_send2_wrapped(struct ssh *ssh) 364 | if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) 365 | goto out; 366 | } 367 | + if(state->obfuscation) 368 | + obfuscate_output(cp, sshbuf_len(state->outgoing_packet)); 369 | #ifdef PACKET_DEBUG 370 | fprintf(stderr, "encrypted: "); 371 | sshbuf_dump(state->output, stderr); 372 | @@ -1534,6 +1539,8 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) 373 | if ((r = sshbuf_reserve(state->incoming_packet, block_size, 374 | &cp)) != 0) 375 | goto out; 376 | + if(state->obfuscation) 377 | + obfuscate_input(sshbuf_mutable_ptr(state->input), block_size); 378 | if ((r = cipher_crypt(state->receive_context, 379 | state->p_send.seqnr, cp, sshbuf_ptr(state->input), 380 | block_size, 0, 0)) != 0) 381 | @@ -1599,6 +1606,8 @@ ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) 382 | goto out; 383 | } 384 | } 385 | + if(state->obfuscation) 386 | + obfuscate_input(sshbuf_mutable_ptr(state->input), need); 387 | if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, 388 | &cp)) != 0) 389 | goto out; 390 | @@ -2717,3 +2726,25 @@ sshpkt_add_padding(struct ssh *ssh, u_char pad) 391 | ssh->state->extra_pad = pad; 392 | return 0; 393 | } 394 | + 395 | +void 396 | +sshpkt_enable_obfuscation(struct ssh *ssh) 397 | +{ 398 | + debug("Obfuscation enabled"); 399 | + ssh->state->obfuscation = 1; 400 | +} 401 | + 402 | +void 403 | +sshpkt_disable_obfuscation(struct ssh *ssh) 404 | +{ 405 | + if(ssh->state->obfuscation) { 406 | + debug("Obfuscation disabled"); 407 | + ssh->state->obfuscation = 0; 408 | + } 409 | +} 410 | + 411 | +int 412 | +sshpkt_get_obfuscation(struct ssh *ssh) 413 | +{ 414 | + return ssh->state->obfuscation; 415 | +} 416 | diff --git a/packet.h b/packet.h 417 | index 176488b1..87864a7d 100644 418 | --- a/packet.h 419 | +++ b/packet.h 420 | @@ -182,6 +182,9 @@ void sshpkt_fatal(struct ssh *ssh, int r, const char *fmt, ...) 421 | __attribute__((format(printf, 3, 4))) 422 | __attribute__((noreturn)); 423 | int sshpkt_msg_ignore(struct ssh *, u_int); 424 | +void sshpkt_enable_obfuscation(struct ssh *); 425 | +void sshpkt_disable_obfuscation(struct ssh *); 426 | +int sshpkt_get_obfuscation(struct ssh *); 427 | 428 | int sshpkt_put(struct ssh *ssh, const void *v, size_t len); 429 | int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b); 430 | diff --git a/readconf.c b/readconf.c 431 | index cf794988..c4f288c7 100644 432 | --- a/readconf.c 433 | +++ b/readconf.c 434 | @@ -143,7 +143,7 @@ typedef enum { 435 | oBadOption, 436 | oHost, oMatch, oInclude, 437 | oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, 438 | - oGatewayPorts, oExitOnForwardFailure, 439 | + oGatewayPorts, oExitOnForwardFailure, oObfuscateHandshake, oObfuscateKeyword, 440 | oPasswordAuthentication, 441 | oXAuthLocation, 442 | oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward, 443 | @@ -323,6 +323,8 @@ static struct { 444 | { "knownhostscommand", oKnownHostsCommand }, 445 | { "requiredrsasize", oRequiredRSASize }, 446 | { "enableescapecommandline", oEnableEscapeCommandline }, 447 | + { "obfuscatehandshake", oObfuscateHandshake }, 448 | + { "obfuscatekeyword", oObfuscateKeyword }, 449 | 450 | { NULL, oBadOption } 451 | }; 452 | @@ -2191,6 +2193,16 @@ parse_pubkey_algos: 453 | intptr = &options->required_rsa_size; 454 | goto parse_int; 455 | 456 | + case oObfuscateHandshake: 457 | + intptr = &options->obfuscate_handshake; 458 | + goto parse_flag; 459 | + 460 | + case oObfuscateKeyword: 461 | + if (*activep) 462 | + options->obfuscate_handshake = 1; 463 | + charptr = &options->obfuscate_keyword; 464 | + goto parse_string; 465 | + 466 | case oDeprecated: 467 | debug("%s line %d: Deprecated option \"%s\"", 468 | filename, linenum, keyword); 469 | @@ -2420,6 +2432,8 @@ initialize_options(Options * options) 470 | options->add_keys_to_agent_lifespan = -1; 471 | options->identity_agent = NULL; 472 | options->visual_host_key = -1; 473 | + options->obfuscate_handshake = 0; 474 | + options->obfuscate_keyword = NULL; 475 | options->ip_qos_interactive = -1; 476 | options->ip_qos_bulk = -1; 477 | options->request_tty = -1; 478 | diff --git a/readconf.h b/readconf.h 479 | index 2ce1b4c3..cdd7d335 100644 480 | --- a/readconf.h 481 | +++ b/readconf.h 482 | @@ -145,6 +145,8 @@ typedef struct { 483 | int permit_local_command; 484 | char *remote_command; 485 | int visual_host_key; 486 | + int obfuscate_handshake; 487 | + char *obfuscate_keyword; 488 | 489 | int request_tty; 490 | int session_type; 491 | diff --git a/scp.c b/scp.c 492 | index 1adff5ce..6e6f0285 100644 493 | --- a/scp.c 494 | +++ b/scp.c 495 | @@ -504,7 +504,7 @@ main(int argc, char **argv) 496 | 497 | fflag = Tflag = tflag = 0; 498 | while ((ch = getopt(argc, argv, 499 | - "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:")) != -1) { 500 | + "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:zZ:")) != -1) { 501 | switch (ch) { 502 | /* User-visible flags. */ 503 | case '1': 504 | @@ -517,6 +517,7 @@ main(int argc, char **argv) 505 | case '4': 506 | case '6': 507 | case 'C': 508 | + case 'z': 509 | addargs(&args, "-%c", ch); 510 | addargs(&remote_remote_args, "-%c", ch); 511 | break; 512 | @@ -534,6 +535,7 @@ main(int argc, char **argv) 513 | case 'i': 514 | case 'F': 515 | case 'J': 516 | + case 'Z': 517 | addargs(&remote_remote_args, "-%c", ch); 518 | addargs(&remote_remote_args, "%s", optarg); 519 | addargs(&args, "-%c", ch); 520 | diff --git a/servconf.c b/servconf.c 521 | index 2e039da8..d87ae19a 100644 522 | --- a/servconf.c 523 | +++ b/servconf.c 524 | @@ -95,6 +95,7 @@ initialize_server_options(ServerOptions *options) 525 | 526 | /* Standard Options */ 527 | options->num_ports = 0; 528 | + options->num_obfuscated_ports = 0; 529 | options->ports_from_cmdline = 0; 530 | options->queued_listen_addrs = NULL; 531 | options->num_queued_listens = 0; 532 | @@ -181,6 +182,7 @@ initialize_server_options(ServerOptions *options) 533 | options->permitted_listens = NULL; 534 | options->adm_forced_command = NULL; 535 | options->chroot_directory = NULL; 536 | + options->obfuscate_keyword = NULL; 537 | options->authorized_keys_command = NULL; 538 | options->authorized_keys_command_user = NULL; 539 | options->revoked_keys_file = NULL; 540 | @@ -300,7 +302,7 @@ fill_default_server_options(ServerOptions *options) 541 | #endif /* WITH_XMSS */ 542 | } 543 | /* No certificates by default */ 544 | - if (options->num_ports == 0) 545 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 546 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 547 | if (options->address_family == -1) 548 | options->address_family = AF_UNSPEC; 549 | @@ -502,7 +504,7 @@ typedef enum { 550 | /* Portable-specific options */ 551 | sUsePAM, 552 | /* Standard Options */ 553 | - sPort, sHostKeyFile, sLoginGraceTime, 554 | + sPort, sObfuscatedPort, sObfuscateKeyword, sHostKeyFile, sLoginGraceTime, 555 | sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose, 556 | sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, 557 | sKerberosGetAFSToken, sPasswordAuthentication, 558 | @@ -557,6 +559,8 @@ static struct { 559 | { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, 560 | /* Standard Options */ 561 | { "port", sPort, SSHCFG_GLOBAL }, 562 | + { "obfuscatedport", sObfuscatedPort, SSHCFG_GLOBAL }, 563 | + { "obfuscatekeyword", sObfuscateKeyword, SSHCFG_GLOBAL }, 564 | { "hostkey", sHostKeyFile, SSHCFG_GLOBAL }, 565 | { "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL }, /* alias */ 566 | { "hostkeyagent", sHostKeyAgent, SSHCFG_GLOBAL }, 567 | @@ -774,6 +778,10 @@ add_listen_addr(ServerOptions *options, const char *addr, 568 | add_one_listen_addr(options, addr, rdomain, 569 | options->ports[i]); 570 | } 571 | + for (i = 0; i < options->num_obfuscated_ports; i++) { 572 | + add_one_listen_addr(options, addr, rdomain, 573 | + options->obfuscated_ports[i]); 574 | + } 575 | } 576 | } 577 | 578 | @@ -888,7 +896,7 @@ process_queued_listen_addrs(ServerOptions *options) 579 | u_int i; 580 | struct queued_listenaddr *qla; 581 | 582 | - if (options->num_ports == 0) 583 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 584 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 585 | if (options->address_family == -1) 586 | options->address_family = AF_UNSPEC; 587 | @@ -1424,6 +1432,30 @@ process_server_config_line_depth(ServerOptions *options, char *line, 588 | filename, linenum); 589 | break; 590 | 591 | + case sObfuscatedPort: 592 | + if(options->ports_from_cmdline) 593 | + return 0; 594 | + if(options->listen_addrs != NULL) 595 | + fatal("%s line %d: ports must be specified before ListenAddress.", filename, linenum); 596 | + if(options->num_obfuscated_ports >= MAX_PORTS) 597 | + fatal("%s line %d: too many ports.", filename, linenum); 598 | + arg = argv_next(&ac, &av); 599 | + if(!arg || *arg == '\0') 600 | + fatal("%s line %d: missing port number.", filename, linenum); 601 | + options->obfuscated_ports[options->num_obfuscated_ports++] = a2port(arg); 602 | + if(options->obfuscated_ports[options->num_obfuscated_ports - 1] <= 0) 603 | + fatal("%s line %d: badly formatted port number.", filename, linenum); 604 | + break; 605 | + case sObfuscateKeyword: 606 | + charptr = &options->obfuscate_keyword; 607 | + arg = argv_next(&ac, &av); 608 | + if(!arg || *arg == '\0') 609 | + fatal("%s line %d: missing keyword argument.", 610 | + filename, linenum); 611 | + if(*activep && *charptr == NULL) 612 | + *charptr = xstrdup(arg); 613 | + break; 614 | + 615 | case sLoginGraceTime: 616 | intptr = &options->login_grace_time; 617 | parse_time: 618 | diff --git a/servconf.h b/servconf.h 619 | index 7ad43de8..7ab1c624 100644 620 | --- a/servconf.h 621 | +++ b/servconf.h 622 | @@ -209,6 +209,11 @@ typedef struct { 623 | u_int num_permitted_listens; 624 | 625 | char *chroot_directory; 626 | + 627 | + int obfuscated_ports[MAX_PORTS]; 628 | + u_int num_obfuscated_ports; 629 | + char *obfuscate_keyword; 630 | + 631 | char *revoked_keys_file; 632 | char *trusted_user_ca_keys; 633 | char *authorized_keys_command; 634 | diff --git a/sftp.c b/sftp.c 635 | index b3616c15..4b84d542 100644 636 | --- a/sftp.c 637 | +++ b/sftp.c 638 | @@ -2450,13 +2450,14 @@ main(int argc, char **argv) 639 | infile = stdin; 640 | 641 | while ((ch = getopt(argc, argv, 642 | - "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:")) != -1) { 643 | + "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:zZ:")) != -1) { 644 | switch (ch) { 645 | /* Passed through to ssh(1) */ 646 | case 'A': 647 | case '4': 648 | case '6': 649 | case 'C': 650 | + case 'z': 651 | addargs(&args, "-%c", ch); 652 | break; 653 | /* Passed through to ssh(1) with argument */ 654 | @@ -2465,6 +2466,7 @@ main(int argc, char **argv) 655 | case 'c': 656 | case 'i': 657 | case 'o': 658 | + case 'Z': 659 | addargs(&args, "-%c", ch); 660 | addargs(&args, "%s", optarg); 661 | break; 662 | diff --git a/ssh.c b/ssh.c 663 | index 87454b84..c2b55079 100644 664 | --- a/ssh.c 665 | +++ b/ssh.c 666 | @@ -179,13 +179,14 @@ static void 667 | usage(void) 668 | { 669 | fprintf(stderr, 670 | -"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]\n" 671 | +"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYyz] [-B bind_interface]\n" 672 | " [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]\n" 673 | " [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]\n" 674 | " [-i identity_file] [-J [user@]host[:port]] [-L address]\n" 675 | " [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n" 676 | " [-Q query_option] [-R address] [-S ctl_path] [-W host:port]\n" 677 | " [-w local_tun[:remote_tun]] destination [command [argument ...]]\n" 678 | +" [-Z obfuscate_keyword]\n" 679 | ); 680 | exit(255); 681 | } 682 | @@ -708,7 +709,7 @@ main(int ac, char **av) 683 | 684 | again: 685 | while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" 686 | - "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { /* HUZdhjruz */ 687 | + "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYyzZ:")) != -1) { /* HUZdhjruz */ 688 | switch (opt) { 689 | case '1': 690 | fatal("SSH protocol v.1 is no longer supported"); 691 | @@ -1053,6 +1054,13 @@ main(int ac, char **av) 692 | case 'F': 693 | config = optarg; 694 | break; 695 | + case 'z': 696 | + options.obfuscate_handshake = 1; 697 | + break; 698 | + case 'Z': 699 | + options.obfuscate_handshake = 1; 700 | + options.obfuscate_keyword = optarg; 701 | + break; 702 | default: 703 | usage(); 704 | } 705 | diff --git a/sshconnect.c b/sshconnect.c 706 | index 792bc34b..d6d8b51f 100644 707 | --- a/sshconnect.c 708 | +++ b/sshconnect.c 709 | @@ -65,6 +65,7 @@ 710 | #include "monitor_fdpass.h" 711 | #include "ssh2.h" 712 | #include "version.h" 713 | +#include "obfuscate.h" 714 | #include "authfile.h" 715 | #include "ssherr.h" 716 | #include "authfd.h" 717 | @@ -269,6 +270,12 @@ ssh_proxy_connect(struct ssh *ssh, const char *host, const char *host_arg, 718 | if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) 719 | return -1; /* ssh_packet_set_connection logs error */ 720 | 721 | + if(options.obfuscate_handshake) { 722 | + if(options.obfuscate_keyword) 723 | + obfuscate_set_keyword(options.obfuscate_keyword); 724 | + sshpkt_enable_obfuscation(ssh); 725 | + } 726 | + 727 | return 0; 728 | } 729 | 730 | @@ -530,6 +537,11 @@ ssh_connect_direct(struct ssh *ssh, const char *host, struct addrinfo *aitop, 731 | /* Set the connection. */ 732 | if (ssh_packet_set_connection(ssh, sock, sock) == NULL) 733 | return -1; /* ssh_packet_set_connection logs error */ 734 | + if(options.obfuscate_handshake) { 735 | + if(options.obfuscate_keyword) 736 | + obfuscate_set_keyword(options.obfuscate_keyword); 737 | + sshpkt_enable_obfuscation(ssh); 738 | + } 739 | 740 | return 0; 741 | } 742 | @@ -1566,6 +1578,9 @@ ssh_login(struct ssh *ssh, Sensitive *sensitive, const char *orighost, 743 | host = xstrdup(orighost); 744 | lowercase(host); 745 | 746 | + if(options.obfuscate_handshake) 747 | + obfuscate_send_seed(ssh_packet_get_connection_out(ssh)); 748 | + 749 | /* Exchange protocol version identification strings with the server. */ 750 | if ((r = kex_exchange_identification(ssh, timeout_ms, NULL)) != 0) 751 | sshpkt_fatal(ssh, r, "banner exchange"); 752 | diff --git a/sshd.c b/sshd.c 753 | index 6321936c..e3ee3301 100644 754 | --- a/sshd.c 755 | +++ b/sshd.c 756 | @@ -121,6 +121,7 @@ 757 | #include "ssh-gss.h" 758 | #endif 759 | #include "monitor_wrap.h" 760 | +#include "obfuscate.h" 761 | #include "ssh-sandbox.h" 762 | #include "auth-options.h" 763 | #include "version.h" 764 | @@ -255,6 +256,9 @@ struct include_list includes = TAILQ_HEAD_INITIALIZER(includes); 765 | /* message to be displayed after login */ 766 | struct sshbuf *loginmsg; 767 | 768 | +/* Enable handshake obfuscation */ 769 | +int use_obfuscation = 0; 770 | + 771 | /* Unprivileged user */ 772 | struct passwd *privsep_pw = NULL; 773 | 774 | @@ -1533,7 +1537,7 @@ main(int ac, char **av) 775 | struct ssh *ssh = NULL; 776 | extern char *optarg; 777 | extern int optind; 778 | - int r, opt, on = 1, already_daemon, remote_port; 779 | + int r, opt, on = 1, already_daemon, remote_port, local_port; 780 | int sock_in = -1, sock_out = -1, newsock = -1; 781 | const char *remote_ip, *rdomain; 782 | char *fp, *line, *laddr, *logfile = NULL; 783 | @@ -2165,6 +2169,14 @@ main(int ac, char **av) 784 | process_channel_timeouts(ssh, &options); 785 | process_permitopen(ssh, &options); 786 | 787 | + local_port = ssh_local_port(ssh); 788 | + for(i = 0; i < options.num_obfuscated_ports; i++) { 789 | + if(options.obfuscated_ports[i] == local_port) { 790 | + use_obfuscation = 1; 791 | + break; 792 | + } 793 | + } 794 | + 795 | /* Set SO_KEEPALIVE if requested. */ 796 | if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && 797 | setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) 798 | @@ -2212,6 +2224,13 @@ main(int ac, char **av) 799 | if (!debug_flag) 800 | alarm(options.login_grace_time); 801 | 802 | + if(use_obfuscation) { 803 | + if(options.obfuscate_keyword) 804 | + obfuscate_set_keyword(options.obfuscate_keyword); 805 | + sshpkt_enable_obfuscation(ssh); 806 | + obfuscate_receive_seed(ssh, sock_in); 807 | + } 808 | + 809 | if ((r = kex_exchange_identification(ssh, -1, 810 | options.version_addendum)) != 0) 811 | sshpkt_fatal(ssh, r, "banner exchange"); 812 | @@ -2237,8 +2256,12 @@ main(int ac, char **av) 813 | auth_debug_reset(); 814 | 815 | if (use_privsep) { 816 | - if (privsep_preauth(ssh) == 1) 817 | + 818 | + if (privsep_preauth(ssh) == 1) { 819 | + if(use_obfuscation) 820 | + sshpkt_disable_obfuscation(ssh); 821 | goto authenticated; 822 | + } 823 | } else if (have_agent) { 824 | if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { 825 | error_r(r, "Unable to get agent socket"); 826 | diff --git a/sshd_config b/sshd_config 827 | index 36894ace..5dfb1c68 100644 828 | --- a/sshd_config 829 | +++ b/sshd_config 830 | @@ -11,6 +11,8 @@ 831 | # default value. 832 | 833 | #Port 22 834 | +#ObfuscatedPort 222 835 | +#ObfuscateKeyword key 836 | #AddressFamily any 837 | #ListenAddress 0.0.0.0 838 | #ListenAddress :: 839 | -------------------------------------------------------------------------------- /portable/9.7.diff: -------------------------------------------------------------------------------- 1 | 9.6.diff -------------------------------------------------------------------------------- /portable/9.8.diff: -------------------------------------------------------------------------------- 1 | diff -Nupr openssh-9.8p1/kex.c ob/kex.c 2 | --- openssh-9.8p1/kex.c 2024-07-01 12:36:28.000000000 +0800 3 | +++ ob/kex.c 2024-07-01 23:59:55.288000539 +0800 4 | @@ -59,6 +59,7 @@ 5 | #include "monitor.h" 6 | #include "myproposal.h" 7 | 8 | +#include "obfuscate.h" 9 | #include "ssherr.h" 10 | #include "sshbuf.h" 11 | #include "digest.h" 12 | @@ -400,8 +401,10 @@ kex_send_newkeys(struct ssh *ssh) 13 | return r; 14 | debug("SSH2_MSG_NEWKEYS sent"); 15 | ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); 16 | + sshpkt_disable_obfuscation(ssh); 17 | if ((r = kex_maybe_send_ext_info(ssh)) != 0) 18 | return r; 19 | + sshpkt_enable_obfuscation(ssh); 20 | debug("expecting SSH2_MSG_NEWKEYS"); 21 | return 0; 22 | } 23 | @@ -565,6 +568,7 @@ kex_input_newkeys(int type, u_int32_t se 24 | kex->flags &= ~KEX_INIT_SENT; 25 | free(kex->name); 26 | kex->name = NULL; 27 | + sshpkt_disable_obfuscation(ssh); 28 | return 0; 29 | } 30 | 31 | @@ -1261,14 +1265,42 @@ kex_exchange_identification(struct ssh * 32 | goto out; 33 | } 34 | 35 | + /* Make copy and obfuscate the original */ 36 | + if (sshpkt_get_obfuscation(ssh) == 1) { 37 | + if ((cp = sshbuf_dup_string(our_version)) == NULL) { 38 | + error("%s: sshbuf_dup_string failed for obfuscation", __func__); 39 | + r = SSH_ERR_ALLOC_FAIL; 40 | + goto out; 41 | + } 42 | + obfuscate_output(sshbuf_mutable_ptr(our_version), sshbuf_len(our_version)); 43 | + } 44 | + 45 | if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), 46 | sshbuf_mutable_ptr(our_version), 47 | sshbuf_len(our_version)) != sshbuf_len(our_version)) { 48 | oerrno = errno; 49 | debug_f("write: %.100s", strerror(errno)); 50 | r = SSH_ERR_SYSTEM_ERROR; 51 | + if (sshpkt_get_obfuscation(ssh) == 1) { 52 | + free(cp); 53 | + } 54 | goto out; 55 | } 56 | + 57 | + /* Restore the original */ 58 | + if (sshpkt_get_obfuscation(ssh) == 1) { 59 | + if ((r = sshbuf_consume(our_version, sshbuf_len(our_version))) != 0) { 60 | + error("%s: sshbuf_consume failed for obfuscation", __func__); 61 | + free(cp); 62 | + goto out; 63 | + } 64 | + if ((r = sshbuf_put(our_version, cp, strlen(cp))) != 0) { 65 | + error("%s: sshbuf_put failed for obfuscation", __func__); 66 | + free(cp); 67 | + goto out; 68 | + } 69 | + } 70 | + 71 | if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ 72 | oerrno = errno; 73 | error_fr(r, "sshbuf_consume_end"); 74 | @@ -1325,6 +1357,8 @@ kex_exchange_identification(struct ssh * 75 | r = SSH_ERR_SYSTEM_ERROR; 76 | goto out; 77 | } 78 | + if(sshpkt_get_obfuscation(ssh) == 1) 79 | + obfuscate_input(&c, 1); 80 | if (c == '\r') { 81 | expect_nl = 1; 82 | continue; 83 | diff -Nupr openssh-9.8p1/Makefile.in ob/Makefile.in 84 | --- openssh-9.8p1/Makefile.in 2024-07-01 12:36:28.000000000 +0800 85 | +++ ob/Makefile.in 2024-07-01 23:59:55.288000539 +0800 86 | @@ -113,7 +113,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ 87 | kexgexc.o kexgexs.o \ 88 | kexsntrup761x25519.o sntrup761.o kexgen.o \ 89 | sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ 90 | - sshbuf-io.o 91 | + sshbuf-io.o obfuscate.o 92 | 93 | SKOBJS= ssh-sk-client.o 94 | 95 | diff -Nupr openssh-9.8p1/obfuscate.c ob/obfuscate.c 96 | --- openssh-9.8p1/obfuscate.c 1970-01-01 08:00:00.000000000 +0800 97 | +++ ob/obfuscate.c 2024-07-01 23:59:55.288000539 +0800 98 | @@ -0,0 +1,220 @@ 99 | +#include "includes.h" 100 | +#include 101 | +#include 102 | +#include "openbsd-compat/openssl-compat.h" 103 | +#include 104 | +#include 105 | +#include "atomicio.h" 106 | +#include "canohost.h" 107 | +#include "xmalloc.h" 108 | +#include "log.h" 109 | +#include "packet.h" 110 | +#include "obfuscate.h" 111 | + 112 | +static RC4_KEY rc4_input; 113 | +static RC4_KEY rc4_output; 114 | + 115 | +static const char *obfuscate_keyword = NULL; 116 | + 117 | +#define OBFUSCATE_KEY_LENGTH 16 118 | +#define OBFUSCATE_SEED_LENGTH 16 119 | +#define OBFUSCATE_HASH_ITERATIONS 6000 120 | +#define OBFUSCATE_MAX_PADDING 8192 121 | +#define OBFUSCATE_MAGIC_VALUE 0x0BF5CA7E 122 | + 123 | +struct seed_msg { 124 | + u_char seed_buffer[OBFUSCATE_SEED_LENGTH]; 125 | + u_int32_t magic; 126 | + u_int32_t padding_length; 127 | + u_char padding[]; 128 | +}; 129 | + 130 | +static void generate_key_pair(const u_char *, u_char *, u_char *); 131 | +static void generate_key(const u_char *, const u_char *, u_int, u_char *); 132 | +static void set_keys(const u_char *, const u_char *); 133 | +static void initialize(const u_char *, int); 134 | +static void read_forever(int); 135 | + 136 | + 137 | +/* 138 | + * Server calls this 139 | + */ 140 | +void 141 | +obfuscate_receive_seed(struct ssh *ssh, int sock_in) 142 | +{ 143 | + struct seed_msg seed; 144 | + 145 | + u_char padding_drain[OBFUSCATE_MAX_PADDING]; 146 | + u_int len; 147 | + u_int32_t padding_length; 148 | + 149 | + len = atomicio(read, sock_in, &seed, sizeof(struct seed_msg)); 150 | + 151 | + debug2("obfuscate_receive_seed: read %d byte seed message from client", len); 152 | + if(len != sizeof(struct seed_msg)) 153 | + fatal("obfuscate_receive_seed: read failed"); 154 | + 155 | + initialize(seed.seed_buffer, 1); 156 | + obfuscate_input((u_char *)&seed.magic, 8); 157 | + 158 | + if(OBFUSCATE_MAGIC_VALUE != ntohl(seed.magic)) { 159 | + logit("Magic value check failed (%u) on obfuscated handshake " 160 | + "from %.200s port %d", ntohl(seed.magic), 161 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 162 | + read_forever(sock_in); 163 | + } 164 | + padding_length = ntohl(seed.padding_length); 165 | + if(padding_length > OBFUSCATE_MAX_PADDING) { 166 | + logit("Illegal padding length %d for obfuscated handshake " 167 | + "from %.200s port %d", ntohl(seed.padding_length), 168 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 169 | + read_forever(sock_in); 170 | + } 171 | + len = atomicio(read, sock_in, padding_drain, padding_length); 172 | + if(len != padding_length) 173 | + fatal("obfuscate_receive_seed: read failed"); 174 | + debug2("obfuscate_receive_seed: read %d bytes of padding from client.", len); 175 | + obfuscate_input(padding_drain, padding_length); 176 | +} 177 | + 178 | +/* 179 | + * Client calls this 180 | + */ 181 | +void 182 | +obfuscate_send_seed(int sock_out) 183 | +{ 184 | + struct seed_msg *seed; 185 | + int i; 186 | + u_int32_t rnd = 0; 187 | + u_int message_length; 188 | + u_int padding_length; 189 | + 190 | + padding_length = arc4random() % OBFUSCATE_MAX_PADDING; 191 | + message_length = padding_length + sizeof(struct seed_msg); 192 | + seed = xmalloc(message_length); 193 | + 194 | + for(i = 0; i < OBFUSCATE_SEED_LENGTH; i++) { 195 | + if(i % 4 == 0) 196 | + rnd = arc4random(); 197 | + seed->seed_buffer[i] = rnd & 0xff; 198 | + rnd >>= 8; 199 | + } 200 | + seed->magic = htonl(OBFUSCATE_MAGIC_VALUE); 201 | + seed->padding_length = htonl(padding_length); 202 | + for(i = 0; i < (int)padding_length; i++) { 203 | + if(i % 4 == 0) 204 | + rnd = arc4random(); 205 | + seed->padding[i] = rnd & 0xff; 206 | + } 207 | + initialize(seed->seed_buffer, 0); 208 | + obfuscate_output(((u_char *)seed) + OBFUSCATE_SEED_LENGTH, 209 | + message_length - OBFUSCATE_SEED_LENGTH); 210 | + debug2("obfuscate_send_seed: Sending seed message with %d bytes of padding", padding_length); 211 | + atomicio(vwrite, sock_out, seed, message_length); 212 | + free(seed); 213 | + 214 | +} 215 | + 216 | +void 217 | +obfuscate_set_keyword(const char *keyword) 218 | +{ 219 | + debug2("obfuscate_set_keyword: Setting obfuscation keyword to '%s'", keyword); 220 | + obfuscate_keyword = keyword; 221 | +} 222 | + 223 | +void 224 | +obfuscate_input(u_char *buffer, u_int buffer_len) 225 | +{ 226 | + RC4(&rc4_input, buffer_len, buffer, buffer); 227 | +} 228 | + 229 | +void 230 | +obfuscate_output(u_char *buffer, u_int buffer_len) 231 | +{ 232 | + RC4(&rc4_output, buffer_len, buffer, buffer); 233 | +} 234 | + 235 | +static void 236 | +initialize(const u_char *seed, int server) 237 | +{ 238 | + u_char client_to_server_key[OBFUSCATE_KEY_LENGTH]; 239 | + u_char server_to_client_key[OBFUSCATE_KEY_LENGTH]; 240 | + 241 | + generate_key_pair(seed, client_to_server_key, server_to_client_key); 242 | + 243 | + if(server) 244 | + set_keys(client_to_server_key, server_to_client_key); 245 | + else 246 | + set_keys(server_to_client_key, client_to_server_key); 247 | +} 248 | + 249 | +static void 250 | +generate_key_pair(const u_char *seed, u_char *client_to_server_key, u_char *server_to_client_key) 251 | +{ 252 | + generate_key(seed, "client_to_server", strlen("client_to_server"), client_to_server_key); 253 | + generate_key(seed, "server_to_client", strlen("server_to_client"), server_to_client_key); 254 | +} 255 | + 256 | +static void 257 | +generate_key(const u_char *seed, const u_char *iv, u_int iv_len, u_char *key_data) 258 | +{ 259 | + EVP_MD_CTX *ctx; 260 | + u_char md_output[EVP_MAX_MD_SIZE]; 261 | + int md_len; 262 | + int i; 263 | + u_char *buffer; 264 | + u_char *p; 265 | + u_int buffer_length; 266 | + 267 | + if ((ctx = EVP_MD_CTX_new()) == NULL) 268 | + fatal("Cannot create new digest context"); 269 | + 270 | + buffer_length = OBFUSCATE_SEED_LENGTH + iv_len; 271 | + if(obfuscate_keyword) 272 | + buffer_length += strlen(obfuscate_keyword); 273 | + 274 | + p = buffer = xmalloc(buffer_length); 275 | + 276 | + memcpy(p, seed, OBFUSCATE_SEED_LENGTH); 277 | + p += OBFUSCATE_SEED_LENGTH; 278 | + 279 | + if(obfuscate_keyword) { 280 | + memcpy(p, obfuscate_keyword, strlen(obfuscate_keyword)); 281 | + p += strlen(obfuscate_keyword); 282 | + } 283 | + memcpy(p, iv, iv_len); 284 | + 285 | + EVP_DigestInit(ctx, EVP_sha1()); 286 | + EVP_DigestUpdate(ctx, buffer, OBFUSCATE_SEED_LENGTH + iv_len); 287 | + EVP_DigestFinal(ctx, md_output, &md_len); 288 | + 289 | + free(buffer); 290 | + 291 | + for(i = 0; i < OBFUSCATE_HASH_ITERATIONS; i++) { 292 | + EVP_DigestInit(ctx, EVP_sha1()); 293 | + EVP_DigestUpdate(ctx, md_output, md_len); 294 | + EVP_DigestFinal(ctx, md_output, &md_len); 295 | + } 296 | + 297 | + if(md_len < OBFUSCATE_KEY_LENGTH) 298 | + fatal("Cannot derive obfuscation keys from hash length of %d", md_len); 299 | + 300 | + memcpy(key_data, md_output, OBFUSCATE_KEY_LENGTH); 301 | +} 302 | + 303 | +static void 304 | +set_keys(const u_char *input_key, const u_char *output_key) 305 | +{ 306 | + RC4_set_key(&rc4_input, OBFUSCATE_KEY_LENGTH, input_key); 307 | + RC4_set_key(&rc4_output, OBFUSCATE_KEY_LENGTH, output_key); 308 | +} 309 | + 310 | +static void 311 | +read_forever(int sock_in) 312 | +{ 313 | + u_char discard_buffer[1024]; 314 | + 315 | + while(atomicio(read, sock_in, discard_buffer, sizeof(discard_buffer)) > 0) 316 | + ; 317 | + cleanup_exit(255); 318 | +} 319 | diff -Nupr openssh-9.8p1/obfuscate.h ob/obfuscate.h 320 | --- openssh-9.8p1/obfuscate.h 1970-01-01 08:00:00.000000000 +0800 321 | +++ ob/obfuscate.h 2024-07-01 23:59:55.288000539 +0800 322 | @@ -0,0 +1,10 @@ 323 | +#ifndef _OBFUSCATE_H 324 | +#define _OBFUSCATE_H 325 | + 326 | +void obfuscate_receive_seed(struct ssh *, int); 327 | +void obfuscate_send_seed(int); 328 | +void obfuscate_set_keyword(const char *); 329 | +void obfuscate_input(u_char *, u_int); 330 | +void obfuscate_output(u_char *, u_int); 331 | + 332 | +#endif 333 | diff -Nupr openssh-9.8p1/packet.c ob/packet.c 334 | --- openssh-9.8p1/packet.c 2024-07-01 12:36:28.000000000 +0800 335 | +++ ob/packet.c 2024-07-01 23:59:55.300000506 +0800 336 | @@ -94,6 +94,7 @@ 337 | #include "channels.h" 338 | #include "ssh.h" 339 | #include "packet.h" 340 | +#include "obfuscate.h" 341 | #include "ssherr.h" 342 | #include "sshbuf.h" 343 | 344 | @@ -177,6 +178,8 @@ struct session_state { 345 | /* Set to true if we are authenticated. */ 346 | int after_authentication; 347 | 348 | + int obfuscation; 349 | + 350 | int keep_alive_timeouts; 351 | 352 | /* The maximum time that we will wait to send or receive a packet */ 353 | @@ -1294,6 +1297,8 @@ ssh_packet_send2_wrapped(struct ssh *ssh 354 | if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) 355 | goto out; 356 | } 357 | + if(state->obfuscation) 358 | + obfuscate_output(cp, sshbuf_len(state->outgoing_packet)); 359 | #ifdef PACKET_DEBUG 360 | fprintf(stderr, "encrypted: "); 361 | sshbuf_dump(state->output, stderr); 362 | @@ -1614,6 +1619,8 @@ ssh_packet_read_poll2(struct ssh *ssh, u 363 | if ((r = sshbuf_reserve(state->incoming_packet, block_size, 364 | &cp)) != 0) 365 | goto out; 366 | + if(state->obfuscation) 367 | + obfuscate_input(sshbuf_mutable_ptr(state->input), block_size); 368 | if ((r = cipher_crypt(state->receive_context, 369 | state->p_send.seqnr, cp, sshbuf_ptr(state->input), 370 | block_size, 0, 0)) != 0) 371 | @@ -1679,6 +1686,8 @@ ssh_packet_read_poll2(struct ssh *ssh, u 372 | goto out; 373 | } 374 | } 375 | + if(state->obfuscation) 376 | + obfuscate_input(sshbuf_mutable_ptr(state->input), need); 377 | if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, 378 | &cp)) != 0) 379 | goto out; 380 | @@ -2855,3 +2864,25 @@ sshpkt_add_padding(struct ssh *ssh, u_ch 381 | ssh->state->extra_pad = pad; 382 | return 0; 383 | } 384 | + 385 | +void 386 | +sshpkt_enable_obfuscation(struct ssh *ssh) 387 | +{ 388 | + debug("Obfuscation enabled"); 389 | + ssh->state->obfuscation = 1; 390 | +} 391 | + 392 | +void 393 | +sshpkt_disable_obfuscation(struct ssh *ssh) 394 | +{ 395 | + if(ssh->state->obfuscation) { 396 | + debug("Obfuscation disabled"); 397 | + ssh->state->obfuscation = 0; 398 | + } 399 | +} 400 | + 401 | +int 402 | +sshpkt_get_obfuscation(struct ssh *ssh) 403 | +{ 404 | + return ssh->state->obfuscation; 405 | +} 406 | diff -Nupr openssh-9.8p1/packet.h ob/packet.h 407 | --- openssh-9.8p1/packet.h 2024-07-01 12:36:28.000000000 +0800 408 | +++ ob/packet.h 2024-07-01 23:59:55.300000506 +0800 409 | @@ -181,6 +181,9 @@ void sshpkt_fatal(struct ssh *ssh, int r 410 | __attribute__((format(printf, 3, 4))) 411 | __attribute__((noreturn)); 412 | int sshpkt_msg_ignore(struct ssh *, u_int); 413 | +void sshpkt_enable_obfuscation(struct ssh *); 414 | +void sshpkt_disable_obfuscation(struct ssh *); 415 | +int sshpkt_get_obfuscation(struct ssh *); 416 | 417 | int sshpkt_put(struct ssh *ssh, const void *v, size_t len); 418 | int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b); 419 | diff -Nupr openssh-9.8p1/readconf.c ob/readconf.c 420 | --- openssh-9.8p1/readconf.c 2024-07-01 12:36:28.000000000 +0800 421 | +++ ob/readconf.c 2024-07-01 23:59:55.300000506 +0800 422 | @@ -146,7 +146,7 @@ typedef enum { 423 | oBadOption, 424 | oHost, oMatch, oInclude, oTag, 425 | oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, 426 | - oGatewayPorts, oExitOnForwardFailure, 427 | + oGatewayPorts, oExitOnForwardFailure, oObfuscateHandshake, oObfuscateKeyword, 428 | oPasswordAuthentication, 429 | oXAuthLocation, 430 | oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward, 431 | @@ -329,6 +329,8 @@ static struct { 432 | { "enableescapecommandline", oEnableEscapeCommandline }, 433 | { "obscurekeystroketiming", oObscureKeystrokeTiming }, 434 | { "channeltimeout", oChannelTimeout }, 435 | + { "obfuscatehandshake", oObfuscateHandshake }, 436 | + { "obfuscatekeyword", oObfuscateKeyword }, 437 | 438 | { NULL, oBadOption } 439 | }; 440 | @@ -2388,6 +2390,16 @@ parse_pubkey_algos: 441 | } 442 | break; 443 | 444 | + case oObfuscateHandshake: 445 | + intptr = &options->obfuscate_handshake; 446 | + goto parse_flag; 447 | + 448 | + case oObfuscateKeyword: 449 | + if (*activep) 450 | + options->obfuscate_handshake = 1; 451 | + charptr = &options->obfuscate_keyword; 452 | + goto parse_string; 453 | + 454 | case oDeprecated: 455 | debug("%s line %d: Deprecated option \"%s\"", 456 | filename, linenum, keyword); 457 | @@ -2619,6 +2631,8 @@ initialize_options(Options * options) 458 | options->add_keys_to_agent_lifespan = -1; 459 | options->identity_agent = NULL; 460 | options->visual_host_key = -1; 461 | + options->obfuscate_handshake = 0; 462 | + options->obfuscate_keyword = NULL; 463 | options->ip_qos_interactive = -1; 464 | options->ip_qos_bulk = -1; 465 | options->request_tty = -1; 466 | diff -Nupr openssh-9.8p1/readconf.h ob/readconf.h 467 | --- openssh-9.8p1/readconf.h 2024-07-01 12:36:28.000000000 +0800 468 | +++ ob/readconf.h 2024-07-01 23:59:55.300000506 +0800 469 | @@ -146,6 +146,8 @@ typedef struct { 470 | int permit_local_command; 471 | char *remote_command; 472 | int visual_host_key; 473 | + int obfuscate_handshake; 474 | + char *obfuscate_keyword; 475 | 476 | int request_tty; 477 | int session_type; 478 | diff -Nupr openssh-9.8p1/scp.c ob/scp.c 479 | --- openssh-9.8p1/scp.c 2024-07-01 12:36:28.000000000 +0800 480 | +++ ob/scp.c 2024-07-01 23:59:55.312000470 +0800 481 | @@ -508,7 +508,7 @@ main(int argc, char **argv) 482 | 483 | fflag = Tflag = tflag = 0; 484 | while ((ch = getopt(argc, argv, 485 | - "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:")) != -1) { 486 | + "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:zZ:")) != -1) { 487 | switch (ch) { 488 | /* User-visible flags. */ 489 | case '1': 490 | @@ -521,6 +521,7 @@ main(int argc, char **argv) 491 | case '4': 492 | case '6': 493 | case 'C': 494 | + case 'z': 495 | addargs(&args, "-%c", ch); 496 | addargs(&remote_remote_args, "-%c", ch); 497 | break; 498 | @@ -538,6 +539,7 @@ main(int argc, char **argv) 499 | case 'i': 500 | case 'F': 501 | case 'J': 502 | + case 'Z': 503 | addargs(&remote_remote_args, "-%c", ch); 504 | addargs(&remote_remote_args, "%s", optarg); 505 | addargs(&args, "-%c", ch); 506 | diff -Nupr openssh-9.8p1/servconf.c ob/servconf.c 507 | --- openssh-9.8p1/servconf.c 2024-07-01 12:36:28.000000000 +0800 508 | +++ ob/servconf.c 2024-07-01 23:59:55.312000470 +0800 509 | @@ -96,6 +96,7 @@ initialize_server_options(ServerOptions 510 | 511 | /* Standard Options */ 512 | options->num_ports = 0; 513 | + options->num_obfuscated_ports = 0; 514 | options->ports_from_cmdline = 0; 515 | options->queued_listen_addrs = NULL; 516 | options->num_queued_listens = 0; 517 | @@ -194,6 +195,7 @@ initialize_server_options(ServerOptions 518 | options->permitted_listens = NULL; 519 | options->adm_forced_command = NULL; 520 | options->chroot_directory = NULL; 521 | + options->obfuscate_keyword = NULL; 522 | options->authorized_keys_command = NULL; 523 | options->authorized_keys_command_user = NULL; 524 | options->revoked_keys_file = NULL; 525 | @@ -316,7 +318,7 @@ fill_default_server_options(ServerOption 526 | #endif /* WITH_XMSS */ 527 | } 528 | /* No certificates by default */ 529 | - if (options->num_ports == 0) 530 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 531 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 532 | if (options->address_family == -1) 533 | options->address_family = AF_UNSPEC; 534 | @@ -539,7 +541,7 @@ typedef enum { 535 | /* Portable-specific options */ 536 | sUsePAM, sPAMServiceName, 537 | /* Standard Options */ 538 | - sPort, sHostKeyFile, sLoginGraceTime, 539 | + sPort, sObfuscatedPort, sObfuscateKeyword, sHostKeyFile, sLoginGraceTime, 540 | sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose, 541 | sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, 542 | sKerberosGetAFSToken, sPasswordAuthentication, 543 | @@ -598,6 +600,8 @@ static struct { 544 | { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, 545 | /* Standard Options */ 546 | { "port", sPort, SSHCFG_GLOBAL }, 547 | + { "obfuscatedport", sObfuscatedPort, SSHCFG_GLOBAL }, 548 | + { "obfuscatekeyword", sObfuscateKeyword, SSHCFG_GLOBAL }, 549 | { "hostkey", sHostKeyFile, SSHCFG_GLOBAL }, 550 | { "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL }, /* alias */ 551 | { "hostkeyagent", sHostKeyAgent, SSHCFG_GLOBAL }, 552 | @@ -818,6 +822,10 @@ add_listen_addr(ServerOptions *options, 553 | add_one_listen_addr(options, addr, rdomain, 554 | options->ports[i]); 555 | } 556 | + for (i = 0; i < options->num_obfuscated_ports; i++) { 557 | + add_one_listen_addr(options, addr, rdomain, 558 | + options->obfuscated_ports[i]); 559 | + } 560 | } 561 | } 562 | 563 | @@ -932,7 +940,7 @@ process_queued_listen_addrs(ServerOption 564 | u_int i; 565 | struct queued_listenaddr *qla; 566 | 567 | - if (options->num_ports == 0) 568 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 569 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 570 | if (options->address_family == -1) 571 | options->address_family = AF_UNSPEC; 572 | @@ -1360,6 +1368,30 @@ process_server_config_line_depth(ServerO 573 | filename, linenum); 574 | break; 575 | 576 | + case sObfuscatedPort: 577 | + if(options->ports_from_cmdline) 578 | + return 0; 579 | + if(options->listen_addrs != NULL) 580 | + fatal("%s line %d: ports must be specified before ListenAddress.", filename, linenum); 581 | + if(options->num_obfuscated_ports >= MAX_PORTS) 582 | + fatal("%s line %d: too many ports.", filename, linenum); 583 | + arg = argv_next(&ac, &av); 584 | + if(!arg || *arg == '\0') 585 | + fatal("%s line %d: missing port number.", filename, linenum); 586 | + options->obfuscated_ports[options->num_obfuscated_ports++] = a2port(arg); 587 | + if(options->obfuscated_ports[options->num_obfuscated_ports - 1] <= 0) 588 | + fatal("%s line %d: badly formatted port number.", filename, linenum); 589 | + break; 590 | + case sObfuscateKeyword: 591 | + charptr = &options->obfuscate_keyword; 592 | + arg = argv_next(&ac, &av); 593 | + if(!arg || *arg == '\0') 594 | + fatal("%s line %d: missing keyword argument.", 595 | + filename, linenum); 596 | + if(*activep && *charptr == NULL) 597 | + *charptr = xstrdup(arg); 598 | + break; 599 | + 600 | case sLoginGraceTime: 601 | intptr = &options->login_grace_time; 602 | parse_time: 603 | diff -Nupr openssh-9.8p1/servconf.h ob/servconf.h 604 | --- openssh-9.8p1/servconf.h 2024-07-01 12:36:28.000000000 +0800 605 | +++ ob/servconf.h 2024-07-01 23:59:55.312000470 +0800 606 | @@ -220,6 +220,11 @@ typedef struct { 607 | u_int num_permitted_listens; 608 | 609 | char *chroot_directory; 610 | + 611 | + int obfuscated_ports[MAX_PORTS]; 612 | + u_int num_obfuscated_ports; 613 | + char *obfuscate_keyword; 614 | + 615 | char *revoked_keys_file; 616 | char *trusted_user_ca_keys; 617 | char *authorized_keys_command; 618 | diff -Nupr openssh-9.8p1/sftp.c ob/sftp.c 619 | --- openssh-9.8p1/sftp.c 2024-07-01 12:36:28.000000000 +0800 620 | +++ ob/sftp.c 2024-07-01 23:59:55.312000470 +0800 621 | @@ -2468,13 +2468,14 @@ main(int argc, char **argv) 622 | infile = stdin; 623 | 624 | while ((ch = getopt(argc, argv, 625 | - "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:")) != -1) { 626 | + "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:zZ:")) != -1) { 627 | switch (ch) { 628 | /* Passed through to ssh(1) */ 629 | case 'A': 630 | case '4': 631 | case '6': 632 | case 'C': 633 | + case 'z': 634 | addargs(&args, "-%c", ch); 635 | break; 636 | /* Passed through to ssh(1) with argument */ 637 | @@ -2483,6 +2484,7 @@ main(int argc, char **argv) 638 | case 'c': 639 | case 'i': 640 | case 'o': 641 | + case 'Z': 642 | addargs(&args, "-%c", ch); 643 | addargs(&args, "%s", optarg); 644 | break; 645 | diff -Nupr openssh-9.8p1/ssh.c ob/ssh.c 646 | --- openssh-9.8p1/ssh.c 2024-07-01 12:36:28.000000000 +0800 647 | +++ ob/ssh.c 2024-07-01 23:59:55.316000458 +0800 648 | @@ -180,12 +180,13 @@ static void 649 | usage(void) 650 | { 651 | fprintf(stderr, 652 | -"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address]\n" 653 | +"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYyz] [-B bind_interface] [-b bind_address]\n" 654 | " [-c cipher_spec] [-D [bind_address:]port] [-E log_file]\n" 655 | " [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file]\n" 656 | " [-J destination] [-L address] [-l login_name] [-m mac_spec]\n" 657 | " [-O ctl_cmd] [-o option] [-P tag] [-p port] [-R address]\n" 658 | " [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]\n" 659 | +" [-Z obfuscate_keyword]\n" 660 | " destination [command [argument ...]]\n" 661 | " ssh [-Q query_option]\n" 662 | ); 663 | @@ -746,7 +747,7 @@ main(int ac, char **av) 664 | 665 | again: 666 | while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" 667 | - "AB:CD:E:F:GI:J:KL:MNO:P:Q:R:S:TVw:W:XYy")) != -1) { /* HUZdhjruz */ 668 | + "AB:CD:E:F:GI:J:KL:MNO:P:Q:R:S:TVw:W:XYyzZ:")) != -1) { /* HUZdhjruz */ 669 | switch (opt) { 670 | case '1': 671 | fatal("SSH protocol v.1 is no longer supported"); 672 | @@ -1098,6 +1099,13 @@ main(int ac, char **av) 673 | case 'F': 674 | config = optarg; 675 | break; 676 | + case 'z': 677 | + options.obfuscate_handshake = 1; 678 | + break; 679 | + case 'Z': 680 | + options.obfuscate_handshake = 1; 681 | + options.obfuscate_keyword = optarg; 682 | + break; 683 | default: 684 | usage(); 685 | } 686 | diff -Nupr openssh-9.8p1/sshconnect.c ob/sshconnect.c 687 | --- openssh-9.8p1/sshconnect.c 2024-07-01 12:36:28.000000000 +0800 688 | +++ ob/sshconnect.c 2024-07-01 23:59:55.316000458 +0800 689 | @@ -65,6 +65,7 @@ 690 | #include "monitor_fdpass.h" 691 | #include "ssh2.h" 692 | #include "version.h" 693 | +#include "obfuscate.h" 694 | #include "authfile.h" 695 | #include "ssherr.h" 696 | #include "authfd.h" 697 | @@ -269,6 +270,12 @@ ssh_proxy_connect(struct ssh *ssh, const 698 | if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) 699 | return -1; /* ssh_packet_set_connection logs error */ 700 | 701 | + if(options.obfuscate_handshake) { 702 | + if(options.obfuscate_keyword) 703 | + obfuscate_set_keyword(options.obfuscate_keyword); 704 | + sshpkt_enable_obfuscation(ssh); 705 | + } 706 | + 707 | return 0; 708 | } 709 | 710 | @@ -538,6 +545,11 @@ ssh_connect_direct(struct ssh *ssh, cons 711 | /* Set the connection. */ 712 | if (ssh_packet_set_connection(ssh, sock, sock) == NULL) 713 | return -1; /* ssh_packet_set_connection logs error */ 714 | + if(options.obfuscate_handshake) { 715 | + if(options.obfuscate_keyword) 716 | + obfuscate_set_keyword(options.obfuscate_keyword); 717 | + sshpkt_enable_obfuscation(ssh); 718 | + } 719 | 720 | return 0; 721 | } 722 | @@ -1603,6 +1615,9 @@ ssh_login(struct ssh *ssh, Sensitive *se 723 | host = xstrdup(orighost); 724 | lowercase(host); 725 | 726 | + if(options.obfuscate_handshake) 727 | + obfuscate_send_seed(ssh_packet_get_connection_out(ssh)); 728 | + 729 | /* Exchange protocol version identification strings with the server. */ 730 | if ((r = kex_exchange_identification(ssh, timeout_ms, NULL)) != 0) 731 | sshpkt_fatal(ssh, r, "banner exchange"); 732 | diff -Nupr openssh-9.8p1/sshd_config ob/sshd_config 733 | --- openssh-9.8p1/sshd_config 2024-07-01 12:36:28.000000000 +0800 734 | +++ ob/sshd_config 2024-07-01 23:59:55.316000458 +0800 735 | @@ -11,6 +11,8 @@ 736 | # default value. 737 | 738 | #Port 22 739 | +#ObfuscatedPort 222 740 | +#ObfuscateKeyword key 741 | #AddressFamily any 742 | #ListenAddress 0.0.0.0 743 | #ListenAddress :: 744 | diff -Nupr openssh-9.8p1/sshd-session.c ob/sshd-session.c 745 | --- openssh-9.8p1/sshd-session.c 2024-07-01 12:36:28.000000000 +0800 746 | +++ ob/sshd-session.c 2024-07-02 01:04:59.795478593 +0800 747 | @@ -102,6 +102,7 @@ 748 | #include "ssh-gss.h" 749 | #endif 750 | #include "monitor_wrap.h" 751 | +#include "obfuscate.h" 752 | #include "ssh-sandbox.h" 753 | #include "auth-options.h" 754 | #include "version.h" 755 | @@ -190,6 +191,9 @@ struct include_list includes = TAILQ_HEA 756 | /* message to be displayed after login */ 757 | struct sshbuf *loginmsg; 758 | 759 | +/* Enable handshake obfuscation */ 760 | +int use_obfuscation = 0; 761 | + 762 | /* Prototypes for various functions defined later in this file. */ 763 | void destroy_sensitive_data(void); 764 | void demote_sensitive_data(void); 765 | @@ -870,7 +874,7 @@ main(int ac, char **av) 766 | struct ssh *ssh = NULL; 767 | extern char *optarg; 768 | extern int optind; 769 | - int r, opt, on = 1, remote_port; 770 | + int r, opt, on = 1, remote_port, local_port; 771 | int sock_in = -1, sock_out = -1, rexeced_flag = 0, have_key = 0; 772 | const char *remote_ip, *rdomain; 773 | char *line, *laddr, *logfile = NULL; 774 | @@ -1234,6 +1238,14 @@ main(int ac, char **av) 775 | server_process_channel_timeouts(ssh); 776 | server_process_permitopen(ssh); 777 | 778 | + local_port = ssh_local_port(ssh); 779 | + for (i = 0; i < options.num_obfuscated_ports; i++) { 780 | + if (options.obfuscated_ports[i] == local_port) { 781 | + use_obfuscation = 1; 782 | + break; 783 | + } 784 | + } 785 | + 786 | /* Set SO_KEEPALIVE if requested. */ 787 | if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && 788 | setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) 789 | @@ -1278,6 +1290,13 @@ main(int ac, char **av) 790 | if (!debug_flag) 791 | alarm(options.login_grace_time); 792 | 793 | + if (use_obfuscation) { 794 | + if (options.obfuscate_keyword) 795 | + obfuscate_set_keyword(options.obfuscate_keyword); 796 | + sshpkt_enable_obfuscation(ssh); 797 | + obfuscate_receive_seed(ssh, sock_in); 798 | + } 799 | + 800 | if ((r = kex_exchange_identification(ssh, -1, 801 | options.version_addendum)) != 0) 802 | sshpkt_fatal(ssh, r, "banner exchange"); 803 | @@ -1300,8 +1319,11 @@ main(int ac, char **av) 804 | fatal("sshbuf_new loginmsg failed"); 805 | auth_debug_reset(); 806 | 807 | - if (privsep_preauth(ssh) == 1) 808 | - goto authenticated; 809 | + if (privsep_preauth(ssh) == 1) { 810 | + if(use_obfuscation) 811 | + sshpkt_disable_obfuscation(ssh); 812 | + goto authenticated; 813 | + } 814 | 815 | /* perform the key exchange */ 816 | /* authenticate user and start session */ 817 | -------------------------------------------------------------------------------- /portable/9.9.diff: -------------------------------------------------------------------------------- 1 | 9.8.diff -------------------------------------------------------------------------------- /ubuntu/8.9p1.patch: -------------------------------------------------------------------------------- 1 | Description: Add handshake obfuscation 2 | Author: zinglau 3 | Index: openssh-8.9p1/Makefile.in 4 | =================================================================== 5 | --- openssh-8.9p1.orig/Makefile.in 2023-12-25 12:09:32.000000000 +0800 6 | +++ openssh-8.9p1/Makefile.in 2023-12-25 12:09:32.000000000 +0800 7 | @@ -112,7 +112,7 @@ 8 | kexsntrup761x25519.o sntrup761.o kexgen.o \ 9 | kexgssc.o \ 10 | sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ 11 | - sshbuf-io.o 12 | + sshbuf-io.o obfuscate.o 13 | 14 | SKOBJS= ssh-sk-client.o 15 | 16 | Index: openssh-8.9p1/kex.c 17 | =================================================================== 18 | --- openssh-8.9p1.orig/kex.c 2023-12-25 12:09:32.000000000 +0800 19 | +++ openssh-8.9p1/kex.c 2023-12-25 12:09:32.000000000 +0800 20 | @@ -59,6 +59,7 @@ 21 | #include "monitor.h" 22 | #include "xmalloc.h" 23 | 24 | +#include "obfuscate.h" 25 | #include "ssherr.h" 26 | #include "sshbuf.h" 27 | #include "digest.h" 28 | @@ -521,9 +522,12 @@ 29 | return r; 30 | debug("SSH2_MSG_NEWKEYS sent"); 31 | ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); 32 | - if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) 33 | + if (ssh->kex->ext_info_c && (ssh->kex->flags & KEX_INITIAL) != 0) { 34 | + sshpkt_disable_obfuscation(ssh); 35 | if ((r = kex_send_ext_info(ssh)) != 0) 36 | return r; 37 | + sshpkt_enable_obfuscation(ssh); 38 | + } 39 | debug("expecting SSH2_MSG_NEWKEYS"); 40 | return 0; 41 | } 42 | @@ -601,6 +605,7 @@ 43 | kex->flags &= ~KEX_INIT_SENT; 44 | free(kex->name); 45 | kex->name = NULL; 46 | + sshpkt_disable_obfuscation(ssh); 47 | return 0; 48 | } 49 | 50 | @@ -1299,14 +1304,42 @@ 51 | goto out; 52 | } 53 | 54 | + /* Make copy and obfuscate the original */ 55 | + if (sshpkt_get_obfuscation(ssh) == 1) { 56 | + if ((cp = sshbuf_dup_string(our_version)) == NULL) { 57 | + error("%s: sshbuf_dup_string failed for obfuscation", __func__); 58 | + r = SSH_ERR_ALLOC_FAIL; 59 | + goto out; 60 | + } 61 | + obfuscate_output(sshbuf_mutable_ptr(our_version), sshbuf_len(our_version)); 62 | + } 63 | + 64 | if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), 65 | sshbuf_mutable_ptr(our_version), 66 | sshbuf_len(our_version)) != sshbuf_len(our_version)) { 67 | oerrno = errno; 68 | debug_f("write: %.100s", strerror(errno)); 69 | r = SSH_ERR_SYSTEM_ERROR; 70 | + if (sshpkt_get_obfuscation(ssh) == 1) { 71 | + free(cp); 72 | + } 73 | goto out; 74 | } 75 | + 76 | + /* Restore the original */ 77 | + if (sshpkt_get_obfuscation(ssh) == 1) { 78 | + if ((r = sshbuf_consume(our_version, sshbuf_len(our_version))) != 0) { 79 | + error("%s: sshbuf_consume failed for obfuscation", __func__); 80 | + free(cp); 81 | + goto out; 82 | + } 83 | + if ((r = sshbuf_put(our_version, cp, strlen(cp))) != 0) { 84 | + error("%s: sshbuf_put failed for obfuscation", __func__); 85 | + free(cp); 86 | + goto out; 87 | + } 88 | + } 89 | + 90 | if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ 91 | oerrno = errno; 92 | error_fr(r, "sshbuf_consume_end"); 93 | @@ -1363,6 +1396,8 @@ 94 | r = SSH_ERR_SYSTEM_ERROR; 95 | goto out; 96 | } 97 | + if(sshpkt_get_obfuscation(ssh) == 1) 98 | + obfuscate_input(&c, 1); 99 | if (c == '\r') { 100 | expect_nl = 1; 101 | continue; 102 | Index: openssh-8.9p1/obfuscate.c 103 | =================================================================== 104 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 105 | +++ openssh-8.9p1/obfuscate.c 2023-12-25 12:09:32.000000000 +0800 106 | @@ -0,0 +1,220 @@ 107 | +#include "includes.h" 108 | +#include 109 | +#include 110 | +#include "openbsd-compat/openssl-compat.h" 111 | +#include 112 | +#include 113 | +#include "atomicio.h" 114 | +#include "canohost.h" 115 | +#include "xmalloc.h" 116 | +#include "log.h" 117 | +#include "packet.h" 118 | +#include "obfuscate.h" 119 | + 120 | +static RC4_KEY rc4_input; 121 | +static RC4_KEY rc4_output; 122 | + 123 | +static const char *obfuscate_keyword = NULL; 124 | + 125 | +#define OBFUSCATE_KEY_LENGTH 16 126 | +#define OBFUSCATE_SEED_LENGTH 16 127 | +#define OBFUSCATE_HASH_ITERATIONS 6000 128 | +#define OBFUSCATE_MAX_PADDING 8192 129 | +#define OBFUSCATE_MAGIC_VALUE 0x0BF5CA7E 130 | + 131 | +struct seed_msg { 132 | + u_char seed_buffer[OBFUSCATE_SEED_LENGTH]; 133 | + u_int32_t magic; 134 | + u_int32_t padding_length; 135 | + u_char padding[]; 136 | +}; 137 | + 138 | +static void generate_key_pair(const u_char *, u_char *, u_char *); 139 | +static void generate_key(const u_char *, const u_char *, u_int, u_char *); 140 | +static void set_keys(const u_char *, const u_char *); 141 | +static void initialize(const u_char *, int); 142 | +static void read_forever(int); 143 | + 144 | + 145 | +/* 146 | + * Server calls this 147 | + */ 148 | +void 149 | +obfuscate_receive_seed(struct ssh *ssh, int sock_in) 150 | +{ 151 | + struct seed_msg seed; 152 | + 153 | + u_char padding_drain[OBFUSCATE_MAX_PADDING]; 154 | + u_int len; 155 | + u_int32_t padding_length; 156 | + 157 | + len = atomicio(read, sock_in, &seed, sizeof(struct seed_msg)); 158 | + 159 | + debug2("obfuscate_receive_seed: read %d byte seed message from client", len); 160 | + if(len != sizeof(struct seed_msg)) 161 | + fatal("obfuscate_receive_seed: read failed"); 162 | + 163 | + initialize(seed.seed_buffer, 1); 164 | + obfuscate_input((u_char *)&seed.magic, 8); 165 | + 166 | + if(OBFUSCATE_MAGIC_VALUE != ntohl(seed.magic)) { 167 | + logit("Magic value check failed (%u) on obfuscated handshake " 168 | + "from %.200s port %d", ntohl(seed.magic), 169 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 170 | + read_forever(sock_in); 171 | + } 172 | + padding_length = ntohl(seed.padding_length); 173 | + if(padding_length > OBFUSCATE_MAX_PADDING) { 174 | + logit("Illegal padding length %d for obfuscated handshake " 175 | + "from %.200s port %d", ntohl(seed.padding_length), 176 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 177 | + read_forever(sock_in); 178 | + } 179 | + len = atomicio(read, sock_in, padding_drain, padding_length); 180 | + if(len != padding_length) 181 | + fatal("obfuscate_receive_seed: read failed"); 182 | + debug2("obfuscate_receive_seed: read %d bytes of padding from client.", len); 183 | + obfuscate_input(padding_drain, padding_length); 184 | +} 185 | + 186 | +/* 187 | + * Client calls this 188 | + */ 189 | +void 190 | +obfuscate_send_seed(int sock_out) 191 | +{ 192 | + struct seed_msg *seed; 193 | + int i; 194 | + u_int32_t rnd = 0; 195 | + u_int message_length; 196 | + u_int padding_length; 197 | + 198 | + padding_length = arc4random() % OBFUSCATE_MAX_PADDING; 199 | + message_length = padding_length + sizeof(struct seed_msg); 200 | + seed = xmalloc(message_length); 201 | + 202 | + for(i = 0; i < OBFUSCATE_SEED_LENGTH; i++) { 203 | + if(i % 4 == 0) 204 | + rnd = arc4random(); 205 | + seed->seed_buffer[i] = rnd & 0xff; 206 | + rnd >>= 8; 207 | + } 208 | + seed->magic = htonl(OBFUSCATE_MAGIC_VALUE); 209 | + seed->padding_length = htonl(padding_length); 210 | + for(i = 0; i < (int)padding_length; i++) { 211 | + if(i % 4 == 0) 212 | + rnd = arc4random(); 213 | + seed->padding[i] = rnd & 0xff; 214 | + } 215 | + initialize(seed->seed_buffer, 0); 216 | + obfuscate_output(((u_char *)seed) + OBFUSCATE_SEED_LENGTH, 217 | + message_length - OBFUSCATE_SEED_LENGTH); 218 | + debug2("obfuscate_send_seed: Sending seed message with %d bytes of padding", padding_length); 219 | + atomicio(vwrite, sock_out, seed, message_length); 220 | + free(seed); 221 | + 222 | +} 223 | + 224 | +void 225 | +obfuscate_set_keyword(const char *keyword) 226 | +{ 227 | + debug2("obfuscate_set_keyword: Setting obfuscation keyword to '%s'", keyword); 228 | + obfuscate_keyword = keyword; 229 | +} 230 | + 231 | +void 232 | +obfuscate_input(u_char *buffer, u_int buffer_len) 233 | +{ 234 | + RC4(&rc4_input, buffer_len, buffer, buffer); 235 | +} 236 | + 237 | +void 238 | +obfuscate_output(u_char *buffer, u_int buffer_len) 239 | +{ 240 | + RC4(&rc4_output, buffer_len, buffer, buffer); 241 | +} 242 | + 243 | +static void 244 | +initialize(const u_char *seed, int server) 245 | +{ 246 | + u_char client_to_server_key[OBFUSCATE_KEY_LENGTH]; 247 | + u_char server_to_client_key[OBFUSCATE_KEY_LENGTH]; 248 | + 249 | + generate_key_pair(seed, client_to_server_key, server_to_client_key); 250 | + 251 | + if(server) 252 | + set_keys(client_to_server_key, server_to_client_key); 253 | + else 254 | + set_keys(server_to_client_key, client_to_server_key); 255 | +} 256 | + 257 | +static void 258 | +generate_key_pair(const u_char *seed, u_char *client_to_server_key, u_char *server_to_client_key) 259 | +{ 260 | + generate_key(seed, "client_to_server", strlen("client_to_server"), client_to_server_key); 261 | + generate_key(seed, "server_to_client", strlen("server_to_client"), server_to_client_key); 262 | +} 263 | + 264 | +static void 265 | +generate_key(const u_char *seed, const u_char *iv, u_int iv_len, u_char *key_data) 266 | +{ 267 | + EVP_MD_CTX *ctx; 268 | + u_char md_output[EVP_MAX_MD_SIZE]; 269 | + int md_len; 270 | + int i; 271 | + u_char *buffer; 272 | + u_char *p; 273 | + u_int buffer_length; 274 | + 275 | + if ((ctx = EVP_MD_CTX_new()) == NULL) 276 | + fatal("Cannot create new digest context"); 277 | + 278 | + buffer_length = OBFUSCATE_SEED_LENGTH + iv_len; 279 | + if(obfuscate_keyword) 280 | + buffer_length += strlen(obfuscate_keyword); 281 | + 282 | + p = buffer = xmalloc(buffer_length); 283 | + 284 | + memcpy(p, seed, OBFUSCATE_SEED_LENGTH); 285 | + p += OBFUSCATE_SEED_LENGTH; 286 | + 287 | + if(obfuscate_keyword) { 288 | + memcpy(p, obfuscate_keyword, strlen(obfuscate_keyword)); 289 | + p += strlen(obfuscate_keyword); 290 | + } 291 | + memcpy(p, iv, iv_len); 292 | + 293 | + EVP_DigestInit(ctx, EVP_sha1()); 294 | + EVP_DigestUpdate(ctx, buffer, OBFUSCATE_SEED_LENGTH + iv_len); 295 | + EVP_DigestFinal(ctx, md_output, &md_len); 296 | + 297 | + free(buffer); 298 | + 299 | + for(i = 0; i < OBFUSCATE_HASH_ITERATIONS; i++) { 300 | + EVP_DigestInit(ctx, EVP_sha1()); 301 | + EVP_DigestUpdate(ctx, md_output, md_len); 302 | + EVP_DigestFinal(ctx, md_output, &md_len); 303 | + } 304 | + 305 | + if(md_len < OBFUSCATE_KEY_LENGTH) 306 | + fatal("Cannot derive obfuscation keys from hash length of %d", md_len); 307 | + 308 | + memcpy(key_data, md_output, OBFUSCATE_KEY_LENGTH); 309 | +} 310 | + 311 | +static void 312 | +set_keys(const u_char *input_key, const u_char *output_key) 313 | +{ 314 | + RC4_set_key(&rc4_input, OBFUSCATE_KEY_LENGTH, input_key); 315 | + RC4_set_key(&rc4_output, OBFUSCATE_KEY_LENGTH, output_key); 316 | +} 317 | + 318 | +static void 319 | +read_forever(int sock_in) 320 | +{ 321 | + u_char discard_buffer[1024]; 322 | + 323 | + while(atomicio(read, sock_in, discard_buffer, sizeof(discard_buffer)) > 0) 324 | + ; 325 | + cleanup_exit(255); 326 | +} 327 | Index: openssh-8.9p1/obfuscate.h 328 | =================================================================== 329 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 330 | +++ openssh-8.9p1/obfuscate.h 2023-12-25 12:09:32.000000000 +0800 331 | @@ -0,0 +1,10 @@ 332 | +#ifndef _OBFUSCATE_H 333 | +#define _OBFUSCATE_H 334 | + 335 | +void obfuscate_receive_seed(struct ssh *, int); 336 | +void obfuscate_send_seed(int); 337 | +void obfuscate_set_keyword(const char *); 338 | +void obfuscate_input(u_char *, u_int); 339 | +void obfuscate_output(u_char *, u_int); 340 | + 341 | +#endif 342 | Index: openssh-8.9p1/packet.c 343 | =================================================================== 344 | --- openssh-8.9p1.orig/packet.c 2022-02-23 19:31:11.000000000 +0800 345 | +++ openssh-8.9p1/packet.c 2023-12-25 12:09:32.000000000 +0800 346 | @@ -94,6 +94,7 @@ 347 | #include "channels.h" 348 | #include "ssh.h" 349 | #include "packet.h" 350 | +#include "obfuscate.h" 351 | #include "ssherr.h" 352 | #include "sshbuf.h" 353 | 354 | @@ -177,6 +178,8 @@ 355 | /* Set to true if we are authenticated. */ 356 | int after_authentication; 357 | 358 | + int obfuscation; 359 | + 360 | int keep_alive_timeouts; 361 | 362 | /* The maximum time that we will wait to send or receive a packet */ 363 | @@ -1200,6 +1203,8 @@ 364 | if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) 365 | goto out; 366 | } 367 | + if(state->obfuscation) 368 | + obfuscate_output(cp, sshbuf_len(state->outgoing_packet)); 369 | #ifdef PACKET_DEBUG 370 | fprintf(stderr, "encrypted: "); 371 | sshbuf_dump(state->output, stderr); 372 | @@ -1534,6 +1539,8 @@ 373 | if ((r = sshbuf_reserve(state->incoming_packet, block_size, 374 | &cp)) != 0) 375 | goto out; 376 | + if(state->obfuscation) 377 | + obfuscate_input(sshbuf_mutable_ptr(state->input), block_size); 378 | if ((r = cipher_crypt(state->receive_context, 379 | state->p_send.seqnr, cp, sshbuf_ptr(state->input), 380 | block_size, 0, 0)) != 0) 381 | @@ -1599,6 +1606,8 @@ 382 | goto out; 383 | } 384 | } 385 | + if(state->obfuscation) 386 | + obfuscate_input(sshbuf_mutable_ptr(state->input), need); 387 | if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, 388 | &cp)) != 0) 389 | goto out; 390 | @@ -2717,3 +2726,25 @@ 391 | ssh->state->extra_pad = pad; 392 | return 0; 393 | } 394 | + 395 | +void 396 | +sshpkt_enable_obfuscation(struct ssh *ssh) 397 | +{ 398 | + debug("Obfuscation enabled"); 399 | + ssh->state->obfuscation = 1; 400 | +} 401 | + 402 | +void 403 | +sshpkt_disable_obfuscation(struct ssh *ssh) 404 | +{ 405 | + if(ssh->state->obfuscation) { 406 | + debug("Obfuscation disabled"); 407 | + ssh->state->obfuscation = 0; 408 | + } 409 | +} 410 | + 411 | +int 412 | +sshpkt_get_obfuscation(struct ssh *ssh) 413 | +{ 414 | + return ssh->state->obfuscation; 415 | +} 416 | Index: openssh-8.9p1/packet.h 417 | =================================================================== 418 | --- openssh-8.9p1.orig/packet.h 2022-02-23 19:31:11.000000000 +0800 419 | +++ openssh-8.9p1/packet.h 2023-12-25 12:09:32.000000000 +0800 420 | @@ -182,6 +182,9 @@ 421 | __attribute__((format(printf, 3, 4))) 422 | __attribute__((noreturn)); 423 | int sshpkt_msg_ignore(struct ssh *, u_int); 424 | +void sshpkt_enable_obfuscation(struct ssh *); 425 | +void sshpkt_disable_obfuscation(struct ssh *); 426 | +int sshpkt_get_obfuscation(struct ssh *); 427 | 428 | int sshpkt_put(struct ssh *ssh, const void *v, size_t len); 429 | int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b); 430 | Index: openssh-8.9p1/readconf.c 431 | =================================================================== 432 | --- openssh-8.9p1.orig/readconf.c 2023-12-25 12:09:32.000000000 +0800 433 | +++ openssh-8.9p1/readconf.c 2023-12-25 12:09:32.000000000 +0800 434 | @@ -144,7 +144,7 @@ 435 | oBadOption, 436 | oHost, oMatch, oInclude, 437 | oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, 438 | - oGatewayPorts, oExitOnForwardFailure, 439 | + oGatewayPorts, oExitOnForwardFailure, oObfuscateHandshake, oObfuscateKeyword, 440 | oPasswordAuthentication, 441 | oXAuthLocation, 442 | oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward, 443 | @@ -339,6 +339,8 @@ 444 | { "knownhostscommand", oKnownHostsCommand }, 445 | { "protocolkeepalives", oProtocolKeepAlives }, 446 | { "setuptimeout", oSetupTimeOut }, 447 | + { "obfuscatehandshake", oObfuscateHandshake }, 448 | + { "obfuscatekeyword", oObfuscateKeyword }, 449 | 450 | { NULL, oBadOption } 451 | }; 452 | @@ -2248,6 +2250,16 @@ 453 | *charptr = xstrdup(arg); 454 | break; 455 | 456 | + case oObfuscateHandshake: 457 | + intptr = &options->obfuscate_handshake; 458 | + goto parse_flag; 459 | + 460 | + case oObfuscateKeyword: 461 | + if (*activep) 462 | + options->obfuscate_handshake = 1; 463 | + charptr = &options->obfuscate_keyword; 464 | + goto parse_string; 465 | + 466 | case oDeprecated: 467 | debug("%s line %d: Deprecated option \"%s\"", 468 | filename, linenum, keyword); 469 | @@ -2481,6 +2493,8 @@ 470 | options->add_keys_to_agent_lifespan = -1; 471 | options->identity_agent = NULL; 472 | options->visual_host_key = -1; 473 | + options->obfuscate_handshake = 0; 474 | + options->obfuscate_keyword = NULL; 475 | options->ip_qos_interactive = -1; 476 | options->ip_qos_bulk = -1; 477 | options->request_tty = -1; 478 | Index: openssh-8.9p1/readconf.h 479 | =================================================================== 480 | --- openssh-8.9p1.orig/readconf.h 2023-12-25 12:09:32.000000000 +0800 481 | +++ openssh-8.9p1/readconf.h 2023-12-25 12:09:32.000000000 +0800 482 | @@ -150,6 +150,8 @@ 483 | int permit_local_command; 484 | char *remote_command; 485 | int visual_host_key; 486 | + int obfuscate_handshake; 487 | + char *obfuscate_keyword; 488 | 489 | int request_tty; 490 | int session_type; 491 | Index: openssh-8.9p1/scp.c 492 | =================================================================== 493 | --- openssh-8.9p1.orig/scp.c 2023-12-25 12:09:32.000000000 +0800 494 | +++ openssh-8.9p1/scp.c 2023-12-25 12:09:32.000000000 +0800 495 | @@ -490,7 +490,7 @@ 496 | 497 | fflag = Tflag = tflag = 0; 498 | while ((ch = getopt(argc, argv, 499 | - "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:")) != -1) { 500 | + "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:zZ:")) != -1) { 501 | switch (ch) { 502 | /* User-visible flags. */ 503 | case '1': 504 | @@ -503,6 +503,7 @@ 505 | case '4': 506 | case '6': 507 | case 'C': 508 | + case 'z': 509 | addargs(&args, "-%c", ch); 510 | addargs(&remote_remote_args, "-%c", ch); 511 | break; 512 | @@ -520,6 +521,7 @@ 513 | case 'i': 514 | case 'F': 515 | case 'J': 516 | + case 'Z': 517 | addargs(&remote_remote_args, "-%c", ch); 518 | addargs(&remote_remote_args, "%s", optarg); 519 | addargs(&args, "-%c", ch); 520 | Index: openssh-8.9p1/servconf.c 521 | =================================================================== 522 | --- openssh-8.9p1.orig/servconf.c 2023-12-25 12:09:32.000000000 +0800 523 | +++ openssh-8.9p1/servconf.c 2023-12-25 12:09:32.000000000 +0800 524 | @@ -96,6 +96,7 @@ 525 | 526 | /* Standard Options */ 527 | options->num_ports = 0; 528 | + options->num_obfuscated_ports = 0; 529 | options->ports_from_cmdline = 0; 530 | options->queued_listen_addrs = NULL; 531 | options->num_queued_listens = 0; 532 | @@ -185,6 +186,7 @@ 533 | options->permitted_listens = NULL; 534 | options->adm_forced_command = NULL; 535 | options->chroot_directory = NULL; 536 | + options->obfuscate_keyword = NULL; 537 | options->authorized_keys_command = NULL; 538 | options->authorized_keys_command_user = NULL; 539 | options->revoked_keys_file = NULL; 540 | @@ -301,7 +303,7 @@ 541 | #endif /* WITH_XMSS */ 542 | } 543 | /* No certificates by default */ 544 | - if (options->num_ports == 0) 545 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 546 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 547 | if (options->address_family == -1) 548 | options->address_family = AF_UNSPEC; 549 | @@ -502,7 +504,7 @@ 550 | /* Portable-specific options */ 551 | sUsePAM, 552 | /* Standard Options */ 553 | - sPort, sHostKeyFile, sLoginGraceTime, 554 | + sPort, sObfuscatedPort, sObfuscateKeyword, sHostKeyFile, sLoginGraceTime, 555 | sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose, 556 | sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, 557 | sKerberosGetAFSToken, sPasswordAuthentication, 558 | @@ -558,6 +560,8 @@ 559 | { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, 560 | /* Standard Options */ 561 | { "port", sPort, SSHCFG_GLOBAL }, 562 | + { "obfuscatedport", sObfuscatedPort, SSHCFG_GLOBAL }, 563 | + { "obfuscatekeyword", sObfuscateKeyword, SSHCFG_GLOBAL }, 564 | { "hostkey", sHostKeyFile, SSHCFG_GLOBAL }, 565 | { "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL }, /* alias */ 566 | { "hostkeyagent", sHostKeyAgent, SSHCFG_GLOBAL }, 567 | @@ -784,6 +788,10 @@ 568 | add_one_listen_addr(options, addr, rdomain, 569 | options->ports[i]); 570 | } 571 | + for (i = 0; i < options->num_obfuscated_ports; i++) { 572 | + add_one_listen_addr(options, addr, rdomain, 573 | + options->obfuscated_ports[i]); 574 | + } 575 | } 576 | } 577 | 578 | @@ -898,7 +906,7 @@ 579 | u_int i; 580 | struct queued_listenaddr *qla; 581 | 582 | - if (options->num_ports == 0) 583 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 584 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 585 | if (options->address_family == -1) 586 | options->address_family = AF_UNSPEC; 587 | @@ -1382,6 +1390,30 @@ 588 | filename, linenum); 589 | break; 590 | 591 | + case sObfuscatedPort: 592 | + if(options->ports_from_cmdline) 593 | + return 0; 594 | + if(options->listen_addrs != NULL) 595 | + fatal("%s line %d: ports must be specified before ListenAddress.", filename, linenum); 596 | + if(options->num_obfuscated_ports >= MAX_PORTS) 597 | + fatal("%s line %d: too many ports.", filename, linenum); 598 | + arg = argv_next(&ac, &av); 599 | + if(!arg || *arg == '\0') 600 | + fatal("%s line %d: missing port number.", filename, linenum); 601 | + options->obfuscated_ports[options->num_obfuscated_ports++] = a2port(arg); 602 | + if(options->obfuscated_ports[options->num_obfuscated_ports - 1] <= 0) 603 | + fatal("%s line %d: badly formatted port number.", filename, linenum); 604 | + break; 605 | + case sObfuscateKeyword: 606 | + charptr = &options->obfuscate_keyword; 607 | + arg = argv_next(&ac, &av); 608 | + if(!arg || *arg == '\0') 609 | + fatal("%s line %d: missing keyword argument.", 610 | + filename, linenum); 611 | + if(*activep && *charptr == NULL) 612 | + *charptr = xstrdup(arg); 613 | + break; 614 | + 615 | case sLoginGraceTime: 616 | intptr = &options->login_grace_time; 617 | parse_time: 618 | Index: openssh-8.9p1/servconf.h 619 | =================================================================== 620 | --- openssh-8.9p1.orig/servconf.h 2023-12-25 12:09:32.000000000 +0800 621 | +++ openssh-8.9p1/servconf.h 2023-12-25 12:09:32.000000000 +0800 622 | @@ -212,6 +212,11 @@ 623 | u_int num_permitted_listens; 624 | 625 | char *chroot_directory; 626 | + 627 | + int obfuscated_ports[MAX_PORTS]; 628 | + u_int num_obfuscated_ports; 629 | + char *obfuscate_keyword; 630 | + 631 | char *revoked_keys_file; 632 | char *trusted_user_ca_keys; 633 | char *authorized_keys_command; 634 | Index: openssh-8.9p1/sftp.c 635 | =================================================================== 636 | --- openssh-8.9p1.orig/sftp.c 2022-02-23 19:31:11.000000000 +0800 637 | +++ openssh-8.9p1/sftp.c 2023-12-25 12:09:32.000000000 +0800 638 | @@ -2380,13 +2380,14 @@ 639 | infile = stdin; 640 | 641 | while ((ch = getopt(argc, argv, 642 | - "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:")) != -1) { 643 | + "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:zZ:")) != -1) { 644 | switch (ch) { 645 | /* Passed through to ssh(1) */ 646 | case 'A': 647 | case '4': 648 | case '6': 649 | case 'C': 650 | + case 'z': 651 | addargs(&args, "-%c", ch); 652 | break; 653 | /* Passed through to ssh(1) with argument */ 654 | @@ -2395,6 +2396,7 @@ 655 | case 'c': 656 | case 'i': 657 | case 'o': 658 | + case 'Z': 659 | addargs(&args, "-%c", ch); 660 | addargs(&args, "%s", optarg); 661 | break; 662 | Index: openssh-8.9p1/ssh.c 663 | =================================================================== 664 | --- openssh-8.9p1.orig/ssh.c 2023-12-25 12:09:32.000000000 +0800 665 | +++ openssh-8.9p1/ssh.c 2023-12-25 12:09:32.000000000 +0800 666 | @@ -179,13 +179,14 @@ 667 | usage(void) 668 | { 669 | fprintf(stderr, 670 | -"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]\n" 671 | +"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYyz] [-B bind_interface]\n" 672 | " [-b bind_address] [-c cipher_spec] [-D [bind_address:]port]\n" 673 | " [-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]\n" 674 | " [-i identity_file] [-J [user@]host[:port]] [-L address]\n" 675 | " [-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]\n" 676 | " [-Q query_option] [-R address] [-S ctl_path] [-W host:port]\n" 677 | " [-w local_tun[:remote_tun]] destination [command [argument ...]]\n" 678 | +" [-Z obfuscate_keyword]\n" 679 | ); 680 | exit(255); 681 | } 682 | @@ -696,7 +697,7 @@ 683 | 684 | again: 685 | while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" 686 | - "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) { 687 | + "AB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYyzZ:")) != -1) { 688 | switch (opt) { 689 | case '1': 690 | fatal("SSH protocol v.1 is no longer supported"); 691 | @@ -1044,6 +1045,13 @@ 692 | case 'F': 693 | config = optarg; 694 | break; 695 | + case 'z': 696 | + options.obfuscate_handshake = 1; 697 | + break; 698 | + case 'Z': 699 | + options.obfuscate_handshake = 1; 700 | + options.obfuscate_keyword = optarg; 701 | + break; 702 | default: 703 | usage(); 704 | } 705 | Index: openssh-8.9p1/sshconnect.c 706 | =================================================================== 707 | --- openssh-8.9p1.orig/sshconnect.c 2023-12-25 12:09:32.000000000 +0800 708 | +++ openssh-8.9p1/sshconnect.c 2023-12-25 12:09:32.000000000 +0800 709 | @@ -65,6 +65,7 @@ 710 | #include "monitor_fdpass.h" 711 | #include "ssh2.h" 712 | #include "version.h" 713 | +#include "obfuscate.h" 714 | #include "authfile.h" 715 | #include "ssherr.h" 716 | #include "authfd.h" 717 | @@ -269,6 +270,12 @@ 718 | if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) 719 | return -1; /* ssh_packet_set_connection logs error */ 720 | 721 | + if(options.obfuscate_handshake) { 722 | + if(options.obfuscate_keyword) 723 | + obfuscate_set_keyword(options.obfuscate_keyword); 724 | + sshpkt_enable_obfuscation(ssh); 725 | + } 726 | + 727 | return 0; 728 | } 729 | 730 | @@ -530,6 +537,11 @@ 731 | /* Set the connection. */ 732 | if (ssh_packet_set_connection(ssh, sock, sock) == NULL) 733 | return -1; /* ssh_packet_set_connection logs error */ 734 | + if(options.obfuscate_handshake) { 735 | + if(options.obfuscate_keyword) 736 | + obfuscate_set_keyword(options.obfuscate_keyword); 737 | + sshpkt_enable_obfuscation(ssh); 738 | + } 739 | 740 | return 0; 741 | } 742 | @@ -1559,6 +1571,9 @@ 743 | host = xstrdup(orighost); 744 | lowercase(host); 745 | 746 | + if(options.obfuscate_handshake) 747 | + obfuscate_send_seed(ssh_packet_get_connection_out(ssh)); 748 | + 749 | /* Exchange protocol version identification strings with the server. */ 750 | if ((r = kex_exchange_identification(ssh, timeout_ms, 1, NULL)) != 0) 751 | sshpkt_fatal(ssh, r, "banner exchange"); 752 | Index: openssh-8.9p1/sshd.c 753 | =================================================================== 754 | --- openssh-8.9p1.orig/sshd.c 2023-12-25 12:09:32.000000000 +0800 755 | +++ openssh-8.9p1/sshd.c 2023-12-25 12:09:32.000000000 +0800 756 | @@ -125,6 +125,7 @@ 757 | #include "ssh-gss.h" 758 | #endif 759 | #include "monitor_wrap.h" 760 | +#include "obfuscate.h" 761 | #include "ssh-sandbox.h" 762 | #include "auth-options.h" 763 | #include "version.h" 764 | @@ -266,6 +267,9 @@ 765 | /* message to be displayed after login */ 766 | struct sshbuf *loginmsg; 767 | 768 | +/* Enable handshake obfuscation */ 769 | +int use_obfuscation = 0; 770 | + 771 | /* Unprivileged user */ 772 | struct passwd *privsep_pw = NULL; 773 | 774 | @@ -1539,7 +1543,7 @@ 775 | struct ssh *ssh = NULL; 776 | extern char *optarg; 777 | extern int optind; 778 | - int r, opt, on = 1, already_daemon, remote_port; 779 | + int r, opt, on = 1, already_daemon, remote_port, local_port; 780 | int sock_in = -1, sock_out = -1, newsock = -1; 781 | const char *remote_ip, *rdomain; 782 | char *fp, *line, *laddr, *logfile = NULL; 783 | @@ -2162,6 +2166,14 @@ 784 | channel_set_af(ssh, options.address_family); 785 | process_permitopen(ssh, &options); 786 | 787 | + local_port = ssh_local_port(ssh); 788 | + for(i = 0; i < options.num_obfuscated_ports; i++) { 789 | + if(options.obfuscated_ports[i] == local_port) { 790 | + use_obfuscation = 1; 791 | + break; 792 | + } 793 | + } 794 | + 795 | /* Set SO_KEEPALIVE if requested. */ 796 | if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && 797 | setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) 798 | @@ -2227,6 +2239,13 @@ 799 | if (!debug_flag) 800 | alarm(options.login_grace_time); 801 | 802 | + if(use_obfuscation) { 803 | + if(options.obfuscate_keyword) 804 | + obfuscate_set_keyword(options.obfuscate_keyword); 805 | + sshpkt_enable_obfuscation(ssh); 806 | + obfuscate_receive_seed(ssh, sock_in); 807 | + } 808 | + 809 | if ((r = kex_exchange_identification(ssh, -1, options.debian_banner, 810 | options.version_addendum)) != 0) 811 | sshpkt_fatal(ssh, r, "banner exchange"); 812 | @@ -2252,8 +2271,12 @@ 813 | auth_debug_reset(); 814 | 815 | if (use_privsep) { 816 | - if (privsep_preauth(ssh) == 1) 817 | + 818 | + if (privsep_preauth(ssh) == 1) { 819 | + if(use_obfuscation) 820 | + sshpkt_disable_obfuscation(ssh); 821 | goto authenticated; 822 | + } 823 | } else if (have_agent) { 824 | if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { 825 | error_r(r, "Unable to get agent socket"); 826 | Index: openssh-8.9p1/sshd_config 827 | =================================================================== 828 | --- openssh-8.9p1.orig/sshd_config 2023-12-25 12:09:32.000000000 +0800 829 | +++ openssh-8.9p1/sshd_config 2023-12-25 12:09:32.000000000 +0800 830 | @@ -13,6 +13,8 @@ 831 | Include /etc/ssh/sshd_config.d/*.conf 832 | 833 | #Port 22 834 | +#ObfuscatedPort 222 835 | +#ObfuscateKeyword key 836 | #AddressFamily any 837 | #ListenAddress 0.0.0.0 838 | #ListenAddress :: 839 | -------------------------------------------------------------------------------- /ubuntu/9.6p1.patch: -------------------------------------------------------------------------------- 1 | debian/patches/obfuscated-handshake.patch 2 | 3 | Description: Add handshake obfuscation 4 | Author: zinglau 5 | Index: openssh-9.6p1/Makefile.in 6 | =================================================================== 7 | --- openssh-9.6p1.orig/Makefile.in 2024-11-14 14:11:16.000000000 +0800 8 | +++ openssh-9.6p1/Makefile.in 2024-11-14 14:11:16.990633260 +0800 9 | @@ -113,7 +113,7 @@ 10 | kexsntrup761x25519.o sntrup761.o kexgen.o \ 11 | kexgssc.o \ 12 | sftp-realpath.o platform-pledge.o platform-tracing.o platform-misc.o \ 13 | - sshbuf-io.o 14 | + sshbuf-io.o obfuscate.o 15 | 16 | SKOBJS= ssh-sk-client.o 17 | 18 | Index: openssh-9.6p1/kex.c 19 | =================================================================== 20 | --- openssh-9.6p1.orig/kex.c 2024-11-14 14:11:16.000000000 +0800 21 | +++ openssh-9.6p1/kex.c 2024-11-14 14:11:16.990633260 +0800 22 | @@ -60,6 +60,7 @@ 23 | #include "myproposal.h" 24 | #include "xmalloc.h" 25 | 26 | +#include "obfuscate.h" 27 | #include "ssherr.h" 28 | #include "sshbuf.h" 29 | #include "digest.h" 30 | @@ -705,8 +706,10 @@ 31 | return r; 32 | debug("SSH2_MSG_NEWKEYS sent"); 33 | ssh_dispatch_set(ssh, SSH2_MSG_NEWKEYS, &kex_input_newkeys); 34 | + sshpkt_disable_obfuscation(ssh); 35 | if ((r = kex_maybe_send_ext_info(ssh)) != 0) 36 | return r; 37 | + sshpkt_enable_obfuscation(ssh); 38 | debug("expecting SSH2_MSG_NEWKEYS"); 39 | return 0; 40 | } 41 | @@ -847,6 +850,7 @@ 42 | kex->flags &= ~KEX_INIT_SENT; 43 | free(kex->name); 44 | kex->name = NULL; 45 | + sshpkt_disable_obfuscation(ssh); 46 | return 0; 47 | } 48 | 49 | @@ -1549,14 +1553,42 @@ 50 | goto out; 51 | } 52 | 53 | + /* Make copy and obfuscate the original */ 54 | + if (sshpkt_get_obfuscation(ssh) == 1) { 55 | + if ((cp = sshbuf_dup_string(our_version)) == NULL) { 56 | + error("%s: sshbuf_dup_string failed for obfuscation", __func__); 57 | + r = SSH_ERR_ALLOC_FAIL; 58 | + goto out; 59 | + } 60 | + obfuscate_output(sshbuf_mutable_ptr(our_version), sshbuf_len(our_version)); 61 | + } 62 | + 63 | if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), 64 | sshbuf_mutable_ptr(our_version), 65 | sshbuf_len(our_version)) != sshbuf_len(our_version)) { 66 | oerrno = errno; 67 | debug_f("write: %.100s", strerror(errno)); 68 | r = SSH_ERR_SYSTEM_ERROR; 69 | + if (sshpkt_get_obfuscation(ssh) == 1) { 70 | + free(cp); 71 | + } 72 | goto out; 73 | } 74 | + 75 | + /* Restore the original */ 76 | + if (sshpkt_get_obfuscation(ssh) == 1) { 77 | + if ((r = sshbuf_consume(our_version, sshbuf_len(our_version))) != 0) { 78 | + error("%s: sshbuf_consume failed for obfuscation", __func__); 79 | + free(cp); 80 | + goto out; 81 | + } 82 | + if ((r = sshbuf_put(our_version, cp, strlen(cp))) != 0) { 83 | + error("%s: sshbuf_put failed for obfuscation", __func__); 84 | + free(cp); 85 | + goto out; 86 | + } 87 | + } 88 | + 89 | if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ 90 | oerrno = errno; 91 | error_fr(r, "sshbuf_consume_end"); 92 | @@ -1613,6 +1645,8 @@ 93 | r = SSH_ERR_SYSTEM_ERROR; 94 | goto out; 95 | } 96 | + if(sshpkt_get_obfuscation(ssh) == 1) 97 | + obfuscate_input(&c, 1); 98 | if (c == '\r') { 99 | expect_nl = 1; 100 | continue; 101 | Index: openssh-9.6p1/obfuscate.c 102 | =================================================================== 103 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 104 | +++ openssh-9.6p1/obfuscate.c 2024-11-14 14:11:16.990633260 +0800 105 | @@ -0,0 +1,220 @@ 106 | +#include "includes.h" 107 | +#include 108 | +#include 109 | +#include "openbsd-compat/openssl-compat.h" 110 | +#include 111 | +#include 112 | +#include "atomicio.h" 113 | +#include "canohost.h" 114 | +#include "xmalloc.h" 115 | +#include "log.h" 116 | +#include "packet.h" 117 | +#include "obfuscate.h" 118 | + 119 | +static RC4_KEY rc4_input; 120 | +static RC4_KEY rc4_output; 121 | + 122 | +static const char *obfuscate_keyword = NULL; 123 | + 124 | +#define OBFUSCATE_KEY_LENGTH 16 125 | +#define OBFUSCATE_SEED_LENGTH 16 126 | +#define OBFUSCATE_HASH_ITERATIONS 6000 127 | +#define OBFUSCATE_MAX_PADDING 8192 128 | +#define OBFUSCATE_MAGIC_VALUE 0x0BF5CA7E 129 | + 130 | +struct seed_msg { 131 | + u_char seed_buffer[OBFUSCATE_SEED_LENGTH]; 132 | + u_int32_t magic; 133 | + u_int32_t padding_length; 134 | + u_char padding[]; 135 | +}; 136 | + 137 | +static void generate_key_pair(const u_char *, u_char *, u_char *); 138 | +static void generate_key(const u_char *, const u_char *, u_int, u_char *); 139 | +static void set_keys(const u_char *, const u_char *); 140 | +static void initialize(const u_char *, int); 141 | +static void read_forever(int); 142 | + 143 | + 144 | +/* 145 | + * Server calls this 146 | + */ 147 | +void 148 | +obfuscate_receive_seed(struct ssh *ssh, int sock_in) 149 | +{ 150 | + struct seed_msg seed; 151 | + 152 | + u_char padding_drain[OBFUSCATE_MAX_PADDING]; 153 | + u_int len; 154 | + u_int32_t padding_length; 155 | + 156 | + len = atomicio(read, sock_in, &seed, sizeof(struct seed_msg)); 157 | + 158 | + debug2("obfuscate_receive_seed: read %d byte seed message from client", len); 159 | + if(len != sizeof(struct seed_msg)) 160 | + fatal("obfuscate_receive_seed: read failed"); 161 | + 162 | + initialize(seed.seed_buffer, 1); 163 | + obfuscate_input((u_char *)&seed.magic, 8); 164 | + 165 | + if(OBFUSCATE_MAGIC_VALUE != ntohl(seed.magic)) { 166 | + logit("Magic value check failed (%u) on obfuscated handshake " 167 | + "from %.200s port %d", ntohl(seed.magic), 168 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 169 | + read_forever(sock_in); 170 | + } 171 | + padding_length = ntohl(seed.padding_length); 172 | + if(padding_length > OBFUSCATE_MAX_PADDING) { 173 | + logit("Illegal padding length %d for obfuscated handshake " 174 | + "from %.200s port %d", ntohl(seed.padding_length), 175 | + ssh_remote_ipaddr(ssh), ssh_remote_port(ssh)); 176 | + read_forever(sock_in); 177 | + } 178 | + len = atomicio(read, sock_in, padding_drain, padding_length); 179 | + if(len != padding_length) 180 | + fatal("obfuscate_receive_seed: read failed"); 181 | + debug2("obfuscate_receive_seed: read %d bytes of padding from client.", len); 182 | + obfuscate_input(padding_drain, padding_length); 183 | +} 184 | + 185 | +/* 186 | + * Client calls this 187 | + */ 188 | +void 189 | +obfuscate_send_seed(int sock_out) 190 | +{ 191 | + struct seed_msg *seed; 192 | + int i; 193 | + u_int32_t rnd = 0; 194 | + u_int message_length; 195 | + u_int padding_length; 196 | + 197 | + padding_length = arc4random() % OBFUSCATE_MAX_PADDING; 198 | + message_length = padding_length + sizeof(struct seed_msg); 199 | + seed = xmalloc(message_length); 200 | + 201 | + for(i = 0; i < OBFUSCATE_SEED_LENGTH; i++) { 202 | + if(i % 4 == 0) 203 | + rnd = arc4random(); 204 | + seed->seed_buffer[i] = rnd & 0xff; 205 | + rnd >>= 8; 206 | + } 207 | + seed->magic = htonl(OBFUSCATE_MAGIC_VALUE); 208 | + seed->padding_length = htonl(padding_length); 209 | + for(i = 0; i < (int)padding_length; i++) { 210 | + if(i % 4 == 0) 211 | + rnd = arc4random(); 212 | + seed->padding[i] = rnd & 0xff; 213 | + } 214 | + initialize(seed->seed_buffer, 0); 215 | + obfuscate_output(((u_char *)seed) + OBFUSCATE_SEED_LENGTH, 216 | + message_length - OBFUSCATE_SEED_LENGTH); 217 | + debug2("obfuscate_send_seed: Sending seed message with %d bytes of padding", padding_length); 218 | + atomicio(vwrite, sock_out, seed, message_length); 219 | + free(seed); 220 | + 221 | +} 222 | + 223 | +void 224 | +obfuscate_set_keyword(const char *keyword) 225 | +{ 226 | + debug2("obfuscate_set_keyword: Setting obfuscation keyword to '%s'", keyword); 227 | + obfuscate_keyword = keyword; 228 | +} 229 | + 230 | +void 231 | +obfuscate_input(u_char *buffer, u_int buffer_len) 232 | +{ 233 | + RC4(&rc4_input, buffer_len, buffer, buffer); 234 | +} 235 | + 236 | +void 237 | +obfuscate_output(u_char *buffer, u_int buffer_len) 238 | +{ 239 | + RC4(&rc4_output, buffer_len, buffer, buffer); 240 | +} 241 | + 242 | +static void 243 | +initialize(const u_char *seed, int server) 244 | +{ 245 | + u_char client_to_server_key[OBFUSCATE_KEY_LENGTH]; 246 | + u_char server_to_client_key[OBFUSCATE_KEY_LENGTH]; 247 | + 248 | + generate_key_pair(seed, client_to_server_key, server_to_client_key); 249 | + 250 | + if(server) 251 | + set_keys(client_to_server_key, server_to_client_key); 252 | + else 253 | + set_keys(server_to_client_key, client_to_server_key); 254 | +} 255 | + 256 | +static void 257 | +generate_key_pair(const u_char *seed, u_char *client_to_server_key, u_char *server_to_client_key) 258 | +{ 259 | + generate_key(seed, "client_to_server", strlen("client_to_server"), client_to_server_key); 260 | + generate_key(seed, "server_to_client", strlen("server_to_client"), server_to_client_key); 261 | +} 262 | + 263 | +static void 264 | +generate_key(const u_char *seed, const u_char *iv, u_int iv_len, u_char *key_data) 265 | +{ 266 | + EVP_MD_CTX *ctx; 267 | + u_char md_output[EVP_MAX_MD_SIZE]; 268 | + int md_len; 269 | + int i; 270 | + u_char *buffer; 271 | + u_char *p; 272 | + u_int buffer_length; 273 | + 274 | + if ((ctx = EVP_MD_CTX_new()) == NULL) 275 | + fatal("Cannot create new digest context"); 276 | + 277 | + buffer_length = OBFUSCATE_SEED_LENGTH + iv_len; 278 | + if(obfuscate_keyword) 279 | + buffer_length += strlen(obfuscate_keyword); 280 | + 281 | + p = buffer = xmalloc(buffer_length); 282 | + 283 | + memcpy(p, seed, OBFUSCATE_SEED_LENGTH); 284 | + p += OBFUSCATE_SEED_LENGTH; 285 | + 286 | + if(obfuscate_keyword) { 287 | + memcpy(p, obfuscate_keyword, strlen(obfuscate_keyword)); 288 | + p += strlen(obfuscate_keyword); 289 | + } 290 | + memcpy(p, iv, iv_len); 291 | + 292 | + EVP_DigestInit(ctx, EVP_sha1()); 293 | + EVP_DigestUpdate(ctx, buffer, OBFUSCATE_SEED_LENGTH + iv_len); 294 | + EVP_DigestFinal(ctx, md_output, &md_len); 295 | + 296 | + free(buffer); 297 | + 298 | + for(i = 0; i < OBFUSCATE_HASH_ITERATIONS; i++) { 299 | + EVP_DigestInit(ctx, EVP_sha1()); 300 | + EVP_DigestUpdate(ctx, md_output, md_len); 301 | + EVP_DigestFinal(ctx, md_output, &md_len); 302 | + } 303 | + 304 | + if(md_len < OBFUSCATE_KEY_LENGTH) 305 | + fatal("Cannot derive obfuscation keys from hash length of %d", md_len); 306 | + 307 | + memcpy(key_data, md_output, OBFUSCATE_KEY_LENGTH); 308 | +} 309 | + 310 | +static void 311 | +set_keys(const u_char *input_key, const u_char *output_key) 312 | +{ 313 | + RC4_set_key(&rc4_input, OBFUSCATE_KEY_LENGTH, input_key); 314 | + RC4_set_key(&rc4_output, OBFUSCATE_KEY_LENGTH, output_key); 315 | +} 316 | + 317 | +static void 318 | +read_forever(int sock_in) 319 | +{ 320 | + u_char discard_buffer[1024]; 321 | + 322 | + while(atomicio(read, sock_in, discard_buffer, sizeof(discard_buffer)) > 0) 323 | + ; 324 | + cleanup_exit(255); 325 | +} 326 | Index: openssh-9.6p1/obfuscate.h 327 | =================================================================== 328 | --- /dev/null 1970-01-01 00:00:00.000000000 +0000 329 | +++ openssh-9.6p1/obfuscate.h 2024-11-14 14:11:16.990633260 +0800 330 | @@ -0,0 +1,10 @@ 331 | +#ifndef _OBFUSCATE_H 332 | +#define _OBFUSCATE_H 333 | + 334 | +void obfuscate_receive_seed(struct ssh *, int); 335 | +void obfuscate_send_seed(int); 336 | +void obfuscate_set_keyword(const char *); 337 | +void obfuscate_input(u_char *, u_int); 338 | +void obfuscate_output(u_char *, u_int); 339 | + 340 | +#endif 341 | Index: openssh-9.6p1/packet.c 342 | =================================================================== 343 | --- openssh-9.6p1.orig/packet.c 2023-12-18 22:59:50.000000000 +0800 344 | +++ openssh-9.6p1/packet.c 2024-11-14 14:11:16.990633260 +0800 345 | @@ -94,6 +94,7 @@ 346 | #include "channels.h" 347 | #include "ssh.h" 348 | #include "packet.h" 349 | +#include "obfuscate.h" 350 | #include "ssherr.h" 351 | #include "sshbuf.h" 352 | 353 | @@ -177,6 +178,8 @@ 354 | /* Set to true if we are authenticated. */ 355 | int after_authentication; 356 | 357 | + int obfuscation; 358 | + 359 | int keep_alive_timeouts; 360 | 361 | /* The maximum time that we will wait to send or receive a packet */ 362 | @@ -1202,6 +1205,8 @@ 363 | if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) 364 | goto out; 365 | } 366 | + if(state->obfuscation) 367 | + obfuscate_output(cp, sshbuf_len(state->outgoing_packet)); 368 | #ifdef PACKET_DEBUG 369 | fprintf(stderr, "encrypted: "); 370 | sshbuf_dump(state->output, stderr); 371 | @@ -1522,6 +1527,8 @@ 372 | if ((r = sshbuf_reserve(state->incoming_packet, block_size, 373 | &cp)) != 0) 374 | goto out; 375 | + if(state->obfuscation) 376 | + obfuscate_input(sshbuf_mutable_ptr(state->input), block_size); 377 | if ((r = cipher_crypt(state->receive_context, 378 | state->p_send.seqnr, cp, sshbuf_ptr(state->input), 379 | block_size, 0, 0)) != 0) 380 | @@ -1587,6 +1594,8 @@ 381 | goto out; 382 | } 383 | } 384 | + if(state->obfuscation) 385 | + obfuscate_input(sshbuf_mutable_ptr(state->input), need); 386 | if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, 387 | &cp)) != 0) 388 | goto out; 389 | @@ -2763,3 +2772,25 @@ 390 | ssh->state->extra_pad = pad; 391 | return 0; 392 | } 393 | + 394 | +void 395 | +sshpkt_enable_obfuscation(struct ssh *ssh) 396 | +{ 397 | + debug("Obfuscation enabled"); 398 | + ssh->state->obfuscation = 1; 399 | +} 400 | + 401 | +void 402 | +sshpkt_disable_obfuscation(struct ssh *ssh) 403 | +{ 404 | + if(ssh->state->obfuscation) { 405 | + debug("Obfuscation disabled"); 406 | + ssh->state->obfuscation = 0; 407 | + } 408 | +} 409 | + 410 | +int 411 | +sshpkt_get_obfuscation(struct ssh *ssh) 412 | +{ 413 | + return ssh->state->obfuscation; 414 | +} 415 | Index: openssh-9.6p1/packet.h 416 | =================================================================== 417 | --- openssh-9.6p1.orig/packet.h 2023-12-18 22:59:50.000000000 +0800 418 | +++ openssh-9.6p1/packet.h 2024-11-14 14:11:16.990633260 +0800 419 | @@ -182,6 +182,9 @@ 420 | __attribute__((format(printf, 3, 4))) 421 | __attribute__((noreturn)); 422 | int sshpkt_msg_ignore(struct ssh *, u_int); 423 | +void sshpkt_enable_obfuscation(struct ssh *); 424 | +void sshpkt_disable_obfuscation(struct ssh *); 425 | +int sshpkt_get_obfuscation(struct ssh *); 426 | 427 | int sshpkt_put(struct ssh *ssh, const void *v, size_t len); 428 | int sshpkt_putb(struct ssh *ssh, const struct sshbuf *b); 429 | Index: openssh-9.6p1/readconf.c 430 | =================================================================== 431 | --- openssh-9.6p1.orig/readconf.c 2024-11-14 14:11:16.000000000 +0800 432 | +++ openssh-9.6p1/readconf.c 2024-11-14 14:11:16.990633260 +0800 433 | @@ -147,7 +147,7 @@ 434 | oBadOption, 435 | oHost, oMatch, oInclude, oTag, 436 | oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, 437 | - oGatewayPorts, oExitOnForwardFailure, 438 | + oGatewayPorts, oExitOnForwardFailure, oObfuscateHandshake, oObfuscateKeyword, 439 | oPasswordAuthentication, 440 | oXAuthLocation, 441 | oIdentityFile, oHostname, oPort, oRemoteForward, oLocalForward, 442 | @@ -348,6 +348,8 @@ 443 | { "channeltimeout", oChannelTimeout }, 444 | { "protocolkeepalives", oProtocolKeepAlives }, 445 | { "setuptimeout", oSetupTimeOut }, 446 | + { "obfuscatehandshake", oObfuscateHandshake }, 447 | + { "obfuscatekeyword", oObfuscateKeyword }, 448 | 449 | { NULL, oBadOption } 450 | }; 451 | @@ -2410,6 +2412,16 @@ 452 | } 453 | break; 454 | 455 | + case oObfuscateHandshake: 456 | + intptr = &options->obfuscate_handshake; 457 | + goto parse_flag; 458 | + 459 | + case oObfuscateKeyword: 460 | + if (*activep) 461 | + options->obfuscate_handshake = 1; 462 | + charptr = &options->obfuscate_keyword; 463 | + goto parse_string; 464 | + 465 | case oDeprecated: 466 | debug("%s line %d: Deprecated option \"%s\"", 467 | filename, linenum, keyword); 468 | @@ -2644,6 +2656,8 @@ 469 | options->add_keys_to_agent_lifespan = -1; 470 | options->identity_agent = NULL; 471 | options->visual_host_key = -1; 472 | + options->obfuscate_handshake = 0; 473 | + options->obfuscate_keyword = NULL; 474 | options->ip_qos_interactive = -1; 475 | options->ip_qos_bulk = -1; 476 | options->request_tty = -1; 477 | Index: openssh-9.6p1/readconf.h 478 | =================================================================== 479 | --- openssh-9.6p1.orig/readconf.h 2024-11-14 14:11:16.000000000 +0800 480 | +++ openssh-9.6p1/readconf.h 2024-11-14 14:11:16.994633258 +0800 481 | @@ -152,6 +152,8 @@ 482 | int permit_local_command; 483 | char *remote_command; 484 | int visual_host_key; 485 | + int obfuscate_handshake; 486 | + char *obfuscate_keyword; 487 | 488 | int request_tty; 489 | int session_type; 490 | Index: openssh-9.6p1/scp.c 491 | =================================================================== 492 | --- openssh-9.6p1.orig/scp.c 2024-11-14 14:11:16.000000000 +0800 493 | +++ openssh-9.6p1/scp.c 2024-11-14 14:11:16.994633258 +0800 494 | @@ -514,7 +514,7 @@ 495 | 496 | fflag = Tflag = tflag = 0; 497 | while ((ch = getopt(argc, argv, 498 | - "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:")) != -1) { 499 | + "12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:X:zZ:")) != -1) { 500 | switch (ch) { 501 | /* User-visible flags. */ 502 | case '1': 503 | @@ -527,6 +527,7 @@ 504 | case '4': 505 | case '6': 506 | case 'C': 507 | + case 'z': 508 | addargs(&args, "-%c", ch); 509 | addargs(&remote_remote_args, "-%c", ch); 510 | break; 511 | @@ -544,6 +545,7 @@ 512 | case 'i': 513 | case 'F': 514 | case 'J': 515 | + case 'Z': 516 | addargs(&remote_remote_args, "-%c", ch); 517 | addargs(&remote_remote_args, "%s", optarg); 518 | addargs(&args, "-%c", ch); 519 | Index: openssh-9.6p1/servconf.c 520 | =================================================================== 521 | --- openssh-9.6p1.orig/servconf.c 2024-11-14 14:11:16.000000000 +0800 522 | +++ openssh-9.6p1/servconf.c 2024-11-14 14:11:16.994633258 +0800 523 | @@ -94,6 +94,7 @@ 524 | 525 | /* Standard Options */ 526 | options->num_ports = 0; 527 | + options->num_obfuscated_ports = 0; 528 | options->ports_from_cmdline = 0; 529 | options->queued_listen_addrs = NULL; 530 | options->num_queued_listens = 0; 531 | @@ -183,6 +184,7 @@ 532 | options->permitted_listens = NULL; 533 | options->adm_forced_command = NULL; 534 | options->chroot_directory = NULL; 535 | + options->obfuscate_keyword = NULL; 536 | options->authorized_keys_command = NULL; 537 | options->authorized_keys_command_user = NULL; 538 | options->revoked_keys_file = NULL; 539 | @@ -303,7 +305,7 @@ 540 | #endif /* WITH_XMSS */ 541 | } 542 | /* No certificates by default */ 543 | - if (options->num_ports == 0) 544 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 545 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 546 | if (options->address_family == -1) 547 | options->address_family = AF_UNSPEC; 548 | @@ -515,7 +517,7 @@ 549 | /* Portable-specific options */ 550 | sUsePAM, 551 | /* Standard Options */ 552 | - sPort, sHostKeyFile, sLoginGraceTime, 553 | + sPort, sObfuscatedPort, sObfuscateKeyword, sHostKeyFile, sLoginGraceTime, 554 | sPermitRootLogin, sLogFacility, sLogLevel, sLogVerbose, 555 | sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup, 556 | sKerberosGetAFSToken, sPasswordAuthentication, 557 | @@ -572,6 +574,8 @@ 558 | { "pamauthenticationviakbdint", sDeprecated, SSHCFG_GLOBAL }, 559 | /* Standard Options */ 560 | { "port", sPort, SSHCFG_GLOBAL }, 561 | + { "obfuscatedport", sObfuscatedPort, SSHCFG_GLOBAL }, 562 | + { "obfuscatekeyword", sObfuscateKeyword, SSHCFG_GLOBAL }, 563 | { "hostkey", sHostKeyFile, SSHCFG_GLOBAL }, 564 | { "hostdsakey", sHostKeyFile, SSHCFG_GLOBAL }, /* alias */ 565 | { "hostkeyagent", sHostKeyAgent, SSHCFG_GLOBAL }, 566 | @@ -801,6 +805,10 @@ 567 | add_one_listen_addr(options, addr, rdomain, 568 | options->ports[i]); 569 | } 570 | + for (i = 0; i < options->num_obfuscated_ports; i++) { 571 | + add_one_listen_addr(options, addr, rdomain, 572 | + options->obfuscated_ports[i]); 573 | + } 574 | } 575 | } 576 | 577 | @@ -915,7 +923,7 @@ 578 | u_int i; 579 | struct queued_listenaddr *qla; 580 | 581 | - if (options->num_ports == 0) 582 | + if (options->num_ports == 0 && options->num_obfuscated_ports == 0) 583 | options->ports[options->num_ports++] = SSH_DEFAULT_PORT; 584 | if (options->address_family == -1) 585 | options->address_family = AF_UNSPEC; 586 | @@ -1420,6 +1428,30 @@ 587 | filename, linenum); 588 | break; 589 | 590 | + case sObfuscatedPort: 591 | + if(options->ports_from_cmdline) 592 | + return 0; 593 | + if(options->listen_addrs != NULL) 594 | + fatal("%s line %d: ports must be specified before ListenAddress.", filename, linenum); 595 | + if(options->num_obfuscated_ports >= MAX_PORTS) 596 | + fatal("%s line %d: too many ports.", filename, linenum); 597 | + arg = argv_next(&ac, &av); 598 | + if(!arg || *arg == '\0') 599 | + fatal("%s line %d: missing port number.", filename, linenum); 600 | + options->obfuscated_ports[options->num_obfuscated_ports++] = a2port(arg); 601 | + if(options->obfuscated_ports[options->num_obfuscated_ports - 1] <= 0) 602 | + fatal("%s line %d: badly formatted port number.", filename, linenum); 603 | + break; 604 | + case sObfuscateKeyword: 605 | + charptr = &options->obfuscate_keyword; 606 | + arg = argv_next(&ac, &av); 607 | + if(!arg || *arg == '\0') 608 | + fatal("%s line %d: missing keyword argument.", 609 | + filename, linenum); 610 | + if(*activep && *charptr == NULL) 611 | + *charptr = xstrdup(arg); 612 | + break; 613 | + 614 | case sLoginGraceTime: 615 | intptr = &options->login_grace_time; 616 | parse_time: 617 | Index: openssh-9.6p1/servconf.h 618 | =================================================================== 619 | --- openssh-9.6p1.orig/servconf.h 2024-11-14 14:11:16.000000000 +0800 620 | +++ openssh-9.6p1/servconf.h 2024-11-14 14:11:16.994633258 +0800 621 | @@ -210,6 +210,11 @@ 622 | u_int num_permitted_listens; 623 | 624 | char *chroot_directory; 625 | + 626 | + int obfuscated_ports[MAX_PORTS]; 627 | + u_int num_obfuscated_ports; 628 | + char *obfuscate_keyword; 629 | + 630 | char *revoked_keys_file; 631 | char *trusted_user_ca_keys; 632 | char *authorized_keys_command; 633 | Index: openssh-9.6p1/sftp.c 634 | =================================================================== 635 | --- openssh-9.6p1.orig/sftp.c 2023-12-18 22:59:50.000000000 +0800 636 | +++ openssh-9.6p1/sftp.c 2024-11-14 14:11:16.994633258 +0800 637 | @@ -2464,13 +2464,14 @@ 638 | infile = stdin; 639 | 640 | while ((ch = getopt(argc, argv, 641 | - "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:")) != -1) { 642 | + "1246AafhNpqrvCc:D:i:l:o:s:S:b:B:F:J:P:R:X:zZ:")) != -1) { 643 | switch (ch) { 644 | /* Passed through to ssh(1) */ 645 | case 'A': 646 | case '4': 647 | case '6': 648 | case 'C': 649 | + case 'z': 650 | addargs(&args, "-%c", ch); 651 | break; 652 | /* Passed through to ssh(1) with argument */ 653 | @@ -2479,6 +2480,7 @@ 654 | case 'c': 655 | case 'i': 656 | case 'o': 657 | + case 'Z': 658 | addargs(&args, "-%c", ch); 659 | addargs(&args, "%s", optarg); 660 | break; 661 | Index: openssh-9.6p1/ssh.c 662 | =================================================================== 663 | --- openssh-9.6p1.orig/ssh.c 2024-11-14 14:11:16.000000000 +0800 664 | +++ openssh-9.6p1/ssh.c 2024-11-14 14:11:16.998633255 +0800 665 | @@ -180,12 +180,13 @@ 666 | usage(void) 667 | { 668 | fprintf(stderr, 669 | -"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address]\n" 670 | +"usage: ssh [-46AaCfGgKkMNnqsTtVvXxYyz] [-B bind_interface] [-b bind_address]\n" 671 | " [-c cipher_spec] [-D [bind_address:]port] [-E log_file]\n" 672 | " [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file]\n" 673 | " [-J destination] [-L address] [-l login_name] [-m mac_spec]\n" 674 | " [-O ctl_cmd] [-o option] [-P tag] [-p port] [-R address]\n" 675 | " [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]\n" 676 | +" [-Z obfuscate_keyword]\n" 677 | " destination [command [argument ...]]\n" 678 | " ssh [-Q query_option]\n" 679 | ); 680 | @@ -746,7 +747,7 @@ 681 | 682 | again: 683 | while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx" 684 | - "AB:CD:E:F:GI:J:KL:MNO:P:Q:R:S:TVw:W:XYy")) != -1) { /* HUZdhjruz */ 685 | + "AB:CD:E:F:GI:J:KL:MNO:P:Q:R:S:TVw:W:XYyzZ:")) != -1) { /* HUZdhjruz */ 686 | switch (opt) { 687 | case '1': 688 | fatal("SSH protocol v.1 is no longer supported"); 689 | @@ -1100,6 +1101,13 @@ 690 | case 'F': 691 | config = optarg; 692 | break; 693 | + case 'z': 694 | + options.obfuscate_handshake = 1; 695 | + break; 696 | + case 'Z': 697 | + options.obfuscate_handshake = 1; 698 | + options.obfuscate_keyword = optarg; 699 | + break; 700 | default: 701 | usage(); 702 | } 703 | Index: openssh-9.6p1/sshconnect.c 704 | =================================================================== 705 | --- openssh-9.6p1.orig/sshconnect.c 2024-11-14 14:11:16.000000000 +0800 706 | +++ openssh-9.6p1/sshconnect.c 2024-11-14 14:11:16.998633255 +0800 707 | @@ -64,6 +64,7 @@ 708 | #include "monitor_fdpass.h" 709 | #include "ssh2.h" 710 | #include "version.h" 711 | +#include "obfuscate.h" 712 | #include "authfile.h" 713 | #include "ssherr.h" 714 | #include "authfd.h" 715 | @@ -268,6 +269,12 @@ 716 | if (ssh_packet_set_connection(ssh, pout[0], pin[1]) == NULL) 717 | return -1; /* ssh_packet_set_connection logs error */ 718 | 719 | + if(options.obfuscate_handshake) { 720 | + if(options.obfuscate_keyword) 721 | + obfuscate_set_keyword(options.obfuscate_keyword); 722 | + sshpkt_enable_obfuscation(ssh); 723 | + } 724 | + 725 | return 0; 726 | } 727 | 728 | @@ -537,6 +544,11 @@ 729 | /* Set the connection. */ 730 | if (ssh_packet_set_connection(ssh, sock, sock) == NULL) 731 | return -1; /* ssh_packet_set_connection logs error */ 732 | + if(options.obfuscate_handshake) { 733 | + if(options.obfuscate_keyword) 734 | + obfuscate_set_keyword(options.obfuscate_keyword); 735 | + sshpkt_enable_obfuscation(ssh); 736 | + } 737 | 738 | return 0; 739 | } 740 | @@ -1580,6 +1592,9 @@ 741 | host = xstrdup(orighost); 742 | lowercase(host); 743 | 744 | + if(options.obfuscate_handshake) 745 | + obfuscate_send_seed(ssh_packet_get_connection_out(ssh)); 746 | + 747 | /* Exchange protocol version identification strings with the server. */ 748 | if ((r = kex_exchange_identification(ssh, timeout_ms, 1, NULL)) != 0) 749 | sshpkt_fatal(ssh, r, "banner exchange"); 750 | Index: openssh-9.6p1/sshd.c 751 | =================================================================== 752 | --- openssh-9.6p1.orig/sshd.c 2024-11-14 14:11:16.000000000 +0800 753 | +++ openssh-9.6p1/sshd.c 2024-11-14 14:11:16.998633255 +0800 754 | @@ -120,6 +120,7 @@ 755 | #include "ssh-gss.h" 756 | #endif 757 | #include "monitor_wrap.h" 758 | +#include "obfuscate.h" 759 | #include "ssh-sandbox.h" 760 | #include "auth-options.h" 761 | #include "version.h" 762 | @@ -269,6 +270,9 @@ 763 | /* message to be displayed after login */ 764 | struct sshbuf *loginmsg; 765 | 766 | +/* Enable handshake obfuscation */ 767 | +int use_obfuscation = 0; 768 | + 769 | /* Unprivileged user */ 770 | struct passwd *privsep_pw = NULL; 771 | 772 | @@ -1668,7 +1672,7 @@ 773 | struct ssh *ssh = NULL; 774 | extern char *optarg; 775 | extern int optind; 776 | - int r, opt, on = 1, do_dump_cfg = 0, already_daemon, remote_port; 777 | + int r, opt, on = 1, do_dump_cfg = 0, already_daemon, remote_port, local_port; 778 | int sock_in = -1, sock_out = -1, newsock = -1; 779 | const char *remote_ip, *rdomain; 780 | char *fp, *line, *laddr, *logfile = NULL; 781 | @@ -2321,6 +2325,14 @@ 782 | process_channel_timeouts(ssh, &options); 783 | process_permitopen(ssh, &options); 784 | 785 | + local_port = ssh_local_port(ssh); 786 | + for(i = 0; i < options.num_obfuscated_ports; i++) { 787 | + if(options.obfuscated_ports[i] == local_port) { 788 | + use_obfuscation = 1; 789 | + break; 790 | + } 791 | + } 792 | + 793 | /* Set SO_KEEPALIVE if requested. */ 794 | if (options.tcp_keep_alive && ssh_packet_connection_is_on_socket(ssh) && 795 | setsockopt(sock_in, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) 796 | @@ -2386,6 +2398,13 @@ 797 | if (!debug_flag) 798 | alarm(options.login_grace_time); 799 | 800 | + if(use_obfuscation) { 801 | + if(options.obfuscate_keyword) 802 | + obfuscate_set_keyword(options.obfuscate_keyword); 803 | + sshpkt_enable_obfuscation(ssh); 804 | + obfuscate_receive_seed(ssh, sock_in); 805 | + } 806 | + 807 | if ((r = kex_exchange_identification(ssh, -1, options.debian_banner, 808 | options.version_addendum)) != 0) 809 | sshpkt_fatal(ssh, r, "banner exchange"); 810 | @@ -2411,8 +2430,12 @@ 811 | auth_debug_reset(); 812 | 813 | if (use_privsep) { 814 | - if (privsep_preauth(ssh) == 1) 815 | + 816 | + if (privsep_preauth(ssh) == 1) { 817 | + if(use_obfuscation) 818 | + sshpkt_disable_obfuscation(ssh); 819 | goto authenticated; 820 | + } 821 | } else if (have_agent) { 822 | if ((r = ssh_get_authentication_socket(&auth_sock)) != 0) { 823 | error_r(r, "Unable to get agent socket"); 824 | Index: openssh-9.6p1/sshd_config 825 | =================================================================== 826 | --- openssh-9.6p1.orig/sshd_config 2024-11-14 14:11:16.000000000 +0800 827 | +++ openssh-9.6p1/sshd_config 2024-11-14 14:11:16.998633255 +0800 828 | @@ -22,6 +22,8 @@ 829 | # systemctl restart ssh.socket 830 | # 831 | #Port 22 832 | +#ObfuscatedPort 222 833 | +#ObfuscateKeyword key 834 | #AddressFamily any 835 | #ListenAddress 0.0.0.0 836 | #ListenAddress :: 837 | --------------------------------------------------------------------------------